Files
lms-fe/scripts/test-font-fallback.cjs
chyoo 8b501266ff feat: improve agent responses and workspace UI
Add live agent progress, elapsed timing, verified code navigation and rich Markdown rendering. Preserve NanumSquare styling with missing glyph fallback, fix platform availability filtering and unify workspace scrollbar and visualization controls.
2026-10-19 17:06:18 +09:00

62 lines
2.9 KiB
JavaScript

const { test } = require('node:test')
const assert = require('node:assert/strict')
const fs = require('node:fs')
const path = require('node:path')
const crypto = require('node:crypto')
const postcss = require('postcss')
const root = path.resolve(__dirname, '..')
const source = fs.readFileSync(path.join(root, 'src/renderer/src/styles/nanumSquare.css'), 'utf8')
const css = postcss.parse(source)
const faces = []
css.walkAtRules('font-face', rule => {
faces.push(Object.fromEntries(rule.nodes.filter(node => node.type === 'decl').map(node => [node.prop, node.value])))
})
function codepoints(range) {
const result = new Set()
let previous = -1
for (const entry of range.split(',')) {
const match = entry.trim().match(/^U\+([0-9A-F]+)(?:-([0-9A-F]+))?$/)
assert.ok(match, `Invalid unicode-range: ${entry}`)
const start = parseInt(match[1], 16)
const end = parseInt(match[2] ?? match[1], 16)
assert.ok(start > previous && end >= start && end <= 0x10ffff)
for (let code = start; code <= end; code++) result.add(code)
previous = end
}
return result
}
test('coverage is tied to the original bundled font bytes for all four weights', () => {
assert.equal(faces.length, 4)
assert.deepEqual(faces.map(face => face['font-weight']), ['300', '400', '700', '800'])
for (const face of faces) {
assert.equal(face['font-family'], "'NanumSquare'")
assert.equal(face['font-display'], 'swap')
const filename = face.src.match(/\/([^/]+\.woff2)'/)[1]
const hash = crypto.createHash('sha256').update(fs.readFileSync(path.join(root, 'src/renderer/public/assets/fonts', filename))).digest('hex')
assert.ok(source.includes(`${filename} sha256: ${hash}`), 'Regenerate coverage if a font changes')
}
})
for (const face of faces) {
test(`weight ${face['font-weight']} preserves visible Hangul and lets blank glyphs fall back`, () => {
const included = codepoints(face['unicode-range'])
assert.equal([...included].filter(code => code >= 0xac00 && code <= 0xd7a3).length, 2479)
for (const char of '가모한글학생SLAM0123 →') assert.ok(included.has(char.codePointAt(0)), `Keep ${char}`)
for (const char of ['몯', '힣', '\u2022']) assert.equal(included.has(char.codePointAt(0)), false, `Let ${char} use fallback`)
for (const code of [0x20, 0x3000, 0x3164]) assert.ok(included.has(code), 'Preserve intentional spacing')
})
}
test('UI keeps its original default font and native fallback without Neo', () => {
const entry = fs.readFileSync(path.join(root, 'src/renderer/src/index.css'), 'utf8')
assert.match(entry, /@import "\.\/styles\/nanumSquare\.css"/)
assert.match(entry, /font-family: 'NanumSquare', Inter, system-ui, sans-serif/)
assert.doesNotMatch(entry + source, /NanumSquareNeo/)
const directFaces = []
postcss.parse(entry).walkAtRules('font-face', rule => directFaces.push(rule))
assert.equal(directFaces.length, 0, 'Unrestricted duplicate faces would suppress fallback')
})