feat(mystery): parseEnding — markdown to typed Ending
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect } from 'vitest'
|
import { describe, it, expect } from 'vitest'
|
||||||
import { parseRoom, parseItem } from './loader'
|
import { parseRoom, parseItem, parseEnding } from './loader'
|
||||||
|
|
||||||
const FOYER_MD = `---
|
const FOYER_MD = `---
|
||||||
id: foyer
|
id: foyer
|
||||||
@@ -214,3 +214,35 @@ takeable: false
|
|||||||
expect(() => parseItem(md, 'items/x.md')).toThrow(/empty long description/i)
|
expect(() => parseItem(md, 'items/x.md')).toThrow(/empty long description/i)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const TRUE_ENDING_MD = `---
|
||||||
|
id: true
|
||||||
|
whenFlags:
|
||||||
|
ratGone: true
|
||||||
|
---
|
||||||
|
|
||||||
|
You stand at the top of the stair. The thing below has settled.
|
||||||
|
|
||||||
|
The door behind you opens, and outside, finally, is morning.
|
||||||
|
`
|
||||||
|
|
||||||
|
describe('parseEnding', () => {
|
||||||
|
it('parses an ending with flags and prose', () => {
|
||||||
|
const result = parseEnding(TRUE_ENDING_MD, 'endings/true.md')
|
||||||
|
expect(result.id).toBe('true')
|
||||||
|
expect(result.ending.whenFlags).toEqual({ ratGone: true })
|
||||||
|
expect(result.ending.narration).toBe(
|
||||||
|
'You stand at the top of the stair. The thing below has settled.\n\nThe door behind you opens, and outside, finally, is morning.',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('accepts an empty body (unreachable ending stub)', () => {
|
||||||
|
const md = `---
|
||||||
|
id: wrong
|
||||||
|
whenFlags: {}
|
||||||
|
---
|
||||||
|
`
|
||||||
|
const result = parseEnding(md, 'endings/wrong.md')
|
||||||
|
expect(result.ending.narration).toBe('')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
+17
-1
@@ -1,7 +1,7 @@
|
|||||||
import matter from 'gray-matter'
|
import matter from 'gray-matter'
|
||||||
import type { Room, RoomDescriptions, Item } from './types'
|
import type { Room, RoomDescriptions, Item } from './types'
|
||||||
import type { Direction } from '../engine/types'
|
import type { Direction } from '../engine/types'
|
||||||
import { roomFrontmatterSchema, itemFrontmatterSchema } from './schema'
|
import { roomFrontmatterSchema, itemFrontmatterSchema, endingFrontmatterSchema } from './schema'
|
||||||
|
|
||||||
const WIKILINK = /^\[\[([^\]|]+)(?:\|[^\]]*)?\]\]$/
|
const WIKILINK = /^\[\[([^\]|]+)(?:\|[^\]]*)?\]\]$/
|
||||||
|
|
||||||
@@ -115,3 +115,19 @@ export function parseItem(raw: string, sourcePath: string): Item {
|
|||||||
takeable: fm.takeable,
|
takeable: fm.takeable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ParsedEnding {
|
||||||
|
id: 'true' | 'wrong' | 'bad'
|
||||||
|
ending: { whenFlags: Record<string, string | boolean | number>; narration: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseEnding(raw: string, _sourcePath: string): ParsedEnding {
|
||||||
|
const parsed = matter(raw)
|
||||||
|
// YAML parses bare `true` as boolean; coerce id to string before schema validation.
|
||||||
|
const data = { ...parsed.data, id: String(parsed.data.id) }
|
||||||
|
const fm = endingFrontmatterSchema.parse(data)
|
||||||
|
return {
|
||||||
|
id: fm.id,
|
||||||
|
ending: { whenFlags: fm.whenFlags, narration: parsed.content.trim() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user