@coherent.js/tooling 1.0.0-rc.1

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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/dist/lsp/analysis/coherent-analyzer.d.ts +93 -0
  4. package/dist/lsp/analysis/coherent-analyzer.d.ts.map +1 -0
  5. package/dist/lsp/analysis/coherent-analyzer.js +288 -0
  6. package/dist/lsp/analysis/coherent-analyzer.js.map +1 -0
  7. package/dist/lsp/analysis/element-validator.d.ts +45 -0
  8. package/dist/lsp/analysis/element-validator.d.ts.map +1 -0
  9. package/dist/lsp/analysis/element-validator.js +84 -0
  10. package/dist/lsp/analysis/element-validator.js.map +1 -0
  11. package/dist/lsp/analysis/nesting-validator.d.ts +49 -0
  12. package/dist/lsp/analysis/nesting-validator.d.ts.map +1 -0
  13. package/dist/lsp/analysis/nesting-validator.js +68 -0
  14. package/dist/lsp/analysis/nesting-validator.js.map +1 -0
  15. package/dist/lsp/data/element-attributes.d.ts +92 -0
  16. package/dist/lsp/data/element-attributes.d.ts.map +1 -0
  17. package/dist/lsp/data/element-attributes.generated.json +7085 -0
  18. package/dist/lsp/data/element-attributes.js +282 -0
  19. package/dist/lsp/data/element-attributes.js.map +1 -0
  20. package/dist/lsp/data/nesting-rules.d.ts +67 -0
  21. package/dist/lsp/data/nesting-rules.d.ts.map +1 -0
  22. package/dist/lsp/data/nesting-rules.js +240 -0
  23. package/dist/lsp/data/nesting-rules.js.map +1 -0
  24. package/dist/lsp/providers/code-actions.d.ts +15 -0
  25. package/dist/lsp/providers/code-actions.d.ts.map +1 -0
  26. package/dist/lsp/providers/code-actions.js +191 -0
  27. package/dist/lsp/providers/code-actions.js.map +1 -0
  28. package/dist/lsp/providers/completion.d.ts +15 -0
  29. package/dist/lsp/providers/completion.d.ts.map +1 -0
  30. package/dist/lsp/providers/completion.js +247 -0
  31. package/dist/lsp/providers/completion.js.map +1 -0
  32. package/dist/lsp/providers/diagnostics.d.ts +26 -0
  33. package/dist/lsp/providers/diagnostics.d.ts.map +1 -0
  34. package/dist/lsp/providers/diagnostics.js +143 -0
  35. package/dist/lsp/providers/diagnostics.js.map +1 -0
  36. package/dist/lsp/providers/hover.d.ts +15 -0
  37. package/dist/lsp/providers/hover.d.ts.map +1 -0
  38. package/dist/lsp/providers/hover.js +215 -0
  39. package/dist/lsp/providers/hover.js.map +1 -0
  40. package/dist/lsp/server.d.ts +17 -0
  41. package/dist/lsp/server.d.ts.map +1 -0
  42. package/dist/lsp/server.js +82 -0
  43. package/dist/lsp/server.js.map +1 -0
  44. package/dist/testing/index.js +746 -0
  45. package/dist/testing/index.js.map +7 -0
  46. package/dist/testing/matchers.js +246 -0
  47. package/dist/testing/matchers.js.map +7 -0
  48. package/dist/testing/test-renderer.js +254 -0
  49. package/dist/testing/test-renderer.js.map +7 -0
  50. package/dist/testing/test-utils.js +262 -0
  51. package/dist/testing/test-utils.js.map +7 -0
  52. package/package.json +96 -0
  53. package/types/testing/index.d.ts +404 -0
@@ -0,0 +1,746 @@
1
+ // src/testing/test-renderer.js
2
+ import { render } from "@coherent.js/core";
3
+ var TestRendererResult = class {
4
+ constructor(component, html, container = null) {
5
+ this.component = component;
6
+ this.html = html;
7
+ this.container = container;
8
+ this.queries = /* @__PURE__ */ new Map();
9
+ }
10
+ /**
11
+ * Get element by test ID
12
+ * @param {string} testId - Test ID to search for
13
+ * @returns {Object|null} Element or null
14
+ */
15
+ getByTestId(testId) {
16
+ const regex = new RegExp(`data-testid="${testId}"[^>]*>([^<]*)<`, "i");
17
+ const match = this.html.match(regex);
18
+ if (!match) {
19
+ throw new Error(`Unable to find element with testId: ${testId}`);
20
+ }
21
+ return {
22
+ text: match[1],
23
+ html: match[0],
24
+ testId,
25
+ exists: true
26
+ };
27
+ }
28
+ /**
29
+ * Query element by test ID (returns null if not found)
30
+ * @param {string} testId - Test ID to search for
31
+ * @returns {Object|null} Element or null
32
+ */
33
+ queryByTestId(testId) {
34
+ try {
35
+ return this.getByTestId(testId);
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+ /**
41
+ * Get element by text content
42
+ * @param {string|RegExp} text - Text to search for
43
+ * @returns {Object} Element
44
+ */
45
+ getByText(text) {
46
+ const regex = typeof text === "string" ? new RegExp(`>([^<]*${text}[^<]*)<`, "i") : new RegExp(`>([^<]*)<`, "i");
47
+ const match = this.html.match(regex);
48
+ if (!match || typeof text === "string" && !match[1].includes(text)) {
49
+ throw new Error(`Unable to find element with text: ${text}`);
50
+ }
51
+ return {
52
+ text: match[1],
53
+ html: match[0],
54
+ exists: true
55
+ };
56
+ }
57
+ /**
58
+ * Query element by text (returns null if not found)
59
+ * @param {string|RegExp} text - Text to search for
60
+ * @returns {Object|null} Element or null
61
+ */
62
+ queryByText(text) {
63
+ try {
64
+ return this.getByText(text);
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+ /**
70
+ * Get element by class name
71
+ * @param {string} className - Class name to search for
72
+ * @returns {Object} Element
73
+ */
74
+ getByClassName(className) {
75
+ const regex = new RegExp(`class="[^"]*${className}[^"]*"[^>]*>([^<]*)<`, "i");
76
+ const match = this.html.match(regex);
77
+ if (!match) {
78
+ throw new Error(`Unable to find element with className: ${className}`);
79
+ }
80
+ return {
81
+ text: match[1],
82
+ html: match[0],
83
+ className,
84
+ exists: true
85
+ };
86
+ }
87
+ /**
88
+ * Query element by class name (returns null if not found)
89
+ * @param {string} className - Class name to search for
90
+ * @returns {Object|null} Element or null
91
+ */
92
+ queryByClassName(className) {
93
+ try {
94
+ return this.getByClassName(className);
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+ /**
100
+ * Get all elements by tag name
101
+ * @param {string} tagName - Tag name to search for
102
+ * @returns {Array<Object>} Array of elements
103
+ */
104
+ getAllByTagName(tagName) {
105
+ const regex = new RegExp(`<${tagName}[^>]*>([^<]*)</${tagName}>`, "gi");
106
+ const matches = [...this.html.matchAll(regex)];
107
+ return matches.map((match) => ({
108
+ text: match[1],
109
+ html: match[0],
110
+ tagName,
111
+ exists: true
112
+ }));
113
+ }
114
+ /**
115
+ * Check if element exists
116
+ * @param {string} selector - Selector (testId, text, className)
117
+ * @param {string} type - Type of selector ('testId', 'text', 'className')
118
+ * @returns {boolean} True if exists
119
+ */
120
+ exists(selector, type = "testId") {
121
+ switch (type) {
122
+ case "testId":
123
+ return this.queryByTestId(selector) !== null;
124
+ case "text":
125
+ return this.queryByText(selector) !== null;
126
+ case "className":
127
+ return this.queryByClassName(selector) !== null;
128
+ default:
129
+ return false;
130
+ }
131
+ }
132
+ /**
133
+ * Get the rendered HTML
134
+ * @returns {string} HTML string
135
+ */
136
+ getHTML() {
137
+ return this.html;
138
+ }
139
+ /**
140
+ * Get the component
141
+ * @returns {Object} Component object
142
+ */
143
+ getComponent() {
144
+ return this.component;
145
+ }
146
+ /**
147
+ * Create a snapshot of the rendered output
148
+ * @returns {string} Formatted HTML for snapshot testing
149
+ */
150
+ toSnapshot() {
151
+ return this.html.replace(/>\s+</g, "><").trim();
152
+ }
153
+ /**
154
+ * Debug: print the rendered HTML
155
+ */
156
+ debug() {
157
+ console.log("=== Rendered HTML ===");
158
+ console.log(this.html);
159
+ console.log("=== Component ===");
160
+ console.log(JSON.stringify(this.component, null, 2));
161
+ }
162
+ };
163
+ function renderComponent(component, options = {}) {
164
+ const html = render(component, options);
165
+ return new TestRendererResult(component, html);
166
+ }
167
+ async function renderComponentAsync(component, props = {}, options = {}) {
168
+ const resolvedComponent = typeof component === "function" ? await component(props) : component;
169
+ const html = render(resolvedComponent, options);
170
+ return new TestRendererResult(resolvedComponent, html);
171
+ }
172
+ var TestRenderer = class {
173
+ constructor(component, options = {}) {
174
+ this.component = component;
175
+ this.options = options;
176
+ this.result = null;
177
+ this.renderCount = 0;
178
+ }
179
+ /**
180
+ * Render the component
181
+ * @returns {TestRendererResult} Render result
182
+ */
183
+ render() {
184
+ this.renderCount++;
185
+ const html = render(this.component, this.options);
186
+ this.result = new TestRendererResult(this.component, html);
187
+ return this.result;
188
+ }
189
+ /**
190
+ * Update the component and re-render
191
+ * @param {Object} newComponent - Updated component
192
+ * @returns {TestRendererResult} Render result
193
+ */
194
+ update(newComponent) {
195
+ this.component = newComponent;
196
+ return this.render();
197
+ }
198
+ /**
199
+ * Get the current result
200
+ * @returns {TestRendererResult|null} Current result
201
+ */
202
+ getResult() {
203
+ return this.result;
204
+ }
205
+ /**
206
+ * Get render count
207
+ * @returns {number} Number of renders
208
+ */
209
+ getRenderCount() {
210
+ return this.renderCount;
211
+ }
212
+ /**
213
+ * Unmount the component
214
+ */
215
+ unmount() {
216
+ this.component = null;
217
+ this.result = null;
218
+ }
219
+ };
220
+ function createTestRenderer(component, options = {}) {
221
+ return new TestRenderer(component, options);
222
+ }
223
+ function shallowRender(component) {
224
+ const shallow = { ...component };
225
+ Object.keys(shallow).forEach((key) => {
226
+ if (shallow[key] && typeof shallow[key] === "object") {
227
+ if (shallow[key].children) {
228
+ shallow[key] = {
229
+ ...shallow[key],
230
+ children: Array.isArray(shallow[key].children) ? shallow[key].children.map(() => ({ _shallow: true })) : { _shallow: true }
231
+ };
232
+ }
233
+ }
234
+ });
235
+ return shallow;
236
+ }
237
+
238
+ // src/testing/test-utils.js
239
+ function fireEvent(element, eventType, eventData = {}) {
240
+ if (!element) {
241
+ throw new Error("Element is required for fireEvent");
242
+ }
243
+ const event = {
244
+ type: eventType,
245
+ target: element,
246
+ currentTarget: element,
247
+ preventDefault: () => {
248
+ },
249
+ stopPropagation: () => {
250
+ },
251
+ ...eventData
252
+ };
253
+ const handlerName = `on${eventType}`;
254
+ if (element[handlerName] && typeof element[handlerName] === "function") {
255
+ element[handlerName](event);
256
+ }
257
+ return event;
258
+ }
259
+ var fireEvent_click = (element, eventData) => fireEvent(element, "click", eventData);
260
+ var fireEvent_change = (element, value) => fireEvent(element, "change", { target: { value } });
261
+ var fireEvent_input = (element, value) => fireEvent(element, "input", { target: { value } });
262
+ var fireEvent_keyDown = (element, key) => fireEvent(element, "keydown", { key });
263
+ var fireEvent_keyUp = (element, key) => fireEvent(element, "keyup", { key });
264
+ var fireEvent_focus = (element) => fireEvent(element, "focus");
265
+ var fireEvent_blur = (element) => fireEvent(element, "blur");
266
+ function waitFor(condition, options = {}) {
267
+ const { timeout = 1e3, interval = 50 } = options;
268
+ return new Promise((resolve, reject) => {
269
+ const startTime = Date.now();
270
+ const check = () => {
271
+ try {
272
+ if (condition()) {
273
+ resolve();
274
+ return;
275
+ }
276
+ } catch {
277
+ }
278
+ if (Date.now() - startTime >= timeout) {
279
+ reject(new Error(`Timeout waiting for condition after ${timeout}ms`));
280
+ return;
281
+ }
282
+ setTimeout(check, interval);
283
+ };
284
+ check();
285
+ });
286
+ }
287
+ async function waitForElement(queryFn, options = {}) {
288
+ let element = null;
289
+ await waitFor(() => {
290
+ element = queryFn();
291
+ return element !== null;
292
+ }, options);
293
+ return element;
294
+ }
295
+ async function waitForElementToBeRemoved(queryFn, options = {}) {
296
+ await waitFor(() => {
297
+ const element = queryFn();
298
+ return element === null;
299
+ }, options);
300
+ }
301
+ async function act(callback) {
302
+ await callback();
303
+ await new Promise((resolve) => setTimeout(resolve, 0));
304
+ }
305
+ function createMock(implementation) {
306
+ const calls = [];
307
+ const results = [];
308
+ const mockFn = function(...args) {
309
+ calls.push(args);
310
+ let result;
311
+ let error;
312
+ try {
313
+ result = implementation ? implementation(...args) : void 0;
314
+ results.push({ type: "return", value: result });
315
+ } catch (err) {
316
+ error = err;
317
+ results.push({ type: "throw", value: error });
318
+ throw error;
319
+ }
320
+ return result;
321
+ };
322
+ mockFn.mock = {
323
+ calls,
324
+ results,
325
+ instances: []
326
+ };
327
+ mockFn.mockClear = () => {
328
+ calls.length = 0;
329
+ results.length = 0;
330
+ };
331
+ mockFn.mockReset = () => {
332
+ mockFn.mockClear();
333
+ implementation = void 0;
334
+ };
335
+ mockFn.mockImplementation = (fn) => {
336
+ implementation = fn;
337
+ return mockFn;
338
+ };
339
+ mockFn.mockReturnValue = (value) => {
340
+ implementation = () => value;
341
+ return mockFn;
342
+ };
343
+ mockFn.mockResolvedValue = (value) => {
344
+ implementation = () => Promise.resolve(value);
345
+ return mockFn;
346
+ };
347
+ mockFn.mockRejectedValue = (error) => {
348
+ implementation = () => Promise.reject(error);
349
+ return mockFn;
350
+ };
351
+ return mockFn;
352
+ }
353
+ function createSpy(object, method) {
354
+ const original = object[method];
355
+ const spy = createMock(original.bind(object));
356
+ object[method] = spy;
357
+ spy.mockRestore = () => {
358
+ object[method] = original;
359
+ };
360
+ return spy;
361
+ }
362
+ function cleanup() {
363
+ }
364
+ function within(container) {
365
+ return {
366
+ getByTestId: (testId) => container.getByTestId(testId),
367
+ queryByTestId: (testId) => container.queryByTestId(testId),
368
+ getByText: (text) => container.getByText(text),
369
+ queryByText: (text) => container.queryByText(text),
370
+ getByClassName: (className) => container.getByClassName(className),
371
+ queryByClassName: (className) => container.queryByClassName(className)
372
+ };
373
+ }
374
+ var screen = {
375
+ _result: null,
376
+ setResult(result) {
377
+ this._result = result;
378
+ },
379
+ getByTestId(testId) {
380
+ if (!this._result) throw new Error("No component rendered");
381
+ return this._result.getByTestId(testId);
382
+ },
383
+ queryByTestId(testId) {
384
+ if (!this._result) return null;
385
+ return this._result.queryByTestId(testId);
386
+ },
387
+ getByText(text) {
388
+ if (!this._result) throw new Error("No component rendered");
389
+ return this._result.getByText(text);
390
+ },
391
+ queryByText(text) {
392
+ if (!this._result) return null;
393
+ return this._result.queryByText(text);
394
+ },
395
+ getByClassName(className) {
396
+ if (!this._result) throw new Error("No component rendered");
397
+ return this._result.getByClassName(className);
398
+ },
399
+ queryByClassName(className) {
400
+ if (!this._result) return null;
401
+ return this._result.queryByClassName(className);
402
+ },
403
+ debug() {
404
+ if (this._result) {
405
+ this._result.debug();
406
+ }
407
+ }
408
+ };
409
+ var userEvent = {
410
+ /**
411
+ * Simulate user typing
412
+ */
413
+ type: async (element, text, options = {}) => {
414
+ const { delay = 0 } = options;
415
+ for (const char of text) {
416
+ fireEvent_keyDown(element, char);
417
+ fireEvent_input(element, element.value + char);
418
+ fireEvent_keyUp(element, char);
419
+ if (delay > 0) {
420
+ await new Promise((resolve) => setTimeout(resolve, delay));
421
+ }
422
+ }
423
+ },
424
+ /**
425
+ * Simulate user click
426
+ */
427
+ click: async (element) => {
428
+ fireEvent_focus(element);
429
+ fireEvent_click(element);
430
+ },
431
+ /**
432
+ * Simulate user double click
433
+ */
434
+ dblClick: async (element) => {
435
+ await userEvent.click(element);
436
+ await userEvent.click(element);
437
+ },
438
+ /**
439
+ * Simulate user clearing input
440
+ */
441
+ clear: async (element) => {
442
+ fireEvent_input(element, "");
443
+ fireEvent_change(element, "");
444
+ },
445
+ /**
446
+ * Simulate user selecting option
447
+ */
448
+ selectOptions: async (element, values) => {
449
+ const valueArray = Array.isArray(values) ? values : [values];
450
+ fireEvent_change(element, valueArray[0]);
451
+ },
452
+ /**
453
+ * Simulate user tab navigation
454
+ */
455
+ tab: async () => {
456
+ const activeElement = document.activeElement;
457
+ if (activeElement) {
458
+ fireEvent_keyDown(activeElement, "Tab");
459
+ fireEvent_blur(activeElement);
460
+ }
461
+ }
462
+ };
463
+
464
+ // src/testing/matchers.js
465
+ var customMatchers = {
466
+ /**
467
+ * Check if element has specific text
468
+ */
469
+ toHaveText(received, expected) {
470
+ const pass = received && received.text === expected;
471
+ return {
472
+ pass,
473
+ message: () => pass ? `Expected element not to have text "${expected}"` : `Expected element to have text "${expected}", but got "${received?.text || "null"}"`
474
+ };
475
+ },
476
+ /**
477
+ * Check if element contains text
478
+ */
479
+ toContainText(received, expected) {
480
+ const pass = received && received.text && received.text.includes(expected);
481
+ return {
482
+ pass,
483
+ message: () => pass ? `Expected element not to contain text "${expected}"` : `Expected element to contain text "${expected}", but got "${received?.text || "null"}"`
484
+ };
485
+ },
486
+ /**
487
+ * Check if element has specific class
488
+ */
489
+ toHaveClass(received, expected) {
490
+ const pass = received && received.className && received.className.includes(expected);
491
+ return {
492
+ pass,
493
+ message: () => pass ? `Expected element not to have class "${expected}"` : `Expected element to have class "${expected}", but got "${received?.className || "null"}"`
494
+ };
495
+ },
496
+ /**
497
+ * Check if element exists
498
+ */
499
+ toBeInTheDocument(received) {
500
+ const pass = received && received.exists === true;
501
+ return {
502
+ pass,
503
+ message: () => pass ? "Expected element not to be in the document" : "Expected element to be in the document"
504
+ };
505
+ },
506
+ /**
507
+ * Check if element is visible (has content)
508
+ */
509
+ toBeVisible(received) {
510
+ const pass = received && received.text && received.text.trim().length > 0;
511
+ return {
512
+ pass,
513
+ message: () => pass ? "Expected element not to be visible" : "Expected element to be visible (have text content)"
514
+ };
515
+ },
516
+ /**
517
+ * Check if element is empty
518
+ */
519
+ toBeEmpty(received) {
520
+ const pass = !received || !received.text || received.text.trim().length === 0;
521
+ return {
522
+ pass,
523
+ message: () => pass ? "Expected element not to be empty" : "Expected element to be empty"
524
+ };
525
+ },
526
+ /**
527
+ * Check if HTML contains specific string
528
+ */
529
+ toContainHTML(received, expected) {
530
+ const html = received?.html || received;
531
+ const pass = typeof html === "string" && html.includes(expected);
532
+ return {
533
+ pass,
534
+ message: () => pass ? `Expected HTML not to contain "${expected}"` : `Expected HTML to contain "${expected}"`
535
+ };
536
+ },
537
+ /**
538
+ * Check if element has attribute
539
+ */
540
+ toHaveAttribute(received, attribute, value) {
541
+ const html = received?.html || "";
542
+ const regex = new RegExp(`${attribute}="([^"]*)"`, "i");
543
+ const match = html.match(regex);
544
+ const pass = value !== void 0 ? match && match[1] === value : match !== null;
545
+ return {
546
+ pass,
547
+ message: () => {
548
+ if (value !== void 0) {
549
+ return pass ? `Expected element not to have attribute ${attribute}="${value}"` : `Expected element to have attribute ${attribute}="${value}", but got "${match?.[1] || "none"}"`;
550
+ }
551
+ return pass ? `Expected element not to have attribute ${attribute}` : `Expected element to have attribute ${attribute}`;
552
+ }
553
+ };
554
+ },
555
+ /**
556
+ * Check if component matches snapshot
557
+ */
558
+ toMatchSnapshot(received) {
559
+ const _snapshot = received?.toSnapshot ? received.toSnapshot() : received;
560
+ return {
561
+ pass: true,
562
+ message: () => "Snapshot comparison"
563
+ };
564
+ },
565
+ /**
566
+ * Check if element has specific tag name
567
+ */
568
+ toHaveTagName(received, tagName) {
569
+ const html = received?.html || "";
570
+ const regex = new RegExp(`<${tagName}[^>]*>`, "i");
571
+ const pass = regex.test(html);
572
+ return {
573
+ pass,
574
+ message: () => pass ? `Expected element not to have tag name "${tagName}"` : `Expected element to have tag name "${tagName}"`
575
+ };
576
+ },
577
+ /**
578
+ * Check if render result contains element
579
+ */
580
+ toContainElement(received, element) {
581
+ const html = received?.html || received;
582
+ const elementHtml = element?.html || element;
583
+ const pass = typeof html === "string" && html.includes(elementHtml);
584
+ return {
585
+ pass,
586
+ message: () => pass ? "Expected not to contain element" : "Expected to contain element"
587
+ };
588
+ },
589
+ /**
590
+ * Check if mock was called
591
+ */
592
+ toHaveBeenCalled(received) {
593
+ const pass = received?.mock?.calls?.length > 0;
594
+ return {
595
+ pass,
596
+ message: () => pass ? "Expected mock not to have been called" : "Expected mock to have been called"
597
+ };
598
+ },
599
+ /**
600
+ * Check if mock was called with specific args
601
+ */
602
+ toHaveBeenCalledWith(received, ...expectedArgs) {
603
+ const calls = received?.mock?.calls || [];
604
+ const pass = calls.some(
605
+ (call) => call.length === expectedArgs.length && call.every((arg, i) => arg === expectedArgs[i])
606
+ );
607
+ return {
608
+ pass,
609
+ message: () => pass ? `Expected mock not to have been called with ${JSON.stringify(expectedArgs)}` : `Expected mock to have been called with ${JSON.stringify(expectedArgs)}`
610
+ };
611
+ },
612
+ /**
613
+ * Check if mock was called N times
614
+ */
615
+ toHaveBeenCalledTimes(received, times) {
616
+ const callCount = received?.mock?.calls?.length || 0;
617
+ const pass = callCount === times;
618
+ return {
619
+ pass,
620
+ message: () => pass ? `Expected mock not to have been called ${times} times` : `Expected mock to have been called ${times} times, but was called ${callCount} times`
621
+ };
622
+ },
623
+ /**
624
+ * Check if component rendered successfully
625
+ */
626
+ toRenderSuccessfully(received) {
627
+ const pass = received && received.html && received.html.length > 0;
628
+ return {
629
+ pass,
630
+ message: () => pass ? "Expected component not to render successfully" : "Expected component to render successfully"
631
+ };
632
+ },
633
+ /**
634
+ * Check if HTML is valid
635
+ */
636
+ toBeValidHTML(received) {
637
+ const html = received?.html || received;
638
+ const openTags = (html.match(/<[^/][^>]*>/g) || []).length;
639
+ const closeTags = (html.match(/<\/[^>]+>/g) || []).length;
640
+ const selfClosing = (html.match(/<[^>]+\/>/g) || []).length;
641
+ const pass = openTags === closeTags + selfClosing;
642
+ return {
643
+ pass,
644
+ message: () => pass ? "Expected HTML not to be valid" : `Expected HTML to be valid (open: ${openTags}, close: ${closeTags}, self-closing: ${selfClosing})`
645
+ };
646
+ }
647
+ };
648
+ function extendExpect(expect) {
649
+ if (expect && expect.extend) {
650
+ expect.extend(customMatchers);
651
+ } else {
652
+ console.warn("Could not extend expect - expect.extend not available");
653
+ }
654
+ }
655
+ var assertions = {
656
+ /**
657
+ * Assert element has text
658
+ */
659
+ assertHasText(element, text) {
660
+ if (!element || element.text !== text) {
661
+ throw new Error(`Expected element to have text "${text}", but got "${element?.text || "null"}"`);
662
+ }
663
+ },
664
+ /**
665
+ * Assert element exists
666
+ */
667
+ assertExists(element) {
668
+ if (!element || !element.exists) {
669
+ throw new Error("Expected element to exist");
670
+ }
671
+ },
672
+ /**
673
+ * Assert element has class
674
+ */
675
+ assertHasClass(element, className) {
676
+ if (!element || !element.className || !element.className.includes(className)) {
677
+ throw new Error(`Expected element to have class "${className}"`);
678
+ }
679
+ },
680
+ /**
681
+ * Assert HTML contains string
682
+ */
683
+ assertContainsHTML(html, substring) {
684
+ const htmlString = html?.html || html;
685
+ if (!htmlString || !htmlString.includes(substring)) {
686
+ throw new Error(`Expected HTML to contain "${substring}"`);
687
+ }
688
+ },
689
+ /**
690
+ * Assert component rendered
691
+ */
692
+ assertRendered(result) {
693
+ if (!result || !result.html || result.html.length === 0) {
694
+ throw new Error("Expected component to render");
695
+ }
696
+ }
697
+ };
698
+
699
+ // src/testing/index.js
700
+ var index_default = {
701
+ // Renderer
702
+ renderComponent,
703
+ renderComponentAsync,
704
+ createTestRenderer,
705
+ shallowRender,
706
+ // Utilities
707
+ fireEvent,
708
+ waitFor,
709
+ waitForElement,
710
+ waitForElementToBeRemoved,
711
+ act,
712
+ createMock,
713
+ createSpy,
714
+ cleanup,
715
+ within,
716
+ screen,
717
+ userEvent,
718
+ // Matchers
719
+ customMatchers,
720
+ extendExpect,
721
+ assertions
722
+ };
723
+ export {
724
+ TestRenderer,
725
+ TestRendererResult,
726
+ act,
727
+ assertions,
728
+ cleanup,
729
+ createMock,
730
+ createSpy,
731
+ createTestRenderer,
732
+ customMatchers,
733
+ index_default as default,
734
+ extendExpect,
735
+ fireEvent,
736
+ renderComponent,
737
+ renderComponentAsync,
738
+ screen,
739
+ shallowRender,
740
+ userEvent,
741
+ waitFor,
742
+ waitForElement,
743
+ waitForElementToBeRemoved,
744
+ within
745
+ };
746
+ //# sourceMappingURL=index.js.map