@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,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
+ }