Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ package-lock.json
yarn.lock

# generated typing output
types
dist
*.tsbuildinfo

# copied documentation
Expand Down
18 changes: 10 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,20 @@ The module is released automatically from the `main` and `next` branches using [

This repository uses `pnpm` as its package manager. See the `pnpm` [installation guide](https://pnpm.io/installation) to set it up through whatever method you prefer.

After cloning the repository, use the `setup` script to install dependencies and run all checks:
After cloning the repository, use the `setup` script to install dependencies, build, and run all checks:

```shell
pnpm run setup
```

### Build

To build types and docs:

```shell
pnpm run build
```

### Lint and format

Run auto-formatting to ensure any changes adhere to the code style of the repository:
Expand Down Expand Up @@ -64,13 +72,7 @@ pnpm run install:3
pnpm run all:legacy
```

### Docs

Use the `docs` script to ensure the README's table of contents is up to date:

```shell
pnpm run docs
```
### Contributors

Use `contributors:add` to add a contributor to the README:

Expand Down
3 changes: 2 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default tseslint.config(
},
{
name: 'ignores',
ignores: ['**/coverage/**', '**/types/**'],
ignores: ['**/coverage/**', '**/dist/**'],
},
{
name: 'simple-import-sort',
Expand Down Expand Up @@ -68,6 +68,7 @@ export default tseslint.config(
name: 'extras',
rules: {
'unicorn/prevent-abbreviations': 'off',
'unicorn/prefer-dom-node-append': 'off',
},
},
{
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"scripts": {
"all": "pnpm contributors:generate && pnpm build && pnpm format && pnpm test:all && pnpm typecheck",
"all:legacy": "pnpm build:types && pnpm test:all:legacy && pnpm typecheck:legacy",
"docs": "",
"lint": "prettier . --check && eslint .",
"format": "prettier . --write && eslint . --fix",
"setup": "pnpm install:5 && pnpm all",
Expand All @@ -21,7 +20,7 @@
"test:all": "pnpm test:vitest:jsdom && pnpm test:vitest:happy-dom && pnpm test:jest && pnpm test:examples",
"test:all:legacy": "pnpm test:vitest:jsdom && pnpm test:vitest:happy-dom && pnpm test:jest",
"build": "pnpm build:types && pnpm build:docs",
"build:types": "tsc --build && cp packages/svelte/src/component-types.d.ts packages/svelte/types",
"build:types": "tsc --build && cp packages/svelte/src/core/types.d.ts packages/svelte/dist/core",
"build:docs": "remark --output --use remark-toc --use remark-code-import --use unified-prettier README.md examples && cp -f README.md packages/svelte",
"contributors:add": "all-contributors add",
"contributors:generate": "all-contributors generate",
Expand Down
10 changes: 5 additions & 5 deletions packages/svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@
"main": "src/index.js",
"exports": {
".": {
"types": "./types/index.d.ts",
"types": "./dist/index.d.ts",
"default": "./src/index.js"
},
"./svelte5": {
"types": "./types/index.d.ts",
"types": "./dist/index.d.ts",
"default": "./src/index.js"
},
"./vitest": {
"types": "./types/vitest.d.ts",
"types": "./dist/vitest.d.ts",
"default": "./src/vitest.js"
},
"./vite": {
"types": "./types/vite.d.ts",
"types": "./dist/vite.d.ts",
"default": "./src/vite.js"
}
},
"type": "module",
"types": "types/index.d.ts",
"types": "dist/index.d.ts",
"license": "MIT",
"homepage": "https://github.com/testing-library/svelte-testing-library#readme",
"repository": {
Expand Down
9 changes: 3 additions & 6 deletions packages/svelte/src/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
* Will switch to legacy, class-based mounting logic
* if it looks like we're in a Svelte <= 4 environment.
*/
export { addCleanupTask, cleanup } from './cleanup.js'
export { mount } from './mount.js'
export {
UnknownSvelteOptionsError,
validateOptions,
} from './validate-options.js'
export * from './cleanup.js'
export * from './mount.js'
export * from './setup.js'
13 changes: 3 additions & 10 deletions packages/svelte/src/core/mount.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,7 @@ import * as Svelte from 'svelte'

import { addCleanupTask, removeCleanupTask } from './cleanup.js'
import { createProps } from './props.svelte.js'

/** Whether we're using Svelte >= 5. */
const IS_MODERN_SVELTE = typeof Svelte.mount === 'function'

/** Allowed options to the `mount` call or legacy component constructor. */
const ALLOWED_MOUNT_OPTIONS = IS_MODERN_SVELTE
? ['target', 'anchor', 'props', 'events', 'context', 'intro']
: ['target', 'accessors', 'anchor', 'props', 'hydrate', 'intro', 'context']
import { IS_MODERN_SVELTE } from './svelte-version.js'

/** Mount a modern Svelte 5 component into the DOM. */
const mountModern = (Component, options) => {
Expand Down Expand Up @@ -78,7 +71,7 @@ const mountComponent = IS_MODERN_SVELTE ? mountModern : mountLegacy
* rerender: (props: Partial<import('./types.js').Props<C>>) => Promise<void>
* }}
*/
const mount = (Component, options = {}) => {
const mount = (Component, options) => {
const { component, unmount, rerender } = mountComponent(Component, options)

return {
Expand All @@ -92,4 +85,4 @@ const mount = (Component, options = {}) => {
}
}

export { ALLOWED_MOUNT_OPTIONS, mount }
export { mount }
86 changes: 86 additions & 0 deletions packages/svelte/src/core/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/** Set up the document to render a component. */
import { addCleanupTask } from './cleanup.js'
import { IS_MODERN_SVELTE } from './svelte-version.js'

/** Allowed options to the `mount` call or legacy component constructor. */
const ALLOWED_MOUNT_OPTIONS = IS_MODERN_SVELTE
? ['target', 'anchor', 'props', 'events', 'context', 'intro']
: ['target', 'accessors', 'anchor', 'props', 'hydrate', 'intro', 'context']

class UnknownSvelteOptionsError extends TypeError {
constructor(unknownOptions) {
super(`Unknown options.

Unknown: [ ${unknownOptions.join(', ')} ]
Allowed: [ ${ALLOWED_MOUNT_OPTIONS.join(', ')} ]

To pass both Svelte options and props to a component,
or to use props that share a name with a Svelte option,
you must place all your props under the \`props\` key:

render(Component, { props: { /** props here **/ } })
`)
this.name = 'UnknownSvelteOptionsError'
}
}

/**
* Validate a component's mount options.
*
* @template {import('./types.js').Component} C
* @param {import('./types.js').ComponentOptions<C>} options - props or mount options
* @returns {Partial<import('./types.js').MountOptions<C>>}
*/
const validateOptions = (options) => {
const isProps = !Object.keys(options).some((option) =>
ALLOWED_MOUNT_OPTIONS.includes(option)
)

if (isProps) {
return { props: options }
}

// Check if any props and Svelte options were accidentally mixed.
const unknownOptions = Object.keys(options).filter(
(option) => !ALLOWED_MOUNT_OPTIONS.includes(option)
)

if (unknownOptions.length > 0) {
throw new UnknownSvelteOptionsError(unknownOptions)
}

return options
}

/**
* Set up the document to render a component.
*
* @template {import('./types.js').Component} C
* @param {import('./types.js').ComponentOptions<C>} componentOptions - props or mount options
* @param {{ baseElement?: HTMLElement | undefined }} setupOptions - base element of the document to bind any queries
* @returns {{
* baseElement: HTMLElement,
* target: HTMLElement,
* mountOptions: import('./types.js).MountOptions<C>
* }}
*/
const setup = (componentOptions, setupOptions) => {
const mountOptions = validateOptions(componentOptions)

const baseElement =
setupOptions.baseElement ?? mountOptions.target ?? document.body

const target =
mountOptions.target ??
baseElement.appendChild(document.createElement('div'))

addCleanupTask(() => {
if (target.parentNode === document.body) {
target.remove()
}
})

return { baseElement, target, mountOptions: { ...mountOptions, target } }
}

export { setup, UnknownSvelteOptionsError }
7 changes: 7 additions & 0 deletions packages/svelte/src/core/svelte-version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** Detect which version of Svelte we're using */
import * as Svelte from 'svelte'

/** Whether we're using Svelte >= 5. */
const IS_MODERN_SVELTE = typeof Svelte.mount === 'function'

export { IS_MODERN_SVELTE }
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-deprecated */
/**
* Component and utility types.
*
* Supports components from Svelte 3, 4, and 5.
*/
import type {
Component as ModernComponent,
ComponentConstructorOptions as LegacyConstructorOptions,
Expand Down Expand Up @@ -59,3 +64,8 @@ export type Exports<C> = IS_MODERN_SVELTE extends true
export type MountOptions<C extends Component> = IS_MODERN_SVELTE extends true
? Parameters<typeof mount<Props<C>, Exports<C>>>[1]
: LegacyConstructorOptions<Props<C>>

/** A component's props or some of its mount options. */
export type ComponentOptions<C extends Component> =
| Props<C>
| Partial<MountOptions<C>>
41 changes: 0 additions & 41 deletions packages/svelte/src/core/validate-options.js

This file was deleted.

12 changes: 8 additions & 4 deletions packages/svelte/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ if (typeof process !== 'undefined' && !process.env.STL_SKIP_AUTO_CLEANUP) {
export * from '@testing-library/dom'

// export svelte-specific functions and custom `fireEvent`
export { UnknownSvelteOptionsError } from './core/index.js'
export * from './pure.js'
// `fireEvent` must be named to take priority over wildcard from @testing-library/dom
export { fireEvent } from './pure.js'
export {
act,
cleanup,
fireEvent,
render,
setup,
UnknownSvelteOptionsError,
} from './pure.js'
Loading