feat: reorder games from settings
The games chip row becomes a row per game, because a reorder affordance needs somewhere to put a handle and two arrows, and at fourteen games a list reads better than a wrapped row of pills anyway. Reordering lives here and nowhere else. The focus bar and the dailies strip are the fastest tap targets in the app, and a drag target sitting on top of a tick target costs somebody a streak the first time it misfires — so the live surfaces stay drag-free and this is the screen you visit on purpose. Both affordances ship. Touch fires no drag events at all, so the arrows are the mechanism and the handle is the pointer fast path; being ordinary buttons is also what makes the whole thing reachable by keyboard and screen reader without a second implementation of the same interaction. An arrow at either end is disabled rather than removed, because a control that disappears on the first row slides the other one under the finger aiming at it. A move writes back the whole displayed list: the indices are positions on screen, so applying them to a stored order that names only some lanes would move the wrong game. Reset writes the field away rather than storing an empty order. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
721c4c90fb
commit
74c1f74e87
@@ -133,3 +133,40 @@ describe("Controls: what am I allowed to look at", () => {
|
||||
expect(render(PREFS, 3)).toContain("Show the 3 events I'm");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Controls: the game order editor", () => {
|
||||
const html = () => render(PREFS);
|
||||
|
||||
test("both affordances ship, because touch fires no drag events", () => {
|
||||
// The arrows are the mechanism and the handle is the pointer fast path. A
|
||||
// drag-only list is unreachable on a phone and by keyboard alike.
|
||||
expect(html()).toContain("draggable");
|
||||
expect(html()).toContain('aria-label="Move Genshin Impact up (1 of 2)"');
|
||||
expect(html()).toContain('aria-label="Move Honkai: Star Rail down (2 of 2)"');
|
||||
});
|
||||
|
||||
test("an arrow at the end is disabled, not missing", () => {
|
||||
// A control that disappears on the first row slides the other one under the
|
||||
// finger aiming at it.
|
||||
const markup = html();
|
||||
// The whole tag: `disabled` is serialised before `aria-label`.
|
||||
const tag = (label: string) =>
|
||||
new RegExp(`<button[^>]*aria-label="${label}"[^>]*>`).exec(markup)?.[0] ?? "";
|
||||
expect(tag("Move Genshin Impact up \\(1 of 2\\)")).toContain("disabled");
|
||||
expect(tag("Move Genshin Impact down \\(1 of 2\\)")).not.toContain("disabled");
|
||||
expect(tag("Move Honkai: Star Rail down \\(2 of 2\\)")).toContain("disabled");
|
||||
});
|
||||
|
||||
test("the row names the game in full, and still toggles it", () => {
|
||||
// Rows have room for the real name where the chips only had `short`.
|
||||
expect(html()).toContain("Honkai: Star Rail");
|
||||
expect(html()).toContain('aria-pressed="true"');
|
||||
});
|
||||
|
||||
test("reset appears only once the reader has an order to reset", () => {
|
||||
expect(html()).not.toContain("Reset to A–Z");
|
||||
expect(render({ ...PREFS, gameOrder: ["hsr", "genshin"] })).toContain(
|
||||
"Reset to A–Z",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user