Skip to content

Commit

Permalink
refactor(types): build types from JS source
Browse files Browse the repository at this point in the history
  • Loading branch information
mcous committed Jun 22, 2024
1 parent 9dce164 commit a2a5039
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 120 deletions.
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
32 changes: 31 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,36 @@ 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' }}
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
runs-on: ubuntu-latest
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
"files": [
"src",
"types",
"!*.test-d.ts",
"!__tests__"
],
"scripts": {
Expand All @@ -69,7 +68,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

0 comments on commit a2a5039

Please sign in to comment.