@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.
- package/README.md +9 -1
- package/dist/lsp/bin.d.ts +11 -0
- package/dist/lsp/bin.d.ts.map +1 -0
- package/dist/lsp/bin.js +12 -0
- package/dist/lsp/bin.js.map +1 -0
- package/dist/lsp/data/element-attributes.d.ts.map +1 -1
- package/dist/lsp/data/element-attributes.generated.json +1 -1
- package/dist/lsp/data/element-attributes.js +24 -33
- package/dist/lsp/data/element-attributes.js.map +1 -1
- package/dist/lsp/data/generated-data.d.ts +15 -0
- package/dist/lsp/data/generated-data.d.ts.map +1 -0
- package/dist/lsp/data/generated-data.js +22 -0
- package/dist/lsp/data/generated-data.js.map +1 -0
- package/dist/lsp/server.d.ts +18 -4
- package/dist/lsp/server.d.ts.map +1 -1
- package/dist/lsp/server.js +73 -61
- package/dist/lsp/server.js.map +1 -1
- package/dist/testing/index.js +199 -91
- package/dist/testing/index.js.map +2 -2
- package/dist/testing/matchers.js +188 -87
- package/dist/testing/matchers.js.map +2 -2
- package/dist/testing/test-renderer.js +9 -4
- package/dist/testing/test-renderer.js.map +2 -2
- package/dist/testing/test-utils.js +2 -0
- package/dist/testing/test-utils.js.map +2 -2
- package/package.json +8 -7
- package/types/testing/index.d.ts +59 -75
|
@@ -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
|
}
|
package/dist/testing/matchers.js
CHANGED
|
@@ -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
|
|
139
|
+
* Check if element (or a render result) has exactly this text content
|
|
5
140
|
*/
|
|
6
141
|
toHaveText(received, expected) {
|
|
7
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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" :
|
|
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
|
|
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
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
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
|
|
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
|
|
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
|
|
107
|
-
const
|
|
108
|
-
const pass =
|
|
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
|
|
119
|
-
const elementHtml = 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
|
|
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
|
|
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
|
|
175
|
-
const
|
|
176
|
-
const
|
|
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
|
|
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
|
-
|
|
198
|
-
|
|
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
|
|
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
|
|
322
|
+
const htmlString = htmlOf(html);
|
|
222
323
|
if (!htmlString || !htmlString.includes(substring)) {
|
|
223
324
|
throw new Error(`Expected HTML to contain "${substring}"`);
|
|
224
325
|
}
|