Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
9 changes: 9 additions & 0 deletions 2nd-gen/packages/core/components/tooltip/Tooltip.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,13 @@ export abstract class TooltipBase extends SpectrumElement {
this.dispatchAfterEvent(this.open);
};

// Allows Escape behavior to be testable, does not interfere with native popover dismissal
private readonly handleKeyDown = (event: KeyboardEvent): void => {
if (event.key === 'Escape' && this.open) {
this.open = false;
}
};

protected override updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (changedProperties.has('open')) {
Expand All @@ -269,12 +276,14 @@ export abstract class TooltipBase extends SpectrumElement {
this.addEventListener('beforetoggle', this.handleBeforeToggle);
this.addEventListener('toggle', this.handleToggle);
this.addEventListener('transitionend', this.handleTransitionEnd);
document.addEventListener('keydown', this.handleKeyDown);
}

public override disconnectedCallback(): void {
super.disconnectedCallback();
this.removeEventListener('beforetoggle', this.handleBeforeToggle);
this.removeEventListener('toggle', this.handleToggle);
this.removeEventListener('transitionend', this.handleTransitionEnd);
document.removeEventListener('keydown', this.handleKeyDown);
}
}
7 changes: 7 additions & 0 deletions 2nd-gen/packages/swc/.storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ const preview = {
'Tools vs packages',
'Writing migration guides',
'Focus management',
'Changelog strategy',
],
'Style guide',
[
Expand Down Expand Up @@ -390,6 +391,11 @@ const preview = {
['Rendering and styling migration analysis'],
'Field label',
['Rendering and styling migration analysis'],
'Grid',
[
'Accessibility migration analysis',
'Rendering and styling migration analysis',
],
'Help text',
['Rendering and styling migration analysis'],
'Illustrated message',
Expand Down Expand Up @@ -436,6 +442,7 @@ const preview = {
'Popover',
[
'Accessibility migration analysis',
'Migration plan',
'Rendering and styling migration analysis',
],
'Progress bar',
Expand Down
5 changes: 5 additions & 0 deletions 2nd-gen/packages/swc/components/tooltip/Tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ import styles from './tooltip.css';
* @slot - Text label displayed in the tooltip.
*
* @cssprop --swc-tooltip-background-color - Background color of the tooltip bubble. Defaults to the neutral background color token.
*
* @fires swc-open - Dispatched when the tooltip begins to open, before the transition plays.
* @fires swc-close - Dispatched when the tooltip begins to close, before the transition plays.
* @fires swc-after-open - Dispatched after the tooltip finishes opening, once the transition completes.
* @fires swc-after-close - Dispatched after the tooltip finishes closing, once the transition completes.
*/
export class Tooltip extends TooltipBase {
// ──────────────────────────────
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import {
import '@adobe/spectrum-wc/components/button/swc-button.js';
import '@adobe/spectrum-wc/components/tooltip/swc-tooltip.js';

// ────────────────────
// METADATA SETUP
// ────────────────────
// ────────────────
// METADATA
// ────────────────

const { args, argTypes, template } = getStorybookHelpers('swc-tooltip');

Expand Down
Empty file.
158 changes: 158 additions & 0 deletions 2nd-gen/packages/swc/components/tooltip/test/tooltip.a11y.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/**
* 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 { expect, test } from '@playwright/test';

import { gotoStory } from '../../../utils/a11y-helpers.js';

/**
* Accessibility tests for Tooltip component (2nd Generation)
*
* ARIA snapshot tests validate the accessibility tree structure.
* aXe WCAG compliance and color contrast validation are run via
* test-storybook (see .storybook/test-runner.ts). Both are included
* in the `test:a11y` command.
*/

test.describe('Tooltip - ARIA Snapshots', () => {
test('closed tooltip is hidden from accessibility tree', async ({ page }) => {
const root = await gotoStory(
page,
'components-tooltip--overview',
'swc-button'
);
// The trigger button is accessible; the closed popover is hidden from the tree.
await expect(root).toMatchAriaSnapshot(`
- button "Open"
`);
});

test('open tooltip exposes role="tooltip" in accessibility tree', async ({
page,
}) => {
await gotoStory(page, 'components-tooltip--overview', 'swc-button');

// Open the tooltip programmatically (HoverController not yet wired).
await page.evaluate(() => {
const tooltip = document.querySelector('swc-tooltip') as HTMLElement & {
open: boolean;
};
if (tooltip) {
tooltip.open = true;
}
});

// Wait for the popover to appear in the top layer.
await page.waitForFunction(() =>
document.querySelector('swc-tooltip')?.matches(':popover-open')
);

const root = page.locator('#storybook-root');
await expect(root).toMatchAriaSnapshot(`
- button "Open"
- tooltip "Save your changes"
`);
});

test('tooltip is removed from accessibility tree when closed', async ({
page,
}) => {
await gotoStory(page, 'components-tooltip--overview', 'swc-button');

// Open then close via the property.
await page.evaluate(() => {
const tooltip = document.querySelector('swc-tooltip') as HTMLElement & {
open: boolean;
};
if (tooltip) {
tooltip.open = true;
}
});
await page.waitForFunction(() =>
document.querySelector('swc-tooltip')?.matches(':popover-open')
);

await page.evaluate(() => {
const tooltip = document.querySelector('swc-tooltip') as HTMLElement & {
open: boolean;
};
if (tooltip) {
tooltip.open = false;
}
});
await page.waitForFunction(
() => !document.querySelector('swc-tooltip')?.matches(':popover-open')
);

const root = page.locator('#storybook-root');
await expect(root).toMatchAriaSnapshot(`
- button "Open"
`);
});

test('Escape closes an open tooltip', async ({ page }) => {
await gotoStory(page, 'components-tooltip--overview', 'swc-button');

await page.evaluate(() => {
const tooltip = document.querySelector('swc-tooltip') as HTMLElement & {
open: boolean;
};
if (tooltip) {
tooltip.open = true;
}
});
await page.waitForFunction(() =>
document.querySelector('swc-tooltip')?.matches(':popover-open')
);

await page.keyboard.press('Escape');

await page.waitForFunction(
() => !document.querySelector('swc-tooltip')?.matches(':popover-open')
);

const open = await page.evaluate(
() => (document.querySelector('swc-tooltip') as { open?: boolean })?.open
);
expect(open, 'tooltip.open is false after Escape').toBe(false);
});

test('all variant triggers are accessible', async ({ page }) => {
const root = await gotoStory(
page,
'components-tooltip--variants',
'swc-button'
);
await expect(root).toMatchAriaSnapshot(`
- button "Save"
- button "Upload"
- button "Delete"
`);
});

test('all placement triggers are accessible', async ({ page }) => {
const root = await gotoStory(
page,
'components-tooltip--placements',
'swc-button'
);
// Each placement renders a separate trigger button.
await expect(root).toMatchAriaSnapshot(`
- button "top"
- button "right"
- button "end"
- button "bottom"
- button "left"
- button "start"
`);
});
});
Loading
Loading