--- title: "Testing Guide - BYO SDK" slug: "testing-guide-byo-sdk" updated: 2026-10-07T18:46:28Z published: 2026-10-07T18:46:28Z canonical: "docs.unqork.io/testing-guide-byo-sdk" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.unqork.io/llms.txt > Use this file to discover all available pages before exploring further. # Testing Guide - BYO SDK Write comprehensive tests for your BYO components using the SDK's testing utilities and [Vitest](https://vitest.dev). The BYO SDK provides the following testing tools: - **Testing utilities**: Helpers for mounting, mocking, and asserting. - **Vitest integration**: Fast unit test runner with happy-dom environment. - **Mock host**: Simulates platform behavior. - **Callback spies**: Lightweight assertion helpers. ## Getting Started Scaffolded projects include Vitest and testing utilities preconfigured. The scaffold adds the following script to `package.json`: ```json {  "scripts": {    "test": "vitest run"  } } ``` To add watch mode, UI mode, and coverage support, add the following scripts manually: ```json {  "scripts": {    "test": "vitest run",    "test:watch": "vitest",    "test:ui": "vitest --ui",    "test:coverage": "vitest --coverage"  } } ``` Run tests using the following commands: ```bash # Run npm test # Watch mode npm run test:watch # UI mode npm run test:ui # With coverage npm run test:coverage ``` ## Testing Utilities Import from `@unqork/byo-sdk/testing`: ```typescript import {  mountByoComponent,  applyDefaults,  createCallbackSpy,  defineMockScenario, } from '@unqork/byo-sdk/testing' ``` ### `mountByoComponent` Mounts a component export as a custom element in the document. ```typescript async function mountByoComponent(  componentExport: ByoComponentExport,  tagPrefix?: string  // default: 'test' ): Promise ``` **Example**: ```typescript import { mountByoComponent } from '@unqork/byo-sdk/testing' import { myButton } from './entry' it('renders a button element', async () => {  const el = await mountByoComponent(myButton)    expect(el.querySelector('button')).toBeTruthy() }) ``` Each call generates a unique tag name to avoid conflicts between tests. ### `applyDefaults` Reads default values from the model schema and sets them as properties. ```typescript async function applyDefaults(  element: HTMLElement,  componentExport: ByoComponentExport ): Promise ``` **Example**: ```typescript import { mountByoComponent, applyDefaults } from '@unqork/byo-sdk/testing' import { myButton } from './entry' it('applies default label', async () => {  const el = await mountByoComponent(myButton) as any  await applyDefaults(el, myButton)    // Model defines: label = 'Click me'  expect(el.label).toBe('Click me') }) ``` Simulates what the platform does before rendering a component. ### `createCallbackSpy` Creates a spy function that records all invocations. ```typescript function createCallbackSpy(): {  fn: (...args: unknown[]) => void  calls: unknown[][] } ``` **Example**: ```typescript import { mountByoComponent, createCallbackSpy } from '@unqork/byo-sdk/testing' import { myButton } from './entry' it('fires callback on click', async () => {  const el = await mountByoComponent(myButton) as any  const spy = createCallbackSpy()    el.onButtonClicked = spy.fn  el.label = 'Test'    el.querySelector('button')!.click()    expect(spy.calls).toHaveLength(1)  expect(spy.calls[0][0]).toEqual({ label: 'Test' }) }) ``` The spy records `calls` as an array of argument arrays. ## Testing Patterns ### Basic Component Test ```typescript import { describe, it, expect } from 'vitest' import { mountByoComponent, applyDefaults } from '@unqork/byo-sdk/testing' import { myButton } from './entry' describe('MyButton', () => {  it('renders with default props', async () => {    const el = await mountByoComponent(myButton)    await applyDefaults(el, myButton)        expect(el.querySelector('button')?.textContent).toBe('Click me')  }) }) ``` ### Testing Props ```typescript it('updates text when label changes', async () => {  const el = await mountByoComponent(myButton) as any    el.label = 'First'  expect(el.querySelector('button')?.textContent).toBe('First')    el.label = 'Second'  expect(el.querySelector('button')?.textContent).toBe('Second') }) it('disables button when disabled prop is true', async () => {  const el = await mountByoComponent(myButton) as any    el.disabled = true    expect(el.querySelector('button')?.disabled).toBe(true) }) ``` ### Testing Callbacks ```typescript import { createCallbackSpy } from '@unqork/byo-sdk/testing' it('fires with correct payload', async () => {  const el = await mountByoComponent(myButton) as any  const spy = createCallbackSpy()    el.onButtonClicked = spy.fn  el.label = 'Submit'    el.querySelector('button')!.click()    expect(spy.calls).toHaveLength(1)  expect(spy.calls[0][0]).toEqual({ label: 'Submit' }) }) it('does not error if callback is not set', async () => {  const el = await mountByoComponent(myButton) as any    // Should not throw  expect(() => {    el.querySelector('button')!.click()  }).not.toThrow() }) ``` ### Testing Submission Data ```typescript it('updates submission data on change', async () => {  const el = await mountByoComponent(myInput) as any  const spy = createCallbackSpy()    el.onSubmissionUpdate = spy.fn    const input = el.querySelector('input')!  input.value = 'test value'  input.dispatchEvent(new Event('input'))    expect(spy.calls).toHaveLength(1)  expect(spy.calls[0][0]).toBe('test value') }) it('initializes from submissionData prop', async () => {  const el = await mountByoComponent(myInput) as any    el.submissionData = 'initial value'    expect(el.querySelector('input')?.value).toBe('initial value') }) ``` ### Testing Complex Interactions ```typescript describe('TaskList', () => {  it('toggles task completion', async () => {    const el = await mountByoComponent(taskList) as any    const toggleSpy = createCallbackSpy()    const updateSpy = createCallbackSpy()        el.onTaskToggled = toggleSpy.fn    el.onSubmissionUpdate = updateSpy.fn        el.items = [      { id: '1', title: 'Task 1', completed: false }    ]        const checkbox = el.querySelector('input[type="checkbox"]')!    checkbox.click()        // Check callback was fired    expect(toggleSpy.calls).toHaveLength(1)    expect(toggleSpy.calls[0][0].task.completed).toBe(true)        // Check submission was updated    expect(updateSpy.calls).toHaveLength(1)    expect(updateSpy.calls[0][0][0].completed).toBe(true)        // Check UI updated    const taskEl = el.querySelector('.task')!    expect(taskEl.classList.contains('completed')).toBe(true)  }) }) ``` ### Testing Error Handling ```typescript it('handles invalid prop gracefully', async () => {  const el = await mountByoComponent(myButton) as any    // Should not throw  expect(() => {    el.label = null  }).not.toThrow()    // Should render empty or default  expect(el.querySelector('button')?.textContent).toBe('') }) it('validates submission data format', async () => {  const el = await mountByoComponent(myInput) as any  const spy = createCallbackSpy()    el.onSubmissionUpdate = spy.fn    // Component should reject invalid data  el.submissionData = { invalid: 'object' }    expect(spy.calls).toHaveLength(0) }) ``` ## Testing Framework-Specific Components ### React Components ```typescript // MyButton.test.tsx import { describe, it, expect } from 'vitest' import { mountByoComponent, createCallbackSpy } from '@unqork/byo-sdk/testing' import { myButton } from './entry' describe('MyButton (React)', () => {  it('renders react component', async () => {    const el = await mountByoComponent(myButton) as any        el.label = 'React Button'        // Wait for React to render    await new Promise(resolve => setTimeout(resolve, 0))        expect(el.shadowRoot?.querySelector('button')?.textContent).toBe('React Button')  }) }) ``` React components use `@r2wc/react-to-web-component`, which renders into a shadow DOM. ### Vue Components ```typescript // MyButton.test.ts import { describe, it, expect } from 'vitest' import { mountByoComponent } from '@unqork/byo-sdk/testing' import { myButton } from './entry' describe('MyButton (Vue)', () => {  it('renders vue component', async () => {    const el = await mountByoComponent(myButton) as any        el.label = 'Vue Button'        // Wait for Vue to render    await new Promise(resolve => setTimeout(resolve, 0))        expect(el.querySelector('button')?.textContent).toBe('Vue Button')  }) }) ``` ## Testing Async Behavior ### Debounced Updates ```typescript import { vi } from 'vitest' it('debounces submission updates', async () => {  vi.useFakeTimers()    const el = await mountByoComponent(myInput) as any  const spy = createCallbackSpy()    el.onSubmissionUpdate = spy.fn    const input = el.querySelector('input')!    input.value = 'a'  input.dispatchEvent(new Event('input'))    input.value = 'ab'  input.dispatchEvent(new Event('input'))    input.value = 'abc'  input.dispatchEvent(new Event('input'))    // Fast-forward time  vi.advanceTimersByTime(300)    // Should call (debounced)  expect(spy.calls).toHaveLength(1)  expect(spy.calls[0][0]).toBe('abc')    vi.useRealTimers() }) ``` ### Lazy Loading ```typescript it('lazy loads chart library on demand', async () => {  const el = await mountByoComponent(myChart) as any    // Initially no chart rendered  expect(el.querySelector('.chart')).toBeNull()    // Trigger chart rendering  el.showChart = true    // Wait for dynamic import  await new Promise(resolve => setTimeout(resolve, 100))    expect(el.querySelector('.chart')).toBeTruthy() }) ``` ## Coverage Run tests with coverage: ```bash npm run test:coverage ``` : ```plaintext Coverage report:  File              | % Stmts | % Branch | % Funcs | % Lines  ------------------|---------|----------|---------|--------  All files         |   92.5  |   88.3   |   95.0  |   93.2  MyButton.ts       |   95.0  |   90.0   |  100.0  |   96.0  MyInput.ts        |   90.0  |   85.0   |   90.0  |   91.0 ``` To configure coverage thresholds, add the following to your `vitest.config.ts`: ```typescript import { defineConfig } from 'vitest/config' export default defineConfig({  test: {    coverage: {      provider: 'v8',      reporter: ['text', 'html', 'lcov'],      thresholds: {        statements: 80,        branches: 75,        functions: 80,        lines: 80,      }    }  } }) ``` ## Best Practices ### 1. Test User Interactions, Not Implementation **Bad**: ```typescript it('sets _label property', () => {  el._label = 'test'  // Testing internal state }) ``` **Good**: ```typescript it('updates button text when label changes', () => {  el.label = 'test'  expect(el.querySelector('button')?.textContent).toBe('test') }) ``` ### 2. Use Descriptive Test Names **Bad**: ```typescript it('works', () => { ... }) ``` **Good**: ```typescript it('fires with label when button is clicked', () => { ... }) ``` ### 3. Test Edge Cases ```typescript describe('MyButton edge cases', () => {  it('handles empty label', async () => {    el.label = ''    expect(el.querySelector('button')?.textContent).toBe('')  })    it('handles null label', async () => {    el.label = null as any    expect(el.querySelector('button')?.textContent).toBe('')  })    it('handles very long label', async () => {    el.label = 'x'.repeat(1000)    expect(el.querySelector('button')?.textContent).toHaveLength(1000)  }) }) ``` ### 4. Clean Up After Tests ```typescript import { afterEach } from 'vitest' afterEach(() => {  // Remove mounted elements  document.body.innerHTML = '' }) ``` ### 5. Test Accessibility ```typescript it('has accessible label', async () => {  const el = await mountByoComponent(myButton) as any  el.label = 'Submit form'    const button = el.querySelector('button')!  expect(button.getAttribute('aria-label')).toBe('Submit form') }) it('supports keyboard navigation', async () => {  const el = await mountByoComponent(myButton) as any  const spy = createCallbackSpy()    el.onButtonClicked = spy.fn    const button = el.querySelector('button')!  button.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))    expect(spy.calls).toHaveLength(1) }) ``` ## Debugging Tests ### Enable Verbose Logging ```bash npm test -- --reporter verbose ``` ### Inspect the DOM ```typescript it('debug test', async () => {  const el = await mountByoComponent(myButton) as any  el.label = 'Test'    console.log(el.outerHTML)  //  //    // }) ``` ### Use the Vitest UI ```bash npm run test:ui ``` Opens an interactive browser UI for running and debugging tests. ## Next Steps - [Building Components](/unqorkai/docs/building-components-byo-sdk) — step-by-step guide. - [CLI Reference](/unqorkai/docs/cli-reference-byo-sdk) — complete command documentation. - [SDK Overview](/unqorkai/docs/byo-sdk) — SDK features and configuration. --- **Changelog** | Date | Change | | --- | --- | | 2026-10-01 | Corrected React bridge package name to `@r2wc/react-to-web-component`; added blank line before list for correct MDX rendering (EN-8151). | | 2026-05-01 | Initial publication | The UI (user interface) is a device's end-user interaction and communication point. It can include display screens, keyboards, and mouse. It is also how a user interacts with your application. Schema is a declarative language that defines the structure, content, and constraints of the data. Rendering is the process of loading components and displaying the final result on the front-end screen. An array is a type of object that stores one or more data types. Data types supported in arrays include numbers, strings, and objects. An array is a type of object that stores one or more data types. Data types supported in arrays include numbers, strings, and objects. Rendering is the process of loading components and displaying the final result on the front-end screen. The Document Object Model (DOM) is the data representation of objects that form the structure and content of a document on the web. The DOM represents the page.