import {
getBitmapFontGlyph,
getBitmapFontKerning,
getBitmapFontMetrics,
getBitmapFontPage,
} from '@flighthq/bitmapfont/contract';
import { createTextureAtlas } from '@flighthq/textureatlas/contract';
import type { ImportDiagnostic } from '@flighthq/types/contract';
import { describe, expect, it } from 'vitest';
import { parseBitmapFontXml } from './bitmapFontXml';
const FNT_XML = [
'',
'',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
' ',
'',
].join('\n');
describe('parseBitmapFontXml', () => {
// A clean parse is two claims: the values are right AND THE PARSER IS NOT COMPLAINING. Every other test
// here checks the first. This checks the second — the one that catches a walk that desynchronised and
// still left the asserted fields looking plausible. Asserted as an EMPTY list rather than a filter over
// truncation-shaped kind names: a pattern built from expected vocabulary silently exempts every kind
// whose name nobody guessed, and this importer has kinds like that.
it('raises no diagnostic at all for a well-formed file', () => {
const diagnostics: ImportDiagnostic[] = [];
parseBitmapFontXml(FNT_XML, { resolvePage: () => createTextureAtlas() }, diagnostics);
const complaints = diagnostics.map((diagnostic) => diagnostic.kind);
expect(complaints, `a good fnt-xml file made the parser complain: ${complaints.join(', ')}`).toEqual([]);
});
it('parses glyphs, kerning, and line metrics with the resolved atlas', () => {
const atlas = createTextureAtlas();
const font = parseBitmapFontXml(FNT_XML, { resolvePage: () => atlas });
expect(font).not.toBeNull();
expect(getBitmapFontPage(font!, 0)).toBe(atlas);
// yoffset=5 with base=26 → bearingY = base - yoffset = 21 (baseline-relative, up-positive).
expect(getBitmapFontGlyph(font!, 65)).toEqual({
advance: 9,
bearingX: 1,
bearingY: 21,
height: 8,
page: 0,
width: 7,
x: 0,
y: 0,
});
expect(getBitmapFontGlyph(font!, 86)?.advance).toBe(8);
expect(getBitmapFontKerning(font!, 65, 86)).toBe(-2);
expect(getBitmapFontMetrics(font!)).toEqual({ ascent: 26, descent: 6, lineGap: 0 });
});
it('passes the parsed page id and file to the resolver', () => {
const seen: Array<[number, string]> = [];
parseBitmapFontXml(FNT_XML, {
resolvePage: (id, file) => {
seen.push([id, file]);
return createTextureAtlas();
},
});
expect(seen).toEqual([[0, 'test_0.png']]);
});
it('returns null for malformed XML without throwing', () => {
expect(parseBitmapFontXml('', { resolvePage: () => createTextureAtlas() })).toBeNull();
expect(parseBitmapFontXml('not xml at all', { resolvePage: () => createTextureAtlas() })).toBeNull();
});
it('returns null when no resolvePage is supplied', () => {
expect(parseBitmapFontXml(FNT_XML)).toBeNull();
});
});