Skip to content
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

fix(types): build types from JS source #376

Merged
merged 1 commit into from
Jun 26, 2024
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
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ scripts/*
.prettierignore
.github/workflows/*
*.md
types
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = {
rules: {
'no-undef-init': 'off',
'prefer-const': 'off',
'svelte/no-unused-svelte-ignore': 'off',
},
},
{
Expand Down
34 changes: 32 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,38 @@ jobs:
run: npm run test:${{ matrix.test-runner }}

- name: ▶️ Run type-checks
if: ${{ matrix.node == '20' && matrix.svelte == '4' && matrix.test-runner == 'vitest:jsdom' }}
# NOTE: `SvelteComponent` is not generic in Svelte v3, so type-checking will not pass
if: ${{ matrix.node == '20' && matrix.svelte != '3' && matrix.test-runner == 'vitest:jsdom' }}
mcous marked this conversation as resolved.
Show resolved Hide resolved
run: npm run types

- name: ⬆️ Upload coverage report
uses: codecov/codecov-action@v3

build:
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4

- name: ⎔ Setup node
uses: actions/setup-node@v4
with:
node-version: 20

- name: 📥 Download deps
run: npm install --no-package-lock

- name: 🏗️ Build types
run: npm run build

- name: ⬆️ Upload types build
uses: actions/upload-artifact@v4
with:
name: types
path: types

release:
needs: main
needs: [main, build]
runs-on: ubuntu-latest
if: ${{ github.repository == 'testing-library/svelte-testing-library' &&
contains('refs/heads/main,refs/heads/next', github.ref) &&
Expand All @@ -80,6 +104,12 @@ jobs:
with:
node-version: 20

- name: 📥 Downloads types build
uses: actions/download-artifact@v4
with:
name: types
path: types

- name: 🚀 Release
uses: cycjimmy/semantic-release-action@v4
with:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ dist
yarn-error.log
package-lock.json
yarn.lock

# generated typing output
types
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"default": "./src/index.js"
},
"./vitest": {
"types": "./types/vitest.d.ts",
"default": "./src/vitest.js"
},
"./vite": {
Expand All @@ -26,7 +27,7 @@
"homepage": "https://github.com/testing-library/svelte-testing-library#readme",
"repository": {
"type": "git",
"url": "https://github.com/testing-library/svelte-testing-library"
"url": "git+https://github.com/testing-library/svelte-testing-library.git"
},
"bugs": {
"url": "https://github.com/testing-library/svelte-testing-library/issues"
Expand All @@ -49,7 +50,6 @@
"files": [
"src",
"types",
"!*.test-d.ts",
"!__tests__"
],
"scripts": {
Expand All @@ -69,7 +69,8 @@
"test:vitest:happy-dom": "vitest run --coverage --environment happy-dom",
"test:jest": "npx --node-options=\"--experimental-vm-modules --no-warnings\" jest --coverage",
"types": "svelte-check",
"validate": "npm-run-all test:vitest:* test:jest types",
"validate": "npm-run-all test:vitest:* test:jest types build",
"build": "tsc -p tsconfig.build.json",
"contributors:add": "all-contributors add",
"contributors:generate": "all-contributors generate",
"preview-release": "./scripts/preview-release"
Expand Down
36 changes: 34 additions & 2 deletions types/types.test-d.ts → src/__tests__/types.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { expectTypeOf } from 'expect-type'
import type { ComponentProps, SvelteComponent } from 'svelte'
import { describe, test } from 'vitest'

import Simple from '../src/__tests__/fixtures/Simple.svelte'
import * as subject from './index.js'
import * as subject from '../index.js'
import Simple from './fixtures/Simple.svelte'

describe('types', () => {
test('render is a function that accepts a Svelte component', () => {
Expand Down Expand Up @@ -62,4 +62,36 @@ describe('types', () => {

expectTypeOf(result.getByVibes).parameters.toMatchTypeOf<[vibes: string]>()
})

test('act is an async function', () => {
expectTypeOf(subject.act).toMatchTypeOf<() => Promise<void>>()
})

test('act accepts a sync function', () => {
expectTypeOf(subject.act).toMatchTypeOf<(fn: () => void) => Promise<void>>()
})

test('act accepts an async function', () => {
expectTypeOf(subject.act).toMatchTypeOf<
(fn: () => Promise<void>) => Promise<void>
>()
})

test('fireEvent is an async function', () => {
expectTypeOf(subject.fireEvent).toMatchTypeOf<
(
element: Element | Node | Document | Window,
event: Event
) => Promise<boolean>
>()
})

test('fireEvent[eventName] is an async function', () => {
expectTypeOf(subject.fireEvent.click).toMatchTypeOf<
(
element: Element | Node | Document | Window,
options?: {}
) => Promise<boolean>
>()
})
})
12 changes: 4 additions & 8 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@ if (typeof afterEach === 'function' && !process.env.STL_SKIP_AUTO_CLEANUP) {
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,
UnknownSvelteOptionsError,
} from './pure.js'
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'
91 changes: 79 additions & 12 deletions src/pure.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,61 @@
import {
fireEvent as dtlFireEvent,
fireEvent as baseFireEvent,
getQueriesForElement,
prettyDOM,
} from '@testing-library/dom'
import { tick } from 'svelte'

import {
mount,
UnknownSvelteOptionsError,
unmount,
updateProps,
validateOptions,
} from './core/index.js'
import { mount, unmount, updateProps, validateOptions } from './core/index.js'

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

/**
* Customize how Svelte renders the component.
*
* @template {import('svelte').SvelteComponent} C
* @typedef {import('svelte').ComponentProps<C> | Partial<import('svelte').ComponentConstructorOptions<import('svelte').ComponentProps<C>>>} SvelteComponentOptions
*/

/**
* Customize how Testing Library sets up the document and binds queries.
*
* @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries]
* @typedef {{
* baseElement?: HTMLElement
* queries?: Q
* }} RenderOptions
*/

/**
* The rendered component and bound testing functions.
*
* @template {import('svelte').SvelteComponent} C
* @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries]
*
* @typedef {{
* container: HTMLElement
* baseElement: HTMLElement
* component: C
* debug: (el?: HTMLElement | DocumentFragment) => void
* rerender: (props: Partial<import('svelte').ComponentProps<C>>) => Promise<void>
* unmount: () => void
* } & {
* [P in keyof Q]: import('@testing-library/dom').BoundFunction<Q[P]>
* }} RenderResult
*/

/**
* Render a component into the document.
*
* @template {import('svelte').SvelteComponent} C
* @template {import('@testing-library/dom').Queries} [Q=typeof import('@testing-library/dom').queries]
*
* @param {import('svelte').ComponentType<C>} Component - The component to render.
* @param {SvelteComponentOptions<C>} options - Customize how Svelte renders the component.
* @param {RenderOptions<Q>} renderOptions - Customize how Testing Library sets up the document and binds queries.
* @returns {RenderResult<C, Q>} The rendered component and bound testing functions.
*/
const render = (Component, options = {}, renderOptions = {}) => {
options = validateOptions(options)

Expand Down Expand Up @@ -62,6 +102,7 @@ const render = (Component, options = {}, renderOptions = {}) => {
}
}

/** Remove a component from the component cache. */
const cleanupComponent = (component) => {
const inCache = componentCache.delete(component)

Expand All @@ -70,6 +111,7 @@ const cleanupComponent = (component) => {
}
}

/** Remove a target element from the target cache. */
const cleanupTarget = (target) => {
const inCache = targetCache.delete(target)

Expand All @@ -78,30 +120,55 @@ const cleanupTarget = (target) => {
}
}

/** Unmount all components and remove elements added to `<body>`. */
const cleanup = () => {
componentCache.forEach(cleanupComponent)
targetCache.forEach(cleanupTarget)
}

/**
* Call a function and wait for Svelte to flush pending changes.
*
* @param {() => unknown} [fn] - A function, which may be `async`, to call before flushing updates.
* @returns {Promise<void>}
*/
const act = async (fn) => {
if (fn) {
await fn()
}
return tick()
}

/**
* @typedef {(...args: Parameters<import('@testing-library/dom').FireFunction>) => Promise<ReturnType<import('@testing-library/dom').FireFunction>>} FireFunction
*/

/**
* @typedef {{
* [K in import('@testing-library/dom').EventType]: (...args: Parameters<import('@testing-library/dom').FireObject[K]>) => Promise<ReturnType<import('@testing-library/dom').FireObject[K]>>
* }} FireObject
*/

/**
* Fire an event on an element.
*
* Consider using `@testing-library/user-event` instead, if possible.
* @see https://testing-library.com/docs/user-event/intro/
*
* @type {FireFunction & FireObject}
*/
const fireEvent = async (...args) => {
const event = dtlFireEvent(...args)
const event = baseFireEvent(...args)
await tick()
return event
}

Object.keys(dtlFireEvent).forEach((key) => {
Object.keys(baseFireEvent).forEach((key) => {
fireEvent[key] = async (...args) => {
const event = dtlFireEvent[key](...args)
const event = baseFireEvent[key](...args)
await tick()
return event
}
})

export { act, cleanup, fireEvent, render, UnknownSvelteOptionsError }
export { act, cleanup, fireEvent, render }
12 changes: 12 additions & 0 deletions tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": ["./tsconfig.json"],
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"noEmit": false,
"rootDir": "src",
"outDir": "types"
},
"exclude": ["src/**/__tests__/**"]
}
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"compilerOptions": {
"module": "node16",
"allowJs": true,
"noEmit": true,
"skipLibCheck": true,
"strict": true,
"types": ["svelte", "vite/client", "vitest", "vitest/globals"]
},
"include": ["src", "types"]
"include": ["src"]
}
Loading
Loading