Socket
Socket
Sign inDemoInstall

@testing-library/svelte

Package Overview
Dependencies
Maintainers
15
Versions
43
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@testing-library/svelte - npm Package Compare versions

Comparing version 4.2.1 to 4.2.2

4

package.json
{
"name": "@testing-library/svelte",
"version": "4.2.1",
"version": "4.2.2",
"description": "Simple and complete Svelte testing utilities that encourage good testing practices.",

@@ -101,3 +101,3 @@ "main": "src/index.js",

"prettier-plugin-svelte": "3.1.2",
"svelte": "^4.2.10",
"svelte": "^3 || ^4 || ^5",
"svelte-check": "^3.6.3",

@@ -104,0 +104,0 @@ "svelte-jester": "^3.0.0",

@@ -1,23 +0,11 @@

import { beforeEach, describe, expect, test } from 'vitest'
import { setTimeout } from 'node:timers/promises'
import { act, fireEvent, render as stlRender } from '@testing-library/svelte'
import { act, render } from '@testing-library/svelte'
import { describe, expect, test } from 'vitest'
import Comp from './fixtures/Comp.svelte'
describe('act', () => {
let props
const render = () => {
return stlRender(Comp, {
props
})
}
beforeEach(() => {
props = {
name: 'World'
}
})
test('state updates are flushed', async () => {
const { getByText } = render()
const { getByText } = render(Comp)
const button = getByText('Button')

@@ -34,20 +22,9 @@

test('findByTestId returns the element', async () => {
const { findByTestId } = render()
expect(await findByTestId('test')).toHaveTextContent(`Hello ${props.name}!`)
})
test('accepts async functions', async () => {
const sleep = (ms) =>
new Promise((resolve) => {
setTimeout(() => resolve(), ms)
})
const { getByText } = render()
const { getByText } = render(Comp)
const button = getByText('Button')
await act(async () => {
await sleep(100)
await fireEvent.click(button)
await setTimeout(100)
button.click()
})

@@ -54,0 +31,0 @@

@@ -0,4 +1,4 @@

import { render } from '@testing-library/svelte'
import { describe, expect, test } from 'vitest'
import { render } from '@testing-library/svelte'
import Comp from './fixtures/Comp.svelte'

@@ -5,0 +5,0 @@

@@ -0,5 +1,4 @@

import { cleanup, render } from '@testing-library/svelte'
import { describe, expect, test, vi } from 'vitest'
import { VERSION as SVELTE_VERSION } from 'svelte/compiler'
import { act, cleanup, render } from '@testing-library/svelte'
import Mounter from './fixtures/Mounter.svelte'

@@ -19,4 +18,4 @@

test.runIf(SVELTE_VERSION < '5')('cleanup unmounts component', async () => {
await act(renderSubject)
test('cleanup unmounts component', () => {
renderSubject()
cleanup()

@@ -23,0 +22,0 @@

@@ -0,4 +1,4 @@

import { render } from '@testing-library/svelte'
import { expect, test } from 'vitest'
import { render } from '@testing-library/svelte'
import Comp from './fixtures/Context.svelte'

@@ -5,0 +5,0 @@ import { IS_HAPPYDOM, IS_SVELTE_5 } from './utils.js'

import { prettyDOM } from '@testing-library/dom'
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { render } from '@testing-library/svelte'
import { describe, expect, test, vi } from 'vitest'
import { render } from '@testing-library/svelte'
import Comp from './fixtures/Comp.svelte'
describe('debug', () => {
beforeEach(() => {
vi.spyOn(console, 'log').mockImplementation(() => { })
})
test('pretty prints the base element', () => {
vi.stubGlobal('console', { log: vi.fn(), warn: vi.fn(), error: vi.fn() })
afterEach(() => {
console.log.mockRestore()
})
const { baseElement, debug } = render(Comp, { props: { name: 'world' } })
test('pretty prints the container', () => {
const { container, debug } = render(Comp, { props: { name: 'world' } })
debug()
expect(console.log).toHaveBeenCalledTimes(1)
expect(console.log).toHaveBeenCalledWith(prettyDOM(container))
expect(console.log).toHaveBeenCalledWith(prettyDOM(baseElement))
})
})

@@ -0,4 +1,4 @@

import { fireEvent, render } from '@testing-library/svelte'
import { describe, expect, test } from 'vitest'
import { fireEvent, render } from '@testing-library/svelte'
import Comp from './fixtures/Comp.svelte'

@@ -11,4 +11,5 @@

await fireEvent.click(button)
const result = fireEvent.click(button)
await expect(result).resolves.toBe(true)
expect(button).toHaveTextContent('Button Clicked')

@@ -21,12 +22,13 @@ })

await fireEvent(
const result = fireEvent(
button,
new MouseEvent('click', {
bubbles: true,
cancelable: true
cancelable: true,
})
)
await expect(result).resolves.toBe(true)
expect(button).toHaveTextContent('Button Clicked')
})
})

@@ -0,4 +1,4 @@

import { act, render, screen } from '@testing-library/svelte'
import { describe, expect, test, vi } from 'vitest'
import { act, render, screen } from '@testing-library/svelte'
import Mounter from './fixtures/Mounter.svelte'

@@ -5,0 +5,0 @@ import { IS_HAPPYDOM, IS_SVELTE_5 } from './utils.js'

@@ -0,4 +1,4 @@

import { render } from '@testing-library/svelte'
import { describe, expect, test } from 'vitest'
import { render } from '@testing-library/svelte'
import Comp from './fixtures/Comp.svelte'

@@ -16,7 +16,7 @@

props: {
name: 'Tree A'
}
name: 'Tree A',
},
},
{
container: treeA
baseElement: treeA,
}

@@ -30,7 +30,7 @@ )

props: {
name: 'Tree B'
}
name: 'Tree B',
},
},
{
container: treeB
baseElement: treeB,
}

@@ -37,0 +37,0 @@ )

@@ -1,27 +0,12 @@

import { VERSION as SVELTE_VERSION } from 'svelte/compiler'
import { beforeEach, describe, expect, test } from 'vitest'
import { render } from '@testing-library/svelte'
import { describe, expect, test } from 'vitest'
import { act, render as stlRender } from '@testing-library/svelte'
import Comp from './fixtures/Comp.svelte'
import CompDefault from './fixtures/Comp2.svelte'
import { IS_SVELTE_5 } from './utils.js'
describe('render', () => {
let props
const props = { name: 'World' }
const render = (additional = {}) => {
return stlRender(Comp, {
target: document.body,
props,
...additional,
})
}
beforeEach(() => {
props = {
name: 'World',
}
})
test('renders component into the document', () => {
const { getByText } = render()
const { getByText } = render(Comp, { props })

@@ -31,94 +16,71 @@ expect(getByText('Hello World!')).toBeInTheDocument()

// Dear reader, this is not something you generally want to do in your tests.
test('programmatically change props', async () => {
const { component, getByText } = render()
test('accepts props directly', () => {
const { getByText } = render(Comp, props)
expect(getByText('Hello World!')).toBeInTheDocument()
await act(() => {
component.$set({ name: 'Worlds' })
})
expect(getByText('Hello Worlds!')).toBeInTheDocument()
})
test('change props with accessors', async () => {
const { component, getByText } = render(
SVELTE_VERSION < '5' ? { accessors: true } : {}
)
expect(getByText('Hello World!')).toBeInTheDocument()
expect(component.name).toBe('World')
await act(() => {
component.value = 'Planet'
})
expect(getByText('Hello World!')).toBeInTheDocument()
test('throws error when mixing svelte component options and props', () => {
expect(() => {
render(Comp, { props, name: 'World' })
}).toThrow(/Unknown options/)
})
test('should accept props directly', () => {
const { getByText } = stlRender(Comp, { name: 'World' })
expect(getByText('Hello World!')).toBeInTheDocument()
test('throws error when mixing target option and props', () => {
expect(() => {
render(Comp, { target: document.createElement('div'), name: 'World' })
}).toThrow(/Unknown options/)
})
test.runIf(SVELTE_VERSION < '5')(
'should accept svelte v4 component options',
() => {
const target = document.createElement('div')
const div = document.createElement('div')
document.body.appendChild(target)
target.appendChild(div)
const { container } = stlRender(Comp, {
target,
anchor: div,
props: { name: 'World' },
context: new Map([['name', 'context']]),
})
expect(container).toMatchSnapshot()
}
)
test('should return a container object wrapping the DOM of the rendered component', () => {
const { container, getByTestId } = render(Comp, props)
const firstElement = getByTestId('test')
test.runIf(SVELTE_VERSION >= '5')(
'should accept svelte v5 component options',
() => {
const target = document.createElement('section')
document.body.appendChild(target)
expect(container.firstChild).toBe(firstElement)
})
const { container } = stlRender(Comp, {
target,
props: { name: 'World' },
context: new Map([['name', 'context']]),
})
expect(container).toMatchSnapshot()
}
)
test('should return a baseElement object, which holds the container', () => {
const { baseElement, container } = render(Comp, props)
test('should throw error when mixing svelte component options and props', () => {
expect(() => {
stlRender(Comp, { props: {}, name: 'World' })
}).toThrow(/Unknown options were found/)
expect(baseElement).toBe(document.body)
expect(baseElement.firstChild).toBe(container)
})
test('should return a container object, which contains the DOM of the rendered component', () => {
const { container } = render()
test('if target is provided, use it as container and baseElement', () => {
const target = document.createElement('div')
const { baseElement, container } = render(Comp, { props, target })
expect(container.innerHTML).toBe(document.body.innerHTML)
expect(container).toBe(target)
expect(baseElement).toBe(target)
})
test('correctly find component constructor on the default property', () => {
const { getByText } = stlRender(CompDefault, { props: { name: 'World' } })
test('allow baseElement to be specified', () => {
const customBaseElement = document.createElement('div')
expect(getByText('Hello World!')).toBeInTheDocument()
const { baseElement, container } = render(
Comp,
{ props },
{ baseElement: customBaseElement }
)
expect(baseElement).toBe(customBaseElement)
expect(baseElement.firstChild).toBe(container)
})
test("accept the 'context' option", () => {
const { getByText } = stlRender(Comp, {
props: { name: 'Universe' },
context: new Map([['name', 'context']]),
})
test.skipIf(IS_SVELTE_5)('should accept anchor option in Svelte v4', () => {
const baseElement = document.body
const target = document.createElement('section')
const anchor = document.createElement('div')
baseElement.appendChild(target)
target.appendChild(anchor)
expect(getByText('we have context')).toBeInTheDocument()
const { getByTestId } = render(
Comp,
{ props, target, anchor },
{ baseElement }
)
const firstElement = getByTestId('test')
expect(target.firstChild).toBe(firstElement)
expect(target.lastChild).toBe(anchor)
})
})

@@ -1,41 +0,50 @@

/**
* @jest-environment jsdom
*/
import { expect, test, vi } from 'vitest'
import { act, render, screen } from '@testing-library/svelte'
import { VERSION as SVELTE_VERSION } from 'svelte/compiler'
import { describe, expect, test, vi } from 'vitest'
import { render, waitFor } from '@testing-library/svelte'
import Comp from './fixtures/Comp.svelte'
import Comp from './fixtures/Rerender.svelte'
describe('rerender', () => {
test('updates props', async () => {
const { rerender } = render(Comp, { name: 'World' })
const element = screen.getByText('Hello World!')
test('mounts new component successfully', async () => {
const onMounted = vi.fn()
const onDestroyed = vi.fn()
await rerender({ name: 'Dolly' })
const { getByTestId, rerender } = render(Comp, {
props: { name: 'World 1', onMounted, onDestroyed },
expect(element).toHaveTextContent('Hello Dolly!')
})
const expectToRender = (content) =>
waitFor(() => {
expect(getByTestId('test')).toHaveTextContent(content)
expect(onMounted).toHaveBeenCalledOnce()
})
test('warns if incorrect arguments shape used', async () => {
vi.stubGlobal('console', { log: vi.fn(), warn: vi.fn(), error: vi.fn() })
await expectToRender('Hello World 1!')
const { rerender } = render(Comp, { name: 'World' })
const element = screen.getByText('Hello World!')
console.warn = vi.fn()
await rerender({ props: { name: 'Dolly' } })
rerender({ props: { name: 'World 2' } })
await expectToRender('Hello World 2!')
expect(onDestroyed).not.toHaveBeenCalled()
expect(element).toHaveTextContent('Hello Dolly!')
expect(console.warn).toHaveBeenCalledOnce()
expect(console.warn).toHaveBeenCalledWith(
expect.stringMatching(/deprecated/iu)
)
})
expect(console.warn).toHaveBeenCalledOnce()
test('change props with accessors', async () => {
const { component, getByText } = render(
Comp,
SVELTE_VERSION < '5'
? { accessors: true, props: { name: 'World' } }
: { name: 'World' }
)
const element = getByText('Hello World!')
console.warn.mockClear()
onDestroyed.mockReset()
rerender({ name: 'World 3' })
await expectToRender('Hello World 3!')
expect(onDestroyed).not.toHaveBeenCalled()
expect(element).toBeInTheDocument()
expect(component.name).toBe('World')
expect(console.warn).not.toHaveBeenCalled()
await act(() => {
component.name = 'Planet'
})
expect(element).toHaveTextContent('Hello Planet!')
})
})

@@ -0,10 +1,9 @@

import { render, screen, waitFor } from '@testing-library/svelte'
import { userEvent } from '@testing-library/user-event'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import Transitioner from './fixtures/Transitioner.svelte'
import { IS_JSDOM, IS_SVELTE_5 } from './utils.js'
import { render, screen, waitFor } from '@testing-library/svelte'
import Transitioner from './fixtures/Transitioner.svelte'
describe.runIf(!IS_SVELTE_5)('transitions', () => {
describe.skipIf(IS_SVELTE_5)('transitions', () => {
beforeEach(() => {

@@ -11,0 +10,0 @@ if (!IS_JSDOM) return

@@ -0,1 +1,2 @@

/* eslint-disable import/export */
import { act, cleanup } from './pure.js'

@@ -15,3 +16,7 @@

export * from './pure.js'
// export all base queries, screen, etc.
export * from '@testing-library/dom'
// export svelte-specific functions and custom `fireEvent`
// `fireEvent` must be a named export to take priority over wildcard export above
export { act, cleanup, fireEvent, render } from './pure.js'

@@ -6,90 +6,72 @@ import {

} from '@testing-library/dom'
import * as Svelte from 'svelte'
import { VERSION as SVELTE_VERSION } from 'svelte/compiler'
import * as Svelte from 'svelte'
const IS_SVELTE_5 = /^5\./.test(SVELTE_VERSION)
export const targetCache = new Set()
export const componentCache = new Set()
const svelteComponentOptions = [
'accessors',
'anchor',
'props',
'hydrate',
'intro',
'context',
]
export class SvelteTestingLibrary {
svelteComponentOptions = [
'target',
'accessors',
'anchor',
'props',
'hydrate',
'intro',
'context',
]
export const buildCheckProps = (svelteComponentOptions) => (options) => {
const isProps = !Object.keys(options).some((option) =>
svelteComponentOptions.includes(option)
)
targetCache = new Set()
componentCache = new Set()
// Check if any props and Svelte options were accidentally mixed.
if (!isProps) {
const unrecognizedOptions = Object.keys(options).filter(
(option) => !svelteComponentOptions.includes(option)
checkProps(options) {
const isProps = !Object.keys(options).some((option) =>
this.svelteComponentOptions.includes(option)
)
if (unrecognizedOptions.length > 0) {
throw Error(`
// Check if any props and Svelte options were accidentally mixed.
if (!isProps) {
const unrecognizedOptions = Object.keys(options).filter(
(option) => !this.svelteComponentOptions.includes(option)
)
if (unrecognizedOptions.length > 0) {
throw Error(`
Unknown options were found [${unrecognizedOptions}]. This might happen if you've mixed
passing in props with Svelte options into the render function. Valid Svelte options
are [${svelteComponentOptions}]. You can either change the prop names, or pass in your
are [${this.svelteComponentOptions}]. You can either change the prop names, or pass in your
props for that component via the \`props\` option.\n\n
Eg: const { /** Results **/ } = render(MyComponent, { props: { /** props here **/ } })\n\n
`)
}
return options
}
return options
return { props: options }
}
return { props: options }
}
render(Component, componentOptions = {}, renderOptions = {}) {
componentOptions = this.checkProps(componentOptions)
const checkProps = buildCheckProps(svelteComponentOptions)
const baseElement =
renderOptions.baseElement ?? componentOptions.target ?? document.body
const buildRenderComponent =
({ target, ComponentConstructor }) =>
(options) => {
options = { target, ...checkProps(options) }
const target =
componentOptions.target ??
baseElement.appendChild(document.createElement('div'))
if (IS_SVELTE_5)
throw new Error('for Svelte 5, use `@testing-library/svelte/svelte5`')
this.targetCache.add(target)
const component = new ComponentConstructor(options)
componentCache.add(component)
// TODO(mcous, 2024-02-11): remove this behavior in the next major version
// It is unnecessary has no path to implementation in Svelte v5
if (!IS_SVELTE_5) {
component.$$.on_destroy.push(() => {
componentCache.delete(component)
})
}
return component
}
export const buildRender =
(buildRenderComponent) =>
(Component, { target, ...options } = {}, { container, queries } = {}) => {
container = container || document.body
target = target || container.appendChild(document.createElement('div'))
targetCache.add(target)
const ComponentConstructor = Component.default || Component
const renderComponent = buildRenderComponent({
const component = this.renderComponent(ComponentConstructor, {
...componentOptions,
target,
ComponentConstructor,
})
let component = renderComponent(options)
return {
container,
baseElement,
component,
debug: (el = container) => console.log(prettyDOM(el)),
container: target,
debug: (el = baseElement) => console.log(prettyDOM(el)),
rerender: async (props) => {

@@ -106,31 +88,53 @@ if (props.props) {

unmount: () => {
cleanupComponent(component)
this.cleanupComponent(component)
},
...getQueriesForElement(container, queries),
...getQueriesForElement(baseElement, renderOptions.queries),
}
}
export const render = buildRender(buildRenderComponent)
renderComponent(ComponentConstructor, componentOptions) {
if (IS_SVELTE_5) {
throw new Error('for Svelte 5, use `@testing-library/svelte/svelte5`')
}
export const cleanupComponent = (component) => {
const inCache = componentCache.delete(component)
const component = new ComponentConstructor(componentOptions)
if (inCache) {
component.$destroy()
this.componentCache.add(component)
// TODO(mcous, 2024-02-11): remove this behavior in the next major version
component.$$.on_destroy.push(() => {
this.componentCache.delete(component)
})
return component
}
}
const cleanupTarget = (target) => {
const inCache = targetCache.delete(target)
cleanupComponent(component) {
const inCache = this.componentCache.delete(component)
if (inCache && target.parentNode === document.body) {
document.body.removeChild(target)
if (inCache) {
component.$destroy()
}
}
}
export const cleanup = () => {
componentCache.forEach(cleanupComponent)
targetCache.forEach(cleanupTarget)
cleanupTarget(target) {
const inCache = this.targetCache.delete(target)
if (inCache && target.parentNode === document.body) {
document.body.removeChild(target)
}
}
cleanup() {
this.componentCache.forEach(this.cleanupComponent.bind(this))
this.targetCache.forEach(this.cleanupTarget.bind(this))
}
}
const instance = new SvelteTestingLibrary()
export const render = instance.render.bind(instance)
export const cleanup = instance.cleanup.bind(instance)
export const act = async (fn) => {

@@ -137,0 +141,0 @@ if (fn) {

@@ -0,1 +1,2 @@

/* eslint-disable import/export */
import { act, cleanup } from './svelte5.js'

@@ -15,4 +16,8 @@

export * from './svelte5.js'
// export all base queries, screen, etc.
export * from '@testing-library/dom'
// export svelte-specific functions and custom `fireEvent`
// `fireEvent` must be a named export to take priority over wildcard export above
export { act, fireEvent } from './pure.js'
export { cleanup, render } from './svelte5.js'
import { createClassComponent } from 'svelte/legacy'
import {
componentCache,
cleanup,
buildCheckProps,
buildRender,
} from './pure.js'
const svelteComponentOptions = [
'target',
'props',
'events',
'context',
'intro',
'recover',
]
import { SvelteTestingLibrary } from './pure.js'
const checkProps = buildCheckProps(svelteComponentOptions)
class Svelte5TestingLibrary extends SvelteTestingLibrary {
svelteComponentOptions = [
'target',
'props',
'events',
'context',
'intro',
'recover',
]
const buildRenderComponent =
({ target, ComponentConstructor }) =>
(options) => {
options = { target, ...checkProps(options) }
renderComponent(ComponentConstructor, componentOptions) {
const component = createClassComponent({
...componentOptions,
component: ComponentConstructor,
...options,
})
componentCache.add(component)
this.componentCache.add(component)
return component
}
}
const render = buildRender(buildRenderComponent)
const instance = new Svelte5TestingLibrary()
/* eslint-disable import/export */
import { act, fireEvent } from './pure.js'
export { render, cleanup, fireEvent, act }
export const render = instance.render.bind(instance)
export const cleanup = instance.cleanup.bind(instance)

@@ -0,5 +1,4 @@

import { act, cleanup } from '@testing-library/svelte'
import { afterEach } from 'vitest'
import { act, cleanup } from '@testing-library/svelte'
afterEach(async () => {

@@ -6,0 +5,0 @@ await act()

@@ -21,9 +21,4 @@ // Type definitions for Svelte Testing Library

| ComponentProps<C>
| Pick<
ComponentConstructorOptions<ComponentProps<C>>,
'anchor' | 'props' | 'hydrate' | 'intro' | 'context'
>
| Partial<ComponentConstructorOptions<ComponentProps<C>>>
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type Constructor<T> = new (...args: any[]) => T

@@ -39,5 +34,6 @@

container: HTMLElement
baseElement: HTMLElement
component: C
debug: (el?: HTMLElement | DocumentFragment) => void
rerender: (props: ComponentProps<C>) => Promise<void>
rerender: (props: Partial<ComponentProps<C>>) => Promise<void>
unmount: () => void

@@ -47,15 +43,12 @@ } & { [P in keyof Q]: BoundFunction<Q[P]> }

export interface RenderOptions<Q extends Queries = typeof queries> {
container?: HTMLElement
baseElement?: HTMLElement
queries?: Q
}
export function render<C extends SvelteComponent>(
export function render<
C extends SvelteComponent,
Q extends Queries = typeof queries,
>(
component: Constructor<C>,
componentOptions?: SvelteComponentOptions<C>,
renderOptions?: Omit<RenderOptions, 'queries'>
): RenderResult<C>
export function render<C extends SvelteComponent, Q extends Queries>(
component: Constructor<C>,
componentOptions?: SvelteComponentOptions<C>,
renderOptions?: RenderOptions<Q>

@@ -62,0 +55,0 @@ ): RenderResult<C, Q>

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc