@testing-library/svelte 4.2.1 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testing-library/svelte",
3
- "version": "4.2.1",
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": {
@@ -98,7 +98,7 @@
98
98
  "npm-run-all": "^4.1.5",
99
99
  "prettier": "3.2.4",
100
100
  "prettier-plugin-svelte": "3.1.2",
101
- "svelte": "^4.2.10",
101
+ "svelte": "^3 || ^4 || ^5",
102
102
  "svelte-check": "^3.6.3",
103
103
  "svelte-jester": "^3.0.0",
104
104
  "typescript": "^5.3.3",
@@ -1,25 +1,13 @@
1
- import { beforeEach, describe, expect, test } from 'vitest'
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 '@testing-library/svelte'
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 sleep = (ms) =>
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 sleep(100)
51
- await fireEvent.click(button)
27
+ await setTimeout(100)
28
+ button.click()
52
29
  })
53
30
 
54
31
  expect(button).toHaveTextContent('Button Clicked')
@@ -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 '@testing-library/svelte'
4
4
  import Comp from './fixtures/Comp.svelte'
5
5
 
6
6
  describe('auto-cleanup', () => {
@@ -1,7 +1,6 @@
1
+ import { cleanup, render } from '@testing-library/svelte'
1
2
  import { describe, expect, test, vi } from 'vitest'
2
- import { VERSION as SVELTE_VERSION } from 'svelte/compiler'
3
3
 
4
- import { act, cleanup, render } from '@testing-library/svelte'
5
4
  import Mounter from './fixtures/Mounter.svelte'
6
5
 
7
6
  const onExecuted = vi.fn()
@@ -16,8 +15,8 @@ describe('cleanup', () => {
16
15
  expect(document.body).toBeEmptyDOMElement()
17
16
  })
18
17
 
19
- test.runIf(SVELTE_VERSION < '5')('cleanup unmounts component', async () => {
20
- await act(renderSubject)
18
+ test('cleanup unmounts component', () => {
19
+ renderSubject()
21
20
  cleanup()
22
21
 
23
22
  expect(onDestroyed).toHaveBeenCalledOnce()
@@ -1,6 +1,6 @@
1
+ import { render } from '@testing-library/svelte'
1
2
  import { expect, test } from 'vitest'
2
3
 
3
- import { render } from '@testing-library/svelte'
4
4
  import Comp from './fixtures/Context.svelte'
5
5
  import { IS_HAPPYDOM, IS_SVELTE_5 } from './utils.js'
6
6
 
@@ -1,24 +1,18 @@
1
1
  import { prettyDOM } from '@testing-library/dom'
2
- import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
3
-
4
2
  import { render } from '@testing-library/svelte'
3
+ import { describe, expect, test, vi } from 'vitest'
4
+
5
5
  import Comp from './fixtures/Comp.svelte'
6
6
 
7
7
  describe('debug', () => {
8
- beforeEach(() => {
9
- vi.spyOn(console, 'log').mockImplementation(() => { })
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
- test('pretty prints the container', () => {
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(container))
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 '@testing-library/svelte'
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
- await fireEvent.click(button)
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
- await fireEvent(
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
- import { getContext } from 'svelte'
5
-
6
- export let name
4
+ export let name = 'World'
7
5
 
8
6
  let buttonText = 'Button'
9
7
 
10
- const contextName = getContext('name')
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,7 +1,7 @@
1
1
  <script>
2
- import { getContext } from 'svelte';
2
+ import { getContext } from 'svelte'
3
3
 
4
- const ctx = getContext('foo');
4
+ const ctx = getContext('foo')
5
5
  </script>
6
6
 
7
7
  <div>{ctx.message}</div>
@@ -1,5 +1,7 @@
1
1
  <script lang="ts">
2
2
  export let name: string
3
+ export let count: number
3
4
  </script>
4
5
 
5
6
  <h1>hello {name}</h1>
7
+ <p>count: {count}</p>
@@ -1,6 +1,6 @@
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 '@testing-library/svelte'
4
4
  import Mounter from './fixtures/Mounter.svelte'
5
5
  import { IS_HAPPYDOM, IS_SVELTE_5 } from './utils.js'
6
6
 
@@ -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 '@testing-library/svelte'
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
- container: treeA
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
- container: treeB
33
+ baseElement: treeB,
34
34
  }
35
35
  )
36
36
 
@@ -1,123 +1,85 @@
1
- import { VERSION as SVELTE_VERSION } from 'svelte/compiler'
2
- import { beforeEach, describe, expect, test } from 'vitest'
1
+ import { render } from '@testing-library/svelte'
2
+ import { describe, expect, test } from 'vitest'
3
3
 
4
- import { act, render as stlRender } from '@testing-library/svelte'
5
4
  import Comp from './fixtures/Comp.svelte'
6
- import CompDefault from './fixtures/Comp2.svelte'
5
+ import { IS_SVELTE_5 } from './utils.js'
7
6
 
8
7
  describe('render', () => {
9
- let props
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
- // Dear reader, this is not something you generally want to do in your tests.
32
- test('programmatically change props', async () => {
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('change props with accessors', async () => {
45
- const { component, getByText } = render(
46
- SVELTE_VERSION < '5' ? { accessors: true } : {}
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
- expect(component.name).toBe('World')
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
- await act(() => {
54
- component.value = 'Planet'
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(getByText('Hello World!')).toBeInTheDocument()
37
+ expect(container.firstChild).toBe(firstElement)
58
38
  })
59
39
 
60
- test('should accept props directly', () => {
61
- const { getByText } = stlRender(Comp, { name: 'World' })
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
- test.runIf(SVELTE_VERSION < '5')(
66
- 'should accept svelte v4 component options',
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('should return a container object, which contains the DOM of the rendered component', () => {
104
- const { container } = render()
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.innerHTML).toBe(document.body.innerHTML)
51
+ expect(container).toBe(target)
52
+ expect(baseElement).toBe(target)
107
53
  })
108
54
 
109
- test('correctly find component constructor on the default property', () => {
110
- const { getByText } = stlRender(CompDefault, { props: { name: 'World' } })
55
+ test('allow baseElement to be specified', () => {
56
+ const customBaseElement = document.createElement('div')
111
57
 
112
- expect(getByText('Hello World!')).toBeInTheDocument()
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("accept the 'context' option", () => {
116
- const { getByText } = stlRender(Comp, {
117
- props: { name: 'Universe' },
118
- context: new Map([['name', 'context']]),
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(getByText('we have context')).toBeInTheDocument()
82
+ expect(target.firstChild).toBe(firstElement)
83
+ expect(target.lastChild).toBe(anchor)
122
84
  })
123
85
  })
@@ -1,41 +1,50 @@
1
- /**
2
- * @jest-environment jsdom
3
- */
4
- import { expect, test, vi } from 'vitest'
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'
5
4
 
6
- import { render, waitFor } from '@testing-library/svelte'
5
+ import Comp from './fixtures/Comp.svelte'
7
6
 
8
- import Comp from './fixtures/Rerender.svelte'
7
+ describe('rerender', () => {
8
+ test('updates props', async () => {
9
+ const { rerender } = render(Comp, { name: 'World' })
10
+ const element = screen.getByText('Hello World!')
9
11
 
10
- test('mounts new component successfully', async () => {
11
- const onMounted = vi.fn()
12
- const onDestroyed = vi.fn()
12
+ await rerender({ name: 'Dolly' })
13
13
 
14
- const { getByTestId, rerender } = render(Comp, {
15
- props: { name: 'World 1', onMounted, onDestroyed },
14
+ expect(element).toHaveTextContent('Hello Dolly!')
16
15
  })
17
16
 
18
- const expectToRender = (content) =>
19
- waitFor(() => {
20
- expect(getByTestId('test')).toHaveTextContent(content)
21
- expect(onMounted).toHaveBeenCalledOnce()
22
- })
17
+ test('warns if incorrect arguments shape used', async () => {
18
+ vi.stubGlobal('console', { log: vi.fn(), warn: vi.fn(), error: vi.fn() })
23
19
 
24
- await expectToRender('Hello World 1!')
20
+ const { rerender } = render(Comp, { name: 'World' })
21
+ const element = screen.getByText('Hello World!')
25
22
 
26
- console.warn = vi.fn()
23
+ await rerender({ props: { name: 'Dolly' } })
27
24
 
28
- rerender({ props: { name: 'World 2' } })
29
- await expectToRender('Hello World 2!')
30
- expect(onDestroyed).not.toHaveBeenCalled()
25
+ expect(element).toHaveTextContent('Hello Dolly!')
26
+ expect(console.warn).toHaveBeenCalledOnce()
27
+ expect(console.warn).toHaveBeenCalledWith(
28
+ expect.stringMatching(/deprecated/iu)
29
+ )
30
+ })
31
31
 
32
- expect(console.warn).toHaveBeenCalledOnce()
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!')
33
40
 
34
- console.warn.mockClear()
35
- onDestroyed.mockReset()
36
- rerender({ name: 'World 3' })
37
- await expectToRender('Hello World 3!')
38
- expect(onDestroyed).not.toHaveBeenCalled()
41
+ expect(element).toBeInTheDocument()
42
+ expect(component.name).toBe('World')
39
43
 
40
- expect(console.warn).not.toHaveBeenCalled()
44
+ await act(() => {
45
+ component.name = 'Planet'
46
+ })
47
+
48
+ expect(element).toHaveTextContent('Hello Planet!')
49
+ })
41
50
  })
@@ -1,12 +1,11 @@
1
+ import { render, screen, waitFor } from '@testing-library/svelte'
1
2
  import { userEvent } from '@testing-library/user-event'
2
3
  import { beforeEach, describe, expect, test, vi } from 'vitest'
3
4
 
4
- import { IS_JSDOM, IS_SVELTE_5 } from './utils.js'
5
-
6
- import { render, screen, waitFor } from '@testing-library/svelte'
7
5
  import Transitioner from './fixtures/Transitioner.svelte'
6
+ import { IS_JSDOM, IS_SVELTE_5 } from './utils.js'
8
7
 
9
- describe.runIf(!IS_SVELTE_5)('transitions', () => {
8
+ describe.skipIf(IS_SVELTE_5)('transitions', () => {
10
9
  beforeEach(() => {
11
10
  if (!IS_JSDOM) return
12
11
 
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,5 +13,9 @@ if (typeof afterEach === 'function' && !process.env.STL_SKIP_AUTO_CLEANUP) {
12
13
  })
13
14
  }
14
15
 
15
- export * from './pure.js'
16
+ // export all base queries, screen, etc.
16
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
@@ -3,94 +3,76 @@ import {
3
3
  getQueriesForElement,
4
4
  prettyDOM,
5
5
  } from '@testing-library/dom'
6
- import { VERSION as SVELTE_VERSION } from 'svelte/compiler'
7
6
  import * as Svelte from 'svelte'
7
+ import { VERSION as SVELTE_VERSION } from 'svelte/compiler'
8
8
 
9
9
  const IS_SVELTE_5 = /^5\./.test(SVELTE_VERSION)
10
- export const targetCache = new Set()
11
- export const componentCache = new Set()
12
-
13
- const svelteComponentOptions = [
14
- 'accessors',
15
- 'anchor',
16
- 'props',
17
- 'hydrate',
18
- 'intro',
19
- 'context',
20
- ]
21
-
22
- export const buildCheckProps = (svelteComponentOptions) => (options) => {
23
- const isProps = !Object.keys(options).some((option) =>
24
- svelteComponentOptions.includes(option)
25
- )
26
-
27
- // Check if any props and Svelte options were accidentally mixed.
28
- if (!isProps) {
29
- const unrecognizedOptions = Object.keys(options).filter(
30
- (option) => !svelteComponentOptions.includes(option)
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)
31
28
  )
32
29
 
33
- if (unrecognizedOptions.length > 0) {
34
- throw Error(`
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(`
35
38
  Unknown options were found [${unrecognizedOptions}]. This might happen if you've mixed
36
39
  passing in props with Svelte options into the render function. Valid Svelte options
37
- 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
38
41
  props for that component via the \`props\` option.\n\n
39
42
  Eg: const { /** Results **/ } = render(MyComponent, { props: { /** props here **/ } })\n\n
40
43
  `)
44
+ }
45
+
46
+ return options
41
47
  }
42
48
 
43
- return options
49
+ return { props: options }
44
50
  }
45
51
 
46
- return { props: options }
47
- }
48
-
49
- const checkProps = buildCheckProps(svelteComponentOptions)
50
-
51
- const buildRenderComponent =
52
- ({ target, ComponentConstructor }) =>
53
- (options) => {
54
- options = { target, ...checkProps(options) }
55
-
56
- if (IS_SVELTE_5)
57
- throw new Error('for Svelte 5, use `@testing-library/svelte/svelte5`')
52
+ render(Component, componentOptions = {}, renderOptions = {}) {
53
+ componentOptions = this.checkProps(componentOptions)
58
54
 
59
- const component = new ComponentConstructor(options)
55
+ const baseElement =
56
+ renderOptions.baseElement ?? componentOptions.target ?? document.body
60
57
 
61
- componentCache.add(component)
58
+ const target =
59
+ componentOptions.target ??
60
+ baseElement.appendChild(document.createElement('div'))
62
61
 
63
- // TODO(mcous, 2024-02-11): remove this behavior in the next major version
64
- // It is unnecessary has no path to implementation in Svelte v5
65
- if (!IS_SVELTE_5) {
66
- component.$$.on_destroy.push(() => {
67
- componentCache.delete(component)
68
- })
69
- }
70
-
71
- return component
72
- }
73
-
74
- export const buildRender =
75
- (buildRenderComponent) =>
76
- (Component, { target, ...options } = {}, { container, queries } = {}) => {
77
- container = container || document.body
78
- target = target || container.appendChild(document.createElement('div'))
79
- targetCache.add(target)
62
+ this.targetCache.add(target)
80
63
 
81
64
  const ComponentConstructor = Component.default || Component
82
65
 
83
- const renderComponent = buildRenderComponent({
66
+ const component = this.renderComponent(ComponentConstructor, {
67
+ ...componentOptions,
84
68
  target,
85
- ComponentConstructor,
86
69
  })
87
70
 
88
- let component = renderComponent(options)
89
-
90
71
  return {
91
- container,
72
+ baseElement,
92
73
  component,
93
- debug: (el = container) => console.log(prettyDOM(el)),
74
+ container: target,
75
+ debug: (el = baseElement) => console.log(prettyDOM(el)),
94
76
  rerender: async (props) => {
95
77
  if (props.props) {
96
78
  console.warn(
@@ -102,35 +84,57 @@ export const buildRender =
102
84
  await Svelte.tick()
103
85
  },
104
86
  unmount: () => {
105
- cleanupComponent(component)
87
+ this.cleanupComponent(component)
106
88
  },
107
- ...getQueriesForElement(container, queries),
89
+ ...getQueriesForElement(baseElement, renderOptions.queries),
108
90
  }
109
91
  }
110
92
 
111
- export const render = buildRender(buildRenderComponent)
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)
99
+
100
+ this.componentCache.add(component)
112
101
 
113
- export const cleanupComponent = (component) => {
114
- const inCache = componentCache.delete(component)
102
+ // TODO(mcous, 2024-02-11): remove this behavior in the next major version
103
+ component.$$.on_destroy.push(() => {
104
+ this.componentCache.delete(component)
105
+ })
115
106
 
116
- if (inCache) {
117
- component.$destroy()
107
+ return component
118
108
  }
119
- }
120
109
 
121
- const cleanupTarget = (target) => {
122
- const inCache = targetCache.delete(target)
110
+ cleanupComponent(component) {
111
+ const inCache = this.componentCache.delete(component)
123
112
 
124
- if (inCache && target.parentNode === document.body) {
125
- document.body.removeChild(target)
113
+ if (inCache) {
114
+ component.$destroy()
115
+ }
126
116
  }
127
- }
128
117
 
129
- export const cleanup = () => {
130
- componentCache.forEach(cleanupComponent)
131
- targetCache.forEach(cleanupTarget)
118
+ cleanupTarget(target) {
119
+ const inCache = this.targetCache.delete(target)
120
+
121
+ if (inCache && target.parentNode === document.body) {
122
+ document.body.removeChild(target)
123
+ }
124
+ }
125
+
126
+ cleanup() {
127
+ this.componentCache.forEach(this.cleanupComponent.bind(this))
128
+ this.targetCache.forEach(this.cleanupTarget.bind(this))
129
+ }
132
130
  }
133
131
 
132
+ const instance = new SvelteTestingLibrary()
133
+
134
+ export const render = instance.render.bind(instance)
135
+
136
+ export const cleanup = instance.cleanup.bind(instance)
137
+
134
138
  export const act = async (fn) => {
135
139
  if (fn) {
136
140
  await fn()
@@ -1,3 +1,4 @@
1
+ /* eslint-disable import/export */
1
2
  import { act, cleanup } from './svelte5.js'
2
3
 
3
4
  // If we're running in a test runner that supports afterEach
@@ -12,6 +13,10 @@ if (typeof afterEach === 'function' && !process.env.STL_SKIP_AUTO_CLEANUP) {
12
13
  })
13
14
  }
14
15
 
15
- export * from './svelte5.js'
16
+ // export all base queries, screen, etc.
16
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
17
21
  export { act, fireEvent } from './pure.js'
22
+ export { cleanup, render } from './svelte5.js'
package/src/svelte5.js CHANGED
@@ -1,41 +1,30 @@
1
1
  import { createClassComponent } from 'svelte/legacy'
2
- import {
3
- componentCache,
4
- cleanup,
5
- buildCheckProps,
6
- buildRender,
7
- } from './pure.js'
8
2
 
9
- const svelteComponentOptions = [
10
- 'target',
11
- 'props',
12
- 'events',
13
- 'context',
14
- 'intro',
15
- 'recover',
16
- ]
3
+ import { SvelteTestingLibrary } from './pure.js'
17
4
 
18
- const checkProps = buildCheckProps(svelteComponentOptions)
19
-
20
- const buildRenderComponent =
21
- ({ target, ComponentConstructor }) =>
22
- (options) => {
23
- options = { target, ...checkProps(options) }
5
+ class Svelte5TestingLibrary extends SvelteTestingLibrary {
6
+ svelteComponentOptions = [
7
+ 'target',
8
+ 'props',
9
+ 'events',
10
+ 'context',
11
+ 'intro',
12
+ 'recover',
13
+ ]
24
14
 
15
+ renderComponent(ComponentConstructor, componentOptions) {
25
16
  const component = createClassComponent({
17
+ ...componentOptions,
26
18
  component: ComponentConstructor,
27
- ...options,
28
19
  })
29
20
 
30
- componentCache.add(component)
21
+ this.componentCache.add(component)
31
22
 
32
23
  return component
33
24
  }
25
+ }
34
26
 
35
- const render = buildRender(buildRenderComponent)
36
-
37
- /* eslint-disable import/export */
38
-
39
- import { act, fireEvent } from './pure.js'
27
+ const instance = new Svelte5TestingLibrary()
40
28
 
41
- export { render, cleanup, fireEvent, act }
29
+ export const render = instance.render.bind(instance)
30
+ export const cleanup = instance.cleanup.bind(instance)
package/src/vitest.js CHANGED
@@ -1,6 +1,5 @@
1
- import { afterEach } from 'vitest'
2
-
3
1
  import { act, cleanup } from '@testing-library/svelte'
2
+ import { afterEach } from 'vitest'
4
3
 
5
4
  afterEach(async () => {
6
5
  await act()
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
- | Pick<
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>) => Promise<void>
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
- container?: HTMLElement
41
+ baseElement?: HTMLElement
46
42
  queries?: Q
47
43
  }
48
44
 
49
- export function render<C extends SvelteComponent>(
50
- component: Constructor<C>,
51
- componentOptions?: SvelteComponentOptions<C>,
52
- renderOptions?: Omit<RenderOptions, 'queries'>
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,15 +0,0 @@
1
- <script>
2
- export let name
3
-
4
- let buttonText = 'Button Text'
5
-
6
- function handleClick () {
7
- buttonText = 'Button Clicked'
8
- }
9
- </script>
10
-
11
- <style></style>
12
-
13
- <h1>Hello {name}!</h1>
14
-
15
- <button on:click={handleClick}>{buttonText}</button>
@@ -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>