@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
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse Validator - Validates hyperscript parsing across languages
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { MultilingualHyperscript } from '@hyperfixi/core/multilingual';
|
|
6
|
+
import type { SemanticNode } from '@lokascript/semantic';
|
|
7
|
+
import type { PatternTranslation, ParseResult, Validator } from '../types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Parse Validator
|
|
11
|
+
*
|
|
12
|
+
* Validates that hyperscript patterns parse correctly using the semantic parser.
|
|
13
|
+
*/
|
|
14
|
+
export class ParseValidator implements Validator<ParseResult[]> {
|
|
15
|
+
private ml: MultilingualHyperscript;
|
|
16
|
+
private initialized = false;
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
this.ml = new MultilingualHyperscript();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initialize the validator
|
|
24
|
+
*/
|
|
25
|
+
async initialize(): Promise<void> {
|
|
26
|
+
if (this.initialized) return;
|
|
27
|
+
await this.ml.initialize();
|
|
28
|
+
this.initialized = true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get validator name
|
|
33
|
+
*/
|
|
34
|
+
getName(): string {
|
|
35
|
+
return 'ParseValidator';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate patterns
|
|
40
|
+
*/
|
|
41
|
+
async validate(patterns: PatternTranslation[]): Promise<ParseResult[]> {
|
|
42
|
+
await this.initialize();
|
|
43
|
+
|
|
44
|
+
const results: ParseResult[] = [];
|
|
45
|
+
|
|
46
|
+
for (const pattern of patterns) {
|
|
47
|
+
const result = await this.validatePattern(pattern);
|
|
48
|
+
results.push(result);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validate a single pattern
|
|
56
|
+
*/
|
|
57
|
+
private async validatePattern(pattern: PatternTranslation): Promise<ParseResult> {
|
|
58
|
+
const startTime = performance.now();
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Parse the hyperscript
|
|
62
|
+
const semanticNode = (await this.ml.parse(
|
|
63
|
+
pattern.hyperscript,
|
|
64
|
+
pattern.language
|
|
65
|
+
)) as SemanticNode;
|
|
66
|
+
|
|
67
|
+
if (!semanticNode) {
|
|
68
|
+
return {
|
|
69
|
+
pattern,
|
|
70
|
+
success: false,
|
|
71
|
+
error: 'Parse returned null or undefined',
|
|
72
|
+
duration: performance.now() - startTime,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Extract command and roles from semantic node
|
|
77
|
+
const command = semanticNode.action;
|
|
78
|
+
const roles = this.extractRoles(semanticNode);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
pattern,
|
|
82
|
+
success: true,
|
|
83
|
+
command,
|
|
84
|
+
roles,
|
|
85
|
+
confidence: pattern.confidence, // Use pattern confidence
|
|
86
|
+
parser: 'semantic',
|
|
87
|
+
duration: performance.now() - startTime,
|
|
88
|
+
};
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return {
|
|
91
|
+
pattern,
|
|
92
|
+
success: false,
|
|
93
|
+
error: error instanceof Error ? error.message : String(error),
|
|
94
|
+
duration: performance.now() - startTime,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Extract semantic roles from node
|
|
101
|
+
*/
|
|
102
|
+
private extractRoles(node: SemanticNode): Record<string, unknown> {
|
|
103
|
+
const roles: Record<string, unknown> = {};
|
|
104
|
+
|
|
105
|
+
for (const [role, value] of Object.entries(node)) {
|
|
106
|
+
// Skip action and metadata fields
|
|
107
|
+
if (role === 'action' || role === 'type' || role === 'confidence') {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (value !== undefined && value !== null) {
|
|
112
|
+
roles[role] = value;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return roles;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Validate patterns in batches for better performance
|
|
121
|
+
*/
|
|
122
|
+
async validateBatch(
|
|
123
|
+
patterns: PatternTranslation[],
|
|
124
|
+
batchSize: number = 10
|
|
125
|
+
): Promise<ParseResult[]> {
|
|
126
|
+
await this.initialize();
|
|
127
|
+
|
|
128
|
+
const results: ParseResult[] = [];
|
|
129
|
+
|
|
130
|
+
for (let i = 0; i < patterns.length; i += batchSize) {
|
|
131
|
+
const batch = patterns.slice(i, i + batchSize);
|
|
132
|
+
const batchResults = await Promise.all(batch.map(pattern => this.validatePattern(pattern)));
|
|
133
|
+
results.push(...batchResults);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return results;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get statistics from parse results
|
|
141
|
+
*/
|
|
142
|
+
static getStatistics(results: ParseResult[]) {
|
|
143
|
+
const total = results.length;
|
|
144
|
+
const successful = results.filter(r => r.success).length;
|
|
145
|
+
const failed = total - successful;
|
|
146
|
+
|
|
147
|
+
const avgDuration = results.reduce((sum, r) => sum + r.duration, 0) / total;
|
|
148
|
+
|
|
149
|
+
const avgConfidence =
|
|
150
|
+
results
|
|
151
|
+
.filter(r => r.success && r.confidence !== undefined)
|
|
152
|
+
.reduce((sum, r) => sum + (r.confidence || 0), 0) / (successful || 1);
|
|
153
|
+
|
|
154
|
+
const byParser: Record<string, number> = {};
|
|
155
|
+
for (const result of results) {
|
|
156
|
+
if (result.parser) {
|
|
157
|
+
byParser[result.parser] = (byParser[result.parser] || 0) + 1;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
total,
|
|
163
|
+
successful,
|
|
164
|
+
failed,
|
|
165
|
+
successRate: total > 0 ? successful / total : 0,
|
|
166
|
+
avgDuration,
|
|
167
|
+
avgConfidence,
|
|
168
|
+
byParser,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Quick validation helper function
|
|
175
|
+
*/
|
|
176
|
+
export async function validatePatterns(patterns: PatternTranslation[]): Promise<ParseResult[]> {
|
|
177
|
+
const validator = new ParseValidator();
|
|
178
|
+
return await validator.validate(patterns);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Validate patterns and return summary
|
|
183
|
+
*/
|
|
184
|
+
export async function validateAndSummarize(patterns: PatternTranslation[]) {
|
|
185
|
+
const results = await validatePatterns(patterns);
|
|
186
|
+
const stats = ParseValidator.getStatistics(results);
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
results,
|
|
190
|
+
stats,
|
|
191
|
+
failures: results.filter(r => !r.success),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Size Validator - Validates bundle sizes against thresholds
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { statSync, existsSync } from 'node:fs';
|
|
6
|
+
import { exec } from 'node:child_process';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
import type { SizeResult, Validator, BundleInfo } from '../types';
|
|
9
|
+
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Bundle size thresholds (bytes)
|
|
14
|
+
*/
|
|
15
|
+
const SIZE_THRESHOLDS = {
|
|
16
|
+
browser: 300 * 1024, // 300 KB for full bundle
|
|
17
|
+
'browser-priority': 200 * 1024, // 200 KB for priority languages
|
|
18
|
+
'browser-western': 150 * 1024, // 150 KB for western languages
|
|
19
|
+
'browser-east-asian': 120 * 1024, // 120 KB for east asian
|
|
20
|
+
'browser-en': 80 * 1024, // 80 KB for single language
|
|
21
|
+
'browser-es': 80 * 1024,
|
|
22
|
+
'browser-ja': 90 * 1024,
|
|
23
|
+
'browser-ar': 85 * 1024,
|
|
24
|
+
'browser-ko': 80 * 1024,
|
|
25
|
+
'browser-zh': 75 * 1024,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Default threshold for unknown bundles (100 KB)
|
|
30
|
+
*/
|
|
31
|
+
const DEFAULT_THRESHOLD = 100 * 1024;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Size Validator
|
|
35
|
+
*
|
|
36
|
+
* Validates bundle sizes and tracks size regression.
|
|
37
|
+
*/
|
|
38
|
+
export class SizeValidator implements Validator<SizeResult> {
|
|
39
|
+
private thresholds: Record<string, number>;
|
|
40
|
+
|
|
41
|
+
constructor(customThresholds?: Record<string, number>) {
|
|
42
|
+
this.thresholds = { ...SIZE_THRESHOLDS, ...customThresholds };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get validator name
|
|
47
|
+
*/
|
|
48
|
+
getName(): string {
|
|
49
|
+
return 'SizeValidator';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Validate bundle size
|
|
54
|
+
*/
|
|
55
|
+
async validate(bundle: BundleInfo): Promise<SizeResult> {
|
|
56
|
+
if (!existsSync(bundle.path)) {
|
|
57
|
+
throw new Error(`Bundle not found: ${bundle.path}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const size = statSync(bundle.path).size;
|
|
61
|
+
const threshold = this.thresholds[bundle.name] || DEFAULT_THRESHOLD;
|
|
62
|
+
|
|
63
|
+
// Try to get gzip size
|
|
64
|
+
let gzipSize: number | undefined;
|
|
65
|
+
try {
|
|
66
|
+
gzipSize = await this.getGzipSize(bundle.path);
|
|
67
|
+
} catch {
|
|
68
|
+
// Gzip measurement failed, continue without it
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
bundlePath: bundle.path,
|
|
73
|
+
size,
|
|
74
|
+
gzipSize,
|
|
75
|
+
exceedsThreshold: size > threshold,
|
|
76
|
+
threshold,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get gzipped size of a file
|
|
82
|
+
*/
|
|
83
|
+
private async getGzipSize(filePath: string): Promise<number> {
|
|
84
|
+
try {
|
|
85
|
+
// Use gzip -c to compress to stdout and wc -c to count bytes
|
|
86
|
+
const { stdout } = await execAsync(`gzip -c "${filePath}" | wc -c`);
|
|
87
|
+
return parseInt(stdout.trim(), 10);
|
|
88
|
+
} catch {
|
|
89
|
+
// Fallback: estimate as ~35% of original size
|
|
90
|
+
const size = statSync(filePath).size;
|
|
91
|
+
return Math.round(size * 0.35);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Validate multiple bundles
|
|
97
|
+
*/
|
|
98
|
+
async validateAll(bundles: BundleInfo[]): Promise<SizeResult[]> {
|
|
99
|
+
const results: SizeResult[] = [];
|
|
100
|
+
|
|
101
|
+
for (const bundle of bundles) {
|
|
102
|
+
if (bundle.exists) {
|
|
103
|
+
const result = await this.validate(bundle);
|
|
104
|
+
results.push(result);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return results;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if size increase is acceptable
|
|
113
|
+
*/
|
|
114
|
+
isSizeIncreaseAcceptable(
|
|
115
|
+
currentSize: number,
|
|
116
|
+
baselineSize: number,
|
|
117
|
+
maxIncrease: number = 0.1 // 10% by default
|
|
118
|
+
): boolean {
|
|
119
|
+
if (baselineSize === 0) return true;
|
|
120
|
+
const increase = (currentSize - baselineSize) / baselineSize;
|
|
121
|
+
return increase <= maxIncrease;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get size change percentage
|
|
126
|
+
*/
|
|
127
|
+
getSizeChange(currentSize: number, baselineSize: number): number {
|
|
128
|
+
if (baselineSize === 0) return 0;
|
|
129
|
+
return ((currentSize - baselineSize) / baselineSize) * 100;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Format size in human-readable format
|
|
134
|
+
*/
|
|
135
|
+
static formatSize(bytes: number): string {
|
|
136
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
137
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
|
|
138
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get statistics from size results
|
|
143
|
+
*/
|
|
144
|
+
static getStatistics(results: SizeResult[]) {
|
|
145
|
+
const total = results.length;
|
|
146
|
+
const exceedingThreshold = results.filter(r => r.exceedsThreshold).length;
|
|
147
|
+
|
|
148
|
+
const totalSize = results.reduce((sum, r) => sum + r.size, 0);
|
|
149
|
+
const totalGzipSize = results.reduce((sum, r) => sum + (r.gzipSize || 0), 0);
|
|
150
|
+
|
|
151
|
+
const avgSize = totalSize / (total || 1);
|
|
152
|
+
const avgGzipSize = totalGzipSize / (total || 1);
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
total,
|
|
156
|
+
exceedingThreshold,
|
|
157
|
+
totalSize,
|
|
158
|
+
totalGzipSize,
|
|
159
|
+
avgSize,
|
|
160
|
+
avgGzipSize,
|
|
161
|
+
compressionRatio: totalSize > 0 ? totalGzipSize / totalSize : 0,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Quick validation helper
|
|
168
|
+
*/
|
|
169
|
+
export async function validateBundleSize(bundle: BundleInfo): Promise<SizeResult> {
|
|
170
|
+
const validator = new SizeValidator();
|
|
171
|
+
return await validator.validate(bundle);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Validate sizes and return summary
|
|
176
|
+
*/
|
|
177
|
+
export async function validateAndSummarizeSizes(bundles: BundleInfo[]) {
|
|
178
|
+
const validator = new SizeValidator();
|
|
179
|
+
const results = await validator.validateAll(bundles);
|
|
180
|
+
const stats = SizeValidator.getStatistics(results);
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
results,
|
|
184
|
+
stats,
|
|
185
|
+
exceeding: results.filter(r => r.exceedsThreshold),
|
|
186
|
+
};
|
|
187
|
+
}
|