Compare commits
1 Commits
feat/issue
...
pr-181
| Author | SHA1 | Date | |
|---|---|---|---|
| acc3420a86 |
1375
frontend/angular/package-lock.json
generated
1375
frontend/angular/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "ng serve",
|
"start": "ng serve",
|
||||||
"build": "ng build"
|
"build": "ng build",
|
||||||
|
"test": "vitest run"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/common": "^19.2.0",
|
"@angular/common": "^19.2.0",
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
"@angular/build": "^19.2.0",
|
"@angular/build": "^19.2.0",
|
||||||
"@angular/cli": "^19.2.0",
|
"@angular/cli": "^19.2.0",
|
||||||
"@angular/compiler-cli": "^19.2.0",
|
"@angular/compiler-cli": "^19.2.0",
|
||||||
"typescript": "~5.7.2"
|
"typescript": "~5.7.2",
|
||||||
|
"vitest": "^2.1.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { HostShellComponent } from './host-shell.component';
|
||||||
|
|
||||||
|
type FetchMock = ReturnType<typeof vi.fn>;
|
||||||
|
|
||||||
|
function jsonResponse(status: number, body: unknown) {
|
||||||
|
return {
|
||||||
|
ok: status >= 200 && status < 300,
|
||||||
|
status,
|
||||||
|
json: vi.fn().mockResolvedValue(body),
|
||||||
|
} as unknown as Response;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('HostShellComponent gameplay wiring', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('runs startRound transition and refreshes session details', async () => {
|
||||||
|
const fetchMock: FetchMock = vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValueOnce(jsonResponse(201, { ok: true }))
|
||||||
|
.mockResolvedValueOnce(
|
||||||
|
jsonResponse(200, {
|
||||||
|
session: { code: 'ABCD12', status: 'lie', current_round: 2 },
|
||||||
|
round_question: { id: 41, prompt: 'Q?', answers: [] },
|
||||||
|
players: [],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.stubGlobal('fetch', fetchMock);
|
||||||
|
|
||||||
|
const component = new HostShellComponent();
|
||||||
|
component.sessionCode = ' abcd12 ';
|
||||||
|
component.categorySlug = ' history ';
|
||||||
|
|
||||||
|
await component.startRound();
|
||||||
|
|
||||||
|
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
'/lobby/sessions/ABCD12/rounds/start',
|
||||||
|
expect.objectContaining({ method: 'POST', body: JSON.stringify({ category_slug: 'history' }) })
|
||||||
|
);
|
||||||
|
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
'/lobby/sessions/ABCD12',
|
||||||
|
expect.objectContaining({ method: 'GET' })
|
||||||
|
);
|
||||||
|
expect(component.session?.session.status).toBe('lie');
|
||||||
|
expect(component.roundQuestionId).toBe('41');
|
||||||
|
expect(component.loading).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('captures scoreboard error for retry path', async () => {
|
||||||
|
const fetchMock: FetchMock = vi.fn().mockResolvedValue(
|
||||||
|
jsonResponse(500, { error: 'Scoreboard unavailable' })
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.stubGlobal('fetch', fetchMock);
|
||||||
|
|
||||||
|
const component = new HostShellComponent();
|
||||||
|
component.sessionCode = 'ABCD12';
|
||||||
|
|
||||||
|
await component.loadScoreboard();
|
||||||
|
|
||||||
|
expect(fetchMock).toHaveBeenCalledWith(
|
||||||
|
'/lobby/sessions/ABCD12/scoreboard',
|
||||||
|
expect.objectContaining({ method: 'GET' })
|
||||||
|
);
|
||||||
|
expect(component.scoreboardError).toContain('Scoreboard failed: Scoreboard unavailable');
|
||||||
|
expect(component.loading).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { PlayerShellComponent } from './player-shell.component';
|
||||||
|
|
||||||
|
type FetchMock = ReturnType<typeof vi.fn>;
|
||||||
|
|
||||||
|
function jsonResponse(status: number, body: unknown) {
|
||||||
|
return {
|
||||||
|
ok: status >= 200 && status < 300,
|
||||||
|
status,
|
||||||
|
json: vi.fn().mockResolvedValue(body),
|
||||||
|
} as unknown as Response;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('PlayerShellComponent gameplay wiring', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clears selected guess when refreshed status is no longer guess', async () => {
|
||||||
|
const fetchMock: FetchMock = vi.fn().mockResolvedValue(
|
||||||
|
jsonResponse(200, {
|
||||||
|
session: { code: 'ABCD12', status: 'reveal', current_round: 1 },
|
||||||
|
round_question: { id: 11, prompt: 'Q?', answers: [{ text: 'A' }] },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.stubGlobal('fetch', fetchMock);
|
||||||
|
|
||||||
|
const component = new PlayerShellComponent();
|
||||||
|
component.sessionCode = 'abcd12';
|
||||||
|
component.selectedGuess = 'A';
|
||||||
|
|
||||||
|
await component.refreshSession();
|
||||||
|
|
||||||
|
expect(fetchMock).toHaveBeenCalledWith(
|
||||||
|
'/lobby/sessions/ABCD12',
|
||||||
|
expect.objectContaining({ method: 'GET' })
|
||||||
|
);
|
||||||
|
expect(component.selectedGuess).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('surfaces lie submit error and allows retry success flow', async () => {
|
||||||
|
const fetchMock: FetchMock = vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValueOnce(jsonResponse(500, { error: 'Temporary submit outage' }))
|
||||||
|
.mockResolvedValueOnce(jsonResponse(200, { ok: true }))
|
||||||
|
.mockResolvedValueOnce(
|
||||||
|
jsonResponse(200, {
|
||||||
|
session: { code: 'ABCD12', status: 'guess', current_round: 1 },
|
||||||
|
round_question: { id: 11, prompt: 'Q?', answers: [{ text: 'A' }, { text: 'B' }] },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.stubGlobal('fetch', fetchMock);
|
||||||
|
|
||||||
|
const component = new PlayerShellComponent();
|
||||||
|
component.sessionCode = 'ABCD12';
|
||||||
|
component.playerId = 9;
|
||||||
|
component.sessionToken = 'token-1';
|
||||||
|
component.lieText = 'my lie';
|
||||||
|
component.session = {
|
||||||
|
session: { code: 'ABCD12', status: 'lie', current_round: 1 },
|
||||||
|
round_question: { id: 11, prompt: 'Q?', answers: [] },
|
||||||
|
};
|
||||||
|
|
||||||
|
await component.submitLie();
|
||||||
|
|
||||||
|
expect(component.submitError?.kind).toBe('lie');
|
||||||
|
expect(component.submitError?.message).toContain('Lie submit failed: Temporary submit outage');
|
||||||
|
|
||||||
|
await component.submitLie();
|
||||||
|
|
||||||
|
expect(component.submitError).toBeNull();
|
||||||
|
expect(component.session?.session.status).toBe('guess');
|
||||||
|
expect(fetchMock).toHaveBeenCalledTimes(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
1
frontend/angular/src/test-setup.ts
Normal file
1
frontend/angular/src/test-setup.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import '@angular/compiler';
|
||||||
8
frontend/angular/vitest.config.ts
Normal file
8
frontend/angular/vitest.config.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
include: ['src/**/*.spec.ts'],
|
||||||
|
setupFiles: ['src/test-setup.ts'],
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user