3
0
mirror of https://github.com/larvit/adf-codec.git synced 2026-11-12 11:48:29 +01:00

Answer stability round 1: 1 🔴, 0 🟡 (1 misread); keep the task list's headroom, and pin its refusal against a bullet chain's

This commit is contained in:
2026-11-09 05:29:06 +01:00
parent 106f9fd9b4
commit 6bfaa683ee
2 changed files with 23 additions and 1 deletions
@@ -511,6 +511,25 @@ test('writes a task list nested to the deepest level the reader carries, and ref
assert.equal(crowded.ok ? undefined : crowded.error.code, 'unsupported-nesting-depth')
})
test('refuses a list that cannot be a markdown list at the depth a bullet chain in its place refuses', () => {
// The item's text is two markers, which reads as a thematic break and the directive form is out of reach.
const unspellable = bulletList(item(bulletList(item())))
const task = (value: string): AdfNode => node('taskItem', { state: 'TODO' }, text(value))
const tasks = node('taskList', {}, task('a'), node('taskList', {}, task('b'), node('taskList', {}, task('c'))))
const bullets = bulletList(item(said('a'), bulletList(item(said('b'), bulletList(item(said('c')))))))
const refusal = (chain: AdfNode, panels: number): string | undefined => {
let wrapped: AdfNode = bulletList(item(unspellable, chain))
for (let level = 0; level < panels; level += 1) wrapped = node('panel', { panelType: 'info' }, wrapped)
const result = adfToPortableMarkdown(document(wrapped))
return result.ok ? undefined : result.error.code
}
const edge = largestNesting - 4
assert.equal(refusal(bullets, edge - 1), undefined)
assert.equal(refusal(bullets, edge), 'unsupported-nesting-depth')
assert.equal(refusal(tasks, edge - 1), undefined)
assert.equal(refusal(tasks, edge), 'unsupported-nesting-depth')
})
test('writes a task list nested in the task before it, and more than one beside each other in that task', () => {
const done = (value: string): AdfNode => node('taskItem', { state: 'DONE' }, text(value))
const list = (...content: AdfNode[]): AdfNode => ({ content, type: 'taskList' })
+4 -1
View File
@@ -7,6 +7,7 @@ import { emitBlock, joinBlocks, quotedBlocks, spelledList, walkBlocks } from '..
import { emitInlineLine } from '../emit/inline-line.ts'
import { failure, success } from '../../result.ts'
import { isTaskItem } from '../../adf/block-nodes.ts'
import { largestNesting } from '../../nesting.ts'
import { nodeAttrs, nodeContent } from '../../adf/document.ts'
const portableBlockWriters: FlavourBlockWriters = {
@@ -40,6 +41,7 @@ function headed(head: string): (body: BlockText) => BlockText {
function tryTaskList(node: AdfNode, path: ConvertErrorPath, depth: number, writing: Writing): Result<EmittedBlock> {
const items: PlacedBlock[][] = []
let headroom = largestNesting - depth - 1
// The portable reduction opens every task list with a task, so this empty array is replaced before any block is pushed to it.
let item: PlacedBlock[] = []
// A child other than a task nests in the task before it.
@@ -47,13 +49,14 @@ function tryTaskList(node: AdfNode, path: ConvertErrorPath, depth: number, writi
const task = isTaskItem(child)
const walk = task ? taskBlocks(child, [...path, 'content', index], depth + 1, writing) : placedBlock(child, [...path, 'content', index], depth + 1, writing)
if (!walk.ok) return walk
headroom = Math.min(headroom, walk.value.headroom)
if (task) {
item = []
items.push(item)
}
for (const block of walk.value.blocks) item.push(block)
}
const list = spelledList(items.map((blocks) => joinBlocks(blocks, 'list-item')), undefined, Number.POSITIVE_INFINITY)
const list = spelledList(items.map((blocks) => joinBlocks(blocks, 'list-item')), undefined, headroom)
// The portable reduction leaves no task a list item cannot hold: a directive here would break the flavour.
return list === undefined ? failure('unsupported-node-shape', 'a task holds blocks no list item spells', path) : success(list)
}