Fix Issue #7: Add status message element and fix column resizing bug #8

Open
Weyoun wants to merge 6 commits from fix/issue-7-and-column-resize into main
6 changed files with 250 additions and 41 deletions
Showing only changes of commit b2df8786ca - Show all commits

View File

@ -33,8 +33,8 @@ jobs:
run: npm run lint run: npm run lint
continue-on-error: false continue-on-error: false
test: test-unit:
name: Run Tests name: Run Unit Tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -50,7 +50,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Run tests with coverage - name: Run unit tests with coverage
run: npm run test:coverage run: npm run test:coverage
- name: Check coverage threshold - name: Check coverage threshold
@ -69,14 +69,45 @@ jobs:
if: always() if: always()
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: test-results name: unit-test-results
path: coverage/ path: coverage/
retention-days: 30 retention-days: 30
test-e2e:
name: Run E2E Tests (Playwright)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps chromium
- name: Run Playwright tests
run: npm run test:e2e
- name: Upload Playwright Report
if: always()
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: playwright-report/
retention-days: 30
build-verification: build-verification:
name: Build Verification name: Build Verification
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [lint, test] needs: [lint, test-unit, test-e2e]
steps: steps:
- name: Checkout code - name: Checkout code
@ -113,7 +144,7 @@ jobs:
quality-report: quality-report:
name: Generate Quality Report name: Generate Quality Report
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [lint, test, build-verification] needs: [lint, test-unit, test-e2e, build-verification]
if: always() if: always()
steps: steps:

64
package-lock.json generated
View File

@ -10,6 +10,7 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@babel/preset-env": "^7.28.5", "@babel/preset-env": "^7.28.5",
"@playwright/test": "^1.56.1",
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"babel-jest": "^30.2.0", "babel-jest": "^30.2.0",
"eslint": "^8.56.0", "eslint": "^8.56.0",
@ -2753,6 +2754,22 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@playwright/test": {
"version": "1.56.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz",
"integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.56.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sinclair/typebox": { "node_modules/@sinclair/typebox": {
"version": "0.27.8", "version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@ -6639,6 +6656,53 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/playwright": {
"version": "1.56.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz",
"integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.56.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.56.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz",
"integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/playwright/node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/portfinder": { "node_modules/portfinder": {
"version": "1.0.38", "version": "1.0.38",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz",

View File

@ -5,7 +5,11 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "npx http-server -p 8080 -o", "dev": "npx http-server -p 8080 -o",
"test": "jest", "test": "npm run test:unit && npm run test:e2e",
"test:unit": "jest",
"test:e2e": "playwright test",
"test:e2e:headed": "playwright test --headed",
"test:e2e:ui": "playwright test --ui",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"test:coverage": "jest --coverage", "test:coverage": "jest --coverage",
"lint": "eslint js/**/*.js", "lint": "eslint js/**/*.js",
@ -21,6 +25,7 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@babel/preset-env": "^7.28.5", "@babel/preset-env": "^7.28.5",
"@playwright/test": "^1.56.1",
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"babel-jest": "^30.2.0", "babel-jest": "^30.2.0",
"eslint": "^8.56.0", "eslint": "^8.56.0",

View File

@ -1,63 +1,45 @@
/**
* Playwright Test Configuration
* @see https://playwright.dev/docs/test-configuration
*/
import { defineConfig, devices } from '@playwright/test'; import { defineConfig, devices } from '@playwright/test';
export default defineConfig({ export default defineConfig({
testDir: './tests/e2e', testDir: './tests/e2e',
// Timeout settings // Maximum time one test can run
timeout: 30000, timeout: 30 * 1000,
expect: {
timeout: 5000
},
// Test execution // Test execution settings
fullyParallel: true, fullyParallel: true,
forbidOnly: !!process.env.CI, forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0, retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined, workers: process.env.CI ? 1 : undefined,
// Reporter // Reporter configuration
reporter: [ reporter: process.env.CI ? 'github' : 'list',
['html'],
['list'],
['json', { outputFile: 'test-results/results.json' }]
],
// Shared settings // Shared settings for all projects
use: { use: {
baseURL: 'http://localhost:8080', baseURL: 'http://localhost:8080',
trace: 'on-first-retry', trace: 'on-first-retry',
screenshot: 'only-on-failure', screenshot: 'only-on-failure',
video: 'retain-on-failure'
}, },
// Browser configurations // Projects for different browsers
projects: [ projects: [
{ {
name: 'chromium', name: 'chromium',
use: { ...devices['Desktop Chrome'] }, use: { ...devices['Desktop Chrome'] },
}, },
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
{
name: 'mobile-chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'mobile-safari',
use: { ...devices['iPhone 12'] },
},
], ],
// Web server // Web server configuration
webServer: { webServer: {
command: 'python -m http.server 8080', command: 'npx http-server -p 8080 -c-1',
url: 'http://localhost:8080', port: 8080,
timeout: 120 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
}, },
}); });

View File

@ -0,0 +1,90 @@
/**
* Layout Stability Tests
* Tests that column widths and row heights remain stable during gameplay
*/
import { test, expect } from '@playwright/test';
test.describe('Layout Stability', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('chess board has fixed dimensions', async ({ page }) => {
const board = page.locator('#chess-board');
const box = await board.boundingBox();
expect(box.width).toBe(600);
expect(box.height).toBe(600);
});
test('board squares are 75px x 75px', async ({ page }) => {
const firstSquare = page.locator('.square').first();
const box = await firstSquare.boundingBox();
expect(box.width).toBe(75);
expect(box.height).toBe(75);
});
test('column widths remain stable when pieces are captured', async ({ page }) => {
// Get initial column widths
const leftSidebar = page.locator('.captured-white').first();
const boardSection = page.locator('.board-section');
const rightSidebar = page.locator('.game-sidebar');
const initialLeft = await leftSidebar.boundingBox();
const initialBoard = await boardSection.boundingBox();
const initialRight = await rightSidebar.boundingBox();
// Make moves that capture pieces
// e2 to e4
await page.locator('.square[data-row="6"][data-col="4"]').click();
await page.locator('.square[data-row="4"][data-col="4"]').click();
// Wait a bit for any animations
await page.waitForTimeout(500);
// Get widths after move
const afterLeft = await leftSidebar.boundingBox();
const afterBoard = await boardSection.boundingBox();
const afterRight = await rightSidebar.boundingBox();
// Widths should remain exactly the same
expect(afterLeft.width).toBe(initialLeft.width);
expect(afterBoard.width).toBe(initialBoard.width);
expect(afterRight.width).toBe(initialRight.width);
});
test('row heights remain stable when highlighting moves', async ({ page }) => {
// Get initial row heights by measuring first and last square
const firstSquare = page.locator('.square').first();
const initialBox = await firstSquare.boundingBox();
// Click a piece to highlight legal moves
await page.locator('.square[data-row="6"][data-col="4"]').click();
// Wait for highlighting
await page.waitForTimeout(300);
// Check that square dimensions haven't changed
const afterBox = await firstSquare.boundingBox();
expect(afterBox.height).toBe(initialBox.height);
});
test('last-move highlighting does not change layout', async ({ page }) => {
const board = page.locator('#chess-board');
const initialBox = await board.boundingBox();
// Make a move (e2 to e4)
await page.locator('.square[data-row="6"][data-col="4"]').click();
await page.locator('.square[data-row="4"][data-col="4"]').click();
// Wait for last-move highlight to apply
await page.waitForTimeout(300);
// Board dimensions should not change
const afterBox = await board.boundingBox();
expect(afterBox.width).toBe(initialBox.width);
expect(afterBox.height).toBe(initialBox.height);
});
});

View File

@ -0,0 +1,37 @@
/**
* Status Message Display Tests
* Tests the status message element functionality and visibility
*/
import { test, expect } from '@playwright/test';
test.describe('Status Message Display', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('status message element exists in DOM', async ({ page }) => {
const statusMessage = page.locator('#status-message');
await expect(statusMessage).toBeAttached();
});
test('status message is hidden by default', async ({ page }) => {
const statusMessage = page.locator('#status-message');
await expect(statusMessage).toHaveCSS('display', 'none');
});
test('new game shows status message', async ({ page }) => {
// Accept the confirm dialog that appears when clicking new game
page.on('dialog', dialog => dialog.accept());
await page.click('#btn-new-game');
const statusMessage = page.locator('#status-message');
await expect(statusMessage).toBeVisible({ timeout: 2000 });
});
test('status message has correct CSS classes', async ({ page }) => {
const statusMessage = page.locator('#status-message');
await expect(statusMessage).toHaveClass(/status-message/);
});
});