@fremtind/jokul 5.0.0-next.10 → 5.0.0-next.11

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 (49) hide show
  1. package/codemods/CODEMODS.md +133 -0
  2. package/codemods/__tests__/import-paths.test.mjs +467 -92
  3. package/codemods/import-paths.mjs +25 -354
  4. package/codemods/transforms/color-tokens.mjs +77 -0
  5. package/codemods/transforms/font-family.mjs +23 -0
  6. package/codemods/transforms/import-specifiers.mjs +230 -0
  7. package/codemods/transforms/warnings.mjs +41 -0
  8. package/codemods/utils.mjs +35 -0
  9. package/package.json +1 -1
  10. package/styles/base.css +32 -50
  11. package/styles/base.min.css +1 -1
  12. package/styles/components/countdown/countdown.css +2 -2
  13. package/styles/components/countdown/countdown.min.css +1 -1
  14. package/styles/components/expander/expandable.css +13 -14
  15. package/styles/components/expander/expandable.min.css +1 -1
  16. package/styles/components/expander/expandable.scss +16 -17
  17. package/styles/components/feedback/feedback.css +2 -2
  18. package/styles/components/feedback/feedback.min.css +1 -1
  19. package/styles/components/file-input/file-input.css +9 -9
  20. package/styles/components/file-input/file-input.min.css +1 -1
  21. package/styles/components/input-group/input-group.css +2 -2
  22. package/styles/components/input-group/input-group.min.css +1 -1
  23. package/styles/components/list/list.css +37 -21
  24. package/styles/components/list/list.min.css +1 -1
  25. package/styles/components/list/list.scss +27 -23
  26. package/styles/components/loader/loader.css +6 -6
  27. package/styles/components/loader/loader.min.css +1 -1
  28. package/styles/components/loader/skeleton-loader.css +3 -3
  29. package/styles/components/loader/skeleton-loader.min.css +1 -1
  30. package/styles/components/message/message.css +2 -2
  31. package/styles/components/message/message.min.css +1 -1
  32. package/styles/components/progress-bar/progress-bar.css +1 -1
  33. package/styles/components/progress-bar/progress-bar.min.css +1 -1
  34. package/styles/components/segmented-control/segmented-control.css +2 -2
  35. package/styles/components/segmented-control/segmented-control.min.css +1 -1
  36. package/styles/components/system-message/system-message.css +2 -2
  37. package/styles/components/system-message/system-message.min.css +1 -1
  38. package/styles/components/toast/toast.css +4 -4
  39. package/styles/components/toast/toast.min.css +1 -1
  40. package/styles/components/typography/text.scss +2 -2
  41. package/styles/components/typography/title.scss +2 -2
  42. package/styles/components.css +74 -59
  43. package/styles/components.min.css +1 -1
  44. package/styles/tailwind.css +2 -2
  45. package/styles/theme/_dynamic-spacing.scss +21 -15
  46. package/styles/theme/_fonts.scss +3 -12
  47. package/styles/theme/_size.scss +20 -20
  48. package/styles/theme/_tokens.scss +11 -11
  49. package/styles/core/utility/_paragraphs.scss +0 -39
@@ -58,71 +58,42 @@ test("migrates internal relative core jkl imports", () => {
58
58
  test("rewrites beta style imports when the beta component is used in the same file", () => {
59
59
  const source = `
60
60
  import { BETA_Select } from "@fremtind/jokul/select";
61
- import "@fremtind/jokul/styles/components/select/_index.scss";
61
+ import "@fremtind/jokul/styles/components/select";
62
62
  `;
63
63
 
64
- const result = transformImportPaths(source, "/tmp/example.tsx");
64
+ const result = transformImportPaths(source, "/tmp/MyForm.tsx");
65
65
 
66
66
  assert.equal(
67
67
  result.text.includes(
68
- '@fremtind/jokul/styles/components/beta/select/_index.scss',
68
+ '@fremtind/jokul/styles/components/beta/select',
69
69
  ),
70
70
  true,
71
71
  );
72
72
  assert.deepEqual(result.warnings, []);
73
73
  });
74
74
 
75
- test("warns when beta style imports are ambiguous", () => {
75
+ test("warns when beta style import is ambiguous (no beta identifier in file)", () => {
76
76
  const source = `
77
- import "@fremtind/jokul/styles/components/nav-link";
77
+ import "@fremtind/jokul/styles/components/select";
78
78
  `;
79
79
 
80
- const result = transformImportPaths(source, "/tmp/example.scss");
80
+ const result = transformImportPaths(source, "/tmp/global.scss");
81
81
 
82
- assert.equal(result.changed, false);
83
82
  assert.equal(result.warnings.length, 1);
83
+ assert.match(result.warnings[0], /Select/);
84
84
  });
85
85
 
86
- test("removes redundant webfonts.css imports when base or components css is also imported", () => {
87
- const source = `import "@fremtind/jokul/styles/styles.css";
88
- import "@fremtind/jokul/styles/core/core.css";
86
+ test("removes esm webfonts.css import when base or components css is present", () => {
87
+ const source = `import "@fremtind/jokul/styles/base.css";
89
88
  import "@fremtind/jokul/styles/fonts/webfonts.css";
90
89
  `;
91
90
 
92
91
  const result = transformImportPaths(source, "/tmp/main.tsx");
93
92
 
94
- assert.equal(
95
- result.text.includes("@fremtind/jokul/styles/fonts/webfonts.css"),
96
- false,
97
- );
98
- assert.equal(
99
- result.text.includes('import "@fremtind/jokul/styles/components.css";'),
100
- true,
101
- );
102
- assert.equal(
103
- result.text.includes('import "@fremtind/jokul/styles/base.css";'),
104
- true,
105
- );
93
+ assert.equal(result.text.includes("webfonts"), false);
106
94
  assert.deepEqual(result.warnings, []);
107
95
  });
108
96
 
109
- test("removes minified webfonts.css imports as well", () => {
110
- const source = `import "@fremtind/jokul/styles/core/core.min.css";
111
- import "@fremtind/jokul/styles/fonts/webfonts.min.css";
112
- `;
113
-
114
- const result = transformImportPaths(source, "/tmp/main.ts");
115
-
116
- assert.equal(
117
- result.text.includes("webfonts"),
118
- false,
119
- );
120
- assert.equal(
121
- result.text.includes('import "@fremtind/jokul/styles/base.min.css";'),
122
- true,
123
- );
124
- });
125
-
126
97
  test("warns when webfonts.css is removed without a base or components import", () => {
127
98
  const source = `import "@fremtind/jokul/styles/fonts/webfonts.css";
128
99
  `;
@@ -149,121 +120,525 @@ test("removes css @import of webfonts.css", () => {
149
120
  assert.deepEqual(result.warnings, []);
150
121
  });
151
122
 
152
- test("warns about removed sass color variables", () => {
153
- const source = `@use "@fremtind/jokul/styles/jkl";
154
123
 
155
- .banner {
156
- background: jkl.$color-granitt;
157
- color: jkl.$color-snohvit;
124
+ test("renames Fremtind Material Symbols font-family", () => {
125
+ const source = `.icon {
126
+ font-family: "Fremtind Material Symbols", "Fremtind Material Symbols Fallback", sans-serif;
158
127
  }
159
128
  `;
160
129
 
161
- const result = transformImportPaths(source, "/tmp/banner.scss");
130
+ const result = transformImportPaths(source, "/tmp/icons.scss");
162
131
 
163
132
  assert.equal(
164
- result.warnings.some((warning) =>
165
- /jkl\.\$color-\*/.test(warning),
133
+ result.text.includes("Fremtind Material Symbols"),
134
+ false,
135
+ );
136
+ assert.equal(
137
+ result.text.includes(
138
+ '"Jokul Icons", "Jokul Icons Fallback", sans-serif',
166
139
  ),
167
140
  true,
168
141
  );
169
- // Bare én advarsel per mønster, selv om det er flere forekomster
142
+ assert.equal(result.replacements, 2);
143
+ });
144
+
145
+ test("renames Fremtind Material Symbols inside CSS files too", () => {
146
+ const source = `.icon {
147
+ font-family: 'Fremtind Material Symbols';
148
+ }
149
+ `;
150
+
151
+ const result = transformImportPaths(source, "/tmp/icons.css");
152
+
170
153
  assert.equal(
171
- result.warnings.filter((warning) =>
172
- /jkl\.\$color-\*/.test(warning),
173
- ).length,
174
- 1,
154
+ result.text.includes("Fremtind Material Symbols"),
155
+ false,
175
156
  );
157
+ assert.equal(result.text.includes("'Jokul Icons'"), true);
176
158
  });
177
159
 
178
- test("warns about removed light/dark mode mixins", () => {
179
- const source = `@use "@fremtind/jokul/styles/jkl";
160
+ test("renames CSS background-action token", () => {
161
+ const source = `.btn {
162
+ background: var(--jkl-color-background-action);
163
+ color: var(--jkl-color-text-on-action);
164
+ }
165
+ `;
166
+
167
+ const result = transformImportPaths(source, "/tmp/button.css");
168
+
169
+ assert.equal(result.text.includes("--jkl-color-background-action"), false);
170
+ assert.equal(result.text.includes("--jkl-color-text-on-action"), false);
171
+ assert.equal(result.text.includes("--jkl-color-background-contrast"), true);
172
+ assert.equal(result.text.includes("--jkl-color-text-on-contrast"), true);
173
+ assert.equal(result.changed, true);
174
+ });
175
+
176
+ test("renames CSS text-inverted token to text-on-contrast", () => {
177
+ const source = `.inverted {
178
+ color: var(--jkl-color-text-inverted);
179
+ }
180
+ `;
181
+
182
+ const result = transformImportPaths(source, "/tmp/theme.css");
183
+
184
+ assert.equal(result.text.includes("--jkl-color-text-inverted"), false);
185
+ assert.equal(result.text.includes("--jkl-color-text-on-contrast"), true);
186
+ });
180
187
 
181
- @include jkl.light-mode-variables {
182
- --min-farge: jkl.$color-granitt;
188
+ test("renames CSS container-high and container-low tokens", () => {
189
+ const source = `.card-high {
190
+ background: var(--jkl-color-background-container-high);
183
191
  }
184
- @include jkl.dark-mode-variables {
185
- --min-farge: jkl.$color-snohvit;
192
+ .card-low {
193
+ background: var(--jkl-color-background-container-low);
186
194
  }
187
195
  `;
188
196
 
189
- const result = transformImportPaths(source, "/tmp/theme.scss");
197
+ const result = transformImportPaths(source, "/tmp/card.css");
190
198
 
199
+ assert.equal(result.text.includes("--jkl-color-background-container-high"), false);
200
+ assert.equal(result.text.includes("--jkl-color-background-container-low"), false);
191
201
  assert.equal(
192
- result.warnings.some((warning) =>
193
- /light-mode-variables/.test(warning),
194
- ),
195
- true,
202
+ result.text.split("--jkl-color-background-container").length - 1,
203
+ 2,
204
+ "should have two occurrences of the new token",
196
205
  );
197
206
  });
198
207
 
199
- test("warns about deprecated text-style names", () => {
200
- const source = `@use "@fremtind/jokul/styles/jkl";
208
+ test("renames CSS alert tokens to feedback tokens", () => {
209
+ const source = `.info { background: var(--jkl-color-background-alert-info); }
210
+ .warning { background: var(--jkl-color-background-alert-warning); }
211
+ .error { background: var(--jkl-color-background-alert-error); }
212
+ .success { background: var(--jkl-color-background-alert-success); }
213
+ `;
201
214
 
202
- .lead {
203
- @include jkl.text-style("body");
204
- }
215
+ const result = transformImportPaths(source, "/tmp/alerts.css");
216
+
217
+ assert.equal(result.text.includes("--jkl-color-background-alert-"), false);
218
+ assert.equal(result.text.includes("--jkl-color-info-background-container"), true);
219
+ assert.equal(result.text.includes("--jkl-color-warning-background-container"), true);
220
+ assert.equal(result.text.includes("--jkl-color-error-background-container"), true);
221
+ assert.equal(result.text.includes("--jkl-color-success-background-container"), true);
222
+ });
205
223
 
206
- .fineprint {
207
- @include jkl.text-style("small");
224
+ test("warns about removed interactive tokens", () => {
225
+ const source = `.item {
226
+ background: var(--jkl-color-background-interactive);
227
+ color: var(--jkl-color-text-interactive);
208
228
  }
209
229
  `;
210
230
 
211
- const result = transformImportPaths(source, "/tmp/typography.scss");
231
+ const result = transformImportPaths(source, "/tmp/interactive.css");
212
232
 
213
233
  assert.equal(
214
- result.warnings.some((warning) =>
215
- /paragraph-large\/medium\/small/.test(warning),
216
- ),
234
+ result.warnings.some((w) => /interactive/.test(w)),
217
235
  true,
218
236
  );
219
237
  assert.equal(
220
- result.warnings.some((warning) => /<Text>-komponenten/.test(warning)),
238
+ result.warnings.filter((w) => /interactive/.test(w)).length,
239
+ 1,
240
+ "should produce one warning for both interactive tokens",
241
+ );
242
+ });
243
+
244
+ test("warns about removed text-on-alert tokens", () => {
245
+ const source = `.info { color: var(--jkl-color-text-on-alert-info); }
246
+ `;
247
+
248
+ const result = transformImportPaths(source, "/tmp/alerts.css");
249
+
250
+ assert.equal(
251
+ result.warnings.some((w) => /text-on-alert/.test(w)),
221
252
  true,
222
253
  );
223
254
  });
224
255
 
225
- test("renames Fremtind Material Symbols font-family", () => {
226
- const source = `.icon {
227
- font-family: "Fremtind Material Symbols", "Fremtind Material Symbols Fallback", sans-serif;
228
- }
256
+ test("warns about Card variant prop", () => {
257
+ const source = `<Card variant="outlined">
258
+ <p>Innhold</p>
259
+ </Card>
229
260
  `;
230
261
 
231
- const result = transformImportPaths(source, "/tmp/icons.scss");
262
+ const result = transformImportPaths(source, "/tmp/MyCard.tsx");
232
263
 
233
264
  assert.equal(
234
- result.text.includes("Fremtind Material Symbols"),
235
- false,
265
+ result.warnings.some((w) => /variant/.test(w) && /Card/.test(w)),
266
+ true,
267
+ );
268
+ });
269
+
270
+ test("warns about Card variant high and low", () => {
271
+ const source = `function Page() {
272
+ return (
273
+ <>
274
+ <Card variant="high">Høy</Card>
275
+ <Card variant="low">Lav</Card>
276
+ </>
236
277
  );
278
+ }
279
+ `;
280
+
281
+ const result = transformImportPaths(source, "/tmp/Page.tsx");
282
+
237
283
  assert.equal(
238
- result.text.includes(
239
- '"Jokul Icons", "Jokul Icons Fallback", sans-serif',
240
- ),
284
+ result.warnings.some((w) => /variant/.test(w)),
241
285
  true,
242
286
  );
287
+ assert.equal(
288
+ result.warnings.filter((w) => /variant/.test(w)).length,
289
+ 1,
290
+ "should produce one warning even with multiple variant usages",
291
+ );
292
+ });
293
+
294
+ test("renames container-inverted to background-contrast", () => {
295
+ const source = `.inverted { background: var(--jkl-color-background-container-inverted); }
296
+ `;
297
+
298
+ const result = transformImportPaths(source, "/tmp/inverted.css");
299
+
300
+ assert.equal(result.text.includes("--jkl-color-background-container-inverted"), false);
301
+ assert.equal(result.text.includes("--jkl-color-background-contrast"), true);
302
+ assert.deepEqual(result.warnings, []);
303
+ });
304
+
305
+ // --- idempotency ---
306
+
307
+ test("is idempotent — running twice gives same result", () => {
308
+ const source = `import "@fremtind/jokul/styles/core/core.scss";
309
+
310
+ .btn { background: var(--jkl-color-background-action); }
311
+ `;
312
+
313
+ const first = transformImportPaths(source, "/tmp/page.scss");
314
+ const second = transformImportPaths(first.text, "/tmp/page.scss");
315
+
316
+ assert.equal(second.changed, false);
317
+ assert.equal(second.replacements, 0);
318
+ assert.equal(second.text, first.text);
319
+ });
320
+
321
+ // --- no-op ---
322
+
323
+ test("returns changed: false for an already-migrated file", () => {
324
+ const source = `import "@fremtind/jokul/styles/base.scss";
325
+
326
+ .btn { background: var(--jkl-color-background-contrast); }
327
+ `;
328
+
329
+ const result = transformImportPaths(source, "/tmp/page.ts");
330
+
331
+ assert.equal(result.changed, false);
332
+ assert.equal(result.replacements, 0);
333
+ assert.deepEqual(result.warnings, []);
334
+ });
335
+
336
+ // --- token boundary safety ---
337
+
338
+ test("does not rename token that is a prefix of a longer token", () => {
339
+ // --jkl-color-text-inverted should not match inside --jkl-color-text-inverted-extra
340
+ const source = `.x { color: var(--jkl-color-text-inverted-extra); }`;
341
+
342
+ const result = transformImportPaths(source, "/tmp/x.css");
343
+
344
+ assert.equal(result.text.includes("--jkl-color-text-inverted-extra"), true);
345
+ assert.equal(result.changed, false);
346
+ });
347
+
348
+ test("does not rename --jkl-color-background-container (base token, not a v4 token)", () => {
349
+ const source = `.x { background: var(--jkl-color-background-container); }`;
350
+
351
+ const result = transformImportPaths(source, "/tmp/x.css");
352
+
353
+ assert.equal(result.text, source);
354
+ assert.equal(result.changed, false);
355
+ });
356
+
357
+ test("renames container-high but leaves sibling container-low and base container intact in same rule", () => {
358
+ const source = `.x {
359
+ --high: var(--jkl-color-background-container-high);
360
+ --low: var(--jkl-color-background-container-low);
361
+ --base: var(--jkl-color-background-container);
362
+ }
363
+ `;
364
+
365
+ const result = transformImportPaths(source, "/tmp/x.css");
366
+
367
+ assert.equal(result.text.includes("--jkl-color-background-container-high"), false);
368
+ assert.equal(result.text.includes("--jkl-color-background-container-low"), false);
369
+ assert.equal(result.text.includes("var(--jkl-color-background-container)"), true,
370
+ "base container token must survive unchanged");
243
371
  assert.equal(result.replacements, 2);
244
372
  });
245
373
 
246
- test("renames Fremtind Material Symbols inside CSS files too", () => {
374
+ test("does not rename alert-info token that has a longer suffix", () => {
375
+ const source = `.x { background: var(--jkl-color-background-alert-info-extra); }`;
376
+
377
+ const result = transformImportPaths(source, "/tmp/x.css");
378
+
379
+ assert.equal(result.changed, false);
380
+ });
381
+
382
+ // --- CSS token as custom property definition ---
383
+
384
+ test("renames token used as a custom property definition, not just inside var()", () => {
385
+ const source = `:root {
386
+ --jkl-color-background-action: #005aa4;
387
+ --jkl-color-text-inverted: #fff;
388
+ }
389
+ `;
390
+
391
+ const result = transformImportPaths(source, "/tmp/overrides.css");
392
+
393
+ assert.equal(result.text.includes("--jkl-color-background-action:"), false);
394
+ assert.equal(result.text.includes("--jkl-color-text-inverted:"), false);
395
+ assert.equal(result.text.includes("--jkl-color-background-contrast:"), true);
396
+ assert.equal(result.text.includes("--jkl-color-text-on-contrast:"), true);
397
+ });
398
+
399
+ // --- webfonts edge cases ---
400
+
401
+ test("removes webfonts.min.css import", () => {
402
+ const source = `import "@fremtind/jokul/styles/base.css";
403
+ import "@fremtind/jokul/styles/fonts/webfonts.min.css";
404
+ `;
405
+
406
+ const result = transformImportPaths(source, "/tmp/main.tsx");
407
+
408
+ assert.equal(result.text.includes("webfonts"), false);
409
+ assert.deepEqual(result.warnings, []);
410
+ });
411
+
412
+ test("removes webfonts.css when imported with require()", () => {
413
+ const source = `require("@fremtind/jokul/styles/base.css");
414
+ require("@fremtind/jokul/styles/fonts/webfonts.css");
415
+ `;
416
+
417
+ const result = transformImportPaths(source, "/tmp/main.cjs");
418
+
419
+ assert.equal(result.text.includes("webfonts"), false);
420
+ assert.deepEqual(result.warnings, []);
421
+ });
422
+
423
+ // --- font family edge cases ---
424
+
425
+ test("renames only the fallback font-family if primary is absent", () => {
247
426
  const source = `.icon {
248
- font-family: 'Fremtind Material Symbols';
427
+ font-family: "Fremtind Material Symbols Fallback";
249
428
  }
250
429
  `;
251
430
 
252
431
  const result = transformImportPaths(source, "/tmp/icons.css");
253
432
 
254
- assert.equal(
255
- result.text.includes("Fremtind Material Symbols"),
256
- false,
257
- );
258
- assert.equal(result.text.includes("'Jokul Icons'"), true);
433
+ assert.equal(result.text.includes("Fremtind Material Symbols Fallback"), false);
434
+ assert.equal(result.text.includes("Jokul Icons Fallback"), true);
435
+ assert.equal(result.replacements, 1);
436
+ });
437
+
438
+ // --- combined transforms ---
439
+
440
+ test("applies import path and token rename in one pass", () => {
441
+ const source = `import "@fremtind/jokul/styles/core/core.scss";
442
+
443
+ .inverted {
444
+ background: var(--jkl-color-background-action);
445
+ }
446
+
447
+ export default function Page() {
448
+ return <section />;
449
+ }
450
+ `;
451
+
452
+ const result = transformImportPaths(source, "/tmp/page.tsx");
453
+
454
+ assert.equal(result.text.includes("styles/core/core"), false);
455
+ assert.equal(result.text.includes("--jkl-color-background-action"), false);
456
+ assert.equal(result.text.includes("styles/base.scss"), true);
457
+ assert.equal(result.text.includes("--jkl-color-background-contrast"), true);
458
+ assert.equal(result.replacements, 2);
459
+ });
460
+
461
+ // --- SCSS-only transforms ---
462
+
463
+ test("does not reorder font import in a .css file", () => {
464
+ const source = `@import "@fremtind/jokul/styles/base.css";
465
+ @import "@fremtind/jokul/styles/theme/fonts";
466
+ `;
467
+
468
+ const result = transformImportPaths(source, "/tmp/global.css");
469
+
470
+ assert.equal(result.reordered, false);
471
+ });
472
+
473
+ // --- Tailwind color class renames ---
474
+
475
+ test("renames bg-background-action to bg-background-contrast", () => {
476
+ const source = `<div className="bg-background-action text-white">Innhold</div>`;
477
+
478
+ const result = transformImportPaths(source, "/tmp/component.tsx");
479
+
480
+ assert.equal(result.text.includes("bg-background-action"), false);
481
+ assert.equal(result.text.includes("bg-background-contrast"), true);
482
+ assert.equal(result.replacements, 1);
259
483
  });
260
484
 
485
+ test("renames text-text-inverted to text-text-on-contrast", () => {
486
+ const source = `<p className="text-text-inverted">Tekst</p>`;
487
+
488
+ const result = transformImportPaths(source, "/tmp/page.tsx");
489
+
490
+ assert.equal(result.text.includes("text-text-inverted"), false);
491
+ assert.equal(result.text.includes("text-text-on-contrast"), true);
492
+ });
493
+
494
+ test("renames text-text-on-action to text-text-on-contrast", () => {
495
+ const source = `<button className="bg-background-contrast text-text-on-action">Knapp</button>`;
496
+
497
+ const result = transformImportPaths(source, "/tmp/button.tsx");
498
+
499
+ assert.equal(result.text.includes("text-text-on-action"), false);
500
+ assert.equal(result.text.includes("text-text-on-contrast"), true);
501
+ });
502
+
503
+ test("renames bg-background-container-high and -low", () => {
504
+ const source = `<div className="bg-background-container-high md:bg-background-container-low">
505
+ Innhold
506
+ </div>`;
507
+
508
+ const result = transformImportPaths(source, "/tmp/card.tsx");
509
+
510
+ assert.equal(result.text.includes("bg-background-container-high"), false);
511
+ assert.equal(result.text.includes("bg-background-container-low"), false);
512
+ assert.equal(result.text.includes("bg-background-container"), true);
513
+ assert.equal(result.replacements, 2);
514
+ });
515
+
516
+ test("does not rename bg-background-container (base Tailwind class)", () => {
517
+ const source = `<div className="bg-background-container">Innhold</div>`;
518
+
519
+ const result = transformImportPaths(source, "/tmp/card.tsx");
520
+
521
+ assert.equal(result.text, source);
522
+ assert.equal(result.changed, false);
523
+ });
524
+
525
+ test("renames bg-background-alert-info to bg-info-background-container", () => {
526
+ const source = `<div className="bg-background-alert-info border-border-subdued">
527
+ Info
528
+ </div>`;
529
+
530
+ const result = transformImportPaths(source, "/tmp/alert.tsx");
531
+
532
+ assert.equal(result.text.includes("bg-background-alert-info"), false);
533
+ assert.equal(result.text.includes("bg-info-background-container"), true);
534
+ assert.equal(result.replacements, 1);
535
+ });
536
+
537
+ test("renames all four alert color classes", () => {
538
+ const source = `className="bg-background-alert-info bg-background-alert-warning bg-background-alert-error bg-background-alert-success"`;
539
+
540
+ const result = transformImportPaths(source, "/tmp/alerts.tsx");
541
+
542
+ assert.equal(result.text.includes("bg-background-alert-"), false);
543
+ assert.equal(result.text.includes("bg-info-background-container"), true);
544
+ assert.equal(result.text.includes("bg-warning-background-container"), true);
545
+ assert.equal(result.text.includes("bg-error-background-container"), true);
546
+ assert.equal(result.text.includes("bg-success-background-container"), true);
547
+ assert.equal(result.replacements, 4);
548
+ });
549
+
550
+ test("renames Tailwind class with hover: modifier", () => {
551
+ const source = `<div className="hover:bg-background-action focus:bg-background-action">X</div>`;
552
+
553
+ const result = transformImportPaths(source, "/tmp/cmp.tsx");
554
+
555
+ assert.equal(result.text.includes("bg-background-action"), false);
556
+ assert.equal(result.text.includes("hover:bg-background-contrast"), true);
557
+ assert.equal(result.text.includes("focus:bg-background-contrast"), true);
558
+ assert.equal(result.replacements, 2);
559
+ });
560
+
561
+ test("renames Tailwind class with opacity modifier (/50)", () => {
562
+ const source = `<div className="bg-background-action/50">X</div>`;
563
+
564
+ const result = transformImportPaths(source, "/tmp/cmp.tsx");
565
+
566
+ assert.equal(result.text.includes("bg-background-action/50"), false);
567
+ assert.equal(result.text.includes("bg-background-contrast/50"), true);
568
+ assert.equal(result.replacements, 1);
569
+ });
570
+
571
+ test("renames Tailwind class in @apply rule", () => {
572
+ const source = `.btn {
573
+ @apply bg-background-action text-text-inverted;
574
+ }
575
+ `;
576
+
577
+ const result = transformImportPaths(source, "/tmp/styles.css");
578
+
579
+ assert.equal(result.text.includes("bg-background-action"), false);
580
+ assert.equal(result.text.includes("text-text-inverted"), false);
581
+ assert.equal(result.text.includes("bg-background-contrast"), true);
582
+ assert.equal(result.text.includes("text-text-on-contrast"), true);
583
+ assert.equal(result.replacements, 2);
584
+ });
585
+
586
+ test("renames non-bg prefix: border-background-action", () => {
587
+ const source = `<div className="border-background-action">X</div>`;
588
+
589
+ const result = transformImportPaths(source, "/tmp/cmp.tsx");
590
+
591
+ assert.equal(result.text.includes("border-background-action"), false);
592
+ assert.equal(result.text.includes("border-background-contrast"), true);
593
+ });
594
+
595
+ test("does not rename longer Tailwind class that shares a prefix", () => {
596
+ // bg-background-container-inverted-extra must not match bg-background-container-inverted
597
+ const source = `<div className="bg-background-container-inverted-extra">X</div>`;
598
+
599
+ const result = transformImportPaths(source, "/tmp/cmp.tsx");
600
+
601
+ assert.equal(result.text, source);
602
+ assert.equal(result.changed, false);
603
+ });
604
+
605
+ test("Tailwind renames are idempotent", () => {
606
+ const source = `<div className="bg-background-contrast text-text-on-contrast">X</div>`;
607
+
608
+ const result = transformImportPaths(source, "/tmp/cmp.tsx");
609
+
610
+ assert.equal(result.changed, false);
611
+ assert.equal(result.replacements, 0);
612
+ });
613
+
614
+ // --- Tailwind warnings ---
615
+
616
+ test("warns about bg-background-interactive Tailwind class", () => {
617
+ const source = `<div className="bg-background-interactive hover:bg-background-interactive-hover">X</div>`;
618
+
619
+ const result = transformImportPaths(source, "/tmp/cmp.tsx");
620
+
621
+ assert.equal(result.warnings.some((w) => /background-interactive/.test(w)), true);
622
+ assert.equal(result.warnings.filter((w) => /background-interactive/.test(w)).length, 1);
623
+ });
624
+
625
+ test("warns about border-border-separator Tailwind class", () => {
626
+ const source = `<div className="border border-border-separator">X</div>`;
627
+
628
+ const result = transformImportPaths(source, "/tmp/cmp.tsx");
629
+
630
+ assert.equal(result.warnings.some((w) => /border-border-separator/.test(w) || /kantklasser/.test(w)), true);
631
+ });
632
+
633
+ // --- does not warn about valid 5.0 patterns ---
634
+
261
635
  test("does not warn about valid 5.0 patterns", () => {
262
636
  const source = `@use "@fremtind/jokul/styles/jkl";
263
637
 
264
638
  .title {
265
639
  @include jkl.text-style("heading-1");
266
640
  color: var(--jkl-color-text-default);
641
+ background: var(--jkl-color-background-contrast);
267
642
  }
268
643
  `;
269
644