Skip to content

Commit 39c13e0

Browse files
authored
Merge pull request #51 from DDecoene/feature/50-test-hardening
test: strict CREATE TABLE, demo schema pins, grid message coverage (#50)
2 parents e9a23e4 + e727a66 commit 39c13e0

21 files changed

Lines changed: 1396 additions & 323 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ webbase-screenshot.png
1111
playwright-report/
1212
test-results/
1313
out/
14+
coverage/

CHANGELOG.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Versions follow [Semantic Versioning](https://semver.org/) — minor bump per su
77

88
---
99

10-
## [Unreleased] — v1.2.0 — TIME columns, WEEK(), grid validation, Overtime demo
10+
## [Unreleased] — v1.2.0 — TIME columns, WEEK(), grid validation, test hardening, Overtime demo
1111

1212
### Added
1313
- `TIME` column type — `CREATE TABLE ... (col TIME)` / `TIME(n)` for a minute-granularity
@@ -27,6 +27,14 @@ Versions follow [Semantic Versioning](https://semver.org/) — minor bump per su
2727
live in `src/shared/cellValidation.ts` and run on both the client (instant feedback) and
2828
the server (`grid-edit` is now validated authoritatively — previously it wrote straight
2929
to SQLite with no check at all). (#45)
30+
- `NUM(p,s)` is now a genuinely supported qualifier — the precision and scale are parsed,
31+
recorded, and enforced on grid edits (`NUM(8,2)` accepts `123456.78`, rejects `1.234`).
32+
Previously the scale silently corrupted the schema; see Fixed. (#45) The Assistant's
33+
**New table** wizard accepts a width (`8`) or a precision,scale pair (`8,2`). (#50)
34+
- `LIST STRUCTURE` prints the **declared** type of every column (`CHAR(10)`, `NUM(8,2)`,
35+
`DATE`, `TIME(15)`, `LOGICAL`, `INT`) rather than SQLite's storage class (`TEXT`/`REAL`/
36+
`INTEGER`). Declared types are recorded per `(database, table, column)` in
37+
`server/ColumnMetaStore.ts`. (#45)
3038

3139
### Fixed
3240
- `CREATE TABLE t (price NUM(8,2))` silently created a **phantom column named `2`** of
@@ -38,6 +46,28 @@ Versions follow [Semantic Versioning](https://semver.org/) — minor bump per su
3846
tables previously shared (and overwrote) one another's declared column types, so a
3947
`TIME(15)` column in one database could be validated against another database's
4048
`CHAR(20)` declaration of the same name. (#45)
49+
- `CREATE TABLE` now **rejects a malformed column list** instead of silently inventing
50+
columns from tokens it doesn't understand. `CREATE TABLE t (a CHAR(10) b INT)` (missing
51+
comma), `(a)` (no type), `(a NUM(8,2,9))` and an unclosed paren all now raise a parse
52+
error naming the offending column, and create nothing. This permissiveness was the root
53+
cause of the phantom-column bug above. (#50)
54+
- **Index metadata is now scoped per database.** `indexes`/`active_indexes` were keyed by
55+
table name alone, so opening `PEOPLE` in one database silently activated an index defined
56+
on a *different* database's `PEOPLE` — pointing the record order at a column that need not
57+
even exist there, and breaking `BROWSE`/`LIST`. On first run, existing index definitions
58+
are adopted into the one database that owns the table; definitions whose owner is ambiguous
59+
(same table name in two databases) or missing are dropped and must be recreated with
60+
`INDEX ON`. The underlying SQLite indexes are untouched. (#50)
61+
- A bare `INPUT "prompt" TO <var>` typed at the REPL silently discarded the value: the
62+
submitted form was only applied when a continuation existed, which is never the case for
63+
a single statement. Values a form collects are now always stored. (#50)
64+
65+
### Changed
66+
- Removed the `input-request` / `input-response` WebSocket message types. They were declared
67+
in the protocol but never sent or handled by anything — `INPUT` collects its value through
68+
`form-open` / `form-submit`. (#50)
69+
- New `npm run coverage` (vitest + v8, reporting only, no thresholds), so modules no test ever
70+
executes stop hiding. (#50)
4171

4272
---
4373

CLAUDE.md

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ server/
5050
SessionManager.ts Tracks all active sessions; broadcast() fans data-changed to peers viewing a mutated table
5151
ServerDatabaseBridge.ts IDatabaseBridge impl wrapping better-sqlite3
5252
ProgramStore.ts .prg program storage in data/system.sqlite3
53-
IndexStore.ts Index metadata + active index in data/system.sqlite3
53+
IndexStore.ts Index metadata + active index per (db, table) in data/system.sqlite3
5454
ColumnMetaStore.ts Declared column types per (db, table, column) in data/system.sqlite3 — SQLite affinity can't distinguish TIME/DATE/CHAR, LOGICAL/INT, or recover NUM(p,s)
5555
ReportStore.ts Report definition storage in data/system.sqlite3 (reports table)
5656
ReportRunner.ts ASCII and HTML report rendering, group breaks, subtotals, grand totals
@@ -115,6 +115,10 @@ tests/
115115
ColumnMeta.test.ts NUM(p,s) parsing, declared types in LIST STRUCTURE, grid-open columnTypes, server-side grid-edit validation
116116
ColumnMetaStore.test.ts Per-(db,table,column) type metadata + legacy-schema migration
117117
CellValidation.test.ts Shared per-type cell validation rules
118+
CreateTableParse.test.ts Strict CREATE TABLE grammar — malformed column lists must throw
119+
DemoSchemas.test.ts Golden column lists for every table the demos create
120+
GridMessages.test.ts grid-edit / grid-delete / grid-new-row / grid-refresh + INPUT form round-trip
121+
IndexStoreMigration.test.ts Adopting pre-#50 unscoped index rows into their owning database
118122
Print.test.ts `?` / `??` print command
119123
Aggregate.test.ts `SUM` / `AVERAGE`
120124
Builtins.test.ts / BuiltinsParse.test.ts built-in functions (direct + through the parser)
@@ -315,6 +319,8 @@ line.
315319
qualifier validated on write; declared types tracked in `server/ColumnMetaStore.ts` (#43) ✅
316320
- ~~`WEEK()` built-in~~ — ISO-8601 week number (#44) ✅
317321
- ~~BROWSE per-cell validation~~ — grid rejects invalid edits per column type, validated on both client and server via `src/shared/cellValidation.ts` (#45) ✅
322+
- ~~Test hardening~~ — strict `CREATE TABLE` grammar, golden demo schemas, coverage for every
323+
grid WS message, per-database index/column metadata scoping, `npm run coverage` (#50) ✅
318324
- `demos/overtime.prg` — overtime tracker showcasing all three of the above (#46)
319325

320326
## Boolean literals
@@ -324,11 +330,37 @@ Both styles accepted: `TRUE`/`FALSE` and `.T.`/`.TRUE.`/`.F.`/`.FALSE.` (dBASE I
324330
## Testing
325331

326332
```bash
327-
npm test # Vitest unit + integration (316 tests)
333+
npm test # Vitest unit + integration (358 tests)
334+
npm run coverage # Vitest + v8 coverage report (reporting only, no thresholds)
328335
npx playwright test # E2E browser tests — requires dev server on :5173/:3000
329336
```
330337

331-
Playwright suites (79 tests): `tests/assistant.spec.ts` (22 tests — sidebar, wizards, report designer, MODIFY STRUCTURE round-trip, `TIME(15)` column + REPLACE validation, Browse-action grid validation, program run, CSV/SORT/SUM-AVERAGE/REINDEX/PACK actions, demo launchers), `tests/integration.spec.ts` (20 tests — full REPL scenario), `tests/inventory.spec.ts` (8 tests — INVENTORY.prg menu + valuation/low-stock report/sort/CSV/JOIN), `tests/crm.spec.ts` (6 tests — CRM demo menu, pipeline summary, sort, report, CSV, JOIN), `tests/parity-commands.spec.ts` (5 tests — `?`/`??`, built-in functions, `WEEK()`, `SUM`/`AVERAGE`, `SORT ON … TO`), `tests/multiarea.spec.ts` (4 tests — multi-work-area, relations, alias.field), `tests/demos.spec.ts` (4 tests — demo program + report seeding), `tests/grid-validation.spec.ts` (3 tests — BROWSE per-cell validation: TIME(15), NUM(p,s)/DATE, Esc abandons), `tests/copycsv.spec.ts` (2 tests — COPY TO download + APPEND FROM upload), `tests/splash.spec.ts` (2 tests — version banner + demo discoverability), `tests/join.spec.ts` (1 test — JOIN materialization), `tests/propagation.spec.ts` (1 test — live multiuser refresh), `tests/program-side-effects.spec.ts` (1 test — CSV/report side-effects fire from inside a program block).
338+
Playwright suites (83 tests): `tests/assistant.spec.ts` (23 tests — sidebar, wizards, report designer, MODIFY STRUCTURE round-trip, `TIME(15)` column + REPLACE validation, `NUM(p,s)` wizard, Browse-action grid validation, program run, CSV/SORT/SUM-AVERAGE/REINDEX/PACK actions, demo launchers), `tests/integration.spec.ts` (20 tests — full REPL scenario), `tests/inventory.spec.ts` (8 tests — INVENTORY.prg menu + valuation/low-stock report/sort/CSV/JOIN), `tests/crm.spec.ts` (6 tests — CRM demo menu, pipeline summary, sort, report, CSV, JOIN), `tests/parity-commands.spec.ts` (5 tests — `?`/`??`, built-in functions, `WEEK()`, `SUM`/`AVERAGE`, `SORT ON … TO`), `tests/multiarea.spec.ts` (4 tests — multi-work-area, relations, alias.field), `tests/demos.spec.ts` (4 tests — demo program + report seeding), `tests/grid-validation.spec.ts` (3 tests — BROWSE per-cell validation: TIME(15), NUM(p,s)/DATE, Esc abandons), `tests/schema-errors.spec.ts` (3 tests — malformed CREATE TABLE errors, NUM(p,s) column count, bare INPUT stores its value), `tests/copycsv.spec.ts` (2 tests — COPY TO download + APPEND FROM upload), `tests/splash.spec.ts` (2 tests — version banner + demo discoverability), `tests/join.spec.ts` (1 test — JOIN materialization), `tests/propagation.spec.ts` (1 test — live multiuser refresh), `tests/program-side-effects.spec.ts` (1 test — CSV/report side-effects fire from inside a program block).
339+
340+
## Test discipline
341+
342+
Two bugs shipped through a 283-test suite (found in #45/#50). Both were structural blind
343+
spots, not bad luck. When adding tests, remember what the existing ones cannot see:
344+
345+
- **`toContain` can only prove presence, never absence.** Almost every assertion in this
346+
repo greps rendered text for a substring, so a *phantom extra column* (`NUM(8,2)` used to
347+
create a column literally named `2`) sailed through every `LIST`/`LIST STRUCTURE` check.
348+
Assert **exact** structure — column lists, record counts — with `toEqual`/`toHaveLength`
349+
wherever you can. `tests/DemoSchemas.test.ts` pins the demo tables for exactly this reason.
350+
- **Test the surface, not the happy path through it.** Four of twelve `ClientMessage` types
351+
had zero tests; `grid-edit` wrote straight to SQLite with no validation and nobody noticed,
352+
because the grid tests only opened the grid and pressed Escape. Every WS message type
353+
should have a test that drives it and asserts the database/UI effect
354+
(`tests/GridMessages.test.ts`).
355+
- **Green CI does not mean correct.** The cross-database `ColumnMetaStore` leak shipped with
356+
seven passing tests, because they all used a single database. When state is keyed by name,
357+
write the test that uses two.
358+
- **Prefer failing loudly to guessing.** The parser used to absorb any token it didn't
359+
understand and invent a column from it. `CREATE TABLE` is now strict; keep it that way.
360+
361+
Run `npm run coverage` when touching an area you suspect is untested. **Never run `npm test`
362+
and `npx playwright test` concurrently** — both mutate `data/` and `data/system.sqlite3`, and
363+
a state-dependent e2e test will fail for reasons that have nothing to do with your change.
332364

333365
## Definition of done
334366

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ WebBase-III supports **unlimited work areas** — each independently holding a t
229229

230230
> Column ops that can invalidate an index (DROP, RENAME, ALTER type) drop all of the table's indexes and warn you to rebuild with `INDEX ON`.
231231
232+
> `CREATE TABLE` rejects a malformed column list (missing comma, missing type, unclosed paren, a third
233+
> type argument) with a parse error naming the offending column, and creates nothing.
234+
232235
**Column types**: `CHAR(n)` (aliases `CHARACTER`/`VARCHAR`/`STRING`/`MEMO`), `NUM`/`NUM(p,s)` (`NUMERIC`/`FLOAT`/`DOUBLE`/`DECIMAL`), `INT`/`INTEGER`, `LOGICAL`/`BOOLEAN`, `DATE`, and `TIME`/`TIME(n)`. `TIME` stores `HH:MM` (24-hour); the optional `TIME(n)` qualifier (e.g. `TIME(15)`) requires minutes to be a multiple of `n`. `REPLACE ... WITH` rejects a malformed or off-granularity `TIME` value instead of silently coercing it, and `LIST STRUCTURE` prints the declared type (`NUM(8,2)`, `TIME(15)`) rather than SQLite's storage class.
233236

234237
> **CSV format (`COPY TO` / `APPEND FROM`):** Unlike dBASE III's headerless,

0 commit comments

Comments
 (0)