feat(filters): show a tag's own colour on its active filter chip (#185)

A tag carries a colour, and every place a tag appears shows it — a product card, the filter drawer's control, the admin taxonomy screen — except the removable chips beside the Filters button, which rendered every filter as a default grey. Picking `vintage` from a control that showed it in red produced a grey chip of the same name right next to it.

Only tags get a colour, because only tags have one. Category, price, favorites and status keep the default, and that asymmetry is the point: in a row mixing four kinds of filter, colour now means "this is a tag". Nothing depends on it — every chip still carries its label — so this reads the same to anyone who cannot distinguish the colours.

The close control inherits the tag's text colour, so a coloured chip gets a matching cross rather than a grey one on a coloured ground. A tag not yet in the loaded options has no colour to use and keeps the default, which is the same window the existing `Tag {id}` label fallback covers.

The test asserts the chip's colour equals the same tag's colour on a product card, rather than asserting it is red. The colour is derived from the tag's name and free to change; what must hold is that a tag looks like itself wherever it appears, and comparing the two places says that directly. Confirmed to fail without the change — the old grey chip sets no colour class at all.

Verified visually as well as by assertion: three tags selected together render in the drawer, in the chip row and on the card in the same colours.

Closes #185
This commit is contained in:
2026-08-25 14:05:34 -05:00
parent 3cffcf772c
commit d6e0942487
3 changed files with 60 additions and 1 deletions
+29
View File
@@ -193,6 +193,35 @@ test.describe('Storefront filters', () => {
await expect(storefront.removeFilterChip(NAMES.furniture)).toBeVisible();
});
// #185. The requirement is not "the chip is red", which would pin a colour
// that is derived from the tag's name and free to change — it is that a tag
// looks like itself wherever it appears. So the chip is compared against the
// same tag on a product card rather than against a literal.
test('shows a selected tag in its own colour, the same one the card uses', async ({
storefront,
filterDrawer
}) => {
await storefront.goto();
await storefront.openFilters();
await filterDrawer.toggleTag(NAMES.vintage);
await filterDrawer.close();
const chip = storefront.filterChip(NAMES.vintage);
await expect(chip).toBeVisible();
const onCard = storefront.cardTag(NAMES.midItem, NAMES.vintage);
await expect(onCard).toBeVisible();
const colourClass = (classes: string | null): string | undefined =>
(classes ?? '').split(/\s+/).find((name) => /^ant-tag-[a-z]+$/.test(name));
const chipColour = colourClass(await chip.getAttribute('class'));
// Coloured at all: before #185 every chip in this row rendered grey, which
// sets no colour class and would leave this undefined.
expect(chipColour).toBeDefined();
expect(chipColour).toBe(colourClass(await onCard.getAttribute('class')));
});
test("shows an item's tags on its card", async ({ storefront, filterDrawer }) => {
await storefront.goto();
await storefront.openFilters();
@@ -135,6 +135,22 @@ export class StorefrontPage {
return this.page.getByRole('button', { name: `Remove filter ${name}` });
}
/**
* The chip itself rather than its remove button, for asserting how it looks.
*
* antd expresses a tag's colour as an `ant-tag-<colour>` class, so the chip
* element is what carries it — the button inside only inherits the text
* colour.
*/
filterChip(name: string): Locator {
return this.activeFilters.locator('.ant-tag').filter({ hasText: name });
}
/** A tag as rendered on a product card, which is where its colour is set. */
cardTag(itemName: string, tagName: string): Locator {
return this.card(itemName).locator('.ant-tag').filter({ hasText: tagName });
}
async clearAllFilters(): Promise<void> {
await this.activeFilters.getByRole('button', { name: 'Clear all' }).click();
}