Workflow: Alexandrite Integration Tests
Use the command reference at reference/compiler-scripts.md for test runner syntax, snapshot workflows, filters, and trace debugging.
Language: Fixtures use PureScript syntax, not Haskell.
Choose the category first
| Category | Alias | Use for | Harness pattern |
|----------|-------|---------|-----------------|
| checking | c | Type checking, inference, kinds, roles, constraints, diagnostics after checking | Main.purs only |
| lowering | l | Lowered core output, binding/equation structure, source-to-core name links | every .purs file |
| resolving | r | Name resolution, imports, exports, qualification, duplicate-name diagnostics | every .purs file |
| lsp | - | Hover, definition, completion, import edits, source locations in LSP reports | Main.purs only |
When a behavior spans phases, test the earliest category that directly owns the behavior. Add a later-phase fixture only if the later report is the clearest way to make the regression reviewable.
Creating a Test
1. Create fixture directory
just t <category> --create "descriptive name"
The CLI picks the next timestamped fixture number, creates the folder under tests-integration/fixtures/<category>/, and writes a Main.purs template.
Tests are auto-discovered by build.rs.
2. Write focused PureScript modules
Keep each fixture about one behavior. Use a small Main.purs by default, and add supporting modules only when imports, exports, qualification, or cross-module behavior are part of the test.
Checking fixtures
Pair explicitly checked and inferred variants when both modes matter:
module Main where
-- Checking mode: explicit signature constrains type checker
test :: Array Int -> Int
test [x] = x
-- Inference mode: type checker infers unconstrained
test' [x] = x
Name declarations predictably: test, test', test2, test2', etc. Include only edge cases relevant to the behavior.
Lowering fixtures
Write source that exposes the lowered structure being tested. Prefer simple declarations whose snapshot makes binding, equation, or source-link changes obvious.
Resolving fixtures
Use descriptive module names when multiple modules participate, such as Library.purs, ReExporter.purs, and Main.purs. Because every .purs file in a resolving fixture is snapshotted, review all generated .snap files before accepting.
LSP fixtures
Use Main.purs as the scenario driver. Add supporting modules for imported symbols and completion candidates. LSP snapshots are generated from Main.purs; supporting modules influence the report but do not get their own LSP snapshots.
3. Run and review
just t <category> NNN MMM
4. Accept or reject snapshots
just t <category> NNN --diff # Inspect a fixture diff
just t <category> NNN --accept # Accept a specific fixture
just t <category> NNN --reject # Reject a specific fixture
just t <category> --accept --confirm # Accept all pending snapshots
Multi-File Tests
For imports, re-exports, or cross-module behavior:
tests-integration/fixtures/<category>/NNN_import_test/
├── Main.purs # Scenario driver
├── Lib.purs # Supporting module
└── Main.snap # Generated snapshot where the harness snapshots Main.purs
Lib.purs:
module Lib where
life :: Int
life = 42
data Maybe a = Just a | Nothing
Main.purs:
module Main where
import Lib (life, Maybe(..))
test :: Maybe Int
test = Just life
- Module name must match filename
- Checking and LSP fixtures snapshot only
Main.purs - Lowering and resolving fixtures snapshot every
.pursfile
Snapshot Review Focus
Checking
Terms
functionName :: InferredOrCheckedType
...
Types
TypeName :: Kind
...
Errors
ErrorKind { details } at [location]
Check inferred/checked types, kind/role output, constraints, diagnostics, and source locations.
Lowering
Check the lowered module report, especially declarations, binders, equations, and source links. Unexpected name-link changes are often as important as textual output changes.
Resolving
Check local/imported/exported references, qualification, hidden imports, re-exports, and duplicate-name diagnostics. Multi-module resolving fixtures can update many snapshots; each changed module should be intentional.
LSP
Check hover text, definitions, completions, edits, and reported positions. Review both the source excerpt and the resulting LSP payload.
Acceptance Criteria
Before accepting, verify:
-
The category is appropriate
- Checking owns type inference/checking behavior
- Lowering owns lowered core/source-link behavior
- Resolving owns name/import/export behavior
- LSP owns editor-facing reports
-
The fixture is narrow
- One behavior per fixture
- Supporting modules exist only when they clarify the behavior
-
Snapshots are intentional
- Checking types are correct
test :: Array Int -> Int- signature preservedtest' :: forall t. Array t -> t- polymorphism inferred- Lowering/resolving/LSP changes match the feature or bug being tested
-
No unexpected
???test :: ???- STOP: inference failureCannotUnify { ??? -> ???, Int }- OK in error tests
-
Errors appear where expected
- Confirm error kind matches (
NoInstanceFound,CannotUnify) - Verify location points to correct declaration
- Confirm error kind matches (
-
Polymorphism is appropriate in checking snapshots
- Type variables scoped correctly
- Constraints propagate as expected
Common Issues
| Symptom | Likely Cause |
|---------|--------------|
| test :: ??? | Syntax error or undefined names |
| Unexpected monomorphism | Missing polymorphic context |
| Wrong error location | Check binder/expression placement |
| Missing types in snapshot | Module header or imports incorrect |
| Missing expected module snapshot | Category snapshots only Main.purs (checking, lsp) or module filename does not match module header |
| Extra resolving/lowering snapshot | Every .purs file is snapshotted in resolving and lowering |