Merge pull request '[MVP][frontend] #249 Angular-first SPA foundation: host/player shell + API client skeleton' (#253) from dev/issue-249-angular-spa-foundation into main
Some checks failed
CI / test-and-quality (push) Has been cancelled
Some checks failed
CI / test-and-quality (push) Has been cancelled
This commit was merged in pull request #253.
This commit is contained in:
@@ -1,7 +1,15 @@
|
|||||||
import { ApplicationConfig } from '@angular/core';
|
import { ApplicationConfig } from '@angular/core';
|
||||||
import { provideRouter, withHashLocation } from '@angular/router';
|
import { provideRouter, withHashLocation } from '@angular/router';
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
|
import { createWppApiClient, WPP_API_CLIENT } from './wpp-api-client';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [provideRouter(routes, withHashLocation())],
|
providers: [
|
||||||
|
provideRouter(routes, withHashLocation()),
|
||||||
|
{
|
||||||
|
provide: WPP_API_CLIENT,
|
||||||
|
useFactory: () => createWppApiClient(),
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
43
frontend/angular/src/app/wpp-api-client.spec.ts
Normal file
43
frontend/angular/src/app/wpp-api-client.spec.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { createWppApiClient } from './wpp-api-client';
|
||||||
|
|
||||||
|
function jsonResponse(status: number, body: unknown) {
|
||||||
|
return {
|
||||||
|
ok: status >= 200 && status < 300,
|
||||||
|
status,
|
||||||
|
json: vi.fn().mockResolvedValue(body),
|
||||||
|
} as unknown as Response;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('WPP Angular API client skeleton', () => {
|
||||||
|
it('normalizes host/player API calls through fetch transport', async () => {
|
||||||
|
const fetchMock = vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValueOnce(jsonResponse(200, { session: { code: 'ABCD12', status: 'lobby', host_id: 1, current_round: 1, players_count: 1 }, players: [], round_question: null, phase_view_model: { status: 'lobby', round_number: 1, players_count: 1, constraints: { min_players_to_start: 2, max_players_mvp: 8, min_players_reached: false, max_players_allowed: true }, host: { can_start_round: false, can_show_question: false, can_mix_answers: false, can_calculate_scores: false, can_reveal_scoreboard: false, can_start_next_round: false, can_finish_game: false }, player: { can_join: true, can_submit_lie: false, can_submit_guess: false, can_view_final_result: false } } }))
|
||||||
|
.mockResolvedValueOnce(jsonResponse(201, { player: { id: 1, nickname: 'Luna', session_token: 'tok', score: 0 }, session: { code: 'ABCD12', status: 'lobby' } }));
|
||||||
|
|
||||||
|
const client = createWppApiClient(fetchMock);
|
||||||
|
|
||||||
|
const session = await client.getSession(' abcd12 ');
|
||||||
|
const joined = await client.joinSession({ code: ' abcd12 ', nickname: ' Luna ' });
|
||||||
|
|
||||||
|
expect(session.ok).toBe(true);
|
||||||
|
expect(joined.ok).toBe(true);
|
||||||
|
|
||||||
|
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
'/lobby/sessions/ABCD12',
|
||||||
|
expect.objectContaining({ method: 'GET', credentials: 'same-origin' })
|
||||||
|
);
|
||||||
|
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
'/lobby/sessions/join',
|
||||||
|
expect.objectContaining({
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: JSON.stringify({ code: 'ABCD12', nickname: 'Luna' }),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
58
frontend/angular/src/app/wpp-api-client.ts
Normal file
58
frontend/angular/src/app/wpp-api-client.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createAngularApiClient,
|
||||||
|
type AngularApiClient,
|
||||||
|
type AngularHttpClientLike,
|
||||||
|
} from '../../../src/api/angular-client';
|
||||||
|
|
||||||
|
export const WPP_API_CLIENT = new InjectionToken<AngularApiClient>('WPP_API_CLIENT');
|
||||||
|
|
||||||
|
export interface FetchLike {
|
||||||
|
(input: string, init?: RequestInit): Promise<Response>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createFetchHttpClient(fetchImpl: FetchLike): AngularHttpClientLike {
|
||||||
|
return {
|
||||||
|
async get<T>(url: string): Promise<T> {
|
||||||
|
const response = await fetchImpl(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { Accept: 'application/json' },
|
||||||
|
credentials: 'same-origin',
|
||||||
|
});
|
||||||
|
const payload = await response.json().catch(() => ({}));
|
||||||
|
if (!response.ok) {
|
||||||
|
throw {
|
||||||
|
status: response.status,
|
||||||
|
message: (payload as { error?: string }).error ?? `HTTP ${response.status}`,
|
||||||
|
error: payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return payload as T;
|
||||||
|
},
|
||||||
|
async post<T>(url: string, body: unknown): Promise<T> {
|
||||||
|
const response = await fetchImpl(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
credentials: 'same-origin',
|
||||||
|
});
|
||||||
|
const payload = await response.json().catch(() => ({}));
|
||||||
|
if (!response.ok) {
|
||||||
|
throw {
|
||||||
|
status: response.status,
|
||||||
|
message: (payload as { error?: string }).error ?? `HTTP ${response.status}`,
|
||||||
|
error: payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return payload as T;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWppApiClient(fetchImpl: FetchLike = fetch.bind(globalThis)): AngularApiClient {
|
||||||
|
return createAngularApiClient(createFetchHttpClient(fetchImpl));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user