mirror of
https://github.com/cloud-shuttle/leptos-shadcn-ui.git
synced 2025-12-22 22:00:00 +00:00
- Fixed compilation errors in menubar, combobox, and drawer packages - Updated to tailwind-rs-core v0.4.0 and tailwind-rs-wasm v0.4.0 for WASM compatibility - Cleaned up unused variable warnings across packages - Updated release documentation with WASM integration details - Demo working with dynamic color API and Tailwind CSS generation - All 25+ core components ready for crates.io publication Key features: ✅ WASM compatibility (no more tokio/mio dependencies) ✅ Dynamic Tailwind CSS class generation ✅ Type-safe color utilities ✅ Production-ready component library
258 lines
9.1 KiB
TypeScript
258 lines
9.1 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('Tailwind-RS-Core Integration Tests @tailwind-rs-core', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/');
|
|
await page.waitForLoadState('networkidle');
|
|
await page.waitForSelector('h1', { timeout: 10000 });
|
|
});
|
|
|
|
test('should generate dynamic classes using TailwindClasses API', async ({ page }) => {
|
|
// Check that the page has proper Tailwind classes applied
|
|
const body = page.locator('body');
|
|
const bodyClasses = await body.evaluate((el) => el.className);
|
|
|
|
// Should have responsive classes
|
|
expect(bodyClasses).toContain('min-h-screen');
|
|
expect(bodyClasses).toContain('transition-all');
|
|
expect(bodyClasses).toContain('duration-700');
|
|
|
|
// Check hero section for TailwindClasses API usage
|
|
const heroSection = page.locator('section').first();
|
|
const heroClasses = await heroSection.evaluate((el) => el.className);
|
|
|
|
// Should have classes generated by TailwindClasses API
|
|
expect(heroClasses).toContain('text-white');
|
|
expect(heroClasses).toContain('py-24');
|
|
expect(heroClasses).toContain('relative');
|
|
expect(heroClasses).toContain('overflow-hidden');
|
|
expect(heroClasses).toContain('flex');
|
|
expect(heroClasses).toContain('items-center');
|
|
expect(heroClasses).toContain('justify-center');
|
|
});
|
|
|
|
test('should apply responsive classes correctly', async ({ page }) => {
|
|
const heroSection = page.locator('section').first();
|
|
|
|
// Test desktop view
|
|
await page.setViewportSize({ width: 1920, height: 1080 });
|
|
await page.waitForTimeout(500);
|
|
|
|
const desktopStyles = await heroSection.evaluate((el) => {
|
|
const styles = window.getComputedStyle(el);
|
|
return {
|
|
paddingTop: styles.paddingTop,
|
|
paddingBottom: styles.paddingBottom,
|
|
};
|
|
});
|
|
|
|
// Should have lg padding (py-24 = 6rem = 96px)
|
|
expect(parseInt(desktopStyles.paddingTop)).toBeGreaterThanOrEqual(90);
|
|
|
|
// Test tablet view
|
|
await page.setViewportSize({ width: 768, height: 1024 });
|
|
await page.waitForTimeout(500);
|
|
|
|
const tabletStyles = await heroSection.evaluate((el) => {
|
|
const styles = window.getComputedStyle(el);
|
|
return {
|
|
paddingTop: styles.paddingTop,
|
|
paddingBottom: styles.paddingBottom,
|
|
};
|
|
});
|
|
|
|
// Should have md padding (py-20 = 5rem = 80px)
|
|
expect(parseInt(tabletStyles.paddingTop)).toBeLessThan(parseInt(desktopStyles.paddingTop));
|
|
|
|
// Test mobile view
|
|
await page.setViewportSize({ width: 375, height: 667 });
|
|
await page.waitForTimeout(500);
|
|
|
|
const mobileStyles = await heroSection.evaluate((el) => {
|
|
const styles = window.getComputedStyle(el);
|
|
return {
|
|
paddingTop: styles.paddingTop,
|
|
paddingBottom: styles.paddingBottom,
|
|
};
|
|
});
|
|
|
|
// Should have sm padding (py-16 = 4rem = 64px)
|
|
expect(parseInt(mobileStyles.paddingTop)).toBeLessThan(parseInt(tabletStyles.paddingTop));
|
|
});
|
|
|
|
test('should merge custom classes with base classes', async ({ page }) => {
|
|
// Check that custom classes are properly merged
|
|
const heroSection = page.locator('section').first();
|
|
const heroClasses = await heroSection.evaluate((el) => el.className);
|
|
|
|
// Should have both base classes and custom classes
|
|
expect(heroClasses).toContain('text-white'); // base class
|
|
expect(heroClasses).toContain('py-24'); // base class
|
|
expect(heroClasses).toContain('flex'); // custom class
|
|
expect(heroClasses).toContain('items-center'); // custom class
|
|
expect(heroClasses).toContain('justify-center'); // custom class
|
|
|
|
// Should have gradient background (custom class)
|
|
const backgroundImage = await heroSection.evaluate((el) => {
|
|
const styles = window.getComputedStyle(el);
|
|
return styles.backgroundImage;
|
|
});
|
|
expect(backgroundImage).toContain('gradient');
|
|
});
|
|
|
|
test('should apply state classes correctly', async ({ page }) => {
|
|
// Check for hover states and transitions
|
|
const buttons = page.locator('button');
|
|
const firstButton = buttons.first();
|
|
|
|
const buttonClasses = await firstButton.evaluate((el) => el.className);
|
|
|
|
// Should have transition classes
|
|
expect(buttonClasses).toMatch(/transition/);
|
|
|
|
// Test hover state
|
|
await firstButton.hover();
|
|
await page.waitForTimeout(100);
|
|
|
|
// Check that hover styles are applied
|
|
const hoverStyles = await firstButton.evaluate((el) => {
|
|
const styles = window.getComputedStyle(el);
|
|
return {
|
|
transform: styles.transform,
|
|
boxShadow: styles.boxShadow,
|
|
};
|
|
});
|
|
|
|
// Should have some hover effect
|
|
expect(hoverStyles.transform).not.toBe('none');
|
|
});
|
|
|
|
test('should generate color-specific classes dynamically', async ({ page }) => {
|
|
// Get initial color classes
|
|
const heroSection = page.locator('section').first();
|
|
const initialClasses = await heroSection.evaluate((el) => el.className);
|
|
|
|
// Click on blue color
|
|
const blueButton = page.locator('button').filter({ hasText: 'blue' });
|
|
await blueButton.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
const blueClasses = await heroSection.evaluate((el) => el.className);
|
|
|
|
// Should have blue-specific classes
|
|
expect(blueClasses).toMatch(/blue/);
|
|
|
|
// Click on green color
|
|
const greenButton = page.locator('button').filter({ hasText: 'green' });
|
|
await greenButton.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
const greenClasses = await heroSection.evaluate((el) => el.className);
|
|
|
|
// Should have green-specific classes
|
|
expect(greenClasses).toMatch(/green/);
|
|
expect(greenClasses).not.toMatch(/blue/);
|
|
|
|
// Click on purple color
|
|
const purpleButton = page.locator('button').filter({ hasText: 'purple' });
|
|
await purpleButton.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
const purpleClasses = await heroSection.evaluate((el) => el.className);
|
|
|
|
// Should have purple-specific classes
|
|
expect(purpleClasses).toMatch(/purple/);
|
|
expect(purpleClasses).not.toMatch(/green/);
|
|
});
|
|
|
|
test('should apply theme-specific classes correctly', async ({ page }) => {
|
|
// Get initial theme classes
|
|
const body = page.locator('body');
|
|
const initialBackground = await body.evaluate((el) => {
|
|
const styles = window.getComputedStyle(el);
|
|
return styles.backgroundImage;
|
|
});
|
|
|
|
// Click on light theme
|
|
const lightThemeButton = page.locator('button').filter({ hasText: 'light' });
|
|
await lightThemeButton.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
const lightBackground = await body.evaluate((el) => {
|
|
const styles = window.getComputedStyle(el);
|
|
return styles.backgroundImage;
|
|
});
|
|
|
|
// Should have light theme classes
|
|
expect(lightBackground).not.toBe(initialBackground);
|
|
expect(lightBackground).toContain('gradient');
|
|
|
|
// Click on dark theme
|
|
const darkThemeButton = page.locator('button').filter({ hasText: 'dark' });
|
|
await darkThemeButton.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
const darkBackground = await body.evaluate((el) => {
|
|
const styles = window.getComputedStyle(el);
|
|
return styles.backgroundImage;
|
|
});
|
|
|
|
// Should have dark theme classes
|
|
expect(darkBackground).not.toBe(lightBackground);
|
|
expect(darkBackground).toContain('gradient');
|
|
});
|
|
|
|
test('should validate class generation performance', async ({ page }) => {
|
|
// Measure time for theme changes
|
|
const startTime = Date.now();
|
|
|
|
// Perform multiple theme changes
|
|
const lightThemeButton = page.locator('button').filter({ hasText: 'light' });
|
|
const darkThemeButton = page.locator('button').filter({ hasText: 'dark' });
|
|
const defaultThemeButton = page.locator('button').filter({ hasText: 'default' });
|
|
|
|
await lightThemeButton.click();
|
|
await page.waitForTimeout(100);
|
|
await darkThemeButton.click();
|
|
await page.waitForTimeout(100);
|
|
await defaultThemeButton.click();
|
|
await page.waitForTimeout(100);
|
|
|
|
const endTime = Date.now();
|
|
const totalTime = endTime - startTime;
|
|
|
|
// Theme changes should be fast (less than 1 second for all changes)
|
|
expect(totalTime).toBeLessThan(1000);
|
|
});
|
|
|
|
test('should handle class conflicts gracefully', async ({ page }) => {
|
|
// Check that conflicting classes are handled properly
|
|
const heroSection = page.locator('section').first();
|
|
|
|
// Get all applied classes
|
|
const allClasses = await heroSection.evaluate((el) => el.className);
|
|
const classArray = allClasses.split(' ');
|
|
|
|
// Check for common conflicts
|
|
const hasConflictingPadding = classArray.some(cls =>
|
|
cls.includes('py-') && classArray.some(other =>
|
|
other.includes('pt-') || other.includes('pb-')
|
|
)
|
|
);
|
|
|
|
// Should not have conflicting padding classes
|
|
expect(hasConflictingPadding).toBe(false);
|
|
|
|
// Check for conflicting margin classes
|
|
const hasConflictingMargin = classArray.some(cls =>
|
|
cls.includes('my-') && classArray.some(other =>
|
|
other.includes('mt-') || other.includes('mb-')
|
|
)
|
|
);
|
|
|
|
// Should not have conflicting margin classes
|
|
expect(hasConflictingMargin).toBe(false);
|
|
});
|
|
});
|
|
|