@neuroequality/neuroadapt-mobile
Version:
Mobile accessibility features for React Native and cross-platform apps
384 lines (383 loc) • 12.9 kB
JavaScript
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
};