Skip to content

Commit a8f21f8

Browse files
authored
fix(types): build types from JS source (#376)
1 parent be82df1 commit a8f21f8

File tree

12 files changed

+172
-122
lines changed

12 files changed

+172
-122
lines changed

.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ scripts/*
33
.prettierignore
44
.github/workflows/*
55
*.md
6+
types

.eslintrc.cjs

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ module.exports = {
2626
rules: {
2727
'no-undef-init': 'off',
2828
'prefer-const': 'off',
29+
'svelte/no-unused-svelte-ignore': 'off',
2930
},
3031
},
3132
{

.github/workflows/release.yml

+32-2
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,38 @@ jobs:
5959
run: npm run test:${{ matrix.test-runner }}
6060

6161
- name: ▶️ Run type-checks
62-
if: ${{ matrix.node == '20' && matrix.svelte == '4' && matrix.test-runner == 'vitest:jsdom' }}
62+
# NOTE: `SvelteComponent` is not generic in Svelte v3, so type-checking will not pass
63+
if: ${{ matrix.node == '20' && matrix.svelte != '3' && matrix.test-runner == 'vitest:jsdom' }}
6364
run: npm run types
6465

6566
- name: ⬆️ Upload coverage report
6667
uses: codecov/codecov-action@v3
6768

69+
build:
70+
runs-on: ubuntu-latest
71+
steps:
72+
- name: ⬇️ Checkout repo
73+
uses: actions/checkout@v4
74+
75+
- name: ⎔ Setup node
76+
uses: actions/setup-node@v4
77+
with:
78+
node-version: 20
79+
80+
- name: 📥 Download deps
81+
run: npm install --no-package-lock
82+
83+
- name: 🏗️ Build types
84+
run: npm run build
85+
86+
- name: ⬆️ Upload types build
87+
uses: actions/upload-artifact@v4
88+
with:
89+
name: types
90+
path: types
91+
6892
release:
69-
needs: main
93+
needs: [main, build]
7094
runs-on: ubuntu-latest
7195
if: ${{ github.repository == 'testing-library/svelte-testing-library' &&
7296
contains('refs/heads/main,refs/heads/next', github.ref) &&
@@ -80,6 +104,12 @@ jobs:
80104
with:
81105
node-version: 20
82106

107+
- name: 📥 Downloads types build
108+
uses: actions/download-artifact@v4
109+
with:
110+
name: types
111+
path: types
112+
83113
- name: 🚀 Release
84114
uses: cycjimmy/semantic-release-action@v4
85115
with:

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ dist
99
yarn-error.log
1010
package-lock.json
1111
yarn.lock
12+
13+
# generated typing output
14+
types

package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"default": "./src/index.js"
1414
},
1515
"./vitest": {
16+
"types": "./types/vitest.d.ts",
1617
"default": "./src/vitest.js"
1718
},
1819
"./vite": {
@@ -26,7 +27,7 @@
2627
"homepage": "https://github.com/testing-library/svelte-testing-library#readme",
2728
"repository": {
2829
"type": "git",
29-
"url": "https://github.com/testing-library/svelte-testing-library"
30+
"url": "git+https://github.com/testing-library/svelte-testing-library.git"
3031
},
3132
"bugs": {
3233
"url": "https://github.com/testing-library/svelte-testing-library/issues"
@@ -49,7 +50,6 @@
4950
"files": [
5051
"src",
5152
"types",
52-
"!*.test-d.ts",
5353
"!__tests__"
5454
],
5555
"scripts": {
@@ -69,7 +69,8 @@
6969
"test:vitest:happy-dom": "vitest run --coverage --environment happy-dom",
7070
"test:jest": "npx --node-options=\"--experimental-vm-modules --no-warnings\" jest --coverage",
7171
"types": "svelte-check",
72-
"validate": "npm-run-all test:vitest:* test:jest types",
72+
"validate": "npm-run-all test:vitest:* test:jest types build",
73+
"build": "tsc -p tsconfig.build.json",
7374
"contributors:add": "all-contributors add",
7475
"contributors:generate": "all-contributors generate",
7576
"preview-release": "./scripts/preview-release"

types/types.test-d.ts src/__tests__/types.test-d.ts

+34-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { expectTypeOf } from 'expect-type'
22
import type { ComponentProps, SvelteComponent } from 'svelte'
33
import { describe, test } from 'vitest'
44

5-
import Simple from '../src/__tests__/fixtures/Simple.svelte'
6-
import * as subject from './index.js'
5+
import * as subject from '../index.js'
6+
import Simple from './fixtures/Simple.svelte'
77

88
describe('types', () => {
99
test('render is a function that accepts a Svelte component', () => {
@@ -62,4 +62,36 @@ describe('types', () => {
6262

6363
expectTypeOf(result.getByVibes).parameters.toMatchTypeOf<[vibes: string]>()
6464
})
65+
66+
test('act is an async function', () => {
67+
expectTypeOf(subject.act).toMatchTypeOf<() => Promise<void>>()
68+
})
69+
70+
test('act accepts a sync function', () => {
71+
expectTypeOf(subject.act).toMatchTypeOf<(fn: () => void) => Promise<void>>()
72+
})
73+
74+
test('act accepts an async function', () => {
75+
expectTypeOf(subject.act).toMatchTypeOf<
76+
(fn: () => Promise<void>) => Promise<void>
77+
>()
78+
})
79+
80+
test('fireEvent is an async function', () => {
81+
expectTypeOf(subject.fireEvent).toMatchTypeOf<
82+
(
83+
element: Element | Node | Document | Window,
84+
event: Event
85+
) => Promise<boolean>
86+
>()
87+
})
88+
89+
test('fireEvent[eventName] is an async function', () => {
90+
expectTypeOf(subject.fireEvent.click).toMatchTypeOf<
91+
(
92+
element: Element | Node | Document | Window,
93+
options?: {}
94+
) => Promise<boolean>
95+
>()
96+
})
6597
})

src/index.js

+4-8
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,7 @@ if (typeof afterEach === 'function' && !process.env.STL_SKIP_AUTO_CLEANUP) {
1616
export * from '@testing-library/dom'
1717

1818
// export svelte-specific functions and custom `fireEvent`
19-
// `fireEvent` must be a named export to take priority over wildcard export above
20-
export {
21-
act,
22-
cleanup,
23-
fireEvent,
24-
render,
25-
UnknownSvelteOptionsError,
26-
} from './pure.js'
19+
export { UnknownSvelteOptionsError } from './core/index.js'
20+
export * from './pure.js'
21+
// `fireEvent` must be named to take priority over wildcard from @testing-library/dom
22+
export { fireEvent } from './pure.js'

src/pure.js

+79-12
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,61 @@
11
import {
2-
fireEvent as dtlFireEvent,
2+
fireEvent as baseFireEvent,
33
getQueriesForElement,
44
prettyDOM,
55
} from '@testing-library/dom'
66
import { tick } from 'svelte'
77

8-
import {
9-
mount,
10-
UnknownSvelteOptionsError,
11-
unmount,
12-
updateProps,
13-
validateOptions,
14-
} from './core/index.js'
8+
import { mount, unmount, updateProps, validateOptions } from './core/index.js'
159

1610
const targetCache = new Set()
1711
const componentCache = new Set()
1812

13+
/**
14+
* Customize how Svelte renders the component.
15+
*
16+
* @template {import('svelte').SvelteComponent} C
17+
* @typedef {import('svelte').ComponentProps<C> | Partial<import('svelte').ComponentConstructorOptions<import('svelte').ComponentProps<C>>>} SvelteComponentOptions
18+
*/
19+
20+
/**
21+
* Customize how Testing Library sets up the document and binds queries.
22+
*
23+
* @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries]
24+
* @typedef {{
25+
* baseElement?: HTMLElement
26+
* queries?: Q
27+
* }} RenderOptions
28+
*/
29+
30+
/**
31+
* The rendered component and bound testing functions.
32+
*
33+
* @template {import('svelte').SvelteComponent} C
34+
* @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries]
35+
*
36+
* @typedef {{
37+
* container: HTMLElement
38+
* baseElement: HTMLElement
39+
* component: C
40+
* debug: (el?: HTMLElement | DocumentFragment) => void
41+
* rerender: (props: Partial<import('svelte').ComponentProps<C>>) => Promise<void>
42+
* unmount: () => void
43+
* } & {
44+
* [P in keyof Q]: import('@testing-library/dom').BoundFunction<Q[P]>
45+
* }} RenderResult
46+
*/
47+
48+
/**
49+
* Render a component into the document.
50+
*
51+
* @template {import('svelte').SvelteComponent} C
52+
* @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries]
53+
*
54+
* @param {import('svelte').ComponentType<C>} Component - The component to render.
55+
* @param {SvelteComponentOptions<C>} options - Customize how Svelte renders the component.
56+
* @param {RenderOptions<Q>} renderOptions - Customize how Testing Library sets up the document and binds queries.
57+
* @returns {RenderResult<C, Q>} The rendered component and bound testing functions.
58+
*/
1959
const render = (Component, options = {}, renderOptions = {}) => {
2060
options = validateOptions(options)
2161

@@ -62,6 +102,7 @@ const render = (Component, options = {}, renderOptions = {}) => {
62102
}
63103
}
64104

105+
/** Remove a component from the component cache. */
65106
const cleanupComponent = (component) => {
66107
const inCache = componentCache.delete(component)
67108

@@ -70,6 +111,7 @@ const cleanupComponent = (component) => {
70111
}
71112
}
72113

114+
/** Remove a target element from the target cache. */
73115
const cleanupTarget = (target) => {
74116
const inCache = targetCache.delete(target)
75117

@@ -78,30 +120,55 @@ const cleanupTarget = (target) => {
78120
}
79121
}
80122

123+
/** Unmount all components and remove elements added to `<body>`. */
81124
const cleanup = () => {
82125
componentCache.forEach(cleanupComponent)
83126
targetCache.forEach(cleanupTarget)
84127
}
85128

129+
/**
130+
* Call a function and wait for Svelte to flush pending changes.
131+
*
132+
* @param {() => unknown} [fn] - A function, which may be `async`, to call before flushing updates.
133+
* @returns {Promise<void>}
134+
*/
86135
const act = async (fn) => {
87136
if (fn) {
88137
await fn()
89138
}
90139
return tick()
91140
}
92141

142+
/**
143+
* @typedef {(...args: Parameters<import('@testing-library/dom').FireFunction>) => Promise<ReturnType<import('@testing-library/dom').FireFunction>>} FireFunction
144+
*/
145+
146+
/**
147+
* @typedef {{
148+
* [K in import('@testing-library/dom').EventType]: (...args: Parameters<import('@testing-library/dom').FireObject[K]>) => Promise<ReturnType<import('@testing-library/dom').FireObject[K]>>
149+
* }} FireObject
150+
*/
151+
152+
/**
153+
* Fire an event on an element.
154+
*
155+
* Consider using `@testing-library/user-event` instead, if possible.
156+
* @see https://testing-library.com/docs/user-event/intro/
157+
*
158+
* @type {FireFunction & FireObject}
159+
*/
93160
const fireEvent = async (...args) => {
94-
const event = dtlFireEvent(...args)
161+
const event = baseFireEvent(...args)
95162
await tick()
96163
return event
97164
}
98165

99-
Object.keys(dtlFireEvent).forEach((key) => {
166+
Object.keys(baseFireEvent).forEach((key) => {
100167
fireEvent[key] = async (...args) => {
101-
const event = dtlFireEvent[key](...args)
168+
const event = baseFireEvent[key](...args)
102169
await tick()
103170
return event
104171
}
105172
})
106173

107-
export { act, cleanup, fireEvent, render, UnknownSvelteOptionsError }
174+
export { act, cleanup, fireEvent, render }

tsconfig.build.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"extends": ["./tsconfig.json"],
3+
"compilerOptions": {
4+
"declaration": true,
5+
"declarationMap": true,
6+
"emitDeclarationOnly": true,
7+
"noEmit": false,
8+
"rootDir": "src",
9+
"outDir": "types"
10+
},
11+
"exclude": ["src/**/__tests__/**"]
12+
}

tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
{
22
"compilerOptions": {
33
"module": "node16",
4+
"allowJs": true,
45
"noEmit": true,
56
"skipLibCheck": true,
67
"strict": true,
78
"types": ["svelte", "vite/client", "vitest", "vitest/globals"]
89
},
9-
"include": ["src", "types"]
10+
"include": ["src"]
1011
}

0 commit comments

Comments
 (0)