@testing-library/svelte 4.2.0 → 4.2.2
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 +15 -0
- package/package.json +6 -2
- package/src/__tests__/act.test.js +8 -31
- package/src/__tests__/auto-cleanup-skip.test.js +1 -1
- package/src/__tests__/auto-cleanup.test.js +1 -1
- package/src/__tests__/cleanup.test.js +3 -3
- package/src/__tests__/context.test.js +3 -2
- package/src/__tests__/debug.test.js +6 -12
- package/src/__tests__/events.test.js +6 -4
- package/src/__tests__/fixtures/Comp.svelte +2 -8
- package/src/__tests__/fixtures/Context.svelte +2 -2
- package/src/__tests__/fixtures/Simple.svelte +2 -0
- package/src/__tests__/mount.test.js +3 -2
- package/src/__tests__/multi-base.test.js +7 -7
- package/src/__tests__/render.test.js +55 -93
- package/src/__tests__/rerender.test.js +37 -28
- package/src/__tests__/transition.test.js +7 -7
- package/src/__tests__/utils.js +7 -0
- package/src/index.js +7 -1
- package/src/pure.js +89 -75
- package/src/svelte5-index.js +22 -0
- package/src/svelte5.js +30 -0
- package/src/vitest.js +1 -2
- package/types/index.d.ts +8 -15
- package/src/__tests__/__snapshots__/render.test.js.snap +0 -48
- package/src/__tests__/fixtures/Comp2.svelte +0 -15
- package/src/__tests__/fixtures/Rerender.svelte +0 -17
package/README.md
CHANGED
|
@@ -80,6 +80,21 @@ This library has `peerDependencies` listings for `svelte >= 3`.
|
|
|
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
|
+
### Svelte 5 support
|
|
84
|
+
|
|
85
|
+
If you are riding the bleeding edge of Svelte 5, you'll need to either
|
|
86
|
+
import from `@testing-library/svelte/svelte5` instead of `@testing-library/svelte`, or have your `vite.config.js` contains the following alias:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
export default defineConfig(({ }) => ({
|
|
90
|
+
test: {
|
|
91
|
+
alias: {
|
|
92
|
+
'@testing-library/svelte': '@testing-library/svelte/svelte5'
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
}))
|
|
96
|
+
```
|
|
97
|
+
|
|
83
98
|
## Docs
|
|
84
99
|
|
|
85
100
|
See the [**docs**](https://testing-library.com/docs/svelte-testing-library/intro) over at the Testing Library website.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testing-library/svelte",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.2",
|
|
4
4
|
"description": "Simple and complete Svelte testing utilities that encourage good testing practices.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
"types": "./types/index.d.ts",
|
|
9
9
|
"default": "./src/index.js"
|
|
10
10
|
},
|
|
11
|
+
"./svelte5": {
|
|
12
|
+
"types": "./types/index.d.ts",
|
|
13
|
+
"default": "./src/svelte5-index.js"
|
|
14
|
+
},
|
|
11
15
|
"./vitest": {
|
|
12
16
|
"default": "./src/vitest.js"
|
|
13
17
|
}
|
|
@@ -94,7 +98,7 @@
|
|
|
94
98
|
"npm-run-all": "^4.1.5",
|
|
95
99
|
"prettier": "3.2.4",
|
|
96
100
|
"prettier-plugin-svelte": "3.1.2",
|
|
97
|
-
"svelte": "^4
|
|
101
|
+
"svelte": "^3 || ^4 || ^5",
|
|
98
102
|
"svelte-check": "^3.6.3",
|
|
99
103
|
"svelte-jester": "^3.0.0",
|
|
100
104
|
"typescript": "^5.3.3",
|
|
@@ -1,25 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { setTimeout } from 'node:timers/promises'
|
|
2
|
+
|
|
3
|
+
import { act, render } from '@testing-library/svelte'
|
|
4
|
+
import { describe, expect, test } from 'vitest'
|
|
2
5
|
|
|
3
|
-
import { act, fireEvent, render as stlRender } from '..'
|
|
4
6
|
import Comp from './fixtures/Comp.svelte'
|
|
5
7
|
|
|
6
8
|
describe('act', () => {
|
|
7
|
-
let props
|
|
8
|
-
|
|
9
|
-
const render = () => {
|
|
10
|
-
return stlRender(Comp, {
|
|
11
|
-
props
|
|
12
|
-
})
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
props = {
|
|
17
|
-
name: 'World'
|
|
18
|
-
}
|
|
19
|
-
})
|
|
20
|
-
|
|
21
9
|
test('state updates are flushed', async () => {
|
|
22
|
-
const { getByText } = render()
|
|
10
|
+
const { getByText } = render(Comp)
|
|
23
11
|
const button = getByText('Button')
|
|
24
12
|
|
|
25
13
|
expect(button).toHaveTextContent('Button')
|
|
@@ -31,24 +19,13 @@ describe('act', () => {
|
|
|
31
19
|
expect(button).toHaveTextContent('Button Clicked')
|
|
32
20
|
})
|
|
33
21
|
|
|
34
|
-
test('findByTestId returns the element', async () => {
|
|
35
|
-
const { findByTestId } = render()
|
|
36
|
-
|
|
37
|
-
expect(await findByTestId('test')).toHaveTextContent(`Hello ${props.name}!`)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
22
|
test('accepts async functions', async () => {
|
|
41
|
-
const
|
|
42
|
-
new Promise((resolve) => {
|
|
43
|
-
setTimeout(() => resolve(), ms)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
const { getByText } = render()
|
|
23
|
+
const { getByText } = render(Comp)
|
|
47
24
|
const button = getByText('Button')
|
|
48
25
|
|
|
49
26
|
await act(async () => {
|
|
50
|
-
await
|
|
51
|
-
|
|
27
|
+
await setTimeout(100)
|
|
28
|
+
button.click()
|
|
52
29
|
})
|
|
53
30
|
|
|
54
31
|
expect(button).toHaveTextContent('Button Clicked')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { cleanup, render } from '@testing-library/svelte'
|
|
1
2
|
import { describe, expect, test, vi } from 'vitest'
|
|
2
3
|
|
|
3
|
-
import { act, cleanup, render } from '..'
|
|
4
4
|
import Mounter from './fixtures/Mounter.svelte'
|
|
5
5
|
|
|
6
6
|
const onExecuted = vi.fn()
|
|
@@ -15,8 +15,8 @@ describe('cleanup', () => {
|
|
|
15
15
|
expect(document.body).toBeEmptyDOMElement()
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
-
test('cleanup unmounts component',
|
|
19
|
-
|
|
18
|
+
test('cleanup unmounts component', () => {
|
|
19
|
+
renderSubject()
|
|
20
20
|
cleanup()
|
|
21
21
|
|
|
22
22
|
expect(onDestroyed).toHaveBeenCalledOnce()
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { render } from '@testing-library/svelte'
|
|
1
2
|
import { expect, test } from 'vitest'
|
|
2
3
|
|
|
3
|
-
import { render } from '..'
|
|
4
4
|
import Comp from './fixtures/Context.svelte'
|
|
5
|
+
import { IS_HAPPYDOM, IS_SVELTE_5 } from './utils.js'
|
|
5
6
|
|
|
6
|
-
test('can set a context', () => {
|
|
7
|
+
test.skipIf(IS_SVELTE_5 && IS_HAPPYDOM)('can set a context', () => {
|
|
7
8
|
const message = 'Got it'
|
|
8
9
|
|
|
9
10
|
const { getByText } = render(Comp, {
|
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
import { prettyDOM } from '@testing-library/dom'
|
|
2
|
-
import {
|
|
2
|
+
import { render } from '@testing-library/svelte'
|
|
3
|
+
import { describe, expect, test, vi } from 'vitest'
|
|
3
4
|
|
|
4
|
-
import { render } from '..'
|
|
5
5
|
import Comp from './fixtures/Comp.svelte'
|
|
6
6
|
|
|
7
7
|
describe('debug', () => {
|
|
8
|
-
|
|
9
|
-
vi.
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
console.log.mockRestore()
|
|
14
|
-
})
|
|
8
|
+
test('pretty prints the base element', () => {
|
|
9
|
+
vi.stubGlobal('console', { log: vi.fn(), warn: vi.fn(), error: vi.fn() })
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
const { container, debug } = render(Comp, { props: { name: 'world' } })
|
|
11
|
+
const { baseElement, debug } = render(Comp, { props: { name: 'world' } })
|
|
18
12
|
|
|
19
13
|
debug()
|
|
20
14
|
|
|
21
15
|
expect(console.log).toHaveBeenCalledTimes(1)
|
|
22
|
-
expect(console.log).toHaveBeenCalledWith(prettyDOM(
|
|
16
|
+
expect(console.log).toHaveBeenCalledWith(prettyDOM(baseElement))
|
|
23
17
|
})
|
|
24
18
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { fireEvent, render } from '@testing-library/svelte'
|
|
1
2
|
import { describe, expect, test } from 'vitest'
|
|
2
3
|
|
|
3
|
-
import { fireEvent, render } from '..'
|
|
4
4
|
import Comp from './fixtures/Comp.svelte'
|
|
5
5
|
|
|
6
6
|
describe('events', () => {
|
|
@@ -8,8 +8,9 @@ describe('events', () => {
|
|
|
8
8
|
const { getByText } = render(Comp, { props: { name: 'World' } })
|
|
9
9
|
const button = getByText('Button')
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
const result = fireEvent.click(button)
|
|
12
12
|
|
|
13
|
+
await expect(result).resolves.toBe(true)
|
|
13
14
|
expect(button).toHaveTextContent('Button Clicked')
|
|
14
15
|
})
|
|
15
16
|
|
|
@@ -17,14 +18,15 @@ describe('events', () => {
|
|
|
17
18
|
const { getByText } = render(Comp, { props: { name: 'World' } })
|
|
18
19
|
const button = getByText('Button')
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
const result = fireEvent(
|
|
21
22
|
button,
|
|
22
23
|
new MouseEvent('click', {
|
|
23
24
|
bubbles: true,
|
|
24
|
-
cancelable: true
|
|
25
|
+
cancelable: true,
|
|
25
26
|
})
|
|
26
27
|
)
|
|
27
28
|
|
|
29
|
+
await expect(result).resolves.toBe(true)
|
|
28
30
|
expect(button).toHaveTextContent('Button Clicked')
|
|
29
31
|
})
|
|
30
32
|
})
|
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
<svelte:options accessors />
|
|
2
2
|
|
|
3
3
|
<script>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export let name
|
|
4
|
+
export let name = 'World'
|
|
7
5
|
|
|
8
6
|
let buttonText = 'Button'
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
function handleClick () {
|
|
8
|
+
function handleClick() {
|
|
13
9
|
buttonText = 'Button Clicked'
|
|
14
10
|
}
|
|
15
11
|
</script>
|
|
16
12
|
|
|
17
13
|
<h1 data-testid="test">Hello {name}!</h1>
|
|
18
14
|
|
|
19
|
-
<div>we have {contextName}</div>
|
|
20
|
-
|
|
21
15
|
<button on:click={handleClick}>{buttonText}</button>
|
|
22
16
|
|
|
23
17
|
<style></style>
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { act, render, screen } from '@testing-library/svelte'
|
|
1
2
|
import { describe, expect, test, vi } from 'vitest'
|
|
2
3
|
|
|
3
|
-
import { act, render, screen } from '..'
|
|
4
4
|
import Mounter from './fixtures/Mounter.svelte'
|
|
5
|
+
import { IS_HAPPYDOM, IS_SVELTE_5 } from './utils.js'
|
|
5
6
|
|
|
6
7
|
const onMounted = vi.fn()
|
|
7
8
|
const onDestroyed = vi.fn()
|
|
8
9
|
const renderSubject = () => render(Mounter, { onMounted, onDestroyed })
|
|
9
10
|
|
|
10
|
-
describe('mount and destroy', () => {
|
|
11
|
+
describe.skipIf(IS_SVELTE_5 && IS_HAPPYDOM)('mount and destroy', () => {
|
|
11
12
|
test('component is mounted', async () => {
|
|
12
13
|
renderSubject()
|
|
13
14
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { render } from '@testing-library/svelte'
|
|
1
2
|
import { describe, expect, test } from 'vitest'
|
|
2
3
|
|
|
3
|
-
import { render } from '..'
|
|
4
4
|
import Comp from './fixtures/Comp.svelte'
|
|
5
5
|
|
|
6
6
|
describe('multi-base', () => {
|
|
@@ -13,11 +13,11 @@ describe('multi-base', () => {
|
|
|
13
13
|
{
|
|
14
14
|
target: treeA,
|
|
15
15
|
props: {
|
|
16
|
-
name: 'Tree A'
|
|
17
|
-
}
|
|
16
|
+
name: 'Tree A',
|
|
17
|
+
},
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
|
-
|
|
20
|
+
baseElement: treeA,
|
|
21
21
|
}
|
|
22
22
|
)
|
|
23
23
|
|
|
@@ -26,11 +26,11 @@ describe('multi-base', () => {
|
|
|
26
26
|
{
|
|
27
27
|
target: treeB,
|
|
28
28
|
props: {
|
|
29
|
-
name: 'Tree B'
|
|
30
|
-
}
|
|
29
|
+
name: 'Tree B',
|
|
30
|
+
},
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
|
-
|
|
33
|
+
baseElement: treeB,
|
|
34
34
|
}
|
|
35
35
|
)
|
|
36
36
|
|
|
@@ -1,123 +1,85 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { render } from '@testing-library/svelte'
|
|
2
|
+
import { describe, expect, test } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import { act, render as stlRender } from '..'
|
|
5
4
|
import Comp from './fixtures/Comp.svelte'
|
|
6
|
-
import
|
|
5
|
+
import { IS_SVELTE_5 } from './utils.js'
|
|
7
6
|
|
|
8
7
|
describe('render', () => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const render = (additional = {}) => {
|
|
12
|
-
return stlRender(Comp, {
|
|
13
|
-
target: document.body,
|
|
14
|
-
props,
|
|
15
|
-
...additional,
|
|
16
|
-
})
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
props = {
|
|
21
|
-
name: 'World',
|
|
22
|
-
}
|
|
23
|
-
})
|
|
8
|
+
const props = { name: 'World' }
|
|
24
9
|
|
|
25
10
|
test('renders component into the document', () => {
|
|
26
|
-
const { getByText } = render()
|
|
11
|
+
const { getByText } = render(Comp, { props })
|
|
27
12
|
|
|
28
13
|
expect(getByText('Hello World!')).toBeInTheDocument()
|
|
29
14
|
})
|
|
30
15
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const { component, getByText } = render()
|
|
34
|
-
|
|
16
|
+
test('accepts props directly', () => {
|
|
17
|
+
const { getByText } = render(Comp, props)
|
|
35
18
|
expect(getByText('Hello World!')).toBeInTheDocument()
|
|
36
|
-
|
|
37
|
-
await act(() => {
|
|
38
|
-
component.$set({ name: 'Worlds' })
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
expect(getByText('Hello Worlds!')).toBeInTheDocument()
|
|
42
19
|
})
|
|
43
20
|
|
|
44
|
-
test('
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
expect(getByText('Hello World!')).toBeInTheDocument()
|
|
21
|
+
test('throws error when mixing svelte component options and props', () => {
|
|
22
|
+
expect(() => {
|
|
23
|
+
render(Comp, { props, name: 'World' })
|
|
24
|
+
}).toThrow(/Unknown options/)
|
|
25
|
+
})
|
|
50
26
|
|
|
51
|
-
|
|
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
|
+
})
|
|
52
32
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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')
|
|
56
36
|
|
|
57
|
-
expect(
|
|
37
|
+
expect(container.firstChild).toBe(firstElement)
|
|
58
38
|
})
|
|
59
39
|
|
|
60
|
-
test('should
|
|
61
|
-
const {
|
|
62
|
-
expect(getByText('Hello World!')).toBeInTheDocument()
|
|
63
|
-
})
|
|
40
|
+
test('should return a baseElement object, which holds the container', () => {
|
|
41
|
+
const { baseElement, container } = render(Comp, props)
|
|
64
42
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
() => {
|
|
68
|
-
const target = document.createElement('div')
|
|
69
|
-
const div = document.createElement('div')
|
|
70
|
-
document.body.appendChild(target)
|
|
71
|
-
target.appendChild(div)
|
|
72
|
-
const { container } = stlRender(Comp, {
|
|
73
|
-
target,
|
|
74
|
-
anchor: div,
|
|
75
|
-
props: { name: 'World' },
|
|
76
|
-
context: new Map([['name', 'context']]),
|
|
77
|
-
})
|
|
78
|
-
expect(container).toMatchSnapshot()
|
|
79
|
-
}
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
test.runIf(SVELTE_VERSION >= '5')(
|
|
83
|
-
'should accept svelte v5 component options',
|
|
84
|
-
() => {
|
|
85
|
-
const target = document.createElement('section')
|
|
86
|
-
document.body.appendChild(target)
|
|
87
|
-
|
|
88
|
-
const { container } = stlRender(Comp, {
|
|
89
|
-
target,
|
|
90
|
-
props: { name: 'World' },
|
|
91
|
-
context: new Map([['name', 'context']]),
|
|
92
|
-
})
|
|
93
|
-
expect(container).toMatchSnapshot()
|
|
94
|
-
}
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
test('should throw error when mixing svelte component options and props', () => {
|
|
98
|
-
expect(() => {
|
|
99
|
-
stlRender(Comp, { props: {}, name: 'World' })
|
|
100
|
-
}).toThrow(/Unknown options were found/)
|
|
43
|
+
expect(baseElement).toBe(document.body)
|
|
44
|
+
expect(baseElement.firstChild).toBe(container)
|
|
101
45
|
})
|
|
102
46
|
|
|
103
|
-
test('
|
|
104
|
-
const
|
|
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 })
|
|
105
50
|
|
|
106
|
-
expect(container
|
|
51
|
+
expect(container).toBe(target)
|
|
52
|
+
expect(baseElement).toBe(target)
|
|
107
53
|
})
|
|
108
54
|
|
|
109
|
-
test('
|
|
110
|
-
const
|
|
55
|
+
test('allow baseElement to be specified', () => {
|
|
56
|
+
const customBaseElement = document.createElement('div')
|
|
111
57
|
|
|
112
|
-
|
|
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)
|
|
113
66
|
})
|
|
114
67
|
|
|
115
|
-
test(
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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')
|
|
120
81
|
|
|
121
|
-
expect(
|
|
82
|
+
expect(target.firstChild).toBe(firstElement)
|
|
83
|
+
expect(target.lastChild).toBe(anchor)
|
|
122
84
|
})
|
|
123
85
|
})
|
|
@@ -1,41 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
1
|
+
import { act, render, screen } from '@testing-library/svelte'
|
|
2
|
+
import { VERSION as SVELTE_VERSION } from 'svelte/compiler'
|
|
4
3
|
import { describe, expect, test, vi } from 'vitest'
|
|
5
|
-
import { writable } from 'svelte/store'
|
|
6
4
|
|
|
7
|
-
import
|
|
8
|
-
import Comp from './fixtures/Rerender.svelte'
|
|
5
|
+
import Comp from './fixtures/Comp.svelte'
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
describe('rerender', () => {
|
|
8
|
+
test('updates props', async () => {
|
|
9
|
+
const { rerender } = render(Comp, { name: 'World' })
|
|
10
|
+
const element = screen.getByText('Hello World!')
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
await rerender({ name: 'Dolly' })
|
|
13
|
+
|
|
14
|
+
expect(element).toHaveTextContent('Hello Dolly!')
|
|
16
15
|
})
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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!')
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
await rerender({ props: { name: 'Dolly' } })
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
expect(element).toHaveTextContent('Hello Dolly!')
|
|
26
|
+
expect(console.warn).toHaveBeenCalledOnce()
|
|
27
|
+
expect(console.warn).toHaveBeenCalledWith(
|
|
28
|
+
expect.stringMatching(/deprecated/iu)
|
|
29
|
+
)
|
|
30
|
+
})
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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!')
|
|
31
40
|
|
|
32
|
-
|
|
41
|
+
expect(element).toBeInTheDocument()
|
|
42
|
+
expect(component.name).toBe('World')
|
|
33
43
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
await expectToRender('Hello World 3!')
|
|
38
|
-
expect(onDestroyed).not.toHaveBeenCalled()
|
|
44
|
+
await act(() => {
|
|
45
|
+
component.name = 'Planet'
|
|
46
|
+
})
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
expect(element).toHaveTextContent('Hello Planet!')
|
|
49
|
+
})
|
|
41
50
|
})
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
+
import { render, screen, waitFor } from '@testing-library/svelte'
|
|
1
2
|
import { userEvent } from '@testing-library/user-event'
|
|
2
|
-
import { VERSION as SVELTE_VERSION } from 'svelte/compiler'
|
|
3
3
|
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
|
4
4
|
|
|
5
|
-
import { render, screen, waitFor } from '..'
|
|
6
5
|
import Transitioner from './fixtures/Transitioner.svelte'
|
|
6
|
+
import { IS_JSDOM, IS_SVELTE_5 } from './utils.js'
|
|
7
7
|
|
|
8
|
-
describe.
|
|
8
|
+
describe.skipIf(IS_SVELTE_5)('transitions', () => {
|
|
9
9
|
beforeEach(() => {
|
|
10
|
-
if (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
if (!IS_JSDOM) return
|
|
11
|
+
|
|
12
|
+
const raf = (fn) => setTimeout(() => fn(new Date()), 16)
|
|
13
|
+
vi.stubGlobal('requestAnimationFrame', raf)
|
|
14
14
|
})
|
|
15
15
|
|
|
16
16
|
test('on:introend', async () => {
|
package/src/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable import/export */
|
|
1
2
|
import { act, cleanup } from './pure.js'
|
|
2
3
|
|
|
3
4
|
// If we're running in a test runner that supports afterEach
|
|
@@ -12,4 +13,9 @@ if (typeof afterEach === 'function' && !process.env.STL_SKIP_AUTO_CLEANUP) {
|
|
|
12
13
|
})
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
export
|
|
16
|
+
// export all base queries, screen, etc.
|
|
17
|
+
export * from '@testing-library/dom'
|
|
18
|
+
|
|
19
|
+
// export svelte-specific functions and custom `fireEvent`
|
|
20
|
+
// `fireEvent` must be a named export to take priority over wildcard export above
|
|
21
|
+
export { act, cleanup, fireEvent, render } from './pure.js'
|
package/src/pure.js
CHANGED
|
@@ -4,42 +4,40 @@ import {
|
|
|
4
4
|
prettyDOM,
|
|
5
5
|
} from '@testing-library/dom'
|
|
6
6
|
import * as Svelte from 'svelte'
|
|
7
|
+
import { VERSION as SVELTE_VERSION } from 'svelte/compiler'
|
|
7
8
|
|
|
8
|
-
const IS_SVELTE_5 =
|
|
9
|
-
const targetCache = new Set()
|
|
10
|
-
const componentCache = new Set()
|
|
9
|
+
const IS_SVELTE_5 = /^5\./.test(SVELTE_VERSION)
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
export class SvelteTestingLibrary {
|
|
12
|
+
svelteComponentOptions = [
|
|
13
|
+
'target',
|
|
14
|
+
'accessors',
|
|
15
|
+
'anchor',
|
|
16
|
+
'props',
|
|
17
|
+
'hydrate',
|
|
18
|
+
'intro',
|
|
19
|
+
'context',
|
|
20
|
+
]
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
{ target, ...options } = {},
|
|
19
|
-
{ container, queries } = {}
|
|
20
|
-
) => {
|
|
21
|
-
container = container || document.body
|
|
22
|
-
target = target || container.appendChild(document.createElement('div'))
|
|
23
|
-
targetCache.add(target)
|
|
22
|
+
targetCache = new Set()
|
|
23
|
+
componentCache = new Set()
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const checkProps = (options) => {
|
|
25
|
+
checkProps(options) {
|
|
28
26
|
const isProps = !Object.keys(options).some((option) =>
|
|
29
|
-
svelteComponentOptions.includes(option)
|
|
27
|
+
this.svelteComponentOptions.includes(option)
|
|
30
28
|
)
|
|
31
29
|
|
|
32
30
|
// Check if any props and Svelte options were accidentally mixed.
|
|
33
31
|
if (!isProps) {
|
|
34
32
|
const unrecognizedOptions = Object.keys(options).filter(
|
|
35
|
-
(option) => !svelteComponentOptions.includes(option)
|
|
33
|
+
(option) => !this.svelteComponentOptions.includes(option)
|
|
36
34
|
)
|
|
37
35
|
|
|
38
36
|
if (unrecognizedOptions.length > 0) {
|
|
39
37
|
throw Error(`
|
|
40
38
|
Unknown options were found [${unrecognizedOptions}]. This might happen if you've mixed
|
|
41
39
|
passing in props with Svelte options into the render function. Valid Svelte options
|
|
42
|
-
are [${svelteComponentOptions}]. You can either change the prop names, or pass in your
|
|
40
|
+
are [${this.svelteComponentOptions}]. You can either change the prop names, or pass in your
|
|
43
41
|
props for that component via the \`props\` option.\n\n
|
|
44
42
|
Eg: const { /** Results **/ } = render(MyComponent, { props: { /** props here **/ } })\n\n
|
|
45
43
|
`)
|
|
@@ -51,78 +49,100 @@ const render = (
|
|
|
51
49
|
return { props: options }
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
|
|
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
|
+
}
|
|
56
97
|
|
|
57
|
-
const component =
|
|
58
|
-
? Svelte.createRoot(ComponentConstructor, options)
|
|
59
|
-
: new ComponentConstructor(options)
|
|
98
|
+
const component = new ComponentConstructor(componentOptions)
|
|
60
99
|
|
|
61
|
-
componentCache.add(component)
|
|
100
|
+
this.componentCache.add(component)
|
|
62
101
|
|
|
63
102
|
// TODO(mcous, 2024-02-11): remove this behavior in the next major version
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
componentCache.delete(component)
|
|
68
|
-
})
|
|
69
|
-
}
|
|
103
|
+
component.$$.on_destroy.push(() => {
|
|
104
|
+
this.componentCache.delete(component)
|
|
105
|
+
})
|
|
70
106
|
|
|
71
107
|
return component
|
|
72
108
|
}
|
|
73
109
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
rerender: async (props) => {
|
|
81
|
-
if (props.props) {
|
|
82
|
-
console.warn(
|
|
83
|
-
'rerender({ props: {...} }) deprecated, use rerender({...}) instead'
|
|
84
|
-
)
|
|
85
|
-
props = props.props
|
|
86
|
-
}
|
|
87
|
-
component.$set(props)
|
|
88
|
-
await Svelte.tick()
|
|
89
|
-
},
|
|
90
|
-
unmount: () => {
|
|
91
|
-
cleanupComponent(component)
|
|
92
|
-
},
|
|
93
|
-
...getQueriesForElement(container, queries),
|
|
110
|
+
cleanupComponent(component) {
|
|
111
|
+
const inCache = this.componentCache.delete(component)
|
|
112
|
+
|
|
113
|
+
if (inCache) {
|
|
114
|
+
component.$destroy()
|
|
115
|
+
}
|
|
94
116
|
}
|
|
95
|
-
}
|
|
96
117
|
|
|
97
|
-
|
|
98
|
-
|
|
118
|
+
cleanupTarget(target) {
|
|
119
|
+
const inCache = this.targetCache.delete(target)
|
|
99
120
|
|
|
100
|
-
|
|
101
|
-
|
|
121
|
+
if (inCache && target.parentNode === document.body) {
|
|
122
|
+
document.body.removeChild(target)
|
|
123
|
+
}
|
|
102
124
|
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const cleanupTarget = (target) => {
|
|
106
|
-
const inCache = targetCache.delete(target)
|
|
107
125
|
|
|
108
|
-
|
|
109
|
-
|
|
126
|
+
cleanup() {
|
|
127
|
+
this.componentCache.forEach(this.cleanupComponent.bind(this))
|
|
128
|
+
this.targetCache.forEach(this.cleanupTarget.bind(this))
|
|
110
129
|
}
|
|
111
130
|
}
|
|
112
131
|
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
132
|
+
const instance = new SvelteTestingLibrary()
|
|
133
|
+
|
|
134
|
+
export const render = instance.render.bind(instance)
|
|
117
135
|
|
|
118
|
-
const
|
|
136
|
+
export const cleanup = instance.cleanup.bind(instance)
|
|
137
|
+
|
|
138
|
+
export const act = async (fn) => {
|
|
119
139
|
if (fn) {
|
|
120
140
|
await fn()
|
|
121
141
|
}
|
|
122
142
|
return Svelte.tick()
|
|
123
143
|
}
|
|
124
144
|
|
|
125
|
-
const fireEvent = async (...args) => {
|
|
145
|
+
export const fireEvent = async (...args) => {
|
|
126
146
|
const event = dtlFireEvent(...args)
|
|
127
147
|
await Svelte.tick()
|
|
128
148
|
return event
|
|
@@ -135,9 +155,3 @@ Object.keys(dtlFireEvent).forEach((key) => {
|
|
|
135
155
|
return event
|
|
136
156
|
}
|
|
137
157
|
})
|
|
138
|
-
|
|
139
|
-
/* eslint-disable import/export */
|
|
140
|
-
|
|
141
|
-
export * from '@testing-library/dom'
|
|
142
|
-
|
|
143
|
-
export { render, cleanup, fireEvent, act }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* eslint-disable import/export */
|
|
2
|
+
import { act, cleanup } from './svelte5.js'
|
|
3
|
+
|
|
4
|
+
// If we're running in a test runner that supports afterEach
|
|
5
|
+
// then we'll automatically run cleanup afterEach test
|
|
6
|
+
// this ensures that tests run in isolation from each other
|
|
7
|
+
// if you don't like this then either import the `pure` module
|
|
8
|
+
// or set the STL_SKIP_AUTO_CLEANUP env variable to 'true'.
|
|
9
|
+
if (typeof afterEach === 'function' && !process.env.STL_SKIP_AUTO_CLEANUP) {
|
|
10
|
+
afterEach(async () => {
|
|
11
|
+
await act()
|
|
12
|
+
cleanup()
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// export all base queries, screen, etc.
|
|
17
|
+
export * from '@testing-library/dom'
|
|
18
|
+
|
|
19
|
+
// export svelte-specific functions and custom `fireEvent`
|
|
20
|
+
// `fireEvent` must be a named export to take priority over wildcard export above
|
|
21
|
+
export { act, fireEvent } from './pure.js'
|
|
22
|
+
export { cleanup, render } from './svelte5.js'
|
package/src/svelte5.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
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)
|
package/src/vitest.js
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -18,12 +18,7 @@ export * from '@testing-library/dom'
|
|
|
18
18
|
|
|
19
19
|
type SvelteComponentOptions<C extends SvelteComponent> =
|
|
20
20
|
| ComponentProps<C>
|
|
21
|
-
|
|
|
22
|
-
ComponentConstructorOptions<ComponentProps<C>>,
|
|
23
|
-
'anchor' | 'props' | 'hydrate' | 'intro' | 'context'
|
|
24
|
-
>
|
|
25
|
-
|
|
26
|
-
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
|
|
21
|
+
| Partial<ComponentConstructorOptions<ComponentProps<C>>>
|
|
27
22
|
|
|
28
23
|
type Constructor<T> = new (...args: any[]) => T
|
|
29
24
|
|
|
@@ -35,24 +30,22 @@ export type RenderResult<
|
|
|
35
30
|
Q extends Queries = typeof queries,
|
|
36
31
|
> = {
|
|
37
32
|
container: HTMLElement
|
|
33
|
+
baseElement: HTMLElement
|
|
38
34
|
component: C
|
|
39
35
|
debug: (el?: HTMLElement | DocumentFragment) => void
|
|
40
|
-
rerender: (props: ComponentProps<C
|
|
36
|
+
rerender: (props: Partial<ComponentProps<C>>) => Promise<void>
|
|
41
37
|
unmount: () => void
|
|
42
38
|
} & { [P in keyof Q]: BoundFunction<Q[P]> }
|
|
43
39
|
|
|
44
40
|
export interface RenderOptions<Q extends Queries = typeof queries> {
|
|
45
|
-
|
|
41
|
+
baseElement?: HTMLElement
|
|
46
42
|
queries?: Q
|
|
47
43
|
}
|
|
48
44
|
|
|
49
|
-
export function render<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
): RenderResult<C>
|
|
54
|
-
|
|
55
|
-
export function render<C extends SvelteComponent, Q extends Queries>(
|
|
45
|
+
export function render<
|
|
46
|
+
C extends SvelteComponent,
|
|
47
|
+
Q extends Queries = typeof queries,
|
|
48
|
+
>(
|
|
56
49
|
component: Constructor<C>,
|
|
57
50
|
componentOptions?: SvelteComponentOptions<C>,
|
|
58
51
|
renderOptions?: RenderOptions<Q>
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
-
|
|
3
|
-
exports[`render > should accept svelte v4 component options 1`] = `
|
|
4
|
-
<body>
|
|
5
|
-
<div>
|
|
6
|
-
<h1
|
|
7
|
-
data-testid="test"
|
|
8
|
-
>
|
|
9
|
-
Hello
|
|
10
|
-
World
|
|
11
|
-
!
|
|
12
|
-
</h1>
|
|
13
|
-
|
|
14
|
-
<div>
|
|
15
|
-
we have context
|
|
16
|
-
</div>
|
|
17
|
-
|
|
18
|
-
<button>
|
|
19
|
-
Button
|
|
20
|
-
</button>
|
|
21
|
-
<div />
|
|
22
|
-
</div>
|
|
23
|
-
</body>
|
|
24
|
-
`;
|
|
25
|
-
|
|
26
|
-
exports[`render > should accept svelte v5 component options 1`] = `
|
|
27
|
-
<body>
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<section>
|
|
32
|
-
<h1
|
|
33
|
-
data-testid="test"
|
|
34
|
-
>
|
|
35
|
-
Hello World!
|
|
36
|
-
</h1>
|
|
37
|
-
|
|
38
|
-
<div>
|
|
39
|
-
we have context
|
|
40
|
-
</div>
|
|
41
|
-
|
|
42
|
-
<button>
|
|
43
|
-
Button
|
|
44
|
-
</button>
|
|
45
|
-
|
|
46
|
-
</section>
|
|
47
|
-
</body>
|
|
48
|
-
`;
|
|
@@ -1,17 +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
|
-
export let name = ''
|
|
9
|
-
|
|
10
|
-
onExecuted?.()
|
|
11
|
-
|
|
12
|
-
onMount(() => onMounted?.())
|
|
13
|
-
|
|
14
|
-
onDestroy(() => onDestroyed?.())
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
|
-
<div data-testid="test">Hello {name}!</div>
|