chore: dependências e teste de ponta a ponta
Saem gsap, motion, clsx e tailwind-merge; entram server-only, @playwright/test e @axe-core/playwright. Suíte e2e (rotas, axe, teclado, navegação, sem JS, rolagem) rodável com pnpm test:e2e contra BASE_URL.
This commit is contained in:
1 parent
318a764190
commit
7b633f0f4a
12 files changed
+459
-94
No files matched your search
@@ -12,6 +12,8 @@
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
|
||||
@@ -54,7 +54,7 @@ Arquivos de cada projeto em `public/videos/<id>/`:
|
||||
| Arquivo | Para quê |
|
||||
| --- | --- |
|
||||
| `desktop.{webm,mp4,webp}` e `mobile.{webm,mp4,webp}` | cards e vídeo ampliado (até 3 MB cada vídeo) |
|
||||
| `scrub-desktop.mp4` e `scrub-mobile.mp4` | vitrine animada: quadro-chave a cada 6 quadros, sem B-frames, para o tempo mudar a cada quadro da rolagem |
|
||||
| `scrub-desktop.mp4` | vitrine animada: quadro-chave a cada 6 quadros, sem B-frames, para o tempo mudar a cada quadro da rolagem (o celular da moldura mostra só o pôster) |
|
||||
| `og.jpg` | capa 1200x630 de compartilhamento |
|
||||
|
||||
Para gravar ou regravar um projeto, use `scripts/record-project-videos.mjs`; para gerar os `scrub-*.mp4`, `scripts/encode-scrub-video.mjs` (as instruções de instalação do Playwright e do ffmpeg estão no cabeçalho de cada script). Depois de gravar, marque os capítulos do projeto em `lib/project-showcase.ts`.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import type { APIRequestContext } from "@playwright/test";
|
||||
|
||||
/** Caminhos (`/`, `/cases/…`) de todas as rotas do /sitemap.xml, sem o domínio de produção. */
|
||||
export async function sitemapPaths(request: APIRequestContext) {
|
||||
const response = await request.get("/sitemap.xml");
|
||||
if (response.status() !== 200)
|
||||
throw new Error(`/sitemap.xml respondeu ${response.status()}`);
|
||||
const xml = await response.text();
|
||||
const paths = [...xml.matchAll(/<loc>\s*([^<\s]+)\s*<\/loc>/g)].map(
|
||||
([, loc]) => {
|
||||
const { pathname } = new URL(loc);
|
||||
return pathname === "" ? "/" : pathname;
|
||||
},
|
||||
);
|
||||
return [...new Set(paths)];
|
||||
}
|
||||
|
||||
/** Nomes dos projetos de cliente, como a home os lista (public/portfolio-data.json, kind "cliente"). */
|
||||
export function clientProjectNames() {
|
||||
const data = JSON.parse(
|
||||
readFileSync(
|
||||
join(__dirname, "..", "public", "portfolio-data.json"),
|
||||
"utf8",
|
||||
),
|
||||
) as { projects: Array<{ title: string; kind?: string }> };
|
||||
return data.projects
|
||||
.filter((project) => project.kind === "cliente")
|
||||
.map((project) => project.title);
|
||||
}
|
||||
|
||||
/** Texto do cabeçalho da seção de contato da home. */
|
||||
export const CONTACT_HEADING =
|
||||
"Vamos conversar sobre o que sua empresa precisa.";
|
||||
|
||||
export const DESKTOP = { width: 1440, height: 900 } as const;
|
||||
export const PHONE = { width: 390, height: 844 } as const;
|
||||
@@ -0,0 +1,62 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { DESKTOP } from "./helpers";
|
||||
|
||||
/**
|
||||
* (c) Do topo da home, o Tab chega ao campo de nome do contato em no máximo 30 toques.
|
||||
* O teste continua apertando Tab (até 120) depois do limite só para dizer, na falha, quantos toques faltavam.
|
||||
*/
|
||||
|
||||
const MAX_TABS = 30;
|
||||
const GIVE_UP_AFTER = 120;
|
||||
|
||||
test.use({ viewport: DESKTOP });
|
||||
|
||||
test(`Tab do topo da home chega ao campo de nome do contato em até ${MAX_TABS} toques`, async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/", { waitUntil: "load" });
|
||||
await page.waitForTimeout(800);
|
||||
// o foco começa no documento, como numa visita nova
|
||||
await page.evaluate(() => (document.activeElement as HTMLElement)?.blur());
|
||||
await page.evaluate(() => window.scrollTo({ top: 0, behavior: "instant" }));
|
||||
|
||||
const visited: string[] = [];
|
||||
let reached = 0;
|
||||
for (let tab = 1; tab <= GIVE_UP_AFTER; tab++) {
|
||||
await page.keyboard.press("Tab");
|
||||
const where = await page.evaluate(() => {
|
||||
const element = document.activeElement as HTMLElement | null;
|
||||
if (!element) return { isName: false, label: "(nada)" };
|
||||
const label = (
|
||||
element.getAttribute("aria-label") ||
|
||||
element.textContent ||
|
||||
element.getAttribute("name") ||
|
||||
element.tagName
|
||||
)
|
||||
.trim()
|
||||
.replace(/\s+/g, " ")
|
||||
.slice(0, 40);
|
||||
return {
|
||||
isName:
|
||||
element.getAttribute("name") === "name" &&
|
||||
Boolean(element.closest("#contact")),
|
||||
label: `${element.tagName.toLowerCase()} "${label}"`,
|
||||
};
|
||||
});
|
||||
visited.push(`${tab}. ${where.label}`);
|
||||
if (where.isName) {
|
||||
reached = tab;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
expect(
|
||||
reached,
|
||||
`o campo de nome não apareceu nem em ${GIVE_UP_AFTER} Tabs`,
|
||||
).toBeGreaterThan(0);
|
||||
expect(
|
||||
reached,
|
||||
`o campo de nome só foi alcançado no Tab ${reached} (limite ${MAX_TABS}); passou por:\n${visited.join("\n")}`,
|
||||
).toBeLessThanOrEqual(MAX_TABS);
|
||||
await expect(page.locator("#contact input[name='name']")).toBeFocused();
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { DESKTOP } from "./helpers";
|
||||
|
||||
/**
|
||||
* (d) Clicar em "Cases" no menu e depois num case navega sem recarregar o documento (um marcador em `window`
|
||||
* sobrevive) e cada troca de página dispara uma startViewTransition; a carga inicial, nenhuma.
|
||||
*/
|
||||
|
||||
type Probe = { __marker?: string; __viewTransitions?: number };
|
||||
|
||||
test.use({ viewport: DESKTOP });
|
||||
|
||||
test("menu Cases e depois um case: sem recarregar o documento, uma transição de página por navegação", async ({
|
||||
page,
|
||||
}) => {
|
||||
// conta as chamadas de document.startViewTransition, desde antes do primeiro script da página
|
||||
await page.addInitScript(() => {
|
||||
const probe = window as unknown as Probe;
|
||||
probe.__viewTransitions = 0;
|
||||
const original = document.startViewTransition?.bind(document);
|
||||
if (!original) return;
|
||||
document.startViewTransition = ((...args: Parameters<typeof original>) => {
|
||||
(probe.__viewTransitions as number) += 1;
|
||||
return original(...args);
|
||||
}) as typeof document.startViewTransition;
|
||||
});
|
||||
const transitions = () =>
|
||||
page.evaluate(() => (window as unknown as Probe).__viewTransitions);
|
||||
|
||||
await page.goto("/", { waitUntil: "load" });
|
||||
await page.waitForTimeout(1500);
|
||||
await page.evaluate(() => {
|
||||
(window as unknown as Probe).__marker = "mesmo-documento";
|
||||
});
|
||||
expect(
|
||||
await page.evaluate(() => "startViewTransition" in document),
|
||||
"o navegador precisa ter startViewTransition",
|
||||
).toBe(true);
|
||||
expect(await transitions(), "carga inicial").toBe(0);
|
||||
|
||||
await page
|
||||
.getByRole("navigation", { name: "Navegação principal" })
|
||||
.getByRole("link", { name: "Cases" })
|
||||
.click();
|
||||
await page.waitForURL("**/cases");
|
||||
await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
|
||||
await page.waitForTimeout(500);
|
||||
expect(await transitions(), "depois de Cases").toBe(1);
|
||||
|
||||
await page.getByRole("link", { name: "Ver o case" }).first().click();
|
||||
await page.waitForURL(/\/cases\/[^/]+$/);
|
||||
await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
|
||||
await page.waitForTimeout(500);
|
||||
expect(await transitions(), "depois de abrir um case").toBe(2);
|
||||
|
||||
expect(
|
||||
await page.evaluate(() => (window as unknown as Probe).__marker),
|
||||
"o documento foi recarregado (o marcador sumiu)",
|
||||
).toBe("mesmo-documento");
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { CONTACT_HEADING, clientProjectNames, DESKTOP } from "./helpers";
|
||||
|
||||
/** (e) Com JavaScript desligado, a home mostra o texto do contato e o nome dos clientes. */
|
||||
|
||||
test.use({ viewport: DESKTOP, javaScriptEnabled: false });
|
||||
|
||||
test("sem JavaScript a home mostra o contato e os nomes dos 4 clientes", async ({
|
||||
page,
|
||||
}) => {
|
||||
const clients = clientProjectNames();
|
||||
expect(clients, "clientes em public/portfolio-data.json").toHaveLength(4);
|
||||
|
||||
const response = await page.goto("/", { waitUntil: "load" });
|
||||
expect(response?.status()).toBe(200);
|
||||
|
||||
await expect(
|
||||
page.getByRole("heading", { level: 2, name: CONTACT_HEADING }),
|
||||
).toBeVisible();
|
||||
for (const name of clients) {
|
||||
await expect(
|
||||
page.getByText(name, { exact: false }).first(),
|
||||
`cliente "${name}"`,
|
||||
).toBeVisible();
|
||||
}
|
||||
// o texto está no HTML entregue, não só na tela: vale também para quem lê sem renderizar
|
||||
const html = await (await page.request.get("/")).text();
|
||||
expect(html).toContain(CONTACT_HEADING);
|
||||
for (const name of clients) expect(html).toContain(name);
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import AxeBuilder from "@axe-core/playwright";
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { DESKTOP, PHONE, sitemapPaths } from "./helpers";
|
||||
|
||||
/**
|
||||
* (a) Todas as rotas do /sitemap.xml respondem 200, sem erro de console e sem rolagem horizontal, no computador
|
||||
* (1440x900) e no celular (390x844).
|
||||
* (b) O axe não acha violação serious ou critical em nenhuma delas.
|
||||
* As rotas vêm do próprio sitemap: uma página nova entra no teste sozinha.
|
||||
*/
|
||||
|
||||
const VIEWPORTS = [
|
||||
{ name: "computador 1440x900", size: DESKTOP },
|
||||
{ name: "celular 390x844", size: PHONE },
|
||||
] as const;
|
||||
|
||||
for (const { name, size } of VIEWPORTS) {
|
||||
test.describe(`rotas do sitemap, ${name}`, () => {
|
||||
test.use({ viewport: size });
|
||||
|
||||
test("respondem 200, sem erro de console e sem rolagem horizontal", async ({
|
||||
page,
|
||||
request,
|
||||
}) => {
|
||||
const paths = await sitemapPaths(request);
|
||||
expect(paths.length, "rotas no sitemap").toBeGreaterThan(10);
|
||||
|
||||
for (const path of paths) {
|
||||
await test.step(path, async () => {
|
||||
const problems: string[] = [];
|
||||
const onConsole = (
|
||||
message: import("@playwright/test").ConsoleMessage,
|
||||
) => {
|
||||
if (message.type() === "error")
|
||||
problems.push(`console.error: ${message.text()}`);
|
||||
};
|
||||
const onPageError = (error: Error) =>
|
||||
problems.push(`erro não tratado: ${error.message}`);
|
||||
page.on("console", onConsole);
|
||||
page.on("pageerror", onPageError);
|
||||
|
||||
const response = await page.goto(path, { waitUntil: "load" });
|
||||
// dá tempo de a hidratação e das primeiras animações acontecerem (e de errarem)
|
||||
await page.waitForTimeout(800);
|
||||
|
||||
page.off("console", onConsole);
|
||||
page.off("pageerror", onPageError);
|
||||
|
||||
expect.soft(response?.status(), `${path}: status`).toBe(200);
|
||||
expect.soft(problems, `${path}: console`).toEqual([]);
|
||||
const overflow = await page.evaluate(
|
||||
() =>
|
||||
Math.max(
|
||||
document.documentElement.scrollWidth,
|
||||
document.body.scrollWidth,
|
||||
) - document.documentElement.clientWidth,
|
||||
);
|
||||
expect
|
||||
.soft(overflow, `${path}: px de rolagem horizontal`)
|
||||
.toBeLessThanOrEqual(0);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test.describe("acessibilidade (axe), computador", () => {
|
||||
test.use({ viewport: DESKTOP });
|
||||
|
||||
test("nenhuma violação serious ou critical em nenhuma rota do sitemap", async ({
|
||||
page,
|
||||
request,
|
||||
}) => {
|
||||
const paths = await sitemapPaths(request);
|
||||
|
||||
for (const path of paths) {
|
||||
await test.step(path, async () => {
|
||||
await page.goto(path, { waitUntil: "load" });
|
||||
await page.waitForTimeout(800);
|
||||
const { violations } = await new AxeBuilder({ page }).analyze();
|
||||
const serious = violations
|
||||
.filter(({ impact }) => impact === "serious" || impact === "critical")
|
||||
.map(
|
||||
({ id, impact, nodes }) =>
|
||||
`${id} (${impact}): ${nodes.length} elemento(s), por exemplo ${nodes[0]?.target.join(" ")}`,
|
||||
);
|
||||
expect.soft(serious, `${path}: axe`).toEqual([]);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { DESKTOP } from "./helpers";
|
||||
|
||||
/**
|
||||
* (f) No computador, nenhuma das paradas a cada 10 % da rolagem da home tem tela vazia: sempre há ao menos um
|
||||
* elemento de texto visível na janela. Cabeçalho, avisos, leitores de tela e itens ocultos não contam
|
||||
* (o cabeçalho fixo estaria em todas as paradas e esconderia uma tela vazia).
|
||||
*/
|
||||
|
||||
test.use({ viewport: DESKTOP });
|
||||
|
||||
test("nenhuma parada a cada 10 % da home tem tela vazia", async ({ page }) => {
|
||||
await page.goto("/", { waitUntil: "load" });
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
const empty: string[] = [];
|
||||
const stops: string[] = [];
|
||||
for (let step = 0; step <= 10; step++) {
|
||||
await page.evaluate((fraction) => {
|
||||
const max = document.documentElement.scrollHeight - innerHeight;
|
||||
window.scrollTo({ top: Math.round(max * fraction), behavior: "instant" });
|
||||
}, step / 10);
|
||||
// a rolagem suave, as animações ligadas à rolagem e as imagens preguiçosas assentam
|
||||
await page.waitForTimeout(700);
|
||||
|
||||
const result = await page.evaluate(() => {
|
||||
const ignored =
|
||||
"header, footer [aria-hidden], [aria-hidden='true'], [role='dialog'], .sr-only, .skip-link, script, style, noscript";
|
||||
const effectiveOpacity = (element: Element | null) => {
|
||||
let opacity = 1;
|
||||
for (let node = element; node; node = node.parentElement)
|
||||
opacity *= Number.parseFloat(getComputedStyle(node).opacity);
|
||||
return opacity;
|
||||
};
|
||||
const visible: string[] = [];
|
||||
const walker = document.createTreeWalker(
|
||||
document.body,
|
||||
NodeFilter.SHOW_TEXT,
|
||||
);
|
||||
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
||||
const text = node.textContent?.trim() ?? "";
|
||||
const parent = node.parentElement;
|
||||
if (text.length < 3 || !parent || parent.closest(ignored)) continue;
|
||||
const style = getComputedStyle(parent);
|
||||
if (style.visibility !== "visible" || style.display === "none")
|
||||
continue;
|
||||
if (effectiveOpacity(parent) < 0.2) continue;
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(node);
|
||||
const rect = range.getBoundingClientRect();
|
||||
const inside =
|
||||
rect.width > 1 &&
|
||||
rect.height > 1 &&
|
||||
rect.bottom > 0 &&
|
||||
rect.top < innerHeight &&
|
||||
rect.right > 0 &&
|
||||
rect.left < innerWidth;
|
||||
if (inside) visible.push(text.slice(0, 40));
|
||||
}
|
||||
return { count: visible.length, sample: visible.slice(0, 2) };
|
||||
});
|
||||
|
||||
const label = `${step * 10}%`;
|
||||
stops.push(`${label}: ${result.count} trecho(s) de texto`);
|
||||
if (result.count === 0) empty.push(label);
|
||||
}
|
||||
|
||||
expect(
|
||||
empty,
|
||||
`paradas com tela vazia (nenhum texto visível):\n${stops.join("\n")}`,
|
||||
).toEqual([]);
|
||||
});
|
||||
+3
-2
@@ -5,12 +5,13 @@ const config: Config = {
|
||||
testEnvironment: "jsdom",
|
||||
roots: ["<rootDir>/app", "<rootDir>/lib"],
|
||||
modulePathIgnorePatterns: ["<rootDir>/.worktrees/"],
|
||||
// o teste de fumaça (Playwright) roda com `pnpm test:e2e`, não no Jest
|
||||
testPathIgnorePatterns: ["/node_modules/", "<rootDir>/e2e/"],
|
||||
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
|
||||
moduleNameMapper: {
|
||||
"^@/(.*)$": "<rootDir>/$1",
|
||||
"^.+\\.(css|sass|scss)$": "identity-obj-proxy",
|
||||
"^.+/animations/PointCloud$":
|
||||
"<rootDir>/app/components/animations/__mocks__/PointCloud.tsx",
|
||||
"^server-only$": "<rootDir>/lib/__mocks__/server-only.ts",
|
||||
},
|
||||
testMatch: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"],
|
||||
transform: {
|
||||
|
||||
+4
-4
@@ -26,24 +26,24 @@
|
||||
"format": "biome format --write",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:e2e": "playwright test",
|
||||
"check:projects": "node scripts/check-projects.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-three/drei": "10.7.7",
|
||||
"@react-three/fiber": "9.5.0",
|
||||
"clsx": "2.1.1",
|
||||
"gsap": "3.13.0",
|
||||
"lenis": "^1.3.26",
|
||||
"lucide-react": "0.542.0",
|
||||
"motion": "12.23.12",
|
||||
"next": "16.3.8",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"tailwind-merge": "3.3.1",
|
||||
"server-only": "0.0.1",
|
||||
"three": "0.182.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "^4.13.0",
|
||||
"@biomejs/biome": "2.2.0",
|
||||
"@playwright/test": "^1.64.1",
|
||||
"@tailwindcss/postcss": "4.1.12",
|
||||
"@testing-library/jest-dom": "6.9.1",
|
||||
"@testing-library/react": "16.3.2",
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Teste de fumaça de ponta a ponta (pasta e2e/), rodado contra um servidor que já está no ar:
|
||||
* BASE_URL endereço do site (padrão http://localhost:3001)
|
||||
* PW_CHROMIUM caminho do chrome.exe, para não baixar navegador do Playwright
|
||||
* (ex.: %LOCALAPPDATA%\ms-playwright\chromium-1248\chrome-win64\chrome.exe)
|
||||
* Este arquivo não sobe servidor nenhum: `pnpm build && pnpm start -p 3001` (ou `pnpm dev`) antes de rodar.
|
||||
*/
|
||||
const baseURL = process.env.BASE_URL ?? "http://localhost:3001";
|
||||
const executablePath = process.env.PW_CHROMIUM || undefined;
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./e2e",
|
||||
// um servidor de desenvolvimento compila cada rota na primeira visita: com folga
|
||||
timeout: 120_000,
|
||||
expect: { timeout: 10_000 },
|
||||
// um navegador por vez: o servidor local (principalmente o dev) não aguenta várias compilações juntas
|
||||
workers: 1,
|
||||
fullyParallel: false,
|
||||
retries: 0,
|
||||
reporter: [["list"]],
|
||||
use: {
|
||||
baseURL,
|
||||
trace: "retain-on-failure",
|
||||
screenshot: "only-on-failure",
|
||||
launchOptions: { executablePath },
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: {
|
||||
...devices["Desktop Chrome"],
|
||||
launchOptions: { executablePath },
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
Generated
+58
-87
@@ -18,40 +18,37 @@ importers:
|
||||
'@react-three/fiber':
|
||||
specifier: 9.5.0
|
||||
version: 9.5.0(@types/[email protected])([email protected]([email protected]))([email protected])([email protected])
|
||||
clsx:
|
||||
specifier: 2.1.1
|
||||
version: 2.1.1
|
||||
gsap:
|
||||
specifier: 3.13.0
|
||||
version: 3.13.0
|
||||
lenis:
|
||||
specifier: ^1.3.26
|
||||
version: 1.3.26([email protected])
|
||||
lucide-react:
|
||||
specifier: 0.542.0
|
||||
version: 0.542.0([email protected])
|
||||
motion:
|
||||
specifier: 12.23.12
|
||||
version: 12.23.12([email protected]([email protected]))([email protected])
|
||||
next:
|
||||
specifier: 16.3.8
|
||||
version: 16.3.8(@babel/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||
version: 16.3.8(@babel/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])
|
||||
react:
|
||||
specifier: 19.1.0
|
||||
version: 19.1.0
|
||||
react-dom:
|
||||
specifier: 19.1.0
|
||||
version: 19.1.0([email protected])
|
||||
tailwind-merge:
|
||||
specifier: 3.3.1
|
||||
version: 3.3.1
|
||||
server-only:
|
||||
specifier: 0.0.1
|
||||
version: 0.0.1
|
||||
three:
|
||||
specifier: 0.182.0
|
||||
version: 0.182.0
|
||||
devDependencies:
|
||||
'@axe-core/playwright':
|
||||
specifier: ^4.13.0
|
||||
version: 4.13.0([email protected])
|
||||
'@biomejs/biome':
|
||||
specifier: 2.2.0
|
||||
version: 2.2.0
|
||||
'@playwright/test':
|
||||
specifier: ^1.64.1
|
||||
version: 1.64.1
|
||||
'@tailwindcss/postcss':
|
||||
specifier: 4.1.12
|
||||
version: 4.1.12
|
||||
@@ -113,6 +110,11 @@ packages:
|
||||
'@asamuzakjp/[email protected]':
|
||||
resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
|
||||
|
||||
'@axe-core/[email protected]':
|
||||
resolution: {integrity: sha512-6YLx+kxXu5GJceG4ozFg+33a2EMTdjYwWGloJ3sb9Kta5pp+ZNS53uxGVog5JetIY8s++P5UrtX+cri+u0VAVg==}
|
||||
peerDependencies:
|
||||
playwright-core: '>= 1.0.0'
|
||||
|
||||
'@babel/[email protected]':
|
||||
resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -769,6 +771,11 @@ packages:
|
||||
resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
|
||||
'@playwright/[email protected]':
|
||||
resolution: {integrity: sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
'@react-three/[email protected]':
|
||||
resolution: {integrity: sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==}
|
||||
peerDependencies:
|
||||
@@ -1203,6 +1210,10 @@ packages:
|
||||
resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==}
|
||||
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
|
||||
@@ -1329,10 +1340,6 @@ packages:
|
||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
|
||||
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
||||
@@ -1507,20 +1514,6 @@ packages:
|
||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g==}
|
||||
peerDependencies:
|
||||
'@emotion/is-prop-valid': '*'
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/is-prop-valid':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
@@ -1560,9 +1553,6 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==}
|
||||
engines: {node: '>=0.4.7'}
|
||||
@@ -2059,26 +2049,6 @@ packages:
|
||||
resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-8jCD8uW5GD1csOoqh1WhH1A6j5APHVE15nuBkFeRiMzYBdRwyAHmSP/oXSuW0WJPZRXTFdBoG4hY9TFWNhhwng==}
|
||||
peerDependencies:
|
||||
'@emotion/is-prop-valid': '*'
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/is-prop-valid':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
@@ -2205,6 +2175,16 @@ packages:
|
||||
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@@ -2310,6 +2290,9 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
@@ -2434,9 +2417,6 @@ packages:
|
||||
resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==}
|
||||
|
||||
@@ -2739,6 +2719,11 @@ snapshots:
|
||||
'@csstools/css-tokenizer': 3.0.4
|
||||
lru-cache: 10.4.3
|
||||
|
||||
'@axe-core/[email protected]([email protected])':
|
||||
dependencies:
|
||||
axe-core: 4.13.0
|
||||
playwright-core: 1.64.1
|
||||
|
||||
'@babel/[email protected]':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.29.7
|
||||
@@ -3468,6 +3453,10 @@ snapshots:
|
||||
|
||||
'@pkgr/[email protected]': {}
|
||||
|
||||
'@playwright/[email protected]':
|
||||
dependencies:
|
||||
playwright: 1.64.1
|
||||
|
||||
'@react-three/[email protected](@react-three/[email protected](@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))(@types/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected])([email protected])':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.7
|
||||
@@ -3866,6 +3855,8 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected](@babel/[email protected]):
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
@@ -4019,8 +4010,6 @@ snapshots:
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
@@ -4168,15 +4157,6 @@ snapshots:
|
||||
cross-spawn: 7.0.6
|
||||
signal-exit: 4.1.0
|
||||
|
||||
[email protected]([email protected]([email protected]))([email protected]):
|
||||
dependencies:
|
||||
motion-dom: 12.43.0
|
||||
motion-utils: 12.39.0
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0([email protected])
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
@@ -4212,8 +4192,6 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
minimist: 1.2.8
|
||||
@@ -4905,20 +4883,6 @@ snapshots:
|
||||
dependencies:
|
||||
minipass: 7.1.3
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
motion-utils: 12.39.0
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]([email protected]([email protected]))([email protected]):
|
||||
dependencies:
|
||||
framer-motion: 12.43.0([email protected]([email protected]))([email protected])
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0([email protected])
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
@@ -4929,7 +4893,7 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected](@babel/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected]):
|
||||
[email protected](@babel/[email protected])(@playwright/[email protected])(@types/[email protected])([email protected]([email protected]))([email protected]):
|
||||
dependencies:
|
||||
'@next/env': 16.3.8
|
||||
'@swc/helpers': 0.5.23
|
||||
@@ -4948,6 +4912,7 @@ snapshots:
|
||||
'@next/swc-linux-x64-musl': 16.3.8
|
||||
'@next/swc-win32-arm64-msvc': 16.3.8
|
||||
'@next/swc-win32-x64-msvc': 16.3.8
|
||||
'@playwright/test': 1.64.1
|
||||
sharp: 0.35.4(@types/[email protected])
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
@@ -5024,6 +4989,12 @@ snapshots:
|
||||
dependencies:
|
||||
find-up: 4.1.0
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
playwright-core: 1.64.1
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
nanoid: 3.3.18
|
||||
@@ -5116,6 +5087,8 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected](@types/[email protected]):
|
||||
dependencies:
|
||||
'@img/colour': 1.1.0
|
||||
@@ -5244,8 +5217,6 @@ snapshots:
|
||||
dependencies:
|
||||
'@pkgr/core': 0.3.6
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
Reference in new issue
Block a user