Skip to content

Testing Your Lexicon

This guide documents what each test file in a lexicon should cover, with patterns and checklists. Follow these to ensure consistent test quality across lexicons.

The serializer is the core of your lexicon — it converts declarables to output format. Cover these 12 cases:

Create mockResource and mockProperty helpers using DECLARABLE_MARKER:

import { DECLARABLE_MARKER } from "@intentius/chant/declarable";
function mockResource(entityType: string, props: Record<string, unknown>): any {
return {
[DECLARABLE_MARKER]: true,
lexicon: "my-lexicon",
entityType,
kind: "resource",
props,
};
}
function mockProperty(entityType: string, props: Record<string, unknown>): any {
return {
[DECLARABLE_MARKER]: true,
lexicon: "my-lexicon",
entityType,
kind: "property",
props,
};
}
#Test caseWhat it verifies
1Serializer nameserializer.name matches lexicon
2Rule prefixserializer.rulePrefix matches convention
3Empty entitiesserialize(new Map()) returns empty string
4Single resourceProduces valid output format (YAML/JSON)
5Auto-generated namecamelCase export name → kebab-case metadata.name
6Explicit name preservedUser-set metadata.name not overwritten
7Multi-resourceMultiple entities joined correctly (e.g., --- separator)
8Default labels mergeddefaultLabels() values appear in all resources
9Default annotations mergeddefaultAnnotations() values appear in all resources
10Explicit labels overrideResource-level labels take precedence
11Property entities skippedkind: "property" entities don’t appear as separate documents
12Key orderingOutput keys in canonical order (e.g., apiVersion → kind → metadata → spec)

Additional lexicon-specific cases:

  • Specless types (K8s: ConfigMap, Secret)
  • Fallback type resolution (GCP: derive GVK from entity type string)

Test the label/annotation declaration utilities:

Test caseWhat it verifies
Correct markersDEFAULT_LABELS_MARKER and DECLARABLE_MARKER are true
Lexicon property.lexicon matches your lexicon name
Entity type.entityType matches convention
Accessible valuesLabels/annotations are readable
Empty alloweddefaultLabels({}) doesn’t throw
Type guardsisDefaultLabels() true for labels, false for annotations/null/undefined
Cross-checksisDefaultAnnotations() false for labels, and vice versa

Use test.skipIf(!hasGenerated) for tests that depend on the generated lexicon registry:

import { existsSync, readFileSync } from "fs";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
const lexiconPath = join(pkgDir, "src", "generated", "lexicon-{name}.json");
const hasGenerated = existsSync(lexiconPath) && (() => {
try {
const content = JSON.parse(readFileSync(lexiconPath, "utf-8"));
return Object.keys(content).length > 0;
} catch { return false; }
})();
Test casehasGeneratedWhat it verifies
Non-constructor contextNoReturns empty array for const x = 42
Constructor prefixYesnew D returns results including Deployment
Prefix filteringYesnew StatefulS returns StatefulSet
Test casehasGeneratedWhat it verifies
Unknown wordNoReturns undefined for nonexistent resource
Known resourceYesReturns defined hover info
Empty stringYesReturns undefined
Content checkYesHover content is non-empty
Test caseWhat it verifies
Empty YAMLReturns empty resources and parameters
Single resourceCorrect type mapping (apiVersion+kind → type name)
Multi-docMultiple resources from ----separated YAML
Type mappingEach apiVersion/kind combo maps correctly
Non-lexicon filteredResources from other lexicons are ignored
Logical namemetadata.name extracted correctly
PropertiesInclude metadata+spec, exclude apiVersion/kind
Parameters emptyLexicons without parameters return []
Test caseWhat it verifies
Valid TypeScriptOutput contains import + constructor
Correct import sourceUses @intentius/chant-lexicon-{name}
Multiple resourcesMultiple export const declarations
Variable namingkebab-case → camelCase conversion
Empty IRDoesn’t crash on empty input
Nested objectsProper formatting of nested props

Create testdata manifests as instance YAML (not CRD schemas):

src/testdata/manifests/
├── resource-a.yaml # Single resource
├── resource-b.yaml # Single resource
└── full-app.yaml # Multi-doc with 3+ resources
Test caseWhat it verifies
Single resource roundtripParse → generate → contains constructor
Multi-doc roundtripAll resources present in output
Inline YAMLParse+generate works without fixture file
const hasGenerated = existsSync(join(generatedDir, "lexicon-{name}.json"));
test.skipIf(!hasGenerated)("analyze function exists", async () => {
const { analyze } = await import("./coverage");
expect(typeof analyze).toBe("function");
});
test("handles missing files gracefully", async () => {
if (!hasGenerated) {
try { await analyze(); } catch { /* Expected */ }
}
});
function makeCtx(yaml: string) {
return {
outputs: new Map([["lexicon-name", yaml]]),
};
}

Every post-synth check needs both positive and negative tests:

describe("WGCXXX: description", () => {
test("flags bad pattern", () => {
const yaml = `...bad yaml...`;
const diags = wgcXXX.check(makeCtx(yaml));
expect(diags.length).toBeGreaterThanOrEqual(1);
expect(diags[0].checkId).toBe("WGCXXX");
});
test("no diagnostic when correct", () => {
const yaml = `...good yaml...`;
const diags = wgcXXX.check(makeCtx(yaml));
expect(diags).toHaveLength(0);
});
});

The chant YAML parser treats unquoted https://... as key-value pairs. Always quote URLs in test YAML:

# Bad — parsed as { https: "//..." }
- https://www.googleapis.com/auth/cloud-platform
# Good
- "https://www.googleapis.com/auth/cloud-platform"

With tests in place, run the full test suite: bun test lexicons/{name}/ and verify all pass.