@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,295 @@
1
+ # Multilingual Testing Framework
2
+
3
+ Automated validation system for HyperFixi's multilingual hyperscript support across 13 languages.
4
+
5
+ ## Overview
6
+
7
+ This framework validates the multilingual system by:
8
+
9
+ - Loading patterns from the patterns-reference database (689 translations)
10
+ - Building or selecting appropriate language bundles
11
+ - Validating parsing across all languages
12
+ - Tracking bundle sizes and performance
13
+ - Comparing against baselines for regression detection
14
+
15
+ ## Quick Start
16
+
17
+ ```bash
18
+ # Test all languages in quick mode (10 patterns/language)
19
+ npm run test:multilingual
20
+
21
+ # Test specific language with verbose output
22
+ npm run test:multilingual -- --language ja --verbose
23
+
24
+ # Test multiple languages in full mode
25
+ npm run test:multilingual -- --languages ja,ko,es --full
26
+
27
+ # Compare against baseline
28
+ npm run test:multilingual -- --regression
29
+
30
+ # Save current results as new baseline
31
+ npm run test:multilingual -- --save-baseline
32
+ ```
33
+
34
+ ## Architecture
35
+
36
+ ```
37
+ multilingual/
38
+ ├── cli.ts # Command-line interface
39
+ ├── orchestrator.ts # Main test runner
40
+ ├── types.ts # TypeScript types
41
+ ├── pattern-loader.ts # Database query layer
42
+ ├── bundle-builder.ts # Bundle selection/generation
43
+ ├── validators/
44
+ │ ├── parse-validator.ts # Parse validation
45
+ │ └── size-validator.ts # Bundle size validation
46
+ └── reporters/
47
+ ├── console-reporter.ts # Minimal console output
48
+ ├── json-reporter.ts # Structured JSON results
49
+ └── regression-reporter.ts # Baseline comparison
50
+ ```
51
+
52
+ ## Test Flow
53
+
54
+ 1. **Load Configuration** - Parse CLI arguments
55
+ 2. **Load Patterns** - Query patterns-reference database
56
+ 3. **Select/Build Bundle** - Find or generate appropriate bundle
57
+ 4. **Validate Parsing** - Test each pattern with semantic parser
58
+ 5. **Report Results** - Output to console, JSON, and regression reports
59
+
60
+ ## CLI Options
61
+
62
+ ### Language Selection
63
+
64
+ ```bash
65
+ -l, --language <code> # Test specific language (en, ja, es, etc.)
66
+ --languages <codes> # Test multiple languages (comma-separated)
67
+ ```
68
+
69
+ ### Bundle Options
70
+
71
+ ```bash
72
+ -b, --bundle <name> # Use specific bundle
73
+ --build # Build bundle before testing
74
+ ```
75
+
76
+ ### Test Modes
77
+
78
+ ```bash
79
+ -m, --mode <mode> # 'quick' or 'full' (default: quick)
80
+ --quick # Quick mode (10 patterns per language)
81
+ --full # Full mode (all patterns)
82
+ --limit <n> # Patterns per language in quick mode
83
+ ```
84
+
85
+ ### Output Options
86
+
87
+ ```bash
88
+ -v, --verbose # Enable verbose output
89
+ -r, --regression # Compare to baseline
90
+ --save-baseline # Save results as new baseline
91
+ ```
92
+
93
+ ### Filtering
94
+
95
+ ```bash
96
+ -c, --confidence <n> # Minimum confidence threshold (0-1)
97
+ --verified-only # Only test verified translations
98
+ --categories <cats> # Filter by categories (comma-separated)
99
+ ```
100
+
101
+ ## Examples
102
+
103
+ ### Test Japanese with verbose output
104
+
105
+ ```bash
106
+ npm run test:multilingual -- --language ja --verbose
107
+ ```
108
+
109
+ Expected output:
110
+
111
+ ```
112
+ Multilingual Test Runner v1.0.0
113
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
114
+
115
+ Languages: ja (1)
116
+ Mode: quick mode (10 patterns/lang)
117
+
118
+ ✓ JA: 53/53 (100%) ⏱ 0.8s conf: 0.92
119
+
120
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
121
+ Summary: ✓ PASS 53/53 Duration: 0.9s
122
+ ```
123
+
124
+ ### Test all priority languages
125
+
126
+ ```bash
127
+ npm run test:multilingual -- --languages en,ja,ko,es --full
128
+ ```
129
+
130
+ ### Run regression test
131
+
132
+ ```bash
133
+ npm run test:multilingual -- --regression
134
+ ```
135
+
136
+ Expected regression output:
137
+
138
+ ```
139
+ Regression Analysis
140
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
141
+ → EN
142
+ Parse Rate: +2.1%
143
+ New Successes: 2
144
+
145
+ ↑ JA
146
+ Parse Rate: +5.3%
147
+ Avg Confidence: +0.04
148
+ New Successes: 3
149
+ ```
150
+
151
+ ### Save new baseline
152
+
153
+ ```bash
154
+ npm run test:multilingual -- --full --save-baseline
155
+ ```
156
+
157
+ ## Programmatic Usage
158
+
159
+ ```typescript
160
+ import { runMultilingualTests } from '@hyperfixi/testing-framework/multilingual';
161
+
162
+ const results = await runMultilingualTests({
163
+ languages: ['ja', 'ko'],
164
+ mode: 'full',
165
+ regression: true,
166
+ });
167
+
168
+ console.log(`Tested ${results.summary.totalPatterns} patterns`);
169
+ console.log(
170
+ `Success rate: ${((results.summary.totalSuccess / results.summary.totalPatterns) * 100).toFixed(1)}%`
171
+ );
172
+ ```
173
+
174
+ ## Output Files
175
+
176
+ ### JSON Results
177
+
178
+ Location: `./test-results/results.json`
179
+
180
+ Structure:
181
+
182
+ ```json
183
+ {
184
+ "timestamp": "2026-01-17T10:30:00Z",
185
+ "commit": "614da020",
186
+ "languageResults": [
187
+ {
188
+ "language": "ja",
189
+ "parseSuccess": 53,
190
+ "parseFailure": 0,
191
+ "parseRate": 1.0,
192
+ "avgConfidence": 0.92
193
+ }
194
+ ],
195
+ "bundles": {
196
+ "browser-ja": {
197
+ "size": 20480,
198
+ "languages": ["ja"]
199
+ }
200
+ }
201
+ }
202
+ ```
203
+
204
+ ### Baseline
205
+
206
+ Location: `./test-results/baseline.json`
207
+
208
+ Structure:
209
+
210
+ ```json
211
+ {
212
+ "timestamp": "2026-01-17T10:00:00Z",
213
+ "commit": "614da020",
214
+ "languages": {
215
+ "ja": {
216
+ "parseSuccess": 51,
217
+ "parseFailure": 2,
218
+ "parseRate": 0.96,
219
+ "avgConfidence": 0.88
220
+ }
221
+ }
222
+ }
223
+ ```
224
+
225
+ ## Supported Languages
226
+
227
+ | Code | Language | Word Order | Status |
228
+ | ---- | ---------- | ---------- | ------------------ |
229
+ | en | English | SVO | ✅ High coverage |
230
+ | ja | Japanese | SOV | ✅ High coverage |
231
+ | ko | Korean | SOV | ✅ High coverage |
232
+ | es | Spanish | SVO | ✅ High coverage |
233
+ | zh | Chinese | SVO | ✅ High coverage |
234
+ | ar | Arabic | VSO | ✅ High coverage |
235
+ | pt | Portuguese | SVO | ✅ Medium coverage |
236
+ | tr | Turkish | SOV | ✅ Medium coverage |
237
+ | de | German | V2 | ✅ Medium coverage |
238
+ | fr | French | SVO | ✅ Medium coverage |
239
+ | id | Indonesian | SVO | ✅ Medium coverage |
240
+ | qu | Quechua | SOV | ⚠️ Low coverage |
241
+ | sw | Swahili | SVO | ⚠️ Low coverage |
242
+
243
+ ## Integration with CI
244
+
245
+ Add to `.github/workflows/test.yml`:
246
+
247
+ ```yaml
248
+ - name: Run Multilingual Tests
249
+ run: |
250
+ npm run test:multilingual -- --full --regression
251
+ ```
252
+
253
+ ## Troubleshooting
254
+
255
+ ### Bundle not found
256
+
257
+ If you see "Bundle not found", build it first:
258
+
259
+ ```bash
260
+ npm run test:multilingual -- --build --language ja
261
+ ```
262
+
263
+ ### Patterns database not populated
264
+
265
+ Ensure the patterns database is populated:
266
+
267
+ ```bash
268
+ cd packages/patterns-reference
269
+ npm run populate
270
+ ```
271
+
272
+ ### Low confidence scores
273
+
274
+ Enable verbose mode to see which patterns are failing:
275
+
276
+ ```bash
277
+ npm run test:multilingual -- --verbose --language ja
278
+ ```
279
+
280
+ ## Future Enhancements
281
+
282
+ - [ ] Browser execution tests (Playwright)
283
+ - [ ] Performance benchmarks
284
+ - [ ] Visual regression for multilingual examples
285
+ - [ ] Auto-fix suggestions for common translation errors
286
+ - [ ] Coverage heatmap visualization
287
+
288
+ ## Contributing
289
+
290
+ When adding a new language:
291
+
292
+ 1. Add patterns to patterns-reference database
293
+ 2. Generate translations with `npm run populate`
294
+ 3. Run tests: `npm run test:multilingual -- --language <code>`
295
+ 4. Save baseline: `npm run test:multilingual -- --language <code> --save-baseline`
@@ -0,0 +1,345 @@
1
+ /**
2
+ * Bundle Builder - Manages language bundle generation and selection
3
+ */
4
+
5
+ import { existsSync, statSync } from 'node:fs';
6
+ import { join, dirname } from 'node:path';
7
+ import { exec } from 'node:child_process';
8
+ import { promisify } from 'node:util';
9
+ import { fileURLToPath } from 'node:url';
10
+ import type { LanguageCode, BundleInfo, BundleBuildOptions } from './types';
11
+
12
+ const execAsync = promisify(exec);
13
+
14
+ // ESM __dirname equivalent
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+
18
+ // Predefined bundle mappings
19
+ const PREDEFINED_BUNDLES = new Map<string, LanguageCode[]>([
20
+ // All 23 languages
21
+ [
22
+ 'browser',
23
+ [
24
+ 'en',
25
+ 'es',
26
+ 'ja',
27
+ 'ar',
28
+ 'ko',
29
+ 'zh',
30
+ 'tr',
31
+ 'pt',
32
+ 'fr',
33
+ 'de',
34
+ 'id',
35
+ 'qu',
36
+ 'sw',
37
+ 'bn',
38
+ 'hi',
39
+ 'it',
40
+ 'ms',
41
+ 'pl',
42
+ 'ru',
43
+ 'th',
44
+ 'tl',
45
+ 'uk',
46
+ 'vi',
47
+ ],
48
+ ],
49
+ ['browser-priority', ['en', 'es', 'pt', 'fr', 'de', 'ja', 'zh', 'ko', 'ar', 'tr', 'id']], // 11 priority
50
+ ['browser-western', ['en', 'es', 'pt', 'fr', 'de']], // Western languages
51
+ ['browser-east-asian', ['ja', 'zh', 'ko']], // East Asian languages
52
+ // Individual language bundles
53
+ ['browser-ar', ['ar']],
54
+ ['browser-bn', ['bn']],
55
+ ['browser-de', ['de']],
56
+ ['browser-en', ['en']],
57
+ ['browser-es', ['es']],
58
+ ['browser-fr', ['fr']],
59
+ ['browser-hi', ['hi']],
60
+ ['browser-id', ['id']],
61
+ ['browser-it', ['it']],
62
+ ['browser-ja', ['ja']],
63
+ ['browser-ko', ['ko']],
64
+ ['browser-ms', ['ms']],
65
+ ['browser-pl', ['pl']],
66
+ ['browser-pt', ['pt']],
67
+ ['browser-qu', ['qu']],
68
+ ['browser-ru', ['ru']],
69
+ ['browser-sw', ['sw']],
70
+ ['browser-th', ['th']],
71
+ ['browser-tl', ['tl']],
72
+ ['browser-tr', ['tr']],
73
+ ['browser-uk', ['uk']],
74
+ ['browser-vi', ['vi']],
75
+ ['browser-zh', ['zh']],
76
+ // Utility bundles
77
+ ['browser-core', []], // Core bundle (language-agnostic)
78
+ ['browser-lazy', []], // Lazy-loading bundle
79
+ ['browser-en-tr', ['en', 'tr']], // English + Turkish
80
+ ['browser-es-en', ['es', 'en']], // Spanish + English
81
+ ]);
82
+
83
+ /**
84
+ * Get bundle path in semantic package
85
+ */
86
+ function getBundlePath(bundleName: string): string {
87
+ // Resolve from this file's location: testing-framework/src/multilingual
88
+ // __dirname = /path/to/hyperfixi/packages/testing-framework/src/multilingual
89
+ // Go up 3 levels to project root, then into packages/semantic
90
+ const projectRoot = join(__dirname, '../../..');
91
+ const semanticRoot = join(projectRoot, 'semantic');
92
+ // Bundle naming: browser-{group}.{group}.global.js
93
+ const group = bundleName.replace('browser-', '');
94
+ return join(semanticRoot, 'dist', `${bundleName}.${group}.global.js`);
95
+ }
96
+
97
+ /**
98
+ * Find existing bundle for languages
99
+ */
100
+ export async function findBundleForLanguages(
101
+ languages: LanguageCode[]
102
+ ): Promise<BundleInfo | null> {
103
+ const langSet = new Set(languages);
104
+
105
+ // Find the smallest predefined bundle that contains all required languages
106
+ let bestBundle: { name: string; langs: LanguageCode[] } | null = null;
107
+ let minSize = Infinity;
108
+
109
+ for (const [bundleName, bundleLangs] of PREDEFINED_BUNDLES.entries()) {
110
+ const bundleSet = new Set(bundleLangs);
111
+
112
+ // Check if bundle contains all required languages
113
+ const containsAll = languages.every(lang => bundleSet.has(lang));
114
+ if (!containsAll) continue;
115
+
116
+ // Prefer smaller bundles
117
+ if (bundleLangs.length < minSize) {
118
+ minSize = bundleLangs.length;
119
+ bestBundle = { name: bundleName, langs: bundleLangs };
120
+ }
121
+ }
122
+
123
+ if (!bestBundle) {
124
+ return null;
125
+ }
126
+
127
+ const bundlePath = getBundlePath(bestBundle.name);
128
+ const exists = existsSync(bundlePath);
129
+
130
+ return {
131
+ name: bestBundle.name,
132
+ path: bundlePath,
133
+ languages: bestBundle.langs,
134
+ size: exists ? statSync(bundlePath).size : 0,
135
+ exists,
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Get bundle info by name
141
+ */
142
+ export async function getBundleInfo(bundleName: string): Promise<BundleInfo | null> {
143
+ const languages = PREDEFINED_BUNDLES.get(bundleName);
144
+ if (!languages) {
145
+ return null;
146
+ }
147
+
148
+ const bundlePath = getBundlePath(bundleName);
149
+ const exists = existsSync(bundlePath);
150
+
151
+ return {
152
+ name: bundleName,
153
+ path: bundlePath,
154
+ languages,
155
+ size: exists ? statSync(bundlePath).size : 0,
156
+ exists,
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Build bundle for languages
162
+ */
163
+ export async function buildBundle(options: BundleBuildOptions): Promise<BundleInfo> {
164
+ const { languages, groupName } = options;
165
+
166
+ // Generate bundle using the generate-bundle.mjs script
167
+ const semanticRoot = join(process.cwd(), 'packages/semantic');
168
+ const scriptPath = join(semanticRoot, 'scripts/generate-bundle.mjs');
169
+
170
+ let bundleName: string;
171
+
172
+ try {
173
+ if (groupName) {
174
+ // Use predefined group
175
+ const cmd = `node ${scriptPath} --group ${groupName} ${options.updateConfig ? '--auto' : ''}`;
176
+ await execAsync(cmd, { cwd: semanticRoot });
177
+ bundleName = `browser-${groupName}`;
178
+ } else {
179
+ // Use specific languages
180
+ const langCodes = languages.join(' ');
181
+ const cmd = `node ${scriptPath} ${langCodes} ${options.updateConfig ? '--auto' : ''}`;
182
+ await execAsync(cmd, { cwd: semanticRoot });
183
+ bundleName = `browser-${languages.join('-')}`;
184
+ }
185
+
186
+ // Build the bundle
187
+ await execAsync('npm run build', { cwd: semanticRoot });
188
+
189
+ const bundlePath = options.outputPath || getBundlePath(bundleName);
190
+ const exists = existsSync(bundlePath);
191
+
192
+ return {
193
+ name: bundleName,
194
+ path: bundlePath,
195
+ languages,
196
+ size: exists ? statSync(bundlePath).size : 0,
197
+ exists,
198
+ };
199
+ } catch (error) {
200
+ throw new Error(
201
+ `Failed to build bundle: ${error instanceof Error ? error.message : String(error)}`
202
+ );
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Select or build bundle for testing
208
+ */
209
+ export async function selectBundle(
210
+ languages: LanguageCode[],
211
+ build: boolean = false
212
+ ): Promise<BundleInfo> {
213
+ // First, try to find existing bundle
214
+ let bundle = await findBundleForLanguages(languages);
215
+
216
+ // If bundle exists and we don't need to build, use it
217
+ if (bundle && bundle.exists && !build) {
218
+ return bundle;
219
+ }
220
+
221
+ // If build is requested or no bundle exists, build it
222
+ if (build || !bundle || !bundle.exists) {
223
+ // Determine if we can use a predefined group
224
+ const groupName = findGroupForLanguages(languages);
225
+
226
+ bundle = await buildBundle({
227
+ languages,
228
+ groupName: groupName !== null ? groupName : undefined,
229
+ outputPath: undefined,
230
+ updateConfig: false, // Don't modify configs during testing
231
+ });
232
+ }
233
+
234
+ if (!bundle.exists) {
235
+ throw new Error(`Bundle not found: ${bundle.path}`);
236
+ }
237
+
238
+ return bundle;
239
+ }
240
+
241
+ /**
242
+ * Find predefined group name for languages
243
+ */
244
+ function findGroupForLanguages(languages: LanguageCode[]): string | null {
245
+ const langSet = new Set(languages);
246
+
247
+ for (const [bundleName, bundleLangs] of PREDEFINED_BUNDLES.entries()) {
248
+ const bundleSet = new Set(bundleLangs);
249
+
250
+ // Exact match
251
+ if (bundleLangs.length === languages.length && languages.every(lang => bundleSet.has(lang))) {
252
+ return bundleName.replace('browser-', '');
253
+ }
254
+ }
255
+
256
+ return null;
257
+ }
258
+
259
+ /**
260
+ * List all available bundles
261
+ */
262
+ export async function listAvailableBundles(): Promise<BundleInfo[]> {
263
+ const bundles: BundleInfo[] = [];
264
+
265
+ for (const [bundleName, languages] of PREDEFINED_BUNDLES.entries()) {
266
+ const bundlePath = getBundlePath(bundleName);
267
+ const exists = existsSync(bundlePath);
268
+
269
+ bundles.push({
270
+ name: bundleName,
271
+ path: bundlePath,
272
+ languages,
273
+ size: exists ? statSync(bundlePath).size : 0,
274
+ exists,
275
+ });
276
+ }
277
+
278
+ return bundles;
279
+ }
280
+
281
+ /**
282
+ * Check if bundle exists
283
+ */
284
+ export async function bundleExists(bundleName: string): Promise<boolean> {
285
+ const bundlePath = getBundlePath(bundleName);
286
+ return existsSync(bundlePath);
287
+ }
288
+
289
+ /**
290
+ * Get bundle size in bytes
291
+ */
292
+ export async function getBundleSize(bundleName: string): Promise<number> {
293
+ const bundlePath = getBundlePath(bundleName);
294
+ if (!existsSync(bundlePath)) {
295
+ return 0;
296
+ }
297
+ return statSync(bundlePath).size;
298
+ }
299
+
300
+ /**
301
+ * Estimate bundle size for languages (without building)
302
+ */
303
+ export function estimateBundleSize(languages: LanguageCode[]): {
304
+ estimatedSize: number;
305
+ estimatedGzipSize: number;
306
+ } {
307
+ const SIZES: Record<string, number> = {
308
+ en: 25,
309
+ es: 22,
310
+ ja: 28,
311
+ ar: 24,
312
+ ko: 22,
313
+ zh: 20,
314
+ tr: 20,
315
+ pt: 22,
316
+ fr: 22,
317
+ de: 24,
318
+ id: 18,
319
+ qu: 16,
320
+ sw: 18,
321
+ bn: 20,
322
+ hi: 20,
323
+ it: 22,
324
+ ms: 18,
325
+ pl: 20,
326
+ ru: 22,
327
+ th: 20,
328
+ tl: 18,
329
+ uk: 22,
330
+ vi: 20,
331
+ base: 45,
332
+ };
333
+
334
+ const GZIP_RATIO = 0.35;
335
+
336
+ let totalSize = SIZES['base'] || 45; // Base infrastructure
337
+ for (const lang of languages) {
338
+ totalSize += SIZES[lang] || 20; // Default to 20 KB if unknown
339
+ }
340
+
341
+ return {
342
+ estimatedSize: totalSize * 1024, // Convert to bytes
343
+ estimatedGzipSize: Math.round(totalSize * 1024 * GZIP_RATIO),
344
+ };
345
+ }