Files

101 lines
5.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Rich-text formatter (public/js/ui/richText.js): official markup parsing, placeholders, bond effects —
// checked against every description in the generated data so no real text breaks the parser.
import { test, describe } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { parseRichText, richTextPlain, rtClassName, formatPlaceholder, fillPlaceholders, formatBondEffect } from '../../public/js/ui/richText.js';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const load = (f) => JSON.parse(readFileSync(path.join(ROOT, 'data', f), 'utf8'));
describe('parseRichText', () => {
test('styled spans, terms, literal trigger labels, newlines', () => {
const segs = parseRichText('所有干员<@ba.vup>防御力</>提升\n<在场<@autochess.dgreen>3</>名>改为<$ba.stun>晕眩</>');
assert.deepEqual(segs, [
{ text: '所有干员', cls: [], term: false },
{ text: '防御力', cls: ['ba.vup'], term: false },
{ text: '提升', cls: [], term: false },
{ br: true },
{ text: '<在场', cls: [], term: false },
{ text: '3', cls: ['autochess.dgreen'], term: false },
{ text: '名>改为', cls: [], term: false },
{ text: '晕眩', cls: ['ba.stun'], term: true },
]);
});
test('nesting, escaped newlines, stray closers, unterminated tags', () => {
const segs = parseRichText('<@ba.vup>a<$ba.cold>b</>c</>d\\ne</>f<@ba.kw>g');
assert.deepEqual(segs.filter((s) => !s.br).map((s) => [s.text, s.cls.join('+'), s.term]), [
['a', 'ba.vup', false], ['b', 'ba.vup+ba.cold', true], ['c', 'ba.vup', false], ['d', '', false], ['e</>f', '', false], ['g', 'ba.kw', false],
]);
assert.equal(segs.filter((s) => s.br).length, 1);
});
test('non-strings and empty', () => {
assert.deepEqual(parseRichText(null), []);
assert.deepEqual(parseRichText(''), []);
assert.deepEqual(parseRichText(42), [{ text: '42', cls: [], term: false }]);
assert.equal(richTextPlain('<获得时>获得<@ba.vup>1</>资金'), '<获得时>获得1资金');
});
test('class names', () => {
assert.equal(rtClassName('ba.vup'), 'rt-vup');
assert.equal(rtClassName('ba.vdown'), 'rt-vdown');
assert.equal(rtClassName('whatever'), 'rt-hl');
assert.equal(rtClassName('ba.stun', true), 'rt-term');
});
test('every official description parses to the same plain text as `desc`', () => {
let checked = 0;
const files = ['bonds.json', 'bands.json', 'items.json', 'garrisons.json', 'effects.json'];
for (const f of files) {
for (const rec of Object.values(load(f))) {
if (typeof rec?.descRaw !== 'string' || typeof rec?.desc !== 'string') continue;
const plain = richTextPlain(rec.descRaw).replace(/\s+/g, '');
assert.equal(plain, rec.desc.replace(/\s+/g, ''), `${f}: ${rec.bondId || rec.bandId || rec.id || rec.garrisonId || rec.effectId}`);
checked++;
}
}
for (const c of Object.values(load('chess.json'))) {
if (!c?.skill?.descRaw) continue;
assert.equal(richTextPlain(c.skill.descRaw).replace(/\s+/g, ''), c.skill.desc.replace(/\s+/g, ''), c.chessId);
checked++;
}
assert.ok(checked > 500, `checked ${checked}`);
});
});
describe('placeholders', () => {
test('formatPlaceholder', () => {
assert.equal(formatPlaceholder(0.15, '0%'), '15%');
assert.equal(formatPlaceholder(0.1234, '0.0%'), '12.3%');
assert.equal(formatPlaceholder(3.6, '0'), '4');
assert.equal(formatPlaceholder(3.64, '0.0'), '3.6');
assert.equal(formatPlaceholder(-0.001, '0'), '0');
assert.equal(formatPlaceholder(NaN, '0'), '?');
assert.equal(formatPlaceholder(2), '2');
});
test('fillPlaceholders keeps unknown indexes', () => {
assert.equal(fillPlaceholders('攻击+{0:0%},持续{1}秒,{2:0}', [0.2, 5]), '攻击+20%,持续5秒,{2:0}');
assert.equal(fillPlaceholders('x{0}', ['文本']), 'x文本');
assert.equal(fillPlaceholders(null, []), '');
});
test('formatBondEffect resolves base + perStack × layers for every bond', () => {
const bonds = load('bonds.json');
const deput = bonds.deputShip;
const txt = formatBondEffect(deput, 10);
const expected = Math.round((deput.bb.base_def + deput.bb.def_per_stack * 10) * 100);
assert.ok(richTextPlain(txt).includes(`+${expected}%`), txt);
for (const b of Object.values(bonds)) {
const out = richTextPlain(formatBondEffect(b, 25));
assert.ok(!/\{\d+(:[^}]*)?\}/.test(out), `${b.bondId} leaves a placeholder: ${out}`);
}
// a chance stops at 100 % (GitHub #108: 不屈 read 124 % at 266 layers; the sim caps it with min(1, …))
for (const id of ['indomShip', 'swiftShip', 'miraShip']) {
const pct = richTextPlain(formatBondEffect(bonds[id], 500)).match(/(\d+)%/);
assert.ok(pct && Number(pct[1]) === 100, `${id} at 500 layers: ${pct && pct[0]}`);
}
assert.equal(formatBondEffect(null, 3), '');
assert.equal(formatBondEffect({ effectDesc: 'x{0:0}', effectDescParams: [{ index: 0, format: '0', base: 'a', perStack: 'b' }], bb: { a: 1, b: 2 } }, 'bad'), 'x1');
});
});