21 tests covering win/loss/draw/score/reset. lcov report fed to SonarQube. Pipeline: Install → Unit Test → Scan Code Quality → Build & Push. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
158 lines
4.6 KiB
JavaScript
158 lines
4.6 KiB
JavaScript
/**
|
|
* @jest-environment jsdom
|
|
*/
|
|
|
|
function setupDOM() {
|
|
document.body.innerHTML = `
|
|
<div id="status">Player X's turn</div>
|
|
<div id="board">
|
|
${Array.from({ length: 9 }, (_, i) => `<div class="cell" data-index="${i}"></div>`).join('')}
|
|
</div>
|
|
<span id="score-x">0</span>
|
|
<span id="score-draw">0</span>
|
|
<span id="score-o">0</span>
|
|
<button id="reset">New Game</button>
|
|
`;
|
|
}
|
|
|
|
function click(index) {
|
|
document.querySelectorAll('.cell')[index].dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
}
|
|
|
|
function reset() {
|
|
document.getElementById('reset').dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
}
|
|
|
|
beforeEach(() => {
|
|
setupDOM();
|
|
jest.resetModules();
|
|
require('../public/game.js');
|
|
});
|
|
|
|
describe('initial state', () => {
|
|
test('all cells empty', () => {
|
|
document.querySelectorAll('.cell').forEach(c => expect(c.textContent).toBe(''));
|
|
});
|
|
|
|
test('status shows X turn', () => {
|
|
expect(document.getElementById('status').textContent).toBe("Player X's turn");
|
|
});
|
|
|
|
test('scores start at 0', () => {
|
|
expect(document.getElementById('score-x').textContent).toBe('0');
|
|
expect(document.getElementById('score-o').textContent).toBe('0');
|
|
expect(document.getElementById('score-draw').textContent).toBe('0');
|
|
});
|
|
});
|
|
|
|
describe('turn alternation', () => {
|
|
test('alternates X and O after each move', () => {
|
|
click(0);
|
|
expect(document.getElementById('status').textContent).toBe("Player O's turn");
|
|
click(1);
|
|
expect(document.getElementById('status').textContent).toBe("Player X's turn");
|
|
});
|
|
|
|
test('cell shows correct player mark', () => {
|
|
click(0);
|
|
expect(document.querySelectorAll('.cell')[0].textContent).toBe('X');
|
|
click(1);
|
|
expect(document.querySelectorAll('.cell')[1].textContent).toBe('O');
|
|
});
|
|
|
|
test('clicking taken cell does nothing', () => {
|
|
click(0);
|
|
click(0);
|
|
expect(document.getElementById('status').textContent).toBe("Player O's turn");
|
|
});
|
|
});
|
|
|
|
describe('X wins', () => {
|
|
// X: 0,1,2 O: 3,4
|
|
beforeEach(() => { click(0); click(3); click(1); click(4); click(2); });
|
|
|
|
test('status shows X wins', () => {
|
|
expect(document.getElementById('status').textContent).toBe('Player X wins!');
|
|
});
|
|
|
|
test('winning cells get winner class', () => {
|
|
const cells = document.querySelectorAll('.cell');
|
|
expect(cells[0].classList.contains('winner')).toBe(true);
|
|
expect(cells[1].classList.contains('winner')).toBe(true);
|
|
expect(cells[2].classList.contains('winner')).toBe(true);
|
|
});
|
|
|
|
test('X score increments', () => {
|
|
expect(document.getElementById('score-x').textContent).toBe('1');
|
|
});
|
|
|
|
test('no moves accepted after win', () => {
|
|
click(5);
|
|
expect(document.querySelectorAll('.cell')[5].textContent).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('O wins', () => {
|
|
// X: 0,6,7 O: 3,4,5
|
|
beforeEach(() => { click(0); click(3); click(6); click(4); click(7); click(5); });
|
|
|
|
test('status shows O wins', () => {
|
|
expect(document.getElementById('status').textContent).toBe('Player O wins!');
|
|
});
|
|
|
|
test('O score increments', () => {
|
|
expect(document.getElementById('score-o').textContent).toBe('1');
|
|
});
|
|
});
|
|
|
|
describe('draw', () => {
|
|
// X: 0,2,5,6,7 O: 1,3,4,8 — fills board, no winner
|
|
beforeEach(() => {
|
|
click(0); click(1); click(2); click(3); click(5); click(4); click(6); click(8); click(7);
|
|
});
|
|
|
|
test('status shows draw', () => {
|
|
expect(document.getElementById('status').textContent).toBe("It's a draw!");
|
|
});
|
|
|
|
test('draw score increments', () => {
|
|
expect(document.getElementById('score-draw').textContent).toBe('1');
|
|
});
|
|
});
|
|
|
|
describe('reset', () => {
|
|
test('clears board after game', () => {
|
|
click(0); click(1);
|
|
reset();
|
|
document.querySelectorAll('.cell').forEach(c => expect(c.textContent).toBe(''));
|
|
});
|
|
|
|
test('resets status to X turn', () => {
|
|
click(0);
|
|
reset();
|
|
expect(document.getElementById('status').textContent).toBe("Player X's turn");
|
|
});
|
|
|
|
test('score persists across reset', () => {
|
|
click(0); click(3); click(1); click(4); click(2);
|
|
reset();
|
|
expect(document.getElementById('score-x').textContent).toBe('1');
|
|
});
|
|
|
|
test('allows play after reset', () => {
|
|
click(0); click(1); click(2); click(3); click(4); click(5); click(6); click(7); click(8);
|
|
reset();
|
|
click(0);
|
|
expect(document.querySelectorAll('.cell')[0].textContent).toBe('X');
|
|
});
|
|
});
|
|
|
|
describe('score accumulates across games', () => {
|
|
test('multiple wins tracked', () => {
|
|
click(0); click(3); click(1); click(4); click(2);
|
|
reset();
|
|
click(0); click(3); click(1); click(4); click(2);
|
|
expect(document.getElementById('score-x').textContent).toBe('2');
|
|
});
|
|
});
|