// The op vocabulary over a Y.Doc (ops.mjs), one test per op family, plus // the properties the core guarantees: a refused batch applies nothing, // columns.children is validated, a layout switch round-trips, and the // committed vectors are what generate.mjs would write today. import test from 'node:test' import assert from 'node:assert/strict' import { readFileSync, readdirSync } from 'node:fs' import { join, dirname } from 'node:path' import { fileURLToPath } from 'node:url' import * as Y from 'yjs' import { applyOps, view, OpsError } from './ops.mjs' import { buildVector, sortedJSON } from './generate.mjs' const here = dirname(fileURLToPath(import.meta.url)) function fresh () { const doc = new Y.Doc({ guid: 'test' }) doc.clientID = 1 return doc } function withSlide (layout = 'flow') { const doc = fresh() applyOps(doc, [{ op: 'add_slide', id: 's1', layout, template: 'title-body' }]) return doc } const heading = (id, md, extra = {}) => ({ op: 'add_block', slide: 's1', id, type: 'heading', props: { level: 1 }, region: 'heading', markdown: md, ...extra }) const text = (id, md, extra = {}) => ({ op: 'add_block', slide: 's1', id, type: 'text', props: {}, region: 'body', markdown: md, ...extra }) test('empty_doc_views_as_defaults', () => { assert.deepEqual(view(fresh()), { meta: { title: '', theme: 'paper', accent: null, fonts: null, aspect: '16:9' }, slides: [], assets: {} }) }) test('set_meta', () => { const doc = fresh() applyOps(doc, [{ op: 'set_meta', title: 'T', theme: 'ink', accent: '#f00', fonts: { heading: 'A', body: 'B' } }]) assert.deepEqual(view(doc).meta, { title: 'T', theme: 'ink', accent: '#f00', fonts: { heading: 'A', body: 'B' }, aspect: '16:9' }) applyOps(doc, [{ op: 'set_meta', accent: null, fonts: null }]) assert.deepEqual(view(doc).meta, { title: 'T', theme: 'ink', accent: null, fonts: null, aspect: '16:9' }) assert.throws(() => applyOps(doc, [{ op: 'set_meta', title: 5 }]), OpsError) assert.throws(() => applyOps(doc, [{ op: 'set_meta', fonts: { heading: 'A' } }]), OpsError) }) test('slides: add, move, remove, set_slide, set_notes', () => { const doc = fresh() applyOps(doc, [ { op: 'add_slide', id: 's1', layout: 'flow', template: 'title' }, { op: 'add_slide', id: 's2', layout: 'flow' }, { op: 'add_slide', id: 's0', layout: 'canvas', after: null }, { op: 'add_slide', id: 's1b', layout: 'flow', after: 's1' } ]) let v = view(doc) assert.deepEqual(v.slides.map(s => s.id), ['s0', 's1', 's1b', 's2']) assert.deepEqual(v.slides[1], { id: 's1', layout: 'flow', template: 'title', background: { kind: 'theme', value: '' }, phone: 'inherit', transition: 'none', notes: [], blocks: [] }) assert.equal(v.slides[3].template, 'blank') applyOps(doc, [{ op: 'move_slide', id: 's2', after: null }, { op: 'move_slide', id: 's0', after: 's1b' }]) assert.deepEqual(view(doc).slides.map(s => s.id), ['s2', 's1', 's1b', 's0']) applyOps(doc, [ { op: 'remove_slide', id: 's1b' }, { op: 'set_slide', id: 's1', template: 'quote', background: { kind: 'color', value: '#123' }, phone: 'reflow', transition: 'fade' }, { op: 'set_notes', id: 's1', markdown: 'Say **hello**\n\n- one\n- two' } ]) v = view(doc) assert.deepEqual(v.slides.map(s => s.id), ['s2', 's1', 's0']) const s1 = v.slides[1] assert.equal(s1.template, 'quote') assert.deepEqual(s1.background, { kind: 'color', value: '#123' }) assert.equal(s1.phone, 'reflow') assert.equal(s1.transition, 'fade') assert.deepEqual(s1.notes, [[{ t: 'Say ' }, { t: 'hello', b: true }], [{ t: 'one' }], [{ t: 'two' }]]) assert.throws(() => applyOps(doc, [{ op: 'add_slide', id: 's1', layout: 'flow' }]), /already exists/) assert.throws(() => applyOps(doc, [{ op: 'add_slide', id: 'x', layout: 'grid' }]), /layout "grid" is not one of/) assert.throws(() => applyOps(doc, [{ op: 'move_slide', id: 's1', after: 's1' }]), /after is the slide itself/) assert.throws(() => applyOps(doc, [{ op: 'set_slide', id: 'nope' }]), /no slide "nope"/) }) test('duplicate_slide', () => { const doc = withSlide() applyOps(doc, [ heading('h1', 'Hi'), text('t1', 'a'), text('t2', 'b'), { op: 'add_block', slide: 's1', id: 'c1', type: 'columns', props: { count: 2, children: ['t1', 't2'] } }, { op: 'duplicate_slide', id: 's1', new_id: 's2', new_block_ids: ['h2', 't3', 't4', 'c2'] } ]) const v = view(doc) assert.deepEqual(v.slides.map(s => s.id), ['s1', 's2']) const copy = v.slides[1] assert.deepEqual(copy.blocks.map(b => b.id), ['h2', 't3', 't4', 'c2']) assert.deepEqual(copy.blocks[3].props, { count: 2, children: ['t3', 't4'] }) assert.deepEqual(copy.blocks[0].text, v.slides[0].blocks[0].text) assert.throws(() => applyOps(doc, [{ op: 'duplicate_slide', id: 's1', new_id: 's3', new_block_ids: ['a'] }]), /4 blocks but 1 new_block_ids/) assert.throws(() => applyOps(doc, [{ op: 'duplicate_slide', id: 's1', new_id: 's3', new_block_ids: ['a', 'a', 'b', 'c'] }]), /distinct and non-empty/) }) test('blocks: add, move, remove, set_block', () => { const doc = withSlide() applyOps(doc, [ heading('h1', 'Hello **world**'), text('t1', 'one'), text('t0', 'zero', { after: null }), text('t1b', 'x', { after: 't1' }), { op: 'add_block', slide: 's1', id: 'img', type: 'image', props: { asset: 'a1', alt: '' } }, { op: 'add_block', slide: 's1', id: 'div', type: 'divider' } ]) let v = view(doc) assert.deepEqual(v.slides[0].blocks.map(b => b.id), ['t0', 'h1', 't1', 't1b', 'img', 'div']) assert.deepEqual(v.slides[0].blocks[1], { id: 'h1', type: 'heading', props: { level: 1 }, region: 'heading', box: null, z: 0, phone: {}, text: [[{ t: 'Hello ' }, { t: 'world', b: true }]] }) assert.equal(v.slides[0].blocks[4].region, 'media') assert.equal('text' in v.slides[0].blocks[4], false) assert.equal(v.slides[0].blocks[5].region, 'body') applyOps(doc, [ { op: 'move_block', slide: 's1', id: 'div', after: null }, { op: 'move_block', slide: 's1', id: 't0', after: 'img' }, { op: 'remove_block', slide: 's1', id: 't1b' }, { op: 'set_block', slide: 's1', id: 'h1', props: { level: 2, extra: 'y' }, region: 'body' }, { op: 'set_block', slide: 's1', id: 'h1', props: { extra: null } }, { op: 'set_text', slide: 's1', id: 'h1', markdown: '*new*' } ]) v = view(doc) assert.deepEqual(v.slides[0].blocks.map(b => b.id), ['div', 'h1', 't1', 'img', 't0']) assert.deepEqual(v.slides[0].blocks[1].props, { level: 2 }) assert.equal(v.slides[0].blocks[1].region, 'body') assert.deepEqual(v.slides[0].blocks[1].text, [[{ t: 'new', i: true }]]) assert.throws(() => applyOps(doc, [{ op: 'add_block', slide: 's1', id: 'h1', type: 'text' }]), /already exists/) assert.throws(() => applyOps(doc, [{ op: 'add_block', slide: 's1', id: 'n', type: 'video' }]), /type "video" is not one of/) assert.throws(() => applyOps(doc, [{ op: 'set_text', slide: 's1', id: 'img', markdown: 'x' }]), /is a image and carries no text/) assert.throws(() => applyOps(doc, [{ op: 'move_block', slide: 's1', id: 'h1', after: 'h1' }]), /after is the block itself/) assert.throws(() => applyOps(doc, [{ op: 'remove_block', slide: 's1', id: 'zz' }]), /no block "zz" in slide "s1"/) }) test('set_phone', () => { const doc = withSlide() applyOps(doc, [text('t1', 'x'), { op: 'set_phone', slide: 's1', id: 't1', phone: { hidden: true, order: 2, size: 's', box: { x: 0, y: 0.5, w: 1, h: 0.25 } } }]) assert.deepEqual(view(doc).slides[0].blocks[0].phone, { hidden: true, order: 2, size: 's', box: { x: 0, y: 0.5, w: 1, h: 0.25 } }) applyOps(doc, [{ op: 'set_phone', slide: 's1', id: 't1', phone: { size: 'l' } }]) assert.deepEqual(view(doc).slides[0].blocks[0].phone, { size: 'l' }) assert.throws(() => applyOps(doc, [{ op: 'set_phone', slide: 's1', id: 't1', phone: { size: 'xl' } }]), /phone.size "xl" is not one of/) assert.throws(() => applyOps(doc, [{ op: 'set_phone', slide: 's1', id: 't1', phone: { colour: 'red' } }]), OpsError) assert.throws(() => applyOps(doc, [{ op: 'set_phone', slide: 's1', id: 't1', phone: { order: 1.5 } }]), OpsError) }) test('table cells follow rows and cols', () => { const doc = withSlide() applyOps(doc, [{ op: 'add_block', slide: 's1', id: 'tb', type: 'table', props: { header: true } }]) let b = view(doc).slides[0].blocks[0] assert.deepEqual(b.props, { header: true, rows: 2, cols: 2, cells: [[], [], [], []] }) assert.equal('text' in b, false) applyOps(doc, [{ op: 'set_block', slide: 's1', id: 'tb', props: { rows: 3 } }]) b = view(doc).slides[0].blocks[0] assert.equal(b.props.cells.length, 6) applyOps(doc, [{ op: 'set_block', slide: 's1', id: 'tb', props: { rows: 1, cols: 1 } }]) assert.equal(view(doc).slides[0].blocks[0].props.cells.length, 1) assert.throws(() => applyOps(doc, [{ op: 'set_block', slide: 's1', id: 'tb', props: { rows: 0 } }]), /table rows must be a whole number from 1 to 100/) assert.throws(() => applyOps(doc, [{ op: 'add_block', slide: 's1', id: 't2', type: 'table', props: { cols: 101 } }]), /table cols/) }) test('canvas blocks: box defaults from the region, z from the index', () => { const doc = withSlide('canvas') applyOps(doc, [ heading('h1', 'x'), { op: 'add_block', slide: 's1', id: 't1', type: 'text', box: { x: 0.1, y: 0.2, w: 0.3, h: 0.44444 }, z: 7 }, { op: 'set_block', slide: 's1', id: 'h1', box: { x: 0.5, y: 0.5, w: 0.1, h: 0.1 }, z: 3 } ]) const v = view(doc) assert.deepEqual(v.slides[0].blocks[0], { id: 'h1', type: 'heading', props: { level: 1 }, region: null, box: { x: 0.5, y: 0.5, w: 0.1, h: 0.1 }, z: 3, phone: {}, text: [[{ t: 'x' }]] }) assert.deepEqual(v.slides[0].blocks[1].box, { x: 0.1, y: 0.2, w: 0.3, h: 0.4444 }) assert.equal(v.slides[0].blocks[1].z, 7) applyOps(doc, [{ op: 'set_block', slide: 's1', id: 'h1', box: null }]) assert.equal(view(doc).slides[0].blocks[0].box, null) }) test('assets: register and remove', () => { const doc = fresh() applyOps(doc, [{ op: 'register_asset', id: 'a1', ref: 'blob/1', name: 'pic.png', width: 10, height: 20, alt: 'A' }, { op: 'register_asset', id: 'a2', ref: 'blob/2' }]) assert.deepEqual(view(doc).assets, { a1: { ref: 'blob/1', name: 'pic.png', width: 10, height: 20, alt: 'A' }, a2: { ref: 'blob/2', name: '', width: 0, height: 0, alt: '' } }) applyOps(doc, [{ op: 'remove_asset', id: 'a1' }]) assert.deepEqual(Object.keys(view(doc).assets), ['a2']) assert.throws(() => applyOps(doc, [{ op: 'register_asset', id: 'a2', ref: 'x' }]), /already registered/) assert.throws(() => applyOps(doc, [{ op: 'remove_asset', id: 'a1' }]), /no asset "a1"/) assert.throws(() => applyOps(doc, [{ op: 'register_asset', id: 'a3', ref: '' }]), /ref is empty/) }) test('markdown: every mark, code blocks are plain lines', () => { const doc = withSlide() applyOps(doc, [ text('t1', '**b** *i* `c *x*` ==h== [t](u) \\* lone* **open'), text('t2', '**a *b* c** [**x**](y)'), { op: 'add_block', slide: 's1', id: 'c1', type: 'code', props: { language: 'go' }, markdown: 'x\n\n**y**' } ]) const [t1, t2, c1] = view(doc).slides[0].blocks assert.deepEqual(t1.text, [[ { t: 'b', b: true }, { t: ' ' }, { t: 'i', i: true }, { t: ' ' }, { t: 'c *x*', c: true }, { t: ' ' }, { t: 'h', h: true }, { t: ' ' }, { t: 't', href: 'u' }, { t: ' * lone* **open' } ]]) assert.deepEqual(t2.text, [[{ t: 'a ', b: true }, { t: 'b', b: true, i: true }, { t: ' c', b: true }, { t: ' ' }, { t: 'x', b: true, href: 'y' }]]) assert.deepEqual(c1.text, [[{ t: 'x' }], [], [{ t: '**y**' }]]) }) test('batch_is_atomic', () => { const doc = withSlide() const before = Y.encodeStateAsUpdate(doc) let origins = 0 doc.on('afterTransaction', () => { origins++ }) let err = null try { applyOps(doc, [ heading('h1', 'x'), { op: 'add_slide', id: 's2', layout: 'flow' }, { op: 'remove_block', slide: 's1', id: 'missing' } ]) } catch (e) { err = e } assert.ok(err instanceof OpsError) assert.equal(err.index, 2) assert.match(err.message, /^op 2: no block "missing" in slide "s1"/) assert.deepEqual(Y.encodeStateAsUpdate(doc), before) assert.equal(origins, 0) assert.throws(() => applyOps(doc, [{ op: 'sing' }]), /op 0:/) assert.throws(() => applyOps(doc, 'nope'), /ops must be a JSON array/) }) test('a batch applies in one transaction with the given origin', () => { const doc = withSlide() const seen = [] doc.on('afterTransaction', tr => { seen.push(tr.origin) }) applyOps(doc, [heading('h1', 'x'), text('t1', 'y')], 'inspector') assert.deepEqual(seen, ['inspector']) }) test('columns_children_validated', () => { const doc = withSlide() applyOps(doc, [text('t1', 'a'), text('t2', 'b')]) assert.throws(() => applyOps(doc, [{ op: 'add_block', slide: 's1', id: 'c1', type: 'columns', props: { children: ['t1', 'zz'] } }]), /names "zz", not a block on slide "s1"/) assert.throws(() => applyOps(doc, [{ op: 'add_block', slide: 's1', id: 'c1', type: 'columns', props: { children: ['c1'] } }]), /cannot name itself/) assert.throws(() => applyOps(doc, [{ op: 'add_block', slide: 's1', id: 'c1', type: 'columns', props: { children: 't1' } }]), /must be an array of block ids/) applyOps(doc, [{ op: 'add_block', slide: 's1', id: 'c1', type: 'columns', props: { count: 2, children: ['t1', 't2'] } }]) assert.throws(() => applyOps(doc, [{ op: 'set_block', slide: 's1', id: 'c1', props: { children: ['t1', 't9'] } }]), /names "t9"/) // Removing a child prunes it from the columns block. applyOps(doc, [{ op: 'remove_block', slide: 's1', id: 't1' }]) assert.deepEqual(view(doc).slides[0].blocks.find(b => b.id === 'c1').props.children, ['t2']) // The model follows the batch: a block removed earlier in the same batch is gone. assert.throws(() => applyOps(doc, [ { op: 'remove_block', slide: 's1', id: 't2' }, { op: 'set_block', slide: 's1', id: 'c1', props: { children: ['t2'] } } ]), /op 1: columns children names "t2"/) applyOps(doc, [{ op: 'set_block', slide: 's1', id: 'c1', props: { children: null } }]) assert.deepEqual(view(doc).slides[0].blocks.find(b => b.id === 'c1').props, { count: 2 }) }) test('layout_switch_round_trip', () => { const doc = withSlide() applyOps(doc, [heading('h1', 'x'), text('t1', 'a'), text('t2', 'b'), { op: 'add_block', slide: 's1', id: 'as', type: 'text', region: 'aside' }]) applyOps(doc, [{ op: 'set_slide', id: 's1', layout: 'canvas' }]) let blocks = view(doc).slides[0].blocks assert.deepEqual(blocks.map(b => [b.id, b.region, b.box, b.z]), [ ['h1', null, { x: 0.06, y: 0.08, w: 0.88, h: 0.18 }, 0], ['t1', null, { x: 0.06, y: 0.3, w: 0.44, h: 0.31 }, 1], ['t2', null, { x: 0.06, y: 0.61, w: 0.44, h: 0.31 }, 2], ['as', null, { x: 0.54, y: 0.3, w: 0.4, h: 0.62 }, 3] ]) // Back to flow: regions from position, order top to bottom then left to right. applyOps(doc, [{ op: 'set_block', slide: 's1', id: 't2', box: { x: 0.5, y: 0.05, w: 0.2, h: 0.2 } }]) applyOps(doc, [{ op: 'set_slide', id: 's1', layout: 'flow' }]) blocks = view(doc).slides[0].blocks assert.deepEqual(blocks.map(b => [b.id, b.region, b.box, b.z]), [ ['t2', 'body', null, 0], ['h1', 'heading', null, 0], ['t1', 'body', null, 0], ['as', 'body', null, 0] ]) // Setting the same layout again is a no-op. const before = view(doc) applyOps(doc, [{ op: 'set_slide', id: 's1', layout: 'flow' }]) assert.deepEqual(view(doc), before) }) test('view_matches_snapshot_fixtures', () => { const dataDir = join(here, 'data') const names = readdirSync(dataDir).filter(f => f.endsWith('.json')).map(f => f.slice(0, -5)) assert.ok(names.length >= 5, 'five vectors at least') const ops = new Set() for (const name of names) { const data = JSON.parse(readFileSync(join(dataDir, `${name}.json`), 'utf8')) data.ops.forEach(o => ops.add(o.op)) const { update, view: v } = buildVector(name, data) const wantView = readFileSync(join(here, 'vectors', `${name}.view.json`), 'utf8') assert.equal(sortedJSON(v), wantView, `${name}.view.json is stale: run node generate.mjs`) const wantUpdate = readFileSync(join(here, 'vectors', `${name}.yjs`)) assert.ok(Buffer.from(update).equals(wantUpdate), `${name}.yjs is stale: run node generate.mjs`) } const every = ['set_meta', 'add_slide', 'remove_slide', 'move_slide', 'duplicate_slide', 'set_slide', 'set_notes', 'add_block', 'remove_block', 'move_block', 'set_block', 'set_text', 'set_phone', 'register_asset', 'remove_asset'] assert.deepEqual(every.filter(o => !ops.has(o)), [], 'every op appears in some vector') }) test('columns children have one parent and no cycles (codex 7)', () => { const doc = withSlide() applyOps(doc, [ text('t1', 'a'), text('t2', 'b'), { op: 'add_block', slide: 's1', id: 'a', type: 'columns', props: { children: ['t1'] } }, { op: 'add_block', slide: 's1', id: 'b', type: 'columns', props: { children: ['a'] } }, { op: 'add_block', slide: 's1', id: 'c', type: 'columns', props: { children: [] } } ]) const before = view(doc) // A → B, B → A: the cycle codex reproduced, with the core's message. assert.throws(() => applyOps(doc, [{ op: 'set_block', slide: 's1', id: 'a', props: { children: ['t1', 'b'] } }]), /op 0: columns children names "b", which would put columns "a" inside itself$/) // A shared child. assert.throws(() => applyOps(doc, [{ op: 'set_block', slide: 's1', id: 'c', props: { children: ['t1'] } }]), /op 0: columns children names "t1", already a child of columns "a"$/) // The same child twice. assert.throws(() => applyOps(doc, [{ op: 'set_block', slide: 's1', id: 'c', props: { children: ['t2', 't2'] } }]), /op 0: columns children names "t2" twice$/) assert.deepEqual(view(doc), before, 'nothing applied') // A longer cycle: c → b → a → c. applyOps(doc, [{ op: 'set_block', slide: 's1', id: 'c', props: { children: ['b'] } }]) assert.throws(() => applyOps(doc, [{ op: 'set_block', slide: 's1', id: 'a', props: { children: ['t1', 'c'] } }]), /inside itself/) // Reparenting inside one batch is fine: the model follows the batch. applyOps(doc, [ { op: 'set_block', slide: 's1', id: 'a', props: { children: [] } }, { op: 'set_block', slide: 's1', id: 'c', props: { children: ['b', 't1'] } } ]) assert.deepEqual(view(doc).slides[0].blocks.find(b => b.id === 'c').props.children, ['b', 't1']) }) /** Two replicas of one document, and the exchange of what each did since. */ function replicas () { const a = fresh() const b = new Y.Doc({ guid: 'test' }) b.clientID = 2 const sync = () => { const fromA = Y.encodeStateAsUpdate(a, Y.encodeStateVector(b)) const fromB = Y.encodeStateAsUpdate(b, Y.encodeStateVector(a)) Y.applyUpdate(a, fromB) Y.applyUpdate(b, fromA) } return { a, b, sync } } test('canvas-to-flow keeps a concurrent text edit (codex 4)', () => { const { a, b, sync } = replicas() applyOps(a, [ { op: 'add_slide', id: 's1', layout: 'canvas' }, { op: 'add_block', slide: 's1', id: 'low', type: 'text', markdown: 'below', box: { x: 0.1, y: 0.6, w: 0.3, h: 0.2 } }, { op: 'add_block', slide: 's1', id: 'high', type: 'text', markdown: 'above', box: { x: 0.1, y: 0.1, w: 0.3, h: 0.2 } } ]) sync() // A converts, which reorders: "high" reads first now. applyOps(a, [{ op: 'set_slide', id: 's1', layout: 'flow' }]) // B, concurrently, types into "low" the way a keystroke through // y-prosemirror does: straight into the shared XmlText. const low = b.getArray('slides').get(0).get('blocks').toArray().find(m => m.get('id') === 'low') const t = low.get('text').get(0).get(0) t.insert(t.length, ' and more') sync() for (const doc of [a, b]) { const blocks = view(doc).slides[0].blocks assert.deepEqual(blocks.map(x => x.id), ['high', 'low']) assert.deepEqual(blocks[1].text, [[{ t: 'below and more' }]]) assert.equal(blocks[1].region, 'body') } assert.deepEqual(view(a), view(b)) // The array was never rebuilt: the maps are the ones the ops made. assert.equal(a.getArray('slides').get(0).get('blocks').toArray()[0].get('id'), 'low') }) test('move_block keeps a concurrent text edit, and later inserts land where after says (codex 4)', () => { const { a, b, sync } = replicas() applyOps(a, [ { op: 'add_slide', id: 's1', layout: 'flow' }, text('one', 'one'), text('two', 'two'), text('three', 'three') ]) sync() applyOps(a, [{ op: 'move_block', slide: 's1', id: 'three' }]) applyOps(b, [{ op: 'set_text', slide: 's1', id: 'three', markdown: 'three, edited' }]) sync() for (const doc of [a, b]) { const blocks = view(doc).slides[0].blocks assert.deepEqual(blocks.map(x => x.id), ['three', 'one', 'two']) assert.deepEqual(blocks[0].text, [[{ t: 'three, edited' }]]) } applyOps(a, [ text('four', 'four', { after: 'three' }), text('five', 'five'), text('zero', 'zero', { after: null }) ]) assert.deepEqual(view(a).slides[0].blocks.map(x => x.id), ['zero', 'three', 'four', 'one', 'two', 'five']) // A duplicate pairs new ids with blocks in reading order. applyOps(a, [{ op: 'duplicate_slide', id: 's1', new_id: 's2', new_block_ids: ['z', 't', 'f', 'o', 'w', 'v'] }]) assert.deepEqual(view(a).slides[1].blocks.map(x => [x.id, x.text[0][0].t]), [['z', 'zero'], ['t', 'three, edited'], ['f', 'four'], ['o', 'one'], ['w', 'two'], ['v', 'five']]) }) test('move_slide keeps a concurrent text edit, and later inserts land where after says (codex 4 at slide level)', () => { const { a, b, sync } = replicas() applyOps(a, [ { op: 'add_slide', id: 's1', layout: 'flow' }, { op: 'add_slide', id: 's2', layout: 'flow' }, { op: 'add_slide', id: 's3', layout: 'flow' }, { op: 'add_block', slide: 's3', id: 't3', type: 'text', region: 'body', markdown: 'three' } ]) sync() // A reorders the deck; B, who has not seen that, types into the // slide being moved and into its notes. applyOps(a, [{ op: 'move_slide', id: 's3' }]) applyOps(b, [ { op: 'set_text', slide: 's3', id: 't3', markdown: 'three, edited' }, { op: 'set_notes', id: 's3', markdown: 'say this' } ]) sync() for (const doc of [a, b]) { const slides = view(doc).slides assert.deepEqual(slides.map(s => s.id), ['s3', 's1', 's2']) assert.deepEqual(slides[0].blocks[0].text, [[{ t: 'three, edited' }]]) assert.deepEqual(slides[0].notes, [[{ t: 'say this' }]]) // The order key is the document's, not the view's. assert.equal(Object.hasOwn(slides[0], 'order'), false) } applyOps(a, [ { op: 'add_slide', id: 's4', layout: 'flow', after: 's3' }, { op: 'add_slide', id: 's5', layout: 'flow' }, { op: 'add_slide', id: 's0', layout: 'flow', after: null }, { op: 'duplicate_slide', id: 's3', new_id: 's3b', new_block_ids: ['t3b'] } ]) assert.deepEqual(view(a).slides.map(s => s.id), ['s0', 's3', 's3b', 's4', 's1', 's2', 's5']) applyOps(a, [{ op: 'remove_slide', id: 's3' }]) assert.deepEqual(view(a).slides.map(s => s.id), ['s0', 's3b', 's4', 's1', 's2', 's5']) })