Files
leptos-shadcn-ui/packages/visual-testing/tests/input.visual.spec.ts
Peter Hanssens eb8f7ae9d6 feat: comprehensive component updates and testing infrastructure
- Update multiple components with improved signal management and error handling
- Add integration tests for dialog, popover, dropdown-menu, command, and sheet components
- Enhance form validation with comprehensive type system
- Add visual testing infrastructure with Playwright
- Add analytics package for component tracking
- Improve lazy loading with new component browser
- Enhance error boundary with context and new_york variants
- Update tailwind-rs-core with improved responsive utilities
- Add extensive error handling utilities across packages

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-10 12:15:52 +00:00

344 lines
12 KiB
TypeScript

/**
* Visual regression tests for Input component
* Tests across multiple themes, viewports, and states
*/
import { test, expect } from '@playwright/test';
import { THEMES, VIEWPORTS, createVisualFramework } from '../src/index.js';
test.describe('Input Visual Tests', () => {
test.describe.configure({ mode: 'parallel' });
// Test default input across themes
for (const theme of THEMES.slice(0, 2)) {
test(`default input in ${theme.name} theme`, async ({ page }) => {
const framework = createVisualFramework(page);
await framework.testStory('components-input', 'default', {
themes: [theme],
viewports: [VIEWPORTS[0]],
threshold: 0.0001,
});
});
}
// Test input states
test('input states (default, disabled, error)', async ({ page }) => {
const framework = createVisualFramework(page);
await framework.testStory('components-input', 'disabled', {
themes: [THEMES[0]],
viewports: [VIEWPORTS[0]],
threshold: 0.0001,
});
});
// Test input focus state
test('input focus state', async ({ page }) => {
await page.goto('/?path=/story/components-input--default');
const input = page.locator('input[type="text"]').first();
await input.focus();
const screenshot = await page.screenshot({ fullPage: false });
expect(screenshot).toMatchSnapshot('input-focus.png');
});
// Test input with placeholder
test('input with placeholder text', async ({ page }) => {
await page.goto('/?path=/story/components-input--default');
const input = page.locator('input[placeholder]').first();
const screenshot = await input.screenshot();
expect(screenshot).toMatchSnapshot('input-placeholder.png');
});
// Test input with text content
test('input with filled text', async ({ page }) => {
await page.goto('/?path=/story/components-input--default');
const input = page.locator('input[type="text"]').first();
await input.fill('Sample text content');
const screenshot = await input.screenshot();
expect(screenshot).toMatchSnapshot('input-filled.png');
});
// Test input with long content
test('input with long text content (scrolling)', async ({ page }) => {
await page.goto('/?path=/story/components-input--default');
const input = page.locator('input[type="text"]').first();
await input.fill('This is a very long text that should scroll within the input field');
const screenshot = await input.screenshot();
expect(screenshot).toMatchSnapshot('input-long-text.png');
});
// Test input types
const inputTypes = ['text', 'email', 'password', 'number', 'tel', 'url'];
for (const type of inputTypes) {
test(`input type: ${type}`, async ({ page }) => {
await page.goto('/?path=/story/components-input--default');
const input = page.locator(`input[type="${type}"]`).first();
await expect(input).toBeVisible();
const screenshot = await input.screenshot();
expect(screenshot).toMatchSnapshot(`input-type-${type}.png`);
});
}
// Test responsive input
test('input responsive design', async ({ page }) => {
const framework = createVisualFramework(page);
await framework.testStory('components-input', 'default', {
themes: [THEMES[0]],
viewports: [VIEWPORTS[0], VIEWPORTS[2], VIEWPORTS[3]],
threshold: 0.0001,
});
});
// Test input with icon
test('input with icon', async ({ page }) => {
await page.goto('/?path=/story/components-input--with-icon');
const container = page.locator('.input-with-icon').first();
const screenshot = await container.screenshot();
expect(screenshot).toMatchSnapshot('input-with-icon.png');
});
// Test input with label
test('input with label', async ({ page }) => {
await page.goto('/?path=/story/components-input--with-label');
const container = page.locator('.form-group').first();
const screenshot = await container.screenshot();
expect(screenshot).toMatchSnapshot('input-with-label.png');
});
// Test input with helper text
test('input with helper text', async ({ page }) => {
await page.goto('/?path=/story/components-input--with-helper');
const container = page.locator('.form-group').first();
const screenshot = await container.screenshot();
expect(screenshot).toMatchSnapshot('input-with-helper.png');
});
// Test input with error state
test('input error state', async ({ page }) => {
await page.goto('/?path=/story/components-input--error');
const container = page.locator('.input-error').first();
const screenshot = await container.screenshot();
expect(screenshot).toMatchSnapshot('input-error.png');
});
// Test input in form
test('input within form layout', async ({ page }) => {
await page.goto('/?path=/story/components-form--default');
const form = page.locator('form').first();
const screenshot = await form.screenshot();
expect(screenshot).toMatchSnapshot('input-in-form.png');
});
// Test multiple inputs together
test('multiple inputs in grid', async ({ page }) => {
await page.goto('/?path=/story/components-input--default');
await page.evaluate(() => {
const container = document.createElement('div');
container.style.display = 'grid';
container.style.gridTemplateColumns = 'repeat(2, 1fr)';
container.style.gap = '16px';
for (let i = 0; i < 4; i++) {
const wrapper = document.createElement('div');
wrapper.innerHTML = `
<label>Field ${i + 1}</label>
<input type="text" placeholder="Enter text" class="input" />
`;
container.appendChild(wrapper);
}
document.body.appendChild(container);
});
const screenshot = await page.locator('div').filter({ hasText: 'Field 1' }).screenshot();
expect(screenshot).toMatchSnapshot('input-grid.png');
});
// Test dark mode
test('input in dark mode', async ({ page }) => {
const framework = createVisualFramework(page);
await framework.testStory('components-input', 'default', {
themes: [THEMES[1]],
viewports: [VIEWPORTS[0]],
threshold: 0.0001,
});
});
// Test RTL support
test('input in RTL layout', async ({ page }) => {
await page.goto('/?path=/story/components-input--default');
await page.evaluate(() => {
document.documentElement.setAttribute('dir', 'rtl');
});
const screenshot = await page.screenshot({ fullPage: false });
expect(screenshot).toMatchSnapshot('input-rtl.png');
});
// Test input with validation styles
test('input validation states', async ({ page }) => {
await page.goto('/?path=/story/components-input--validation');
const validInput = page.locator('input[aria-valid="true"]').first();
const invalidInput = page.locator('input[aria-invalid="true"]').first();
await expect(validInput).toBeVisible();
await expect(invalidInput).toBeVisible();
const screenshot = await page.locator('.validation-states').screenshot();
expect(screenshot).toMatchSnapshot('input-validation.png');
});
// Test textarea (related input component)
test('textarea visual consistency', async ({ page }) => {
await page.goto('/?path=/story/components-textarea--default');
const textarea = page.locator('textarea').first();
await textarea.fill('This is sample text in a textarea component that can handle multiple lines of content.');
const screenshot = await textarea.screenshot();
expect(screenshot).toMatchSnapshot('textarea-default.png');
});
// Test input sizing
test('input sizes (sm, default, lg)', async ({ page }) => {
await page.goto('/?path=/story/components-input--sizes');
const screenshot = await page.screenshot({ fullPage: false });
expect(screenshot).toMatchSnapshot('input-sizes.png');
});
// Test input file upload
test('file input styling', async ({ page }) => {
await page.goto('/?path=/story/components-input--file');
const fileInput = page.locator('input[type="file"]').first();
await expect(fileInput).toBeVisible();
const screenshot = await fileInput.screenshot();
expect(screenshot).toMatchSnapshot('input-file.png');
});
// Test search input
test('search input with icon', async ({ page }) => {
await page.goto('/?path=/story/components-input--search');
const searchContainer = page.locator('.search-input').first();
const screenshot = await searchContainer.screenshot();
expect(screenshot).toMatchSnapshot('input-search.png');
});
// Test date input
test('date input styling', async ({ page }) => {
await page.goto('/?path=/story/components-input--date');
const dateInput = page.locator('input[type="date"]').first();
const screenshot = await dateInput.screenshot();
expect(screenshot).toMatchSnapshot('input-date.png');
});
// Test input with clear button
test('input with clear button', async ({ page }) => {
await page.goto('/?path=/story/components-input--clearable');
const input = page.locator('input[type="text"]').first();
await input.fill('Text to clear');
const container = page.locator('.clearable-input').first();
const screenshot = await container.screenshot();
expect(screenshot).toMatchSnapshot('input-clearable.png');
});
});
// Accessibility tests
test.describe('Input Accessibility Tests', () => {
test('input has proper ARIA labels', async ({ page }) => {
await page.goto('/?path=/story/components-input--default');
const input = page.locator('input[type="text"]').first();
// Check for accessible name
const ariaLabel = await input.evaluate(el =>
el.getAttribute('aria-label') || el.getAttribute('aria-labelledby')
);
const hasLabel = await page.locator('label').count() > 0;
expect(ariaLabel || hasLabel).toBeTruthy();
});
test('disabled input has proper ARIA attributes', async ({ page }) => {
await page.goto('/?path=/story/components-input--disabled');
const disabledInput = page.locator('input[disabled]').first();
await expect(disabledInput).toHaveAttribute('disabled');
await expect(disabledInput).toHaveAttribute('aria-disabled', 'true');
});
test('required input has proper ARIA attributes', async ({ page }) => {
await page.goto('/?path=/story/components-input--required');
const requiredInput = page.locator('input[required]').first();
await expect(requiredInput).toHaveAttribute('required');
await expect(requiredInput).toHaveAttribute('aria-required', 'true');
});
test('invalid input has proper error indication', async ({ page }) => {
await page.goto('/?path=/story/components-input--error');
const invalidInput = page.locator('input[aria-invalid="true"]').first();
await expect(invalidInput).toHaveAttribute('aria-invalid', 'true');
await expect(invalidInput).toHaveAttribute('aria-describedby');
});
});
// Performance tests
test.describe('Input Performance Tests', () => {
test('renders 50 inputs without lag', async ({ page }) => {
await page.goto('/?path=/story/components-input--default');
await page.evaluate(() => {
const container = document.createElement('div');
container.style.display = 'grid';
container.style.gridTemplateColumns = 'repeat(5, 1fr)';
container.style.gap = '8px';
for (let i = 0; i < 50; i++) {
const input = document.createElement('input');
input.type = 'text';
input.placeholder = `Field ${i + 1}`;
input.className = 'input';
container.appendChild(input);
}
document.body.appendChild(container);
});
const screenshot = await page.screenshot({ fullPage: true });
expect(screenshot).toMatchSnapshot('input-grid-50.png');
});
});