@testing-library/svelte 5.0.1 → 5.2.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -14
- package/package.json +43 -23
- package/src/core/index.js +27 -0
- package/src/core/legacy.js +46 -0
- package/src/core/modern.svelte.js +50 -0
- package/src/core/validate-options.js +39 -0
- package/src/index.js +8 -3
- package/src/pure.js +75 -125
- package/src/vite.js +75 -0
- package/types/vite.d.ts +12 -0
- package/src/__tests__/__snapshots__/auto-cleanup-skip.test.js.snap +0 -3
- package/src/__tests__/_vitest-setup.js +0 -2
- package/src/__tests__/act.test.js +0 -33
- package/src/__tests__/auto-cleanup-skip.test.js +0 -23
- package/src/__tests__/auto-cleanup.test.js +0 -31
- package/src/__tests__/cleanup.test.js +0 -35
- package/src/__tests__/context.test.js +0 -14
- package/src/__tests__/debug.test.js +0 -18
- package/src/__tests__/events.test.js +0 -32
- package/src/__tests__/fixtures/Comp.svelte +0 -17
- package/src/__tests__/fixtures/Context.svelte +0 -7
- package/src/__tests__/fixtures/Mounter.svelte +0 -19
- package/src/__tests__/fixtures/Simple.svelte +0 -7
- package/src/__tests__/fixtures/Transitioner.svelte +0 -18
- package/src/__tests__/mount.test.js +0 -33
- package/src/__tests__/multi-base.test.js +0 -42
- package/src/__tests__/render.test.js +0 -85
- package/src/__tests__/rerender.test.js +0 -50
- package/src/__tests__/transition.test.js +0 -31
- package/src/__tests__/utils.js +0 -7
- package/src/svelte5-index.js +0 -23
- package/src/svelte5.js +0 -30
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<p>Simple and complete Svelte testing utilities that encourage good testing practices.</p>
|
|
14
14
|
|
|
15
15
|
[**Read The Docs**](https://testing-library.com/docs/svelte-testing-library/intro) |
|
|
16
|
-
[Edit the docs](https://github.com/
|
|
16
|
+
[Edit the docs](https://github.com/testing-library/testing-library-docs)
|
|
17
17
|
|
|
18
18
|
<!-- prettier-ignore-start -->
|
|
19
19
|
[![Build Status][build-badge]][build]
|
|
@@ -71,29 +71,36 @@ primary guiding principle is:
|
|
|
71
71
|
This module is distributed via [npm][npm] which is bundled with [node][node] and
|
|
72
72
|
should be installed as one of your project's `devDependencies`:
|
|
73
73
|
|
|
74
|
-
```
|
|
74
|
+
```shell
|
|
75
75
|
npm install --save-dev @testing-library/svelte
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
This library
|
|
78
|
+
This library supports `svelte` versions `3`, `4`, and `5`.
|
|
79
79
|
|
|
80
80
|
You may also be interested in installing `@testing-library/jest-dom` so you can use
|
|
81
81
|
[the custom jest matchers](https://github.com/testing-library/jest-dom).
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
## Setup
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
import from `@testing-library/svelte/svelte5` instead of `@testing-library/svelte`, or have your `vite.config.js` contains the following alias:
|
|
85
|
+
We recommend using `@testing-library/svelte` with [Vitest][] as your test runner. To get started, add the `svelteTesting` plugin to your Vite or Vitest config.
|
|
87
86
|
|
|
87
|
+
```diff
|
|
88
|
+
// vite.config.js
|
|
89
|
+
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
90
|
+
+ import { svelteTesting } from '@testing-library/svelte/vite'
|
|
91
|
+
|
|
92
|
+
export default defineConfig({
|
|
93
|
+
plugins: [
|
|
94
|
+
svelte(),
|
|
95
|
+
+ svelteTesting(),
|
|
96
|
+
]
|
|
97
|
+
});
|
|
88
98
|
```
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
},
|
|
95
|
-
}))
|
|
96
|
-
```
|
|
99
|
+
|
|
100
|
+
See the [setup docs][] for more detailed setup instructions, including for other test runners like Jest.
|
|
101
|
+
|
|
102
|
+
[vitest]: https://vitest.dev/
|
|
103
|
+
[setup docs]: https://testing-library.com/docs/svelte-testing-library/setup
|
|
97
104
|
|
|
98
105
|
## Docs
|
|
99
106
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testing-library/svelte",
|
|
3
|
-
"version": "5.0.1",
|
|
3
|
+
"version": "5.2.0-next.1",
|
|
4
4
|
"description": "Simple and complete Svelte testing utilities that encourage good testing practices.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -10,10 +10,14 @@
|
|
|
10
10
|
},
|
|
11
11
|
"./svelte5": {
|
|
12
12
|
"types": "./types/index.d.ts",
|
|
13
|
-
"default": "./src/
|
|
13
|
+
"default": "./src/index.js"
|
|
14
14
|
},
|
|
15
15
|
"./vitest": {
|
|
16
16
|
"default": "./src/vitest.js"
|
|
17
|
+
},
|
|
18
|
+
"./vite": {
|
|
19
|
+
"types": "./types/vite.d.ts",
|
|
20
|
+
"default": "./src/vite.js"
|
|
17
21
|
}
|
|
18
22
|
},
|
|
19
23
|
"type": "module",
|
|
@@ -43,8 +47,10 @@
|
|
|
43
47
|
"e2e"
|
|
44
48
|
],
|
|
45
49
|
"files": [
|
|
46
|
-
"src
|
|
47
|
-
"types
|
|
50
|
+
"src",
|
|
51
|
+
"types",
|
|
52
|
+
"!*.test-d.ts",
|
|
53
|
+
"!__tests__"
|
|
48
54
|
],
|
|
49
55
|
"scripts": {
|
|
50
56
|
"toc": "doctoc README.md",
|
|
@@ -59,50 +65,64 @@
|
|
|
59
65
|
"setup": "npm install && npm run validate",
|
|
60
66
|
"test": "vitest run --coverage",
|
|
61
67
|
"test:watch": "vitest",
|
|
62
|
-
"test:update": "vitest run --update",
|
|
63
68
|
"test:vitest:jsdom": "vitest run --coverage --environment jsdom",
|
|
64
69
|
"test:vitest:happy-dom": "vitest run --coverage --environment happy-dom",
|
|
70
|
+
"test:jest": "npx --node-options=\"--experimental-vm-modules --no-warnings\" jest --coverage",
|
|
65
71
|
"types": "svelte-check",
|
|
66
|
-
"validate": "npm-run-all test:vitest:* types",
|
|
72
|
+
"validate": "npm-run-all test:vitest:* test:jest types",
|
|
67
73
|
"contributors:add": "all-contributors add",
|
|
68
|
-
"contributors:generate": "all-contributors generate"
|
|
74
|
+
"contributors:generate": "all-contributors generate",
|
|
75
|
+
"preview-release": "./scripts/preview-release"
|
|
69
76
|
},
|
|
70
77
|
"peerDependencies": {
|
|
71
|
-
"svelte": "^3 || ^4 || ^5"
|
|
78
|
+
"svelte": "^3 || ^4 || ^5",
|
|
79
|
+
"vite": "*",
|
|
80
|
+
"vitest": "*"
|
|
81
|
+
},
|
|
82
|
+
"peerDependenciesMeta": {
|
|
83
|
+
"vite": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"vitest": {
|
|
87
|
+
"optional": true
|
|
88
|
+
}
|
|
72
89
|
},
|
|
73
90
|
"dependencies": {
|
|
74
|
-
"@testing-library/dom": "^
|
|
91
|
+
"@testing-library/dom": "^10.0.0"
|
|
75
92
|
},
|
|
76
93
|
"devDependencies": {
|
|
77
|
-
"@
|
|
94
|
+
"@jest/globals": "^29.7.0",
|
|
95
|
+
"@sveltejs/vite-plugin-svelte": "^3.1.1",
|
|
78
96
|
"@testing-library/jest-dom": "^6.3.0",
|
|
79
97
|
"@testing-library/user-event": "^14.5.2",
|
|
80
|
-
"@typescript-eslint/eslint-plugin": "
|
|
81
|
-
"@typescript-eslint/parser": "
|
|
82
|
-
"@vitest/coverage-v8": "^
|
|
98
|
+
"@typescript-eslint/eslint-plugin": "7.8.0",
|
|
99
|
+
"@typescript-eslint/parser": "7.8.0",
|
|
100
|
+
"@vitest/coverage-v8": "^1.5.2",
|
|
83
101
|
"all-contributors-cli": "^6.26.1",
|
|
84
102
|
"doctoc": "^2.2.1",
|
|
85
|
-
"eslint": "8.
|
|
103
|
+
"eslint": "8.57.0",
|
|
86
104
|
"eslint-config-prettier": "9.1.0",
|
|
87
105
|
"eslint-config-standard": "17.1.0",
|
|
88
106
|
"eslint-plugin-import": "2.29.1",
|
|
89
107
|
"eslint-plugin-json-files": "^4.1.0",
|
|
90
108
|
"eslint-plugin-n": "16.6.2",
|
|
91
109
|
"eslint-plugin-promise": "6.1.1",
|
|
92
|
-
"eslint-plugin-simple-import-sort": "
|
|
93
|
-
"eslint-plugin-svelte": "2.
|
|
94
|
-
"eslint-plugin-vitest-globals": "1.
|
|
95
|
-
"expect-type": "^0.
|
|
110
|
+
"eslint-plugin-simple-import-sort": "12.1.0",
|
|
111
|
+
"eslint-plugin-svelte": "2.38.0",
|
|
112
|
+
"eslint-plugin-vitest-globals": "1.5.0",
|
|
113
|
+
"expect-type": "^0.19.0",
|
|
96
114
|
"happy-dom": "^14.7.1",
|
|
97
|
-
"
|
|
115
|
+
"jest": "^29.7.0",
|
|
116
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
117
|
+
"jsdom": "^24.0.0",
|
|
98
118
|
"npm-run-all": "^4.1.5",
|
|
99
|
-
"prettier": "3.2.
|
|
100
|
-
"prettier-plugin-svelte": "3.
|
|
119
|
+
"prettier": "3.2.5",
|
|
120
|
+
"prettier-plugin-svelte": "3.2.3",
|
|
101
121
|
"svelte": "^3 || ^4 || ^5",
|
|
102
122
|
"svelte-check": "^3.6.3",
|
|
103
|
-
"svelte-jester": "^
|
|
123
|
+
"svelte-jester": "^5.0.0",
|
|
104
124
|
"typescript": "^5.3.3",
|
|
105
125
|
"vite": "^5.1.1",
|
|
106
|
-
"vitest": "^
|
|
126
|
+
"vitest": "^1.5.2"
|
|
107
127
|
}
|
|
108
128
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rendering core for svelte-testing-library.
|
|
3
|
+
*
|
|
4
|
+
* Defines how components are added to and removed from the DOM.
|
|
5
|
+
* Will switch to legacy, class-based mounting logic
|
|
6
|
+
* if it looks like we're in a Svelte <= 4 environment.
|
|
7
|
+
*/
|
|
8
|
+
import * as LegacyCore from './legacy.js'
|
|
9
|
+
import * as ModernCore from './modern.svelte.js'
|
|
10
|
+
import {
|
|
11
|
+
createValidateOptions,
|
|
12
|
+
UnknownSvelteOptionsError,
|
|
13
|
+
} from './validate-options.js'
|
|
14
|
+
|
|
15
|
+
const { mount, unmount, updateProps, allowedOptions } =
|
|
16
|
+
ModernCore.IS_MODERN_SVELTE ? ModernCore : LegacyCore
|
|
17
|
+
|
|
18
|
+
/** Validate component options. */
|
|
19
|
+
const validateOptions = createValidateOptions(allowedOptions)
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
mount,
|
|
23
|
+
UnknownSvelteOptionsError,
|
|
24
|
+
unmount,
|
|
25
|
+
updateProps,
|
|
26
|
+
validateOptions,
|
|
27
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legacy rendering core for svelte-testing-library.
|
|
3
|
+
*
|
|
4
|
+
* Supports Svelte <= 4.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Allowed options for the component constructor. */
|
|
8
|
+
const allowedOptions = [
|
|
9
|
+
'target',
|
|
10
|
+
'accessors',
|
|
11
|
+
'anchor',
|
|
12
|
+
'props',
|
|
13
|
+
'hydrate',
|
|
14
|
+
'intro',
|
|
15
|
+
'context',
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Mount the component into the DOM.
|
|
20
|
+
*
|
|
21
|
+
* The `onDestroy` callback is included for strict backwards compatibility
|
|
22
|
+
* with previous versions of this library. It's mostly unnecessary logic.
|
|
23
|
+
*/
|
|
24
|
+
const mount = (Component, options, onDestroy) => {
|
|
25
|
+
const component = new Component(options)
|
|
26
|
+
|
|
27
|
+
if (typeof onDestroy === 'function') {
|
|
28
|
+
component.$$.on_destroy.push(() => {
|
|
29
|
+
onDestroy(component)
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return component
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Remove the component from the DOM. */
|
|
37
|
+
const unmount = (component) => {
|
|
38
|
+
component.$destroy()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Update the component's props. */
|
|
42
|
+
const updateProps = (component, nextProps) => {
|
|
43
|
+
component.$set(nextProps)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { allowedOptions, mount, unmount, updateProps }
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modern rendering core for svelte-testing-library.
|
|
3
|
+
*
|
|
4
|
+
* Supports Svelte >= 5.
|
|
5
|
+
*/
|
|
6
|
+
import * as Svelte from 'svelte'
|
|
7
|
+
|
|
8
|
+
/** Props signals for each rendered component. */
|
|
9
|
+
const propsByComponent = new Map()
|
|
10
|
+
|
|
11
|
+
/** Whether we're using Svelte >= 5. */
|
|
12
|
+
const IS_MODERN_SVELTE = typeof Svelte.mount === 'function'
|
|
13
|
+
|
|
14
|
+
/** Allowed options to the `mount` call. */
|
|
15
|
+
const allowedOptions = [
|
|
16
|
+
'target',
|
|
17
|
+
'anchor',
|
|
18
|
+
'props',
|
|
19
|
+
'events',
|
|
20
|
+
'context',
|
|
21
|
+
'intro',
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
/** Mount the component into the DOM. */
|
|
25
|
+
const mount = (Component, options) => {
|
|
26
|
+
const props = $state(options.props ?? {})
|
|
27
|
+
const component = Svelte.mount(Component, { ...options, props })
|
|
28
|
+
|
|
29
|
+
propsByComponent.set(component, props)
|
|
30
|
+
|
|
31
|
+
return component
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Remove the component from the DOM. */
|
|
35
|
+
const unmount = (component) => {
|
|
36
|
+
propsByComponent.delete(component)
|
|
37
|
+
Svelte.unmount(component)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Update the component's props.
|
|
42
|
+
*
|
|
43
|
+
* Relies on the `$state` signal added in `mount`.
|
|
44
|
+
*/
|
|
45
|
+
const updateProps = (component, nextProps) => {
|
|
46
|
+
const prevProps = propsByComponent.get(component)
|
|
47
|
+
Object.assign(prevProps, nextProps)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { allowedOptions, IS_MODERN_SVELTE, mount, unmount, updateProps }
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class UnknownSvelteOptionsError extends TypeError {
|
|
2
|
+
constructor(unknownOptions, allowedOptions) {
|
|
3
|
+
super(`Unknown options.
|
|
4
|
+
|
|
5
|
+
Unknown: [ ${unknownOptions.join(', ')} ]
|
|
6
|
+
Allowed: [ ${allowedOptions.join(', ')} ]
|
|
7
|
+
|
|
8
|
+
To pass both Svelte options and props to a component,
|
|
9
|
+
or to use props that share a name with a Svelte option,
|
|
10
|
+
you must place all your props under the \`props\` key:
|
|
11
|
+
|
|
12
|
+
render(Component, { props: { /** props here **/ } })
|
|
13
|
+
`)
|
|
14
|
+
this.name = 'UnknownSvelteOptionsError'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const createValidateOptions = (allowedOptions) => (options) => {
|
|
19
|
+
const isProps = !Object.keys(options).some((option) =>
|
|
20
|
+
allowedOptions.includes(option)
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if (isProps) {
|
|
24
|
+
return { props: options }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Check if any props and Svelte options were accidentally mixed.
|
|
28
|
+
const unknownOptions = Object.keys(options).filter(
|
|
29
|
+
(option) => !allowedOptions.includes(option)
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
if (unknownOptions.length > 0) {
|
|
33
|
+
throw new UnknownSvelteOptionsError(unknownOptions, allowedOptions)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return options
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { createValidateOptions, UnknownSvelteOptionsError }
|
package/src/index.js
CHANGED
|
@@ -4,8 +4,7 @@ import { act, cleanup } from './pure.js'
|
|
|
4
4
|
// If we're running in a test runner that supports afterEach
|
|
5
5
|
// then we'll automatically run cleanup afterEach test
|
|
6
6
|
// this ensures that tests run in isolation from each other
|
|
7
|
-
// if you don't like this then
|
|
8
|
-
// or set the STL_SKIP_AUTO_CLEANUP env variable to 'true'.
|
|
7
|
+
// if you don't like this then set the STL_SKIP_AUTO_CLEANUP env variable.
|
|
9
8
|
if (typeof afterEach === 'function' && !process.env.STL_SKIP_AUTO_CLEANUP) {
|
|
10
9
|
afterEach(async () => {
|
|
11
10
|
await act()
|
|
@@ -18,4 +17,10 @@ export * from '@testing-library/dom'
|
|
|
18
17
|
|
|
19
18
|
// export svelte-specific functions and custom `fireEvent`
|
|
20
19
|
// `fireEvent` must be a named export to take priority over wildcard export above
|
|
21
|
-
export {
|
|
20
|
+
export {
|
|
21
|
+
act,
|
|
22
|
+
cleanup,
|
|
23
|
+
fireEvent,
|
|
24
|
+
render,
|
|
25
|
+
UnknownSvelteOptionsError,
|
|
26
|
+
} from './pure.js'
|
package/src/pure.js
CHANGED
|
@@ -3,155 +3,105 @@ import {
|
|
|
3
3
|
getQueriesForElement,
|
|
4
4
|
prettyDOM,
|
|
5
5
|
} from '@testing-library/dom'
|
|
6
|
-
import
|
|
7
|
-
import { VERSION as SVELTE_VERSION } from 'svelte/compiler'
|
|
8
|
-
|
|
9
|
-
const IS_SVELTE_5 = /^5\./.test(SVELTE_VERSION)
|
|
10
|
-
|
|
11
|
-
export class SvelteTestingLibrary {
|
|
12
|
-
svelteComponentOptions = [
|
|
13
|
-
'target',
|
|
14
|
-
'accessors',
|
|
15
|
-
'anchor',
|
|
16
|
-
'props',
|
|
17
|
-
'hydrate',
|
|
18
|
-
'intro',
|
|
19
|
-
'context',
|
|
20
|
-
]
|
|
21
|
-
|
|
22
|
-
targetCache = new Set()
|
|
23
|
-
componentCache = new Set()
|
|
24
|
-
|
|
25
|
-
checkProps(options) {
|
|
26
|
-
const isProps = !Object.keys(options).some((option) =>
|
|
27
|
-
this.svelteComponentOptions.includes(option)
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
// Check if any props and Svelte options were accidentally mixed.
|
|
31
|
-
if (!isProps) {
|
|
32
|
-
const unrecognizedOptions = Object.keys(options).filter(
|
|
33
|
-
(option) => !this.svelteComponentOptions.includes(option)
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
if (unrecognizedOptions.length > 0) {
|
|
37
|
-
throw Error(`
|
|
38
|
-
Unknown options were found [${unrecognizedOptions}]. This might happen if you've mixed
|
|
39
|
-
passing in props with Svelte options into the render function. Valid Svelte options
|
|
40
|
-
are [${this.svelteComponentOptions}]. You can either change the prop names, or pass in your
|
|
41
|
-
props for that component via the \`props\` option.\n\n
|
|
42
|
-
Eg: const { /** Results **/ } = render(MyComponent, { props: { /** props here **/ } })\n\n
|
|
43
|
-
`)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return options
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return { props: options }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
render(Component, componentOptions = {}, renderOptions = {}) {
|
|
53
|
-
componentOptions = this.checkProps(componentOptions)
|
|
54
|
-
|
|
55
|
-
const baseElement =
|
|
56
|
-
renderOptions.baseElement ?? componentOptions.target ?? document.body
|
|
57
|
-
|
|
58
|
-
const target =
|
|
59
|
-
componentOptions.target ??
|
|
60
|
-
baseElement.appendChild(document.createElement('div'))
|
|
61
|
-
|
|
62
|
-
this.targetCache.add(target)
|
|
63
|
-
|
|
64
|
-
const ComponentConstructor = Component.default || Component
|
|
65
|
-
|
|
66
|
-
const component = this.renderComponent(ComponentConstructor, {
|
|
67
|
-
...componentOptions,
|
|
68
|
-
target,
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
baseElement,
|
|
73
|
-
component,
|
|
74
|
-
container: target,
|
|
75
|
-
debug: (el = baseElement) => console.log(prettyDOM(el)),
|
|
76
|
-
rerender: async (props) => {
|
|
77
|
-
if (props.props) {
|
|
78
|
-
console.warn(
|
|
79
|
-
'rerender({ props: {...} }) deprecated, use rerender({...}) instead'
|
|
80
|
-
)
|
|
81
|
-
props = props.props
|
|
82
|
-
}
|
|
83
|
-
component.$set(props)
|
|
84
|
-
await Svelte.tick()
|
|
85
|
-
},
|
|
86
|
-
unmount: () => {
|
|
87
|
-
this.cleanupComponent(component)
|
|
88
|
-
},
|
|
89
|
-
...getQueriesForElement(baseElement, renderOptions.queries),
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
renderComponent(ComponentConstructor, componentOptions) {
|
|
94
|
-
if (IS_SVELTE_5) {
|
|
95
|
-
throw new Error('for Svelte 5, use `@testing-library/svelte/svelte5`')
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const component = new ComponentConstructor(componentOptions)
|
|
6
|
+
import { tick } from 'svelte'
|
|
99
7
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
8
|
+
import {
|
|
9
|
+
mount,
|
|
10
|
+
UnknownSvelteOptionsError,
|
|
11
|
+
unmount,
|
|
12
|
+
updateProps,
|
|
13
|
+
validateOptions,
|
|
14
|
+
} from './core/index.js'
|
|
15
|
+
|
|
16
|
+
const targetCache = new Set()
|
|
17
|
+
const componentCache = new Set()
|
|
18
|
+
|
|
19
|
+
const render = (Component, options = {}, renderOptions = {}) => {
|
|
20
|
+
options = validateOptions(options)
|
|
21
|
+
|
|
22
|
+
const baseElement =
|
|
23
|
+
renderOptions.baseElement ?? options.target ?? document.body
|
|
24
|
+
|
|
25
|
+
const queries = getQueriesForElement(baseElement, renderOptions.queries)
|
|
26
|
+
|
|
27
|
+
const target =
|
|
28
|
+
options.target ?? baseElement.appendChild(document.createElement('div'))
|
|
29
|
+
|
|
30
|
+
targetCache.add(target)
|
|
31
|
+
|
|
32
|
+
const component = mount(
|
|
33
|
+
Component.default ?? Component,
|
|
34
|
+
{ ...options, target },
|
|
35
|
+
cleanupComponent
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
componentCache.add(component)
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
baseElement,
|
|
42
|
+
component,
|
|
43
|
+
container: target,
|
|
44
|
+
debug: (el = baseElement) => {
|
|
45
|
+
console.log(prettyDOM(el))
|
|
46
|
+
},
|
|
47
|
+
rerender: async (props) => {
|
|
48
|
+
if (props.props) {
|
|
49
|
+
console.warn(
|
|
50
|
+
'rerender({ props: {...} }) deprecated, use rerender({...}) instead'
|
|
51
|
+
)
|
|
52
|
+
props = props.props
|
|
53
|
+
}
|
|
106
54
|
|
|
107
|
-
|
|
55
|
+
updateProps(component, props)
|
|
56
|
+
await tick()
|
|
57
|
+
},
|
|
58
|
+
unmount: () => {
|
|
59
|
+
cleanupComponent(component)
|
|
60
|
+
},
|
|
61
|
+
...queries,
|
|
108
62
|
}
|
|
63
|
+
}
|
|
109
64
|
|
|
110
|
-
|
|
111
|
-
|
|
65
|
+
const cleanupComponent = (component) => {
|
|
66
|
+
const inCache = componentCache.delete(component)
|
|
112
67
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
68
|
+
if (inCache) {
|
|
69
|
+
unmount(component)
|
|
116
70
|
}
|
|
71
|
+
}
|
|
117
72
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (inCache && target.parentNode === document.body) {
|
|
122
|
-
document.body.removeChild(target)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
73
|
+
const cleanupTarget = (target) => {
|
|
74
|
+
const inCache = targetCache.delete(target)
|
|
125
75
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
this.targetCache.forEach(this.cleanupTarget.bind(this))
|
|
76
|
+
if (inCache && target.parentNode === document.body) {
|
|
77
|
+
document.body.removeChild(target)
|
|
129
78
|
}
|
|
130
79
|
}
|
|
131
80
|
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
export const cleanup = instance.cleanup.bind(instance)
|
|
81
|
+
const cleanup = () => {
|
|
82
|
+
componentCache.forEach(cleanupComponent)
|
|
83
|
+
targetCache.forEach(cleanupTarget)
|
|
84
|
+
}
|
|
137
85
|
|
|
138
|
-
|
|
86
|
+
const act = async (fn) => {
|
|
139
87
|
if (fn) {
|
|
140
88
|
await fn()
|
|
141
89
|
}
|
|
142
|
-
return
|
|
90
|
+
return tick()
|
|
143
91
|
}
|
|
144
92
|
|
|
145
|
-
|
|
93
|
+
const fireEvent = async (...args) => {
|
|
146
94
|
const event = dtlFireEvent(...args)
|
|
147
|
-
await
|
|
95
|
+
await tick()
|
|
148
96
|
return event
|
|
149
97
|
}
|
|
150
98
|
|
|
151
99
|
Object.keys(dtlFireEvent).forEach((key) => {
|
|
152
100
|
fireEvent[key] = async (...args) => {
|
|
153
101
|
const event = dtlFireEvent[key](...args)
|
|
154
|
-
await
|
|
102
|
+
await tick()
|
|
155
103
|
return event
|
|
156
104
|
}
|
|
157
105
|
})
|
|
106
|
+
|
|
107
|
+
export { act, cleanup, fireEvent, render, UnknownSvelteOptionsError }
|
package/src/vite.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { dirname, join } from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Vite plugin to configure @testing-library/svelte.
|
|
6
|
+
*
|
|
7
|
+
* Ensures Svelte is imported correctly in tests
|
|
8
|
+
* and that the DOM is cleaned up after each test.
|
|
9
|
+
*
|
|
10
|
+
* @param {{resolveBrowser?: boolean, autoCleanup?: boolean}} options
|
|
11
|
+
* @returns {import('vite').Plugin}
|
|
12
|
+
*/
|
|
13
|
+
export const svelteTesting = ({
|
|
14
|
+
resolveBrowser = true,
|
|
15
|
+
autoCleanup = true,
|
|
16
|
+
} = {}) => ({
|
|
17
|
+
name: 'vite-plugin-svelte-testing-library',
|
|
18
|
+
config: (config) => {
|
|
19
|
+
if (!process.env.VITEST) {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (resolveBrowser) {
|
|
24
|
+
addBrowserCondition(config)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (autoCleanup) {
|
|
28
|
+
addAutoCleanup(config)
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Add `browser` to `resolve.conditions` before `node`.
|
|
35
|
+
*
|
|
36
|
+
* This ensures that Svelte's browser code is used in tests,
|
|
37
|
+
* rather than its SSR code.
|
|
38
|
+
*
|
|
39
|
+
* @param {import('vitest/config').UserConfig} config
|
|
40
|
+
*/
|
|
41
|
+
const addBrowserCondition = (config) => {
|
|
42
|
+
const resolve = config.resolve ?? {}
|
|
43
|
+
const conditions = resolve.conditions ?? []
|
|
44
|
+
const nodeConditionIndex = conditions.indexOf('node')
|
|
45
|
+
const browserConditionIndex = conditions.indexOf('browser')
|
|
46
|
+
|
|
47
|
+
if (
|
|
48
|
+
nodeConditionIndex >= 0 &&
|
|
49
|
+
(nodeConditionIndex < browserConditionIndex || browserConditionIndex < 0)
|
|
50
|
+
) {
|
|
51
|
+
conditions.splice(nodeConditionIndex, 0, 'browser')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
resolve.conditions = conditions
|
|
55
|
+
config.resolve = resolve
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Add auto-cleanup file to Vitest's setup files.
|
|
60
|
+
*
|
|
61
|
+
* @param {import('vitest/config').UserConfig} config
|
|
62
|
+
*/
|
|
63
|
+
const addAutoCleanup = (config) => {
|
|
64
|
+
const test = config.test ?? {}
|
|
65
|
+
let setupFiles = test.setupFiles ?? []
|
|
66
|
+
|
|
67
|
+
if (typeof setupFiles === 'string') {
|
|
68
|
+
setupFiles = [setupFiles]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setupFiles.push(join(dirname(fileURLToPath(import.meta.url)), './vitest.js'))
|
|
72
|
+
|
|
73
|
+
test.setupFiles = setupFiles
|
|
74
|
+
config.test = test
|
|
75
|
+
}
|
package/types/vite.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Plugin } from 'vite'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Vite plugin to configure @testing-library/svelte.
|
|
5
|
+
*
|
|
6
|
+
* Ensures Svelte is imported correctly in tests
|
|
7
|
+
* and that the DOM is cleaned up after each test.
|
|
8
|
+
*/
|
|
9
|
+
export function svelteTesting(options?: {
|
|
10
|
+
resolveBrowser?: boolean
|
|
11
|
+
autoCleanup?: boolean
|
|
12
|
+
}): Plugin
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { setTimeout } from 'node:timers/promises'
|
|
2
|
-
|
|
3
|
-
import { act, render } from '@testing-library/svelte'
|
|
4
|
-
import { describe, expect, test } from 'vitest'
|
|
5
|
-
|
|
6
|
-
import Comp from './fixtures/Comp.svelte'
|
|
7
|
-
|
|
8
|
-
describe('act', () => {
|
|
9
|
-
test('state updates are flushed', async () => {
|
|
10
|
-
const { getByText } = render(Comp)
|
|
11
|
-
const button = getByText('Button')
|
|
12
|
-
|
|
13
|
-
expect(button).toHaveTextContent('Button')
|
|
14
|
-
|
|
15
|
-
await act(() => {
|
|
16
|
-
button.click()
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
expect(button).toHaveTextContent('Button Clicked')
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
test('accepts async functions', async () => {
|
|
23
|
-
const { getByText } = render(Comp)
|
|
24
|
-
const button = getByText('Button')
|
|
25
|
-
|
|
26
|
-
await act(async () => {
|
|
27
|
-
await setTimeout(100)
|
|
28
|
-
button.click()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
expect(button).toHaveTextContent('Button Clicked')
|
|
32
|
-
})
|
|
33
|
-
})
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { beforeAll, describe, expect, test } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import Comp from './fixtures/Comp.svelte'
|
|
4
|
-
|
|
5
|
-
describe('auto-cleanup-skip', () => {
|
|
6
|
-
let render
|
|
7
|
-
|
|
8
|
-
beforeAll(async () => {
|
|
9
|
-
process.env.STL_SKIP_AUTO_CLEANUP = 'true'
|
|
10
|
-
const stl = await import('@testing-library/svelte')
|
|
11
|
-
render = stl.render
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
// This one verifies that if STL_SKIP_AUTO_CLEANUP is set
|
|
15
|
-
// then we DON'T auto-wire up the afterEach for folks
|
|
16
|
-
test('first', () => {
|
|
17
|
-
render(Comp, { props: { name: 'world' } })
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
test('second', () => {
|
|
21
|
-
expect(document.body.innerHTML).toMatchSnapshot()
|
|
22
|
-
})
|
|
23
|
-
})
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { render } from '@testing-library/svelte'
|
|
2
|
-
import { describe, expect, test } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import Comp from './fixtures/Comp.svelte'
|
|
5
|
-
|
|
6
|
-
describe('auto-cleanup', () => {
|
|
7
|
-
// This just verifies that by importing STL in an
|
|
8
|
-
// environment which supports afterEach (like jest)
|
|
9
|
-
// we'll get automatic cleanup between tests.
|
|
10
|
-
test('first', () => {
|
|
11
|
-
render(Comp, { props: { name: 'world' } })
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
test('second', () => {
|
|
15
|
-
expect(document.body.innerHTML).toEqual('')
|
|
16
|
-
})
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
describe('cleanup of two components', () => {
|
|
20
|
-
// This just verifies that by importing STL in an
|
|
21
|
-
// environment which supports afterEach (like jest)
|
|
22
|
-
// we'll get automatic cleanup between tests.
|
|
23
|
-
test('first', () => {
|
|
24
|
-
render(Comp, { props: { name: 'world' } })
|
|
25
|
-
render(Comp, { props: { name: 'universe' } })
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
test('second', () => {
|
|
29
|
-
expect(document.body.innerHTML).toEqual('')
|
|
30
|
-
})
|
|
31
|
-
})
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { cleanup, render } from '@testing-library/svelte'
|
|
2
|
-
import { describe, expect, test, vi } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import Mounter from './fixtures/Mounter.svelte'
|
|
5
|
-
|
|
6
|
-
const onExecuted = vi.fn()
|
|
7
|
-
const onDestroyed = vi.fn()
|
|
8
|
-
const renderSubject = () => render(Mounter, { onExecuted, onDestroyed })
|
|
9
|
-
|
|
10
|
-
describe('cleanup', () => {
|
|
11
|
-
test('cleanup deletes element', async () => {
|
|
12
|
-
renderSubject()
|
|
13
|
-
cleanup()
|
|
14
|
-
|
|
15
|
-
expect(document.body).toBeEmptyDOMElement()
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
test('cleanup unmounts component', () => {
|
|
19
|
-
renderSubject()
|
|
20
|
-
cleanup()
|
|
21
|
-
|
|
22
|
-
expect(onDestroyed).toHaveBeenCalledOnce()
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
test('cleanup handles unexpected errors during mount', () => {
|
|
26
|
-
onExecuted.mockImplementation(() => {
|
|
27
|
-
throw new Error('oh no!')
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
expect(renderSubject).toThrowError()
|
|
31
|
-
cleanup()
|
|
32
|
-
|
|
33
|
-
expect(document.body).toBeEmptyDOMElement()
|
|
34
|
-
})
|
|
35
|
-
})
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { render } from '@testing-library/svelte'
|
|
2
|
-
import { expect, test } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import Comp from './fixtures/Context.svelte'
|
|
5
|
-
|
|
6
|
-
test('can set a context', () => {
|
|
7
|
-
const message = 'Got it'
|
|
8
|
-
|
|
9
|
-
const { getByText } = render(Comp, {
|
|
10
|
-
context: new Map(Object.entries({ foo: { message } })),
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
expect(getByText(message)).toBeTruthy()
|
|
14
|
-
})
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { prettyDOM } from '@testing-library/dom'
|
|
2
|
-
import { render } from '@testing-library/svelte'
|
|
3
|
-
import { describe, expect, test, vi } from 'vitest'
|
|
4
|
-
|
|
5
|
-
import Comp from './fixtures/Comp.svelte'
|
|
6
|
-
|
|
7
|
-
describe('debug', () => {
|
|
8
|
-
test('pretty prints the base element', () => {
|
|
9
|
-
vi.stubGlobal('console', { log: vi.fn(), warn: vi.fn(), error: vi.fn() })
|
|
10
|
-
|
|
11
|
-
const { baseElement, debug } = render(Comp, { props: { name: 'world' } })
|
|
12
|
-
|
|
13
|
-
debug()
|
|
14
|
-
|
|
15
|
-
expect(console.log).toHaveBeenCalledTimes(1)
|
|
16
|
-
expect(console.log).toHaveBeenCalledWith(prettyDOM(baseElement))
|
|
17
|
-
})
|
|
18
|
-
})
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { fireEvent, render } from '@testing-library/svelte'
|
|
2
|
-
import { describe, expect, test } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import Comp from './fixtures/Comp.svelte'
|
|
5
|
-
|
|
6
|
-
describe('events', () => {
|
|
7
|
-
test('state changes are flushed after firing an event', async () => {
|
|
8
|
-
const { getByText } = render(Comp, { props: { name: 'World' } })
|
|
9
|
-
const button = getByText('Button')
|
|
10
|
-
|
|
11
|
-
const result = fireEvent.click(button)
|
|
12
|
-
|
|
13
|
-
await expect(result).resolves.toBe(true)
|
|
14
|
-
expect(button).toHaveTextContent('Button Clicked')
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
test('calling `fireEvent` directly works too', async () => {
|
|
18
|
-
const { getByText } = render(Comp, { props: { name: 'World' } })
|
|
19
|
-
const button = getByText('Button')
|
|
20
|
-
|
|
21
|
-
const result = fireEvent(
|
|
22
|
-
button,
|
|
23
|
-
new MouseEvent('click', {
|
|
24
|
-
bubbles: true,
|
|
25
|
-
cancelable: true,
|
|
26
|
-
})
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
await expect(result).resolves.toBe(true)
|
|
30
|
-
expect(button).toHaveTextContent('Button Clicked')
|
|
31
|
-
})
|
|
32
|
-
})
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<svelte:options accessors />
|
|
2
|
-
|
|
3
|
-
<script>
|
|
4
|
-
export let name = 'World'
|
|
5
|
-
|
|
6
|
-
let buttonText = 'Button'
|
|
7
|
-
|
|
8
|
-
function handleClick() {
|
|
9
|
-
buttonText = 'Button Clicked'
|
|
10
|
-
}
|
|
11
|
-
</script>
|
|
12
|
-
|
|
13
|
-
<h1 data-testid="test">Hello {name}!</h1>
|
|
14
|
-
|
|
15
|
-
<button on:click={handleClick}>{buttonText}</button>
|
|
16
|
-
|
|
17
|
-
<style></style>
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import { onDestroy, onMount } from 'svelte'
|
|
3
|
-
|
|
4
|
-
export let onExecuted = undefined
|
|
5
|
-
export let onMounted = undefined
|
|
6
|
-
export let onDestroyed = undefined
|
|
7
|
-
|
|
8
|
-
onExecuted?.()
|
|
9
|
-
|
|
10
|
-
onMount(() => {
|
|
11
|
-
onMounted?.()
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
onDestroy(() => {
|
|
15
|
-
onDestroyed?.()
|
|
16
|
-
})
|
|
17
|
-
</script>
|
|
18
|
-
|
|
19
|
-
<button />
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import { blur } from 'svelte/transition'
|
|
3
|
-
|
|
4
|
-
let show = false
|
|
5
|
-
let introDone = false
|
|
6
|
-
</script>
|
|
7
|
-
|
|
8
|
-
<button on:click={() => (show = true)}>Show</button>
|
|
9
|
-
|
|
10
|
-
{#if show}
|
|
11
|
-
<div in:blur={{ duration: 64 }} on:introend={() => (introDone = true)}>
|
|
12
|
-
{#if introDone}
|
|
13
|
-
<p data-testid="intro-done">Done</p>
|
|
14
|
-
{:else}
|
|
15
|
-
<p data-testid="intro-pending">Pending</p>
|
|
16
|
-
{/if}
|
|
17
|
-
</div>
|
|
18
|
-
{/if}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { act, render, screen } from '@testing-library/svelte'
|
|
2
|
-
import { describe, expect, test, vi } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import Mounter from './fixtures/Mounter.svelte'
|
|
5
|
-
|
|
6
|
-
const onMounted = vi.fn()
|
|
7
|
-
const onDestroyed = vi.fn()
|
|
8
|
-
const renderSubject = () => render(Mounter, { onMounted, onDestroyed })
|
|
9
|
-
|
|
10
|
-
describe('mount and destroy', () => {
|
|
11
|
-
test('component is mounted', async () => {
|
|
12
|
-
renderSubject()
|
|
13
|
-
|
|
14
|
-
const content = screen.getByRole('button')
|
|
15
|
-
|
|
16
|
-
expect(content).toBeInTheDocument()
|
|
17
|
-
await act()
|
|
18
|
-
expect(onMounted).toHaveBeenCalledOnce()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
test('component is destroyed', async () => {
|
|
22
|
-
const { unmount } = renderSubject()
|
|
23
|
-
|
|
24
|
-
await act()
|
|
25
|
-
unmount()
|
|
26
|
-
|
|
27
|
-
const content = screen.queryByRole('button')
|
|
28
|
-
|
|
29
|
-
expect(content).not.toBeInTheDocument()
|
|
30
|
-
await act()
|
|
31
|
-
expect(onDestroyed).toHaveBeenCalledOnce()
|
|
32
|
-
})
|
|
33
|
-
})
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { render } from '@testing-library/svelte'
|
|
2
|
-
import { describe, expect, test } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import Comp from './fixtures/Comp.svelte'
|
|
5
|
-
|
|
6
|
-
describe('multi-base', () => {
|
|
7
|
-
const treeA = document.createElement('div')
|
|
8
|
-
const treeB = document.createElement('div')
|
|
9
|
-
|
|
10
|
-
test('container isolates trees from one another', () => {
|
|
11
|
-
const { getByText: getByTextInA } = render(
|
|
12
|
-
Comp,
|
|
13
|
-
{
|
|
14
|
-
target: treeA,
|
|
15
|
-
props: {
|
|
16
|
-
name: 'Tree A',
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
baseElement: treeA,
|
|
21
|
-
}
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
const { getByText: getByTextInB } = render(
|
|
25
|
-
Comp,
|
|
26
|
-
{
|
|
27
|
-
target: treeB,
|
|
28
|
-
props: {
|
|
29
|
-
name: 'Tree B',
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
baseElement: treeB,
|
|
34
|
-
}
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
expect(() => getByTextInA('Hello Tree A!')).not.toThrow()
|
|
38
|
-
expect(() => getByTextInB('Hello Tree A!')).toThrow()
|
|
39
|
-
expect(() => getByTextInA('Hello Tree B!')).toThrow()
|
|
40
|
-
expect(() => getByTextInB('Hello Tree B!')).not.toThrow()
|
|
41
|
-
})
|
|
42
|
-
})
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { render } from '@testing-library/svelte'
|
|
2
|
-
import { describe, expect, test } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import Comp from './fixtures/Comp.svelte'
|
|
5
|
-
import { IS_SVELTE_5 } from './utils.js'
|
|
6
|
-
|
|
7
|
-
describe('render', () => {
|
|
8
|
-
const props = { name: 'World' }
|
|
9
|
-
|
|
10
|
-
test('renders component into the document', () => {
|
|
11
|
-
const { getByText } = render(Comp, { props })
|
|
12
|
-
|
|
13
|
-
expect(getByText('Hello World!')).toBeInTheDocument()
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
test('accepts props directly', () => {
|
|
17
|
-
const { getByText } = render(Comp, props)
|
|
18
|
-
expect(getByText('Hello World!')).toBeInTheDocument()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
test('throws error when mixing svelte component options and props', () => {
|
|
22
|
-
expect(() => {
|
|
23
|
-
render(Comp, { props, name: 'World' })
|
|
24
|
-
}).toThrow(/Unknown options/)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
test('throws error when mixing target option and props', () => {
|
|
28
|
-
expect(() => {
|
|
29
|
-
render(Comp, { target: document.createElement('div'), name: 'World' })
|
|
30
|
-
}).toThrow(/Unknown options/)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
test('should return a container object wrapping the DOM of the rendered component', () => {
|
|
34
|
-
const { container, getByTestId } = render(Comp, props)
|
|
35
|
-
const firstElement = getByTestId('test')
|
|
36
|
-
|
|
37
|
-
expect(container.firstChild).toBe(firstElement)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
test('should return a baseElement object, which holds the container', () => {
|
|
41
|
-
const { baseElement, container } = render(Comp, props)
|
|
42
|
-
|
|
43
|
-
expect(baseElement).toBe(document.body)
|
|
44
|
-
expect(baseElement.firstChild).toBe(container)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test('if target is provided, use it as container and baseElement', () => {
|
|
48
|
-
const target = document.createElement('div')
|
|
49
|
-
const { baseElement, container } = render(Comp, { props, target })
|
|
50
|
-
|
|
51
|
-
expect(container).toBe(target)
|
|
52
|
-
expect(baseElement).toBe(target)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
test('allow baseElement to be specified', () => {
|
|
56
|
-
const customBaseElement = document.createElement('div')
|
|
57
|
-
|
|
58
|
-
const { baseElement, container } = render(
|
|
59
|
-
Comp,
|
|
60
|
-
{ props },
|
|
61
|
-
{ baseElement: customBaseElement }
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
expect(baseElement).toBe(customBaseElement)
|
|
65
|
-
expect(baseElement.firstChild).toBe(container)
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
test.skipIf(IS_SVELTE_5)('should accept anchor option in Svelte v4', () => {
|
|
69
|
-
const baseElement = document.body
|
|
70
|
-
const target = document.createElement('section')
|
|
71
|
-
const anchor = document.createElement('div')
|
|
72
|
-
baseElement.appendChild(target)
|
|
73
|
-
target.appendChild(anchor)
|
|
74
|
-
|
|
75
|
-
const { getByTestId } = render(
|
|
76
|
-
Comp,
|
|
77
|
-
{ props, target, anchor },
|
|
78
|
-
{ baseElement }
|
|
79
|
-
)
|
|
80
|
-
const firstElement = getByTestId('test')
|
|
81
|
-
|
|
82
|
-
expect(target.firstChild).toBe(firstElement)
|
|
83
|
-
expect(target.lastChild).toBe(anchor)
|
|
84
|
-
})
|
|
85
|
-
})
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { act, render, screen } from '@testing-library/svelte'
|
|
2
|
-
import { VERSION as SVELTE_VERSION } from 'svelte/compiler'
|
|
3
|
-
import { describe, expect, test, vi } from 'vitest'
|
|
4
|
-
|
|
5
|
-
import Comp from './fixtures/Comp.svelte'
|
|
6
|
-
|
|
7
|
-
describe('rerender', () => {
|
|
8
|
-
test('updates props', async () => {
|
|
9
|
-
const { rerender } = render(Comp, { name: 'World' })
|
|
10
|
-
const element = screen.getByText('Hello World!')
|
|
11
|
-
|
|
12
|
-
await rerender({ name: 'Dolly' })
|
|
13
|
-
|
|
14
|
-
expect(element).toHaveTextContent('Hello Dolly!')
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
test('warns if incorrect arguments shape used', async () => {
|
|
18
|
-
vi.stubGlobal('console', { log: vi.fn(), warn: vi.fn(), error: vi.fn() })
|
|
19
|
-
|
|
20
|
-
const { rerender } = render(Comp, { name: 'World' })
|
|
21
|
-
const element = screen.getByText('Hello World!')
|
|
22
|
-
|
|
23
|
-
await rerender({ props: { name: 'Dolly' } })
|
|
24
|
-
|
|
25
|
-
expect(element).toHaveTextContent('Hello Dolly!')
|
|
26
|
-
expect(console.warn).toHaveBeenCalledOnce()
|
|
27
|
-
expect(console.warn).toHaveBeenCalledWith(
|
|
28
|
-
expect.stringMatching(/deprecated/iu)
|
|
29
|
-
)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
test('change props with accessors', async () => {
|
|
33
|
-
const { component, getByText } = render(
|
|
34
|
-
Comp,
|
|
35
|
-
SVELTE_VERSION < '5'
|
|
36
|
-
? { accessors: true, props: { name: 'World' } }
|
|
37
|
-
: { name: 'World' }
|
|
38
|
-
)
|
|
39
|
-
const element = getByText('Hello World!')
|
|
40
|
-
|
|
41
|
-
expect(element).toBeInTheDocument()
|
|
42
|
-
expect(component.name).toBe('World')
|
|
43
|
-
|
|
44
|
-
await act(() => {
|
|
45
|
-
component.name = 'Planet'
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
expect(element).toHaveTextContent('Hello Planet!')
|
|
49
|
-
})
|
|
50
|
-
})
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { render, screen, waitFor } from '@testing-library/svelte'
|
|
2
|
-
import { userEvent } from '@testing-library/user-event'
|
|
3
|
-
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
|
4
|
-
|
|
5
|
-
import Transitioner from './fixtures/Transitioner.svelte'
|
|
6
|
-
import { IS_JSDOM, IS_SVELTE_5 } from './utils.js'
|
|
7
|
-
|
|
8
|
-
describe.skipIf(IS_SVELTE_5)('transitions', () => {
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
if (!IS_JSDOM) return
|
|
11
|
-
|
|
12
|
-
const raf = (fn) => setTimeout(() => fn(new Date()), 16)
|
|
13
|
-
vi.stubGlobal('requestAnimationFrame', raf)
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
test('on:introend', async () => {
|
|
17
|
-
const user = userEvent.setup()
|
|
18
|
-
|
|
19
|
-
render(Transitioner)
|
|
20
|
-
const start = screen.getByRole('button')
|
|
21
|
-
await user.click(start)
|
|
22
|
-
|
|
23
|
-
const pending = screen.getByTestId('intro-pending')
|
|
24
|
-
expect(pending).toBeInTheDocument()
|
|
25
|
-
|
|
26
|
-
await waitFor(() => {
|
|
27
|
-
const done = screen.queryByTestId('intro-done')
|
|
28
|
-
expect(done).toBeInTheDocument()
|
|
29
|
-
})
|
|
30
|
-
})
|
|
31
|
-
})
|
package/src/__tests__/utils.js
DELETED
package/src/svelte5-index.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/* eslint-disable import/export */
|
|
2
|
-
import { act } from './pure.js'
|
|
3
|
-
import { cleanup } from './svelte5.js'
|
|
4
|
-
|
|
5
|
-
// If we're running in a test runner that supports afterEach
|
|
6
|
-
// then we'll automatically run cleanup afterEach test
|
|
7
|
-
// this ensures that tests run in isolation from each other
|
|
8
|
-
// if you don't like this then either import the `pure` module
|
|
9
|
-
// or set the STL_SKIP_AUTO_CLEANUP env variable to 'true'.
|
|
10
|
-
if (typeof afterEach === 'function' && !process.env.STL_SKIP_AUTO_CLEANUP) {
|
|
11
|
-
afterEach(async () => {
|
|
12
|
-
await act()
|
|
13
|
-
cleanup()
|
|
14
|
-
})
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// export all base queries, screen, etc.
|
|
18
|
-
export * from '@testing-library/dom'
|
|
19
|
-
|
|
20
|
-
// export svelte-specific functions and custom `fireEvent`
|
|
21
|
-
// `fireEvent` must be a named export to take priority over wildcard export above
|
|
22
|
-
export { act, fireEvent } from './pure.js'
|
|
23
|
-
export { cleanup, render } from './svelte5.js'
|
package/src/svelte5.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { createClassComponent } from 'svelte/legacy'
|
|
2
|
-
|
|
3
|
-
import { SvelteTestingLibrary } from './pure.js'
|
|
4
|
-
|
|
5
|
-
class Svelte5TestingLibrary extends SvelteTestingLibrary {
|
|
6
|
-
svelteComponentOptions = [
|
|
7
|
-
'target',
|
|
8
|
-
'props',
|
|
9
|
-
'events',
|
|
10
|
-
'context',
|
|
11
|
-
'intro',
|
|
12
|
-
'recover',
|
|
13
|
-
]
|
|
14
|
-
|
|
15
|
-
renderComponent(ComponentConstructor, componentOptions) {
|
|
16
|
-
const component = createClassComponent({
|
|
17
|
-
...componentOptions,
|
|
18
|
-
component: ComponentConstructor,
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
this.componentCache.add(component)
|
|
22
|
-
|
|
23
|
-
return component
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const instance = new Svelte5TestingLibrary()
|
|
28
|
-
|
|
29
|
-
export const render = instance.render.bind(instance)
|
|
30
|
-
export const cleanup = instance.cleanup.bind(instance)
|