@messagevisor/core 0.0.1 → 0.1.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 (211) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +7 -0
  4. package/jest.config.js +8 -0
  5. package/lib/benchmark/index.d.ts +2 -0
  6. package/lib/benchmark/index.js +417 -0
  7. package/lib/benchmark/index.js.map +1 -0
  8. package/lib/builder/index.d.ts +70 -0
  9. package/lib/builder/index.js +831 -0
  10. package/lib/builder/index.js.map +1 -0
  11. package/lib/cli/index.d.ts +28 -0
  12. package/lib/cli/index.js +182 -0
  13. package/lib/cli/index.js.map +1 -0
  14. package/lib/config/index.d.ts +61 -0
  15. package/lib/config/index.js +255 -0
  16. package/lib/config/index.js.map +1 -0
  17. package/lib/create/index.d.ts +2 -0
  18. package/lib/create/index.js +405 -0
  19. package/lib/create/index.js.map +1 -0
  20. package/lib/datasource/filesystemAdapter.d.ts +44 -0
  21. package/lib/datasource/filesystemAdapter.js +424 -0
  22. package/lib/datasource/filesystemAdapter.js.map +1 -0
  23. package/lib/datasource/index.d.ts +39 -0
  24. package/lib/datasource/index.js +96 -0
  25. package/lib/datasource/index.js.map +1 -0
  26. package/lib/error.d.ts +6 -0
  27. package/lib/error.js +49 -0
  28. package/lib/error.js.map +1 -0
  29. package/lib/evaluate/cli.d.ts +8 -0
  30. package/lib/evaluate/cli.js +179 -0
  31. package/lib/evaluate/cli.js.map +1 -0
  32. package/lib/evaluate/index.d.ts +10 -0
  33. package/lib/evaluate/index.js +131 -0
  34. package/lib/evaluate/index.js.map +1 -0
  35. package/lib/examples/coerceExampleIsoDates.d.ts +12 -0
  36. package/lib/examples/coerceExampleIsoDates.js +81 -0
  37. package/lib/examples/coerceExampleIsoDates.js.map +1 -0
  38. package/lib/examples/index.d.ts +63 -0
  39. package/lib/examples/index.js +713 -0
  40. package/lib/examples/index.js.map +1 -0
  41. package/lib/exporter/index.d.ts +60 -0
  42. package/lib/exporter/index.js +610 -0
  43. package/lib/exporter/index.js.map +1 -0
  44. package/lib/find-duplicates/index.d.ts +41 -0
  45. package/lib/find-duplicates/index.js +297 -0
  46. package/lib/find-duplicates/index.js.map +1 -0
  47. package/lib/generate-code/index.d.ts +11 -0
  48. package/lib/generate-code/index.js +157 -0
  49. package/lib/generate-code/index.js.map +1 -0
  50. package/lib/generate-code/typescript.d.ts +14 -0
  51. package/lib/generate-code/typescript.js +307 -0
  52. package/lib/generate-code/typescript.js.map +1 -0
  53. package/lib/importer/index.d.ts +64 -0
  54. package/lib/importer/index.js +1092 -0
  55. package/lib/importer/index.js.map +1 -0
  56. package/lib/index.d.ts +18 -0
  57. package/lib/index.js +35 -0
  58. package/lib/index.js.map +1 -0
  59. package/lib/info/index.d.ts +17 -0
  60. package/lib/info/index.js +132 -0
  61. package/lib/info/index.js.map +1 -0
  62. package/lib/init/index.d.ts +30 -0
  63. package/lib/init/index.js +348 -0
  64. package/lib/init/index.js.map +1 -0
  65. package/lib/lint/index.d.ts +1 -0
  66. package/lib/lint/index.js +6 -0
  67. package/lib/lint/index.js.map +1 -0
  68. package/lib/linter/attributeSchema.d.ts +7 -0
  69. package/lib/linter/attributeSchema.js +36 -0
  70. package/lib/linter/attributeSchema.js.map +1 -0
  71. package/lib/linter/checkLocaleCircularDependency.d.ts +7 -0
  72. package/lib/linter/checkLocaleCircularDependency.js +42 -0
  73. package/lib/linter/checkLocaleCircularDependency.js.map +1 -0
  74. package/lib/linter/conditionSchema.d.ts +3 -0
  75. package/lib/linter/conditionSchema.js +283 -0
  76. package/lib/linter/conditionSchema.js.map +1 -0
  77. package/lib/linter/formatSchema.d.ts +325 -0
  78. package/lib/linter/formatSchema.js +165 -0
  79. package/lib/linter/formatSchema.js.map +1 -0
  80. package/lib/linter/icuStyleLint.d.ts +6 -0
  81. package/lib/linter/icuStyleLint.js +226 -0
  82. package/lib/linter/icuStyleLint.js.map +1 -0
  83. package/lib/linter/index.d.ts +34 -0
  84. package/lib/linter/index.js +557 -0
  85. package/lib/linter/index.js.map +1 -0
  86. package/lib/linter/localeSchema.d.ts +672 -0
  87. package/lib/linter/localeSchema.js +50 -0
  88. package/lib/linter/localeSchema.js.map +1 -0
  89. package/lib/linter/messageSchema.d.ts +35 -0
  90. package/lib/linter/messageSchema.js +115 -0
  91. package/lib/linter/messageSchema.js.map +1 -0
  92. package/lib/linter/printError.d.ts +8 -0
  93. package/lib/linter/printError.js +41 -0
  94. package/lib/linter/printError.js.map +1 -0
  95. package/lib/linter/schema.d.ts +33 -0
  96. package/lib/linter/schema.js +192 -0
  97. package/lib/linter/schema.js.map +1 -0
  98. package/lib/linter/segmentSchema.d.ts +8 -0
  99. package/lib/linter/segmentSchema.js +18 -0
  100. package/lib/linter/segmentSchema.js.map +1 -0
  101. package/lib/linter/targetSchema.d.ts +337 -0
  102. package/lib/linter/targetSchema.js +39 -0
  103. package/lib/linter/targetSchema.js.map +1 -0
  104. package/lib/linter/testSchema.d.ts +71 -0
  105. package/lib/linter/testSchema.js +165 -0
  106. package/lib/linter/testSchema.js.map +1 -0
  107. package/lib/linter/zodHelpers.d.ts +2 -0
  108. package/lib/linter/zodHelpers.js +15 -0
  109. package/lib/linter/zodHelpers.js.map +1 -0
  110. package/lib/list/index.d.ts +8 -0
  111. package/lib/list/index.js +524 -0
  112. package/lib/list/index.js.map +1 -0
  113. package/lib/matrix.d.ts +4 -0
  114. package/lib/matrix.js +66 -0
  115. package/lib/matrix.js.map +1 -0
  116. package/lib/promoter/index.d.ts +65 -0
  117. package/lib/promoter/index.js +1208 -0
  118. package/lib/promoter/index.js.map +1 -0
  119. package/lib/prune/index.d.ts +37 -0
  120. package/lib/prune/index.js +673 -0
  121. package/lib/prune/index.js.map +1 -0
  122. package/lib/sets.d.ts +10 -0
  123. package/lib/sets.js +120 -0
  124. package/lib/sets.js.map +1 -0
  125. package/lib/tester/cliFormat.d.ts +8 -0
  126. package/lib/tester/cliFormat.js +15 -0
  127. package/lib/tester/cliFormat.js.map +1 -0
  128. package/lib/tester/index.d.ts +35 -0
  129. package/lib/tester/index.js +713 -0
  130. package/lib/tester/index.js.map +1 -0
  131. package/lib/tester/matrix.d.ts +14 -0
  132. package/lib/tester/matrix.js +76 -0
  133. package/lib/tester/matrix.js.map +1 -0
  134. package/lib/tester/prettyDuration.d.ts +1 -0
  135. package/lib/tester/prettyDuration.js +30 -0
  136. package/lib/tester/prettyDuration.js.map +1 -0
  137. package/lib/tester/printTestResult.d.ts +2 -0
  138. package/lib/tester/printTestResult.js +32 -0
  139. package/lib/tester/printTestResult.js.map +1 -0
  140. package/lib/tester/types.d.ts +29 -0
  141. package/lib/tester/types.js +3 -0
  142. package/lib/tester/types.js.map +1 -0
  143. package/package.json +41 -13
  144. package/src/benchmark/index.spec.ts +375 -0
  145. package/src/benchmark/index.ts +433 -0
  146. package/src/builder/index.spec.ts +822 -0
  147. package/src/builder/index.ts +920 -0
  148. package/src/cli/index.spec.ts +54 -0
  149. package/src/cli/index.ts +150 -0
  150. package/src/config/index.spec.ts +70 -0
  151. package/src/config/index.ts +259 -0
  152. package/src/create/index.spec.ts +272 -0
  153. package/src/create/index.ts +295 -0
  154. package/src/datasource/filesystemAdapter.ts +313 -0
  155. package/src/datasource/index.ts +135 -0
  156. package/src/error.ts +33 -0
  157. package/src/evaluate/cli.spec.ts +368 -0
  158. package/src/evaluate/cli.ts +130 -0
  159. package/src/evaluate/index.ts +161 -0
  160. package/src/examples/coerceExampleIsoDates.spec.ts +81 -0
  161. package/src/examples/coerceExampleIsoDates.ts +98 -0
  162. package/src/examples/index.spec.ts +453 -0
  163. package/src/examples/index.ts +854 -0
  164. package/src/exporter/index.spec.ts +443 -0
  165. package/src/exporter/index.ts +643 -0
  166. package/src/find-duplicates/index.spec.ts +289 -0
  167. package/src/find-duplicates/index.ts +314 -0
  168. package/src/generate-code/index.ts +92 -0
  169. package/src/generate-code/typescript.spec.ts +241 -0
  170. package/src/generate-code/typescript.ts +284 -0
  171. package/src/importer/index.spec.ts +1101 -0
  172. package/src/importer/index.ts +1190 -0
  173. package/src/index.ts +18 -0
  174. package/src/info/index.ts +67 -0
  175. package/src/init/index.spec.ts +279 -0
  176. package/src/init/index.ts +292 -0
  177. package/src/lint/index.ts +1 -0
  178. package/src/linter/attributeSchema.ts +38 -0
  179. package/src/linter/checkLocaleCircularDependency.ts +51 -0
  180. package/src/linter/conditionSchema.ts +386 -0
  181. package/src/linter/formatSchema.ts +170 -0
  182. package/src/linter/icuStyleLint.ts +312 -0
  183. package/src/linter/index.spec.ts +824 -0
  184. package/src/linter/index.ts +460 -0
  185. package/src/linter/localeSchema.ts +70 -0
  186. package/src/linter/messageSchema.ts +152 -0
  187. package/src/linter/printError.ts +52 -0
  188. package/src/linter/schema.ts +230 -0
  189. package/src/linter/segmentSchema.ts +15 -0
  190. package/src/linter/targetSchema.ts +50 -0
  191. package/src/linter/testSchema.spec.ts +405 -0
  192. package/src/linter/testSchema.ts +239 -0
  193. package/src/linter/zodHelpers.ts +16 -0
  194. package/src/list/index.spec.ts +431 -0
  195. package/src/list/index.ts +463 -0
  196. package/src/matrix.ts +69 -0
  197. package/src/promoter/index.spec.ts +584 -0
  198. package/src/promoter/index.ts +1267 -0
  199. package/src/prune/index.spec.ts +418 -0
  200. package/src/prune/index.ts +693 -0
  201. package/src/sets.ts +74 -0
  202. package/src/tester/cliFormat.ts +11 -0
  203. package/src/tester/featurevisorIntegration.spec.ts +101 -0
  204. package/src/tester/index.spec.ts +577 -0
  205. package/src/tester/index.ts +679 -0
  206. package/src/tester/matrix.ts +106 -0
  207. package/src/tester/prettyDuration.ts +34 -0
  208. package/src/tester/printTestResult.ts +40 -0
  209. package/src/tester/types.ts +32 -0
  210. package/tsconfig.cjs.json +11 -0
  211. package/tsconfig.typecheck.json +4 -0
@@ -0,0 +1,577 @@
1
+ import * as fs from "fs";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+
5
+ import { getProjectConfig } from "../config";
6
+ import { Datasource } from "../datasource";
7
+ import { testProject } from "./index";
8
+
9
+ async function writeFile(root: string, relativePath: string, content: string) {
10
+ const filePath = path.join(root, relativePath);
11
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
12
+ await fs.promises.writeFile(filePath, content);
13
+ }
14
+
15
+ async function createProject(configContent = "module.exports = {};\n") {
16
+ const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "messagevisor-tester-"));
17
+
18
+ await writeFile(root, "messagevisor.config.js", configContent);
19
+ await writeFile(
20
+ root,
21
+ "locales/en.yml",
22
+ [
23
+ "description: English",
24
+ "formats:",
25
+ " number:",
26
+ " money:",
27
+ " style: currency",
28
+ " currency: USD",
29
+ " currencyDisplay: symbol",
30
+ " decimal:",
31
+ " maximumFractionDigits: 2",
32
+ " date:",
33
+ " short:",
34
+ " year: numeric",
35
+ " month: numeric",
36
+ " day: numeric",
37
+ "",
38
+ ].join("\n"),
39
+ );
40
+ await writeFile(
41
+ root,
42
+ "targets/web.yml",
43
+ ["description: Web", "includeMessages:", " - common*", "locales:", " - en", ""].join("\n"),
44
+ );
45
+ await writeFile(
46
+ root,
47
+ "messages/common/welcome.yml",
48
+ ["description: Welcome", "translations:", " en: Welcome", ""].join("\n"),
49
+ );
50
+ await writeFile(
51
+ root,
52
+ "tests/locales/en.spec.yml",
53
+ [
54
+ "locale: en",
55
+ "assertions:",
56
+ " - expectedFormats:",
57
+ " number:",
58
+ " money:",
59
+ " currency: USD",
60
+ "",
61
+ ].join("\n"),
62
+ );
63
+
64
+ return root;
65
+ }
66
+
67
+ describe("testProject", function () {
68
+ it("allows locale expectedFormats to assert a partial nested subset", async function () {
69
+ const root = await createProject();
70
+ const projectConfig = getProjectConfig(root);
71
+ const datasource = new Datasource(projectConfig, root);
72
+
73
+ const result = await testProject(projectConfig, datasource, {
74
+ keyPattern: "^locales",
75
+ });
76
+
77
+ expect(result.hasError).toEqual(false);
78
+ expect(result.testsCount).toEqual({
79
+ passed: 1,
80
+ failed: 0,
81
+ });
82
+ expect(result.assertionsCount).toEqual({
83
+ passed: 1,
84
+ failed: 0,
85
+ });
86
+ });
87
+
88
+ it("supports locale raw-message translation, ad hoc formats, and target-aware format overrides", async function () {
89
+ const icuModulePath = path.join(
90
+ path.resolve(__dirname, "../../../.."),
91
+ "packages/module-icu/src/index.ts",
92
+ );
93
+ const root = await createProject(
94
+ [
95
+ `const { createICUModule } = require(${JSON.stringify(icuModulePath)});`,
96
+ "module.exports = {",
97
+ " modules: [createICUModule()],",
98
+ "};",
99
+ "",
100
+ ].join("\n"),
101
+ );
102
+
103
+ await writeFile(
104
+ root,
105
+ "targets/preview.yml",
106
+ [
107
+ "description: Preview",
108
+ "locales:",
109
+ " - en",
110
+ "formats:",
111
+ " en:",
112
+ " number:",
113
+ " money:",
114
+ " style: currency",
115
+ " currency: GBP",
116
+ "",
117
+ ].join("\n"),
118
+ );
119
+ await writeFile(
120
+ root,
121
+ "tests/locales/en.spec.yml",
122
+ [
123
+ "locale: en",
124
+ "assertions:",
125
+ " - description: Base locale formats and translation",
126
+ " expectedFormats:",
127
+ " number:",
128
+ " money:",
129
+ " currency: USD",
130
+ " rawMessage: '{amount, number, money}'",
131
+ " values:",
132
+ " amount: 12",
133
+ " expectedTranslation: '$12.00'",
134
+ " - description: Ad hoc formats are merged for translation",
135
+ " rawMessage: '{value, number, signed}'",
136
+ " values:",
137
+ " value: 7",
138
+ " formats:",
139
+ " number:",
140
+ " signed:",
141
+ " signDisplay: always",
142
+ " maximumFractionDigits: 0",
143
+ " expectedTranslation: '+7'",
144
+ " - description: Target overrides affect locale formats and translation",
145
+ " target: preview",
146
+ " expectedFormats:",
147
+ " number:",
148
+ " money:",
149
+ " currency: GBP",
150
+ " rawMessage: '{amount, number, money}'",
151
+ " values:",
152
+ " amount: 12",
153
+ " expectedTranslation: '£12.00'",
154
+ " - matrix:",
155
+ " name: [Ada, Sam]",
156
+ " description: Greeting ${{ name }}",
157
+ " rawMessage: 'Hello {name}'",
158
+ " values:",
159
+ " name: ${{ name }}",
160
+ " expectedTranslation: 'Hello ${{ name }}'",
161
+ "",
162
+ ].join("\n"),
163
+ );
164
+
165
+ const projectConfig = getProjectConfig(root);
166
+ const datasource = new Datasource(projectConfig, root);
167
+ const result = await testProject(projectConfig, datasource, {
168
+ keyPattern: "^locales",
169
+ });
170
+
171
+ expect(result.hasError).toEqual(false);
172
+ expect(result.testsCount).toEqual({
173
+ passed: 1,
174
+ failed: 0,
175
+ });
176
+ expect(result.assertionsCount).toEqual({
177
+ passed: 5,
178
+ failed: 0,
179
+ });
180
+ });
181
+
182
+ it("supports target raw-message and message translation through built target datafiles", async function () {
183
+ const icuModulePath = path.join(
184
+ path.resolve(__dirname, "../../../.."),
185
+ "packages/module-icu/src/index.ts",
186
+ );
187
+ const interpolationModulePath = path.join(
188
+ path.resolve(__dirname, "../../../.."),
189
+ "packages/module-interpolation/src/index.ts",
190
+ );
191
+ const root = await createProject(
192
+ [
193
+ `const { createICUModule } = require(${JSON.stringify(icuModulePath)});`,
194
+ `const { createInterpolationModule } = require(${JSON.stringify(interpolationModulePath)});`,
195
+ "module.exports = {",
196
+ " modules: [createInterpolationModule(), createICUModule()],",
197
+ "};",
198
+ "",
199
+ ].join("\n"),
200
+ );
201
+
202
+ await writeFile(
203
+ root,
204
+ "targets/mobile.yml",
205
+ [
206
+ "description: Mobile",
207
+ "includeMessages:",
208
+ " - common.welcome",
209
+ "locales:",
210
+ " - en",
211
+ "context:",
212
+ " platform: mobile",
213
+ "",
214
+ ].join("\n"),
215
+ );
216
+ await writeFile(
217
+ root,
218
+ "messages/common/welcome.yml",
219
+ [
220
+ "description: Welcome",
221
+ "translations:",
222
+ " en: Hello {name}",
223
+ "overrides:",
224
+ " - key: mobile",
225
+ " conditions:",
226
+ " attribute: platform",
227
+ " operator: equals",
228
+ " value: mobile",
229
+ " translations:",
230
+ " en: Hello mobile {name}",
231
+ "",
232
+ ].join("\n"),
233
+ );
234
+ await writeFile(
235
+ root,
236
+ "messages/common/hidden.yml",
237
+ ["description: Hidden", "translations:", " en: Hidden target message", ""].join("\n"),
238
+ );
239
+ await writeFile(
240
+ root,
241
+ "tests/targets/mobile.spec.yml",
242
+ [
243
+ "target: mobile",
244
+ "assertions:",
245
+ " - description: Target structure and raw translation",
246
+ " locale: en",
247
+ " expectedToIncludeMessages:",
248
+ " - common.welcome",
249
+ " expectedToNotIncludeMessages:",
250
+ " - common.hidden",
251
+ " expectedFormats:",
252
+ " number:",
253
+ " money:",
254
+ " currency: USD",
255
+ " rawMessage: 'Total: {amount, number, money}'",
256
+ " values:",
257
+ " amount: 12",
258
+ " expectedTranslation: 'Total: $12.00'",
259
+ " - description: Target message translation uses built target datafile",
260
+ " locale: en",
261
+ " message: common.welcome",
262
+ " values:",
263
+ " name: Ada",
264
+ " context:",
265
+ " platform: mobile",
266
+ " expectedTranslation: Hello mobile Ada",
267
+ " - description: Missing translation follows normal SDK behavior",
268
+ " locale: en",
269
+ " message: common.hidden",
270
+ " expectedTranslation: common.hidden",
271
+ " - matrix:",
272
+ " amount: [7, 8]",
273
+ " description: Matrix target raw ${{ amount }}",
274
+ " locale: en",
275
+ " rawMessage: 'Amount: {amount, number, money}'",
276
+ " values:",
277
+ " amount: ${{ amount }}",
278
+ " expectedTranslation: 'Amount: $${{ amount }}.00'",
279
+ "",
280
+ ].join("\n"),
281
+ );
282
+
283
+ const projectConfig = getProjectConfig(root);
284
+ const datasource = new Datasource(projectConfig, root);
285
+ const result = await testProject(projectConfig, datasource, {
286
+ keyPattern: "^targets",
287
+ });
288
+
289
+ expect(result.hasError).toEqual(false);
290
+ expect(result.testsCount).toEqual({
291
+ passed: 1,
292
+ failed: 0,
293
+ });
294
+ expect(result.assertionsCount).toEqual({
295
+ passed: 5,
296
+ failed: 0,
297
+ });
298
+ });
299
+
300
+ it("keeps raw ICU-looking message text when project config does not register modules", async function () {
301
+ const root = await createProject();
302
+
303
+ await writeFile(
304
+ root,
305
+ "messages/common/welcome.yml",
306
+ ["description: Welcome", "translations:", " en: Welcome {name}", ""].join("\n"),
307
+ );
308
+ await writeFile(
309
+ root,
310
+ "tests/messages/common/welcome.spec.yml",
311
+ [
312
+ "message: common.welcome",
313
+ "assertions:",
314
+ " - locale: en",
315
+ " target: web",
316
+ " values:",
317
+ " name: Ada",
318
+ " expectedTranslation: Welcome {name}",
319
+ "",
320
+ ].join("\n"),
321
+ );
322
+
323
+ const projectConfig = getProjectConfig(root);
324
+ const datasource = new Datasource(projectConfig, root);
325
+ const result = await testProject(projectConfig, datasource, {
326
+ keyPattern: "^messages",
327
+ });
328
+
329
+ expect(projectConfig.modules).toEqual([]);
330
+ expect(result.hasError).toEqual(false);
331
+ expect(result.assertionsCount).toEqual({
332
+ passed: 1,
333
+ failed: 0,
334
+ });
335
+ });
336
+
337
+ it("uses withFlags and withVariations for feature and experiment assertions", async function () {
338
+ const root = await createProject();
339
+
340
+ await writeFile(
341
+ root,
342
+ "messages/common/welcome.yml",
343
+ [
344
+ "description: Welcome",
345
+ "translations:",
346
+ " en: Default welcome",
347
+ "overrides:",
348
+ " - key: feature",
349
+ " conditions:",
350
+ " feature: new-homepage",
351
+ " operator: isEnabled",
352
+ " translations:",
353
+ " en: Feature welcome",
354
+ " - key: variation",
355
+ " conditions:",
356
+ " experiment: homepage-copy",
357
+ " operator: hasVariation",
358
+ " value: bold",
359
+ " translations:",
360
+ " en: Bold welcome",
361
+ "",
362
+ ].join("\n"),
363
+ );
364
+ await writeFile(
365
+ root,
366
+ "tests/messages/common/welcome.spec.yml",
367
+ [
368
+ "message: common.welcome",
369
+ "assertions:",
370
+ " - locale: en",
371
+ " target: web",
372
+ " expectedTranslation: Default welcome",
373
+ " - locale: en",
374
+ " target: web",
375
+ " withFlags:",
376
+ " new-homepage: true",
377
+ " expectedTranslation: Feature welcome",
378
+ " - locale: en",
379
+ " target: web",
380
+ " withVariations:",
381
+ " homepage-copy: bold",
382
+ " expectedTranslation: Bold welcome",
383
+ "",
384
+ ].join("\n"),
385
+ );
386
+
387
+ const projectConfig = getProjectConfig(root);
388
+ const datasource = new Datasource(projectConfig, root);
389
+ const result = await testProject(projectConfig, datasource, {
390
+ keyPattern: "^messages",
391
+ });
392
+
393
+ expect(result.hasError).toEqual(false);
394
+ expect(result.assertionsCount).toEqual({
395
+ passed: 3,
396
+ failed: 0,
397
+ });
398
+ });
399
+
400
+ it("expands matrix assertions across message, segment, locale, and target tests", async function () {
401
+ const interpolationModulePath = path.join(
402
+ path.resolve(__dirname, "../../../.."),
403
+ "packages/module-interpolation/src/index.ts",
404
+ );
405
+ const root = await createProject(
406
+ [
407
+ `const { createInterpolationModule } = require(${JSON.stringify(interpolationModulePath)});`,
408
+ "module.exports = {",
409
+ " modules: [createInterpolationModule()],",
410
+ "};",
411
+ "",
412
+ ].join("\n"),
413
+ );
414
+
415
+ await writeFile(
416
+ root,
417
+ "messages/common/welcome.yml",
418
+ [
419
+ "description: Welcome",
420
+ "translations:",
421
+ " en: Hello {name}",
422
+ "overrides:",
423
+ " - key: adult",
424
+ " segments: adult",
425
+ " translations:",
426
+ " en: Hello adult",
427
+ "",
428
+ ].join("\n"),
429
+ );
430
+ await writeFile(
431
+ root,
432
+ "segments/adult.yml",
433
+ [
434
+ "description: Adult",
435
+ "conditions:",
436
+ " - attribute: age",
437
+ " operator: greaterThanOrEquals",
438
+ " value: 18",
439
+ "",
440
+ ].join("\n"),
441
+ );
442
+ await writeFile(
443
+ root,
444
+ "tests/messages/common/welcome.spec.yml",
445
+ [
446
+ "message: common.welcome",
447
+ "assertions:",
448
+ " - matrix:",
449
+ " name: [Ada, Sam]",
450
+ " locale: en",
451
+ " target: web",
452
+ " description: Greeting ${{ name }}",
453
+ " values:",
454
+ " name: ${{ name }}",
455
+ " expectedTranslation: Hello ${{ name }}",
456
+ " - matrix:",
457
+ " age: [21, 22]",
458
+ " expected: [Hello adult]",
459
+ " locale: en",
460
+ " target: web",
461
+ " description: Adult check ${{ age }}",
462
+ " context:",
463
+ " age: ${{ age }}",
464
+ " expectedTranslation: ${{ expected }}",
465
+ "",
466
+ ].join("\n"),
467
+ );
468
+ await writeFile(
469
+ root,
470
+ "tests/segments/adult.spec.yml",
471
+ [
472
+ "segment: adult",
473
+ "assertions:",
474
+ " - matrix:",
475
+ " age: [21, 22]",
476
+ " expected: [true]",
477
+ " description: Segment ${{ age }}",
478
+ " segment: adult",
479
+ " context:",
480
+ " age: ${{ age }}",
481
+ " expectedToMatch: ${{ expected }}",
482
+ "",
483
+ ].join("\n"),
484
+ );
485
+ await writeFile(
486
+ root,
487
+ "tests/locales/en.spec.yml",
488
+ [
489
+ "locale: en",
490
+ "assertions:",
491
+ " - matrix:",
492
+ " label: [primary, secondary]",
493
+ " currency: [USD]",
494
+ " description: Locale ${{ label }}",
495
+ " expectedFormats:",
496
+ " number:",
497
+ " money:",
498
+ " currency: ${{ currency }}",
499
+ "",
500
+ ].join("\n"),
501
+ );
502
+ await writeFile(
503
+ root,
504
+ "tests/targets/web.spec.yml",
505
+ [
506
+ "target: web",
507
+ "assertions:",
508
+ " - matrix:",
509
+ " label: [web-a, web-b]",
510
+ " currency: [USD]",
511
+ " description: Target ${{ label }}",
512
+ " locale: en",
513
+ " expectedFormats:",
514
+ " number:",
515
+ " money:",
516
+ " currency: ${{ currency }}",
517
+ "",
518
+ ].join("\n"),
519
+ );
520
+
521
+ const projectConfig = getProjectConfig(root);
522
+ const datasource = new Datasource(projectConfig, root);
523
+ const result = await testProject(projectConfig, datasource);
524
+
525
+ expect(result.hasError).toEqual(false);
526
+ expect(result.assertionsCount).toEqual({
527
+ passed: 10,
528
+ failed: 0,
529
+ });
530
+ });
531
+
532
+ it("filters expanded matrix assertions by resolved description", async function () {
533
+ const root = await createProject();
534
+
535
+ await writeFile(
536
+ root,
537
+ "tests/segments/adult.spec.yml",
538
+ [
539
+ "segment: adult",
540
+ "assertions:",
541
+ " - matrix:",
542
+ " city: [Amsterdam, Rotterdam]",
543
+ " description: Segment ${{ city }}",
544
+ " segment: adult",
545
+ " context:",
546
+ " age: 21",
547
+ " expectedToMatch: true",
548
+ "",
549
+ ].join("\n"),
550
+ );
551
+ await writeFile(
552
+ root,
553
+ "segments/adult.yml",
554
+ [
555
+ "description: Adult",
556
+ "conditions:",
557
+ " - attribute: age",
558
+ " operator: greaterThanOrEquals",
559
+ " value: 18",
560
+ "",
561
+ ].join("\n"),
562
+ );
563
+
564
+ const projectConfig = getProjectConfig(root);
565
+ const datasource = new Datasource(projectConfig, root);
566
+ const result = await testProject(projectConfig, datasource, {
567
+ keyPattern: "^segments",
568
+ assertionPattern: "Rotterdam",
569
+ });
570
+
571
+ expect(result.hasError).toEqual(false);
572
+ expect(result.assertionsCount).toEqual({
573
+ passed: 1,
574
+ failed: 0,
575
+ });
576
+ });
577
+ });