- 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>
103 lines
3.2 KiB
TypeScript
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);
|
|
});
|
|
});
|