Files
halfstreet/src/world/loader.test.ts
T

249 lines
5.2 KiB
TypeScript
Raw Normal View History

import { describe, it, expect } from 'vitest'
import { parseRoom, parseItem, parseEnding } from './loader'
const FOYER_MD = `---
id: foyer
title: "[ Foyer ]"
exitN: "[[hallway]]"
exitS: null
exitE: null
exitW: null
exitU: null
exitD: null
items:
- "[[letter]]"
encounter: null
safe: true
---
## first-visit
You stand in the foyer. A folded letter lies on a table.
A hallway leads north.
## revisit
The foyer.
## examined
A foyer with peeling paper.
`
describe('parseRoom', () => {
it('parses frontmatter and strips wikilinks', () => {
const room = parseRoom(FOYER_MD, 'rooms/foyer.md')
expect(room.id).toBe('foyer')
expect(room.title).toBe('[ Foyer ]')
expect(room.exits).toEqual({ n: 'hallway' })
expect(room.items).toEqual(['letter'])
expect(room.safe).toBe(true)
expect(room.encounter).toBeUndefined()
})
it('captures all three description sections with multi-paragraph prose', () => {
const room = parseRoom(FOYER_MD, 'rooms/foyer.md')
expect(room.descriptions.firstVisit).toBe(
'You stand in the foyer. A folded letter lies on a table.\n\nA hallway leads north.',
)
expect(room.descriptions.revisit).toBe('The foyer.')
expect(room.descriptions.examined).toBe('A foyer with peeling paper.')
})
it('throws when a required section is missing', () => {
const incomplete = FOYER_MD.replace('## examined\nA foyer with peeling paper.\n', '')
expect(() => parseRoom(incomplete, 'rooms/foyer.md')).toThrow(/missing required section.*examined/i)
})
it('throws on malformed frontmatter', () => {
expect(() => parseRoom('## first-visit\nhi', 'rooms/x.md')).toThrow()
})
it('parses a locked exit into lockedExits', () => {
const md = `---
id: r
title: "[ R ]"
exitN: null
exitS: null
exitE: null
exitW: null
exitU: null
exitD: "[[vault]]"
exitDRequires: "[[rusted-key]]"
exitDLockedText: The door is locked.
items: []
---
## first-visit
.
## revisit
.
## examined
.
`
const room = parseRoom(md, 'rooms/r.md')
expect(room.exits).toEqual({ d: 'vault' })
expect(room.lockedExits).toEqual({
d: { requires: 'rusted-key', lockedNarration: 'The door is locked.' },
})
})
it('strips aliased wikilinks like [[id|display text]] to just the id', () => {
const md = `---
id: foyer
title: "[ Foyer ]"
exitN: "[[hallway|the long hallway]]"
exitS: null
exitE: null
exitW: null
exitU: null
exitD: null
items:
- "[[letter|the folded letter]]"
encounter: null
---
## first-visit
.
## revisit
.
## examined
.
`
const room = parseRoom(md, 'rooms/foyer.md')
expect(room.exits).toEqual({ n: 'hallway' })
expect(room.items).toEqual(['letter'])
})
it('throws when locked text is set without requires', () => {
const md = `---
id: r
title: "[ R ]"
exitN: null
exitS: null
exitE: null
exitW: null
exitU: null
exitD: "[[vault]]"
exitDLockedText: The door is locked.
items: []
---
## first-visit
.
## revisit
.
## examined
.
`
expect(() => parseRoom(md, 'rooms/r.md')).toThrow(/exitDLockedText is set but exitDRequires is missing/)
})
it('throws when requires is set without locked text', () => {
const md = `---
id: r
title: "[ R ]"
exitN: null
exitS: null
exitE: null
exitW: null
exitU: null
exitD: "[[vault]]"
exitDRequires: "[[rusted-key]]"
items: []
---
## first-visit
.
## revisit
.
## examined
.
`
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)
})
})
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('')
})
})