diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9dbf325..8b2ac4a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -34,7 +34,7 @@ jobs:
# 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' }
+ - { 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' }
diff --git a/package.json b/package.json
index 3fbcde7..e536ce0 100644
--- a/package.json
+++ b/package.json
@@ -70,6 +70,7 @@
"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",
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..62b6986 100644
--- a/src/__tests__/fixtures/Simple.svelte
+++ b/src/__tests__/fixtures/Typed.svelte
@@ -1,6 +1,8 @@
hello {name}
diff --git a/src/__tests__/fixtures/TypedRunes.svelte b/src/__tests__/fixtures/TypedRunes.svelte
new file mode 100644
index 0000000..83ccd97
--- /dev/null
+++ b/src/__tests__/fixtures/TypedRunes.svelte
@@ -0,0 +1,8 @@
+
+
+hello {name}
+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
+ unmount: () => void
+ }>()
+ })
+})
diff --git a/src/__tests__/types.test-d.ts b/src/__tests__/render-utilities.test-d.ts
similarity index 55%
rename from src/__tests__/types.test-d.ts
rename to src/__tests__/render-utilities.test-d.ts
index 077bbcd..b45614e 100644
--- a/src/__tests__/types.test-d.ts
+++ b/src/__tests__/render-utilities.test-d.ts
@@ -1,45 +1,12 @@
import { expectTypeOf } from 'expect-type'
-import type { ComponentProps, SvelteComponent } from 'svelte'
import { describe, test } from 'vitest'
import * as subject from '../index.js'
-import Simple from './fixtures/Simple.svelte'
-
-describe('types', () => {
- test('render is a function that accepts a Svelte component', () => {
- subject.render(Simple, { name: 'Alice', count: 42 })
- subject.render(Simple, { props: { name: 'Alice', count: 42 } })
- })
-
- test('rerender is a function that accepts partial props', async () => {
- const { rerender } = subject.render(Simple, { 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(Simple, { name: 42 })
-
- // @ts-expect-error: name should be a string
- subject.render(Simple, { props: { name: 42 } })
- })
-
- test('render result has container and component', () => {
- const result = subject.render(Simple, { name: 'Alice', count: 42 })
-
- expectTypeOf(result).toMatchTypeOf<{
- container: HTMLElement
- component: SvelteComponent<{ name: string }>
- debug: (el?: HTMLElement) => void
- rerender: (props: Partial>) => Promise
- unmount: () => void
- }>()
- })
+import Component from './fixtures/Comp.svelte'
+describe('render query and utility types', () => {
test('render result has default queries', () => {
- const result = subject.render(Simple, { name: 'Alice', count: 42 })
+ const result = subject.render(Component, { name: 'Alice' })
expectTypeOf(result.getByRole).parameters.toMatchTypeOf<
[role: subject.ByRoleMatcher, options?: subject.ByRoleOptions]
@@ -55,8 +22,8 @@ describe('types', () => {
() => ''
)
const result = subject.render(
- Simple,
- { name: 'Alice', count: 42 },
+ Component,
+ { name: 'Alice' },
{ queries: { getByVibes } }
)
diff --git a/src/__tests__/render.test-d.ts b/src/__tests__/render.test-d.ts
new file mode 100644
index 0000000..f8d1d90
--- /dev/null
+++ b/src/__tests__/render.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/Typed.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
+ unmount: () => void
+ }>()
+ })
+})
diff --git a/src/component-types.d.ts b/src/component-types.d.ts
new file mode 100644
index 0000000..42deccc
--- /dev/null
+++ b/src/component-types.d.ts
@@ -0,0 +1,23 @@
+import type * as Svelte from 'svelte'
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export type Component = any extends Svelte.Component
+ ? Svelte.SvelteComponent
+ : Svelte.Component
| Svelte.SvelteComponent
+
+export type ComponentType = C extends Svelte.SvelteComponent
+ ? Svelte.ComponentType
+ : C
+
+export type Props = Svelte.ComponentProps
+
+export type Exports = C extends Svelte.SvelteComponent
+ ? C
+ : C extends Svelte.Component
+ ? E
+ : never
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export type MountOptions = any extends Svelte.mount
+ ? Svelte.ComponentConstructorOptions>
+ : Parameters, Exports>>[1]
diff --git a/src/pure.js b/src/pure.js
index edb94b3..875f87c 100644
--- a/src/pure.js
+++ b/src/pure.js
@@ -13,8 +13,8 @@ const componentCache = new Set()
/**
* Customize how Svelte renders the component.
*
- * @template {import('svelte').SvelteComponent} C
- * @typedef {import('svelte').ComponentProps | Partial>>} SvelteComponentOptions
+ * @template {import('./component-types.js').Component} C
+ * @typedef {import('./component-types.js').Props | Partial>} SvelteComponentOptions
*/
/**
@@ -30,15 +30,15 @@ const componentCache = new Set()
/**
* The rendered component and bound testing functions.
*
- * @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]
*
* @typedef {{
* container: HTMLElement
* baseElement: HTMLElement
- * component: C
+ * component: import('./component-types.js').Exports
* debug: (el?: HTMLElement | DocumentFragment) => void
- * rerender: (props: Partial>) => Promise
+ * rerender: (props: Partial>) => Promise
* unmount: () => void
* } & {
* [P in keyof Q]: import('@testing-library/dom').BoundFunction
@@ -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} Component - The component to render.
+ * @param {import('./component-types.js').ComponentType} Component - The component to render.
* @param {SvelteComponentOptions} options - Customize how Svelte renders the component.
* @param {RenderOptions} renderOptions - Customize how Testing Library sets up the document and binds queries.
* @returns {RenderResult} The rendered component and bound testing functions.
diff --git a/tsconfig.legacy.json b/tsconfig.legacy.json
new file mode 100644
index 0000000..b5d9e57
--- /dev/null
+++ b/tsconfig.legacy.json
@@ -0,0 +1,8 @@
+{
+ "extends": ["./tsconfig.json"],
+ "exclude": [
+ "src/__tests__/render-runes.test-d.ts",
+ "src/__tests__/fixtures/CompRunes.svelte",
+ "src/__tests__/fixtures/TypedRunes.svelte"
+ ]
+}