render-persistence-schema.spec.ts 4.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. /** Linked catalog regressions for transitive changes and shared definitions. */
  2. import { describe, expect, it } from 'vitest'
  3. import { canonicalizeSchema, schemaDigest, type PersistenceSchemaInventory, type PersistenceType, type SchemaNode } from './persistence-schema-model.ts'
  4. import { renderPersistenceSchemaDefinitions, renderPersistenceSchemaIndex } from './render-persistence-schema.ts'
  5. function fixture(optional = true): PersistenceSchemaInventory {
  6. const types = new Map<string, PersistenceType>()
  7. const roots = ['first/event', 'second/event'].map((event) => {
  8. const nodes: SchemaNode[] = [
  9. { kind: 'object', properties: [{ name: 'type', type: 1, optional: false }, { name: 'data', type: 2, optional: false }], indices: [] },
  10. { kind: 'literal', value: event },
  11. { kind: 'object', properties: [{ name: 'error', type: 3, optional: true }], indices: [] },
  12. { kind: 'object', properties: [{ name: 'reason', type: 4, optional }], indices: [] },
  13. { kind: 'primitive', type: 'string' },
  14. ]
  15. nodes.forEach((_, index) => {
  16. const schema = canonicalizeSchema(nodes, index)
  17. const digest = schemaDigest(schema)
  18. if (!types.has(digest)) types.set(digest, { digest, schema, names: index === 3 ? ['ErrorInfo'] : [], sources: index === 3 ? ['packages/core/example/src/types.ts:8'] : [] })
  19. })
  20. const schema = canonicalizeSchema(nodes, 0)
  21. return { key: `event:${event}`, kind: 'event' as const, event, surface: false, digest: schemaDigest(schema), schema }
  22. })
  23. return { formatVersion: 1, roots, types: [...types.values()] }
  24. }
  25. describe('persistence schema catalog', () => {
  26. it('shows a shared nested definition once and updates both affected root fingerprints', () => {
  27. const before = fixture()
  28. const after = fixture(false)
  29. const definitions = renderPersistenceSchemaDefinitions(after)
  30. expect(definitions.match(/### `ErrorInfo`/gu)).toHaveLength(1)
  31. expect(definitions).toContain('| `reason` | required | `string` |')
  32. expect(definitions).toContain('[`ErrorInfo`](#persistence-type-errorinfo)')
  33. const index = renderPersistenceSchemaIndex(after)
  34. for (const [position, root] of after.roots.entries()) {
  35. expect(root.digest).not.toBe(before.roots[position]?.digest)
  36. expect(index).toContain(root.digest)
  37. }
  38. })
  39. it('keeps named definition anchors when their digest changes', () => {
  40. const before = renderPersistenceSchemaDefinitions(fixture())
  41. const after = renderPersistenceSchemaDefinitions(fixture(false))
  42. expect(before).toContain('<a id="persistence-type-errorinfo"></a>')
  43. expect(after).toContain('<a id="persistence-type-errorinfo"></a>')
  44. expect(before).not.toBe(after)
  45. })
  46. it('refuses a current inventory that omits a referenced definition', () => {
  47. const complete = fixture()
  48. const missing: PersistenceSchemaInventory = { ...complete, types: [] }
  49. expect(() => renderPersistenceSchemaIndex(missing)).toThrow(/absent from the inventory/u)
  50. })
  51. it('renders a recursive definition through a finite self-reference', () => {
  52. const schema = canonicalizeSchema([{ kind: 'object', properties: [{ name: 'next', type: 0, optional: true }], indices: [] }], 0)
  53. const digest = schemaDigest(schema)
  54. const inventory: PersistenceSchemaInventory = {
  55. formatVersion: 1,
  56. roots: [{ key: 'SessionHeader', kind: 'header', digest, schema }],
  57. types: [{ digest, schema, names: ['Recursive'], sources: [] }],
  58. }
  59. expect(renderPersistenceSchemaDefinitions(inventory)).toContain('| `next` | optional | [`Recursive`](#persistence-type-recursive) |')
  60. })
  61. it('uses intrinsic scalar labels even when an alias or reference supplied a name', () => {
  62. const schema = canonicalizeSchema([{ kind: 'primitive', type: 'string' }], 0)
  63. const digest = schemaDigest(schema)
  64. const inventory: PersistenceSchemaInventory = {
  65. formatVersion: 1,
  66. roots: [],
  67. types: [{ digest, schema, names: ['SessionHeader.agentPreset'], sources: [] }],
  68. }
  69. const rendered = renderPersistenceSchemaDefinitions(inventory)
  70. expect(rendered).toContain('### `string`')
  71. expect(rendered).not.toContain('### `SessionHeader.agentPreset`')
  72. })
  73. })