mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
9b80d4ce9e
The `@property` atrule allows users to define custom CSS variables. These changes update the compiler to account for when namespacing variables.
690 lines
25 KiB
TypeScript
690 lines
25 KiB
TypeScript
/**
|
||
* @license
|
||
* Copyright Google LLC All Rights Reserved.
|
||
*
|
||
* Use of this source code is governed by an MIT-style license that can be
|
||
* found in the LICENSE file at https://angular.dev/license
|
||
*/
|
||
|
||
import {namespaceCssVariables} from '../../src/shadow_css';
|
||
import {shim} from './utils';
|
||
|
||
describe('ShadowCss', () => {
|
||
it('should handle empty string', () => {
|
||
expect(shim('', 'contenta')).toEqualCss('');
|
||
});
|
||
|
||
it('should add an attribute to every rule', () => {
|
||
const css = 'one {color: red;}two {color: red;}';
|
||
const expected = 'one[contenta] {color:red;}two[contenta] {color:red;}';
|
||
expect(shim(css, 'contenta')).toEqualCss(expected);
|
||
});
|
||
|
||
it('should handle invalid css', () => {
|
||
const css = 'one {color: red;}garbage';
|
||
const expected = 'one[contenta] {color:red;}garbage';
|
||
expect(shim(css, 'contenta')).toEqualCss(expected);
|
||
});
|
||
|
||
it('should add an attribute to every selector', () => {
|
||
const css = 'one, two {color: red;}';
|
||
const expected = 'one[contenta], two[contenta] {color:red;}';
|
||
expect(shim(css, 'contenta')).toEqualCss(expected);
|
||
});
|
||
|
||
it('should support newlines in the selector and content ', () => {
|
||
const css = `
|
||
one,
|
||
two {
|
||
color: red;
|
||
}
|
||
`;
|
||
const expected = `
|
||
one[contenta],
|
||
two[contenta] {
|
||
color: red;
|
||
}
|
||
`;
|
||
expect(shim(css, 'contenta')).toEqualCss(expected);
|
||
});
|
||
|
||
it('should support newlines in the same selector and content ', () => {
|
||
const selector = `.foo:not(
|
||
.bar) {
|
||
background-color:
|
||
green;
|
||
}`;
|
||
expect(shim(selector, 'contenta', 'a-host')).toEqualCss(
|
||
'.foo[contenta]:not( .bar) { background-color:green;}',
|
||
);
|
||
});
|
||
|
||
it('should handle complicated selectors', () => {
|
||
expect(shim('one::before {}', 'contenta')).toEqualCss('one[contenta]::before {}');
|
||
expect(shim('one two {}', 'contenta')).toEqualCss('one[contenta] two[contenta] {}');
|
||
expect(shim('one > two {}', 'contenta')).toEqualCss('one[contenta] > two[contenta] {}');
|
||
expect(shim('one + two {}', 'contenta')).toEqualCss('one[contenta] + two[contenta] {}');
|
||
expect(shim('one ~ two {}', 'contenta')).toEqualCss('one[contenta] ~ two[contenta] {}');
|
||
expect(shim('.one.two > three {}', 'contenta')).toEqualCss(
|
||
'.one.two[contenta] > three[contenta] {}',
|
||
);
|
||
expect(shim('one[attr="value"] {}', 'contenta')).toEqualCss('one[attr="value"][contenta] {}');
|
||
expect(shim('one[attr=value] {}', 'contenta')).toEqualCss('one[attr=value][contenta] {}');
|
||
expect(shim('one[attr^="value"] {}', 'contenta')).toEqualCss('one[attr^="value"][contenta] {}');
|
||
expect(shim('one[attr$="value"] {}', 'contenta')).toEqualCss('one[attr$="value"][contenta] {}');
|
||
expect(shim('one[attr*="value"] {}', 'contenta')).toEqualCss('one[attr*="value"][contenta] {}');
|
||
expect(shim('one[attr|="value"] {}', 'contenta')).toEqualCss('one[attr|="value"][contenta] {}');
|
||
expect(shim('one[attr~="value"] {}', 'contenta')).toEqualCss('one[attr~="value"][contenta] {}');
|
||
expect(shim('one[attr="va lue"] {}', 'contenta')).toEqualCss('one[attr="va lue"][contenta] {}');
|
||
expect(shim('one[attr] {}', 'contenta')).toEqualCss('one[attr][contenta] {}');
|
||
expect(shim('[is="one"] {}', 'contenta')).toEqualCss('[is="one"][contenta] {}');
|
||
expect(shim('[attr] {}', 'contenta')).toEqualCss('[attr][contenta] {}');
|
||
});
|
||
|
||
it('should transform :host with attributes', () => {
|
||
expect(shim(':host [attr] {}', 'contenta', 'hosta')).toEqualCss('[hosta] [attr][contenta] {}');
|
||
expect(shim(':host(create-first-project) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'create-first-project[hosta] {}',
|
||
);
|
||
expect(shim(':host[attr] {}', 'contenta', 'hosta')).toEqualCss('[attr][hosta] {}');
|
||
expect(shim(':host[attr]:where(:not(.cm-button)) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'[attr][hosta]:where(:not(.cm-button)) {}',
|
||
);
|
||
});
|
||
|
||
it('should handle escaped sequences in selectors', () => {
|
||
expect(shim('one\\/two {}', 'contenta')).toEqualCss('one\\/two[contenta] {}');
|
||
expect(shim('one\\:two {}', 'contenta')).toEqualCss('one\\:two[contenta] {}');
|
||
expect(shim('one\\\\:two {}', 'contenta')).toEqualCss('one\\\\[contenta]:two {}');
|
||
expect(shim('.one\\:two {}', 'contenta')).toEqualCss('.one\\:two[contenta] {}');
|
||
expect(shim('.one\\:\\fc ber {}', 'contenta')).toEqualCss('.one\\:\\fc ber[contenta] {}');
|
||
expect(shim('.one\\:two .three\\:four {}', 'contenta')).toEqualCss(
|
||
'.one\\:two[contenta] .three\\:four[contenta] {}',
|
||
);
|
||
expect(shim('div:where(.one) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'div[contenta]:where(.one) {}',
|
||
);
|
||
expect(shim('div:where() {}', 'contenta', 'hosta')).toEqualCss('div[contenta]:where() {}');
|
||
expect(shim(':where(a):where(b) {}', 'contenta', 'hosta')).toEqualCss(
|
||
':where(a[contenta]):where(b[contenta]) {}',
|
||
);
|
||
expect(shim('*:where(.one) {}', 'contenta', 'hosta')).toEqualCss('*[contenta]:where(.one) {}');
|
||
expect(shim('*:where(.one) ::ng-deep .foo {}', 'contenta', 'hosta')).toEqualCss(
|
||
'*[contenta]:where(.one) .foo {}',
|
||
);
|
||
});
|
||
|
||
it('should handle pseudo functions correctly', () => {
|
||
// :where()
|
||
expect(shim(':where(.one) {}', 'contenta', 'hosta')).toEqualCss(':where(.one[contenta]) {}');
|
||
expect(shim(':where(div.one span.two) {}', 'contenta', 'hosta')).toEqualCss(
|
||
':where(div.one[contenta] span.two[contenta]) {}',
|
||
);
|
||
expect(shim(':where(.one) .two {}', 'contenta', 'hosta')).toEqualCss(
|
||
':where(.one[contenta]) .two[contenta] {}',
|
||
);
|
||
expect(shim(':where(:host) {}', 'contenta', 'hosta')).toEqualCss(':where([hosta]) {}');
|
||
expect(shim(':where(:host) .one {}', 'contenta', 'hosta')).toEqualCss(
|
||
':where([hosta]) .one[contenta] {}',
|
||
);
|
||
expect(shim(':where(.one) :where(:host) {}', 'contenta', 'hosta')).toEqualCss(
|
||
':where(.one) :where([hosta]) {}',
|
||
);
|
||
expect(shim(':where(.one :host) {}', 'contenta', 'hosta')).toEqualCss(
|
||
':where(.one [hosta]) {}',
|
||
);
|
||
expect(shim('div :where(.one) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'div[contenta] :where(.one[contenta]) {}',
|
||
);
|
||
expect(shim(':host :where(.one .two) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'[hosta] :where(.one[contenta] .two[contenta]) {}',
|
||
);
|
||
expect(shim(':where(.one, .two) {}', 'contenta', 'hosta')).toEqualCss(
|
||
':where(.one[contenta], .two[contenta]) {}',
|
||
);
|
||
expect(shim(':where(.one > .two) {}', 'contenta', 'hosta')).toEqualCss(
|
||
':where(.one[contenta] > .two[contenta]) {}',
|
||
);
|
||
expect(shim(':where(> .one) {}', 'contenta', 'hosta')).toEqualCss(
|
||
':where( > .one[contenta]) {}',
|
||
);
|
||
expect(shim(':where(:not(.one) ~ .two) {}', 'contenta', 'hosta')).toEqualCss(
|
||
':where([contenta]:not(.one) ~ .two[contenta]) {}',
|
||
);
|
||
expect(shim(':where([foo]) {}', 'contenta', 'hosta')).toEqualCss(':where([foo][contenta]) {}');
|
||
|
||
// :is()
|
||
expect(shim('div:is(.foo) {}', 'contenta', 'a-host')).toEqualCss('div[contenta]:is(.foo) {}');
|
||
expect(shim(':is(.dark :host) {}', 'contenta', 'a-host')).toEqualCss(':is(.dark [a-host]) {}');
|
||
expect(shim(':is(.dark) :is(:host) {}', 'contenta', 'a-host')).toEqualCss(
|
||
':is(.dark) :is([a-host]) {}',
|
||
);
|
||
expect(shim(':host:is(.foo) {}', 'contenta', 'a-host')).toEqualCss('[a-host]:is(.foo) {}');
|
||
expect(shim(':is(.foo) {}', 'contenta', 'a-host')).toEqualCss(':is(.foo[contenta]) {}');
|
||
expect(shim(':is(.foo, .bar, .baz) {}', 'contenta', 'a-host')).toEqualCss(
|
||
':is(.foo[contenta], .bar[contenta], .baz[contenta]) {}',
|
||
);
|
||
expect(shim(':is(.foo, .bar) :host {}', 'contenta', 'a-host')).toEqualCss(
|
||
':is(.foo, .bar) [a-host] {}',
|
||
);
|
||
|
||
// :is() and :where()
|
||
expect(
|
||
shim(
|
||
':is(.foo, .bar) :is(.baz) :where(.one, .two) :host :where(.three:first-child) {}',
|
||
'contenta',
|
||
'a-host',
|
||
),
|
||
).toEqualCss(
|
||
':is(.foo, .bar) :is(.baz) :where(.one, .two) [a-host] :where(.three[contenta]:first-child) {}',
|
||
);
|
||
expect(shim(':where(:is(a)) {}', 'contenta', 'hosta')).toEqualCss(
|
||
':where(:is(a[contenta])) {}',
|
||
);
|
||
expect(shim(':where(:is(a, b)) {}', 'contenta', 'hosta')).toEqualCss(
|
||
':where(:is(a[contenta], b[contenta])) {}',
|
||
);
|
||
expect(shim(':where(:host:is(.one, .two)) {}', 'contenta', 'hosta')).toEqualCss(
|
||
':where([hosta]:is(.one, .two)) {}',
|
||
);
|
||
expect(shim(':where(:host :is(.one, .two)) {}', 'contenta', 'hosta')).toEqualCss(
|
||
':where([hosta] :is(.one[contenta], .two[contenta])) {}',
|
||
);
|
||
expect(shim(':where(:is(a, b) :is(.one, .two)) {}', 'contenta', 'hosta')).toEqualCss(
|
||
':where(:is(a[contenta], b[contenta]) :is(.one[contenta], .two[contenta])) {}',
|
||
);
|
||
expect(
|
||
shim(
|
||
':where(:where(a:has(.foo), b) :is(.one, .two:where(.foo > .bar))) {}',
|
||
'contenta',
|
||
'hosta',
|
||
),
|
||
).toEqualCss(
|
||
':where(:where(a[contenta]:has(.foo), b[contenta]) :is(.one[contenta], .two[contenta]:where(.foo > .bar))) {}',
|
||
);
|
||
expect(shim(':where(.two):first-child {}', 'contenta', 'hosta')).toEqualCss(
|
||
'[contenta]:where(.two):first-child {}',
|
||
);
|
||
expect(shim(':first-child:where(.two) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'[contenta]:first-child:where(.two) {}',
|
||
);
|
||
expect(shim(':where(.two):nth-child(3) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'[contenta]:where(.two):nth-child(3) {}',
|
||
);
|
||
expect(shim('table :where(td, th):hover { color: lime; }', 'contenta', 'hosta')).toEqualCss(
|
||
'table[contenta] [contenta]:where(td, th):hover { color:lime;}',
|
||
);
|
||
|
||
// :nth
|
||
expect(shim(':nth-child(3n of :not(p, a), :is(.foo)) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'[contenta]:nth-child(3n of :not(p, a), :is(.foo)) {}',
|
||
);
|
||
expect(shim('li:nth-last-child(-n + 3) {}', 'contenta', 'a-host')).toEqualCss(
|
||
'li[contenta]:nth-last-child(-n + 3) {}',
|
||
);
|
||
expect(shim('dd:nth-last-of-type(3n) {}', 'contenta', 'a-host')).toEqualCss(
|
||
'dd[contenta]:nth-last-of-type(3n) {}',
|
||
);
|
||
expect(shim('dd:nth-of-type(even) {}', 'contenta', 'a-host')).toEqualCss(
|
||
'dd[contenta]:nth-of-type(even) {}',
|
||
);
|
||
|
||
// complex selectors
|
||
expect(shim(':host:is([foo],[foo-2])>div.example-2 {}', 'contenta', 'a-host')).toEqualCss(
|
||
'[a-host]:is([foo],[foo-2]) > div.example-2[contenta] {}',
|
||
);
|
||
expect(shim(':host:is([foo], [foo-2]) > div.example-2 {}', 'contenta', 'a-host')).toEqualCss(
|
||
'[a-host]:is([foo], [foo-2]) > div.example-2[contenta] {}',
|
||
);
|
||
expect(shim(':host:has([foo],[foo-2])>div.example-2 {}', 'contenta', 'a-host')).toEqualCss(
|
||
'[a-host]:has([foo],[foo-2]) > div.example-2[contenta] {}',
|
||
);
|
||
|
||
// :has()
|
||
expect(shim('div:has(a) {}', 'contenta', 'hosta')).toEqualCss('div[contenta]:has(a) {}');
|
||
expect(shim('div:has(a) :host {}', 'contenta', 'hosta')).toEqualCss('div:has(a) [hosta] {}');
|
||
expect(shim(':has(a) :host :has(b) {}', 'contenta', 'hosta')).toEqualCss(
|
||
':has(a) [hosta] [contenta]:has(b) {}',
|
||
);
|
||
expect(shim('div:has(~ .one) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'div[contenta]:has(~ .one) {}',
|
||
);
|
||
// Unlike `:is()` or `:where()` the attribute selector isn't placed inside
|
||
// of `:has()`. That is deliberate, `[contenta]:has(a)` would select all
|
||
// `[contenta]` with `a` inside, while `:has(a[contenta])` would select
|
||
// everything that contains `a[contenta]`, targeting elements outside of
|
||
// encapsulated scope.
|
||
expect(shim(':has(a) :has(b) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'[contenta]:has(a) [contenta]:has(b) {}',
|
||
);
|
||
expect(shim(':has(a, b) {}', 'contenta', 'hosta')).toEqualCss('[contenta]:has(a, b) {}');
|
||
expect(shim(':has(a, b:where(.foo), :is(.bar)) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'[contenta]:has(a, b:where(.foo), :is(.bar)) {}',
|
||
);
|
||
expect(
|
||
shim(':has(a, b:where(.foo), :is(.bar):first-child):first-letter {}', 'contenta', 'hosta'),
|
||
).toEqualCss('[contenta]:has(a, b:where(.foo), :is(.bar):first-child):first-letter {}');
|
||
expect(
|
||
shim(':where(a, b:where(.foo), :has(.bar):first-child) {}', 'contenta', 'hosta'),
|
||
).toEqualCss(
|
||
':where(a[contenta], b[contenta]:where(.foo), [contenta]:has(.bar):first-child) {}',
|
||
);
|
||
expect(shim(':has(.one :host, .two) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'[contenta]:has(.one [hosta], .two) {}',
|
||
);
|
||
expect(shim(':has(.one, :host) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'[contenta]:has(.one, [hosta]) {}',
|
||
);
|
||
});
|
||
|
||
it('should handle :host inclusions inside pseudo-selectors selectors', () => {
|
||
expect(shim('.header:not(.admin) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'.header[contenta]:not(.admin) {}',
|
||
);
|
||
expect(shim('.header:is(:host > .toolbar, :host ~ .panel) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'.header[contenta]:is([hosta] > .toolbar, [hosta] ~ .panel) {}',
|
||
);
|
||
expect(
|
||
shim('.header:where(:host > .toolbar, :host ~ .panel) {}', 'contenta', 'hosta'),
|
||
).toEqualCss('.header[contenta]:where([hosta] > .toolbar, [hosta] ~ .panel) {}');
|
||
expect(shim('.header:not(.admin, :host.super .header) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'.header[contenta]:not(.admin, .super[hosta] .header) {}',
|
||
);
|
||
expect(
|
||
shim('.header:not(.admin, :host.super .header, :host.mega .header) {}', 'contenta', 'hosta'),
|
||
).toEqualCss('.header[contenta]:not(.admin, .super[hosta] .header, .mega[hosta] .header) {}');
|
||
|
||
expect(shim('.one :where(.two, :host) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'.one :where(.two[contenta], [hosta]) {}',
|
||
);
|
||
expect(shim('.one :where(:host, .two) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'.one :where([hosta], .two[contenta]) {}',
|
||
);
|
||
expect(shim(':is(.foo):is(:host):is(.two) {}', 'contenta', 'hosta')).toEqualCss(
|
||
':is(.foo):is([hosta]):is(.two[contenta]) {}',
|
||
);
|
||
expect(shim(':where(.one, :host .two):first-letter {}', 'contenta', 'hosta')).toEqualCss(
|
||
'[contenta]:where(.one, [hosta] .two):first-letter {}',
|
||
);
|
||
expect(shim(':first-child:where(.one, :host .two) {}', 'contenta', 'hosta')).toEqualCss(
|
||
'[contenta]:first-child:where(.one, [hosta] .two) {}',
|
||
);
|
||
expect(
|
||
shim(':where(.one, :host .two):nth-child(3):is(.foo, a:where(.bar)) {}', 'contenta', 'hosta'),
|
||
).toEqualCss('[contenta]:where(.one, [hosta] .two):nth-child(3):is(.foo, a:where(.bar)) {}');
|
||
});
|
||
|
||
it('should handle escaped selector with space (if followed by a hex char)', () => {
|
||
// When esbuild runs with optimization.minify
|
||
// selectors are escaped: .über becomes .\fc ber.
|
||
// The space here isn't a separator between 2 selectors
|
||
expect(shim('.\\fc ber {}', 'contenta')).toEqual('.\\fc ber[contenta] {}');
|
||
expect(shim('.\\fc ker {}', 'contenta')).toEqual('.\\fc[contenta] ker[contenta] {}');
|
||
expect(shim('.pr\\fc fung {}', 'contenta')).toEqual('.pr\\fc fung[contenta] {}');
|
||
});
|
||
|
||
it('should leave calc() unchanged', () => {
|
||
const styleStr = 'div {height:calc(100% - 55px);}';
|
||
const css = shim(styleStr, 'contenta');
|
||
expect(css).toEqualCss('div[contenta] {height:calc(100% - 55px);}');
|
||
});
|
||
|
||
it('should shim rules with quoted content', () => {
|
||
const styleStr = 'div {background-image: url("a.jpg"); color: red;}';
|
||
const css = shim(styleStr, 'contenta');
|
||
expect(css).toEqualCss('div[contenta] {background-image:url("a.jpg"); color:red;}');
|
||
});
|
||
|
||
it('should handle when quoted content contains a closing parenthesis', () => {
|
||
// Regression test for https://github.com/angular/angular/issues/65137
|
||
expect(shim('p { background-image: url(")") } p { color: red }', 'contenta')).toEqualCss(
|
||
'p[contenta] { background-image: url(")") } p[contenta] { color: red }',
|
||
);
|
||
});
|
||
|
||
it('should shim rules with an escaped quote inside quoted content', () => {
|
||
const styleStr = 'div::after { content: "\\"" }';
|
||
const css = shim(styleStr, 'contenta');
|
||
expect(css).toEqualCss('div[contenta]::after { content:"\\""}');
|
||
});
|
||
|
||
it('should shim rules with curly braces inside quoted content', () => {
|
||
const styleStr = 'div::after { content: "{}" }';
|
||
const css = shim(styleStr, 'contenta');
|
||
expect(css).toEqualCss('div[contenta]::after { content:"{}"}');
|
||
});
|
||
|
||
it('should keep retain multiline selectors', () => {
|
||
// This is needed as shifting in line number will cause sourcemaps to break.
|
||
const styleStr = '.foo,\n.bar { color: red;}';
|
||
const css = shim(styleStr, 'contenta');
|
||
expect(css).toEqual('.foo[contenta], \n.bar[contenta] { color: red;}');
|
||
});
|
||
|
||
describe('comments', () => {
|
||
// Comments should be kept in the same position as otherwise inline sourcemaps break due to
|
||
// shift in lines.
|
||
it('should remove inline comments without adding extra lines', () => {
|
||
expect(shim('/* b {} */ b {}', 'contenta')).toBe(' b[contenta] {}');
|
||
});
|
||
|
||
it('should preserve internal newlines from multiline comments', () => {
|
||
expect(shim('/* b {}\n */ b {}', 'contenta')).toBe('\n b[contenta] {}');
|
||
});
|
||
|
||
it('should remove multiple inline comments without adding extra lines', () => {
|
||
expect(shim('/* b {} */ b {} /* a {} */ a {}', 'contenta')).toBe(
|
||
' b[contenta] {} a[contenta] {}',
|
||
);
|
||
});
|
||
|
||
it('should keep sourceMappingURL comments', () => {
|
||
expect(shim('b {} /*# sourceMappingURL=data:x */', 'contenta')).toBe(
|
||
'b[contenta] {} /*# sourceMappingURL=data:x */',
|
||
);
|
||
expect(shim('b {}/* #sourceMappingURL=data:x */', 'contenta')).toBe(
|
||
'b[contenta] {}/* #sourceMappingURL=data:x */',
|
||
);
|
||
});
|
||
|
||
it('should handle adjacent comments', () => {
|
||
expect(shim('/* comment 1 */ /* comment 2 */ b {}', 'contenta')).toBe(' b[contenta] {}');
|
||
});
|
||
});
|
||
|
||
describe('CSS variable namespacing', () => {
|
||
it('should inject `%NS%` placeholder into CSS variable declarations and usages', () => {
|
||
const input = `
|
||
.foo {
|
||
--my-color: red;
|
||
color: var(--my-color, blue);
|
||
}
|
||
`.trim();
|
||
|
||
const expected = `
|
||
.foo {
|
||
--%NS%my-color: red;
|
||
color: var(--%NS%my-color, blue);
|
||
}
|
||
`.trim();
|
||
|
||
expect(namespaceCssVariables(input)).toEqualCss(expected);
|
||
});
|
||
|
||
it('should not inject `%NS%` when `--global--` prefix is present', () => {
|
||
const input = `
|
||
.foo {
|
||
--global--my-color: green;
|
||
background: var(--global--my-color);
|
||
}
|
||
`.trim();
|
||
|
||
const expected = `
|
||
.foo {
|
||
--my-color: green;
|
||
background: var(--my-color);
|
||
}
|
||
`.trim();
|
||
|
||
expect(namespaceCssVariables(input)).toEqualCss(expected);
|
||
});
|
||
|
||
it('should handle multiple variables with mixed namespacing', () => {
|
||
const input = `
|
||
.foo {
|
||
border: var(--global--border-size) solid var(--border-color);
|
||
box-shadow:
|
||
var(--shadow-1),
|
||
var(--global--shadow-2),
|
||
var(--shadow-3);
|
||
}
|
||
`.trim();
|
||
|
||
const expected = `
|
||
.foo {
|
||
border: var(--border-size) solid var(--%NS%border-color);
|
||
box-shadow:
|
||
var(--%NS%shadow-1),
|
||
var(--shadow-2),
|
||
var(--%NS%shadow-3);
|
||
}
|
||
`.trim();
|
||
|
||
expect(namespaceCssVariables(input)).toEqualCss(expected);
|
||
});
|
||
|
||
it('should not namespace or modify -- in comments', () => {
|
||
expect(namespaceCssVariables('/* --bar */')).toBe('/* --bar */');
|
||
expect(namespaceCssVariables('/* --global--bar */')).toBe('/* --global--bar */');
|
||
});
|
||
|
||
it('should not namespace or modify -- in strings', () => {
|
||
expect(namespaceCssVariables('div { content: "--bar"; }')).toBe('div { content: "--bar"; }');
|
||
expect(namespaceCssVariables('div { content: "--global--bar"; }')).toBe(
|
||
'div { content: "--global--bar"; }',
|
||
);
|
||
});
|
||
|
||
it('should not namespace or modify -- in the middle of identifiers', () => {
|
||
expect(namespaceCssVariables('.foo--bar { color: red; }')).toBe('.foo--bar { color: red; }');
|
||
expect(namespaceCssVariables('.foo--global--bar { color: red; }')).toBe(
|
||
'.foo--global--bar { color: red; }',
|
||
);
|
||
});
|
||
|
||
it('should not namespace or modify -- in attribute names', () => {
|
||
expect(namespaceCssVariables('[data---bar] { color: red; }')).toBe(
|
||
'[data---bar] { color: red; }',
|
||
);
|
||
expect(namespaceCssVariables('[data---global--bar] { color: red; }')).toBe(
|
||
'[data---global--bar] { color: red; }',
|
||
);
|
||
});
|
||
|
||
it('should not namespace or modify -- in unquoted attribute values', () => {
|
||
expect(namespaceCssVariables('[data-status=foo--bar] { color: red; }')).toBe(
|
||
'[data-status=foo--bar] { color: red; }',
|
||
);
|
||
expect(namespaceCssVariables('[data-status=foo--global--bar] { color: red; }')).toBe(
|
||
'[data-status=foo--global--bar] { color: red; }',
|
||
);
|
||
});
|
||
|
||
it('should not namespace or modify CDO and CDC tokens', () => {
|
||
expect(namespaceCssVariables('<!--foo')).toBe('<!--foo');
|
||
expect(namespaceCssVariables('--global-->')).toBe('--global-->');
|
||
});
|
||
|
||
it('should not namespace or modify -- in URLs', () => {
|
||
expect(namespaceCssVariables('div { background: url(--bar); }')).toBe(
|
||
'div { background: url(--bar); }',
|
||
);
|
||
expect(namespaceCssVariables('div { background: url(--global--bar); }')).toBe(
|
||
'div { background: url(--global--bar); }',
|
||
);
|
||
});
|
||
|
||
it('should not namespace or modify -- in custom media queries', () => {
|
||
expect(namespaceCssVariables('@custom-media --bar (max-width: 30em);')).toBe(
|
||
'@custom-media --bar (max-width: 30em);',
|
||
);
|
||
expect(namespaceCssVariables('@custom-media --global--bar (max-width: 30em);')).toBe(
|
||
'@custom-media --global--bar (max-width: 30em);',
|
||
);
|
||
});
|
||
|
||
it('should handle whitespace in variable declarations and usages', () => {
|
||
expect(
|
||
namespaceCssVariables(`
|
||
p {
|
||
color: var(
|
||
--bgc
|
||
);
|
||
--bgc
|
||
: red;
|
||
}
|
||
`),
|
||
).toBe(`
|
||
p {
|
||
color: var(
|
||
--%NS%bgc
|
||
);
|
||
--%NS%bgc
|
||
: red;
|
||
}
|
||
`);
|
||
|
||
expect(
|
||
namespaceCssVariables(`
|
||
p {
|
||
color: var(
|
||
--global--bgc
|
||
);
|
||
--global--bgc
|
||
: red;
|
||
}
|
||
`),
|
||
).toBe(`
|
||
p {
|
||
color: var(
|
||
--bgc
|
||
);
|
||
--bgc
|
||
: red;
|
||
}
|
||
`);
|
||
});
|
||
|
||
it('should handle non-ascii characters', () => {
|
||
expect(
|
||
namespaceCssVariables(`
|
||
p {
|
||
--🅰️ngular: red;
|
||
color: var(--🅰️ngular);
|
||
}
|
||
`),
|
||
).toEqualCss(`
|
||
p {
|
||
--%NS%🅰️ngular: red;
|
||
color: var(--%NS%🅰️ngular);
|
||
}
|
||
`);
|
||
|
||
expect(
|
||
namespaceCssVariables(`
|
||
p {
|
||
--global--🅰️ngular: red;
|
||
color: var(--global--🅰️ngular);
|
||
}
|
||
`),
|
||
).toEqualCss(`
|
||
p {
|
||
--🅰️ngular: red;
|
||
color: var(--🅰️ngular);
|
||
}
|
||
`);
|
||
});
|
||
|
||
// TODO: Enforce this in v23.
|
||
xit('should throw an error when a CSS variable has a single hyphen after --global', () => {
|
||
expect(() => namespaceCssVariables('p { --global-foo: red; }')).toThrowError(
|
||
'CSS variable "--global-foo" has a single hyphen after "--global". Use two hyphens ("--global--foo") to opt-out of namespacing.',
|
||
);
|
||
expect(() => namespaceCssVariables('p { color: var(--global-my-color); }')).toThrowError(
|
||
'CSS variable "--global-my-color" has a single hyphen after "--global". Use two hyphens ("--global--my-color") to opt-out of namespacing.',
|
||
);
|
||
expect(() => namespaceCssVariables('p { --global-: red; }')).toThrowError(
|
||
'CSS variable "--global-" has a single hyphen after "--global". Use two hyphens ("--global--") to opt-out of namespacing.',
|
||
);
|
||
});
|
||
|
||
it('should namespace @property declarations', () => {
|
||
const input = `
|
||
@property --myColor {
|
||
syntax: "<color>";
|
||
inherits: true;
|
||
initial-value: rebeccapurple;
|
||
}
|
||
|
||
@property --myWidth {
|
||
syntax: "<length> | <percentage>";
|
||
inherits: true;
|
||
initial-value: 200px;
|
||
}
|
||
|
||
p {
|
||
background-color: var(--myColor);
|
||
width: var(--myWidth);
|
||
color: white;
|
||
}
|
||
`.trim();
|
||
|
||
const expected = `
|
||
@property --%NS%myColor {
|
||
syntax: "<color>";
|
||
inherits: true;
|
||
initial-value: rebeccapurple;
|
||
}
|
||
|
||
@property --%NS%myWidth {
|
||
syntax: "<length> | <percentage>";
|
||
inherits: true;
|
||
initial-value: 200px;
|
||
}
|
||
|
||
p {
|
||
background-color: var(--%NS%myColor);
|
||
width: var(--%NS%myWidth);
|
||
color: white;
|
||
}
|
||
`.trim();
|
||
|
||
expect(namespaceCssVariables(input)).toBe(expected);
|
||
});
|
||
|
||
it('should not namespace @property if --global-- is present', () => {
|
||
const input = `
|
||
@property --global--my-color {
|
||
syntax: "<color>";
|
||
inherits: true;
|
||
initial-value: rebeccapurple;
|
||
}
|
||
|
||
@property --global--my-width {
|
||
syntax: "<length> | <percentage>";
|
||
inherits: true;
|
||
initial-value: 200px;
|
||
}
|
||
|
||
p {
|
||
background-color: var(--global--my-color);
|
||
width: var(--global--my-width);
|
||
color: white;
|
||
}
|
||
`.trim();
|
||
|
||
const expected = `
|
||
@property --my-color {
|
||
syntax: "<color>";
|
||
inherits: true;
|
||
initial-value: rebeccapurple;
|
||
}
|
||
|
||
@property --my-width {
|
||
syntax: "<length> | <percentage>";
|
||
inherits: true;
|
||
initial-value: 200px;
|
||
}
|
||
|
||
p {
|
||
background-color: var(--my-color);
|
||
width: var(--my-width);
|
||
color: white;
|
||
}
|
||
`.trim();
|
||
|
||
expect(namespaceCssVariables(input)).toBe(expected);
|
||
});
|
||
});
|
||
});
|