oficina / slides Public

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

Plain git — no account needed to clone.

Download

Download this file

1// switcher.mjs's rule: in the phone view an edit to a block's box,
2// size, order or hidden becomes set_phone over the whole override map;
3// on the desktop, box and size are the block's own. Plus the stored
4// view, read defensively.
5import test from 'node:test'
6import assert from 'node:assert/strict'
7import * as Y from 'yjs'
8import { applyOps, view } from './ops.mjs'
9import { routeEdit, phoneMap, resetPhone, hasOverride, readView, writeView, STORAGE_KEY } from './switcher.mjs'
10
11const block = { id: 'blockaaaaaaa', type: 'text', props: { size: 'm' }, box: { x: 0.1, y: 0.1, w: 0.5, h: 0.3 }, z: 0, phone: {} }
12const withPhone = { ...block, phone: { hidden: true, order: 2, size: 's', box: { x: 0, y: 0, w: 1, h: 0.5 } } }
13
14test('desktop view: box and size are the block; hidden and order are always the phone map', () => {
15 assert.deepEqual(routeEdit({ view: 'desktop', slide: 's', block, field: 'box', value: { x: 0.2, y: 0.2, w: 0.5, h: 0.3 } }),
16 { op: 'set_block', slide: 's', id: block.id, box: { x: 0.2, y: 0.2, w: 0.5, h: 0.3 } })
17 assert.deepEqual(routeEdit({ view: 'desktop', slide: 's', block, field: 'size', value: 'l' }),
18 { op: 'set_block', slide: 's', id: block.id, props: { size: 'l' } })
19 assert.deepEqual(routeEdit({ view: 'desktop', slide: 's', block, field: 'size', value: '' }),
20 { op: 'set_block', slide: 's', id: block.id, props: { size: null } })
21 assert.deepEqual(routeEdit({ view: 'desktop', slide: 's', block, field: 'hidden', value: true }),
22 { op: 'set_phone', slide: 's', id: block.id, phone: { hidden: true } })
23 assert.deepEqual(routeEdit({ view: 'both', slide: 's', block, field: 'order', value: 3 }),
24 { op: 'set_phone', slide: 's', id: block.id, phone: { order: 3 } })
25})
26
27test('phone view: everything goes to set_phone, carrying the rest of the map', () => {
28 assert.deepEqual(routeEdit({ view: 'phone', slide: 's', block: withPhone, field: 'box', value: { x: 0.12345, y: 0, w: 0.5, h: 0.5 } }),
29 { op: 'set_phone', slide: 's', id: block.id, phone: { hidden: true, order: 2, size: 's', box: { x: 0.1235, y: 0, w: 0.5, h: 0.5 } } })
30 assert.deepEqual(routeEdit({ view: 'phone', slide: 's', block: withPhone, field: 'size', value: 'l' }).phone.size, 'l')
31 assert.deepEqual(routeEdit({ view: 'phone', slide: 's', block: withPhone, field: 'hidden', value: false }).phone,
32 { order: 2, size: 's', box: { x: 0, y: 0, w: 1, h: 0.5 } })
33 assert.deepEqual(routeEdit({ view: 'phone', slide: 's', block: withPhone, field: 'order', value: null }).phone,
34 { hidden: true, size: 's', box: { x: 0, y: 0, w: 1, h: 0.5 } })
35 assert.deepEqual(routeEdit({ view: 'phone', slide: 's', block, field: 'size', value: 'l' }),
36 { op: 'set_phone', slide: 's', id: block.id, phone: { size: 'l' } })
37 assert.throws(() => routeEdit({ view: 'phone', slide: 's', block, field: 'level', value: 2 }))
38})
39
40test('the ops it makes are ones the document takes', () => {
41 const doc = new Y.Doc()
42 applyOps(doc, [
43 { op: 'add_slide', id: 'slideaaaaaaa', layout: 'canvas' },
44 { op: 'add_block', slide: 'slideaaaaaaa', id: block.id, type: 'text', box: block.box, z: 0, markdown: 'x' }
45 ], 'test')
46 const b = view(doc).slides[0].blocks[0]
47 applyOps(doc, [routeEdit({ view: 'phone', slide: 'slideaaaaaaa', block: b, field: 'box', value: { x: 0, y: 0, w: 1, h: 0.4 } })], 'test')
48 applyOps(doc, [routeEdit({ view: 'phone', slide: 'slideaaaaaaa', block: view(doc).slides[0].blocks[0], field: 'hidden', value: true })], 'test')
49 const after = view(doc).slides[0].blocks[0]
50 assert.deepEqual(after.phone, { hidden: true, box: { x: 0, y: 0, w: 1, h: 0.4 } })
51 assert.deepEqual(after.box, block.box)
52 assert.equal(hasOverride(after), true)
53 applyOps(doc, [resetPhone('slideaaaaaaa', after)], 'test')
54 assert.deepEqual(view(doc).slides[0].blocks[0].phone, {})
55 assert.equal(hasOverride(view(doc).slides[0].blocks[0]), false)
56})
57
58test('phoneMap reads only the four keys, and only well-typed', () => {
59 assert.deepEqual(phoneMap({ phone: { hidden: 'yes', order: 1.5, size: '', box: null, other: 1 } }), {})
60 assert.deepEqual(phoneMap({ phone: { hidden: false, order: -1, size: 'l' } }), { hidden: false, order: -1, size: 'l' })
61 assert.deepEqual(phoneMap({}), {})
62})
63
64test('the stored view is read defensively', () => {
65 const mem = new Map()
66 const storage = { getItem: k => mem.get(k) ?? null, setItem: (k, v) => mem.set(k, v) }
67 assert.equal(readView(storage), 'desktop')
68 writeView(storage, 'phone')
69 assert.equal(mem.get(STORAGE_KEY), 'phone')
70 assert.equal(readView(storage), 'phone')
71 mem.set(STORAGE_KEY, 'tablet')
72 assert.equal(readView(storage), 'desktop')
73 const broken = { getItem () { throw new Error('private') }, setItem () { throw new Error('quota') } }
74 assert.equal(readView(broken), 'desktop')
75 assert.doesNotThrow(() => writeView(broken, 'both'))
76})
77