mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 07:30:16 +03:00
chore: demo app
This commit is contained in:
151
examples/react-vite/src/app/app.test.tsx
Normal file
151
examples/react-vite/src/app/app.test.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { clearSimpleRestApiTokens } from 'infra/simple-rest-api'
|
||||
import { App } from './app'
|
||||
import { AppProviders } from './providers/app-providers'
|
||||
|
||||
const demoUser = {
|
||||
id: 'user-admin',
|
||||
email: 'admin@demo.local',
|
||||
name: 'Demo Admin',
|
||||
role: 'admin',
|
||||
avatarUrl: 'https://i.pravatar.cc/160?img=12'
|
||||
}
|
||||
|
||||
const demoProduct = {
|
||||
id: 'product-keyboard',
|
||||
name: 'Mechanical Keyboard',
|
||||
slug: 'mechanical-keyboard',
|
||||
description: 'Mechanical Keyboard is a deterministic demo product used by frontend examples.',
|
||||
priceCents: 12990,
|
||||
currency: 'USD',
|
||||
categoryId: 'category-electronics',
|
||||
stock: 24,
|
||||
rating: 4.8,
|
||||
imageUrl: 'https://picsum.photos/seed/mechanical-keyboard/640/480',
|
||||
createdAt: '2026-07-10T09:00:00.000Z',
|
||||
version: 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Создаёт JSON response, совместимый с generated HttpClient.
|
||||
*/
|
||||
const jsonResponse = (body: unknown, status = 200): Response => {
|
||||
return new Response(JSON.stringify(body), {
|
||||
status,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Эмулирует минимальный Simple API для app-level smoke-сценария.
|
||||
*/
|
||||
const createSimpleApiFetch = () => {
|
||||
return vi.fn(async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||
const url = new URL(String(input))
|
||||
const method = init?.method ?? 'GET'
|
||||
|
||||
if (url.pathname === '/api/v1/auth/login' && method === 'POST') {
|
||||
return jsonResponse({
|
||||
data: {
|
||||
tokens: {
|
||||
accessToken: 'access-token',
|
||||
refreshToken: 'refresh-token',
|
||||
expiresIn: 60,
|
||||
tokenType: 'Bearer'
|
||||
},
|
||||
user: demoUser
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (url.pathname === '/api/v1/products' && method === 'GET') {
|
||||
return jsonResponse({
|
||||
data: [demoProduct],
|
||||
meta: { page: 1, limit: 6, total: 1, totalPages: 1 }
|
||||
})
|
||||
}
|
||||
|
||||
if (url.pathname === '/api/v1/categories' && method === 'GET') {
|
||||
return jsonResponse({
|
||||
data: [
|
||||
{
|
||||
id: 'category-electronics',
|
||||
name: 'Electronics',
|
||||
slug: 'electronics',
|
||||
productCount: 1
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
if (url.pathname === '/api/v1/orders' && method === 'GET') {
|
||||
return jsonResponse({
|
||||
data: [],
|
||||
meta: { page: 1, limit: 20, total: 0, totalPages: 0 }
|
||||
})
|
||||
}
|
||||
|
||||
if (url.pathname === '/api/v1/orders' && method === 'POST') {
|
||||
return jsonResponse(
|
||||
{
|
||||
data: {
|
||||
id: 'order-010',
|
||||
userId: 'user-admin',
|
||||
status: 'pending',
|
||||
items: [
|
||||
{
|
||||
productId: demoProduct.id,
|
||||
productName: demoProduct.name,
|
||||
quantity: 1,
|
||||
unitPriceCents: demoProduct.priceCents
|
||||
}
|
||||
],
|
||||
totalCents: demoProduct.priceCents,
|
||||
currency: 'USD',
|
||||
createdAt: '2026-08-10T10:00:00.000Z'
|
||||
}
|
||||
},
|
||||
201
|
||||
)
|
||||
}
|
||||
|
||||
return jsonResponse({ code: 'NOT_FOUND', message: 'Not found' }, 404)
|
||||
})
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
clearSimpleRestApiTokens()
|
||||
})
|
||||
|
||||
describe('App', () => {
|
||||
it('authenticates, loads domain data and checks out a product', async () => {
|
||||
const user = userEvent.setup()
|
||||
const fetchMock = createSimpleApiFetch()
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
|
||||
render(
|
||||
<AppProviders>
|
||||
<App />
|
||||
</AppProviders>
|
||||
)
|
||||
|
||||
await user.click(await screen.findByRole('button', { name: 'Войти в магазин' }))
|
||||
|
||||
expect(await screen.findByRole('heading', { name: 'Mechanical Keyboard' })).toBeInTheDocument()
|
||||
expect(screen.getByText('Demo Admin')).toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'В заказ' }))
|
||||
expect(screen.getByText('Draft order')).toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Оформить заказ' }))
|
||||
|
||||
expect(await screen.findByText('order-010')).toBeInTheDocument()
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'http://localhost:3001/api/v1/orders',
|
||||
expect.objectContaining({ method: 'POST' })
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user