oficina / docs Public

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

Plain git — no account needed to clone.

Download

Download this file

1// fixtures/fonts.test.mjs — the font vocabulary, and the one thing in
2// it that can be wrong without anything breaking.
3//
4// Runs under Node's own test runner, no framework and no DOM: fonts.mjs
5// is four functions over plain strings, and the toolbar's own reading of
6// a selection reduces to fontForMarks once the active marks are known.
7//
8// The expectations below are LITERAL — 'sans', 'font_serif' — and not
9// derived from DEFAULT_FONT or FONT_MARKS. That is the whole point.
10// Asserting fontForMarks([]) === DEFAULT_FONT would pass for any value
11// of DEFAULT_FONT, including one that disagrees with the stylesheet, and
12// this repo has found seven tests that could not fail against the thing
13// they guarded. So the default is written out here, and moving it is a
14// two-file edit on purpose. internal/docs's
15// TestTheToolbarDefaultFontIsTheDefaultFace is the other half: it holds
16// the literal in fonts.mjs to internal/fonts' own Default, which is what
17// the PDF exporter and the stylesheet answer to.
18// Importing fonts.mjs is also what makes Node run its load-time guard
19// (fontsAreCovered), for the reason align.test.mjs gives about
20// toolbar.mjs's: a guard that runs only when a browser mounts the
21// editor is dead code in the gate. That guard is why there is no test
22// below for "the default is one of the faces" or "the default is listed
23// first" — it throws on import, so a file that gets this far has
24// already satisfied both, and a test restating them could never fail.
25import { test } from 'node:test'
26import assert from 'node:assert/strict'
27import { FONTS, FONT_MARKS, fontMark, fontForMarks } from './fonts.mjs'
28
29test('the faces are serif, sans and mono', () => {
30 assert.deepEqual([...FONTS].sort(), ['mono', 'sans', 'serif'])
31 assert.deepEqual([...FONT_MARKS].sort(), ['font_mono', 'font_sans', 'font_serif'])
32})
33
34// The default, written out. If this line and views/show.html's
35// [docs-text] rule disagree, the font control names a face the text is
36// not in and nothing else goes wrong.
37test('an unmarked run is sans', () => {
38 assert.equal(fontForMarks([]), 'sans')
39})
40
41// The default is spelled as the absence of a mark, so choosing it is a
42// clear rather than a set. A document only carries a font mark where
43// somebody asked for a face other than the one they had.
44test('choosing the default clears the mark rather than setting one', () => {
45 assert.equal(fontMark('sans'), null)
46})
47
48test('choosing a non-default face sets that face mark', () => {
49 assert.equal(fontMark('serif'), 'font_serif')
50 assert.equal(fontMark('mono'), 'font_mono')
51})
52
53// undefined, not null: null means "clear the group", which is a real
54// edit. A stale page sending an option this build has dropped must
55// change nothing, not silently reset a person's font to the default.
56test('a face this build does not know is not an instruction to clear', () => {
57 assert.equal(fontMark('comic'), undefined)
58 assert.equal(fontMark(''), undefined)
59 assert.equal(fontMark('constructor'), undefined)
60})
61
62// Every mark reads back as the face that set it, the default's mark
63// included. font_sans is not hypothetical: it is what everyone who chose
64// sans before the move has in their documents, and the control must go
65// on saying "Sans serif" over those runs rather than treating the mark
66// as noise now that it names the default.
67test('every font mark reads back as its own face', () => {
68 assert.equal(fontForMarks(['font_serif']), 'serif')
69 assert.equal(fontForMarks(['font_sans']), 'sans')
70 assert.equal(fontForMarks(['font_mono']), 'mono')
71})
72
73// A mark from some other group must not be read as a font.
74test('a non-font mark leaves the face at the default', () => {
75 assert.equal(fontForMarks(['strong', 'size_lg']), 'sans')
76})
77
78// The schema's `excludes` makes this unreachable through the editor and
79// POST /d/{sqid}/updates makes it reachable anyway. So the answer is not
80// free: the screen and the PDF both already answer it, and they answer
81// it the same way.
82//
83// core/src/fragment.rs opens its spans in MARKS order and closes them
84// reversed, so a run marked mono and sans is <span serif-ward>…<span
85// mono>text</span></span> and the INNERMOST family wins the cascade:
86// mono. internal/export/pdf.go's styleFor tests font_mono first for the
87// same result. A toolbar that said "sans" over text drawn in mono would
88// be the exact mislabel this module exists to prevent, so the literal
89// below is 'mono' and not merely stable.
90test('a run carrying two font marks reads as the face that is drawn', () => {
91 const both = ['font_mono', 'font_sans']
92 assert.equal(fontForMarks(both), fontForMarks([...both].reverse()))
93 assert.equal(fontForMarks(both), 'mono')
94})
95