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.

module

@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

tests/sanity.test.ts
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 suite

Numeric matchers

MatcherChecks
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 / toBeFalsyNon-zero / zero
expect(() => ...)Pass a zero-arg function — it runs on the board and the return value is checked

String matchers (expectString)

MatcherChecks
toBe(text) / toNotBe(text)Exact match
toContain(text)Substring
toHaveLength(n)Length check
command

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

terminal
typecad-hal test
typecad-hal test --port COM4 --include tests/sanity.test.ts
FlagMeaning
--port, -pSerial port — overrides config and TYPECAD_HAL_PORT
--baudBaud rate (default 115200)
--timeout, -tSuite timeout in ms (default 30000)
--include, -i / --exclude, -xGlob filters (repeatable)
--config <path>A different config file
--discoverList USB serial ports with VID:PID
--dry-runShow what would run
--bailStop on the first failure
--verbose, -vChatty output

The test config section

typecad-hal.config.ts
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.

pattern

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

test-pins.json
{
  "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:

directives.ts
// @typecad-skip-target esp32s3: needs the ADC calibration
// @typecad-only-target xiao_ble
// @typecad-requires-roles pwm, cs — skipped unless the board supplies them
reference

source-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

terminal
error: 'i2c_device' was not declared
  at src/main.ts:15:8 (emitted at out/main.cpp:42:10)
© 2026 typeCAD — code-defined electronics github