From 4a69a7df8a629cb7f4abd5f5ba35dd9b5a46d9b8 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sat, 17 Oct 2026 00:58:27 +0200 Subject: [PATCH] test: darken cells coloured with bgcolor in dark mode #693 --- lib/__tests__/email-dark-mode.test.ts | 69 +++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/lib/__tests__/email-dark-mode.test.ts b/lib/__tests__/email-dark-mode.test.ts index 52b6dd28c..298084a9f 100644 --- a/lib/__tests__/email-dark-mode.test.ts +++ b/lib/__tests__/email-dark-mode.test.ts @@ -163,3 +163,72 @@ describe('applyEmailDarkMode', () => { expect(doc.documentElement.classList.contains(DARK_PENDING_CLASS)).toBe(false); }); }); + +// #693: cells coloured through the legacy bgcolor attribute stayed white in +// dark mode. Browsers map bgcolor to background-color as a presentational hint +// (below every author style), which the pass reads back through computed +// style. jsdom has no presentational hints, so the helper below adds the same +// mapping as zero-specificity rules at the top of the cascade. +describe('applyEmailDarkMode with bgcolor', () => { + const mapBgcolorLikeBrowser = (doc: Document) => { + const rules: string[] = []; + doc.querySelectorAll('[bgcolor]').forEach((el, i) => { + el.setAttribute('data-bgcolor-hint', String(i)); + rules.push(`:where([data-bgcolor-hint="${i}"]) { background-color: ${el.getAttribute('bgcolor')}; }`); + }); + const sheet = doc.createElement('style'); + sheet.textContent = rules.join('\n'); + doc.head.prepend(sheet); + }; + + const render = (mail: string, bodyBgcolor?: string) => { + const { html } = sanitizeEmailBodyForIframe(mail, true); + const frame = document.createElement('iframe'); + document.body.appendChild(frame); + const doc = frame.contentDocument!; + doc.documentElement.className = DARK_PENDING_CLASS; + doc.documentElement.style.backgroundColor = 'rgb(20, 20, 20)'; + if (bodyBgcolor) doc.body.setAttribute('bgcolor', bodyBgcolor); + doc.body.innerHTML = html; + mapBgcolorLikeBrowser(doc); + applyEmailDarkMode(doc); + return doc; + }; + + // The darkened colour the pass wrote over the sender's one. + const rewrittenBg = (el: HTMLElement) => { + expect(el.style.getPropertyPriority('background-color')).toBe('important'); + return c(el.ownerDocument.defaultView!.getComputedStyle(el).backgroundColor); + }; + + it.each([ + ['td', '

Hello

'], + ['tr', '

Hello

'], + ['table', '

Hello

'], + ['td with a named colour', '

Hello

'], + ['td with a light tint', '

Hello

'], + ])('darkens a light bgcolor on %s and keeps its text readable', (_, mail) => { + const doc = render(mail); + const bg = rewrittenBg(doc.getElementById('x')!); + expect(lightness(bg)).toBeLessThan(40); + const text = c(doc.defaultView!.getComputedStyle(doc.getElementById('p')!).color); + expect(contrastRatio(text, bg)).toBeGreaterThan(4.5); + }); + + it('darkens the inline background when a cell has both', () => { + const doc = render('
Hello
'); + expect(lightness(rewrittenBg(doc.getElementById('x')!))).toBeLessThan(40); + }); + + it('keeps a vivid bgcolor as sent', () => { + const doc = render('
Sale
'); + const cell = doc.getElementById('x')!; + expect(c(doc.defaultView!.getComputedStyle(cell).backgroundColor)).toEqual(c('rgb(227, 6, 19)')); + expect(cell.style.getPropertyValue('background-color')).toBe(''); + }); + + it('darkens a white body bgcolor', () => { + const doc = render('

Hello

', '#ffffff'); + expect(lightness(rewrittenBg(doc.body))).toBeLessThan(40); + }); +});