@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,263 @@
1
+ /**
2
+ * Regression Reporter - Compares current results against baseline
3
+ */
4
+
5
+ import { readFileSync, existsSync, writeFileSync } from 'node:fs';
6
+ import type {
7
+ Reporter,
8
+ TestConfig,
9
+ LanguageCode,
10
+ LanguageResults,
11
+ TestResults,
12
+ Baseline,
13
+ RegressionResult,
14
+ } from '../types';
15
+
16
+ /**
17
+ * Regression Reporter
18
+ *
19
+ * Compares test results against a baseline and reports regressions.
20
+ */
21
+ export class RegressionReporter implements Reporter {
22
+ private baselinePath: string;
23
+ private baseline: Baseline | null = null;
24
+ private currentResults: TestResults | null = null;
25
+ private regressionResults: RegressionResult[] = [];
26
+
27
+ constructor(baselinePath: string = './test-results/baseline.json') {
28
+ this.baselinePath = baselinePath;
29
+ this.loadBaseline();
30
+ }
31
+
32
+ /**
33
+ * Load baseline from file
34
+ */
35
+ private loadBaseline(): void {
36
+ if (!existsSync(this.baselinePath)) {
37
+ console.warn(`Warning: Baseline file not found at ${this.baselinePath}`);
38
+ return;
39
+ }
40
+
41
+ try {
42
+ const data = readFileSync(this.baselinePath, 'utf-8');
43
+ this.baseline = JSON.parse(data);
44
+ } catch (error) {
45
+ console.error(
46
+ `Error loading baseline: ${error instanceof Error ? error.message : String(error)}`
47
+ );
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Report test start
53
+ */
54
+ reportStart(_config: TestConfig): void {
55
+ // No-op
56
+ }
57
+
58
+ /**
59
+ * Report language start
60
+ */
61
+ reportLanguageStart(_language: LanguageCode, _bundle: string): void {
62
+ // No-op
63
+ }
64
+
65
+ /**
66
+ * Report language complete
67
+ */
68
+ reportLanguageComplete(_results: LanguageResults): void {
69
+ // No-op
70
+ }
71
+
72
+ /**
73
+ * Report complete test run - compare to baseline
74
+ */
75
+ reportComplete(results: TestResults): void {
76
+ this.currentResults = results;
77
+
78
+ if (!this.baseline) {
79
+ console.log('No baseline available - skipping regression analysis');
80
+ return;
81
+ }
82
+
83
+ this.regressionResults = this.compareToBaseline(results);
84
+ }
85
+
86
+ /**
87
+ * Get regression results
88
+ */
89
+ getRegressionResults(): RegressionResult[] {
90
+ return this.regressionResults;
91
+ }
92
+
93
+ /**
94
+ * Report error
95
+ */
96
+ reportError(_error: Error): void {
97
+ // No-op
98
+ }
99
+
100
+ /**
101
+ * Compare current results to baseline
102
+ */
103
+ private compareToBaseline(results: TestResults): RegressionResult[] {
104
+ if (!this.baseline) return [];
105
+
106
+ const regressions: RegressionResult[] = [];
107
+
108
+ for (const langResult of results.languageResults) {
109
+ const baselineLang = this.baseline.languages[langResult.language];
110
+ if (!baselineLang) continue;
111
+
112
+ const parseRateDelta = (langResult.parseRate - baselineLang.parseRate) * 100;
113
+ const avgConfidenceDelta = langResult.avgConfidence - baselineLang.avgConfidence;
114
+ const bundleSizeDelta = this.getBundleSizeDelta(langResult, baselineLang);
115
+
116
+ // Identify new failures and successes
117
+ const newFailures = this.findNewFailures(langResult, baselineLang);
118
+ const newSuccesses = this.findNewSuccesses(langResult, baselineLang);
119
+
120
+ // Determine status
121
+ let status: 'improved' | 'regressed' | 'unchanged' = 'unchanged';
122
+ if (parseRateDelta < -5 || newFailures.length > 0) {
123
+ status = 'regressed';
124
+ } else if (parseRateDelta > 5 || newSuccesses.length > 0) {
125
+ status = 'improved';
126
+ }
127
+
128
+ regressions.push({
129
+ language: langResult.language,
130
+ parseRateDelta,
131
+ avgConfidenceDelta,
132
+ bundleSizeDelta: bundleSizeDelta !== undefined ? bundleSizeDelta : undefined,
133
+ newFailures,
134
+ newSuccesses,
135
+ status,
136
+ });
137
+ }
138
+
139
+ return regressions;
140
+ }
141
+
142
+ /**
143
+ * Get bundle size delta
144
+ */
145
+ private getBundleSizeDelta(
146
+ current: LanguageResults,
147
+ baseline: { bundleSize: number | undefined }
148
+ ): number | undefined {
149
+ if (!current.bundleSize || !baseline.bundleSize) return undefined;
150
+ return ((current.bundleSize - baseline.bundleSize) / baseline.bundleSize) * 100;
151
+ }
152
+
153
+ /**
154
+ * Find patterns that failed in current but passed in baseline
155
+ */
156
+ private findNewFailures(
157
+ current: LanguageResults,
158
+ baseline: {
159
+ parseSuccess: number;
160
+ patterns: Record<string, { success: boolean; confidence: number | undefined }> | undefined;
161
+ }
162
+ ): string[] {
163
+ // If no pattern-level baseline, return empty array
164
+ if (!baseline.patterns) return [];
165
+
166
+ const newFailures: string[] = [];
167
+
168
+ for (const result of current.parseResults) {
169
+ const baselinePattern = baseline.patterns[result.pattern.codeExampleId];
170
+ if (baselinePattern && baselinePattern.success && !result.success) {
171
+ newFailures.push(result.pattern.codeExampleId);
172
+ }
173
+ }
174
+
175
+ return newFailures.slice(0, 10); // Limit to 10 for reporting
176
+ }
177
+
178
+ /**
179
+ * Find patterns that passed in current but failed in baseline
180
+ */
181
+ private findNewSuccesses(
182
+ current: LanguageResults,
183
+ baseline: {
184
+ parseSuccess: number;
185
+ patterns: Record<string, { success: boolean; confidence: number | undefined }> | undefined;
186
+ }
187
+ ): string[] {
188
+ // If no pattern-level baseline, return empty array
189
+ if (!baseline.patterns) return [];
190
+
191
+ const newSuccesses: string[] = [];
192
+
193
+ for (const result of current.parseResults) {
194
+ const baselinePattern = baseline.patterns[result.pattern.codeExampleId];
195
+ if (baselinePattern && !baselinePattern.success && result.success) {
196
+ newSuccesses.push(result.pattern.codeExampleId);
197
+ }
198
+ }
199
+
200
+ return newSuccesses.slice(0, 10); // Limit to 10 for reporting
201
+ }
202
+
203
+ /**
204
+ * Save current results as new baseline
205
+ */
206
+ saveAsBaseline(results: TestResults): void {
207
+ const baseline: Baseline = {
208
+ timestamp: results.timestamp,
209
+ commit: results.commit || 'unknown',
210
+ languages: {},
211
+ bundles: {} as Record<
212
+ string,
213
+ { size: number; languages: LanguageCode[]; gzipSize: number | undefined }
214
+ >,
215
+ };
216
+
217
+ // Convert bundles to proper format
218
+ for (const [name, info] of Object.entries(results.bundles)) {
219
+ baseline.bundles[name] = {
220
+ size: info.size,
221
+ languages: info.languages,
222
+ gzipSize: info.gzipSize ?? undefined,
223
+ };
224
+ }
225
+
226
+ for (const langResult of results.languageResults) {
227
+ // Build pattern-level tracking
228
+ const patterns: Record<string, { success: boolean; confidence: number | undefined }> = {};
229
+ for (const result of langResult.parseResults) {
230
+ patterns[result.pattern.codeExampleId] = {
231
+ success: result.success,
232
+ confidence: result.confidence,
233
+ };
234
+ }
235
+
236
+ baseline.languages[langResult.language] = {
237
+ parseSuccess: langResult.parseSuccess,
238
+ parseFailure: langResult.parseFailure,
239
+ parseRate: langResult.parseRate,
240
+ avgConfidence: langResult.avgConfidence,
241
+ bundleSize: langResult.bundleSize ?? undefined,
242
+ patterns,
243
+ };
244
+ }
245
+
246
+ writeFileSync(this.baselinePath, JSON.stringify(baseline, null, 2));
247
+ console.log(`Baseline saved to ${this.baselinePath}`);
248
+ }
249
+
250
+ /**
251
+ * Check if baseline exists
252
+ */
253
+ hasBaseline(): boolean {
254
+ return this.baseline !== null;
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Create a regression reporter
260
+ */
261
+ export function createRegressionReporter(baselinePath?: string): RegressionReporter {
262
+ return new RegressionReporter(baselinePath);
263
+ }
@@ -0,0 +1,326 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Parse Failure Analysis Tool
4
+ *
5
+ * Analyzes parse failures to identify patterns and suggest fixes
6
+ */
7
+
8
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ import { FailureAnalyzer } from '../validators/parse-diagnostics';
11
+ import type { TestResults, ParseResult, LanguageCode } from '../types';
12
+
13
+ interface AnalysisOptions {
14
+ language: LanguageCode | undefined;
15
+ verbose: boolean;
16
+ outputFile: string | undefined;
17
+ }
18
+
19
+ /**
20
+ * Parse command-line arguments
21
+ */
22
+ function parseArgs(): AnalysisOptions {
23
+ const args = process.argv.slice(2);
24
+ const options: AnalysisOptions = {
25
+ language: undefined,
26
+ verbose: false,
27
+ outputFile: undefined,
28
+ };
29
+
30
+ for (let i = 0; i < args.length; i++) {
31
+ switch (args[i]) {
32
+ case '--language':
33
+ case '-l': {
34
+ const lang = args[++i];
35
+ if (lang) options.language = lang as LanguageCode;
36
+ break;
37
+ }
38
+
39
+ case '--verbose':
40
+ case '-v':
41
+ options.verbose = true;
42
+ break;
43
+
44
+ case '--output':
45
+ case '-o': {
46
+ const file = args[++i];
47
+ if (file) options.outputFile = file;
48
+ break;
49
+ }
50
+
51
+ case '--help':
52
+ case '-h':
53
+ showHelp();
54
+ process.exit(0);
55
+ break;
56
+ }
57
+ }
58
+
59
+ return options;
60
+ }
61
+
62
+ /**
63
+ * Show help message
64
+ */
65
+ function showHelp(): void {
66
+ console.log(`
67
+ Parse Failure Analysis Tool
68
+
69
+ Analyzes test results to identify failure patterns and suggest improvements.
70
+
71
+ USAGE:
72
+ npm run analyze-failures [OPTIONS]
73
+
74
+ OPTIONS:
75
+ -l, --language <code> Analyze specific language only
76
+ -v, --verbose Show detailed output
77
+ -o, --output <file> Write report to file
78
+ -h, --help Show this help message
79
+
80
+ EXAMPLES:
81
+ # Analyze all failures
82
+ npm run analyze-failures
83
+
84
+ # Analyze Japanese failures with verbose output
85
+ npm run analyze-failures -- --language ja --verbose
86
+
87
+ # Generate report file
88
+ npm run analyze-failures -- --output failure-analysis.md
89
+ `);
90
+ }
91
+
92
+ /**
93
+ * Load test results
94
+ */
95
+ function loadResults(): TestResults | undefined {
96
+ const resultsPath = join(process.cwd(), 'test-results', 'results.json');
97
+
98
+ if (!existsSync(resultsPath)) {
99
+ console.error('Error: No test results found at:', resultsPath);
100
+ console.error('Run tests first: npm run test:multilingual -- --full');
101
+ return undefined;
102
+ }
103
+
104
+ const data = readFileSync(resultsPath, 'utf-8');
105
+ return JSON.parse(data);
106
+ }
107
+
108
+ /**
109
+ * Extract all parse results from test results
110
+ */
111
+ function extractParseResults(
112
+ results: TestResults,
113
+ language: LanguageCode | undefined
114
+ ): ParseResult[] {
115
+ let allResults: ParseResult[] = [];
116
+
117
+ for (const langResult of results.languageResults) {
118
+ if (language && langResult.language !== language) continue;
119
+ allResults = allResults.concat(langResult.parseResults);
120
+ }
121
+
122
+ return allResults;
123
+ }
124
+
125
+ /**
126
+ * Generate detailed failure breakdown
127
+ */
128
+ function generateDetailedBreakdown(analyzer: FailureAnalyzer, results: ParseResult[]): string {
129
+ const failures = results.filter(r => !r.success);
130
+ const byLanguage = analyzer.analyzeByLanguage(results);
131
+
132
+ let output = '\n';
133
+ output += '═══════════════════════════════════════════════════════════════\n';
134
+ output += ' DETAILED FAILURE BREAKDOWN\n';
135
+ output += '═══════════════════════════════════════════════════════════════\n\n';
136
+
137
+ const sortedLangs = Object.entries(byLanguage)
138
+ .sort(([, a], [, b]) => b.failures - a.failures)
139
+ .filter(([, data]) => data.failures > 0);
140
+
141
+ for (const [lang, data] of sortedLangs) {
142
+ const failRate = ((data.failures / data.totalPatterns) * 100).toFixed(1);
143
+ const passRate = (100 - parseFloat(failRate)).toFixed(1);
144
+
145
+ output += `\n${lang.toUpperCase()}\n`;
146
+ output += '─'.repeat(60) + '\n';
147
+ output += ` Status: ${data.failures}/${data.totalPatterns} failures (${failRate}% fail, ${passRate}% pass)\n\n`;
148
+
149
+ output += ' Common Error Types:\n';
150
+ const errorCounts: Map<string, number> = new Map();
151
+ const langFailures = failures.filter(f => f.pattern.language === lang);
152
+
153
+ for (const failure of langFailures) {
154
+ const err = failure.error || 'Unknown';
155
+ errorCounts.set(err, (errorCounts.get(err) || 0) + 1);
156
+ }
157
+
158
+ const sortedErrors = Array.from(errorCounts.entries())
159
+ .sort(([, a], [, b]) => b - a)
160
+ .slice(0, 5);
161
+
162
+ for (const [error, count] of sortedErrors) {
163
+ const pct = ((count / data.failures) * 100).toFixed(1);
164
+ output += ` - ${error}: ${count} (${pct}%)\n`;
165
+ }
166
+
167
+ output += '\n Problematic Patterns (first 5):\n';
168
+ data.problematicPatterns.slice(0, 5).forEach((pattern: string, i: number) => {
169
+ output += ` ${i + 1}. "${pattern}"\n`;
170
+ });
171
+
172
+ output += '\n';
173
+ }
174
+
175
+ return output;
176
+ }
177
+
178
+ /**
179
+ * Generate recommendations
180
+ */
181
+ function generateRecommendations(analyzer: FailureAnalyzer, results: ParseResult[]): string {
182
+ const failures = results.filter(r => !r.success);
183
+ const patterns = analyzer.identifyFailurePatterns(failures);
184
+ const byLanguage = analyzer.analyzeByLanguage(results);
185
+
186
+ let output = '\n';
187
+ output += '═══════════════════════════════════════════════════════════════\n';
188
+ output += ' RECOMMENDATIONS\n';
189
+ output += '═══════════════════════════════════════════════════════════════\n\n';
190
+
191
+ // Language-specific recommendations
192
+ const sovLanguages: LanguageCode[] = ['ja', 'ko', 'tr'];
193
+ const vsoLanguages: LanguageCode[] = ['ar'];
194
+ const sovFailures = sovLanguages.filter(
195
+ lang => byLanguage[lang] && byLanguage[lang].failures > byLanguage[lang].totalPatterns * 0.3
196
+ );
197
+ const vsoFailures = vsoLanguages.filter(
198
+ lang => byLanguage[lang] && byLanguage[lang].failures > byLanguage[lang].totalPatterns * 0.3
199
+ );
200
+
201
+ if (sovFailures.length > 0) {
202
+ output +=
203
+ '## SOV Language Issues (' + sovFailures.map(l => l.toUpperCase()).join(', ') + ')\n\n';
204
+ output += ' Priority Actions:\n';
205
+ output += ' 1. Improve particle detection and positioning\n';
206
+ output += ' 2. Enhance verb conjugation recognition\n';
207
+ output += ' 3. Add support for complex clause patterns\n\n';
208
+ output += ' Implementation Files:\n';
209
+ output += ' - packages/semantic/src/tokenizers/ja.ts (Japanese)\n';
210
+ output += ' - packages/semantic/src/tokenizers/ko.ts (Korean)\n';
211
+ output += ' - packages/semantic/src/tokenizers/tr.ts (Turkish)\n';
212
+ output += ' - packages/semantic/src/parser/semantic-parser.ts\n\n';
213
+ }
214
+
215
+ if (vsoFailures.length > 0) {
216
+ output +=
217
+ '## VSO Language Issues (' + vsoFailures.map(l => l.toUpperCase()).join(', ') + ')\n\n';
218
+ output += ' Priority Actions:\n';
219
+ output += ' 1. Implement verb-first pattern matching\n';
220
+ output += ' 2. Enhance preposition handling\n';
221
+ output += ' 3. Improve right-to-left text processing\n\n';
222
+ output += ' Implementation Files:\n';
223
+ output += ' - packages/semantic/src/tokenizers/ar.ts\n';
224
+ output += ' - packages/semantic/src/parser/semantic-parser.ts\n\n';
225
+ }
226
+
227
+ // Pattern-specific recommendations
228
+ output += '## Common Failure Patterns\n\n';
229
+
230
+ if (patterns.multiClause > failures.length * 0.2) {
231
+ output += ` ⚠️ Multi-clause patterns: ${patterns.multiClause} failures\n`;
232
+ output += ' → Improve conjunction handling (それから/then/ثم/그러면)\n';
233
+ output += ' → Better clause separation logic\n\n';
234
+ }
235
+
236
+ if (patterns.styleProperties > failures.length * 0.15) {
237
+ output += ` ⚠️ Style property access: ${patterns.styleProperties} failures\n`;
238
+ output += ' → Add *property syntax support across all languages\n';
239
+ output += ' → Validate CSS property name recognition\n\n';
240
+ }
241
+
242
+ if (patterns.eventModifiers > failures.length * 0.15) {
243
+ output += ` ⚠️ Event modifiers: ${patterns.eventModifiers} failures\n`;
244
+ output += ' → Improve bracket syntax parsing\n';
245
+ output += ' → Add debounced/throttled keyword support\n\n';
246
+ }
247
+
248
+ if (patterns.conditionals > failures.length * 0.15) {
249
+ output += ` ⚠️ Conditionals: ${patterns.conditionals} failures\n`;
250
+ output += ' → Enhance if/else block parsing\n';
251
+ output += ' → Add match/conditional keyword recognition\n\n';
252
+ }
253
+
254
+ if (patterns.windowEvents > failures.length * 0.1) {
255
+ output += ` ⚠️ Window events: ${patterns.windowEvents} failures\n`;
256
+ output += ' → Add window/document scope handling\n';
257
+ output += ' → Validate event target resolution\n\n';
258
+ }
259
+
260
+ if (patterns.complexSelectors > failures.length * 0.1) {
261
+ output += ` ⚠️ Complex selectors: ${patterns.complexSelectors} failures\n`;
262
+ output += ' → Improve closest/first/parent selector parsing\n';
263
+ output += ' → Add HTML literal syntax support (<div.class/>)\n\n';
264
+ }
265
+
266
+ return output;
267
+ }
268
+
269
+ /**
270
+ * Main analysis function
271
+ */
272
+ async function main(): Promise<void> {
273
+ const options = parseArgs();
274
+
275
+ console.log('Loading test results...');
276
+ const results = loadResults();
277
+ if (!results) {
278
+ process.exit(1);
279
+ }
280
+
281
+ console.log('Analyzing failures...\n');
282
+ const parseResults = extractParseResults(results, options.language);
283
+ const analyzer = new FailureAnalyzer();
284
+
285
+ // Generate basic report
286
+ const report = analyzer.generateReport(parseResults);
287
+
288
+ // Generate detailed breakdown
289
+ const breakdown = generateDetailedBreakdown(analyzer, parseResults);
290
+
291
+ // Generate recommendations
292
+ const recommendations = generateRecommendations(analyzer, parseResults);
293
+
294
+ // Combine all sections
295
+ const fullReport = report + breakdown + recommendations;
296
+
297
+ // Output
298
+ if (options.outputFile) {
299
+ writeFileSync(options.outputFile, fullReport);
300
+ console.log(`Report written to: ${options.outputFile}`);
301
+ } else {
302
+ console.log(fullReport);
303
+ }
304
+
305
+ // Summary
306
+ const failures = parseResults.filter(r => !r.success);
307
+ const failRate = ((failures.length / parseResults.length) * 100).toFixed(1);
308
+
309
+ console.log('\n═══════════════════════════════════════════════════════════════');
310
+ console.log(' ANALYSIS COMPLETE');
311
+ console.log('═══════════════════════════════════════════════════════════════\n');
312
+ console.log(` Analyzed: ${parseResults.length} patterns`);
313
+ console.log(` Failures: ${failures.length} (${failRate}%)`);
314
+ console.log(` Languages: ${options.language || 'all'}`);
315
+ console.log();
316
+ }
317
+
318
+ // Run if called directly
319
+ if (import.meta.url === `file://${process.argv[1]}`) {
320
+ main().catch(err => {
321
+ console.error('Error:', err.message);
322
+ process.exit(1);
323
+ });
324
+ }
325
+
326
+ export { main, parseArgs, showHelp };