diff --git a/__tests__/e2e/__snapshots__/empty-project.e2e.ts.snap b/__tests__/e2e/__snapshots__/empty-project.e2e.ts.snap new file mode 100644 index 0000000..03ad0bd --- /dev/null +++ b/__tests__/e2e/__snapshots__/empty-project.e2e.ts.snap @@ -0,0 +1,64 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`when the project is empty > shows "Select framework" instead of "Start setup" on the Welcome screen > welcome-no-framework 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + + Confidence Quickstart + + Feature flags and experiments, set up with AI in minutes. + This wizard will help you get started with Confidence. + + 1. It will check your system + 2. Sign you in to Confidence workspace + 3. Teach your AI agent about Confidence + 4. Integrate the SDK into your project + 5. Show a working feature flag example + + + Directory ✔ + Framework ✘ Could not auto-detect + Telemetry ✘ off + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Please, select your framework + + ❯ Select framework + About Confidence + Quit + + ↑↓ navigate enter confirm vX.Y.Z" +`; + +exports[`when the project is empty > shows "Start setup" after selecting a framework > welcome-after-framework-selected 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + + Confidence Quickstart + + Feature flags and experiments, set up with AI in minutes. + This wizard will help you get started with Confidence. + + 1. It will check your system + 2. Sign you in to Confidence workspace + 3. Teach your AI agent about Confidence + 4. Integrate the SDK into your project + 5. Show a working feature flag example + + + Directory ✔ + Framework ✔ React (selected) + Telemetry ✘ off + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Ready to get started? + + ❯ Start setup + Change framework + About Confidence + Quit + + ↑↓ navigate enter confirm vX.Y.Z" +`; diff --git a/__tests__/e2e/__snapshots__/happy-path.e2e.ts.snap b/__tests__/e2e/__snapshots__/happy-path.e2e.ts.snap new file mode 100644 index 0000000..91fa0d9 --- /dev/null +++ b/__tests__/e2e/__snapshots__/happy-path.e2e.ts.snap @@ -0,0 +1,156 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`happy-path flow > navigates Welcome → SystemCheck → Authenticate → InstallPlugins → ConnectTools → OnboardProject → Done > authenticate 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + Sign in to Confidence Todo (2/5) + + Sign in so the wizard can create flags and set up your project. ● Check system + ● Sign in to Confidence + ✔ Authenticated as test@example.com ○ Set up your agent + ○ Connect tools + Continuing... ○ Onboard project" +`; + +exports[`happy-path flow > navigates Welcome → SystemCheck → Authenticate → InstallPlugins → ConnectTools → OnboardProject → Done > connect-tools 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + Connect your AI to Confidence Todo (3/5) + + Give your AI assistant direct access to flags, docs, and ● Check system + integration tools. With it, you'll be able to: ● Sign in to Confidence + ● Set up your agent + ● Ask your agent to create and manage feature flags. ▶ Connect tools + ● Search docs and generate code, all from your IDE. ○ Onboard project + ● Avoid context-switching or manual lookups. Your assistant has the + keys. + + Confidence tools: + ─ confidence-flags + ─ confidence-docs + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Connect Confidence tools? + + ❯ Connect all tools + Connect confidence-flags + Connect confidence-docs + Skip for now + + ↑↓ navigate enter confirm vX.Y.Z" +`; + +exports[`happy-path flow > navigates Welcome → SystemCheck → Authenticate → InstallPlugins → ConnectTools → OnboardProject → Done > done 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + + ✔ Confidence is ready! + + What we have set up: + ● Added feature flag example + ● Created confidence.config.ts + + + Full report: [CONFIDENCE_QUICKSTART.md] + Documentation: https://confidence.spotify.com/docs + Dashboard: https://app.confidence.spotify.com + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + What's next? + + ❯ Continue work with Claude Code + Exit + + ↑↓ navigate enter confirm esc cancel vX.Y.Z" +`; + +exports[`happy-path flow > navigates Welcome → SystemCheck → Authenticate → InstallPlugins → ConnectTools → OnboardProject → Done > install-plugins 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + Teach your AI Confidence Todo (2/5) + + Plugins give your agent tool Confidence-specific skills — flag ● Check system + management, warehouse setup, migrations, onboarding — no more ● Sign in to Confidence + searching docs yourself. ▶ Set up your agent + ○ Connect tools + ○ Onboard project + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Which agent tool are you using? + + ❯ Claude Code + Cursor + Codex + Skip (install manually later) + + ↑↓ navigate enter confirm vX.Y.Z" +`; + +exports[`happy-path flow > navigates Welcome → SystemCheck → Authenticate → InstallPlugins → ConnectTools → OnboardProject → Done > onboard-project 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + Set up your project Todo (4/5) + + The wizard will add the Confidence SDK and create your first ● Check system + feature flag. ● Sign in to Confidence + ● Set up your agent + ◆ The AI agent will be able to read and write files in your ● Connect tools + project. ▶ Onboard project + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Start onboarding? + + ❯ Start onboarding + Skip for now + + ↑↓ navigate enter confirm vX.Y.Z" +`; + +exports[`happy-path flow > navigates Welcome → SystemCheck → Authenticate → InstallPlugins → ConnectTools → OnboardProject → Done > system-check 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + System Check Todo (1/5) + + Checking that your system has everything we need. ● Check system + ○ Sign in to Confidence + ✔ Node.js vX.Y.Z ○ Set up your agent + ✔ Git X.Y.Z ○ Connect tools + ○ Onboard project + All checks passed. Continuing..." +`; + +exports[`happy-path flow > navigates Welcome → SystemCheck → Authenticate → InstallPlugins → ConnectTools → OnboardProject → Done > welcome 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + + Confidence Quickstart + + Feature flags and experiments, set up with AI in minutes. + This wizard will help you get started with Confidence. + + 1. It will check your system + 2. Sign you in to Confidence workspace + 3. Teach your AI agent about Confidence + 4. Integrate the SDK into your project + 5. Show a working feature flag example + + + Directory ✔ + Framework ✔ React (detected) + Telemetry ✘ off + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Ready to get started? + + ❯ Start setup + Change framework + About Confidence + Quit + + ↑↓ navigate enter confirm vX.Y.Z" +`; diff --git a/__tests__/e2e/__snapshots__/skip-onboarding.e2e.ts.snap b/__tests__/e2e/__snapshots__/skip-onboarding.e2e.ts.snap new file mode 100644 index 0000000..5b5cbb8 --- /dev/null +++ b/__tests__/e2e/__snapshots__/skip-onboarding.e2e.ts.snap @@ -0,0 +1,45 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`when the user skips onboarding > offers "Continue work with Claude Code" since plugin was installed > done-skipped-with-ide 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + + ◆ Onboarding skipped + + You can always use Confidence AI plugin to run onboarding yourself later. + + + Documentation: https://confidence.spotify.com/docs + Dashboard: https://app.confidence.spotify.com + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + What's next? + + ❯ Continue work with Claude Code + Exit + + ↑↓ navigate enter confirm esc cancel vX.Y.Z" +`; + +exports[`when the user skips onboarding > shows Done screen without report file or code changes > done-skipped 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + + ◆ Onboarding skipped + + You can always use Confidence AI plugin to run onboarding yourself later. + + + Documentation: https://confidence.spotify.com/docs + Dashboard: https://app.confidence.spotify.com + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + What's next? + + ❯ Continue work with Claude Code + Exit + + ↑↓ navigate enter confirm esc cancel vX.Y.Z" +`; diff --git a/__tests__/e2e/__snapshots__/skip-plugins.e2e.ts.snap b/__tests__/e2e/__snapshots__/skip-plugins.e2e.ts.snap new file mode 100644 index 0000000..c2707f8 --- /dev/null +++ b/__tests__/e2e/__snapshots__/skip-plugins.e2e.ts.snap @@ -0,0 +1,49 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`when the user skips installing AI plugin > does not show "Continue work with" on the Done screen > done-no-ide 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + + ✔ Confidence is ready! + + What we have set up: + ● Added feature flag example + ● Created confidence.config.ts + + + Full report: [CONFIDENCE_QUICKSTART.md] + Documentation: https://confidence.spotify.com/docs + Dashboard: https://app.confidence.spotify.com + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + What's next? + + ❯ Exit + + enter confirm esc cancel vX.Y.Z" +`; + +exports[`when the user skips installing AI plugin > still shows report file and code changes on the Done screen > done-no-ide-with-report 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + + ✔ Confidence is ready! + + What we have set up: + ● Added feature flag example + ● Created confidence.config.ts + + + Full report: [CONFIDENCE_QUICKSTART.md] + Documentation: https://confidence.spotify.com/docs + Dashboard: https://app.confidence.spotify.com + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + What's next? + + ❯ Exit + + enter confirm esc cancel vX.Y.Z" +`; diff --git a/__tests__/e2e/__snapshots__/skip-tools.e2e.ts.snap b/__tests__/e2e/__snapshots__/skip-tools.e2e.ts.snap new file mode 100644 index 0000000..3fc5a76 --- /dev/null +++ b/__tests__/e2e/__snapshots__/skip-tools.e2e.ts.snap @@ -0,0 +1,26 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`when the user skips connecting tools > shows skip message and proceeds to onboarding > done-tools-skipped 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + + ✔ Confidence is ready! + + What we have set up: + ● Added feature flag example + ● Created confidence.config.ts + + + Full report: [CONFIDENCE_QUICKSTART.md] + Documentation: https://confidence.spotify.com/docs + Dashboard: https://app.confidence.spotify.com + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + What's next? + + ❯ Continue work with Claude Code + Exit + + ↑↓ navigate enter confirm esc cancel vX.Y.Z" +`; diff --git a/__tests__/e2e/__snapshots__/stale-auth.e2e.ts.snap b/__tests__/e2e/__snapshots__/stale-auth.e2e.ts.snap new file mode 100644 index 0000000..b84379c --- /dev/null +++ b/__tests__/e2e/__snapshots__/stale-auth.e2e.ts.snap @@ -0,0 +1,106 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`when auth token is stale > allows re-authentication after expired token and proceeds normally > auth-re-authenticated 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + Teach your AI Confidence Todo (2/5) + + Plugins give your agent tool Confidence-specific skills — flag ● Check system + management, warehouse setup, migrations, onboarding — no more ● Sign in to Confidence + searching docs yourself. ▶ Set up your agent + ○ Connect tools + ○ Onboard project + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Which agent tool are you using? + + ❯ Claude Code + Cursor + Codex + Skip (install manually later) + + ↑↓ navigate enter confirm vX.Y.Z" +`; + +exports[`when auth token is stale > prompts user to sign in again instead of using the expired token > auth-expired 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + Sign in to Confidence Todo (1/5) + + Sign in so the wizard can create flags and set up your project. ● Check system + ▶ Sign in to Confidence + ○ Set up your agent + ○ Connect tools + ○ Onboard project + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + We'll open your browser to sign in. Continue? + + ❯ Sign in to a Confidence account + + enter confirm vX.Y.Z" +`; + +exports[`when auth token is stale > shows "Use existing account" when the token is still valid > auth-existing 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + Sign in to Confidence Todo (1/5) + + Sign in so the wizard can create flags and set up your project. ● Check system + ▶ Sign in to Confidence + ○ Set up your agent + ○ Connect tools + ○ Onboard project + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Found existing account (test@example.com). What would you like to do? + + ❯ Use existing account + Sign in to a different account + + ↑↓ navigate enter confirm vX.Y.Z" +`; + +exports[`when auth token is stale > treats a malformed token as invalid and prompts sign-in > auth-malformed 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + Sign in to Confidence Todo (1/5) + + Sign in so the wizard can create flags and set up your project. ● Check system + ▶ Sign in to Confidence + ○ Set up your agent + ○ Connect tools + ○ Onboard project + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + We'll open your browser to sign in. Continue? + + ❯ Sign in to a Confidence account + + enter confirm vX.Y.Z" +`; + +exports[`when auth token is stale > treats a near-expiry token as valid > auth-near-expiry 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + Sign in to Confidence Todo (1/5) + + Sign in so the wizard can create flags and set up your project. ● Check system + ▶ Sign in to Confidence + ○ Set up your agent + ○ Connect tools + ○ Onboard project + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Found existing account (test@example.com). What would you like to do? + + ❯ Use existing account + Sign in to a different account + + ↑↓ navigate enter confirm vX.Y.Z" +`; diff --git a/__tests__/e2e/__snapshots__/stale-mcp-auth.e2e.ts.snap b/__tests__/e2e/__snapshots__/stale-mcp-auth.e2e.ts.snap new file mode 100644 index 0000000..7aac2e0 --- /dev/null +++ b/__tests__/e2e/__snapshots__/stale-mcp-auth.e2e.ts.snap @@ -0,0 +1,75 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`when MCP config has expired auth tokens > allows skipping when auth is expired > mcp-auth-skipped 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + Set up your project Todo (4/5) + + The wizard will add the Confidence SDK and create your first ● Check system + feature flag. ● Sign in to Confidence + ● Set up your agent + ◆ The AI agent will be able to read and write files in your ● Connect tools + project. ▶ Onboard project + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Start onboarding? + + ❯ Start onboarding + Skip for now + + ↑↓ navigate enter confirm vX.Y.Z" +`; + +exports[`when MCP config has expired auth tokens > shows auth-expired status and reconnects successfully > connect-tools-expired 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + Connect your AI to Confidence Todo (3/5) + + Give your AI assistant direct access to flags, docs, and ● Check system + integration tools. With it, you'll be able to: ● Sign in to Confidence + ● Set up your agent + ● Ask your agent to create and manage feature flags. ▶ Connect tools + ● Search docs and generate code, all from your IDE. ○ Onboard project + ● Avoid context-switching or manual lookups. Your assistant has the + keys. + + Confidence tools: + ○ confidence-flags (auth expired) + ○ confidence-docs (auth expired) + + + Authentication expired — reconnect to refresh your credentials. + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Reconnect to refresh credentials? + + ❯ Reconnect all tools + Reconnect confidence-flags + Reconnect confidence-docs + Skip for now + + ↑↓ navigate enter confirm vX.Y.Z" +`; + +exports[`when MCP config has expired auth tokens > shows auth-expired status and reconnects successfully > connect-tools-reconnected 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + Connect your AI to Confidence Todo (4/5) + + Give your AI assistant direct access to flags, docs, and ● Check system + integration tools. With it, you'll be able to: ● Sign in to Confidence + ● Set up your agent + ● Ask your agent to create and manage feature flags. ● Connect tools + ● Search docs and generate code, all from your IDE. ○ Onboard project + ● Avoid context-switching or manual lookups. Your assistant has the + keys. + + Confidence tools: + ✔ confidence-flags + ✔ confidence-docs + + + Connected successfully. Continuing..." +`; diff --git a/__tests__/e2e/__snapshots__/system-check-failure.e2e.ts.snap b/__tests__/e2e/__snapshots__/system-check-failure.e2e.ts.snap new file mode 100644 index 0000000..b8cb671 --- /dev/null +++ b/__tests__/e2e/__snapshots__/system-check-failure.e2e.ts.snap @@ -0,0 +1,45 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`when system check fails > shows error when git is missing > system-check-failure 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + System Check Todo (0/5) + + Checking that your system has everything we need. ✘ Check system + ○ Sign in to Confidence + ✔ Node.js vX.Y.Z ○ Set up your agent + ✘ Git ○ Connect tools + ○ Onboard project + Some required tools are missing. Install them and retry. + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Required tools are missing. + + ❯ Retry + Quit + + ↑↓ navigate enter confirm vX.Y.Z" +`; + +exports[`when system check fails > shows error when node is not on PATH > system-check-node-missing 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + System Check Todo (0/5) + + Checking that your system has everything we need. ✘ Check system + ○ Sign in to Confidence + ✘ Node.js ○ Set up your agent + ✔ Git X.Y.Z ○ Connect tools + ○ Onboard project + Some required tools are missing. Install them and retry. + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Required tools are missing. + + ❯ Retry + Quit + + ↑↓ navigate enter confirm vX.Y.Z" +`; diff --git a/__tests__/e2e/__snapshots__/welcome-navigation.e2e.ts.snap b/__tests__/e2e/__snapshots__/welcome-navigation.e2e.ts.snap new file mode 100644 index 0000000..5ed7105 --- /dev/null +++ b/__tests__/e2e/__snapshots__/welcome-navigation.e2e.ts.snap @@ -0,0 +1,86 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`welcome screen navigation > exits cleanly on Quit > welcome-quit-selected 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + + Confidence Quickstart + + Feature flags and experiments, set up with AI in minutes. + This wizard will help you get started with Confidence. + + 1. It will check your system + 2. Sign you in to Confidence workspace + 3. Teach your AI agent about Confidence + 4. Integrate the SDK into your project + 5. Show a working feature flag example + + + Directory ✔ + Framework ✔ React (detected) + Telemetry ✘ off + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Ready to get started? + + Start setup + Change framework + About Confidence + ❯ Quit ✔ + + ↑↓ navigate enter confirm vX.Y.Z" +`; + +exports[`welcome screen navigation > navigates to About screen > about 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + + About Confidence + + Confidence is Spotify's feature flagging and experimentation platform. + Run experiments in your data warehouse — BigQuery, Snowflake, + Redshift, Databricks. Your data stays under your control. + OpenFeature-compliant SDKs mean no vendor lock-in. + + + Docs: https://confidence.spotify.com/docs + Dashboard: https://app.confidence.spotify.com + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Back to setup? + + ❯ Back + + enter confirm esc cancel vX.Y.Z" +`; + +exports[`welcome screen navigation > navigates to SelectFramework screen > select-framework 1`] = ` +" Confidence by Spotify https://confidence.spotify.com/ + + + Select Framework + + Choose your project's SDK and the wizard will: + 1. Install the matching Confidence SDK package + 2. Generate framework-specific integration code + 3. Guide you through a working feature flag example + + Currently: react + + + ────────────────────────────────────────────────────────────────────────────────────────────────── + Select your project's framework or language: + + ❯ React + TypeScript + JavaScript + Swift + Kotlin + Java + Python + Go + + ↑↓ navigate enter confirm esc cancel vX.Y.Z" +`; diff --git a/__tests__/e2e/chat-launch.e2e.ts b/__tests__/e2e/chat-launch.e2e.ts index ade7807..b9df036 100644 --- a/__tests__/e2e/chat-launch.e2e.ts +++ b/__tests__/e2e/chat-launch.e2e.ts @@ -15,7 +15,7 @@ describe('when the user starts chat after onboarding', () => { await navigateToOnboarding(session); await session.sendKey(ENTER); - await session.waitForText('onboarding complete', { timeout: 60_000 }); + await session.waitForText('onboarding complete', { timeout: 30_000 }); await session.waitForText('Continue work with Claude Code'); await session.sendKey(ENTER); @@ -65,7 +65,7 @@ describe('when the user starts chat after onboarding', () => { await session.waitForText('Start onboarding?'); await session.waitForText('Skip for now'); await session.sendKey(ENTER); - await session.waitForText('onboarding complete', { timeout: 60_000 }); + await session.waitForText('onboarding complete', { timeout: 30_000 }); // Chat await session.waitForText('Continue work with Claude Code'); diff --git a/__tests__/e2e/empty-project.e2e.ts b/__tests__/e2e/empty-project.e2e.ts index 303b513..27fc03b 100644 --- a/__tests__/e2e/empty-project.e2e.ts +++ b/__tests__/e2e/empty-project.e2e.ts @@ -8,6 +8,7 @@ describe('when the project is empty', () => { await session.waitForText('Could not auto-detect'); await session.waitForText('Please, select your framework'); await session.waitForText('Select framework'); + expect(session.snapshot()).toMatchSnapshot('welcome-no-framework'); }); it('shows "Start setup" after selecting a framework', async () => { @@ -25,5 +26,6 @@ describe('when the project is empty', () => { // Back to Welcome — now with framework set await session.waitForText('Start setup'); await session.waitForText('Ready to get started?'); + expect(session.snapshot()).toMatchSnapshot('welcome-after-framework-selected'); }); }); diff --git a/__tests__/e2e/happy-path.e2e.ts b/__tests__/e2e/happy-path.e2e.ts index 202e147..ed46b47 100644 --- a/__tests__/e2e/happy-path.e2e.ts +++ b/__tests__/e2e/happy-path.e2e.ts @@ -7,11 +7,15 @@ describe('happy-path flow', () => { // Welcome await session.waitForText('Confidence Quickstart'); await session.waitForText('Start setup'); + expect(session.snapshot()).toMatchSnapshot('welcome'); + session.checkpoint(); await session.sendKey(ENTER); // SystemCheck await session.waitForText('System Check'); await session.waitForText('All checks passed'); + expect(session.snapshot()).toMatchSnapshot('system-check'); + session.checkpoint(); // Authenticate await session.waitForText('Sign in to Confidence'); @@ -20,27 +24,36 @@ describe('happy-path flow', () => { await session.waitForText('Waiting for browser'); await simulateAuthCallback(); await session.waitForText('Authenticated'); + expect(session.snapshot()).toMatchSnapshot('authenticate'); + session.checkpoint(); // InstallPlugins await session.waitForText('Teach your AI'); await session.waitForText('Which agent tool are you using?'); + expect(session.snapshot()).toMatchSnapshot('install-plugins'); + session.checkpoint(); await session.sendKey(ENTER); // ConnectTools await session.waitForText('Connect your AI to Confidence'); await session.waitForText('Connect Confidence tools?'); + expect(session.snapshot()).toMatchSnapshot('connect-tools'); + session.checkpoint(); await session.sendKey(ENTER); await session.waitForText('Connected successfully'); // OnboardProject await session.waitForText('Start onboarding?'); + expect(session.snapshot()).toMatchSnapshot('onboard-project'); + session.checkpoint(); await session.sendKey(ENTER); await session.waitForText('Installing @spotify-confidence/sdk'); - await session.waitForText('onboarding complete', { timeout: 60_000 }); + await session.waitForText('onboarding complete', { timeout: 30_000 }); // Done await session.waitForText('Confidence is ready'); await session.waitForText("What's next?"); + expect(session.snapshot()).toMatchSnapshot('done'); await session.sendKey(ARROW_DOWN); await session.sendKey(ENTER); diff --git a/__tests__/e2e/helpers/index.ts b/__tests__/e2e/helpers/index.ts index dc0b39a..5169830 100644 --- a/__tests__/e2e/helpers/index.ts +++ b/__tests__/e2e/helpers/index.ts @@ -13,7 +13,7 @@ export { ARROW_DOWN, ARROW_UP, ENTER, ESCAPE } from './keys.js'; export { AUTH_CALLBACK_PORT } from './env.js'; export { CHAT_PROMPT_FILE, ONBOARDING_INVOCATION_FILE } from './mock-binaries.js'; -const DEFAULT_TIMEOUT = 30_000; +const DEFAULT_TIMEOUT = 15_000; type ProjectType = 'react' | 'empty'; @@ -31,7 +31,7 @@ export function createSession({ systemPath?: string; } = {}): TerminalSession { const mockBinDir = process.env.E2E_MOCK_BIN_DIR!; - const projectDir = mkdtempSync(join(tmpdir(), 'e2e-project-')); + const projectDir = mkdtempSync('/tmp/e2e-project-'); if (project === 'react') { writeFileSync( diff --git a/__tests__/e2e/helpers/pty.ts b/__tests__/e2e/helpers/pty.ts index b36ee6f..013ab93 100644 --- a/__tests__/e2e/helpers/pty.ts +++ b/__tests__/e2e/helpers/pty.ts @@ -4,12 +4,13 @@ import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { stripAnsi } from './strip-ansi.js'; +import { renderScreen, normalizeSnapshot } from './screen-buffer.js'; import { E2E_BASE_ENV } from './env.js'; const CLI_PATH = resolve(import.meta.dirname, '../../../dist/bin/cli.js'); const DEFAULT_COLS = 100; const DEFAULT_ROWS = 40; -const DEFAULT_TIMEOUT = 30_000; +const DEFAULT_TIMEOUT = 15_000; type SessionOptions = { args?: string[]; @@ -29,15 +30,21 @@ export class TerminalSession { private cachedScreen = ''; private cachedRawLength = 0; private markPosition = 0; + private rawMarkPosition = 0; private exitCode: number | null = null; private exitPromise: Promise; private tempDirs: string[] = []; readonly cwd: string; + readonly cols: number; + readonly rows: number; constructor(options: SessionOptions = {}) { const { args = ['--debug'], env = {}, cols = DEFAULT_COLS, rows = DEFAULT_ROWS, cwd } = options; + this.cols = cols; + this.rows = rows; + const isolatedTmpDir = env.TMPDIR ?? mkdtempSync(join(tmpdir(), 'e2e-')); this.cwd = cwd ?? process.cwd(); this.tempDirs.push(isolatedTmpDir); @@ -51,6 +58,7 @@ export class TerminalSession { ...process.env, ...E2E_BASE_ENV, ...env, + HOME: isolatedTmpDir, TMPDIR: isolatedTmpDir, }, }); @@ -85,6 +93,13 @@ export class TerminalSession { checkpoint(): void { this.markPosition = this.screen.length; + this.rawMarkPosition = this.rawOutput.length; + } + + snapshot(): string { + const raw = this.rawOutput.slice(this.rawMarkPosition); + const rendered = renderScreen(raw, this.cols, this.rows); + return normalizeSnapshot(rendered, this.cwd); } send(data: string): void { diff --git a/__tests__/e2e/helpers/screen-buffer.ts b/__tests__/e2e/helpers/screen-buffer.ts new file mode 100644 index 0000000..8cc7e11 --- /dev/null +++ b/__tests__/e2e/helpers/screen-buffer.ts @@ -0,0 +1,201 @@ +class ScreenBuffer { + private cells: string[][]; + private row = 0; + private col = 0; + + constructor( + private rows: number, + private cols: number, + ) { + this.cells = Array.from({ length: rows }, () => Array(cols).fill(' ') as string[]); + } + + write(data: string): void { + let i = 0; + while (i < data.length) { + const ch = data[i]!; + + if (ch === '\x1B') { + i = this.parseEscape(data, i); + } else if (ch === '\n') { + this.lineFeed(); + i++; + } else if (ch === '\r') { + this.col = 0; + i++; + } else if (ch === '\t') { + this.col = Math.min(this.col + (8 - (this.col % 8)), this.cols - 1); + i++; + } else if (ch === '\x08') { + this.col = Math.max(0, this.col - 1); + i++; + } else if (ch < '\x20') { + i++; + } else { + if (this.col < this.cols) { + this.cells[this.row]![this.col] = ch; + this.col++; + } + i++; + } + } + } + + toText(): string { + const lines: string[] = []; + + for (let r = 0; r < this.rows; r++) { + lines.push(this.cells[r]!.join('').trimEnd()); + } + + while (lines.length > 0 && lines[lines.length - 1] === '') { + lines.pop(); + } + + return lines.join('\n'); + } + + private lineFeed(): void { + this.col = 0; + if (this.row < this.rows - 1) { + this.row++; + } else { + this.scrollUp(); + } + } + + private scrollUp(): void { + this.cells.shift(); + this.cells.push(Array(this.cols).fill(' ') as string[]); + } + + private parseEscape(data: string, start: number): number { + if (start + 1 >= data.length) return start + 1; + + const next = data[start + 1]; + + if (next === '[') { + return this.parseCSI(data, start + 2); + } + + if (next === ']') { + let j = start + 2; + while (j < data.length) { + if (data[j] === '\x07') return j + 1; + if (data[j] === '\x1B' && j + 1 < data.length && data[j + 1] === '\\') return j + 2; + j++; + } + return j; + } + + return start + 2; + } + + private parseCSI(data: string, start: number): number { + let j = start; + + if ( + j < data.length && + (data[j] === '?' || data[j] === '>' || data[j] === '<' || data[j] === '=') + ) { + j++; + } + + while (j < data.length) { + const ch = data[j]!; + if ((ch >= '0' && ch <= '9') || ch === ';') { + j++; + } else if (ch >= ' ' && ch <= '/') { + j++; + } else if (ch >= '@' && ch <= '~') { + this.handleCSI(data.slice(start, j), ch); + return j + 1; + } else { + return j; + } + } + + return j; + } + + private handleCSI(paramStr: string, cmd: string): void { + const clean = paramStr.replace(/^[?>=<]/, ''); + if (clean !== paramStr) return; + + const parts = clean === '' ? [0] : clean.split(';').map((p) => parseInt(p, 10) || 0); + + switch (cmd) { + case 'A': + this.row = Math.max(0, this.row - (parts[0] || 1)); + break; + case 'B': + this.row = Math.min(this.rows - 1, this.row + (parts[0] || 1)); + break; + case 'C': + this.col = Math.min(this.cols - 1, this.col + (parts[0] || 1)); + break; + case 'D': + this.col = Math.max(0, this.col - (parts[0] || 1)); + break; + case 'G': + this.col = Math.max(0, Math.min(this.cols - 1, (parts[0] || 1) - 1)); + break; + case 'H': + case 'f': + this.row = Math.max(0, Math.min(this.rows - 1, (parts[0] || 1) - 1)); + this.col = Math.max(0, Math.min(this.cols - 1, (parts[1] || 1) - 1)); + break; + case 'J': + this.eraseDisplay(parts[0] || 0); + break; + case 'K': + this.eraseInLine(parts[0] || 0); + break; + } + } + + private eraseDisplay(mode: number): void { + if (mode === 2 || mode === 3) { + for (let r = 0; r < this.rows; r++) { + this.cells[r] = Array(this.cols).fill(' ') as string[]; + } + this.row = 0; + this.col = 0; + } else if (mode === 0) { + this.cells[this.row]!.fill(' ', this.col); + for (let r = this.row + 1; r < this.rows; r++) { + this.cells[r] = Array(this.cols).fill(' ') as string[]; + } + } else if (mode === 1) { + for (let r = 0; r < this.row; r++) { + this.cells[r] = Array(this.cols).fill(' ') as string[]; + } + this.cells[this.row]!.fill(' ', 0, this.col + 1); + } + } + + private eraseInLine(mode: number): void { + const row = this.cells[this.row]!; + if (mode === 0) { + row.fill(' ', this.col); + } else if (mode === 1) { + row.fill(' ', 0, this.col + 1); + } else if (mode === 2) { + row.fill(' '); + } + } +} + +export function renderScreen(raw: string, cols: number, rows: number): string { + const buf = new ScreenBuffer(rows, cols); + buf.write(raw); + return buf.toText(); +} + +export function normalizeSnapshot(text: string, cwd: string): string { + let result = text.replaceAll(cwd, ''); + result = result.replace(/v\d+\.\d+\.\d+(-[\w.]+)?/g, (m) => 'vX.Y.Z'.padEnd(m.length)); + result = result.replace(/(?<=[\s])\d+\.\d+\.\d+/gm, (m) => 'X.Y.Z'.padEnd(m.length)); + result = result.replace(/\n{4,}/g, '\n\n\n'); + return result; +} diff --git a/__tests__/e2e/skip-onboarding.e2e.ts b/__tests__/e2e/skip-onboarding.e2e.ts index 0f41122..a506b23 100644 --- a/__tests__/e2e/skip-onboarding.e2e.ts +++ b/__tests__/e2e/skip-onboarding.e2e.ts @@ -17,6 +17,7 @@ describe('when the user skips onboarding', () => { // Docs and dashboard links still appear await session.waitForText('Documentation:'); await session.waitForText('Dashboard:'); + expect(session.snapshot()).toMatchSnapshot('done-skipped'); }); it('offers "Continue work with Claude Code" since plugin was installed', async () => { @@ -28,5 +29,6 @@ describe('when the user skips onboarding', () => { await session.sendKey(ENTER); await session.waitForText('Continue work with Claude Code'); + expect(session.snapshot()).toMatchSnapshot('done-skipped-with-ide'); }); }); diff --git a/__tests__/e2e/skip-plugins.e2e.ts b/__tests__/e2e/skip-plugins.e2e.ts index f6ffa43..ad26041 100644 --- a/__tests__/e2e/skip-plugins.e2e.ts +++ b/__tests__/e2e/skip-plugins.e2e.ts @@ -18,12 +18,13 @@ describe('when the user skips installing AI plugin', () => { // OnboardProject await session.waitForText('Start onboarding?'); await session.sendKey(ENTER); - await session.waitForText('onboarding complete', { timeout: 60_000 }); + await session.waitForText('onboarding complete', { timeout: 30_000 }); // Done — no IDE set, so only "Exit" option (no "Continue work with") await session.waitForText('Confidence is ready'); await session.waitForText("What's next?"); await session.waitForText('Exit'); + expect(session.snapshot()).toMatchSnapshot('done-no-ide'); await session.sendKey(ENTER); const exitCode = await session.waitForExit(); @@ -46,10 +47,11 @@ describe('when the user skips installing AI plugin', () => { await session.waitForText('Start onboarding?'); await session.sendKey(ENTER); - await session.waitForText('onboarding complete', { timeout: 60_000 }); + await session.waitForText('onboarding complete', { timeout: 30_000 }); // Done — onboarding ran so report file and code changes appear await session.waitForText('What we have set up'); await session.waitForText('CONFIDENCE_QUICKSTART.md'); + expect(session.snapshot()).toMatchSnapshot('done-no-ide-with-report'); }); }); diff --git a/__tests__/e2e/skip-tools.e2e.ts b/__tests__/e2e/skip-tools.e2e.ts index e2f2357..efbb020 100644 --- a/__tests__/e2e/skip-tools.e2e.ts +++ b/__tests__/e2e/skip-tools.e2e.ts @@ -16,8 +16,9 @@ describe('when the user skips connecting tools', () => { // Still proceeds to OnboardProject await session.waitForText('Start onboarding?'); await session.sendKey(ENTER); - await session.waitForText('onboarding complete', { timeout: 60_000 }); + await session.waitForText('onboarding complete', { timeout: 30_000 }); await session.waitForText('Confidence is ready'); + expect(session.snapshot()).toMatchSnapshot('done-tools-skipped'); }); }); diff --git a/__tests__/e2e/stale-auth.e2e.ts b/__tests__/e2e/stale-auth.e2e.ts index f4bc4c5..11c9811 100644 --- a/__tests__/e2e/stale-auth.e2e.ts +++ b/__tests__/e2e/stale-auth.e2e.ts @@ -18,6 +18,7 @@ describe('when auth token is stale', () => { // Authenticate — expired token should be rejected, showing choose-action await session.waitForText('Sign in to a Confidence account'); + expect(session.snapshot()).toMatchSnapshot('auth-expired'); }); it('shows "Use existing account" when the token is still valid', async () => { @@ -27,6 +28,7 @@ describe('when auth token is stale', () => { // Authenticate — valid token found await session.waitForText('Use existing account'); + expect(session.snapshot()).toMatchSnapshot('auth-existing'); }); it('allows re-authentication after expired token and proceeds normally', async () => { @@ -43,6 +45,7 @@ describe('when auth token is stale', () => { // Continues to InstallPlugins await session.waitForText('Which agent tool are you using?'); + expect(session.snapshot()).toMatchSnapshot('auth-re-authenticated'); }); it('treats a near-expiry token as valid', async () => { @@ -53,6 +56,7 @@ describe('when auth token is stale', () => { // Token expires in 5s — no buffer in validateToken, so still accepted await session.waitForText('Use existing account'); + expect(session.snapshot()).toMatchSnapshot('auth-near-expiry'); }); it('treats a malformed token as invalid and prompts sign-in', async () => { @@ -62,5 +66,6 @@ describe('when auth token is stale', () => { // Malformed JWT cannot be decoded — treated as invalid await session.waitForText('Sign in to a Confidence account'); + expect(session.snapshot()).toMatchSnapshot('auth-malformed'); }); }); diff --git a/__tests__/e2e/stale-mcp-auth.e2e.ts b/__tests__/e2e/stale-mcp-auth.e2e.ts index ae68c0a..67c3714 100644 --- a/__tests__/e2e/stale-mcp-auth.e2e.ts +++ b/__tests__/e2e/stale-mcp-auth.e2e.ts @@ -55,10 +55,12 @@ describe('when MCP config has expired auth tokens', () => { await session.waitForText('auth expired'); await session.waitForText('Reconnect to refresh credentials?'); await session.waitForText('Reconnect all tools'); + expect(session.snapshot()).toMatchSnapshot('connect-tools-expired'); // Select "Reconnect all tools" await session.sendKey(ENTER); await session.waitForText('Connected successfully'); + expect(session.snapshot()).toMatchSnapshot('connect-tools-reconnected'); }); it('allows skipping when auth is expired', async () => { @@ -82,5 +84,6 @@ describe('when MCP config has expired auth tokens', () => { // Proceeds to OnboardProject await session.waitForText('Start onboarding?'); + expect(session.snapshot()).toMatchSnapshot('mcp-auth-skipped'); }); }); diff --git a/__tests__/e2e/system-check-failure.e2e.ts b/__tests__/e2e/system-check-failure.e2e.ts index 6501cbd..e076794 100644 --- a/__tests__/e2e/system-check-failure.e2e.ts +++ b/__tests__/e2e/system-check-failure.e2e.ts @@ -19,6 +19,7 @@ describe('when system check fails', () => { await session.waitForText('Some required tools are missing'); await session.waitForText('Retry'); await session.waitForText('Quit'); + expect(session.snapshot()).toMatchSnapshot('system-check-failure'); }); it('shows error when node is not on PATH', async () => { @@ -31,6 +32,7 @@ describe('when system check fails', () => { // SystemCheck — node not found on PATH await session.waitForText('System Check'); await session.waitForText('Some required tools are missing'); + expect(session.snapshot()).toMatchSnapshot('system-check-node-missing'); }); it('exits with code 1 when user selects Quit', async () => { diff --git a/__tests__/e2e/welcome-navigation.e2e.ts b/__tests__/e2e/welcome-navigation.e2e.ts index fbe05af..fac6754 100644 --- a/__tests__/e2e/welcome-navigation.e2e.ts +++ b/__tests__/e2e/welcome-navigation.e2e.ts @@ -11,6 +11,7 @@ describe('welcome screen navigation', () => { await session.sendKey(ENTER); await session.waitForText('About Confidence'); + expect(session.snapshot()).toMatchSnapshot('about'); }); it('navigates to SelectFramework screen', async () => { @@ -23,6 +24,7 @@ describe('welcome screen navigation', () => { await session.sendKey(ENTER); await session.waitForText('Select Framework'); + expect(session.snapshot()).toMatchSnapshot('select-framework'); }); it('exits cleanly on Quit', async () => { @@ -33,6 +35,7 @@ describe('welcome screen navigation', () => { await session.sendKeyRepeat(ARROW_DOWN, 3); await session.sendKey(ENTER); + expect(session.snapshot()).toMatchSnapshot('welcome-quit-selected'); const exitCode = await session.waitForExit(); expect(exitCode).toBe(0); diff --git a/__tests__/ui/helpers/spawn.ts b/__tests__/ui/helpers/spawn.ts index 17eea0a..dc1b67f 100644 --- a/__tests__/ui/helpers/spawn.ts +++ b/__tests__/ui/helpers/spawn.ts @@ -25,7 +25,7 @@ export function createFakeChild( stderr, stdin: null, pid: 1234, - kill: vi.fn(), + kill: vi.fn() as (...args: unknown[]) => boolean, }); setTimeout(() => { diff --git a/package.json b/package.json index 27d2b4c..c93c05a 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "try": "NODE_ENV=development node --import tsx bin/cli.ts", "test": "vitest run", "test:e2e": "pnpm build && vitest run --config vitest.config.e2e.ts", + "test:e2e:update": "pnpm test:e2e -- --update", "test:watch": "vitest", "lint": "pnpm lint:src && pnpm lint:scripts && pnpm format:check", "lint:src": "eslint .", @@ -56,7 +57,7 @@ "tsx": "^4.19.0", "typescript": "^5.8.3", "typescript-eslint": "^8.33.1", - "vitest": "^3.2.1" + "vitest": "^4.1.10" }, "engines": { "node": ">=24.0.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f85bd72..5c10fe3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,8 +92,8 @@ importers: specifier: ^8.33.1 version: 8.62.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) vitest: - specifier: ^3.2.1 - version: 3.2.6(@types/node@22.20.0)(jiti@2.7.0)(msw@2.14.6(@types/node@22.20.0)(typescript@5.9.3))(tsx@4.22.4)(yaml@2.9.0) + specifier: ^4.1.10 + version: 4.1.10(@types/node@22.20.0)(msw@2.14.6(@types/node@22.20.0)(typescript@5.9.3))(vite@7.3.6(@types/node@22.20.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) packages: @@ -773,6 +773,9 @@ packages: cpu: [x64] os: [win32] + '@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'} @@ -890,34 +893,34 @@ packages: resolution: {integrity: sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/expect@3.2.6': - resolution: {integrity: sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ==} + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} - '@vitest/mocker@3.2.6': - resolution: {integrity: sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw==} + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@3.2.6': - resolution: {integrity: sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA==} + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} - '@vitest/runner@3.2.6': - resolution: {integrity: sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q==} + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} - '@vitest/snapshot@3.2.6': - resolution: {integrity: sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw==} + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} - '@vitest/spy@3.2.6': - resolution: {integrity: sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg==} + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} - '@vitest/utils@3.2.6': - resolution: {integrity: sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg==} + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} @@ -1020,8 +1023,8 @@ packages: caniuse-lite@1.0.30001803: resolution: {integrity: sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg==} - chai@5.3.3: - resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} chalk@4.1.2: @@ -1032,10 +1035,6 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - check-error@2.1.3: - resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} - engines: {node: '>= 16'} - chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -1143,10 +1142,6 @@ packages: supports-color: optional: true - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1205,8 +1200,8 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} es-toolkit@1.49.0: resolution: {integrity: sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==} @@ -1514,9 +1509,6 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@4.3.0: resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} hasBin: true @@ -1608,9 +1600,6 @@ packages: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} - loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -1682,6 +1671,10 @@ packages: resolution: {integrity: sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==} engines: {node: '>=18'} + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -1743,10 +1736,6 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1919,8 +1908,8 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} @@ -1953,9 +1942,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strip-literal@3.1.0: - resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1978,9 +1964,6 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyexec@1.2.4: resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} engines: {node: '>=18'} @@ -1989,16 +1972,8 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - - tinyspy@4.0.4: - resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} tldts-core@7.4.6: @@ -2093,11 +2068,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - vite@7.3.6: resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2138,26 +2108,39 @@ packages: yaml: optional: true - vitest@3.2.6: - resolution: {integrity: sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.6 - '@vitest/ui': 3.2.6 + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 happy-dom: '*' jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true - '@types/debug': + '@opentelemetry/api': optional: true '@types/node': optional: true - '@vitest/browser': + '@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 @@ -2841,6 +2824,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.62.2': optional: true + '@standard-schema/spec@1.1.0': {} + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.7 @@ -2994,48 +2979,47 @@ snapshots: '@typescript-eslint/types': 8.62.1 eslint-visitor-keys: 5.0.1 - '@vitest/expect@3.2.6': + '@vitest/expect@4.1.10': dependencies: + '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 3.2.6 - '@vitest/utils': 3.2.6 - chai: 5.3.3 - tinyrainbow: 2.0.0 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + chai: 6.2.2 + tinyrainbow: 3.1.0 - '@vitest/mocker@3.2.6(msw@2.14.6(@types/node@22.20.0)(typescript@5.9.3))(vite@7.3.6(@types/node@22.20.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))': + '@vitest/mocker@4.1.10(msw@2.14.6(@types/node@22.20.0)(typescript@5.9.3))(vite@7.3.6(@types/node@22.20.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: - '@vitest/spy': 3.2.6 + '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.14.6(@types/node@22.20.0)(typescript@5.9.3) vite: 7.3.6(@types/node@22.20.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0) - '@vitest/pretty-format@3.2.6': + '@vitest/pretty-format@4.1.10': dependencies: - tinyrainbow: 2.0.0 + tinyrainbow: 3.1.0 - '@vitest/runner@3.2.6': + '@vitest/runner@4.1.10': dependencies: - '@vitest/utils': 3.2.6 + '@vitest/utils': 4.1.10 pathe: 2.0.3 - strip-literal: 3.1.0 - '@vitest/snapshot@3.2.6': + '@vitest/snapshot@4.1.10': dependencies: - '@vitest/pretty-format': 3.2.6 + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@3.2.6': - dependencies: - tinyspy: 4.0.4 + '@vitest/spy@4.1.10': {} - '@vitest/utils@3.2.6': + '@vitest/utils@4.1.10': dependencies: - '@vitest/pretty-format': 3.2.6 - loupe: 3.2.1 - tinyrainbow: 2.0.0 + '@vitest/pretty-format': 4.1.10 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 JSONStream@1.3.5: dependencies: @@ -3121,13 +3105,7 @@ snapshots: caniuse-lite@1.0.30001803: {} - chai@5.3.3: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.3 - deep-eql: 5.0.2 - loupe: 3.2.1 - pathval: 2.0.1 + chai@6.2.2: {} chalk@4.1.2: dependencies: @@ -3136,8 +3114,6 @@ snapshots: chalk@5.6.2: {} - check-error@2.1.3: {} - chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -3233,8 +3209,6 @@ snapshots: dependencies: ms: 2.1.3 - deep-eql@5.0.2: {} - deep-is@0.1.4: {} deepmerge@4.3.1: {} @@ -3269,7 +3243,7 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-module-lexer@1.7.0: {} + es-module-lexer@2.3.1: {} es-toolkit@1.49.0: {} @@ -3586,8 +3560,6 @@ snapshots: js-tokens@4.0.0: {} - js-tokens@9.0.1: {} - js-yaml@4.3.0: dependencies: argparse: 2.0.1 @@ -3670,8 +3642,6 @@ snapshots: strip-ansi: 7.2.0 wrap-ansi: 9.0.2 - loupe@3.2.1: {} - lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -3741,6 +3711,8 @@ snapshots: node-releases@2.0.50: {} + obug@2.1.4: {} + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -3799,8 +3771,6 @@ snapshots: pathe@2.0.3: {} - pathval@2.0.1: {} - picocolors@1.1.1: {} picomatch@4.0.5: {} @@ -3976,7 +3946,7 @@ snapshots: statuses@2.0.2: {} - std-env@3.10.0: {} + std-env@4.2.0: {} strict-event-emitter@0.5.1: {} @@ -4009,10 +3979,6 @@ snapshots: strip-json-comments@3.1.1: {} - strip-literal@3.1.0: - dependencies: - js-tokens: 9.0.1 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -4027,8 +3993,6 @@ snapshots: tinybench@2.9.0: {} - tinyexec@0.3.2: {} - tinyexec@1.2.4: {} tinyglobby@0.2.17: @@ -4036,11 +4000,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.5) picomatch: 4.0.5 - tinypool@1.1.1: {} - - tinyrainbow@2.0.0: {} - - tinyspy@4.0.4: {} + tinyrainbow@3.1.0: {} tldts-core@7.4.6: {} @@ -4138,27 +4098,6 @@ snapshots: dependencies: punycode: 2.3.1 - vite-node@3.2.4(@types/node@22.20.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.3.6(@types/node@22.20.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vite@7.3.6(@types/node@22.20.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0): dependencies: esbuild: 0.28.1 @@ -4174,46 +4113,32 @@ snapshots: tsx: 4.22.4 yaml: 2.9.0 - vitest@3.2.6(@types/node@22.20.0)(jiti@2.7.0)(msw@2.14.6(@types/node@22.20.0)(typescript@5.9.3))(tsx@4.22.4)(yaml@2.9.0): + vitest@4.1.10(@types/node@22.20.0)(msw@2.14.6(@types/node@22.20.0)(typescript@5.9.3))(vite@7.3.6(@types/node@22.20.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)): dependencies: - '@types/chai': 5.2.3 - '@vitest/expect': 3.2.6 - '@vitest/mocker': 3.2.6(msw@2.14.6(@types/node@22.20.0)(typescript@5.9.3))(vite@7.3.6(@types/node@22.20.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) - '@vitest/pretty-format': 3.2.6 - '@vitest/runner': 3.2.6 - '@vitest/snapshot': 3.2.6 - '@vitest/spy': 3.2.6 - '@vitest/utils': 3.2.6 - chai: 5.3.3 - debug: 4.4.3 + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(msw@2.14.6(@types/node@22.20.0)(typescript@5.9.3))(vite@7.3.6(@types/node@22.20.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 expect-type: 1.4.0 magic-string: 0.30.21 + obug: 2.1.4 pathe: 2.0.3 picomatch: 4.0.5 - std-env: 3.10.0 + std-env: 4.2.0 tinybench: 2.9.0 - tinyexec: 0.3.2 + tinyexec: 1.2.4 tinyglobby: 0.2.17 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 + tinyrainbow: 3.1.0 vite: 7.3.6(@types/node@22.20.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0) - vite-node: 3.2.4(@types/node@22.20.0)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.20.0 transitivePeerDependencies: - - jiti - - less - - lightningcss - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml which@2.0.2: dependencies: diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts index b0bbc2e..0481f0b 100644 --- a/vitest.config.e2e.ts +++ b/vitest.config.e2e.ts @@ -17,9 +17,13 @@ export default defineConfig({ include: ['__tests__/e2e/**/*.e2e.ts'], globalSetup: ['__tests__/e2e/global-setup.ts'], setupFiles: [], - testTimeout: 120_000, - hookTimeout: 120_000, + testTimeout: 60_000, + hookTimeout: 60_000, maxWorkers: 1, pool: 'forks', + retry: { + count: 2, + condition: /timed out/i, + }, }, }); diff --git a/vitest.config.ts b/vitest.config.ts index 3fa0744..8ffe454 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -22,11 +22,7 @@ export default defineConfig({ clearMocks: true, maxWorkers: isCI ? 1 : 4, pool: 'forks', - poolOptions: { - forks: { - execArgv: ['--import', './__tests__/msw/localstorage-fake.mjs'], - }, - }, + execArgv: ['--import', './__tests__/msw/localstorage-fake.mjs'], }, esbuild: { jsx: 'automatic',