fix(mystery): handle aliased wikilinks; symmetric locked-exit validation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -85,4 +85,79 @@ items: []
|
|||||||
d: { requires: 'rusted-key', lockedNarration: 'The door is locked.' },
|
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/)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+7
-3
@@ -3,7 +3,7 @@ import type { Room, RoomDescriptions } from './types'
|
|||||||
import type { Direction } from '../engine/types'
|
import type { Direction } from '../engine/types'
|
||||||
import { roomFrontmatterSchema } from './schema'
|
import { roomFrontmatterSchema } from './schema'
|
||||||
|
|
||||||
const WIKILINK = /^\[\[(.+)\]\]$/
|
const WIKILINK = /^\[\[([^\]|]+)(?:\|[^\]]*)?\]\]$/
|
||||||
|
|
||||||
function stripWikilink(value: unknown): unknown {
|
function stripWikilink(value: unknown): unknown {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
@@ -21,6 +21,7 @@ function stripWikilink(value: unknown): unknown {
|
|||||||
|
|
||||||
function splitSections(body: string): Record<string, string> {
|
function splitSections(body: string): Record<string, string> {
|
||||||
const sections: Record<string, string> = {}
|
const sections: Record<string, string> = {}
|
||||||
|
// Section names use only [A-Za-z0-9_-]; headers with spaces or dots are silently skipped.
|
||||||
const re = /^##\s+([\w-]+)\s*$/gm
|
const re = /^##\s+([\w-]+)\s*$/gm
|
||||||
const matches = [...body.matchAll(re)]
|
const matches = [...body.matchAll(re)]
|
||||||
for (let i = 0; i < matches.length; i++) {
|
for (let i = 0; i < matches.length; i++) {
|
||||||
@@ -72,10 +73,13 @@ export function parseRoom(raw: string, sourcePath: string): Room {
|
|||||||
exits[dir] = dest
|
exits[dir] = dest
|
||||||
const req = (fm as Record<string, unknown>)[keys.requires] as string | undefined
|
const req = (fm as Record<string, unknown>)[keys.requires] as string | undefined
|
||||||
const locked = (fm as Record<string, unknown>)[keys.locked] as string | undefined
|
const locked = (fm as Record<string, unknown>)[keys.locked] as string | undefined
|
||||||
if (req !== undefined) {
|
if (req !== undefined && locked === undefined) {
|
||||||
if (locked === undefined) {
|
|
||||||
throw new Error(`${sourcePath}: ${keys.requires} is set but ${keys.locked} is missing`)
|
throw new Error(`${sourcePath}: ${keys.requires} is set but ${keys.locked} is missing`)
|
||||||
}
|
}
|
||||||
|
if (locked !== undefined && req === undefined) {
|
||||||
|
throw new Error(`${sourcePath}: ${keys.locked} is set but ${keys.requires} is missing`)
|
||||||
|
}
|
||||||
|
if (req !== undefined && locked !== undefined) {
|
||||||
lockedExits[dir] = { requires: req, lockedNarration: locked }
|
lockedExits[dir] = { requires: req, lockedNarration: locked }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user