| 1 | // changes.mjs sorts what a transaction touched into what the editor |
| 2 | // has to do about it: text on a slide flows through y-prosemirror, |
| 3 | // structure on a slide means the stage re-fetches it, and a change to |
| 4 | // the slide list means the strip re-syncs. Driven with a real Y.Doc |
| 5 | // and real observeDeep events, because the walk from an event's target |
| 6 | // up to its slide is the whole of the module. |
| 7 | import test from 'node:test' |
| 8 | import assert from 'node:assert/strict' |
| 9 | import * as Y from 'yjs' |
| 10 | import { applyOps } from './ops.mjs' |
| 11 | import { classify } from './changes.mjs' |
| 12 | import { blockFragment } from './stage.mjs' |
| 13 | |
| 14 | function deck () { |
| 15 | const doc = new Y.Doc({ guid: 'changes-test' }) |
| 16 | doc.clientID = 3 |
| 17 | applyOps(doc, [ |
| 18 | { op: 'add_slide', id: 's1', layout: 'flow' }, |
| 19 | { op: 'add_block', slide: 's1', id: 'b1', type: 'text', region: 'body', markdown: 'one' }, |
| 20 | { op: 'add_slide', id: 's2', layout: 'flow' }, |
| 21 | { op: 'add_block', slide: 's2', id: 'b2', type: 'heading', region: 'heading', markdown: 'two' } |
| 22 | ], 'setup') |
| 23 | const slides = doc.getArray('slides') |
| 24 | let last = null |
| 25 | slides.observeDeep(events => { last = classify(events, slides) }) |
| 26 | return { doc, take: () => last } |
| 27 | } |
| 28 | |
| 29 | test('typing into a block is a text change on its slide only', () => { |
| 30 | const { doc, take } = deck() |
| 31 | const f = blockFragment(doc, 's1', 'b1') |
| 32 | doc.transact(() => { f.get(0).get(0).insert(0, 'x') }, 'typing') |
| 33 | assert.deepEqual(take(), { text: ['s1'], structure: [], order: false }) |
| 34 | }) |
| 35 | |
| 36 | test('a block\'s props changing is structure on its slide', () => { |
| 37 | const { doc, take } = deck() |
| 38 | applyOps(doc, [{ op: 'set_block', slide: 's2', id: 'b2', props: { level: 2 } }], 'ops') |
| 39 | assert.deepEqual(take(), { text: [], structure: ['s2'], order: false }) |
| 40 | }) |
| 41 | |
| 42 | test('adding, moving or removing a block is structure on its slide', () => { |
| 43 | const { doc, take } = deck() |
| 44 | applyOps(doc, [{ op: 'add_block', slide: 's1', id: 'b3', type: 'text', region: 'body', markdown: 'three' }], 'ops') |
| 45 | assert.deepEqual(take(), { text: [], structure: ['s1'], order: false }) |
| 46 | applyOps(doc, [{ op: 'remove_block', slide: 's1', id: 'b3' }], 'ops') |
| 47 | assert.deepEqual(take(), { text: [], structure: ['s1'], order: false }) |
| 48 | }) |
| 49 | |
| 50 | test('the notes are text on their slide', () => { |
| 51 | const { doc, take } = deck() |
| 52 | applyOps(doc, [{ op: 'set_notes', id: 's2', markdown: 'note' }], 'ops') |
| 53 | assert.deepEqual(take(), { text: ['s2'], structure: [], order: false }) |
| 54 | }) |
| 55 | |
| 56 | test('a slide added, removed or moved is an order change', () => { |
| 57 | const { doc, take } = deck() |
| 58 | applyOps(doc, [{ op: 'add_slide', id: 's3', layout: 'flow', after: 's1' }], 'ops') |
| 59 | assert.deepEqual(take(), { text: [], structure: [], order: true }) |
| 60 | applyOps(doc, [{ op: 'move_slide', id: 's3', after: 's2' }], 'ops') |
| 61 | assert.deepEqual(take(), { text: [], structure: [], order: true }) |
| 62 | applyOps(doc, [{ op: 'remove_slide', id: 's3' }], 'ops') |
| 63 | assert.deepEqual(take(), { text: [], structure: [], order: true }) |
| 64 | }) |
| 65 | |
| 66 | test('one transaction touching several things reports each once', () => { |
| 67 | const { doc, take } = deck() |
| 68 | applyOps(doc, [ |
| 69 | { op: 'set_slide', id: 's1', template: 'section' }, |
| 70 | { op: 'set_block', slide: 's1', id: 'b1', props: { size: 'l' } }, |
| 71 | { op: 'set_text', slide: 's2', id: 'b2', markdown: 'changed' } |
| 72 | ], 'ops') |
| 73 | assert.deepEqual(take(), { text: ['s2'], structure: ['s1'], order: false }) |
| 74 | }) |
| 75 | |