5c: match the mark set's nesting on both sides of the round-trip
This commit is contained in:
@@ -61,14 +61,16 @@ test('rejects a node whose shape ProseMirror JSON cannot hold', () => {
|
||||
})
|
||||
|
||||
test('names the attribute nesting past the levels the parser reads one at, and still calls the value a document', () => {
|
||||
const deeper = (key: string, type: string): string => `the ${key} attribute of ${type} nests deeper than the ${largestNesting} levels an attribute carries`
|
||||
const deeper = (key: string, type: string, levels: number = largestNesting): string =>
|
||||
`the ${key} attribute of ${type} nests deeper than the ${levels} levels an attribute carries`
|
||||
assert.equal(fault(withAttribute(nested(largestNesting))), 'accepted')
|
||||
assert.equal(fault(withAttribute(nested(largestNesting + 1))), deeper('a', 'paragraph'))
|
||||
assert.equal(faultCode(withAttribute(nested(largestNesting + 1))), 'unsupported-nesting-depth')
|
||||
assert.equal(isAdfDocument(withAttribute(nested(largestNesting + 1))), true)
|
||||
const marked = { content: [{ marks: [{ attrs: { a: nested(largestNesting + 1) }, type: 'link' }], text: 'x', type: 'text' }], type: 'doc', version: 1 }
|
||||
assert.equal(fault(marked), deeper('a', 'link'))
|
||||
assert.equal(isAdfDocument(marked), true)
|
||||
const marked = (levels: number): unknown => ({ content: [{ marks: [{ attrs: { a: nested(levels) }, type: 'link' }], text: 'x', type: 'text' }], type: 'doc', version: 1 })
|
||||
assert.equal(fault(marked(largestNesting - 3)), 'accepted')
|
||||
assert.equal(fault(marked(largestNesting - 2)), deeper('a', 'link', largestNesting - 3))
|
||||
assert.equal(isAdfDocument(marked(largestNesting - 2)), true)
|
||||
})
|
||||
|
||||
test('accepts the JSON values an attribute may hold', () => {
|
||||
|
||||
+8
-5
@@ -23,6 +23,9 @@ export type AdfDocument = {
|
||||
version: number
|
||||
}
|
||||
|
||||
// A block directive spells the whole mark set as one JSON attribute, so a mark's value sits three levels inside it.
|
||||
const markAttributeNesting = largestNesting - 3
|
||||
|
||||
const documentKeys = ['content', 'type', 'version']
|
||||
const markKeys = ['attrs', 'type']
|
||||
const nodeKeys = ['attrs', 'content', 'marks', 'text', 'type']
|
||||
@@ -44,8 +47,8 @@ export function adfDocumentFault(value: unknown): ConvertFault | undefined {
|
||||
return nestingFault(content)
|
||||
}
|
||||
|
||||
export function attributeNestingMessage(key: string, type: string): string {
|
||||
return `the ${key} attribute of ${type} nests deeper than the ${largestNesting} levels an attribute carries`
|
||||
export function attributeNestingMessage(key: string, type: string, levels: number = largestNesting): string {
|
||||
return `the ${key} attribute of ${type} nests deeper than the ${levels} levels an attribute carries`
|
||||
}
|
||||
|
||||
export function carriesOnly(node: AdfNode, attributes: readonly string[]): boolean {
|
||||
@@ -101,15 +104,15 @@ function nestingFault(nodes: readonly AdfNode[]): ConvertFault | undefined {
|
||||
|
||||
function marksFault(marks: readonly AdfMark[] | undefined): ConvertFault | undefined {
|
||||
for (const mark of marks ?? []) {
|
||||
const fault = attributesFault(mark.attrs, mark.type)
|
||||
const fault = attributesFault(mark.attrs, mark.type, markAttributeNesting)
|
||||
if (fault !== undefined) return fault
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function attributesFault(attrs: AdfAttributes | undefined, type: string): ConvertFault | undefined {
|
||||
function attributesFault(attrs: AdfAttributes | undefined, type: string, levels: number = largestNesting): ConvertFault | undefined {
|
||||
for (const [key, value] of Object.entries(attrs ?? {})) {
|
||||
if (overNested(value)) return { code: 'unsupported-nesting-depth', message: attributeNestingMessage(key, type) }
|
||||
if (overNested(value, levels)) return { code: 'unsupported-nesting-depth', message: attributeNestingMessage(key, type, levels) }
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -336,19 +336,33 @@ test('refuses marks and attributes nested deeper than the emitter carries', () =
|
||||
assert.equal(code(adfToMarkdown(document(paragraph({ marks, text: 'x', type: 'text' })))), 'unsupported-nesting-depth')
|
||||
let attrs: AdfMark['attrs'] = { depth: 'x' }
|
||||
for (let depth = 0; depth < 600; depth += 1) attrs = { depth: attrs }
|
||||
const deeper = (key: string, type: string): string =>
|
||||
`unsupported-nesting-depth: the ${key} attribute of ${type} nests deeper than the ${largestNesting} levels an attribute carries`
|
||||
assert.equal(markdown(adfToMarkdown(document(paragraph({ marks: [{ attrs, type: 'em' }], text: 'x', type: 'text' })))), deeper('depth', 'em'))
|
||||
const card = (levels: number): AdfNode => {
|
||||
let data: JsonValue = 1
|
||||
for (let level = 0; level < levels; level += 1) data = [data]
|
||||
return { attrs: { data, url: 'https://example.com/a' }, type: 'inlineCard' }
|
||||
const deeper = (key: string, type: string, levels: number = largestNesting): string =>
|
||||
`unsupported-nesting-depth: the ${key} attribute of ${type} nests deeper than the ${levels} levels an attribute carries`
|
||||
const nested = (levels: number): JsonValue => {
|
||||
let value: JsonValue = 1
|
||||
for (let level = 0; level < levels; level += 1) value = [value]
|
||||
return value
|
||||
}
|
||||
const card = (levels: number): AdfNode => ({ attrs: { data: nested(levels), url: 'https://example.com/a' }, type: 'inlineCard' })
|
||||
// A block directive spells the mark set as one JSON attribute, so a mark's value is read three levels in.
|
||||
const marked = (levels: number): AdfNode => ({
|
||||
attrs: { panelType: 'info' },
|
||||
content: [paragraph({ text: 'x', type: 'text' })],
|
||||
marks: [{ attrs: { deep: nested(levels) }, type: 'em' }],
|
||||
type: 'panel',
|
||||
})
|
||||
const roundTrips = (node: AdfNode): void => {
|
||||
const spelled = adfToMarkdown(document(node))
|
||||
assert.ok(spelled.ok, spelled.ok ? '' : spelled.error.message)
|
||||
assert.deepEqual(markdownToAdf(spelled.value), { ok: true, value: document(node) })
|
||||
}
|
||||
|
||||
assert.equal(markdown(adfToMarkdown(document(paragraph({ marks: [{ attrs, type: 'em' }], text: 'x', type: 'text' })))), deeper('depth', 'em', largestNesting - 3))
|
||||
assert.equal(markdown(adfToMarkdown(document(paragraph(card(largestNesting + 1))))), deeper('data', 'inlineCard'))
|
||||
assert.deepEqual(path(adfToMarkdown(document(paragraph(card(largestNesting + 1))))), [])
|
||||
const spelled = adfToMarkdown(document(paragraph(card(largestNesting))))
|
||||
assert.ok(spelled.ok, spelled.ok ? '' : spelled.error.message)
|
||||
assert.deepEqual(markdownToAdf(spelled.value), { ok: true, value: document(paragraph(card(largestNesting))) })
|
||||
roundTrips(paragraph(card(largestNesting)))
|
||||
assert.equal(markdown(adfToMarkdown(document(marked(largestNesting - 2)))), deeper('deep', 'em', largestNesting - 3))
|
||||
roundTrips(marked(largestNesting - 3))
|
||||
})
|
||||
|
||||
test('escapes a literal delimiter that would merge with an emitted one', () => {
|
||||
|
||||
@@ -441,6 +441,7 @@ test('names the depth an attribute value nests past, never the kind the JSON rea
|
||||
`unsupported-nesting-depth: the ${key} attribute of ${type} nests deeper than the ${largestNesting} levels an attribute carries`
|
||||
assert.equal(content(markdownToAdf(`:::tableCell {colwidth="${nested(largestNesting + 1)}"}\n:::\n`)), deeper('colwidth', 'tableCell'))
|
||||
assert.equal(content(markdownToAdf(`::rule {marks="${nested(largestNesting + 1)}"}\n`)), deeper('marks', 'rule'))
|
||||
assert.equal(content(markdownToAdf(`::rule {marks="[{\\"attrs\\":{\\"deep\\":${nested(largestNesting - 2)}},\\"type\\":\\"em\\"}]"}\n`)), deeper('marks', 'rule'))
|
||||
assert.equal(content(markdownToAdf(`::media {width="${nested(largestNesting + 1)}"}\n`)), 'unsupported-node-shape: the width attribute of media is no number')
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user