mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 16:13:15 +00:00
171 lines
7.9 KiB
TypeScript
171 lines
7.9 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { computeFitScale } from '@/lib/email-fit-width';
|
|
import { applyMailTextSize, mailFitOptions, mailFrameCss, mailOverrideActive, mailOverrideCss, mailTextFactor, plainBodyFontSize, resolveSystemToggle, scaledTextSize } from '@/lib/mail-reading';
|
|
|
|
describe('mailTextFactor', () => {
|
|
it('follows the app text size when the mail size is 0', () => {
|
|
expect(mailTextFactor(0, 'medium')).toBe(1);
|
|
expect(mailTextFactor(0, 'xx-large')).toBe(1.5);
|
|
});
|
|
it('uses the mail size when one is set', () => {
|
|
expect(mailTextFactor(1.25, 'huge')).toBe(1.25);
|
|
});
|
|
});
|
|
|
|
describe('plainBodyFontSize', () => {
|
|
it('stays at the old size by default', () => {
|
|
expect(plainBodyFontSize(14, 0, 'medium', 0)).toBe('0.875rem');
|
|
});
|
|
it('relies on rem for the app size and multiplies only what the mail size adds', () => {
|
|
// rem already carries 1.25, so 1.5 needs another 1.2.
|
|
expect(plainBodyFontSize(14, 1.5, 'x-large', 0)).toBe('calc(0.875rem * 1.2)');
|
|
expect(plainBodyFontSize(14, 0, 'x-large', 0)).toBe('0.875rem');
|
|
});
|
|
it('puts a floor under it', () => {
|
|
expect(plainBodyFontSize(13, 0, 'medium', 16)).toBe('max(0.8125rem, 16px)');
|
|
});
|
|
});
|
|
|
|
describe('scaledTextSize', () => {
|
|
it('scales font and px line height together', () => {
|
|
expect(scaledTextSize(10, 15, { factor: 1.5, minPx: 0 })).toEqual({ font: 15, lineHeight: 22.5 });
|
|
});
|
|
it('raises text below the minimum, with its line height', () => {
|
|
expect(scaledTextSize(9, 12, { factor: 1, minPx: 18 })).toEqual({ font: 18, lineHeight: 24 });
|
|
expect(scaledTextSize(20, null, { factor: 1, minPx: 18 })).toEqual({ font: 20, lineHeight: null });
|
|
});
|
|
it('applies the minimum after scaling', () => {
|
|
expect(scaledTextSize(10, null, { factor: 2, minPx: 16 }).font).toBe(20);
|
|
expect(scaledTextSize(6, null, { factor: 2, minPx: 16 }).font).toBe(16);
|
|
});
|
|
});
|
|
|
|
function mailDoc(html: string): Document {
|
|
// A frame's document, so it has a window for getComputedStyle.
|
|
const frame = document.createElement('iframe');
|
|
document.body.appendChild(frame);
|
|
const doc = frame.contentDocument!;
|
|
doc.body.innerHTML = html;
|
|
return doc;
|
|
}
|
|
|
|
describe('applyMailTextSize', () => {
|
|
it('leaves a mail alone at 100 % with no minimum', () => {
|
|
const doc = mailDoc('<p style="font-size: 10px">tiny</p>');
|
|
expect(applyMailTextSize(doc, { factor: 1, minPx: 0 })).toBe(false);
|
|
expect(doc.querySelector('p')!.style.fontSize).toBe('10px');
|
|
});
|
|
|
|
it('enlarges small text to the minimum and pins the rest', () => {
|
|
const doc = mailDoc('<div style="font-size: 20px"><span style="font-size: 9px">fine print</span></div>');
|
|
doc.body.style.fontSize = '14px';
|
|
expect(applyMailTextSize(doc, { factor: 1, minPx: 12 })).toBe(true);
|
|
expect(doc.querySelector('span')!.style.getPropertyValue('font-size')).toBe('12px');
|
|
expect(doc.querySelector('span')!.style.getPropertyPriority('font-size')).toBe('important');
|
|
expect(doc.querySelector('div')!.style.getPropertyValue('font-size')).toBe('20px');
|
|
});
|
|
|
|
it('scales every text size and runs once per document', () => {
|
|
const doc = mailDoc('<p style="font-size: 16px; line-height: 20px">a</p>');
|
|
doc.body.style.fontSize = '14px';
|
|
applyMailTextSize(doc, { factor: 1.5, minPx: 0 });
|
|
const p = doc.querySelector('p')!;
|
|
expect(p.style.getPropertyValue('font-size')).toBe('24px');
|
|
expect(p.style.getPropertyValue('line-height')).toBe('30px');
|
|
expect(doc.body.style.getPropertyValue('font-size')).toBe('21px');
|
|
expect(applyMailTextSize(doc, { factor: 1.5, minPx: 0 })).toBe(false);
|
|
expect(p.style.getPropertyValue('font-size')).toBe('24px');
|
|
});
|
|
});
|
|
|
|
describe('resolveSystemToggle', () => {
|
|
it('follows the OS only for system', () => {
|
|
expect(resolveSystemToggle('system', true)).toBe(true);
|
|
expect(resolveSystemToggle('system', false)).toBe(false);
|
|
expect(resolveSystemToggle('on', false)).toBe(true);
|
|
expect(resolveSystemToggle('off', true)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('mailFrameCss', () => {
|
|
it('is empty when nothing is asked for', () => {
|
|
expect(mailFrameCss({ reduceMotion: false })).toBe('');
|
|
});
|
|
it('stops animations and transitions for reduce motion', () => {
|
|
expect(mailFrameCss({ reduceMotion: true })).toMatch(/animation-duration:0\.01ms!important.*transition-duration:0\.01ms!important/);
|
|
});
|
|
});
|
|
|
|
describe('mailOverrideActive', () => {
|
|
it('always, never, or only with more contrast', () => {
|
|
expect(mailOverrideActive('always', 'normal', false)).toBe(true);
|
|
expect(mailOverrideActive('never', 'more', true)).toBe(false);
|
|
expect(mailOverrideActive('high-contrast', 'more', false)).toBe(true);
|
|
expect(mailOverrideActive('high-contrast', 'system', true)).toBe(true);
|
|
expect(mailOverrideActive('high-contrast', 'system', false)).toBe(false);
|
|
expect(mailOverrideActive('high-contrast', 'normal', true)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('mailOverrideCss', () => {
|
|
const base = {
|
|
fontFamily: '"Lexend", sans-serif', foreground: '#111111', background: '#fafafa', link: '#1d4ed8',
|
|
lineSpacing: 'normal', letterSpacing: 'normal', wordSpacing: 'normal', paragraphSpacing: 'normal',
|
|
textFactor: 1.25, minPx: 0,
|
|
} as const;
|
|
|
|
it('puts the app font, size and colours on the mail', () => {
|
|
const css = mailOverrideCss(base);
|
|
expect(css).toContain('html,body{background:#fafafa!important;color:#111111!important}');
|
|
expect(css).toContain('font-family:"Lexend", sans-serif!important;font-size:calc(0.875rem * 1.25)!important');
|
|
expect(css).toMatch(/body \*:not\([^)]*\)\{font-family:inherit!important;font-size:1em!important[^}]*color:#111111!important;background-color:transparent!important;background-image:none!important/);
|
|
expect(css).toContain('a[href],a[href] *{color:#1d4ed8!important}');
|
|
expect(css).toContain('a[href]{text-decoration-line:underline!important}');
|
|
expect(css).not.toContain('letter-spacing');
|
|
});
|
|
|
|
it('carries the spacing settings and the minimum size', () => {
|
|
const css = mailOverrideCss({ ...base, lineSpacing: 'loose', letterSpacing: 'wider', wordSpacing: 'wide', paragraphSpacing: 'wide', minPx: 18 });
|
|
expect(css).toContain('line-height:1.9!important');
|
|
expect(css).toContain('letter-spacing:0.12em!important');
|
|
expect(css).toContain('word-spacing:0.16em!important');
|
|
expect(css).toContain('body p{margin-block-end:1.5em!important}');
|
|
expect(css).toContain('font-size:max(calc(0.875rem * 1.25), 18px)!important');
|
|
});
|
|
|
|
it('cannot be broken out of by a hostile value', () => {
|
|
const css = mailOverrideCss({ ...base, fontFamily: 'x}</style><img src=x>{' });
|
|
expect(css).not.toContain('</style>');
|
|
expect(css).not.toMatch(/font-family:x\}/);
|
|
});
|
|
});
|
|
|
|
describe('mailFrameCss underline', () => {
|
|
it('underlines links when asked', () => {
|
|
expect(mailFrameCss({ reduceMotion: false, underlineLinks: true })).toBe('a[href]{text-decoration-line:underline!important}');
|
|
});
|
|
});
|
|
|
|
describe('applyMailTextSize with the override stylesheet', () => {
|
|
it('leaves sizes to the stylesheet and only takes out justified text', () => {
|
|
const doc = mailDoc('<p style="font-size: 9px; text-align: justify">x</p>');
|
|
applyMailTextSize(doc, { factor: 2, minPx: 16, unjustify: true, sizesFromCss: true });
|
|
const p = doc.querySelector('p')!;
|
|
expect(p.style.getPropertyValue('font-size')).toBe('9px');
|
|
expect(p.style.getPropertyValue('text-align')).toBe('start');
|
|
});
|
|
});
|
|
|
|
describe('mailFitOptions', () => {
|
|
it('fits only on phones by default', () => {
|
|
expect(computeFitScale(1200, 800, mailFitOptions(false, false))).toBe(1);
|
|
expect(mailFitOptions(false, false).enabled).toBe(false);
|
|
expect(mailFitOptions(true, false).enabled).toBe(true);
|
|
});
|
|
it('fits a wide mail into a desktop pane with "Fit wide mail" on', () => {
|
|
const options = mailFitOptions(false, true);
|
|
expect(options.enabled).toBe(true);
|
|
expect(computeFitScale(1200, 800, options)).toBeCloseTo(2 / 3);
|
|
});
|
|
});
|