// fixtures/fonts.test.mjs — the font vocabulary, and the one thing in // it that can be wrong without anything breaking. // // Runs under Node's own test runner, no framework and no DOM: fonts.mjs // is four functions over plain strings, and the toolbar's own reading of // a selection reduces to fontForMarks once the active marks are known. // // The expectations below are LITERAL — 'sans', 'font_serif' — and not // derived from DEFAULT_FONT or FONT_MARKS. That is the whole point. // Asserting fontForMarks([]) === DEFAULT_FONT would pass for any value // of DEFAULT_FONT, including one that disagrees with the stylesheet, and // this repo has found seven tests that could not fail against the thing // they guarded. So the default is written out here, and moving it is a // two-file edit on purpose. internal/docs's // TestTheToolbarDefaultFontIsTheDefaultFace is the other half: it holds // the literal in fonts.mjs to internal/fonts' own Default, which is what // the PDF exporter and the stylesheet answer to. // Importing fonts.mjs is also what makes Node run its load-time guard // (fontsAreCovered), for the reason align.test.mjs gives about // toolbar.mjs's: a guard that runs only when a browser mounts the // editor is dead code in the gate. That guard is why there is no test // below for "the default is one of the faces" or "the default is listed // first" — it throws on import, so a file that gets this far has // already satisfied both, and a test restating them could never fail. import { test } from 'node:test' import assert from 'node:assert/strict' import { FONTS, FONT_MARKS, fontMark, fontForMarks } from './fonts.mjs' test('the faces are serif, sans and mono', () => { assert.deepEqual([...FONTS].sort(), ['mono', 'sans', 'serif']) assert.deepEqual([...FONT_MARKS].sort(), ['font_mono', 'font_sans', 'font_serif']) }) // The default, written out. If this line and views/show.html's // [docs-text] rule disagree, the font control names a face the text is // not in and nothing else goes wrong. test('an unmarked run is sans', () => { assert.equal(fontForMarks([]), 'sans') }) // The default is spelled as the absence of a mark, so choosing it is a // clear rather than a set. A document only carries a font mark where // somebody asked for a face other than the one they had. test('choosing the default clears the mark rather than setting one', () => { assert.equal(fontMark('sans'), null) }) test('choosing a non-default face sets that face mark', () => { assert.equal(fontMark('serif'), 'font_serif') assert.equal(fontMark('mono'), 'font_mono') }) // undefined, not null: null means "clear the group", which is a real // edit. A stale page sending an option this build has dropped must // change nothing, not silently reset a person's font to the default. test('a face this build does not know is not an instruction to clear', () => { assert.equal(fontMark('comic'), undefined) assert.equal(fontMark(''), undefined) assert.equal(fontMark('constructor'), undefined) }) // Every mark reads back as the face that set it, the default's mark // included. font_sans is not hypothetical: it is what everyone who chose // sans before the move has in their documents, and the control must go // on saying "Sans serif" over those runs rather than treating the mark // as noise now that it names the default. test('every font mark reads back as its own face', () => { assert.equal(fontForMarks(['font_serif']), 'serif') assert.equal(fontForMarks(['font_sans']), 'sans') assert.equal(fontForMarks(['font_mono']), 'mono') }) // A mark from some other group must not be read as a font. test('a non-font mark leaves the face at the default', () => { assert.equal(fontForMarks(['strong', 'size_lg']), 'sans') }) // The schema's `excludes` makes this unreachable through the editor and // POST /d/{sqid}/updates makes it reachable anyway. So the answer is not // free: the screen and the PDF both already answer it, and they answer // it the same way. // // core/src/fragment.rs opens its spans in MARKS order and closes them // reversed, so a run marked mono and sans is …text and the INNERMOST family wins the cascade: // mono. internal/export/pdf.go's styleFor tests font_mono first for the // same result. A toolbar that said "sans" over text drawn in mono would // be the exact mislabel this module exists to prevent, so the literal // below is 'mono' and not merely stable. test('a run carrying two font marks reads as the face that is drawn', () => { const both = ['font_mono', 'font_sans'] assert.equal(fontForMarks(both), fontForMarks([...both].reverse())) assert.equal(fontForMarks(both), 'mono') })