3
0
mirror of https://github.com/larvit/adf-codec.git synced 2026-11-14 20:40:25 +01:00

Answer stability round 1: a task list nested beside a task item of paragraphs stays nested when written, the property checks every block task item holds paragraphs alone, item 49 names the checkbox loss it closes, and item 93 files the stale refusal path

This commit is contained in:
2026-10-09 13:21:44 +02:00
committed by Lillem4n
parent 1ece265ea4
commit 67bdc3ca44
5 changed files with 29 additions and 7 deletions
+12 -1
View File
@@ -25,6 +25,16 @@ function shownText(nodes: readonly AdfNode[]): string[] {
return shown.filter((text) => text !== '')
}
function heldByBlockTaskItems(nodes: readonly AdfNode[]): string[] {
const held: string[] = []
for (const node of nodes) {
const content = node.content ?? []
if (node.type === 'blockTaskItem') for (const child of content) held.push(child.type)
for (const type of heldByBlockTaskItems(content)) held.push(type)
}
return held
}
test('a generated document emits markdown that reads back to it', { timeout: propertyTimeout }, () => {
fc.assert(
fc.property(adfDocument, (document) => {
@@ -38,13 +48,14 @@ test('a generated document emits markdown that reads back to it', { timeout: pro
)
})
test('a generated document writes portable markdown refusing only what the guard refuses, and that markdown reads back to its text and to itself', { timeout: propertyTimeout }, () => {
test('a generated document writes portable markdown refusing only what the guard refuses, and that markdown reads back to its text and to itself, a block task item holding paragraphs alone', { timeout: propertyTimeout }, () => {
fc.assert(
fc.property(adfDocument, (document) => {
const written = adfToPortableMarkdown(document)
assert.ok(written.ok, written.ok ? '' : `${written.error.code}: ${written.error.message}`)
const read = portableMarkdownToAdf(written.value)
assert.ok(read.ok, read.ok ? '' : `${read.error.code}: ${read.error.message} — reading ${JSON.stringify(written.value)}`)
assert.deepEqual(heldByBlockTaskItems(read.value.content ?? []).filter((type) => type !== 'paragraph'), [], `reading ${JSON.stringify(written.value)}`)
const reduced = reduceToPortable(document)
assert.deepEqual(shownText(read.value.content ?? []), reduced.ok ? shownText(reduced.value.content ?? []) : reduced, `reading ${JSON.stringify(written.value)}`)
assert.deepEqual(adfToPortableMarkdown(read.value), written, `reading ${JSON.stringify(written.value)}`)
@@ -148,6 +148,9 @@ test('reads a bullet list whose every item leads with a task marker to a task li
test('moves a nested task list beside its item and makes an item holding more than one paragraph a block task item', () => {
assert.deepEqual(read('- [x] Parent\n - [ ] Child\n- [ ] Next\n'), [bare('taskList', task('DONE', text('Parent')), bare('taskList', task('TODO', text('Child'))), task('TODO', text('Next')))])
assert.deepEqual(read('- [x] First.\n\n Second.\n- [ ] Next\n'), [bare('taskList', node('blockTaskItem', { state: 'DONE' }, said('First.'), said('Second.')), task('TODO', text('Next')))])
const nested = bare('taskList', node('blockTaskItem', { state: 'DONE' }, said('p1'), said('p2')), bare('taskList', task('TODO', text('n'))), task('TODO', text('B')))
assert.deepEqual(read('- [x] p1\n\n p2\n - [ ] n\n- [ ] B\n'), [nested])
assert.deepEqual(roundTripped(nested), [nested])
})
test('stands a block a task item cannot hold, and what follows it in the item, after the task list, which resumes at the next task', () => {
@@ -132,6 +132,8 @@ test('spells a task list as a bullet list whose items lead with their state', ()
const blockTask = node('blockTaskItem', { state: 'DONE' }, said('First.'), said('Second.'))
const codeTask = node('blockTaskItem', { state: 'TODO' }, { content: [text('x')], type: 'codeBlock' })
assert.equal(portable(node('taskList', {}, blockTask, codeTask)), '- [x] First.\n\n Second.\n- [ ]\n\n```\nx\n```\n')
const parent = node('blockTaskItem', { state: 'TODO' }, said('q'), said('q2'))
assert.equal(portable(node('taskList', {}, parent, node('taskList', {}, task('DONE', 'n')), task('DONE', 'B'))), '- [ ] q\n\n q2\n - [x] n\n- [x] B\n')
const listTask = node('blockTaskItem', { state: 'DONE' }, bulletList(item(said('a'))))
assert.equal(portable(node('taskList', {}, task('TODO', ''), listTask, node('taskList', {}, task('TODO', 'b')))), '- \\[ ]\n- \\[x]\n- a\n- \\[ ] b\n')
assert.equal(portable(node('taskList', {}, task('DONE', 'a'), node('taskList', {}, task('TODO', '')))), '- [x] a\n - [ ]\n')
@@ -314,11 +314,8 @@ function reduceTaskList(node: AdfNode, reduction: Reduction): Result<AdfNode[]>
return success(listOf(nodeContent(tasksAsText({ content: tasks, type: 'taskList' })), 'bulletList'))
}
// The writer nests a list in the task before it, so one closing a block task item's blocks merges with it.
function nestIn(tasks: AdfNode[], nested: readonly AdfNode[]): void {
const previous = tasks.at(-1)
if (previous?.type === 'blockTaskItem') tasks[tasks.length - 1] = { ...previous, content: mergedLists([...nodeContent(previous), ...nested]) }
else for (const block of mergedLists(nested)) tasks.push(block)
for (const block of mergedLists(nested)) tasks.push(block)
}
function isTask(node: AdfNode | undefined): boolean {