Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
46 changes: 46 additions & 0 deletions src/components/SplitButton/SplitButton.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,49 @@ export const Playground: Story = {
menu: menuItems,
},
};

// Button Types
export const Primary: Story = {
args: {
type: 'primary',
children: 'Primary Split Button',
menu: menuItems,
},
};

export const Secondary: Story = {
args: {
type: 'secondary',
children: 'Secondary Split Button',
menu: menuItems,
},
};

// Disabled States
export const PrimaryDisabled: Story = {
args: {
type: 'primary',
children: 'Disabled Primary',
menu: menuItems,
disabled: true,
},
};

export const SecondaryDisabled: Story = {
args: {
type: 'secondary',
children: 'Disabled Secondary',
menu: menuItems,
disabled: true,
},
};

// Interactive
export const Interactive: Story = {
args: {
type: 'primary',
children: 'Interactive Split Button',
menu: menuItems,
onClick: () => console.log('clicked'),
},
};
3 changes: 3 additions & 0 deletions src/components/SplitButton/SplitButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@ export const SplitButton = ({
modal={modal}
>
<SplitButtonTrigger
data-testid="split-button"
$disabled={disabled}
$fillWidth={fillWidth}
ref={ref}
$type={type}
>
<PrimaryButton
disabled={disabled}
aria-disabled={disabled || undefined}
$type={type}
$fillWidth={fillWidth}
{...props}
Expand All @@ -75,6 +77,7 @@ export const SplitButton = ({
<SecondaryButton
as={Dropdown.Trigger}
disabled={disabled}
aria-disabled={disabled || undefined}
$type={type}
asChild
data-testid="split-button-dropdown"
Expand Down
318 changes: 318 additions & 0 deletions tests/buttons/splitbutton.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
import { test as it, expect } from '@playwright/test';
import { getStoryUrl } from '../utils';

const { describe, use } = it;

describe('SplitButton Visual Regression', () => {
describe('Light Theme (Storybook Global)', () => {
describe('Button Types', () => {
it('primary button matches snapshot', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--primary', 'light'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
await expect(splitButton).toHaveScreenshot('splitbutton-primary-light.png', {
maxDiffPixels: 100,
});
});

it('secondary button matches snapshot', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--secondary', 'light'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
await expect(splitButton).toHaveScreenshot('splitbutton-secondary-light.png', {
maxDiffPixels: 100,
});
});
});

describe('Disabled States', () => {
it('primary disabled matches snapshot', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--primary-disabled', 'light'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
const primaryButton = page.getByRole('button').first();
const dropdownTrigger = page.locator('[data-testid="split-button-dropdown"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
await expect(primaryButton).toHaveAttribute('aria-disabled', 'true');
await expect(dropdownTrigger).toHaveAttribute('aria-disabled', 'true');
await expect(splitButton).toHaveScreenshot(
'splitbutton-primary-disabled-light.png',
{
maxDiffPixels: 100,
}
);
});

it('secondary disabled matches snapshot', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--secondary-disabled', 'light'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
const primaryButton = page.getByRole('button').first();
const dropdownTrigger = page.locator('[data-testid="split-button-dropdown"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
await expect(primaryButton).toHaveAttribute('aria-disabled', 'true');
await expect(dropdownTrigger).toHaveAttribute('aria-disabled', 'true');
await expect(splitButton).toHaveScreenshot(
'splitbutton-secondary-disabled-light.png',
{
maxDiffPixels: 100,
}
);
});
});

describe('Interactive States', () => {
it('hover state - primary', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--primary', 'light'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
await splitButton.hover();
await page.waitForTimeout(100);
await expect(splitButton).toHaveScreenshot(
'splitbutton-primary-hover-light.png',
{
maxDiffPixels: 100,
}
);
});

it('focus state - primary', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--primary', 'light'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
// Focus the primary button inside the split button
const primaryButton = page.getByRole('button').first();
await primaryButton.focus();
await page.waitForTimeout(100);
await expect(splitButton).toHaveScreenshot(
'splitbutton-primary-focus-light.png',
{
maxDiffPixels: 100,
}
);
});

it('hover state - secondary', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--secondary', 'light'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
await splitButton.hover();
await page.waitForTimeout(100);
await expect(splitButton).toHaveScreenshot(
'splitbutton-secondary-hover-light.png',
{
maxDiffPixels: 100,
}
);
});

it('focus state - secondary', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--secondary', 'light'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
// Focus the primary button inside the split button
const primaryButton = page.getByRole('button').first();
await primaryButton.focus();
await page.waitForTimeout(100);
await expect(splitButton).toHaveScreenshot(
'splitbutton-secondary-focus-light.png',
{
maxDiffPixels: 100,
}
);
});
});
});

describe('Dark Theme (System prefers-color-scheme)', () => {
use({ colorScheme: 'dark' });

describe('Button Types', () => {
it('primary button matches snapshot', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--primary'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
await expect(splitButton).toHaveScreenshot('splitbutton-primary-dark.png', {
maxDiffPixels: 100,
});
});

it('secondary button matches snapshot', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--secondary'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
await expect(splitButton).toHaveScreenshot('splitbutton-secondary-dark.png', {
maxDiffPixels: 100,
});
});
});

describe('Disabled States', () => {
it('primary disabled matches snapshot', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--primary-disabled'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
const primaryButton = page.getByRole('button').first();
const dropdownTrigger = page.locator('[data-testid="split-button-dropdown"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
await expect(primaryButton).toHaveAttribute('aria-disabled', 'true');
await expect(dropdownTrigger).toHaveAttribute('aria-disabled', 'true');
await expect(splitButton).toHaveScreenshot(
'splitbutton-primary-disabled-dark.png',
{
maxDiffPixels: 100,
}
);
});

it('secondary disabled matches snapshot', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--secondary-disabled'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
const primaryButton = page.getByRole('button').first();
const dropdownTrigger = page.locator('[data-testid="split-button-dropdown"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
await expect(primaryButton).toHaveAttribute('aria-disabled', 'true');
await expect(dropdownTrigger).toHaveAttribute('aria-disabled', 'true');
await expect(splitButton).toHaveScreenshot(
'splitbutton-secondary-disabled-dark.png',
{
maxDiffPixels: 100,
}
);
});
});

describe('Interactive States', () => {
it('hover state - primary', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--primary'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
await splitButton.hover();
await page.waitForTimeout(100);
await expect(splitButton).toHaveScreenshot('splitbutton-primary-hover-dark.png', {
maxDiffPixels: 100,
});
});

it('focus state - primary', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--primary'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
// Focus the primary button inside the split button
const primaryButton = page.getByRole('button').first();
await primaryButton.focus();
await page.waitForTimeout(100);
await expect(splitButton).toHaveScreenshot('splitbutton-primary-focus-dark.png', {
maxDiffPixels: 100,
});
});

it('hover state - secondary', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--secondary'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
await splitButton.hover();
await page.waitForTimeout(100);
await expect(splitButton).toHaveScreenshot(
'splitbutton-secondary-hover-dark.png',
{
maxDiffPixels: 100,
}
);
});

it('focus state - secondary', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--secondary'), {
waitUntil: 'networkidle',
});
const splitButton = page.locator('[data-testid="split-button"]');
await expect(splitButton).toBeVisible({ timeout: 10000 });
// Focus the primary button inside the split button
const primaryButton = page.getByRole('button').first();
await primaryButton.focus();
await page.waitForTimeout(100);
await expect(splitButton).toHaveScreenshot(
'splitbutton-secondary-focus-dark.png',
{
maxDiffPixels: 100,
}
);
});
});
});

describe('Events and Accessibility', () => {
it('click event fires correctly', async ({ page }) => {
const consoleMessages: string[] = [];
page.on('console', msg => consoleMessages.push(msg.text()));

await page.goto(getStoryUrl('buttons-splitbutton--interactive', 'light'), {
waitUntil: 'networkidle',
});
const primaryButton = page.getByRole('button').first();
await expect(primaryButton).toBeVisible({ timeout: 10000 });
await expect(primaryButton).toBeEnabled();

await primaryButton.click();

// Verify console log was triggered
expect(consoleMessages.some(msg => msg.includes('clicked'))).toBe(true);
});

it('disabled button prevents click', async ({ page }) => {
await page.goto(getStoryUrl('buttons-splitbutton--primary-disabled', 'light'), {
waitUntil: 'networkidle',
});
const primaryButton = page.getByRole('button').first();
const dropdownTrigger = page.locator('[data-testid="split-button-dropdown"]');
await expect(primaryButton).toBeDisabled();
await expect(primaryButton).toHaveAttribute('aria-disabled', 'true');
await expect(dropdownTrigger).toHaveAttribute('aria-disabled', 'true');
});

it('keyboard navigation works', async ({ page }) => {
const consoleMessages: string[] = [];
page.on('console', msg => consoleMessages.push(msg.text()));

await page.goto(getStoryUrl('buttons-splitbutton--interactive', 'light'), {
waitUntil: 'networkidle',
});
const primaryButton = page.getByRole('button').first();
await expect(primaryButton).toBeVisible({ timeout: 10000 });

await primaryButton.focus();
await expect(primaryButton).toBeFocused();
await primaryButton.press('Enter');

// Verify console log was triggered
expect(consoleMessages.some(msg => msg.includes('clicked'))).toBe(true);
});
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading