1321 lines
53 KiB
TypeScript
1321 lines
53 KiB
TypeScript
import '../support/polyfills';
|
|
import {DEFAULT_THEME} from '../../../src/defaults';
|
|
import {createOrUpdateDynamicTheme, removeDynamicTheme} from '../../../src/inject/dynamic-theme';
|
|
import {injectProxy} from '../../../src/inject/dynamic-theme/stylesheet-proxy';
|
|
import {isFirefox} from '../../../src/utils/platform';
|
|
import {stubChromeRuntimeGetURL} from '../support/background-stub';
|
|
import {getJSEchoURL} from '../support/echo-client';
|
|
import {multiline, timeout, waitForEvent} from '../support/test-utils';
|
|
|
|
const theme = {
|
|
...DEFAULT_THEME,
|
|
darkSchemeBackgroundColor: 'black',
|
|
darkSchemeTextColor: 'white',
|
|
};
|
|
let container: HTMLElement;
|
|
|
|
beforeAll(() => {
|
|
const loader = multiline(
|
|
'(function loader() {',
|
|
' document && document.currentScript && document.currentScript.remove();',
|
|
' const argString = document && document.currentScript && document.currentScript.dataset.arg;',
|
|
' if (argString !== undefined) {',
|
|
' const arg = JSON.parse(argString);',
|
|
` (${injectProxy.toString()})(arg);`,
|
|
' }',
|
|
'})()',
|
|
);
|
|
const url = getJSEchoURL(loader);
|
|
stubChromeRuntimeGetURL('inject/proxy.js', url);
|
|
});
|
|
|
|
beforeEach(() => {
|
|
container = document.body;
|
|
container.innerHTML = '';
|
|
});
|
|
|
|
afterEach(() => {
|
|
removeDynamicTheme();
|
|
container.innerHTML = '';
|
|
document.documentElement.removeAttribute('style');
|
|
});
|
|
|
|
describe('CSS VARIABLES OVERRIDE', () => {
|
|
it('should override style with variables', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --bg: gray;',
|
|
' --text: red;',
|
|
' }',
|
|
' h1 { background: var(--bg); }',
|
|
' h1 strong { color: var(--text); }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container).backgroundColor).toBe('rgb(0, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(102, 102, 102)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
it('should override style with variables(that contain spaces)', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --bg: gray;',
|
|
' --text: red;',
|
|
' }',
|
|
' h1 { background: var( --bg ); }',
|
|
' h1 strong { color: var( --text ); }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container).backgroundColor).toBe('rgb(0, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(102, 102, 102)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
it('should override style with variables (reverse order)', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 { background: var(--bg); }',
|
|
' h1 strong { color: var(--text); }',
|
|
'</style>',
|
|
'<style>',
|
|
' :root {',
|
|
' --bg: gray;',
|
|
' --text: green;',
|
|
' }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container).backgroundColor).toBe('rgb(0, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(102, 102, 102)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(140, 255, 140)');
|
|
});
|
|
|
|
it('should skip undefined variables', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 { background: var(--bg); }',
|
|
' h1 strong { color: var(--text); }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container).backgroundColor).toBe('rgb(0, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgba(0, 0, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 255, 255)');
|
|
});
|
|
|
|
it('should use fallback variable value', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 { color: var(--text, red); }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
it('should not use fallback variable value', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --text: green;',
|
|
' }',
|
|
' h1 { color: var(--text, red); }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(140, 255, 140)');
|
|
});
|
|
|
|
it('should handle variables referencing other variables', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --alert: red;',
|
|
' --text: var(--alert);',
|
|
' }',
|
|
' h1 { color: var(--text); }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
it('should handle variables referencing other variables (reverse order)', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --text: var(--alert);',
|
|
' --alert: red;',
|
|
' }',
|
|
' h1 { color: var(--text); }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
it('should handle shorthand background with deep color refs', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --red: red;',
|
|
' --bg: var(--red);',
|
|
' }',
|
|
' h1 {',
|
|
' background: var(--bg);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong></h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(204, 0, 0)');
|
|
});
|
|
|
|
it('should handle background with deep color refs (backwards)', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 {',
|
|
' background: var(--bg);',
|
|
' }',
|
|
' :root {',
|
|
' --red: red;',
|
|
' --bg: var(--red);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong></h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(204, 0, 0)');
|
|
});
|
|
|
|
it('should handle multi-type vars with deep color refs', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 {',
|
|
' background: var(--bg);',
|
|
' color: var(--red);',
|
|
' }',
|
|
' :root {',
|
|
' --red: red;',
|
|
' --bg: var(--red);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong></h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(204, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
it('should not hang on exponentially expanding variable references', () => {
|
|
const depth = 30;
|
|
const varLines: string[] = [];
|
|
for (let i = 1; i < depth; i++) {
|
|
varLines.push(` --x${i}: var(--x${i + 1}) var(--x${i + 1});`);
|
|
}
|
|
varLines.push(` --x${depth}: #770000;`);
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
...varLines,
|
|
' }',
|
|
' h1 {',
|
|
' color: rgb(var(--x1), 0, 0);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong></h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
});
|
|
|
|
it('should handle variables having multiple types', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --dark: red;',
|
|
' --light: green;',
|
|
' }',
|
|
' .dark {',
|
|
' background-color: var(--dark);',
|
|
' color: var(--light);',
|
|
' }',
|
|
' .light {',
|
|
' background: var(--light);',
|
|
' color: var(--dark);',
|
|
' }',
|
|
'</style>',
|
|
'<h1 class="dark">Dark background light text</h1>',
|
|
'<h1 class="light">Light background dark text</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('.dark')!).backgroundColor).toBe('rgb(204, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('.dark')!).color).toBe('rgb(140, 255, 140)');
|
|
expect(getComputedStyle(container.querySelector('.light')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
|
expect(getComputedStyle(container.querySelector('.light')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
/*
|
|
it('should handle variables having multiple types in shorthand properties', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --color: green;',
|
|
' }',
|
|
' h1 {',
|
|
' border: 1px solid var(--color);',
|
|
' }',
|
|
' h1 {',
|
|
' background: linear-gradient(var(--color), white);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Variables</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundImage).toBe('linear-gradient(rgb(0, 102, 0), rgb(0, 0, 0))');
|
|
expect(getComputedStyle(container.querySelector('h1')!).borderColor).toBe('rgb(0, 217, 0)');
|
|
});
|
|
*/
|
|
|
|
it('should use fallback when nested variables are missing', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 { color: var(--text, var(--alert, green)); }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(140, 255, 140)');
|
|
});
|
|
|
|
it('should not freeze on cyclic references', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --text: var(--text);',
|
|
' }',
|
|
' h1 { color: var(--text); }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
});
|
|
|
|
it('should not freeze on nested cyclic references', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --alert: var(--text);',
|
|
' --text: var(--alert);',
|
|
' }',
|
|
' h1 { color: var(--text); }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
});
|
|
|
|
it('should react on variable change', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style id="variables">',
|
|
' :root {',
|
|
' --text: red;',
|
|
' }',
|
|
'</style>',
|
|
'<style>',
|
|
' h1 { color: var(--text); }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 26, 26)');
|
|
|
|
const styleElement = document.getElementById('variables') as HTMLStyleElement;
|
|
styleElement.textContent = ':root { --text: green; }';
|
|
await timeout(0);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(140, 255, 140)');
|
|
});
|
|
|
|
it('should use <html> element variables', async () => {
|
|
document.documentElement.setAttribute('style', '--text: red;');
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 { color: var(--text); }',
|
|
'</style>',
|
|
'<h1>CSS <strong>variables</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 26, 26)');
|
|
|
|
document.documentElement.setAttribute('style', '--text: green;');
|
|
await timeout(0);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(140, 255, 140)');
|
|
});
|
|
|
|
it('should consider variable selector', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' .red {',
|
|
' --text: red;',
|
|
' }',
|
|
' .green {',
|
|
' --text: green;',
|
|
' }',
|
|
' h1 { color: var(--text); }',
|
|
'</style>',
|
|
'<h1 class="red">Red CSS variable</h1>',
|
|
'<h1 class="green">Green CSS variable</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('.red')!).color).toBe('rgb(255, 26, 26)');
|
|
expect(getComputedStyle(container.querySelector('.green')!).color).toBe('rgb(140, 255, 140)');
|
|
});
|
|
|
|
it('should handle internal conversion of hex to RGB', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --bg: #fff;',
|
|
' --text: #000;',
|
|
' }',
|
|
'</style>',
|
|
'<style>',
|
|
' h1 {',
|
|
' background: var(--bg);',
|
|
' color: var(--text);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Dark <strong>Theme</strong>!</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(0, 0, 0)');
|
|
});
|
|
|
|
it('should handle variables inside color values (constructed colors)', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --bg: 255, 255, 255;',
|
|
' --text: 0, 0, 0;',
|
|
' }',
|
|
'</style>',
|
|
'<style>',
|
|
' h1 {',
|
|
' background: rgb(var(--bg));',
|
|
' color: rgb(var(--text));',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Colors with variables inside</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(0, 0, 0)');
|
|
});
|
|
|
|
it('should use fallback when variable inside a color not found', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 {',
|
|
' background: rgb(var(--bg, 255, 0, 0));',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Colors with variables inside</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(204, 0, 0)');
|
|
});
|
|
|
|
it('should handle variables in constructed colors that refer to other vars', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --v255: 255;',
|
|
' --red: var(--v255), 0, 0;',
|
|
' --bg: var(--unknown, var(--red));',
|
|
' --text: var(--red);',
|
|
' }',
|
|
'</style>',
|
|
'<style>',
|
|
' h1 {',
|
|
' background: rgb(var(--bg));',
|
|
' color: rgb(var(--text));',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Colors with variables inside</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 26, 26)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(204, 0, 0)');
|
|
});
|
|
|
|
it('should handle variables that refer to constructed colors', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --red: 255, 0, 0;',
|
|
' --blue: 0, 0, 255;',
|
|
' --rgb-blue: rgb(var(--blue));',
|
|
' --bg: rgb(var(--red));',
|
|
' --text: var(--rgb-blue);',
|
|
' }',
|
|
'</style>',
|
|
'<style>',
|
|
' h1 {',
|
|
' background: var(--bg);',
|
|
' color: var(--text);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Colors with variables inside</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(204, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(51, 125, 255)');
|
|
});
|
|
|
|
it('should handle variables that refer to constructed colors asynchronously', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --red: 255, 0, 0;',
|
|
' --bg: rgb(var(--red));',
|
|
' }',
|
|
' h1 {',
|
|
' color: var(--text);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Colors with variables inside</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
const anotherStyle = document.createElement('style');
|
|
anotherStyle.textContent = multiline(
|
|
':root {',
|
|
' --blue: 0, 0, 255;',
|
|
' --rgb-blue: rgb(var(--blue));',
|
|
' --text: var(--rgb-blue);',
|
|
'}',
|
|
'h1 {',
|
|
' background: var(--bg);',
|
|
'}',
|
|
);
|
|
container.append(anotherStyle);
|
|
await timeout(0);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(204, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(51, 125, 255)');
|
|
});
|
|
|
|
it('should handle variables that are both contructed and usual colors', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --red: 255, 0, 0;',
|
|
' --bg: green;',
|
|
' }',
|
|
' h1 {',
|
|
' --bg: rgb(var(--red));',
|
|
' background: var(--bg)',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Colors with variables inside</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(204, 0, 0)');
|
|
});
|
|
|
|
it('should handle cyclic references in constructed colors', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --bg: var(--text);',
|
|
' --text: var(--bg);',
|
|
' }',
|
|
'</style>',
|
|
'<style>',
|
|
' h1 {',
|
|
' background: rgb(var(--bg));',
|
|
' color: rgb(var(--text));',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Colors with variables inside</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(0, 0, 0)');
|
|
});
|
|
|
|
it('should handle variables inside border color values', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --red: 255, 0, 0;',
|
|
' }',
|
|
'</style>',
|
|
'<style>',
|
|
' h1 {',
|
|
' border: 1px solid rgb(var(--red));',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Colors with variables inside</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
const elementStyle = getComputedStyle(container.querySelector('h1')!);
|
|
if (isFirefox) {
|
|
expect(elementStyle.borderTopColor).toBe('rgb(179, 0, 0)');
|
|
expect(elementStyle.borderRightColor).toBe('rgb(179, 0, 0)');
|
|
expect(elementStyle.borderBottomColor).toBe('rgb(179, 0, 0)');
|
|
expect(elementStyle.borderLeftColor).toBe('rgb(179, 0, 0)');
|
|
} else {
|
|
expect(elementStyle.borderColor).toBe('rgb(179, 0, 0)');
|
|
}
|
|
});
|
|
|
|
it('should handle media variables', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' @media screen and (min-width: 2px) {',
|
|
' .red {',
|
|
' --text: red;',
|
|
' }',
|
|
' .orange {',
|
|
' --text: orange',
|
|
' }',
|
|
' }',
|
|
' @media screen and (min-width: 2000000px) {',
|
|
' .green {',
|
|
' --text: green;',
|
|
' }',
|
|
' }',
|
|
' h1 { color: var(--text); }',
|
|
'</style>',
|
|
'<h1 class="red">Red CSS variable</h1>',
|
|
'<h1 class="green">Green CSS variable</h1>',
|
|
'<h1 class="orange">Orange CSS variable</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container).backgroundColor).toBe('rgb(0, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('.red')!).color).toBe('rgb(255, 26, 26)');
|
|
expect(getComputedStyle(container.querySelector('.green')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('.orange')!).color).toBe('rgb(255, 174, 26)');
|
|
});
|
|
|
|
it('should handle nested variables', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' @media screen and (min-width: 2px) {',
|
|
' @media screen and (min-width: 3px) {',
|
|
' .red {',
|
|
' --text: red;',
|
|
' }',
|
|
' }',
|
|
' }',
|
|
' @media screen and (min-width: 2px) {',
|
|
' @media screen and (min-width: 2000000px) {',
|
|
' .green {',
|
|
' --text: green;',
|
|
' }',
|
|
' }',
|
|
' }',
|
|
' @media screen and (min-width: 2000000px) {',
|
|
' @media screen and (min-width: 2px) {',
|
|
' .orange {',
|
|
' --text: orange;',
|
|
' }',
|
|
' }',
|
|
' }',
|
|
' h1 {',
|
|
' color: var(--text);',
|
|
' }',
|
|
'</style>',
|
|
'<h1 class="red">Red CSS variable</h1>',
|
|
'<h1 class="green">Green CSS variable</h1>',
|
|
'<h1 class="orange">Orange CSS variable</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container).backgroundColor).toBe('rgb(0, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('.red')!).color).toBe('rgb(255, 26, 26)');
|
|
expect(getComputedStyle(container.querySelector('.green')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('.orange')!).color).toBe('rgb(255, 255, 255)');
|
|
});
|
|
|
|
it('should handle media with the same selectors', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' @media screen and (min-width: 2px) {',
|
|
' h1 {',
|
|
' --text: green;',
|
|
' }',
|
|
' }',
|
|
' @media screen and (max-width: 2px) {',
|
|
' h1 {',
|
|
' --text: red;',
|
|
' }',
|
|
' }',
|
|
' h1 { color: var(--text); }',
|
|
'</style>',
|
|
'<h1>Media with same selectors</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(140, 255, 140)');
|
|
});
|
|
|
|
it('should preserve media after change', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' @media screen and (min-width: 2px) {',
|
|
' /* This media should be used */',
|
|
' h1 {',
|
|
' --text: green;',
|
|
' }',
|
|
' }',
|
|
' @media screen and (max-width: 2px) {',
|
|
' /* This media should not be used */',
|
|
' h1 {',
|
|
' --text: red;',
|
|
' }',
|
|
' }',
|
|
' h1 { color: var(--text); }',
|
|
'</style>',
|
|
'<h1>Media after change</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(140, 255, 140)');
|
|
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(140, 255, 140)');
|
|
});
|
|
|
|
it('should handle same selector with different variables', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' @media screen and (min-width: 2px) {',
|
|
' h1 {',
|
|
' --text: green;',
|
|
' }',
|
|
' h1 {',
|
|
' --bg: red;',
|
|
' }',
|
|
' }',
|
|
' h1 { color: var(--text); background-color: var(--bg); }',
|
|
'</style>',
|
|
'<h1>Media with same selectors</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(140, 255, 140)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(204, 0, 0)');
|
|
});
|
|
|
|
it('should properly modify variables in light mode', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --bg: white;',
|
|
' --text: black;',
|
|
' }',
|
|
' h1 {',
|
|
' background: var(--bg);',
|
|
' color: var(--text);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Light scheme</h1>',
|
|
);
|
|
const lightTheme = {...theme, mode: 0, lightSchemeBackgroundColor: '#dddddd', lightSchemeTextColor: '#222222'};
|
|
createOrUpdateDynamicTheme(lightTheme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(221, 221, 221)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(34, 34, 34)');
|
|
});
|
|
|
|
it('should handle variables inside values', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --border-color: #ff0000;',
|
|
' --text: green;',
|
|
' }',
|
|
' h1 {',
|
|
' border-bottom: 2px solid var(--border-color);',
|
|
' color: var(--text);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Border with variable</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).borderBottomColor).toBe('rgb(179, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(140, 255, 140)');
|
|
});
|
|
|
|
it('should handle variables that have variables inside', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --dark-red: #ff0000;',
|
|
' --border: 2px solid var(--dark-red);',
|
|
' --text: green;',
|
|
' }',
|
|
' h1 {',
|
|
' border: var(--border);',
|
|
' color: var(--text);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Border with variable</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).borderTopColor).toBe('rgb(179, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).borderBottomColor).toBe('rgb(179, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).borderLeftColor).toBe('rgb(179, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).borderRightColor).toBe('rgb(179, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(140, 255, 140)');
|
|
});
|
|
|
|
it('should handle border color variables', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --border-color: green;',
|
|
' }',
|
|
' h1 {',
|
|
' border: 1px solid;',
|
|
' border-color: var(--border-color);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Border color with variable</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
const elementStyle = getComputedStyle(container.querySelector('h1')!);
|
|
if (isFirefox) {
|
|
expect(elementStyle.borderTopColor).toBe('rgb(0, 217, 0)');
|
|
expect(elementStyle.borderRightColor).toBe('rgb(0, 217, 0)');
|
|
expect(elementStyle.borderBottomColor).toBe('rgb(0, 217, 0)');
|
|
expect(elementStyle.borderLeftColor).toBe('rgb(0, 217, 0)');
|
|
} else {
|
|
expect(elementStyle.borderColor).toBe('rgb(0, 217, 0)');
|
|
}
|
|
});
|
|
|
|
it('should handle variables with gradients', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --text: red;',
|
|
' --gradient: linear-gradient(red, white);',
|
|
' --bg: green;',
|
|
' }',
|
|
' h1 {',
|
|
' color: var(--text);',
|
|
' background-color: var(--bg);',
|
|
' background-image: var(--gradient);',
|
|
' }',
|
|
' h2 {',
|
|
' background: var(--gradient);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Weow Gradients</h1>',
|
|
'<h2>Gradient 2</h2>'
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 26, 26)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundImage).toBe('linear-gradient(rgb(204, 0, 0), rgb(0, 0, 0))');
|
|
expect(getComputedStyle(container.querySelector('h2')!).backgroundImage).toBe('linear-gradient(rgb(204, 0, 0), rgb(0, 0, 0))');
|
|
});
|
|
|
|
it('should handle variables with background images', async () => {
|
|
const darkIcon = multiline(
|
|
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" width="8" height="8">',
|
|
' <circle fill="black" cx="4" cy="4" r="3" />',
|
|
'</svg>',
|
|
);
|
|
const redCross = multiline(
|
|
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" width="8" height="8">',
|
|
' <path fill="red" d="M3,1 h2 v2 h2 v2 h-2 v2 h-2 v-2 h-2 v-2 h2 z" />',
|
|
'</svg>',
|
|
);
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
` --icon: url("data:image/svg+xml;base64,${btoa(darkIcon)}");`,
|
|
` --red-cross: url("data:image/svg+xml;base64,${btoa(redCross)}");`,
|
|
' --bg: green;',
|
|
' }',
|
|
' .icon1 {',
|
|
' background-color: var(--bg);',
|
|
' background-image: var(--icon);',
|
|
' background-repeat: no-repeat;',
|
|
' background-size: 100%;',
|
|
' display: inline-block;',
|
|
' height: 1rem;',
|
|
' width: 1rem;',
|
|
' }',
|
|
' .icon2 {',
|
|
' background: no-repeat center/100% var(--icon);',
|
|
' display: inline-block;',
|
|
' height: 1rem;',
|
|
' width: 1rem;',
|
|
' }',
|
|
' .icon3 {',
|
|
' background: no-repeat center/100% var(--red-cross), no-repeat center/100% var(--icon);',
|
|
' display: inline-block;',
|
|
' height: 1rem;',
|
|
' width: 1rem;',
|
|
' }',
|
|
'</style>',
|
|
'<h1>',
|
|
' <i class="icon1"></i>',
|
|
' <i class="icon2"></i>',
|
|
' <i class="icon3"></i>',
|
|
' Icons',
|
|
'</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
await waitForEvent('__darkreader__test__asyncQueueComplete');
|
|
expect(getComputedStyle(container.querySelector('.icon1')!).backgroundImage).toMatch(/^url\("blob:.*"\)$/);
|
|
expect(getComputedStyle(container.querySelector('.icon2')!).backgroundImage).toMatch(/^url\("blob:.*"\)$/);
|
|
expect(getComputedStyle(container.querySelector('.icon3')!).backgroundImage).toMatch(/^url\("blob:.*"\), url\("blob:.*"\)$/);
|
|
});
|
|
|
|
it('should handle variables with gradients and images', async () => {
|
|
const darkIcon = multiline(
|
|
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" width="8" height="8">',
|
|
' <circle fill="black" cx="4" cy="4" r="3" />',
|
|
'</svg>',
|
|
);
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
` --icon: url("data:image/svg+xml;base64,${btoa(darkIcon)}");`,
|
|
' --gradient: linear-gradient(red, white);',
|
|
' }',
|
|
' .icon {',
|
|
' background: no-repeat center/100% var(--icon), var(--gradient);',
|
|
' display: inline-block;',
|
|
' height: 1rem;',
|
|
' width: 1rem;',
|
|
' }',
|
|
'</style>',
|
|
'<h1><i class="icon"></i>Mixed background</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
await waitForEvent('__darkreader__test__asyncQueueComplete');
|
|
expect(getComputedStyle(container.querySelector('.icon')!).backgroundImage).toMatch(/^url\("blob:.*"\), linear-gradient\(rgb\(204, 0, 0\), rgb\(0, 0, 0\)\)$/);
|
|
});
|
|
|
|
it('should handle asynchronous variable type resolution', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --color1: red;',
|
|
' --color2: green;',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Asynchronous variables</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgba(0, 0, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
|
|
const anotherStyle = document.createElement('style');
|
|
anotherStyle.textContent = multiline(
|
|
'h1 {',
|
|
' background: var(--color2);',
|
|
' color: var(--color1);',
|
|
'}',
|
|
);
|
|
container.append(anotherStyle);
|
|
await timeout(0);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
it('should handle async resolution of multiple variable types', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --color: green;',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Asynchronous variables with multiple types</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgba(0, 0, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
|
|
const anotherStyle = document.createElement('style');
|
|
anotherStyle.textContent = multiline(
|
|
'h1 {',
|
|
' background: var(--color);',
|
|
' border: 1px solid var(--color);',
|
|
' color: var(--color);',
|
|
'}',
|
|
);
|
|
container.append(anotherStyle);
|
|
await timeout(0);
|
|
const updatedStyle = getComputedStyle(container.querySelector('h1')!);
|
|
expect(updatedStyle.backgroundColor).toBe('rgb(0, 102, 0)');
|
|
expect(updatedStyle.color).toBe('rgb(140, 255, 140)');
|
|
if (isFirefox) {
|
|
expect(updatedStyle.borderTopColor).toBe('rgb(0, 217, 0)');
|
|
expect(updatedStyle.borderRightColor).toBe('rgb(0, 217, 0)');
|
|
expect(updatedStyle.borderBottomColor).toBe('rgb(0, 217, 0)');
|
|
expect(updatedStyle.borderLeftColor).toBe('rgb(0, 217, 0)');
|
|
} else {
|
|
expect(updatedStyle.borderColor).toBe('rgb(0, 217, 0)');
|
|
}
|
|
});
|
|
|
|
it('should handle variable type resolution when style changed', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --color1: yellow;',
|
|
' --color2: blue;',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Asynchronous variables</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgba(0, 0, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
|
|
const styleEl = container.querySelector('style')!;
|
|
styleEl.textContent = multiline(
|
|
':root {',
|
|
' --color1: red;',
|
|
' --color2: green;',
|
|
' --color3: blue;',
|
|
'}',
|
|
);
|
|
await timeout(0);
|
|
|
|
const anotherStyle = document.createElement('style');
|
|
anotherStyle.textContent = multiline(
|
|
'h1 {',
|
|
' background: var(--color2);',
|
|
' color: var(--color1);',
|
|
'}',
|
|
);
|
|
container.append(anotherStyle);
|
|
await timeout(0);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
it('should handle variable type change', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --color: green;',
|
|
' }',
|
|
' h1 {',
|
|
' background: var(--color);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Asynchronous variables</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(255, 255, 255)');
|
|
|
|
const anotherStyle = document.createElement('style');
|
|
anotherStyle.textContent = multiline(
|
|
'h1 {',
|
|
' color: var(--color);',
|
|
'}',
|
|
);
|
|
container.append(anotherStyle);
|
|
await timeout(0);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
|
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(140, 255, 140)');
|
|
});
|
|
|
|
it('should rebuild dependant variable rule when type becomes known', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 {',
|
|
' background: var(--color);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Asynchronous variables</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgba(0, 0, 0, 0)');
|
|
|
|
const anotherStyle = document.createElement('style');
|
|
anotherStyle.textContent = multiline(
|
|
':root {',
|
|
' --color: green;',
|
|
'}',
|
|
);
|
|
container.append(anotherStyle);
|
|
await timeout(50);
|
|
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
|
});
|
|
|
|
it('should not affect other declarations when variable type resolved', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' :root {',
|
|
' --color1: red;',
|
|
' --color2: green;',
|
|
' }',
|
|
'</style>',
|
|
'<h1 class="color1">Variables with color 1</h1>',
|
|
'<h1 class="color2">Variables with color 2</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
expect(getComputedStyle(container.querySelector('.color1')!).backgroundColor).toBe('rgba(0, 0, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('.color1')!).color).toBe('rgb(255, 255, 255)');
|
|
expect(getComputedStyle(container.querySelector('.color2')!).backgroundColor).toBe('rgba(0, 0, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('.color2')!).color).toBe('rgb(255, 255, 255)');
|
|
|
|
const anotherStyle = document.createElement('style');
|
|
anotherStyle.textContent = multiline(
|
|
'.color1 {',
|
|
' background: var(--color1);',
|
|
' color: var(--color1);',
|
|
'}',
|
|
'.color2 {',
|
|
' background: var(--color2);',
|
|
' color: var(--color2);',
|
|
'}',
|
|
);
|
|
container.append(anotherStyle);
|
|
await timeout(0);
|
|
expect(getComputedStyle(container.querySelector('.color1')!).backgroundColor).toBe('rgb(204, 0, 0)');
|
|
expect(getComputedStyle(container.querySelector('.color1')!).color).toBe('rgb(255, 26, 26)');
|
|
expect(getComputedStyle(container.querySelector('.color2')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
|
expect(getComputedStyle(container.querySelector('.color2')!).color).toBe('rgb(140, 255, 140)');
|
|
});
|
|
|
|
it('should not affect other declarations when variable type resolved', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 {',
|
|
' --color-bg: red;',
|
|
' border: green;',
|
|
' }',
|
|
' h1 {',
|
|
' --color-text: green;',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Variables along with other declarations</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
const elementStyle = getComputedStyle(container.querySelector('h1')!);
|
|
expect(elementStyle.backgroundColor).toBe('rgba(0, 0, 0, 0)');
|
|
expect(elementStyle.color).toBe('rgb(255, 255, 255)');
|
|
if (isFirefox) {
|
|
expect(elementStyle.borderTopColor).toBe('rgb(0, 217, 0)');
|
|
expect(elementStyle.borderRightColor).toBe('rgb(0, 217, 0)');
|
|
expect(elementStyle.borderBottomColor).toBe('rgb(0, 217, 0)');
|
|
expect(elementStyle.borderLeftColor).toBe('rgb(0, 217, 0)');
|
|
} else {
|
|
expect(elementStyle.borderColor).toBe('rgb(0, 217, 0)');
|
|
}
|
|
|
|
const anotherStyle = document.createElement('style');
|
|
anotherStyle.textContent = multiline(
|
|
'h1 {',
|
|
' background-color: var(--color-bg);',
|
|
' color: var(--color-text);',
|
|
'}',
|
|
);
|
|
container.append(anotherStyle);
|
|
await timeout(0);
|
|
const updatedStyle = getComputedStyle(container.querySelector('h1')!);
|
|
expect(updatedStyle.backgroundColor).toBe('rgb(204, 0, 0)');
|
|
expect(updatedStyle.color).toBe('rgb(140, 255, 140)');
|
|
if (isFirefox) {
|
|
expect(updatedStyle.borderTopColor).toBe('rgb(0, 217, 0)');
|
|
expect(updatedStyle.borderRightColor).toBe('rgb(0, 217, 0)');
|
|
expect(updatedStyle.borderBottomColor).toBe('rgb(0, 217, 0)');
|
|
expect(updatedStyle.borderLeftColor).toBe('rgb(0, 217, 0)');
|
|
} else {
|
|
expect(updatedStyle.borderColor).toBe('rgb(0, 217, 0)');
|
|
}
|
|
});
|
|
|
|
it('should not affect other declarations when dependency variable type resolved', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 {',
|
|
' background: var(--color);',
|
|
' border: green;',
|
|
' }',
|
|
' h1 {',
|
|
' color: red;',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Dependency variable</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
const elementStyle = getComputedStyle(container.querySelector('h1')!);
|
|
expect(elementStyle.backgroundColor).toBe('rgba(0, 0, 0, 0)');
|
|
expect(elementStyle.color).toBe('rgb(255, 26, 26)');
|
|
if (isFirefox) {
|
|
expect(elementStyle.borderTopColor).toBe('rgb(0, 217, 0)');
|
|
expect(elementStyle.borderRightColor).toBe('rgb(0, 217, 0)');
|
|
expect(elementStyle.borderBottomColor).toBe('rgb(0, 217, 0)');
|
|
expect(elementStyle.borderLeftColor).toBe('rgb(0, 217, 0)');
|
|
} else {
|
|
expect(elementStyle.borderColor).toBe('rgb(0, 217, 0)');
|
|
}
|
|
|
|
const anotherStyle = document.createElement('style');
|
|
anotherStyle.textContent = multiline(
|
|
':root {',
|
|
' --color: green;',
|
|
'}',
|
|
);
|
|
container.append(anotherStyle);
|
|
await timeout(50);
|
|
const updatedStyle = getComputedStyle(container.querySelector('h1')!);
|
|
expect(updatedStyle.backgroundColor).toBe('rgb(0, 102, 0)');
|
|
expect(updatedStyle.color).toBe('rgb(255, 26, 26)');
|
|
if (isFirefox) {
|
|
expect(updatedStyle.borderTopColor).toBe('rgb(0, 217, 0)');
|
|
expect(updatedStyle.borderRightColor).toBe('rgb(0, 217, 0)');
|
|
expect(updatedStyle.borderBottomColor).toBe('rgb(0, 217, 0)');
|
|
expect(updatedStyle.borderLeftColor).toBe('rgb(0, 217, 0)');
|
|
} else {
|
|
expect(updatedStyle.borderColor).toBe('rgb(0, 217, 0)');
|
|
}
|
|
});
|
|
|
|
it('should add variables to root after the variable type is discovered', async () => {
|
|
document.documentElement.setAttribute('style', '--text: red;');
|
|
container.innerHTML = multiline(
|
|
'<style class="testcase-sheet">',
|
|
'</style>',
|
|
'<h1>Dependency variable</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
const sheet = (document.querySelector('.testcase-sheet') as HTMLStyleElement).sheet!;
|
|
sheet.insertRule('h1 { color: var(--text);');
|
|
|
|
await timeout(0);
|
|
expect(getComputedStyle(document.querySelector('h1')!).color).toBe('rgb(255, 26, 26)');
|
|
});
|
|
|
|
it('should modify the caret-color property', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' * {',
|
|
' --caret-color: green;',
|
|
' }',
|
|
' h1 {',
|
|
' caret-color: var(--caret-color);',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Caret Color</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
|
|
await timeout(0);
|
|
const elementStyle = getComputedStyle(container.querySelector('h1')!);
|
|
|
|
expect(elementStyle.caretColor).toBe('rgb(140, 255, 140)');
|
|
});
|
|
|
|
it('should modify the box-shadow actual color values in a variable', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 {',
|
|
' --offset: 8px;',
|
|
' }',
|
|
' h1 {',
|
|
' box-shadow: calc(var(--offset)*-1 - 1px) 0 0 0 #fff',
|
|
' }',
|
|
'</style>',
|
|
'<h1>COmplicated shit :(</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
|
|
await timeout(0);
|
|
const elementStyle = getComputedStyle(container.querySelector('h1')!);
|
|
|
|
expect(elementStyle.boxShadow).toBe('rgb(0, 0, 0) -9px 0px 0px 0px');
|
|
});
|
|
|
|
it(`shouldn't modify the raw value of box-shadow when their is no color`, async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 {',
|
|
' --color-border-muted: green;',
|
|
' }',
|
|
' h1 {',
|
|
' box-shadow: inset 0 -1px 0 var(--color-border-muted)',
|
|
' }',
|
|
'</style>',
|
|
'<h1>COmplicated shit :(</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
|
|
await timeout(0);
|
|
const elementStyle = getComputedStyle(container.querySelector('h1')!);
|
|
|
|
expect(elementStyle.boxShadow).toBe('rgb(0, 102, 0) 0px -1px 0px 0px inset');
|
|
});
|
|
|
|
it('should handle raw values within variable declarations and use proper replacement', async () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 {',
|
|
' border: 1px solid rgba(var(--color,240,240,240),1);',
|
|
' }',
|
|
' :root {',
|
|
' --color: 123,123,123 !important;',
|
|
' }',
|
|
' div {',
|
|
' --color: 0,0,0 !important;',
|
|
' }',
|
|
'</style>',
|
|
'<h1>Raw values are spooky</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
|
|
await timeout(0);
|
|
const elementStyle = getComputedStyle(container.querySelector('h1')!);
|
|
|
|
if (isFirefox) {
|
|
expect(elementStyle.borderTopColor).toBe('rgb(91, 91, 91)');
|
|
expect(elementStyle.borderRightColor).toBe('rgb(91, 91, 91)');
|
|
expect(elementStyle.borderBottomColor).toBe('rgb(91, 91, 91)');
|
|
expect(elementStyle.borderLeftColor).toBe('rgb(91, 91, 91)');
|
|
} else {
|
|
expect(elementStyle.borderColor).toBe('rgb(91, 91, 91)');
|
|
}
|
|
});
|
|
|
|
it('should modify inline variable', () => {
|
|
container.innerHTML = multiline(
|
|
'<style>',
|
|
' h1 {',
|
|
' background-color: var(--inline-var);',
|
|
' }',
|
|
'</style>',
|
|
'<h1 style="--inline-var: red">Raw values are spooky</h1>',
|
|
);
|
|
createOrUpdateDynamicTheme(theme, null, false);
|
|
|
|
const elementStyle = getComputedStyle(container.querySelector('h1')!);
|
|
expect(elementStyle.backgroundColor).toBe('rgb(204, 0, 0)');
|
|
});
|
|
});
|