First implementation of 10 finger typing

This commit is contained in:
2026-09-11 21:55:45 +02:00
parent 7e5fd5ab75
commit f97de193d8
63 changed files with 8275 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
import { describe, expect, it } from "vitest";
import {
FINGERS,
GRUNDSTELLUNG,
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 gross = key.toUpperCase();
if ([...gross].length !== 1) continue;
expect(fingerOf(gross)?.id, gross).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 Grundstellung to the eight home fingers, left to right", () => {
const expected = [
"links-klein",
"links-ring",
"links-mitte",
"links-zeige",
"rechts-zeige",
"rechts-mitte",
"rechts-ring",
"rechts-klein",
];
GRUNDSTELLUNG.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 GRUNDSTELLUNG) 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("links-zeige");
for (const key of "zuhjnm") expect(fingerOf(key)?.id).toBe("rechts-zeige");
});
});
describe("hands", () => {
it("splits the letters into two disjoint, non-empty sets", () => {
const links = [...ALPHABET].filter((key) => handOf(key) === "links");
const rechts = [...ALPHABET].filter((key) => handOf(key) === "rechts");
expect(links.length).toBeGreaterThan(0);
expect(rechts.length).toBeGreaterThan(0);
expect(links.length + rechts.length).toBe(ALPHABET.length);
expect(links.some((key) => rechts.includes(key))).toBe(false);
});
it("shifts with the opposite hand", () => {
expect(shiftHandFor("a")).toBe("rechts");
expect(shiftHandFor("l")).toBe("links");
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 Grundstellung in the Grundreihe", () => {
for (const key of GRUNDSTELLUNG) expect(rowOf(key)).toBe("grund");
});
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);
});
});