@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,224 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Multilingual Testing CLI
4
+ *
5
+ * Command-line interface for running multilingual tests.
6
+ */
7
+
8
+ import { TestOrchestrator } from './orchestrator';
9
+ import { RegressionReporter } from './reporters/regression-reporter';
10
+ import type { TestConfig, LanguageCode } from './types';
11
+
12
+ /**
13
+ * Parse command-line arguments
14
+ */
15
+ function parseArgs(): TestConfig {
16
+ const args = process.argv.slice(2);
17
+ const config: TestConfig = {
18
+ mode: 'quick',
19
+ quickModeLimit: 10,
20
+ };
21
+
22
+ for (let i = 0; i < args.length; i++) {
23
+ const arg = args[i];
24
+
25
+ switch (arg) {
26
+ case '--language':
27
+ case '-l': {
28
+ const lang = args[++i];
29
+ if (lang) config.languages = [lang as LanguageCode];
30
+ break;
31
+ }
32
+
33
+ case '--languages': {
34
+ const langs = args[++i];
35
+ if (langs) config.languages = langs.split(',') as LanguageCode[];
36
+ break;
37
+ }
38
+
39
+ case '--bundle':
40
+ case '-b': {
41
+ const bundle = args[++i];
42
+ if (bundle) config.bundle = bundle;
43
+ break;
44
+ }
45
+
46
+ case '--build':
47
+ config.build = true;
48
+ break;
49
+
50
+ case '--mode':
51
+ case '-m':
52
+ config.mode = args[++i] as 'quick' | 'full';
53
+ break;
54
+
55
+ case '--quick':
56
+ config.mode = 'quick';
57
+ break;
58
+
59
+ case '--full':
60
+ config.mode = 'full';
61
+ break;
62
+
63
+ case '--verbose':
64
+ case '-v':
65
+ config.verbose = true;
66
+ break;
67
+
68
+ case '--regression':
69
+ case '-r':
70
+ config.regression = true;
71
+ break;
72
+
73
+ case '--confidence':
74
+ case '-c': {
75
+ const conf = args[++i];
76
+ if (conf) config.confidenceThreshold = parseFloat(conf);
77
+ break;
78
+ }
79
+
80
+ case '--verified-only':
81
+ config.verifiedOnly = true;
82
+ break;
83
+
84
+ case '--categories': {
85
+ const cats = args[++i];
86
+ if (cats) config.categories = cats.split(',');
87
+ break;
88
+ }
89
+
90
+ case '--limit': {
91
+ const lim = args[++i];
92
+ if (lim) config.quickModeLimit = parseInt(lim, 10);
93
+ break;
94
+ }
95
+
96
+ case '--help':
97
+ case '-h':
98
+ showHelp();
99
+ process.exit(0);
100
+ break;
101
+
102
+ case '--save-baseline':
103
+ // Special flag to save results as baseline
104
+ runAndSaveBaseline();
105
+ return config; // Early return to avoid running tests below
106
+
107
+ default:
108
+ if (arg && arg.startsWith('-')) {
109
+ console.error(`Unknown option: ${arg}`);
110
+ process.exit(1);
111
+ }
112
+ break;
113
+ }
114
+ }
115
+
116
+ return config;
117
+ }
118
+
119
+ /**
120
+ * Show help message
121
+ */
122
+ function showHelp(): void {
123
+ console.log(`
124
+ Multilingual Test Runner
125
+
126
+ USAGE:
127
+ npm run test:multilingual [OPTIONS]
128
+
129
+ OPTIONS:
130
+ -l, --language <code> Test specific language (en, ja, es, etc.)
131
+ --languages <codes> Test multiple languages (comma-separated)
132
+ -b, --bundle <name> Use specific bundle
133
+ --build Build bundle before testing
134
+ -m, --mode <mode> Test mode: 'quick' or 'full' (default: quick)
135
+ --quick Quick mode (10 patterns per language)
136
+ --full Full mode (all patterns)
137
+ -v, --verbose Enable verbose output
138
+ -r, --regression Compare to baseline and report regressions
139
+ -c, --confidence <n> Minimum confidence threshold (0-1)
140
+ --verified-only Only test verified translations
141
+ --categories <cats> Filter by categories (comma-separated)
142
+ --limit <n> Patterns per language in quick mode (default: 10)
143
+ --save-baseline Save current results as new baseline
144
+ -h, --help Show this help message
145
+
146
+ EXAMPLES:
147
+ # Test all languages in quick mode
148
+ npm run test:multilingual
149
+
150
+ # Test specific language with verbose output
151
+ npm run test:multilingual -- --language ja --verbose
152
+
153
+ # Test multiple languages in full mode
154
+ npm run test:multilingual -- --languages ja,ko,es --full
155
+
156
+ # Build bundle and test with regression comparison
157
+ npm run test:multilingual -- --build --regression
158
+
159
+ # Save current results as baseline
160
+ npm run test:multilingual -- --save-baseline
161
+ `);
162
+ }
163
+
164
+ /**
165
+ * Run tests and save as baseline
166
+ */
167
+ async function runAndSaveBaseline(): Promise<void> {
168
+ try {
169
+ // Parse config without --save-baseline
170
+ const args = process.argv.slice(2).filter(arg => arg !== '--save-baseline');
171
+ process.argv = [...process.argv.slice(0, 2), ...args];
172
+
173
+ const config = parseArgs();
174
+ config.regression = true; // Enable regression reporter
175
+
176
+ const orchestrator = new TestOrchestrator(config);
177
+ const results = await orchestrator.run();
178
+
179
+ // Save as baseline
180
+ const regressionReporter = orchestrator.getRegressionReporter();
181
+ if (regressionReporter) {
182
+ regressionReporter.saveAsBaseline(results);
183
+ } else {
184
+ // Create reporter and save
185
+ const reporter = new RegressionReporter();
186
+ reporter.saveAsBaseline(results);
187
+ }
188
+
189
+ process.exit(0);
190
+ } catch (error) {
191
+ console.error('Error:', error instanceof Error ? error.message : String(error));
192
+ process.exit(1);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Main CLI entry point
198
+ */
199
+ async function main(): Promise<void> {
200
+ try {
201
+ const config = parseArgs();
202
+
203
+ const orchestrator = new TestOrchestrator(config);
204
+ const results = await orchestrator.run();
205
+
206
+ // Exit with appropriate code
207
+ const exitCode = results.summary.overallStatus === 'pass' ? 0 : 1;
208
+ process.exit(exitCode);
209
+ } catch (error) {
210
+ console.error('Error:', error instanceof Error ? error.message : String(error));
211
+ if (error instanceof Error && error.stack) {
212
+ console.error(error.stack);
213
+ }
214
+ process.exit(1);
215
+ }
216
+ }
217
+
218
+ // Check if running as CLI
219
+ if (import.meta.url === `file://${process.argv[1]}`) {
220
+ main();
221
+ }
222
+
223
+ // Export for programmatic use
224
+ export { main, parseArgs, showHelp };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Multilingual Testing Framework
3
+ *
4
+ * Automated validation of HyperFixi's multilingual system.
5
+ */
6
+
7
+ // Main orchestrator
8
+ export { TestOrchestrator, runMultilingualTests } from './orchestrator';
9
+
10
+ // Types
11
+ export type {
12
+ LanguageCode,
13
+ WordOrder,
14
+ Pattern,
15
+ PatternTranslation,
16
+ TestConfig,
17
+ ParseResult,
18
+ SizeResult,
19
+ LanguageResults,
20
+ TestResults,
21
+ Baseline,
22
+ RegressionResult,
23
+ BundleInfo,
24
+ BundleBuildOptions,
25
+ Reporter,
26
+ Validator,
27
+ SamplingStrategy,
28
+ } from './types';
29
+
30
+ // Pattern loading
31
+ export {
32
+ loadPatterns,
33
+ getPatternStatistics,
34
+ loadPatternsByCategory,
35
+ countPatternsByLanguage,
36
+ } from './pattern-loader';
37
+
38
+ // Bundle building
39
+ export {
40
+ findBundleForLanguages,
41
+ getBundleInfo,
42
+ buildBundle,
43
+ selectBundle,
44
+ listAvailableBundles,
45
+ bundleExists,
46
+ getBundleSize,
47
+ estimateBundleSize,
48
+ } from './bundle-builder';
49
+
50
+ // Validators
51
+ export {
52
+ ParseValidator,
53
+ validatePatterns,
54
+ validateAndSummarize,
55
+ } from './validators/parse-validator';
56
+ export {
57
+ SizeValidator,
58
+ validateBundleSize,
59
+ validateAndSummarizeSizes,
60
+ } from './validators/size-validator';
61
+
62
+ // Reporters
63
+ export { ConsoleReporter, createConsoleReporter } from './reporters/console-reporter';
64
+ export { JSONReporter, createJSONReporter } from './reporters/json-reporter';
65
+ export { RegressionReporter, createRegressionReporter } from './reporters/regression-reporter';
66
+
67
+ // CLI
68
+ export { main as runCLI, parseArgs, showHelp } from './cli';
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Orchestrator - Main test runner that coordinates all components
3
+ */
4
+
5
+ import { exec } from 'node:child_process';
6
+ import { promisify } from 'node:util';
7
+ import { loadPatterns } from './pattern-loader';
8
+ import { selectBundle, getBundleInfo } from './bundle-builder';
9
+ import { ParseValidator } from './validators/parse-validator';
10
+ import { SizeValidator } from './validators/size-validator';
11
+ import { ConsoleReporter } from './reporters/console-reporter';
12
+ import { JSONReporter } from './reporters/json-reporter';
13
+ import { RegressionReporter } from './reporters/regression-reporter';
14
+ import type { TestConfig, TestResults, LanguageResults, LanguageCode, Reporter } from './types';
15
+
16
+ const execAsync = promisify(exec);
17
+
18
+ /**
19
+ * Test Orchestrator
20
+ *
21
+ * Coordinates the entire multilingual test workflow:
22
+ * 1. Load patterns from database
23
+ * 2. Select/build bundles
24
+ * 3. Run validators
25
+ * 4. Report results
26
+ */
27
+ export class TestOrchestrator {
28
+ private config: TestConfig;
29
+ private reporters: Reporter[] = [];
30
+
31
+ constructor(config: TestConfig) {
32
+ this.config = config;
33
+ this.setupReporters();
34
+ }
35
+
36
+ /**
37
+ * Setup default reporters
38
+ */
39
+ private setupReporters(): void {
40
+ // Always add console reporter
41
+ this.reporters.push(
42
+ new ConsoleReporter({
43
+ verbose: this.config.verbose || false,
44
+ useColors: true,
45
+ })
46
+ );
47
+
48
+ // Add JSON reporter for structured output
49
+ this.reporters.push(new JSONReporter('./test-results/results.json'));
50
+
51
+ // Add regression reporter if requested
52
+ if (this.config.regression) {
53
+ this.reporters.push(new RegressionReporter('./test-results/baseline.json'));
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Add custom reporter
59
+ */
60
+ addReporter(reporter: Reporter): void {
61
+ this.reporters.push(reporter);
62
+ }
63
+
64
+ /**
65
+ * Run the test suite
66
+ */
67
+ async run(): Promise<TestResults> {
68
+ // Notify reporters of test start
69
+ for (const reporter of this.reporters) {
70
+ reporter.reportStart(this.config);
71
+ }
72
+
73
+ try {
74
+ // Get git commit for tracking
75
+ const commit = await this.getGitCommit();
76
+
77
+ // Load patterns
78
+ const patterns = await loadPatterns(this.config);
79
+
80
+ // Group patterns by language
81
+ const patternsByLanguage = this.groupByLanguage(patterns);
82
+
83
+ // Test each language
84
+ const languageResults: LanguageResults[] = [];
85
+
86
+ for (const [language, langPatterns] of patternsByLanguage.entries()) {
87
+ const result = await this.testLanguage(language as LanguageCode, langPatterns);
88
+ languageResults.push(result);
89
+ }
90
+
91
+ // Collect bundle information
92
+ const bundles: TestResults['bundles'] = {};
93
+ for (const langResult of languageResults) {
94
+ if (!bundles[langResult.bundle]) {
95
+ const bundleInfo = await getBundleInfo(langResult.bundle);
96
+ if (bundleInfo && bundleInfo.exists) {
97
+ bundles[langResult.bundle] = {
98
+ size: bundleInfo.size,
99
+ languages: bundleInfo.languages,
100
+ };
101
+ }
102
+ }
103
+ }
104
+
105
+ // Calculate summary
106
+ const summary = {
107
+ totalPatterns: patterns.length,
108
+ totalSuccess: languageResults.reduce((sum, r) => sum + r.parseSuccess, 0),
109
+ totalFailure: languageResults.reduce((sum, r) => sum + r.parseFailure, 0),
110
+ totalDuration: languageResults.reduce((sum, r) => sum + r.duration, 0),
111
+ overallStatus: this.calculateOverallStatus(languageResults),
112
+ };
113
+
114
+ const results: TestResults = {
115
+ timestamp: new Date().toISOString(),
116
+ commit,
117
+ config: this.config,
118
+ languageResults,
119
+ bundles,
120
+ summary,
121
+ };
122
+
123
+ // Report completion
124
+ for (const reporter of this.reporters) {
125
+ reporter.reportComplete(results);
126
+ }
127
+
128
+ // Report regression if enabled
129
+ if (this.config.regression) {
130
+ const regressionReporter = this.reporters.find(r => r instanceof RegressionReporter) as
131
+ | RegressionReporter
132
+ | undefined;
133
+
134
+ if (regressionReporter?.hasBaseline()) {
135
+ const regressionResults = regressionReporter.getRegressionResults();
136
+ for (const reporter of this.reporters) {
137
+ if (reporter.reportRegression) {
138
+ reporter.reportRegression(regressionResults);
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ return results;
145
+ } catch (error) {
146
+ // Report error
147
+ const err = error instanceof Error ? error : new Error(String(error));
148
+ for (const reporter of this.reporters) {
149
+ reporter.reportError(err);
150
+ }
151
+ throw err;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Test a single language
157
+ */
158
+ private async testLanguage(language: LanguageCode, patterns: any[]): Promise<LanguageResults> {
159
+ const startTime = performance.now();
160
+
161
+ // Select bundle for this language
162
+ const bundle = this.config.bundle
163
+ ? await getBundleInfo(this.config.bundle)
164
+ : await selectBundle([language], this.config.build || false);
165
+
166
+ if (!bundle || !bundle.exists) {
167
+ throw new Error(`Bundle not found for language: ${language}`);
168
+ }
169
+
170
+ // Notify reporters
171
+ for (const reporter of this.reporters) {
172
+ reporter.reportLanguageStart(language, bundle.name);
173
+ }
174
+
175
+ // Run parse validation
176
+ const parseValidator = new ParseValidator();
177
+ const parseResults = await parseValidator.validate(patterns);
178
+
179
+ // Calculate statistics
180
+ const parseSuccess = parseResults.filter(r => r.success).length;
181
+ const parseFailure = parseResults.length - parseSuccess;
182
+ const parseRate = parseResults.length > 0 ? parseSuccess / parseResults.length : 0;
183
+ const avgConfidence =
184
+ parseResults
185
+ .filter(r => r.success && r.confidence !== undefined)
186
+ .reduce((sum, r) => sum + (r.confidence || 0), 0) / (parseSuccess || 1);
187
+
188
+ // Determine status
189
+ const status = parseRate >= 0.95 ? 'pass' : parseRate >= 0.8 ? 'warning' : 'fail';
190
+
191
+ const result: LanguageResults = {
192
+ language,
193
+ bundle: bundle.name,
194
+ bundleSize: bundle.size,
195
+ parseResults,
196
+ parseSuccess,
197
+ parseFailure,
198
+ parseRate,
199
+ avgConfidence,
200
+ duration: performance.now() - startTime,
201
+ status,
202
+ };
203
+
204
+ // Notify reporters
205
+ for (const reporter of this.reporters) {
206
+ reporter.reportLanguageComplete(result);
207
+ }
208
+
209
+ return result;
210
+ }
211
+
212
+ /**
213
+ * Group patterns by language
214
+ */
215
+ private groupByLanguage(patterns: any[]): Map<string, any[]> {
216
+ const grouped = new Map<string, any[]>();
217
+
218
+ for (const pattern of patterns) {
219
+ const lang = pattern.language;
220
+ if (!grouped.has(lang)) {
221
+ grouped.set(lang, []);
222
+ }
223
+ grouped.get(lang)!.push(pattern);
224
+ }
225
+
226
+ return grouped;
227
+ }
228
+
229
+ /**
230
+ * Calculate overall test status
231
+ */
232
+ private calculateOverallStatus(results: LanguageResults[]): 'pass' | 'warning' | 'fail' {
233
+ const hasFail = results.some(r => r.status === 'fail');
234
+ const hasWarning = results.some(r => r.status === 'warning');
235
+
236
+ if (hasFail) return 'fail';
237
+ if (hasWarning) return 'warning';
238
+ return 'pass';
239
+ }
240
+
241
+ /**
242
+ * Get current git commit
243
+ */
244
+ private async getGitCommit(): Promise<string | undefined> {
245
+ try {
246
+ const { stdout } = await execAsync('git rev-parse --short HEAD');
247
+ return stdout.trim();
248
+ } catch {
249
+ return undefined;
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Get regression reporter
255
+ */
256
+ getRegressionReporter(): RegressionReporter | undefined {
257
+ return this.reporters.find(r => r instanceof RegressionReporter) as
258
+ | RegressionReporter
259
+ | undefined;
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Quick run helper
265
+ */
266
+ export async function runMultilingualTests(config: Partial<TestConfig>): Promise<TestResults> {
267
+ const fullConfig: TestConfig = {
268
+ mode: 'quick',
269
+ quickModeLimit: 10,
270
+ ...config,
271
+ };
272
+
273
+ const orchestrator = new TestOrchestrator(fullConfig);
274
+ return await orchestrator.run();
275
+ }