From a8f5a68a1909a8ee4635e43f3487dab918a4a04f Mon Sep 17 00:00:00 2001 From: Rajdeep Chandra Date: Tue, 4 Aug 2026 17:50:56 +0530 Subject: [PATCH 1/2] chore(meter): add vrt coverage Adds dedicated Storybook VRT stories for swc-meter, following the button/action-button VRT foundation: size, variant, label-position, value-state (including over-100% and anatomy/label variations), behaviors, wrapping, CJK, and static-color permutations plus a forced-colors snapshot in meter.vrt.ts, and one reference/override row per public --swc-linear-progress-* custom property in meter-custom-properties.vrt.ts. Co-Authored-By: Claude Sonnet 5 --- .../test/vrt/meter-custom-properties.vrt.ts | 84 ++++++ .../components/meter/test/vrt/meter.vrt.ts | 273 ++++++++++++++++++ 2 files changed, 357 insertions(+) create mode 100644 2nd-gen/packages/swc/components/meter/test/vrt/meter-custom-properties.vrt.ts create mode 100644 2nd-gen/packages/swc/components/meter/test/vrt/meter.vrt.ts diff --git a/2nd-gen/packages/swc/components/meter/test/vrt/meter-custom-properties.vrt.ts b/2nd-gen/packages/swc/components/meter/test/vrt/meter-custom-properties.vrt.ts new file mode 100644 index 00000000000..2933b7bf973 --- /dev/null +++ b/2nd-gen/packages/swc/components/meter/test/vrt/meter-custom-properties.vrt.ts @@ -0,0 +1,84 @@ +/** + * Copyright 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { html, nothing } from 'lit'; +import type { Meta, StoryObj as Story } from '@storybook/web-components'; + +import '@adobe/spectrum-wc/components/meter/swc-meter.js'; + +import type { CustomPropertyCase } from '../../../../.storybook/helpers/index.js'; +import { + coveredCustomProperties, + customPropertyRows, + theme, + verifyCustomPropertyCoverage, + vrtParameters, +} from '../../../../.storybook/helpers/index.js'; +import customElementsManifest from '../../../../dist/custom-elements.json'; + +// Metadata + +const meta: Meta = { + title: 'Meter/Meter VRT', + component: 'swc-meter', + tags: ['dev'], +}; + +export default meta; + +// Helpers + +// Every `--swc-linear-progress-*` custom property documented on +// `` (see Meter.ts's `@cssprop` list) is a public contract that +// `` shares with the not-yet-migrated ``. One +// row per property: a reference meter next to the same meter with that one +// property overridden to an obviously different value, so a real difference +// confirms the override still works. +type MeterPropertyCase = CustomPropertyCase<`--swc-linear-progress-${string}`>; + +const MOD_PROPERTY_CASES: readonly MeterPropertyCase[] = [ + { property: '--swc-linear-progress-fill-color', value: 'magenta' }, + { property: '--swc-linear-progress-track-color', value: 'magenta' }, + { property: '--swc-linear-progress-text-color', value: 'magenta' }, + { property: '--swc-linear-progress-thickness', value: '40px' }, + { property: '--swc-linear-progress-font-size', value: '32px' }, + { property: '--swc-linear-progress-top-to-text', value: '40px' }, +]; + +const modPropertyMeter = (_case: MeterPropertyCase, style?: string) => html` + + Storage used + +`; + +const modPropertiesContent = () => + customPropertyRows(MOD_PROPERTY_CASES, modPropertyMeter); + +const coveredMeterCustomProperties = + coveredCustomProperties(MOD_PROPERTY_CASES); + +const verifyCoverage = async () => { + await verifyCustomPropertyCoverage({ + customElementsManifest, + modulePath: 'components/meter/Meter.ts', + declarationName: 'Meter', + coveredProperties: coveredMeterCustomProperties, + }); +}; + +// VRT stories + +export const CustomProperties: Story = { + render: () => theme(modPropertiesContent(), 'light', 'ltr'), + parameters: vrtParameters, + play: verifyCoverage, +}; diff --git a/2nd-gen/packages/swc/components/meter/test/vrt/meter.vrt.ts b/2nd-gen/packages/swc/components/meter/test/vrt/meter.vrt.ts new file mode 100644 index 00000000000..a7caed69978 --- /dev/null +++ b/2nd-gen/packages/swc/components/meter/test/vrt/meter.vrt.ts @@ -0,0 +1,273 @@ +/** + * Copyright 2026 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { html, nothing } from 'lit'; +import type { Meta, StoryObj as Story } from '@storybook/web-components'; + +import { + METER_VARIANTS, + type MeterVariant, +} from '@adobe/spectrum-wc-core/components/meter'; +import { + LINEAR_PROGRESS_LABEL_POSITIONS, + LINEAR_PROGRESS_STATIC_COLORS, + LINEAR_PROGRESS_VALID_SIZES, + type LinearProgressLabelPosition, + type LinearProgressSize, + type LinearProgressStaticColor, +} from '@adobe/spectrum-wc-core/mixins/index.js'; + +import '@adobe/spectrum-wc/components/meter/swc-meter.js'; + +import { + forcedColorsVrtParameters, + row, + staticColorBackground, + theme, + vrtParameters, +} from '../../../../.storybook/helpers/index.js'; + +// Metadata + +const meta: Meta = { + title: 'Meter/Meter VRT', + component: 'swc-meter', + tags: ['dev'], +}; + +export default meta; + +// Helpers + +const sizeLabels = { + s: 'Small', + m: 'Medium', + l: 'Large', + xl: 'Extra-large', +} as const satisfies Record; + +const variantLabels = { + informative: 'Informative', + positive: 'Positive', + notice: 'Notice', + negative: 'Negative', +} as const satisfies Record; + +const labelPositionLabels = { + top: 'Top label', + side: 'Side label', +} as const satisfies Record; + +const staticColorLabels = { + white: 'Static white', + black: 'Static black', +} as const satisfies Record; + +type MeterCase = { + size?: LinearProgressSize; + variant?: MeterVariant; + value?: number; + minValue?: number; + maxValue?: number; + labelPosition?: LinearProgressLabelPosition; + staticColor?: LinearProgressStaticColor; + label?: string; + description?: string; + accessibleLabel?: string; + valueLabel?: string; + formatOptions?: Intl.NumberFormatOptions; + lang?: string; +}; + +// `formatOptions` is a JS-only property (no attribute), so it's set via a +// `.formatOptions=` property binding rather than an attribute like the rest +// of these cases. +const renderMeter = ({ + size, + variant, + value = 50, + minValue, + maxValue, + labelPosition, + staticColor, + label, + description, + accessibleLabel, + valueLabel, + formatOptions, + lang, +}: MeterCase) => html` + + ${label + ? html` + ${label} + ` + : nothing} + ${description + ? html` + ${description} + ` + : nothing} + +`; + +// Every size and variant (each independent of the other, since size only +// scales thickness/font-size and variant only swaps the fill color — no +// interaction effect to cross-check), both label positions, the CSS-visible +// range edges (0 %, midpoint, 100 %), an over-range value clamped to a full +// bar while `value-label` still reports the real underlying number, every +// anatomy combination from the docs Anatomy story (label only, label + +// description, accessible-label-only fallback, custom value text), the +// custom-range and format-options behaviors, label wrapping, CJK label +// rendering (line-height differs under linear-progress-base.css's +// `:lang(ja/zh/ko)` rule), and static colors on their contrast backgrounds. +const permutationContent = () => html` + ${row( + LINEAR_PROGRESS_VALID_SIZES.map((size) => + renderMeter({ size, label: sizeLabels[size] }) + ), + 'Sizes' + )} + ${row( + METER_VARIANTS.map((variant) => + renderMeter({ variant, label: variantLabels[variant] }) + ), + 'Variants' + )} + ${row( + LINEAR_PROGRESS_LABEL_POSITIONS.map((labelPosition) => + renderMeter({ + labelPosition, + label: labelPositionLabels[labelPosition], + }) + ), + 'Label position' + )} + ${row( + [ + renderMeter({ value: 0, label: '0%' }), + renderMeter({ value: 25, label: '25%' }), + renderMeter({ value: 50, label: '50%' }), + renderMeter({ value: 75, label: '75%' }), + renderMeter({ value: 100, label: '100%' }), + renderMeter({ + value: 150, + maxValue: 100, + label: 'Over 100%', + valueLabel: '150%', + }), + ], + 'Values' + )} + ${row( + [ + renderMeter({ value: 40, label: 'Label only' }), + renderMeter({ + value: 40, + label: 'Label and description', + description: 'Additional context below the bar', + }), + renderMeter({ value: 40, accessibleLabel: 'Screen-reader-only label' }), + renderMeter({ + value: 40, + label: 'Custom value text', + valueLabel: '1 of 4', + }), + ], + 'Anatomy' + )} + ${row( + [ + renderMeter({ + minValue: 0, + maxValue: 10, + value: 3, + label: 'Custom range', + valueLabel: '3 of 10', + }), + renderMeter({ + value: 42, + label: 'Format options', + formatOptions: { style: 'currency', currency: 'USD' }, + }), + ], + 'Behaviors' + )} + ${row( + [ + renderMeter({ + label: + 'A label long enough to wrap onto multiple lines within the available inline space', + }), + ], + 'Wrapping' + )} + ${row( + [ + renderMeter({ lang: 'ja', label: '承認ワークフローの進行状況' }), + renderMeter({ lang: 'ko', label: '승인 워크플로 진행 상황' }), + renderMeter({ lang: 'zh', label: '审批工作流进度' }), + ], + 'CJK language' + )} + ${LINEAR_PROGRESS_STATIC_COLORS.map((staticColor) => + staticColorBackground( + row( + [ + renderMeter({ staticColor, label: staticColorLabels[staticColor] }), + renderMeter({ + staticColor, + labelPosition: 'side', + label: `${staticColorLabels[staticColor]} · side label`, + }), + ], + staticColorLabels[staticColor] + ), + staticColor + ) + )} +`; + +// VRT stories + +// Rendered once in light/ltr and once in dark/rtl (that combination covers +// both axes), all still in a single story so it costs one snapshot. +export const Permutations: Story = { + render: () => html` + ${theme(permutationContent(), 'light', 'ltr')} + ${theme(permutationContent(), 'dark', 'rtl')} + `, + parameters: vrtParameters, +}; + +// `forced-colors` replaces the whole page palette, so it can't be scoped to +// a subtree the way theme()'s light/dark split is, and needs its own +// snapshot rather than folding into Permutations. Confirms the +// `@media (forced-colors: active)` override in linear-progress-base.css +// (ButtonFace track, ButtonText fill) applies regardless of variant or +// static-color. +export const ForcedColors: Story = { + render: () => theme(permutationContent(), 'light', 'ltr'), + parameters: forcedColorsVrtParameters, +}; From ba1f188a7695f6944a8d0ccc8e654e84830e902d Mon Sep 17 00:00:00 2001 From: Rajdeep Chandra Date: Fri, 7 Aug 2026 17:15:12 +0530 Subject: [PATCH 2/2] chore(meter): address review feedback on vrt coverage - Constrain the Wrapping case's width so the label actually wraps in the snapshot instead of fitting on one line (pfulton). - Drop two low-value comments above Permutations/ForcedColors (pfulton). - Swap the --swc-linear-progress-text-color override from magenta to darkmagenta: magenta text fails WCAG contrast on the default light background and was flagged by axe (miwha-adobe). Co-Authored-By: Claude Sonnet 5 --- .../meter/test/vrt/meter-custom-properties.vrt.ts | 8 +++++++- .../swc/components/meter/test/vrt/meter.vrt.ts | 12 ++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/2nd-gen/packages/swc/components/meter/test/vrt/meter-custom-properties.vrt.ts b/2nd-gen/packages/swc/components/meter/test/vrt/meter-custom-properties.vrt.ts index 2933b7bf973..d6f6913f758 100644 --- a/2nd-gen/packages/swc/components/meter/test/vrt/meter-custom-properties.vrt.ts +++ b/2nd-gen/packages/swc/components/meter/test/vrt/meter-custom-properties.vrt.ts @@ -48,7 +48,13 @@ type MeterPropertyCase = CustomPropertyCase<`--swc-linear-progress-${string}`>; const MOD_PROPERTY_CASES: readonly MeterPropertyCase[] = [ { property: '--swc-linear-progress-fill-color', value: 'magenta' }, { property: '--swc-linear-progress-track-color', value: 'magenta' }, - { property: '--swc-linear-progress-text-color', value: 'magenta' }, + // `magenta` text on the default light background fails WCAG contrast + // (~2.3:1, well under the 4.5:1 AA minimum for normal text), which the + // axe check run against this `dev`-tagged story would flag. `darkmagenta` + // (~7.5:1 on white) stays obviously different from the default subdued + // gray while passing contrast. `fill-color`/`track-color` above aren't + // text colors, so they aren't subject to this check. + { property: '--swc-linear-progress-text-color', value: 'darkmagenta' }, { property: '--swc-linear-progress-thickness', value: '40px' }, { property: '--swc-linear-progress-font-size', value: '32px' }, { property: '--swc-linear-progress-top-to-text', value: '40px' }, diff --git a/2nd-gen/packages/swc/components/meter/test/vrt/meter.vrt.ts b/2nd-gen/packages/swc/components/meter/test/vrt/meter.vrt.ts index a7caed69978..4b039e801b1 100644 --- a/2nd-gen/packages/swc/components/meter/test/vrt/meter.vrt.ts +++ b/2nd-gen/packages/swc/components/meter/test/vrt/meter.vrt.ts @@ -86,6 +86,7 @@ type MeterCase = { valueLabel?: string; formatOptions?: Intl.NumberFormatOptions; lang?: string; + style?: string; }; // `formatOptions` is a JS-only property (no attribute), so it's set via a @@ -105,6 +106,7 @@ const renderMeter = ({ valueLabel, formatOptions, lang, + style, }: MeterCase) => html` ${label @@ -219,6 +222,7 @@ const permutationContent = () => html` renderMeter({ label: 'A label long enough to wrap onto multiple lines within the available inline space', + style: 'max-inline-size: 200px;', }), ], 'Wrapping' @@ -251,8 +255,6 @@ const permutationContent = () => html` // VRT stories -// Rendered once in light/ltr and once in dark/rtl (that combination covers -// both axes), all still in a single story so it costs one snapshot. export const Permutations: Story = { render: () => html` ${theme(permutationContent(), 'light', 'ltr')} @@ -261,12 +263,6 @@ export const Permutations: Story = { parameters: vrtParameters, }; -// `forced-colors` replaces the whole page palette, so it can't be scoped to -// a subtree the way theme()'s light/dark split is, and needs its own -// snapshot rather than folding into Permutations. Confirms the -// `@media (forced-colors: active)` override in linear-progress-base.css -// (ButtonFace track, ButtonText fill) applies regardless of variant or -// static-color. export const ForcedColors: Story = { render: () => theme(permutationContent(), 'light', 'ltr'), parameters: forcedColorsVrtParameters,