-
Notifications
You must be signed in to change notification settings - Fork 33
fix: $destroy and createRoot are no more #328
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
44b20b4
d55fbd7
6f494ad
914a5bb
7ff2e51
9d86208
92b651f
8f233ab
a3d1c96
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,10 +4,8 @@ | |
"description": "Simple and complete Svelte testing utilities that encourage good testing practices.", | ||
"main": "src/index.js", | ||
"exports": { | ||
".": { | ||
"types": "./types/index.d.ts", | ||
"default": "./src/index.js" | ||
}, | ||
".": "./src/index.js", | ||
"./svelte5": "./src/svelte5-index.js", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need a corresponding |
||
"./vitest": { | ||
"default": "./src/vitest.js" | ||
} | ||
|
@@ -56,8 +54,8 @@ | |
"test": "vitest run --coverage", | ||
"test:watch": "vitest", | ||
"test:update": "vitest run --update", | ||
"test:vitest:jsdom": "vitest run --coverage --environment jsdom", | ||
"test:vitest:happy-dom": "vitest run --coverage --environment happy-dom", | ||
"test:vitest:jsdom": "VITEST_ENV=jsdom vitest run --coverage --environment jsdom", | ||
"test:vitest:happy-dom": "VITEST_ENV=happy-dom vitest run --coverage --environment happy-dom", | ||
"types": "svelte-check", | ||
"validate": "npm-run-all test:vitest:* types", | ||
"contributors:add": "all-contributors add", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,18 @@ | ||
import { expect, test } from 'vitest' | ||
import { VERSION as SVELTE_VERSION } from 'svelte/compiler' | ||
|
||
import { render } from '..' | ||
import Comp from './fixtures/Context.svelte' | ||
|
||
test('can set a context', () => { | ||
const message = 'Got it' | ||
test.skipIf(SVELTE_VERSION >= '5' && process.env.VITEST_ENV == 'happy-dom')( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In another test I used the user-agent to test for jsdom vs happy-dom. It's ever-so-slightly janky, but I like it because it doesn't require adding more environment variables / setup. Using an environment variable makes it harder to use Related, should we have a little There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yeah, that would make a lot of sense. |
||
'can set a context', | ||
() => { | ||
const message = 'Got it' | ||
|
||
const { getByText } = render(Comp, { | ||
context: new Map(Object.entries({ foo: { message } })), | ||
}) | ||
const { getByText } = render(Comp, { | ||
context: new Map(Object.entries({ foo: { message } })), | ||
}) | ||
|
||
expect(getByText(message)).toBeTruthy() | ||
}) | ||
expect(getByText(message)).toBeTruthy() | ||
} | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,12 +3,16 @@ import { | |
getQueriesForElement, | ||
prettyDOM, | ||
} from '@testing-library/dom' | ||
import { VERSION as SVELTE_VERSION } from 'svelte/compiler' | ||
import * as Svelte from 'svelte' | ||
|
||
const IS_SVELTE_5 = typeof Svelte.createRoot === 'function' | ||
const IS_SVELTE_5 = /^5\./.test(SVELTE_VERSION) | ||
const targetCache = new Set() | ||
const componentCache = new Set() | ||
|
||
if (IS_SVELTE_5) | ||
console.warn('for Svelte 5, use `@testing-library/svelte/svelte5`') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general, I'm open to the separate entry points approach! That being said, I don't see anything in this PR that really justifies it. Is it a future-proofing thing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As mentioned in the previous comment, it's to deal with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doh, yeah duh, my bad, that makes total sense. Aside: this makes me think dropping 3.0 in the I'm not totally sure what to do about collecting multiple breaking changes without a prerelease version. Maybe we should switch over to an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
What would we cut off? |
||
|
||
const svelteComponentOptions = IS_SVELTE_5 | ||
? ['target', 'props', 'events', 'context', 'intro', 'recover'] | ||
: ['accessors', 'anchor', 'props', 'hydrate', 'intro', 'context'] | ||
|
@@ -54,9 +58,10 @@ const render = ( | |
const renderComponent = (options) => { | ||
options = { target, ...checkProps(options) } | ||
|
||
const component = IS_SVELTE_5 | ||
? Svelte.createRoot(ComponentConstructor, options) | ||
: new ComponentConstructor(options) | ||
if (IS_SVELTE_5) | ||
throw new Error('for Svelte 5, use `@testing-library/svelte/svelte5`') | ||
|
||
const component = new ComponentConstructor(options) | ||
|
||
componentCache.add(component) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { act, cleanup } from './svelte5.js' | ||
|
||
// If we're running in a test runner that supports afterEach | ||
// then we'll automatically run cleanup afterEach test | ||
// this ensures that tests run in isolation from each other | ||
// if you don't like this then either import the `pure` module | ||
// or set the STL_SKIP_AUTO_CLEANUP env variable to 'true'. | ||
if (typeof afterEach === 'function' && !process.env.STL_SKIP_AUTO_CLEANUP) { | ||
afterEach(async () => { | ||
await act() | ||
cleanup() | ||
}) | ||
} | ||
|
||
export * from './svelte5.js' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels like a lot of duplication for little payoff. If you want to have separate entry points, would it be better to re-export Alternatively, can we move the split to an internal facade instead? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My development steps are
This PR is at stage (2). all the functions that aren't different in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure thing, happy to defer to whatever approach you want to take. Just wanted to point out opportunities nip stuff in the bud LoC-wise |
||
fireEvent as dtlFireEvent, | ||
getQueriesForElement, | ||
prettyDOM, | ||
} from '@testing-library/dom' | ||
import { VERSION as SVELTE_VERSION } from 'svelte/compiler' | ||
import { createClassComponent as createComponentSvelte5 } from 'svelte/legacy' | ||
import * as Svelte from 'svelte' | ||
|
||
const IS_SVELTE_5 = /^5\./.test(SVELTE_VERSION) | ||
const targetCache = new Set() | ||
const componentCache = new Set() | ||
|
||
const svelteComponentOptions = IS_SVELTE_5 | ||
? ['target', 'props', 'events', 'context', 'intro', 'recover'] | ||
: ['accessors', 'anchor', 'props', 'hydrate', 'intro', 'context'] | ||
|
||
const render = ( | ||
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 checkProps = (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) | ||
) | ||
|
||
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 | ||
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 renderComponent = (options) => { | ||
options = { target, ...checkProps(options) } | ||
|
||
const component = IS_SVELTE_5 | ||
? createComponentSvelte5({ component: ComponentConstructor, ...options }) | ||
: 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 | ||
} | ||
|
||
let component = renderComponent(options) | ||
|
||
return { | ||
container, | ||
component, | ||
debug: (el = container) => console.log(prettyDOM(el)), | ||
rerender: async (props) => { | ||
if (props.props) { | ||
console.warn( | ||
'rerender({ props: {...} }) deprecated, use rerender({...}) instead' | ||
) | ||
props = props.props | ||
} | ||
component.$set(props) | ||
await Svelte.tick() | ||
}, | ||
unmount: () => { | ||
cleanupComponent(component) | ||
}, | ||
...getQueriesForElement(container, queries), | ||
} | ||
} | ||
|
||
const cleanupComponent = (component) => { | ||
const inCache = componentCache.delete(component) | ||
|
||
if (inCache) { | ||
component.$destroy() | ||
} | ||
} | ||
|
||
const cleanupTarget = (target) => { | ||
const inCache = targetCache.delete(target) | ||
|
||
if (inCache && target.parentNode === document.body) { | ||
document.body.removeChild(target) | ||
} | ||
} | ||
|
||
const cleanup = () => { | ||
componentCache.forEach(cleanupComponent) | ||
targetCache.forEach(cleanupTarget) | ||
} | ||
|
||
const act = async (fn) => { | ||
if (fn) { | ||
await fn() | ||
} | ||
return Svelte.tick() | ||
} | ||
|
||
const fireEvent = async (...args) => { | ||
const event = dtlFireEvent(...args) | ||
await Svelte.tick() | ||
return event | ||
} | ||
|
||
Object.keys(dtlFireEvent).forEach((key) => { | ||
fireEvent[key] = async (...args) => { | ||
const event = dtlFireEvent[key](...args) | ||
await Svelte.tick() | ||
return event | ||
} | ||
}) | ||
|
||
/* eslint-disable import/export */ | ||
|
||
export * from '@testing-library/dom' | ||
|
||
export { render, cleanup, fireEvent, act } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, we need to make sure we don't drop this
types
export, or it'll break TS using more modernmoduleResolution
modesThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't the
types
key right below the exports take care of that, though?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, no. If TS sees
"exports"
inmoduleResolute: nodenext
orbundler
and there's noexports[].types
it'll get sadSee #228