diff --git a/.editorconfig b/.editorconfig index ff9b6fc..03c17bf 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,5 +8,4 @@ insert_final_newline = true trim_trailing_whitespace = true [*.md] -max_line_length = off trim_trailing_whitespace = false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e321926..2178ff8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -138,8 +138,8 @@ jobs: - name: 🚀 Release run: | pnpm \ - --package="@anolilab/multi-semantic-release@2" \ - --package="@anolilab/semantic-release-pnpm@2" \ + --package="@anolilab/multi-semantic-release@3" \ + --package="@anolilab/semantic-release-pnpm@3" \ --package="semantic-release@25" \ --package="conventional-changelog-conventionalcommits@9" \ dlx \ diff --git a/package.json b/package.json index 020da30..307b300 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ "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/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", + "build:types": "tsc --build", + "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", "contributors:add": "all-contributors add", "contributors:generate": "all-contributors generate", "install:3": "./scripts/install-dependencies 3", diff --git a/packages/svelte-core/README.md b/packages/svelte-core/README.md new file mode 100644 index 0000000..ebe6f07 --- /dev/null +++ b/packages/svelte-core/README.md @@ -0,0 +1,173 @@ +# @testing-library/svelte-core + +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 + +## Table of Contents + +- [Example Usage](#example-usage) +- [API](#api) + - [`render`](#render) + - [`setup`](#setup) + - [`mount`](#mount) + - [`cleanup`](#cleanup) + - [`addCleanupTask`](#addcleanuptask) + - [`removeCleanupTask`](#removecleanuptask) + - [Utility types](#utility-types) + +## Example Usage + +```ts +import { beforeEach } from 'vitest' +import * as SvelteCore from '@testing-library/svelte-core' + +import { bindQueries, type Screen } from './bring-your-own-queries.js' + +beforeEach(() => { + SvelteCore.cleanup() +}) + +export interface RenderResult< + C extends SvelteCore.Component, +> extends SvelteCore.RenderResult { + screen: Screen +} + +export const render = ( + Component: SvelteCore.ComponentImport, + options: SvelteCore.ComponentOptions +): RenderResult => { + const renderResult = SvelteCore.render(Component, options) + const screen = bindQueries(baseElement) + + return { screen, ...renderResult } +} +``` + +## API + +### `render` + +Set up the document and mount a component into that document. + +```ts +const { baseElement, container, component, unmount, rerender } = render( + Component, + componentOptions, + setupOptions +) +``` + +| Argument | Type | Description | +| ------------------ | ------------------------------------------------------- | --------------------------------------------- | +| `Component` | [Svelte component][svelte-component-docs] | An imported Svelte component | +| `componentOptions` | `Props` or partial [`mount` options][svelte-mount-docs] | Options for how the component will be mounted | +| `setupOptions` | `{ baseElement?: HTMLElement }` | Optionally override `baseElement` | + +| Result | Type | Description | Default | +| ------------- | ------------------------------------------ | ---------------------------------------- | ----------------------------------- | +| `baseElement` | `HTMLElement` | The base element | `document.body` | +| `container` | `HTMLElement` | The component's immediate parent element | `
` appended to `document.body` | +| `component` | [component exports][svelte-mount-docs] | The component's exports from `mount` | N/A | +| `rerender` | `(props: Partial) => Promise` | Update the component's props | N/A | +| `unmount` | `() => void` | Unmount the component from the document | N/A | + +> \[!TIP] +> Calling `render` is equivalent to calling `setup` followed by `mount` +> +> ```ts +> const { baseElement, container, mountOptions } = setup( +> componentOptions, +> setupOptions +> ) +> const { component, rerender, unmount } = mount(Component, mountOptions) +> ``` + +[svelte-component-docs]: https://svelte.dev/docs/svelte-components +[svelte-mount-docs]: https://svelte.dev/docs/svelte/imperative-component-api#mount + +### `setup` + +Validate options and prepare document elements for rendering. + +```ts +const { baseElement, target, mountOptions } = setup(options, renderOptions) +``` + +| Argument | Type | Description | +| ------------------ | ------------------------------------------------------- | --------------------------------------------- | +| `componentOptions` | `Props` or partial [`mount` options][svelte-mount-docs] | Options for how the component will be mounted | +| `setupOptions` | `{ baseElement?: HTMLElement }` | Optionally override `baseElement` | + +| Result | Type | Description | Default | +| -------------- | ------------------------------------ | ---------------------------------------- | ----------------------------------- | +| `baseElement` | `HTMLElement` | The base element | `document.body` | +| `container` | `HTMLElement` | The component's immediate parent element | `
` appended to `document.body` | +| `mountOptions` | [`mount` options][svelte-mount-docs] | Validated options to pass to `mount` | `{ target, props: {} }` | + +### `mount` + +Mount a Svelte component into the document. + +```ts +const { component, unmount, rerender } = mount(Component, options) +``` + +| Argument | Type | Description | +| -------------- | ----------------------------------------- | -------------------------------------------- | +| `Component` | [Svelte component][svelte-component-docs] | An imported Svelte component | +| `mountOptions` | [component options][svelte-mount-docs] | Options to pass to Svelte's `mount` function | + +| Result | Type | Description | +| ----------- | ------------------------------------------ | --------------------------------------- | +| `component` | [component exports][svelte-mount-docs] | The component's exports from `mount` | +| `unmount` | `() => void` | Unmount the component from the document | +| `rerender` | `(props: Partial) => Promise` | Update the component's props | + +### `cleanup` + +Cleanup rendered components and added elements. Call this when your tests are +over. + +```ts +cleanup() +``` + +### `addCleanupTask` + +Add a custom cleanup task to be called with `cleanup()` + +```ts +addCleanupTask(() => { + // ...reset something +}) +``` + +### `removeCleanupTask` + +Remove a cleanup task from `cleanup()`. Useful if a cleanup task can only be run +once and may be run outside of `cleanup` + +```ts +const customCleanup = () => { + // ...reset something +} + +addCleanupTask(customCleanup) + +const manuallyCleanupEarly = () => { + customCleanup() + removeCleanupTask(customCleanup) +} +``` + +### Utility types + +This module exports various utility types from +`@testing-library/svelte-core/types`. They adapt to whatever Svelte version is +installed, and can be used to get type signatures for imported components, +props, exports, etc. + +See [`./types.d.ts`](./types.d.ts) for the full list of available types. diff --git a/packages/svelte-core/package.json b/packages/svelte-core/package.json new file mode 100644 index 0000000..76811c8 --- /dev/null +++ b/packages/svelte-core/package.json @@ -0,0 +1,53 @@ +{ + "name": "@testing-library/svelte-core", + "version": "0.0.0-semantically-released", + "description": "Core rendering and cleanup logic for Svelte testing utilities.", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "svelte": "./src/index.js", + "default": "./src/index.js" + }, + "./types": { + "types": "./types.d.ts" + } + }, + "type": "module", + "license": "MIT", + "homepage": "https://github.com/testing-library/svelte-testing-library#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/testing-library/svelte-testing-library.git", + "directory": "packages/svelte-core" + }, + "bugs": { + "url": "https://github.com/testing-library/svelte-testing-library/issues" + }, + "engines": { + "node": ">=16" + }, + "keywords": [ + "testing", + "svelte", + "ui", + "dom", + "jsdom", + "unit", + "integration", + "functional", + "end-to-end", + "e2e" + ], + "files": [ + "dist", + "src", + "types.d.ts" + ], + "peerDependencies": { + "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0" + }, + "publishConfig": { + "access": "public", + "provenance": true + } +} diff --git a/packages/svelte/src/core/cleanup.js b/packages/svelte-core/src/cleanup.js similarity index 100% rename from packages/svelte/src/core/cleanup.js rename to packages/svelte-core/src/cleanup.js diff --git a/packages/svelte/src/core/index.js b/packages/svelte-core/src/index.js similarity index 91% rename from packages/svelte/src/core/index.js rename to packages/svelte-core/src/index.js index eb6d2b2..ce20a67 100644 --- a/packages/svelte/src/core/index.js +++ b/packages/svelte-core/src/index.js @@ -7,4 +7,5 @@ */ export * from './cleanup.js' export * from './mount.js' +export * from './render.js' export * from './setup.js' diff --git a/packages/svelte/src/core/mount.js b/packages/svelte-core/src/mount.js similarity index 60% rename from packages/svelte/src/core/mount.js rename to packages/svelte-core/src/mount.js index 6e5d1c5..2927437 100644 --- a/packages/svelte/src/core/mount.js +++ b/packages/svelte-core/src/mount.js @@ -7,7 +7,14 @@ import { addCleanupTask, removeCleanupTask } from './cleanup.js' import { createProps } from './props.svelte.js' import { IS_MODERN_SVELTE } from './svelte-version.js' -/** Mount a modern Svelte 5 component into the DOM. */ +/** + * Mount a modern Svelte 5 component into the DOM. + * + * @template {import('../types.js').Component} C + * @param {import('../types.js').ComponentType} Component + * @param {import('../types.js').MountOptions} options + * @returns {import('../types.js').MountResult} + */ const mountModern = (Component, options) => { const [props, updateProps] = createProps(options.props) const component = Svelte.mount(Component, { ...options, props }) @@ -29,7 +36,14 @@ const mountModern = (Component, options) => { return { component, unmount, rerender } } -/** Mount a legacy Svelte 3 or 4 component into the DOM. */ +/** + * Mount a legacy Svelte 3 or 4 component into the DOM. + * + * @template {import('../types.js').LegacyComponent} C + * @param {import('../types.js').ComponentType} Component + * @param {import('../types.js').MountOptions} options + * @returns {import('../types.js').MountResult} + */ const mountLegacy = (Component, options) => { const component = new Component(options) @@ -62,24 +76,30 @@ const mountComponent = IS_MODERN_SVELTE ? mountModern : mountLegacy /** * Render a Svelte component into the document. * - * @template {import('./types.js').Component} C - * @param {import('./types.js').ComponentType} Component - * @param {import('./types.js').MountOptions} options - * @returns {{ - * component: C - * unmount: () => void - * rerender: (props: Partial>) => Promise - * }} + * @template {import('../types.js').Component} C + * @param {import('../types.js').ComponentImport} Component + * @param {import('../types.js').MountOptions} options + * @returns {import('../types.js').MountResult} */ const mount = (Component, options) => { - const { component, unmount, rerender } = mountComponent(Component, options) + const { component, unmount, rerender } = mountComponent( + 'default' in Component ? Component.default : Component, + options + ) return { component, unmount, rerender: async (props) => { + if ('props' in props) { + console.warn( + 'rerender({ props: { ... } }) deprecated, use rerender({ ... }) instead' + ) + props = props.props + } + rerender(props) - // Await the next tick for Svelte 4, which cannot flush changes synchronously + // Await the next tick for Svelte 3/4, which cannot flush changes synchronously await Svelte.tick() }, } diff --git a/packages/svelte/src/core/props.svelte.js b/packages/svelte-core/src/props.svelte.js similarity index 81% rename from packages/svelte/src/core/props.svelte.js rename to packages/svelte-core/src/props.svelte.js index f181e72..322d6e2 100644 --- a/packages/svelte/src/core/props.svelte.js +++ b/packages/svelte-core/src/props.svelte.js @@ -8,11 +8,10 @@ * @param {Props} initialProps * @returns {[Props, (nextProps: Partial) => void]} */ -const createProps = (initialProps) => { - const targetProps = initialProps ?? {} - let currentProps = $state.raw(targetProps) +const createProps = (initialProps = {}) => { + let currentProps = $state.raw(initialProps) - const props = new Proxy(targetProps, { + const props = new Proxy(initialProps, { get(_, key) { return currentProps[key] }, diff --git a/packages/svelte-core/src/render.js b/packages/svelte-core/src/render.js new file mode 100644 index 0000000..583ec20 --- /dev/null +++ b/packages/svelte-core/src/render.js @@ -0,0 +1,21 @@ +import { mount } from './mount.js' +import { setup } from './setup.js' + +/** + * Render a component into the document. + * + * @template {import('../types.js').Component} C + * + * @param {import('../types.js').ComponentImport} Component - The component to render. + * @param {import('../types.js').ComponentOptions} componentOptions - Customize how Svelte renders the component. + * @param {import('../types.js').SetupOptions} setupOptions - Customize how the document is set up. + * @returns {import('../types.js').RenderResult} The rendered component. + */ +const render = (Component, componentOptions, setupOptions = {}) => { + const { mountOptions, ...setupResult } = setup(componentOptions, setupOptions) + const mountResult = mount(Component, mountOptions) + + return { ...setupResult, ...mountResult } +} + +export { render } diff --git a/packages/svelte/src/core/setup.js b/packages/svelte-core/src/setup.js similarity index 69% rename from packages/svelte/src/core/setup.js rename to packages/svelte-core/src/setup.js index 25466be..4748a45 100644 --- a/packages/svelte/src/core/setup.js +++ b/packages/svelte-core/src/setup.js @@ -27,9 +27,9 @@ class UnknownSvelteOptionsError extends TypeError { /** * Validate a component's mount options. * - * @template {import('./types.js').Component} C - * @param {import('./types.js').ComponentOptions} options - props or mount options - * @returns {Partial>} + * @template {import('../types.js').Component} C + * @param {import('../types.js').ComponentOptions} options - props or mount options + * @returns {Partial>} */ const validateOptions = (options) => { const isProps = !Object.keys(options).some((option) => @@ -55,32 +55,32 @@ const validateOptions = (options) => { /** * Set up the document to render a component. * - * @template {import('./types.js').Component} C - * @param {import('./types.js').ComponentOptions} 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 - * }} + * @template {import('../types.js').Component} C + * @param {import('../types.js').ComponentOptions} componentOptions - props or mount options + * @param {import('../types.js').SetupOptions} setupOptions - base element of the document to bind any queries + * @returns {import('../types.js').SetupResult} */ -const setup = (componentOptions, setupOptions) => { +const setup = (componentOptions, setupOptions = {}) => { const mountOptions = validateOptions(componentOptions) const baseElement = setupOptions.baseElement ?? mountOptions.target ?? document.body - const target = + const container = mountOptions.target ?? baseElement.appendChild(document.createElement('div')) addCleanupTask(() => { - if (target.parentNode === document.body) { - target.remove() + if (container.parentNode === document.body) { + container.remove() } }) - return { baseElement, target, mountOptions: { ...mountOptions, target } } + return { + baseElement, + container, + mountOptions: { ...mountOptions, target: container }, + } } export { setup, UnknownSvelteOptionsError } diff --git a/packages/svelte/src/core/svelte-version.js b/packages/svelte-core/src/svelte-version.js similarity index 100% rename from packages/svelte/src/core/svelte-version.js rename to packages/svelte-core/src/svelte-version.js diff --git a/packages/svelte-core/tsconfig.json b/packages/svelte-core/tsconfig.json new file mode 100644 index 0000000..9e5fee6 --- /dev/null +++ b/packages/svelte-core/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "node16", + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "composite": true, + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src", "types.d.ts"] +} diff --git a/packages/svelte-core/types.d.ts b/packages/svelte-core/types.d.ts new file mode 100644 index 0000000..1a3b3ab --- /dev/null +++ b/packages/svelte-core/types.d.ts @@ -0,0 +1,131 @@ +/* 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, + ComponentProps, + EventDispatcher, + mount, + SvelteComponent as Svelte4LegacyComponent, + SvelteComponentTyped as Svelte3LegacyComponent, +} from 'svelte' + +type IS_MODERN_SVELTE = ModernComponent extends (...args: any[]) => any + ? true + : false + +type IS_LEGACY_SVELTE_4 = + EventDispatcher extends (...args: any[]) => any ? true : false + +/** A compiled, imported Svelte component. */ +export type Component< + P extends Record = any, + E extends Record = any, +> = IS_MODERN_SVELTE extends true + ? ModernComponent | LegacyComponent

+ : LegacyComponent

+ +/** A compiled, imported Svelte 3 or 4 component. */ +export type LegacyComponent

= any> = + IS_LEGACY_SVELTE_4 extends true + ? Svelte4LegacyComponent

+ : Svelte3LegacyComponent

+ +/** + * The "type" of an imported, compiled Svelte component. + * + * In Svelte 5, there is no difference between the + * imported component and its "type" - it's just a function. + * In Svelte 3/4, the imported component is a class. + */ +export type ComponentType = C extends LegacyComponent + ? new (...args: any[]) => C + : C + +/** + * A component import. + * + * A convenience type to allow dynamically `import(...)`'d + * components to be passed directly to `mount.` + */ +export type ComponentImport = + | ComponentType + | { default: ComponentType } + +/** The props of a component. */ +export type Props = ComponentProps + +/** + * The exported fields of a component. + * + * In Svelte 5, this is the set of variables marked as `export`'d. + * In Svelte 4, this is simply the instance of the component class. + */ +export type Exports = IS_MODERN_SVELTE extends true + ? C extends ModernComponent + ? E + : C & { $set: never; $on: never; $destroy: never } + : C + +/** + * Options that may be passed to `mount` when rendering the component. + * + * In Svelte 4, these are the options passed to the component constructor. + */ +export type MountOptions = IS_MODERN_SVELTE extends true + ? Parameters, Exports>>[1] + : LegacyConstructorOptions> + +/** A component's props or some of its mount options. */ +export type ComponentOptions = + | Props + | Partial> + +/** Update a component's props and trigger updates to the DOM. */ +export type Rerender = ( + props: Partial> +) => Promise + +/** The result of mounting a component into the document. */ +export interface MountResult { + /** The mounted component's exports. */ + component: Exports + /** Unmount the component. */ + unmount: () => void + /** Rerender the component. */ + rerender: Rerender +} + +/** Options for configuring the document. */ +export interface SetupOptions { + /** The base document element, `document.body` if unspecified. */ + baseElement?: HTMLElement +} + +/** The result of setting up the document for rendering. */ +export interface SetupResult { + /** The base document element, usually `document.body`. */ + baseElement: HTMLElement + /** The component's immediate container element, usually a `

` appended to `document.body`. */ + container: HTMLElement + /** Options to pass to `mount`. */ + mountOptions: MountOptions +} + +/** The result of setting up the document and rendering the component. */ +export interface RenderResult { + /** The base document element, usually `document.body`. */ + baseElement: HTMLElement + /** The component's immediate container element, usually a `
` appended to `document.body`. */ + container: HTMLElement + /** The mounted component's exports. */ + component: Exports + /** Unmount the component. */ + unmount: () => void + /** Rerender the component. */ + rerender: Rerender +} diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 11837b0..098d966 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -50,7 +50,7 @@ ], "files": [ "src", - "types" + "dist" ], "peerDependencies": { "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0", @@ -66,6 +66,11 @@ } }, "dependencies": { - "@testing-library/dom": "9.x.x || 10.x.x" + "@testing-library/dom": "9.x.x || 10.x.x", + "@testing-library/svelte-core": "workspace:*" + }, + "publishConfig": { + "access": "public", + "provenance": true } } diff --git a/packages/svelte/src/core/types.d.ts b/packages/svelte/src/core/types.d.ts deleted file mode 100644 index 7edd519..0000000 --- a/packages/svelte/src/core/types.d.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* 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, - ComponentProps, - EventDispatcher, - mount, - SvelteComponent as LegacyComponent, - SvelteComponentTyped as Svelte3LegacyComponent, -} from 'svelte' - -type IS_MODERN_SVELTE = ModernComponent extends (...args: any[]) => any - ? true - : false - -type IS_LEGACY_SVELTE_4 = - EventDispatcher extends (...args: any[]) => any ? true : false - -/** A compiled, imported Svelte component. */ -export type Component< - P extends Record = any, - E extends Record = any, -> = IS_MODERN_SVELTE extends true - ? ModernComponent | LegacyComponent

- : IS_LEGACY_SVELTE_4 extends true - ? LegacyComponent

- : Svelte3LegacyComponent

- -/** - * The type of an imported, compiled Svelte component. - * - * In Svelte 5, this distinction no longer matters. - * In Svelte 4, this is the Svelte component class constructor. - */ -export type ComponentType = C extends LegacyComponent - ? new (...args: any[]) => C - : C - -/** The props of a component. */ -export type Props = ComponentProps - -/** - * The exported fields of a component. - * - * In Svelte 5, this is the set of variables marked as `export`'d. - * In Svelte 4, this is simply the instance of the component class. - */ -export type Exports = IS_MODERN_SVELTE extends true - ? C extends ModernComponent - ? E - : C & { $set: never; $on: never; $destroy: never } - : C - -/** - * Options that may be passed to `mount` when rendering the component. - * - * In Svelte 4, these are the options passed to the component constructor. - */ -export type MountOptions = IS_MODERN_SVELTE extends true - ? Parameters, Exports>>[1] - : LegacyConstructorOptions> - -/** A component's props or some of its mount options. */ -export type ComponentOptions = - | Props - | Partial> diff --git a/packages/svelte/src/pure.js b/packages/svelte/src/pure.js index 8ae70f4..6e7f369 100644 --- a/packages/svelte/src/pure.js +++ b/packages/svelte/src/pure.js @@ -5,15 +5,14 @@ import { getQueriesForElement, prettyDOM, } from '@testing-library/dom' +import * as Core from '@testing-library/svelte-core' import * as Svelte from 'svelte' -import * as Core from './core/index.js' - /** * Customize how Svelte renders the component. * - * @template {import('./core/types.js').Component} C - * @typedef {import('./core/types.js').ComponentOptions} SvelteComponentOptions + * @template {import('@testing-library/svelte-core/types').Component} C + * @typedef {import('@testing-library/svelte-core/types').ComponentOptions} SvelteComponentOptions */ /** @@ -29,15 +28,15 @@ import * as Core from './core/index.js' /** * The rendered component and bound testing functions. * - * @template {import('./core/types.js').Component} C + * @template {import('@testing-library/svelte-core/types').Component} C * @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries] * * @typedef {{ * container: HTMLElement * baseElement: HTMLElement - * component: import('./core/types.js').Exports + * component: import('@testing-library/svelte-core/types').Exports * debug: (el?: HTMLElement | DocumentFragment) => void - * rerender: (props: Partial>) => Promise + * rerender: import('@testing-library/svelte-core/types').Rerender * unmount: () => void * } & { * [P in keyof Q]: import('@testing-library/dom').BoundFunction @@ -47,46 +46,29 @@ import * as Core from './core/index.js' /** * Render a component into the document. * - * @template {import('./core/types.js').Component} C + * @template {import('@testing-library/svelte-core/types').Component} C * @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries] * - * @param {import('./core/types.js').ComponentType} Component - The component to render. + * @param {import('@testing-library/svelte-core/types').ComponentImport} Component - The component to render. * @param {SvelteComponentOptions} options - Customize how Svelte renders the component. * @param {RenderOptions} renderOptions - Customize how Testing Library sets up the document and binds queries. * @returns {RenderResult} The rendered component and bound testing functions. */ const render = (Component, options = {}, renderOptions = {}) => { - const { baseElement, target, mountOptions } = Core.setup( + const { baseElement, container, component, unmount, rerender } = Core.render( + Component, options, renderOptions ) - const queries = getQueriesForElement(baseElement, renderOptions.queries) - - const { component, unmount, rerender } = Core.mount( - Component.default ?? Component, - mountOptions - ) - return { baseElement, + container, component, - container: target, - debug: (el = baseElement) => { - console.log(prettyDOM(el)) - }, - rerender: async (props) => { - if (props.props) { - console.warn( - 'rerender({ props: {...} }) deprecated, use rerender({...}) instead' - ) - props = props.props - } - - await rerender(props) - }, + rerender, unmount, - ...queries, + debug: (el = baseElement) => console.log(prettyDOM(el)), + ...getQueriesForElement(baseElement, renderOptions.queries), } } @@ -155,5 +137,5 @@ for (const [key, baseEvent] of Object.entries(baseFireEvent)) { fireEvent[key] = async (...args) => act(() => baseEvent(...args)) } -export { UnknownSvelteOptionsError } from './core/index.js' +export { UnknownSvelteOptionsError } from '@testing-library/svelte-core' export { act, cleanup, fireEvent, render, setup } diff --git a/packages/svelte/src/vite.js b/packages/svelte/src/vite.js index b57c886..b859b59 100644 --- a/packages/svelte/src/vite.js +++ b/packages/svelte/src/vite.js @@ -1,6 +1,8 @@ import path from 'node:path' import url from 'node:url' +const STL_PACKAGES = ['@testing-library/svelte', '@testing-library/svelte-core'] + /** * Vite plugin to configure @testing-library/svelte. * @@ -107,17 +109,25 @@ const addNoExternal = (config) => { return } - for (const rule of noExternal) { - if (typeof rule === 'string' && rule === '@testing-library/svelte') { - return - } + const missingPackages = [] - if (rule instanceof RegExp && rule.test('@testing-library/svelte')) { - return + for (const packageName of STL_PACKAGES) { + const isIncluded = noExternal.some( + (rule) => + (typeof rule === 'string' && rule === packageName) || + (rule instanceof RegExp && rule.test(packageName)) + ) + + if (!isIncluded) { + missingPackages.push(packageName) } } - noExternal.push('@testing-library/svelte') + if (missingPackages.length === 0) { + return + } + + noExternal.push(...missingPackages) ssr.noExternal = noExternal config.ssr = ssr } diff --git a/packages/svelte/tsconfig.json b/packages/svelte/tsconfig.json index 66fe363..2e23d68 100644 --- a/packages/svelte/tsconfig.json +++ b/packages/svelte/tsconfig.json @@ -1,4 +1,5 @@ { + "references": [{ "path": "../svelte-core" }], "compilerOptions": { "module": "node16", "allowJs": true, diff --git a/prettier.config.js b/prettier.config.js index 55343f2..19919d0 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -13,7 +13,6 @@ export default { { files: 'examples/**/*.md', options: { - printWidth: 80, proseWrap: 'always', }, }, diff --git a/tests/vite-plugin.test.js b/tests/vite-plugin.test.js index 92fc8f5..480a260 100644 --- a/tests/vite-plugin.test.js +++ b/tests/vite-plugin.test.js @@ -147,19 +147,47 @@ describe.skipIf(IS_JEST)('vite plugin', () => { test.each([ { config: () => ({ ssr: { noExternal: [] } }), - expectedNoExternal: ['@testing-library/svelte'], + expectedNoExternal: [ + '@testing-library/svelte', + '@testing-library/svelte-core', + ], }, { config: () => ({}), - expectedNoExternal: ['@testing-library/svelte'], + expectedNoExternal: [ + '@testing-library/svelte', + '@testing-library/svelte-core', + ], }, { config: () => ({ ssr: { noExternal: 'other-file.js' } }), - expectedNoExternal: ['other-file.js', '@testing-library/svelte'], + expectedNoExternal: [ + 'other-file.js', + '@testing-library/svelte', + '@testing-library/svelte-core', + ], }, { config: () => ({ ssr: { noExternal: /other/u } }), - expectedNoExternal: [/other/u, '@testing-library/svelte'], + expectedNoExternal: [ + /other/u, + '@testing-library/svelte', + '@testing-library/svelte-core', + ], + }, + { + config: () => ({ ssr: { noExternal: '@testing-library/svelte' } }), + expectedNoExternal: [ + '@testing-library/svelte', + '@testing-library/svelte-core', + ], + }, + { + config: () => ({ ssr: { noExternal: '@testing-library/svelte-core' } }), + expectedNoExternal: [ + '@testing-library/svelte-core', + '@testing-library/svelte', + ], }, ])('adds noExternal rule', ({ config, expectedNoExternal }) => { const subject = svelteTesting({ @@ -183,10 +211,6 @@ describe.skipIf(IS_JEST)('vite plugin', () => { config: () => ({ ssr: { noExternal: true } }), expectedNoExternal: true, }, - { - config: () => ({ ssr: { noExternal: '@testing-library/svelte' } }), - expectedNoExternal: '@testing-library/svelte', - }, { config: () => ({ ssr: { noExternal: /svelte/u } }), expectedNoExternal: /svelte/u, diff --git a/tsconfig.json b/tsconfig.json index 493e5db..a36e1bf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,7 @@ { - "references": [{ "path": "packages/svelte" }], + "references": [ + { "path": "packages/svelte" }, + { "path": "packages/svelte-core" } + ], "files": [] }