Workflow: Iris Integration Tests

SkillDev tools

Workflow for adding and updating Iris integration-test fixtures for unified compiler, lowering, resolving, and LSP behavior. Use when creating compiler integration tests, reviewing snapshots or generated JavaScript, or using `just t <category>`.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Workflow: Iris Integration Tests skill

What this skill tells your AI

The instructions your AI receives, as published by purefunctor/purescript-iris in .agents/skills/workflow-integration-tests/SKILL.md and read by ahel’s review.

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

CategoryAliasUse forHarness pattern
compilercTypes, diagnostics, semantic recovery, functional conversion, generated JavaScript, and executionMain.purs, named reports, output/, optional verify.mjs
loweringlLowered core output, binding/equation structure, source-to-core name linksevery .purs file
resolvingrName resolution, imports, exports, qualification, duplicate-name diagnosticsevery .purs file
lsp-Hover, definition, completion, import edits, source locations in LSP reportsMain.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 the datatest harnesses in tests-integration/tests/.

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.

Compiler fixtures

Use tests-integration/fixtures/compiler/<fixture>/Main.purs as the entry point. Main.checking.snap contains checked types, kinds, and declaration metadata, without diagnostics. Main.diagnostics.snap contains parser, checking, foreign, and backend diagnostics for all fixture-owned modules with stable fixture-relative paths. Main.semantic.snap contains checked trees, including recovery. Main.functional.snap contains a successful functional tree or an explicit rejection.

Track generated JavaScript and adjacent FFI under output/ only for fixture-owned modules reachable from Main.purs. Add verify.mjs when Node execution is part of the contract; it runs against a fresh full dependency closure, never tracked goldens. Do not commit registry output, runtime.js, or a fixture package.json.

Compiler fixtures use tests-integration/packages.json. Prefer real package modules to stand-ins. Deliberately malformed package cases require a fixture-local replacements.json mapping every replaced registry module to a nonempty reason; Prim and fixture-module collisions cannot be replaced. Keep both ordinary and malformed fixtures focused on one behavior.

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.

For semantic behavior, write the smallest source that distinguishes the recovery, declaration, expression, binder, or evidence shape under test.

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

When an intentional compiler change affects generated JavaScript, review the reported files and update only the relevant fixtures:

just t compiler NNN --update-output

Ordinary compiler runs must remain read-only. Do not set IRIS_UPDATE_JAVASCRIPT_OUTPUT directly; compiler-scripts owns that implementation detail.

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/compiler/NNN_import_test/
├── Main.purs    # Scenario driver
├── Lib.purs     # Supporting module
├── Main.checking.snap
├── Main.diagnostics.snap
├── Main.semantic.snap
├── Main.functional.snap
└── output/      # Reachable fixture-owned JavaScript

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
  • Compiler fixtures enter through Main.purs; diagnostics cover all fixture-owned modules
  • LSP fixtures snapshot only Main.purs
  • Lowering and resolving fixtures snapshot every .purs file

Snapshot Review Focus

Compiler

Review each named report against its contract. Checking contains only types; diagnostics cover all fixture-owned modules with stable paths; semantic preserves useful recovery structure; and functional records success or explicit rejection. Review every changed output/ file for reachability and fixture ownership. When verify.mjs exists, confirm it passes against fresh full generated output.

Checking output resembles:

Terms
functionName :: InferredOrCheckedType
...

Types
TypeName :: Kind
...

Check diagnostics and locations in Main.diagnostics.snap, not the types-only checking report. In Main.semantic.snap, check declarations, finalized types and kinds, binders, expressions, evidence, and recovery.

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:

  1. The category is appropriate

    • Compiler owns types, diagnostics, semantic recovery, functional conversion, and generated or executed JavaScript
    • Lowering owns lowered core/source-link behavior
    • Resolving owns name/import/export behavior
    • LSP owns editor-facing reports
  2. The fixture is narrow

    • One behavior per fixture
    • Supporting modules exist only when they clarify the behavior
  3. Snapshots are intentional

    • Compiler reports and generated JavaScript changes are correct
    • test :: Array Int -> Int - signature preserved
    • test' :: forall t. Array t -> t - polymorphism inferred
    • Semantic/lowering/resolving/LSP changes match the feature or bug being tested
  4. No unexpected ???

    • test :: ??? - STOP: inference failure
    • CannotUnify { ??? -> ???, Int } - OK in error tests
  5. Errors appear where expected

    • Confirm error kind matches (NoInstanceFound, CannotUnify)
    • Verify location points to correct declaration
  6. Polymorphism is appropriate in Main.checking.snap

    • Type variables scoped correctly
    • Constraints propagate as expected

Common Issues

SymptomLikely Cause
test :: ???Syntax error or undefined names
Unexpected monomorphismMissing polymorphic context
Wrong error locationCheck binder/expression placement
Missing types in snapshotModule header or imports incorrect
Missing compiler diagnosticsConfirm the source is fixture-owned and loaded; diagnostics include unreferenced supporting modules and use source paths, not module-header names
Extra resolving/lowering snapshotEvery .purs file is snapshotted in resolving and lowering

Signals

GitHub stars
102
Forks
11
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
workflow-integration-tests
Source
github.com/purefunctor/purescript-iris