Files
musicmouse/tippen/src/lib/__tests__/fingers.test.ts
Martin Bauer 498243af46 Anglicize tippen's codebase and finish pending UI/curriculum cleanup
- Rename all German identifiers, types, mode ids, file names, CSS classes
  and data-attributes to English throughout tippen/src; only user-facing
  text (lesson titles, word lists, labels, spoken praise) stays German.
- Add a word/nonsense-word list to the "Übung: die Grundstellung" home-row
  lesson in the curriculum.
- Remove the unused HandHint component and speech.ts, and carry forward
  the in-progress App.tsx/component/generator/progress edits from other
  sessions.
- Refresh the regenerated music-library cache index.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 00:07:18 +02:00

103 lines
3.2 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
FINGERS,
HOME_ROW,
KEYBOARD_ROWS,
fingerOf,
handOf,
homeKeyOf,
keysInRow,
rowOf,
shiftHandFor,
} from "../fingers";
const ALPHABET = "abcdefghijklmnopqrstuvwxyzäöüß";
describe("fingerOf", () => {
it("maps every German letter to exactly one finger", () => {
for (const key of ALPHABET) expect(fingerOf(key), key).not.toBeNull();
});
it("is case-insensitive", () => {
for (const key of ALPHABET) {
// "ß".toUpperCase() is "SS" - two characters, and not a key. `event.key` never
// reports that, so the single-character case is the one that has to hold.
const upper = key.toUpperCase();
if ([...upper].length !== 1) continue;
expect(fingerOf(upper)?.id, upper).toBe(fingerOf(key)?.id);
}
});
it("returns null for keys that are not on the layout", () => {
for (const key of ["Enter", "F1", "€", ""]) expect(fingerOf(key)).toBeNull();
});
it("assigns the home row to the eight home fingers, left to right", () => {
const expected = [
"left-pinky",
"left-ring",
"left-middle",
"left-index",
"right-index",
"right-middle",
"right-ring",
"right-pinky",
];
HOME_ROW.forEach((key, i) => expect(fingerOf(key)?.id).toBe(expected[i]));
});
it("gives each finger the home key it actually rests on", () => {
for (const key of HOME_ROW) expect(homeKeyOf(key)).toBe(key);
expect(homeKeyOf(" ")).toBe(" ");
});
it("sends the two index fingers to their stretch keys", () => {
for (const key of "rtfgvb") expect(fingerOf(key)?.id).toBe("left-index");
for (const key of "zuhjnm") expect(fingerOf(key)?.id).toBe("right-index");
});
});
describe("hands", () => {
it("splits the letters into two disjoint, non-empty sets", () => {
const left = [...ALPHABET].filter((key) => handOf(key) === "left");
const right = [...ALPHABET].filter((key) => handOf(key) === "right");
expect(left.length).toBeGreaterThan(0);
expect(right.length).toBeGreaterThan(0);
expect(left.length + right.length).toBe(ALPHABET.length);
expect(left.some((key) => right.includes(key))).toBe(false);
});
it("shifts with the opposite hand", () => {
expect(shiftHandFor("a")).toBe("right");
expect(shiftHandFor("l")).toBe("left");
expect(shiftHandFor("Enter")).toBeNull();
});
});
describe("rows", () => {
it("places every letter in a row", () => {
for (const key of ALPHABET) expect(rowOf(key), key).not.toBeNull();
});
it("has the home row in the home row", () => {
for (const key of HOME_ROW) expect(rowOf(key)).toBe("home");
});
it("draws three rows, each with keys", () => {
expect(KEYBOARD_ROWS).toHaveLength(3);
for (const row of KEYBOARD_ROWS) expect(keysInRow(row).length).toBeGreaterThan(0);
});
});
describe("FINGERS", () => {
it("gives every finger a distinct hue so the colours can be named", () => {
const hues = Object.values(FINGERS).map((finger) => finger.hue);
expect(new Set(hues).size).toBe(hues.length);
});
it("keeps each finger's id and key consistent", () => {
for (const [id, finger] of Object.entries(FINGERS)) expect(finger.id).toBe(id);
});
});