Skip to content

Commit

Permalink
chore: merge from main
Browse files Browse the repository at this point in the history
  • Loading branch information
makafsal committed Dec 16, 2024
2 parents 0aa4d5c + 1174a16 commit 83bd974
Show file tree
Hide file tree
Showing 25 changed files with 735 additions and 188 deletions.
22 changes: 0 additions & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,28 +182,6 @@ jobs:
name: playwright-avt-report
path: .playwright

vrt-runner:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '20.x'
cache: yarn
- name: Install
run: yarn
- name: Install browsers
run: yarn playwright install --with-deps
- name: Build project
run: yarn build
- name: Run VRT
working-directory: packages/core
env:
PERCY_TOKEN: web_d04495b0b413d61c2ea6b9118d1748b43f4fdd58d17ebe453ef8e0016b5766e4
run: yarn percy storybook storybook-static

avt:
if: ${{ always() }}
runs-on: ubuntu-latest
Expand Down
250 changes: 250 additions & 0 deletions e2e/components/ProductiveCard/ProductiveCard-test.avt.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import { expect, test } from '@playwright/test';
import { visitStory } from '../../test-utils/storybook';
import { carbon, pkg } from '../../../packages/ibm-products/src/settings';

test.describe('ProductiveCard @avt', () => {
test('@avt-default-state', async ({ page }) => {
Expand All @@ -23,4 +24,253 @@ test.describe('ProductiveCard @avt', () => {
'ProductiveCard @avt-default-state'
);
});

test('@avt-with-caption', async ({ page }) => {
await visitStory(page, {
component: 'ProductiveCard',
id: 'ibm-products-components-cards-productivecard--with-caption',
globals: {
carbonTheme: 'white',
},
});
await expect(page).toHaveNoACViolations('ProductiveCard @avt-with-caption');
});

// Disabled state test
test('@avt-disabled: validates disabled button state', async ({ page }) => {
await visitStory(page, {
component: 'ProductiveCard',
id: 'ibm-products-components-cards-productivecard--with-action-ghost-button',
});

await expect(page).toHaveNoACViolations('ProductiveCard @avt-disabled');
const editButton = page.getByRole('button', { name: 'Edit' });
const deleteButton = page.getByRole('button', { name: 'Delete' });
const disabledButton = page.getByRole('button', { name: 'Read more' });
expect(disabledButton.getAttribute('disabled')).not.toBeNull();

await page.keyboard.press('Tab');
expect(editButton).toBeFocused();

await page.keyboard.press('Tab');
expect(deleteButton).toBeFocused();
// disabled button
await page.keyboard.press('Tab');
expect(
await disabledButton.evaluate((btn) => document.activeElement !== btn)
).toBe(true);
});

// Overflow menu open/close states test
test('@avt-overflow-menu: validates overflow menu interactions', async ({
page,
}) => {
await visitStory(page, {
component: 'ProductiveCard',
id: 'ibm-products-components-cards-productivecard--with-overflow',
});

const menuButton = page.getByRole('button', { label: 'Option' });
const menu = page.getByRole('menu');

// Check initial state
expect(await menuButton.getAttribute('aria-expanded')).toBe('false');

// Open the menu
await menuButton.click();

// Wait for menu to be visible
await expect(menu).toBeVisible();

expect(await menuButton.getAttribute('aria-expanded')).toBe('true');
await expect(page).toHaveNoACViolations('ProductiveCard @menu-open');

// Close the menu with Escape
await page.keyboard.press('Escape');
await expect(menu).not.toBeVisible();

expect(await menuButton.getAttribute('aria-expanded')).toBe('false');
await expect(page).toHaveNoACViolations('ProductiveCard @menu-closed');

// Reopen the menu via keyboard
await page.keyboard.press('Tab');
expect(
await menuButton.evaluate((btn) => document.activeElement === btn)
).toBe(true);

await page.keyboard.press('Enter');
await expect(menu).toBeVisible();

// Check menu item count and focus
const menuItems = page.locator(`li.${carbon.prefix}--menu-item`);
expect(await menuItems.count()).toBeGreaterThan(0);
expect(
await menuItems.first().evaluate((btn) => document.activeElement === btn)
).toBe(true);
expect(await menuButton.getAttribute('aria-expanded')).toBe('true');

// Ensure the menu is closed when pressing Escape
await page.keyboard.press('Escape');
// Focus returns to menu button
expect(
await menuButton.evaluate((btn) => document.activeElement === btn)
).toBe(true);

// Check final state
await expect(menu).not.toBeVisible();
});

test('@avt-keyboard: validates keyboard navigation for all interactive elements', async ({
page,
}) => {
// Navigate to the "Supplemental Bottom Bar" story for ProductiveCard, that has all interactive elements
await visitStory(page, {
component: 'ProductiveCard',
id: 'ibm-products-components-cards-productivecard--supplemental-bottom-bar',
});

// Ensure no accessibility violations for the story
await expect(page).toHaveNoACViolations(
'ProductiveCard @keyboard-navigation - Supplemental Bottom Bar'
);

// Move focus to the Edit button and validate
await page.keyboard.press('Tab');
const editButton = page.getByLabel('Edit');
await expect(editButton).toBeVisible();
await expect(editButton).toBeFocused();
await expect(page).toHaveNoACViolations(
'ProductiveCard @keyboard-navigation - Edit Button'
);

// Move focus to the Delete button and validate
await page.keyboard.press('Tab');
const deleteButton = page.getByLabel('Delete');
await expect(deleteButton).toBeVisible();
await expect(deleteButton).toBeFocused();
await expect(page).toHaveNoACViolations(
'ProductiveCard @keyboard-navigation - Delete Button'
);

// Move focus to the Read more button and validate
await page.keyboard.press('Tab');
const readMoreButton1 = page.getByText('Read more');
await expect(readMoreButton1).toBeVisible();
await expect(readMoreButton1).toBeFocused();
await expect(page).toHaveNoACViolations(
'ProductiveCard @keyboard-navigation - Read more Button'
);

// Tab Navigation in "Clickable Card" story for ProductiveCard, (zone one is default, whole card recieves focus)
await visitStory(page, {
component: 'ProductiveCard',
id: 'ibm-products-components-cards-productivecard--clickable',
});

// Ensure no accessibility violations for the story
await expect(page).toHaveNoACViolations(
'ProductiveCard @keyboard-navigation - Clickable Card'
);

// Move focus to the card element and validate
await page.keyboard.press('Tab');
const zone1 = page.locator(`.${pkg.prefix}--card__clickable`);
await expect(zone1).toBeFocused();
await expect(zone1).toHaveAttribute('role', 'button');

// Move focus to the Read more button and validate
await page.keyboard.press('Tab');
const readMoreButton2 = page.getByText('Read more');
await expect(readMoreButton2).toBeVisible();
await expect(readMoreButton2).toBeFocused();

// Validate zone two focus
await visitStory(page, {
component: 'ProductiveCard',
id: 'ibm-products-components-cards-productivecard--clickable&args=clickZone:two',
});
await page.keyboard.press('Tab');

const zone2 = page.locator(`.${pkg.prefix}--card__header-body-container`);
await expect(zone2).toBeFocused();
await expect(zone2).toHaveAttribute('role', 'button');

// Move focus to the Read more button and validate
await page.keyboard.press('Tab');
const readMoreButton3 = page.getByText('Read more');
await expect(readMoreButton3).toBeVisible();
await expect(readMoreButton3).toBeFocused();

// Validate zone three focus
await visitStory(page, {
component: 'ProductiveCard',
id: 'ibm-products-components-cards-productivecard--clickable&args=clickZone:three',
});
await page.keyboard.press('Tab');
const zone3 = page.locator(`.${pkg.prefix}--card__body`);
await expect(zone3).toBeFocused();
await expect(zone3).toHaveAttribute('role', 'button');

// Move focus to the Read more button and validate
await page.keyboard.press('Tab');
const readMoreButton4 = page.getByText('Read more');
await expect(readMoreButton4).toBeVisible();
await expect(readMoreButton4).toBeFocused();

// Navigate to the "button with href" story for ProductiveCard
await visitStory(page, {
component: 'ProductiveCard',
id: 'ibm-products-components-cards-productivecard--with-button-href',
});

// Ensure no accessibility violations for the story
await expect(page).toHaveNoACViolations(
'ProductiveCard @keyboard-navigation - button with href'
);

// Move focus to the href button and validate
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
const hrefButton = page.getByText('Read more');
await expect(hrefButton).toHaveAttribute('href', '#');
await expect(hrefButton).toBeVisible();
await expect(hrefButton).toBeFocused();
await expect(page).toHaveNoACViolations(
'ProductiveCard @keyboard-navigation - href Button'
);
});

// hover states
test('@avt-hover: validates hover states', async ({ page }) => {
await visitStory(page, {
component: 'ProductiveCard',
id: 'ibm-products-components-cards-productivecard--with-overflow',
});
const menuButton = page.getByRole('button', { label: 'Overflow menu' });
const tooltip = page.getByRole('tooltip', { name: 'Overflow menu' });

await menuButton.hover();
await expect(page).toHaveNoACViolations(
'ProductiveCard @hover - with overflow'
);
await expect(tooltip).toBeVisible();

await visitStory(page, {
component: 'ProductiveCard',
id: 'ibm-products-components-cards-productivecard--default',
});
const editButton = page.getByLabel('Edit');
const editTooltip = page.getByRole('tooltip', { name: 'Edit' });
const deleteButton = page.getByLabel('Delete');
const deleteTooltip = page.getByRole('tooltip', { name: 'Delete' });

await editButton.hover();
await expect(page).toHaveNoACViolations('ProductiveCard @hover - default');
await expect(editTooltip).toBeVisible();

await deleteButton.hover();
await expect(page).toHaveNoACViolations('ProductiveCard @hover - default');
await expect(deleteTooltip).toBeVisible();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -538,14 +538,8 @@ $right-section-alt-width: 100% - $left-section-alt-width;
}

.#{$block-class}__subtitle-row {
display: -webkit-box;
overflow: hidden;
max-width: 100%;
margin-top: $spacing-03;

-webkit-box-orient: vertical;
-webkit-line-clamp: 2;

@include breakpoint-up('md') {
max-width: $left-section-std-width;
}
Expand All @@ -559,6 +553,24 @@ $right-section-alt-width: 100% - $left-section-alt-width;
@include type.type-style('body-01');
}

.#{$block-class}__subtitle-tooltip .#{$carbon-prefix}--definition-term {
border-bottom: 0;
letter-spacing: inherit;
}

// overwrites the existing styles to make the popover bigger because in some cases the narrow space can be too constricting for the header
.#{$block-class}__subtitle-tooltip
.#{$carbon-prefix}--popover-content.#{$carbon-prefix}--definition-tooltip {
max-inline-size: fit-content;
}

.#{$block-class}__subtitle-text {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}

.#{$block-class}__available-row {
@include type.type-style('body-01');

Expand Down
5 changes: 2 additions & 3 deletions packages/ibm-products/src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,9 @@ export const Card = forwardRef(
onClick,
onKeyDown,
onPrimaryButtonClick,
onSecondaryButtonClick,
overflowActions = Object.freeze([]),
overflowAriaLabel,
onSecondaryButtonClick,
pictogram: Pictogram,
primaryButtonDisabled,
primaryButtonHref,
Expand Down Expand Up @@ -179,8 +179,7 @@ export const Card = forwardRef(
autoAlign
menuAlignment={pos}
size={size}
aria-label={overflowAriaLabel}
label={iconDescription}
label={overflowAriaLabel || iconDescription}
>
{overflowActions.map(({ id, itemText, ...rest }) => (
<MenuItem key={id} label={itemText} {...rest} />
Expand Down
Loading

0 comments on commit 83bd974

Please sign in to comment.