feat(a11y): message semantics for screen readers (P3-4)
- Each message is role="article"; collapsed messages (consecutive from one sender) now carry an aria-label with sender + time — previously a screen reader heard only the body with no attribution (the biggest a11y gap). Pure messageAriaLabel() reuses the existing time utils (+3 tests). - Editing a message announces "Editing message from <sender>" (ariaLabel threaded MessageEditor → CustomEditor; the main composer is unaffected). - System emoji get role="img" + aria-label from the shortcode; custom emoticons always have an accessible name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import dayjs from 'dayjs';
|
||||
import { messageAriaLabel } from './a11y';
|
||||
import { timeDayMonthYear, timeHourMinute } from './time';
|
||||
|
||||
test('messageAriaLabel composes sender, date and time (24h)', () => {
|
||||
const ts = dayjs('2026-07-01T14:30:00').valueOf();
|
||||
assert.equal(
|
||||
messageAriaLabel('Alice', ts, true),
|
||||
`Alice, ${timeDayMonthYear(ts)} ${timeHourMinute(ts, true)}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('messageAriaLabel honours the 12-hour clock preference', () => {
|
||||
const ts = dayjs('2026-07-01T14:30:00').valueOf();
|
||||
assert.equal(
|
||||
messageAriaLabel('Bob', ts, false),
|
||||
`Bob, ${timeDayMonthYear(ts)} ${timeHourMinute(ts, false)}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('messageAriaLabel keeps the sender name verbatim as plain text', () => {
|
||||
const ts = dayjs('2026-07-01T09:05:00').valueOf();
|
||||
const label = messageAriaLabel('@user:example.org', ts, true);
|
||||
assert.ok(label.startsWith('@user:example.org, '));
|
||||
assert.ok(!label.includes('<'));
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { timeDayMonthYear, timeHourMinute } from './time';
|
||||
|
||||
/**
|
||||
* Builds a plain-text accessible label for a message row, used when the
|
||||
* visible sender/timestamp header is collapsed and therefore hidden from
|
||||
* assistive technology.
|
||||
*
|
||||
* @param sender - Sender display name (already resolved to a human string).
|
||||
* @param ts - Message origin timestamp in milliseconds.
|
||||
* @param hour24Clock - Whether to format the time using a 24-hour clock.
|
||||
* @returns A label such as `Alice, 1 July 2026 14:30`.
|
||||
*/
|
||||
export const messageAriaLabel = (sender: string, ts: number, hour24Clock: boolean): string =>
|
||||
`${sender}, ${timeDayMonthYear(ts)} ${timeHourMinute(ts, hour24Clock)}`;
|
||||
Reference in New Issue
Block a user