Skip to content

Commit

Permalink
feat(svelte5): incorporate Svelte 5 support into main entry point
Browse files Browse the repository at this point in the history
  • Loading branch information
mcous committed May 15, 2024
1 parent cb66333 commit 7a4da48
Show file tree
Hide file tree
Showing 16 changed files with 230 additions and 228 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module.exports = {
},
rules: {
'no-undef-init': 'off',
'prefer-const': 'off',
},
},
{
Expand All @@ -49,5 +50,6 @@ module.exports = {
ecmaVersion: 2022,
sourceType: 'module',
},
globals: { $state: 'readonly', $props: 'readonly' },
ignorePatterns: ['!/.*'],
}
20 changes: 2 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ primary guiding principle is:
This module is distributed via [npm][npm] which is bundled with [node][node] and
should be installed as one of your project's `devDependencies`:

```
```shell
npm install --save-dev @testing-library/svelte
```

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

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

### Svelte 5 support

If you are riding the bleeding edge of Svelte 5, you'll need to either
import from `@testing-library/svelte/svelte5` instead of `@testing-library/svelte`, or add an alias to your `vite.config.js`:

```js
export default defineConfig({
plugins: [svelte(), svelteTesting()],
test: {
alias: {
'@testing-library/svelte': '@testing-library/svelte/svelte5',
},
},
})
```

## Docs

See the [**docs**](https://testing-library.com/docs/svelte-testing-library/intro) over at the Testing Library website.
Expand Down
7 changes: 0 additions & 7 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import { VERSION as SVELTE_VERSION } from 'svelte/compiler'

const IS_SVELTE_5 = SVELTE_VERSION >= '5'

export default {
testMatch: ['<rootDir>/src/__tests__/**/*.test.js'],
transform: {
Expand All @@ -14,9 +10,6 @@ export default {
injectGlobals: false,
moduleNameMapper: {
'^vitest$': '<rootDir>/src/__tests__/_jest-vitest-alias.js',
'^@testing-library/svelte$': IS_SVELTE_5
? '<rootDir>/src/svelte5-index.js'
: '<rootDir>/src/index.js',
},
resetMocks: true,
restoreMocks: true,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"./svelte5": {
"types": "./types/index.d.ts",
"default": "./src/svelte5-index.js"
"default": "./src/index.js"
},
"./vitest": {
"default": "./src/vitest.js"
Expand Down
9 changes: 2 additions & 7 deletions src/__tests__/auto-cleanup.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'

import { IS_SVELTE_5 } from './utils.js'

const importSvelteTestingLibrary = async () =>
IS_SVELTE_5 ? import('../svelte5-index.js') : import('../index.js')

const globalAfterEach = vi.fn()

describe('auto-cleanup', () => {
Expand All @@ -19,7 +14,7 @@ describe('auto-cleanup', () => {
})

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

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

await importSvelteTestingLibrary()
await import('../index.js')

expect(globalAfterEach).toHaveBeenCalledTimes(0)
})
Expand Down
2 changes: 0 additions & 2 deletions src/__tests__/fixtures/Comp.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,3 @@
<h1 data-testid="test">Hello {name}!</h1>

<button on:click={handleClick}>{buttonText}</button>

<style></style>
13 changes: 13 additions & 0 deletions src/__tests__/fixtures/CompRunes.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script>
let { name = 'World' } = $props()
let buttonText = $state('Button')
function handleClick() {
buttonText = 'Button Clicked'
}
</script>

<h1 data-testid="test">Hello {name}!</h1>

<button onclick={handleClick}>{buttonText}</button>
14 changes: 9 additions & 5 deletions src/__tests__/render.test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { render } from '@testing-library/svelte'
import { describe, expect, test } from 'vitest'
import { beforeAll, describe, expect, test } from 'vitest'

import Comp from './fixtures/Comp.svelte'
import { IS_SVELTE_5 } from './utils.js'
import { COMPONENT_FIXTURES } from './utils.js'

describe('render', () => {
describe.each(COMPONENT_FIXTURES)('render $name', ({ component }) => {
const props = { name: 'World' }
let Comp

beforeAll(async () => {
Comp = await import(component)
})

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

test.skipIf(IS_SVELTE_5)('should accept anchor option in Svelte v4', () => {
test('should accept anchor option', () => {
const baseElement = document.body
const target = document.createElement('section')
const anchor = document.createElement('div')
Expand Down
26 changes: 15 additions & 11 deletions src/__tests__/rerender.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { act, render, screen } from '@testing-library/svelte'
import { VERSION as SVELTE_VERSION } from 'svelte/compiler'
import { describe, expect, test, vi } from 'vitest'
import { beforeAll, describe, expect, test, vi } from 'vitest'

import Comp from './fixtures/Comp.svelte'
import { COMPONENT_FIXTURES, IS_SVELTE_5, TYPE_RUNES } from './utils.js'

describe.each(COMPONENT_FIXTURES)('rerender $type', ({ type, component }) => {
let Comp

beforeAll(async () => {
Comp = await import(component)
})

describe('rerender', () => {
test('updates props', async () => {
const { rerender } = render(Comp, { name: 'World' })
const element = screen.getByText('Hello World!')
Expand All @@ -29,13 +34,12 @@ describe('rerender', () => {
)
})

test('change props with accessors', async () => {
const { component, getByText } = render(
Comp,
SVELTE_VERSION < '5'
? { accessors: true, props: { name: 'World' } }
: { name: 'World' }
)
test.skipIf(type === TYPE_RUNES)('change props with accessors', async () => {
const componentOptions = IS_SVELTE_5
? { name: 'World' }
: { accessors: true, props: { name: 'World' } }

const { component, getByText } = render(Comp, componentOptions)
const element = getByText('Hello World!')

expect(element).toBeInTheDocument()
Expand Down
11 changes: 11 additions & 0 deletions src/__tests__/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,14 @@ export const IS_JSDOM = window.navigator.userAgent.includes('jsdom')
export const IS_HAPPYDOM = !IS_JSDOM // right now it's happy or js

export const IS_SVELTE_5 = SVELTE_VERSION >= '5'

export const TYPE_LEGACY = 'legacy'

export const TYPE_RUNES = 'runes'

export const COMPONENT_FIXTURES = [
{ type: TYPE_LEGACY, component: './fixtures/Comp.svelte' },
IS_SVELTE_5
? { type: TYPE_RUNES, component: './fixtures/CompRunes.svelte' }
: [],
].flat()
44 changes: 44 additions & 0 deletions src/core-legacy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Legacy rendering core for svelte-testing-library.
*
* Supports Svelte <= 4. See `core.js` for more details.
*/

export const LegacyCore = {
/** Allowed options for the component constructor. */
componentOptions: [
'target',
'accessors',
'anchor',
'props',
'hydrate',
'intro',
'context',
],

/**
* Mount the component into the DOM.
*
* The `onDestroy` callback is included for strict backwards compatibility
* with previous versions of this library. It's mostly unnecessary logic.
*/
renderComponent: (ComponentConstructor, componentOptions, onDestroy) => {
const component = new ComponentConstructor(componentOptions)

component.$$.on_destroy.push(() => {
onDestroy(component)
})

return component
},

/** Update the component's props. */
updateProps: (component, nextProps) => {
component.$set(nextProps)
},

/** Remove the component from the DOM. */
cleanupComponent: (component) => {
component.$destroy()
},
}
51 changes: 51 additions & 0 deletions src/core.svelte.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Rendering core for svelte-testing-library.
*
* Defines how components are added to and removed from the DOM.
* Will switch to legacy, class-based mounting logic
* if it looks like we're in a Svelte <= 4 environment.
*/
import * as Svelte from 'svelte'

import { LegacyCore } from './core-legacy'

const IS_MODERN_SVELTE = typeof Svelte.mount === 'function'

/** Props signals for each rendered component. */
const propsByComponent = new Map()

const ModernCore = {
/** Allowed options to the `mount` call. */
componentOptions: ['target', 'anchor', 'props', 'events', 'context', 'intro'],

/** Mount the component into the DOM. */
renderComponent: (ComponentConstructor, componentOptions) => {
const props = $state(componentOptions.props ?? {})
const component = Svelte.mount(ComponentConstructor, {
...componentOptions,
props,
})

propsByComponent.set(component, props)

return component
},

/**
* Update the component's props.
*
* Relies on the `$state` signal added in `renderComponent`.
*/
updateProps: (component, nextProps) => {
const prevProps = propsByComponent.get(component)
Object.assign(prevProps, nextProps)
},

/** Remove the component from the DOM. */
cleanupComponent: (component) => {
propsByComponent.delete(component)
Svelte.unmount(component)
},
}

export const Core = IS_MODERN_SVELTE ? ModernCore : LegacyCore
Loading

0 comments on commit 7a4da48

Please sign in to comment.