326 lines
8.0 KiB
TypeScript
326 lines
8.0 KiB
TypeScript
/**
|
||
* 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,
|
||
}; |