Skip to content

Commit 7a4da48

Browse files
committed
feat(svelte5): incorporate Svelte 5 support into main entry point
1 parent cb66333 commit 7a4da48

16 files changed

+230
-228
lines changed

.eslintrc.cjs

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ module.exports = {
2525
},
2626
rules: {
2727
'no-undef-init': 'off',
28+
'prefer-const': 'off',
2829
},
2930
},
3031
{
@@ -49,5 +50,6 @@ module.exports = {
4950
ecmaVersion: 2022,
5051
sourceType: 'module',
5152
},
53+
globals: { $state: 'readonly', $props: 'readonly' },
5254
ignorePatterns: ['!/.*'],
5355
}

README.md

+2-18
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ primary guiding principle is:
7171
This module is distributed via [npm][npm] which is bundled with [node][node] and
7272
should be installed as one of your project's `devDependencies`:
7373

74-
```
74+
```shell
7575
npm install --save-dev @testing-library/svelte
7676
```
7777

78-
This library has `peerDependencies` listings for `svelte >= 3`.
78+
This library supports `svelte` versions `3`, `4`, and `5`.
7979

8080
You may also be interested in installing `@testing-library/jest-dom` so you can use
8181
[the custom jest matchers](https://github.com/testing-library/jest-dom).
@@ -102,22 +102,6 @@ See the [setup docs][] for more detailed setup instructions, including for other
102102
[vitest]: https://vitest.dev/
103103
[setup docs]: https://testing-library.com/docs/svelte-testing-library/setup
104104

105-
### Svelte 5 support
106-
107-
If you are riding the bleeding edge of Svelte 5, you'll need to either
108-
import from `@testing-library/svelte/svelte5` instead of `@testing-library/svelte`, or add an alias to your `vite.config.js`:
109-
110-
```js
111-
export default defineConfig({
112-
plugins: [svelte(), svelteTesting()],
113-
test: {
114-
alias: {
115-
'@testing-library/svelte': '@testing-library/svelte/svelte5',
116-
},
117-
},
118-
})
119-
```
120-
121105
## Docs
122106

123107
See the [**docs**](https://testing-library.com/docs/svelte-testing-library/intro) over at the Testing Library website.

jest.config.js

-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import { VERSION as SVELTE_VERSION } from 'svelte/compiler'
2-
3-
const IS_SVELTE_5 = SVELTE_VERSION >= '5'
4-
51
export default {
62
testMatch: ['<rootDir>/src/__tests__/**/*.test.js'],
73
transform: {
@@ -14,9 +10,6 @@ export default {
1410
injectGlobals: false,
1511
moduleNameMapper: {
1612
'^vitest$': '<rootDir>/src/__tests__/_jest-vitest-alias.js',
17-
'^@testing-library/svelte$': IS_SVELTE_5
18-
? '<rootDir>/src/svelte5-index.js'
19-
: '<rootDir>/src/index.js',
2013
},
2114
resetMocks: true,
2215
restoreMocks: true,

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
},
1111
"./svelte5": {
1212
"types": "./types/index.d.ts",
13-
"default": "./src/svelte5-index.js"
13+
"default": "./src/index.js"
1414
},
1515
"./vitest": {
1616
"default": "./src/vitest.js"

src/__tests__/auto-cleanup.test.js

+2-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
22

3-
import { IS_SVELTE_5 } from './utils.js'
4-
5-
const importSvelteTestingLibrary = async () =>
6-
IS_SVELTE_5 ? import('../svelte5-index.js') : import('../index.js')
7-
83
const globalAfterEach = vi.fn()
94

105
describe('auto-cleanup', () => {
@@ -19,7 +14,7 @@ describe('auto-cleanup', () => {
1914
})
2015

2116
test('calls afterEach with cleanup if globally defined', async () => {
22-
const { render } = await importSvelteTestingLibrary()
17+
const { render } = await import('../index.js')
2318

2419
expect(globalAfterEach).toHaveBeenCalledTimes(1)
2520
expect(globalAfterEach).toHaveBeenLastCalledWith(expect.any(Function))
@@ -35,7 +30,7 @@ describe('auto-cleanup', () => {
3530
test('does not call afterEach if process STL_SKIP_AUTO_CLEANUP is set', async () => {
3631
process.env.STL_SKIP_AUTO_CLEANUP = 'true'
3732

38-
await importSvelteTestingLibrary()
33+
await import('../index.js')
3934

4035
expect(globalAfterEach).toHaveBeenCalledTimes(0)
4136
})

src/__tests__/fixtures/Comp.svelte

-2
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,3 @@
1313
<h1 data-testid="test">Hello {name}!</h1>
1414

1515
<button on:click={handleClick}>{buttonText}</button>
16-
17-
<style></style>
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
let { name = 'World' } = $props()
3+
4+
let buttonText = $state('Button')
5+
6+
function handleClick() {
7+
buttonText = 'Button Clicked'
8+
}
9+
</script>
10+
11+
<h1 data-testid="test">Hello {name}!</h1>
12+
13+
<button onclick={handleClick}>{buttonText}</button>

src/__tests__/render.test.js

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { render } from '@testing-library/svelte'
2-
import { describe, expect, test } from 'vitest'
2+
import { beforeAll, describe, expect, test } from 'vitest'
33

4-
import Comp from './fixtures/Comp.svelte'
5-
import { IS_SVELTE_5 } from './utils.js'
4+
import { COMPONENT_FIXTURES } from './utils.js'
65

7-
describe('render', () => {
6+
describe.each(COMPONENT_FIXTURES)('render $name', ({ component }) => {
87
const props = { name: 'World' }
8+
let Comp
9+
10+
beforeAll(async () => {
11+
Comp = await import(component)
12+
})
913

1014
test('renders component into the document', () => {
1115
const { getByText } = render(Comp, { props })
@@ -65,7 +69,7 @@ describe('render', () => {
6569
expect(baseElement.firstChild).toBe(container)
6670
})
6771

68-
test.skipIf(IS_SVELTE_5)('should accept anchor option in Svelte v4', () => {
72+
test('should accept anchor option', () => {
6973
const baseElement = document.body
7074
const target = document.createElement('section')
7175
const anchor = document.createElement('div')

src/__tests__/rerender.test.js

+15-11
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
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'
2+
import { beforeAll, describe, expect, test, vi } from 'vitest'
43

5-
import Comp from './fixtures/Comp.svelte'
4+
import { COMPONENT_FIXTURES, IS_SVELTE_5, TYPE_RUNES } from './utils.js'
5+
6+
describe.each(COMPONENT_FIXTURES)('rerender $type', ({ type, component }) => {
7+
let Comp
8+
9+
beforeAll(async () => {
10+
Comp = await import(component)
11+
})
612

7-
describe('rerender', () => {
813
test('updates props', async () => {
914
const { rerender } = render(Comp, { name: 'World' })
1015
const element = screen.getByText('Hello World!')
@@ -29,13 +34,12 @@ describe('rerender', () => {
2934
)
3035
})
3136

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-
)
37+
test.skipIf(type === TYPE_RUNES)('change props with accessors', async () => {
38+
const componentOptions = IS_SVELTE_5
39+
? { name: 'World' }
40+
: { accessors: true, props: { name: 'World' } }
41+
42+
const { component, getByText } = render(Comp, componentOptions)
3943
const element = getByText('Hello World!')
4044

4145
expect(element).toBeInTheDocument()

src/__tests__/utils.js

+11
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,14 @@ export const IS_JSDOM = window.navigator.userAgent.includes('jsdom')
55
export const IS_HAPPYDOM = !IS_JSDOM // right now it's happy or js
66

77
export const IS_SVELTE_5 = SVELTE_VERSION >= '5'
8+
9+
export const TYPE_LEGACY = 'legacy'
10+
11+
export const TYPE_RUNES = 'runes'
12+
13+
export const COMPONENT_FIXTURES = [
14+
{ type: TYPE_LEGACY, component: './fixtures/Comp.svelte' },
15+
IS_SVELTE_5
16+
? { type: TYPE_RUNES, component: './fixtures/CompRunes.svelte' }
17+
: [],
18+
].flat()

src/core-legacy.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Legacy rendering core for svelte-testing-library.
3+
*
4+
* Supports Svelte <= 4. See `core.js` for more details.
5+
*/
6+
7+
export const LegacyCore = {
8+
/** Allowed options for the component constructor. */
9+
componentOptions: [
10+
'target',
11+
'accessors',
12+
'anchor',
13+
'props',
14+
'hydrate',
15+
'intro',
16+
'context',
17+
],
18+
19+
/**
20+
* Mount the component into the DOM.
21+
*
22+
* The `onDestroy` callback is included for strict backwards compatibility
23+
* with previous versions of this library. It's mostly unnecessary logic.
24+
*/
25+
renderComponent: (ComponentConstructor, componentOptions, onDestroy) => {
26+
const component = new ComponentConstructor(componentOptions)
27+
28+
component.$$.on_destroy.push(() => {
29+
onDestroy(component)
30+
})
31+
32+
return component
33+
},
34+
35+
/** Update the component's props. */
36+
updateProps: (component, nextProps) => {
37+
component.$set(nextProps)
38+
},
39+
40+
/** Remove the component from the DOM. */
41+
cleanupComponent: (component) => {
42+
component.$destroy()
43+
},
44+
}

src/core.svelte.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Rendering core for svelte-testing-library.
3+
*
4+
* Defines how components are added to and removed from the DOM.
5+
* Will switch to legacy, class-based mounting logic
6+
* if it looks like we're in a Svelte <= 4 environment.
7+
*/
8+
import * as Svelte from 'svelte'
9+
10+
import { LegacyCore } from './core-legacy'
11+
12+
const IS_MODERN_SVELTE = typeof Svelte.mount === 'function'
13+
14+
/** Props signals for each rendered component. */
15+
const propsByComponent = new Map()
16+
17+
const ModernCore = {
18+
/** Allowed options to the `mount` call. */
19+
componentOptions: ['target', 'anchor', 'props', 'events', 'context', 'intro'],
20+
21+
/** Mount the component into the DOM. */
22+
renderComponent: (ComponentConstructor, componentOptions) => {
23+
const props = $state(componentOptions.props ?? {})
24+
const component = Svelte.mount(ComponentConstructor, {
25+
...componentOptions,
26+
props,
27+
})
28+
29+
propsByComponent.set(component, props)
30+
31+
return component
32+
},
33+
34+
/**
35+
* Update the component's props.
36+
*
37+
* Relies on the `$state` signal added in `renderComponent`.
38+
*/
39+
updateProps: (component, nextProps) => {
40+
const prevProps = propsByComponent.get(component)
41+
Object.assign(prevProps, nextProps)
42+
},
43+
44+
/** Remove the component from the DOM. */
45+
cleanupComponent: (component) => {
46+
propsByComponent.delete(component)
47+
Svelte.unmount(component)
48+
},
49+
}
50+
51+
export const Core = IS_MODERN_SVELTE ? ModernCore : LegacyCore

0 commit comments

Comments
 (0)