@coherent.js/testing 1.0.0-beta.5 → 1.0.0-beta.7

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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/test-renderer.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"],
5
+ "mappings": ";AASA,SAAS,cAAc;AAMhB,IAAM,qBAAN,MAAyB;AAAA,EAC9B,YAAY,WAAW,MAAM,YAAY,MAAM;AAC7C,SAAK,YAAY;AACjB,SAAK,OAAO;AACZ,SAAK,YAAY;AACjB,SAAK,UAAU,oBAAI,IAAI;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY,QAAQ;AAClB,UAAM,QAAQ,IAAI,OAAO,gBAAgB,MAAM,mBAAmB,GAAG;AACrE,UAAM,QAAQ,KAAK,KAAK,MAAM,KAAK;AAEnC,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,uCAAuC,MAAM,EAAE;AAAA,IACjE;AAEA,WAAO;AAAA,MACL,MAAM,MAAM,CAAC;AAAA,MACb,MAAM,MAAM,CAAC;AAAA,MACb;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc,QAAQ;AACpB,QAAI;AACF,aAAO,KAAK,YAAY,MAAM;AAAA,IAChC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,MAAM;AACd,UAAM,QAAQ,OAAO,SAAS,WAC1B,IAAI,OAAO,UAAU,IAAI,WAAW,GAAG,IACvC,IAAI,OAAO,aAAa,GAAG;AAE/B,UAAM,QAAQ,KAAK,KAAK,MAAM,KAAK;AAEnC,QAAI,CAAC,SAAU,OAAO,SAAS,YAAY,CAAC,MAAM,CAAC,EAAE,SAAS,IAAI,GAAI;AACpE,YAAM,IAAI,MAAM,qCAAqC,IAAI,EAAE;AAAA,IAC7D;AAEA,WAAO;AAAA,MACL,MAAM,MAAM,CAAC;AAAA,MACb,MAAM,MAAM,CAAC;AAAA,MACb,QAAQ;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY,MAAM;AAChB,QAAI;AACF,aAAO,KAAK,UAAU,IAAI;AAAA,IAC5B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe,WAAW;AACxB,UAAM,QAAQ,IAAI,OAAO,eAAe,SAAS,wBAAwB,GAAG;AAC5E,UAAM,QAAQ,KAAK,KAAK,MAAM,KAAK;AAEnC,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,0CAA0C,SAAS,EAAE;AAAA,IACvE;AAEA,WAAO;AAAA,MACL,MAAM,MAAM,CAAC;AAAA,MACb,MAAM,MAAM,CAAC;AAAA,MACb;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB,WAAW;AAC1B,QAAI;AACF,aAAO,KAAK,eAAe,SAAS;AAAA,IACtC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgB,SAAS;AACvB,UAAM,QAAQ,IAAI,OAAO,IAAI,OAAO,kBAAkB,OAAO,KAAK,IAAI;AACtE,UAAM,UAAU,CAAC,GAAG,KAAK,KAAK,SAAS,KAAK,CAAC;AAE7C,WAAO,QAAQ,IAAI,YAAU;AAAA,MAC3B,MAAM,MAAM,CAAC;AAAA,MACb,MAAM,MAAM,CAAC;AAAA,MACb;AAAA,MACA,QAAQ;AAAA,IACV,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,UAAU,OAAO,UAAU;AAChC,YAAQ,MAAM;AAAA,MACZ,KAAK;AACH,eAAO,KAAK,cAAc,QAAQ,MAAM;AAAA,MAC1C,KAAK;AACH,eAAO,KAAK,YAAY,QAAQ,MAAM;AAAA,MACxC,KAAK;AACH,eAAO,KAAK,iBAAiB,QAAQ,MAAM;AAAA,MAC7C;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU;AACR,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAe;AACb,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa;AACX,WAAO,KAAK,KACT,QAAQ,UAAU,IAAI,EACtB,KAAK;AAAA,EACV;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ;AACN,YAAQ,IAAI,uBAAuB;AACnC,YAAQ,IAAI,KAAK,IAAI;AACrB,YAAQ,IAAI,mBAAmB;AAC/B,YAAQ,IAAI,KAAK,UAAU,KAAK,WAAW,MAAM,CAAC,CAAC;AAAA,EACrD;AACF;AAmBO,SAAS,gBAAgB,WAAW,UAAU,CAAC,GAAG;AACvD,QAAM,OAAO,OAAO,WAAW,OAAO;AACtC,SAAO,IAAI,mBAAmB,WAAW,IAAI;AAC/C;AAUA,eAAsB,qBAAqB,WAAW,QAAQ,CAAC,GAAG,UAAU,CAAC,GAAG;AAE9E,QAAM,oBAAoB,OAAO,cAAc,aAC3C,MAAM,UAAU,KAAK,IACrB;AAEJ,QAAM,OAAO,OAAO,mBAAmB,OAAO;AAC9C,SAAO,IAAI,mBAAmB,mBAAmB,IAAI;AACvD;AAMO,IAAM,eAAN,MAAmB;AAAA,EACxB,YAAY,WAAW,UAAU,CAAC,GAAG;AACnC,SAAK,YAAY;AACjB,SAAK,UAAU;AACf,SAAK,SAAS;AACd,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAS;AACP,SAAK;AACL,UAAM,OAAO,OAAO,KAAK,WAAW,KAAK,OAAO;AAChD,SAAK,SAAS,IAAI,mBAAmB,KAAK,WAAW,IAAI;AACzD,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,cAAc;AACnB,SAAK,YAAY;AACjB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY;AACV,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,iBAAiB;AACf,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU;AACR,SAAK,YAAY;AACjB,SAAK,SAAS;AAAA,EAChB;AACF;AAkBO,SAAS,mBAAmB,WAAW,UAAU,CAAC,GAAG;AAC1D,SAAO,IAAI,aAAa,WAAW,OAAO;AAC5C;AAQO,SAAS,cAAc,WAAW;AAEvC,QAAM,UAAU,EAAE,GAAG,UAAU;AAE/B,SAAO,KAAK,OAAO,EAAE,QAAQ,SAAO;AAClC,QAAI,QAAQ,GAAG,KAAK,OAAO,QAAQ,GAAG,MAAM,UAAU;AACpD,UAAI,QAAQ,GAAG,EAAE,UAAU;AACzB,gBAAQ,GAAG,IAAI;AAAA,UACb,GAAG,QAAQ,GAAG;AAAA,UACd,UAAU,MAAM,QAAQ,QAAQ,GAAG,EAAE,QAAQ,IACzC,QAAQ,GAAG,EAAE,SAAS,IAAI,OAAO,EAAE,UAAU,KAAK,EAAE,IACpD,EAAE,UAAU,KAAK;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAKA,IAAO,wBAAQ;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,262 @@
1
+ // src/test-utils.js
2
+ function fireEvent(element, eventType, eventData = {}) {
3
+ if (!element) {
4
+ throw new Error("Element is required for fireEvent");
5
+ }
6
+ const event = {
7
+ type: eventType,
8
+ target: element,
9
+ currentTarget: element,
10
+ preventDefault: () => {
11
+ },
12
+ stopPropagation: () => {
13
+ },
14
+ ...eventData
15
+ };
16
+ const handlerName = `on${eventType}`;
17
+ if (element[handlerName] && typeof element[handlerName] === "function") {
18
+ element[handlerName](event);
19
+ }
20
+ return event;
21
+ }
22
+ var fireEvent_click = (element, eventData) => fireEvent(element, "click", eventData);
23
+ var fireEvent_change = (element, value) => fireEvent(element, "change", { target: { value } });
24
+ var fireEvent_input = (element, value) => fireEvent(element, "input", { target: { value } });
25
+ var fireEvent_submit = (element, eventData) => fireEvent(element, "submit", eventData);
26
+ var fireEvent_keyDown = (element, key) => fireEvent(element, "keydown", { key });
27
+ var fireEvent_keyUp = (element, key) => fireEvent(element, "keyup", { key });
28
+ var fireEvent_focus = (element) => fireEvent(element, "focus");
29
+ var fireEvent_blur = (element) => fireEvent(element, "blur");
30
+ function waitFor(condition, options = {}) {
31
+ const { timeout = 1e3, interval = 50 } = options;
32
+ return new Promise((resolve, reject) => {
33
+ const startTime = Date.now();
34
+ const check = () => {
35
+ try {
36
+ if (condition()) {
37
+ resolve();
38
+ return;
39
+ }
40
+ } catch {
41
+ }
42
+ if (Date.now() - startTime >= timeout) {
43
+ reject(new Error(`Timeout waiting for condition after ${timeout}ms`));
44
+ return;
45
+ }
46
+ setTimeout(check, interval);
47
+ };
48
+ check();
49
+ });
50
+ }
51
+ async function waitForElement(queryFn, options = {}) {
52
+ let element = null;
53
+ await waitFor(() => {
54
+ element = queryFn();
55
+ return element !== null;
56
+ }, options);
57
+ return element;
58
+ }
59
+ async function waitForElementToBeRemoved(queryFn, options = {}) {
60
+ await waitFor(() => {
61
+ const element = queryFn();
62
+ return element === null;
63
+ }, options);
64
+ }
65
+ async function act(callback) {
66
+ await callback();
67
+ await new Promise((resolve) => setTimeout(resolve, 0));
68
+ }
69
+ function createMock(implementation) {
70
+ const calls = [];
71
+ const results = [];
72
+ const mockFn = function(...args) {
73
+ calls.push(args);
74
+ let result;
75
+ let error;
76
+ try {
77
+ result = implementation ? implementation(...args) : void 0;
78
+ results.push({ type: "return", value: result });
79
+ } catch (err) {
80
+ error = err;
81
+ results.push({ type: "throw", value: error });
82
+ throw error;
83
+ }
84
+ return result;
85
+ };
86
+ mockFn.mock = {
87
+ calls,
88
+ results,
89
+ instances: []
90
+ };
91
+ mockFn.mockClear = () => {
92
+ calls.length = 0;
93
+ results.length = 0;
94
+ };
95
+ mockFn.mockReset = () => {
96
+ mockFn.mockClear();
97
+ implementation = void 0;
98
+ };
99
+ mockFn.mockImplementation = (fn) => {
100
+ implementation = fn;
101
+ return mockFn;
102
+ };
103
+ mockFn.mockReturnValue = (value) => {
104
+ implementation = () => value;
105
+ return mockFn;
106
+ };
107
+ mockFn.mockResolvedValue = (value) => {
108
+ implementation = () => Promise.resolve(value);
109
+ return mockFn;
110
+ };
111
+ mockFn.mockRejectedValue = (error) => {
112
+ implementation = () => Promise.reject(error);
113
+ return mockFn;
114
+ };
115
+ return mockFn;
116
+ }
117
+ function createSpy(object, method) {
118
+ const original = object[method];
119
+ const spy = createMock(original.bind(object));
120
+ object[method] = spy;
121
+ spy.mockRestore = () => {
122
+ object[method] = original;
123
+ };
124
+ return spy;
125
+ }
126
+ function cleanup() {
127
+ }
128
+ function within(container) {
129
+ return {
130
+ getByTestId: (testId) => container.getByTestId(testId),
131
+ queryByTestId: (testId) => container.queryByTestId(testId),
132
+ getByText: (text) => container.getByText(text),
133
+ queryByText: (text) => container.queryByText(text),
134
+ getByClassName: (className) => container.getByClassName(className),
135
+ queryByClassName: (className) => container.queryByClassName(className)
136
+ };
137
+ }
138
+ var screen = {
139
+ _result: null,
140
+ setResult(result) {
141
+ this._result = result;
142
+ },
143
+ getByTestId(testId) {
144
+ if (!this._result) throw new Error("No component rendered");
145
+ return this._result.getByTestId(testId);
146
+ },
147
+ queryByTestId(testId) {
148
+ if (!this._result) return null;
149
+ return this._result.queryByTestId(testId);
150
+ },
151
+ getByText(text) {
152
+ if (!this._result) throw new Error("No component rendered");
153
+ return this._result.getByText(text);
154
+ },
155
+ queryByText(text) {
156
+ if (!this._result) return null;
157
+ return this._result.queryByText(text);
158
+ },
159
+ getByClassName(className) {
160
+ if (!this._result) throw new Error("No component rendered");
161
+ return this._result.getByClassName(className);
162
+ },
163
+ queryByClassName(className) {
164
+ if (!this._result) return null;
165
+ return this._result.queryByClassName(className);
166
+ },
167
+ debug() {
168
+ if (this._result) {
169
+ this._result.debug();
170
+ }
171
+ }
172
+ };
173
+ var userEvent = {
174
+ /**
175
+ * Simulate user typing
176
+ */
177
+ type: async (element, text, options = {}) => {
178
+ const { delay = 0 } = options;
179
+ for (const char of text) {
180
+ fireEvent_keyDown(element, char);
181
+ fireEvent_input(element, element.value + char);
182
+ fireEvent_keyUp(element, char);
183
+ if (delay > 0) {
184
+ await new Promise((resolve) => setTimeout(resolve, delay));
185
+ }
186
+ }
187
+ },
188
+ /**
189
+ * Simulate user click
190
+ */
191
+ click: async (element) => {
192
+ fireEvent_focus(element);
193
+ fireEvent_click(element);
194
+ },
195
+ /**
196
+ * Simulate user double click
197
+ */
198
+ dblClick: async (element) => {
199
+ await userEvent.click(element);
200
+ await userEvent.click(element);
201
+ },
202
+ /**
203
+ * Simulate user clearing input
204
+ */
205
+ clear: async (element) => {
206
+ fireEvent_input(element, "");
207
+ fireEvent_change(element, "");
208
+ },
209
+ /**
210
+ * Simulate user selecting option
211
+ */
212
+ selectOptions: async (element, values) => {
213
+ const valueArray = Array.isArray(values) ? values : [values];
214
+ fireEvent_change(element, valueArray[0]);
215
+ },
216
+ /**
217
+ * Simulate user tab navigation
218
+ */
219
+ tab: async () => {
220
+ const activeElement = document.activeElement;
221
+ if (activeElement) {
222
+ fireEvent_keyDown(activeElement, "Tab");
223
+ fireEvent_blur(activeElement);
224
+ }
225
+ }
226
+ };
227
+ var test_utils_default = {
228
+ fireEvent,
229
+ waitFor,
230
+ waitForElement,
231
+ waitForElementToBeRemoved,
232
+ act,
233
+ createMock,
234
+ createSpy,
235
+ cleanup,
236
+ within,
237
+ screen,
238
+ userEvent
239
+ };
240
+ export {
241
+ act,
242
+ cleanup,
243
+ createMock,
244
+ createSpy,
245
+ test_utils_default as default,
246
+ fireEvent,
247
+ fireEvent_blur,
248
+ fireEvent_change,
249
+ fireEvent_click,
250
+ fireEvent_focus,
251
+ fireEvent_input,
252
+ fireEvent_keyDown,
253
+ fireEvent_keyUp,
254
+ fireEvent_submit,
255
+ screen,
256
+ userEvent,
257
+ waitFor,
258
+ waitForElement,
259
+ waitForElementToBeRemoved,
260
+ within
261
+ };
262
+ //# sourceMappingURL=test-utils.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/test-utils.js"],
4
+ "sourcesContent": ["/**\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"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coherent.js/testing",
3
- "version": "1.0.0-beta.5",
3
+ "version": "1.0.0-beta.7",
4
4
  "description": "Testing utilities for Coherent.js applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,7 +19,7 @@
19
19
  "author": "Coherent.js Team",
20
20
  "license": "MIT",
21
21
  "peerDependencies": {
22
- "@coherent.js/core": "1.0.0-beta.5"
22
+ "@coherent.js/core": "1.0.0-beta.7"
23
23
  },
24
24
  "devDependencies": {
25
25
  "vitest": "^1.0.0"
@@ -28,11 +28,19 @@
28
28
  "type": "git",
29
29
  "url": "git+https://github.com/Tomdrouv1/coherent.js.git"
30
30
  },
31
+ "homepage": "https://github.com/Tomdrouv1/coherent.js",
32
+ "bugs": {
33
+ "url": "https://github.com/Tomdrouv1/coherent.js/issues"
34
+ },
31
35
  "publishConfig": {
32
36
  "access": "public"
33
37
  },
38
+ "engines": {
39
+ "node": ">=20.0.0"
40
+ },
34
41
  "types": "./types/index.d.ts",
35
42
  "files": [
43
+ "dist/",
36
44
  "LICENSE",
37
45
  "README.md",
38
46
  "types/"