@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.
- package/dist/assertions-BImuwZsB.d.mts +458 -0
- package/dist/assertions-BImuwZsB.d.ts +458 -0
- package/dist/assertions.d.mts +1 -0
- package/dist/assertions.d.ts +1 -0
- package/dist/assertions.js +625 -0
- package/dist/assertions.js.map +1 -0
- package/dist/assertions.mjs +621 -0
- package/dist/assertions.mjs.map +1 -0
- package/dist/index.d.mts +246 -0
- package/dist/index.d.ts +246 -0
- package/dist/index.js +1465 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1435 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +121 -0
- package/src/assertions.test.ts +490 -0
- package/src/assertions.ts +779 -0
- package/src/index.test.ts +490 -0
- package/src/index.ts +601 -0
- package/src/jsdom.d.ts +7 -0
- package/src/multilingual/README.md +295 -0
- package/src/multilingual/bundle-builder.ts +345 -0
- package/src/multilingual/cli.ts +224 -0
- package/src/multilingual/index.ts +68 -0
- package/src/multilingual/orchestrator.ts +275 -0
- package/src/multilingual/pattern-loader.ts +176 -0
- package/src/multilingual/reporters/console-reporter.ts +294 -0
- package/src/multilingual/reporters/json-reporter.ts +99 -0
- package/src/multilingual/reporters/regression-reporter.ts +263 -0
- package/src/multilingual/tools/analyze-failures.ts +326 -0
- package/src/multilingual/types.ts +268 -0
- package/src/multilingual/validators/parse-diagnostics.ts +337 -0
- package/src/multilingual/validators/parse-validator.ts +193 -0
- package/src/multilingual/validators/size-validator.ts +187 -0
- package/src/runner.test.ts +813 -0
- package/src/runner.ts +639 -0
- 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 };
|