test(app): remove useless frontend tests (#2819)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-17 15:05:36 +02:00
1 parent ed9528834d
commit deac653a9e
341 files changed
-62758

No files matched your search

@@ -1,225 +0,0 @@
// @vitest-environment happy-dom
// SPDX-License-Identifier: AGPL-3.0-or-later
import {readFileSync} from 'node:fs';
import {join} from 'node:path';
import {
hanChunkForLanguage,
noteLocale,
noteText,
requestedScriptChunks,
resetScriptFontLoaderForTests,
type ScriptChunk,
whenScriptChunksSettled,
} from '@app/features/theme/fonts/ScriptFontLoader';
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
const FONTS_CSS = join(process.cwd(), '..', 'packages', 'fonts', 'css');
function chunks(): Array<ScriptChunk> {
return [...requestedScriptChunks()].sort();
}
beforeEach(() => {
resetScriptFontLoaderForTests();
document.documentElement.lang = '';
});
afterEach(async () => {
await whenScriptChunksSettled();
});
describe('script font loading gate', () => {
it('requests nothing for text that is entirely Latin, which is the whole point of the split', () => {
noteText('Hello there, this is an ordinary English message with punctuation: 1, 2, 3!');
noteText('Ça va? Grüße! Añadir — naïve café résumé');
expect(chunks()).toEqual([]);
});
it('requests nothing for empty or absent text', () => {
noteText('');
noteText(null);
noteText(undefined);
expect(chunks()).toEqual([]);
});
it('requests the JP chunk on kana, which no other bundled family covers', () => {
noteText('こんにちは');
expect(chunks()).toEqual(['jp']);
});
it('requests the KR chunk on hangul', () => {
noteText('안녕하세요');
expect(chunks()).toEqual(['kr']);
});
it('requests only the family the cascade would select for Han, not all three', () => {
noteText('你好世界');
expect(chunks()).toEqual(['sc']);
});
it('breaks the Han tie with the document language, never with it deciding whether to load', () => {
document.documentElement.lang = 'ja';
noteText('漢字');
expect(chunks()).toEqual(['jp']);
resetScriptFontLoaderForTests();
document.documentElement.lang = 'zh-TW';
noteText('漢字');
expect(chunks()).toEqual(['tc']);
resetScriptFontLoaderForTests();
document.documentElement.lang = 'zh-CN';
noteText('汉字');
expect(chunks()).toEqual(['sc']);
});
it('serves mixed-script text from a Latin locale, which is what a locale gate would get wrong', () => {
document.documentElement.lang = 'en-US';
noteText('Hey ソラ, did 김민준 see 你好?');
expect(chunks()).toEqual(['jp', 'kr', 'sc']);
});
it('never withholds a chunk because of the locale: a Japanese UI still loads Korean text', () => {
document.documentElement.lang = 'ja';
noteText('안녕하세요');
expect(chunks()).toEqual(['kr']);
});
it('requests each chunk once, so a busy render path cannot storm the network', () => {
noteText('こんにちは');
const first = requestedScriptChunks().size;
for (let i = 0; i < 100; i += 1) noteText('こんにちは');
expect(requestedScriptChunks().size).toBe(first);
});
it('brings a CJK locale forward at activation without waiting for a codepoint', () => {
noteLocale('ko');
expect(chunks()).toEqual(['kr']);
resetScriptFontLoaderForTests();
noteLocale('en-US');
expect(chunks()).toEqual([]);
});
it('takes the tie-break on ideograph-adjacent text that is not itself an ideograph', () => {
for (const [label, text] of [
['iteration mark', '々'],
['ideographic zero', '〇'],
['kangxi radical', '⼀'],
['ideographic full stop', '。'],
['fullwidth exclamation', '!'],
['ideographic space', ' '],
] as const) {
resetScriptFontLoaderForTests();
document.documentElement.lang = 'en-US';
noteText(text);
expect(chunks(), `${label} announced no family`).toEqual(['sc']);
}
});
it('routes ideograph-adjacent text through the same tie-break as Han, not a fixed family', () => {
document.documentElement.lang = 'ja';
noteText('。');
expect(chunks()).toEqual(['jp']);
resetScriptFontLoaderForTests();
document.documentElement.lang = 'zh-TW';
noteText('()');
expect(chunks()).toEqual(['tc']);
});
it('keeps halfwidth katakana on JP alone rather than also taking the Han tie-break', () => {
document.documentElement.lang = 'en-US';
noteText('ソラ');
expect(chunks()).toEqual(['jp']);
});
it('still requests nothing for ordinary Latin punctuation, which shares no block with CJK', () => {
document.documentElement.lang = 'en-US';
noteText('!?()[]{}<>@#$%^&*-_=+/\\|~`"\'.,;: 「」');
resetScriptFontLoaderForTests();
noteText('!?()[]{}<>@#$%^&*-_=+/\\|~`"\'.,;: ');
expect(chunks()).toEqual([]);
});
it('resolves the Han tie-break the same way the base --font-sans chain orders the families', () => {
expect(hanChunkForLanguage('ja')).toBe('jp');
expect(hanChunkForLanguage('ja-JP')).toBe('jp');
expect(hanChunkForLanguage('zh-TW')).toBe('tc');
expect(hanChunkForLanguage('zh-HK')).toBe('tc');
expect(hanChunkForLanguage('zh-Hant')).toBe('tc');
expect(hanChunkForLanguage('zh-CN')).toBe('sc');
expect(hanChunkForLanguage('zh')).toBe('sc');
expect(hanChunkForLanguage('en-US')).toBe('sc');
expect(hanChunkForLanguage(null)).toBe('sc');
});
});
describe('the ingest points that feed the gate', () => {
const SRC = join(process.cwd(), 'src');
const INGEST: Array<[string, string]> = [
['features/guild/models/Guild.ts', 'guild names'],
['features/guild/models/GuildRole.ts', 'role names'],
['features/channel/models/Channel.ts', 'channel names'],
['features/channel/state/ChannelDisplayName.ts', 'computed channel display names'],
['features/user/utils/NicknameUtils.ts', 'display names and nicknames'],
['features/messaging/components/markdown/index.tsx', 'message content'],
];
for (const [path, what] of INGEST) {
it(`observes ${what} at ingest (${path})`, () => {
const source = readFileSync(join(SRC, path), 'utf8');
expect(source).toContain("from '@app/features/theme/fonts/ScriptFontLoader'");
expect(source).toMatch(/\bnoteText\(/);
});
}
});
describe('the cascade the gate is computed from', () => {
const variables = readFileSync(join(FONTS_CSS, 'variables.css'), 'utf8');
const fallbacks = readFileSync(join(FONTS_CSS, 'locale-fallbacks.css'), 'utf8');
it('names every gated family in the base --font-sans, or the gate could never select them', () => {
const chain = /--font-sans:([^;]*);/.exec(variables)?.[1] ?? '';
for (const family of ['Fluxer Sans SC', 'Fluxer Sans TC', 'Fluxer Sans JP', 'Fluxer Sans KR']) {
expect(chain).toContain(family);
}
});
it('orders the base chain SC before TC before JP, which is the no-signal Han tie-break', () => {
const chain = /--font-sans:([^;]*);/.exec(variables)?.[1] ?? '';
expect(chain.indexOf('Fluxer Sans SC')).toBeLessThan(chain.indexOf('Fluxer Sans TC'));
expect(chain.indexOf('Fluxer Sans TC')).toBeLessThan(chain.indexOf('Fluxer Sans JP'));
});
it('leaves kana to JP, so the Han tie-break cannot claim text that is only ever Japanese', () => {
for (const sheet of ['script-sc.css', 'script-tc.css']) {
const text = readFileSync(join(FONTS_CSS, sheet), 'utf8');
for (const [, value] of text.matchAll(/unicode-range:([^;]*);/g)) {
for (const part of value.split(',')) {
const [start, end] = part.trim().replace('U+', '').split('-');
const from = Number.parseInt(start, 16);
const to = end === undefined ? from : Number.parseInt(end, 16);
expect(from > 0x30ff || to < 0x3040, `${sheet} claims ${part.trim()}`).toBe(true);
}
}
}
});
it('hoists the family hanChunkForLanguage picks, for every locale rule that exists', () => {
const expected: Record<string, string> = {
ja: 'Fluxer Sans JP',
ko: 'Fluxer Sans KR',
'zh-CN': 'Fluxer Sans SC',
'zh-TW': 'Fluxer Sans TC',
};
for (const [tag, family] of Object.entries(expected)) {
const rule = new RegExp(`:root:lang\\(${tag}\\)[^{]*\\{([^}]*)\\}`).exec(fallbacks);
expect(rule, `no :root:lang(${tag}) rule in locale-fallbacks.css`).not.toBeNull();
const chain = rule![1];
const hoisted = /'(Fluxer Sans (?:SC|TC|JP|KR))'/.exec(chain)?.[1];
expect(hoisted, `:root:lang(${tag}) hoists the wrong family`).toBe(family);
}
});
});
@@ -1,36 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {getMessageLayoutCssVariables, MESSAGE_LAYOUT_SPEC} from '@app/features/theme/layout/MessageLayoutSpec';
import {describe, expect, it} from 'vitest';
function collectStringValues(value: unknown, acc: Array<string>): void {
if (typeof value === 'string') {
acc.push(value);
return;
}
if (value && typeof value === 'object') {
for (const nested of Object.values(value)) {
collectStringValues(nested, acc);
}
}
}
describe('MESSAGE_LAYOUT_SPEC', () => {
it('declares every length in rem so message layout scales with zoom', () => {
const values: Array<string> = [];
collectStringValues(MESSAGE_LAYOUT_SPEC, values);
expect(values.length).toBeGreaterThan(0);
for (const value of values) {
expect(value === '100%' || /^-?\d+(?:\.\d+)?rem$/.test(value)).toBe(true);
}
});
it('emits CSS variables free of raw px lengths', () => {
for (const value of Object.values(getMessageLayoutCssVariables())) {
if (value.startsWith('calc(') || value.startsWith('clamp(')) {
continue;
}
expect(value).not.toMatch(/\d+px/);
}
});
});
@@ -1,270 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {
clearRemScaleCache,
getRemScaleForDocument,
REM_BASE_PX,
remFromPx,
startRemScaleTracking,
} from '@app/features/theme/layout/RemFromPx';
import {describe, expect, it} from 'vitest';
interface StubDocumentHarness {
ownerDocument: Document;
setRootFontSize: (fontSize: string) => void;
getComputedStyleCallCount: () => number;
animationFrameRequestCount: () => number;
resizeListenerCount: () => number;
resolutionListenerCount: () => number;
watchedResolutionQueries: () => Array<string>;
dispatchResize: () => void;
setDevicePixelRatio: (devicePixelRatio: number) => void;
setInnerWidth: (innerWidth: number) => void;
setResolutionQueryMatches: (matches: boolean) => void;
dispatchResolutionChange: () => void;
}
function createStubDocumentHarness(initialRootFontSize: string): StubDocumentHarness {
let rootFontSize = initialRootFontSize;
let getComputedStyleCallCount = 0;
let animationFrameRequestCount = 0;
let resolutionQueryMatches = true;
const resizeListeners = new Set<() => void>();
const resolutionQueries = new Map<string, Set<() => void>>();
const documentElement = {} as HTMLElement;
const ownerWindow = {
devicePixelRatio: 1,
innerWidth: 1280,
getComputedStyle: () => {
getComputedStyleCallCount += 1;
return {fontSize: rootFontSize} as CSSStyleDeclaration;
},
requestAnimationFrame: () => {
animationFrameRequestCount += 1;
return 0;
},
cancelAnimationFrame: () => {},
addEventListener: (type: string, listener: () => void) => {
if (type !== 'resize') return;
resizeListeners.add(listener);
},
removeEventListener: (type: string, listener: () => void) => {
if (type !== 'resize') return;
resizeListeners.delete(listener);
},
matchMedia: (media: string) => {
const listeners = resolutionQueries.get(media) ?? new Set<() => void>();
resolutionQueries.set(media, listeners);
return {
media,
matches: resolutionQueryMatches,
addEventListener: (_type: string, listener: () => void) => listeners.add(listener),
removeEventListener: (_type: string, listener: () => void) => listeners.delete(listener),
} as unknown as MediaQueryList;
},
};
const ownerDocument = {documentElement, defaultView: ownerWindow} as unknown as Document;
return {
ownerDocument,
setRootFontSize: (fontSize) => {
rootFontSize = fontSize;
},
getComputedStyleCallCount: () => getComputedStyleCallCount,
animationFrameRequestCount: () => animationFrameRequestCount,
resizeListenerCount: () => resizeListeners.size,
resolutionListenerCount: () =>
[...resolutionQueries.values()].reduce((total, listeners) => total + listeners.size, 0),
watchedResolutionQueries: () =>
[...resolutionQueries.entries()].filter(([, listeners]) => listeners.size > 0).map(([media]) => media),
dispatchResize: () => {
for (const listener of [...resizeListeners]) listener();
},
setDevicePixelRatio: (devicePixelRatio) => {
ownerWindow.devicePixelRatio = devicePixelRatio;
},
setInnerWidth: (innerWidth) => {
ownerWindow.innerWidth = innerWidth;
},
setResolutionQueryMatches: (matches) => {
resolutionQueryMatches = matches;
},
dispatchResolutionChange: () => {
for (const listeners of [...resolutionQueries.values()]) {
for (const listener of [...listeners]) listener();
}
},
};
}
describe('remFromPx', () => {
it('uses a 16px base', () => {
expect(REM_BASE_PX).toBe(16);
});
it('converts whole pixel sizes to rem', () => {
expect(remFromPx(16)).toBe('1rem');
expect(remFromPx(40)).toBe('2.5rem');
expect(remFromPx(80)).toBe('5rem');
expect(remFromPx(0)).toBe('0rem');
});
it('converts fractional pixel sizes without trailing-zero noise', () => {
expect(remFromPx(10)).toBe('0.625rem');
expect(remFromPx(3.2)).toBe('0.2rem');
expect(remFromPx(4.8)).toBe('0.3rem');
expect(remFromPx(2.5)).toBe('0.15625rem');
});
it('preserves the sign of negative offsets', () => {
expect(remFromPx(-64.8)).toBe('-4.05rem');
});
it('always produces a value parseable as a rem length', () => {
for (const px of [12, 14, 18, 24, 36, 55, 105, 140, 120]) {
expect(remFromPx(px)).toMatch(/^-?\d+(?:\.\d+)?rem$/);
}
});
});
describe('getRemScaleForDocument', () => {
it('reports the root font size relative to the rem base', () => {
const defaultRoot = createStubDocumentHarness('16px');
const scaledRoot = createStubDocumentHarness('20px');
const zoomedRoot = createStubDocumentHarness('24px');
expect(getRemScaleForDocument(defaultRoot.ownerDocument)).toBe(1);
expect(getRemScaleForDocument(scaledRoot.ownerDocument)).toBe(1.25);
expect(getRemScaleForDocument(zoomedRoot.ownerDocument)).toBe(1.5);
});
it('reads the computed root font size once for repeated calls', () => {
const harness = createStubDocumentHarness('16px');
for (let call = 0; call < 200; call += 1) {
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1);
}
expect(harness.getComputedStyleCallCount()).toBe(1);
});
it('never schedules per-frame invalidation work', () => {
const harness = createStubDocumentHarness('16px');
getRemScaleForDocument(harness.ownerDocument);
getRemScaleForDocument(harness.ownerDocument);
expect(harness.animationFrameRequestCount()).toBe(0);
});
it('re-reads after a resize that changes the device pixel ratio', () => {
const harness = createStubDocumentHarness('16px');
startRemScaleTracking(harness.ownerDocument);
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1);
harness.setRootFontSize('20px');
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1);
harness.setDevicePixelRatio(1.25);
harness.dispatchResize();
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1.25);
expect(harness.watchedResolutionQueries()).toEqual(['(resolution: 1.25dppx)']);
});
it('re-reads after a resize that changes the viewport width', () => {
const harness = createStubDocumentHarness('16px');
startRemScaleTracking(harness.ownerDocument);
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1);
harness.setRootFontSize('20px');
harness.setInnerWidth(720);
harness.dispatchResize();
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1.25);
});
it('ignores resizes that change neither the device pixel ratio nor the viewport width', () => {
const harness = createStubDocumentHarness('16px');
startRemScaleTracking(harness.ownerDocument);
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1);
harness.setRootFontSize('20px');
for (let frame = 0; frame < 60; frame += 1) {
harness.dispatchResize();
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1);
}
expect(harness.getComputedStyleCallCount()).toBe(1);
});
it('re-reads after a browser zoom changes the device pixel ratio', () => {
const harness = createStubDocumentHarness('16px');
startRemScaleTracking(harness.ownerDocument);
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1);
expect(harness.watchedResolutionQueries()).toEqual(['(resolution: 1dppx)']);
harness.setRootFontSize('24px');
harness.setDevicePixelRatio(1.5);
harness.dispatchResolutionChange();
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1.5);
expect(harness.watchedResolutionQueries()).toEqual(['(resolution: 1.5dppx)']);
expect(harness.resolutionListenerCount()).toBe(1);
});
it('re-binds the resolution query from a resize when the query never matched', () => {
const harness = createStubDocumentHarness('16px');
harness.setResolutionQueryMatches(false);
startRemScaleTracking(harness.ownerDocument);
expect(harness.resolutionListenerCount()).toBe(0);
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1);
harness.setRootFontSize('20px');
harness.dispatchResize();
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1);
harness.setDevicePixelRatio(1.5);
harness.setResolutionQueryMatches(true);
harness.dispatchResize();
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1.25);
expect(harness.watchedResolutionQueries()).toEqual(['(resolution: 1.5dppx)']);
expect(harness.resolutionListenerCount()).toBe(1);
});
it('re-reads after an explicit cache clear', () => {
const harness = createStubDocumentHarness('16px');
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1);
harness.setRootFontSize('12px');
clearRemScaleCache(harness.ownerDocument);
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(0.75);
expect(harness.getComputedStyleCallCount()).toBe(2);
});
it('releases every listener when tracking is disposed', () => {
const harness = createStubDocumentHarness('16px');
const stopTracking = startRemScaleTracking(harness.ownerDocument);
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1);
expect(harness.resizeListenerCount()).toBe(1);
expect(harness.resolutionListenerCount()).toBe(1);
stopTracking();
expect(harness.resizeListenerCount()).toBe(0);
expect(harness.resolutionListenerCount()).toBe(0);
harness.setRootFontSize('20px');
expect(getRemScaleForDocument(harness.ownerDocument)).toBe(1.25);
expect(harness.resizeListenerCount()).toBe(0);
expect(harness.resolutionListenerCount()).toBe(0);
});
it('never registers listeners as a side effect of reading', () => {
const harness = createStubDocumentHarness('16px');
for (let call = 0; call < 5; call += 1) {
getRemScaleForDocument(harness.ownerDocument);
}
expect(harness.resizeListenerCount()).toBe(0);
expect(harness.resolutionListenerCount()).toBe(0);
});
it('invalidates every document when cleared without a target', () => {
const main = createStubDocumentHarness('16px');
const popout = createStubDocumentHarness('16px');
expect(getRemScaleForDocument(main.ownerDocument)).toBe(1);
expect(getRemScaleForDocument(popout.ownerDocument)).toBe(1);
main.setRootFontSize('20px');
popout.setRootFontSize('20px');
clearRemScaleCache();
expect(getRemScaleForDocument(main.ownerDocument)).toBe(1.25);
expect(getRemScaleForDocument(popout.ownerDocument)).toBe(1.25);
});
it('falls back to the rem base when the document has no view or no usable root font size', () => {
const detachedDocument = {documentElement: {} as HTMLElement, defaultView: null} as unknown as Document;
const brokenRoot = createStubDocumentHarness('not-a-length');
expect(getRemScaleForDocument(null)).toBe(1);
expect(getRemScaleForDocument(detachedDocument)).toBe(1);
expect(getRemScaleForDocument(brokenRoot.ownerDocument)).toBe(1);
});
});
@@ -1,58 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {readFileSync} from 'node:fs';
import {fileURLToPath} from 'node:url';
import {describe, expect, it} from 'vitest';
const MESSAGE_CSS = readFileSync(fileURLToPath(new URL('./Message.module.css', import.meta.url)), 'utf8');
const ISOLATED_NAMES = ['.messageUsername', '.repliedUsername'];
const ISOLATED_TIMESTAMPS = [
'.messageTimestamp',
'.messageTimestampCompact',
'.messageTimestampHover',
'.messageTimestampCompactHover',
];
function rules(css: string): Array<{selector: string; declarations: string}> {
const source = css.replace(/\/\*[\s\S]*?\*\//g, '');
const out: Array<{selector: string; declarations: string}> = [];
const chain: Array<string> = [];
let buffer = '';
for (const char of source) {
if (char === '{') {
chain.push(buffer.split(/[;}]/).pop()?.trim().replace(/\s+/g, ' ') ?? '');
buffer = '';
} else if (char === '}') {
if (chain.length > 0) {
out.push({selector: chain.join(' '), declarations: buffer});
chain.pop();
}
buffer = '';
} else {
buffer += char;
}
}
return out;
}
function declaredBidi(className: string): Array<string> {
const selectorPattern = new RegExp(`\\${className}(?![\\w-])`);
const out: Array<string> = [];
for (const rule of rules(MESSAGE_CSS)) {
if (!rule.selector.split(',').some((selector) => selectorPattern.test(selector))) continue;
const match = rule.declarations.match(/unicode-bidi\s*:\s*([^;]+)/);
if (match) out.push(match[1].trim());
}
return out;
}
describe('message bidi isolation', () => {
it.each(ISOLATED_NAMES)('resolves %s in its own bidi paragraph', (className) => {
expect(declaredBidi(className)).toContain('plaintext');
});
it.each(ISOLATED_TIMESTAMPS)('keeps %s out of a neighbouring right-to-left run', (className) => {
expect(declaredBidi(className)).toContain('isolate');
});
});
@@ -1,163 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {readdirSync, readFileSync} from 'node:fs';
import {join} from 'node:path';
import {fileURLToPath} from 'node:url';
import {describe, expect, it} from 'vitest';
const APP_SRC = fileURLToPath(new URL('../../../', import.meta.url));
const FORBIDDEN_SMOOTHING_PROPERTIES = ['-webkit-font-smoothing', 'font-smooth', 'text-rendering'];
const LAYER_PROMOTING_WILL_CHANGE = /transform|opacity|filter/;
const WILL_CHANGE = /will-change\s*:\s*([^;]*)/;
const ANIMATION_STATE_SELECTOR = /\[data-(dragging|state|animating)|:hover|:active|Animating|Animated|Dragging/;
const NON_TEXT_WILL_CHANGE_ALLOWLIST = new Set([
'features/channel/components/GifPicker.module.css .loadingSkeletonBlob',
'features/channel/components/GuildMembersPage.module.css .progressBar',
'features/channel/components/direct_message/DMChannelView.module.css .callParticipantAvatar',
'features/messaging/components/modals/MediaModal.module.css .desktopViewerContent',
'features/ui/components/form/FormSwitch.module.css .switchThumb',
'features/ui/scroller/ScrollerTrack.module.css .thumb',
'features/user/components/profile/UserProfileLoadingSkeleton.module.css .pulse',
'features/voice/components/LiveRecordingWaveform.module.css .track',
'features/voice/components/PiPOverlay.module.css .container',
]);
function collectFiles(directory: string, extension: string, out: Array<string> = []): Array<string> {
for (const entry of readdirSync(directory, {withFileTypes: true})) {
const full = join(directory, entry.name);
if (entry.isDirectory()) {
collectFiles(full, extension, out);
} else if (entry.name.endsWith(extension)) {
out.push(full);
}
}
return out;
}
function rules(css: string): Array<{selector: string; declarations: string}> {
const source = css.replace(/\/\*[\s\S]*?\*\//g, '');
const out: Array<{selector: string; declarations: string}> = [];
const chain: Array<string> = [];
let buffer = '';
for (const char of source) {
if (char === '{') {
chain.push(buffer.split(/[;}]/).pop()?.trim().replace(/\s+/g, ' ') ?? '');
buffer = '';
} else if (char === '}') {
if (chain.length > 0) {
out.push({selector: chain.join(' '), declarations: buffer});
chain.pop();
}
buffer = '';
} else {
buffer += char;
}
}
return out;
}
function relative(file: string): string {
return file.slice(APP_SRC.length);
}
describe('text rendering', () => {
const stylesheets = collectFiles(APP_SRC, '.css');
it('finds the authored stylesheets it is meant to guard', () => {
expect(stylesheets.length).toBeGreaterThan(100);
});
it('never overrides font smoothing, which would disable ClearType on Windows', () => {
const offenders: Array<string> = [];
for (const file of stylesheets) {
const css = readFileSync(file, 'utf8');
for (const property of FORBIDDEN_SMOOTHING_PROPERTIES) {
if (css.includes(property)) {
offenders.push(`${relative(file)}: ${property}`);
}
}
}
expect(offenders).toEqual([]);
});
it('only allows a layer-promoting will-change when it is animation-scoped or a reviewed non-text rule', () => {
const offenders: Array<string> = [];
for (const file of stylesheets) {
for (const {selector, declarations} of rules(readFileSync(file, 'utf8'))) {
const willChange = WILL_CHANGE.exec(declarations);
if (willChange == null) continue;
if (!LAYER_PROMOTING_WILL_CHANGE.test(willChange[1]!)) continue;
if (ANIMATION_STATE_SELECTOR.test(selector)) continue;
if (NON_TEXT_WILL_CHANGE_ALLOWLIST.has(`${relative(file)} ${selector}`)) continue;
offenders.push(`${relative(file)} ${selector}: will-change:${willChange[1]!.trim()}`);
}
}
expect(offenders).toEqual([]);
});
it('never promotes a layer with an inline willChange style, which the stylesheet guard cannot see', () => {
const offenders: Array<string> = [];
for (const file of collectFiles(APP_SRC, '.tsx')) {
if (readFileSync(file, 'utf8').includes('willChange')) {
offenders.push(relative(file));
}
}
expect(offenders).toEqual([]);
});
});
describe('popout text rendering', () => {
it('shadows popouts with box-shadow, since any filter disables subpixel antialiasing', () => {
const css = readFileSync(join(APP_SRC, 'features/ui/popover/PopoverPopout.module.css'), 'utf8');
expect(css).toContain('box-shadow: 0 0.5rem 1rem var(--popout-shadow);');
expect(css).not.toMatch(/(^|[\s;])filter\s*:/m);
expect(css).not.toContain('popoutStableText');
});
});
const FRACTIONAL_OPACITY = /(^|[;\s])opacity\s*:\s*(0?\.\d+)\s*(;|$)/;
const TEXT_DECLARATION =
/(^|[;\s])(color|font-size|font-weight|font-family|font-style|line-height|letter-spacing|text-transform|text-overflow|white-space)\s*:/;
const TRANSIENT_STATE_SELECTOR =
/:hover|:focus|:active|:disabled|::before|::after|\[data-|%|\bfrom\b|\bto\b|Animating|Animated|Dragging|Hover|Pressed|Selected/;
const PERSISTENT_TEXT_OPACITY_ALLOWLIST = new Set([
'features/channel/components/ChannelDivider.module.css .unreadBadge',
'features/theme/styles/AttachmentFile.module.css .statusBadge',
'features/app/components/layout/sidebar_nav/GuildListDMItem.module.css .muted',
'features/app/components/layout/NativeTitlebar.module.css .left',
'features/app/components/layout/ChannelItem.module.css .channelItemMutedState',
'features/app/components/layout/FavoritesChannelListContent.module.css .favoriteItemMuted',
'features/channel/components/direct_message/DirectMessageList.module.css .dmItemMuted',
'features/channel/components/direct_message/DirectMessageList.module.css .dmItemMobileMuted',
'features/ui/action_menu/ContextMenu.module.css .item.disabled',
'features/ui/action_menu/MenuItem.module.css .sliderItem.disabled',
'features/app/components/dialogs/Modal.module.css .headerInner button',
'features/channel/components/active_now/ActiveNowSidebar.module.css .emptyIcon',
'features/channel/components/shared/MemberListUnavailableFallback.module.css .icon',
'features/messaging/components/markdown/renderers/MessageJumpLink.module.css .divider',
'features/search/components/search/SearchFilterChip.module.css .removeButton',
'features/theme/styles/AttachmentGridItem.module.css .audioPlaceholder svg',
'features/theme/styles/AttachmentMosaic.module.css .audioPlaceholder svg',
'features/user/components/modals/UserProfileModal.module.css .emptyStateIcon',
]);
describe('persistent text opacity', () => {
it('dims text through colour, not through a layer opacity that would kill subpixel antialiasing', () => {
const offenders: Array<string> = [];
for (const file of collectFiles(APP_SRC, '.module.css')) {
for (const {selector, declarations} of rules(readFileSync(file, 'utf8'))) {
if (!FRACTIONAL_OPACITY.test(declarations)) continue;
if (!TEXT_DECLARATION.test(declarations)) continue;
if (TRANSIENT_STATE_SELECTOR.test(selector)) continue;
const key = `${relative(file)} ${selector}`;
if (PERSISTENT_TEXT_OPACITY_ALLOWLIST.has(key)) continue;
offenders.push(key);
}
}
expect(offenders).toEqual([]);
});
});
@@ -1,63 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {getActiveHolidaySlugs, millisecondsUntilNextLocalMidnight} from '@app/features/theme/utils/HolidayClassUtils';
import {describe, expect, it} from 'vitest';
describe('getActiveHolidaySlugs', () => {
it('fires christmas + christmastide on Dec 25', () => {
const slugs = getActiveHolidaySlugs(new Date(2026, 11, 25));
expect(slugs).toContain('christmas');
expect(slugs).toContain('christmastide');
});
it('fires both boxing-day and christmastide on Dec 26', () => {
const slugs = getActiveHolidaySlugs(new Date(2026, 11, 26));
expect(slugs).toContain('boxing-day');
expect(slugs).toContain('christmastide');
expect(slugs).not.toContain('christmas');
});
it('co-fires india-independence-day and korea-liberation-day on Aug 15', () => {
const slugs = getActiveHolidaySlugs(new Date(2026, 7, 15));
expect(slugs).toContain('india-independence-day');
expect(slugs).toContain('korea-liberation-day');
});
it('fires pride-month every day in June', () => {
expect(getActiveHolidaySlugs(new Date(2026, 5, 1))).toContain('pride-month');
expect(getActiveHolidaySlugs(new Date(2026, 5, 15))).toContain('pride-month');
expect(getActiveHolidaySlugs(new Date(2026, 5, 30))).toContain('pride-month');
expect(getActiveHolidaySlugs(new Date(2026, 4, 31))).not.toContain('pride-month');
expect(getActiveHolidaySlugs(new Date(2026, 6, 1))).not.toContain('pride-month');
});
it('co-fires stonewall-day with pride-month on Jun 28', () => {
const slugs = getActiveHolidaySlugs(new Date(2026, 5, 28));
expect(slugs).toContain('stonewall-day');
expect(slugs).toContain('pride-month');
});
it('fires juneteenth on Jun 19', () => {
expect(getActiveHolidaySlugs(new Date(2026, 5, 19))).toContain('juneteenth');
});
it('fires back-to-the-future-day on Oct 21', () => {
expect(getActiveHolidaySlugs(new Date(2026, 9, 21))).toContain('back-to-the-future-day');
});
it('returns empty on a quiet day', () => {
expect(getActiveHolidaySlugs(new Date(2026, 1, 17))).toEqual([]);
});
it('handles leap day without crashing', () => {
expect(() => getActiveHolidaySlugs(new Date(2028, 1, 29))).not.toThrow();
expect(getActiveHolidaySlugs(new Date(2028, 1, 29))).toEqual([]);
});
});
describe('millisecondsUntilNextLocalMidnight', () => {
it('returns the gap to next local midnight', () => {
const now = new Date(2026, 5, 15, 23, 59, 59, 0);
const ms = millisecondsUntilNextLocalMidnight(now);
expect(ms).toBeGreaterThanOrEqual(1000);
expect(ms).toBeLessThanOrEqual(1000 + 999);
});
it('returns close to 24h at the start of a day', () => {
const now = new Date(2026, 5, 15, 0, 0, 1, 0);
const ms = millisecondsUntilNextLocalMidnight(now);
expect(ms).toBeGreaterThan(23 * 60 * 60 * 1000);
expect(ms).toBeLessThanOrEqual(24 * 60 * 60 * 1000);
});
});