feat(mystery): parseItem — markdown to typed Item
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 } from './loader'
|
import { parseRoom, parseItem } from './loader'
|
||||||
|
|
||||||
const FOYER_MD = `---
|
const FOYER_MD = `---
|
||||||
id: foyer
|
id: foyer
|
||||||
@@ -161,3 +161,56 @@ items: []
|
|||||||
expect(() => parseRoom(md, 'rooms/r.md')).toThrow(/exitDRequires is set but exitDLockedText is missing/)
|
expect(() => parseRoom(md, 'rooms/r.md')).toThrow(/exitDRequires is set but exitDLockedText is missing/)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Add to the bottom of loader.test.ts
|
||||||
|
|
||||||
|
const LAMP_MD = `---
|
||||||
|
id: lamp
|
||||||
|
names: [lamp, oil lamp, torch]
|
||||||
|
short: an oil lamp
|
||||||
|
takeable: true
|
||||||
|
initialState:
|
||||||
|
lit: false
|
||||||
|
---
|
||||||
|
|
||||||
|
An iron oil lamp with a glass chimney. Currently unlit.
|
||||||
|
`
|
||||||
|
|
||||||
|
describe('parseItem', () => {
|
||||||
|
it('parses an item with state', () => {
|
||||||
|
const item = parseItem(LAMP_MD, 'items/lamp.md')
|
||||||
|
expect(item).toEqual({
|
||||||
|
id: 'lamp',
|
||||||
|
names: ['lamp', 'oil lamp', 'torch'],
|
||||||
|
short: 'an oil lamp',
|
||||||
|
long: 'An iron oil lamp with a glass chimney. Currently unlit.',
|
||||||
|
initialState: { lit: false },
|
||||||
|
takeable: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses empty initialState when omitted', () => {
|
||||||
|
const md = `---
|
||||||
|
id: x
|
||||||
|
names: [x]
|
||||||
|
short: x
|
||||||
|
takeable: false
|
||||||
|
---
|
||||||
|
|
||||||
|
The long description.
|
||||||
|
`
|
||||||
|
const item = parseItem(md, 'items/x.md')
|
||||||
|
expect(item.initialState).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws when body is empty', () => {
|
||||||
|
const md = `---
|
||||||
|
id: x
|
||||||
|
names: [x]
|
||||||
|
short: x
|
||||||
|
takeable: false
|
||||||
|
---
|
||||||
|
`
|
||||||
|
expect(() => parseItem(md, 'items/x.md')).toThrow(/empty long description/i)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
+20
-2
@@ -1,7 +1,7 @@
|
|||||||
import matter from 'gray-matter'
|
import matter from 'gray-matter'
|
||||||
import type { Room, RoomDescriptions } from './types'
|
import type { Room, RoomDescriptions, Item } from './types'
|
||||||
import type { Direction } from '../engine/types'
|
import type { Direction } from '../engine/types'
|
||||||
import { roomFrontmatterSchema } from './schema'
|
import { roomFrontmatterSchema, itemFrontmatterSchema } from './schema'
|
||||||
|
|
||||||
const WIKILINK = /^\[\[([^\]|]+)(?:\|[^\]]*)?\]\]$/
|
const WIKILINK = /^\[\[([^\]|]+)(?:\|[^\]]*)?\]\]$/
|
||||||
|
|
||||||
@@ -97,3 +97,21 @@ export function parseRoom(raw: string, sourcePath: string): Room {
|
|||||||
if (fm.safe) room.safe = fm.safe
|
if (fm.safe) room.safe = fm.safe
|
||||||
return room
|
return room
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseItem(raw: string, sourcePath: string): Item {
|
||||||
|
const parsed = matter(raw)
|
||||||
|
const frontmatter = stripWikilink(parsed.data) as Record<string, unknown>
|
||||||
|
const fm = itemFrontmatterSchema.parse(frontmatter)
|
||||||
|
const long = parsed.content.trim()
|
||||||
|
if (long.length === 0) {
|
||||||
|
throw new Error(`${sourcePath}: empty long description`)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: fm.id,
|
||||||
|
names: fm.names,
|
||||||
|
short: fm.short,
|
||||||
|
long,
|
||||||
|
initialState: fm.initialState,
|
||||||
|
takeable: fm.takeable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user