@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
package/src/index.ts ADDED
@@ -0,0 +1,601 @@
1
+ /**
2
+ * Testing Framework for HyperFixi Applications
3
+ *
4
+ * Provides comprehensive cross-platform behavior testing including:
5
+ * - Multi-environment test execution (JSDOM, Browser, Node.js)
6
+ * - Rich assertion library with DOM and HyperScript matchers
7
+ * - E2E testing with Playwright and Puppeteer
8
+ * - Visual regression testing
9
+ * - Performance testing
10
+ * - Accessibility testing
11
+ */
12
+
13
+ // Import types first for local use
14
+ import type {
15
+ TestSuite,
16
+ TestCase,
17
+ TestFunction,
18
+ TestContext,
19
+ TestConfig,
20
+ TestResult,
21
+ TestEnvironment,
22
+ BrowserType,
23
+ } from './types';
24
+
25
+ // Import functions for internal use
26
+ import { createTestRunner as createTestRunnerInternal } from './runner';
27
+
28
+ // Core exports
29
+ export { CoreTestRunner, ParallelTestRunner, createTestRunner, measurePerformance } from './runner';
30
+ export { createExpectAPI, createAssertAPI, AssertionError } from './assertions';
31
+
32
+ // Type exports
33
+ export type {
34
+ // Test definition types
35
+ TestSuite,
36
+ TestCase,
37
+ TestFunction,
38
+ TestContext,
39
+ TestConfig,
40
+
41
+ // Result types
42
+ TestResult,
43
+ TestStatus,
44
+ TestError,
45
+ TestLog,
46
+ CoverageData,
47
+
48
+ // Environment types
49
+ TestEnvironment,
50
+ BrowserType,
51
+ BrowserTestOptions,
52
+
53
+ // Assertion types
54
+ ExpectAPI,
55
+ ExpectMatcher,
56
+ AssertAPI,
57
+
58
+ // Reporter types
59
+ TestReporter,
60
+
61
+ // Utility types
62
+ PageObject,
63
+ TestDataProvider,
64
+ TestFixture,
65
+ TestRunner,
66
+ TestDiscoveryOptions,
67
+ MockFunction,
68
+ SpyFunction,
69
+
70
+ // E2E types
71
+ E2EStep,
72
+ E2EAction,
73
+
74
+ // Testing types
75
+ VisualTestConfig,
76
+ PerformanceMetrics,
77
+ AccessibilityResult,
78
+ AccessibilityViolation,
79
+ AccessibilityPass,
80
+ AccessibilityIncomplete,
81
+ AccessibilityNode,
82
+ AccessibilityCheckResult,
83
+ } from './types';
84
+
85
+ /**
86
+ * Global test suite registry
87
+ */
88
+ const testSuites: TestSuite[] = [];
89
+ let currentSuite: TestSuite | null = null;
90
+
91
+ /**
92
+ * Default test configuration
93
+ */
94
+ const DEFAULT_CONFIG: TestConfig = {
95
+ environment: 'jsdom',
96
+ browser: 'chromium',
97
+ timeout: 5000,
98
+ retries: 0,
99
+ parallel: false,
100
+ maxWorkers: 1,
101
+ headless: true,
102
+ viewport: {
103
+ width: 1280,
104
+ height: 720,
105
+ },
106
+ setupFiles: [],
107
+ teardownFiles: [],
108
+ reporters: [],
109
+ coverage: {
110
+ enabled: false,
111
+ threshold: 80,
112
+ include: ['src/**/*'],
113
+ exclude: ['**/*.test.*', '**/*.spec.*'],
114
+ },
115
+ fixtures: {},
116
+ };
117
+
118
+ /**
119
+ * Create a test suite
120
+ */
121
+ export function describe(name: string, fn: () => void): void;
122
+ export function describe(name: string, options: Partial<TestSuite>, fn: () => void): void;
123
+ export function describe(
124
+ name: string,
125
+ optionsOrFn: Partial<TestSuite> | (() => void),
126
+ fn?: () => void
127
+ ): void {
128
+ const actualOptions = typeof optionsOrFn === 'function' ? {} : optionsOrFn;
129
+ const actualFn = typeof optionsOrFn === 'function' ? optionsOrFn : fn!;
130
+
131
+ const suite: TestSuite = {
132
+ name,
133
+ ...(actualOptions.description !== undefined ? { description: actualOptions.description } : {}),
134
+ ...(actualOptions.setup !== undefined ? { setup: actualOptions.setup } : {}),
135
+ ...(actualOptions.teardown !== undefined ? { teardown: actualOptions.teardown } : {}),
136
+ ...(actualOptions.beforeEach !== undefined ? { beforeEach: actualOptions.beforeEach } : {}),
137
+ ...(actualOptions.afterEach !== undefined ? { afterEach: actualOptions.afterEach } : {}),
138
+ tests: [],
139
+ suites: [],
140
+ ...(actualOptions.timeout !== undefined ? { timeout: actualOptions.timeout } : {}),
141
+ ...(actualOptions.retries !== undefined ? { retries: actualOptions.retries } : {}),
142
+ ...(actualOptions.skip !== undefined ? { skip: actualOptions.skip } : {}),
143
+ ...(actualOptions.only !== undefined ? { only: actualOptions.only } : {}),
144
+ };
145
+
146
+ const parentSuite = currentSuite;
147
+ if (parentSuite) {
148
+ parentSuite.suites.push(suite);
149
+ } else {
150
+ testSuites.push(suite);
151
+ }
152
+
153
+ currentSuite = suite;
154
+ actualFn();
155
+ currentSuite = parentSuite;
156
+ }
157
+
158
+ /**
159
+ * Create a test case
160
+ */
161
+ export function it(name: string, fn: TestFunction): void;
162
+ export function it(name: string, options: Partial<TestCase>, fn: TestFunction): void;
163
+ export function it(
164
+ name: string,
165
+ optionsOrFn: Partial<TestCase> | TestFunction,
166
+ fn?: TestFunction
167
+ ): void {
168
+ const actualOptions = typeof optionsOrFn === 'function' ? {} : optionsOrFn;
169
+ const actualFn = typeof optionsOrFn === 'function' ? optionsOrFn : fn!;
170
+
171
+ if (!currentSuite) {
172
+ throw new Error('Test case must be defined within a test suite (describe block)');
173
+ }
174
+
175
+ const test: TestCase = {
176
+ name,
177
+ ...(actualOptions.description !== undefined ? { description: actualOptions.description } : {}),
178
+ fn: actualFn,
179
+ ...(actualOptions.timeout !== undefined ? { timeout: actualOptions.timeout } : {}),
180
+ ...(actualOptions.retries !== undefined ? { retries: actualOptions.retries } : {}),
181
+ ...(actualOptions.skip !== undefined ? { skip: actualOptions.skip } : {}),
182
+ ...(actualOptions.only !== undefined ? { only: actualOptions.only } : {}),
183
+ tags: actualOptions.tags || [],
184
+ fixtures: actualOptions.fixtures || {},
185
+ };
186
+
187
+ currentSuite.tests.push(test);
188
+ }
189
+
190
+ /**
191
+ * Alias for it()
192
+ */
193
+ export const test = it;
194
+
195
+ /**
196
+ * Skip a test suite
197
+ */
198
+ export function describe_skip(name: string, fn: () => void): void {
199
+ describe(name, { skip: true }, fn);
200
+ }
201
+
202
+ /**
203
+ * Skip a test case
204
+ */
205
+ export function it_skip(name: string, fn: TestFunction): void {
206
+ it(name, { skip: true }, fn);
207
+ }
208
+
209
+ /**
210
+ * Focus on a test suite (only run this suite)
211
+ */
212
+ export function describe_only(name: string, fn: () => void): void {
213
+ describe(name, { only: true }, fn);
214
+ }
215
+
216
+ /**
217
+ * Focus on a test case (only run this test)
218
+ */
219
+ export function it_only(name: string, fn: TestFunction): void {
220
+ it(name, { only: true }, fn);
221
+ }
222
+
223
+ /**
224
+ * Setup function to run before all tests in a suite
225
+ */
226
+ export function beforeAll(fn: () => Promise<void> | void): void {
227
+ if (!currentSuite) {
228
+ throw new Error('beforeAll must be called within a test suite');
229
+ }
230
+ currentSuite.setup = fn;
231
+ }
232
+
233
+ /**
234
+ * Teardown function to run after all tests in a suite
235
+ */
236
+ export function afterAll(fn: () => Promise<void> | void): void {
237
+ if (!currentSuite) {
238
+ throw new Error('afterAll must be called within a test suite');
239
+ }
240
+ currentSuite.teardown = fn;
241
+ }
242
+
243
+ /**
244
+ * Setup function to run before each test in a suite
245
+ */
246
+ export function beforeEach(fn: () => Promise<void> | void): void {
247
+ if (!currentSuite) {
248
+ throw new Error('beforeEach must be called within a test suite');
249
+ }
250
+ currentSuite.beforeEach = fn;
251
+ }
252
+
253
+ /**
254
+ * Teardown function to run after each test in a suite
255
+ */
256
+ export function afterEach(fn: () => Promise<void> | void): void {
257
+ if (!currentSuite) {
258
+ throw new Error('afterEach must be called within a test suite');
259
+ }
260
+ currentSuite.afterEach = fn;
261
+ }
262
+
263
+ /**
264
+ * Run all registered test suites
265
+ */
266
+ export async function runTests(config: Partial<TestConfig> = {}): Promise<TestResult[]> {
267
+ const finalConfig = { ...DEFAULT_CONFIG, ...config };
268
+ const runner = createTestRunnerInternal(finalConfig);
269
+
270
+ // Filter suites based on 'only' flag
271
+ let suitesToRun = testSuites;
272
+ const onlySuites = testSuites.filter(suite => suite.only);
273
+ if (onlySuites.length > 0) {
274
+ suitesToRun = onlySuites;
275
+ }
276
+
277
+ return runner.run(suitesToRun, finalConfig);
278
+ }
279
+
280
+ /**
281
+ * Clear all registered test suites
282
+ */
283
+ export function clearTests(): void {
284
+ testSuites.length = 0;
285
+ currentSuite = null;
286
+ }
287
+
288
+ /**
289
+ * Create a test fixture
290
+ */
291
+ export function fixture<T>(
292
+ name: string,
293
+ setup: () => T | Promise<T>,
294
+ teardown?: (fixture: T) => void | Promise<void>
295
+ ) {
296
+ return {
297
+ name,
298
+ async create(): Promise<T> {
299
+ const instance = await setup();
300
+
301
+ // Store teardown function for later cleanup
302
+ if (teardown) {
303
+ (instance as any).__teardown = () => teardown(instance);
304
+ }
305
+
306
+ return instance;
307
+ },
308
+ async destroy(instance: T): Promise<void> {
309
+ if ((instance as any).__teardown) {
310
+ await (instance as any).__teardown();
311
+ }
312
+ },
313
+ };
314
+ }
315
+
316
+ /**
317
+ * Create a mock function
318
+ */
319
+ export function createMock<T extends (...args: any[]) => any>(
320
+ implementation?: T
321
+ ): import('./types').MockFunction {
322
+ const calls: any[][] = [];
323
+ const results: Array<{ type: 'return' | 'throw'; value: any }> = [];
324
+ let mockImplementation: ((...args: any[]) => any) | undefined = implementation;
325
+
326
+ const mockFn = function (...args: any[]) {
327
+ calls.push(args);
328
+
329
+ try {
330
+ if (mockImplementation) {
331
+ const result = mockImplementation(...args);
332
+ results.push({ type: 'return', value: result });
333
+ return result;
334
+ }
335
+
336
+ const result = undefined;
337
+ results.push({ type: 'return', value: result });
338
+ return result;
339
+ } catch (error) {
340
+ results.push({ type: 'throw', value: error });
341
+ throw error;
342
+ }
343
+ } as import('./types').MockFunction;
344
+
345
+ mockFn.mockReturnValue = (value: any) => {
346
+ mockImplementation = () => value;
347
+ return mockFn;
348
+ };
349
+
350
+ mockFn.mockReturnValueOnce = (value: any) => {
351
+ const originalImpl = mockImplementation;
352
+ let called = false;
353
+ mockImplementation = (...args: any[]) => {
354
+ if (!called) {
355
+ called = true;
356
+ return value;
357
+ }
358
+ return originalImpl ? originalImpl(...args) : undefined;
359
+ };
360
+ return mockFn;
361
+ };
362
+
363
+ mockFn.mockResolvedValue = (value: any) => {
364
+ mockImplementation = () => Promise.resolve(value);
365
+ return mockFn;
366
+ };
367
+
368
+ mockFn.mockRejectedValue = (error: any) => {
369
+ mockImplementation = () => Promise.reject(error);
370
+ return mockFn;
371
+ };
372
+
373
+ mockFn.mockImplementation = (fn: (...args: any[]) => any) => {
374
+ mockImplementation = fn;
375
+ return mockFn;
376
+ };
377
+
378
+ mockFn.mockClear = () => {
379
+ calls.length = 0;
380
+ results.length = 0;
381
+ return mockFn;
382
+ };
383
+
384
+ mockFn.mockReset = () => {
385
+ calls.length = 0;
386
+ results.length = 0;
387
+ mockImplementation = undefined;
388
+ return mockFn;
389
+ };
390
+
391
+ mockFn.mockRestore = () => {
392
+ calls.length = 0;
393
+ results.length = 0;
394
+ mockImplementation = implementation;
395
+ return mockFn;
396
+ };
397
+
398
+ Object.defineProperty(mockFn, 'calls', {
399
+ get: () => calls,
400
+ });
401
+
402
+ Object.defineProperty(mockFn, 'results', {
403
+ get: () => results,
404
+ });
405
+
406
+ return mockFn;
407
+ }
408
+
409
+ /**
410
+ * Create a spy function
411
+ */
412
+ export function createSpy<T extends (...args: any[]) => any>(
413
+ target: any,
414
+ method: string
415
+ ): import('./types').SpyFunction {
416
+ const original = target[method];
417
+ const spy = createMock(original) as import('./types').SpyFunction;
418
+
419
+ spy.original = original;
420
+ target[method] = spy;
421
+
422
+ const originalRestore = spy.mockRestore;
423
+ spy.mockRestore = () => {
424
+ target[method] = original;
425
+ return originalRestore();
426
+ };
427
+
428
+ return spy;
429
+ }
430
+
431
+ /**
432
+ * Wait for a condition to be true
433
+ */
434
+ export async function waitFor(
435
+ condition: () => boolean | Promise<boolean>,
436
+ options: { timeout?: number; interval?: number } = {}
437
+ ): Promise<void> {
438
+ const { timeout = 5000, interval = 100 } = options;
439
+ const startTime = Date.now();
440
+
441
+ while (Date.now() - startTime < timeout) {
442
+ if (await condition()) {
443
+ return;
444
+ }
445
+ await new Promise(resolve => setTimeout(resolve, interval));
446
+ }
447
+
448
+ throw new Error(`Condition not met within ${timeout}ms`);
449
+ }
450
+
451
+ /**
452
+ * Create a page object for E2E testing
453
+ */
454
+ export function createPageObject(definition: import('./types').PageObject) {
455
+ return {
456
+ ...definition,
457
+
458
+ async goto(page: any) {
459
+ if (definition.url) {
460
+ await page.goto(definition.url);
461
+ }
462
+ },
463
+
464
+ element(name: string) {
465
+ const selector = definition.selectors[name];
466
+ if (!selector) {
467
+ throw new Error(`Selector "${name}" not found in page object "${definition.name}"`);
468
+ }
469
+ return selector;
470
+ },
471
+
472
+ async action(page: any, name: string, ...args: any[]) {
473
+ const action = definition.actions[name];
474
+ if (!action) {
475
+ throw new Error(`Action "${name}" not found in page object "${definition.name}"`);
476
+ }
477
+ return action.call(this, page, ...args);
478
+ },
479
+
480
+ async assert(page: any, name: string, ...args: any[]) {
481
+ const assertion = definition.assertions[name];
482
+ if (!assertion) {
483
+ throw new Error(`Assertion "${name}" not found in page object "${definition.name}"`);
484
+ }
485
+ return assertion.call(this, page, ...args);
486
+ },
487
+ };
488
+ }
489
+
490
+ /**
491
+ * Integration with LokaScript core for behavior testing
492
+ */
493
+ export function createLokaScriptTestContext(hyperfixi: any) {
494
+ return {
495
+ // Compile and test hyperscript
496
+ async compileAndTest(script: string, element: Element, event?: string) {
497
+ const compiled = await hyperfixi.compile(script);
498
+
499
+ if (element && compiled) {
500
+ // Apply compiled script to element
501
+ element.setAttribute('data-compiled-script', compiled);
502
+
503
+ // Trigger event if specified
504
+ if (event) {
505
+ element.dispatchEvent(new Event(event));
506
+ }
507
+ }
508
+
509
+ return compiled;
510
+ },
511
+
512
+ // Test script compilation
513
+ async testCompilation(script: string) {
514
+ try {
515
+ const result = await hyperfixi.compile(script);
516
+ return { success: true, result };
517
+ } catch (error) {
518
+ return { success: false, error };
519
+ }
520
+ },
521
+
522
+ // Test script execution
523
+ async testExecution(script: string, element: Element, eventType: string = 'click') {
524
+ const compiled = await hyperfixi.compile(script);
525
+
526
+ if (compiled) {
527
+ // Setup execution tracking
528
+ let executed = false;
529
+ const originalConsoleLog = console.log;
530
+ console.log = (...args) => {
531
+ if (args.includes('HYPERSCRIPT_EXECUTED')) {
532
+ executed = true;
533
+ }
534
+ originalConsoleLog(...args);
535
+ };
536
+
537
+ try {
538
+ // Apply script and trigger event
539
+ element.setAttribute('data-hyperscript', script);
540
+ element.dispatchEvent(new Event(eventType));
541
+
542
+ // Wait for execution
543
+ await new Promise(resolve => setTimeout(resolve, 100));
544
+
545
+ return { executed, compiled };
546
+ } finally {
547
+ console.log = originalConsoleLog;
548
+ }
549
+ }
550
+
551
+ return { executed: false, compiled: null };
552
+ },
553
+ };
554
+ }
555
+
556
+ /**
557
+ * Quick start function for basic testing setup
558
+ */
559
+ export async function quickStartTesting(
560
+ options: {
561
+ environment?: TestEnvironment;
562
+ browser?: BrowserType;
563
+ headless?: boolean;
564
+ baseURL?: string;
565
+ testFiles?: string[];
566
+ } = {}
567
+ ) {
568
+ const config: Partial<TestConfig> = {
569
+ environment: options.environment || 'jsdom',
570
+ browser: options.browser || 'chromium',
571
+ headless: options.headless !== false,
572
+ ...(options.baseURL !== undefined ? { baseURL: options.baseURL } : {}),
573
+ };
574
+
575
+ // Load test files if specified
576
+ if (options.testFiles) {
577
+ for (const file of options.testFiles) {
578
+ try {
579
+ await import(file);
580
+ } catch (error) {
581
+ console.error(`Failed to load test file ${file}:`, error);
582
+ }
583
+ }
584
+ }
585
+
586
+ return {
587
+ config,
588
+ run: () => runTests(config),
589
+ clear: clearTests,
590
+ };
591
+ }
592
+
593
+ /**
594
+ * Version information
595
+ */
596
+ export const VERSION = '0.1.0';
597
+
598
+ /**
599
+ * Default test configuration
600
+ */
601
+ export { DEFAULT_CONFIG };
package/src/jsdom.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ declare module 'jsdom' {
2
+ export class JSDOM {
3
+ constructor(html?: string, options?: any);
4
+ window: any;
5
+ serialize(): string;
6
+ }
7
+ }