Skip to content

Commit 6e578c5

Browse files
committed
feat: init
1 parent 8a173b7 commit 6e578c5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+13296
-0
lines changed

.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

.eslintrc.base.json

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"root": true,
3+
"ignorePatterns": ["**/*"],
4+
"plugins": ["@nx"],
5+
"overrides": [
6+
// {
7+
// "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
8+
// "rules": {
9+
// "@nx/enforce-module-boundaries": [
10+
// "error",
11+
// {
12+
// "enforceBuildableLibDependency": true,
13+
// "allow": [],
14+
// "depConstraints": [
15+
// {
16+
// "sourceTag": "*",
17+
// "onlyDependOnLibsWithTags": ["*"]
18+
// }
19+
// ]
20+
// }
21+
// ]
22+
// }
23+
// },
24+
{
25+
"files": ["*.ts", "*.tsx"],
26+
"extends": ["plugin:@nx/typescript"],
27+
"rules": {}
28+
},
29+
{
30+
"files": ["*.js", "*.jsx"],
31+
"extends": ["plugin:@nx/javascript"],
32+
"rules": {}
33+
}
34+
]
35+
}

.github/workflows/ci.yml

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
workflow_dispatch:
9+
10+
jobs:
11+
main:
12+
name: Nx Cloud - Main Job
13+
uses: nrwl/ci/.github/workflows/[email protected]
14+
with:
15+
main-branch-name: main
16+
number-of-agents: 5
17+
init-commands: |
18+
npx nx-cloud start-ci-run --stop-agents-after="build" --agent-count=5
19+
parallel-commands: |
20+
npx nx-cloud record -- npx nx format:check
21+
parallel-commands-on-agents: |
22+
npx nx affected --target=lint --parallel=3
23+
npx nx affected --target=test --parallel=3 --ci --code-coverage
24+
npx nx affected --target=build --parallel=3
25+
npx nx affected --target=e2e --parallel=1
26+
27+
agents:
28+
name: Nx Cloud - Agents
29+
uses: nrwl/ci/.github/workflows/[email protected]
30+
with:
31+
number-of-agents: 5

.gitignore

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# See http://help.github.com/ignore-files/ for more about ignoring files.
2+
3+
# compiled output
4+
dist
5+
tmp
6+
/out-tsc
7+
8+
# dependencies
9+
node_modules
10+
11+
# IDEs and editors
12+
/.idea
13+
.project
14+
.classpath
15+
.c9/
16+
*.launch
17+
.settings/
18+
*.sublime-workspace
19+
20+
# IDE - VSCode
21+
.vscode/*
22+
!.vscode/settings.json
23+
!.vscode/tasks.json
24+
!.vscode/launch.json
25+
!.vscode/extensions.json
26+
27+
# misc
28+
/.sass-cache
29+
/connect.lock
30+
/coverage
31+
/libpeerconnection.log
32+
npm-debug.log
33+
yarn-error.log
34+
testem.log
35+
/typings
36+
37+
# System Files
38+
.DS_Store
39+
Thumbs.db

.prettierignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Add files here to ignore them from prettier formatting
2+
/dist
3+
/coverage

.prettierrc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"singleQuote": true
3+
}

.vscode/extensions.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"recommendations": [
3+
"nrwl.angular-console",
4+
"esbenp.prettier-vscode",
5+
"dbaeumer.vscode-eslint",
6+
"ms-playwright.playwright"
7+
]
8+
}

apps/multi-player-e2e/.eslintrc.json

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"extends": ["../../.eslintrc.base.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7+
"rules": {}
8+
},
9+
{
10+
"files": ["*.ts", "*.tsx"],
11+
"rules": {}
12+
},
13+
{
14+
"files": ["*.js", "*.jsx"],
15+
"rules": {}
16+
},
17+
{
18+
"files": ["src/**/*.{ts,js,tsx,jsx}"],
19+
"rules": {}
20+
}
21+
]
22+
}
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { defineConfig, devices } from '@playwright/test';
2+
import { nxE2EPreset } from '@nx/playwright/preset';
3+
4+
import { workspaceRoot } from '@nx/devkit';
5+
6+
// For CI, you may want to set BASE_URL to the deployed application.
7+
const baseURL = process.env['BASE_URL'] || 'http://localhost:4200';
8+
9+
/**
10+
* Read environment variables from file.
11+
* https://github.com/motdotla/dotenv
12+
*/
13+
// require('dotenv').config();
14+
15+
/**
16+
* See https://playwright.dev/docs/test-configuration.
17+
*/
18+
export default defineConfig({
19+
...nxE2EPreset(__filename, { testDir: './src' }),
20+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
21+
use: {
22+
baseURL,
23+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
24+
trace: 'on-first-retry',
25+
},
26+
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
27+
/* Run your local dev server before starting the tests */
28+
webServer: {
29+
command: 'npx nx serve multi-player',
30+
url: 'http://localhost:4200',
31+
reuseExistingServer: !process.env.CI,
32+
cwd: workspaceRoot,
33+
},
34+
workers: 1,
35+
// timeout: 5000,
36+
});

apps/multi-player-e2e/project.json

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "multi-player-e2e",
3+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
4+
"sourceRoot": "apps/multi-player-e2e/src",
5+
"targets": {
6+
"e2e": {
7+
"executor": "@nx/playwright:playwright",
8+
"outputs": ["{workspaceRoot}/dist/.playwright/apps/multi-player-e2e"],
9+
"options": {
10+
"config": "apps/multi-player-e2e/playwright.config.ts"
11+
}
12+
},
13+
"lint": {
14+
"executor": "@nx/linter:eslint",
15+
"outputs": ["{options.outputFile}"],
16+
"options": {
17+
"lintFilePatterns": ["apps/multi-player-e2e/**/*.{js,ts}"]
18+
}
19+
}
20+
},
21+
"implicitDependencies": ["multi-player"]
22+
}
+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { Page, test, expect } from '@playwright/test';
2+
import {
3+
Board,
4+
BoardIndex,
5+
SpaceContent,
6+
SpaceCoordinates,
7+
} from '../../../libs/tic-tac-toe-logic/src';
8+
import { startServer } from '../../multi-player-server/src/start-server';
9+
10+
let serverProcess;
11+
12+
test.beforeEach(() => {
13+
const { server } = startServer();
14+
serverProcess = server;
15+
});
16+
17+
test.afterEach(() => {
18+
serverProcess.close();
19+
});
20+
21+
test('starts with an empty board', async ({ page: xPlayer, browser }) => {
22+
const oPlayer = await browser.newPage();
23+
await xPlayer.goto('/');
24+
await oPlayer.goto('/');
25+
await assertBoardMatches(xPlayer, [
26+
['', '', ''],
27+
['', '', ''],
28+
['', '', ''],
29+
]);
30+
await assertBoardMatches(oPlayer, [
31+
['', '', ''],
32+
['', '', ''],
33+
['', '', ''],
34+
]);
35+
await assertTurn(xPlayer, `Your turn`);
36+
await assertTurn(oPlayer, `Waiting on opponent...`);
37+
});
38+
39+
test('clicking works', async ({ page: xPlayer, browser }) => {
40+
const oPlayer = await browser.newPage();
41+
await xPlayer.goto('/');
42+
await oPlayer.goto('/');
43+
await assertBoardMatches(xPlayer, [
44+
['', '', ''],
45+
['', '', ''],
46+
['', '', ''],
47+
]);
48+
await assertBoardMatches(oPlayer, [
49+
['', '', ''],
50+
['', '', ''],
51+
['', '', ''],
52+
]);
53+
await clickSpace(xPlayer, { row: 0, column: 0 });
54+
await assertBoardMatches(xPlayer, [
55+
['x', '', ''],
56+
['', '', ''],
57+
['', '', ''],
58+
]);
59+
await assertBoardMatches(oPlayer, [
60+
['x', '', ''],
61+
['', '', ''],
62+
['', '', ''],
63+
]);
64+
await assertTurn(xPlayer, `Waiting on opponent...`);
65+
await assertTurn(oPlayer, `Your turn`);
66+
});
67+
68+
test('x wins', async ({ page: xPlayer, browser }) => {
69+
const oPlayer = await browser.newPage();
70+
await xPlayer.goto('/');
71+
await oPlayer.goto('/');
72+
await clickSpace(xPlayer, { row: 0, column: 0 });
73+
await clickSpace(oPlayer, { row: 1, column: 0 });
74+
await clickSpace(xPlayer, { row: 0, column: 1 });
75+
await clickSpace(oPlayer, { row: 1, column: 1 });
76+
await clickSpace(xPlayer, { row: 0, column: 2 });
77+
await assertBoardMatches(xPlayer, [
78+
['x', 'x', 'x'],
79+
['o', 'o', ''],
80+
['', '', ''],
81+
]);
82+
await assertBoardMatches(oPlayer, [
83+
['x', 'x', 'x'],
84+
['o', 'o', ''],
85+
['', '', ''],
86+
]);
87+
await assertTurn(xPlayer, `You win!`);
88+
await assertTurn(oPlayer, `You lose!`);
89+
});
90+
91+
test('cats game', async ({ page: xPlayer, browser }) => {
92+
const oPlayer = await browser.newPage();
93+
await xPlayer.goto('/');
94+
await oPlayer.goto('/');
95+
await clickSpace(xPlayer, { row: 0, column: 0 });
96+
await clickSpace(oPlayer, { row: 0, column: 1 });
97+
await clickSpace(xPlayer, { row: 0, column: 2 });
98+
await clickSpace(oPlayer, { row: 1, column: 0 });
99+
await clickSpace(xPlayer, { row: 1, column: 2 });
100+
await clickSpace(oPlayer, { row: 1, column: 1 });
101+
await clickSpace(xPlayer, { row: 2, column: 0 });
102+
await clickSpace(oPlayer, { row: 2, column: 2 });
103+
await clickSpace(xPlayer, { row: 2, column: 1 });
104+
await assertBoardMatches(xPlayer, [
105+
['x', 'o', 'x'],
106+
['o', 'o', 'x'],
107+
['x', 'x', 'o'],
108+
]);
109+
await assertBoardMatches(oPlayer, [
110+
['x', 'o', 'x'],
111+
['o', 'o', 'x'],
112+
['x', 'x', 'o'],
113+
]);
114+
await assertTurn(xPlayer, `Tie game`);
115+
await assertTurn(oPlayer, `Tie game`);
116+
});
117+
118+
async function clickSpace(
119+
page: Page,
120+
{ row, column }: SpaceCoordinates
121+
): Promise<void> {
122+
await page.click(`[data-e2e="space-button-${row}-${column}"]`);
123+
}
124+
125+
async function assertTurn(page: Page, expectedTurnText: string): Promise<void> {
126+
const turn = await getTurn(page);
127+
expect(turn).toEqual(expectedTurnText);
128+
}
129+
130+
async function assertBoardMatches(
131+
page: Page,
132+
expectedBoard: Board
133+
): Promise<void> {
134+
const board = await getBoard(page);
135+
expect(JSON.stringify(board)).toEqual(JSON.stringify(expectedBoard));
136+
}
137+
138+
async function getBoard(page: Page): Promise<Board> {
139+
const board: Board = [
140+
['', '', ''],
141+
['', '', ''],
142+
['', '', ''],
143+
];
144+
await Promise.all(
145+
[0, 1, 2, 3, 4, 5, 6, 7, 8].map(async (i) => {
146+
const [row, column] = [i % 3, Math.floor(i / 3)] as [
147+
BoardIndex,
148+
BoardIndex
149+
];
150+
const space = await getSpace(page, { row, column });
151+
board[row][column] = space;
152+
})
153+
);
154+
return board;
155+
}
156+
157+
async function getTurn(page: Page): Promise<string> {
158+
const turnText = await page.locator('[data-e2e="turn-label"]').innerText();
159+
return turnText;
160+
}
161+
162+
async function getSpace(
163+
page: Page,
164+
{ row, column }: SpaceCoordinates
165+
): Promise<SpaceContent> {
166+
const content = await page
167+
.locator(`[data-e2e="space-button-${row}-${column}"]`)
168+
.innerText();
169+
return content.toLowerCase() as SpaceContent;
170+
}

0 commit comments

Comments
 (0)