seveibar/keyboard-utils
Based on the code provided, here's a concise summary of the electronic component: A keyboard utility library for parsing and generating keyboard layouts using the Keyboard Layout Editor (KLE) JSON format, with support for generating key matrices, reference designators, and diode routing for keyboard PCBs. Key features: - Parses KLE JSON layouts into key coordinate and metadata objects - Supports various keyboard layouts (60%, ANSI, ISO, Ergodox, etc.) - Generates unique reference designators for keys - Provides utility for adding diode routing in key matrices - TypeScript-based with React and tscircuit integration The library allows keyboard designers to easily convert layout designs into structured, machine-readable keyboard specifications.
- Version
- 0.0.19
- License
- unset
- Stars
- 3
tests/KLELayout.test.ts
import { describe, expect, test } from "bun:test";
import { KLELayout, parseKLELayout } from "../lib/KLELayout";
describe("KLELayout Parser", () => {
test("should parse simple 2x2 grid", () => {
const layout: KLELayout = [
["A", "B"],
["C", "D"]
];
const parsed = parseKLELayout(layout);
expect(parsed.length).toBe(4);
// Check key names
expect(parsed[0].name).toBe("K_A");
expect(parsed[1].name).toBe("K_B");
expect(parsed[2].name).toBe("K_C");
expect(parsed[3].name).toBe("K_D");
// Check positions - first row
expect(parsed[0].row).toBe(0);
expect(parsed[0].col).toBe(0);
expect(parsed[1].row).toBe(0);
expect(parsed[1].col).toBe(1);
// Check positions - second row
expect(parsed[2].row).toBe(1);
expect(parsed[2].col).toBe(0);
expect(parsed[3].row).toBe(1);
expect(parsed[3].col).toBe(1);
// Standard key size should be 19.05mm
const KEY_SIZE = 19.05;
expect(parsed[0].width).toBe(KEY_SIZE);
expect(parsed[0].height).toBe(KEY_SIZE);
});
test("should handle special key properties", () => {
const layout: KLELayout = [
[
{ w: 1.5 },
"Tab",
"Q"
]
];
const parsed = parseKLELayout(layout);
expect(parsed.length).toBe(2);
expect(parsed[0].name).toBe("K_TAB");
expect(parsed[1].name).toBe("K_Q");
// Tab should be 1.5u wide
const KEY_SIZE = 19.05;
expect(parsed[0].width).toBe(1.5 * KEY_SIZE);
expect(parsed[1].width).toBe(KEY_SIZE);
});
test("should handle rotations", () => {
const layout: KLELayout = [
[
{ r: 15, rx: 1, ry: 1 },
"R"
]
];
const parsed = parseKLELayout(layout);
expect(parsed.length).toBe(1);
expect(parsed[0].name).toBe("K_R");
expect(parsed[0].rotation).toBe(15);
expect(parsed[0].rotationX).toBe(1 * 19.05);
expect(parsed[0].rotationY).toBe(-1 * 19.05);
});
test("should handle duplicate key labels with counters", () => {
const layout: KLELayout = [
["A", "A"]
];
const parsed = parseKLELayout(layout);
expect(parsed.length).toBe(2);
expect(parsed[0].name).toBe("K_A1");
expect(parsed[1].name).toBe("K_A2");
});
test("should handle special characters", () => {
const layout: KLELayout = [
["!", "@", "#", "quot;]
];
const parsed = parseKLELayout(layout);
expect(parsed.length).toBe(4);
expect(parsed[0].name).toBe("K_EXCLAMATION");
expect(parsed[1].name).toBe("K_AT");
expect(parsed[2].name).toBe("K_HASH");
expect(parsed[3].name).toBe("K_DOLLAR");
});
test("should handle x/y offsets", () => {
const layout: KLELayout = [
[
{ x: 0.5 },
"A",
{ x: 0.25 },
"B"
]
];
const parsed = parseKLELayout(layout);
expect(parsed.length).toBe(2);
// First key should be offset by 0.5u
const KEY_SIZE = 19.05;
expect(parsed[0].x).toBeCloseTo((0.5 + 0.5) * KEY_SIZE);
// Second key should be 1.25u to the right of first key
expect(parsed[1].x).toBeCloseTo((0.5 + 1 + 0.25 + 0.5) * KEY_SIZE);
});
test("should handle rotation center positioning correctly", () => {
// Simplified thumb cluster layout similar to ergodox
const layout: KLELayout = [
[
{ r: 30, rx: 2, ry: 2, x: 1, y: -1 },
"Space"
]
];
const parsed = parseKLELayout(layout);
expect(parsed.length).toBe(1);
expect(parsed[0].name).toBe("K_SPACE");
expect(parsed[0].rotation).toBe(30);
// With proper KLE behavior, position starts from rotation center (rx: 2, ry: 2)
// then applies offsets (x: 1, y: -1) and centers the key (+ 0.5, + 0.5)
const KEY_SIZE = 19.05;
const expectedX = (2 + 1 + 0.5) * KEY_SIZE; // rx + x + width/2
const expectedY = -(2 + (-1) + 0.5) * KEY_SIZE; // -(ry + y + height/2)
// Position should match KLE positioning logic
expect(parsed[0].x).toBeCloseTo(expectedX);
expect(parsed[0].y).toBeCloseTo(expectedY);
// Verify the rotation center is stored correctly
expect(parsed[0].rotationX).toBe(2 * KEY_SIZE);
expect(parsed[0].rotationY).toBe(-2 * KEY_SIZE);
});
test("should handle full ergodox-style layout with thumb clusters", () => {
// Simplified version of the ergodox layout from your reference
const layout: KLELayout = [
// Main keyboard area (simplified)
["Q", "W", "E"],
["A", "S", "D"],
["Z", "X", "C"],
// Left thumb cluster
[
{ r: -45, rx: 6.5, ry: 4.25, y: -1, x: 1 },
"Alt",
"Ctrl"
],
[
{ h: 2 },
"Space",
{ h: 2 },
"Enter",
"Shift"
],
[
{ x: 2 },
"Fn"
],
// Right thumb cluster
[
{ r: 45, rx: 13, y: -1, x: -3 },
"Left",
"Right"
],
[
{ x: -3 },
"Up",
{ h: 2 },
"Down",
{ h: 2 },
"PgUp"
],
[
{ x: -3 },
"PgDn"
]
];
const parsed = parseKLELayout(layout);
// Should have all keys
expect(parsed.length).toBe(21);
// Check main keyboard keys are positioned normally
const qKey = parsed.find(k => k.name === "K_Q");
const aKey = parsed.find(k => k.name === "K_A");
const zKey = parsed.find(k => k.name === "K_Z");
expect(qKey?.rotation).toBe(0);
expect(aKey?.rotation).toBe(0);
expect(zKey?.rotation).toBe(0);
// Check left thumb cluster keys have correct rotation and are positioned relative to rx/ry
const altKey = parsed.find(k => k.name === "K_ALT");
const spaceKey = parsed.find(k => k.name === "K_SPACE");
expect(altKey?.rotation).toBe(-45);
expect(altKey?.rotationX).toBe(6.5 * 19.05);
expect(altKey?.rotationY).toBe(-4.25 * 19.05);
expect(spaceKey?.rotation).toBe(-45); // Should inherit rotation from previous row
expect(spaceKey?.height).toBe(2 * 19.05); // Should be double height
// Check right thumb cluster keys have correct negative rotation
const leftKey = parsed.find(k => k.name === "K_LEFT");
const upKey = parsed.find(k => k.name === "K_UP");
expect(leftKey?.rotation).toBe(45);
expect(leftKey?.rotationX).toBe(13 * 19.05);
expect(upKey?.rotation).toBe(45); // Should inherit rotation
});
});