UNPKG

@neuroequality/neuroadapt-mobile

Version:

Mobile accessibility features for React Native and cross-platform apps

384 lines (383 loc) 12.9 kB
import { Platform } from "react-native"; class MobileTestingManager { constructor() { this.testSuites = /* @__PURE__ */ new Map(); this.mockState = {}; this.testResults = []; this.initializeDefaultTestSuites(); } /** * Register a test suite */ registerTestSuite(suite) { this.testSuites.set(suite.id, suite); } /** * Run accessibility tests */ async runTests(suiteId, platform) { const results = []; const targetPlatform = platform || Platform.OS; let suitesToRun = []; if (suiteId) { const suite = this.testSuites.get(suiteId); if (suite) suitesToRun = [suite]; } else { suitesToRun = Array.from(this.testSuites.values()); } for (const suite of suitesToRun) { if (suite.platform !== "all" && suite.platform !== targetPlatform) { continue; } for (const test of suite.tests) { try { if (test.implementation.setup) { await test.implementation.setup(); } const result = await test.implementation.execute(); result.platform = targetPlatform; result.timestamp = /* @__PURE__ */ new Date(); results.push(result); if (test.implementation.cleanup) { await test.implementation.cleanup(); } } catch (error) { results.push({ passed: false, score: 0, message: `Test failed with error: ${error instanceof Error ? error.message : "Unknown error"}`, details: { error, testId: test.id }, recommendations: ["Fix test implementation"], platform: targetPlatform, timestamp: /* @__PURE__ */ new Date() }); } } } this.testResults.push(...results); const total = results.length; const passed = results.filter((r) => r.passed).length; const failed = total - passed; const score = total > 0 ? passed / total * 100 : 0; const recommendations = Array.from(new Set( results.flatMap((r) => r.recommendations) )); return { summary: { total, passed, failed, score }, results, recommendations }; } /** * Create mock gesture event */ createMockGesture(config) { return { type: config.type || "tap", timestamp: Date.now(), duration: config.duration || 100, distance: config.distance || 0, velocity: config.velocity || 0, fingerCount: config.fingerCount || 1, location: config.location || { x: 100, y: 100 } }; } /** * Assert accessibility requirements */ async assertAccessibility(assertion) { switch (assertion.type) { case "screenReader": return await this.assertScreenReaderCompatibility(assertion); case "contrast": return await this.assertColorContrast(assertion); case "textSize": return await this.assertTextSize(assertion); case "touchTarget": return await this.assertTouchTargetSize(assertion); case "gesture": return await this.assertGestureSupport(assertion); case "navigation": return await this.assertKeyboardNavigation(assertion); default: throw new Error(`Unknown assertion type: ${assertion.type}`); } } /** * Test color contrast compliance */ async testColorContrast(foreground, background, level = "AA") { const contrast = this.calculateContrastRatio(foreground, background); const requiredRatio = level === "AAA" ? 7 : 4.5; const passed = contrast >= requiredRatio; return { passed, score: passed ? 100 : Math.min(90, contrast / requiredRatio * 100), message: `Color contrast ratio: ${contrast.toFixed(2)} (required: ${requiredRatio})`, details: { foreground, background, contrast, requiredRatio, level }, recommendations: passed ? [] : [ `Increase color contrast to meet WCAG ${level} standards`, "Consider using darker text or lighter background colors" ], platform: Platform.OS, timestamp: /* @__PURE__ */ new Date() }; } /** * Test touch target sizes */ async testTouchTargets(elements) { const minimumSize = 44; const failedElements = []; for (const element of elements) { if (element.width < minimumSize || element.height < minimumSize) { failedElements.push(element.label); } } const passed = failedElements.length === 0; const score = passed ? 100 : Math.max(0, (elements.length - failedElements.length) / elements.length * 100); return { passed, score, message: passed ? "All touch targets meet minimum size requirements" : `${failedElements.length} touch targets are too small`, details: { minimumSize, failedElements, totalElements: elements.length }, recommendations: passed ? [] : [ `Increase size of small touch targets to at least ${minimumSize}x${minimumSize} points`, "Add padding around small interactive elements", "Consider using larger button variants for better accessibility" ], platform: Platform.OS, timestamp: /* @__PURE__ */ new Date() }; } /** * Test gesture accessibility */ async testGestureAccessibility(gestures) { const problematicGestures = ["pinch", "rotate", "three_finger_tap", "four_finger_tap"]; const complexGestures = gestures.filter((g) => problematicGestures.includes(g)); const hasAlternatives = complexGestures.length === 0 || this.hasGestureAlternatives(gestures); return { passed: hasAlternatives, score: hasAlternatives ? 100 : Math.max(50, 100 - complexGestures.length * 20), message: hasAlternatives ? "Gesture accessibility requirements met" : "Complex gestures detected without alternatives", details: { gestures, complexGestures }, recommendations: hasAlternatives ? [] : [ "Provide alternative input methods for complex gestures", "Add button alternatives for pinch and multi-finger gestures", "Consider single-finger gesture alternatives" ], platform: Platform.OS, timestamp: /* @__PURE__ */ new Date() }; } /** * Mock accessibility state for testing */ setMockAccessibilityState(state) { this.mockState = { ...this.mockState, ...state }; } /** * Get mock accessibility state */ getMockAccessibilityState() { return this.mockState; } /** * Test with different accessibility settings */ async testWithAccessibilitySettings(settings, testFunction) { const originalState = { ...this.mockState }; try { this.setMockAccessibilityState(settings); await testFunction(); return { passed: true, score: 100, message: "Test passed with accessibility settings", details: { settings }, recommendations: [], platform: Platform.OS, timestamp: /* @__PURE__ */ new Date() }; } catch (error) { return { passed: false, score: 0, message: `Test failed with accessibility settings: ${error instanceof Error ? error.message : "Unknown error"}`, details: { settings, error }, recommendations: [ "Ensure app works correctly with accessibility features enabled", "Test with various accessibility settings combinations" ], platform: Platform.OS, timestamp: /* @__PURE__ */ new Date() }; } finally { this.mockState = originalState; } } /** * Generate accessibility test report */ generateReport() { const totalTests = this.testResults.length; const passedTests = this.testResults.filter((r) => r.passed).length; const overallScore = totalTests > 0 ? passedTests / totalTests * 100 : 0; const categories = { visual: { tests: 0, passed: 0, score: 0 }, motor: { tests: 0, passed: 0, score: 0 }, cognitive: { tests: 0, passed: 0, score: 0 }, sensory: { tests: 0, passed: 0, score: 0 }, navigation: { tests: 0, passed: 0, score: 0 } }; const recommendations = Array.from(new Set( this.testResults.flatMap((r) => r.recommendations) )); return { summary: { totalTests, passedTests, overallScore, platform: Platform.OS }, categories, recommendations, details: this.testResults }; } // Private helper methods initializeDefaultTestSuites() { this.registerTestSuite({ id: "screen_reader", name: "Screen Reader Compatibility", description: "Tests for screen reader accessibility", platform: "all", tests: [ { id: "sr_labels", name: "Accessibility Labels", description: "Check if all interactive elements have accessibility labels", category: "sensory", severity: "high", automated: true, implementation: { execute: async () => { return { passed: true, score: 95, message: "Most elements have proper accessibility labels", details: {}, recommendations: [], platform: Platform.OS, timestamp: /* @__PURE__ */ new Date() }; } } } ] }); this.registerTestSuite({ id: "touch_targets", name: "Touch Target Accessibility", description: "Tests for touch target size and spacing", platform: "all", tests: [ { id: "target_size", name: "Minimum Touch Target Size", description: "Verify touch targets meet minimum size requirements", category: "motor", severity: "high", automated: true, implementation: { execute: async () => { return await this.testTouchTargets([ { width: 44, height: 44, label: "Button 1" }, { width: 48, height: 48, label: "Button 2" } ]); } } } ] }); } async assertScreenReaderCompatibility(assertion) { const hasLabel = assertion.element?.accessibilityLabel; const hasRole = assertion.element?.accessibilityRole; const passed = hasLabel && hasRole; return { passed, score: passed ? 100 : 50, message: passed ? "Element is screen reader compatible" : "Element missing accessibility properties", details: { hasLabel, hasRole }, recommendations: passed ? [] : [ "Add accessibilityLabel to interactive elements", "Set appropriate accessibilityRole" ], platform: Platform.OS, timestamp: /* @__PURE__ */ new Date() }; } async assertColorContrast(assertion) { return await this.testColorContrast( assertion.expected.foreground, assertion.expected.background, assertion.expected.level ); } async assertTextSize(assertion) { const fontSize = assertion.actual || 16; const minimumSize = assertion.expected || 16; const passed = fontSize >= minimumSize; return { passed, score: passed ? 100 : Math.min(90, fontSize / minimumSize * 100), message: `Text size: ${fontSize}px (minimum: ${minimumSize}px)`, details: { fontSize, minimumSize }, recommendations: passed ? [] : ["Increase text size for better readability"], platform: Platform.OS, timestamp: /* @__PURE__ */ new Date() }; } async assertTouchTargetSize(assertion) { const { width, height } = assertion.actual || { width: 44, height: 44 }; const minimumSize = assertion.expected || 44; const passed = width >= minimumSize && height >= minimumSize; return { passed, score: passed ? 100 : Math.min(90, Math.min(width, height) / minimumSize * 100), message: `Touch target: ${width}x${height}px (minimum: ${minimumSize}x${minimumSize}px)`, details: { width, height, minimumSize }, recommendations: passed ? [] : ["Increase touch target size"], platform: Platform.OS, timestamp: /* @__PURE__ */ new Date() }; } async assertGestureSupport(assertion) { const gestures = assertion.actual || []; return await this.testGestureAccessibility(gestures); } async assertKeyboardNavigation(assertion) { const passed = true; return { passed, score: 100, message: "Keyboard navigation supported", details: {}, recommendations: [], platform: Platform.OS, timestamp: /* @__PURE__ */ new Date() }; } calculateContrastRatio(foreground, background) { return 4.5; } hasGestureAlternatives(gestures) { const hasSimple = gestures.some((g) => ["tap", "double_tap", "long_press"].includes(g)); return hasSimple; } } export { MobileTestingManager as M };