Skip to content

Commit 694e49d

Browse files
committed
feat(core): add public API for core
1 parent d64de4a commit 694e49d

File tree

12 files changed

+298
-181
lines changed

12 files changed

+298
-181
lines changed

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
"types": "./types/index.d.ts",
99
"default": "./src/index.js"
1010
},
11+
"./core": {
12+
"types": "./types/core/index.d.ts",
13+
"default": "./src/core/index.js"
14+
},
1115
"./svelte5": {
1216
"types": "./types/index.d.ts",
1317
"default": "./src/index.js"
@@ -53,7 +57,7 @@
5357
"!__tests__"
5458
],
5559
"scripts": {
56-
"toc": "doctoc README.md",
60+
"toc": "doctoc README.md src/core/README.md",
5761
"lint": "prettier . --check && eslint .",
5862
"lint:delta": "npm-run-all -p prettier:delta eslint:delta",
5963
"prettier:delta": "prettier --check `./scripts/changed-files`",

src/core/README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# @testing-library/svelte core
2+
3+
Do you want to build your own Svelte testing library? You may want to use our rendering core, which abstracts away differences in Svelte versions to provide a simple API to render Svelte components into the document and clean them up afterwards
4+
5+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
6+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
7+
8+
- [Usage](#usage)
9+
- [API](#api)
10+
- [`prepareDocument`](#preparedocument)
11+
- [`renderComponent`](#rendercomponent)
12+
- [`cleanup`](#cleanup)
13+
14+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
15+
16+
## Usage
17+
18+
```ts
19+
import { prepareDocument, renderComponent, cleanup } from '@testing-library/svelte/core'
20+
import MyCoolComponent from './my-cool-component.svelte'
21+
22+
const { baseElement, target, options } = prepareDocument({ awesome: true })
23+
const { component, unmount, rerender } = renderComponent(MyCoolComponent, options)
24+
25+
// later
26+
cleanup()
27+
```
28+
29+
## API
30+
31+
### `prepareDocument`
32+
33+
Validate options and prepare document elements for rendering.
34+
35+
```ts
36+
const { baseElement, target, options } = prepareDocument(propsOrOptions, renderOptions)
37+
```
38+
39+
| Argument | Type | Description |
40+
| ---------------- | ---------------------------------------- | --------------------------------------------------------------------- |
41+
| `propsOrOptions` | `Props` or partial [component options][] | The component's props, or options to pass to Svelte's client-side API |
42+
| `renderOptions` | `{ baseElement?: HTMLElement }` | customize `baseElement`; will be `document.body` if unspecified |
43+
44+
| Result | Type | Description |
45+
| ------------- | --------------------- | -------------------------------------------------------------------- |
46+
| `baseElement` | `HTMLElement` | The base element, `document.body` by default |
47+
| `target` | `HTMLElement` | The component's `target` element, a `<div>` by default |
48+
| `options` | [component options][] | Validated and normalized Svelte options to pass to `renderComponent` |
49+
50+
[component options]: https://svelte.dev/docs/client-side-component-api
51+
52+
### `renderComponent`
53+
54+
Render a Svelte component into the document.
55+
56+
```ts
57+
const { component, unmount, rerender } = renderComponent(Component, options)
58+
```
59+
60+
| Argument | Type | Description |
61+
| ----------- | --------------------- | ---------------------------- |
62+
| `Component` | [Svelte component][] | An imported Svelte component |
63+
| `options` | [component options][] | Svelte component options |
64+
65+
| Result | Type | Description |
66+
| ----------- | ------------------------------------------ | -------------------------------------------------- |
67+
| `component` | [component instance][] | The component instance |
68+
| `unmount` | `() => void` | Unmount the component from the document |
69+
| `rerender` | `(props: Partial<Props>) => Promise<void>` | Update the component's props and wait for rerender |
70+
71+
[Svelte component]: https://svelte.dev/docs/svelte-components
72+
[component instance]: https://svelte.dev/docs/client-side-component-api
73+
74+
### `cleanup`
75+
76+
Cleanup rendered components and added elements. Call this when your tests are over.
77+
78+
```ts
79+
cleanup()
80+
```

src/core/cleanup.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/** @type {Map<unknown, () => void} */
2+
const itemsToClean = new Map()
3+
4+
/** Register an item for later cleanup. */
5+
const addItemToCleanup = (item, onCleanup) => {
6+
itemsToClean.set(item, onCleanup)
7+
}
8+
9+
/** Remove an individual item from cleanup without running its cleanup handler. */
10+
const removeItemFromCleanup = (item) => {
11+
itemsToClean.delete(item)
12+
}
13+
14+
/** Clean up an individual item. */
15+
const cleanupItem = (item) => {
16+
const handleCleanup = itemsToClean.get(item)
17+
handleCleanup?.()
18+
itemsToClean.delete(item)
19+
}
20+
21+
/** Clean up all components and elements added to the document. */
22+
const cleanup = () => {
23+
for (const handleCleanup of itemsToClean.values()) {
24+
handleCleanup()
25+
}
26+
27+
itemsToClean.clear()
28+
}
29+
30+
export { addItemToCleanup, cleanup, cleanupItem, removeItemFromCleanup }

src/core/index.js

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,6 @@
55
* Will switch to legacy, class-based mounting logic
66
* if it looks like we're in a Svelte <= 4 environment.
77
*/
8-
import * as LegacyCore from './legacy.js'
9-
import * as ModernCore from './modern.svelte.js'
10-
import {
11-
createValidateOptions,
12-
UnknownSvelteOptionsError,
13-
} from './validate-options.js'
14-
15-
const { mount, unmount, updateProps, allowedOptions } =
16-
ModernCore.IS_MODERN_SVELTE ? ModernCore : LegacyCore
17-
18-
/** Validate component options. */
19-
const validateOptions = createValidateOptions(allowedOptions)
20-
21-
export {
22-
mount,
23-
UnknownSvelteOptionsError,
24-
unmount,
25-
updateProps,
26-
validateOptions,
27-
}
8+
export { cleanup } from './cleanup.js'
9+
export { mount, prepareDocument } from './mount.js'
10+
export { UnknownSvelteOptionsError } from './validate-options.js'

src/core/legacy.js

Lines changed: 0 additions & 46 deletions
This file was deleted.

src/core/modern.svelte.js

Lines changed: 0 additions & 50 deletions
This file was deleted.

src/core/mount-legacy.js

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

src/core/mount-modern.svelte.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Modern rendering core for svelte-testing-library.
3+
*
4+
* Supports Svelte >= 5.
5+
*/
6+
import * as Svelte from 'svelte'
7+
8+
/** Whether we're using Svelte >= 5. */
9+
const IS_MODERN_SVELTE = typeof Svelte.mount === 'function'
10+
11+
/** Allowed options to the `mount` call. */
12+
const allowedOptions = [
13+
'target',
14+
'anchor',
15+
'props',
16+
'events',
17+
'context',
18+
'intro',
19+
]
20+
21+
/** Mount the component into the DOM. */
22+
const mountComponent = (Component, options) => {
23+
const props = $state(options.props ?? {})
24+
const component = Svelte.mount(Component, { ...options, props })
25+
26+
/** Remove the component from the DOM. */
27+
const unmountComponent = () => {
28+
Svelte.unmount(component)
29+
}
30+
31+
/** Update the component's props. */
32+
const updateProps = (nextProps) => {
33+
Object.assign(props, nextProps)
34+
}
35+
36+
return { component, unmountComponent, updateProps }
37+
}
38+
39+
export { allowedOptions, IS_MODERN_SVELTE, mountComponent }

0 commit comments

Comments
 (0)