diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7eff1b9fe7..365717755e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,12 +18,12 @@ jobs: strategy: matrix: include: - - node-version: 18 + # Vitest 4 requires Node 20+, so tests run on 20/22/24. The published + # Svelte package still supports Node >=18 (see packages/svelte/package.json). + - node-version: 20 os: windows-latest - - node-version: 18 + - node-version: 20 os: macOS-latest - - node-version: 18 - os: ubuntu-latest - node-version: 20 os: ubuntu-latest - node-version: 22 diff --git a/package.json b/package.json index f741ec3e1f..0d2fbc7fd6 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "@svitejs/changesets-changelog-github-compact": "^1.1.0", "@types/node": "^20.11.5", "@types/picomatch": "^4.0.2", - "@vitest/coverage-v8": "^2.1.9", + "@vitest/coverage-v8": "^4.1.7", "eslint": "^10.0.0", "eslint-plugin-lube": "^0.5.1", "eslint-plugin-svelte": "^3.15.0", @@ -44,6 +44,6 @@ "typescript": "^5.5.4", "typescript-eslint": "^8.56.0", "v8-natives": "^1.2.5", - "vitest": "^2.1.9" + "vitest": "^4.1.7" } } diff --git a/packages/svelte/package.json b/packages/svelte/package.json index ff2c0c4c3e..b7e370dc7d 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -166,7 +166,7 @@ "source-map": "^0.7.4", "tinyglobby": "^0.2.12", "typescript": "^5.5.4", - "vitest": "^2.1.9", + "vitest": "^4.1.7", "web-features": "^3.29.0" }, "dependencies": { diff --git a/packages/svelte/tests/hydration/test.ts b/packages/svelte/tests/hydration/test.ts index 16e4070303..d2bf6fde34 100644 --- a/packages/svelte/tests/hydration/test.ts +++ b/packages/svelte/tests/hydration/test.ts @@ -25,7 +25,21 @@ interface HydrationTest extends BaseTest { expect_hydration_error?: true; snapshot?: (target: HTMLElement) => any; test?: ( - assert: typeof import('vitest').assert & { + // `_config.js` test callbacks rely on inferred parameter types, which + // TS treats as non-explicit and rejects for chai 5's assertion-function + // signatures (TS2775). Override the assertion methods we actually use + // with non-assertion equivalents. + assert: Omit< + typeof import('vitest').assert, + 'ok' | 'isOk' | 'isTrue' | 'isFalse' | 'exists' | 'notExists' | 'instanceOf' + > & { + ok(value: unknown, message?: string): void; + isOk(value: unknown, message?: string): void; + isTrue(value: unknown, message?: string): void; + isFalse(value: unknown, message?: string): void; + exists(value: unknown, message?: string): void; + notExists(value: unknown, message?: string): void; + instanceOf(value: unknown, type: Function, message?: string): void; htmlEqual(a: string, b: string, description?: string): void; }, target: HTMLElement, @@ -152,7 +166,6 @@ const { test, run } = suite(async (config, cwd) => { if (config.test) { await config.test( - // @ts-expect-error TS doesn't get it { ...assert, htmlEqual: assert_html_equal diff --git a/packages/svelte/tests/runtime-browser/test.ts b/packages/svelte/tests/runtime-browser/test.ts index 54cdc0f8be..22e8ad6489 100644 --- a/packages/svelte/tests/runtime-browser/test.ts +++ b/packages/svelte/tests/runtime-browser/test.ts @@ -42,9 +42,9 @@ const { run: run_browser_tests } = suite_with_variants< describe.concurrent( 'runtime-browser', - () => run_browser_tests(__dirname), // Browser tests are brittle and slow on CI - { timeout: 20000, retry: process.env.CI ? 1 : 0 } + { timeout: 20000, retry: process.env.CI ? 1 : 0 }, + () => run_browser_tests(__dirname) ); const { run: run_ce_tests } = suite>( @@ -55,9 +55,9 @@ const { run: run_ce_tests } = suite>( describe.concurrent( 'custom-elements', - () => run_ce_tests(__dirname, 'custom-elements-samples'), // Browser tests are brittle and slow on CI - { timeout: 20000, retry: process.env.CI ? 1 : 0 } + { timeout: 20000, retry: process.env.CI ? 1 : 0 }, + () => run_ce_tests(__dirname, 'custom-elements-samples') ); async function run_test( diff --git a/packages/svelte/tests/runtime-legacy/shared.ts b/packages/svelte/tests/runtime-legacy/shared.ts index 6f30fb5d98..5c2d0dfe1c 100644 --- a/packages/svelte/tests/runtime-legacy/shared.ts +++ b/packages/svelte/tests/runtime-legacy/shared.ts @@ -15,19 +15,35 @@ import { clear } from '../../src/internal/client/reactivity/batch.js'; import { hydrating } from '../../src/internal/client/dom/hydration.js'; import { ssr_context } from '../../src/internal/server/context.js'; -type Assert = typeof import('vitest').assert & { - htmlEqual(a: string, b: string, description?: string): void; - htmlEqualWithOptions( - a: string, - b: string, - opts: { - preserveComments: boolean; - withoutNormalizeHtml: boolean; - }, - description?: string - ): void; +// `_config.js` files call `assert.ok` etc. with `assert` typed via parameter +// inference, which TypeScript treats as non-explicit. chai 5 (pulled in by +// vitest 4) declares these as assertion functions (`asserts value`), so TS2775 +// fires on every call. Override the affected methods with non-assertion +// signatures — the runtime behavior is unchanged. +type NonAssertingMethods = { + ok(value: unknown, message?: string): void; + isOk(value: unknown, message?: string): void; + isTrue(value: unknown, message?: string): void; + isFalse(value: unknown, message?: string): void; + exists(value: unknown, message?: string): void; + notExists(value: unknown, message?: string): void; + instanceOf(value: unknown, type: Function, message?: string): void; }; +type Assert = Omit & + NonAssertingMethods & { + htmlEqual(a: string, b: string, description?: string): void; + htmlEqualWithOptions( + a: string, + b: string, + opts: { + preserveComments: boolean; + withoutNormalizeHtml: boolean; + }, + description?: string + ): void; + }; + // TODO remove this shim when we can // @ts-expect-error Promise.withResolvers = () => { @@ -75,6 +91,7 @@ export interface RuntimeTest = Record void; }; + snapshot: any; target: HTMLElement; window: Window & { Event: typeof Event; @@ -126,6 +143,16 @@ const listeners = process.rawListeners('unhandledRejection'); beforeAll(() => { // @ts-expect-error TODO huh? process.prependListener('unhandledRejection', unhandled_rejection_handler); + + // Route inline-`