Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 6f45a96

Browse files
authored
fix(svelte5): update typings to support new component types (#400)
1 parent 2cf781c commit 6f45a96

File tree

11 files changed

+154
-47
lines changed

11 files changed

+154
-47
lines changed

‎.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
# We only need to lint once, so do it on latest Node and Svelte
3535
- { node: '20', svelte: '4', check: 'lint' }
3636
# `SvelteComponent` is not generic in Svelte 3, so type-checking only passes in >= 4
37-
- { node: '20', svelte: '4', check: 'types' }
37+
- { node: '20', svelte: '4', check: 'types:legacy' }
3838
- { node: '20', svelte: 'next', check: 'types' }
3939
# Only run Svelte 5 checks on latest Node
4040
- { node: '20', svelte: 'next', check: 'test:vitest:jsdom' }

‎package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"test:vitest:happy-dom": "vitest run --coverage --environment happy-dom",
7171
"test:jest": "npx --node-options=\"--experimental-vm-modules --no-warnings\" jest --coverage",
7272
"types": "svelte-check",
73+
"types:legacy": "svelte-check --tsconfig tsconfig.legacy.json",
7374
"validate": "npm-run-all test:vitest:* test:jest types build",
7475
"build": "tsc -p tsconfig.build.json",
7576
"contributors:add": "all-contributors add",

‎src/__tests__/fixtures/Mounter.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@
1616
})
1717
</script>
1818
19-
<button></button>
19+
<button>click me</button>

‎src/__tests__/fixtures/Simple.svelte renamed to ‎src/__tests__/fixtures/Typed.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<script lang="ts">
22
export let name: string
33
export let count: number
4+
5+
export const hello: string = 'hello'
46
</script>
57

68
<h1>hello {name}</h1>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script lang="ts">
2+
const { name, count }: { name: string; count: number } = $props()
3+
4+
export const hello: string = 'hello'
5+
</script>
6+
7+
<h1>hello {name}</h1>
8+
<p>count: {count}</p>

‎src/__tests__/render-runes.test-d.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { expectTypeOf } from 'expect-type'
2+
import { describe, test } from 'vitest'
3+
4+
import * as subject from '../index.js'
5+
import Component from './fixtures/TypedRunes.svelte'
6+
7+
describe('types', () => {
8+
test('render is a function that accepts a Svelte component', () => {
9+
subject.render(Component, { name: 'Alice', count: 42 })
10+
subject.render(Component, { props: { name: 'Alice', count: 42 } })
11+
})
12+
13+
test('rerender is a function that accepts partial props', async () => {
14+
const { rerender } = subject.render(Component, { name: 'Alice', count: 42 })
15+
16+
await rerender({ name: 'Bob' })
17+
await rerender({ count: 0 })
18+
})
19+
20+
test('invalid prop types are rejected', () => {
21+
// @ts-expect-error: name should be a string
22+
subject.render(Component, { name: 42 })
23+
24+
// @ts-expect-error: name should be a string
25+
subject.render(Component, { props: { name: 42 } })
26+
})
27+
28+
test('render result has container and component', () => {
29+
const result = subject.render(Component, { name: 'Alice', count: 42 })
30+
31+
expectTypeOf(result).toMatchTypeOf<{
32+
container: HTMLElement
33+
component: { hello: string }
34+
debug: (el?: HTMLElement) => void
35+
rerender: (props: { name?: string; count?: number }) => Promise<void>
36+
unmount: () => void
37+
}>()
38+
})
39+
})

‎src/__tests__/types.test-d.ts renamed to ‎src/__tests__/render-utilities.test-d.ts

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,12 @@
11
import { expectTypeOf } from 'expect-type'
2-
import type { ComponentProps, SvelteComponent } from 'svelte'
32
import { describe, test } from 'vitest'
43

54
import * as subject from '../index.js'
6-
import Simple from './fixtures/Simple.svelte'
7-
8-
describe('types', () => {
9-
test('render is a function that accepts a Svelte component', () => {
10-
subject.render(Simple, { name: 'Alice', count: 42 })
11-
subject.render(Simple, { props: { name: 'Alice', count: 42 } })
12-
})
13-
14-
test('rerender is a function that accepts partial props', async () => {
15-
const { rerender } = subject.render(Simple, { name: 'Alice', count: 42 })
16-
17-
await rerender({ name: 'Bob' })
18-
await rerender({ count: 0 })
19-
})
20-
21-
test('invalid prop types are rejected', () => {
22-
// @ts-expect-error: name should be a string
23-
subject.render(Simple, { name: 42 })
24-
25-
// @ts-expect-error: name should be a string
26-
subject.render(Simple, { props: { name: 42 } })
27-
})
28-
29-
test('render result has container and component', () => {
30-
const result = subject.render(Simple, { name: 'Alice', count: 42 })
31-
32-
expectTypeOf(result).toMatchTypeOf<{
33-
container: HTMLElement
34-
component: SvelteComponent<{ name: string }>
35-
debug: (el?: HTMLElement) => void
36-
rerender: (props: Partial<ComponentProps<Simple>>) => Promise<void>
37-
unmount: () => void
38-
}>()
39-
})
5+
import Component from './fixtures/Comp.svelte'
406

7+
describe('render query and utility types', () => {
418
test('render result has default queries', () => {
42-
const result = subject.render(Simple, { name: 'Alice',count: 42 })
9+
const result = subject.render(Component, { name: 'Alice' })
4310

4411
expectTypeOf(result.getByRole).parameters.toMatchTypeOf<
4512
[role: subject.ByRoleMatcher, options?: subject.ByRoleOptions]
@@ -55,8 +22,8 @@ describe('types', () => {
5522
() => ''
5623
)
5724
const result = subject.render(
58-
Simple,
59-
{ name: 'Alice',count: 42 },
25+
Component,
26+
{ name: 'Alice' },
6027
{ queries: { getByVibes } }
6128
)
6229

‎src/__tests__/render.test-d.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { expectTypeOf } from 'expect-type'
2+
import { describe, test } from 'vitest'
3+
4+
import * as subject from '../index.js'
5+
import Component from './fixtures/Typed.svelte'
6+
7+
describe('types', () => {
8+
test('render is a function that accepts a Svelte component', () => {
9+
subject.render(Component, { name: 'Alice', count: 42 })
10+
subject.render(Component, { props: { name: 'Alice', count: 42 } })
11+
})
12+
13+
test('rerender is a function that accepts partial props', async () => {
14+
const { rerender } = subject.render(Component, { name: 'Alice', count: 42 })
15+
16+
await rerender({ name: 'Bob' })
17+
await rerender({ count: 0 })
18+
})
19+
20+
test('invalid prop types are rejected', () => {
21+
// @ts-expect-error: name should be a string
22+
subject.render(Component, { name: 42 })
23+
24+
// @ts-expect-error: name should be a string
25+
subject.render(Component, { props: { name: 42 } })
26+
})
27+
28+
test('render result has container and component', () => {
29+
const result = subject.render(Component, { name: 'Alice', count: 42 })
30+
31+
expectTypeOf(result).toMatchTypeOf<{
32+
container: HTMLElement
33+
component: { hello: string }
34+
debug: (el?: HTMLElement) => void
35+
rerender: (props: { name?: string; count?: number }) => Promise<void>
36+
unmount: () => void
37+
}>()
38+
})
39+
})

‎src/component-types.d.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type * as Svelte from 'svelte'
2+
3+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4+
type IS_MODERN_SVELTE = any extends Svelte.Component ? false : true
5+
6+
/** A compiled, imported Svelte component. */
7+
export type Component<P> = IS_MODERN_SVELTE extends true
8+
? Svelte.Component<P> | Svelte.SvelteComponent<P>
9+
: Svelte.SvelteComponent<P>
10+
11+
/**
12+
* The type of an imported, compiled Svelte component.
13+
*
14+
* In Svelte 4, this was the Svelte component class' type.
15+
* In Svelte 5, this distinction no longer matters.
16+
*/
17+
export type ComponentType<C> = C extends Svelte.SvelteComponent
18+
? Svelte.ComponentType<C>
19+
: C
20+
21+
/** The props of a component. */
22+
export type Props<C> = Svelte.ComponentProps<C>
23+
24+
/**
25+
* The exported fields of a component.
26+
*
27+
* In Svelte 4, this is simply the instance of the component class.
28+
* In Svelte 5, this is the set of variables marked as `export`'d.
29+
*/
30+
export type Exports<C> = C extends Svelte.SvelteComponent
31+
? C
32+
: C extends Svelte.Component<unknown, infer E>
33+
? E
34+
: never
35+
36+
/**
37+
* Options that may be passed to `mount` when rendering the component.
38+
*
39+
* In Svelte 4, these are the options passed to the component constructor.
40+
*/
41+
export type MountOptions<C> = IS_MODERN_SVELTE extends true
42+
? Parameters<typeof Svelte.mount<Props<C>, Exports<C>>>[1]
43+
: Svelte.ComponentConstructorOptions<Props<C>>

‎src/pure.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ const componentCache = new Set()
1313
/**
1414
* Customize how Svelte renders the component.
1515
*
16-
* @template {import('svelte').SvelteComponent} C
17-
* @typedef {import('svelte').ComponentProps<C> | Partial<import('svelte').ComponentConstructorOptions<import('svelte').ComponentProps<C>>>} SvelteComponentOptions
16+
* @template {import('./component-types.js').Component} C
17+
* @typedef {import('./component-types.js').Props<C> | Partial<import('./component-types.js').MountOptions<C>>} SvelteComponentOptions
1818
*/
1919

2020
/**
@@ -30,15 +30,15 @@ const componentCache = new Set()
3030
/**
3131
* The rendered component and bound testing functions.
3232
*
33-
* @template {import('svelte').SvelteComponent} C
33+
* @template {import('./component-types.js').Component} C
3434
* @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries]
3535
*
3636
* @typedef {{
3737
* container: HTMLElement
3838
* baseElement: HTMLElement
39-
* component: C
39+
* component: import('./component-types.js').Exports<C>
4040
* debug: (el?: HTMLElement | DocumentFragment) => void
41-
* rerender: (props: Partial<import('svelte').ComponentProps<C>>) => Promise<void>
41+
* rerender: (props: Partial<import('./component-types.js').Props<C>>) => Promise<void>
4242
* unmount: () => void
4343
* } & {
4444
* [P in keyof Q]: import('@testing-library/dom').BoundFunction<Q[P]>
@@ -48,10 +48,10 @@ const componentCache = new Set()
4848
/**
4949
* Render a component into the document.
5050
*
51-
* @template {import('svelte').SvelteComponent} C
51+
* @template {import('./component-types.js').Component} C
5252
* @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries]
5353
*
54-
* @param {import('svelte').ComponentType<C>} Component - The component to render.
54+
* @param {import('./component-types.js').ComponentType<C>} Component - The component to render.
5555
* @param {SvelteComponentOptions<C>} options - Customize how Svelte renders the component.
5656
* @param {RenderOptions<Q>} renderOptions - Customize how Testing Library sets up the document and binds queries.
5757
* @returns {RenderResult<C, Q>} The rendered component and bound testing functions.

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /