642d0f720e
- стек по §5.2: React 19, React Router, TanStack Query, Zustand, Tailwind CSS 4, i18next (ru/en), Vitest + Testing Library - дизайн-токены §3 docs/client-web.md (тёмная тема по умолчанию, светлая, accent) - типизированный API-клиент §8.1: единый формат ошибок, ApiError с кодом - App: экран состояния фундамента с readz/meta, локализация ошибок по code - тесты: клиент API, форматирование, рендер App (успех/ошибка кода/сеть) - eslint + prettier + tsc strict; initial JS 97.6 KB gzip
66 lines
2.0 KiB
TypeScript
66 lines
2.0 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import { ApiError, request } from '@/api/client';
|
|
|
|
function jsonResponse(body: unknown, status = 200): Response {
|
|
return new Response(JSON.stringify(body), {
|
|
status,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
|
|
describe('api client', () => {
|
|
it('returns the parsed payload on success', async () => {
|
|
const fetchMock = vi.fn().mockResolvedValue(jsonResponse({ api_version: 'v1' }));
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
|
|
await expect(request<{ api_version: string }>('/meta')).resolves.toEqual({
|
|
api_version: 'v1',
|
|
});
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
'/api/v1/meta',
|
|
expect.objectContaining({ method: 'GET', credentials: 'same-origin' }),
|
|
);
|
|
});
|
|
|
|
it('maps the server error envelope to ApiError', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi
|
|
.fn()
|
|
.mockResolvedValue(
|
|
jsonResponse({ error: { code: 'perm.denied', message: 'no access' } }, 403),
|
|
),
|
|
);
|
|
|
|
await expect(request('/guilds/1')).rejects.toMatchObject({
|
|
name: 'ApiError',
|
|
code: 'perm.denied',
|
|
status: 403,
|
|
});
|
|
});
|
|
|
|
it('falls back to internal.error when the body is not an envelope', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn().mockResolvedValue(new Response('gateway timeout', { status: 504 })),
|
|
);
|
|
|
|
const error = await request('/meta').catch((reason: unknown) => reason);
|
|
expect(error).toBeInstanceOf(ApiError);
|
|
expect((error as ApiError).code).toBe('internal.error');
|
|
expect((error as ApiError).status).toBe(504);
|
|
});
|
|
|
|
it('sends a json body for write requests', async () => {
|
|
const fetchMock = vi.fn().mockResolvedValue(jsonResponse({ ok: true }));
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
|
|
await request('/guilds', { method: 'POST', body: { name: 'test' } });
|
|
|
|
const init = fetchMock.mock.calls[0]?.[1] as RequestInit;
|
|
expect(init.body).toBe('{"name":"test"}');
|
|
expect((init.headers as Record<string, string>)['Content-Type']).toBe('application/json');
|
|
});
|
|
});
|