# Testing Guide

> How tests are structured, how to run them, and how to write new ones.

---

## Overview

| Property | Detail                                                     |
|----------|------------------------------------------------------------|
| Test runner | Jest v29 with ts-jest preset                               |
| Test files | **63** focused files in `src/tests/unit/`                  |
| Test count | **2460** (v1.7.0) — up from 2276 in v1.7.0, 1333 in v1.6.0 |
| Coverage | ~86.7% stmts, ~75.47% branch, ~77.3% funcs, ~88.78% lines  |
| Fixtures | `src/tests/fixtures/` — real XML + XSD files               |
| Node.js | >= 18 (CI: 18, 20, 22)                                     |

---

## Running Tests

```bash
npm test                                          # all tests
npm run test:coverage                             # with coverage report
npm run test:watch                                # watch mode
npx jest src/tests/unit/xml-parser.test.ts        # single file
npx jest --testNamePattern="parseXml"             # by name
npx jest --verbose                                # verbose output
```

---

## Test Structure

```
src/tests/
├── fixtures/
│   ├── library.xml          valid XML
│   ├── library.xsd          matching schema
│   ├── library-invalid.xml  intentionally invalid
│   └── catalog.xml          CDATA + array test
└── unit/
    ├── xml-parser.test.ts
    ├── xml-lexer.test.ts
    ├── xml-serializer.test.ts
    ├── sax-parser.test.ts
    ├── xsd-parser.test.ts
    ├── schema-model.test.ts
    ├── validation-engine.test.ts
    ├── type-validator.test.ts
    ├── xml-error.test.ts
    ├── validation-result.test.ts
    ├── xml-nodes.test.ts
    ├── xpath-engine.test.ts
    ├── xml-stream-parser.test.ts
    ├── xml-utils.test.ts
    ├── string-reader.test.ts
    ├── parse-budget.test.ts
    ├── schema-cache.test.ts
    ├── formatters.test.ts
    ├── batch-validator.test.ts
    ├── file-utils.test.ts
    ├── xml-to-json.test.ts
    ├── json-to-xml.test.ts
    ├── xslt-transformer.test.ts
    ├── codegen.test.ts
    ├── plugin-registry.test.ts
    ├── v1.4-features.test.ts    v1.4: Pipeline, Compiler, Namespace, XPath cache, Error UX
    ├── v1.5-features.test.ts    v1.5: Identity, Assertions, Preflight, SAX instrumentation
    ├── v1.6-features.test.ts    v1.6: Diff, Inference, Fragment, Encoding, Source maps
    ├── v1.7-features.test.ts    v1.7: PSVI, C14N, xs:redefine, Mixed content
    ├── sha256.test.ts
    ├── schema-cache-hash.test.ts
    ├── dtd-entities.test.ts
    ├── xml-id-base.test.ts
    ├── list-union-edge-cases.test.ts
    ├── xs-assert.test.ts
    ├── xpath2-functions.test.ts
    ├── platform-entry-points.test.ts
    ├── cli.test.ts
    ├── cli-process.test.ts
    ├── coverage-boost.test.ts        broad coverage
    ├── coverage-boost-v170.test.ts   v1.7.0 targeted (SchemaMerger, Psvi, ...)
    ├── coverage-boost-v170b.test.ts  v1.7.0 targeted (XmlDiff, Inference, ...)
    ├── coverage-boost-v170c.test.ts  v1.7.0 targeted (SchemaCompilerLite, SaxParser, ...)
    ├── coverage-boost-v2.test.ts     v1.7.x coverage expansion
    ├── coverage-boost-v3.test.ts     v1.7.x coverage expansion
    ├── coverage-boost-v4.test.ts     v1.7.x coverage expansion
    ├── coverage-boost.test.ts        broad coverage
    ├── coverage-final.test.ts        final gap-filling coverage
    ├── coverage-gap-fill.test.ts     targeted gap filling
    ├── format-cli.test.ts            CLI format output tests
    ├── index-barrel.test.ts          public export surface verification
    ├── np-issue-fixes.test.ts        non-priority issue fixes
    ├── v1.7-features.test.ts         v1.7: PSVI, C14N, xs:redefine, Mixed content
    ├── v1.7-issue-fixes.test.ts      v1.7: T-01 through T-09 issue fixes
    ├── v1.7-regression.test.ts       v1.7: regression coverage
    └── v1.7-post-release-fixes.test.ts  v1.7.x: A-07, P-01, P-04, T-04, T-08, T-09 (26 tests)
```

---

## Writing Tests

```ts
describe('XmlParser', () => {
  it('parses simple element', () => {
    const doc = parseXml('<root/>');
    expect(doc.root?.tagName).toBe('root');
  });

  it('throws PARSE_MISMATCHED_TAG on mismatched tags', () => {
    expect(() => parseXml('<a></b>')).toThrow(
      expect.objectContaining({ code: 'PARSE_MISMATCHED_TAG' })
    );
  });
});
```

---

## Coverage Targets

| Metric | v1.6.0 | v1.7.0 | v2.0 Target |
|--------|--------|--------|------------|
| Statements | 85.51% | 86.7%  | 90% |
| Branches | 74.66% | 75.47% | 90% |
| Functions | 76.27% | 77.3%  | 90% |
| Lines | 87.71% | 88.78% | 90% |

### Known low-coverage areas

| Module | Branch% | Reason |
|--------|---------|--------|
| `src/cli` | ~76% | CLI functions require process.argv mocking |
| `src/io/XmlStreamParser.ts` | 50% | String-chunk branch covered in v1.7.0; binary-chunk tested |
| `sha256.ts` | ~17% | Pure-JS fallback unreachable in Node.js |
| `browser.ts / bun.ts` | low | Platform-specific runtime paths |

---

## Testing Error Conditions

```ts
// Synchronous
expect(() => parseXml('<unclosed>')).toThrow();
expect(() => parseXml('<a></b>')).toThrow(
  expect.objectContaining({ code: 'PARSE_MISMATCHED_TAG' })
);

// Async
await expect(readXmlFile('/no/such/file')).rejects.toThrow();
```

---

## Security Limit Tests

```ts
it('throws SEC_MAX_DEPTH', () => {
  const deep = '<a>'.repeat(10) + '</a>'.repeat(10);
  expect(() => parseXml(deep, { maxDepth: 5 })).toThrow(
    expect.objectContaining({ code: 'SEC_MAX_DEPTH' })
  );
});
```

---

## CLI Tests

Two strategies:
- **In-process** (`cli.test.ts`): import exported helpers — counts toward coverage
- **Spawned** (`cli-process.test.ts`): spawn `dist/cjs/cli/cli.js` — requires `npm run build`

---

## CI

GitHub Actions matrix: Node 18, 20, 22. Steps: `npm ci` → `npm run build` → `npm test -- --ci --coverage`.
