feat(web): alphabet rail always shows #/A-Z/& with disabled empty buckets
test-web / test (push) Successful in 33s
test-web / test (push) Successful in 33s
Operator: prior rail only emitted buttons for letters with items, so
jumping to a letter required scrolling to that section first — not
useful as a navigation tool. Rail now always renders the full set so
the page reads as a stable A-Z reference regardless of which letters
are present.
- New bucketFor() classifies the first character into '#' (digits),
'A'-'Z' (letters), or '&' (everything else). Anchor ids switch
from per-letter to per-bucket: alpha-#, alpha-A, ..., alpha-&.
- ALPHABET + railEntries hardcode the full # / A-Z / & order.
- Buttons for empty buckets render disabled with a low-opacity tint
+ cursor:default so they read as 'no entries here' rather than
broken jumps. aria-label changes too — 'Jump to A' (enabled) vs
'C — no entries' (disabled) so screen readers announce state.
- # / & get verbose labels ('numbers'/'symbols') because the bare
glyph isn't readable.
Tests rewritten — 4 cases: full-rail-with-disabled-buckets,
DOM-order, populated-bucket ids, and a separate fixture confirming
digit-starting items bucket under '#'.
This commit is contained in:
@@ -23,7 +23,7 @@ const itemSnippet = createRawSnippet<[Item]>((getIt) => ({
|
||||
type AnyProps = any;
|
||||
|
||||
describe('AlphabeticalGrid', () => {
|
||||
test('emits a jump button for each distinct first-letter', () => {
|
||||
test('rail renders the full #/A-Z/& set; empty buckets are disabled', () => {
|
||||
render(AlphabeticalGrid, {
|
||||
props: {
|
||||
items,
|
||||
@@ -31,13 +31,17 @@ describe('AlphabeticalGrid', () => {
|
||||
item: itemSnippet
|
||||
} as AnyProps
|
||||
});
|
||||
// Rail has one button per distinct first-letter (A, B, D);
|
||||
// C is absent because no items use C. Letters render as
|
||||
// <button> elements, not text dividers.
|
||||
expect(screen.getByRole('button', { name: /jump to a/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /jump to b/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /jump to d/i })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /jump to c/i })).not.toBeInTheDocument();
|
||||
// Populated buckets are enabled with 'Jump to <letter>' label.
|
||||
expect(screen.getByRole('button', { name: 'Jump to A' })).not.toBeDisabled();
|
||||
expect(screen.getByRole('button', { name: 'Jump to B' })).not.toBeDisabled();
|
||||
expect(screen.getByRole('button', { name: 'Jump to D' })).not.toBeDisabled();
|
||||
// Empty buckets still render but are disabled and labelled
|
||||
// 'X — no entries' so screen readers announce the empty state.
|
||||
expect(screen.getByRole('button', { name: 'C — no entries' })).toBeDisabled();
|
||||
expect(screen.getByRole('button', { name: 'Z — no entries' })).toBeDisabled();
|
||||
// # (numbers) and & (symbols) always present too.
|
||||
expect(screen.getByRole('button', { name: 'Numbers — no entries' })).toBeDisabled();
|
||||
expect(screen.getByRole('button', { name: 'Symbols — no entries' })).toBeDisabled();
|
||||
});
|
||||
|
||||
test('renders items in given order followed by the jump rail', () => {
|
||||
@@ -48,13 +52,10 @@ describe('AlphabeticalGrid', () => {
|
||||
item: itemSnippet
|
||||
} as AnyProps
|
||||
});
|
||||
// New layout: continuous grid (items in order) then the
|
||||
// sticky alphabet rail. Items come first in DOM order; rail
|
||||
// is the trailing nav.
|
||||
expect(container.textContent).toMatch(/Apple.*Avocado.*Banana.*Date.*A.*B.*D/s);
|
||||
expect(container.textContent).toMatch(/Apple.*Avocado.*Banana.*Date.*A.*B.*C.*D/s);
|
||||
});
|
||||
|
||||
test('tags the first item of each letter with an alpha-<letter> id', () => {
|
||||
test('tags the first item of each populated bucket with an alpha-<bucket> id', () => {
|
||||
const { container } = render(AlphabeticalGrid, {
|
||||
props: {
|
||||
items,
|
||||
@@ -65,6 +66,24 @@ describe('AlphabeticalGrid', () => {
|
||||
expect(container.querySelector('#alpha-A')).not.toBeNull();
|
||||
expect(container.querySelector('#alpha-B')).not.toBeNull();
|
||||
expect(container.querySelector('#alpha-D')).not.toBeNull();
|
||||
// C has no items so no anchor exists in the grid.
|
||||
expect(container.querySelector('#alpha-C')).toBeNull();
|
||||
});
|
||||
|
||||
test('digit-starting items bucket under #', () => {
|
||||
const mixed: Item[] = [
|
||||
{ id: 'n1', key: '2', label: '2-Mello' },
|
||||
{ id: 'n2', key: '88', label: '88-Keys' },
|
||||
{ id: 'n3', key: 'A', label: 'Apple' }
|
||||
];
|
||||
const { container } = render(AlphabeticalGrid, {
|
||||
props: {
|
||||
items: mixed,
|
||||
getKey: (it: Item) => it.key,
|
||||
item: itemSnippet
|
||||
} as AnyProps
|
||||
});
|
||||
expect(container.querySelector('#alpha-\\#')).not.toBeNull();
|
||||
expect(screen.getByRole('button', { name: 'Jump to numbers' })).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user