From b2005b75128c3d45dad96b860558ac21cf85bf66 Mon Sep 17 00:00:00 2001 From: Michael Cousins Date: Wed, 7 May 2025 11:35:21 -0400 Subject: [PATCH 1/5] test: replace deprecated `toMatchTypeOf` (#434) --- package.json | 1 + src/__tests__/render-runes.test-d.ts | 4 ++-- src/__tests__/render-utilities.test-d.ts | 14 +++++++------- src/__tests__/render.test-d.ts | 2 +- tsconfig.json | 3 ++- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 77bba08..8bab118 100644 --- a/package.json +++ b/package.json @@ -127,6 +127,7 @@ "svelte-check": "^3.0.0 || ^4.0.4", "svelte-jester": "^5.0.0", "typescript": "^5.5.3", + "typescript-svelte-plugin": "^0.3.46", "vite": "^4.0.0 || ^5.3.3", "vitest": "0.x.x || ^1.0.0 || ^2.0.2" } diff --git a/src/__tests__/render-runes.test-d.ts b/src/__tests__/render-runes.test-d.ts index 48d9d27..51b7cfa 100644 --- a/src/__tests__/render-runes.test-d.ts +++ b/src/__tests__/render-runes.test-d.ts @@ -29,7 +29,7 @@ describe('types', () => { test('render result has container and component', () => { const result = subject.render(Component, { name: 'Alice', count: 42 }) - expectTypeOf(result).toMatchTypeOf<{ + expectTypeOf(result).toExtend<{ container: HTMLElement component: { hello: string } debug: (el?: HTMLElement) => void @@ -55,7 +55,7 @@ describe('legacy component types', () => { count: 42, }) - expectTypeOf(component).toMatchTypeOf<{ hello: string }>() + expectTypeOf(component).toExtend<{ hello: string }>() // @ts-expect-error: Svelte 5 mount does not return `$set` component.$on('greeting', onGreeting) diff --git a/src/__tests__/render-utilities.test-d.ts b/src/__tests__/render-utilities.test-d.ts index b45614e..d1e3869 100644 --- a/src/__tests__/render-utilities.test-d.ts +++ b/src/__tests__/render-utilities.test-d.ts @@ -8,7 +8,7 @@ describe('render query and utility types', () => { test('render result has default queries', () => { const result = subject.render(Component, { name: 'Alice' }) - expectTypeOf(result.getByRole).parameters.toMatchTypeOf< + expectTypeOf(result.getByRole).parameters.toExtend< [role: subject.ByRoleMatcher, options?: subject.ByRoleOptions] >() }) @@ -27,25 +27,25 @@ describe('render query and utility types', () => { { queries: { getByVibes } } ) - expectTypeOf(result.getByVibes).parameters.toMatchTypeOf<[vibes: string]>() + expectTypeOf(result.getByVibes).parameters.toExtend<[vibes: string]>() }) test('act is an async function', () => { - expectTypeOf(subject.act).toMatchTypeOf<() => Promise>() + expectTypeOf(subject.act).toExtend<() => Promise>() }) test('act accepts a sync function', () => { - expectTypeOf(subject.act).toMatchTypeOf<(fn: () => void) => Promise>() + expectTypeOf(subject.act).toExtend<(fn: () => void) => Promise>() }) test('act accepts an async function', () => { - expectTypeOf(subject.act).toMatchTypeOf< + expectTypeOf(subject.act).toExtend< (fn: () => Promise) => Promise >() }) test('fireEvent is an async function', () => { - expectTypeOf(subject.fireEvent).toMatchTypeOf< + expectTypeOf(subject.fireEvent).toExtend< ( element: Element | Node | Document | Window, event: Event @@ -54,7 +54,7 @@ describe('render query and utility types', () => { }) test('fireEvent[eventName] is an async function', () => { - expectTypeOf(subject.fireEvent.click).toMatchTypeOf< + expectTypeOf(subject.fireEvent.click).toExtend< ( element: Element | Node | Document | Window, // eslint-disable-next-line @typescript-eslint/no-empty-object-type diff --git a/src/__tests__/render.test-d.ts b/src/__tests__/render.test-d.ts index 08cad70..bf0a611 100644 --- a/src/__tests__/render.test-d.ts +++ b/src/__tests__/render.test-d.ts @@ -37,7 +37,7 @@ describe('types', () => { test('render result has container and component', () => { const result = subject.render(Component, { name: 'Alice', count: 42 }) - expectTypeOf(result).toMatchTypeOf<{ + expectTypeOf(result).toExtend<{ container: HTMLElement component: { hello: string } debug: (el?: HTMLElement) => void diff --git a/tsconfig.json b/tsconfig.json index f79cace..0cc0fdf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,8 @@ "noEmit": true, "skipLibCheck": true, "strict": true, - "types": ["svelte", "vite/client", "vitest", "vitest/globals"] + "types": ["svelte", "vite/client", "vitest", "vitest/globals"], + "plugins": [{ "name": "typescript-svelte-plugin" }] }, "include": ["src"] } From ea37c470b02233baca598834143c3ac27f669b9d Mon Sep 17 00:00:00 2001 From: Michael Cousins Date: Wed, 7 May 2025 12:17:32 -0400 Subject: [PATCH 2/5] test: extract `tests` directory out of `src` (#433) --- jest.config.js | 8 ++++---- package.json | 3 +-- src/__tests__/utils.js => tests/_env.js | 0 {src/__tests__ => tests}/_jest-setup.js | 0 {src/__tests__ => tests}/_jest-vitest-alias.js | 0 {src/__tests__ => tests}/_vitest-setup.js | 0 {src/__tests__ => tests}/act.test.js | 0 {src/__tests__ => tests}/auto-cleanup.test.js | 6 +++--- {src/__tests__ => tests}/cleanup.test.js | 0 {src/__tests__ => tests}/context.test.js | 0 {src/__tests__ => tests}/debug.test.js | 0 {src/__tests__ => tests}/events.test.js | 0 {src/__tests__ => tests}/fixtures/Comp.svelte | 0 {src/__tests__ => tests}/fixtures/CompRunes.svelte | 0 {src/__tests__ => tests}/fixtures/Context.svelte | 0 {src/__tests__ => tests}/fixtures/Mounter.svelte | 0 {src/__tests__ => tests}/fixtures/Transitioner.svelte | 0 {src/__tests__ => tests}/fixtures/Typed.svelte | 0 {src/__tests__ => tests}/fixtures/TypedRunes.svelte | 0 {src/__tests__ => tests}/mount.test.js | 0 {src/__tests__ => tests}/multi-base.test.js | 0 {src/__tests__ => tests}/render-runes.test-d.ts | 2 +- {src/__tests__ => tests}/render-utilities.test-d.ts | 2 +- {src/__tests__ => tests}/render.test-d.ts | 2 +- {src/__tests__ => tests}/render.test.js | 2 +- {src/__tests__ => tests}/rerender.test.js | 2 +- {src/__tests__ => tests}/transition.test.js | 2 +- {src/__tests__ => tests}/vite-plugin.test.js | 4 ++-- tsconfig.build.json | 2 +- tsconfig.json | 6 +++++- tsconfig.legacy.json | 6 +++--- vite.config.js | 9 ++++----- 32 files changed, 29 insertions(+), 27 deletions(-) rename src/__tests__/utils.js => tests/_env.js (100%) rename {src/__tests__ => tests}/_jest-setup.js (100%) rename {src/__tests__ => tests}/_jest-vitest-alias.js (100%) rename {src/__tests__ => tests}/_vitest-setup.js (100%) rename {src/__tests__ => tests}/act.test.js (100%) rename {src/__tests__ => tests}/auto-cleanup.test.js (89%) rename {src/__tests__ => tests}/cleanup.test.js (100%) rename {src/__tests__ => tests}/context.test.js (100%) rename {src/__tests__ => tests}/debug.test.js (100%) rename {src/__tests__ => tests}/events.test.js (100%) rename {src/__tests__ => tests}/fixtures/Comp.svelte (100%) rename {src/__tests__ => tests}/fixtures/CompRunes.svelte (100%) rename {src/__tests__ => tests}/fixtures/Context.svelte (100%) rename {src/__tests__ => tests}/fixtures/Mounter.svelte (100%) rename {src/__tests__ => tests}/fixtures/Transitioner.svelte (100%) rename {src/__tests__ => tests}/fixtures/Typed.svelte (100%) rename {src/__tests__ => tests}/fixtures/TypedRunes.svelte (100%) rename {src/__tests__ => tests}/mount.test.js (100%) rename {src/__tests__ => tests}/multi-base.test.js (100%) rename {src/__tests__ => tests}/render-runes.test-d.ts (97%) rename {src/__tests__ => tests}/render-utilities.test-d.ts (97%) rename {src/__tests__ => tests}/render.test-d.ts (97%) rename {src/__tests__ => tests}/render.test.js (98%) rename {src/__tests__ => tests}/rerender.test.js (95%) rename {src/__tests__ => tests}/transition.test.js (94%) rename {src/__tests__ => tests}/vite-plugin.test.js (98%) diff --git a/jest.config.js b/jest.config.js index 4857b64..ff518c4 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,23 +4,23 @@ const SVELTE_TRANSFORM_PATTERN = SVELTE_VERSION >= '5' ? '^.+\\.svelte(?:\\.js)?$' : '^.+\\.svelte$' export default { - testMatch: ['/src/__tests__/**/*.test.js'], + testMatch: ['/tests/**/*.test.js'], transform: { [SVELTE_TRANSFORM_PATTERN]: 'svelte-jester', }, moduleFileExtensions: ['js', 'svelte'], extensionsToTreatAsEsm: ['.svelte'], testEnvironment: 'jsdom', - setupFilesAfterEnv: ['/src/__tests__/_jest-setup.js'], + setupFilesAfterEnv: ['/tests/_jest-setup.js'], injectGlobals: false, moduleNameMapper: { - '^vitest$': '/src/__tests__/_jest-vitest-alias.js', + '^vitest$': '/tests/_jest-vitest-alias.js', + '^@testing-library\\/svelte$': '/src/index.js', }, resetMocks: true, restoreMocks: true, collectCoverageFrom: ['/src/**/*'], coveragePathIgnorePatterns: [ - '/__tests__/', '/src/vite.js', '/src/vitest.js', ], diff --git a/package.json b/package.json index 8bab118..d686b2c 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,7 @@ ], "files": [ "src", - "types", - "!__tests__" + "types" ], "scripts": { "all": "npm-run-all contributors:generate toc format types build test:vitest:* test:jest", diff --git a/src/__tests__/utils.js b/tests/_env.js similarity index 100% rename from src/__tests__/utils.js rename to tests/_env.js diff --git a/src/__tests__/_jest-setup.js b/tests/_jest-setup.js similarity index 100% rename from src/__tests__/_jest-setup.js rename to tests/_jest-setup.js diff --git a/src/__tests__/_jest-vitest-alias.js b/tests/_jest-vitest-alias.js similarity index 100% rename from src/__tests__/_jest-vitest-alias.js rename to tests/_jest-vitest-alias.js diff --git a/src/__tests__/_vitest-setup.js b/tests/_vitest-setup.js similarity index 100% rename from src/__tests__/_vitest-setup.js rename to tests/_vitest-setup.js diff --git a/src/__tests__/act.test.js b/tests/act.test.js similarity index 100% rename from src/__tests__/act.test.js rename to tests/act.test.js diff --git a/src/__tests__/auto-cleanup.test.js b/tests/auto-cleanup.test.js similarity index 89% rename from src/__tests__/auto-cleanup.test.js rename to tests/auto-cleanup.test.js index 7962925..d6ba28b 100644 --- a/src/__tests__/auto-cleanup.test.js +++ b/tests/auto-cleanup.test.js @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' -import { IS_JEST } from './utils.js' +import { IS_JEST } from './_env.js' // TODO(mcous, 2024-12-08): clearing module cache and re-importing // in Jest breaks Svelte's environment checking heuristics. @@ -19,7 +19,7 @@ describe.skipIf(IS_JEST)('auto-cleanup', () => { }) test('calls afterEach with cleanup if globally defined', async () => { - const { render } = await import('../index.js') + const { render } = await import('@testing-library/svelte') expect(globalAfterEach).toHaveBeenCalledTimes(1) expect(globalAfterEach).toHaveBeenLastCalledWith(expect.any(Function)) @@ -35,7 +35,7 @@ describe.skipIf(IS_JEST)('auto-cleanup', () => { test('does not call afterEach if process STL_SKIP_AUTO_CLEANUP is set', async () => { process.env.STL_SKIP_AUTO_CLEANUP = 'true' - await import('../index.js') + await import('@testing-library/svelte') expect(globalAfterEach).toHaveBeenCalledTimes(0) }) diff --git a/src/__tests__/cleanup.test.js b/tests/cleanup.test.js similarity index 100% rename from src/__tests__/cleanup.test.js rename to tests/cleanup.test.js diff --git a/src/__tests__/context.test.js b/tests/context.test.js similarity index 100% rename from src/__tests__/context.test.js rename to tests/context.test.js diff --git a/src/__tests__/debug.test.js b/tests/debug.test.js similarity index 100% rename from src/__tests__/debug.test.js rename to tests/debug.test.js diff --git a/src/__tests__/events.test.js b/tests/events.test.js similarity index 100% rename from src/__tests__/events.test.js rename to tests/events.test.js diff --git a/src/__tests__/fixtures/Comp.svelte b/tests/fixtures/Comp.svelte similarity index 100% rename from src/__tests__/fixtures/Comp.svelte rename to tests/fixtures/Comp.svelte diff --git a/src/__tests__/fixtures/CompRunes.svelte b/tests/fixtures/CompRunes.svelte similarity index 100% rename from src/__tests__/fixtures/CompRunes.svelte rename to tests/fixtures/CompRunes.svelte diff --git a/src/__tests__/fixtures/Context.svelte b/tests/fixtures/Context.svelte similarity index 100% rename from src/__tests__/fixtures/Context.svelte rename to tests/fixtures/Context.svelte diff --git a/src/__tests__/fixtures/Mounter.svelte b/tests/fixtures/Mounter.svelte similarity index 100% rename from src/__tests__/fixtures/Mounter.svelte rename to tests/fixtures/Mounter.svelte diff --git a/src/__tests__/fixtures/Transitioner.svelte b/tests/fixtures/Transitioner.svelte similarity index 100% rename from src/__tests__/fixtures/Transitioner.svelte rename to tests/fixtures/Transitioner.svelte diff --git a/src/__tests__/fixtures/Typed.svelte b/tests/fixtures/Typed.svelte similarity index 100% rename from src/__tests__/fixtures/Typed.svelte rename to tests/fixtures/Typed.svelte diff --git a/src/__tests__/fixtures/TypedRunes.svelte b/tests/fixtures/TypedRunes.svelte similarity index 100% rename from src/__tests__/fixtures/TypedRunes.svelte rename to tests/fixtures/TypedRunes.svelte diff --git a/src/__tests__/mount.test.js b/tests/mount.test.js similarity index 100% rename from src/__tests__/mount.test.js rename to tests/mount.test.js diff --git a/src/__tests__/multi-base.test.js b/tests/multi-base.test.js similarity index 100% rename from src/__tests__/multi-base.test.js rename to tests/multi-base.test.js diff --git a/src/__tests__/render-runes.test-d.ts b/tests/render-runes.test-d.ts similarity index 97% rename from src/__tests__/render-runes.test-d.ts rename to tests/render-runes.test-d.ts index 51b7cfa..1e42544 100644 --- a/src/__tests__/render-runes.test-d.ts +++ b/tests/render-runes.test-d.ts @@ -1,7 +1,7 @@ +import * as subject from '@testing-library/svelte' import { expectTypeOf } from 'expect-type' import { describe, test, vi } from 'vitest' -import * as subject from '../index.js' import LegacyComponent from './fixtures/Typed.svelte' import Component from './fixtures/TypedRunes.svelte' diff --git a/src/__tests__/render-utilities.test-d.ts b/tests/render-utilities.test-d.ts similarity index 97% rename from src/__tests__/render-utilities.test-d.ts rename to tests/render-utilities.test-d.ts index d1e3869..c72f761 100644 --- a/src/__tests__/render-utilities.test-d.ts +++ b/tests/render-utilities.test-d.ts @@ -1,7 +1,7 @@ +import * as subject from '@testing-library/svelte' import { expectTypeOf } from 'expect-type' import { describe, test } from 'vitest' -import * as subject from '../index.js' import Component from './fixtures/Comp.svelte' describe('render query and utility types', () => { diff --git a/src/__tests__/render.test-d.ts b/tests/render.test-d.ts similarity index 97% rename from src/__tests__/render.test-d.ts rename to tests/render.test-d.ts index bf0a611..eb1c639 100644 --- a/src/__tests__/render.test-d.ts +++ b/tests/render.test-d.ts @@ -1,8 +1,8 @@ +import * as subject from '@testing-library/svelte' import { expectTypeOf } from 'expect-type' import { ComponentProps } from 'svelte' import { describe, test } from 'vitest' -import * as subject from '../index.js' import Component from './fixtures/Typed.svelte' describe('types', () => { diff --git a/src/__tests__/render.test.js b/tests/render.test.js similarity index 98% rename from src/__tests__/render.test.js rename to tests/render.test.js index f396751..943c920 100644 --- a/src/__tests__/render.test.js +++ b/tests/render.test.js @@ -1,7 +1,7 @@ import { render } from '@testing-library/svelte' import { beforeAll, describe, expect, test } from 'vitest' -import { COMPONENT_FIXTURES } from './utils.js' +import { COMPONENT_FIXTURES } from './_env.js' describe.each(COMPONENT_FIXTURES)('render ($mode)', ({ component }) => { const props = { name: 'World' } diff --git a/src/__tests__/rerender.test.js b/tests/rerender.test.js similarity index 95% rename from src/__tests__/rerender.test.js rename to tests/rerender.test.js index 52acd0f..2a20143 100644 --- a/src/__tests__/rerender.test.js +++ b/tests/rerender.test.js @@ -1,7 +1,7 @@ import { act, render, screen } from '@testing-library/svelte' import { beforeAll, describe, expect, test, vi } from 'vitest' -import { COMPONENT_FIXTURES, IS_SVELTE_5, MODE_RUNES } from './utils.js' +import { COMPONENT_FIXTURES, IS_SVELTE_5, MODE_RUNES } from './_env.js' describe.each(COMPONENT_FIXTURES)('rerender ($mode)', ({ mode, component }) => { let Comp diff --git a/src/__tests__/transition.test.js b/tests/transition.test.js similarity index 94% rename from src/__tests__/transition.test.js rename to tests/transition.test.js index a9c8e02..3acf1d0 100644 --- a/src/__tests__/transition.test.js +++ b/tests/transition.test.js @@ -2,8 +2,8 @@ import { render, screen, waitFor } from '@testing-library/svelte' import { userEvent } from '@testing-library/user-event' import { beforeEach, describe, expect, test, vi } from 'vitest' +import { IS_JSDOM, IS_SVELTE_5 } from './_env.js' import Transitioner from './fixtures/Transitioner.svelte' -import { IS_JSDOM, IS_SVELTE_5 } from './utils.js' describe.skipIf(IS_SVELTE_5)('transitions', () => { beforeEach(() => { diff --git a/src/__tests__/vite-plugin.test.js b/tests/vite-plugin.test.js similarity index 98% rename from src/__tests__/vite-plugin.test.js rename to tests/vite-plugin.test.js index 9232f65..92fc8f5 100644 --- a/src/__tests__/vite-plugin.test.js +++ b/tests/vite-plugin.test.js @@ -1,7 +1,7 @@ +import { svelteTesting } from '@testing-library/svelte/vite' import { beforeEach, describe, expect, test, vi } from 'vitest' -import { svelteTesting } from '../vite.js' -import { IS_JEST } from './utils.js' +import { IS_JEST } from './_env.js' describe.skipIf(IS_JEST)('vite plugin', () => { beforeEach(() => { diff --git a/tsconfig.build.json b/tsconfig.build.json index 0baa218..bfc566b 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -8,5 +8,5 @@ "rootDir": "src", "outDir": "types" }, - "exclude": ["src/**/__tests__/**"] + "include": ["src"] } diff --git a/tsconfig.json b/tsconfig.json index 0cc0fdf..fda8aba 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,11 @@ "skipLibCheck": true, "strict": true, "types": ["svelte", "vite/client", "vitest", "vitest/globals"], + "baseUrl": "./", + "paths": { + "@testing-library/svelte": ["./src"] + }, "plugins": [{ "name": "typescript-svelte-plugin" }] }, - "include": ["src"] + "include": ["src", "tests"] } diff --git a/tsconfig.legacy.json b/tsconfig.legacy.json index b5d9e57..304d872 100644 --- a/tsconfig.legacy.json +++ b/tsconfig.legacy.json @@ -1,8 +1,8 @@ { "extends": ["./tsconfig.json"], "exclude": [ - "src/__tests__/render-runes.test-d.ts", - "src/__tests__/fixtures/CompRunes.svelte", - "src/__tests__/fixtures/TypedRunes.svelte" + "tests/render-runes.test-d.ts", + "tests/fixtures/CompRunes.svelte", + "tests/fixtures/TypedRunes.svelte" ] } diff --git a/vite.config.js b/vite.config.js index 2db8a40..75bd66a 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,10 +1,9 @@ import { createRequire } from 'node:module' import { svelte } from '@sveltejs/vite-plugin-svelte' +import { svelteTesting } from '@testing-library/svelte/vite' import { defineConfig } from 'vite' -import { svelteTesting } from './src/vite.js' - const require = createRequire(import.meta.url) // https://vitejs.dev/config/ @@ -12,17 +11,17 @@ export default defineConfig({ plugins: [svelte(), svelteTesting()], test: { environment: 'jsdom', - setupFiles: ['./src/__tests__/_vitest-setup.js'], + setupFiles: ['./tests/_vitest-setup.js'], mockReset: true, unstubGlobals: true, unstubEnvs: true, coverage: { provider: 'v8', include: ['src/**/*'], - exclude: ['**/__tests__/**', 'src/vite.js', 'src/vitest.js'], }, alias: { - '@testing-library/svelte': require.resolve('.'), + '@testing-library/svelte/vite': require.resolve('./src/vite.js'), + '@testing-library/svelte': require.resolve('./src/index.js'), }, }, }) From 6096f05e805cf55474f52f303562f4013785d25f Mon Sep 17 00:00:00 2001 From: Michael Cousins Date: Wed, 7 May 2025 12:52:03 -0400 Subject: [PATCH 3/5] ci: expand matrix to include Node 22 (#435) --- .github/workflows/release.yml | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 79dbe5a..c61b1d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: main: # ignore all-contributors PRs if: ${{ !contains(github.head_ref, 'all-contributors') }} - name: Node ${{ matrix.node }}, Svelte ${{ matrix.svelte }}, ${{ matrix.check }} + name: Svelte ${{ matrix.svelte }}, Node ${{ matrix.node }}, ${{ matrix.check }} runs-on: ubuntu-latest # enable OIDC for codecov uploads @@ -27,20 +27,22 @@ jobs: strategy: fail-fast: false matrix: - node: ['16', '18', '20'] - svelte: ['3', '4'] + node: ['16', '18', '20', '22'] + svelte: ['3', '4', '5'] check: ['test:vitest:jsdom', 'test:vitest:happy-dom', 'test:jest'] + exclude: + # Don't run Svelte 3 on Node versions greater than 20 + - { svelte: '3', node: '22' } + # Only run Svelte 5 on Node versions greater than or equal to 20 + - { svelte: '5', node: '16' } + - { svelte: '5', node: '18' } include: # We only need to lint once, so do it on latest Node and Svelte - - { node: '20', svelte: '4', check: 'lint' } - # Run type checks in latest node - - { node: '20', svelte: '3', check: 'types:legacy' } - - { node: '20', svelte: '4', check: 'types:legacy' } - - { node: '20', svelte: '5', check: 'types' } - # Only run Svelte 5 checks on latest Node - - { node: '20', svelte: '5', check: 'test:vitest:jsdom' } - - { node: '20', svelte: '5', check: 'test:vitest:happy-dom' } - - { node: '20', svelte: '5', check: 'test:jest' } + - { svelte: '5', node: '22', check: 'lint' } + # Run type checks in latest applicable Node + - { svelte: '3', node: '20', check: 'types:legacy' } + - { svelte: '4', node: '22', check: 'types:legacy' } + - { svelte: '5', node: '22', check: 'types' } steps: - name: ⬇️ Checkout repo From f1fb8ddd7e1b599244e9359bb71db47268139f64 Mon Sep 17 00:00:00 2001 From: Michael Cousins Date: Fri, 9 May 2025 12:11:54 -0400 Subject: [PATCH 4/5] ci: use specific dependencies for different Svelte/Node versions (#436) --- .github/workflows/release.yml | 6 ++-- .npmrc | 1 + CONTRIBUTING.md | 20 ++++++------ package.json | 41 ++++++++++-------------- scripts/changed-files | 3 -- scripts/install-dependencies | 44 ++++++++++++++------------ src/component-types.d.ts | 2 +- tests/envs/svelte3/node16/package.json | 24 ++++++++++++++ tests/envs/svelte3/package.json | 24 ++++++++++++++ tests/envs/svelte4/node16/package.json | 24 ++++++++++++++ tests/envs/svelte4/package.json | 24 ++++++++++++++ vite.config.js | 3 +- 12 files changed, 153 insertions(+), 63 deletions(-) delete mode 100755 scripts/changed-files create mode 100644 tests/envs/svelte3/node16/package.json create mode 100644 tests/envs/svelte3/package.json create mode 100644 tests/envs/svelte4/node16/package.json create mode 100644 tests/envs/svelte4/package.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c61b1d6..be02b70 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -75,10 +75,10 @@ jobs: - name: ⎔ Setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - name: 📥 Download deps - run: npm install --no-package-lock + run: npm install - name: 🏗️ Build types run: npm run build @@ -102,7 +102,7 @@ jobs: - name: ⎔ Setup node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - name: 📥 Downloads types build uses: actions/download-artifact@v4 diff --git a/.npmrc b/.npmrc index 43c97e7..86916fa 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ package-lock=false +engine-strict=true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4c40705..41193a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,11 +28,10 @@ npm run preview-release ## Development setup -After cloning the repository, install the project's dependencies and run the `validate` script to run all checks and tests to verify your setup. +After cloning the repository, use the `setup` script to install dependencies and run all checks: ```shell -npm install # or `pnpm install`, or `yarn install`, etc. -npm run validate +npm run setup ``` ### Lint and format @@ -40,13 +39,13 @@ npm run validate Run auto-formatting to ensure any changes adhere to the code style of the repository: ```shell -npm run format:delta +npm run format ``` To run lint and format checks without making any changes: ```shell -npm run lint:delta +npm run lint ``` ### Test @@ -63,18 +62,19 @@ npm run test:watch Use the provided script to set up your environment for different versions of Svelte: ```shell -# install Svelte 5 +# Svelte 5 npm run install:5 +npm run all -# install Svelte 4 +# Svelte 4 npm run install:4 +npm run all:legacy -# install Svelte 3 +# Svelte 3 npm run install:3 +npm run all:legacy ``` -Not all checks will pass on `svelte<5`. Reference the CI workflows to see which checks are expected to pass on older versions. - ### Docs Use the `toc` script to ensure the README's table of contents is up to date: diff --git a/package.json b/package.json index d686b2c..7e50de7 100644 --- a/package.json +++ b/package.json @@ -53,16 +53,11 @@ ], "scripts": { "all": "npm-run-all contributors:generate toc format types build test:vitest:* test:jest", + "all:legacy": "npm-run-all types:legacy test:vitest:* test:jest", "toc": "doctoc README.md", "lint": "prettier . --check && eslint .", - "lint:delta": "npm-run-all -p prettier:delta eslint:delta", - "prettier:delta": "prettier --check `./scripts/changed-files`", - "eslint:delta": "eslint `./scripts/changed-files`", "format": "prettier . --write && eslint . --fix", - "format:delta": "npm-run-all format:prettier:delta format:eslint:delta", - "format:prettier:delta": "prettier --write `./scripts/changed-files`", - "format:eslint:delta": "eslint --fix `./scripts/changed-files`", - "setup": "npm install && npm run all", + "setup": "npm run install:5 && npm run all", "test": "vitest run --coverage", "test:watch": "vitest", "test:vitest:jsdom": "vitest run --coverage --environment jsdom", @@ -70,7 +65,6 @@ "test:jest": "npx --node-options=\"--experimental-vm-modules --no-warnings\" jest --coverage", "types": "svelte-check", "types:legacy": "svelte-check --tsconfig tsconfig.legacy.json", - "validate": "npm-run-all test:vitest:* test:jest types build", "build": "tsc -p tsconfig.build.json && cp src/component-types.d.ts types", "contributors:add": "all-contributors add", "contributors:generate": "all-contributors generate", @@ -93,19 +87,18 @@ } }, "dependencies": { - "@testing-library/dom": "^10.0.0" + "@testing-library/dom": "9.x.x || 10.x.x" }, "devDependencies": { "@jest/globals": "^29.7.0", - "@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0", - "@testing-library/jest-dom": "^6.3.0", - "@testing-library/user-event": "^14.5.2", + "@sveltejs/vite-plugin-svelte": "^5.0.3", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/user-event": "^14.6.1", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", - "@vitest/coverage-v8": "0.x.x || ^1.0.0 || ^2.0.2", + "@vitest/coverage-v8": "^3.1.3", "all-contributors-cli": "^6.26.1", "doctoc": "^2.2.1", - "esbuild": "*", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-config-standard": "^17.1.0", @@ -114,20 +107,20 @@ "eslint-plugin-promise": "^6.4.0", "eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-svelte": "^2.42.0", - "expect-type": "^1.1.0", - "happy-dom": "^16.3.0", + "expect-type": "^1.2.1", + "happy-dom": "^17.4.6", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "jsdom": "^25.0.0", + "jsdom": "^26.1.0", "npm-run-all": "^4.1.5", - "prettier": "^3.3.3", - "prettier-plugin-svelte": "^3.2.5", - "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0", - "svelte-check": "^3.0.0 || ^4.0.4", + "prettier": "^3.5.3", + "prettier-plugin-svelte": "^3.3.3", + "svelte": "^5.28.2", + "svelte-check": "^4.1.7", "svelte-jester": "^5.0.0", - "typescript": "^5.5.3", + "typescript": "^5.8.3", "typescript-svelte-plugin": "^0.3.46", - "vite": "^4.0.0 || ^5.3.3", - "vitest": "0.x.x || ^1.0.0 || ^2.0.2" + "vite": "^6.3.5", + "vitest": "^3.1.3" } } diff --git a/scripts/changed-files b/scripts/changed-files deleted file mode 100755 index 1275bd8..0000000 --- a/scripts/changed-files +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -git diff --name-only --diff-filter=d main diff --git a/scripts/install-dependencies b/scripts/install-dependencies index 8f403a0..aef6257 100755 --- a/scripts/install-dependencies +++ b/scripts/install-dependencies @@ -2,30 +2,34 @@ # Install dependencies for a given version of Svelte set -euxo pipefail -svelte_version=${1} -node_version=$(node --version) +svelte_version="${1-}" +node_version=$(node --version | sed 's/^v\([0-9]*\).*/\1/') +env_dir="tests/envs/svelte$svelte_version" +env_dir_by_node="$env_dir/node$node_version" -rm -rf node_modules -npm install --no-package-lock - -if [[ "${svelte_version}" == "3" ]]; then - npm uninstall --no-save vite vitest @vitest/coverage-v8 @sveltejs/vite-plugin-svelte svelte-check svelte - npm install --no-save vite@4 vitest@0.x.x @vitest/coverage-v8@0.x.x @sveltejs/vite-plugin-svelte@2 svelte-check@3 svelte@3 -elif [[ "${svelte_version}" == "4" ]]; then - npm uninstall --no-save @sveltejs/vite-plugin-svelte svelte - npm install --no-save @sveltejs/vite-plugin-svelte@3 svelte@4 +if [[ -d $env_dir_by_node ]]; then + env_dir="$env_dir_by_node" fi -if [[ "${node_version}" =~ "v16" ]]; then - npm uninstall --no-save vite vitest @vitest/coverage-v8 @sveltejs/vite-plugin-svelte - npm install --no-save vite@4 vitest@0.x.x @vitest/coverage-v8@0.x.x @sveltejs/vite-plugin-svelte@2 +if [[ "$svelte_version" == "5" ]]; then + rm -rf coverage node_modules + npm install + exit 0 fi -npm dedupe -installed_version=$(npm ls --depth=0 --parseable svelte@${svelte_version}) - -if [[ -z "${installed_version}" ]]; then - echo "Error: expected svelte@${svelte_version}" - npm ls --depth=0 svelte +if [[ -z "$svelte_version" ]]; then + echo "Invalid usage: missing Svelte version" >&2; exit 1 fi + +if [[ ! -d "$env_dir" ]]; then + echo "Error: package.json for Svelte $svelte_version, Node $node_version not found" 1>&2 + exit 2 +fi + +rm -rf coverage node_modules "$env_dir/node_modules" +pushd "$env_dir" +npm install --no-package-lock --engine-strict +npm ls "$env_dir" svelte +popd +mv "$env_dir/node_modules" . diff --git a/src/component-types.d.ts b/src/component-types.d.ts index 8300243..007f1e4 100644 --- a/src/component-types.d.ts +++ b/src/component-types.d.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-redundant-type-constituents */ +/* eslint-disable @typescript-eslint/no-explicit-any */ import type { Component as ModernComponent, ComponentConstructorOptions as LegacyConstructorOptions, diff --git a/tests/envs/svelte3/node16/package.json b/tests/envs/svelte3/node16/package.json new file mode 100644 index 0000000..8acb921 --- /dev/null +++ b/tests/envs/svelte3/node16/package.json @@ -0,0 +1,24 @@ +{ + "private": true, + "engines": { + "node": "16.x.x" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "2.x.x", + "@testing-library/dom": "9.x.x", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/user-event": "^14.6.1", + "@vitest/coverage-v8": "0.x.x", + "expect-type": "^1.2.1", + "happy-dom": "14.x.x", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jsdom": "22.x.x", + "npm-run-all": "^4.1.5", + "svelte": "3.x.x", + "svelte-check": "3.x.x", + "svelte-jester": "3.x.x", + "vite": "4.x.x", + "vitest": "0.x.x" + } +} diff --git a/tests/envs/svelte3/package.json b/tests/envs/svelte3/package.json new file mode 100644 index 0000000..d563d97 --- /dev/null +++ b/tests/envs/svelte3/package.json @@ -0,0 +1,24 @@ +{ + "private": true, + "engines": { + "node": ">=18" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "2.x.x", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/user-event": "^14.6.1", + "@vitest/coverage-v8": "0.x.x", + "expect-type": "^1.2.1", + "happy-dom": "^17.4.6", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jsdom": "^26.1.0", + "npm-run-all": "^4.1.5", + "svelte": "3.x.x", + "svelte-check": "3.x.x", + "svelte-jester": "3.x.x", + "vite": "4.x.x", + "vitest": "0.x.x" + } +} diff --git a/tests/envs/svelte4/node16/package.json b/tests/envs/svelte4/node16/package.json new file mode 100644 index 0000000..ce420a1 --- /dev/null +++ b/tests/envs/svelte4/node16/package.json @@ -0,0 +1,24 @@ +{ + "private": true, + "engines": { + "node": "16.x.x" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "2.x.x", + "@testing-library/dom": "9.x.x", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/user-event": "^14.6.1", + "@vitest/coverage-v8": "0.x.x", + "expect-type": "^1.2.1", + "happy-dom": "14.x.x", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jsdom": "22.x.x", + "npm-run-all": "^4.1.5", + "svelte": "4.x.x", + "svelte-check": "3.x.x", + "svelte-jester": "3.x.x", + "vite": "4.x.x", + "vitest": "0.x.x" + } +} diff --git a/tests/envs/svelte4/package.json b/tests/envs/svelte4/package.json new file mode 100644 index 0000000..b90351d --- /dev/null +++ b/tests/envs/svelte4/package.json @@ -0,0 +1,24 @@ +{ + "private": true, + "engines": { + "node": ">=18" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "3.x.x", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/user-event": "^14.6.1", + "@vitest/coverage-v8": "2.x.x", + "expect-type": "^1.2.1", + "happy-dom": "^17.4.6", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jsdom": "^26.1.0", + "npm-run-all": "^4.1.5", + "svelte": "4.x.x", + "svelte-check": "^4.1.7", + "svelte-jester": "^5.0.0", + "vite": "5.x.x", + "vitest": "2.x.x" + } +} diff --git a/vite.config.js b/vite.config.js index 75bd66a..65e1ca7 100644 --- a/vite.config.js +++ b/vite.config.js @@ -6,9 +6,8 @@ import { defineConfig } from 'vite' const require = createRequire(import.meta.url) -// https://vitejs.dev/config/ export default defineConfig({ - plugins: [svelte(), svelteTesting()], + plugins: [svelte({ hot: false }), svelteTesting()], test: { environment: 'jsdom', setupFiles: ['./tests/_vitest-setup.js'], From 1aa370137003ce8e887d96f881d52c33d3dd6f12 Mon Sep 17 00:00:00 2001 From: Michael Cousins Date: Fri, 9 May 2025 12:21:42 -0400 Subject: [PATCH 5/5] style: update to ESLint v9 and rework lint config (#438) --- .eslintignore | 6 -- .eslintrc.cjs | 45 --------------- .github/dependabot.yml | 9 --- .prettierignore | 3 - .prettierrc.yaml | 9 --- eslint.config.js | 95 ++++++++++++++++++++++++++++++++ jest.config.js | 6 +- package.json | 20 ++++--- prettier.config.js | 14 +++++ src/core/index.js | 14 +---- src/index.js | 1 - src/pure.js | 17 ++++-- src/vite.js | 12 ++-- tests/_env.js | 2 +- tests/act.test.js | 10 ++-- tests/context.test.js | 6 +- tests/events.test.js | 10 ++-- tests/fixtures/Typed.svelte | 2 +- tests/fixtures/TypedRunes.svelte | 2 +- tests/render.test.js | 27 ++++----- tests/rerender.test.js | 4 +- tests/transition.test.js | 13 +++-- 22 files changed, 182 insertions(+), 145 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.cjs delete mode 100644 .prettierrc.yaml create mode 100644 eslint.config.js create mode 100644 prettier.config.js diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 39f9d9e..0000000 --- a/.eslintignore +++ /dev/null @@ -1,6 +0,0 @@ -scripts/* -.eslintignore -.prettierignore -.github/workflows/* -*.md -types diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 77f8b7c..0000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,45 +0,0 @@ -module.exports = { - root: true, - env: { - browser: true, - es6: true, - }, - extends: ['standard', 'plugin:svelte/recommended', 'prettier'], - plugins: ['svelte', 'simple-import-sort'], - rules: { - 'simple-import-sort/imports': 'error', - 'simple-import-sort/exports': 'error', - }, - overrides: [ - { - files: ['*.svelte'], - parser: 'svelte-eslint-parser', - parserOptions: { - parser: '@typescript-eslint/parser', - }, - rules: { - 'no-undef-init': 'off', - 'prefer-const': 'off', - 'svelte/no-unused-svelte-ignore': 'off', - }, - }, - { - files: ['*.ts'], - parser: '@typescript-eslint/parser', - parserOptions: { - project: './tsconfig.json', - }, - extends: [ - 'plugin:@typescript-eslint/strict-type-checked', - 'plugin:@typescript-eslint/stylistic-type-checked', - 'prettier', - ], - }, - ], - parserOptions: { - ecmaVersion: 2022, - sourceType: 'module', - }, - globals: { afterEach: 'readonly', $state: 'readonly', $props: 'readonly' }, - ignorePatterns: ['!/.*'], -} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c9eb8a7..434a8bb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -23,15 +23,6 @@ updates: development: dependency-type: 'development' - # TODO(mcous, 2024-04-30): update to ESLint v9 + flat config - ignore: - - dependency-name: 'eslint' - versions: ['>=9'] - - dependency-name: 'eslint-plugin-n' - versions: ['>=17'] - - dependency-name: 'eslint-plugin-promise' - versions: ['>=7'] - # Update GitHub Actions dependencies - package-ecosystem: 'github-actions' directory: '/' diff --git a/.prettierignore b/.prettierignore index 5e50c20..c750ecf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1 @@ -scripts/* -.eslintignore -.prettierignore .all-contributorsrc diff --git a/.prettierrc.yaml b/.prettierrc.yaml deleted file mode 100644 index 0a2ace3..0000000 --- a/.prettierrc.yaml +++ /dev/null @@ -1,9 +0,0 @@ -semi: false -singleQuote: true -trailingComma: es5 -plugins: - - prettier-plugin-svelte -overrides: - - files: '*.svelte' - options: - parser: svelte diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..c03de8c --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,95 @@ +import js from '@eslint/js' +import eslintPluginVitest from '@vitest/eslint-plugin' +import eslintConfigPrettier from 'eslint-config-prettier' +import eslintPluginJestDom from 'eslint-plugin-jest-dom' +import eslintPluginPromise from 'eslint-plugin-promise' +import eslintPluginSimpleImportSort from 'eslint-plugin-simple-import-sort' +import eslintPluginSvelte from 'eslint-plugin-svelte' +import eslintPluginTestingLibrary from 'eslint-plugin-testing-library' +import eslintPluginUnicorn from 'eslint-plugin-unicorn' +import globals from 'globals' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + js.configs.recommended, + tseslint.configs.strict, + tseslint.configs.stylistic, + eslintPluginUnicorn.configs['flat/recommended'], + eslintPluginPromise.configs['flat/recommended'], + eslintPluginSvelte.configs['flat/recommended'], + eslintPluginSvelte.configs['flat/prettier'], + eslintConfigPrettier, + { + name: 'settings', + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + parserOptions: { + parser: tseslint.parser, + extraFileExtensions: ['.svelte'], + }, + globals: { + ...globals.browser, + ...globals.node, + ...globals.jest, + }, + }, + }, + { + name: 'ignores', + ignores: ['coverage', 'types'], + }, + { + name: 'simple-import-sort', + plugins: { + 'simple-import-sort': eslintPluginSimpleImportSort, + }, + rules: { + 'simple-import-sort/imports': 'error', + 'simple-import-sort/exports': 'error', + }, + }, + { + name: 'tests', + files: ['**/*.test.js'], + extends: [ + eslintPluginVitest.configs.recommended, + eslintPluginJestDom.configs['flat/recommended'], + eslintPluginTestingLibrary.configs['flat/dom'], + ], + rules: { + 'testing-library/no-node-access': [ + 'error', + { allowContainerFirstChild: true }, + ], + }, + }, + { + name: 'extras', + rules: { + 'unicorn/prevent-abbreviations': 'off', + }, + }, + { + name: 'svelte-extras', + files: ['**/*.svelte'], + rules: { + 'svelte/no-unused-svelte-ignore': 'off', + 'unicorn/filename-case': ['error', { case: 'pascalCase' }], + 'unicorn/no-useless-undefined': 'off', + }, + }, + { + name: 'ts-extras', + files: ['**/*.ts'], + extends: [ + tseslint.configs.strictTypeChecked, + tseslint.configs.stylisticTypeChecked, + ], + languageOptions: { + parserOptions: { + projectService: true, + }, + }, + } +) diff --git a/jest.config.js b/jest.config.js index ff518c4..b590229 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,9 @@ import { VERSION as SVELTE_VERSION } from 'svelte/compiler' const SVELTE_TRANSFORM_PATTERN = - SVELTE_VERSION >= '5' ? '^.+\\.svelte(?:\\.js)?$' : '^.+\\.svelte$' + SVELTE_VERSION >= '5' + ? String.raw`^.+\.svelte(?:\.js)?$` + : String.raw`^.+\.svelte$` export default { testMatch: ['/tests/**/*.test.js'], @@ -15,7 +17,7 @@ export default { injectGlobals: false, moduleNameMapper: { '^vitest$': '/tests/_jest-vitest-alias.js', - '^@testing-library\\/svelte$': '/src/index.js', + [String.raw`^@testing-library\/svelte$`]: '/src/index.js', }, resetMocks: true, restoreMocks: true, diff --git a/package.json b/package.json index 7e50de7..fea66a7 100644 --- a/package.json +++ b/package.json @@ -90,24 +90,25 @@ "@testing-library/dom": "9.x.x || 10.x.x" }, "devDependencies": { + "@eslint/js": "^9.26.0", "@jest/globals": "^29.7.0", "@sveltejs/vite-plugin-svelte": "^5.0.3", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", "@vitest/coverage-v8": "^3.1.3", + "@vitest/eslint-plugin": "^1.1.44", "all-contributors-cli": "^6.26.1", "doctoc": "^2.2.1", - "eslint": "^8.57.0", - "eslint-config-prettier": "^9.1.0", - "eslint-config-standard": "^17.1.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-n": "^16.6.2", - "eslint-plugin-promise": "^6.4.0", + "eslint": "^9.26.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-jest-dom": "^5.5.0", + "eslint-plugin-promise": "^7.2.1", "eslint-plugin-simple-import-sort": "^12.1.1", - "eslint-plugin-svelte": "^2.42.0", + "eslint-plugin-svelte": "^3.5.1", + "eslint-plugin-testing-library": "^7.1.1", + "eslint-plugin-unicorn": "^59.0.1", "expect-type": "^1.2.1", + "globals": "^16.1.0", "happy-dom": "^17.4.6", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", @@ -119,6 +120,7 @@ "svelte-check": "^4.1.7", "svelte-jester": "^5.0.0", "typescript": "^5.8.3", + "typescript-eslint": "^8.32.0", "typescript-svelte-plugin": "^0.3.46", "vite": "^6.3.5", "vitest": "^3.1.3" diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..8c5a52d --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,14 @@ +export default { + semi: false, + singleQuote: true, + trailingComma: 'es5', + plugins: ['prettier-plugin-svelte'], + overrides: [ + { + files: '*.svelte', + options: { + parser: 'svelte', + }, + }, + ], +} diff --git a/src/core/index.js b/src/core/index.js index f4a40aa..9e41adf 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -7,10 +7,7 @@ */ import * as LegacyCore from './legacy.js' import * as ModernCore from './modern.svelte.js' -import { - createValidateOptions, - UnknownSvelteOptionsError, -} from './validate-options.js' +import { createValidateOptions } from './validate-options.js' const { mount, unmount, updateProps, allowedOptions } = ModernCore.IS_MODERN_SVELTE ? ModernCore : LegacyCore @@ -18,10 +15,5 @@ const { mount, unmount, updateProps, allowedOptions } = /** Validate component options. */ const validateOptions = createValidateOptions(allowedOptions) -export { - mount, - UnknownSvelteOptionsError, - unmount, - updateProps, - validateOptions, -} +export { mount, unmount, updateProps, validateOptions } +export { UnknownSvelteOptionsError } from './validate-options.js' diff --git a/src/index.js b/src/index.js index 2704824..5a65c08 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,3 @@ -/* eslint-disable import/export */ import { act, cleanup } from './pure.js' // If we're running in a test runner that supports afterEach diff --git a/src/pure.js b/src/pure.js index 875f87c..44e68d6 100644 --- a/src/pure.js +++ b/src/pure.js @@ -65,6 +65,7 @@ const render = (Component, options = {}, renderOptions = {}) => { const queries = getQueriesForElement(baseElement, renderOptions.queries) const target = + // eslint-disable-next-line unicorn/prefer-dom-node-append options.target ?? baseElement.appendChild(document.createElement('div')) targetCache.add(target) @@ -116,14 +117,18 @@ const cleanupTarget = (target) => { const inCache = targetCache.delete(target) if (inCache && target.parentNode === document.body) { - document.body.removeChild(target) + target.remove() } } /** Unmount all components and remove elements added to ``. */ const cleanup = () => { - componentCache.forEach(cleanupComponent) - targetCache.forEach(cleanupTarget) + for (const component of componentCache) { + cleanupComponent(component) + } + for (const target of targetCache) { + cleanupTarget(target) + } } /** @@ -163,12 +168,12 @@ const fireEvent = async (...args) => { return event } -Object.keys(baseFireEvent).forEach((key) => { +for (const [key, baseEvent] of Object.entries(baseFireEvent)) { fireEvent[key] = async (...args) => { - const event = baseFireEvent[key](...args) + const event = baseEvent(...args) await tick() return event } -}) +} export { act, cleanup, fireEvent, render } diff --git a/src/vite.js b/src/vite.js index 1ad712f..b57c886 100644 --- a/src/vite.js +++ b/src/vite.js @@ -1,5 +1,5 @@ -import { dirname, join } from 'node:path' -import { fileURLToPath } from 'node:url' +import path from 'node:path' +import url from 'node:url' /** * Vite plugin to configure @testing-library/svelte. @@ -50,8 +50,8 @@ const addBrowserCondition = (config) => { const browserConditionIndex = conditions.indexOf('browser') if ( - nodeConditionIndex >= 0 && - (nodeConditionIndex < browserConditionIndex || browserConditionIndex < 0) + nodeConditionIndex !== -1 && + (nodeConditionIndex < browserConditionIndex || browserConditionIndex === -1) ) { conditions.splice(nodeConditionIndex, 0, 'browser') } @@ -77,7 +77,9 @@ const addAutoCleanup = (config) => { setupFiles = [setupFiles] } - setupFiles.push(join(dirname(fileURLToPath(import.meta.url)), './vitest.js')) + setupFiles.push( + path.join(path.dirname(url.fileURLToPath(import.meta.url)), './vitest.js') + ) test.setupFiles = setupFiles config.test = test diff --git a/tests/_env.js b/tests/_env.js index f637d6f..96b30f4 100644 --- a/tests/_env.js +++ b/tests/_env.js @@ -1,6 +1,6 @@ import { VERSION as SVELTE_VERSION } from 'svelte/compiler' -export const IS_JSDOM = window.navigator.userAgent.includes('jsdom') +export const IS_JSDOM = globalThis.navigator.userAgent.includes('jsdom') export const IS_HAPPYDOM = !IS_JSDOM // right now it's happy or js diff --git a/tests/act.test.js b/tests/act.test.js index 75c9ded..9308d75 100644 --- a/tests/act.test.js +++ b/tests/act.test.js @@ -1,14 +1,14 @@ import { setTimeout } from 'node:timers/promises' -import { act, render } from '@testing-library/svelte' +import { act, render, screen } from '@testing-library/svelte' import { describe, expect, test } from 'vitest' import Comp from './fixtures/Comp.svelte' describe('act', () => { test('state updates are flushed', async () => { - const { getByText } = render(Comp) - const button = getByText('Button') + render(Comp) + const button = screen.getByText('Button') expect(button).toHaveTextContent('Button') @@ -20,8 +20,8 @@ describe('act', () => { }) test('accepts async functions', async () => { - const { getByText } = render(Comp) - const button = getByText('Button') + render(Comp) + const button = screen.getByText('Button') await act(async () => { await setTimeout(100) diff --git a/tests/context.test.js b/tests/context.test.js index da54b9d..e9d83eb 100644 --- a/tests/context.test.js +++ b/tests/context.test.js @@ -1,4 +1,4 @@ -import { render } from '@testing-library/svelte' +import { render, screen } from '@testing-library/svelte' import { expect, test } from 'vitest' import Comp from './fixtures/Context.svelte' @@ -6,9 +6,9 @@ import Comp from './fixtures/Context.svelte' test('can set a context', () => { const message = 'Got it' - const { getByText } = render(Comp, { + render(Comp, { context: new Map(Object.entries({ foo: { message } })), }) - expect(getByText(message)).toBeTruthy() + expect(screen.getByText(message)).toBeInTheDocument() }) diff --git a/tests/events.test.js b/tests/events.test.js index e0aba4d..9864692 100644 --- a/tests/events.test.js +++ b/tests/events.test.js @@ -1,12 +1,12 @@ -import { fireEvent, render } from '@testing-library/svelte' +import { fireEvent, render, screen } from '@testing-library/svelte' import { describe, expect, test } from 'vitest' import Comp from './fixtures/Comp.svelte' describe('events', () => { test('state changes are flushed after firing an event', async () => { - const { getByText } = render(Comp, { props: { name: 'World' } }) - const button = getByText('Button') + render(Comp, { props: { name: 'World' } }) + const button = screen.getByText('Button') const result = fireEvent.click(button) @@ -15,8 +15,8 @@ describe('events', () => { }) test('calling `fireEvent` directly works too', async () => { - const { getByText } = render(Comp, { props: { name: 'World' } }) - const button = getByText('Button') + render(Comp, { props: { name: 'World' } }) + const button = screen.getByText('Button') const result = fireEvent( button, diff --git a/tests/fixtures/Typed.svelte b/tests/fixtures/Typed.svelte index 383214f..dad8e14 100644 --- a/tests/fixtures/Typed.svelte +++ b/tests/fixtures/Typed.svelte @@ -4,7 +4,7 @@ export let name: string export let count: number - export const hello: string = 'hello' + export const hello = 'hello' const dispatch = createEventDispatcher<{ greeting: string }>() diff --git a/tests/fixtures/TypedRunes.svelte b/tests/fixtures/TypedRunes.svelte index 979be41..0fb690b 100644 --- a/tests/fixtures/TypedRunes.svelte +++ b/tests/fixtures/TypedRunes.svelte @@ -1,7 +1,7 @@

hello {name}

diff --git a/tests/render.test.js b/tests/render.test.js index 943c920..19bf2c3 100644 --- a/tests/render.test.js +++ b/tests/render.test.js @@ -1,4 +1,4 @@ -import { render } from '@testing-library/svelte' +import { render, screen } from '@testing-library/svelte' import { beforeAll, describe, expect, test } from 'vitest' import { COMPONENT_FIXTURES } from './_env.js' @@ -12,14 +12,14 @@ describe.each(COMPONENT_FIXTURES)('render ($mode)', ({ component }) => { }) test('renders component into the document', () => { - const { getByText } = render(Comp, { props }) + render(Comp, { props }) - expect(getByText('Hello World!')).toBeInTheDocument() + expect(screen.getByText('Hello World!')).toBeInTheDocument() }) test('accepts props directly', () => { - const { getByText } = render(Comp, props) - expect(getByText('Hello World!')).toBeInTheDocument() + render(Comp, props) + expect(screen.getByText('Hello World!')).toBeInTheDocument() }) test('throws error when mixing svelte component options and props', () => { @@ -35,8 +35,8 @@ describe.each(COMPONENT_FIXTURES)('render ($mode)', ({ component }) => { }) test('should return a container object wrapping the DOM of the rendered component', () => { - const { container, getByTestId } = render(Comp, props) - const firstElement = getByTestId('test') + const { container } = render(Comp, props) + const firstElement = screen.getByTestId('test') expect(container.firstChild).toBe(firstElement) }) @@ -73,17 +73,14 @@ describe.each(COMPONENT_FIXTURES)('render ($mode)', ({ component }) => { const baseElement = document.body const target = document.createElement('section') const anchor = document.createElement('div') - baseElement.appendChild(target) - target.appendChild(anchor) + baseElement.append(target) + target.append(anchor) - const { getByTestId } = render( - Comp, - { props, target, anchor }, - { baseElement } - ) - const firstElement = getByTestId('test') + render(Comp, { props, target, anchor }, { baseElement }) + const firstElement = screen.getByTestId('test') expect(target.firstChild).toBe(firstElement) + // eslint-disable-next-line testing-library/no-node-access expect(target.lastChild).toBe(anchor) }) }) diff --git a/tests/rerender.test.js b/tests/rerender.test.js index 2a20143..2fdff0d 100644 --- a/tests/rerender.test.js +++ b/tests/rerender.test.js @@ -39,8 +39,8 @@ describe.each(COMPONENT_FIXTURES)('rerender ($mode)', ({ mode, component }) => { ? { name: 'World' } : { accessors: true, props: { name: 'World' } } - const { component, getByText } = render(Comp, componentOptions) - const element = getByText('Hello World!') + const { component } = render(Comp, componentOptions) + const element = screen.getByText('Hello World!') expect(element).toBeInTheDocument() expect(component.name).toBe('World') diff --git a/tests/transition.test.js b/tests/transition.test.js index 3acf1d0..27b236e 100644 --- a/tests/transition.test.js +++ b/tests/transition.test.js @@ -6,12 +6,13 @@ import { IS_JSDOM, IS_SVELTE_5 } from './_env.js' import Transitioner from './fixtures/Transitioner.svelte' describe.skipIf(IS_SVELTE_5)('transitions', () => { - beforeEach(() => { - if (!IS_JSDOM) return - - const raf = (fn) => setTimeout(() => fn(new Date()), 16) - vi.stubGlobal('requestAnimationFrame', raf) - }) + if (IS_JSDOM) { + beforeEach(() => { + vi.stubGlobal('requestAnimationFrame', (fn) => + setTimeout(() => fn(new Date()), 16) + ) + }) + } test('on:introend', async () => { const user = userEvent.setup()