diff --git a/AGENTS.md b/AGENTS.md index d5d999b..589f201 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -139,12 +139,60 @@ Use the bounded-context include prefix. Examples: ## Testing +### Regra global — toda feature deve ser testada + +**Cobertura mínima obrigatória: 80%** em linhas, funções, branches e statements. +Nenhuma feature é considerada completa sem testes que satisfaçam este limiar. + +### C++ (libs/physics, libs/simulation, libs/vessel, …) + - Every force module has a unit test before it is integrated into `RHS` - Unit tests use analytical solutions or known limits (e.g., zero current → zero current force) - Integration tests run a full simulation and assert on conserved quantities or known maneuver behavior - Test files mirror source structure: `tests/physics/forces/TestCurrentForces.cpp` - Test executables are defined in `tests/CMakeLists.txt` and linked via `Ymir::*` targets -- Coverage target: ≥80% line coverage measured via `-DYMIR_ENABLE_COVERAGE=ON` +- Coverage measured via `-DYMIR_ENABLE_COVERAGE=ON` (gcov/llvm-cov) + +### Frontend (apps/web) + +Dois níveis de teste, ambos obrigatórios para features de UI: + +| Nível | Ferramenta | Localização | O que testa | +|-------|-----------|-------------|-------------| +| Unit / Component | **Vitest** + Testing Library | `src/**/*.test.{ts,tsx}` | Lógica pura, hooks, stores, componentes isolados | +| E2E / Flow | **Playwright** | `e2e/**/*.spec.ts` | Fluxos completos de usuário no browser | + +**Comandos:** + +```bash +# Unit tests + cobertura (enforces 80% threshold — falha abaixo) +pnpm --filter @ymir/web test + +# E2E (requer servidor rodando ou usa webServer do playwright.config.ts) +pnpm --filter @ymir/web test:e2e + +# Modo watch (desenvolvimento) +pnpm --filter @ymir/web test:watch +``` + +**Regras de cobertura:** + +- Thresholds configurados em `apps/web/vitest.config.ts` — `lines: 80, functions: 80, branches: 80` +- `pnpm test` falha se qualquer threshold não for atingido (bloqueante em CI) +- Excluções válidas: `src/main.tsx`, arquivos `*.d.ts`, barris `index.ts` sem lógica +- Mocks de módulos externos (Leaflet, WASM) são aceitos e não contam contra cobertura + +**Playwright:** + +- Configuração em `apps/web/playwright.config.ts` +- Browser: Chromium (headless em CI, headed em dev) +- `webServer` inicia `vite dev` automaticamente antes dos testes +- Toda feature nova deve ter ao menos um teste E2E cobrindo o happy path + +### Backend (apps/api) + +- Testes unitários de serviços e repositórios: a definir quando volume justificar framework +- Enquanto não configurado: testar manualmente via `curl` e documentar no `SUMMARY.md` da task --- diff --git a/apps/web/e2e/smoke.spec.ts b/apps/web/e2e/smoke.spec.ts new file mode 100644 index 0000000..5cece3d --- /dev/null +++ b/apps/web/e2e/smoke.spec.ts @@ -0,0 +1,8 @@ +import { test, expect } from '@playwright/test' + +test('app loads without errors', async ({ page }) => { + await page.goto('/') + await expect(page).not.toHaveURL(/error/) + // Basic smoke: page title or root element renders + await expect(page.locator('body')).toBeVisible() +}) diff --git a/apps/web/package.json b/apps/web/package.json index 38023cc..29ded30 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -7,7 +7,11 @@ "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", - "clean": "rm -rf dist" + "clean": "rm -rf dist coverage", + "test": "vitest run --coverage", + "test:watch": "vitest", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui" }, "dependencies": { "@ymir/types": "workspace:*", @@ -16,10 +20,17 @@ "zustand": "^5.0.0" }, "devDependencies": { + "@playwright/test": "^1.61.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.0", + "@vitest/coverage-v8": "^4.1.9", + "jsdom": "^29.1.1", "typescript": "^5.8.0", - "vite": "^6.3.0" + "vite": "^6.3.0", + "vitest": "^4.1.9" } } diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts new file mode 100644 index 0000000..16f2b71 --- /dev/null +++ b/apps/web/playwright.config.ts @@ -0,0 +1,31 @@ +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + testDir: './e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: process.env.CI ? 'github' : 'list', + + use: { + baseURL: 'http://localhost:5173', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + // Start the Vite dev server before running tests + webServer: { + command: 'pnpm dev', + url: 'http://localhost:5173', + reuseExistingServer: !process.env.CI, + timeout: 30_000, + }, +}) diff --git a/apps/web/src/test/setup.ts b/apps/web/src/test/setup.ts new file mode 100644 index 0000000..c44951a --- /dev/null +++ b/apps/web/src/test/setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom' diff --git a/apps/web/vitest.config.ts b/apps/web/vitest.config.ts new file mode 100644 index 0000000..15bc3ec --- /dev/null +++ b/apps/web/vitest.config.ts @@ -0,0 +1,37 @@ +import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react' +import { resolve } from 'path' + +export default defineConfig({ + plugins: [react()], + test: { + environment: 'jsdom', + globals: true, + setupFiles: ['./src/test/setup.ts'], + include: ['src/**/*.{test,spec}.{ts,tsx}'], + exclude: ['e2e/**', 'node_modules/**'], + coverage: { + provider: 'v8', + reporter: ['text', 'lcov', 'html'], + reportsDirectory: './coverage', + include: ['src/**/*.{ts,tsx}'], + exclude: [ + 'src/test/**', + 'src/main.tsx', + 'src/**/*.d.ts', + 'src/**/index.ts', + ], + thresholds: { + lines: 80, + functions: 80, + branches: 80, + statements: 80, + }, + }, + }, + resolve: { + alias: { + '@ymir/types': resolve(__dirname, '../../packages/types/src/index.ts'), + }, + }, +}) diff --git a/docs/conventions/frontend.md b/docs/conventions/frontend.md index 460722a..a7413b9 100644 --- a/docs/conventions/frontend.md +++ b/docs/conventions/frontend.md @@ -189,3 +189,79 @@ features/simulation/components/ - Sem `console.log` em código de produção - Ordenação de imports: `externos → @ymir/* → features/ → components/ → relativos` - Componentes funcionais apenas — sem class components + +--- + +## Testes + +### Obrigatoriedade + +**Toda feature de UI deve ter testes antes de ser considerada completa.** +Cobertura mínima: **80%** em linhas, funções, branches e statements. +O build CI falha se os thresholds não forem atingidos (`pnpm test` retorna exit 1). + +### Dois níveis obrigatórios + +| Nível | Ferramenta | Onde | O que testar | +|-------|-----------|------|-------------| +| Unit / Component | Vitest + Testing Library | `src/**/*.test.{ts,tsx}` | Lógica pura, hooks, stores, componentes isolados | +| E2E / Fluxo | Playwright | `e2e/**/*.spec.ts` | Happy path e fluxos críticos no browser real | + +### Convenções de testes unitários (Vitest) + +```typescript +// src/features/scenario-creator/store.test.ts +import { renderHook, act } from '@testing-library/react' +import { useScenarioStore } from './store' + +describe('useScenarioStore', () => { + beforeEach(() => { + useScenarioStore.getState().reset() + }) + + it('adds a vessel to the scenario', () => { + const { result } = renderHook(() => useScenarioStore()) + act(() => { + result.current.addVessel({ vesselId: 1, name: 'VLCC' }) + }) + expect(result.current.vessels).toHaveLength(1) + expect(result.current.vessels[0].vesselId).toBe(1) + }) +}) +``` + +- Arquivo de teste co-localizado com o arquivo testado: `Foo.tsx` → `Foo.test.tsx` +- `describe` agrupa por módulo/componente; `it` descreve comportamento observável +- Mocks externos (Leaflet, fetch, WASM) em `src/test/mocks/` +- Setup global em `src/test/setup.ts` (já configurado) +- Rodar: `pnpm --filter @ymir/web test` + +### Convenções de testes E2E (Playwright) + +```typescript +// e2e/scenario-creator.spec.ts +import { test, expect } from '@playwright/test' + +test('instrutor cria e inicia simulação', async ({ page }) => { + await page.goto('/') + await page.getByRole('combobox', { name: /área/i }).selectOption('baia_de_guanabara') + await page.getByRole('button', { name: /adicionar vessel/i }).click() + await page.getByRole('button', { name: /iniciar/i }).click() + await expect(page.getByText(/rodando/i)).toBeVisible() +}) +``` + +- Usar `getByRole` / `getByText` / `getByLabel` — nunca seletores CSS frágeis +- Um arquivo por feature: `e2e/scenario-creator.spec.ts` +- Toda feature nova: ao menos um spec cobrindo o happy path +- Rodar: `pnpm --filter @ymir/web test:e2e` +- Config em `apps/web/playwright.config.ts` (Chromium, webServer automático) + +### Comandos + +```bash +pnpm --filter @ymir/web test # unit + coverage (CI gate) +pnpm --filter @ymir/web test:watch # modo watch (desenvolvimento) +pnpm --filter @ymir/web test:e2e # E2E headless +pnpm --filter @ymir/web test:e2e:ui # E2E com UI do Playwright +``` diff --git a/docs/specs/features/scenario-creator/tasks.md b/docs/specs/features/scenario-creator/tasks.md new file mode 100644 index 0000000..6787b3a --- /dev/null +++ b/docs/specs/features/scenario-creator/tasks.md @@ -0,0 +1,485 @@ +# Scenario Creator — Tasks + +**Spec**: `docs/specs/features/scenario-creator/spec.md` +**Design**: `docs/specs/features/scenario-creator/design.md` +**Status**: Pending + +--- + +## Dependências externas a instalar antes de começar + +```bash +pnpm --filter @ymir/web add leaflet react-leaflet @types/leaflet +``` + +--- + +## Mapa de dependências + +``` +T01 (geo.ts) + └─► T04 (AreaMapView) + └─► T07 (ScenarioCreatorPage) + +T02 (useScenarioStore) + └─► T03 (useAreas / useVessels hooks) + └─► T04 (AreaMapView) + └─► T05 (ScenarioForm) + └─► T06 (VesselList) + +T05, T06, T08 (SimulationControls) ──► T09 (Sidebar) + └─► T07 (ScenarioCreatorPage) + +T10 (extend simulationStore) ──► T07 +T11 (extend simulation.worker) ──► T10 + +T07 ──► T12 (App.tsx swap) +T07 ──► T13 (unit tests) +T12 ──► T14 (E2E tests) +``` + +--- + +## Tasks + +--- + +### T01 — `geo.ts`: conversão lat/lng ↔ metros + +**Requisitos cobertos**: SC-P1-01, SC-P1-02 + +**O que**: Criar utilitário de projeção equiretangular local. + +**Onde**: +- `apps/web/src/lib/geo.ts` (criar) +- `apps/web/src/lib/geo.test.ts` (criar) + +**Depende de**: nada + +**Reutiliza**: nada + +**Interfaces**: + +```typescript +export function latlngToMeters( + lat: number, lng: number, + origin: { latitude: number; longitude: number } +): { x: number; y: number } + +export function metersToLatLng( + x: number, y: number, + origin: { latitude: number; longitude: number } +): [number, number] // [lat, lng] + +export function isInsidePolygon( + x: number, y: number, + polygonMeters: Array<{ x: number; y: number }> +): boolean +``` + +**Done when**: +- [ ] `latlngToMeters` e `metersToLatLng` são inversas (round-trip error < 1m em 100km) +- [ ] `isInsidePolygon` usa ray-casting algorithm +- [ ] Cobertura ≥ 80% no módulo + +**Gate**: `pnpm --filter @ymir/web test -- src/lib/geo.test.ts` + +--- + +### T02 — `useScenarioStore`: estado do rascunho de cenário + +**Requisitos cobertos**: SC-P1-02, SC-P1-03, SC-P2-01 + +**O que**: Criar Zustand store para o cenário em construção. + +**Onde**: +- `apps/web/src/features/scenario-creator/store.ts` (criar) +- `apps/web/src/features/scenario-creator/store.test.ts` (criar) + +**Depende de**: `@ymir/types` (AreaDTO, CreateScenarioDTO) + +**Reutiliza**: padrão de `stores/simulationStore.ts` + +**Interface do store**: + +```typescript +interface ScenarioDraftVessel { + vesselId: number + name: string + x: number // metros, relativo à origem da área + y: number + headingDeg: number +} + +interface ScenarioStore { + name: string + areaId: number | null + area: AreaDTO | null + vessels: ScenarioDraftVessel[] + + setName: (name: string) => void + setArea: (area: AreaDTO) => void + addVessel: (v: Pick) => void + removeVessel: (vesselId: number) => void + updateVesselPosition: (vesselId: number, x: number, y: number) => void + updateVesselHeading: (vesselId: number, headingDeg: number) => void + reset: () => void + toCreateScenarioDTO: () => CreateScenarioDTO +} +``` + +**Done when**: +- [ ] Todas as ações mudam estado corretamente +- [ ] `toCreateScenarioDTO()` serializa `initialConditions` com `psi = headingDeg * π/180` +- [ ] Nome padrão gerado como `"Cenário ${new Date().toLocaleDateString('pt-BR')}"` +- [ ] Cobertura ≥ 80% + +**Gate**: `pnpm --filter @ymir/web test -- src/features/scenario-creator/store.test.ts` + +--- + +### T03 — Hooks de dados: `useAreas` e `useVessels` + +**Requisitos cobertos**: SC-P1-01, SC-P1-02 + +**O que**: Dois hooks que buscam dados da API. + +**Onde**: +- `apps/web/src/features/scenario-creator/hooks/use-areas.ts` (criar) +- `apps/web/src/features/scenario-creator/hooks/use-vessels.ts` (criar) +- `apps/web/src/features/scenario-creator/hooks/use-areas.test.ts` (criar) +- `apps/web/src/features/scenario-creator/hooks/use-vessels.test.ts` (criar) + +**Depende de**: `@ymir/types` + +**Interfaces**: + +```typescript +// use-areas.ts +export function useAreas(): { + areas: AreaDTO[] + loading: boolean + error: string | null + retry: () => void +} + +// use-vessels.ts +export function useVessels(): { + vessels: VesselDTO[] + loading: boolean + error: string | null +} +``` + +**Done when**: +- [ ] Fetch via `VITE_API_URL` env var (fallback `http://localhost:3000`) +- [ ] Estado `loading` correto durante fetch +- [ ] `error` populado em caso de falha de rede +- [ ] `retry()` reexecuta o fetch +- [ ] Testes com `fetch` mockado cobrem: success, loading, error +- [ ] Cobertura ≥ 80% + +**Gate**: `pnpm --filter @ymir/web test -- src/features/scenario-creator/hooks/` + +--- + +### T04 — `AreaMapView` + `VesselMarker`: mapa com marcadores + +**Requisitos cobertos**: SC-P1-01, SC-P1-02, SC-P1-03, SC-P2-02 + +**O que**: Componente Leaflet com polígono da área e marcadores draggáveis. + +**Onde**: +- `apps/web/src/features/scenario-creator/components/AreaMapView.tsx` (criar) +- `apps/web/src/features/scenario-creator/components/VesselMarker.tsx` (criar) +- `apps/web/src/features/scenario-creator/components/AreaMapView.test.tsx` (criar) + +**Depende de**: T01 (geo.ts), T02 (useScenarioStore), `react-leaflet`, `leaflet` + +**Instalar antes**: +```bash +pnpm --filter @ymir/web add leaflet react-leaflet @types/leaflet +``` + +**CSS do Leaflet**: importar `leaflet/dist/leaflet.css` em `main.tsx`. + +**Done when**: +- [ ] Mapa renderiza tile OSM centrado na origem da área selecionada +- [ ] Polígono da área desenhado em destaque (cor distinta, sem preenchimento sólido) +- [ ] Bounds do mapa ajustados ao polígono na seleção da área +- [ ] Cada vessel em `useScenarioStore.vessels` tem marcador no mapa +- [ ] Drag do marcador chama `updateVesselPosition` com coords em metros +- [ ] Durante simulação ativa (`status === 'running'`): marcadores seguem `SimulationStateDTO`; drag desabilitado +- [ ] Marcadores com heading rotacionado visualmente +- [ ] Cobertura ≥ 80% (mock `react-leaflet` em testes unitários) + +**Nota**: Leaflet precisa de `height` explícito no container CSS — usar `height: 100%` com parent `100vh`. + +**Gate**: `pnpm --filter @ymir/web test -- src/features/scenario-creator/components/AreaMapView.test.tsx` + +--- + +### T05 — `ScenarioForm`: nome do cenário + seletor de área + +**Requisitos cobertos**: SC-P1-01, SC-P2-01 + +**O que**: Formulário com campo de nome e dropdown de áreas. + +**Onde**: +- `apps/web/src/features/scenario-creator/components/ScenarioForm.tsx` (criar) +- `apps/web/src/features/scenario-creator/components/ScenarioForm.test.tsx` (criar) + +**Depende de**: T02 (useScenarioStore), T03 (useAreas) + +**Done when**: +- [ ] Input de nome controlado por `useScenarioStore.setName` +- [ ] Dropdown lista áreas de `useAreas()` — cada option tem `value={area.id}` +- [ ] Seleção de área chama `useScenarioStore.setArea(selectedArea)` +- [ ] Estado `loading` exibe "Carregando áreas..." +- [ ] Estado `error` exibe mensagem + botão retry +- [ ] Cobertura ≥ 80% + +**Gate**: `pnpm --filter @ymir/web test -- src/features/scenario-creator/components/ScenarioForm.test.tsx` + +--- + +### T06 — `VesselList`: lista de vessels + seletor + +**Requisitos cobertos**: SC-P1-02, SC-P1-03 + +**O que**: Lista vessels adicionados ao cenário; permite adicionar, remover e editar heading. + +**Onde**: +- `apps/web/src/features/scenario-creator/components/VesselList.tsx` (criar) +- `apps/web/src/features/scenario-creator/components/VesselList.test.tsx` (criar) + +**Depende de**: T02 (useScenarioStore), T03 (useVessels) + +**Done when**: +- [ ] Botão "Adicionar vessel" desabilitado quando `area === null` +- [ ] Clique abre seletor com vessels de `useVessels()` +- [ ] Seleção chama `addVessel({ vesselId, name })` +- [ ] Cada item mostra: nome, x/y arredondados, input de heading (0–360) +- [ ] Input de heading chama `updateVesselHeading` e normaliza para [0, 360) +- [ ] Botão remover chama `removeVessel(vesselId)` +- [ ] Cobertura ≥ 80% + +**Gate**: `pnpm --filter @ymir/web test -- src/features/scenario-creator/components/VesselList.test.tsx` + +--- + +### T07 — `SimulationControls`: iniciar / parar + status + +**Requisitos cobertos**: SC-P1-04 + +**O que**: Botões de controle da simulação e indicador de status. + +**Onde**: +- `apps/web/src/features/scenario-creator/components/SimulationControls.tsx` (criar) +- `apps/web/src/features/scenario-creator/components/SimulationControls.test.tsx` (criar) + +**Depende de**: T02 (useScenarioStore), `stores/simulationStore.ts` + +**Done when**: +- [ ] "Iniciar" desabilitado quando `areaId === null` ou `vessels.length === 0` +- [ ] "Iniciar" chama `POST /scenarios` com `toCreateScenarioDTO()`, depois `simulationStore.start()` +- [ ] "Parar" chama `simulationStore.stop()` +- [ ] Indicador de status reflete `simulationStore.status` (idle/loading/running/error) +- [ ] Exibe tempo de simulação `t` em segundos quando `running` +- [ ] Cobertura ≥ 80% + +**Gate**: `pnpm --filter @ymir/web test -- src/features/scenario-creator/components/SimulationControls.test.tsx` + +--- + +### T08 — `Sidebar`: compõe ScenarioForm + VesselList + SimulationControls + +**Requisitos cobertos**: todos (composição) + +**O que**: Painel lateral que agrupa os três subcomponentes. + +**Onde**: +- `apps/web/src/features/scenario-creator/components/Sidebar.tsx` (criar) + +**Depende de**: T05, T06, T07 + +**Done when**: +- [ ] Renderiza ``, ``, `` na ordem correta +- [ ] Largura fixa (~320px), altura 100vh, scroll interno se necessário +- [ ] Sem lógica de negócio — apenas composição e layout + +**Gate**: inspeção visual (sem gate de teste unitário — lógica está nos filhos) + +--- + +### T09 — `ScenarioCreatorPage`: layout raiz + +**Requisitos cobertos**: todos + +**O que**: Container full-screen que posiciona Sidebar + AreaMapView lado a lado. + +**Onde**: +- `apps/web/src/features/scenario-creator/ScenarioCreatorPage.tsx` (criar) + +**Depende de**: T04, T08 + +**Done when**: +- [ ] Layout `display: flex; height: 100vh` — Sidebar fixa + mapa ocupa o restante +- [ ] Sem estado próprio — lê stores + +**Gate**: inspeção visual + +--- + +### T10 — Extender `useSimulationStore` com `loadScenario` + +**Requisitos cobertos**: SC-P1-04 + +**O que**: Adicionar `loadScenario(vessels)` ao store existente para substituir o `addVessel(1)` hardcoded. + +**Onde**: +- `apps/web/src/stores/simulationStore.ts` (modificar) + +**Depende de**: nada (T07 consome isso) + +**Done when**: +- [ ] `loadScenario(vessels: ScenarioDraftVessel[])` armazena vessels no store +- [ ] `start()` usa vessels do store (não mais `addVessel(1)` hardcoded no worker init) +- [ ] `reset()` limpa vessels + +**Gate**: `pnpm --filter @ymir/web test -- src/stores/simulationStore.test.ts` (criar junto) + +--- + +### T11 — Extender `simulation.worker.ts` com suporte a múltiplos vessels + +**Requisitos cobertos**: SC-P1-04 + +**O que**: Worker recebe lista de vessels com condições iniciais reais. + +**Onde**: +- `apps/web/src/workers/simulation.worker.ts` (modificar) + +**Depende de**: T10 + +**Done when**: +- [ ] Worker aceita mensagem `{ type: 'loadScenario', vessels: ScenarioDraftVessel[] }` +- [ ] Para cada vessel: chama `simulation.addVessel(id)` e (quando disponível) `simulation.setInitialConditions(id, x, y, psi)` +- [ ] Remove chamada hardcoded `simulation.addVessel(1)` do `initWasm()` + +**Gate**: `pnpm --filter @ymir/web test:e2e` smoke test passa + +--- + +### T12 — Atualizar `App.tsx` + +**Requisitos cobertos**: todos (entry point) + +**O que**: Substituir o `` atual por ``. + +**Onde**: +- `apps/web/src/App.tsx` (modificar) +- `apps/web/src/main.tsx` — adicionar import de `leaflet/dist/leaflet.css` + +**Depende de**: T09 + +**Done when**: +- [ ] `App.tsx` renderiza apenas `` +- [ ] `main.tsx` importa `leaflet/dist/leaflet.css` antes de `App` +- [ ] `tsc --noEmit` sem erros + +**Gate**: `pnpm --filter @ymir/web build` + +--- + +### T13 — Testes unitários das utilidades e stores + +**Requisitos cobertos**: Regra de 80% de cobertura + +**O que**: Testes unitários para todos os módulos lógicos não cobertos em T01–T11. + +**Onde**: +- `apps/web/src/lib/geo.test.ts` +- `apps/web/src/features/scenario-creator/store.test.ts` +- `apps/web/src/stores/simulationStore.test.ts` +- `apps/web/src/features/scenario-creator/hooks/use-areas.test.ts` +- `apps/web/src/features/scenario-creator/hooks/use-vessels.test.ts` + +**Depende de**: T01, T02, T03, T10 + +**Done when**: +- [ ] `pnpm --filter @ymir/web test` passa com cobertura ≥ 80% em todos os thresholds +- [ ] Nenhum teste usa implementação interna — apenas API pública dos módulos + +**Gate**: `pnpm --filter @ymir/web test` — exit 0 + +--- + +### T14 — Testes E2E com Playwright + +**Requisitos cobertos**: SC-P1-01 a SC-P1-04 (happy paths) + +**O que**: Spec E2E cobrindo o fluxo completo do instrutor. + +**Onde**: +- `apps/web/e2e/scenario-creator.spec.ts` (criar) + +**Depende de**: T12 (app funcionando), API rodando com seed + +**Pré-condição**: `pnpm --filter @ymir/api dev` rodando com `vessel1.json` e `area.json`. + +**Cenários obrigatórios**: + +1. **Seleção de área**: selecionar "baia_de_guanabara" → mapa visível → polígono presente +2. **Adicionar vessel**: clicar "Adicionar vessel" → selecionar VLCC → marcador aparece no mapa +3. **Iniciar simulação**: clicar "Iniciar" → status muda para "running" +4. **Parar simulação**: clicar "Parar" → status muda para estado parado + +**Done when**: +- [ ] Todos os 4 cenários passam em Chromium headless +- [ ] `pnpm --filter @ymir/web test:e2e` — exit 0 + +**Gate**: `pnpm --filter @ymir/web test:e2e` + +--- + +## Ordem de execução recomendada + +``` +[P] T01 + T02 # paralelos — sem dependência entre si + ↓ +[P] T03 + T10 # paralelos + ↓ +[P] T04 + T05 + T06 + T07 + T11 # paralelos após T01-T03-T10 + ↓ + T08 # depende de T05, T06, T07 + ↓ + T09 # depende de T04, T08 + ↓ + T12 # depende de T09 + ↓ +[P] T13 + T14 # paralelos — testes finais +``` + +**Estimativa**: 14 tasks, ~8 sessões de implementação com subagentes paralelos. + +--- + +## Status + +| Task | Descrição | Status | +|------|-----------|--------| +| T01 | geo.ts | Pending | +| T02 | useScenarioStore | Pending | +| T03 | useAreas / useVessels | Pending | +| T04 | AreaMapView + VesselMarker | Pending | +| T05 | ScenarioForm | Pending | +| T06 | VesselList | Pending | +| T07 | SimulationControls | Pending | +| T08 | Sidebar | Pending | +| T09 | ScenarioCreatorPage | Pending | +| T10 | extend simulationStore | Pending | +| T11 | extend simulation.worker | Pending | +| T12 | App.tsx swap | Pending | +| T13 | Unit tests | Pending | +| T14 | E2E Playwright | Pending | diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ff0b5a..be220fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -64,6 +64,18 @@ importers: specifier: ^5.0.0 version: 5.0.14(@types/react@18.3.31)(react@18.3.1) devDependencies: + '@playwright/test': + specifier: ^1.61.1 + version: 1.61.1 + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) '@types/react': specifier: ^18.3.0 version: 18.3.31 @@ -73,12 +85,21 @@ importers: '@vitejs/plugin-react': specifier: ^4.3.0 version: 4.7.0(vite@6.4.3(@types/node@22.20.0)(tsx@4.22.4)) + '@vitest/coverage-v8': + specifier: ^4.1.9 + version: 4.1.9(vitest@4.1.9) + jsdom: + specifier: ^29.1.1 + version: 29.1.1 typescript: specifier: ^5.8.0 version: 5.9.3 vite: specifier: ^6.3.0 version: 6.4.3(@types/node@22.20.0)(tsx@4.22.4) + vitest: + specifier: ^4.1.9 + version: 4.1.9(@types/node@22.20.0)(@vitest/coverage-v8@4.1.9)(jsdom@29.1.1)(vite@6.4.3(@types/node@22.20.0)(tsx@4.22.4)) packages/types: dependencies: @@ -92,6 +113,24 @@ importers: packages: + '@adobe/css-tools@4.5.0': + resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} + + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@7.1.1': + resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} @@ -163,6 +202,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + '@babel/template@7.29.7': resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} @@ -175,6 +218,50 @@ packages: resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.1.0': + resolution: {integrity: sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.9': + resolution: {integrity: sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.5': + resolution: {integrity: sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} @@ -768,6 +855,15 @@ packages: cpu: [x64] os: [win32] + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@fastify/ajv-compiler@3.6.0': resolution: {integrity: sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==} @@ -805,6 +901,11 @@ packages: '@pinojs/redact@0.4.0': resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@playwright/test@1.61.1': + resolution: {integrity: sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==} + engines: {node: '>=18'} + hasBin: true + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -936,6 +1037,38 @@ packages: '@sinclair/typebox@0.34.49': resolution: {integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + '@turbo/darwin-64@2.10.0': resolution: {integrity: sha512-EwvHThXzpY0KGd1/NAmuewI5D+aVa3Rl/OlxE36yfjUKb/+ySrfJrSlEFt8aD1OXwnnaHnQnPKHFndor0Zxlsg==} cpu: [x64] @@ -966,6 +1099,9 @@ packages: cpu: [arm64] os: [win32] + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -981,6 +1117,12 @@ packages: '@types/better-sqlite3@7.6.13': resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} @@ -1004,6 +1146,44 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/coverage-v8@4.1.9': + resolution: {integrity: sha512-G9/lgqibheLVBDRuya45EbsEXTYcWoSG+TLg7i2axuzx0Eq62eXn+aWXyaVdV5vKvFSWd6ywcX8hA7la9Pvu8g==} + peerDependencies: + '@vitest/browser': 4.1.9 + vitest: 4.1.9 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.1.9': + resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==} + + '@vitest/mocker@4.1.9': + resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.9': + resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==} + + '@vitest/runner@4.1.9': + resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==} + + '@vitest/snapshot@4.1.9': + resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==} + + '@vitest/spy@4.1.9': + resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==} + + '@vitest/utils@4.1.9': + resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==} + abstract-logging@2.0.1: resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} @@ -1026,6 +1206,28 @@ packages: ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.4: + resolution: {integrity: sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==} + atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} @@ -1044,6 +1246,9 @@ packages: better-sqlite3@11.10.0: resolution: {integrity: sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==} + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -1064,6 +1269,10 @@ packages: caniuse-lite@1.0.30001799: resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -1074,9 +1283,20 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1086,6 +1306,9 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -1094,10 +1317,20 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + drizzle-kit@0.30.6: resolution: {integrity: sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g==} hasBin: true @@ -1200,10 +1433,17 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + env-paths@3.0.0: resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + esbuild-register@3.6.0: resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} peerDependencies: @@ -1233,10 +1473,17 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + fast-content-type-parse@1.1.0: resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==} @@ -1290,6 +1537,11 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1310,9 +1562,24 @@ packages: github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -1323,13 +1590,40 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + isexe@3.1.5: resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} engines: {node: '>=18'} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + jsdom@29.1.1: + resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1353,13 +1647,38 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.3: + resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -1391,6 +1710,10 @@ packages: obliterator@2.0.5: resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} + obug@2.1.3: + resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} + engines: {node: '>=12.20.0'} + on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} @@ -1398,6 +1721,12 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1415,6 +1744,16 @@ packages: resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==} hasBin: true + playwright-core@1.61.1: + resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.61.1: + resolution: {integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==} + engines: {node: '>=18'} + hasBin: true + postcss@8.5.15: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} @@ -1425,6 +1764,10 @@ packages: deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. hasBin: true + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + process-warning@3.0.0: resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} @@ -1438,6 +1781,10 @@ packages: pump@3.0.4: resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} @@ -1450,6 +1797,9 @@ packages: peerDependencies: react: ^18.3.1 + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -1466,6 +1816,10 @@ packages: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -1499,6 +1853,10 @@ packages: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} @@ -1521,6 +1879,9 @@ packages: resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} @@ -1545,13 +1906,30 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tar-fs@2.1.5: resolution: {integrity: sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==} @@ -1562,14 +1940,40 @@ packages: thread-stream@3.2.0: resolution: {integrity: sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tldts-core@7.4.4: + resolution: {integrity: sha512-vwVLJVvvpslm7vqAH7+XNj/neA/Ynq7DT2EEcMuwc5YzN5XaMyRAqxwU+uX3azZ1FQtB2gvrvnLnAEkvYlVdfg==} + + tldts@7.4.4: + resolution: {integrity: sha512-kFXFK7O4WPextIUAOk8qtnw9dxR9UIXP9CjuH1cTBVBZMDeQcUPgr/IazGiw1B0Yiw5L75gHLWeW4iD793r90g==} + hasBin: true + toad-cache@3.7.1: resolution: {integrity: sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ==} engines: {node: '>=20'} + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + tsx@4.22.4: resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} engines: {node: '>=18.0.0'} @@ -1590,6 +1994,10 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@7.28.0: + resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} + engines: {node: '>=20.18.1'} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -1639,14 +2047,83 @@ packages: yaml: optional: true + vitest@4.1.9: + resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.9 + '@vitest/browser-preview': 4.1.9 + '@vitest/browser-webdriverio': 4.1.9 + '@vitest/coverage-istanbul': 4.1.9 + '@vitest/coverage-v8': 4.1.9 + '@vitest/ui': 4.1.9 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + which@4.0.0: resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} engines: {node: ^16.13.0 || >=18.0.0} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -1670,6 +2147,28 @@ packages: snapshots: + '@adobe/css-tools@4.5.0': {} + + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@asamuzakjp/dom-selector@7.1.1': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + + '@asamuzakjp/generational-cache@1.0.1': {} + + '@asamuzakjp/nwsapi@2.3.9': {} + '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 @@ -1759,6 +2258,8 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 + '@babel/runtime@7.29.7': {} + '@babel/template@7.29.7': dependencies: '@babel/code-frame': 7.29.7 @@ -1782,6 +2283,36 @@ snapshots: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 + '@bcoe/v8-coverage@1.0.2': {} + + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.1.0': {} + + '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.9(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.1.0 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.5(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + '@drizzle-team/brocli@0.10.2': {} '@esbuild-kit/core-utils@3.3.2': @@ -2085,6 +2616,8 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true + '@exodus/bytes@1.15.1': {} + '@fastify/ajv-compiler@3.6.0': dependencies: ajv: 8.20.0 @@ -2129,6 +2662,10 @@ snapshots: '@pinojs/redact@0.4.0': {} + '@playwright/test@1.61.1': + dependencies: + playwright: 1.61.1 + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.62.2': @@ -2208,6 +2745,42 @@ snapshots: '@sinclair/typebox@0.34.49': {} + '@standard-schema/spec@1.1.0': {} + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.5.0 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.7 + '@testing-library/dom': 10.4.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.31 + '@types/react-dom': 18.3.7(@types/react@18.3.31) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + '@turbo/darwin-64@2.10.0': optional: true @@ -2226,6 +2799,8 @@ snapshots: '@turbo/windows-arm64@2.10.0': optional: true + '@types/aria-query@5.0.4': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.7 @@ -2251,6 +2826,13 @@ snapshots: dependencies: '@types/node': 22.20.0 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.9': {} '@types/node@22.20.0': @@ -2280,6 +2862,61 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/coverage-v8@4.1.9(vitest@4.1.9)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.9 + ast-v8-to-istanbul: 1.0.4 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.3 + obug: 2.1.3 + std-env: 4.1.0 + tinyrainbow: 3.1.0 + vitest: 4.1.9(@types/node@22.20.0)(@vitest/coverage-v8@4.1.9)(jsdom@29.1.1)(vite@6.4.3(@types/node@22.20.0)(tsx@4.22.4)) + + '@vitest/expect@4.1.9': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.9 + '@vitest/utils': 4.1.9 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.9(vite@6.4.3(@types/node@22.20.0)(tsx@4.22.4))': + dependencies: + '@vitest/spy': 4.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.3(@types/node@22.20.0)(tsx@4.22.4) + + '@vitest/pretty-format@4.1.9': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.9': + dependencies: + '@vitest/utils': 4.1.9 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.9': + dependencies: + '@vitest/pretty-format': 4.1.9 + '@vitest/utils': 4.1.9 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.9': {} + + '@vitest/utils@4.1.9': + dependencies: + '@vitest/pretty-format': 4.1.9 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + abstract-logging@2.0.1: {} ajv-formats@2.1.1(ajv@8.20.0): @@ -2297,6 +2934,24 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ansi-regex@5.0.1: {} + + ansi-styles@5.2.0: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.4: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + atomic-sleep@1.0.0: {} avvio@8.4.0: @@ -2313,6 +2968,10 @@ snapshots: bindings: 1.5.0 prebuild-install: 7.1.3 + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 @@ -2340,26 +2999,50 @@ snapshots: caniuse-lite@1.0.30001799: {} + chai@6.2.2: {} + chownr@1.1.4: {} convert-source-map@2.0.0: {} cookie@0.7.2: {} + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css.escape@1.5.1: {} + csstype@3.2.3: {} + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + debug@4.4.3: dependencies: ms: 2.1.3 + decimal.js@10.6.0: {} + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 deep-extend@0.6.0: {} + dequal@2.0.3: {} + detect-libc@2.1.2: {} + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + drizzle-kit@0.30.6: dependencies: '@drizzle-team/brocli': 0.10.2 @@ -2382,8 +3065,12 @@ snapshots: dependencies: once: 1.4.0 + entities@8.0.0: {} + env-paths@3.0.0: {} + es-module-lexer@2.1.0: {} + esbuild-register@3.6.0(esbuild@0.19.12): dependencies: debug: 4.4.3 @@ -2502,8 +3189,14 @@ snapshots: escalade@3.2.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + expand-template@2.0.3: {} + expect-type@1.4.0: {} + fast-content-type-parse@1.1.0: {} fast-decode-uri-component@1.0.1: {} @@ -2569,6 +3262,9 @@ snapshots: fs-constants@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -2591,18 +3287,73 @@ snapshots: github-from-package@0.0.0: {} + has-flag@4.0.0: {} + + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.1 + transitivePeerDependencies: + - '@noble/hashes' + + html-escaper@2.0.2: {} + ieee754@1.2.1: {} + indent-string@4.0.0: {} + inherits@2.0.4: {} ini@1.3.8: {} ipaddr.js@1.9.1: {} + is-potential-custom-element-name@1.0.1: {} + isexe@3.1.5: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + js-tokens@10.0.0: {} + js-tokens@4.0.0: {} + jsdom@29.1.1: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@asamuzakjp/dom-selector': 7.1.1 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.5(css-tree@3.2.1) + '@exodus/bytes': 1.15.1 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.1 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.1 + undici: 7.28.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + jsesc@3.1.0: {} json-schema-ref-resolver@1.0.1: @@ -2623,12 +3374,34 @@ snapshots: dependencies: js-tokens: 4.0.0 + lru-cache@11.5.1: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.3: + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.8.5 + + mdn-data@2.27.1: {} + mimic-response@3.1.0: {} + min-indent@1.0.1: {} + minimist@1.2.8: {} mkdirp-classic@0.5.3: {} @@ -2651,12 +3424,20 @@ snapshots: obliterator@2.0.5: {} + obug@2.1.3: {} + on-exit-leak-free@2.1.2: {} once@1.4.0: dependencies: wrappy: 1.0.2 + parse5@8.0.1: + dependencies: + entities: 8.0.0 + + pathe@2.0.3: {} + picocolors@1.1.1: {} picomatch@4.0.4: {} @@ -2681,6 +3462,14 @@ snapshots: sonic-boom: 4.2.1 thread-stream: 3.2.0 + playwright-core@1.61.1: {} + + playwright@1.61.1: + dependencies: + playwright-core: 1.61.1 + optionalDependencies: + fsevents: 2.3.2 + postcss@8.5.15: dependencies: nanoid: 3.3.15 @@ -2702,6 +3491,12 @@ snapshots: tar-fs: 2.1.5 tunnel-agent: 0.6.0 + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + process-warning@3.0.0: {} process-warning@5.0.0: {} @@ -2716,6 +3511,8 @@ snapshots: end-of-stream: 1.4.5 once: 1.4.0 + punycode@2.3.1: {} + quick-format-unescaped@4.0.4: {} rc@1.2.8: @@ -2731,6 +3528,8 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-is@17.0.2: {} + react-refresh@0.17.0: {} react@18.3.1: @@ -2745,6 +3544,11 @@ snapshots: real-require@0.2.0: {} + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + require-from-string@2.0.2: {} resolve-pkg-maps@1.0.0: {} @@ -2794,6 +3598,10 @@ snapshots: safe-stable-stringify@2.5.0: {} + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -2808,6 +3616,8 @@ snapshots: shell-quote@1.9.0: {} + siginfo@2.0.0: {} + simple-concat@1.0.1: {} simple-get@4.0.1: @@ -2831,12 +3641,26 @@ snapshots: split2@4.2.0: {} + stackback@0.0.2: {} + + std-env@4.1.0: {} + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + strip-json-comments@2.0.1: {} + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + symbol-tree@3.2.4: {} + tar-fs@2.1.5: dependencies: chownr: 1.1.4 @@ -2856,13 +3680,33 @@ snapshots: dependencies: real-require: 0.2.0 + tinybench@2.9.0: {} + + tinyexec@1.2.4: {} + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyrainbow@3.1.0: {} + + tldts-core@7.4.4: {} + + tldts@7.4.4: + dependencies: + tldts-core: 7.4.4 + toad-cache@3.7.1: {} + tough-cookie@6.0.1: + dependencies: + tldts: 7.4.4 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + tsx@4.22.4: dependencies: esbuild: 0.28.1 @@ -2886,6 +3730,8 @@ snapshots: undici-types@6.21.0: {} + undici@7.28.0: {} + update-browserslist-db@1.2.3(browserslist@4.28.4): dependencies: browserslist: 4.28.4 @@ -2907,12 +3753,66 @@ snapshots: fsevents: 2.3.3 tsx: 4.22.4 + vitest@4.1.9(@types/node@22.20.0)(@vitest/coverage-v8@4.1.9)(jsdom@29.1.1)(vite@6.4.3(@types/node@22.20.0)(tsx@4.22.4)): + dependencies: + '@vitest/expect': 4.1.9 + '@vitest/mocker': 4.1.9(vite@6.4.3(@types/node@22.20.0)(tsx@4.22.4)) + '@vitest/pretty-format': 4.1.9 + '@vitest/runner': 4.1.9 + '@vitest/snapshot': 4.1.9 + '@vitest/spy': 4.1.9 + '@vitest/utils': 4.1.9 + es-module-lexer: 2.1.0 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.3 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 6.4.3(@types/node@22.20.0)(tsx@4.22.4) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.20.0 + '@vitest/coverage-v8': 4.1.9(vitest@4.1.9) + jsdom: 29.1.1 + transitivePeerDependencies: + - msw + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + which@4.0.0: dependencies: isexe: 3.1.5 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wrappy@1.0.2: {} + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + yallist@3.1.1: {} zustand@5.0.14(@types/react@18.3.31)(react@18.3.1): diff --git a/turbo.json b/turbo.json index e387cad..c891e49 100644 --- a/turbo.json +++ b/turbo.json @@ -21,6 +21,11 @@ "outputs": ["coverage/**"], "cache": true }, + "test:e2e": { + "dependsOn": ["^build"], + "outputs": ["playwright-report/**", "test-results/**"], + "cache": false + }, "lint": { "outputs": [], "cache": true