migration wip
This commit is contained in:
326
lib/responsive-test.ts
Normal file
326
lib/responsive-test.ts
Normal file
@@ -0,0 +1,326 @@
|
||||
/**
|
||||
* Responsive Testing Utilities for KLZ Cables
|
||||
* Tools for testing and validating responsive design
|
||||
*/
|
||||
|
||||
import { BREAKPOINTS, getViewport, Viewport } from './responsive';
|
||||
|
||||
// Test viewport configurations
|
||||
export const TEST_VIEWPORTS = {
|
||||
mobile: {
|
||||
width: 375,
|
||||
height: 667,
|
||||
name: 'Mobile (iPhone SE)',
|
||||
breakpoint: 'xs',
|
||||
},
|
||||
mobileLarge: {
|
||||
width: 414,
|
||||
height: 896,
|
||||
name: 'Mobile Large (iPhone 11)',
|
||||
breakpoint: 'sm',
|
||||
},
|
||||
tablet: {
|
||||
width: 768,
|
||||
height: 1024,
|
||||
name: 'Tablet (iPad)',
|
||||
breakpoint: 'md',
|
||||
},
|
||||
tabletLandscape: {
|
||||
width: 1024,
|
||||
height: 768,
|
||||
name: 'Tablet Landscape',
|
||||
breakpoint: 'lg',
|
||||
},
|
||||
desktop: {
|
||||
width: 1280,
|
||||
height: 800,
|
||||
name: 'Desktop (Laptop)',
|
||||
breakpoint: 'xl',
|
||||
},
|
||||
desktopLarge: {
|
||||
width: 1440,
|
||||
height: 900,
|
||||
name: 'Desktop Large',
|
||||
breakpoint: '2xl',
|
||||
},
|
||||
desktopWide: {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
name: 'Desktop Wide (Full HD)',
|
||||
breakpoint: '3xl',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Responsive Design Checklist
|
||||
* Comprehensive checklist for validating responsive design
|
||||
*/
|
||||
export const RESPONSIVE_CHECKLIST = {
|
||||
layout: [
|
||||
'Content stacks properly on mobile (1 column)',
|
||||
'Grid layouts adapt to screen size (2-4 columns)',
|
||||
'No horizontal scrolling at any breakpoint',
|
||||
'Content remains within safe areas',
|
||||
'Padding and margins scale appropriately',
|
||||
],
|
||||
typography: [
|
||||
'Text remains readable at all sizes',
|
||||
'Line height is optimized for mobile',
|
||||
'Headings scale appropriately',
|
||||
'No text overflow or clipping',
|
||||
'Font size meets WCAG guidelines (16px minimum)',
|
||||
],
|
||||
navigation: [
|
||||
'Mobile menu is accessible (44px touch targets)',
|
||||
'Desktop navigation hides on mobile',
|
||||
'Menu items are properly spaced',
|
||||
'Active states are visible',
|
||||
'Back/forward navigation works',
|
||||
],
|
||||
images: [
|
||||
'Images load with appropriate sizes',
|
||||
'Aspect ratios are maintained',
|
||||
'No layout shift during loading',
|
||||
'Lazy loading works correctly',
|
||||
'Placeholder blur is applied',
|
||||
],
|
||||
forms: [
|
||||
'Input fields are 44px minimum touch target',
|
||||
'Labels remain visible',
|
||||
'Error messages are readable',
|
||||
'Form submits on mobile',
|
||||
'Keyboard navigation works',
|
||||
],
|
||||
performance: [
|
||||
'Images are properly sized for viewport',
|
||||
'No unnecessary large assets on mobile',
|
||||
'Critical CSS is loaded',
|
||||
'Touch interactions are smooth',
|
||||
'No layout thrashing',
|
||||
],
|
||||
accessibility: [
|
||||
'Touch targets are 44px minimum',
|
||||
'Focus indicators are visible',
|
||||
'Screen readers work correctly',
|
||||
'Color contrast meets WCAG AA',
|
||||
'Zoom is not restricted',
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate responsive design report
|
||||
*/
|
||||
export function generateResponsiveReport(): string {
|
||||
const viewport = getViewport();
|
||||
|
||||
const report = `
|
||||
Responsive Design Report - KLZ Cables
|
||||
=====================================
|
||||
|
||||
Current Viewport:
|
||||
- Width: ${viewport.width}px
|
||||
- Height: ${viewport.height}px
|
||||
- Breakpoint: ${viewport.breakpoint}
|
||||
- Device Type: ${viewport.isMobile ? 'Mobile' : viewport.isTablet ? 'Tablet' : 'Desktop'}
|
||||
|
||||
Breakpoint Configuration:
|
||||
- xs: ${BREAKPOINTS.xs}px
|
||||
- sm: ${BREAKPOINTS.sm}px
|
||||
- md: ${BREAKPOINTS.md}px
|
||||
- lg: ${BREAKPOINTS.lg}px
|
||||
- xl: ${BREAKPOINTS.xl}px
|
||||
- 2xl: ${BREAKPOINTS['2xl']}px
|
||||
- 3xl: ${BREAKPOINTS['3xl']}px
|
||||
|
||||
Touch Target Verification:
|
||||
- Minimum: 44px × 44px
|
||||
- Recommended: 48px × 48px
|
||||
- Large: 56px × 56px
|
||||
|
||||
Image Optimization:
|
||||
- Mobile Quality: 75%
|
||||
- Tablet Quality: 85%
|
||||
- Desktop Quality: 90%
|
||||
|
||||
Typography Scale:
|
||||
- Fluid typography using CSS clamp()
|
||||
- Mobile: 16px base
|
||||
- Desktop: 18px base
|
||||
- Line height: 1.4-1.6
|
||||
|
||||
Generated: ${new Date().toISOString()}
|
||||
`.trim();
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate responsive design rules
|
||||
*/
|
||||
export function validateResponsiveDesign(): {
|
||||
passed: boolean;
|
||||
warnings: string[];
|
||||
errors: string[];
|
||||
} {
|
||||
const warnings: string[] = [];
|
||||
const errors: string[] = [];
|
||||
|
||||
// Check viewport
|
||||
if (typeof window === 'undefined') {
|
||||
warnings.push('Server-side rendering detected - some checks skipped');
|
||||
}
|
||||
|
||||
// Check minimum touch target size
|
||||
const buttons = document.querySelectorAll('button, a');
|
||||
buttons.forEach((el) => {
|
||||
const rect = el.getBoundingClientRect();
|
||||
if (rect.width < 44 || rect.height < 44) {
|
||||
warnings.push(`Element ${el.tagName} has touch target < 44px`);
|
||||
}
|
||||
});
|
||||
|
||||
// Check for horizontal scroll
|
||||
if (document.body.scrollWidth > window.innerWidth) {
|
||||
errors.push('Horizontal scrolling detected');
|
||||
}
|
||||
|
||||
// Check text size
|
||||
const textElements = document.querySelectorAll('p, span, div');
|
||||
textElements.forEach((el) => {
|
||||
const computed = window.getComputedStyle(el);
|
||||
const fontSize = parseFloat(computed.fontSize);
|
||||
if (fontSize < 16 && el.textContent && el.textContent.length > 50) {
|
||||
warnings.push(`Text element ${el.tagName} has font-size < 16px`);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
passed: errors.length === 0,
|
||||
warnings,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsive design utilities for testing
|
||||
*/
|
||||
export const ResponsiveTestUtils = {
|
||||
// Set viewport for testing
|
||||
setViewport: (width: number, height: number) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.innerWidth = width;
|
||||
window.innerHeight = height;
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}
|
||||
},
|
||||
|
||||
// Simulate mobile viewport
|
||||
simulateMobile: () => {
|
||||
ResponsiveTestUtils.setViewport(375, 667);
|
||||
},
|
||||
|
||||
// Simulate tablet viewport
|
||||
simulateTablet: () => {
|
||||
ResponsiveTestUtils.setViewport(768, 1024);
|
||||
},
|
||||
|
||||
// Simulate desktop viewport
|
||||
simulateDesktop: () => {
|
||||
ResponsiveTestUtils.setViewport(1280, 800);
|
||||
},
|
||||
|
||||
// Check if element is in viewport
|
||||
isElementInViewport: (element: HTMLElement, offset = 0): boolean => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return (
|
||||
rect.top >= -offset &&
|
||||
rect.left >= -offset &&
|
||||
rect.bottom <= (window.innerHeight + offset) &&
|
||||
rect.right <= (window.innerWidth + offset)
|
||||
);
|
||||
},
|
||||
|
||||
// Measure touch target size
|
||||
measureTouchTarget: (element: HTMLElement): { width: number; height: number; valid: boolean } => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
valid: rect.width >= 44 && rect.height >= 44,
|
||||
};
|
||||
},
|
||||
|
||||
// Check text readability
|
||||
checkTextReadability: (element: HTMLElement): { fontSize: number; lineHeight: number; valid: boolean } => {
|
||||
const computed = window.getComputedStyle(element);
|
||||
const fontSize = parseFloat(computed.fontSize);
|
||||
const lineHeight = parseFloat(computed.lineHeight);
|
||||
|
||||
return {
|
||||
fontSize,
|
||||
lineHeight,
|
||||
valid: fontSize >= 16 && lineHeight >= 1.4,
|
||||
};
|
||||
},
|
||||
|
||||
// Generate responsive test report
|
||||
generateTestReport: () => {
|
||||
const viewport = getViewport();
|
||||
const validation = validateResponsiveDesign();
|
||||
|
||||
return {
|
||||
viewport,
|
||||
validation,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Responsive design patterns for common scenarios
|
||||
*/
|
||||
export const RESPONSIVE_PATTERNS = {
|
||||
// Mobile-first card grid
|
||||
cardGrid: {
|
||||
mobile: { columns: 1, gap: '1rem' },
|
||||
tablet: { columns: 2, gap: '1.5rem' },
|
||||
desktop: { columns: 3, gap: '2rem' },
|
||||
},
|
||||
|
||||
// Hero section
|
||||
hero: {
|
||||
mobile: { layout: 'stacked', padding: '2rem 1rem' },
|
||||
tablet: { layout: 'split', padding: '3rem 2rem' },
|
||||
desktop: { layout: 'split', padding: '4rem 3rem' },
|
||||
},
|
||||
|
||||
// Form layout
|
||||
form: {
|
||||
mobile: { columns: 1, fieldWidth: '100%' },
|
||||
tablet: { columns: 2, fieldWidth: '48%' },
|
||||
desktop: { columns: 2, fieldWidth: '48%' },
|
||||
},
|
||||
|
||||
// Navigation
|
||||
navigation: {
|
||||
mobile: { type: 'hamburger', itemsPerScreen: 6 },
|
||||
tablet: { type: 'hybrid', itemsPerScreen: 8 },
|
||||
desktop: { type: 'full', itemsPerScreen: 12 },
|
||||
},
|
||||
|
||||
// Image gallery
|
||||
gallery: {
|
||||
mobile: { columns: 1, aspectRatio: '4:3' },
|
||||
tablet: { columns: 2, aspectRatio: '1:1' },
|
||||
desktop: { columns: 3, aspectRatio: '16:9' },
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
TEST_VIEWPORTS,
|
||||
RESPONSIVE_CHECKLIST,
|
||||
generateResponsiveReport,
|
||||
validateResponsiveDesign,
|
||||
ResponsiveTestUtils,
|
||||
RESPONSIVE_PATTERNS,
|
||||
};
|
||||
Reference in New Issue
Block a user