Skip to content

Commit

Permalink
refactor: implement inheritance with classes (#330)
Browse files Browse the repository at this point in the history
  • Loading branch information
yanick authored Feb 28, 2024
1 parent 40973c5 commit 0593819
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 105 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
"npm-run-all": "^4.1.5",
"prettier": "3.2.4",
"prettier-plugin-svelte": "3.1.2",
"svelte": "^4.2.10",
"svelte": "^3 || ^4 || ^5",
"svelte-check": "^3.6.3",
"svelte-jester": "^3.0.0",
"typescript": "^5.3.3",
Expand Down
156 changes: 79 additions & 77 deletions src/pure.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,85 +7,61 @@ 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 const buildCheckProps = (svelteComponentOptions) => (options) => {
const isProps = !Object.keys(options).some((option) =>
svelteComponentOptions.includes(option)
)

// Check if any props and Svelte options were accidentally mixed.
if (!isProps) {
const unrecognizedOptions = Object.keys(options).filter(
(option) => !svelteComponentOptions.includes(option)

export class SvelteTestingLibrary {
svelteComponentOptions = [
'accessors',
'anchor',
'props',
'hydrate',
'intro',
'context',
]

targetCache = new Set()
componentCache = new Set()

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 { props: options }
}

const checkProps = buildCheckProps(svelteComponentOptions)

const buildRenderComponent =
({ target, ComponentConstructor }) =>
(options) => {
options = { target, ...checkProps(options) }

if (IS_SVELTE_5)
throw new Error('for Svelte 5, use `@testing-library/svelte/svelte5`')
}

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 options
}

return component
return { props: options }
}

export const buildRender =
(buildRenderComponent) =>
(Component, { target, ...options } = {}, { container, queries } = {}) => {
render(Component, { target, ...options } = {}, { container, queries } = {}) {
container = container || document.body
target = target || container.appendChild(document.createElement('div'))
targetCache.add(target)
this.targetCache.add(target)

const ComponentConstructor = Component.default || Component

const renderComponent = buildRenderComponent({
target,
ComponentConstructor,
})

let component = renderComponent(options)
const component = this.renderComponent(
{
target,
ComponentConstructor,
},
options
)

return {
container,
Expand All @@ -102,35 +78,61 @@ export const buildRender =
await Svelte.tick()
},
unmount: () => {
cleanupComponent(component)
this.cleanupComponent(component)
},
...getQueriesForElement(container, queries),
}
}

export const render = buildRender(buildRenderComponent)
renderComponent({ target, ComponentConstructor }, options) {
options = { target, ...this.checkProps(options) }

export const cleanupComponent = (component) => {
const inCache = componentCache.delete(component)
if (IS_SVELTE_5)
throw new Error('for Svelte 5, use `@testing-library/svelte/svelte5`')

const component = new ComponentConstructor(options)

this.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(() => {
this.componentCache.delete(component)
})
}

if (inCache) {
component.$destroy()
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()
}
}

cleanupTarget(target) {
const inCache = this.targetCache.delete(target)

if (inCache && target.parentNode === document.body) {
document.body.removeChild(target)
}
}
}

export const cleanup = () => {
componentCache.forEach(cleanupComponent)
targetCache.forEach(cleanupTarget)
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) => {
if (fn) {
await fn()
Expand Down
44 changes: 17 additions & 27 deletions src/svelte5.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,31 @@
import { createClassComponent } from 'svelte/legacy'
import {
componentCache,
cleanup,
buildCheckProps,
buildRender,
} from './pure.js'
import { SvelteTestingLibrary } from './pure.js'

const svelteComponentOptions = [
'target',
'props',
'events',
'context',
'intro',
'recover',
]
class Svelte5TestingLibrary extends SvelteTestingLibrary {
svelteComponentOptions = [
'target',
'props',
'events',
'context',
'intro',
'recover',
]

const checkProps = buildCheckProps(svelteComponentOptions)

const buildRenderComponent =
({ target, ComponentConstructor }) =>
(options) => {
options = { target, ...checkProps(options) }
renderComponent({ target, ComponentConstructor }, options) {
options = { target, ...this.checkProps(options) }

const component = createClassComponent({
component: ComponentConstructor,
...options,
})

componentCache.add(component)
this.componentCache.add(component)

return component
}
}

const render = buildRender(buildRenderComponent)

/* eslint-disable import/export */

import { act, fireEvent } from './pure.js'
const instance = new Svelte5TestingLibrary()

export { render, cleanup, fireEvent, act }
export const render = instance.render.bind(instance)
export const cleanup = instance.cleanup.bind(instance)

0 comments on commit 0593819

Please sign in to comment.