Skip to content

test: add automated test suite (Vitest) for the SQL import/render pipeline - #8

Merged
KarimTamani merged 4 commits into
stackrender:mainfrom
albertoarena:test/automated-tests
Aug 5, 2026
Merged

test: add automated test suite (Vitest) for the SQL import/render pipeline#8
KarimTamani merged 4 commits into
stackrender:mainfrom
albertoarena:test/automated-tests

Conversation

@albertoarena

Copy link
Copy Markdown
Contributor

What

Adds the first automated test suite to StackRender. There is currently no test runner, no test script and no CI, so this lands the harness plus a meaningful suite covering the core SQL import/export pipeline across all six dialects.

88 tests, all green (npm test). One commit per phase for easy review.

Why

The SQL import and render code is pure, dialect-heavy and the most bug-prone part of the app. It is also the cheapest to test (no DOM, no WASM SQLite, no PowerSync). This suite locks in current behavior and gives a safety net for future dialect work.

Tooling

  • Vitest 2.x (matches Vite 5), near-zero config.
  • Standalone vitest.config.ts (node environment, vite-tsconfig-paths for the @/ alias). It does not reuse the app Vite config, so the Tailwind/React/WASM plugins are not pulled into pure-logic tests.
  • New scripts: test (vitest run) and test:watch.

What is covered

  • Phase 1, pure helpers: getNextSequence / cloneField, the charset/collation and SQLite integer-column reordering plus enum naming in render-uttils.ts, and orderTables including the CircularDependencyError cycle path behind the Foreign Key Cycle Detection feature.
  • Phase 2, SQL import: getImporter().parseSql for MySQL, MariaDB, PostgreSQL, SQLite, Oracle and SQL Server. Representative CREATE TABLE + foreign key + index fixtures assert table/column counts, primary key, NOT NULL, UNIQUE, DEFAULT, type mapping and the captured foreign key. Also parses the bundled PostgreSQL dump and checks malformed input surfaces errors without dropping valid tables.
  • Phase 3, SQL render: getRenderer().renderDDL for all six dialects from a shared DatabaseType fixture, asserting the emitted DDL creates both tables and carries the primary key, unique constraint, dialect-specific auto-increment spelling (AUTO_INCREMENT / SERIAL / AUTOINCREMENT / IDENTITY), length-carrying text type, DEFAULT and the foreign key with ON DELETE CASCADE.
  • Phase 4, round-trip: parse DDL to model, render it back, parse again and assert the two models are equal (normalized on names, resolved types, key/constraint flags and relationships, not raw SQL). This is the strongest integration check of the pipeline.

Data-type fixtures are built from the seed arrays via the same transform the app uses in seedDataTypes, so tests run without booting the WASM SQLite database.

Notes for reviewers

  • The parser (@guanmingchiu/sqlparser-ts) init() is async and the importer constructor does not await it (correct in the browser, where it resolves before any import). The suite awaits init() once in beforeAll.
  • The round-trip canonicalizes primary keys to non-nullable in its comparison. SQL Server renders the primary key as a table-level constraint, and the importer only forces NOT NULL for inline primary keys, so the flag would otherwise differ on the round trip although the schemas are equivalent. This looks like a small importer asymmetry worth a follow-up.
  • CI (.github/workflows/ci.yml) runs npm ci + npm test on pushes to main and on pull requests. Typecheck and lint are intentionally left out for now: the current codebase does not pass a strict project typecheck or a clean lint, so gating on them would fail CI on pre-existing, unrelated issues. Happy to add them in a separate cleanup PR.

Verifying

npm install
npm test

Introduce automated testing to a project that had no test runner, no
test script and no CI. Vitest fits the existing Vite setup with near-zero
config; a standalone vitest.config.ts wires the @/ alias via
vite-tsconfig-paths and runs in the node environment, avoiding the app's
Tailwind/React/WASM plugins that pure-logic tests do not need.

Cover the cheapest, highest-value surface first: pure helpers with no DOM,
WASM or database. getNextSequence and cloneField (field.ts), the
charset/collation and SQLite integer-column reordering plus enum naming
(render-uttils.ts), and orderTables including the CircularDependencyError
cycle path that backs the Foreign Key Cycle Detection feature.

The build's tsc step is unaffected (plain tsc no-ops on the root config)
and the new files typecheck clean under strict and lint clean.
Cover getImporter().parseSql across MySQL, MariaDB, PostgreSQL, SQLite,
Oracle and SQL Server. Each dialect gets a representative CREATE TABLE +
foreign key + index fixture, asserting table and column counts, primary
key, NOT NULL, UNIQUE and DEFAULT parsing, data-type mapping, and the
captured foreign key (direction, cardinality and ON DELETE action). Also
parse the bundled PostgreSQL dump as a realistic case and verify malformed
input surfaces errors without dropping valid tables or throwing.

Data-type fixtures are built from the seed arrays via the same transform
the app uses in seedDataTypes, so tests run without booting the WASM
SQLite database. The parser is a WASM module whose init() is async; the
importer constructor does not await it, so the suite awaits init() once in
beforeAll, after which every synchronous parseSql call resolves.
Cover getRenderer().renderDDL across MySQL, MariaDB, PostgreSQL, SQLite,
Oracle and SQL Server. A shared DatabaseType fixture (users + posts with a
primary key, auto-increment, NOT NULL UNIQUE column with a length, DEFAULT
and a posts to users foreign key) is rendered per dialect, asserting the
emitted DDL creates both tables and columns and carries the primary key,
unique constraint, dialect-specific auto-increment spelling (AUTO_INCREMENT,
SERIAL, AUTOINCREMENT, IDENTITY), variable-length text type, DEFAULT value
and the foreign key with ON DELETE CASCADE.

The fixture is built from the seed data types so field type ids hydrate the
way the app hydrates them, and embeds relationship source/target objects
because the SQLite renderer orders tables from the raw database before the
migration step re-hydrates. Assertions match quote-agnostic patterns rather
than exact strings, since identifier quoting and formatting differ by dialect.
Add the Phase 4 import -> render -> import round-trip: for each of the six
dialects, parse a CREATE TABLE + foreign key schema into a model, render it
back to DDL, parse the rendered DDL again, and assert the two models are
equal. Comparison is on a normalized model (table and column names, resolved
type names, key/constraint flags, relationships), not raw SQL, since
formatting and identifier quoting legitimately differ. A small in-memory
adapter assembles the DatabaseType the renderer consumes from the importer
output, which the app normally reconstructs via the database.

Primary keys are canonicalized to non-nullable in the comparison: SQL Server
emits its primary key as a table-level constraint and the importer only
forces NOT NULL for inline primary keys, so the flag would otherwise differ
on the round trip although the schemas are equivalent.

Add a GitHub Actions workflow running npm ci + npm test on pushes to main
and on pull requests. Typecheck and lint are intentionally left out for now:
the current codebase does not pass a strict project typecheck or a clean
lint, so gating on them would fail CI on pre-existing, unrelated issues.

@KarimTamani KarimTamani left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the PR and tested it locally. Everything looks good .
This is a great addition to the project.

@KarimTamani

Copy link
Copy Markdown
Member

Thanks @albertoarena for the detailed notes , they're very helpful.

Regarding the init() call for @guanmingchiu/sqlparser-ts, I agree with the approach used in the test suite. Since constructors in TypeScript can't be asynchronous, the importer can't await the parser initialization there. In the application itself, the importer is initialized long before the user imports any SQL, so the parser has sufficient time to initialize before it's actually used.

As for the SQL Server round-trip asymmetry, I agree that this looks like a separate issue rather than something that should block this PR. The normalization in the round-trip comparison is a reasonable way to keep the tests focused on schema equivalence. We can address the importer behavior in a follow-up PR without delaying the introduction of the test suite.

Thanks for putting this together! Having a solid automated test suite and CI in place is a big improvement for StackRender. I really appreciate the effort and the well-structured PR. Looking forward to more contributions!

@KarimTamani
KarimTamani merged commit 994c797 into stackrender:main Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants