chore: 整理 third-party Dark Reader 目录并移除本地说明文件跟踪
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import '../support/polyfills';
|
||||
import {DEFAULT_THEME} from '../../../src/defaults';
|
||||
import {createOrUpdateDynamicTheme, removeDynamicTheme} from '../../../src/inject/dynamic-theme';
|
||||
import {multiline} from '../support/test-utils';
|
||||
|
||||
const theme = {
|
||||
...DEFAULT_THEME,
|
||||
};
|
||||
let container: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.body;
|
||||
container.innerHTML = '';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
removeDynamicTheme();
|
||||
container.innerHTML = '';
|
||||
});
|
||||
|
||||
describe('COLOR PARSING', () => {
|
||||
it('should modify RGBA', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
' h1 { background: rgb(245, 185, 124); }',
|
||||
' h1 strong { color: rgb(34, 52, 34); }',
|
||||
' body { background-color: rgba(51, 170, 51, .4); }',
|
||||
'</style>',
|
||||
'<h1>RGB <strong>Power</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(126, 68, 10)');
|
||||
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(205, 200, 194)');
|
||||
expect(getComputedStyle(container).backgroundColor).toBe('rgba(41, 136, 41, 0.4)');
|
||||
});
|
||||
|
||||
it('should modify HSL', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
' h1 { background: hsl(270,60%,70%); }',
|
||||
' h1 strong { color: hsl(.75turn, 60%, 70%); }',
|
||||
' body { background-color: hsl(4.71239rad, 60%, 70%); }',
|
||||
'</style>',
|
||||
'<h1>HSL <strong>Power</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(72, 29, 114)');
|
||||
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(176, 129, 223)');
|
||||
expect(getComputedStyle(container).backgroundColor).toBe('rgb(72, 29, 114)');
|
||||
});
|
||||
|
||||
it('should modify HSLA', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
' h1 { background: hsla(240, 100%, 50%, .7); }',
|
||||
' h1 strong { color: hsla(240, 100%, 50%, 1); }',
|
||||
' body { background-color: hsla(240, 100%, 50%, .05); }',
|
||||
'</style>',
|
||||
'<h1>HSLA <strong>Power</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgba(0, 0, 204, 0.7)');
|
||||
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(51, 125, 255)');
|
||||
expect(getComputedStyle(container).backgroundColor).toBe('rgba(0, 0, 204, 0.05)');
|
||||
});
|
||||
|
||||
it('should modify knownColors', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
' h1 { background: rebeccapurple; }',
|
||||
' h1 strong { color: InfoBackground; }',
|
||||
' body { background-color: transparent; }',
|
||||
'</style>',
|
||||
'<h1>Weird color <strong>Power</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(82, 41, 122)');
|
||||
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(249, 250, 166)');
|
||||
expect(getComputedStyle(container).backgroundColor).toBe('rgba(0, 0, 0, 0)');
|
||||
});
|
||||
|
||||
it('should handle calc(...) cases', () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
' h1 { background-color: hsl(0, 0%, calc(95% - 3%)) }',
|
||||
'</style>',
|
||||
'<h1>Weird color <strong>Power</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(35, 38, 40)');
|
||||
});
|
||||
|
||||
it('should handle gradient\'s cases with rgb(...) xx%', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
' h1 { background-image: -webkit-linear-gradient(bottom, rgb(255, 255, 255) 15%, rgb(246, 246, 245) 85%); }',
|
||||
'</style>',
|
||||
'<h1>Weird color <strong>Power</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundImage).toBe('-webkit-linear-gradient(bottom, rgb(24, 26, 27) 15%, rgb(29, 32, 33) 85%)');
|
||||
});
|
||||
|
||||
it('should handle complex calc(...) cases', () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
' h1 { background-color: rgb(calc(216.75 + 153 * .15), calc(216.75 + 205 * .15), calc(216.75 + 255 * .15)) }',
|
||||
'</style>',
|
||||
'<h1>Weird color <strong>Power</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(28, 31, 32)');
|
||||
});
|
||||
|
||||
it('should handle gradients with calc(...) cases', () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
' h1 { background-image: linear-gradient(rgb(249, 249, 251) calc(100% - 3px), transparent), linear-gradient(-90deg, rgb(255, 145, 0), rgb(241, 3, 102) 50%, rgb(97, 115, 255)) }',
|
||||
'</style>',
|
||||
'<h1>Weird color <strong>Power</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundImage).toBe('linear-gradient(rgb(27, 29, 30), calc(100% - 3px), rgba(0, 0, 0, 0)), linear-gradient(-90deg, rgb(204, 116, 0), rgb(193, 2, 82) 50%, rgb(0, 17, 146))');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
import '../support/polyfills';
|
||||
import {DEFAULT_THEME} from '../../../src/defaults';
|
||||
import type {DynamicThemeFix} from '../../../src/definitions';
|
||||
import {FilterMode} from '../../../src/generators/css-filter';
|
||||
import {createOrUpdateDynamicTheme, removeDynamicTheme} from '../../../src/inject/dynamic-theme';
|
||||
import {removeNode} from '../../../src/inject/utils/dom';
|
||||
import {multiline, timeout} from '../support/test-utils';
|
||||
|
||||
let container: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.body;
|
||||
container.innerHTML = '';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
removeDynamicTheme();
|
||||
container.innerHTML = '';
|
||||
removeNode(document.head.querySelector('meta[name="darkreader-lock"]'));
|
||||
});
|
||||
|
||||
describe('FIXES', () => {
|
||||
it('should add custom attributes to root element', () => {
|
||||
createOrUpdateDynamicTheme(DEFAULT_THEME, null, false);
|
||||
expect(document.documentElement.getAttribute(`data-darkreader-mode`)).toBe('dynamic');
|
||||
expect(document.documentElement.getAttribute('data-darkreader-scheme')).toBe('dark');
|
||||
|
||||
createOrUpdateDynamicTheme({...DEFAULT_THEME, mode: FilterMode.light}, null, false);
|
||||
expect(document.documentElement.getAttribute('data-darkreader-scheme')).toBe('dimmed');
|
||||
});
|
||||
|
||||
it('should invert selectors', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<div class="logo">Some logo</div>',
|
||||
);
|
||||
const fixes: DynamicThemeFix[] = [{
|
||||
url: ['*'],
|
||||
invert: ['.logo'],
|
||||
css: '',
|
||||
ignoreInlineStyle: [],
|
||||
ignoreImageAnalysis: [],
|
||||
ignoreCSSUrl: [],
|
||||
disableStyleSheetsProxy: false,
|
||||
disableCustomElementRegistryProxy: false,
|
||||
}];
|
||||
createOrUpdateDynamicTheme(DEFAULT_THEME, fixes, false);
|
||||
expect(getComputedStyle(container.querySelector('.logo')!).filter).toBe('invert(1) hue-rotate(180deg) contrast(0.9)');
|
||||
});
|
||||
|
||||
it('should insert CSS', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<p class="text">Some text need to be red</p>',
|
||||
);
|
||||
const fixes: DynamicThemeFix[] = [{
|
||||
url: ['*'],
|
||||
invert: [''],
|
||||
css: '.text { color: red }',
|
||||
ignoreInlineStyle: [],
|
||||
ignoreImageAnalysis: [],
|
||||
ignoreCSSUrl: [],
|
||||
disableStyleSheetsProxy: false,
|
||||
disableCustomElementRegistryProxy: false,
|
||||
}];
|
||||
createOrUpdateDynamicTheme(DEFAULT_THEME, fixes, false);
|
||||
expect(getComputedStyle(container.querySelector('.text')!).color).toBe('rgb(255, 0, 0)');
|
||||
});
|
||||
|
||||
it('should ignore inline style', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<p class="text" style="background-color: purple">Some text need to be red</p>',
|
||||
);
|
||||
const fixes: DynamicThemeFix[] = [{
|
||||
url: ['*'],
|
||||
invert: [''],
|
||||
css: '',
|
||||
ignoreInlineStyle: ['.text'],
|
||||
ignoreImageAnalysis: [],
|
||||
ignoreCSSUrl: [],
|
||||
disableStyleSheetsProxy: false,
|
||||
disableCustomElementRegistryProxy: false,
|
||||
}];
|
||||
createOrUpdateDynamicTheme(DEFAULT_THEME, fixes, false);
|
||||
expect(getComputedStyle(container.querySelector('.text')!).backgroundColor).toBe('rgb(128, 0, 128)');
|
||||
});
|
||||
|
||||
it('should ignore styling when darkreader-lock detected', async () => {
|
||||
document.head.innerHTML = '<meta name="darkreader-lock">',
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
' body {',
|
||||
' background-color: pink !important;',
|
||||
' }',
|
||||
'</style>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(DEFAULT_THEME, null, false);
|
||||
|
||||
expect(getComputedStyle(document.body).backgroundColor).toBe('rgb(255, 192, 203)');
|
||||
});
|
||||
|
||||
it('should ignore styling when delayed darkreader-lock detected', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
' body {',
|
||||
' background-color: pink !important;',
|
||||
' }',
|
||||
'</style>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(DEFAULT_THEME, null, false);
|
||||
|
||||
expect(getComputedStyle(container).backgroundColor).toBe('rgb(89, 0, 16)');
|
||||
const metaElement: HTMLMetaElement = document.createElement('meta');
|
||||
metaElement.name = 'darkreader-lock';
|
||||
document.head.appendChild(metaElement);
|
||||
await timeout(100);
|
||||
|
||||
expect(getComputedStyle(container).backgroundColor).toBe('rgb(255, 192, 203)');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,290 @@
|
||||
import '../support/polyfills';
|
||||
import {DEFAULT_THEME} from '../../../src/defaults';
|
||||
import type {DynamicThemeFix} from '../../../src/definitions';
|
||||
import {createOrUpdateDynamicTheme, removeDynamicTheme} from '../../../src/inject/dynamic-theme';
|
||||
import {getImageDetails} from '../../../src/inject/dynamic-theme/image';
|
||||
import {multiline, timeout, waitForEvent} from '../support/test-utils';
|
||||
|
||||
const theme = {
|
||||
...DEFAULT_THEME,
|
||||
darkSchemeBackgroundColor: 'black',
|
||||
darkSchemeTextColor: 'white',
|
||||
};
|
||||
let container: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.body;
|
||||
container.innerHTML = '';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
removeDynamicTheme();
|
||||
container.innerHTML = '';
|
||||
});
|
||||
|
||||
function svgToDataURL(svg: string) {
|
||||
return `data:image/svg+xml;base64,${btoa(svg)}`;
|
||||
}
|
||||
|
||||
function getSVGImageCSS(svg: string, width: number, height: number, selector: string) {
|
||||
return multiline(
|
||||
`${selector} {`,
|
||||
` background-image: url("${svgToDataURL(svg)}");`,
|
||||
' background-position: center;',
|
||||
' background-repeat: no-repeat;',
|
||||
' background-size: cover;',
|
||||
' display: inline-block;',
|
||||
` height: ${height}px;`,
|
||||
` width: ${width}px;`,
|
||||
'}',
|
||||
);
|
||||
}
|
||||
|
||||
const images = {
|
||||
darkIcon: multiline(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" width="8" height="8">',
|
||||
' <rect fill="black" width="100%" height="100%" />',
|
||||
'</svg>',
|
||||
),
|
||||
lightIcon: multiline(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" width="8" height="8">',
|
||||
' <rect fill="white" width="100%" height="100%" />',
|
||||
'</svg>',
|
||||
),
|
||||
darkTransparentIcon: 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>',
|
||||
),
|
||||
lightTransparentIcon: multiline(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" width="8" height="8">',
|
||||
' <circle fill="white" cx="4" cy="4" r="3" />',
|
||||
'</svg>',
|
||||
),
|
||||
largeDarkImage: multiline(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" width="1024" height="1024">',
|
||||
' <rect fill="black" width="100%" height="100%" />',
|
||||
'</svg>',
|
||||
),
|
||||
largeLightImage: multiline(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" width="1024" height="1024">',
|
||||
' <rect fill="white" width="100%" height="100%" />',
|
||||
'</svg>',
|
||||
),
|
||||
};
|
||||
|
||||
async function urlToImage(url: string) {
|
||||
return new Promise<HTMLImageElement>((resolve, reject) => {
|
||||
const image = new Image();
|
||||
image.onload = () => resolve(image);
|
||||
image.onerror = () => reject(`Unable to load image ${url}`);
|
||||
image.src = url;
|
||||
});
|
||||
}
|
||||
|
||||
async function getBgImageInfo(bgImageValue: string) {
|
||||
const bgImageURL = bgImageValue.match(/^url\("(.*)"\)$/)![1];
|
||||
const image = await urlToImage(bgImageURL);
|
||||
|
||||
const width = 8;
|
||||
const height = 8;
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const context = canvas.getContext('2d')!;
|
||||
context.drawImage(image, 0, 0, width, height);
|
||||
|
||||
const d = context.getImageData(0, 0, width, height).data;
|
||||
let lightPixels = 0;
|
||||
let darkPixels = 0;
|
||||
let opaquePixels = 0;
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
const i = 4 * (x + width * y);
|
||||
const r = d[i + 0];
|
||||
const g = d[i + 1];
|
||||
const b = d[i + 2];
|
||||
const a = d[i + 3];
|
||||
if (a > 127) {
|
||||
opaquePixels++;
|
||||
const lightness = (r + g + b) / 3 / 255;
|
||||
if (lightness > 0.7) {
|
||||
lightPixels++;
|
||||
} else if (lightness < 0.3) {
|
||||
darkPixels++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
lightness: lightPixels / opaquePixels,
|
||||
darkness: darkPixels / opaquePixels,
|
||||
};
|
||||
}
|
||||
|
||||
describe('IMAGE ANALYSIS', () => {
|
||||
it('should analyze dark icon', async () => {
|
||||
const details = await getImageDetails(svgToDataURL(images.darkIcon));
|
||||
expect(details.width).toBe(8);
|
||||
expect(details.height).toBe(8);
|
||||
expect(details.isDark).toBe(true);
|
||||
expect(details.isLight).toBe(false);
|
||||
expect(details.isTransparent).toBe(false);
|
||||
expect(details.isLarge).toBe(false);
|
||||
});
|
||||
|
||||
it('should analyze light icon', async () => {
|
||||
const details = await getImageDetails(svgToDataURL(images.lightIcon));
|
||||
expect(details.width).toBe(8);
|
||||
expect(details.height).toBe(8);
|
||||
expect(details.isDark).toBe(false);
|
||||
expect(details.isLight).toBe(true);
|
||||
expect(details.isTransparent).toBe(false);
|
||||
expect(details.isLarge).toBe(false);
|
||||
});
|
||||
|
||||
it('should analyze dark transparent icon', async () => {
|
||||
const details = await getImageDetails(svgToDataURL(images.darkTransparentIcon));
|
||||
expect(details.width).toBe(8);
|
||||
expect(details.height).toBe(8);
|
||||
expect(details.isDark).toBe(true);
|
||||
expect(details.isLight).toBe(false);
|
||||
expect(details.isTransparent).toBe(true);
|
||||
expect(details.isLarge).toBe(false);
|
||||
});
|
||||
|
||||
it('should not analyze large image', async () => {
|
||||
const details = await getImageDetails(svgToDataURL(images.largeDarkImage));
|
||||
expect(details.width).toBe(1024);
|
||||
expect(details.height).toBe(1024);
|
||||
expect(details.isLarge).toBe(true);
|
||||
});
|
||||
|
||||
it('should invert dark icons', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
getSVGImageCSS(images.darkTransparentIcon, 16, 16, 'i'),
|
||||
'</style>',
|
||||
'<h1>Dark icon <i></i></h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
await waitForEvent('__darkreader__test__asyncQueueComplete');
|
||||
await timeout(500);
|
||||
const bgImageValue = getComputedStyle(container.querySelector('i')!).backgroundImage;
|
||||
const info = await getBgImageInfo(bgImageValue);
|
||||
expect(info.darkness).toBe(0);
|
||||
expect(info.lightness).toBe(1);
|
||||
});
|
||||
|
||||
it('should not invert light icons', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
getSVGImageCSS(images.lightTransparentIcon, 16, 16, 'i'),
|
||||
'</style>',
|
||||
'<h1>Light icon <i></i></h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
const bgImageValue = getComputedStyle(container.querySelector('i')!).backgroundImage;
|
||||
const info = await getBgImageInfo(bgImageValue);
|
||||
expect(info.darkness).toBe(0);
|
||||
expect(info.lightness).toBe(1);
|
||||
});
|
||||
|
||||
it('should not invert dark backgrounds', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
getSVGImageCSS(images.largeDarkImage, 320, 320, 'h1'),
|
||||
'</style>',
|
||||
'<h1>Dark background</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
const bgImageValue = getComputedStyle(container.querySelector('h1')!).backgroundImage;
|
||||
const info = await getBgImageInfo(bgImageValue);
|
||||
expect(info.darkness).toBe(1);
|
||||
expect(info.lightness).toBe(0);
|
||||
});
|
||||
|
||||
it('should hide light backgrounds', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
getSVGImageCSS(images.largeLightImage, 320, 320, 'h1'),
|
||||
'</style>',
|
||||
'<h1>Light background</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
await waitForEvent('__darkreader__test__asyncQueueComplete');
|
||||
const bgImageValue = getComputedStyle(container.querySelector('h1')!).backgroundImage;
|
||||
expect(bgImageValue).toBe('none');
|
||||
});
|
||||
|
||||
it('should ignore image analysis', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
getSVGImageCSS(images.darkTransparentIcon, 16, 16, 'i'),
|
||||
'</style>',
|
||||
'<h1>Dark icon <i></i></h1>',
|
||||
);
|
||||
const fixes: DynamicThemeFix[] = [{
|
||||
url: ['*'],
|
||||
invert: [''],
|
||||
css: '',
|
||||
ignoreInlineStyle: ['.'],
|
||||
ignoreImageAnalysis: ['*'],
|
||||
ignoreCSSUrl: [],
|
||||
disableStyleSheetsProxy: false,
|
||||
disableCustomElementRegistryProxy: false,
|
||||
}];
|
||||
createOrUpdateDynamicTheme(theme, fixes, false);
|
||||
const backgroundImage = getComputedStyle(container.querySelector('i')!).backgroundImage;
|
||||
expect(backgroundImage).toContain('data:');
|
||||
});
|
||||
|
||||
it('should handle background-image with URL and gradient', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
` h1 { background-image: url("${svgToDataURL(images.lightIcon)}"), linear-gradient(red, white);`,
|
||||
'</style>',
|
||||
'<h1>Weird color <strong>Power</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
await waitForEvent('__darkreader__test__asyncQueueComplete');
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundImage).toMatch(/^url\("blob:.*"\), linear-gradient\(rgb\(204, 0, 0\), rgb\(0, 0, 0\)\)$/);
|
||||
});
|
||||
|
||||
it('should handle background-image with non-base64 data URL', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
` h1 { background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="6" height="3">%3Cpath%20d%3D%22m0%202.5%20l2%20-1.5%20l1%200%20l2%201.5%20l1%200%22%20stroke%3D%22%23d11%22%20fill%3D%22none%22%20stroke-width%3D%22.7%22%2F%3E</svg>');`,
|
||||
'</style>',
|
||||
'<h1>Weird color <strong>Power</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
await waitForEvent('__darkreader__test__asyncQueueComplete');
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundImage).toMatch(/^url\("blob:.*"\)$/);
|
||||
});
|
||||
|
||||
it('should handle background-image with URL and gradient (revered)', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
` h1 { background-image: linear-gradient(red, white), url("${svgToDataURL(images.lightIcon)}");`,
|
||||
'</style>',
|
||||
'<h1>Weird color <strong>Power</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
await waitForEvent('__darkreader__test__asyncQueueComplete');
|
||||
await timeout(500);
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundImage).toMatch(/^linear-gradient\(rgb\(204, 0, 0\), rgb\(0, 0, 0\)\), url\("blob:.*"\)$/);
|
||||
});
|
||||
|
||||
it('should handle background-image with empty URLs', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
` h1 { background-image: url(''), url(''), url("${svgToDataURL(images.lightIcon)}");`,
|
||||
'</style>',
|
||||
'<h1>Weird color <strong>Power</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
await waitForEvent('__darkreader__test__asyncQueueComplete');
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundImage).toMatch(/^url\(""\), url\(""\), url\("blob:.*"\)$/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import {injectStyleAway, removeStyleContainer} from '../../../src/inject/dynamic-theme/injection';
|
||||
import {timeout} from '../support/test-utils';
|
||||
|
||||
describe('STYLE INJECTION', () => {
|
||||
let originalBody: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
removeStyleContainer();
|
||||
originalBody = document.body;
|
||||
originalBody.remove();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
removeStyleContainer();
|
||||
document.body?.remove();
|
||||
document.documentElement.append(originalBody);
|
||||
});
|
||||
|
||||
it('should inject a queued style when the body becomes available', async () => {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = 'body { color: white; }';
|
||||
|
||||
injectStyleAway(style);
|
||||
expect(style.isConnected).toBe(false);
|
||||
|
||||
const body = document.createElement('body');
|
||||
document.documentElement.append(body);
|
||||
await timeout(0);
|
||||
|
||||
const container = body.querySelector('.darkreader-style-container');
|
||||
expect(container).not.toBeNull();
|
||||
expect(container!.lastElementChild).toBe(style);
|
||||
expect(style.sheet!.cssRules.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should clear queued styles and allow observing a new body', async () => {
|
||||
const discardedStyle = document.createElement('style');
|
||||
injectStyleAway(discardedStyle);
|
||||
removeStyleContainer();
|
||||
|
||||
const firstBody = document.createElement('body');
|
||||
document.documentElement.append(firstBody);
|
||||
await timeout(0);
|
||||
|
||||
expect(firstBody.querySelector('.darkreader-style-container')).toBeNull();
|
||||
expect(discardedStyle.isConnected).toBe(false);
|
||||
|
||||
firstBody.remove();
|
||||
const injectedStyle = document.createElement('style');
|
||||
injectStyleAway(injectedStyle);
|
||||
|
||||
const secondBody = document.createElement('body');
|
||||
document.documentElement.append(secondBody);
|
||||
await timeout(0);
|
||||
|
||||
expect(secondBody.querySelector('.darkreader-style-container')?.lastElementChild).toBe(injectedStyle);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
import '../support/polyfills';
|
||||
import {DEFAULT_THEME} from '../../../src/defaults';
|
||||
import {createOrUpdateDynamicTheme, removeDynamicTheme} from '../../../src/inject/dynamic-theme';
|
||||
import {multiline, timeout} from '../support/test-utils';
|
||||
|
||||
const theme = {
|
||||
...DEFAULT_THEME,
|
||||
darkSchemeBackgroundColor: 'black',
|
||||
darkSchemeTextColor: 'white',
|
||||
};
|
||||
let container: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.body;
|
||||
container.innerHTML = '';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
removeDynamicTheme();
|
||||
container.innerHTML = '';
|
||||
});
|
||||
|
||||
describe('INLINE STYLES', () => {
|
||||
it('should override inline style', () => {
|
||||
container.innerHTML = '<span style="color: red;">Inline style override</span>';
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(document.querySelector('span')!).color).toBe('rgb(255, 26, 26)');
|
||||
});
|
||||
|
||||
it('should watch for inline style change', async () => {
|
||||
container.innerHTML = '<span style="color: red;">Watch inline style</span>';
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
const span = document.querySelector('span')!;
|
||||
expect(getComputedStyle(span).color).toBe('rgb(255, 26, 26)');
|
||||
|
||||
span.style.color = 'green';
|
||||
await timeout(0);
|
||||
expect(getComputedStyle(span).color).toBe('rgb(140, 255, 140)');
|
||||
});
|
||||
|
||||
it('should override only a single inline style property', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>.bg-gray { background: gray; }</style>',
|
||||
'<span class="bg-gray" style="color: red;">Inline style override</span>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
const span = container.querySelector('span')!;
|
||||
expect(getComputedStyle(span).backgroundColor).toBe('rgb(102, 102, 102)');
|
||||
expect(getComputedStyle(span).color).toBe('rgb(255, 26, 26)');
|
||||
|
||||
span.style.color = 'green';
|
||||
await timeout(0);
|
||||
expect(getComputedStyle(span).color).toBe('rgb(140, 255, 140)');
|
||||
expect(getComputedStyle(span).backgroundColor).toBe('rgb(102, 102, 102)');
|
||||
});
|
||||
|
||||
it('should clean up the customProp after originial is gone', async () => {
|
||||
container.innerHTML = '<span style="color: red;">Watch inline style</span>';
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
const span = document.querySelector('span')!;
|
||||
expect(span.getAttribute('style')!.startsWith('color: red; --darkreader-inline-color:')).toBeTrue();
|
||||
expect(span.getAttribute('style')!.includes('--darkreader-inline-color: var(--darkreader-text-ff0000, #ff1a1a);')).toBe(true);
|
||||
|
||||
span.style.color = '';
|
||||
await timeout(0);
|
||||
expect(span.getAttribute('style')).toBe('');
|
||||
});
|
||||
|
||||
it(`shouldn't touch rel="mask-icon"`, async () => {
|
||||
container.innerHTML = '<link rel="mask-icon" color="red">';
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
const maskIcon = document.querySelector('link[rel="mask-icon"]')!;
|
||||
expect(maskIcon.getAttribute('style')).toBe(null);
|
||||
});
|
||||
|
||||
it(`shouldn't touch a "none" value for fill`, async () => {
|
||||
container.innerHTML = `<svg> <rect width="100" height="100" fill="none" /></svg>`;
|
||||
container.innerHTML += `<style> rect[width][height] { fill: red }</style>`;
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
const rect = container.querySelector('rect')!;
|
||||
expect(getComputedStyle(rect).fill).toBe('rgb(255, 26, 26)');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,225 @@
|
||||
import {DEFAULT_THEME} from '../../../src/defaults';
|
||||
import {createOrUpdateDynamicTheme, removeDynamicTheme} from '../../../src/inject/dynamic-theme';
|
||||
import {resetChromeRuntimeMessageStub, stubBackgroundFetchResponse, stubChromeRuntimeMessage} from '../support/background-stub';
|
||||
import {getCSSEchoURL} from '../support/echo-client';
|
||||
import {multiline, timeout, waitForEvent} from '../support/test-utils';
|
||||
|
||||
const theme = {
|
||||
...DEFAULT_THEME,
|
||||
darkSchemeBackgroundColor: 'black',
|
||||
darkSchemeTextColor: 'white',
|
||||
};
|
||||
let container: HTMLElement;
|
||||
const links: HTMLLinkElement[] = [];
|
||||
|
||||
function createStyleLink(href: string | null) {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.classList.add('testcase--link');
|
||||
if (href) {
|
||||
link.href = href;
|
||||
}
|
||||
document.head.append(link);
|
||||
links.push(link);
|
||||
return link;
|
||||
}
|
||||
|
||||
function selectTestStyleLink() {
|
||||
return document.querySelector('.testcase--link')! as HTMLLinkElement;
|
||||
}
|
||||
|
||||
function createCorsLink(content: string) {
|
||||
const url = getCSSEchoURL(content);
|
||||
stubBackgroundFetchResponse(url, content);
|
||||
return createStyleLink(url);
|
||||
}
|
||||
|
||||
async function waitForLinkLoading(link: HTMLLinkElement) {
|
||||
return new Promise((resolve, reject) => {
|
||||
link.addEventListener('load', resolve, {once: true});
|
||||
link.addEventListener('error', reject, {once: true});
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.body;
|
||||
container.innerHTML = '';
|
||||
stubChromeRuntimeMessage();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
removeDynamicTheme();
|
||||
container.innerHTML = '';
|
||||
links.forEach((l) => l.remove());
|
||||
links.splice(0);
|
||||
resetChromeRuntimeMessageStub();
|
||||
});
|
||||
|
||||
describe('LINK STYLES', () => {
|
||||
it('should override same-origin link', async () => {
|
||||
createStyleLink(`data:text/css;utf8,${encodeURIComponent(multiline(
|
||||
'h1 { background: gray; }',
|
||||
'h1 strong { color: red; }',
|
||||
))}`);
|
||||
container.innerHTML = multiline(
|
||||
'<h1>Link <strong>override</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
await timeout(50);
|
||||
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 cross-origin link', async () => {
|
||||
createCorsLink(multiline(
|
||||
'h1 { background: gray; }',
|
||||
'h1 strong { color: red; }',
|
||||
));
|
||||
container.innerHTML = multiline(
|
||||
'<h1><strong>Cross-origin</strong> link override</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
await waitForEvent('__darkreader__test__dynamicUpdateComplete');
|
||||
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 cross-origin imports in linked CSS', async () => {
|
||||
const importedCSS = 'h1 { background: gray; }';
|
||||
const importedURL = getCSSEchoURL(importedCSS);
|
||||
stubBackgroundFetchResponse(importedURL, importedCSS);
|
||||
createCorsLink(multiline(
|
||||
`@import "${importedURL}";`,
|
||||
'h1 strong { color: red; }',
|
||||
));
|
||||
container.innerHTML = multiline(
|
||||
'<h1><strong>Cross-origin import</strong> link override</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
await waitForEvent('__darkreader__test__dynamicUpdateComplete');
|
||||
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 cross-origin imports in linked CSS with capital @import', async () => {
|
||||
const importedCSS = 'h1 { background: gray; }';
|
||||
const importedURL = getCSSEchoURL(importedCSS);
|
||||
stubBackgroundFetchResponse(importedURL, importedCSS);
|
||||
createCorsLink(multiline(
|
||||
`@IMPORT "${importedURL}";`,
|
||||
'h1 strong { color: red; }',
|
||||
));
|
||||
container.innerHTML = multiline(
|
||||
'<h1><strong>Cross-origin import</strong> link override</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
await waitForEvent('__darkreader__test__dynamicUpdateComplete');
|
||||
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 cross-origin link that has already been loaded', async () => {
|
||||
createCorsLink(multiline(
|
||||
'h1 { background: gray; }',
|
||||
'h1 strong { color: red; }',
|
||||
));
|
||||
container.innerHTML = multiline(
|
||||
'<h1>Loaded <strong>cross-origin</strong> link override</h1>',
|
||||
);
|
||||
|
||||
await waitForLinkLoading(selectTestStyleLink());
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(128, 128, 128)');
|
||||
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(0, 0, 0)');
|
||||
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 0, 0)');
|
||||
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
await waitForEvent('__darkreader__test__dynamicUpdateComplete');
|
||||
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 remove manager from disabled link', async () => {
|
||||
const link = createStyleLink(`data:text/css;utf8,${encodeURIComponent(multiline(
|
||||
'h1 { background: gray; }',
|
||||
'h1 strong { color: red; }',
|
||||
))}`);
|
||||
container.innerHTML = multiline(
|
||||
'<h1>Link <strong>disabled</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
await timeout(50);
|
||||
expect(selectTestStyleLink().nextElementSibling!.classList.contains('darkreader--sync')).toBe(true);
|
||||
link.disabled = true;
|
||||
await timeout(0);
|
||||
expect(selectTestStyleLink().nextElementSibling!.classList.contains('darkreader--sync')).toBe(false);
|
||||
});
|
||||
it("Shouldn't wait on link that won't be loaded", async () => {
|
||||
const link = createStyleLink(null);
|
||||
link.setAttribute('data-href', `data:text/css;utf8,${encodeURIComponent(multiline(
|
||||
'h1 { background: green !important; }',
|
||||
'h1 strong { color: orange !important; }',
|
||||
))}`);
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
' h1 { background: gray; }',
|
||||
'</style>',
|
||||
'<h1>Link <strong>loading with non-href attribute</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
await timeout(50);
|
||||
const h1 = document.querySelector('h1')!;
|
||||
expect(getComputedStyle(h1).backgroundColor).toBe('rgb(102, 102, 102)');
|
||||
expect(document.querySelector('.darkreader--fallback')!.textContent).toBe('');
|
||||
});
|
||||
|
||||
it('should handle styles with @import "..." screen;', async () => {
|
||||
const importedCSS = 'h1 { background: gray; }';
|
||||
const importedURL = getCSSEchoURL(importedCSS);
|
||||
stubBackgroundFetchResponse(importedURL, importedCSS);
|
||||
createCorsLink(multiline(
|
||||
`@import "${importedURL}" screen;`,
|
||||
'h1 strong { color: red; }',
|
||||
));
|
||||
container.innerHTML = multiline(
|
||||
'<h1><strong>Cross-origin import</strong> link override</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
await waitForEvent('__darkreader__test__dynamicUpdateComplete');
|
||||
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 handle styles with invalid url(...)', async () => {
|
||||
const importedCSS = 'h1 { background-image: url("freecookies:3https://example.com"); background-color: gray; }';
|
||||
const importedURL = getCSSEchoURL(importedCSS);
|
||||
stubBackgroundFetchResponse(importedURL, importedCSS);
|
||||
stubBackgroundFetchResponse('freecookies:3https://example.com', '');
|
||||
createCorsLink(multiline(
|
||||
`@import "${importedURL}" screen;`,
|
||||
'h1 strong { color: red; }',
|
||||
));
|
||||
container.innerHTML = multiline(
|
||||
'<h1><strong>Cross-origin import</strong> link override</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
await waitForEvent('__darkreader__test__dynamicUpdateComplete');
|
||||
await timeout(1000);
|
||||
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)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import '../support/polyfills';
|
||||
import {DEFAULT_THEME} from '../../../src/defaults';
|
||||
import {createOrUpdateDynamicTheme, removeDynamicTheme} from '../../../src/inject/dynamic-theme';
|
||||
import {multiline, timeout} from '../support/test-utils';
|
||||
|
||||
const theme = {
|
||||
...DEFAULT_THEME,
|
||||
darkSchemeBackgroundColor: 'black',
|
||||
darkSchemeTextColor: 'white',
|
||||
};
|
||||
let container: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.body;
|
||||
container.innerHTML = '';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
removeDynamicTheme();
|
||||
container.innerHTML = '';
|
||||
});
|
||||
|
||||
describe('MEDIA QUERIES', () => {
|
||||
it('should not style blacklisted media', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style class="testcase-style">',
|
||||
' h1 { background: green; }',
|
||||
' h1 strong { color: orange; }',
|
||||
'</style>',
|
||||
'<style class="testcase-style-2" media="print">',
|
||||
' h1 { background: gray; }',
|
||||
' h1 strong { color: red; }',
|
||||
'</style>',
|
||||
'<h1>Some test foor...... <strong>Oh uhm removing styles :(</strong>!</h1>',
|
||||
);
|
||||
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(document.querySelector('h1')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
||||
expect(getComputedStyle(document.querySelector('h1 strong')!).color).toBe('rgb(255, 174, 26)');
|
||||
expect(document.querySelector('.testcase-style-2')!.nextElementSibling!.classList.contains('darkreader--sync')).toBe(false);
|
||||
});
|
||||
|
||||
it('should style lazyloaded media', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style class="testcase-style" media="print">',
|
||||
' h1 { background: green; }',
|
||||
' h1 strong { color: orange; }',
|
||||
'</style>',
|
||||
'<h1>Some test foor...... <strong>Oh uhm removing styles :(</strong>!</h1>',
|
||||
);
|
||||
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
(document.querySelector('.testcase-style') as HTMLStyleElement).media = 'screen';
|
||||
await timeout(0);
|
||||
expect((document.querySelector('.testcase-style') as HTMLStyleElement).media).toBe('screen');
|
||||
expect(getComputedStyle(document.querySelector('h1')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
||||
expect(getComputedStyle(document.querySelector('h1 strong')!).color).toBe('rgb(255, 174, 26)');
|
||||
expect(document.querySelector('.testcase-style')!.nextElementSibling!.classList.contains('darkreader--sync')).toBe(true);
|
||||
});
|
||||
|
||||
it('should check for CSS support', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style class="testcase-style">',
|
||||
' @supports (background: green) {',
|
||||
' h1 { background: green; }',
|
||||
' }',
|
||||
' @supports (color: orange) {',
|
||||
' h1 strong { color: orange; }',
|
||||
' }',
|
||||
' @supports (some-non-existing-prop: some-value) {',
|
||||
' body { background: pink; }',
|
||||
' }',
|
||||
'</style>',
|
||||
'<h1>Some test foor...... <strong>Oh uhm removing styles :(</strong>!</h1>',
|
||||
);
|
||||
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(document.querySelector('h1')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
||||
expect(getComputedStyle(document.querySelector('h1 strong')!).color).toBe('rgb(255, 174, 26)');
|
||||
expect(getComputedStyle(document.body).backgroundColor).toBe('rgb(0, 0, 0)');
|
||||
});
|
||||
|
||||
it('should check for CSS @media', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style class="testcase-style">',
|
||||
' @media screen and (min-width: 2px) {',
|
||||
' h1 { background: green; }',
|
||||
' }',
|
||||
' @media screen and (min-width: 200000px) {',
|
||||
' h1 { background: orange; }',
|
||||
' }',
|
||||
'</style>',
|
||||
'<h1>Some test media query i guess</h1>',
|
||||
);
|
||||
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(document.querySelector('h1')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
||||
expect((document.querySelector('.testcase-style')!.nextElementSibling as HTMLStyleElement).sheet!.cssRules.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should check for nested CSS @media', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style class="testcase-style">',
|
||||
' @media screen and (min-width: 2px) {',
|
||||
' @media screen and (min-width: 2px) {',
|
||||
' h1 { background: green; }',
|
||||
' }',
|
||||
' }',
|
||||
' @media screen and (min-width: 200000px) {',
|
||||
' h1 { background: orange; }',
|
||||
' }',
|
||||
'</style>',
|
||||
'<h1>Some test media query i guess</h1>',
|
||||
);
|
||||
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(document.querySelector('h1')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
||||
expect((document.querySelector('.testcase-style')!.nextElementSibling as HTMLStyleElement).sheet!.cssRules.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should style print/media query', () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style class="testcase-style">',
|
||||
' h1 { background: green; }',
|
||||
' h1 strong { color: orange; }',
|
||||
'</style>',
|
||||
'<style class="testcase-style-2" media="print, screen and (max-width: 9999999px)">',
|
||||
' h1 { background: gray; }',
|
||||
' h1 strong { color: red; }',
|
||||
'</style>',
|
||||
'<h1>Some test foor...... <strong>Oh uh media query :D</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(document.querySelector('h1')!).backgroundColor).toBe('rgb(102, 102, 102)');
|
||||
expect(getComputedStyle(document.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
||||
expect(document.querySelector('.testcase-style-2')!.nextElementSibling!.classList.contains('darkreader--sync')).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle same cssText but different media rule', () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style class="testcase-style">',
|
||||
' @media screen and (min-width: 200000000000000000000px) {',
|
||||
' h1 { background: green; }',
|
||||
' }',
|
||||
' @media screen and (min-width: 4px) {',
|
||||
' h1 { background: green; }',
|
||||
' }',
|
||||
'</style>',
|
||||
'<h1>Some test foor...... <strong>Oh uhm removing styles :(</strong>!</h1>',
|
||||
);
|
||||
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(document.querySelector('h1')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
||||
});
|
||||
|
||||
it('should not style blacklisted media and handle uppercase media', () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style class="testcase-style">',
|
||||
' h1 { background: green; }',
|
||||
' h1 strong { color: orange; }',
|
||||
'</style>',
|
||||
'<style class="testcase-style-2" media="Print">',
|
||||
' h1 { background: gray; }',
|
||||
' h1 strong { color: red; }',
|
||||
'</style>',
|
||||
'<h1>Some test foor...... <strong>Oh uhm removing styles :(</strong>!</h1>',
|
||||
);
|
||||
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(document.querySelector('h1')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
||||
expect(getComputedStyle(document.querySelector('h1 strong')!).color).toBe('rgb(255, 174, 26)');
|
||||
expect(document.querySelector('.testcase-style-2')!.nextElementSibling!.classList.contains('darkreader--sync')).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle media query and print', () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style class="testcase-style">',
|
||||
' @media (min-width: 2px), print {',
|
||||
' h1 { background: green; }',
|
||||
' }',
|
||||
'</style>',
|
||||
'<h1>Some test foor...... <strong>Oh uhm removing styles :(</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(document.querySelector('h1')!).backgroundColor).toBe('rgb(0, 102, 0)');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,178 @@
|
||||
import '../support/polyfills';
|
||||
import {DEFAULT_THEME} from '../../../src/defaults';
|
||||
import {createOrUpdateDynamicTheme, removeDynamicTheme} from '../../../src/inject/dynamic-theme';
|
||||
import {isFirefox, isSafari} from '../../../src/utils/platform';
|
||||
import {multiline, timeout} from '../support/test-utils';
|
||||
|
||||
const theme = {
|
||||
...DEFAULT_THEME,
|
||||
darkSchemeBackgroundColor: 'black',
|
||||
darkSchemeTextColor: 'white',
|
||||
};
|
||||
let container: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.body;
|
||||
container.innerHTML = '';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
removeDynamicTheme();
|
||||
container.innerHTML = '';
|
||||
});
|
||||
|
||||
describe('SHADOW DOM', () => {
|
||||
it('should add static overrides', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<div class="shadow-dom-wrapper"></div>',
|
||||
);
|
||||
document.querySelector('.shadow-dom-wrapper')!.attachShadow({mode: 'open'});
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
const shadowRoot = document.querySelector('.shadow-dom-wrapper')!.shadowRoot!;
|
||||
expect(shadowRoot.firstElementChild!.classList.contains('darkreader--inline')).toBe(true);
|
||||
expect(shadowRoot.firstElementChild!.nextElementSibling!.classList.contains('darkreader--override')).toBe(true);
|
||||
expect(shadowRoot.firstElementChild!.nextElementSibling!.nextElementSibling!.classList.contains('darkreader--invert')).toBe(true);
|
||||
});
|
||||
|
||||
it('should override styles', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<div class="shadow-dom-wrapper"></div>',
|
||||
);
|
||||
const shadow = document.querySelector('.shadow-dom-wrapper')!.attachShadow({mode: 'open'});
|
||||
const style = document.createElement('style');
|
||||
style.classList.add('test-case-style');
|
||||
shadow.appendChild(style);
|
||||
style.sheet!.insertRule('h1 { color: gray }');
|
||||
style.sheet!.insertRule('strong { color: red }');
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
const shadowRoot = document.querySelector('.shadow-dom-wrapper')!.shadowRoot!;
|
||||
const testCase = shadowRoot.querySelector('.test-case-style')!;
|
||||
expect(testCase.nextElementSibling!.classList.contains('darkreader--sync')).toBe(true);
|
||||
expect((testCase.nextElementSibling as HTMLStyleElement).sheet!.cssRules.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should react to DOM changes', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<div class="shadow-dom-wrapper"></div>',
|
||||
);
|
||||
const shadow = document.querySelector('.shadow-dom-wrapper')!.attachShadow({mode: 'open'});
|
||||
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
const style = document.createElement('style');
|
||||
style.classList.add('test-case-style');
|
||||
shadow.appendChild(style);
|
||||
style.sheet!.insertRule('h1 { color: gray }');
|
||||
style.sheet!.insertRule('strong { color: red }');
|
||||
|
||||
await timeout(0);
|
||||
const shadowRoot = document.querySelector('.shadow-dom-wrapper')!.shadowRoot!;
|
||||
const testCase = shadowRoot.querySelector('.test-case-style')!;
|
||||
expect(shadowRoot.firstElementChild!.classList.contains('darkreader--inline')).toBe(true);
|
||||
expect(shadowRoot.firstElementChild!.nextElementSibling!.classList.contains('darkreader--override')).toBe(true);
|
||||
expect(testCase.nextElementSibling!.classList.contains('darkreader--sync')).toBe(true);
|
||||
expect((testCase.nextElementSibling as HTMLStyleElement).sheet!.cssRules.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should override inline styles', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<div class="shadow-dom-wrapper"></div>',
|
||||
);
|
||||
const shadow = document.querySelector('.shadow-dom-wrapper')!.attachShadow({mode: 'open'});
|
||||
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
const standardElement = document.createElement('p');
|
||||
standardElement.style.color = 'red';
|
||||
shadow.appendChild(standardElement);
|
||||
|
||||
await timeout(0);
|
||||
const shadowRoot = document.querySelector('.shadow-dom-wrapper')!.shadowRoot!;
|
||||
expect(getComputedStyle(shadowRoot.querySelector('p')!).color).toBe('rgb(255, 26, 26)');
|
||||
});
|
||||
|
||||
it('should handle defined custom elements', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<custom-element>',
|
||||
'</custom-element>',
|
||||
);
|
||||
class CustomElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
const shadowRoot = this.attachShadow({mode: 'open'});
|
||||
const style = document.createElement('style');
|
||||
style.textContent = 'p { color: pink }';
|
||||
const paragraph = document.createElement('p');
|
||||
paragraph.textContent = 'Some text content that should be pink.';
|
||||
|
||||
shadowRoot.append(style);
|
||||
shadowRoot.append(paragraph);
|
||||
}
|
||||
}
|
||||
customElements.define('custom-element', CustomElement);
|
||||
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
const shadowRoot = document.querySelector('custom-element')!.shadowRoot!;
|
||||
expect(getComputedStyle(shadowRoot.querySelector('p')!).color).toBe('rgb(255, 198, 208)');
|
||||
});
|
||||
|
||||
it('should react to defined custom elements', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<delayed-custom-element>',
|
||||
'</delayed-custom-element>',
|
||||
);
|
||||
class DelayedCustomElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
const shadowRoot = this.attachShadow({mode: 'open'});
|
||||
const style = document.createElement('style');
|
||||
style.textContent = 'p { color: pink }';
|
||||
const paragraph = document.createElement('p');
|
||||
paragraph.textContent = 'Some text content that should be pink.';
|
||||
|
||||
shadowRoot.append(style);
|
||||
shadowRoot.append(paragraph);
|
||||
}
|
||||
}
|
||||
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
customElements.define('delayed-custom-element', DelayedCustomElement);
|
||||
await timeout(0);
|
||||
const shadowRoot = document.querySelector('delayed-custom-element')!.shadowRoot!;
|
||||
expect(getComputedStyle(shadowRoot.querySelector('p')!).color).toBe('rgb(255, 198, 208)');
|
||||
});
|
||||
|
||||
it('should override styles', async () => {
|
||||
// Firefox by default doesn't enable the CSSStyleSheet constructor.
|
||||
if (isFirefox || isSafari) {
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = multiline(
|
||||
'<div class="shadow-dom-wrapper"></div>',
|
||||
);
|
||||
|
||||
const newRule = new CSSStyleSheet();
|
||||
newRule.insertRule(':host { --red: red }');
|
||||
|
||||
const shadow = document.querySelector('.shadow-dom-wrapper')!.attachShadow({mode: 'open'});
|
||||
shadow.adoptedStyleSheets = [newRule];
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.classList.add('test-case-style');
|
||||
shadow.appendChild(style);
|
||||
style.sheet!.insertRule('h1 { color: var(--red) }');
|
||||
|
||||
const h1 = document.createElement('h1');
|
||||
shadow.appendChild(h1);
|
||||
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
const shadowRoot = document.querySelector('.shadow-dom-wrapper')!.shadowRoot!;
|
||||
const testCase = shadowRoot.querySelector('.test-case-style')!;
|
||||
const darkendH1 = shadowRoot.querySelector('h1')!;
|
||||
expect(testCase.nextElementSibling!.classList.contains('darkreader--sync')).toBe(true);
|
||||
expect((testCase.nextElementSibling as HTMLStyleElement).sheet!.cssRules.length).toBe(1);
|
||||
expect(shadowRoot.adoptedStyleSheets.length).toBe(2);
|
||||
expect(getComputedStyle(darkendH1).color).toBe('rgb(255, 26, 26)');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,248 @@
|
||||
import '../support/polyfills';
|
||||
import {DEFAULT_THEME} from '../../../src/defaults';
|
||||
import {createOrUpdateDynamicTheme, removeDynamicTheme} from '../../../src/inject/dynamic-theme';
|
||||
import {createStyleSheetModifier} from '../../../src/inject/dynamic-theme/stylesheet-modifier';
|
||||
import {multiline, timeout} from '../support/test-utils';
|
||||
|
||||
const theme = {
|
||||
...DEFAULT_THEME,
|
||||
darkSchemeBackgroundColor: 'black',
|
||||
darkSchemeTextColor: 'white',
|
||||
};
|
||||
let container: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.body;
|
||||
container.innerHTML = '';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
removeDynamicTheme();
|
||||
container.innerHTML = '';
|
||||
});
|
||||
|
||||
describe('STYLE ELEMENTS', () => {
|
||||
it('should fill CSSStyleSheet with overridden rules', () => {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = 'body { background-color: white; } h1 { color: black; }';
|
||||
container.append(style);
|
||||
|
||||
const modifier = createStyleSheetModifier();
|
||||
const overrideStyle = document.createElement('style');
|
||||
container.append(overrideStyle);
|
||||
const override = overrideStyle.sheet!;
|
||||
modifier.modifySheet({
|
||||
theme,
|
||||
sourceCSSRules: style.sheet!.cssRules,
|
||||
ignoreImageAnalysis: [],
|
||||
force: false,
|
||||
prepareSheet: () => override,
|
||||
isAsyncCancelled: () => false,
|
||||
});
|
||||
|
||||
expect(override.cssRules.length).toBe(2);
|
||||
expect((override.cssRules[0] as CSSStyleRule).selectorText).toBe('body');
|
||||
expect((override.cssRules[0] as CSSStyleRule).style.getPropertyValue('background-color')).toBe('var(--darkreader-background-ffffff, #000000)');
|
||||
expect((override.cssRules[1] as CSSStyleRule).selectorText).toBe('h1');
|
||||
expect((override.cssRules[1] as CSSStyleRule).style.getPropertyValue('color')).toBe('var(--darkreader-text-000000, #ffffff)');
|
||||
});
|
||||
|
||||
it('should override User Agent style', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<span>Text</span>',
|
||||
'<a href="#">Link</a>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(container).backgroundColor).toBe('rgb(0, 0, 0)');
|
||||
expect(getComputedStyle(container).color).toBe('rgb(255, 255, 255)');
|
||||
expect(getComputedStyle(container.querySelector('span')!).color).toBe('rgb(255, 255, 255)');
|
||||
expect(getComputedStyle(container.querySelector('a')!).color).toBe('rgb(51, 145, 255)');
|
||||
});
|
||||
|
||||
it('should override static style', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
' h1 { background: gray; }',
|
||||
' h1 strong { color: red; }',
|
||||
'</style>',
|
||||
'<h1>Style <strong>override</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 @import', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
` @import "data:text/css;utf8,${encodeURIComponent('h1 { background: gray; }')}";`,
|
||||
' h1 strong { color: red; }',
|
||||
'</style>',
|
||||
'<h1>Style <strong>with @import</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
await timeout(50);
|
||||
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 @import"..."', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style>',
|
||||
` @import"data:text/css;utf8,${encodeURIComponent('h1 { background: gray; }')}";`,
|
||||
' h1 strong { color: red; }',
|
||||
'</style>',
|
||||
'<h1>Style <strong>with @import"..."</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
await timeout(50);
|
||||
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 restore override', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style class="testcase-style">',
|
||||
' h1 { color: gray; }',
|
||||
' h1 strong { color: red; }',
|
||||
'</style>',
|
||||
'<h1>Style <strong>override</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(141, 141, 141)');
|
||||
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
||||
|
||||
const style = document.querySelector('.testcase-style')!;
|
||||
style.nextSibling!.remove();
|
||||
await timeout(0);
|
||||
expect((style.nextSibling as HTMLStyleElement).classList.contains('darkreader--sync')).toBe(true);
|
||||
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(141, 141, 141)');
|
||||
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
||||
});
|
||||
|
||||
it('should move override', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style class="testcase-style">',
|
||||
' h1 { background: gray; }',
|
||||
' h1 strong { color: red; }',
|
||||
'</style>',
|
||||
'<h1>Some test foor...... <strong>Moving styles</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(102, 102, 102)');
|
||||
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
||||
|
||||
const style = document.querySelector('.testcase-style')!;
|
||||
container.append(style);
|
||||
await timeout(0);
|
||||
expect((style.nextSibling as HTMLStyleElement).classList.contains('darkreader--sync')).toBe(true);
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(102, 102, 102)');
|
||||
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
||||
});
|
||||
|
||||
it('should remove override', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style class="testcase-style">',
|
||||
' h1 { background: gray; }',
|
||||
' h1 strong { color: red; }',
|
||||
'</style>',
|
||||
'<h1>Some test foor...... <strong>Oh uhm removing styles :(</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
const style = document.querySelector('.testcase-style')!;
|
||||
const sibling = style.nextSibling!;
|
||||
style.remove();
|
||||
await timeout(0);
|
||||
expect(sibling.isConnected).toBe(false);
|
||||
expect(document.querySelector('.darkreader--sync')).toBe(null);
|
||||
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 react to updated style', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style class="testcase-style"></style>',
|
||||
'<h1>Some test foor...... <strong>Oh uhm a pink background</strong></h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
const style: HTMLStyleElement = document.querySelector('.testcase-style')!;
|
||||
style.sheet!.insertRule('h1 { color: gray }');
|
||||
style.sheet!.insertRule('strong { color: red }');
|
||||
style.sheet!.insertRule('body { background-color: pink }');
|
||||
await timeout(0);
|
||||
expect((style.nextSibling as HTMLStyleElement).sheet!.cssRules[0].cssText).toBe('body { background-color: var(--darkreader-background-ffc0cb, #320009); }');
|
||||
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(141, 141, 141)');
|
||||
expect(getComputedStyle(container.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
||||
expect(getComputedStyle(document.body).backgroundColor).toBe('rgb(50, 0, 9)');
|
||||
});
|
||||
|
||||
it('should react on style text change', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<style class="testcase-style">',
|
||||
' h1 strong { color: red; }',
|
||||
'</style>',
|
||||
'<h1>Style <strong>text change</strong></h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(document.querySelector('h1 strong')!).color).toBe('rgb(255, 26, 26)');
|
||||
|
||||
document.querySelector('.testcase-style')!.textContent = 'h1 strong { color: green; }';
|
||||
await timeout(0);
|
||||
expect(getComputedStyle(document.querySelector('h1 strong')!).color).toBe('rgb(140, 255, 140)');
|
||||
});
|
||||
|
||||
it('should react to a new style', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<h1>Some test foor...... <strong>Oh uhm what?</strong>!</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
|
||||
const style: HTMLStyleElement = document.createElement('style');
|
||||
style.classList.add('testcase-style');
|
||||
container.append(style);
|
||||
style.sheet!.insertRule('h1 { color: pink }');
|
||||
style.sheet!.insertRule('strong { color: orange }');
|
||||
|
||||
await timeout(0);
|
||||
const newStyle: HTMLStyleElement = document.querySelector('.testcase-style')!;
|
||||
const nextSibling = newStyle.nextSibling as HTMLStyleElement;
|
||||
expect(nextSibling.sheet!.cssRules.length).toBe(2);
|
||||
expect(nextSibling.classList.contains('darkreader--sync')).toBe(true);
|
||||
expect(getComputedStyle(document.querySelector('h1')!).color).toBe('rgb(255, 198, 208)');
|
||||
expect(getComputedStyle(document.querySelector('h1 strong')!).color).toBe('rgb(255, 174, 26)');
|
||||
});
|
||||
|
||||
it('should restore override after container move', async () => {
|
||||
container.innerHTML = multiline(
|
||||
'<div class="style-container">',
|
||||
' <style class="testcase-style">',
|
||||
' h1 { background: gray; }',
|
||||
' h1 { color: green; }',
|
||||
' </style>',
|
||||
'</div>',
|
||||
'<h1>Moving container</h1>',
|
||||
);
|
||||
createOrUpdateDynamicTheme(theme, null, false);
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(102, 102, 102)');
|
||||
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(140, 255, 140)');
|
||||
|
||||
const styleContainer = document.querySelector('.style-container')!;
|
||||
const style = styleContainer.querySelector('.testcase-style')!;
|
||||
const override = style.nextElementSibling as HTMLStyleElement;
|
||||
container.append(styleContainer);
|
||||
await timeout(0);
|
||||
expect(style.nextElementSibling).toBe(override);
|
||||
expect(override.sheet!.cssRules.length).toBe(2);
|
||||
expect(getComputedStyle(container.querySelector('h1')!).backgroundColor).toBe('rgb(102, 102, 102)');
|
||||
expect(getComputedStyle(container.querySelector('h1')!).color).toBe('rgb(140, 255, 140)');
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user