oficina / slides Public

Clone
git clone https://amadan.net/oficina/slides

Plain git — no account needed to clone.

Download

Download this file

1// check.mjs: the accepted-shape check over a document's text, and the
2// sanitiser the stage runs before binding (codex 3). The messages are
3// core/src/check.rs's, so a case here has a twin in its tests.
4import test from 'node:test'
5import assert from 'node:assert/strict'
6import * as Y from 'yjs'
7import { yXmlFragmentToProseMirrorRootNode } from 'y-prosemirror'
8import { applyOps, view } from './ops.mjs'
9import { schema, readText } from './schema.mjs'
10import { checkDoc, checkFragment, sanitiseFragment, MARK_ATTRIBUTES } from './check.mjs'
11import { blockFragment } from './stage.mjs'
12import { poisonDoc } from './poison.mjs'
13
14function deck () {
15 const doc = new Y.Doc({ guid: 'check-test' })
16 doc.clientID = 1
17 applyOps(doc, [
18 { op: 'add_slide', id: 'sld', layout: 'flow', template: 'title-body' },
19 { op: 'set_notes', id: 'sld', markdown: 'Say **hello**' },
20 { op: 'add_block', slide: 'sld', id: 'blk', type: 'text', markdown: '**b** *i* `c` ==h== [l](https://x)' },
21 { op: 'add_block', slide: 'sld', id: 'tbl', type: 'table', props: { rows: 1, cols: 2 } }
22 ])
23 return doc
24}
25
26/** The one XmlText inside the block's first paragraph. */
27function firstText (fragment) {
28 return fragment.get(0).get(0)
29}
30
31test('what the ops write passes, and so does an empty document', () => {
32 assert.equal(checkDoc(deck()), null)
33 assert.equal(checkDoc(new Y.Doc({ guid: 'empty' })), null)
34})
35
36test('the shapes y-prosemirror and older documents write pass', () => {
37 const doc = deck()
38 const t = firstText(blockFragment(doc, 'sld', 'blk'))
39 t.applyDelta([{ insert: 'x', attributes: { b: {}, href: 'https://old' } }])
40 assert.equal(checkDoc(doc), null)
41})
42
43test('an unknown formatting attribute is reported with the core\'s message', () => {
44 const doc = deck()
45 const t = firstText(blockFragment(doc, 'sld', 'blk'))
46 t.applyDelta([{ insert: 'lost', attributes: { unknown_mark: true } }])
47 // The projection still reads the words: that is exactly why the gate
48 // cannot be the projection.
49 assert.equal(view(doc).slides[0].blocks[0].text[0][0].t, 'lost')
50 assert.equal(checkDoc(doc),
51 'slide "sld" block "blk" text: paragraph 0 carries the formatting attribute "unknown_mark", which is not one of b, i, c, h, href')
52})
53
54test('an element inside a paragraph, a non-paragraph child and bare text are reported', () => {
55 const doc = deck()
56 const slide = doc.getArray('slides').get(0)
57 slide.get('notes').get(0).insert(1, [new Y.XmlElement('strong')])
58 assert.equal(checkDoc(doc), 'slide "sld" notes: paragraph 0 holds a <strong> element, and a paragraph holds only text')
59
60 const doc2 = deck()
61 const tbl = doc2.getArray('slides').get(0).get('blocks').get(1)
62 tbl.get('cells').get(1).insert(0, [new Y.XmlElement('heading')])
63 assert.equal(checkDoc(doc2), 'slide "sld" block "tbl" cell 1: child 0 is a <heading> element, and only <paragraph> is allowed')
64
65 const doc3 = deck()
66 doc3.getArray('slides').get(0).get('notes').insert(1, [new Y.XmlText('loose')])
67 assert.equal(checkDoc(doc3), 'slide "sld" notes: child 1 is text outside a paragraph, and only <paragraph> is allowed')
68
69 const frag = new Y.Doc({ guid: 'f' }).getXmlFragment('t')
70 frag.insert(0, [new Y.XmlFragment()])
71 assert.equal(checkFragment(frag, 'x'), 'x: child 0 is a nested fragment, and only <paragraph> is allowed')
72})
73
74test('the unknownmark poison shape is what the check refuses', () => {
75 const doc = poisonDoc('unknownmark')
76 assert.match(checkDoc(doc), /^slide "poisonmrksld" block "poisonmrkblk" text: paragraph 0 carries the formatting attribute "unknown_mark"/)
77 assert.deepEqual(view(doc).slides[0].blocks[0].text, [[{ t: 'keep these words' }]])
78})
79
80test('y-prosemirror 1.3.7 deletes the words under an unknown mark; sanitising first keeps them', () => {
81 // Codex 3 reproduced: the library's own conversion, no DOM needed.
82 const lost = deck()
83 const lostFrag = blockFragment(lost, 'sld', 'blk')
84 firstText(lostFrag).applyDelta([{ insert: 'keep ' }, { insert: 'these', attributes: { unknown_mark: true } }])
85 assert.equal(readText(lostFrag)[0].map(r => r.t).join(''), 'keep theseb i c h l')
86 const node = yXmlFragmentToProseMirrorRootNode(lostFrag, schema)
87 assert.equal(node.textContent, '', 'the library emptied the paragraph')
88 assert.deepEqual(readText(lostFrag), [[]], 'and deleted the XmlText from the document')
89
90 // The same document, sanitised the way bindView does before binding.
91 const kept = deck()
92 const keptFrag = blockFragment(kept, 'sld', 'blk')
93 firstText(keptFrag).applyDelta([{ insert: 'keep ' }, { insert: 'these', attributes: { unknown_mark: true, i: true } }])
94 const origins = []
95 kept.on('afterTransaction', tr => origins.push(tr.origin))
96 assert.equal(sanitiseFragment(keptFrag), 1)
97 assert.deepEqual(origins, ['sanitise'], 'one transaction, under an origin the undo manager does not track')
98 assert.equal(checkDoc(kept), null)
99 const bound = yXmlFragmentToProseMirrorRootNode(keptFrag, schema)
100 assert.equal(bound.textContent, 'keep theseb i c h l', 'every word survives the bind')
101 // The known mark on the same run is untouched; only the unknown one went.
102 assert.deepEqual(readText(keptFrag)[0].slice(0, 2), [{ t: 'keep ' }, { t: 'these', i: true }])
103 // Nothing to strip is not a write.
104 assert.equal(sanitiseFragment(keptFrag), 0)
105 assert.deepEqual(origins, ['sanitise'])
106})
107
108test('the vocabulary is the five marks and nothing else', () => {
109 assert.deepEqual(MARK_ATTRIBUTES, ['b', 'i', 'c', 'h', 'href'])
110})
111