Skip to content

Commit 4e49ff7

Browse files
committed
feat(core): create standalone core module
1 parent dc415ae commit 4e49ff7

File tree

22 files changed

+531
-160
lines changed

22 files changed

+531
-160
lines changed

.editorconfig

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,4 @@ insert_final_newline = true
88
trim_trailing_whitespace = true
99

1010
[*.md]
11-
max_line_length = off
1211
trim_trailing_whitespace = false

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ jobs:
138138
- name: 🚀 Release
139139
run: |
140140
pnpm \
141-
--package="@anolilab/multi-semantic-release@2" \
142-
--package="@anolilab/semantic-release-pnpm@2" \
141+
--package="@anolilab/multi-semantic-release@3" \
142+
--package="@anolilab/semantic-release-pnpm@3" \
143143
--package="semantic-release@25" \
144144
--package="conventional-changelog-conventionalcommits@9" \
145145
dlx \

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
"test:all": "pnpm test:vitest:jsdom && pnpm test:vitest:happy-dom && pnpm test:jest && pnpm test:examples",
2121
"test:all:legacy": "pnpm test:vitest:jsdom && pnpm test:vitest:happy-dom && pnpm test:jest",
2222
"build": "pnpm build:types && pnpm build:docs",
23-
"build:types": "tsc --build && cp packages/svelte/src/core/types.d.ts packages/svelte/dist/core",
24-
"build:docs": "remark --output --use remark-toc --use remark-code-import --use unified-prettier README.md examples && cp -f README.md packages/svelte",
23+
"build:types": "tsc --build",
24+
"build:docs": "remark --output --use remark-toc --use remark-code-import --use unified-prettier README.md packages/*/README.md examples && cp -f README.md packages/svelte",
2525
"contributors:add": "all-contributors add",
2626
"contributors:generate": "all-contributors generate",
2727
"install:3": "./scripts/install-dependencies 3",

packages/svelte-core/README.md

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# @testing-library/svelte-core
2+
3+
Do you want to build your own Svelte testing library? You may want to use our
4+
rendering core, which abstracts away differences in Svelte versions to provide a
5+
simple API to render Svelte components into the document and clean them up
6+
afterwards
7+
8+
## Table of Contents
9+
10+
- [Example Usage](#example-usage)
11+
- [API](#api)
12+
- [`render`](#render)
13+
- [`setup`](#setup)
14+
- [`mount`](#mount)
15+
- [`cleanup`](#cleanup)
16+
- [`addCleanupTask`](#addcleanuptask)
17+
- [`removeCleanupTask`](#removecleanuptask)
18+
- [Utility types](#utility-types)
19+
20+
## Example Usage
21+
22+
```ts
23+
import { beforeEach } from 'vitest'
24+
import * as SvelteCore from '@testing-library/svelte-core'
25+
26+
import { bindQueries, type Screen } from './bring-your-own-queries.js'
27+
28+
beforeEach(() => {
29+
cleanup()
30+
})
31+
32+
export interface RenderResult<C extends SvelteCore.Component> {
33+
screen: Screen
34+
component: SvelteCore.Exports<C>
35+
rerender: SvelteCore.Rerender<C>
36+
unmount: () => void
37+
}
38+
39+
export const render = <C extends SvelteCore.Component>(
40+
component: SvelteCore.ComponentImport<C>,
41+
options: SvelteCore.ComponentOptions<C>
42+
): RenderResult<C> => {
43+
const { baseElement, component, unmount, rerender } = SvelteCore.render(
44+
component,
45+
options
46+
)
47+
const screen = bindQueries(baseElement)
48+
49+
return { screen, component, rerender, unmount }
50+
}
51+
```
52+
53+
## API
54+
55+
### `render`
56+
57+
Set up the document and mount a component into that document.
58+
59+
```ts
60+
const { baseElement, container, component, unmount, rerender } = render(
61+
Component,
62+
componentOptions,
63+
setupOptions
64+
)
65+
```
66+
67+
| Argument | Type | Description |
68+
| ------------------ | ---------------------------------------- | ---------------------------------------------- |
69+
| `Component` | [Svelte component][] | An imported Svelte component |
70+
| `componentOptions` | `Props` or partial [component options][] | Options for how the component will be rendered |
71+
| `setupOptions` | `{ baseElement?: HTMLElement }` | Optionally override `baseElement` |
72+
73+
| Result | Type | Description | Default |
74+
| ------------- | ------------------------------------------ | ---------------------------------------- | ----------------------------------- |
75+
| `baseElement` | `HTMLElement` | The base element | `document.body` |
76+
| `target` | `HTMLElement` | The component's immediate parent element | `<div>` appended to `document.body` |
77+
| `component` | [component instance][] | The component instance | N/A |
78+
| `rerender` | `(props: Partial<Props>) => Promise<void>` | Update the component's props | N/A |
79+
| `unmount` | `() => void` | Unmount the component from the document | N/A |
80+
81+
> \[!TIP]
82+
> Calling `render` is equivalent to calling `setup` followed by `mount`
83+
>
84+
> ```ts
85+
> const { baseElement, container, mountOptions } = setup(
86+
> componentOptions,
87+
> setupOptions
88+
> )
89+
> const { component, rerender, unmount } = mount(Component, mountOptions)
90+
> ```
91+
92+
### `setup`
93+
94+
Validate options and prepare document elements for rendering.
95+
96+
```ts
97+
const { baseElement, target, mountOptions } = setup(options, renderOptions)
98+
```
99+
100+
| Argument | Type | Description |
101+
| ------------------ | ---------------------------------------- | ---------------------------------------------- |
102+
| `componentOptions` | `Props` or partial [component options][] | Options for how the component will be rendered |
103+
| `setupOptions` | `{ baseElement?: HTMLElement }` | Optionally override `baseElement` |
104+
105+
| Result | Type | Description | Default |
106+
| -------------- | --------------------- | ------------------------------------------- | ----------------------------------- |
107+
| `baseElement` | `HTMLElement` | The base element | `document.body` |
108+
| `container` | `HTMLElement` | The component's immediate parent element | `<div>` appended to `document.body` |
109+
| `mountOptions` | [component options][] | Validated Svelte options to pass to `mount` | `{ target, props: {} }` |
110+
111+
[component options]: https://svelte.dev/docs/client-side-component-api
112+
113+
### `mount`
114+
115+
Mount a Svelte component into the document.
116+
117+
```ts
118+
const { component, unmount, rerender } = mount(Component, options)
119+
```
120+
121+
| Argument | Type | Description |
122+
| -------------- | --------------------- | ---------------------------- |
123+
| `Component` | [Svelte component][] | An imported Svelte component |
124+
| `mountOptions` | [component options][] | Svelte component options |
125+
126+
| Result | Type | Description |
127+
| ----------- | ------------------------------------------ | --------------------------------------- |
128+
| `component` | [component instance][] | The component instance |
129+
| `unmount` | `() => void` | Unmount the component from the document |
130+
| `rerender` | `(props: Partial<Props>) => Promise<void>` | Update the component's props |
131+
132+
[Svelte component]: https://svelte.dev/docs/svelte-components
133+
[component instance]: https://svelte.dev/docs/client-side-component-api
134+
135+
### `cleanup`
136+
137+
Cleanup rendered components and added elements. Call this when your tests are
138+
over.
139+
140+
```ts
141+
cleanup()
142+
```
143+
144+
### `addCleanupTask`
145+
146+
Add a custom cleanup task to be called with `cleanup()`
147+
148+
```ts
149+
addCleanupTask(() => {
150+
// ...reset something
151+
})
152+
```
153+
154+
### `removeCleanupTask`
155+
156+
Remove a cleanup task from `cleanup()`. Useful if a cleanup task can only be run
157+
once and may be run outside of `cleanup`
158+
159+
```ts
160+
const customCleanup = () => {
161+
// ...reset something
162+
}
163+
164+
addCleanupTask(customCleanup)
165+
166+
const manuallyCleanupEarly = () => {
167+
customCleanup()
168+
removeCleanupTask(customCleanup)
169+
}
170+
```
171+
172+
### Utility types
173+
174+
This module exports various utility types from
175+
`@testing-library/svelte-core/types`. They adapt to whatever Svelte version is
176+
installed, and can be used to get type signatures for imported components,
177+
props, events, etc.
178+
179+
See [`./types.d.ts`](./types.d.ts) for the full list of types available.

packages/svelte-core/package.json

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"name": "@testing-library/svelte-core",
3+
"version": "0.0.0-semantically-released",
4+
"description": "Core rendering and cleanup logic for Svelte testing utilities.",
5+
"exports": {
6+
".": {
7+
"types": "./dist/index.d.ts",
8+
"svelte": "./src/index.js",
9+
"default": "./src/index.js"
10+
},
11+
"./types": {
12+
"types": "./types.d.ts"
13+
}
14+
},
15+
"type": "module",
16+
"license": "MIT",
17+
"homepage": "https://github.com/testing-library/svelte-testing-library#readme",
18+
"repository": {
19+
"type": "git",
20+
"url": "git+https://github.com/testing-library/svelte-testing-library.git",
21+
"directory": "packages/svelte-core"
22+
},
23+
"bugs": {
24+
"url": "https://github.com/testing-library/svelte-testing-library/issues"
25+
},
26+
"engines": {
27+
"node": ">=16"
28+
},
29+
"keywords": [
30+
"testing",
31+
"svelte",
32+
"ui",
33+
"dom",
34+
"jsdom",
35+
"unit",
36+
"integration",
37+
"functional",
38+
"end-to-end",
39+
"e2e"
40+
],
41+
"files": [
42+
"dist",
43+
"src",
44+
"types.d.ts"
45+
],
46+
"peerDependencies": {
47+
"svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0"
48+
},
49+
"publishConfig": {
50+
"access": "public",
51+
"provenance": true
52+
}
53+
}
File renamed without changes.

packages/svelte/src/core/index.js renamed to packages/svelte-core/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
*/
88
export * from './cleanup.js'
99
export * from './mount.js'
10+
export * from './render.js'
1011
export * from './setup.js'

packages/svelte/src/core/mount.js renamed to packages/svelte-core/src/mount.js

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ import { addCleanupTask, removeCleanupTask } from './cleanup.js'
77
import { createProps } from './props.svelte.js'
88
import { IS_MODERN_SVELTE } from './svelte-version.js'
99

10-
/** Mount a modern Svelte 5 component into the DOM. */
10+
/**
11+
* Mount a modern Svelte 5 component into the DOM.
12+
*
13+
* @template {import('../types.js').Component} C
14+
* @param {import('../types.js').ComponentType<C>} Component
15+
* @param {import('../types.js').MountOptions<C>} options
16+
* @returns {import('../types.js').MountResult<C>}
17+
*/
1118
const mountModern = (Component, options) => {
1219
const [props, updateProps] = createProps(options.props)
1320
const component = Svelte.mount(Component, { ...options, props })
@@ -29,7 +36,14 @@ const mountModern = (Component, options) => {
2936
return { component, unmount, rerender }
3037
}
3138

32-
/** Mount a legacy Svelte 3 or 4 component into the DOM. */
39+
/**
40+
* Mount a legacy Svelte 3 or 4 component into the DOM.
41+
*
42+
* @template {import('../types.js').LegacyComponent} C
43+
* @param {import('../types.js').ComponentType<C>} Component
44+
* @param {import('../types.js').MountOptions<C>} options
45+
* @returns {import('../types.js').MountResult<C>}
46+
*/
3347
const mountLegacy = (Component, options) => {
3448
const component = new Component(options)
3549

@@ -62,24 +76,30 @@ const mountComponent = IS_MODERN_SVELTE ? mountModern : mountLegacy
6276
/**
6377
* Render a Svelte component into the document.
6478
*
65-
* @template {import('./types.js').Component} C
66-
* @param {import('./types.js').ComponentType<C>} Component
67-
* @param {import('./types.js').MountOptions<C>} options
68-
* @returns {{
69-
* component: C
70-
* unmount: () => void
71-
* rerender: (props: Partial<import('./types.js').Props<C>>) => Promise<void>
72-
* }}
79+
* @template {import('../types.js').Component} C
80+
* @param {import('../types.js').ComponentImport<C>} Component
81+
* @param {import('../types.js').MountOptions<C>} options
82+
* @returns {import('../types.js').MountResult<C>}
7383
*/
7484
const mount = (Component, options) => {
75-
const { component, unmount, rerender } = mountComponent(Component, options)
85+
const { component, unmount, rerender } = mountComponent(
86+
'default' in Component ? Component.default : Component,
87+
options
88+
)
7689

7790
return {
7891
component,
7992
unmount,
8093
rerender: async (props) => {
94+
if ('props' in props) {
95+
console.warn(
96+
'rerender({ props: { ... } }) deprecated, use rerender({ ... }) instead'
97+
)
98+
props = props.props
99+
}
100+
81101
rerender(props)
82-
// Await the next tick for Svelte 4, which cannot flush changes synchronously
102+
// Await the next tick for Svelte 3/4, which cannot flush changes synchronously
83103
await Svelte.tick()
84104
},
85105
}

packages/svelte/src/core/props.svelte.js renamed to packages/svelte-core/src/props.svelte.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@
88
* @param {Props} initialProps
99
* @returns {[Props, (nextProps: Partial<Props>) => void]}
1010
*/
11-
const createProps = (initialProps) => {
12-
const targetProps = initialProps ?? {}
13-
let currentProps = $state.raw(targetProps)
11+
const createProps = (initialProps = {}) => {
12+
let currentProps = $state.raw(initialProps)
1413

15-
const props = new Proxy(targetProps, {
14+
const props = new Proxy(initialProps, {
1615
get(_, key) {
1716
return currentProps[key]
1817
},

packages/svelte-core/src/render.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { mount } from './mount.js'
2+
import { setup } from './setup.js'
3+
4+
/**
5+
* Render a component into the document.
6+
*
7+
* @template {import('../types.js').Component} C
8+
*
9+
* @param {import('../types.js').ComponentImport<C>} Component - The component to render.
10+
* @param {import('../types.js').ComponentOptions<C>} componentOptions - Customize how Svelte renders the component.
11+
* @param {import('../types.js').SetupOptions<C>} setupOptions - Customize how the document is set up.
12+
* @returns {import('../types.js').RenderResult<C>} The rendered component.
13+
*/
14+
const render = (Component, componentOptions, setupOptions = {}) => {
15+
const { mountOptions, ...setupResult } = setup(componentOptions, setupOptions)
16+
const mountResult = mount(Component, mountOptions)
17+
18+
return { ...setupResult, ...mountResult }
19+
}
20+
21+
export { render }

0 commit comments

Comments
 (0)