@coherent.js/tooling 1.1.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/testing/test-renderer.js", "../../src/testing/test-utils.js", "../../src/testing/matchers.js", "../../src/testing/index.js"],
4
- "sourcesContent": ["/**\n * Coherent.js Test Renderer\n * \n * Provides utilities for rendering and testing Coherent.js components\n * in a test environment.\n * \n * @module testing/test-renderer\n */\n\nimport { render } from '@coherent.js/core';\n\n/**\n * Test renderer result\n * Provides methods to query and interact with rendered components\n */\nexport class TestRendererResult {\n constructor(component, html, container = null) {\n this.component = component;\n this.html = html;\n this.container = container;\n this.queries = new Map();\n }\n\n /**\n * Get element by test ID\n * @param {string} testId - Test ID to search for\n * @returns {Object|null} Element or null\n */\n getByTestId(testId) {\n const regex = new RegExp(`data-testid=\"${testId}\"[^>]*>([^<]*)<`, 'i');\n const match = this.html.match(regex);\n \n if (!match) {\n throw new Error(`Unable to find element with testId: ${testId}`);\n }\n \n return {\n text: match[1],\n html: match[0],\n testId,\n exists: true\n };\n }\n\n /**\n * Query element by test ID (returns null if not found)\n * @param {string} testId - Test ID to search for\n * @returns {Object|null} Element or null\n */\n queryByTestId(testId) {\n try {\n return this.getByTestId(testId);\n } catch {\n return null;\n }\n }\n\n /**\n * Get element by text content\n * @param {string|RegExp} text - Text to search for\n * @returns {Object} Element\n */\n getByText(text) {\n const regex = typeof text === 'string' \n ? new RegExp(`>([^<]*${text}[^<]*)<`, 'i')\n : new RegExp(`>([^<]*)<`, 'i');\n \n const match = this.html.match(regex);\n \n if (!match || (typeof text === 'string' && !match[1].includes(text))) {\n throw new Error(`Unable to find element with text: ${text}`);\n }\n \n return {\n text: match[1],\n html: match[0],\n exists: true\n };\n }\n\n /**\n * Query element by text (returns null if not found)\n * @param {string|RegExp} text - Text to search for\n * @returns {Object|null} Element or null\n */\n queryByText(text) {\n try {\n return this.getByText(text);\n } catch {\n return null;\n }\n }\n\n /**\n * Get element by class name\n * @param {string} className - Class name to search for\n * @returns {Object} Element\n */\n getByClassName(className) {\n const regex = new RegExp(`class=\"[^\"]*${className}[^\"]*\"[^>]*>([^<]*)<`, 'i');\n const match = this.html.match(regex);\n \n if (!match) {\n throw new Error(`Unable to find element with className: ${className}`);\n }\n \n return {\n text: match[1],\n html: match[0],\n className,\n exists: true\n };\n }\n\n /**\n * Query element by class name (returns null if not found)\n * @param {string} className - Class name to search for\n * @returns {Object|null} Element or null\n */\n queryByClassName(className) {\n try {\n return this.getByClassName(className);\n } catch {\n return null;\n }\n }\n\n /**\n * Get all elements by tag name\n * @param {string} tagName - Tag name to search for\n * @returns {Array<Object>} Array of elements\n */\n getAllByTagName(tagName) {\n const regex = new RegExp(`<${tagName}[^>]*>([^<]*)</${tagName}>`, 'gi');\n const matches = [...this.html.matchAll(regex)];\n \n return matches.map(match => ({\n text: match[1],\n html: match[0],\n tagName,\n exists: true\n }));\n }\n\n /**\n * Check if element exists\n * @param {string} selector - Selector (testId, text, className)\n * @param {string} type - Type of selector ('testId', 'text', 'className')\n * @returns {boolean} True if exists\n */\n exists(selector, type = 'testId') {\n switch (type) {\n case 'testId':\n return this.queryByTestId(selector) !== null;\n case 'text':\n return this.queryByText(selector) !== null;\n case 'className':\n return this.queryByClassName(selector) !== null;\n default:\n return false;\n }\n }\n\n /**\n * Get the rendered HTML\n * @returns {string} HTML string\n */\n getHTML() {\n return this.html;\n }\n\n /**\n * Get the component\n * @returns {Object} Component object\n */\n getComponent() {\n return this.component;\n }\n\n /**\n * Create a snapshot of the rendered output\n * @returns {string} Formatted HTML for snapshot testing\n */\n toSnapshot() {\n return this.html\n .replace(/>\\s+</g, '><') // Remove whitespace between tags\n .trim();\n }\n\n /**\n * Debug: print the rendered HTML\n */\n debug() {\n console.log('=== Rendered HTML ===');\n console.log(this.html);\n console.log('=== Component ===');\n console.log(JSON.stringify(this.component, null, 2));\n }\n}\n\n/**\n * Render a component for testing\n * \n * @param {Object} component - Component to render\n * @param {Object} [options] - Render options\n * @returns {TestRendererResult} Test renderer result\n * \n * @example\n * const { getByTestId } = renderComponent({\n * div: {\n * 'data-testid': 'my-div',\n * text: 'Hello World'\n * }\n * });\n * \n * expect(getByTestId('my-div').text).toBe('Hello World');\n */\nexport function renderComponent(component, options = {}) {\n const html = render(component, options);\n return new TestRendererResult(component, html);\n}\n\n/**\n * Render a component asynchronously\n * \n * @param {Object|Function} component - Component or component factory\n * @param {Object} [props] - Component props\n * @param {Object} [options] - Render options\n * @returns {Promise<TestRendererResult>} Test renderer result\n */\nexport async function renderComponentAsync(component, props = {}, options = {}) {\n // If component is a function, call it with props\n const resolvedComponent = typeof component === 'function' \n ? await component(props)\n : component;\n \n const html = render(resolvedComponent, options);\n return new TestRendererResult(resolvedComponent, html);\n}\n\n/**\n * Create a test renderer instance\n * Useful for testing component updates\n */\nexport class TestRenderer {\n constructor(component, options = {}) {\n this.component = component;\n this.options = options;\n this.result = null;\n this.renderCount = 0;\n }\n\n /**\n * Render the component\n * @returns {TestRendererResult} Render result\n */\n render() {\n this.renderCount++;\n const html = render(this.component, this.options);\n this.result = new TestRendererResult(this.component, html);\n return this.result;\n }\n\n /**\n * Update the component and re-render\n * @param {Object} newComponent - Updated component\n * @returns {TestRendererResult} Render result\n */\n update(newComponent) {\n this.component = newComponent;\n return this.render();\n }\n\n /**\n * Get the current result\n * @returns {TestRendererResult|null} Current result\n */\n getResult() {\n return this.result;\n }\n\n /**\n * Get render count\n * @returns {number} Number of renders\n */\n getRenderCount() {\n return this.renderCount;\n }\n\n /**\n * Unmount the component\n */\n unmount() {\n this.component = null;\n this.result = null;\n }\n}\n\n/**\n * Create a test renderer\n * \n * @param {Object} component - Component to render\n * @param {Object} [options] - Render options\n * @returns {TestRenderer} Test renderer instance\n * \n * @example\n * const renderer = createTestRenderer(MyComponent);\n * const result = renderer.render();\n * expect(result.getByText('Hello')).toBeTruthy();\n * \n * // Update and re-render\n * renderer.update(UpdatedComponent);\n * expect(renderer.getRenderCount()).toBe(2);\n */\nexport function createTestRenderer(component, options = {}) {\n return new TestRenderer(component, options);\n}\n\n/**\n * Shallow render a component (only render top level)\n * \n * @param {Object} component - Component to render\n * @returns {Object} Shallow rendered component\n */\nexport function shallowRender(component) {\n // Clone component without rendering children\n const shallow = { ...component };\n \n Object.keys(shallow).forEach(key => {\n if (shallow[key] && typeof shallow[key] === 'object') {\n if (shallow[key].children) {\n shallow[key] = {\n ...shallow[key],\n children: Array.isArray(shallow[key].children)\n ? shallow[key].children.map(() => ({ _shallow: true }))\n : { _shallow: true }\n };\n }\n }\n });\n \n return shallow;\n}\n\n/**\n * Export all testing utilities\n */\nexport default {\n renderComponent,\n renderComponentAsync,\n createTestRenderer,\n shallowRender,\n TestRenderer,\n TestRendererResult\n};\n", "/**\n * Coherent.js Test Utilities\n * \n * Helper functions for testing Coherent.js components\n * \n * @module testing/test-utils\n */\n\n/**\n * Simulate an event on an element\n * \n * @param {Object} element - Element to fire event on\n * @param {string} eventType - Type of event (click, change, etc.)\n * @param {Object} [eventData] - Additional event data\n */\nexport function fireEvent(element, eventType, eventData = {}) {\n if (!element) {\n throw new Error('Element is required for fireEvent');\n }\n \n // In a test environment, we simulate the event\n const event = {\n type: eventType,\n target: element,\n currentTarget: element,\n preventDefault: () => {},\n stopPropagation: () => {},\n ...eventData\n };\n \n // If element has an event handler, call it\n const handlerName = `on${eventType}`;\n if (element[handlerName] && typeof element[handlerName] === 'function') {\n element[handlerName](event);\n }\n \n return event;\n}\n\n/**\n * Common event helpers\n */\nexport const fireEvent_click = (element, eventData) => \n fireEvent(element, 'click', eventData);\n\nexport const fireEvent_change = (element, value) => \n fireEvent(element, 'change', { target: { value } });\n\nexport const fireEvent_input = (element, value) => \n fireEvent(element, 'input', { target: { value } });\n\nexport const fireEvent_submit = (element, eventData) => \n fireEvent(element, 'submit', eventData);\n\nexport const fireEvent_keyDown = (element, key) => \n fireEvent(element, 'keydown', { key });\n\nexport const fireEvent_keyUp = (element, key) => \n fireEvent(element, 'keyup', { key });\n\nexport const fireEvent_focus = (element) => \n fireEvent(element, 'focus');\n\nexport const fireEvent_blur = (element) => \n fireEvent(element, 'blur');\n\n/**\n * Wait for a condition to be true\n * \n * @param {Function} condition - Condition function\n * @param {Object} [options] - Wait options\n * @param {number} [options.timeout=1000] - Timeout in ms\n * @param {number} [options.interval=50] - Check interval in ms\n * @returns {Promise<void>}\n * \n * @example\n * await waitFor(() => getByText('Loaded').exists, { timeout: 2000 });\n */\nexport function waitFor(condition, options = {}) {\n const { timeout = 1000, interval = 50 } = options;\n \n return new Promise((resolve, reject) => {\n const startTime = Date.now();\n \n const check = () => {\n try {\n if (condition()) {\n resolve();\n return;\n }\n } catch {\n // Condition threw an error, keep waiting\n }\n \n if (Date.now() - startTime >= timeout) {\n reject(new Error(`Timeout waiting for condition after ${timeout}ms`));\n return;\n }\n \n setTimeout(check, interval);\n };\n \n check();\n });\n}\n\n/**\n * Wait for element to appear\n * \n * @param {Function} queryFn - Query function that returns element\n * @param {Object} [options] - Wait options\n * @returns {Promise<Object>} Element\n */\nexport async function waitForElement(queryFn, options = {}) {\n let element = null;\n \n await waitFor(() => {\n element = queryFn();\n return element !== null;\n }, options);\n \n return element;\n}\n\n/**\n * Wait for element to disappear\n * \n * @param {Function} queryFn - Query function that returns element\n * @param {Object} [options] - Wait options\n * @returns {Promise<void>}\n */\nexport async function waitForElementToBeRemoved(queryFn, options = {}) {\n await waitFor(() => {\n const element = queryFn();\n return element === null;\n }, options);\n}\n\n/**\n * Act utility for batching updates\n * Useful for testing state changes\n * \n * @param {Function} callback - Callback to execute\n * @returns {Promise<void>}\n */\nexport async function act(callback) {\n await callback();\n // Allow any pending updates to flush\n await new Promise(resolve => setTimeout(resolve, 0));\n}\n\n/**\n * Create a mock function\n * \n * @param {Function} [implementation] - Optional implementation\n * @returns {Function} Mock function\n */\nexport function createMock(implementation) {\n const calls = [];\n const results = [];\n \n const mockFn = function(...args) {\n calls.push(args);\n \n let result;\n let error;\n \n try {\n result = implementation ? implementation(...args) : undefined;\n results.push({ type: 'return', value: result });\n } catch (err) {\n error = err;\n results.push({ type: 'throw', value: error });\n throw error;\n }\n \n return result;\n };\n \n // Add mock utilities\n mockFn.mock = {\n calls,\n results,\n instances: []\n };\n \n mockFn.mockClear = () => {\n calls.length = 0;\n results.length = 0;\n };\n \n mockFn.mockReset = () => {\n mockFn.mockClear();\n implementation = undefined;\n };\n \n mockFn.mockImplementation = (fn) => {\n implementation = fn;\n return mockFn;\n };\n \n mockFn.mockReturnValue = (value) => {\n implementation = () => value;\n return mockFn;\n };\n \n mockFn.mockResolvedValue = (value) => {\n implementation = () => Promise.resolve(value);\n return mockFn;\n };\n \n mockFn.mockRejectedValue = (error) => {\n implementation = () => Promise.reject(error);\n return mockFn;\n };\n \n return mockFn;\n}\n\n/**\n * Create a spy on an object method\n * \n * @param {Object} object - Object to spy on\n * @param {string} method - Method name\n * @returns {Function} Spy function\n */\nexport function createSpy(object, method) {\n const original = object[method];\n const spy = createMock(original.bind(object));\n \n object[method] = spy;\n \n spy.mockRestore = () => {\n object[method] = original;\n };\n \n return spy;\n}\n\n/**\n * Cleanup utility\n * Cleans up after tests\n */\nexport function cleanup() {\n // Clear any timers\n // Reset any global state\n // This would be expanded based on framework needs\n}\n\n/**\n * Within utility - scopes queries to a container\n * \n * @param {Object} container - Container result\n * @returns {Object} Scoped queries\n */\nexport function within(container) {\n return {\n getByTestId: (testId) => container.getByTestId(testId),\n queryByTestId: (testId) => container.queryByTestId(testId),\n getByText: (text) => container.getByText(text),\n queryByText: (text) => container.queryByText(text),\n getByClassName: (className) => container.getByClassName(className),\n queryByClassName: (className) => container.queryByClassName(className)\n };\n}\n\n/**\n * Screen utility - global queries\n * Useful for accessing rendered content without storing result\n */\nexport const screen = {\n _result: null,\n \n setResult(result) {\n this._result = result;\n },\n \n getByTestId(testId) {\n if (!this._result) throw new Error('No component rendered');\n return this._result.getByTestId(testId);\n },\n \n queryByTestId(testId) {\n if (!this._result) return null;\n return this._result.queryByTestId(testId);\n },\n \n getByText(text) {\n if (!this._result) throw new Error('No component rendered');\n return this._result.getByText(text);\n },\n \n queryByText(text) {\n if (!this._result) return null;\n return this._result.queryByText(text);\n },\n \n getByClassName(className) {\n if (!this._result) throw new Error('No component rendered');\n return this._result.getByClassName(className);\n },\n \n queryByClassName(className) {\n if (!this._result) return null;\n return this._result.queryByClassName(className);\n },\n \n debug() {\n if (this._result) {\n this._result.debug();\n }\n }\n};\n\n/**\n * User event simulation\n * More realistic event simulation than fireEvent\n */\nexport const userEvent = {\n /**\n * Simulate user typing\n */\n type: async (element, text, options = {}) => {\n const { delay = 0 } = options;\n \n for (const char of text) {\n fireEvent_keyDown(element, char);\n fireEvent_input(element, element.value + char);\n fireEvent_keyUp(element, char);\n \n if (delay > 0) {\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n },\n \n /**\n * Simulate user click\n */\n click: async (element) => {\n fireEvent_focus(element);\n fireEvent_click(element);\n },\n \n /**\n * Simulate user double click\n */\n dblClick: async (element) => {\n await userEvent.click(element);\n await userEvent.click(element);\n },\n \n /**\n * Simulate user clearing input\n */\n clear: async (element) => {\n fireEvent_input(element, '');\n fireEvent_change(element, '');\n },\n \n /**\n * Simulate user selecting option\n */\n selectOptions: async (element, values) => {\n const valueArray = Array.isArray(values) ? values : [values];\n fireEvent_change(element, valueArray[0]);\n },\n \n /**\n * Simulate user tab navigation\n */\n tab: async () => {\n // Simulate tab key\n const activeElement = document.activeElement;\n if (activeElement) {\n fireEvent_keyDown(activeElement, 'Tab');\n fireEvent_blur(activeElement);\n }\n }\n};\n\n/**\n * Export all utilities\n */\nexport default {\n fireEvent,\n waitFor,\n waitForElement,\n waitForElementToBeRemoved,\n act,\n createMock,\n createSpy,\n cleanup,\n within,\n screen,\n userEvent\n};\n", "/**\n * Coherent.js Custom Test Matchers\n * \n * Custom matchers for testing Coherent.js components\n * Compatible with Vitest, Jest, and other testing frameworks\n * \n * @module testing/matchers\n */\n\n/**\n * Custom matchers for Coherent.js testing\n */\nexport const customMatchers = {\n /**\n * Check if element has specific text\n */\n toHaveText(received, expected) {\n const pass = received && received.text === expected;\n \n return {\n pass,\n message: () => pass\n ? `Expected element not to have text \"${expected}\"`\n : `Expected element to have text \"${expected}\", but got \"${received?.text || 'null'}\"`\n };\n },\n\n /**\n * Check if element contains text\n */\n toContainText(received, expected) {\n const pass = received && received.text && received.text.includes(expected);\n \n return {\n pass,\n message: () => pass\n ? `Expected element not to contain text \"${expected}\"`\n : `Expected element to contain text \"${expected}\", but got \"${received?.text || 'null'}\"`\n };\n },\n\n /**\n * Check if element has specific class\n */\n toHaveClass(received, expected) {\n const pass = received && received.className && received.className.includes(expected);\n \n return {\n pass,\n message: () => pass\n ? `Expected element not to have class \"${expected}\"`\n : `Expected element to have class \"${expected}\", but got \"${received?.className || 'null'}\"`\n };\n },\n\n /**\n * Check if element exists\n */\n toBeInTheDocument(received) {\n const pass = received && received.exists === true;\n \n return {\n pass,\n message: () => pass\n ? 'Expected element not to be in the document'\n : 'Expected element to be in the document'\n };\n },\n\n /**\n * Check if element is visible (has content)\n */\n toBeVisible(received) {\n const pass = received && received.text && received.text.trim().length > 0;\n \n return {\n pass,\n message: () => pass\n ? 'Expected element not to be visible'\n : 'Expected element to be visible (have text content)'\n };\n },\n\n /**\n * Check if element is empty\n */\n toBeEmpty(received) {\n const pass = !received || !received.text || received.text.trim().length === 0;\n \n return {\n pass,\n message: () => pass\n ? 'Expected element not to be empty'\n : 'Expected element to be empty'\n };\n },\n\n /**\n * Check if HTML contains specific string\n */\n toContainHTML(received, expected) {\n const html = received?.html || received;\n const pass = typeof html === 'string' && html.includes(expected);\n \n return {\n pass,\n message: () => pass\n ? `Expected HTML not to contain \"${expected}\"`\n : `Expected HTML to contain \"${expected}\"`\n };\n },\n\n /**\n * Check if element has attribute\n */\n toHaveAttribute(received, attribute, value) {\n const html = received?.html || '';\n const regex = new RegExp(`${attribute}=\"([^\"]*)\"`, 'i');\n const match = html.match(regex);\n \n const pass = value !== undefined\n ? match && match[1] === value\n : match !== null;\n \n return {\n pass,\n message: () => {\n if (value !== undefined) {\n return pass\n ? `Expected element not to have attribute ${attribute}=\"${value}\"`\n : `Expected element to have attribute ${attribute}=\"${value}\", but got \"${match?.[1] || 'none'}\"`;\n }\n return pass\n ? `Expected element not to have attribute ${attribute}`\n : `Expected element to have attribute ${attribute}`;\n }\n };\n },\n\n /**\n * Check if component matches snapshot\n */\n toMatchSnapshot(received) {\n const _snapshot = received?.toSnapshot ? received.toSnapshot() : received;\n \n // This would integrate with the testing framework's snapshot system\n return {\n pass: true,\n message: () => 'Snapshot comparison'\n };\n },\n\n /**\n * Check if element has specific tag name\n */\n toHaveTagName(received, tagName) {\n const html = received?.html || '';\n const regex = new RegExp(`<${tagName}[^>]*>`, 'i');\n const pass = regex.test(html);\n \n return {\n pass,\n message: () => pass\n ? `Expected element not to have tag name \"${tagName}\"`\n : `Expected element to have tag name \"${tagName}\"`\n };\n },\n\n /**\n * Check if render result contains element\n */\n toContainElement(received, element) {\n const html = received?.html || received;\n const elementHtml = element?.html || element;\n const pass = typeof html === 'string' && html.includes(elementHtml);\n \n return {\n pass,\n message: () => pass\n ? 'Expected not to contain element'\n : 'Expected to contain element'\n };\n },\n\n /**\n * Check if mock was called\n */\n toHaveBeenCalled(received) {\n const pass = received?.mock?.calls?.length > 0;\n \n return {\n pass,\n message: () => pass\n ? 'Expected mock not to have been called'\n : 'Expected mock to have been called'\n };\n },\n\n /**\n * Check if mock was called with specific args\n */\n toHaveBeenCalledWith(received, ...expectedArgs) {\n const calls = received?.mock?.calls || [];\n const pass = calls.some(call => \n call.length === expectedArgs.length &&\n call.every((arg, i) => arg === expectedArgs[i])\n );\n \n return {\n pass,\n message: () => pass\n ? `Expected mock not to have been called with ${JSON.stringify(expectedArgs)}`\n : `Expected mock to have been called with ${JSON.stringify(expectedArgs)}`\n };\n },\n\n /**\n * Check if mock was called N times\n */\n toHaveBeenCalledTimes(received, times) {\n const callCount = received?.mock?.calls?.length || 0;\n const pass = callCount === times;\n \n return {\n pass,\n message: () => pass\n ? `Expected mock not to have been called ${times} times`\n : `Expected mock to have been called ${times} times, but was called ${callCount} times`\n };\n },\n\n /**\n * Check if component rendered successfully\n */\n toRenderSuccessfully(received) {\n const pass = received && received.html && received.html.length > 0;\n \n return {\n pass,\n message: () => pass\n ? 'Expected component not to render successfully'\n : 'Expected component to render successfully'\n };\n },\n\n /**\n * Check if HTML is valid\n */\n toBeValidHTML(received) {\n const html = received?.html || received;\n \n // Basic HTML validation. Excluding '<' from the inner classes stops a\n // run of unclosed '<' from being rescanned at every position \u2014 16,000\n // of them took ~200ms per pattern. Counts are unchanged for markup,\n // where a tag never contains '<'. CodeQL js/polynomial-redos.\n const openTags = (html.match(/<[^/<>][^<>]*>/g) || []).length;\n const closeTags = (html.match(/<\\/[^<>]+>/g) || []).length;\n const selfClosing = (html.match(/<[^<>]+\\/>/g) || []).length;\n \n const pass = openTags === closeTags + selfClosing;\n \n return {\n pass,\n message: () => pass\n ? 'Expected HTML not to be valid'\n : `Expected HTML to be valid (open: ${openTags}, close: ${closeTags}, self-closing: ${selfClosing})`\n };\n }\n};\n\n/**\n * Extend expect with custom matchers\n * \n * @param {Object} expect - Expect function from testing framework\n * \n * @example\n * import { expect } from 'vitest';\n * import { extendExpect } from '@coherent.js/tooling/testing/matchers';\n * \n * extendExpect(expect);\n * \n * // Now you can use custom matchers\n * expect(element).toHaveText('Hello');\n */\nexport function extendExpect(expect) {\n if (expect && expect.extend) {\n expect.extend(customMatchers);\n } else {\n console.warn('Could not extend expect - expect.extend not available');\n }\n}\n\n/**\n * Create assertion helpers\n */\nexport const assertions = {\n /**\n * Assert element has text\n */\n assertHasText(element, text) {\n if (!element || element.text !== text) {\n throw new Error(`Expected element to have text \"${text}\", but got \"${element?.text || 'null'}\"`);\n }\n },\n\n /**\n * Assert element exists\n */\n assertExists(element) {\n if (!element || !element.exists) {\n throw new Error('Expected element to exist');\n }\n },\n\n /**\n * Assert element has class\n */\n assertHasClass(element, className) {\n if (!element || !element.className || !element.className.includes(className)) {\n throw new Error(`Expected element to have class \"${className}\"`);\n }\n },\n\n /**\n * Assert HTML contains string\n */\n assertContainsHTML(html, substring) {\n const htmlString = html?.html || html;\n if (!htmlString || !htmlString.includes(substring)) {\n throw new Error(`Expected HTML to contain \"${substring}\"`);\n }\n },\n\n /**\n * Assert component rendered\n */\n assertRendered(result) {\n if (!result || !result.html || result.html.length === 0) {\n throw new Error('Expected component to render');\n }\n }\n};\n\n/**\n * Export all matchers and utilities\n */\nexport default {\n customMatchers,\n extendExpect,\n assertions\n};\n", "/**\n * Coherent.js Testing Utilities\n * \n * Complete testing solution for Coherent.js applications\n * \n * @module testing\n */\n\n// Export test renderer\nexport {\n renderComponent,\n renderComponentAsync,\n createTestRenderer,\n shallowRender,\n TestRenderer,\n TestRendererResult\n} from './test-renderer.js';\n\n// Export test utilities\nexport {\n fireEvent,\n waitFor,\n waitForElement,\n waitForElementToBeRemoved,\n act,\n createMock,\n createSpy,\n cleanup,\n within,\n screen,\n userEvent\n} from './test-utils.js';\n\n// Export matchers\nexport {\n customMatchers,\n extendExpect,\n assertions\n} from './matchers.js';\n\n// Re-import for default export\nimport {\n renderComponent as _renderComponent,\n renderComponentAsync as _renderComponentAsync,\n createTestRenderer as _createTestRenderer,\n shallowRender as _shallowRender\n} from './test-renderer.js';\n\nimport {\n fireEvent as _fireEvent,\n waitFor as _waitFor,\n waitForElement as _waitForElement,\n waitForElementToBeRemoved as _waitForElementToBeRemoved,\n act as _act,\n createMock as _createMock,\n createSpy as _createSpy,\n cleanup as _cleanup,\n within as _within,\n screen as _screen,\n userEvent as _userEvent\n} from './test-utils.js';\n\nimport {\n customMatchers as _customMatchers,\n extendExpect as _extendExpect,\n assertions as _assertions\n} from './matchers.js';\n\n// Default export with all utilities\nexport default {\n // Renderer\n renderComponent: _renderComponent,\n renderComponentAsync: _renderComponentAsync,\n createTestRenderer: _createTestRenderer,\n shallowRender: _shallowRender,\n\n // Utilities\n fireEvent: _fireEvent,\n waitFor: _waitFor,\n waitForElement: _waitForElement,\n waitForElementToBeRemoved: _waitForElementToBeRemoved,\n act: _act,\n createMock: _createMock,\n createSpy: _createSpy,\n cleanup: _cleanup,\n within: _within,\n screen: _screen,\n userEvent: _userEvent,\n\n // Matchers\n customMatchers: _customMatchers,\n extendExpect: _extendExpect,\n assertions: _assertions\n};\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Coherent.js Test Renderer\n * \n * Provides utilities for rendering and testing Coherent.js components\n * in a test environment.\n * \n * @module testing/test-renderer\n */\n\nimport { render } from '@coherent.js/core';\n\n/** Escape a string for literal use inside a RegExp. */\nfunction escapeRegExp(text) {\n return String(text).replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Test renderer result\n * Provides methods to query and interact with rendered components\n */\nexport class TestRendererResult {\n constructor(component, html, container = null) {\n this.component = component;\n this.html = html;\n this.container = container;\n this.queries = new Map();\n }\n\n /**\n * Get element by test ID\n * @param {string} testId - Test ID to search for\n * @returns {Object|null} Element or null\n */\n getByTestId(testId) {\n // Capture from the start of the opening tag, so the match's `html`\n // carries the element's tag name and all of its attributes.\n const regex = new RegExp(`<[a-zA-Z][\\\\w:-]*(?:\\\\s[^>]*?)?\\\\sdata-testid=\"${escapeRegExp(testId)}\"[^>]*>([^<]*)<`, 'i');\n const match = this.html.match(regex);\n \n if (!match) {\n throw new Error(`Unable to find element with testId: ${testId}`);\n }\n \n return {\n text: match[1],\n html: match[0],\n testId,\n exists: true\n };\n }\n\n /**\n * Query element by test ID (returns null if not found)\n * @param {string} testId - Test ID to search for\n * @returns {Object|null} Element or null\n */\n queryByTestId(testId) {\n try {\n return this.getByTestId(testId);\n } catch {\n return null;\n }\n }\n\n /**\n * Get element by text content\n * @param {string|RegExp} text - Text to search for\n * @returns {Object} Element\n */\n getByText(text) {\n const regex = typeof text === 'string' \n ? new RegExp(`>([^<]*${escapeRegExp(text)}[^<]*)<`, 'i')\n : new RegExp(`>([^<]*)<`, 'i');\n \n const match = this.html.match(regex);\n \n if (!match || (typeof text === 'string' && !match[1].includes(text))) {\n throw new Error(`Unable to find element with text: ${text}`);\n }\n \n return {\n text: match[1],\n html: match[0],\n exists: true\n };\n }\n\n /**\n * Query element by text (returns null if not found)\n * @param {string|RegExp} text - Text to search for\n * @returns {Object|null} Element or null\n */\n queryByText(text) {\n try {\n return this.getByText(text);\n } catch {\n return null;\n }\n }\n\n /**\n * Get element by class name\n * @param {string} className - Class name to search for\n * @returns {Object} Element\n */\n getByClassName(className) {\n // Whole class tokens: 'btn' matches class=\"btn primary\", not \"btn-primary\".\n const token = escapeRegExp(className);\n const regex = new RegExp(`<[a-zA-Z][\\\\w:-]*(?:\\\\s[^>]*?)?\\\\sclass=\"(?:[^\"]*\\\\s)?${token}(?:\\\\s[^\"]*)?\"[^>]*>([^<]*)<`, 'i');\n const match = this.html.match(regex);\n \n if (!match) {\n throw new Error(`Unable to find element with className: ${className}`);\n }\n \n return {\n text: match[1],\n html: match[0],\n className,\n exists: true\n };\n }\n\n /**\n * Query element by class name (returns null if not found)\n * @param {string} className - Class name to search for\n * @returns {Object|null} Element or null\n */\n queryByClassName(className) {\n try {\n return this.getByClassName(className);\n } catch {\n return null;\n }\n }\n\n /**\n * Get all elements by tag name\n * @param {string} tagName - Tag name to search for\n * @returns {Array<Object>} Array of elements\n */\n getAllByTagName(tagName) {\n const tag = escapeRegExp(tagName);\n const regex = new RegExp(`<${tag}(?=[\\\\s/>])[^>]*>([^<]*)</${tag}>`, 'gi');\n const matches = [...this.html.matchAll(regex)];\n \n return matches.map(match => ({\n text: match[1],\n html: match[0],\n tagName,\n exists: true\n }));\n }\n\n /**\n * Check if element exists\n * @param {string} selector - Selector (testId, text, className)\n * @param {string} type - Type of selector ('testId', 'text', 'className')\n * @returns {boolean} True if exists\n */\n exists(selector, type = 'testId') {\n switch (type) {\n case 'testId':\n return this.queryByTestId(selector) !== null;\n case 'text':\n return this.queryByText(selector) !== null;\n case 'className':\n return this.queryByClassName(selector) !== null;\n default:\n return false;\n }\n }\n\n /**\n * Get the rendered HTML\n * @returns {string} HTML string\n */\n getHTML() {\n return this.html;\n }\n\n /**\n * Get the component\n * @returns {Object} Component object\n */\n getComponent() {\n return this.component;\n }\n\n /**\n * Create a snapshot of the rendered output\n * @returns {string} Formatted HTML for snapshot testing\n */\n toSnapshot() {\n return this.html\n .replace(/>\\s+</g, '><') // Remove whitespace between tags\n .trim();\n }\n\n /**\n * Debug: print the rendered HTML\n */\n debug() {\n console.log('=== Rendered HTML ===');\n console.log(this.html);\n console.log('=== Component ===');\n console.log(JSON.stringify(this.component, null, 2));\n }\n}\n\n/**\n * Render a component for testing\n * \n * @param {Object} component - Component to render\n * @param {Object} [options] - Render options\n * @returns {TestRendererResult} Test renderer result\n * \n * @example\n * const { getByTestId } = renderComponent({\n * div: {\n * 'data-testid': 'my-div',\n * text: 'Hello World'\n * }\n * });\n * \n * expect(getByTestId('my-div').text).toBe('Hello World');\n */\nexport function renderComponent(component, options = {}) {\n const html = render(component, options);\n return new TestRendererResult(component, html);\n}\n\n/**\n * Render a component asynchronously\n * \n * @param {Object|Function} component - Component or component factory\n * @param {Object} [props] - Component props\n * @param {Object} [options] - Render options\n * @returns {Promise<TestRendererResult>} Test renderer result\n */\nexport async function renderComponentAsync(component, props = {}, options = {}) {\n // If component is a function, call it with props\n const resolvedComponent = typeof component === 'function' \n ? await component(props)\n : component;\n \n const html = render(resolvedComponent, options);\n return new TestRendererResult(resolvedComponent, html);\n}\n\n/**\n * Create a test renderer instance\n * Useful for testing component updates\n */\nexport class TestRenderer {\n constructor(component, options = {}) {\n this.component = component;\n this.options = options;\n this.result = null;\n this.renderCount = 0;\n }\n\n /**\n * Render the component\n * @returns {TestRendererResult} Render result\n */\n render() {\n this.renderCount++;\n const html = render(this.component, this.options);\n this.result = new TestRendererResult(this.component, html);\n return this.result;\n }\n\n /**\n * Update the component and re-render\n * @param {Object} newComponent - Updated component\n * @returns {TestRendererResult} Render result\n */\n update(newComponent) {\n this.component = newComponent;\n return this.render();\n }\n\n /**\n * Get the current result\n * @returns {TestRendererResult|null} Current result\n */\n getResult() {\n return this.result;\n }\n\n /**\n * Get render count\n * @returns {number} Number of renders\n */\n getRenderCount() {\n return this.renderCount;\n }\n\n /**\n * Unmount the component\n */\n unmount() {\n this.component = null;\n this.result = null;\n }\n}\n\n/**\n * Create a test renderer\n * \n * @param {Object} component - Component to render\n * @param {Object} [options] - Render options\n * @returns {TestRenderer} Test renderer instance\n * \n * @example\n * const renderer = createTestRenderer(MyComponent);\n * const result = renderer.render();\n * expect(result.getByText('Hello')).toBeTruthy();\n * \n * // Update and re-render\n * renderer.update(UpdatedComponent);\n * expect(renderer.getRenderCount()).toBe(2);\n */\nexport function createTestRenderer(component, options = {}) {\n return new TestRenderer(component, options);\n}\n\n/**\n * Shallow render a component (only render top level)\n * \n * @param {Object} component - Component to render\n * @returns {Object} Shallow rendered component\n */\nexport function shallowRender(component) {\n // Clone component without rendering children\n const shallow = { ...component };\n \n Object.keys(shallow).forEach(key => {\n if (shallow[key] && typeof shallow[key] === 'object') {\n if (shallow[key].children) {\n shallow[key] = {\n ...shallow[key],\n children: Array.isArray(shallow[key].children)\n ? shallow[key].children.map(() => ({ _shallow: true }))\n : { _shallow: true }\n };\n }\n }\n });\n \n return shallow;\n}\n\n/**\n * Export all testing utilities\n */\nexport default {\n renderComponent,\n renderComponentAsync,\n createTestRenderer,\n shallowRender,\n TestRenderer,\n TestRendererResult\n};\n", "/**\n * Coherent.js Test Utilities\n * \n * Helper functions for testing Coherent.js components\n * \n * @module testing/test-utils\n */\n\n/**\n * Simulate an event on an element\n * \n * @param {Object} element - Element to fire event on\n * @param {string} eventType - Type of event (click, change, etc.)\n * @param {Object} [eventData] - Additional event data\n */\nexport function fireEvent(element, eventType, eventData = {}) {\n if (!element) {\n throw new Error('Element is required for fireEvent');\n }\n \n // In a test environment, we simulate the event\n const event = {\n type: eventType,\n target: element,\n currentTarget: element,\n preventDefault: () => {},\n stopPropagation: () => {},\n ...eventData\n };\n \n // If element has an event handler, call it\n const handlerName = `on${eventType}`;\n if (element[handlerName] && typeof element[handlerName] === 'function') {\n element[handlerName](event);\n }\n \n return event;\n}\n\n/**\n * Common event helpers\n */\nexport const fireEvent_click = (element, eventData) => \n fireEvent(element, 'click', eventData);\n\nexport const fireEvent_change = (element, value) => \n fireEvent(element, 'change', { target: { value } });\n\nexport const fireEvent_input = (element, value) => \n fireEvent(element, 'input', { target: { value } });\n\nexport const fireEvent_submit = (element, eventData) => \n fireEvent(element, 'submit', eventData);\n\nexport const fireEvent_keyDown = (element, key) => \n fireEvent(element, 'keydown', { key });\n\nexport const fireEvent_keyUp = (element, key) => \n fireEvent(element, 'keyup', { key });\n\nexport const fireEvent_focus = (element) => \n fireEvent(element, 'focus');\n\nexport const fireEvent_blur = (element) => \n fireEvent(element, 'blur');\n\n/**\n * Wait for a condition to be true\n * \n * @param {Function} condition - Condition function\n * @param {Object} [options] - Wait options\n * @param {number} [options.timeout=1000] - Timeout in ms\n * @param {number} [options.interval=50] - Check interval in ms\n * @returns {Promise<void>}\n * \n * @example\n * await waitFor(() => getByText('Loaded').exists, { timeout: 2000 });\n */\nexport function waitFor(condition, options = {}) {\n const { timeout = 1000, interval = 50 } = options;\n \n return new Promise((resolve, reject) => {\n const startTime = Date.now();\n \n const check = () => {\n try {\n if (condition()) {\n resolve();\n return;\n }\n } catch {\n // Condition threw an error, keep waiting\n }\n \n if (Date.now() - startTime >= timeout) {\n reject(new Error(`Timeout waiting for condition after ${timeout}ms`));\n return;\n }\n \n setTimeout(check, interval);\n };\n \n check();\n });\n}\n\n/**\n * Wait for element to appear\n * \n * @param {Function} queryFn - Query function that returns element\n * @param {Object} [options] - Wait options\n * @returns {Promise<Object>} Element\n */\nexport async function waitForElement(queryFn, options = {}) {\n let element = null;\n \n await waitFor(() => {\n element = queryFn();\n return element !== null;\n }, options);\n \n return element;\n}\n\n/**\n * Wait for element to disappear\n * \n * @param {Function} queryFn - Query function that returns element\n * @param {Object} [options] - Wait options\n * @returns {Promise<void>}\n */\nexport async function waitForElementToBeRemoved(queryFn, options = {}) {\n await waitFor(() => {\n const element = queryFn();\n return element === null;\n }, options);\n}\n\n/**\n * Act utility for batching updates\n * Useful for testing state changes\n * \n * @param {Function} callback - Callback to execute\n * @returns {Promise<void>}\n */\nexport async function act(callback) {\n await callback();\n // Allow any pending updates to flush\n await new Promise(resolve => setTimeout(resolve, 0));\n}\n\n/**\n * Create a mock function\n * \n * @param {Function} [implementation] - Optional implementation\n * @returns {Function} Mock function\n */\nexport function createMock(implementation) {\n const calls = [];\n const results = [];\n \n const mockFn = function(...args) {\n calls.push(args);\n \n let result;\n let error;\n \n try {\n result = implementation ? implementation(...args) : undefined;\n results.push({ type: 'return', value: result });\n } catch (err) {\n error = err;\n results.push({ type: 'throw', value: error });\n throw error;\n }\n \n return result;\n };\n \n // Add mock utilities\n mockFn.mock = {\n calls,\n results,\n instances: []\n };\n \n mockFn.mockClear = () => {\n calls.length = 0;\n results.length = 0;\n };\n \n mockFn.mockReset = () => {\n mockFn.mockClear();\n implementation = undefined;\n };\n \n mockFn.mockImplementation = (fn) => {\n implementation = fn;\n return mockFn;\n };\n \n mockFn.mockReturnValue = (value) => {\n implementation = () => value;\n return mockFn;\n };\n \n mockFn.mockResolvedValue = (value) => {\n implementation = () => Promise.resolve(value);\n return mockFn;\n };\n \n mockFn.mockRejectedValue = (error) => {\n implementation = () => Promise.reject(error);\n return mockFn;\n };\n\n // Mark it the way Vitest and Jest recognise mocks, so their built-in\n // toHaveBeenCalled / toHaveBeenCalledWith / toHaveBeenCalledTimes\n // (deep-equality argument matching) work on it.\n Object.defineProperty(mockFn, '_isMockFunction', { value: true });\n mockFn.getMockName = () => 'createMock()';\n\n return mockFn;\n}\n\n/**\n * Create a spy on an object method\n * \n * @param {Object} object - Object to spy on\n * @param {string} method - Method name\n * @returns {Function} Spy function\n */\nexport function createSpy(object, method) {\n const original = object[method];\n const spy = createMock(original.bind(object));\n \n object[method] = spy;\n \n spy.mockRestore = () => {\n object[method] = original;\n };\n \n return spy;\n}\n\n/**\n * Cleanup utility\n * Cleans up after tests\n */\nexport function cleanup() {\n // Clear any timers\n // Reset any global state\n // This would be expanded based on framework needs\n}\n\n/**\n * Within utility - scopes queries to a container\n * \n * @param {Object} container - Container result\n * @returns {Object} Scoped queries\n */\nexport function within(container) {\n return {\n getByTestId: (testId) => container.getByTestId(testId),\n queryByTestId: (testId) => container.queryByTestId(testId),\n getByText: (text) => container.getByText(text),\n queryByText: (text) => container.queryByText(text),\n getByClassName: (className) => container.getByClassName(className),\n queryByClassName: (className) => container.queryByClassName(className)\n };\n}\n\n/**\n * Screen utility - global queries\n * Useful for accessing rendered content without storing result\n */\nexport const screen = {\n _result: null,\n \n setResult(result) {\n this._result = result;\n },\n \n getByTestId(testId) {\n if (!this._result) throw new Error('No component rendered');\n return this._result.getByTestId(testId);\n },\n \n queryByTestId(testId) {\n if (!this._result) return null;\n return this._result.queryByTestId(testId);\n },\n \n getByText(text) {\n if (!this._result) throw new Error('No component rendered');\n return this._result.getByText(text);\n },\n \n queryByText(text) {\n if (!this._result) return null;\n return this._result.queryByText(text);\n },\n \n getByClassName(className) {\n if (!this._result) throw new Error('No component rendered');\n return this._result.getByClassName(className);\n },\n \n queryByClassName(className) {\n if (!this._result) return null;\n return this._result.queryByClassName(className);\n },\n \n debug() {\n if (this._result) {\n this._result.debug();\n }\n }\n};\n\n/**\n * User event simulation\n * More realistic event simulation than fireEvent\n */\nexport const userEvent = {\n /**\n * Simulate user typing\n */\n type: async (element, text, options = {}) => {\n const { delay = 0 } = options;\n \n for (const char of text) {\n fireEvent_keyDown(element, char);\n fireEvent_input(element, element.value + char);\n fireEvent_keyUp(element, char);\n \n if (delay > 0) {\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n },\n \n /**\n * Simulate user click\n */\n click: async (element) => {\n fireEvent_focus(element);\n fireEvent_click(element);\n },\n \n /**\n * Simulate user double click\n */\n dblClick: async (element) => {\n await userEvent.click(element);\n await userEvent.click(element);\n },\n \n /**\n * Simulate user clearing input\n */\n clear: async (element) => {\n fireEvent_input(element, '');\n fireEvent_change(element, '');\n },\n \n /**\n * Simulate user selecting option\n */\n selectOptions: async (element, values) => {\n const valueArray = Array.isArray(values) ? values : [values];\n fireEvent_change(element, valueArray[0]);\n },\n \n /**\n * Simulate user tab navigation\n */\n tab: async () => {\n // Simulate tab key\n const activeElement = document.activeElement;\n if (activeElement) {\n fireEvent_keyDown(activeElement, 'Tab');\n fireEvent_blur(activeElement);\n }\n }\n};\n\n/**\n * Export all utilities\n */\nexport default {\n fireEvent,\n waitFor,\n waitForElement,\n waitForElementToBeRemoved,\n act,\n createMock,\n createSpy,\n cleanup,\n within,\n screen,\n userEvent\n};\n", "/**\n * Coherent.js Custom Test Matchers\n *\n * Custom matchers for testing Coherent.js components\n * Compatible with Vitest, Jest, and other testing frameworks\n *\n * The matchers accept what this package's own helpers return: a render\n * result from `renderComponent()` (`{ html }`), a match from its query\n * helpers (`{ html, text, exists }`), or a plain HTML string. Element\n * matchers (`toHaveClass`, `toHaveAttribute`, `toHaveTagName`) look at the\n * first element in that HTML.\n *\n * None of them reuses the name of a matcher Vitest or Jest already ships:\n * `expect.extend()` would replace the built-in for the whole test run.\n * Snapshot with `expect(result.toSnapshot()).toMatchSnapshot()`, and assert\n * on mocks (`vi.fn()` or this package's `createMock()`) with the built-in\n * `toHaveBeenCalled*` matchers.\n *\n * @module testing/matchers\n */\n\n/** Elements that never have a closing tag. */\nconst VOID_ELEMENTS = new Set([\n 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen',\n 'link', 'meta', 'param', 'source', 'track', 'wbr'\n]);\n\n/** Elements whose content is raw text, not markup. */\nconst RAW_TEXT_ELEMENTS = new Set(['script', 'style', 'textarea', 'title']);\n\nconst NAMED_ENTITIES = { amp: '&', lt: '<', gt: '>', quot: '\"', apos: \"'\", nbsp: '\u00A0' };\n\nfunction decodeEntities(text) {\n return text.replace(/&(#x[0-9a-f]+|#\\d+|[a-z]+);/gi, (entity, body) => {\n if (body[0] === '#') {\n const codePoint = body[1] === 'x' || body[1] === 'X'\n ? Number.parseInt(body.slice(2), 16)\n : Number.parseInt(body.slice(1), 10);\n return codePoint <= 0x10ffff ? String.fromCodePoint(codePoint) : entity;\n }\n return NAMED_ENTITIES[body.toLowerCase()] ?? entity;\n });\n}\n\n/** The HTML string behind a render result, a query match, or a string. */\nfunction htmlOf(received) {\n if (typeof received === 'string') return received;\n if (received && typeof received.html === 'string') return received.html;\n return null;\n}\n\n/**\n * Text content: a query match's `text`, or the HTML with its tags removed.\n * Entities are decoded, so assertions use the text a reader sees.\n */\nfunction textOf(received) {\n if (received && typeof received === 'object' && typeof received.text === 'string') {\n return decodeEntities(received.text);\n }\n const html = htmlOf(received);\n if (html === null) return null;\n return decodeEntities(stripTags(html));\n}\n\n/**\n * Remove tags (`<...>`) in one linear pass; a `<` with no `>` after it stays.\n * /<[^>]*>/g rescans the rest of the input from every `<` when no `>`\n * follows: seconds on '<<<<\u2026' of 50 KB.\n */\nfunction stripTags(html) {\n let out = '';\n let cursor = 0;\n while (cursor < html.length) {\n const open = html.indexOf('<', cursor);\n if (open === -1) break;\n const close = html.indexOf('>', open + 1);\n if (close === -1) break;\n out += html.slice(cursor, open);\n cursor = close + 1;\n }\n return out + html.slice(cursor);\n}\n\nconst isSpace = (ch) => ch === ' ' || ch === '\\n' || ch === '\\t' || ch === '\\r' || ch === '\\f';\n\n/**\n * Parse the first opening tag in `html` into its name and attributes.\n * Hand-rolled (no backtracking regex), so hostile input stays linear.\n *\n * @returns {{ tagName: string, attributes: Map<string, string> } | null}\n */\nfunction parseOpeningTag(html) {\n const start = html.search(/<[a-zA-Z]/);\n if (start === -1) return null;\n\n let i = start + 1;\n let tagName = '';\n while (i < html.length && /[\\w:-]/.test(html[i])) tagName += html[i++];\n\n const attributes = new Map();\n while (i < html.length) {\n while (i < html.length && isSpace(html[i])) i++;\n if (i >= html.length || html[i] === '>') break;\n if (html[i] === '/') {\n i++;\n continue;\n }\n\n let name = '';\n while (i < html.length && !isSpace(html[i]) && html[i] !== '=' && html[i] !== '>' && html[i] !== '/') {\n name += html[i++];\n }\n while (i < html.length && isSpace(html[i])) i++;\n\n let value = '';\n if (html[i] === '=') {\n i++;\n while (i < html.length && isSpace(html[i])) i++;\n const quote = html[i];\n if (quote === '\"' || quote === \"'\") {\n const end = html.indexOf(quote, i + 1);\n if (end === -1) break;\n value = html.slice(i + 1, end);\n i = end + 1;\n } else {\n while (i < html.length && !isSpace(html[i]) && html[i] !== '>') value += html[i++];\n }\n }\n if (name) attributes.set(name.toLowerCase(), decodeEntities(value));\n }\n\n return { tagName: tagName.toLowerCase(), attributes };\n}\n\n/** Class tokens of the first element (or a match's `className`), or null. */\nfunction classesOf(received) {\n const html = htmlOf(received);\n const tag = html === null ? null : parseOpeningTag(html);\n let value = tag?.attributes.get('class');\n if (value === undefined && typeof received?.className === 'string') value = received.className;\n return value === undefined ? null : value.split(/\\s+/).filter(Boolean);\n}\n\n/**\n * Check tag balance with a stack, skipping comments, doctypes, void\n * elements and raw-text content. Linear: every '<' is visited once.\n *\n * @returns {string|null} Why the HTML is invalid, or null when it is valid.\n */\nfunction findHTMLError(html) {\n const stack = [];\n let lower = null;\n let i = 0;\n\n while ((i = html.indexOf('<', i)) !== -1) {\n if (html.startsWith('<!--', i)) {\n const end = html.indexOf('-->', i + 4);\n if (end === -1) return 'unterminated comment';\n i = end + 3;\n continue;\n }\n\n const close = html.indexOf('>', i);\n if (close === -1) return 'unterminated tag';\n const tag = html.slice(i + 1, close);\n i = close + 1;\n\n if (tag[0] === '!' || tag[0] === '?') continue; // <!DOCTYPE \u2026>, <?xml \u2026?>\n const match = /^(\\/?)([a-zA-Z][\\w:-]*)/.exec(tag);\n if (!match) continue;\n const name = match[2].toLowerCase();\n\n if (match[1]) {\n if (VOID_ELEMENTS.has(name)) return `</${name}> closes a void element`;\n const open = stack.pop();\n if (open !== name) return open ? `</${name}> does not close <${open}>` : `</${name}> has no opening tag`;\n } else if (!VOID_ELEMENTS.has(name) && !tag.endsWith('/')) {\n stack.push(name);\n if (RAW_TEXT_ELEMENTS.has(name)) {\n lower ??= html.toLowerCase();\n const end = lower.indexOf(`</${name}`, i);\n if (end === -1) return `<${name}> is never closed`;\n i = end;\n }\n }\n }\n\n return stack.length > 0 ? `<${stack[stack.length - 1]}> is never closed` : null;\n}\n\nconst show = (value) => (value === null || value === undefined ? 'nothing' : JSON.stringify(value));\n\n/**\n * Custom matchers for Coherent.js testing\n */\nexport const customMatchers = {\n /**\n * Check if element (or a render result) has exactly this text content\n */\n toHaveText(received, expected) {\n const text = textOf(received);\n const pass = text === expected;\n\n return {\n pass,\n message: () => pass\n ? `Expected element not to have text \"${expected}\"`\n : `Expected element to have text \"${expected}\", but got ${show(text)}`\n };\n },\n\n /**\n * Check if element (or a render result) contains text\n */\n toContainText(received, expected) {\n const text = textOf(received);\n const pass = typeof text === 'string' && text.includes(expected);\n\n return {\n pass,\n message: () => pass\n ? `Expected element not to contain text \"${expected}\"`\n : `Expected element to contain text \"${expected}\", but got ${show(text)}`\n };\n },\n\n /**\n * Check if the element has every given class (whole class tokens:\n * 'btn' does not match 'btn-primary')\n */\n toHaveClass(received, expected) {\n const classes = classesOf(received) ?? [];\n const wanted = String(expected).split(/\\s+/).filter(Boolean);\n const pass = wanted.length > 0 && wanted.every((name) => classes.includes(name));\n\n return {\n pass,\n message: () => pass\n ? `Expected element not to have class \"${expected}\"`\n : `Expected element to have class \"${expected}\", but its classes are ${show(classes.join(' '))}`\n };\n },\n\n /**\n * Check if element exists\n */\n toBeInTheDocument(received) {\n const pass = Boolean(received && received.exists === true);\n\n return {\n pass,\n message: () => pass\n ? 'Expected element not to be in the document'\n : 'Expected element to be in the document'\n };\n },\n\n /**\n * Check if element is visible (has text content)\n */\n toBeVisible(received) {\n const text = textOf(received);\n const pass = typeof text === 'string' && text.trim().length > 0;\n\n return {\n pass,\n message: () => pass\n ? 'Expected element not to be visible'\n : 'Expected element to be visible (have text content)'\n };\n },\n\n /**\n * Check if element is empty (no text content)\n */\n toBeEmpty(received) {\n const text = textOf(received);\n const pass = !text || text.trim().length === 0;\n\n return {\n pass,\n message: () => pass\n ? 'Expected element not to be empty'\n : `Expected element to be empty, but it has text ${show(text)}`\n };\n },\n\n /**\n * Check if HTML contains specific string\n */\n toContainHTML(received, expected) {\n const html = htmlOf(received);\n const pass = typeof html === 'string' && html.includes(expected);\n\n return {\n pass,\n message: () => pass\n ? `Expected HTML not to contain \"${expected}\"`\n : `Expected HTML to contain \"${expected}\"`\n };\n },\n\n /**\n * Check if the element has an attribute (optionally with this value)\n */\n toHaveAttribute(received, attribute, value) {\n const html = htmlOf(received);\n const tag = html === null ? null : parseOpeningTag(html);\n const name = String(attribute).toLowerCase();\n const has = Boolean(tag?.attributes.has(name));\n const actual = has ? tag.attributes.get(name) : undefined;\n const pass = value !== undefined ? has && actual === String(value) : has;\n\n return {\n pass,\n message: () => {\n if (value !== undefined) {\n return pass\n ? `Expected element not to have attribute ${attribute}=\"${value}\"`\n : `Expected element to have attribute ${attribute}=\"${value}\", but got ${has ? show(actual) : 'none'}`;\n }\n return pass\n ? `Expected element not to have attribute ${attribute}`\n : `Expected element to have attribute ${attribute}`;\n }\n };\n },\n\n /**\n * Check if the element has this tag name\n */\n toHaveTagName(received, tagName) {\n const html = htmlOf(received);\n const tag = html === null ? null : parseOpeningTag(html);\n const pass = Boolean(tag) && tag.tagName === String(tagName).toLowerCase();\n\n return {\n pass,\n message: () => pass\n ? `Expected element not to have tag name \"${tagName}\"`\n : `Expected element to have tag name \"${tagName}\", but got ${show(tag?.tagName)}`\n };\n },\n\n /**\n * Check if render result contains element\n */\n toContainElement(received, element) {\n const html = htmlOf(received);\n const elementHtml = htmlOf(element);\n const pass = typeof html === 'string' && typeof elementHtml === 'string' && html.includes(elementHtml);\n\n return {\n pass,\n message: () => pass\n ? 'Expected not to contain element'\n : 'Expected to contain element'\n };\n },\n\n /**\n * Check if component rendered successfully\n */\n toRenderSuccessfully(received) {\n const html = htmlOf(received);\n const pass = typeof html === 'string' && html.length > 0;\n\n return {\n pass,\n message: () => pass\n ? 'Expected component not to render successfully'\n : 'Expected component to render successfully'\n };\n },\n\n /**\n * Check that every tag is closed in order. Void elements (<input>, <br>,\n * <img>, \u2026) need no closing tag.\n */\n toBeValidHTML(received) {\n const html = htmlOf(received);\n const error = typeof html === 'string' ? findHTMLError(html) : 'received no HTML';\n const pass = error === null;\n\n return {\n pass,\n message: () => pass\n ? 'Expected HTML not to be valid'\n : `Expected HTML to be valid, but ${error}`\n };\n }\n};\n\n/**\n * Extend expect with custom matchers\n *\n * @param {Object} expect - Expect function from testing framework\n *\n * @example\n * import { expect } from 'vitest';\n * import { extendExpect } from '@coherent.js/tooling/testing/matchers';\n *\n * extendExpect(expect);\n *\n * // Now you can use custom matchers\n * expect(element).toHaveText('Hello');\n */\nexport function extendExpect(expect) {\n if (expect && expect.extend) {\n expect.extend(customMatchers);\n } else {\n console.warn('Could not extend expect - expect.extend not available');\n }\n}\n\n/**\n * Create assertion helpers\n */\nexport const assertions = {\n /**\n * Assert element has text\n */\n assertHasText(element, text) {\n const actual = textOf(element);\n if (actual !== text) {\n throw new Error(`Expected element to have text \"${text}\", but got ${show(actual)}`);\n }\n },\n\n /**\n * Assert element exists\n */\n assertExists(element) {\n if (!element || !element.exists) {\n throw new Error('Expected element to exist');\n }\n },\n\n /**\n * Assert element has class (a whole class token)\n */\n assertHasClass(element, className) {\n if (!(classesOf(element) ?? []).includes(className)) {\n throw new Error(`Expected element to have class \"${className}\"`);\n }\n },\n\n /**\n * Assert HTML contains string\n */\n assertContainsHTML(html, substring) {\n const htmlString = htmlOf(html);\n if (!htmlString || !htmlString.includes(substring)) {\n throw new Error(`Expected HTML to contain \"${substring}\"`);\n }\n },\n\n /**\n * Assert component rendered\n */\n assertRendered(result) {\n if (!result || !result.html || result.html.length === 0) {\n throw new Error('Expected component to render');\n }\n }\n};\n\n/**\n * Export all matchers and utilities\n */\nexport default {\n customMatchers,\n extendExpect,\n assertions\n};\n", "/**\n * Coherent.js Testing Utilities\n * \n * Complete testing solution for Coherent.js applications\n * \n * @module testing\n */\n\n// Export test renderer\nexport {\n renderComponent,\n renderComponentAsync,\n createTestRenderer,\n shallowRender,\n TestRenderer,\n TestRendererResult\n} from './test-renderer.js';\n\n// Export test utilities\nexport {\n fireEvent,\n waitFor,\n waitForElement,\n waitForElementToBeRemoved,\n act,\n createMock,\n createSpy,\n cleanup,\n within,\n screen,\n userEvent\n} from './test-utils.js';\n\n// Export matchers\nexport {\n customMatchers,\n extendExpect,\n assertions\n} from './matchers.js';\n\n// Re-import for default export\nimport {\n renderComponent as _renderComponent,\n renderComponentAsync as _renderComponentAsync,\n createTestRenderer as _createTestRenderer,\n shallowRender as _shallowRender\n} from './test-renderer.js';\n\nimport {\n fireEvent as _fireEvent,\n waitFor as _waitFor,\n waitForElement as _waitForElement,\n waitForElementToBeRemoved as _waitForElementToBeRemoved,\n act as _act,\n createMock as _createMock,\n createSpy as _createSpy,\n cleanup as _cleanup,\n within as _within,\n screen as _screen,\n userEvent as _userEvent\n} from './test-utils.js';\n\nimport {\n customMatchers as _customMatchers,\n extendExpect as _extendExpect,\n assertions as _assertions\n} from './matchers.js';\n\n// Default export with all utilities\nexport default {\n // Renderer\n renderComponent: _renderComponent,\n renderComponentAsync: _renderComponentAsync,\n createTestRenderer: _createTestRenderer,\n shallowRender: _shallowRender,\n\n // Utilities\n fireEvent: _fireEvent,\n waitFor: _waitFor,\n waitForElement: _waitForElement,\n waitForElementToBeRemoved: _waitForElementToBeRemoved,\n act: _act,\n createMock: _createMock,\n createSpy: _createSpy,\n cleanup: _cleanup,\n within: _within,\n screen: _screen,\n userEvent: _userEvent,\n\n // Matchers\n customMatchers: _customMatchers,\n extendExpect: _extendExpect,\n assertions: _assertions\n};\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,70 +1,212 @@
1
1
  // src/testing/matchers.js
2
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
3
+ "area",
4
+ "base",
5
+ "br",
6
+ "col",
7
+ "embed",
8
+ "hr",
9
+ "img",
10
+ "input",
11
+ "keygen",
12
+ "link",
13
+ "meta",
14
+ "param",
15
+ "source",
16
+ "track",
17
+ "wbr"
18
+ ]);
19
+ var RAW_TEXT_ELEMENTS = /* @__PURE__ */ new Set(["script", "style", "textarea", "title"]);
20
+ var NAMED_ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: "\xA0" };
21
+ function decodeEntities(text) {
22
+ return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (entity, body) => {
23
+ if (body[0] === "#") {
24
+ const codePoint = body[1] === "x" || body[1] === "X" ? Number.parseInt(body.slice(2), 16) : Number.parseInt(body.slice(1), 10);
25
+ return codePoint <= 1114111 ? String.fromCodePoint(codePoint) : entity;
26
+ }
27
+ return NAMED_ENTITIES[body.toLowerCase()] ?? entity;
28
+ });
29
+ }
30
+ function htmlOf(received) {
31
+ if (typeof received === "string") return received;
32
+ if (received && typeof received.html === "string") return received.html;
33
+ return null;
34
+ }
35
+ function textOf(received) {
36
+ if (received && typeof received === "object" && typeof received.text === "string") {
37
+ return decodeEntities(received.text);
38
+ }
39
+ const html = htmlOf(received);
40
+ if (html === null) return null;
41
+ return decodeEntities(stripTags(html));
42
+ }
43
+ function stripTags(html) {
44
+ let out = "";
45
+ let cursor = 0;
46
+ while (cursor < html.length) {
47
+ const open = html.indexOf("<", cursor);
48
+ if (open === -1) break;
49
+ const close = html.indexOf(">", open + 1);
50
+ if (close === -1) break;
51
+ out += html.slice(cursor, open);
52
+ cursor = close + 1;
53
+ }
54
+ return out + html.slice(cursor);
55
+ }
56
+ var isSpace = (ch) => ch === " " || ch === "\n" || ch === " " || ch === "\r" || ch === "\f";
57
+ function parseOpeningTag(html) {
58
+ const start = html.search(/<[a-zA-Z]/);
59
+ if (start === -1) return null;
60
+ let i = start + 1;
61
+ let tagName = "";
62
+ while (i < html.length && /[\w:-]/.test(html[i])) tagName += html[i++];
63
+ const attributes = /* @__PURE__ */ new Map();
64
+ while (i < html.length) {
65
+ while (i < html.length && isSpace(html[i])) i++;
66
+ if (i >= html.length || html[i] === ">") break;
67
+ if (html[i] === "/") {
68
+ i++;
69
+ continue;
70
+ }
71
+ let name = "";
72
+ while (i < html.length && !isSpace(html[i]) && html[i] !== "=" && html[i] !== ">" && html[i] !== "/") {
73
+ name += html[i++];
74
+ }
75
+ while (i < html.length && isSpace(html[i])) i++;
76
+ let value = "";
77
+ if (html[i] === "=") {
78
+ i++;
79
+ while (i < html.length && isSpace(html[i])) i++;
80
+ const quote = html[i];
81
+ if (quote === '"' || quote === "'") {
82
+ const end = html.indexOf(quote, i + 1);
83
+ if (end === -1) break;
84
+ value = html.slice(i + 1, end);
85
+ i = end + 1;
86
+ } else {
87
+ while (i < html.length && !isSpace(html[i]) && html[i] !== ">") value += html[i++];
88
+ }
89
+ }
90
+ if (name) attributes.set(name.toLowerCase(), decodeEntities(value));
91
+ }
92
+ return { tagName: tagName.toLowerCase(), attributes };
93
+ }
94
+ function classesOf(received) {
95
+ const html = htmlOf(received);
96
+ const tag = html === null ? null : parseOpeningTag(html);
97
+ let value = tag?.attributes.get("class");
98
+ if (value === void 0 && typeof received?.className === "string") value = received.className;
99
+ return value === void 0 ? null : value.split(/\s+/).filter(Boolean);
100
+ }
101
+ function findHTMLError(html) {
102
+ const stack = [];
103
+ let lower = null;
104
+ let i = 0;
105
+ while ((i = html.indexOf("<", i)) !== -1) {
106
+ if (html.startsWith("<!--", i)) {
107
+ const end = html.indexOf("-->", i + 4);
108
+ if (end === -1) return "unterminated comment";
109
+ i = end + 3;
110
+ continue;
111
+ }
112
+ const close = html.indexOf(">", i);
113
+ if (close === -1) return "unterminated tag";
114
+ const tag = html.slice(i + 1, close);
115
+ i = close + 1;
116
+ if (tag[0] === "!" || tag[0] === "?") continue;
117
+ const match = /^(\/?)([a-zA-Z][\w:-]*)/.exec(tag);
118
+ if (!match) continue;
119
+ const name = match[2].toLowerCase();
120
+ if (match[1]) {
121
+ if (VOID_ELEMENTS.has(name)) return `</${name}> closes a void element`;
122
+ const open = stack.pop();
123
+ if (open !== name) return open ? `</${name}> does not close <${open}>` : `</${name}> has no opening tag`;
124
+ } else if (!VOID_ELEMENTS.has(name) && !tag.endsWith("/")) {
125
+ stack.push(name);
126
+ if (RAW_TEXT_ELEMENTS.has(name)) {
127
+ lower ??= html.toLowerCase();
128
+ const end = lower.indexOf(`</${name}`, i);
129
+ if (end === -1) return `<${name}> is never closed`;
130
+ i = end;
131
+ }
132
+ }
133
+ }
134
+ return stack.length > 0 ? `<${stack[stack.length - 1]}> is never closed` : null;
135
+ }
136
+ var show = (value) => value === null || value === void 0 ? "nothing" : JSON.stringify(value);
2
137
  var customMatchers = {
3
138
  /**
4
- * Check if element has specific text
139
+ * Check if element (or a render result) has exactly this text content
5
140
  */
6
141
  toHaveText(received, expected) {
7
- const pass = received && received.text === expected;
142
+ const text = textOf(received);
143
+ const pass = text === expected;
8
144
  return {
9
145
  pass,
10
- message: () => pass ? `Expected element not to have text "${expected}"` : `Expected element to have text "${expected}", but got "${received?.text || "null"}"`
146
+ message: () => pass ? `Expected element not to have text "${expected}"` : `Expected element to have text "${expected}", but got ${show(text)}`
11
147
  };
12
148
  },
13
149
  /**
14
- * Check if element contains text
150
+ * Check if element (or a render result) contains text
15
151
  */
16
152
  toContainText(received, expected) {
17
- const pass = received && received.text && received.text.includes(expected);
153
+ const text = textOf(received);
154
+ const pass = typeof text === "string" && text.includes(expected);
18
155
  return {
19
156
  pass,
20
- message: () => pass ? `Expected element not to contain text "${expected}"` : `Expected element to contain text "${expected}", but got "${received?.text || "null"}"`
157
+ message: () => pass ? `Expected element not to contain text "${expected}"` : `Expected element to contain text "${expected}", but got ${show(text)}`
21
158
  };
22
159
  },
23
160
  /**
24
- * Check if element has specific class
161
+ * Check if the element has every given class (whole class tokens:
162
+ * 'btn' does not match 'btn-primary')
25
163
  */
26
164
  toHaveClass(received, expected) {
27
- const pass = received && received.className && received.className.includes(expected);
165
+ const classes = classesOf(received) ?? [];
166
+ const wanted = String(expected).split(/\s+/).filter(Boolean);
167
+ const pass = wanted.length > 0 && wanted.every((name) => classes.includes(name));
28
168
  return {
29
169
  pass,
30
- message: () => pass ? `Expected element not to have class "${expected}"` : `Expected element to have class "${expected}", but got "${received?.className || "null"}"`
170
+ message: () => pass ? `Expected element not to have class "${expected}"` : `Expected element to have class "${expected}", but its classes are ${show(classes.join(" "))}`
31
171
  };
32
172
  },
33
173
  /**
34
174
  * Check if element exists
35
175
  */
36
176
  toBeInTheDocument(received) {
37
- const pass = received && received.exists === true;
177
+ const pass = Boolean(received && received.exists === true);
38
178
  return {
39
179
  pass,
40
180
  message: () => pass ? "Expected element not to be in the document" : "Expected element to be in the document"
41
181
  };
42
182
  },
43
183
  /**
44
- * Check if element is visible (has content)
184
+ * Check if element is visible (has text content)
45
185
  */
46
186
  toBeVisible(received) {
47
- const pass = received && received.text && received.text.trim().length > 0;
187
+ const text = textOf(received);
188
+ const pass = typeof text === "string" && text.trim().length > 0;
48
189
  return {
49
190
  pass,
50
191
  message: () => pass ? "Expected element not to be visible" : "Expected element to be visible (have text content)"
51
192
  };
52
193
  },
53
194
  /**
54
- * Check if element is empty
195
+ * Check if element is empty (no text content)
55
196
  */
56
197
  toBeEmpty(received) {
57
- const pass = !received || !received.text || received.text.trim().length === 0;
198
+ const text = textOf(received);
199
+ const pass = !text || text.trim().length === 0;
58
200
  return {
59
201
  pass,
60
- message: () => pass ? "Expected element not to be empty" : "Expected element to be empty"
202
+ message: () => pass ? "Expected element not to be empty" : `Expected element to be empty, but it has text ${show(text)}`
61
203
  };
62
204
  },
63
205
  /**
64
206
  * Check if HTML contains specific string
65
207
  */
66
208
  toContainHTML(received, expected) {
67
- const html = received?.html || received;
209
+ const html = htmlOf(received);
68
210
  const pass = typeof html === "string" && html.includes(expected);
69
211
  return {
70
212
  pass,
@@ -72,113 +214,71 @@ var customMatchers = {
72
214
  };
73
215
  },
74
216
  /**
75
- * Check if element has attribute
217
+ * Check if the element has an attribute (optionally with this value)
76
218
  */
77
219
  toHaveAttribute(received, attribute, value) {
78
- const html = received?.html || "";
79
- const regex = new RegExp(`${attribute}="([^"]*)"`, "i");
80
- const match = html.match(regex);
81
- const pass = value !== void 0 ? match && match[1] === value : match !== null;
220
+ const html = htmlOf(received);
221
+ const tag = html === null ? null : parseOpeningTag(html);
222
+ const name = String(attribute).toLowerCase();
223
+ const has = Boolean(tag?.attributes.has(name));
224
+ const actual = has ? tag.attributes.get(name) : void 0;
225
+ const pass = value !== void 0 ? has && actual === String(value) : has;
82
226
  return {
83
227
  pass,
84
228
  message: () => {
85
229
  if (value !== void 0) {
86
- return pass ? `Expected element not to have attribute ${attribute}="${value}"` : `Expected element to have attribute ${attribute}="${value}", but got "${match?.[1] || "none"}"`;
230
+ return pass ? `Expected element not to have attribute ${attribute}="${value}"` : `Expected element to have attribute ${attribute}="${value}", but got ${has ? show(actual) : "none"}`;
87
231
  }
88
232
  return pass ? `Expected element not to have attribute ${attribute}` : `Expected element to have attribute ${attribute}`;
89
233
  }
90
234
  };
91
235
  },
92
236
  /**
93
- * Check if component matches snapshot
94
- */
95
- toMatchSnapshot(received) {
96
- const _snapshot = received?.toSnapshot ? received.toSnapshot() : received;
97
- return {
98
- pass: true,
99
- message: () => "Snapshot comparison"
100
- };
101
- },
102
- /**
103
- * Check if element has specific tag name
237
+ * Check if the element has this tag name
104
238
  */
105
239
  toHaveTagName(received, tagName) {
106
- const html = received?.html || "";
107
- const regex = new RegExp(`<${tagName}[^>]*>`, "i");
108
- const pass = regex.test(html);
240
+ const html = htmlOf(received);
241
+ const tag = html === null ? null : parseOpeningTag(html);
242
+ const pass = Boolean(tag) && tag.tagName === String(tagName).toLowerCase();
109
243
  return {
110
244
  pass,
111
- message: () => pass ? `Expected element not to have tag name "${tagName}"` : `Expected element to have tag name "${tagName}"`
245
+ message: () => pass ? `Expected element not to have tag name "${tagName}"` : `Expected element to have tag name "${tagName}", but got ${show(tag?.tagName)}`
112
246
  };
113
247
  },
114
248
  /**
115
249
  * Check if render result contains element
116
250
  */
117
251
  toContainElement(received, element) {
118
- const html = received?.html || received;
119
- const elementHtml = element?.html || element;
120
- const pass = typeof html === "string" && html.includes(elementHtml);
252
+ const html = htmlOf(received);
253
+ const elementHtml = htmlOf(element);
254
+ const pass = typeof html === "string" && typeof elementHtml === "string" && html.includes(elementHtml);
121
255
  return {
122
256
  pass,
123
257
  message: () => pass ? "Expected not to contain element" : "Expected to contain element"
124
258
  };
125
259
  },
126
- /**
127
- * Check if mock was called
128
- */
129
- toHaveBeenCalled(received) {
130
- const pass = received?.mock?.calls?.length > 0;
131
- return {
132
- pass,
133
- message: () => pass ? "Expected mock not to have been called" : "Expected mock to have been called"
134
- };
135
- },
136
- /**
137
- * Check if mock was called with specific args
138
- */
139
- toHaveBeenCalledWith(received, ...expectedArgs) {
140
- const calls = received?.mock?.calls || [];
141
- const pass = calls.some(
142
- (call) => call.length === expectedArgs.length && call.every((arg, i) => arg === expectedArgs[i])
143
- );
144
- return {
145
- pass,
146
- message: () => pass ? `Expected mock not to have been called with ${JSON.stringify(expectedArgs)}` : `Expected mock to have been called with ${JSON.stringify(expectedArgs)}`
147
- };
148
- },
149
- /**
150
- * Check if mock was called N times
151
- */
152
- toHaveBeenCalledTimes(received, times) {
153
- const callCount = received?.mock?.calls?.length || 0;
154
- const pass = callCount === times;
155
- return {
156
- pass,
157
- message: () => pass ? `Expected mock not to have been called ${times} times` : `Expected mock to have been called ${times} times, but was called ${callCount} times`
158
- };
159
- },
160
260
  /**
161
261
  * Check if component rendered successfully
162
262
  */
163
263
  toRenderSuccessfully(received) {
164
- const pass = received && received.html && received.html.length > 0;
264
+ const html = htmlOf(received);
265
+ const pass = typeof html === "string" && html.length > 0;
165
266
  return {
166
267
  pass,
167
268
  message: () => pass ? "Expected component not to render successfully" : "Expected component to render successfully"
168
269
  };
169
270
  },
170
271
  /**
171
- * Check if HTML is valid
272
+ * Check that every tag is closed in order. Void elements (<input>, <br>,
273
+ * <img>, …) need no closing tag.
172
274
  */
173
275
  toBeValidHTML(received) {
174
- const html = received?.html || received;
175
- const openTags = (html.match(/<[^/<>][^<>]*>/g) || []).length;
176
- const closeTags = (html.match(/<\/[^<>]+>/g) || []).length;
177
- const selfClosing = (html.match(/<[^<>]+\/>/g) || []).length;
178
- const pass = openTags === closeTags + selfClosing;
276
+ const html = htmlOf(received);
277
+ const error = typeof html === "string" ? findHTMLError(html) : "received no HTML";
278
+ const pass = error === null;
179
279
  return {
180
280
  pass,
181
- message: () => pass ? "Expected HTML not to be valid" : `Expected HTML to be valid (open: ${openTags}, close: ${closeTags}, self-closing: ${selfClosing})`
281
+ message: () => pass ? "Expected HTML not to be valid" : `Expected HTML to be valid, but ${error}`
182
282
  };
183
283
  }
184
284
  };
@@ -194,8 +294,9 @@ var assertions = {
194
294
  * Assert element has text
195
295
  */
196
296
  assertHasText(element, text) {
197
- if (!element || element.text !== text) {
198
- throw new Error(`Expected element to have text "${text}", but got "${element?.text || "null"}"`);
297
+ const actual = textOf(element);
298
+ if (actual !== text) {
299
+ throw new Error(`Expected element to have text "${text}", but got ${show(actual)}`);
199
300
  }
200
301
  },
201
302
  /**
@@ -207,10 +308,10 @@ var assertions = {
207
308
  }
208
309
  },
209
310
  /**
210
- * Assert element has class
311
+ * Assert element has class (a whole class token)
211
312
  */
212
313
  assertHasClass(element, className) {
213
- if (!element || !element.className || !element.className.includes(className)) {
314
+ if (!(classesOf(element) ?? []).includes(className)) {
214
315
  throw new Error(`Expected element to have class "${className}"`);
215
316
  }
216
317
  },
@@ -218,7 +319,7 @@ var assertions = {
218
319
  * Assert HTML contains string
219
320
  */
220
321
  assertContainsHTML(html, substring) {
221
- const htmlString = html?.html || html;
322
+ const htmlString = htmlOf(html);
222
323
  if (!htmlString || !htmlString.includes(substring)) {
223
324
  throw new Error(`Expected HTML to contain "${substring}"`);
224
325
  }