@astryxdesign/core 0.6.3-canary.f22695a → 0.6.3

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 (118) hide show
  1. package/dist/Button/Button.d.ts.map +1 -1
  2. package/dist/Button/Button.js +28 -13
  3. package/dist/Chat/ChatLayout.d.ts +0 -19
  4. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  5. package/dist/Chat/ChatLayout.js +0 -19
  6. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  7. package/dist/Chat/ChatLayoutScrollButton.js +34 -44
  8. package/dist/Citation/Citation.d.ts +1 -1
  9. package/dist/Citation/Citation.d.ts.map +1 -1
  10. package/dist/Citation/Citation.js +2 -3
  11. package/dist/CodeBlock/highlightRanges.d.ts.map +1 -1
  12. package/dist/CodeBlock/highlightRanges.js +3 -14
  13. package/dist/Icon/Icon.d.ts +31 -6
  14. package/dist/Icon/Icon.d.ts.map +1 -1
  15. package/dist/Icon/Icon.js +71 -8
  16. package/dist/Link/useLinkComponent.d.ts +15 -5
  17. package/dist/Link/useLinkComponent.d.ts.map +1 -1
  18. package/dist/Link/useLinkComponent.js +51 -34
  19. package/dist/Markdown/Markdown.js +2 -2
  20. package/dist/Markdown/url.d.ts +0 -2
  21. package/dist/Markdown/url.d.ts.map +1 -1
  22. package/dist/Markdown/url.js +2 -8
  23. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  24. package/dist/PowerSearch/PowerSearchValueEditor.js +0 -5
  25. package/dist/TopNav/TopNavMenu.d.ts +1 -1
  26. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  27. package/dist/TopNav/TopNavMenu.js +2 -3
  28. package/dist/astryx.css +4 -2
  29. package/dist/hooks/useClickableContainer.d.ts +1 -10
  30. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  31. package/dist/hooks/useClickableContainer.js +4 -15
  32. package/dist/index.d.ts +0 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +0 -3
  35. package/dist/theme/generateThemeRules.d.ts +7 -13
  36. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  37. package/dist/theme/generateThemeRules.js +35 -131
  38. package/package.json +4 -9
  39. package/src/Button/Button.doc.mjs +5 -5
  40. package/src/Button/Button.tsx +13 -15
  41. package/src/Chat/ChatLayout.doc.mjs +4 -12
  42. package/src/Chat/ChatLayout.test.tsx +0 -28
  43. package/src/Chat/ChatLayout.tsx +0 -19
  44. package/src/Chat/ChatLayoutScrollButton.test.tsx +0 -31
  45. package/src/Chat/ChatLayoutScrollButton.tsx +14 -31
  46. package/src/Citation/Citation.doc.mjs +2 -2
  47. package/src/Citation/Citation.test.tsx +0 -17
  48. package/src/Citation/Citation.tsx +2 -4
  49. package/src/ClickableCard/ClickableCard.doc.mjs +2 -2
  50. package/src/CodeBlock/highlightRanges.test.ts +1 -143
  51. package/src/CodeBlock/highlightRanges.ts +5 -15
  52. package/src/Icon/Icon.doc.mjs +5 -7
  53. package/src/Icon/Icon.spec.md +40 -81
  54. package/src/Icon/Icon.test.tsx +3 -6
  55. package/src/Icon/Icon.tsx +68 -20
  56. package/src/IconButton/IconButton.doc.mjs +3 -3
  57. package/src/IconButton/IconButton.test.tsx +0 -40
  58. package/src/Item/Item.doc.mjs +2 -2
  59. package/src/Link/Link.doc.mjs +4 -6
  60. package/src/Link/LinkProvider.doc.mjs +2 -2
  61. package/src/Link/useLinkComponent.test.tsx +0 -333
  62. package/src/Link/useLinkComponent.ts +57 -29
  63. package/src/List/ListItem.doc.mjs +2 -2
  64. package/src/Markdown/Markdown.doc.mjs +2 -2
  65. package/src/Markdown/Markdown.spec.md +15 -19
  66. package/src/Markdown/Markdown.test.tsx +0 -52
  67. package/src/Markdown/Markdown.tsx +2 -2
  68. package/src/Markdown/parser.test.ts +0 -20
  69. package/src/Markdown/url.ts +3 -9
  70. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +0 -51
  71. package/src/PowerSearch/PowerSearchValueEditor.tsx +0 -4
  72. package/src/Token/Token.doc.mjs +2 -2
  73. package/src/TopNav/TopNavMenu.doc.mjs +2 -2
  74. package/src/TopNav/TopNavMenu.test.tsx +0 -30
  75. package/src/TopNav/TopNavMenu.tsx +2 -7
  76. package/src/hooks/useClickableContainer.ts +4 -20
  77. package/src/index.ts +0 -3
  78. package/src/tailwind-theme.css +6 -0
  79. package/src/theme/Theme.test.tsx +0 -32
  80. package/src/theme/generateThemeRules.test.ts +2 -470
  81. package/src/theme/generateThemeRules.ts +51 -219
  82. package/src/theme/tokenValueCompat.test.ts +1 -4
  83. package/dist/Icon/IconDefaultSizeContext.d.ts +0 -4
  84. package/dist/Icon/IconDefaultSizeContext.d.ts.map +0 -1
  85. package/dist/Icon/IconDefaultSizeContext.js +0 -18
  86. package/dist/Icon/IconSize.stylex.d.ts +0 -65
  87. package/dist/Icon/IconSize.stylex.d.ts.map +0 -1
  88. package/dist/Icon/IconSize.stylex.js +0 -75
  89. package/dist/Timer/Timer.d.ts +0 -49
  90. package/dist/Timer/Timer.d.ts.map +0 -1
  91. package/dist/Timer/Timer.js +0 -158
  92. package/dist/Timer/index.d.ts +0 -9
  93. package/dist/Timer/index.d.ts.map +0 -1
  94. package/dist/Timer/index.js +0 -10
  95. package/dist/theme/declarationBoundary.d.ts +0 -17
  96. package/dist/theme/declarationBoundary.d.ts.map +0 -1
  97. package/dist/theme/declarationBoundary.js +0 -343
  98. package/dist/utils/safeUrl.d.ts +0 -11
  99. package/dist/utils/safeUrl.d.ts.map +0 -1
  100. package/dist/utils/safeUrl.js +0 -78
  101. package/src/Chat/ChatLayout.spec.md +0 -271
  102. package/src/Chat/ChatLayoutScrollButton.spec.md +0 -338
  103. package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +0 -514
  104. package/src/Chat/__tests__/ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts +0 -740
  105. package/src/Icon/IconDefaultSizeContext.ts +0 -23
  106. package/src/Icon/IconSize.stylex.ts +0 -70
  107. package/src/Link/__tests__/Link.navigation.a11y.chromium.spec.ts +0 -261
  108. package/src/Markdown/Markdown.renderBoundary.test.tsx +0 -72
  109. package/src/Timer/Timer.doc.mjs +0 -183
  110. package/src/Timer/Timer.spec.md +0 -215
  111. package/src/Timer/Timer.test.tsx +0 -302
  112. package/src/Timer/Timer.tsx +0 -258
  113. package/src/Timer/index.ts +0 -11
  114. package/src/hooks/useClickableContainer.test.tsx +0 -236
  115. package/src/theme/declarationBoundary.test.ts +0 -126
  116. package/src/theme/declarationBoundary.ts +0 -347
  117. package/src/utils/safeUrl.test.ts +0 -88
  118. package/src/utils/safeUrl.ts +0 -85
@@ -55,38 +55,6 @@ describe('Theme', () => {
55
55
  expect(getByText('hello')).toBeTruthy();
56
56
  });
57
57
 
58
- it('mounts the remaining CSS when one authored declaration is dropped', () => {
59
- const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
60
- try {
61
- const theme = defineTheme({
62
- name: 'declaration-boundary',
63
- tokens: {'--color-accent': 'red; } body { color: red'},
64
- components: {button: {base: {borderRadius: '4px'}}},
65
- });
66
- render(
67
- <Theme theme={theme}>
68
- <span>child</span>
69
- </Theme>,
70
- );
71
- const css = Array.from(document.querySelectorAll('style'))
72
- .map(tag => tag.textContent ?? '')
73
- .join('\n');
74
- expect(css).not.toContain('body { color: red');
75
- expect(css).toContain('border-radius: 4px;');
76
- expect(css).toContain(':where(p)');
77
- expect(
78
- warn.mock.calls.filter(([message]) =>
79
- String(message).startsWith('[astryx theme] dropped'),
80
- ),
81
- ).toHaveLength(1);
82
- expect(warn).toHaveBeenCalledWith(
83
- expect.stringContaining('dropped "--color-accent" in tokens'),
84
- );
85
- } finally {
86
- warn.mockRestore();
87
- }
88
- });
89
-
90
58
  it('sets data-astryx-theme on wrapper div', () => {
91
59
  const {container} = render(
92
60
  <Theme theme={testTheme}>
@@ -6,7 +6,7 @@
6
6
  * correct, consistently ordered CSS for both runtime and build paths.
7
7
  */
8
8
 
9
- import {describe, it, expect, vi} from 'vitest';
9
+ import {describe, it, expect} from 'vitest';
10
10
  import {
11
11
  dataTokenDefaults,
12
12
  defineTheme,
@@ -14,7 +14,6 @@ import {
14
14
  generateThemeCSS,
15
15
  generateOnMediaCSS,
16
16
  generateThemeRules,
17
- type DefinedTheme,
18
17
  } from './index';
19
18
  import {generateDataTokenDefaultsCSS} from './generateThemeRules';
20
19
 
@@ -294,9 +293,7 @@ describe('generateThemeRules', () => {
294
293
  r.includes('font-family: var(--font-family-body)'),
295
294
  );
296
295
  expect(fontRule).toBeDefined();
297
- expect(fontRule).toBe(
298
- ' :scope {\n font-family: var(--font-family-body);\n }',
299
- );
296
+ expect(fontRule).toBe(' :scope {\n font-family: var(--font-family-body);\n }');
300
297
  });
301
298
 
302
299
  it('emits no scope font rule when the theme declares no body font', () => {
@@ -1390,468 +1387,3 @@ describe('data visualization tokens', () => {
1390
1387
  ).toEqual(['component', 'prose']);
1391
1388
  });
1392
1389
  });
1393
-
1394
- describe('declaration assembly keeps values as values', () => {
1395
- /** Run the full runtime generator, collecting every dropped declaration. */
1396
- function generate(theme: DefinedTheme) {
1397
- const diagnostics: string[] = [];
1398
- const {component, prose} = generateThemeCSS(theme, diagnostics);
1399
- return {css: component + prose, component, prose, diagnostics};
1400
- }
1401
-
1402
- /**
1403
- * Every path a consumer-supplied declaration can take into the stylesheet.
1404
- * `build` places `value` on that path; `emitted` is the exact declaration
1405
- * text the path writes for it; `property`/`location` are what a diagnostic
1406
- * for it must carry.
1407
- */
1408
- const paths: {
1409
- label: string;
1410
- property: string;
1411
- location: string;
1412
- build: (value: string) => DefinedTheme;
1413
- emitted: (value: string) => string;
1414
- }[] = [
1415
- {
1416
- label: 'root tokens',
1417
- property: '--color-accent',
1418
- location: 'tokens',
1419
- build: value =>
1420
- defineTheme({name: 'brand', tokens: {'--color-accent': value}}),
1421
- emitted: value => `--color-accent: ${value};`,
1422
- },
1423
- {
1424
- label: 'theme-local tokens',
1425
- property: '--brand-accent',
1426
- location: 'localTokens',
1427
- build: value =>
1428
- defineTheme({name: 'brand', localTokens: {'--brand-accent': value}}),
1429
- emitted: value => `--brand-accent: ${value};`,
1430
- },
1431
- {
1432
- label: 'tokens inherited from a base theme',
1433
- property: '--color-accent',
1434
- location: 'tokens',
1435
- build: value =>
1436
- defineTheme({
1437
- name: 'child',
1438
- extends: defineTheme({
1439
- name: 'base',
1440
- tokens: {'--color-accent': value},
1441
- }),
1442
- }),
1443
- emitted: value => `--color-accent: ${value};`,
1444
- },
1445
- {
1446
- label: 'component variant',
1447
- property: 'background-color',
1448
- location: 'components.button["variant:secondary"]',
1449
- build: value =>
1450
- defineTheme({
1451
- name: 'brand',
1452
- components: {
1453
- button: {'variant:secondary': {backgroundColor: value}},
1454
- },
1455
- }),
1456
- emitted: value => `background-color: ${value};`,
1457
- },
1458
- {
1459
- label: 'component variant inherited from a base theme',
1460
- property: 'background-color',
1461
- location: 'components.button["variant:secondary"]',
1462
- build: value =>
1463
- defineTheme({
1464
- name: 'child',
1465
- extends: defineTheme({
1466
- name: 'base',
1467
- components: {
1468
- button: {'variant:secondary': {backgroundColor: value}},
1469
- },
1470
- }),
1471
- }),
1472
- emitted: value => `background-color: ${value};`,
1473
- },
1474
- {
1475
- label: 'component pseudo block',
1476
- property: 'background-color',
1477
- location: 'components.button["variant:secondary"][":hover"]',
1478
- build: value =>
1479
- defineTheme({
1480
- name: 'brand',
1481
- components: {
1482
- button: {'variant:secondary': {':hover': {backgroundColor: value}}},
1483
- },
1484
- }),
1485
- emitted: value => `background-color: ${value};`,
1486
- },
1487
- {
1488
- label: 'onDark tokens',
1489
- property: '--color-accent',
1490
- location: 'onDark.tokens',
1491
- build: value =>
1492
- defineTheme({
1493
- name: 'brand',
1494
- onDark: {tokens: {'--color-accent': value}},
1495
- }),
1496
- emitted: value => `--color-accent: ${value};`,
1497
- },
1498
- {
1499
- label: 'onLight component variant',
1500
- property: 'background-color',
1501
- location: 'onLight.components.button["variant:secondary"]',
1502
- build: value =>
1503
- defineTheme({
1504
- name: 'brand',
1505
- onLight: {
1506
- components: {
1507
- button: {'variant:secondary': {backgroundColor: value}},
1508
- },
1509
- },
1510
- }),
1511
- emitted: value => `background-color: ${value};`,
1512
- },
1513
- {
1514
- label: 'onDark component pseudo block',
1515
- property: 'background-color',
1516
- location: 'onDark.components.button["variant:secondary"][":hover"]',
1517
- build: value =>
1518
- defineTheme({
1519
- name: 'brand',
1520
- onDark: {
1521
- components: {
1522
- button: {
1523
- 'variant:secondary': {':hover': {backgroundColor: value}},
1524
- },
1525
- },
1526
- },
1527
- }),
1528
- emitted: value => `background-color: ${value};`,
1529
- },
1530
- {
1531
- label: 'adaptation tokens',
1532
- property: '--color-accent',
1533
- location: 'adaptations[0].tokens',
1534
- build: value =>
1535
- defineTheme({
1536
- name: 'brand',
1537
- adaptations: {
1538
- rules: [
1539
- {
1540
- when: {pointer: 'coarse'},
1541
- value: {tokens: {'--color-accent': value}},
1542
- },
1543
- ],
1544
- },
1545
- }),
1546
- emitted: value => `--color-accent: ${value};`,
1547
- },
1548
- {
1549
- label: 'adaptation component variant',
1550
- property: 'background-color',
1551
- location: 'adaptations[1].components.button["variant:secondary"]',
1552
- build: value =>
1553
- defineTheme({
1554
- name: 'brand',
1555
- adaptations: {
1556
- rules: [
1557
- {
1558
- when: {pointer: 'fine'},
1559
- value: {tokens: {'--spacing-4': '12px'}},
1560
- },
1561
- {
1562
- when: {pointer: 'coarse'},
1563
- value: {
1564
- components: {
1565
- button: {'variant:secondary': {backgroundColor: value}},
1566
- },
1567
- },
1568
- },
1569
- ],
1570
- },
1571
- }),
1572
- emitted: value => `background-color: ${value};`,
1573
- },
1574
- ];
1575
-
1576
- /** Valid CSS a browser keeps inside one declaration. */
1577
- const preserved: [string, string][] = [
1578
- ['a color-mix()', 'color-mix(in oklch, #FF00FF 80%, white)'],
1579
- ['a gradient', 'linear-gradient(135deg, #FF00FF 0%, #00FFFF 100%)'],
1580
- ['calc()', 'calc(100% - 24px)'],
1581
- ['light-dark()', 'light-dark(#fff, #111)'],
1582
- ['a quoted font stack', "'Inter Var', ui-sans-serif, system-ui"],
1583
- [
1584
- 'a data: URI (semicolon inside url())',
1585
- 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUg==)',
1586
- ],
1587
- ['an uppercase URL()', 'URL(data:image/svg+xml;base64,PHN2Zz4=)'],
1588
- [
1589
- 'a quoted url() carrying a semicolon',
1590
- 'url("data:image/svg+xml;utf8,<svg/>")',
1591
- ],
1592
- ['an escaped identifier', 'Gill\\ Sans, serif'],
1593
- ['a closed comment with a semicolon inside', 'red /* ; } */'],
1594
- ['a semicolon inside a string', '"a;b"'],
1595
- ['an escaped quote inside a string', '"a\\" ; b"'],
1596
- ['quoted braces', '"} body { color: red }"'],
1597
- ['important', 'red !important'],
1598
- ['nested functions', 'var(--fallback, calc(1px + 2px))'],
1599
- ['CRLF string continuation', '"a\\\r\nb"'],
1600
- ['closed comment with a control character', 'red /* \u0001 ; } */'],
1601
- [
1602
- 'balanced brackets and parentheses',
1603
- '[full-start] minmax(0, 1fr) [full-end]',
1604
- ],
1605
- ];
1606
-
1607
- /** Values a browser reads as the end of the declaration or rule. */
1608
- const rejected: [string, string, string][] = [
1609
- [
1610
- 'an unquoted semicolon adds a declaration',
1611
- 'red; background-image: url(https://example.com/leak)',
1612
- ';',
1613
- ],
1614
- [
1615
- 'an unquoted brace closes the rule',
1616
- 'red } input[value^="a"] { background: url(https://example.com/leak?a) ',
1617
- '}',
1618
- ],
1619
- [
1620
- 'an unquoted brace opens a block',
1621
- 'red { background: url(https://example.com/leak) }',
1622
- '{',
1623
- ],
1624
- [
1625
- 'an escaped quote is not a string opener',
1626
- '\\"; background: url(https://example.com/leak); "',
1627
- ';',
1628
- ],
1629
- [
1630
- 'an unclosed comment swallows the rule',
1631
- 'red /* https://example.com/leak',
1632
- 'comment',
1633
- ],
1634
- [
1635
- 'an unclosed string swallows the rule',
1636
- '"https://example.com/leak',
1637
- 'string',
1638
- ],
1639
- [
1640
- 'an unclosed url() swallows the rule',
1641
- 'url(https://example.com/leak',
1642
- 'url(',
1643
- ],
1644
- [
1645
- 'an unclosed paren swallows the rule',
1646
- 'calc(1px + var(--example.com/leak)',
1647
- '(',
1648
- ],
1649
- [
1650
- 'an unbalanced closer',
1651
- 'red) ; background: url(https://example.com/leak)',
1652
- ')',
1653
- ],
1654
- ['a bad url', 'url(https://example.com/leak a)', 'bad url'],
1655
- [
1656
- 'a trailing backslash escapes the terminator',
1657
- 'https://example.com/leak\\',
1658
- 'backslash',
1659
- ],
1660
- ];
1661
-
1662
- describe.each(paths)('$label', path => {
1663
- it.each(preserved)('emits %s byte-identically', (_label, value) => {
1664
- const {css, diagnostics} = generate(path.build(value));
1665
- expect(css).toContain(path.emitted(value));
1666
- expect(diagnostics).toEqual([]);
1667
- });
1668
-
1669
- it.each(rejected)('drops a value where %s', (_label, value, reason) => {
1670
- const {css, diagnostics} = generate(path.build(value));
1671
- expect(css).not.toContain('example.com');
1672
- expect(css).not.toContain('leak');
1673
- expect(diagnostics).toHaveLength(1);
1674
- expect(diagnostics[0]).toContain(`"${path.property}"`);
1675
- expect(diagnostics[0]).toContain(path.location);
1676
- expect(diagnostics[0]).toContain(reason);
1677
- });
1678
- });
1679
-
1680
- it('keeps a vendor-prefixed property name', () => {
1681
- const {css, diagnostics} = generate(
1682
- defineTheme({
1683
- name: 'brand',
1684
- components: {
1685
- button: {
1686
- 'variant:secondary': {
1687
- WebkitLineClamp: '2',
1688
- MozOsxFontSmoothing: 'grayscale',
1689
- },
1690
- },
1691
- },
1692
- }),
1693
- );
1694
- expect(css).toContain('-webkit-line-clamp: 2;');
1695
- expect(css).toContain('-moz-osx-font-smoothing: grayscale;');
1696
- expect(diagnostics).toEqual([]);
1697
- });
1698
-
1699
- it('drops a property name that is not a property name', () => {
1700
- const {css, diagnostics} = generate(
1701
- defineTheme({
1702
- name: 'brand',
1703
- tokens: {
1704
- '--x:red} body{color:blue': 'red',
1705
- } as Record<string, string>,
1706
- }),
1707
- );
1708
- expect(css).not.toContain('body{color:blue');
1709
- expect(css).not.toContain('body {');
1710
- expect(diagnostics).toHaveLength(1);
1711
- expect(diagnostics[0]).toContain('"--x:red} body{color:blue" in tokens');
1712
- expect(diagnostics[0]).toContain('identifier');
1713
- });
1714
-
1715
- it('keeps prose rules intact when a typed token carries a payload', () => {
1716
- // Prose rules reference the token through var(), so the payload never
1717
- // reaches them; the token block drops it and the :where(p) rule stays.
1718
- const {css, prose, diagnostics} = generate(
1719
- defineTheme({
1720
- name: 'brand',
1721
- tokens: {
1722
- '--text-body-size': '1rem; } body { background: url(x) ',
1723
- },
1724
- }),
1725
- );
1726
- expect(css).not.toContain('background: url(x)');
1727
- expect(css).not.toContain('body {');
1728
- expect(prose).toContain('font-size: var(--text-body-size);');
1729
- expect(diagnostics).toHaveLength(1);
1730
- expect(diagnostics[0]).toContain('"--text-body-size" in tokens');
1731
- });
1732
-
1733
- it('falls back to the token weight when an authored Heading weight cannot stay a declaration', () => {
1734
- const payload = '700; } body { background: url(https://example.com/leak) ';
1735
- const {css, diagnostics} = generate(
1736
- defineTheme({
1737
- name: 'brand',
1738
- components: {heading: {'weight:bold': {fontWeight: payload}}},
1739
- }),
1740
- );
1741
- expect(css).not.toContain('example.com');
1742
- expect(css).toContain(
1743
- '.astryx-heading[data-weight="bold"] { font-weight: var(--font-weight-bold); }',
1744
- );
1745
- // Reported once, where the authored rule's own declarations are emitted.
1746
- expect(diagnostics).toHaveLength(1);
1747
- expect(diagnostics[0]).toContain(
1748
- '"font-weight" in components.heading["weight:bold"]',
1749
- );
1750
- });
1751
-
1752
- it('falls back to the token weight when an inherited Heading weight cannot stay a declaration', () => {
1753
- const payload = '700; } body { background: url(https://example.com/leak) ';
1754
- const {css} = generate(
1755
- defineTheme({
1756
- name: 'brand',
1757
- components: {heading: {'weight:bold': {fontWeight: payload}}},
1758
- onDark: {components: {heading: {'level:1': {fontSize: '2rem'}}}},
1759
- }),
1760
- );
1761
- expect(css).not.toContain('example.com');
1762
- expect(css).toContain(
1763
- ':is([data-astryx-media="dark"]) :is(.astryx-heading[data-weight="bold"]) { font-weight: var(--font-weight-bold); }',
1764
- );
1765
- });
1766
-
1767
- it('a dropped container padding still leaves the rest of the rule intact', () => {
1768
- const {css, diagnostics} = generate(
1769
- defineTheme({
1770
- name: 'brand',
1771
- components: {
1772
- card: {
1773
- base: {
1774
- padding:
1775
- '16px; } body { background: url(https://example.com/leak) ',
1776
- borderRadius: '8px',
1777
- },
1778
- },
1779
- },
1780
- }),
1781
- );
1782
- expect(css).not.toContain('example.com');
1783
- expect(css).toContain('border-radius: 8px;');
1784
- expect(diagnostics).toHaveLength(1);
1785
- expect(diagnostics[0]).toContain('"padding" in components.card["base"]');
1786
- expect(css).not.toContain('--astryx-card-padding');
1787
- });
1788
-
1789
- it('warns on the console only when no warning collector is given', () => {
1790
- const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
1791
- const theme = defineTheme({
1792
- name: 'brand',
1793
- tokens: {
1794
- '--color-accent': 'red; background: url(https://example.com/leak)',
1795
- },
1796
- });
1797
-
1798
- const plainCSS = generateThemeCSS(theme);
1799
- expect(warn).toHaveBeenCalledTimes(1);
1800
- expect(warn).toHaveBeenCalledWith(
1801
- expect.stringContaining(
1802
- '[astryx theme] dropped "--color-accent" in tokens',
1803
- ),
1804
- );
1805
-
1806
- warn.mockClear();
1807
- const diagnostics: string[] = ['earlier build warning'];
1808
- const before = JSON.stringify(theme);
1809
- expect(generateThemeCSS(theme, diagnostics)).toEqual(plainCSS);
1810
- expect(JSON.stringify(theme)).toBe(before);
1811
- expect(warn).not.toHaveBeenCalled();
1812
- expect(diagnostics).toHaveLength(2);
1813
- expect(diagnostics[0]).toBe('earlier build warning');
1814
- expect(diagnostics[1]).toContain('dropped "--color-accent" in tokens');
1815
- warn.mockRestore();
1816
- });
1817
-
1818
- it('every shipped generator entry point appends to the warning collector', () => {
1819
- const theme = defineTheme({
1820
- name: 'brand',
1821
- tokens: {
1822
- '--color-accent': 'red; background: url(https://example.com/leak)',
1823
- },
1824
- onDark: {
1825
- tokens: {
1826
- '--color-accent': 'red; background: url(https://example.com/dark)',
1827
- },
1828
- },
1829
- adaptations: {
1830
- rules: [
1831
- {
1832
- when: {pointer: 'coarse'},
1833
- value: {
1834
- tokens: {
1835
- '--color-accent':
1836
- 'red; background: url(https://example.com/coarse)',
1837
- },
1838
- },
1839
- },
1840
- ],
1841
- },
1842
- });
1843
- const seen: string[] = [];
1844
- expect(generateThemeRules(theme, seen).join('')).not.toContain(
1845
- 'example.com',
1846
- );
1847
- expect(generateOnMediaCSS(theme, seen)).not.toContain('example.com');
1848
- expect(generateAdaptationCSS(theme, seen).component).not.toContain(
1849
- 'example.com',
1850
- );
1851
- expect(seen).toEqual([
1852
- expect.stringContaining('in tokens:'),
1853
- expect.stringContaining('in onDark.tokens:'),
1854
- expect.stringContaining('in adaptations[0].tokens:'),
1855
- ]);
1856
- });
1857
- });