@testing-library/svelte 3.2.1 → 4.0.0
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 +1 -2
- package/package.json +22 -49
- package/src/__tests__/__snapshots__/auto-cleanup-skip.test.js.snap +3 -0
- package/src/__tests__/__snapshots__/render.test.js.snap +25 -0
- package/src/__tests__/act.test.js +56 -0
- package/src/__tests__/auto-cleanup-skip.test.js +23 -0
- package/src/__tests__/auto-cleanup.test.js +31 -0
- package/src/__tests__/debug.test.js +24 -0
- package/src/__tests__/events.test.js +30 -0
- package/src/__tests__/fixtures/Comp.svelte +23 -0
- package/src/__tests__/fixtures/Comp2.svelte +15 -0
- package/src/__tests__/fixtures/Stopwatch.svelte +40 -0
- package/src/__tests__/multi-base.test.js +42 -0
- package/src/__tests__/render.test.js +104 -0
- package/src/__tests__/rerender.test.js +38 -0
- package/src/__tests__/unmount.test.js +35 -0
- package/src/index.js +15 -0
- package/src/pure.js +138 -0
- package/src/test-setup.js +11 -0
- package/dist/index.js +0 -30
- package/dist/pure.js +0 -168
- package/pure.js +0 -2
package/README.md
CHANGED
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
/>
|
|
11
11
|
</a>
|
|
12
12
|
|
|
13
|
-
<p>Simple and complete Svelte testing utilities that encourage good testing
|
|
14
|
-
practices.</p>
|
|
13
|
+
<p>Simple and complete Svelte testing utilities that encourage good testing practices.</p>
|
|
15
14
|
|
|
16
15
|
[**Read The Docs**](https://testing-library.com/docs/svelte-testing-library/intro) |
|
|
17
16
|
[Edit the docs](https://github.com/alexkrolick/testing-library-docs)
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testing-library/svelte",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Simple and complete Svelte testing utilities that encourage good testing practices.",
|
|
5
|
-
"
|
|
5
|
+
"exports": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"types": "types/index.d.ts",
|
|
7
8
|
"license": "MIT",
|
|
8
9
|
"homepage": "https://github.com/testing-library/svelte-testing-library#readme",
|
|
@@ -29,57 +30,50 @@
|
|
|
29
30
|
"e2e"
|
|
30
31
|
],
|
|
31
32
|
"files": [
|
|
32
|
-
"
|
|
33
|
-
"dont-cleanup-after-each.js",
|
|
34
|
-
"pure.js",
|
|
33
|
+
"src/",
|
|
35
34
|
"types/index.d.ts"
|
|
36
35
|
],
|
|
37
36
|
"scripts": {
|
|
38
37
|
"toc": "doctoc README.md",
|
|
39
38
|
"lint": "eslint src --fix",
|
|
40
|
-
"
|
|
41
|
-
"build": "npm run clean && babel src --out-dir dist --ignore '**/__tests__/**'",
|
|
42
|
-
"test": "jest src",
|
|
39
|
+
"test": "vitest run src",
|
|
43
40
|
"test:watch": "npm run test -- --watch",
|
|
44
41
|
"test:update": "npm run test -- --updateSnapshot --coverage",
|
|
45
42
|
"setup": "npm install && npm run validate",
|
|
46
|
-
"validate": "npm
|
|
43
|
+
"validate": "npm-run-all lint test",
|
|
47
44
|
"contributors:add": "all-contributors add",
|
|
48
45
|
"contributors:generate": "all-contributors generate"
|
|
49
46
|
},
|
|
50
47
|
"peerDependencies": {
|
|
51
|
-
"svelte": "3
|
|
48
|
+
"svelte": "^3 || ^4"
|
|
52
49
|
},
|
|
53
50
|
"dependencies": {
|
|
54
51
|
"@testing-library/dom": "^8.1.0"
|
|
55
52
|
},
|
|
56
53
|
"devDependencies": {
|
|
57
|
-
"@babel/cli": "^7.6.2",
|
|
58
|
-
"@babel/core": "^7.6.2",
|
|
59
|
-
"@babel/plugin-transform-modules-commonjs": "^7.6.0",
|
|
60
|
-
"@babel/preset-env": "^7.6.2",
|
|
61
54
|
"@commitlint/cli": "^13.1.0",
|
|
62
55
|
"@commitlint/config-conventional": "^13.1.0",
|
|
63
|
-
"@
|
|
64
|
-
"@
|
|
56
|
+
"@sveltejs/vite-plugin-svelte": "^2.4.1",
|
|
57
|
+
"@testing-library/jest-dom": "^5.16.5",
|
|
58
|
+
"@vitest/coverage-c8": "^0.32.0",
|
|
65
59
|
"all-contributors-cli": "^6.9.0",
|
|
66
|
-
"babel-eslint": "^10.0.3",
|
|
67
|
-
"babel-jest": "^27.0.6",
|
|
68
60
|
"doctoc": "^2.0.0",
|
|
69
|
-
"eslint": "^
|
|
70
|
-
"eslint-
|
|
71
|
-
"eslint-plugin-
|
|
72
|
-
"eslint-plugin-
|
|
73
|
-
"eslint-plugin-
|
|
74
|
-
"eslint-
|
|
75
|
-
"eslint-plugin-
|
|
61
|
+
"eslint": "^8.42.0",
|
|
62
|
+
"eslint-plugin-import": "^2.27.5",
|
|
63
|
+
"eslint-plugin-n": "^16.0.0",
|
|
64
|
+
"eslint-plugin-promise": "^6.1.1",
|
|
65
|
+
"eslint-plugin-simple-import-sort": "^10.0.0",
|
|
66
|
+
"eslint-config-standard": "^17.1.0",
|
|
67
|
+
"eslint-plugin-svelte": "^2.30.0",
|
|
68
|
+
"eslint-plugin-vitest-globals": "^1.3.1",
|
|
76
69
|
"husky": "^7.0.1",
|
|
77
|
-
"
|
|
70
|
+
"jsdom": "^22.1.0",
|
|
78
71
|
"lint-staged": "^11.1.1",
|
|
79
72
|
"npm-run-all": "^4.1.5",
|
|
80
73
|
"prettier": "^2.0.1",
|
|
81
|
-
"svelte": "^3.
|
|
82
|
-
"
|
|
74
|
+
"svelte": "^3.59.1",
|
|
75
|
+
"vite": "^4.3.9",
|
|
76
|
+
"vitest": "^0.32.0"
|
|
83
77
|
},
|
|
84
78
|
"husky": {
|
|
85
79
|
"hooks": {
|
|
@@ -107,26 +101,5 @@
|
|
|
107
101
|
"extends": [
|
|
108
102
|
"@commitlint/config-conventional"
|
|
109
103
|
]
|
|
110
|
-
},
|
|
111
|
-
"jest": {
|
|
112
|
-
"testPathIgnorePatterns": [
|
|
113
|
-
"src/__tests__/fixtures"
|
|
114
|
-
],
|
|
115
|
-
"collectCoverageFrom": [
|
|
116
|
-
"src/*.js"
|
|
117
|
-
],
|
|
118
|
-
"setupFilesAfterEnv": [
|
|
119
|
-
"@testing-library/jest-dom/extend-expect"
|
|
120
|
-
],
|
|
121
|
-
"testEnvironment": "jsdom",
|
|
122
|
-
"transform": {
|
|
123
|
-
"^.+\\.js$": "babel-jest",
|
|
124
|
-
"^.+\\.svelte$": "svelte-jester",
|
|
125
|
-
"^.+\\.html$": "svelte-jester"
|
|
126
|
-
},
|
|
127
|
-
"moduleFileExtensions": [
|
|
128
|
-
"js",
|
|
129
|
-
"svelte"
|
|
130
|
-
]
|
|
131
104
|
}
|
|
132
105
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`render > should accept svelte 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
|
+
<!--<Comp>-->
|
|
22
|
+
<div />
|
|
23
|
+
</div>
|
|
24
|
+
</body>
|
|
25
|
+
`;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { act, fireEvent, render as stlRender } from '..'
|
|
4
|
+
import Comp from './fixtures/Comp.svelte'
|
|
5
|
+
|
|
6
|
+
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
|
+
test('state updates are flushed', async () => {
|
|
22
|
+
const { getByText } = render()
|
|
23
|
+
const button = getByText('Button')
|
|
24
|
+
|
|
25
|
+
expect(button).toHaveTextContent('Button')
|
|
26
|
+
|
|
27
|
+
await act(() => {
|
|
28
|
+
button.click()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
expect(button).toHaveTextContent('Button Clicked')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('findByTestId returns the element', async () => {
|
|
35
|
+
const { findByTestId } = render()
|
|
36
|
+
|
|
37
|
+
expect(await findByTestId('test')).toHaveTextContent(`Hello ${props.name}!`)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('accepts async functions', async () => {
|
|
41
|
+
const sleep = (ms) =>
|
|
42
|
+
new Promise((resolve) => {
|
|
43
|
+
setTimeout(() => resolve(), ms)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const { getByText } = render()
|
|
47
|
+
const button = getByText('Button')
|
|
48
|
+
|
|
49
|
+
await act(async () => {
|
|
50
|
+
await sleep(100)
|
|
51
|
+
await fireEvent.click(button)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
expect(button).toHaveTextContent('Button Clicked')
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
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('..')
|
|
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
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { render } from '..'
|
|
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
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { prettyDOM } from '@testing-library/dom'
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { render } from '..'
|
|
5
|
+
import Comp from './fixtures/Comp.svelte'
|
|
6
|
+
|
|
7
|
+
describe('debug', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
vi.spyOn(console, 'log').mockImplementation(() => { })
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
console.log.mockRestore()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('pretty prints the container', () => {
|
|
17
|
+
const { container, debug } = render(Comp, { props: { name: 'world' } })
|
|
18
|
+
|
|
19
|
+
debug()
|
|
20
|
+
|
|
21
|
+
expect(console.log).toHaveBeenCalledTimes(1)
|
|
22
|
+
expect(console.log).toHaveBeenCalledWith(prettyDOM(container))
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { fireEvent, render } from '..'
|
|
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
|
+
await fireEvent.click(button)
|
|
12
|
+
|
|
13
|
+
expect(button).toHaveTextContent('Button Clicked')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('calling `fireEvent` directly works too', async () => {
|
|
17
|
+
const { getByText } = render(Comp, { props: { name: 'World' } })
|
|
18
|
+
const button = getByText('Button')
|
|
19
|
+
|
|
20
|
+
await fireEvent(
|
|
21
|
+
button,
|
|
22
|
+
new MouseEvent('click', {
|
|
23
|
+
bubbles: true,
|
|
24
|
+
cancelable: true
|
|
25
|
+
})
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
expect(button).toHaveTextContent('Button Clicked')
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<svelte:options accessors />
|
|
2
|
+
|
|
3
|
+
<script>
|
|
4
|
+
import { getContext } from 'svelte'
|
|
5
|
+
|
|
6
|
+
export let name
|
|
7
|
+
|
|
8
|
+
let buttonText = 'Button'
|
|
9
|
+
|
|
10
|
+
const contextName = getContext('name')
|
|
11
|
+
|
|
12
|
+
function handleClick () {
|
|
13
|
+
buttonText = 'Button Clicked'
|
|
14
|
+
}
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<h1 data-testid="test">Hello {name}!</h1>
|
|
18
|
+
|
|
19
|
+
<div>we have {contextName}</div>
|
|
20
|
+
|
|
21
|
+
<button on:click={handleClick}>{buttonText}</button>
|
|
22
|
+
|
|
23
|
+
<style></style>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { onDestroy } from 'svelte'
|
|
3
|
+
|
|
4
|
+
let timer
|
|
5
|
+
let lapse = 0
|
|
6
|
+
let running = false
|
|
7
|
+
|
|
8
|
+
function handleRunClick () {
|
|
9
|
+
if (running) {
|
|
10
|
+
clearInterval(timer)
|
|
11
|
+
} else {
|
|
12
|
+
const startTime = Date.now() - lapse
|
|
13
|
+
|
|
14
|
+
timer = setInterval(() => {
|
|
15
|
+
lapse = Date.now() - startTime
|
|
16
|
+
}, 1)
|
|
17
|
+
}
|
|
18
|
+
running = true
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function handleClearClick () {
|
|
22
|
+
clearInterval(timer)
|
|
23
|
+
lapse = 0
|
|
24
|
+
running = false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
onDestroy(() => {
|
|
28
|
+
clearInterval(timer)
|
|
29
|
+
})
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<style></style>
|
|
33
|
+
|
|
34
|
+
<span>{lapse}ms</span>
|
|
35
|
+
|
|
36
|
+
<button on:click={handleRunClick}>
|
|
37
|
+
{running ? 'Stop' : 'Start'}
|
|
38
|
+
</button>
|
|
39
|
+
|
|
40
|
+
<button on:click={handleClearClick}>Clear</button>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { render } from '..'
|
|
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
|
+
container: 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
|
+
container: 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
|
+
})
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { act, render as stlRender } from '..'
|
|
4
|
+
import Comp from './fixtures/Comp.svelte'
|
|
5
|
+
import CompDefault from './fixtures/Comp2.svelte'
|
|
6
|
+
|
|
7
|
+
describe('render', () => {
|
|
8
|
+
let props
|
|
9
|
+
|
|
10
|
+
const render = (additional = {}) => {
|
|
11
|
+
return stlRender(Comp, {
|
|
12
|
+
target: document.body,
|
|
13
|
+
props,
|
|
14
|
+
...additional
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
props = {
|
|
20
|
+
name: 'World'
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('renders component into the document', () => {
|
|
25
|
+
const { getByText } = render()
|
|
26
|
+
|
|
27
|
+
expect(getByText('Hello World!')).toBeInTheDocument()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Dear reader, this is not something you generally want to do in your tests.
|
|
31
|
+
test('programmatically change props', async () => {
|
|
32
|
+
const { component, getByText } = render()
|
|
33
|
+
|
|
34
|
+
expect(getByText('Hello World!')).toBeInTheDocument()
|
|
35
|
+
|
|
36
|
+
await act(() => {
|
|
37
|
+
component.$set({ name: 'Worlds' })
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
expect(getByText('Hello Worlds!')).toBeInTheDocument()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('change props with accessors', async () => {
|
|
44
|
+
const { component, getByText } = render({ accessors: true })
|
|
45
|
+
|
|
46
|
+
expect(getByText('Hello World!')).toBeInTheDocument()
|
|
47
|
+
|
|
48
|
+
expect(component.name).toBe('World')
|
|
49
|
+
|
|
50
|
+
await act(() => {
|
|
51
|
+
component.value = 'Planet'
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
expect(getByText('Hello World!')).toBeInTheDocument()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('should accept props directly', () => {
|
|
58
|
+
const { getByText } = stlRender(Comp, { name: 'World' })
|
|
59
|
+
expect(getByText('Hello World!')).toBeInTheDocument()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('should accept svelte component options', () => {
|
|
63
|
+
const target = document.createElement('div')
|
|
64
|
+
const div = document.createElement('div')
|
|
65
|
+
document.body.appendChild(target)
|
|
66
|
+
target.appendChild(div)
|
|
67
|
+
const { container } = stlRender(Comp, {
|
|
68
|
+
target,
|
|
69
|
+
anchor: div,
|
|
70
|
+
props: { name: 'World' },
|
|
71
|
+
context: new Map([['name', 'context']])
|
|
72
|
+
})
|
|
73
|
+
expect(container).toMatchSnapshot()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('should throw error when mixing svelte component options and props', () => {
|
|
77
|
+
expect(() => {
|
|
78
|
+
stlRender(Comp, { anchor: '', name: 'World' })
|
|
79
|
+
}).toThrow(/Unknown options were found/)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('should return a container object, which contains the DOM of the rendered component', () => {
|
|
83
|
+
const { container } = render()
|
|
84
|
+
|
|
85
|
+
expect(container.innerHTML).toBe(document.body.innerHTML)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('correctly find component constructor on the default property', () => {
|
|
89
|
+
const { getByText } = render(CompDefault, { props: { name: 'World' } })
|
|
90
|
+
|
|
91
|
+
expect(getByText('Hello World!')).toBeInTheDocument()
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test("accept the 'context' option", () => {
|
|
95
|
+
const { getByText } = stlRender(Comp, {
|
|
96
|
+
props: {
|
|
97
|
+
name: 'Universe'
|
|
98
|
+
},
|
|
99
|
+
context: new Map([['name', 'context']])
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
expect(getByText('we have context')).toBeInTheDocument()
|
|
103
|
+
})
|
|
104
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
import { describe, expect, test } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { render } from '..'
|
|
7
|
+
import Comp from './fixtures/Comp.svelte'
|
|
8
|
+
|
|
9
|
+
describe('rerender', () => {
|
|
10
|
+
test('mounts new component successfully', () => {
|
|
11
|
+
const { container, rerender } = render(Comp, { props: { name: 'World 1' } })
|
|
12
|
+
|
|
13
|
+
expect(container.firstChild).toHaveTextContent('Hello World 1!')
|
|
14
|
+
rerender({ props: { name: 'World 2' } })
|
|
15
|
+
expect(container.firstChild).toHaveTextContent('Hello World 2!')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('destroys old component', () => {
|
|
19
|
+
let isDestroyed
|
|
20
|
+
|
|
21
|
+
const { rerender, component } = render(Comp, { props: { name: '' } })
|
|
22
|
+
|
|
23
|
+
component.$$.on_destroy.push(() => {
|
|
24
|
+
isDestroyed = true
|
|
25
|
+
})
|
|
26
|
+
rerender({ props: { name: '' } })
|
|
27
|
+
expect(isDestroyed).toBeTruthy()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('destroys old components on multiple rerenders', () => {
|
|
31
|
+
const { rerender, queryByText } = render(Comp, { props: { name: 'Neil' } })
|
|
32
|
+
|
|
33
|
+
rerender({ props: { name: 'Alex' } })
|
|
34
|
+
expect(queryByText('Hello Neil!')).not.toBeInTheDocument()
|
|
35
|
+
rerender({ props: { name: 'Geddy' } })
|
|
36
|
+
expect(queryByText('Hello Alex!')).not.toBeInTheDocument()
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { act, fireEvent, render } from '..'
|
|
4
|
+
import Stopwatch from './fixtures/Stopwatch.svelte'
|
|
5
|
+
|
|
6
|
+
describe('unmount', () => {
|
|
7
|
+
test('unmounts component successfully', async () => {
|
|
8
|
+
console.warn = vi.fn()
|
|
9
|
+
|
|
10
|
+
const { unmount, getByText, container } = render(Stopwatch)
|
|
11
|
+
|
|
12
|
+
await fireEvent.click(getByText('Start'))
|
|
13
|
+
|
|
14
|
+
unmount()
|
|
15
|
+
|
|
16
|
+
// Hey there reader! You don't need to have an assertion like this one
|
|
17
|
+
// this is just me making sure that the unmount function works.
|
|
18
|
+
// You don't need to do this in your apps. Just rely on the fact that this works.
|
|
19
|
+
expect(container.innerHTML).toBe('<div></div>')
|
|
20
|
+
|
|
21
|
+
await act()
|
|
22
|
+
expect(console.warn).not.toHaveBeenCalled()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('destroying component directly and calling unmount does not log warning', async () => {
|
|
26
|
+
console.warn = vi.fn()
|
|
27
|
+
|
|
28
|
+
const { unmount, component } = render(Stopwatch)
|
|
29
|
+
|
|
30
|
+
component.$destroy()
|
|
31
|
+
unmount()
|
|
32
|
+
|
|
33
|
+
expect(console.warn).not.toHaveBeenCalled()
|
|
34
|
+
})
|
|
35
|
+
})
|
package/src/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { act, cleanup } from './pure'
|
|
2
|
+
|
|
3
|
+
// If we're running in a test runner that supports afterEach
|
|
4
|
+
// then we'll automatically run cleanup afterEach test
|
|
5
|
+
// this ensures that tests run in isolation from each other
|
|
6
|
+
// if you don't like this then either import the `pure` module
|
|
7
|
+
// or set the STL_SKIP_AUTO_CLEANUP env variable to 'true'.
|
|
8
|
+
if (typeof afterEach === 'function' && !process.env.STL_SKIP_AUTO_CLEANUP) {
|
|
9
|
+
afterEach(async () => {
|
|
10
|
+
await act()
|
|
11
|
+
cleanup()
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export * from './pure'
|
package/src/pure.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fireEvent as dtlFireEvent,
|
|
3
|
+
getQueriesForElement,
|
|
4
|
+
prettyDOM
|
|
5
|
+
} from '@testing-library/dom'
|
|
6
|
+
import { tick } from 'svelte'
|
|
7
|
+
|
|
8
|
+
const containerCache = new Set()
|
|
9
|
+
const componentCache = new Set()
|
|
10
|
+
|
|
11
|
+
const svelteComponentOptions = [
|
|
12
|
+
'accessors',
|
|
13
|
+
'anchor',
|
|
14
|
+
'props',
|
|
15
|
+
'hydrate',
|
|
16
|
+
'intro',
|
|
17
|
+
'context'
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
const render = (
|
|
21
|
+
Component,
|
|
22
|
+
{ target, ...options } = {},
|
|
23
|
+
{ container, queries } = {}
|
|
24
|
+
) => {
|
|
25
|
+
container = container || document.body
|
|
26
|
+
target = target || container.appendChild(document.createElement('div'))
|
|
27
|
+
|
|
28
|
+
const ComponentConstructor = Component.default || Component
|
|
29
|
+
|
|
30
|
+
const checkProps = (options) => {
|
|
31
|
+
const isProps = !Object.keys(options).some((option) =>
|
|
32
|
+
svelteComponentOptions.includes(option)
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
// Check if any props and Svelte options were accidentally mixed.
|
|
36
|
+
if (!isProps) {
|
|
37
|
+
const unrecognizedOptions = Object.keys(options).filter(
|
|
38
|
+
(option) => !svelteComponentOptions.includes(option)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if (unrecognizedOptions.length > 0) {
|
|
42
|
+
throw Error(`
|
|
43
|
+
Unknown options were found [${unrecognizedOptions}]. This might happen if you've mixed
|
|
44
|
+
passing in props with Svelte options into the render function. Valid Svelte options
|
|
45
|
+
are [${svelteComponentOptions}]. You can either change the prop names, or pass in your
|
|
46
|
+
props for that component via the \`props\` option.\n\n
|
|
47
|
+
Eg: const { /** Results **/ } = render(MyComponent, { props: { /** props here **/ } })\n\n
|
|
48
|
+
`)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return options
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { props: options }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let component = new ComponentConstructor({
|
|
58
|
+
target,
|
|
59
|
+
...checkProps(options)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
containerCache.add({ container, target, component })
|
|
63
|
+
componentCache.add(component)
|
|
64
|
+
|
|
65
|
+
component.$$.on_destroy.push(() => {
|
|
66
|
+
componentCache.delete(component)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
container,
|
|
71
|
+
component,
|
|
72
|
+
debug: (el = container) => console.log(prettyDOM(el)),
|
|
73
|
+
rerender: (options) => {
|
|
74
|
+
if (componentCache.has(component)) component.$destroy()
|
|
75
|
+
|
|
76
|
+
// eslint-disable-next-line no-new
|
|
77
|
+
component = new ComponentConstructor({
|
|
78
|
+
target,
|
|
79
|
+
...checkProps(options)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
containerCache.add({ container, target, component })
|
|
83
|
+
componentCache.add(component)
|
|
84
|
+
|
|
85
|
+
component.$$.on_destroy.push(() => {
|
|
86
|
+
componentCache.delete(component)
|
|
87
|
+
})
|
|
88
|
+
},
|
|
89
|
+
unmount: () => {
|
|
90
|
+
if (componentCache.has(component)) component.$destroy()
|
|
91
|
+
},
|
|
92
|
+
...getQueriesForElement(container, queries)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const cleanupAtContainer = (cached) => {
|
|
97
|
+
const { target, component } = cached
|
|
98
|
+
|
|
99
|
+
if (componentCache.has(component)) component.$destroy()
|
|
100
|
+
|
|
101
|
+
if (target.parentNode === document.body) {
|
|
102
|
+
document.body.removeChild(target)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
containerCache.delete(cached)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const cleanup = () => {
|
|
109
|
+
Array.from(containerCache.keys()).forEach(cleanupAtContainer)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const act = (fn) => {
|
|
113
|
+
const value = fn && fn()
|
|
114
|
+
if (value !== undefined && typeof value.then === 'function') {
|
|
115
|
+
return value.then(() => tick())
|
|
116
|
+
}
|
|
117
|
+
return tick()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const fireEvent = async (...args) => {
|
|
121
|
+
const event = dtlFireEvent(...args)
|
|
122
|
+
await tick()
|
|
123
|
+
return event
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
Object.keys(dtlFireEvent).forEach((key) => {
|
|
127
|
+
fireEvent[key] = async (...args) => {
|
|
128
|
+
const event = dtlFireEvent[key](...args)
|
|
129
|
+
await tick()
|
|
130
|
+
return event
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
/* eslint-disable import/export */
|
|
135
|
+
|
|
136
|
+
export * from '@testing-library/dom'
|
|
137
|
+
|
|
138
|
+
export { render, cleanup, fireEvent, act }
|
package/dist/index.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
|
|
7
|
-
var _pure = require("./pure");
|
|
8
|
-
|
|
9
|
-
Object.keys(_pure).forEach(function (key) {
|
|
10
|
-
if (key === "default" || key === "__esModule") return;
|
|
11
|
-
if (key in exports && exports[key] === _pure[key]) return;
|
|
12
|
-
Object.defineProperty(exports, key, {
|
|
13
|
-
enumerable: true,
|
|
14
|
-
get: function () {
|
|
15
|
-
return _pure[key];
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
// If we're running in a test runner that supports afterEach
|
|
21
|
-
// then we'll automatically run cleanup afterEach test
|
|
22
|
-
// this ensures that tests run in isolation from each other
|
|
23
|
-
// if you don't like this then either import the `pure` module
|
|
24
|
-
// or set the STL_SKIP_AUTO_CLEANUP env variable to 'true'.
|
|
25
|
-
if (typeof afterEach === 'function' && !process.env.STL_SKIP_AUTO_CLEANUP) {
|
|
26
|
-
afterEach(async () => {
|
|
27
|
-
await (0, _pure.act)();
|
|
28
|
-
(0, _pure.cleanup)();
|
|
29
|
-
});
|
|
30
|
-
}
|
package/dist/pure.js
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
var _exportNames = {
|
|
7
|
-
render: true,
|
|
8
|
-
cleanup: true,
|
|
9
|
-
fireEvent: true,
|
|
10
|
-
act: true
|
|
11
|
-
};
|
|
12
|
-
exports.render = exports.fireEvent = exports.cleanup = exports.act = void 0;
|
|
13
|
-
|
|
14
|
-
var _dom = require("@testing-library/dom");
|
|
15
|
-
|
|
16
|
-
Object.keys(_dom).forEach(function (key) {
|
|
17
|
-
if (key === "default" || key === "__esModule") return;
|
|
18
|
-
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
19
|
-
if (key in exports && exports[key] === _dom[key]) return;
|
|
20
|
-
Object.defineProperty(exports, key, {
|
|
21
|
-
enumerable: true,
|
|
22
|
-
get: function () {
|
|
23
|
-
return _dom[key];
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
var _svelte = require("svelte");
|
|
29
|
-
|
|
30
|
-
const _excluded = ["target"];
|
|
31
|
-
|
|
32
|
-
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
33
|
-
|
|
34
|
-
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
35
|
-
|
|
36
|
-
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
37
|
-
|
|
38
|
-
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
|
|
39
|
-
|
|
40
|
-
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
|
|
41
|
-
|
|
42
|
-
const containerCache = new Map();
|
|
43
|
-
const componentCache = new Set();
|
|
44
|
-
const svelteComponentOptions = ['accessors', 'anchor', 'props', 'hydrate', 'intro', 'context'];
|
|
45
|
-
|
|
46
|
-
const render = (Component, _ref = {}, {
|
|
47
|
-
container,
|
|
48
|
-
queries
|
|
49
|
-
} = {}) => {
|
|
50
|
-
let {
|
|
51
|
-
target
|
|
52
|
-
} = _ref,
|
|
53
|
-
options = _objectWithoutProperties(_ref, _excluded);
|
|
54
|
-
|
|
55
|
-
container = container || document.body;
|
|
56
|
-
target = target || container.appendChild(document.createElement('div'));
|
|
57
|
-
const ComponentConstructor = Component.default || Component;
|
|
58
|
-
|
|
59
|
-
const checkProps = options => {
|
|
60
|
-
const isProps = !Object.keys(options).some(option => svelteComponentOptions.includes(option)); // Check if any props and Svelte options were accidentally mixed.
|
|
61
|
-
|
|
62
|
-
if (!isProps) {
|
|
63
|
-
const unrecognizedOptions = Object.keys(options).filter(option => !svelteComponentOptions.includes(option));
|
|
64
|
-
|
|
65
|
-
if (unrecognizedOptions.length > 0) {
|
|
66
|
-
throw Error(`
|
|
67
|
-
Unknown options were found [${unrecognizedOptions}]. This might happen if you've mixed
|
|
68
|
-
passing in props with Svelte options into the render function. Valid Svelte options
|
|
69
|
-
are [${svelteComponentOptions}]. You can either change the prop names, or pass in your
|
|
70
|
-
props for that component via the \`props\` option.\n\n
|
|
71
|
-
Eg: const { /** Results **/ } = render(MyComponent, { props: { /** props here **/ } })\n\n
|
|
72
|
-
`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return options;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
props: options
|
|
80
|
-
};
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
let component = new ComponentConstructor(_objectSpread({
|
|
84
|
-
target
|
|
85
|
-
}, checkProps(options)));
|
|
86
|
-
containerCache.set(container, {
|
|
87
|
-
target,
|
|
88
|
-
component
|
|
89
|
-
});
|
|
90
|
-
componentCache.add(component);
|
|
91
|
-
component.$$.on_destroy.push(() => {
|
|
92
|
-
componentCache.delete(component);
|
|
93
|
-
});
|
|
94
|
-
return _objectSpread({
|
|
95
|
-
container,
|
|
96
|
-
component,
|
|
97
|
-
debug: (el = container) => console.log((0, _dom.prettyDOM)(el)),
|
|
98
|
-
rerender: options => {
|
|
99
|
-
if (componentCache.has(component)) component.$destroy(); // eslint-disable-next-line no-new
|
|
100
|
-
|
|
101
|
-
component = new ComponentConstructor(_objectSpread({
|
|
102
|
-
target
|
|
103
|
-
}, checkProps(options)));
|
|
104
|
-
containerCache.set(container, {
|
|
105
|
-
target,
|
|
106
|
-
component
|
|
107
|
-
});
|
|
108
|
-
componentCache.add(component);
|
|
109
|
-
component.$$.on_destroy.push(() => {
|
|
110
|
-
componentCache.delete(component);
|
|
111
|
-
});
|
|
112
|
-
},
|
|
113
|
-
unmount: () => {
|
|
114
|
-
if (componentCache.has(component)) component.$destroy();
|
|
115
|
-
}
|
|
116
|
-
}, (0, _dom.getQueriesForElement)(container, queries));
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
exports.render = render;
|
|
120
|
-
|
|
121
|
-
const cleanupAtContainer = container => {
|
|
122
|
-
const {
|
|
123
|
-
target,
|
|
124
|
-
component
|
|
125
|
-
} = containerCache.get(container);
|
|
126
|
-
if (componentCache.has(component)) component.$destroy();
|
|
127
|
-
|
|
128
|
-
if (target.parentNode === document.body) {
|
|
129
|
-
document.body.removeChild(target);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
containerCache.delete(container);
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const cleanup = () => {
|
|
136
|
-
Array.from(containerCache.keys()).forEach(cleanupAtContainer);
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
exports.cleanup = cleanup;
|
|
140
|
-
|
|
141
|
-
const act = fn => {
|
|
142
|
-
const value = fn && fn();
|
|
143
|
-
|
|
144
|
-
if (value !== undefined && typeof value.then === 'function') {
|
|
145
|
-
return value.then(() => (0, _svelte.tick)());
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return (0, _svelte.tick)();
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
exports.act = act;
|
|
152
|
-
|
|
153
|
-
const fireEvent = async (...args) => {
|
|
154
|
-
const event = (0, _dom.fireEvent)(...args);
|
|
155
|
-
await (0, _svelte.tick)();
|
|
156
|
-
return event;
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
exports.fireEvent = fireEvent;
|
|
160
|
-
Object.keys(_dom.fireEvent).forEach(key => {
|
|
161
|
-
fireEvent[key] = async (...args) => {
|
|
162
|
-
const event = _dom.fireEvent[key](...args);
|
|
163
|
-
|
|
164
|
-
await (0, _svelte.tick)();
|
|
165
|
-
return event;
|
|
166
|
-
};
|
|
167
|
-
});
|
|
168
|
-
/* eslint-disable import/export */
|
package/pure.js
DELETED