fix(ui): keep segmented selections visible in forced colors (#127165)

* test(ui): cover segmented controls in forced colors

Amp-Thread-ID: https://ampcode.com/threads/T-01a021f4-b547-7788-a916-d4a94cbd3e3b

* fix(ui): preserve segmented selection in forced colors

Amp-Thread-ID: https://ampcode.com/threads/T-01a021f4-b547-7788-a916-d4a94cbd3e3b

* fix(ui): separate forced-color selection and focus

Amp-Thread-ID: https://ampcode.com/threads/T-01a021f4-b547-7788-a916-d4a94cbd3e3b

---------

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Peter Steinberger
2026-08-21 04:53:52 -07:00
committed by GitHub
parent cb563bb16d
commit 1dbcbb2fb0
2 changed files with 120 additions and 0 deletions
@@ -108,6 +108,114 @@ async function captureBrowserSettingProof(
}
suite.define(() => {
it("keeps selected segmented options distinct in forced colors", async () => {
const cases = [
{
colorScheme: "dark" as const,
config: { ui: { prefs: { themeMode: "dark" } } },
featureMethods: undefined,
route: "settings/appearance",
selected: "Dark",
unselected: "Light",
},
{
colorScheme: "light" as const,
config: { update: { auto: { enabled: true }, channel: "beta" } },
featureMethods: ["config.get", "config.set", "config.apply", "update.run"],
route: "settings/updates",
selected: "Beta",
unselected: "Stable",
},
];
for (const scenario of cases) {
await suite.withPage(
{
colorScheme: scenario.colorScheme,
forcedColors: "active",
locale: "en-US",
serviceWorkers: "block",
viewport: { height: 900, width: 1280 },
},
async ({ page }) => {
await installMockGateway(page, {
featureMethods: scenario.featureMethods,
methodResponses: {
"config.get": {
config: scenario.config,
hash: `forced-colors-${scenario.route}`,
issues: [],
raw: JSON.stringify(scenario.config),
runtimeConfig: scenario.config,
valid: true,
},
},
operatorScopes: ["operator.read", "operator.admin"],
});
const response = await page.goto(`${suite.server.baseUrl}${scenario.route}`);
expect(response?.status()).toBe(200);
expect(await page.evaluate(() => matchMedia("(forced-colors: active)").matches)).toBe(
true,
);
const selected = page.getByRole("radio", { name: scenario.selected, exact: true });
const unselected = page.getByRole("radio", { name: scenario.unselected, exact: true });
await selected.waitFor();
expect(await selected.getAttribute("aria-checked")).toBe("true");
expect(await unselected.getAttribute("aria-checked")).toBe("false");
if (captureUiProofEnabled) {
await mkdir(uiProofArtifactDir, { recursive: true });
await selected
.locator(
"xpath=ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' settings-row ')][1]",
)
.screenshot({
animations: "disabled",
path: path.join(
uiProofArtifactDir,
`segmented-forced-colors-${scenario.colorScheme}.png`,
),
});
}
expect(
await selected.evaluate((element) => {
const style = getComputedStyle(element);
return {
textDecorationLine: style.textDecorationLine,
textDecorationThickness: style.textDecorationThickness,
};
}),
).toEqual({ textDecorationLine: "underline", textDecorationThickness: "2px" });
expect(
await unselected.evaluate((element) => getComputedStyle(element).textDecorationLine),
).toBe("none");
await selected.focus();
expect(await selected.evaluate((element) => element.matches(":focus-visible"))).toBe(
true,
);
expect(
await selected.evaluate((element) => {
const style = getComputedStyle(element);
return {
outlineOffset: style.outlineOffset,
outlineStyle: style.outlineStyle,
outlineWidth: style.outlineWidth,
textDecorationLine: style.textDecorationLine,
};
}),
).toEqual({
outlineOffset: "2px",
outlineStyle: "solid",
outlineWidth: "2px",
textDecorationLine: "underline",
});
},
);
}
});
it("keeps checked switches on the scene accent on the security page", async () => {
await suite.withPage(
{
+12
View File
@@ -90,6 +90,18 @@ wa-radio-group.settings-segmented::part(radios) {
cursor: not-allowed;
}
@media (forced-colors: active) {
.settings-segmented__btn--active {
text-decoration: underline 2px;
text-underline-offset: 3px;
}
.settings-segmented__btn:focus-visible {
outline: 2px solid Highlight;
outline-offset: 2px;
}
}
input.settings-input,
select.settings-select:not([multiple]) {
height: var(--settings-control-height);