Spaces:
Sleeping
Sleeping
| /** | |
| * Simple accessibility validation script for asset management components | |
| * This script checks for common accessibility patterns in our components | |
| */ | |
| import fs from 'fs'; | |
| import path from 'path'; | |
| interface AccessibilityCheck { | |
| name: string; | |
| pattern: RegExp; | |
| description: string; | |
| required: boolean; | |
| } | |
| const accessibilityChecks: AccessibilityCheck[] = [ | |
| { | |
| name: 'ARIA Labels', | |
| pattern: /aria-label(ledby)?=/g, | |
| description: 'Components should have proper ARIA labels', | |
| required: true | |
| }, | |
| { | |
| name: 'Screen Reader Text', | |
| pattern: /sr-only|screen-reader/g, | |
| description: 'Important information should be available to screen readers', | |
| required: true | |
| }, | |
| { | |
| name: 'Keyboard Navigation', | |
| pattern: /onKeyDown|tabIndex|role="button"/g, | |
| description: 'Interactive elements should support keyboard navigation', | |
| required: true | |
| }, | |
| { | |
| name: 'Live Regions', | |
| pattern: /aria-live|aria-atomic/g, | |
| description: 'Dynamic content should announce changes to screen readers', | |
| required: true | |
| }, | |
| { | |
| name: 'Focus Management', | |
| pattern: /focus\(\)|useRef.*focus|Focus/g, | |
| description: 'Components should manage focus appropriately', | |
| required: false | |
| }, | |
| { | |
| name: 'Semantic HTML', | |
| pattern: /role="(navigation|dialog|button|searchbox|tablist|tab|tabpanel)"/g, | |
| description: 'Components should use semantic HTML and ARIA roles', | |
| required: true | |
| } | |
| ]; | |
| const responsiveChecks: AccessibilityCheck[] = [ | |
| { | |
| name: 'Mobile Breakpoints', | |
| pattern: /(sm:|md:|lg:|xl:)/g, | |
| description: 'Components should use responsive breakpoints', | |
| required: true | |
| }, | |
| { | |
| name: 'Flexible Layouts', | |
| pattern: /(flex|grid|w-full|min-w-0)/g, | |
| description: 'Components should use flexible layout patterns', | |
| required: true | |
| }, | |
| { | |
| name: 'Touch Targets', | |
| pattern: /(h-8|w-8|h-10|w-10|h-12|w-12|min-h-|min-w-)/g, | |
| description: 'Interactive elements should have adequate touch target sizes', | |
| required: true | |
| }, | |
| { | |
| name: 'Text Wrapping', | |
| pattern: /(break-words|truncate|text-wrap)/g, | |
| description: 'Text should wrap appropriately on small screens', | |
| required: false | |
| } | |
| ]; | |
| function analyzeFile(filePath: string): { accessibility: number; responsive: number; issues: string[] } { | |
| const content = fs.readFileSync(filePath, 'utf-8'); | |
| const issues: string[] = []; | |
| let accessibilityScore = 0; | |
| let responsiveScore = 0; | |
| console.log(`\nπ Analyzing: ${path.relative(process.cwd(), filePath)}`); | |
| // Check accessibility patterns | |
| console.log('\nπ Accessibility Checks:'); | |
| accessibilityChecks.forEach(check => { | |
| const matches = content.match(check.pattern); | |
| const count = matches ? matches.length : 0; | |
| if (count > 0) { | |
| console.log(` β ${check.name}: ${count} instances found`); | |
| accessibilityScore += check.required ? 2 : 1; | |
| } else { | |
| const severity = check.required ? 'β' : 'β οΈ'; | |
| console.log(` ${severity} ${check.name}: Not found`); | |
| if (check.required) { | |
| issues.push(`Missing ${check.name}: ${check.description}`); | |
| } | |
| } | |
| }); | |
| // Check responsive patterns | |
| console.log('\nπ± Responsive Design Checks:'); | |
| responsiveChecks.forEach(check => { | |
| const matches = content.match(check.pattern); | |
| const count = matches ? matches.length : 0; | |
| if (count > 0) { | |
| console.log(` β ${check.name}: ${count} instances found`); | |
| responsiveScore += check.required ? 2 : 1; | |
| } else { | |
| const severity = check.required ? 'β' : 'β οΈ'; | |
| console.log(` ${severity} ${check.name}: Not found`); | |
| if (check.required) { | |
| issues.push(`Missing ${check.name}: ${check.description}`); | |
| } | |
| } | |
| }); | |
| return { accessibility: accessibilityScore, responsive: responsiveScore, issues }; | |
| } | |
| function validateAccessibility() { | |
| console.log('π Asset Management Accessibility Validation\n'); | |
| console.log('=' .repeat(60)); | |
| const componentDir = path.join(process.cwd(), 'src/components/asset-management'); | |
| const files = [ | |
| 'AssetList.tsx', | |
| 'AssetForm.tsx', | |
| 'AssetDetail.tsx', | |
| 'shared/ConfirmDialog.tsx', | |
| 'shared/Pagination.tsx' | |
| ]; | |
| let totalAccessibilityScore = 0; | |
| let totalResponsiveScore = 0; | |
| let totalIssues: string[] = []; | |
| let filesAnalyzed = 0; | |
| files.forEach(file => { | |
| const filePath = path.join(componentDir, file); | |
| if (fs.existsSync(filePath)) { | |
| const result = analyzeFile(filePath); | |
| totalAccessibilityScore += result.accessibility; | |
| totalResponsiveScore += result.responsive; | |
| totalIssues.push(...result.issues); | |
| filesAnalyzed++; | |
| } else { | |
| console.log(`β οΈ File not found: ${file}`); | |
| } | |
| }); | |
| // Calculate scores | |
| const maxAccessibilityScore = filesAnalyzed * accessibilityChecks.filter(c => c.required).length * 2; | |
| const maxResponsiveScore = filesAnalyzed * responsiveChecks.filter(c => c.required).length * 2; | |
| const accessibilityPercentage = Math.round((totalAccessibilityScore / maxAccessibilityScore) * 100); | |
| const responsivePercentage = Math.round((totalResponsiveScore / maxResponsiveScore) * 100); | |
| // Print summary | |
| console.log('\n' + '=' .repeat(60)); | |
| console.log('π SUMMARY REPORT'); | |
| console.log('=' .repeat(60)); | |
| console.log(`\nπ Accessibility Score: ${accessibilityPercentage}% (${totalAccessibilityScore}/${maxAccessibilityScore})`); | |
| console.log(`π± Responsive Score: ${responsivePercentage}% (${totalResponsiveScore}/${maxResponsiveScore})`); | |
| console.log(`π Files Analyzed: ${filesAnalyzed}`); | |
| console.log(`β οΈ Total Issues: ${totalIssues.length}`); | |
| if (totalIssues.length > 0) { | |
| console.log('\nπ§ Issues to Address:'); | |
| totalIssues.forEach((issue, index) => { | |
| console.log(` ${index + 1}. ${issue}`); | |
| }); | |
| } | |
| // Grade the implementation | |
| const overallScore = (accessibilityPercentage + responsivePercentage) / 2; | |
| let grade = 'F'; | |
| let emoji = 'β'; | |
| if (overallScore >= 90) { | |
| grade = 'A'; | |
| emoji = 'π'; | |
| } else if (overallScore >= 80) { | |
| grade = 'B'; | |
| emoji = 'β '; | |
| } else if (overallScore >= 70) { | |
| grade = 'C'; | |
| emoji = 'β οΈ'; | |
| } else if (overallScore >= 60) { | |
| grade = 'D'; | |
| emoji = 'πΆ'; | |
| } | |
| console.log(`\n${emoji} Overall Grade: ${grade} (${Math.round(overallScore)}%)`); | |
| if (overallScore >= 80) { | |
| console.log('\nπ Great job! Your components have excellent accessibility and responsive design.'); | |
| } else if (overallScore >= 60) { | |
| console.log('\nπ Good progress! Consider addressing the issues above to improve accessibility.'); | |
| } else { | |
| console.log('\nπ§ More work needed. Focus on the required accessibility patterns.'); | |
| } | |
| console.log('\n' + '=' .repeat(60)); | |
| } | |
| // Run the validation | |
| validateAccessibility(); |