Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions assets/scss/_styles_project.scss
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,21 @@
}
}

// The header uses white controls on purple, but the language popover uses
// OINK's theme-colored surface. Keep its text independent of the header and
// let the outer utility provide the trigger's hover background.
.td-home > .td-site-header,
.td-shell-chrome .td-site-header {
.td-site-nav__language > ul {
color: var(--bs-body-color);
}

.td-site-nav__language > .td-language-selector__trigger:hover,
.td-site-nav__language > .td-language-selector__trigger:focus-visible {
background: transparent;
}
}

// The desktop Docs sidebar deliberately overlays the navbar's left edge. Give
// its identity row the same band treatment so the two structural regions read
// as one header, while the drawer keeps its light mobile surface.
Expand Down
53 changes: 53 additions & 0 deletions tests/e2e/theme-upgrade.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,59 @@ const expectedVersions = (process.env.EXPECTED_VERSIONS || 'latest,1.7,1.5,1.3,1

const appearanceTrigger = '[data-td-appearance-trigger][aria-controls="td-appearance-navbar"]';

// The project uses a white-on-purple navbar, while OINK's language popover
// uses the current theme's surface. Check the rendered colors rather than
// tying this regression to a particular selector or palette.
for (const [route, language] of [['/', 'zh-CN'], ['/cn/', 'en-US'], ['/docs/', 'zh-CN'], ['/cn/docs/', 'en-US']]) {
for (const theme of ['light', 'dark']) {
test(`language menu remains legible on ${route} in ${theme} mode`, async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
await page.addInitScript(theme => {
localStorage.setItem('td-color-theme', theme);
localStorage.setItem('hg-language', 'en');
}, theme);
await page.goto(route);
await expect(page.locator('html')).toHaveAttribute('data-bs-theme', theme);
const menu = page.locator('.td-site-header .td-site-nav__language');
const trigger = menu.locator('.td-language-selector__trigger');
const list = menu.locator('ul');
const alternate = list.locator(`a[hreflang="${language}"]`);

// Contrast is calculated against the menu's opaque surface, including
// the current item and the link which previously inherited white.
const contrast = async item => item.evaluate(node => {
const rgb = value => value.match(/[\d.]+/g).slice(0, 3).map(Number);
const luminance = value => rgb(value).map(channel => {
channel /= 255;
return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
}).reduce((sum, channel, index) => sum + channel * [0.2126, 0.7152, 0.0722][index], 0);
const background = getComputedStyle(node).backgroundColor === 'rgba(0, 0, 0, 0)'
? getComputedStyle(node.closest('ul')).backgroundColor : getComputedStyle(node).backgroundColor;
const a = luminance(getComputedStyle(node).color), b = luminance(background);
return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
});
await trigger.hover();
await expect(list).toBeVisible();
await expect.poll(() => contrast(alternate)).toBeGreaterThanOrEqual(4.5);
await expect.poll(() => contrast(list.locator('.active'))).toBeGreaterThanOrEqual(4.5);
// OINK's opaque light hover background previously concealed the white
// icon. The trigger must leave the branded navbar visible underneath.
await expect.poll(() => trigger.evaluate(node => getComputedStyle(node).backgroundColor)).toBe('rgba(0, 0, 0, 0)');

await page.mouse.move(0, 500);
await page.keyboard.press('Tab');
await trigger.focus();
await expect(trigger).toBeFocused();
await expect(list).toBeVisible();
await expect.poll(() => contrast(alternate)).toBeGreaterThanOrEqual(4.5);
await expect.poll(() => trigger.evaluate(node => getComputedStyle(node).backgroundColor)).toBe('rgba(0, 0, 0, 0)');
const target = route.startsWith('/cn/') ? route.slice(3) : `/cn${route}`;
await alternate.click();
await expect(page).toHaveURL(url => url.pathname === target);
});
}
}

// These exercise the adopted components on real documentation, including
// their original content and destinations, rather than a component fixture.
for (const language of ['en', 'cn']) {
Expand Down
Loading