From b749d65a4a55af5b73577ab8132a5298ae9d389a Mon Sep 17 00:00:00 2001 From: Nathan Clevenger Date: Wed, 11 Jun 2025 07:59:16 -0500 Subject: [PATCH] test(core): add component and workflow tests --- .../tests/__mocks__/testing-library-react.ts | 45 +++++++++++++ packages/mdxui/core/tests/index.test.ts | 7 -- packages/mdxui/core/tests/index.test.tsx | 30 +++++++++ packages/mdxui/core/tests/workflow.test.ts | 65 +++++++++++++------ packages/mdxui/core/vitest.config.ts | 7 +- 5 files changed, 125 insertions(+), 29 deletions(-) create mode 100644 packages/mdxui/core/tests/__mocks__/testing-library-react.ts delete mode 100644 packages/mdxui/core/tests/index.test.ts create mode 100644 packages/mdxui/core/tests/index.test.tsx diff --git a/packages/mdxui/core/tests/__mocks__/testing-library-react.ts b/packages/mdxui/core/tests/__mocks__/testing-library-react.ts new file mode 100644 index 000000000..05b282501 --- /dev/null +++ b/packages/mdxui/core/tests/__mocks__/testing-library-react.ts @@ -0,0 +1,45 @@ +import React from 'react' + +function createDomNode(element: React.ReactElement): HTMLElement { + if (typeof element.type === 'function') { + return createDomNode(element.type(element.props)) + } + const node = document.createElement(element.type as string) + const { children, className, ...rest } = element.props || {} + if (className) node.setAttribute('class', className) + Object.entries(rest).forEach(([k, v]) => { + if (v != null) node.setAttribute(k, String(v)) + }) + React.Children.toArray(children).forEach((child) => { + if (typeof child === 'string') { + node.appendChild(document.createTextNode(child)) + } else { + node.appendChild(createDomNode(child as React.ReactElement)) + } + }) + return node +} + +export function render(ui: React.ReactElement) { + const container = document.createElement('div') + container.appendChild(createDomNode(ui)) + document.body.appendChild(container) + return { container } +} + +export const screen = { + getByRole(role: string) { + return document.querySelector(`[role="${role}"]`) as HTMLElement + }, + getByText(text: string) { + const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT) + while (walker.nextNode()) { + const el = walker.currentNode as HTMLElement + if (el.textContent && el.textContent.includes(text)) return el + } + throw new Error('Element not found') + }, + getByTestId(id: string) { + return document.querySelector(`[data-testid="${id}"]`) as HTMLElement + } +} diff --git a/packages/mdxui/core/tests/index.test.ts b/packages/mdxui/core/tests/index.test.ts deleted file mode 100644 index 9d8fa7c94..000000000 --- a/packages/mdxui/core/tests/index.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, it, expect } from 'vitest' - -describe('mdxui package', () => { - it('should be importable', () => { - expect(true).toBe(true) - }) -}) diff --git a/packages/mdxui/core/tests/index.test.tsx b/packages/mdxui/core/tests/index.test.tsx new file mode 100644 index 000000000..e526b428a --- /dev/null +++ b/packages/mdxui/core/tests/index.test.tsx @@ -0,0 +1,30 @@ +import { describe, it, expect } from 'vitest' +import { render, screen } from '@testing-library/react' +import React from 'react' +import { Button } from '../components/button' +import { Card } from '../components/card' +import { Gradient } from '../components/gradient' + +describe('exported components', () => { + it('renders Button', () => { + render() + const button = document.querySelector('button') + expect(button).not.toBeNull() + }) + + it('renders Card', () => { + render( + + Card body + + ) + const link = document.querySelector('a') + expect(link?.getAttribute('href')).to.contain('https://example.com') + expect(screen.getByText('Card body')).not.toBeNull() + }) + + it('renders Gradient', () => { + const { container } = render(
) + expect(container.querySelector('span')).not.toBeNull() + }) +}) diff --git a/packages/mdxui/core/tests/workflow.test.ts b/packages/mdxui/core/tests/workflow.test.ts index eed43911f..a758bd3c1 100644 --- a/packages/mdxui/core/tests/workflow.test.ts +++ b/packages/mdxui/core/tests/workflow.test.ts @@ -1,33 +1,56 @@ import { describe, it, expect } from 'vitest' import { z } from 'zod' -import type { Step, Workflow } from '../workflow' - -describe('Workflow interfaces', () => { - it('should define Step interface correctly', () => { - const step: Step = { - id: 'test-step', - name: 'Test Step', - description: 'A test step', +import type { Step, Workflow, WorkflowExecution } from '../workflow' + +async function executeStep(step: Step, input: TInput): Promise { + if (step.inputSchema) step.inputSchema.parse(input) + const result = await step.execute?.(input) + return step.outputSchema.parse(result) +} + +describe('workflow utilities', () => { + it('executes a step with schema validation', async () => { + const step: Step<{ input: string }, { output: string }> = { + id: 'step', + name: 'Example', inputSchema: z.object({ input: z.string() }), outputSchema: z.object({ output: z.string() }), + execute: async (value) => ({ output: value.input.toUpperCase() }), } - expect(step.id).toBe('test-step') - expect(step.name).toBe('Test Step') - expect(step.inputSchema).toBeDefined() - expect(step.outputSchema).toBeDefined() + const result = await executeStep(step, { input: 'test' }) + expect(result.output).toBe('TEST') }) - it('should define Workflow interface correctly', () => { - const workflow: Workflow = { - id: 'test-workflow', - name: 'Test Workflow', - inputSchema: z.object({ start: z.string() }), - outputSchema: z.object({ end: z.string() }), - steps: [], + it('tracks workflow execution state', async () => { + const step: Step = { + id: 's1', + name: 'first', + outputSchema: z.string(), + execute: async () => 'done', + } + + const workflow: Workflow = { + id: 'wf', + name: 'wf', + inputSchema: z.undefined(), + outputSchema: z.string(), + steps: [step], } - expect(workflow.id).toBe('test-workflow') - expect(workflow.steps).toEqual([]) + const execution: WorkflowExecution = { + workflow, + currentStepIndex: 0, + stepResults: {}, + status: 'pending', + } + + const output = await executeStep(workflow.steps[0], undefined) + execution.stepResults[step.id] = output + execution.currentStepIndex = 1 + execution.status = 'completed' + + expect(execution.stepResults[step.id]).toBe('done') + expect(execution.status).toBe('completed') }) }) diff --git a/packages/mdxui/core/vitest.config.ts b/packages/mdxui/core/vitest.config.ts index 9fd90ef52..0c25681d7 100644 --- a/packages/mdxui/core/vitest.config.ts +++ b/packages/mdxui/core/vitest.config.ts @@ -1,10 +1,15 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ + resolve: { + alias: { + '@testing-library/react': new URL('./tests/__mocks__/testing-library-react.ts', import.meta.url).pathname, + }, + }, test: { globals: true, environment: 'jsdom', - include: ['src/**/*.test.{ts,tsx}'], + include: ['src/**/*.test.{ts,tsx}', 'tests/**/*.test.{ts,tsx}'], exclude: ['**/node_modules/**', '**/dist/**'], testTimeout: 300000, coverage: {