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
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
{
"name": "nanook",
"source": "./",
"description": "nanook.xhub: draft decision tables as XLSX, check the coverage, generate test data."
"description": "nanook.xhub: draft decision tables as XLSX, check the coverage, generate test data from new or existing tables."
}
]
}
10 changes: 6 additions & 4 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "nanook",
"description": "nanook.xhub: draft decision tables (equivalence classes, CASCADE coverage) as XLSX for a form, page or API, check the coverage and generate test data with @xhubio/nanook-table.",
"version": "0.3.0",
"description": "nanook.xhub: draft decision tables (equivalence classes, CASCADE coverage) as XLSX for a form, page or API, check the coverage, generate test data from new or existing tables and use it in tests with @xhubio/nanook-table.",
"version": "0.4.0",
"author": {
"name": "xhub.io",
"url": "https://xhub.io"
Expand All @@ -14,9 +14,11 @@
"test-data",
"equivalence-classes",
"decision-table",
"xlsx"
"xlsx",
"fixtures"
],
"skills": [
"./skills/create-equivalence-class-table"
"./skills/create-equivalence-class-table",
"./skills/generate-test-data"
]
}
2 changes: 2 additions & 0 deletions .claude/commands/createEquivalenceClassTable.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ After the table is created:
1. Open the workbook in a spreadsheet app and check markers and coverage
2. Register further generators in `scripts/generate-fixtures.mts` if the table calls any
3. `node scripts/generate-fixtures.mts resources/<name>-tests.xlsx` to generate the fixtures
4. For own generators, a count that does not match and data-driven tests from the fixtures, use the
skill `generate-test-data` (`/nanook:generate-test-data` in the plugin)

---

Expand Down
1 change: 1 addition & 0 deletions .claude/skills/generate-test-data
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,15 @@ See the [docs/](docs/) directory for detailed guides, API reference, and tutoria

[![skills.sh](https://skills.sh/b/xhubio/nanook-table)](https://skills.sh/xhubio/nanook-table)

The nanook.xhub skill `create-equivalence-class-table` drafts a decision table for a form, page or API
as a formatted XLSX, checks its coverage and generates the test data. It lives in
[`skills/`](skills/create-equivalence-class-table/SKILL.md) and ships in the npm package. It is listed on
[skills.sh](https://skills.sh/xhubio/nanook-table/create-equivalence-class-table), and the docs are
Two nanook.xhub skills live in [`skills/`](skills/) and ship in the npm package:

- [`create-equivalence-class-table`](skills/create-equivalence-class-table/SKILL.md) drafts a decision
table for a form, page or API as a formatted XLSX, checks its coverage and generates the test data.
- [`generate-test-data`](skills/generate-test-data/SKILL.md) starts from a table that already exists:
it inspects the workbook, writes and registers the generators it calls, generates one JSON fixture
per test case, checks the count and wires the fixtures into Vitest or Playwright tests.

They are listed on [skills.sh](https://skills.sh/xhubio/nanook-table), and the docs are
indexed on [Context7](https://context7.com/xhubio/nanook-table).

**Claude Code** (CLI, desktop, web, IDE), as a plugin:
Expand All @@ -189,20 +194,23 @@ indexed on [Context7](https://context7.com/xhubio/nanook-table).
/plugin marketplace add xhubio/nanook-table
/plugin install nanook@nanook
/nanook:create-equivalence-class-table Login form with email and password
/nanook:generate-test-data resources/login-tests.xlsx
```

**Other agents** (Codex, Cursor, Copilot, Gemini CLI and others that read
[Agent Skills](https://agentskills.io)):

```bash
npx skills add xhubio/nanook-table --skill create-equivalence-class-table
npx skills add xhubio/nanook-table --skill generate-test-data
```

**Any agent that reads `AGENTS.md`**: paste the block from
[docs/agents-snippet.md](docs/agents-snippet.md) into your project's `AGENTS.md`. It points the agent
to the Markdown docs in `node_modules/@xhubio/nanook-table/docs/`, which match the installed version.

The generated script needs `exceljs` in your project (`npm install -D exceljs`). Setup for each agent,
The script that `create-equivalence-class-table` generates needs `exceljs` in your project
(`npm install -D exceljs`); `generate-test-data` does not. Setup for each agent,
the docs as plain text (`llms.txt`) and how to check what the agent produced:
[nanook.xhub.io/docs/guide/use-with-ai](https://nanook.xhub.io/docs/guide/use-with-ai).

Expand Down
21 changes: 13 additions & 8 deletions skills/create-equivalence-class-table/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
---
name: create-equivalence-class-table
description: >
nanook.xhub: Draft a decision table (equivalence classes, test cases, CASCADE coverage)
nanook.xhub: Draft a new decision table (equivalence classes, test cases, CASCADE coverage)
as a formatted XLSX for a form, page or API, check its coverage and generate test
data from it with @xhubio/nanook-table. Use when asked to create an equivalence
class table, a decision table, a test case table or test data for a form or API
with Nanook. Also: "equivalence class table", "decision table",
data from it with @xhubio/nanook-table. Use when asked to create or extend an equivalence
class table, a decision table or a test case table for a form or API with Nanook.
For a table that already exists and only needs test data, fixtures or data-driven tests,
use generate-test-data instead. Also: "equivalence class table", "decision table",
"test data table", "nanook table".
license: MIT
metadata:
version: "0.3.0"
version: "0.4.0"
---

# nanook.xhub: Create a decision table
Expand Down Expand Up @@ -51,7 +52,7 @@ node scripts/generate-fixtures.mts resources/<name>-tests.xlsx fixtures/<name>
| Script | What it does | Exit 1 if |
|---|---|---|
| `check-classes.mts` | reads the markers from the cells (not the formula values), recomputes combinations, column products and coverage, reports every class without its own `x` | coverage < 100 %, class without `x`, field without marker, no decision table |
| `generate-fixtures.mts` | Nanook reads the workbook, generators `faker` and `text` are registered, one JSON per test case, count per table | Nanook logged errors (Nanook does not throw, it logs and carries on) |
| `generate-fixtures.mts` | Nanook reads the workbook, generators `faker` and `text` and the filter processors `SimpleArrayFilter` and `SimpleArrayIgnoreFilter` are registered, one JSON per test case, count per table | Nanook logged errors (Nanook does not throw, it logs and carries on) |

The `text` generator (in `generate-fixtures.mts`) provides the edge cases `faker` cannot:
`gen::text:empty` (empty string), `gen::text:spaces:N`, `gen::text:alpha:N`,
Expand Down Expand Up @@ -92,6 +93,8 @@ TCs needed for 100% CASCADE = (sum of all non-preferred EqClasses) + 1 happy
- Run the script → generate the Excel file
- Open it in a spreadsheet → check colours, formulas, markers
- Run Nanook generate → check the fixtures
- Own generators, a run with fewer test cases than expected, using the fixtures in Vitest or
Playwright: the `generate-test-data` skill covers that part

## Column layout (ParserDecision)

Expand Down Expand Up @@ -145,9 +148,9 @@ Category ← TagSection with "negative"/"valid" rows
| GeneratorSwitchSection | Multi-Row | Switch off specific generators per TC |

### ExecuteSection values
- **True**: `x`, `1`, `y`, `j`, `yes`, `ja`, `si`, `true`, `ok`, `T` (case-insensitive)
- **True**: `T`, `1`, `y`, `j`, `yes`, `ja`, `si`, `true`, `ok` (case-insensitive)
- **False**: `F` or any other value
- **CAUTION**: 'x' is recognised as TRUE! Always use 'F' for sub-tables
- **CAUTION**: `x` is recognised as FALSE! Write `T` for tables that generate, `F` for sub-tables

## Marker system

Expand Down Expand Up @@ -554,6 +557,8 @@ Category | TagSection | |
further element of a range reference. Fewer means: a generator failed.
5. Tell the user to open the workbook in a spreadsheet application (colours, formulas, summary row),
and name the assumptions that did not come from their request (lengths, error codes).
6. For the next steps (generators the table calls that are not registered yet, using the fixtures
in tests), point to the `generate-test-data` skill.

## References between tables (Nanook's core feature)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
* text gen::text:empty | spaces:N | alpha:N | email:N
* Register your own generators below if the table calls others.
*
* Filter processors registered (FilterSection, column B of a row = processor name):
* SimpleArrayFilter keep the test case if one of the tags is in the expression (a;b;c)
* SimpleArrayIgnoreFilter drop the test case if one of the tags is in the expression
*
* Nanook does not throw on a failed generator or an unresolved reference; it logs and goes on,
* and the test case is missing. So the logger is checked after the run: exit 1 on any error.
* Compare the count per table with the number of test-case columns (plus one per extra element
Expand All @@ -21,6 +25,8 @@ import {
GeneratorFaker,
DataGeneratorBase,
DataGeneratorRegistry,
SimpleArrayFilterProcessor,
SimpleArrayIgnoreFilterProcessor,
createDefaultFileProcessor
} from '@xhubio/nanook-table'
import type {
Expand Down Expand Up @@ -51,6 +57,13 @@ function randomAlpha(n: number): string {
}

class GeneratorText extends DataGeneratorBase {
// DataGeneratorBase caches per instance id only, and gen::text:… has the same id for every
// field of a test case: all of them would get the first value. Key on the instruction too.
public generate(request: DataGeneratorGenerateRequest): Promise<string> {
const config = request.generatorDirective?.config ?? ''
return super.generate({ ...request, instanceId: `${request.instanceId}:${config}` })
}

protected doGenerate(request: DataGeneratorGenerateRequest): Promise<string> {
const config = request.generatorDirective?.config ?? ''
const [kind, arg] = config.split(':')
Expand Down Expand Up @@ -119,6 +132,8 @@ async function main() {

const writer = new FixtureWriter(logger)
const processor = new TestcaseProcessor({ logger, generatorRegistry: registry, writer: [writer], tables })
processor.addFilterProcessor(new SimpleArrayFilterProcessor({ name: 'SimpleArrayFilter' }))
processor.addFilterProcessor(new SimpleArrayIgnoreFilterProcessor({ name: 'SimpleArrayIgnoreFilter' }))
await processor.process()

const perTable: Record<string, number> = {}
Expand Down
Loading
Loading