@hyperfixi/testing-framework 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.
Files changed (37) hide show
  1. package/dist/assertions-BImuwZsB.d.mts +458 -0
  2. package/dist/assertions-BImuwZsB.d.ts +458 -0
  3. package/dist/assertions.d.mts +1 -0
  4. package/dist/assertions.d.ts +1 -0
  5. package/dist/assertions.js +625 -0
  6. package/dist/assertions.js.map +1 -0
  7. package/dist/assertions.mjs +621 -0
  8. package/dist/assertions.mjs.map +1 -0
  9. package/dist/index.d.mts +246 -0
  10. package/dist/index.d.ts +246 -0
  11. package/dist/index.js +1465 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/index.mjs +1435 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/package.json +121 -0
  16. package/src/assertions.test.ts +490 -0
  17. package/src/assertions.ts +779 -0
  18. package/src/index.test.ts +490 -0
  19. package/src/index.ts +601 -0
  20. package/src/jsdom.d.ts +7 -0
  21. package/src/multilingual/README.md +295 -0
  22. package/src/multilingual/bundle-builder.ts +345 -0
  23. package/src/multilingual/cli.ts +224 -0
  24. package/src/multilingual/index.ts +68 -0
  25. package/src/multilingual/orchestrator.ts +275 -0
  26. package/src/multilingual/pattern-loader.ts +176 -0
  27. package/src/multilingual/reporters/console-reporter.ts +294 -0
  28. package/src/multilingual/reporters/json-reporter.ts +99 -0
  29. package/src/multilingual/reporters/regression-reporter.ts +263 -0
  30. package/src/multilingual/tools/analyze-failures.ts +326 -0
  31. package/src/multilingual/types.ts +268 -0
  32. package/src/multilingual/validators/parse-diagnostics.ts +337 -0
  33. package/src/multilingual/validators/parse-validator.ts +193 -0
  34. package/src/multilingual/validators/size-validator.ts +187 -0
  35. package/src/runner.test.ts +813 -0
  36. package/src/runner.ts +639 -0
  37. package/src/types.ts +505 -0
@@ -0,0 +1,779 @@
1
+ /**
2
+ * Assertion Library
3
+ * Comprehensive assertion and expectation framework for HyperFixi testing
4
+ */
5
+
6
+ import { diffLines } from 'diff';
7
+ import type { ExpectAPI, ExpectMatcher, AssertAPI } from './types';
8
+
9
+ /**
10
+ * Assertion error class
11
+ */
12
+ export class AssertionError extends Error {
13
+ constructor(
14
+ message: string,
15
+ public expected?: any,
16
+ public actual?: any,
17
+ public operator?: string
18
+ ) {
19
+ super(message);
20
+ this.name = 'AssertionError';
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Deep equality comparison
26
+ */
27
+ function deepEqual(actual: any, expected: any): boolean {
28
+ if (actual === expected) return true;
29
+
30
+ if (actual == null || expected == null) return actual === expected;
31
+
32
+ if (typeof actual !== typeof expected) return false;
33
+
34
+ if (actual instanceof Date && expected instanceof Date) {
35
+ return actual.getTime() === expected.getTime();
36
+ }
37
+
38
+ if (actual instanceof RegExp && expected instanceof RegExp) {
39
+ return actual.toString() === expected.toString();
40
+ }
41
+
42
+ if (Array.isArray(actual) && Array.isArray(expected)) {
43
+ if (actual.length !== expected.length) return false;
44
+ for (let i = 0; i < actual.length; i++) {
45
+ if (!deepEqual(actual[i], expected[i])) return false;
46
+ }
47
+ return true;
48
+ }
49
+
50
+ if (typeof actual === 'object' && typeof expected === 'object') {
51
+ const actualKeys = Object.keys(actual);
52
+ const expectedKeys = Object.keys(expected);
53
+
54
+ if (actualKeys.length !== expectedKeys.length) return false;
55
+
56
+ for (const key of actualKeys) {
57
+ if (!expectedKeys.includes(key)) return false;
58
+ if (!deepEqual(actual[key], expected[key])) return false;
59
+ }
60
+
61
+ return true;
62
+ }
63
+
64
+ return false;
65
+ }
66
+
67
+ /**
68
+ * Generate diff between actual and expected values
69
+ */
70
+ function generateDiff(actual: any, expected: any): string {
71
+ const actualStr = typeof actual === 'string' ? actual : JSON.stringify(actual, null, 2);
72
+ const expectedStr = typeof expected === 'string' ? expected : JSON.stringify(expected, null, 2);
73
+
74
+ const diff = diffLines(expectedStr, actualStr);
75
+
76
+ return diff
77
+ .map(part => {
78
+ const prefix = part.added ? '+' : part.removed ? '-' : ' ';
79
+ return part.value
80
+ .split('\n')
81
+ .map(line => `${prefix} ${line}`)
82
+ .join('\n');
83
+ })
84
+ .join('\n');
85
+ }
86
+
87
+ /**
88
+ * Check if element is in the document
89
+ */
90
+ function isInDocument(element: Element): boolean {
91
+ if (typeof document === 'undefined') return false;
92
+ return document.contains(element);
93
+ }
94
+
95
+ /**
96
+ * Check if element is visible
97
+ */
98
+ function isVisible(element: Element): boolean {
99
+ if (!isInDocument(element)) return false;
100
+
101
+ const style = window.getComputedStyle(element);
102
+ return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
103
+ }
104
+
105
+ /**
106
+ * ExpectMatcher implementation
107
+ */
108
+ class ExpectMatcherImpl implements ExpectMatcher {
109
+ constructor(
110
+ private actual: any,
111
+ private isNot: boolean = false
112
+ ) {}
113
+
114
+ get not(): ExpectMatcher {
115
+ return new ExpectMatcherImpl(this.actual, !this.isNot);
116
+ }
117
+
118
+ get resolves(): ExpectMatcher {
119
+ if (!this.isPromise(this.actual)) {
120
+ throw new AssertionError('Expected value to be a Promise');
121
+ }
122
+
123
+ // Return a new matcher that operates on the resolved value
124
+ return new ExpectMatcherImpl(
125
+ this.actual.then((value: any) => value),
126
+ this.isNot
127
+ );
128
+ }
129
+
130
+ get rejects(): ExpectMatcher {
131
+ if (!this.isPromise(this.actual)) {
132
+ throw new AssertionError('Expected value to be a Promise');
133
+ }
134
+
135
+ // Return a new matcher that expects the promise to reject
136
+ return new ExpectMatcherImpl(
137
+ this.actual.then(
138
+ (value: any) => {
139
+ throw new Error('Promise did not reject');
140
+ },
141
+ (error: any) => error
142
+ ),
143
+ this.isNot
144
+ );
145
+ }
146
+
147
+ toBe(expected: any): void {
148
+ const passed = this.actual === expected;
149
+ this.assert(
150
+ passed,
151
+ 'toBe',
152
+ expected,
153
+ `Expected ${this.format(this.actual)} to be ${this.format(expected)}`
154
+ );
155
+ }
156
+
157
+ toEqual(expected: any): void {
158
+ const passed = deepEqual(this.actual, expected);
159
+ this.assert(
160
+ passed,
161
+ 'toEqual',
162
+ expected,
163
+ `Expected ${this.format(this.actual)} to equal ${this.format(expected)}`
164
+ );
165
+ }
166
+
167
+ toStrictEqual(expected: any): void {
168
+ const passed = this.actual === expected && deepEqual(this.actual, expected);
169
+ this.assert(
170
+ passed,
171
+ 'toStrictEqual',
172
+ expected,
173
+ `Expected ${this.format(this.actual)} to strictly equal ${this.format(expected)}`
174
+ );
175
+ }
176
+
177
+ toBeTruthy(): void {
178
+ const passed = Boolean(this.actual);
179
+ this.assert(passed, 'toBeTruthy', true, `Expected ${this.format(this.actual)} to be truthy`);
180
+ }
181
+
182
+ toBeFalsy(): void {
183
+ const passed = !Boolean(this.actual);
184
+ this.assert(passed, 'toBeFalsy', false, `Expected ${this.format(this.actual)} to be falsy`);
185
+ }
186
+
187
+ toBeNull(): void {
188
+ const passed = this.actual === null;
189
+ this.assert(passed, 'toBeNull', null, `Expected ${this.format(this.actual)} to be null`);
190
+ }
191
+
192
+ toBeUndefined(): void {
193
+ const passed = this.actual === undefined;
194
+ this.assert(
195
+ passed,
196
+ 'toBeUndefined',
197
+ undefined,
198
+ `Expected ${this.format(this.actual)} to be undefined`
199
+ );
200
+ }
201
+
202
+ toBeDefined(): void {
203
+ const passed = this.actual !== undefined;
204
+ this.assert(
205
+ passed,
206
+ 'toBeDefined',
207
+ 'defined',
208
+ `Expected ${this.format(this.actual)} to be defined`
209
+ );
210
+ }
211
+
212
+ toBeNaN(): void {
213
+ const passed = Number.isNaN(this.actual);
214
+ this.assert(passed, 'toBeNaN', NaN, `Expected ${this.format(this.actual)} to be NaN`);
215
+ }
216
+
217
+ toBeInstanceOf(constructor: any): void {
218
+ const passed = this.actual instanceof constructor;
219
+ this.assert(
220
+ passed,
221
+ 'toBeInstanceOf',
222
+ constructor,
223
+ `Expected ${this.format(this.actual)} to be instance of ${constructor.name}`
224
+ );
225
+ }
226
+
227
+ toBeCloseTo(expected: number, precision: number = 2): void {
228
+ if (typeof this.actual !== 'number' || typeof expected !== 'number') {
229
+ throw new AssertionError('Expected both values to be numbers');
230
+ }
231
+
232
+ const diff = Math.abs(this.actual - expected);
233
+ const passed = diff < Math.pow(10, -precision) / 2;
234
+ this.assert(
235
+ passed,
236
+ 'toBeCloseTo',
237
+ expected,
238
+ `Expected ${this.actual} to be close to ${expected} (precision: ${precision})`
239
+ );
240
+ }
241
+
242
+ toMatch(expected: string | RegExp): void {
243
+ if (typeof this.actual !== 'string') {
244
+ throw new AssertionError('Expected value to be a string');
245
+ }
246
+
247
+ const regex = typeof expected === 'string' ? new RegExp(expected) : expected;
248
+ const passed = regex.test(this.actual);
249
+ this.assert(passed, 'toMatch', expected, `Expected "${this.actual}" to match ${expected}`);
250
+ }
251
+
252
+ toContain(expected: any): void {
253
+ let passed = false;
254
+
255
+ if (typeof this.actual === 'string' && typeof expected === 'string') {
256
+ passed = this.actual.includes(expected);
257
+ } else if (Array.isArray(this.actual)) {
258
+ passed = this.actual.includes(expected);
259
+ } else if (this.actual && typeof this.actual.has === 'function') {
260
+ passed = this.actual.has(expected);
261
+ }
262
+
263
+ this.assert(
264
+ passed,
265
+ 'toContain',
266
+ expected,
267
+ `Expected ${this.format(this.actual)} to contain ${this.format(expected)}`
268
+ );
269
+ }
270
+
271
+ toHaveLength(expected: number): void {
272
+ if (this.actual == null || typeof this.actual.length !== 'number') {
273
+ throw new AssertionError('Expected value to have a length property');
274
+ }
275
+
276
+ const passed = this.actual.length === expected;
277
+ this.assert(
278
+ passed,
279
+ 'toHaveLength',
280
+ expected,
281
+ `Expected length ${this.actual.length} to be ${expected}`
282
+ );
283
+ }
284
+
285
+ toContainEqual(expected: any): void {
286
+ if (!Array.isArray(this.actual)) {
287
+ throw new AssertionError('Expected value to be an array');
288
+ }
289
+
290
+ const passed = this.actual.some(item => deepEqual(item, expected));
291
+ this.assert(
292
+ passed,
293
+ 'toContainEqual',
294
+ expected,
295
+ `Expected array to contain equal to ${this.format(expected)}`
296
+ );
297
+ }
298
+
299
+ toHaveProperty(path: string, value?: any): void {
300
+ const keys = path.split('.');
301
+ let current = this.actual;
302
+
303
+ for (const key of keys) {
304
+ if (current == null || !Object.prototype.hasOwnProperty.call(current, key)) {
305
+ this.assert(false, 'toHaveProperty', path, `Expected object to have property "${path}"`);
306
+ return;
307
+ }
308
+ current = current[key];
309
+ }
310
+
311
+ if (value !== undefined) {
312
+ const passed = deepEqual(current, value);
313
+ this.assert(
314
+ passed,
315
+ 'toHaveProperty',
316
+ value,
317
+ `Expected property "${path}" to have value ${this.format(value)}, got ${this.format(current)}`
318
+ );
319
+ } else {
320
+ this.assert(true, 'toHaveProperty', path, '');
321
+ }
322
+ }
323
+
324
+ toMatchObject(expected: object): void {
325
+ if (typeof this.actual !== 'object' || this.actual === null) {
326
+ throw new AssertionError('Expected value to be an object');
327
+ }
328
+
329
+ const passed = Object.keys(expected).every(key =>
330
+ deepEqual(this.actual[key], (expected as any)[key])
331
+ );
332
+
333
+ this.assert(
334
+ passed,
335
+ 'toMatchObject',
336
+ expected,
337
+ `Expected object to match ${this.format(expected)}`
338
+ );
339
+ }
340
+
341
+ // DOM-specific matchers
342
+ toBeInTheDocument(): void {
343
+ if (!(this.actual instanceof Element)) {
344
+ throw new AssertionError('Expected value to be a DOM element');
345
+ }
346
+
347
+ const passed = isInDocument(this.actual);
348
+ this.assert(
349
+ passed,
350
+ 'toBeInTheDocument',
351
+ 'in document',
352
+ 'Expected element to be in the document'
353
+ );
354
+ }
355
+
356
+ toBeVisible(): void {
357
+ if (!(this.actual instanceof Element)) {
358
+ throw new AssertionError('Expected value to be a DOM element');
359
+ }
360
+
361
+ const passed = isVisible(this.actual);
362
+ this.assert(passed, 'toBeVisible', 'visible', 'Expected element to be visible');
363
+ }
364
+
365
+ toBeDisabled(): void {
366
+ if (!(this.actual instanceof Element)) {
367
+ throw new AssertionError('Expected value to be a DOM element');
368
+ }
369
+
370
+ const passed = (this.actual as any).disabled === true || this.actual.hasAttribute('disabled');
371
+ this.assert(passed, 'toBeDisabled', 'disabled', 'Expected element to be disabled');
372
+ }
373
+
374
+ toBeEnabled(): void {
375
+ if (!(this.actual instanceof Element)) {
376
+ throw new AssertionError('Expected value to be a DOM element');
377
+ }
378
+
379
+ const passed = (this.actual as any).disabled !== true && !this.actual.hasAttribute('disabled');
380
+ this.assert(passed, 'toBeEnabled', 'enabled', 'Expected element to be enabled');
381
+ }
382
+
383
+ toBeChecked(): void {
384
+ if (!(this.actual instanceof Element)) {
385
+ throw new AssertionError('Expected value to be a DOM element');
386
+ }
387
+
388
+ const passed = (this.actual as any).checked === true;
389
+ this.assert(passed, 'toBeChecked', 'checked', 'Expected element to be checked');
390
+ }
391
+
392
+ toHaveClass(className: string): void {
393
+ if (!(this.actual instanceof Element)) {
394
+ throw new AssertionError('Expected value to be a DOM element');
395
+ }
396
+
397
+ const passed = this.actual.classList.contains(className);
398
+ this.assert(passed, 'toHaveClass', className, `Expected element to have class "${className}"`);
399
+ }
400
+
401
+ toHaveAttribute(name: string, value?: string): void {
402
+ if (!(this.actual instanceof Element)) {
403
+ throw new AssertionError('Expected value to be a DOM element');
404
+ }
405
+
406
+ if (value === undefined) {
407
+ const passed = this.actual.hasAttribute(name);
408
+ this.assert(
409
+ passed,
410
+ 'toHaveAttribute',
411
+ `attribute "${name}"`,
412
+ `Expected element to have attribute "${name}"`
413
+ );
414
+ } else {
415
+ const passed = this.actual.getAttribute(name) === value;
416
+ this.assert(
417
+ passed,
418
+ 'toHaveAttribute',
419
+ `${name}="${value}"`,
420
+ `Expected element to have attribute "${name}" with value "${value}"`
421
+ );
422
+ }
423
+ }
424
+
425
+ toHaveTextContent(text: string | RegExp): void {
426
+ if (!(this.actual instanceof Element)) {
427
+ throw new AssertionError('Expected value to be a DOM element');
428
+ }
429
+
430
+ const textContent = this.actual.textContent || '';
431
+ const passed = typeof text === 'string' ? textContent === text : text.test(textContent);
432
+
433
+ this.assert(
434
+ passed,
435
+ 'toHaveTextContent',
436
+ text,
437
+ `Expected element to have text content "${text}"`
438
+ );
439
+ }
440
+
441
+ toHaveValue(value: any): void {
442
+ if (!(this.actual instanceof Element)) {
443
+ throw new AssertionError('Expected value to be a DOM element');
444
+ }
445
+
446
+ const actualValue = (this.actual as any).value;
447
+ const passed = actualValue === value;
448
+ this.assert(
449
+ passed,
450
+ 'toHaveValue',
451
+ value,
452
+ `Expected element to have value "${value}", got "${actualValue}"`
453
+ );
454
+ }
455
+
456
+ // HyperScript-specific matchers
457
+ toHaveCompiledScript(): void {
458
+ // Implementation would depend on HyperFixi API
459
+ const passed = this.actual && typeof this.actual._hyperscript !== 'undefined';
460
+ this.assert(
461
+ passed,
462
+ 'toHaveCompiledScript',
463
+ 'compiled script',
464
+ 'Expected element to have compiled hyperscript'
465
+ );
466
+ }
467
+
468
+ toHaveExecutedScript(): void {
469
+ // Implementation would depend on HyperFixi API
470
+ const passed = this.actual && this.actual._hyperscriptExecuted === true;
471
+ this.assert(
472
+ passed,
473
+ 'toHaveExecutedScript',
474
+ 'executed script',
475
+ 'Expected hyperscript to have executed'
476
+ );
477
+ }
478
+
479
+ toHaveTriggeredEvent(eventType: string): void {
480
+ // Implementation would depend on event tracking
481
+ const passed =
482
+ this.actual &&
483
+ this.actual._triggeredEvents &&
484
+ this.actual._triggeredEvents.includes(eventType);
485
+ this.assert(
486
+ passed,
487
+ 'toHaveTriggeredEvent',
488
+ eventType,
489
+ `Expected event "${eventType}" to have been triggered`
490
+ );
491
+ }
492
+
493
+ toHaveUpdatedElement(): void {
494
+ // Implementation would depend on DOM change tracking
495
+ const passed = this.actual && this.actual._wasUpdated === true;
496
+ this.assert(
497
+ passed,
498
+ 'toHaveUpdatedElement',
499
+ 'updated element',
500
+ 'Expected element to have been updated'
501
+ );
502
+ }
503
+
504
+ private assert(passed: boolean, operator: string, expected: any, message: string): void {
505
+ if (this.isNot) {
506
+ passed = !passed;
507
+ message = message.replace('Expected', 'Expected NOT');
508
+ }
509
+
510
+ if (!passed) {
511
+ const error = new AssertionError(message, expected, this.actual, operator);
512
+
513
+ if (typeof this.actual === 'object' && typeof expected === 'object') {
514
+ error.message += '\n\nDiff:\n' + generateDiff(this.actual, expected);
515
+ }
516
+
517
+ throw error;
518
+ }
519
+ }
520
+
521
+ private format(value: any): string {
522
+ if (value === null) return 'null';
523
+ if (value === undefined) return 'undefined';
524
+ if (typeof value === 'string') return `"${value}"`;
525
+ if (typeof value === 'function') return value.name || 'function';
526
+ if (value instanceof Date) return value.toISOString();
527
+ if (value instanceof RegExp) return value.toString();
528
+ if (Array.isArray(value)) return `[${value.map(v => this.format(v)).join(', ')}]`;
529
+ if (typeof value === 'object') return JSON.stringify(value);
530
+ return String(value);
531
+ }
532
+
533
+ private isPromise(value: any): boolean {
534
+ return value && typeof value.then === 'function';
535
+ }
536
+ }
537
+
538
+ /**
539
+ * Create expect API
540
+ */
541
+ export function createExpectAPI(): ExpectAPI {
542
+ return function expect(actual: any): ExpectMatcher {
543
+ return new ExpectMatcherImpl(actual);
544
+ };
545
+ }
546
+
547
+ /**
548
+ * Assert API implementation
549
+ */
550
+ class AssertAPIImpl implements AssertAPI {
551
+ ok(value: any, message?: string): void {
552
+ if (!value) {
553
+ throw new AssertionError(message || `Expected ${value} to be truthy`);
554
+ }
555
+ }
556
+
557
+ equal(actual: any, expected: any, message?: string): void {
558
+ if (actual != expected) {
559
+ throw new AssertionError(
560
+ message || `Expected ${actual} to equal ${expected}`,
561
+ expected,
562
+ actual,
563
+ 'equal'
564
+ );
565
+ }
566
+ }
567
+
568
+ strictEqual(actual: any, expected: any, message?: string): void {
569
+ if (actual !== expected) {
570
+ throw new AssertionError(
571
+ message || `Expected ${actual} to strictly equal ${expected}`,
572
+ expected,
573
+ actual,
574
+ 'strictEqual'
575
+ );
576
+ }
577
+ }
578
+
579
+ notEqual(actual: any, expected: any, message?: string): void {
580
+ if (actual == expected) {
581
+ throw new AssertionError(
582
+ message || `Expected ${actual} to not equal ${expected}`,
583
+ expected,
584
+ actual,
585
+ 'notEqual'
586
+ );
587
+ }
588
+ }
589
+
590
+ notStrictEqual(actual: any, expected: any, message?: string): void {
591
+ if (actual === expected) {
592
+ throw new AssertionError(
593
+ message || `Expected ${actual} to not strictly equal ${expected}`,
594
+ expected,
595
+ actual,
596
+ 'notStrictEqual'
597
+ );
598
+ }
599
+ }
600
+
601
+ throws(fn: () => void, error?: any, message?: string): void {
602
+ let thrown = false;
603
+ let actualError: any;
604
+
605
+ try {
606
+ fn();
607
+ } catch (e) {
608
+ thrown = true;
609
+ actualError = e;
610
+ }
611
+
612
+ if (!thrown) {
613
+ throw new AssertionError(message || 'Expected function to throw');
614
+ }
615
+
616
+ if (error && !this.matchesError(actualError, error)) {
617
+ throw new AssertionError(
618
+ message || `Expected function to throw ${error}, but got ${actualError}`,
619
+ error,
620
+ actualError,
621
+ 'throws'
622
+ );
623
+ }
624
+ }
625
+
626
+ doesNotThrow(fn: () => void, message?: string): void {
627
+ try {
628
+ fn();
629
+ } catch (e) {
630
+ throw new AssertionError(
631
+ message || `Expected function to not throw, but got: ${e}`,
632
+ 'no error',
633
+ e,
634
+ 'doesNotThrow'
635
+ );
636
+ }
637
+ }
638
+
639
+ ifError(value: any): void {
640
+ if (value) {
641
+ throw value;
642
+ }
643
+ }
644
+
645
+ async rejects(promise: Promise<any>, error?: any, message?: string): Promise<void> {
646
+ let rejected = false;
647
+ let actualError: any;
648
+
649
+ try {
650
+ await promise;
651
+ } catch (e) {
652
+ rejected = true;
653
+ actualError = e;
654
+ }
655
+
656
+ if (!rejected) {
657
+ throw new AssertionError(message || 'Expected promise to reject');
658
+ }
659
+
660
+ if (error && !this.matchesError(actualError, error)) {
661
+ throw new AssertionError(
662
+ message || `Expected promise to reject with ${error}, but got ${actualError}`,
663
+ error,
664
+ actualError,
665
+ 'rejects'
666
+ );
667
+ }
668
+ }
669
+
670
+ async doesNotReject(promise: Promise<any>, message?: string): Promise<void> {
671
+ try {
672
+ await promise;
673
+ } catch (e) {
674
+ throw new AssertionError(
675
+ message || `Expected promise to not reject, but got: ${e}`,
676
+ 'no error',
677
+ e,
678
+ 'doesNotReject'
679
+ );
680
+ }
681
+ }
682
+
683
+ elementExists(selector: string, message?: string): void {
684
+ if (typeof document === 'undefined') {
685
+ throw new AssertionError('DOM not available');
686
+ }
687
+
688
+ const element = document.querySelector(selector);
689
+ if (!element) {
690
+ throw new AssertionError(message || `Expected element with selector "${selector}" to exist`);
691
+ }
692
+ }
693
+
694
+ elementVisible(selector: string, message?: string): void {
695
+ if (typeof document === 'undefined') {
696
+ throw new AssertionError('DOM not available');
697
+ }
698
+
699
+ const element = document.querySelector(selector);
700
+ if (!element) {
701
+ throw new AssertionError(`Element with selector "${selector}" does not exist`);
702
+ }
703
+
704
+ if (!isVisible(element)) {
705
+ throw new AssertionError(
706
+ message || `Expected element with selector "${selector}" to be visible`
707
+ );
708
+ }
709
+ }
710
+
711
+ elementHasClass(selector: string, className: string, message?: string): void {
712
+ if (typeof document === 'undefined') {
713
+ throw new AssertionError('DOM not available');
714
+ }
715
+
716
+ const element = document.querySelector(selector);
717
+ if (!element) {
718
+ throw new AssertionError(`Element with selector "${selector}" does not exist`);
719
+ }
720
+
721
+ if (!element.classList.contains(className)) {
722
+ throw new AssertionError(
723
+ message || `Expected element with selector "${selector}" to have class "${className}"`
724
+ );
725
+ }
726
+ }
727
+
728
+ elementHasText(selector: string, text: string, message?: string): void {
729
+ if (typeof document === 'undefined') {
730
+ throw new AssertionError('DOM not available');
731
+ }
732
+
733
+ const element = document.querySelector(selector);
734
+ if (!element) {
735
+ throw new AssertionError(`Element with selector "${selector}" does not exist`);
736
+ }
737
+
738
+ if (element.textContent !== text) {
739
+ throw new AssertionError(
740
+ message || `Expected element with selector "${selector}" to have text "${text}"`
741
+ );
742
+ }
743
+ }
744
+
745
+ scriptCompiled(script: string, message?: string): void {
746
+ // Implementation would depend on HyperFixi API
747
+ throw new AssertionError(message || 'Script compilation assertion not implemented');
748
+ }
749
+
750
+ scriptExecuted(script: string, message?: string): void {
751
+ // Implementation would depend on HyperFixi API
752
+ throw new AssertionError(message || 'Script execution assertion not implemented');
753
+ }
754
+
755
+ eventTriggered(eventType: string, message?: string): void {
756
+ // Implementation would depend on event tracking
757
+ throw new AssertionError(message || 'Event trigger assertion not implemented');
758
+ }
759
+
760
+ private matchesError(actual: any, expected: any): boolean {
761
+ if (typeof expected === 'string') {
762
+ return actual.message === expected;
763
+ }
764
+ if (expected instanceof RegExp) {
765
+ return expected.test(actual.message);
766
+ }
767
+ if (typeof expected === 'function') {
768
+ return actual instanceof expected;
769
+ }
770
+ return actual === expected;
771
+ }
772
+ }
773
+
774
+ /**
775
+ * Create assert API
776
+ */
777
+ export function createAssertAPI(): AssertAPI {
778
+ return new AssertAPIImpl();
779
+ }