/** * Accessibility Report Generator * Generates downloadable reports in various formats */ import type { AccessibilityReport } from '../../Button/accessibility/ButtonAccessibilityReport' export type ReportFormat = 'json' | 'markdown' | 'html' | 'pdf' type ReportGenerationOptions = { format: ReportFormat includeTestResults?: boolean includeRecommendations?: boolean } /** * Generate accessibility report in specified format */ export function generateAccessibilityReport( report: AccessibilityReport, options: ReportGenerationOptions = { format: 'markdown', includeTestResults: true, includeRecommendations: true, } ): string { switch (options.format) { case 'json': return generateJSONReport(report, options) case 'markdown': return generateMarkdownReport(report, options) case 'html': return generateHTMLReport(report, options) default: return generateMarkdownReport(report, options) } } /** * Generate JSON report */ function generateJSONReport( report: AccessibilityReport, options: ReportGenerationOptions ): string { const { ...reportWithoutRecommendations } = report const reportData = { ...(options.includeRecommendations ? report : reportWithoutRecommendations), criteria: options.includeTestResults ? report.criteria : report.criteria.map(({ ...rest }) => rest), } return JSON.stringify(reportData, null, 2) } /** * Generate Markdown report */ function generateMarkdownReport( report: AccessibilityReport, options: ReportGenerationOptions ): string { const statusEmoji = { compliant: '✅', 'non-compliant': '❌', unsure: '⚠️', 'not-applicable': '➖', } const levelBadge = { A: '[Level A]', AA: '[Level AA]', AAA: '[Level AAA]', } let markdown = `# ${report.componentName} Component Accessibility Report\n\n` markdown += `**WCAG Version:** ${report.wcagVersion} \n` markdown += `**Report Date:** ${report.reportDate} \n` markdown += `**Conformance Level:** ${report.conformanceLevel} \n` markdown += `**Overall Status:** ${report.overallStatus.toUpperCase()} \n\n` markdown += `## WCAG Guidelines Coverage\n\n` markdown += `This report evaluates criteria from WCAG 2.0, 2.1, and 2.2:\n\n` markdown += `### WCAG 2.0 (2008)\n` report.wcagVersions['2.0'].forEach((item) => { markdown += `- ${item}\n` }) markdown += `\n### WCAG 2.1 (2018)\n` report.wcagVersions['2.1'].forEach((item) => { markdown += `- ${item}\n` }) markdown += `\n### WCAG 2.2 (2023)\n` report.wcagVersions['2.2'].forEach((item) => { markdown += `- ${item}\n` }) markdown += `\n` markdown += `## Executive Summary\n\n` markdown += `${report.summary}\n\n` markdown += `## WCAG Success Criteria\n\n` const criteriaByLevel = { A: report.criteria.filter((c) => c.level === 'A'), AA: report.criteria.filter((c) => c.level === 'AA'), AAA: report.criteria.filter((c) => c.level === 'AAA'), } for (const [level, criteria] of Object.entries(criteriaByLevel)) { if (criteria.length === 0) continue markdown += `### Level ${level} Criteria\n\n` for (const criterion of criteria) { markdown += `### ${statusEmoji[criterion.status]} ${criterion.id} ${criterion.title} ${levelBadge[criterion.level]}\n\n` markdown += `**Status:** ${criterion.status.toUpperCase()} \n\n` markdown += `**Description:**\n${criterion.description}\n\n` markdown += `**Implementation:**\n${criterion.implementation}\n\n` if (options.includeTestResults && criterion.testResults) { markdown += `**Test Results:**\n${criterion.testResults}\n\n` } if (criterion.notes) { markdown += `**Notes:**\n${criterion.notes}\n\n` } markdown += `---\n\n` } } if (options.includeRecommendations && report.recommendations.length > 0) { markdown += `## Recommendations\n\n` for (const rec of report.recommendations) { markdown += `- ${rec}\n` } markdown += `\n` } markdown += `## Strengths\n\n` for (const strength of report.strengths) { markdown += `- ${strength}\n` } markdown += `\n` markdown += `## Test Methodology\n\n` markdown += `### Automated Testing\n\n` for (const test of report.testMethodology.automated) { markdown += `- ${test}\n` } markdown += `\n### Manual Testing\n\n` for (const test of report.testMethodology.manual) { markdown += `- ${test}\n` } markdown += `\n` const compliantCount = report.criteria.filter( (c) => c.status === 'compliant' ).length const totalCount = report.criteria.filter( (c) => c.status !== 'not-applicable' ).length const unsureCount = report.criteria.filter( (c) => c.status === 'unsure' ).length markdown += `## Summary Statistics\n\n` markdown += `- **Total Criteria:** ${report.criteria.length}\n` markdown += `- **Compliant:** ${compliantCount}\n` markdown += `- **Non-Compliant:** ${report.criteria.filter((c) => c.status === 'non-compliant').length}\n` markdown += `- **Unsure:** ${unsureCount}\n` markdown += `- **Not Applicable:** ${report.criteria.filter((c) => c.status === 'not-applicable').length}\n` markdown += `- **Compliance Rate:** ${totalCount > 0 ? Math.round((compliantCount / totalCount) * 100) : 0}%\n\n` if (unsureCount > 0) { markdown += `⚠️ **Note:** ${unsureCount} criterion/criteria marked as "unsure" require manual verification in application context.\n\n` } markdown += `---\n\n` markdown += `*Report generated on ${new Date().toLocaleString()}*\n` return markdown } /** * Generate HTML report */ function generateHTMLReport( report: AccessibilityReport, options: ReportGenerationOptions ): string { const statusColors: Record< AccessibilityReport['overallStatus'] | 'not-applicable', string > = { compliant: '#10b981', 'non-compliant': '#ef4444', unsure: '#f59e0b', partial: '#3b82f6', 'not-applicable': '#6b7280', } const statusEmoji = { compliant: '✅', 'non-compliant': '❌', unsure: '⚠️', 'not-applicable': '➖', } let html = `
This report evaluates criteria from WCAG 2.0, 2.1, and 2.2:
Foundation guidelines
Current standard
Latest updates
${report.summary}
Status: ${criterion.status.toUpperCase()}
Description: ${criterion.description}
Implementation: ${criterion.implementation}
${options.includeTestResults && criterion.testResults ? `Test Results: ${criterion.testResults}
` : ''} ${criterion.notes ? `Notes: ${criterion.notes}
` : ''}Level A Criteria: ${report.testMethodology.wcagLevels.A.join(', ')}
Level AA Criteria: ${report.testMethodology.wcagLevels.AA.join(', ')}
Level AAA Criteria: ${report.testMethodology.wcagLevels.AAA.join(', ')}
${ report.criteria.filter((c) => c.status === 'unsure').length > 0 ? `Report generated on ${new Date().toLocaleString()}
` return html } /** * Download report as file */ export function downloadReport( content: string, filename: string, mimeType: string ): void { try { // Create blob with proper encoding const blob = new Blob([content], { type: mimeType + ';charset=utf-8', }) const url = URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = filename link.style.display = 'none' document.body.appendChild(link) link.click() // Cleanup setTimeout(() => { document.body.removeChild(link) URL.revokeObjectURL(url) }, 100) } catch (error) { console.error('Download error:', error) // Fallback: open in new window for HTML if (mimeType === 'text/html') { const newWindow = window.open() if (newWindow) { newWindow.document.write(content) newWindow.document.close() } } else { throw error } } } /** * Get MIME type for report format */ export function getMimeType(format: ReportFormat): string { switch (format) { case 'json': return 'application/json' case 'markdown': return 'text/markdown' case 'html': return 'text/html' case 'pdf': return 'application/pdf' default: return 'text/plain' } } /** * Get file extension for report format */ export function getFileExtension(format: ReportFormat): string { switch (format) { case 'json': return 'json' case 'markdown': return 'md' case 'html': return 'html' case 'pdf': return 'pdf' default: return 'txt' } }