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(); }); });