Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: test

on:
push:
branches: [main]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22 # Vitest 3's own supported range: ^18 || ^20 || >=22 (package.json#engines)

# `npm ci` pulls the `canvas` devDependency (see AGENTS.md's Testing
# section) — it ships prebuilt binaries for linux-x64-glibc, which
# ubuntu-latest is, so no apt-get should be needed. If a future runner
# image ever lacks a prebuilt and `canvas` falls back to compiling from
# source, uncomment the block below (node-canvas's own documented
# Debian/Ubuntu build dependencies):
#
# - run: sudo apt-get update && sudo apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev

- run: npm ci
- run: npm test
- run: npx tsc --noEmit
- run: npx tsc --noEmit -p test/tsconfig.json
- run: npm run build
70 changes: 65 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ src/Preview/
demo/index.html # manual test page — Tailwind CDN, loads ../build/web-escpos-printer.js + app.js
demo/app.js # demo page's own logic — no build step
Dockerfile / docker-compose.yml / scripts/nginx/default.conf # serves demo/ + build/ on :3000

test/ # Vitest suite — see "## Testing" below
helpers/ # buildEncoder/buildBytes/withDom/pixelFixture/assertBytes
config.test.ts, Text/, Images/, Preview/, Printer/ # mirrors the src/ tree above

vitest.config.ts # test.include: test/**/*.test.ts
.github/workflows/test.yml # runs `npm test` on push/PR
```

## Critical gotchas (read before editing ReceiptBuilder.ts / Text / Preview)
Expand Down Expand Up @@ -144,11 +151,11 @@ time. Full report linked where one exists under `docs/notes/`.
one → check the other. `wrapText`, `justifyLine`, image sizing/dithering
are already shared for this reason; new alignment/layout logic should be
shared the same way.
- **No test framework.** Verification: `npm run build` + `npx tsc --noEmit`
+ throwaway Node scripts that `import('@point-of-sale/receipt-printer-encoder')`
and inspect real encoded bytes + the Docker demo for real hardware.
Reproduce encoder bugs against the *real* installed library before
trusting a fix.
- **Automated tests + the Docker demo.** `npm test` (see "## Testing" below)
covers everything DOM-free-or-jsdom-reachable; the Docker demo is still
the only way to confirm real hardware behavior. Reproduce encoder bugs
against the *real* installed library before trusting a fix — same
standard the test suite itself follows (no mocked encoder anywhere).
- **Comments explain "why", not "what."**
- **Don't hand-type `\uXXXX` escapes directly** — this environment has
corrupted literal `\u0300-\u036f` (in `stripAccents()`) into raw
Expand Down Expand Up @@ -182,6 +189,59 @@ fork/rebuild needed. Not a "gotcha" (no hardware bug behind it, this is
an intentional API) — see the README's "Manual Bluetooth profile"
section for consumer-facing docs.

## Testing

```sh
npm test # node:test, ~70 assertions, a few hundred ms
npx tsc --noEmit # src/, unaffected by test/ — unchanged from before
npx tsc --noEmit -p test/tsconfig.json # type-checks test/ itself (own tsconfig: adds "node" types, noEmit)
npm run build
```

`npm test` runs `vitest run` (config: `vitest.config.ts`, just
`test.include: ['test/**/*.test.ts']`) — test files `import { describe, it,
expect } from 'vitest'`, not `node:test`/`node:assert`. Vitest was chosen
over `node:test` specifically because its resolver (Vite's, ESM-native,
resolves extensions automatically) has no trouble with this repo's
extension-less relative imports (`moduleResolution: "Bundler"`-style) or
with dependencies whose `package.json#exports` defines only an `"import"`
condition (e.g. `@bwip-js/browser`) — confirmed directly. `tsx` was tried
first for this and hard-failed (`ERR_PACKAGE_PATH_NOT_EXPORTED`) on exactly
that: without `"type": "module"` in *this* package's `package.json` (which
can't be added — see below), `tsx` resolves bare specifiers via a
CommonJS-style algorithm that can't see an import-only `exports` map — a
plain Node `node:test` setup needed its own custom loader hook to work
around the same issue; Vitest needs none of that. Don't add
`"type": "module"` to fix this a different way: it would make
`build/web-escpos-printer.js` (the UMD bundle) unrequireable, breaking the
documented `require('web-escpos-printer')` compatibility path.

Default test environment is Node, not Vitest's built-in `jsdom` — the
DOM-dependent tests below use this repo's own `test/helpers/dom.ts#withDom()`
(manual jsdom+`canvas` setup, scoped per test) instead, so DOM-free test
files never see a jsdom global leak into them.

**Scope**: everything DOM-free (the real encoder, `ReceiptBuilder.ts`'s
dispatch logic, `Text/`, `config.ts`, `SafeMode.ts`,
`resolvePdf417Columns()`) plus, via the `jsdom` + `canvas` devDependencies
(`test/helpers/dom.ts`'s `withDom()`), the DOM-dependent layer too —
`Images/image.ts`'s real source loading and the `Preview/` raster builders'
actual pixel output (`buildQrCodeRasterImage`/`buildPdf417RasterImage`/
`buildItf`). jsdom's own `HTMLCanvasElement`/`Image` pixel decoding is
wired to the `canvas` npm package specifically (not `@napi-rs/canvas` —
confirmed, jsdom doesn't know how to talk to it). Not covered: real
Bluetooth/QZ transport connections and `profiles.ts`'s `findProfile()`
(would need a mocked Web Bluetooth API, not requested), and pixel-perfect
golden-image diffing (tests assert structural correctness — dimensions,
multiple-of-8 padding, non-white pixels present — not exact pixel match).

Most `docs/notes/*.md` gotchas now have a pinned regression test — see each
note's own "Pinned by" line where present. A few genuinely can't be: real
BLE characteristic behavior (#03) and Windows driver routing (#08) need
actual hardware/OS, and bundle size (#07) is a build-output assertion, not
a runtime one. Add a new `test/**/*.test.ts` file (mirroring the `src/`
path of what it tests) for any new gotcha the same way.

## Docker demo

`docker compose up --build -d` → `http://localhost:3000/`. Rebuild after
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ npm install
npm run build # UMD + ESM + .d.ts (what gets published to npm)
npm run build:standalone # only build/web-escpos-printer.js
npm run build:dev # same as build, in watch mode
npm test # node:test suite against the real encoder — see AGENTS.md's "Testing" section
```

## License
Expand Down
10 changes: 10 additions & 0 deletions docs/notes/02-paperwidth-scales-columns-and-imagemaxwidth.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,15 @@ nothing for images/barcodes.
`PaperWidth` is `'58mm' | '80mm' | '112mm'` — `80mm` cross-checked against
real hardware (576 dots); `112mm` is an estimate, not hardware-verified.

**Known bug, found by the test suite below, not yet fixed**: `112mm` maps
to `columns: 56`, but the real encoder's constructor only accepts columns
of 32/35/42/44/48 (confirmed by reading the installed library) — it throws
`"The width of the paper must me either 32, 35, 42, 44 or 48 columns"`.
`paperWidth: '112mm'` therefore currently fails to build *any* receipt at
all, not just images. `58mm`(32)/`80mm`(42) are unaffected (both valid).
Pinned (as a currently-failing-on-purpose regression) by
`test/Printer/ReceiptBuilder.pdf417.test.ts`'s "paperWidth" suite — flip
that test to `doesNotReject` once this is actually fixed.

---
Referenced from [AGENTS.md](../../AGENTS.md)'s "Critical gotchas" section (gotcha #2).
2 changes: 2 additions & 0 deletions docs/notes/04-feedbeforecut-defaults-to-zero.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ reason — `ReceiptBuilder.ts` always passes it explicitly;
`PreviewRenderer.ts` mirrors the same gap before its "✂ cut" mark (see
AGENTS.md's "preview/print parity" rule in "Coding conventions").

Pinned by `test/config.test.ts` (`DEFAULT_CONFIG.feedBeforeCut` must stay `4`).

---
Referenced from [AGENTS.md](../../AGENTS.md)'s "Critical gotchas" section (gotcha #4).
4 changes: 4 additions & 0 deletions docs/notes/05-bwip-js-validates-pdf417-capacity.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,9 @@ run before committing to a non-auto `columns`, falling back to
fully-automatic (the pre-existing, safe behavior) when it doesn't fit.
Never pass a fixed `columns` to the real encoder without this check.

Pinned by `test/Preview/pdf417.raster.test.ts`'s `resolvePdf417Columns`
suite (auto mode must fall back to `undefined` instead of forwarding an
overflowing `columns`) and its own capacity-error propagation test.

---
Referenced from [AGENTS.md](../../AGENTS.md)'s "Critical gotchas" section (gotcha #5).
3 changes: 3 additions & 0 deletions docs/notes/06-bwip-js-default-eclevel-differs.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@ shape mismatch that survived even after `columns` was aligned ([gotcha #5](05-bw
`errorlevel`, so both `buildPdf417()` and `resolvePdf417Columns()`
render/validate against the same level the real print already assumes.

Pinned by `test/Printer/ReceiptBuilder.pdf417.test.ts` (an explicit
`errorlevel: 1` must produce byte-identical output to leaving it unset).

---
Referenced from [AGENTS.md](../../AGENTS.md)'s "Critical gotchas" section (gotcha #6).
4 changes: 4 additions & 0 deletions docs/notes/09-clone-printers-lack-native-pdf417.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,9 @@ correctly) vs. a clone Bluetooth printer (silently drops it, same bytes).
bwip-js renderer `renderPreview()` already uses for the PDF417 preview.
Other element types may gain the same flag later.

Pinned by `test/Printer/SafeMode.test.ts` (the shared substitution
mechanism) and `test/Printer/ReceiptBuilder.pdf417.test.ts`/`qrcode.test.ts`
(safeMode produces different bytes than the native command, end-to-end).

---
Referenced from [AGENTS.md](../../AGENTS.md)'s "Critical gotchas" section (gotcha #9).
3 changes: 3 additions & 0 deletions docs/notes/10-clone-printers-mangle-rule-character.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@ a valid plain-text substitute here. Off by default: the native rule
character works fine on printers that support it, and produces a slightly
different (solid vs. dashed) look.

Pinned by `test/Printer/ReceiptBuilder.rule.test.ts` (native `rule()` never
contains the plain-ASCII safeMode line, and vice versa).

---
Referenced from [AGENTS.md](../../AGENTS.md)'s "Critical gotchas" section (gotcha #10).
Loading
Loading