= Cutdown conformance corpus

This directory contains the golden test fixtures for the Cutdown language spec. A compliant parser must pass all of them.

== Structure

Fixtures are organized by spec section. Each subdirectory maps to one section:

```
tests/
  003-document-model/
  004-block-segments/
  005-inline-segments/
  006-universal-attributes/
  008-escaping/
  010-block-structure/
  012-whitespace-rules/
  017-canonical-form/    ← writer fixtures; assert `canonical`, not `ast`
  diagnostics/          ← one fixture per CDN-xxxx diagnostic code
```

A test runner must walk all ``*.yaml`` files recursively — loading only the top level will silently pass zero tests.

== Fixture format

Each ``.yaml`` file is a single fixture:

```yaml
id: emphasis-basic
section: "5.2"
description: "__ delimiters produce Emphasis; single _ is literal text"

input: "__bold__"

ast:
  - type: Paragraph
    children:
      - type: Emphasis
        children:
          - type: Text
            value: bold
```

=== Fields

| Field | Required | Description |
|---|---|---|
| ``id`` | yes | Unique fixture ID (kebab-case, matches filename without ``.yaml``) |
| ``section`` | yes | Spec section number as a string (e.g. ``"9.4"``) |
| ``description`` | yes | One-line human-readable description |
| ``input`` | yes | Raw Cutdown source text |
| ``ast`` | — | Expected children of the first page (see [Assertions](#assertions)) |
| ``pages`` | — | Expected full ``Document.children`` array; use when the fixture spans multiple pages. Overrides ``ast`` when present |
| ``diagnostics`` | — | Expected diagnostics (see [Assertions](#assertions)); if omitted, the parser must emit zero diagnostics |
| ``canonical`` | — | Expected canonical source ([§17](/latest/#s-17)). Used only by fixtures under ``017-canonical-form/`` |

Exactly ONE OF ``ast`` or ``pages`` MUST be present, except under ``017-canonical-form/``, where ``canonical`` is present instead.

=== Assertions

**AST matching** uses a **deep subset match**:

- All keys present in the expected object MUST exist in the actual output with identical values.
- Extra keys in the actual output MAY exist and are ignored (implementations may add position info, etc.).
- Arrays MUST be the **same length**; each element is subset-matched recursively.
- Primitives MUST be compared by value (``===``).

This means a minimal fixture only needs to assert the fields it cares about — it will not break if the parser adds new optional fields later.

=== Streaming-profile assertions

Fixtures under ``016-streaming-conformance-profile/`` use the ordinary final-input assertions above and MAY add a ``streaming`` object:

```yaml
streaming:
  unit: unicode-scalar
  checkpoints:
    - at: 2
      ast: []
      diagnostics: []
```

``at`` is the count of decoded Unicode scalar values from the beginning of ``input``. A streaming-profile runner MUST parse **every** scalar prefix of ``input``, including the empty prefix and the full input. It MUST verify total, deterministic parsing for every prefix. It additionally verifies the listed checkpoint AST/pages and diagnostics. Each checkpoint is evaluated as an ordinary input snapshot; it is not a separate parser mode.

**Diagnostics matching** is order-independent:

- Every ``{ code, level }`` entry listed under ``diagnostics`` MUST appear in the actual output.
- No diagnostic MAY appear in the actual output that is not listed in the fixture.
- If ``diagnostics`` is omitted entirely, the parser MUST produce zero diagnostics.

=== ``ast`` vs ``pages``

``ast`` matches the ``children`` array of the first page — the common case for single-page documents:

```yaml
ast:
  - type: Paragraph
    children:
      - type: Text
        value: Hello
```

``pages`` matches the full ``Document.children`` array and is used when the fixture tests page-splitting (PageBreaker, Meta block pagination). A Ghost Page is written ``{}``:

```yaml
pages:
  - children:
      - type: Paragraph
        children:
          - type: Text
            value: First
  - {}
```

== Canonical-form fixtures

Fixtures under ``017-canonical-form/`` test a **writer**, not a parser. ``input`` is valid source in some spelling; ``canonical`` is the source a conforming writer emits for the same AST ([§17](/latest/#s-17)). A runner parses ``input``, writes the result back out, and compares to ``canonical`` byte for byte. Parsing ``canonical`` MUST yield the same AST as parsing ``input`` — that equality is what makes the pair an alias.

A parser-only implementation skips this directory. Nothing here is a parser obligation.

== Attribute fixtures

Attributes are asserted as an ordered array of ``{ key, value }`` objects. The ``class`` shorthand (``.class``) produces ``value: string[]`` (array of strings); all other values are plain strings:

```yaml
ast:
  - type: List
    attributes:
      - key: class
        value: [list-class]
    children:
      - type: ListItem
        attributes:
          - key: class
            value: [item-class]
```

== Diagnostic fixtures

Fixtures in ``diagnostics/`` test warning behaviour. The fixture asserts both the AST produced (recovery output) and the diagnostic code emitted:

```yaml
id: cdn-0001-unclosed-codeblock
section: "4.4"
description: "Unclosed ``` fence; content runs to end of document; CDN-0001 warning emitted"

input: "```python\nx = 1\n"

ast:
  - type: CodeBlock
    language: python
    content: x = 1

diagnostics:
  - code: CDN-0001
    level: warning
```

== ID and filename conventions

- ID is kebab-case: ``code-block-basic``, ``cdn-0001-unclosed-codeblock``
- Filename matches ID exactly: ``code-block-basic.yaml``
- Diagnostic fixtures are prefixed with the code: ``cdn-0001-*.yaml``
- Variant fixtures suffix the base name: ``emphasis-basic``, ``emphasis-triple``, ``emphasis-unclosed``

== Contributing a test

1. Identify the spec section the fixture covers.
2. Create ``tests/<section-dir>/<id>.yaml``. Use an existing fixture in that directory as a template.
3. Fill all required fields. Keep ``input`` minimal — test one behaviour per fixture.
4. Verify the ``id`` is unique across the entire corpus (no two fixtures share an ``id``).
5. Open a PR. See [``CONTRIBUTION.md``](/latest/contribution) for process.
