Enhance screen reader support: Add status updates for video seek actions
This commit is contained in:
parent
cf270ea82c
commit
5692874b10
2 changed files with 396 additions and 0 deletions
|
|
@ -473,6 +473,9 @@
|
|||
const currentTime = AppState.player.currentTime();
|
||||
AppState.player.currentTime(currentTime + 10); // Skip forward 10 seconds
|
||||
showToast('Skipped forward 10s', 1000);
|
||||
if (window.ScreenReader) {
|
||||
ScreenReader.statusUpdate('video', 'skipped forward 10 seconds', 'seek');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Swipe right - seek backward
|
||||
|
|
@ -480,6 +483,9 @@
|
|||
const currentTime = AppState.player.currentTime();
|
||||
AppState.player.currentTime(Math.max(0, currentTime - 10)); // Skip back 10 seconds
|
||||
showToast('Skipped back 10s', 1000);
|
||||
if (window.ScreenReader) {
|
||||
ScreenReader.statusUpdate('video', 'skipped back 10 seconds', 'seek');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
390
screen-reader-test.html
Normal file
390
screen-reader-test.html
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Screen Reader Support Test - Dodgers Stream Theater</title>
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=1.4.2">
|
||||
<style>
|
||||
.test-section {
|
||||
margin-bottom: 2rem;
|
||||
padding: 1rem;
|
||||
border: 2px solid var(--dodgers-blue);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.test-button {
|
||||
margin: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--dodgers-blue);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.test-button:hover {
|
||||
background: var(--dodgers-red);
|
||||
}
|
||||
.test-results {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
background: var(--dodgers-gray-100);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute !important;
|
||||
width: 1px !important;
|
||||
height: 1px !important;
|
||||
padding: 0 !important;
|
||||
margin: -1px !important;
|
||||
overflow: hidden !important;
|
||||
clip: rect(0, 0, 0, 0) !important;
|
||||
white-space: nowrap !important;
|
||||
border: 0 !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="max-width: 1200px; margin: 0 auto; padding: 2rem;">
|
||||
<h1>Screen Reader Support Test Suite</h1>
|
||||
<p>This test validates the ARIA live regions and screen reader announcements implementation for the Dodgers Stream Theater application.</p>
|
||||
|
||||
<!-- Screen Reader Announcement Regions (for testing) -->
|
||||
<div id="sr-stream-announcer" class="sr-only" aria-live="assertive" aria-atomic="true" aria-label="Stream status announcements"></div>
|
||||
<div id="sr-connection-announcer" class="sr-only" aria-live="assertive" aria-atomic="true" aria-label="Connection status announcements"></div>
|
||||
<div id="sr-system-announcer" class="sr-only" aria-live="assertive" aria-atomic="true" aria-label="System announcements"></div>
|
||||
<div id="sr-message-group-announcer" class="sr-only" aria-live="polite" aria-atomic="true" aria-label="Message group announcements"></div>
|
||||
<div id="sr-viewer-announcer" class="sr-only" aria-live="polite" aria-atomic="true" aria-label="Viewer count announcements"></div>
|
||||
<div id="sr-activity-announcer" class="sr-only" aria-live="polite" aria-atomic="true" aria-label="Activity announcements"></div>
|
||||
<div id="sr-form-announcer" class="sr-only" aria-live="assertive" aria-atomic="true" aria-label="Form validation announcements"></div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>🚀 Stream Status Tests</h2>
|
||||
<p>Test announcements for different stream states.</p>
|
||||
<button class="test-button" onclick="testStreamStatus('online')">Stream Goes Online</button>
|
||||
<button class="test-button" onclick="testStreamStatus('offline')">Stream Goes Offline</button>
|
||||
<button class="test-button" onclick="testStreamStatus('reconnecting')">Stream Reconnecting</button>
|
||||
<button class="test-button" onclick="testStreamStatus('quality-changed', '4K')">Quality Changed to 4K</button>
|
||||
<button class="test-button" onclick="testStreamStatus('buffering')">Stream Buffering</button>
|
||||
<div class="test-results" id="stream-test-results">Results will appear here...</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>🌐 Connection Status Tests</h2>
|
||||
<p>Test announcements for connection state changes.</p>
|
||||
<button class="test-button" onclick="testConnectionStatus('connected')">Chat Connected</button>
|
||||
<button class="test-button" onclick="testConnectionStatus('disconnected')">Chat Disconnected</button>
|
||||
<button class="test-button" onclick="testConnectionStatus('reconnecting')">Chat Reconnecting</button>
|
||||
<button class="test-button" onclick="testConnectionStatus('error', 'Network timeout')">Connection Error</button>
|
||||
<div class="test-results" id="connection-test-results">Results will appear here...</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>👥 Viewer Count Tests</h2>
|
||||
<p>Test announcements for viewer count changes (threshold-based to prevent spam).</p>
|
||||
<button class="test-button" onclick="testViewerCount(0)">0 Viewers</button>
|
||||
<button class="test-button" onclick="testViewerCount(1)">1 Viewer</button>
|
||||
<button class="test-button" onclick="testViewerCount(5)">5 Viewers</button>
|
||||
<button class="test-button" onclick="testViewerCount(12)">12 Viewers (should announce)</button>
|
||||
<button class="test-button" onclick="testViewerCount(25)">25 Viewers</button>
|
||||
<div class="test-results" id="viewer-test-results">Results will appear here...</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>⚡ System Message Tests</h2>
|
||||
<p>Test announcements for admin actions and system notifications.</p>
|
||||
<button class="test-button" onclick="testSystemMessage('Message deleted by admin')">Admin Action: Delete Message</button>
|
||||
<button class="test-button" onclick="testSystemMessage('User banned from chat by admin')">Admin Action: Ban User</button>
|
||||
<button class="test-button" onclick="testSystemMessage('Chat was cleared by admin')">Admin Action: Clear Chat</button>
|
||||
<button class="test-button" onclick="testSystemMessage('Server maintenance starting')">System Notification</button>
|
||||
<div class="test-results" id="system-test-results">Results will appear here...</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>💬 Message Group Tests</h2>
|
||||
<p>Test announcements for batch message loading.</p>
|
||||
<button class="test-button" onclick="testMessageGroup(1, 'added')">1 New Message (no announcement)</button>
|
||||
<button class="test-button" onclick="testMessageGroup(5, 'added')">5 New Messages</button>
|
||||
<button class="test-button" onclick="testMessageGroup(15, 'filtered')">15 Messages Filtered</button>
|
||||
<div class="test-results" id="message-group-test-results">Results will appear here...</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>🔧 Form Validation Tests</h2>
|
||||
<p>Test announcements for form validation feedback.</p>
|
||||
<button class="test-button" onclick="testFormValidation('nickname', false, 'Nickname is required')">Nickname Required Error</button>
|
||||
<button class="test-button" onclick="testFormValidation('messageInput', false, 'Message cannot be empty')">Message Empty Error</button>
|
||||
<button class="test-button" onclick="testFormValidation('nickname', true, 'Nickname is valid')">Nickname Success</button>
|
||||
<button class="test-button" onclick="testFormValidation('messageInput', true, 'Message sent successfully')">Form Success</button>
|
||||
<div class="test-results" id="form-test-results">Results will appear here...</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>📊 Activity Feed Tests</h2>
|
||||
<p>Test announcements for activity feed updates.</p>
|
||||
<button class="test-button" onclick="testActivity('joined', 'johndoe')">User Joined: johndoe</button>
|
||||
<button class="test-button" onclick="testActivity('left', 'janedoe')">User Left: janedoe</button>
|
||||
<button class="test-button" onclick="testActivity('admin-action', 'Stream quality updated to HD')">Admin Action</button>
|
||||
<div class="test-results" id="activity-test-results">Results will appear here...</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>⚙️ Status Update Tests</h2>
|
||||
<p>Test announcements for status updates (uptime, quality, etc.).</p>
|
||||
<button class="test-button" onclick="testStatusUpdate('uptime', '05:23')">Uptime: 5 minutes 23 seconds</button>
|
||||
<button class="test-button" onclick="testStatusUpdate('quality', '1080p')">Quality: 1080p</button>
|
||||
<button class="test-button" onclick="testStatusUpdate('buffer', 75)">Buffer: 75%</button>
|
||||
<button class="test-button" onclick="testStatusUpdate('video', 'skipped forward 10 seconds', 'seek')">Video Seek Forward</button>
|
||||
<div class="test-results" id="status-test-results">Results will appear here...</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>🧪 Implementation Status</h2>
|
||||
<div id="implementation-checklist">
|
||||
<h3>ARIA Live Regions Check</h3>
|
||||
<ul>
|
||||
<li id="live-regions-check">✅ Screen reader announcement regions present</li>
|
||||
<li id="assertive-regions-check">✅ Assertive live regions configured</li>
|
||||
<li id="polite-regions-check">✅ Polite live regions configured</li>
|
||||
<li id="sr-only-check">✅ Screen reader only class applied</li>
|
||||
<li id="aria-atomic-check">✅ ARIA atomic attribute set</li>
|
||||
</ul>
|
||||
|
||||
<h3>JavaScript Integration Check</h3>
|
||||
<ul>
|
||||
<li id="module-loaded-check">❓ Screen Reader module loaded</li>
|
||||
<li id="functions-available-check">❓ All announcement functions available</li>
|
||||
<li id="queue-system-check">❓ Announcement queue system functional</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button class="test-button" onclick="runSystemCheck()">Run System Check</button>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>🎯 Test Results Summary</h2>
|
||||
<div id="test-summary">
|
||||
<p>Total Tests Run: <span id="total-tests">0</span></p>
|
||||
<p>Passed: <span id="passed-tests">0</span></p>
|
||||
<p>Failed: <span id="failed-tests">0</span></p>
|
||||
<p>Success Rate: <span id="success-rate">0%</span></p>
|
||||
</div>
|
||||
<button class="test-button" onclick="clearTestResults()">Clear All Results</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Include the screen reader module -->
|
||||
<script defer src="assets/js/app.js?v=1.4.4"></script>
|
||||
<script defer src="assets/js/screen-reader.js?v=1.4.4"></script>
|
||||
<script>
|
||||
// Test state tracking
|
||||
let testResults = {
|
||||
total: 0,
|
||||
passed: 0,
|
||||
failed: 0
|
||||
};
|
||||
|
||||
// DOM ready
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('Screen Reader Test Suite loaded');
|
||||
// Wait for modules to load
|
||||
setTimeout(runSystemCheck, 500);
|
||||
});
|
||||
|
||||
// Test functions
|
||||
function testStreamStatus(status, details) {
|
||||
testResults.total++;
|
||||
try {
|
||||
ScreenReader.streamStatus(status, details);
|
||||
logTestResult('stream-test-results', `Stream ${status}${details ? ` (${details})` : ''} announced`, true);
|
||||
} catch (error) {
|
||||
logTestResult('stream-test-results', `Error: ${error.message}`, false);
|
||||
}
|
||||
}
|
||||
|
||||
function testConnectionStatus(status, details) {
|
||||
testResults.total++;
|
||||
try {
|
||||
ScreenReader.connectionStatus(status, details);
|
||||
logTestResult('connection-test-results', `Connection ${status}${details ? ` (${details})` : ''} announced`, true);
|
||||
} catch (error) {
|
||||
logTestResult('connection-test-results', `Error: ${error.message}`, false);
|
||||
}
|
||||
}
|
||||
|
||||
function testViewerCount(count) {
|
||||
testResults.total++;
|
||||
try {
|
||||
ScreenReader.viewerCount(count);
|
||||
logTestResult('viewer-test-results', `Viewer count ${count} announced`, true);
|
||||
} catch (error) {
|
||||
logTestResult('viewer-test-results', `Error: ${error.message}`, false);
|
||||
}
|
||||
}
|
||||
|
||||
function testSystemMessage(message) {
|
||||
testResults.total++;
|
||||
try {
|
||||
ScreenReader.systemMessage(message);
|
||||
logTestResult('system-test-results', `System message announced: "${message}"`, true);
|
||||
} catch (error) {
|
||||
logTestResult('system-test-results', `Error: ${error.message}`, false);
|
||||
}
|
||||
}
|
||||
|
||||
function testMessageGroup(count, action) {
|
||||
testResults.total++;
|
||||
try {
|
||||
const mockMessages = Array(count).fill({}).map((_, i) => ({ id: i, message: `Message ${i + 1}` }));
|
||||
ScreenReader.messageGroup(mockMessages, action);
|
||||
logTestResult('message-group-test-results', `${count} messages ${action} announced`, true);
|
||||
} catch (error) {
|
||||
logTestResult('message-group-test-results', `Error: ${error.message}`, false);
|
||||
}
|
||||
}
|
||||
|
||||
function testFormValidation(field, isValid, message) {
|
||||
testResults.total++;
|
||||
try {
|
||||
ScreenReader.formValidation(field, isValid, message);
|
||||
logTestResult('form-test-results', `Form validation "${message}" announced`, true);
|
||||
} catch (error) {
|
||||
logTestResult('form-test-results', `Error: ${error.message}`, false);
|
||||
}
|
||||
}
|
||||
|
||||
function testActivity(action, details) {
|
||||
testResults.total++;
|
||||
try {
|
||||
ScreenReader.activity(action, details);
|
||||
logTestResult('activity-test-results', `Activity ${action} with "${details}" announced`, true);
|
||||
} catch (error) {
|
||||
logTestResult('activity-test-results', `Error: ${error.message}`, false);
|
||||
}
|
||||
}
|
||||
|
||||
function testStatusUpdate(type, value, unit) {
|
||||
testResults.total++;
|
||||
try {
|
||||
ScreenReader.statusUpdate(type, value, unit);
|
||||
logTestResult('status-test-results', `Status ${type}: ${value}${unit ? ` ${unit}` : ''} announced`, true);
|
||||
} catch (error) {
|
||||
logTestResult('status-test-results', `Error: ${error.message}`, false);
|
||||
}
|
||||
}
|
||||
|
||||
function logTestResult(elementId, message, success) {
|
||||
const element = document.getElementById(elementId);
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const statusIcon = success ? '✅' : '❌';
|
||||
const resultHTML = `
|
||||
<div style="margin: 0.5rem 0; padding: 0.25rem; border-left: 3px solid ${success ? 'var(--dodgers-blue)' : 'var(--dodgers-red)'}">
|
||||
<strong>${statusIcon} ${timestamp}:</strong> ${message}
|
||||
</div>
|
||||
`;
|
||||
|
||||
element.innerHTML += resultHTML;
|
||||
|
||||
if (success) {
|
||||
testResults.passed++;
|
||||
} else {
|
||||
testResults.failed++;
|
||||
}
|
||||
|
||||
updateTestSummary();
|
||||
}
|
||||
|
||||
function updateTestSummary() {
|
||||
document.getElementById('total-tests').textContent = testResults.total;
|
||||
document.getElementById('passed-tests').textContent = testResults.passed;
|
||||
document.getElementById('failed-tests').textContent = testResults.failed;
|
||||
const rate = testResults.total > 0 ? Math.round((testResults.passed / testResults.total) * 100) : 0;
|
||||
document.getElementById('success-rate').textContent = `${rate}%`;
|
||||
}
|
||||
|
||||
function runSystemCheck() {
|
||||
// Check ARIA regions
|
||||
const regions = ['sr-stream-announcer', 'sr-connection-announcer', 'sr-system-announcer',
|
||||
'sr-message-group-announcer', 'sr-viewer-announcer', 'sr-activity-announcer',
|
||||
'sr-form-announcer'];
|
||||
|
||||
let regionsPresent = 0;
|
||||
let assertiveRegions = 0;
|
||||
let politeRegions = 0;
|
||||
let srOnlyCount = 0;
|
||||
let atomicCount = 0;
|
||||
|
||||
regions.forEach(regionId => {
|
||||
const region = document.getElementById(regionId);
|
||||
if (region) {
|
||||
regionsPresent++;
|
||||
if (region.getAttribute('aria-live') === 'assertive') assertiveRegions++;
|
||||
if (region.getAttribute('aria-live') === 'polite') politeRegions++;
|
||||
if (region.classList.contains('sr-only')) srOnlyCount++;
|
||||
if (region.getAttribute('aria-atomic') === 'true') atomicCount++;
|
||||
}
|
||||
});
|
||||
|
||||
updateChecklistItem('live-regions-check', regionsPresent === 7, `${regionsPresent}/7 regions present`);
|
||||
updateChecklistItem('assertive-regions-check', assertiveRegions >= 3, `${assertiveRegions} assertive regions`);
|
||||
updateChecklistItem('polite-regions-check', politeRegions >= 3, `${politeRegions} polite regions`);
|
||||
updateChecklistItem('sr-only-check', srOnlyCount === 7, `${srOnlyCount}/7 with sr-only class`);
|
||||
updateChecklistItem('aria-atomic-check', atomicCount === 7, `${atomicCount}/7 with aria-atomic`);
|
||||
|
||||
// Check JavaScript availability
|
||||
const hasScreenReader = typeof window.ScreenReader !== 'undefined';
|
||||
updateChecklistItem('module-loaded-check', hasScreenReader, hasScreenReader ? 'Screen Reader module loaded' : 'Module not loaded');
|
||||
|
||||
if (hasScreenReader) {
|
||||
const requiredFunctions = ['streamStatus', 'connectionStatus', 'viewerCount', 'systemMessage',
|
||||
'messageGroup', 'formValidation', 'activity', 'statusUpdate'];
|
||||
let availableFunctions = 0;
|
||||
requiredFunctions.forEach(func => {
|
||||
if (typeof ScreenReader[func] === 'function') availableFunctions++;
|
||||
});
|
||||
|
||||
updateChecklistItem('functions-available-check', availableFunctions === 8, `${availableFunctions}/8 functions available`);
|
||||
|
||||
// Test queue system (we can only test this indirectly)
|
||||
updateChecklistItem('queue-system-check', typeof ScreenReader.queueAnnouncement === 'function', 'Queue system available');
|
||||
}
|
||||
}
|
||||
|
||||
function updateChecklistItem(itemId, success, details) {
|
||||
const item = document.getElementById(itemId);
|
||||
if (item) {
|
||||
const status = success ? '✅' : '❌';
|
||||
item.innerHTML = `${status} ${details}`;
|
||||
}
|
||||
}
|
||||
|
||||
function clearTestResults() {
|
||||
// Clear all test result containers
|
||||
const containers = ['stream-test-results', 'connection-test-results', 'viewer-test-results',
|
||||
'system-test-results', 'message-group-test-results', 'form-test-results',
|
||||
'activity-test-results', 'status-test-results'];
|
||||
|
||||
containers.forEach(containerId => {
|
||||
const container = document.getElementById(containerId);
|
||||
if (container) {
|
||||
container.innerHTML = 'Results will appear here...';
|
||||
}
|
||||
});
|
||||
|
||||
// Reset test counters
|
||||
testResults = { total: 0, passed: 0, failed: 0 };
|
||||
updateTestSummary();
|
||||
}
|
||||
|
||||
// Make functions globally available for button clicks
|
||||
window.testStreamStatus = testStreamStatus;
|
||||
window.testConnectionStatus = testConnectionStatus;
|
||||
window.testViewerCount = testViewerCount;
|
||||
window.testSystemMessage = testSystemMessage;
|
||||
window.testMessageGroup = testMessageGroup;
|
||||
window.testFormValidation = testFormValidation;
|
||||
window.testActivity = testActivity;
|
||||
window.testStatusUpdate = testStatusUpdate;
|
||||
window.runSystemCheck = runSystemCheck;
|
||||
window.clearTestResults = clearTestResults;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue