oficina / docs Public

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

Plain git — no account needed to clone.

Download

Download this file

1// fixtures/indent.test.mjs — the indent vocabulary, under plain Node.
2//
3// align.test.mjs's counterpart, and the difference between them is the
4// point. Alignment's value is a string from a closed set, so its tests
5// are about a table being consulted and anything outside it dropped.
6// Indent's value is a number, so these are about a CLAMP — a different
7// mechanism, with its own ways to be wrong.
8//
9// The cases worth having are the ones no editor can produce.
10// POST /d/{sqid}/updates accepts raw Yjs bytes that never met the
11// schema, and core/src/fragment.rs writes its output into the page
12// unescaped, so "99", "0" and "3; drop" are all reachable and all have
13// to render as something safe. core/tests/fragment.rs holds the same
14// three in Rust against the same fixture; both sides check
15// independently because a document must not indent one distance on
16// screen and another on paper.
17
18import { test } from 'node:test'
19import assert from 'node:assert/strict'
20
21import { MAX_INDENT, INDENT_STEP_IN, indentClass, normaliseIndent, indentFromClasses } from './indent.mjs'
22
23test('a real level becomes its class', () => {
24 assert.equal(indentClass(1), 'docs-indent-1')
25 assert.equal(indentClass(MAX_INDENT), 'docs-indent-' + MAX_INDENT)
26})
27
28test('zero, null and absent are all the same answer: no indent', () => {
29 assert.equal(indentClass(0), null)
30 assert.equal(indentClass(null), null)
31 assert.equal(indentClass(undefined), null)
32 assert.equal(indentClass(''), null)
33})
34
35test('a run of digits too large for any integer type still clamps', () => {
36 // The last corner where this and core/src/fragment.rs disagreed.
37 // Rust parses as u32, which a twenty-digit run overflows; it now
38 // treats an overflow as "too large" and clamps, matching what
39 // Number() does here. Before that, the same document indented at 8 in
40 // the editor and rendered flush left for a reader with no
41 // JavaScript.
42 assert.equal(normaliseIndent('99999999999999999999'), MAX_INDENT)
43 assert.equal(indentClass('99999999999999999999'), 'docs-indent-' + MAX_INDENT)
44})
45
46test('a level past the maximum clamps rather than being emitted', () => {
47 assert.equal(normaliseIndent(99), MAX_INDENT)
48 assert.equal(indentClass(99), 'docs-indent-' + MAX_INDENT)
49 // The failure this guards against is docs-indent-99 reaching a class
50 // attribute — the value would have come from the document.
51 assert.equal(indentClass(99).includes('99'), false)
52})
53
54test('anything that is not a whole number renders flush left', () => {
55 for (const bad of ['3; drop', 'x', '1.5', -2, NaN, {}, [], '<script>']) {
56 assert.equal(indentClass(bad), null, `indentClass(${JSON.stringify(bad)}) should be null`)
57 }
58})
59
60test('a numeric string is a number, because that is what the wire carries', () => {
61 // y-prosemirror stores node attributes as Yjs XML attributes, which
62 // are strings — so the renderer and this module both meet "3" rather
63 // than 3, and treating only the number type as valid would drop every
64 // indent in every real document.
65 assert.equal(normaliseIndent('3'), 3)
66 assert.equal(indentClass('3'), 'docs-indent-3')
67})
68
69test('the class round-trips back to its level for a paste', () => {
70 assert.equal(indentFromClasses(['docs-indent-4']), 4)
71 assert.equal(indentFromClasses(['docs-align-center', 'docs-indent-2']), 2)
72 assert.equal(indentFromClasses(['docs-align-center']), null)
73 assert.equal(indentFromClasses([]), null)
74 // A class naming a level out of range is not a level this build
75 // wrote, and must not become one on the way in either.
76 assert.equal(indentFromClasses(['docs-indent-99']), MAX_INDENT)
77 assert.equal(indentFromClasses(['docs-indent-0']), null)
78})
79
80test('the step is the half inch every word processor uses', () => {
81 // Stated here as a literal rather than derived from the constant, so
82 // that changing the constant fails this test and makes someone look
83 // at views/show.html's .docs-indent-N rules and
84 // internal/export/pdf.go's IndentStepPt, which restate the same
85 // number and would otherwise silently disagree.
86 assert.equal(INDENT_STEP_IN, 0.5)
87 assert.equal(MAX_INDENT, 8)
88})
89