chore: 整理 third-party Dark Reader 目录并移除本地说明文件跟踪

This commit is contained in:
2026-08-08 17:38:08 +08:00
parent 202ab537d1
commit 0bb552b026
1004 changed files with 115066 additions and 20081 deletions
+138
View File
@@ -0,0 +1,138 @@
import type {HSLA} from '../../../src/utils/color';
import {lowerCalcExpression, parse, hslToRGB, rgbToHSL, rgbToString, rgbToHexString, hslToString, getRGBValues} from '../../../src/utils/color';
test('Color parsing', () => {
expect(parse('rgb(255,0,153)')).toEqual({r: 255, g: 0, b: 153, a: 1});
expect(parse('rgb(255, 0, 153)')).toEqual({r: 255, g: 0, b: 153, a: 1});
expect(parse('rgb(100%,0%,60%)')).toEqual({r: 255, g: 0, b: 153, a: 1});
expect(parse('rgb(100%, 0%, 60%)')).toEqual({r: 255, g: 0, b: 153, a: 1});
expect(parse('rgb(255 0 153)')).toEqual({r: 255, g: 0, b: 153, a: 1});
expect(parse('rgb(255, 0, 153, 1)')).toEqual({r: 255, g: 0, b: 153, a: 1});
expect(parse('rgb(255, 0, 153, 100%)')).toEqual({r: 255, g: 0, b: 153, a: 1});
expect(parse('rgb(255 0 153 / 1)')).toEqual({r: 255, g: 0, b: 153, a: 1});
expect(parse('rgb(255 0 153 / 100%)')).toEqual({r: 255, g: 0, b: 153, a: 1});
expect(parse('rgb(255, 0, 153.6, 1)')).toEqual({r: 255, g: 0, b: 154, a: 1});
expect(parse('rgb(0 0 0/0.04)')).toEqual({r: 0, g: 0, b: 0, a: 0.04});
expect(parse('rgb(1e2, .5e1, .5e0, +.25e2%)')).toEqual({r: 100, g: 5, b: 1, a: 0.25});
expect(parse('rgba(51, 170, 51, .1)')).toEqual({r: 51, g: 170, b: 51, a: 0.1});
expect(parse('rgba(51, 170, 51, .4)')).toEqual({r: 51, g: 170, b: 51, a: 0.4});
expect(parse('rgba(51, 170, 51, .7)')).toEqual({r: 51, g: 170, b: 51, a: 0.7});
expect(parse('rgba(51, 170, 51, 1)')).toEqual({r: 51, g: 170, b: 51, a: 1});
expect(parse('rgba(51 170 51 / 0.4)')).toEqual({r: 51, g: 170, b: 51, a: 0.4});
expect(parse('rgba(51 170 51 / 40%)')).toEqual({r: 51, g: 170, b: 51, a: 0.4});
expect(parse('rgba(255, 0, 153.6, 1)')).toEqual({r: 255, g: 0, b: 154, a: 1});
expect(parse('rgba(1e2, .5e1, .5e0, +.25e2%)')).toEqual({r: 100, g: 5, b: 1, a: 0.25});
expect(parse('hsl(270,60%,70%)')).toEqual({r: 179, g: 133, b: 224, a: 1});
expect(parse('hsl(270, 60%, 70%)')).toEqual({r: 179, g: 133, b: 224, a: 1});
expect(parse('hsl(270 60% 70%)')).toEqual({r: 179, g: 133, b: 224, a: 1});
expect(parse('hsl(270deg, 60%, 70%)')).toEqual({r: 179, g: 133, b: 224, a: 1});
expect(parse('hsl(4.71239rad, 60%, 70%)')).toEqual({r: 179, g: 133, b: 224, a: 1});
expect(parse('hsl(.75turn, 60%, 70%)')).toEqual({r: 179, g: 133, b: 224, a: 1});
expect(parse('hsl(270, 60%, 50%, .15)')).toEqual({r: 128, g: 51, b: 204, a: 0.15});
expect(parse('hsl(270, 60%, 50%, 15%)')).toEqual({r: 128, g: 51, b: 204, a: 0.15});
expect(parse('hsl(270 60% 50% / .15)')).toEqual({r: 128, g: 51, b: 204, a: 0.15});
expect(parse('hsl(270 60% 50% / 15%)')).toEqual({r: 128, g: 51, b: 204, a: 0.15});
expect(parse('hsla(240, 100%, 50%, .05)')).toEqual({r: 0, g: 0, b: 255, a: 0.05});
expect(parse('hsla(240, 100%, 50%, .4)')).toEqual({r: 0, g: 0, b: 255, a: 0.4});
expect(parse('hsla(240, 100%, 50%, .7)')).toEqual({r: 0, g: 0, b: 255, a: 0.7});
expect(parse('hsla(240, 100%, 50%, 1)')).toEqual({r: 0, g: 0, b: 255, a: 1});
expect(parse('hsla(240 100% 50% / .05)')).toEqual({r: 0, g: 0, b: 255, a: 0.05});
expect(parse('hsla(240 100% 50% / 5%)')).toEqual({r: 0, g: 0, b: 255, a: 0.05});
expect(parse('#f09')).toEqual({r: 255, g: 0, b: 153, a: 1});
expect(parse('#F09')).toEqual({r: 255, g: 0, b: 153, a: 1});
expect(parse('#ff0099')).toEqual({r: 255, g: 0, b: 153, a: 1});
expect(parse('#FF0099')).toEqual({r: 255, g: 0, b: 153, a: 1});
expect(parse('#3a30')).toEqual({r: 51, g: 170, b: 51, a: 0});
expect(parse('#3A3F')).toEqual({r: 51, g: 170, b: 51, a: 1});
expect(parse('#33aa3300')).toEqual({r: 51, g: 170, b: 51, a: 0});
expect(parse('#33AA3388')).toEqual({r: 51, g: 170, b: 51, a: 136 / 255});
expect(parse('rebeccapurple')).toEqual({r: 102, g: 51, b: 153, a: 1});
expect(parse('transparent')).toEqual({r: 0, g: 0, b: 0, a: 0});
expect(parse('InfoBackground')).toEqual({r: 251, g: 252, b: 197, a: 1});
expect(parse('-webkit-focus-ring-color')).toEqual({r: 229, g: 151, b: 0, a: 1});
expect(parse('sponge')).toBeNull();
expect(parse('hsl(0, 0%, 0%) rgb(0, 0, 0)')).toBeNull();
expect(parse('#hello')).toBeNull();
});
test('Stringify color', () => {
expect(rgbToString({r: 255, g: 0, b: 153})).toEqual('rgb(255, 0, 153)');
expect(rgbToString({r: 255, g: 0, b: 153, a: 1})).toEqual('rgb(255, 0, 153)');
expect(rgbToString({r: 255, g: 0, b: 153, a: 0.25003})).toEqual('rgba(255, 0, 153, 0.25)');
expect(rgbToHexString({r: 255, g: 0, b: 153})).toEqual('#ff0099');
expect(rgbToHexString({r: 255, g: 0, b: 153, a: 1})).toEqual('#ff0099');
expect(rgbToHexString({r: 255, g: 0, b: 153, a: 0.25003})).toEqual('#ff009940');
expect(hslToString({h: 270, s: 0.6, l: 0.7})).toEqual('hsl(270, 60%, 70%)');
expect(hslToString({h: 270, s: 0.6, l: 0.7, a: 1})).toEqual('hsl(270, 60%, 70%)');
expect(hslToString({h: 270.25, s: 0.5988, l: 0.702, a: 0.33333})).toEqual('hsla(270, 60%, 70%, 0.33)');
expect(hslToString({h: 270.25, s: 0.5988, l: 0.702, a: 0.00032})).toEqual('hsla(270, 60%, 70%, 0)');
});
test('Color conversion', () => {
expect(hslToRGB({h: 180, s: 1, l: 0.5, a: 0.25})).toEqual({r: 0, g: 255, b: 255, a: 0.25});
expect(hslToRGB({h: 0, s: 1, l: 0.25, a: 0.5})).toEqual({r: 128, g: 0, b: 0, a: 0.5});
expect(hslToRGB({h: 12, s: 0.78, l: 0.61})).toEqual({r: 233, g: 109, b: 78, a: 1});
expect(hslToRGB({h: 192, s: 0.57, l: 0.10})).toEqual({r: 11, g: 34, b: 40, a: 1});
expect(hslToRGB({h: 0, s: 0, l: 0.5})).toEqual({r: 128, g: 128, b: 128, a: 1});
const round = (color: HSLA) => Object.entries(color).reduce((c, [k, v]) => (c[k as keyof HSLA] = k === 'h' ? Math.round(v) : Math.round(v * 100) / 100, c), {} as HSLA);
expect(round(rgbToHSL({r: 0, g: 255, b: 255, a: 0.25}))).toEqual({h: 180, s: 1, l: 0.5, a: 0.25});
expect(round(rgbToHSL({r: 128, g: 0, b: 0, a: 0.5}))).toEqual({h: 0, s: 1, l: 0.25, a: 0.5});
expect(round(rgbToHSL({r: 233, g: 109, b: 78}))).toEqual({h: 12, s: 0.78, l: 0.61, a: 1});
expect(round(rgbToHSL({r: 11, g: 34, b: 40}))).toEqual({h: 192, s: 0.57, l: 0.10, a: 1});
expect(round(rgbToHSL({r: 161, g: 28, b: 61}))).toEqual({h: 345, s: 0.7, l: 0.37, a: 1});
});
test('Lower calc expressions', () => {
expect(lowerCalcExpression('hsl(0, 0%, calc(95% - 3%))')).toEqual('hsl(0, 0%, 92%)');
expect(lowerCalcExpression('hsl(0, calc(25% + 12%), calc(95% - 3%))')).toEqual('hsl(0, 37%, 92%)');
expect(lowerCalcExpression('rgb(calc(216.75 + 153 * .15), calc(216.75 + 205 * .15), calc(216.75 + 255 * .15))')).toEqual('rgb(240, 248, 255)');
});
test('RGB numbers', () => {
expect(getRGBValues('0 0 0')).toEqual([0, 0, 0, 1]);
expect(getRGBValues('255 255 255')).toEqual([255, 255, 255, 1]);
expect(getRGBValues('0, 0, 0')).toEqual([0, 0, 0, 1]);
expect(getRGBValues('255, 255, 255')).toEqual([255, 255, 255, 1]);
expect(getRGBValues('0 0 0 / 0.5')).toEqual([0, 0, 0, 0.5]);
expect(getRGBValues('255 255 255 / 0.25')).toEqual([255, 255, 255, 0.25]);
expect(getRGBValues('0, 0, 0, 0.5')).toEqual([0, 0, 0, 0.5]);
expect(getRGBValues('255, 255, 255, 0.25')).toEqual([255, 255, 255, 0.25]);
expect(getRGBValues('0, 0, 0, .5')).toEqual([0, 0, 0, 0.5]);
expect(getRGBValues('255, 255, 255, .25')).toEqual([255, 255, 255, 0.25]);
expect(getRGBValues('0 0 0 / 50%')).toEqual([0, 0, 0, 0.5]);
expect(getRGBValues('255 255 255 / 50%')).toEqual([255, 255, 255, 0.5]);
expect(getRGBValues('50% 0 25% / 50%')).toEqual([128, 0, 64, 0.5]);
expect(getRGBValues('.8% 0 255 / 12.5%')).toEqual([2, 0, 255, 0.125]);
expect(getRGBValues('255 , 255 , 255')).toEqual([255, 255, 255, 1]);
expect(getRGBValues('50%0%25%/50%')).toEqual([128, 0, 64, 0.5]);
expect(getRGBValues('0')).toEqual(null);
expect(getRGBValues('0 0')).toEqual(null);
expect(getRGBValues('0, 0')).toEqual(null);
expect(getRGBValues('0%, 0%, 0%, 0%')).toEqual(null);
expect(getRGBValues('0%, 0%, 0% / 0%')).toEqual(null);
expect(getRGBValues('%')).toEqual(null);
expect(getRGBValues('.')).toEqual(null);
expect(getRGBValues('. . .')).toEqual(null);
});
+48
View File
@@ -0,0 +1,48 @@
import {scale, clamp, multiplyMatrices} from '../../../src/utils/math';
test('Scale', () => {
expect(scale(5, 0, 10, 0, 100)).toEqual(50);
});
test('Clamp', () => {
expect(clamp(1, 2, 3)).toEqual(2);
expect(clamp(2, 1, 3)).toEqual(2);
expect(clamp(3, 1, 2)).toEqual(2);
});
test('Matrix multiplication', () => {
expect(multiplyMatrices([
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20],
[21, 22, 23, 24, 25],
],
[
[1], [2], [3], [4], [5],
])).toEqual([
[55], [130], [205], [280], [355],
]);
expect(multiplyMatrices([
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20],
[21, 22, 23, 24, 25],
],
[
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20],
[21, 22, 23, 24, 25],
])).toEqual([
[215, 230, 245, 260, 275],
[490, 530, 570, 610, 650],
[765, 830, 895, 960, 1025],
[1040, 1130, 1220, 1310, 1400],
[1315, 1430, 1545, 1660, 1775],
]);
// expect(multiplyMatrices([[1, 2, 3], [6, 5, 3]], [[1, 2], [7, 4], [6, 2]])).toEqual([[33, 16], [59, 38]]);
});
+119
View File
@@ -0,0 +1,119 @@
import {parseCSS} from '../../../src/utils/css-text/parse-css';
import {parseGradient} from '../../../src/utils/css-text/parse-gradient';
test('Parse CSS', () => {
const cssText = '@media all { div { } img[src*="a,b;c"] { background-color: black; background-image: url(data:image/gif;base64,XYZ); color: red !important; } } @media print, screen and (min-width: 20rem) { h1, h2 { } } c-wiz[data-p^="%.@.[[1,3"] { color: green; }';
const parsedCSS = parseCSS(cssText);
expect(parsedCSS).toEqual([
{
type: '@media',
query: 'all',
rules: [
{
selectors: ['div'],
declarations: [],
},
{
selectors: ['img[src*="a,b;c"]'],
declarations: [
{
property: 'background-color',
value: 'black',
important: false,
},
{
property: 'background-image',
value: 'url(data:image/gif;base64,XYZ)',
important: false,
},
{
property: 'color',
value: 'red',
important: true,
},
],
},
],
},
{
type: '@media',
query: 'print, screen and (min-width: 20rem)',
rules: [
{
selectors: ['h1', 'h2'],
declarations: [],
},
],
},
{
selectors: ['c-wiz[data-p^="%.@.[[1,3"]'],
declarations: [
{
property: 'color',
value: 'green',
important: false,
},
],
},
]);
});
test('type gradients', () => {
expect(parseGradient('linear-gradient(rgb(200))')).toEqual([{
typeGradient: 'linear-gradient',
match: 'rgb(200)',
offset: 17,
index: 0,
hasComma: false,
}]);
expect(parseGradient(`linear-gradient(217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70.71%),
linear-gradient(127deg, rgba(0,255,0,.8), rgba(0,255,0,0) 70.71%),
linear-gradient(336deg, rgba(0,0,255,.8), rgba(0,0,255,0) 70.71%);`)).toEqual([
{
offset: 17,
match: '217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70.71%',
hasComma: true,
index: 0,
typeGradient: 'linear-gradient',
},
{
typeGradient: 'linear-gradient',
match: '127deg, rgba(0,255,0,.8), rgba(0,255,0,0) 70.71%',
hasComma: true,
offset: 17,
index: 71,
},
{
typeGradient: 'linear-gradient',
match: '336deg, rgba(0,0,255,.8), rgba(0,0,255,0) 70.71%',
hasComma: false,
offset: 17,
index: 142,
},
]);
expect(parseGradient('radial-gradient(rgb(200))')).toEqual([{
typeGradient: 'radial-gradient',
match: 'rgb(200)',
index: 0,
offset: 17,
hasComma: false,
}]);
expect(parseGradient('repeating-linear-gradient(transparent, #4d9f0c 40px), repeating-linear-gradient(0.25turn, transparent, #3f87a6 20px);')).toEqual([
{
typeGradient: 'repeating-linear-gradient',
match: 'transparent, #4d9f0c 40px',
hasComma: true,
offset: 27,
index: 0,
},
{
typeGradient: 'repeating-linear-gradient',
match: '0.25turn, transparent, #3f87a6 20px',
hasComma: false,
index: 54,
offset: 27,
},
]);
});
@@ -0,0 +1,99 @@
import {expect, jest, test} from '@jest/globals';
import {PromiseBarrier} from '../../../src/utils/promise-barrier';
describe('Promise barrier utility', () => {
test('basic functionality', async () => {
const barrier = new PromiseBarrier();
const fn1 = jest.fn();
const fn2 = jest.fn();
(async () => {
fn1();
const result = await barrier.entry();
fn2(result);
})();
expect(fn1).toHaveBeenCalled();
expect(fn2).not.toHaveBeenCalled();
expect(barrier.isPending()).toBe(true);
expect(barrier.isFulfilled()).toBe(false);
expect(barrier.isRejected()).toBe(false);
await barrier.resolve(2);
expect(barrier.isFulfilled()).toBe(true);
expect(barrier.isPending()).toBe(false);
expect(barrier.isRejected()).toBe(false);
expect(fn1).toHaveBeenCalledTimes(1);
expect(fn2).toHaveBeenCalledWith(2);
});
test('awaiting for barrier after it was settled', async () => {
const barrierFulfilled = new PromiseBarrier();
expect(barrierFulfilled.isPending()).toBe(true);
expect(barrierFulfilled.isFulfilled()).toBe(false);
expect(barrierFulfilled.isRejected()).toBe(false);
const promise1 = barrierFulfilled.resolve('Hello World!');
expect(barrierFulfilled.isFulfilled()).toBe(true);
expect(barrierFulfilled.isPending()).toBe(false);
expect(barrierFulfilled.isRejected()).toBe(false);
const fn1 = jest.fn();
await (async () => fn1(await barrierFulfilled.entry()))();
await promise1;
expect(fn1).toHaveBeenCalledWith('Hello World!');
const barrierRejected = new PromiseBarrier();
const promise2 = barrierRejected.reject('rejection reason');
const fn2 = jest.fn();
await (async () => {
try {
await barrierRejected.entry();
} catch (e) {
fn2(e);
}
})();
await promise2;
expect(fn2).toHaveBeenCalledWith('rejection reason');
});
test('resolving multiple times', async () => {
const barrierFulfilled = new PromiseBarrier();
expect(barrierFulfilled.isPending()).toBe(true);
expect(barrierFulfilled.isFulfilled()).toBe(false);
expect(barrierFulfilled.isRejected()).toBe(false);
await barrierFulfilled.resolve('Hello World!');
expect(barrierFulfilled.isFulfilled()).toBe(true);
expect(barrierFulfilled.isPending()).toBe(false);
expect(barrierFulfilled.isRejected()).toBe(false);
await barrierFulfilled.resolve('Hello World 2!');
expect(barrierFulfilled.isFulfilled()).toBe(true);
expect(barrierFulfilled.isPending()).toBe(false);
expect(barrierFulfilled.isRejected()).toBe(false);
const fn1 = jest.fn();
await (async () => fn1(await barrierFulfilled.entry()))();
expect(fn1).toHaveBeenCalledWith('Hello World!');
});
test('rejecting multiple times', async () => {
const barrierRejected = new PromiseBarrier();
expect(barrierRejected.isPending()).toBe(true);
expect(barrierRejected.isFulfilled()).toBe(false);
expect(barrierRejected.isRejected()).toBe(false);
await barrierRejected.reject('rejection reason');
expect(barrierRejected.isRejected()).toBe(true);
expect(barrierRejected.isPending()).toBe(false);
expect(barrierRejected.isFulfilled()).toBe(false);
await barrierRejected.reject('rejection reason 2');
const fn2 = jest.fn();
await (async () => {
try {
await barrierRejected.entry();
} catch (e) {
fn2(e);
}
})();
expect(fn2).toHaveBeenCalledWith('rejection reason');
});
});
@@ -0,0 +1,644 @@
import {expect, jest, test} from '@jest/globals';
import {StateManagerImpl} from '../../../src/utils/state-manager-impl';
class PromiseWrapper {
promises: Map<Promise<void>, 'pending' | 'resolved' | 'rejected'>;
constructor() {
this.promises = new Map();
}
add(promises: Promise<void> | Array<Promise<void>>) {
if (!Array.isArray(promises)) {
promises = [promises];
}
promises.forEach((promise) => {
if (this.promises.has(promise)) {
throw new Error('Already added');
}
this.promises.set(promise, 'pending');
promise.then(() => this.promises.set(promise, 'resolved'))
.catch(() => this.promises.set(promise, 'rejected'));
});
}
clear() {
this.promises.clear();
}
getState(promise: Promise<void>) {
return this.promises.get(promise);
}
all(state: 'pending' | 'resolved' | 'rejected') {
this.promises.forEach((s) => expect(s).toEqual(state));
}
}
describe('State manager utility', () => {
const nextTick = () => new Promise((r) => setTimeout(r));
const noop = () => { /* noop */ };
test('State manager basic functionality', async () => {
const key = 'key';
const storage: any = {
fromStorage: 'fromStorage',
};
const getMock = jest.fn();
const get = (storageKey: string, callback: any) => {
expect(storageKey).toEqual(key);
getMock();
callback({});
};
const setMock = jest.fn();
const set = (items: any, callback: () => void) => {
Object.assign(storage, items);
setMock();
callback();
};
const parent: any = {
noSync: true,
fromParent: 'fromParent',
fromStorage: undefined,
};
const stateManager = new StateManagerImpl(key, parent, {
fromParent: 'fromDefault',
fromStorage: 'fromDefault',
}, {get, set}, noop, noop);
expect(getMock).not.toHaveBeenCalled();
expect(setMock).not.toHaveBeenCalled();
expect(parent).toEqual({
noSync: true,
fromParent: 'fromParent',
fromStorage: undefined,
});
await stateManager.loadState();
expect(getMock).toHaveBeenCalled();
expect(setMock).not.toHaveBeenCalled();
expect(parent).toEqual({
noSync: true,
fromParent: 'fromDefault',
fromStorage: 'fromDefault',
});
parent.other = 'another';
await stateManager.saveState();
expect(setMock).toHaveBeenCalled();
expect(storage[key]).toEqual({
fromParent: 'fromDefault',
fromStorage: 'fromDefault',
});
expect(parent).toEqual({
fromParent: 'fromDefault',
fromStorage: 'fromDefault',
noSync: true,
other: 'another',
});
});
test('State manager handles multiple concurrent loadState() calls', async () => {
const key = 'key';
let getCount = 0;
let getCallback: (data: any) => void;
const get = (storageKey: string, callback: (data: any) => void) => {
getCount++;
expect(storageKey).toEqual(key);
getCallback = callback;
};
const setMock = jest.fn();
const parent: any = {
noSync: true,
data: 'fromParent',
};
const stateManager = new StateManagerImpl(key, parent, {
data: 'fromStorage',
}, {get, set: setMock}, noop, noop);
expect(setMock).not.toHaveBeenCalled();
expect(parent).toEqual({
noSync: true,
data: 'fromParent',
});
// Multiple calls to loadState() should result in a single call to c.s.l.get()
const promises = new PromiseWrapper();
for (let i = 0; i < 5; i++) {
promises.add(stateManager.loadState());
}
expect(getCount).toEqual(1);
expect(parent).toEqual({
noSync: true,
data: 'fromParent',
});
promises.all('pending');
expect(getCount).toEqual(1);
getCallback!({});
promises.all('pending');
await nextTick();
promises.all('resolved');
expect(getCount).toEqual(1);
expect(setMock).not.toHaveBeenCalled();
});
test('State manager handles multiple concurrent saveState() calls', async () => {
const key = 'key';
const storage: any = {};
const get = (storageKey: string, callback: (data: any) => void) => {
expect(storageKey).toEqual(key);
callback({[storageKey]: storage[storageKey]});
};
const resolveSet = () => setCallback();
let setCount = 0;
let setCallback: () => void;
const set = (items: any, callback: () => void) => {
setCount++;
setCallback = () => {
Object.assign(storage, items);
callback();
};
};
const parent: any = {
noSync: true,
count: 100,
};
const stateManager = new StateManagerImpl(key, parent, {
count: 0,
}, {get, set}, noop, noop);
expect(parent).toEqual({
noSync: true,
count: 100,
});
await stateManager.loadState();
expect(parent).toEqual({
noSync: true,
count: 0,
});
// Multiple calls to loadState() should result in a single call to c.s.l.get()
const promises = new PromiseWrapper();
for (let i = 0; i < 5; i++) {
parent.count++;
promises.add(stateManager.saveState());
}
expect(setCount).toEqual(1);
expect(parent).toEqual({
noSync: true,
count: 5,
});
promises.all('pending');
// Resolve the first write
expect(setCount).toEqual(1);
resolveSet();
expect(setCount).toEqual(2);
promises.all('pending');
await nextTick();
promises.all('pending');
expect(parent).toEqual({
noSync: true,
count: 5,
});
// Resolve the second write
resolveSet();
expect(setCount).toEqual(2);
expect(storage).toEqual({
key: {
count: 5,
},
});
await nextTick();
promises.all('resolved');
});
test('State manager handles multiple concurrent saveState() calls', async () => {
const key = 'key';
const storage: any = {};
const get = (storageKey: string, callback: (data: any) => void) => {
expect(storageKey).toEqual(key);
callback({[storageKey]: storage[storageKey]});
};
const resolveSet = () => setCallback();
let setCount = 0;
let setCallback: () => void;
const set = (items: any, callback: () => void) => {
setCount++;
setCallback = () => {
Object.assign(storage, items);
callback();
};
};
const parent: any = {
noSync: true,
count: 100,
};
const stateManager = new StateManagerImpl(key, parent, {
count: 0,
}, {get, set}, noop, noop);
expect(parent).toEqual({
noSync: true,
count: 100,
});
await stateManager.loadState();
expect(parent).toEqual({
noSync: true,
count: 0,
});
// Multiple calls to loadState() should result in a single call to c.s.l.get()
const promises = new PromiseWrapper();
for (let i = 0; i < 5; i++) {
parent.count++;
promises.add(stateManager.saveState());
}
expect(setCount).toEqual(1);
expect(parent).toEqual({
noSync: true,
count: 5,
});
promises.all('pending');
// Resolve the first write
expect(setCount).toEqual(1);
resolveSet();
expect(setCount).toEqual(2);
promises.all('pending');
// Wait for the next tick when all loadState promises resolve
await nextTick();
promises.all('pending');
expect(parent).toEqual({
noSync: true,
count: 5,
});
// Resolve the second write
resolveSet();
expect(setCount).toEqual(2);
expect(storage).toEqual({
key: {
count: 5,
},
});
await nextTick();
promises.all('resolved');
});
test('State manager handles saveState() before loadState()', async () => {
const key = 'key';
const storage: any = {};
let getCount = 0;
let getCallback: () => void;
const resolveGet = () => getCallback();
const get = (storageKey: string, callback: (data: any) => void) => {
expect(storageKey).toEqual(key);
getCount++;
getCallback = () => {
callback({[storageKey]: storage[storageKey]});
};
};
let setCount = 0;
const set = () => setCount++;
const parent: any = {
data: 'fromParent',
};
const stateManager = new StateManagerImpl(key, parent, {
data: 'fromDefault',
}, {get, set}, noop, noop);
expect(parent).toEqual({
data: 'fromParent',
});
const promise = new PromiseWrapper();
promise.add(stateManager.saveState());
expect(parent).toEqual({
data: 'fromParent',
});
expect(getCount).toEqual(1);
expect(setCount).toEqual(0);
await nextTick();
promise.promises.forEach((state) => expect(state).toBe('pending'));
resolveGet();
await nextTick();
promise.promises.forEach((state) => expect(state).toBe('resolved'));
expect(parent).toEqual({
data: 'fromDefault',
});
await stateManager.loadState();
expect(parent).toEqual({
data: 'fromDefault',
});
expect(setCount).toEqual(0);
});
test('State manager handles interleaved saveState() and loadState()', async () => {
const key = 'key';
const storage: any = {};
let getCount = 0;
let getCallback: () => void;
const resolveGet = () => getCallback();
const get = (storageKey: string, callback: (data: any) => void) => {
expect(storageKey).toEqual(key);
getCount++;
getCallback = () => {
callback({[storageKey]: storage[storageKey]});
};
};
let setCount = 0;
let setCallback: () => void;
const resolveSet = () => setCallback();
const set = (items: any, callback: () => void) => {
setCount++;
setCallback = () => {
Object.assign(storage, items);
callback();
};
};
const parent: any = {
data: 'fromParent',
};
const stateManager = new StateManagerImpl(key, parent, {
data: 'fromDefault',
}, {get, set}, noop, noop);
expect(parent).toEqual({
data: 'fromParent',
});
const promises = new PromiseWrapper();
promises.add([
stateManager.loadState(),
stateManager.loadState(),
stateManager.saveState(),
stateManager.loadState(),
stateManager.loadState(),
stateManager.saveState(),
stateManager.saveState(),
stateManager.saveState(),
]);
expect(parent).toEqual({
data: 'fromParent',
});
expect(getCount).toEqual(1);
expect(setCount).toEqual(0);
await nextTick();
expect(getCount).toEqual(1);
expect(setCount).toEqual(0);
promises.all('pending');
resolveGet();
await nextTick();
expect(parent).toEqual({
data: 'fromDefault',
});
expect(getCount).toEqual(1);
expect(setCount).toEqual(0);
promises.all('resolved');
parent.data = 'new';
await stateManager.loadState();
expect(parent).toEqual({
data: 'new',
});
expect(getCount).toEqual(1);
expect(setCount).toEqual(0);
const promises2 = new PromiseWrapper();
promises2.add([
stateManager.saveState(),
stateManager.loadState(),
stateManager.saveState(),
stateManager.loadState(),
stateManager.loadState(),
stateManager.saveState(),
stateManager.loadState(),
stateManager.saveState(),
]);
expect(getCount).toEqual(1);
expect(setCount).toEqual(1);
expect(parent).toEqual({
data: 'new',
});
resolveSet();
await nextTick();
expect(parent).toEqual({
data: 'new',
});
expect(getCount).toEqual(1);
expect(setCount).toEqual(2);
resolveSet();
await nextTick();
promises2.all('resolved');
});
test('State manager handles onChanged during saveState() and loadState()', async () => {
const key = 'key';
const storage: any = {};
let getCount = 0;
let getCallback: (() => void) | undefined;
const resolveGet = () => {
getCallback!();
getCallback = undefined;
};
const get = (storageKey: string, callback: (data: any) => void) => {
expect(storageKey).toEqual(key);
getCount++;
getCallback = () => {
callback({[storageKey]: storage[storageKey]});
};
};
let setCount = 0;
let setCallback: (() => void) | undefined;
const resolveSet = () => {
setCallback!();
setCallback = undefined;
};
const set = (items: any, callback: () => void) => {
setCount++;
setCallback = () => {
Object.assign(storage, items);
callback();
};
};
let onChangedListener: ((data: any) => void) | undefined;
const modifyInternalState = (data: any) => {
expect(onChangedListener).toBeTruthy();
const oldValue = storage[key];
storage[key] = data;
onChangedListener!({
[key]: {
oldValue,
newValue: data,
},
});
};
const parent: any = {
data: 'fromParent',
};
const stateManager = new StateManagerImpl(key, parent, {
data: 'fromDefault',
}, {get, set}, (listener) => onChangedListener = listener, noop);
const c1 = jest.fn();
stateManager.addChangeListener(c1);
expect(parent).toEqual({
data: 'fromParent',
});
const promises = new PromiseWrapper();
promises.add(stateManager.loadState());
expect(parent).toEqual({
data: 'fromParent',
});
expect(getCount).toEqual(1);
expect(setCount).toEqual(0);
await nextTick();
expect(getCount).toEqual(1);
expect(setCount).toEqual(0);
promises.all('pending');
expect(c1).not.toHaveBeenCalled();
modifyInternalState({
data: 'fromStorageChange',
});
resolveGet();
await nextTick();
expect(c1).toHaveBeenCalled();
expect(parent).toEqual({
data: 'fromStorageChange',
});
expect(getCount).toEqual(2);
expect(setCount).toEqual(0);
promises.all('resolved');
expect(stateManager.getStateForTesting()).toEqual('Ready');
const c2 = jest.fn();
stateManager.addChangeListener(c2);
parent.data = 'new';
await stateManager.loadState();
expect(parent).toEqual({
data: 'new',
});
expect(getCount).toEqual(2);
expect(setCount).toEqual(0);
expect(stateManager.getStateForTesting()).toEqual('Ready');
const promises2 = new PromiseWrapper;
promises2.add(stateManager.saveState());
modifyInternalState({
data: 'fromStorageChange2',
});
expect(getCount).toEqual(2);
expect(setCount).toEqual(1);
promises2.all('pending');
// During data race the JS-world data does not get overwriten
expect(parent).toEqual({
data: 'new',
});
resolveSet();
await nextTick();
expect(parent).toEqual({
data: 'new',
});
expect(getCount).toEqual(3);
expect(setCount).toEqual(1);
promises2.all('pending');
expect(c2).not.toHaveBeenCalled();
resolveGet();
await nextTick();
expect(c2).toHaveBeenCalled();
expect(parent).toEqual({
data: 'new',
});
expect(getCount).toEqual(3);
expect(setCount).toEqual(1);
promises2.all('resolved');
});
});
+112
View File
@@ -0,0 +1,112 @@
import {formatCSS} from '../../../src/utils/css-text/format-css';
import {getParenthesesRange} from '../../../src/utils/text';
test('CSS formatting', () => {
expect(formatCSS('div { color: red; }'))
.toEqual([
'div {',
' color: red;',
'}',
].join('\n'));
expect(formatCSS('div { color: red; } .list-item { background: rgb(0, 0, 0); color: white; }'))
.toEqual([
'div {',
' color: red;',
'}',
'.list-item {',
' background: rgb(0, 0, 0);',
' color: white;',
'}',
].join('\n'));
expect(formatCSS('@media screen { div { color: red; } span { color: red; } } @media all { p { color: green; } }'))
.toEqual([
'@media screen {',
' div {',
' color: red;',
' }',
' span {',
' color: red;',
' }',
'}',
'@media all {',
' p {',
' color: green;',
' }',
'}',
].join('\n'));
expect(formatCSS('div, span { background: green; color: red; }'))
.toEqual([
'div,',
'span {',
' background: green;',
' color: red;',
'}',
].join('\n'));
expect(formatCSS('@media print, screen and (min-width: 20rem) { div, span { background: green; color: red; } }'))
.toEqual([
'@media print, screen and (min-width: 20rem) {',
' div,',
' span {',
' background: green;',
' color: red;',
' }',
'}',
].join('\n'));
expect(formatCSS('.icon { background-image: url(data:image/gif;base64,XYZ); }'))
.toEqual([
'.icon {',
' background-image: url(data:image/gif;base64,XYZ);',
'}',
].join('\n'));
expect(formatCSS('img[src*="a,b;c"] { filter: invert(1); }'))
.toEqual([
'img[src*="a,b;c"] {',
' filter: invert(1);',
'}',
].join('\n'));
expect(formatCSS('img[src*="a,b;c"] {\n filter: invert(1); \n}'))
.toEqual([
'img[src*="a,b;c"] {',
' filter: invert(1);',
'}',
].join('\n'));
expect(formatCSS('div { } span { color: red; } button { }'))
.toEqual([
'span {',
' color: red;',
'}',
].join('\n'));
expect(formatCSS('@media all { div { } span { color: red; } } @media all { div { } } @media all { button { color: green; } }'))
.toEqual([
'@media all {',
' span {',
' color: red;',
' }',
'}',
'@media all {',
' button {',
' color: green;',
' }',
'}',
].join('\n'));
});
test('Parenthesis Range', () => {
expect(getParenthesesRange('missing')).toBe(null);
expect(getParenthesesRange('()')).toEqual({start: 0, end: 2});
expect(getParenthesesRange('rgb(0, 0, 0)')).toEqual({start: 3, end: 12});
expect(getParenthesesRange('rgb(0, 0, 0), rgb(0, 0, 0)')).toEqual({start: 3, end: 12});
expect(getParenthesesRange('rgb(0, 0, 0), rgb(0, 0, 0)', 12)).toEqual({start: 17, end: 26});
expect(getParenthesesRange('rgb(0, var(--x, var(--y)), 0)')).toEqual({start: 3, end: 29});
expect(getParenthesesRange('rgb(0, var(--x, var(--y)), 0)', 4)).toEqual({start: 10, end: 25});
expect(getParenthesesRange('rgb(0, var(--x, var(--y)), 0), rgb(0, 0, 0)')).toEqual({start: 3, end: 29});
});
+128
View File
@@ -0,0 +1,128 @@
import {isInTimeIntervalLocal, nextTimeInterval, isNightAtLocation, nextTimeChangeAtLocation, parseTime, getDuration, getDurationInMinutes} from '../../../src/utils/time';
test('Time interval', () => {
// isInTimeIntervalLocal is time-zone dependent
expect(isInTimeIntervalLocal('9:00', '12:00', new Date(2018, 11, 4, 10))).toEqual(true);
expect(isInTimeIntervalLocal('10:00', '10:00', new Date(2018, 11, 4, 10))).toEqual(false);
expect(isInTimeIntervalLocal('10:00', '10:00', new Date(2018, 11, 4, 12))).toEqual(false);
expect(isInTimeIntervalLocal('10:00', '10:00', new Date(2018, 11, 4, 8))).toEqual(false);
expect(isInTimeIntervalLocal('9:00', '10:00', new Date(2018, 11, 4, 10))).toEqual(false);
expect(isInTimeIntervalLocal('9:01', '10:00', new Date(2018, 11, 4, 9, 2))).toEqual(true);
expect(isInTimeIntervalLocal('9:00', '10:01', new Date(2018, 11, 4, 10))).toEqual(true);
expect(isInTimeIntervalLocal('18:00', '12:00', new Date(2018, 11, 4, 10))).toEqual(true);
expect(isInTimeIntervalLocal('18:00', '9:00', new Date(2018, 11, 4, 10))).toEqual(false);
expect(isInTimeIntervalLocal('18:00', '9:00', new Date(2018, 11, 4, 22))).toEqual(true);
});
test('Time interval prediction', () => {
// nextTimeInterval() returns time-zone dependent timestamp
expect(nextTimeInterval('9:00', '12:00', new Date(2018, 11, 4, 10))).toEqual(new Date(2018, 11, 4, 12).getTime());
expect(nextTimeInterval('10:00', '10:00', new Date(2018, 11, 4, 10))).toEqual(null);
expect(nextTimeInterval('10:00', '10:00', new Date(2018, 11, 4, 12))).toEqual(null);
expect(nextTimeInterval('10:00', '10:00', new Date(2018, 11, 4, 8))).toEqual(null);
expect(nextTimeInterval('9:00', '10:00', new Date(2018, 11, 4, 10))).toEqual(new Date(2018, 11, 5, 9).getTime());
expect(nextTimeInterval('9:01', '10:00', new Date(2018, 11, 4, 9, 2))).toEqual(new Date(2018, 11, 4, 10).getTime());
expect(nextTimeInterval('9:00', '10:01', new Date(2018, 11, 4, 10))).toEqual(new Date(2018, 11, 4, 10, 1).getTime());
expect(nextTimeInterval('18:00', '12:00', new Date(2018, 11, 4, 10))).toEqual(new Date(2018, 11, 4, 12).getTime());
expect(nextTimeInterval('18:00', '9:00', new Date(2018, 11, 4, 10))).toEqual(new Date(2018, 11, 4, 18).getTime());
expect(nextTimeInterval('18:00', '9:00', new Date(2018, 11, 4, 22))).toEqual(new Date(2018, 11, 5, 9).getTime());
});
test('Time parse', () => {
expect(parseTime('10:30')).toEqual([10, 30]);
expect(parseTime('10:30AM')).toEqual([10, 30]);
expect(parseTime('10:30 a.m.')).toEqual([10, 30]);
expect(parseTime('10:30PM')).toEqual([22, 30]);
expect(parseTime('10:30 p.m.')).toEqual([22, 30]);
expect(parseTime('0:30')).toEqual([0, 30]);
expect(parseTime('12:30am')).toEqual([0, 30]);
expect(parseTime('12:30pm')).toEqual([12, 30]);
});
test('Duration', () => {
expect(getDuration({
seconds: 48,
minutes: 24,
hours: 8,
days: 3,
})).toEqual(289488000);
expect(getDurationInMinutes({
seconds: 27,
minutes: 25,
hours: 5,
days: 7,
})).toEqual(10405.45);
});
test('Nigth check', () => {
const utcDate = (y: number, m: number, d: number, hh: number, mm: number) => new Date(Date.UTC(y, m, d, hh, mm));
expect(isNightAtLocation(52, 0, utcDate(2019, 8, 9, 0, 0))).toEqual(true);
expect(isNightAtLocation(52, 0, utcDate(2019, 8, 9, 5, 0))).toEqual(true);
expect(isNightAtLocation(52, 0, utcDate(2019, 8, 9, 7, 0))).toEqual(false);
expect(isNightAtLocation(52, 0, utcDate(2019, 8, 9, 12, 0))).toEqual(false);
expect(isNightAtLocation(52, 0, utcDate(2019, 8, 9, 18, 0))).toEqual(false);
expect(isNightAtLocation(52, 0, utcDate(2019, 8, 9, 20, 0))).toEqual(true);
expect(isNightAtLocation(52, 0, utcDate(2019, 8, 9, 23, 59))).toEqual(true);
expect(isNightAtLocation(52, 30, utcDate(2019, 8, 9, 0, 0))).toEqual(true);
expect(isNightAtLocation(52, 30, utcDate(2019, 8, 9, 3, 0))).toEqual(true);
expect(isNightAtLocation(52, 30, utcDate(2019, 8, 9, 5, 0))).toEqual(false);
expect(isNightAtLocation(52, 30, utcDate(2019, 8, 9, 10, 0))).toEqual(false);
expect(isNightAtLocation(52, 30, utcDate(2019, 8, 9, 16, 0))).toEqual(false);
expect(isNightAtLocation(52, 30, utcDate(2019, 8, 9, 18, 0))).toEqual(true);
expect(isNightAtLocation(52, 30, utcDate(2019, 8, 9, 23, 59))).toEqual(true);
expect(isNightAtLocation(52, -30, utcDate(2019, 8, 9, 0, 0))).toEqual(true);
expect(isNightAtLocation(52, -30, utcDate(2019, 8, 9, 7, 0))).toEqual(true);
expect(isNightAtLocation(52, -30, utcDate(2019, 8, 9, 9, 0))).toEqual(false);
expect(isNightAtLocation(52, -30, utcDate(2019, 8, 9, 14, 0))).toEqual(false);
expect(isNightAtLocation(52, -30, utcDate(2019, 8, 9, 20, 0))).toEqual(false);
expect(isNightAtLocation(52, -30, utcDate(2019, 8, 9, 22, 0))).toEqual(true);
expect(isNightAtLocation(52, -30, utcDate(2019, 8, 9, 23, 59))).toEqual(true);
// Polar day and night
expect(isNightAtLocation(71, 0, utcDate(2019, 5, 15, 0, 0))).toEqual(false);
expect(isNightAtLocation(-71, 0, utcDate(2019, 5, 15, 0, 0))).toEqual(true);
// Places where sunset comes before sunrise (in UTC)
expect(isNightAtLocation(0, 180, utcDate(2019, 8, 9, 0, 0))).toEqual(false);
expect(isNightAtLocation(0, 180, utcDate(2019, 8, 9, 7, 0))).toEqual(true);
});
test('Sunrise/sunset', () => {
const utcDate = (y: number, m: number, d: number, hh: number, mm: number) => new Date(Date.UTC(y, m, d, hh, mm));
expect(nextTimeChangeAtLocation(52, 0, utcDate(2019, 8, 9, 0, 0))).toEqual(Date.UTC(2019, 8, 9, 5, 24, 2, 501));
expect(nextTimeChangeAtLocation(52, 0, utcDate(2019, 8, 9, 5, 0))).toEqual(Date.UTC(2019, 8, 9, 5, 24, 2, 501));
expect(nextTimeChangeAtLocation(52, 0, utcDate(2019, 8, 9, 7, 0))).toEqual(Date.UTC(2019, 8, 9, 18, 29, 46, 448));
expect(nextTimeChangeAtLocation(52, 0, utcDate(2019, 8, 9, 12, 0))).toEqual(Date.UTC(2019, 8, 9, 18, 29, 46, 448));
expect(nextTimeChangeAtLocation(52, 0, utcDate(2019, 8, 9, 18, 0))).toEqual(Date.UTC(2019, 8, 9, 18, 29, 46, 448));
expect(nextTimeChangeAtLocation(52, 0, utcDate(2019, 8, 9, 20, 0))).toEqual(Date.UTC(2019, 8, 10, 5, 24, 2, 501));
expect(nextTimeChangeAtLocation(52, 0, utcDate(2019, 8, 9, 23, 59))).toEqual(Date.UTC(2019, 8, 10, 5, 24, 2, 501));
expect(nextTimeChangeAtLocation(52, 30, utcDate(2019, 8, 9, 0, 0))).toEqual(Date.UTC(2019, 8, 9, 3, 23, 54, 365));
expect(nextTimeChangeAtLocation(52, 30, utcDate(2019, 8, 9, 3, 0))).toEqual(Date.UTC(2019, 8, 9, 3, 23, 54, 365));
expect(nextTimeChangeAtLocation(52, 30, utcDate(2019, 8, 9, 5, 0))).toEqual(Date.UTC(2019, 8, 9, 16, 29, 58, 45));
expect(nextTimeChangeAtLocation(52, 30, utcDate(2019, 8, 9, 10, 0))).toEqual(Date.UTC(2019, 8, 9, 16, 29, 58, 45));
expect(nextTimeChangeAtLocation(52, 30, utcDate(2019, 8, 9, 16, 0))).toEqual(Date.UTC(2019, 8, 9, 16, 29, 58, 45));
expect(nextTimeChangeAtLocation(52, 30, utcDate(2019, 8, 9, 18, 0))).toEqual(Date.UTC(2019, 8, 10, 3, 23, 54, 365));
expect(nextTimeChangeAtLocation(52, 30, utcDate(2019, 8, 9, 23, 59))).toEqual(Date.UTC(2019, 8, 10, 3, 23, 54, 365));
expect(nextTimeChangeAtLocation(52, -30, utcDate(2019, 8, 9, 0, 0))).toEqual(Date.UTC(2019, 8, 9, 7, 24, 10, 637));
expect(nextTimeChangeAtLocation(52, -30, utcDate(2019, 8, 9, 7, 0))).toEqual(Date.UTC(2019, 8, 9, 7, 24, 10, 637));
expect(nextTimeChangeAtLocation(52, -30, utcDate(2019, 8, 9, 9, 0))).toEqual(Date.UTC(2019, 8, 9, 20, 29, 34, 848));
expect(nextTimeChangeAtLocation(52, -30, utcDate(2019, 8, 9, 14, 0))).toEqual(Date.UTC(2019, 8, 9, 20, 29, 34, 848));
expect(nextTimeChangeAtLocation(52, -30, utcDate(2019, 8, 9, 20, 0))).toEqual(Date.UTC(2019, 8, 9, 20, 29, 34, 848));
expect(nextTimeChangeAtLocation(52, -30, utcDate(2019, 8, 9, 22, 0))).toEqual(Date.UTC(2019, 8, 10, 7, 24, 10, 637));
expect(nextTimeChangeAtLocation(52, -30, utcDate(2019, 8, 9, 23, 59))).toEqual(Date.UTC(2019, 8, 10, 7, 24, 10, 637));
// Polar day and night
expect(nextTimeChangeAtLocation(71, 0, utcDate(2019, 5, 15, 0, 0))).toEqual(Date.UTC(2019, 5, 16, 0, 0, 0, 0));
expect(nextTimeChangeAtLocation(-71, 0, utcDate(2019, 5, 15, 0, 0))).toEqual(Date.UTC(2019, 5, 16, 0, 0, 0, 0));
// Places where sunset comes before sunrise (in UTC)
expect(nextTimeChangeAtLocation(0, 180, utcDate(2019, 8, 9, 0, 0))).toEqual(Date.UTC(2019, 8, 9, 6, 0, 50, 152));
expect(nextTimeChangeAtLocation(0, 180, utcDate(2019, 8, 9, 7, 0))).toEqual(Date.UTC(2019, 8, 9, 17, 54, 18, 610));
});
+49
View File
@@ -0,0 +1,49 @@
import {randomFillSync} from 'crypto';
import {generateUID} from '../../../src/utils/uid';
test('Unique identifier generation (popyfilled)', () => {
if ('crypto' in globalThis) {
return;
}
// Make sure we are not messing up global context for somebody else
expect('crypto' in globalThis).toEqual(false);
// Create a shim for crypto API
// TODO: remove any cast once type declarations are updated
const shim1 = jest.fn();
globalThis.crypto = {
getRandomValues: (buffer: Uint8Array) => {
shim1();
return randomFillSync(buffer);
},
} as any;
const uid1 = generateUID();
expect(/^[a-f0-9]{32}$/.test(uid1)).toEqual(true);
expect(shim1).toHaveBeenCalled();
const shim2 = jest.fn();
globalThis.crypto = {
randomUUID: () => {
shim2();
return 'a19cc926-bf7f-4d5f-bf9c-202bc7a4c7c6';
},
} as any;
expect(generateUID()).toEqual('a19cc926bf7f4d5fbf9c202bc7a4c7c6');
expect(shim2).toHaveBeenCalled();
delete (globalThis as any).crypto;
});
test('Unique identifier generation (NodeJS 19+)', () => {
if (!('crypto' in globalThis)) {
return;
}
// Make sure crypto.randomUUID() is actually supported
expect('crypto' in globalThis && 'randomUUID' in crypto).toEqual(true);
const uid1 = generateUID();
expect(/^[a-f0-9]{32}$/.test(uid1)).toEqual(true);
});
+146
View File
@@ -0,0 +1,146 @@
import {indexURLTemplateList, isPDF, isURLInIndexedList, isURLMatched} from '../../../src/utils/url';
describe('Domain utilities', () => {
test('URL match', () => {
expect(isURLMatched('https://www.example.com/', '*')).toEqual(true);
expect(isURLMatched('https://www.example.com/', '*.*')).toEqual(true);
expect(isURLMatched('https://www.example.com/', '*.*.*')).toEqual(true);
expect(isURLMatched('https://www.example.com/', '*.*.*.*')).toEqual(false);
expect(isURLMatched('https://www.example.com/page/1', 'example.com')).toEqual(true);
expect(isURLMatched('https://www.failure.com/page/1', 'example.com')).toEqual(false);
expect(isURLMatched('https://xyz.example.com/page/1', 'example.com')).toEqual(false);
expect(isURLMatched('https://xyz.www.example.com/page/1', 'example.com')).toEqual(false);
expect(isURLMatched('https://xyz.example.com/page/1', '*.example.com')).toEqual(true);
expect(isURLMatched('https://abc.xyz.example.com/page/1', '*.example.com')).toEqual(true);
expect(isURLMatched('https://xyz.failure.com/page/1', '*.example.com')).toEqual(false);
expect(isURLMatched('https://www.example.com/page/1', 'example.com/page')).toEqual(true);
expect(isURLMatched('https://www.example.com/fail/1', 'example.com/page')).toEqual(false);
expect(isURLMatched('https://example.com/page/1', '^example.com')).toEqual(true);
expect(isURLMatched('https://www.example.com/page/1', '^example.com')).toEqual(false);
expect(isURLMatched('https://www.example.com/', 'example.com$')).toEqual(true);
expect(isURLMatched('https://www.example.com/page/1', 'example.com$')).toEqual(false);
expect(isURLMatched('https://www.example.de/', 'example.*')).toEqual(true);
expect(isURLMatched('https://www.failure.com/', 'example.*')).toEqual(false);
expect(isURLMatched('https://www.example.co.uk/', 'example.*.*')).toEqual(true);
expect(isURLMatched('https://www.example.com/', 'example.*.*')).toEqual(false);
expect(isURLMatched('https://www.example.com/path/long/enough', 'example.com/path/*')).toEqual(true);
expect(isURLMatched('https://www.failure.com/fail/long/enough', 'example.com/path/*')).toEqual(false);
expect(isURLMatched('http://localhost:8080/', 'localhost:8080')).toEqual(true);
expect(isURLMatched('http://localhost:1024/', 'localhost:8080')).toEqual(false);
expect(isURLMatched('http://localhost:8080/', 'localhost:*')).toEqual(true);
expect(isURLMatched('http://172.168.0.100:8080/', 'localhost:*')).toEqual(false);
expect(isURLMatched('http://www.example.com/page/1', 'http://*')).toEqual(true);
expect(isURLMatched('https://www.example.com/page/1', 'http://*')).toEqual(false);
expect(isURLMatched('file:///C:/My%20Documents/balance.xlsx', 'file:///C:')).toEqual(true);
expect(isURLMatched('file:///D:/Bin/cat.gif', 'file:///C:')).toEqual(false);
expect(isURLMatched('https://www.example.com/page/1', '/www\.ex.*\.com/')).toEqual(true);
expect(isURLMatched('https://www.failure.com/page/1', '/www\.ex.*\.com/')).toEqual(false);
expect(isURLMatched('https://[2001:0DB8:AC10:FE01::200E]/', '[2001:0DB8:AC10:FE01::200E]')).toEqual(true);
expect(isURLMatched('https://[2001:0DB8:AC10:FE02::200E]/', '[2001:0DB8:AC10:FE01::200E]')).toEqual(false);
expect(isURLMatched('https://[2001:0DB8:AC10:FE01::200E]:8080/', '[2001:0DB8:AC10:FE01::200E]:8080')).toEqual(true);
expect(isURLMatched('https://[2001:0DB8:AC10:FE02::200E]:1024/', '[2001:0DB8:AC10:FE01::200E]:8080')).toEqual(false);
});
test('URL is PDF', () => {
expect(isPDF('https://www.google.com/file.pdf')).toBe(true);
expect(isPDF('https://www.google.com/file.pdf?id=2')).toBe(true);
expect(isPDF('https://www.google.com/file.pdf/resource')).toBe(false);
expect(isPDF('https://www.google.com/resource?file=file.pdf')).toBe(false);
expect(isPDF('https://www.google.com/very/good/hidden/folder/pdf#file.pdf')).toBe(false);
expect(isPDF('https://fi.wikipedia.org/wiki/Tiedosto:ExtIPA_chart_(2015).pdf?uselang=en')).toBe(false);
expect(isPDF('https://commons.wikimedia.org/wiki/File:ExtIPA_chart_(2015).pdf')).toBe(false);
expect(isPDF('https://upload.wikimedia.org/wikipedia/commons/5/56/ExtIPA_chart_(2015).pdf')).toBe(true);
});
test('URL list index', () => {
const simplePatterns = [
'apple.com',
'google.com',
];
let indexed = indexURLTemplateList(simplePatterns);
expect(isURLInIndexedList('https://apple.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://www.apple.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://google.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://www.google.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://www.google.co.uk/', indexed)).toEqual(false);
expect(isURLInIndexedList('https://www.google.com/maps', indexed)).toEqual(true);
expect(isURLInIndexedList('https://mail.google.com/', indexed)).toEqual(false);
expect(isURLInIndexedList('https://www.example.com/', indexed)).toEqual(false);
const wildcardPatterns = [
'apple.com',
'google.*',
'*.example.com',
];
indexed = indexURLTemplateList(wildcardPatterns);
expect(isURLInIndexedList('https://apple.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://www.apple.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://google.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://www.google.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://www.google.co.uk/', indexed)).toEqual(false);
expect(isURLInIndexedList('https://www.google.com/maps', indexed)).toEqual(true);
expect(isURLInIndexedList('https://mail.google.com/', indexed)).toEqual(false);
expect(isURLInIndexedList('https://example.com/', indexed)).toEqual(false);
expect(isURLInIndexedList('https://www.example.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://test.example.com/', indexed)).toEqual(true);
const pathPatterns = [
'apple.com',
'google.*/maps',
];
indexed = indexURLTemplateList(pathPatterns);
expect(isURLInIndexedList('https://apple.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://www.apple.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://google.com/', indexed)).toEqual(false);
expect(isURLInIndexedList('https://www.google.com/', indexed)).toEqual(false);
expect(isURLInIndexedList('https://www.google.co.uk/', indexed)).toEqual(false);
expect(isURLInIndexedList('https://www.google.com/maps', indexed)).toEqual(true);
expect(isURLInIndexedList('https://www.google.com/maps/edit', indexed)).toEqual(true);
expect(isURLInIndexedList('https://www.google.co.uk/maps', indexed)).toEqual(false);
expect(isURLInIndexedList('https://www.google.com/mail', indexed)).toEqual(false);
expect(isURLInIndexedList('https://mail.google.com/', indexed)).toEqual(false);
expect(isURLInIndexedList('https://www.example.com/', indexed)).toEqual(false);
const mixedPatterns = [
'apple.com',
'google.*/maps',
'google.*.*/maps',
'*.example.com',
'office.com/*/edit',
'mail.google.*/mail',
];
indexed = indexURLTemplateList(mixedPatterns);
expect(isURLInIndexedList('https://apple.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://www.apple.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://google.com/', indexed)).toEqual(false);
expect(isURLInIndexedList('https://www.google.com/', indexed)).toEqual(false);
expect(isURLInIndexedList('https://www.google.co.uk/', indexed)).toEqual(false);
expect(isURLInIndexedList('https://www.google.com/maps', indexed)).toEqual(true);
expect(isURLInIndexedList('https://www.google.co.uk/maps', indexed)).toEqual(true);
expect(isURLInIndexedList('https://www.google.com/maps/edit', indexed)).toEqual(true);
expect(isURLInIndexedList('https://www.google.com/mail', indexed)).toEqual(false);
expect(isURLInIndexedList('https://mail.google.com/', indexed)).toEqual(false);
expect(isURLInIndexedList('https://mail.google.com/mail/u/0/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://example.com/', indexed)).toEqual(false);
expect(isURLInIndexedList('https://www.example.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://test.example.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://long.test.example.com/', indexed)).toEqual(true);
expect(isURLInIndexedList('https://www.office.com/excel/', indexed)).toEqual(false);
expect(isURLInIndexedList('https://www.office.com/excel/edit', indexed)).toEqual(true);
expect(isURLInIndexedList('https://www.office.com/excel/edit/2000', indexed)).toEqual(true);
});
});
@@ -0,0 +1,214 @@
import {DEFAULT_SETTINGS, DEFAULT_THEME} from '../../../src/defaults';
import type {Theme, UserSettings} from '../../../src/definitions';
import {ThemeEngine} from '../../../src/generators/theme-engines';
import {AutomationMode} from '../../../src/utils/automation';
import {validateSettings, validateTheme} from '../../../src/utils/validation';
test('Settings Validation', () => {
const defaultTheme = JSON.parse(JSON.stringify(DEFAULT_THEME));
let themeValidation = validateTheme(defaultTheme);
expect(themeValidation.errors).toEqual([]);
expect(defaultTheme).toEqual(DEFAULT_THEME);
const partTheme = {
brightness: 125,
contrast: -50,
};
themeValidation = validateTheme(partTheme as any);
expect(themeValidation.errors.length).toBe(1);
expect(partTheme).toEqual({
brightness: 125,
contrast: DEFAULT_THEME.contrast,
});
const wonkyTheme = {
mode: 'dark',
brightness: 250,
contrast: -50,
grayscale: false,
sepia: 105,
useFont: 'ok',
fontFamily: '',
textStroke: 2,
engine: 'dymanic',
stylesheet: 3,
darkSchemeBackgroundColor: 'red',
darkSchemeTextColor: '#abc12x',
lightSchemeBackgroundColor: '',
lightSchemeTextColor: '#ffffff00',
scrollbarColor: false,
selectionColor: 'green',
styleSystemControls: null as boolean | null,
lightColorScheme: '',
darkColorScheme: false,
immediateModify: 1,
};
themeValidation = validateTheme(wonkyTheme as any);
expect(themeValidation.errors.length).toBeGreaterThan(0);
expect(wonkyTheme as any).toEqual(DEFAULT_THEME);
const defaultSet = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
let validation = validateSettings(defaultSet);
expect(validation.errors).toEqual([]);
expect(defaultSet).toEqual(DEFAULT_SETTINGS);
const wonkySet = {
schemeVersion: 'x',
enabled: '',
fetchNews: null as boolean | null,
theme: {
mode: 'dark',
brightness: 250,
contrast: -50,
grayscale: false,
sepia: 105,
useFont: 'ok',
fontFamily: '',
textStroke: 2,
engine: 'dymanic',
stylesheet: 3,
darkSchemeBackgroundColor: 'red',
darkSchemeTextColor: '#abc12x',
lightSchemeBackgroundColor: '',
lightSchemeTextColor: '#ffffff00',
scrollbarColor: false,
selectionColor: 'green',
styleSystemControls: null as boolean | null,
lightColorScheme: '',
darkColorScheme: false,
immediateModify: 1,
},
presets: [
{id: '', name: 'P1', urls: ['a.com'], theme: {brightness: 100}},
{id: 'p2', name: '', urls: ['a.com'], theme: {brightness: 100}},
{id: 'p3', name: 'P3', urls: [], theme: {brightness: 100}},
{id: 'p4', name: 'P4', urls: ['a.com'], theme: {brightness: -50}},
{id: 'p5', name: 'P5', urls: ['a.com'], theme: {brightness: 100}},
{id: 'p6', urls: ['a.com'], theme: {brightness: 100}},
{id: 'p7', name: 'P7', urls: ['a.com'], theme: null},
null,
],
customThemes: [
{url: [] as any[], theme: {brightness: 100}},
{url: [''], theme: {brightness: 100}},
{url: ['a.com'], theme: {brightness: 100}},
{url: ['a.com'], theme: {brightness: -50}},
{url: ['a.com']},
{url: ['a.com'], theme: null},
null,
],
disabledFor: ['a.com', '', 'b.com'],
enabledFor: {0: 'a.com', 1: 'b.com'},
enabledByDefault: true,
changeBrowserTheme: 1,
syncSettings: null as boolean | null,
syncSitesFixes: 0,
automation: {
enabled: false,
behavior: 'OnOff',
mode: '',
},
time: {
activation: '10:00PM',
deactivation: '19:00',
},
location: {
latitude: 59,
longitude: '5.3',
},
previewNewDesign: '',
previewNewestDesign: 2,
enableForPDF: null as boolean | null,
enableForProtectedPages: 'ok',
enableContextMenus: 'yes',
detectDarkTheme: 'no',
};
validation = validateSettings(wonkySet as any);
expect(validation.errors.length).toBeGreaterThan(0);
expect(wonkySet as any).toEqual({
...DEFAULT_SETTINGS,
disabledFor: ['a.com', 'b.com'],
presets: [{id: 'p5', name: 'P5', urls: ['a.com'], theme: {brightness: 100}}],
customThemes: [{url: ['a.com'], theme: {brightness: 100}}],
});
const nullishSet = {
theme: null as any,
presets: null as any,
customThemes: null as any,
time: null as any,
location: null as any,
};
validation = validateSettings(nullishSet);
expect(validation.errors.length).toBe(5);
expect(nullishSet).toEqual({
theme: DEFAULT_THEME,
presets: DEFAULT_SETTINGS.presets,
customThemes: DEFAULT_SETTINGS.customThemes,
time: DEFAULT_SETTINGS.time,
location: DEFAULT_SETTINGS.location,
});
const validSet: UserSettings = {
schemeVersion: 2,
enabled: true,
fetchNews: true,
theme: {
mode: 0,
brightness: 125,
contrast: 50,
grayscale: 25,
sepia: 5,
useFont: true,
fontFamily: 'Comic Sans',
textStroke: 0.5,
engine: ThemeEngine.dynamicTheme,
stylesheet: '.x { color: red; }',
darkSchemeBackgroundColor: '#abcdef',
darkSchemeTextColor: '#abc123',
lightSchemeBackgroundColor: '#ff0000',
lightSchemeTextColor: '#ffffff',
scrollbarColor: '',
selectionColor: 'auto',
styleSystemControls: false,
lightColorScheme: 'Lightness',
darkColorScheme: 'Darkness',
immediateModify: true,
},
presets: [
{id: 'p5', name: 'P5', urls: ['a.com'], theme: {brightness: 100} as Theme},
],
customThemes: [
{url: ['a.com'], theme: {brightness: 100} as Theme},
],
disabledFor: ['a.com', 'b.com'],
enabledFor: ['c.com'],
enabledByDefault: false,
changeBrowserTheme: true,
syncSettings: false,
syncSitesFixes: true,
automation: {
enabled: true,
mode: AutomationMode.LOCATION,
behavior: 'OnOff',
},
time: {
activation: '18:00',
deactivation: '7:00',
},
location: {
latitude: 59,
longitude: 53,
},
previewNewDesign: true,
previewNewestDesign: false,
enableForPDF: false,
enableForProtectedPages: true,
enableContextMenus: true,
detectDarkTheme: true,
};
const validSetCopy = JSON.parse(JSON.stringify(validSet));
validation = validateSettings(validSet);
expect(validation.errors).toEqual([]);
expect(validSet).toEqual(validSetCopy);
});