Testing & Diagnostics
Hal tests are hardware-in-the-loop: the test file is flashed to the board, the assertions evaluate on real hardware, and pass/fail comes back over serial — one command, no test framework to install.
@typecad/hal/testing
A testing-library-style chain: describe → it → expect → done. The comparing happens on the host; the firmware only ships raw values over serial. done() must be the last statement — it ends the suite.
Example first
import { describe, done } from '@typecad/hal/testing';
import { GPIO, LED, ADC } from '@typecad/hal';
import { LED_PIN, ADC_PIN } from '@typecad/test-pins';
describe('board sanity')
.it('LED output reads back')
.expect((() => {
const led = new GPIO(LED_PIN, GPIO.OUTPUT);
led.set(true);
return led.get();
})).toBe(true)
.it('ADC reads in range')
.expect((): number => new ADC(ADC_PIN).read())
.toBeWithinRange(0, 4095);
done(); // required — ends the suiteNumeric matchers
| Matcher | Checks |
|---|---|
| toBe(value) | Equal |
| toNotBe(value) | Not equal |
| toBeGreaterThan(n) / toBeGreaterThanOrEqual(n) | Ordering |
| toBeLessThan(n) / toBeLessThanOrEqual(n) | Ordering |
| toBeCloseTo(n, precision?) | Rounded comparison (default precision 2) |
| toBeWithinRange(min, max) | Inclusive bounds |
| toBeTruthy / toBeFalsy | Non-zero / zero |
| expect(() => ...) | Pass a zero-arg function — it runs on the board and the return value is checked |
String matchers (expectString)
| Matcher | Checks |
|---|---|
| toBe(text) / toNotBe(text) | Exact match |
| toContain(text) | Substring |
| toHaveLength(n) | Length check |
typecad-hal test
Finds test files (tests/**/*.test.ts by default), transpiles, flashes, and evaluates the assertions. Also available as typecad-hal build --expect to test right after a build.
Example first
typecad-hal test
typecad-hal test --port COM4 --include tests/sanity.test.ts| Flag | Meaning |
|---|---|
| --port, -p | Serial port — overrides config and TYPECAD_HAL_PORT |
| --baud | Baud rate (default 115200) |
| --timeout, -t | Suite timeout in ms (default 30000) |
| --include, -i / --exclude, -x | Glob filters (repeatable) |
| --config <path> | A different config file |
| --discover | List USB serial ports with VID:PID |
| --dry-run | Show what would run |
| --bail | Stop on the first failure |
| --verbose, -v | Chatty output |
The test config section
import type { TypecadConfig } from '@typecad/hal/config';
const config: TypecadConfig = {
board: 'xiao_ble/nrf52840',
test: {
port: 'COM4', // required in config — or use the flag / env var
baudRate: 115200,
timeout: 30000,
include: ['tests/**/*.test.ts'],
},
};
export default config;Port resolution: --port flag, then the TYPECAD_HAL_PORT env var,
then test.port in the config.
role pins
Tests written against roles instead of part numbers run on any board. The virtual module '@typecad/test-pins' provides LED_PIN, BUTTON_PIN, ADC_PIN, PWM_PIN, CS_PIN, INT_PIN, GPIO_OUT, GPIO_IN, I2C_BUS and facts like ADC_MAX — a project-local test-pins.json maps each role onto the real pin.
Example first
{
"pins": { "led": "LED", "gpioOut": "PA1", "gpioIn": "PA2" },
"facts": { "adcMax": 4095 }
}Per-target skips
Comment directives scope a file to the boards where it makes sense:
// @typecad-skip-target esp32s3: needs the ADC calibration
// @typecad-only-target xiao_ble
// @typecad-requires-roles pwm, cs — skipped unless the board supplies themsource-mapped diagnostics
When the generated C++ fails to compile, the error points at your TypeScript line — not the generated file. Source maps ride along with every build; add --diagnostics for the full report (pin usage, peripheral allocation, memory estimates) as Markdown or JSON.
Example first
error: 'i2c_device' was not declared
at src/main.ts:15:8 (emitted at out/main.cpp:42:10)