Skip to content

Commit 2f88c78

Browse files
committed
fix(svelte5): use state rune for rerender
1 parent 266e2df commit 2f88c78

10 files changed

+106
-50
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
}

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

+8-4
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, IS_SVELTE_5 } 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 })

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/pure.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ export class SvelteTestingLibrary {
8080
)
8181
props = props.props
8282
}
83-
component.$set(props)
83+
84+
this.rerenderComponent(component, props)
8485
await Svelte.tick()
8586
},
8687
unmount: () => {
@@ -107,6 +108,10 @@ export class SvelteTestingLibrary {
107108
return component
108109
}
109110

111+
rerenderComponent(component, nextProps) {
112+
component.$set(nextProps)
113+
}
114+
110115
cleanupComponent(component) {
111116
const inCache = this.componentCache.delete(component)
112117

src/svelte5-index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable import/export */
22
import { act } from './pure.js'
3-
import { cleanup } from './svelte5.js'
3+
import { cleanup } from './svelte5.svelte.js'
44

55
// If we're running in a test runner that supports afterEach
66
// then we'll automatically run cleanup afterEach test
@@ -20,4 +20,4 @@ export * from '@testing-library/dom'
2020
// export svelte-specific functions and custom `fireEvent`
2121
// `fireEvent` must be a named export to take priority over wildcard export above
2222
export { act, fireEvent } from './pure.js'
23-
export { cleanup, render } from './svelte5.js'
23+
export { cleanup, render } from './svelte5.svelte.js'

src/svelte5.js

-30
This file was deleted.

src/svelte5.svelte.js

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/** eslint-global: $state */
2+
import { mount, unmount } from 'svelte'
3+
4+
import { SvelteTestingLibrary } from './pure.js'
5+
6+
class Svelte5TestingLibrary extends SvelteTestingLibrary {
7+
svelteComponentOptions = [
8+
'target',
9+
'props',
10+
'events',
11+
'context',
12+
'intro',
13+
'recover',
14+
]
15+
16+
propsByComponent = new Map()
17+
18+
renderComponent(ComponentConstructor, componentOptions) {
19+
const props = $state(componentOptions.props ?? {})
20+
const component = mount(ComponentConstructor, {
21+
...componentOptions,
22+
props,
23+
})
24+
25+
this.componentCache.add(component)
26+
this.propsByComponent.set(component, props)
27+
28+
return component
29+
}
30+
31+
rerenderComponent(component, nextProps) {
32+
const prevProps = this.propsByComponent.get(component)
33+
Object.assign(prevProps, nextProps)
34+
}
35+
36+
cleanupComponent(component) {
37+
const inCache = this.componentCache.delete(component)
38+
this.propsByComponent.delete(component)
39+
40+
if (inCache) {
41+
unmount(component)
42+
}
43+
}
44+
}
45+
46+
const instance = new Svelte5TestingLibrary()
47+
48+
export const render = instance.render.bind(instance)
49+
export const cleanup = instance.cleanup.bind(instance)

0 commit comments

Comments
 (0)