diff --git a/.eslintrc.cjs b/.eslintrc.cjs index f403dca..91f6920 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -3,15 +3,9 @@ module.exports = { env: { browser: true, es6: true, - 'vitest-globals/env': true, }, - extends: [ - 'standard', - 'plugin:vitest-globals/recommended', - 'plugin:svelte/recommended', - 'prettier', - ], - plugins: ['svelte', 'simple-import-sort', 'json-files'], + extends: ['standard', 'plugin:svelte/recommended', 'prettier'], + plugins: ['svelte', 'simple-import-sort'], rules: { 'simple-import-sort/imports': 'error', 'simple-import-sort/exports': 'error', @@ -37,20 +31,12 @@ module.exports = { 'plugin:@typescript-eslint/stylistic', 'prettier', ], - rules: { - '@typescript-eslint/ban-types': [ - 'error', - { types: { '{}': false }, extendDefaults: true }, - ], - '@typescript-eslint/no-explicit-any': 'off', - 'import/export': 'off', - }, }, ], parserOptions: { ecmaVersion: 2022, sourceType: 'module', }, - globals: { $state: 'readonly', $props: 'readonly' }, + globals: { afterEach: 'readonly', $state: 'readonly', $props: 'readonly' }, ignorePatterns: ['!/.*'], } diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d4a16b5..c9eb8a7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -29,6 +29,8 @@ updates: versions: ['>=9'] - dependency-name: 'eslint-plugin-n' versions: ['>=17'] + - dependency-name: 'eslint-plugin-promise' + versions: ['>=7'] # Update GitHub Actions dependencies - package-ecosystem: 'github-actions' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b7ca01..8b2ac4a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,9 +17,8 @@ jobs: main: # ignore all-contributors PRs if: ${{ !contains(github.head_ref, 'all-contributors') }} - name: Node ${{ matrix.node }}, Svelte ${{ matrix.svelte }}, ${{ matrix.test-runner }} + name: Node ${{ matrix.node }}, Svelte ${{ matrix.svelte }}, ${{ matrix.check }} runs-on: ubuntu-latest - continue-on-error: ${{ matrix.experimental }} # enable OIDC for codecov uploads permissions: @@ -30,21 +29,17 @@ jobs: matrix: node: ['16', '18', '20'] svelte: ['3', '4'] - test-runner: ['vitest:jsdom', 'vitest:happy-dom', 'jest'] - experimental: [false] + check: ['test:vitest:jsdom', 'test:vitest:happy-dom', 'test:jest'] include: - - node: '20' - svelte: 'next' - test-runner: 'vitest:jsdom' - experimental: true - - node: '20' - svelte: 'next' - test-runner: 'vitest:happy-dom' - experimental: true - - node: '20' - svelte: 'next' - test-runner: 'jest' - experimental: true + # We only need to lint once, so do it on latest Node and Svelte + - { node: '20', svelte: '4', check: 'lint' } + # `SvelteComponent` is not generic in Svelte 3, so type-checking only passes in >= 4 + - { node: '20', svelte: '4', check: 'types:legacy' } + - { node: '20', svelte: 'next', check: 'types' } + # Only run Svelte 5 checks on latest Node + - { node: '20', svelte: 'next', check: 'test:vitest:jsdom' } + - { node: '20', svelte: 'next', check: 'test:vitest:happy-dom' } + - { node: '20', svelte: 'next', check: 'test:jest' } steps: - name: ⬇️ Checkout repo @@ -60,15 +55,11 @@ jobs: npm install --no-package-lock npm install --no-save svelte@${{ matrix.svelte }} - - name: ▶️ Run tests - run: npm run test:${{ matrix.test-runner }} - - - name: ▶️ Run type-checks - # NOTE: `SvelteComponent` is not generic in Svelte v3, so type-checking will not pass - if: ${{ matrix.node == '20' && matrix.svelte != '3' && matrix.test-runner == 'vitest:jsdom' }} - run: npm run types + - name: ▶️ Run ${{ matrix.check }} + run: npm run ${{ matrix.check }} - name: ⬆️ Upload coverage report + if: ${{ startsWith(matrix.check, 'test:') }} uses: codecov/codecov-action@v4 with: use_oidc: true diff --git a/package.json b/package.json index c4924f5..e536ce0 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "!__tests__" ], "scripts": { + "all": "npm-run-all contributors:generate toc format types build test:vitest:* test:jest", "toc": "doctoc README.md", "lint": "prettier . --check && eslint .", "lint:delta": "npm-run-all -p prettier:delta eslint:delta", @@ -62,13 +63,14 @@ "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 validate", + "setup": "npm install && npm run all", "test": "vitest run --coverage", "test:watch": "vitest", "test:vitest:jsdom": "vitest run --coverage --environment jsdom", "test:vitest:happy-dom": "vitest run --coverage --environment happy-dom", "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", "contributors:add": "all-contributors add", @@ -96,34 +98,32 @@ "@sveltejs/vite-plugin-svelte": "^3.1.1", "@testing-library/jest-dom": "^6.3.0", "@testing-library/user-event": "^14.5.2", - "@typescript-eslint/eslint-plugin": "7.15.0", - "@typescript-eslint/parser": "7.15.0", - "@vitest/coverage-v8": "^1.5.2", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", + "@vitest/coverage-v8": "^2.0.2", "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-json-files": "^4.1.0", - "eslint-plugin-n": "16.6.2", - "eslint-plugin-promise": "6.4.0", - "eslint-plugin-simple-import-sort": "12.1.0", - "eslint-plugin-svelte": "2.41.0", - "eslint-plugin-vitest-globals": "1.5.0", - "expect-type": "^0.19.0", - "happy-dom": "^14.7.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-plugin-simple-import-sort": "^12.1.1", + "eslint-plugin-svelte": "^2.42.0", + "expect-type": "^0.20.0", + "happy-dom": "^15.7.3", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "jsdom": "^24.0.0", + "jsdom": "^25.0.0", "npm-run-all": "^4.1.5", - "prettier": "3.3.2", - "prettier-plugin-svelte": "3.2.5", + "prettier": "^3.3.3", + "prettier-plugin-svelte": "^3.2.5", "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0", - "svelte-check": "^3.6.3", + "svelte-check": "^3.8.4", "svelte-jester": "^5.0.0", - "typescript": "^5.3.3", - "vite": "^5.1.1", - "vitest": "^1.5.2" + "typescript": "^5.5.3", + "vite": "^5.3.3", + "vitest": "^2.0.2" } } diff --git a/src/__tests__/fixtures/Mounter.svelte b/src/__tests__/fixtures/Mounter.svelte index 27205dd..68f72f9 100644 --- a/src/__tests__/fixtures/Mounter.svelte +++ b/src/__tests__/fixtures/Mounter.svelte @@ -16,4 +16,4 @@ }) - + diff --git a/src/__tests__/fixtures/Simple.svelte b/src/__tests__/fixtures/Typed.svelte similarity index 76% rename from src/__tests__/fixtures/Simple.svelte rename to src/__tests__/fixtures/Typed.svelte index c9c2f15..44960ab 100644 --- a/src/__tests__/fixtures/Simple.svelte +++ b/src/__tests__/fixtures/Typed.svelte @@ -1,6 +1,8 @@
count: {count}
diff --git a/src/__tests__/render-runes.test-d.ts b/src/__tests__/render-runes.test-d.ts new file mode 100644 index 0000000..2d0c69f --- /dev/null +++ b/src/__tests__/render-runes.test-d.ts @@ -0,0 +1,39 @@ +import { expectTypeOf } from 'expect-type' +import { describe, test } from 'vitest' + +import * as subject from '../index.js' +import Component from './fixtures/TypedRunes.svelte' + +describe('types', () => { + test('render is a function that accepts a Svelte component', () => { + subject.render(Component, { name: 'Alice', count: 42 }) + subject.render(Component, { props: { name: 'Alice', count: 42 } }) + }) + + test('rerender is a function that accepts partial props', async () => { + const { rerender } = subject.render(Component, { name: 'Alice', count: 42 }) + + await rerender({ name: 'Bob' }) + await rerender({ count: 0 }) + }) + + test('invalid prop types are rejected', () => { + // @ts-expect-error: name should be a string + subject.render(Component, { name: 42 }) + + // @ts-expect-error: name should be a string + subject.render(Component, { props: { name: 42 } }) + }) + + test('render result has container and component', () => { + const result = subject.render(Component, { name: 'Alice', count: 42 }) + + expectTypeOf(result).toMatchTypeOf<{ + container: HTMLElement + component: { hello: string } + debug: (el?: HTMLElement) => void + rerender: (props: { name?: string; count?: number }) => Promise= IS_MODERN_SVELTE extends true + ? Svelte.Component
| Svelte.SvelteComponent
+ : Svelte.SvelteComponent
+
+/**
+ * The type of an imported, compiled Svelte component.
+ *
+ * In Svelte 4, this was the Svelte component class' type.
+ * In Svelte 5, this distinction no longer matters.
+ */
+export type ComponentType
@@ -48,10 +48,10 @@ const componentCache = new Set()
/**
* Render a component into the document.
*
- * @template {import('svelte').SvelteComponent} C
+ * @template {import('./component-types.js').Component} C
* @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries]
*
- * @param {import('svelte').ComponentType
} renderOptions - Customize how Testing Library sets up the document and binds queries.
* @returns {RenderResult