/** * scripts/verify-tooltip-empirical.ts * * Standalone Empirical Challenger Verification & Stress Test Suite * for Milestone M3 (Diablo II v1.13c Item Tooltips & Parity). * * Validates: * 1. Tooltip rendering across all 10 starter equipped items: * Shako, Mara's, Oculus, Tal Armor, Spirit, Magefist, Arachnid, SOJ, BK, WT. * 2. Tooltip rendering across starter bag items: * CTA, Torch, Anni, Gheed's Fortune. * 3. Deep canonical assertions on Harlequin Crest (Shako) and Call to Arms (CTA). * 4. Dynamic vertical flip threshold (hover.y = 200, 279, 280, 500) and viewport clamping. * 5. formatItemTooltip runeword, unique, set, and socketable formatting parity. * 6. Adversarial edge cases: extreme coordinates, long text lines, zero/missing fields. * 7. Rapid iteration stress harness (2,000 hover frames). * 8. Zero runtime MPQ/DLL network/file dependency guard. */ import { WorldPanelsHud } from '../src/ui/world-panels.ts' import { STARTER_EQUIPPED_GEAR, STARTER_BAG_ITEMS, InventoryPanel, EQUIP_SLOTS_LAYOUT, INV_GRID_ORIGIN, type UiInventoryItem, type EquipSlotId, } from '../src/ui/inventory.ts' import { formatItemTooltip, type FormattedItemTooltip } from '../src/game/item-tooltip.ts' import type { Item, ItemBase } from '../src/game/items.ts' import type { D2ColorCode, D2FontName, D2FontRenderer } from '../src/ui/font.ts' interface DrawCall { text: string x: number y: number options?: { font?: D2FontName color?: D2ColorCode align?: 'left' | 'center' | 'right' shadow?: boolean } } interface MockFontRenderer extends D2FontRenderer { drawCalls: DrawCall[] } function createMockFont(): MockFontRenderer { const drawCalls: DrawCall[] = [] return { metas: {} as any, images: new Map(), tintedCache: new Map(), getTintedAtlas: () => null as any, measureText: (text: string, _font: D2FontName = 'font8') => { return text.length * 7 }, drawText: (_ctx: any, text: string, x: number, y: number, options?: any) => { drawCalls.push({ text, x, y, options }) }, drawCalls, } as unknown as MockFontRenderer } interface MockCanvasContext { fillStyle: string strokeStyle: string fillRectCalls: [number, number, number, number][] strokeRectCalls: [number, number, number, number][] fillRect: (x: number, y: number, w: number, h: number) => void strokeRect: (x: number, y: number, w: number, h: number) => void } function createMockCanvas(): MockCanvasContext { const fillRectCalls: [number, number, number, number][] = [] const strokeRectCalls: [number, number, number, number][] = [] return { fillStyle: '', strokeStyle: '', fillRectCalls, strokeRectCalls, fillRect: (x: number, y: number, w: number, h: number) => { fillRectCalls.push([x, y, w, h]) }, strokeRect: (x: number, y: number, w: number, h: number) => { strokeRectCalls.push([x, y, w, h]) }, } } let totalAssertions = 0 let failedAssertions = 0 function assert(condition: boolean, message: string) { totalAssertions++ if (!condition) { failedAssertions++ console.error(` [FAIL] ${message}`) } else { console.log(` [PASS] ${message}`) } } function section(title: string) { console.log(`\n======================================================`) console.log(`>>> ${title}`) console.log(`======================================================`) } // --------------------------------------------------------------------------- // Section 1: All 10 Starter Equipped Items Traversal // --------------------------------------------------------------------------- section('1. Verification of 10 Starter Equipped Items in drawItemTooltip') const worldPanels = new WorldPanelsHud() const equippedSlots: EquipSlotId[] = [ 'helm', 'amulet', 'weapon1', 'armor', 'weapon2', 'gloves', 'ring1', 'belt', 'ring2', 'boots', ] for (const slot of equippedSlots) { const item = STARTER_EQUIPPED_GEAR[slot] assert(item !== undefined, `Slot '${slot}' must have defined starter item`) if (!item) continue const font = createMockFont() const canvas = createMockCanvas() worldPanels.drawItemTooltip(canvas as any, { item, x: 400, y: 300 }, font) assert(canvas.fillRectCalls.length === 1, `${slot} (${item.name}): exactly 1 tooltip background rect drawn`) assert(canvas.strokeRectCalls.length === 1, `${slot} (${item.name}): exactly 1 tooltip border rect drawn`) assert(font.drawCalls.length >= 3, `${slot} (${item.name}): has at least title, base name, and stats`) const titleCall = font.drawCalls[0] assert(titleCall?.text === item.nameZh, `${slot}: Title is '${item.nameZh}'`) assert(titleCall?.options?.font === 'fontexocet10', `${slot}: Title font is 'fontexocet10'`) const expectedTitleColor = item.quality === 'unique' || item.quality === 'rune' ? 'gold' : item.quality === 'set' ? 'green' : 'white' assert(titleCall?.options?.color === expectedTitleColor, `${slot}: Title color is '${expectedTitleColor}'`) } // --------------------------------------------------------------------------- // Section 2: Deep Canonical Assertions on Harlequin Crest (Shako) // --------------------------------------------------------------------------- section('2. Detailed Parity on Harlequin Crest (Shako)') const shako = STARTER_EQUIPPED_GEAR.helm! const shakoFont = createMockFont() const shakoCanvas = createMockCanvas() worldPanels.drawItemTooltip(shakoCanvas as any, { item: shako, x: 400, y: 320 }, shakoFont) // Title const shakoTitle = shakoFont.drawCalls.find(d => d.text.includes('谐角之冠')) assert(shakoTitle !== undefined, 'Harlequin Crest title line exists') assert(shakoTitle?.options?.color === 'gold', 'Harlequin Crest title line is in gold') assert(shakoTitle?.options?.font === 'fontexocet10', 'Harlequin Crest title line uses fontexocet10') // Base Name const shakoBase = shakoFont.drawCalls.find(d => d.text.includes('军帽')) assert(shakoBase !== undefined, 'Base name "军帽" line exists') assert(shakoBase?.options?.color === 'white', 'Base name "军帽" is in white') assert(shakoBase?.options?.font === 'font8', 'Base name "军帽" uses font8') // Defense const shakoDef = shakoFont.drawCalls.find(d => d.text === '防御: 141') assert(shakoDef !== undefined, 'Defense line "防御: 141" exists') assert(shakoDef?.options?.color === 'white', 'Defense line is in white') // Durability const shakoDur = shakoFont.drawCalls.find(d => d.text === '耐久度: 12 之 12') assert(shakoDur !== undefined, 'Durability line "耐久度: 12 之 12" exists') assert(shakoDur?.options?.color === 'white', 'Durability line is in white') // Required Strength const shakoStr = shakoFont.drawCalls.find(d => d.text === '需要力量: 50') assert(shakoStr !== undefined, 'Req strength line "需要力量: 50" exists') assert(shakoStr?.options?.color === 'white', 'Req strength line is in white') // Required Level const shakoLvl = shakoFont.drawCalls.find(d => d.text === '需要等级: 62') assert(shakoLvl !== undefined, 'Req level line "需要等级: 62" exists') assert(shakoLvl?.options?.color === 'white', 'Req level line is in white') // 6 Canonical Stats const expectedShakoStats = [ '+2 所有技能 (+2 To All Skills)', '+1.5 生命 (依角色等级而定)', '+1.5 法力 (依角色等级而定)', '物理伤害减少 10%', '50% 更佳机会取得魔法装备 (MF)', '+2 所有属性', ] for (const statText of expectedShakoStats) { const found = shakoFont.drawCalls.find(d => d.text === statText) assert(found !== undefined, `Shako stat "${statText}" is drawn`) assert(found?.options?.color === 'blue', `Shako stat "${statText}" is drawn in blue`) } // --------------------------------------------------------------------------- // Section 3: Deep Canonical Assertions on Call to Arms (CTA) // --------------------------------------------------------------------------- section('3. Detailed Parity on Call to Arms (CTA)') const ctaPlacement = STARTER_BAG_ITEMS.find(p => p.item.id === 'bag-cta') assert(ctaPlacement !== undefined, 'CTA placement exists in STARTER_BAG_ITEMS') const cta = ctaPlacement!.item const ctaFont = createMockFont() const ctaCanvas = createMockCanvas() worldPanels.drawItemTooltip(ctaCanvas as any, { item: cta, x: 400, y: 350 }, ctaFont) // Title const ctaTitle = ctaFont.drawCalls[0] assert(ctaTitle?.text === '战争召唤 (Call to Arms)', 'CTA title is "战争召唤 (Call to Arms)"') assert(ctaTitle?.options?.color === 'gold', 'CTA title is in gold') assert(ctaTitle?.options?.font === 'fontexocet10', 'CTA title uses fontexocet10') // Runes line const ctaRunes = ctaFont.drawCalls[1] assert(ctaRunes?.text === "'AmnRalMalIstOhm'", "CTA runes line is ''AmnRalMalIstOhm''") assert(ctaRunes?.options?.color === 'gold', 'CTA runes line is in gold') assert(ctaRunes?.options?.font === 'font8', 'CTA runes line uses font8') // Base name const ctaBase = ctaFont.drawCalls[2] assert(ctaBase?.text === '水晶剑 (Crystal Sword)', 'CTA base name is "水晶剑 (Crystal Sword)"') assert(ctaBase?.options?.color === 'white', 'CTA base name is in white') assert(ctaBase?.options?.font === 'font8', 'CTA base name uses font8') // Damage const ctaDmg = ctaFont.drawCalls.find(d => d.text.includes('17 - 52')) assert(ctaDmg !== undefined, 'CTA damage line "单手伤害: 17 - 52" exists') assert(ctaDmg?.text === '单手伤害: 17 - 52', 'CTA damage line is exact "单手伤害: 17 - 52"') assert(ctaDmg?.options?.color === 'white', 'CTA damage line is in white') // Durability const ctaDur = ctaFont.drawCalls.find(d => d.text === '耐久度: 20 之 20') assert(ctaDur !== undefined, 'CTA durability line "耐久度: 20 之 20" exists') assert(ctaDur?.options?.color === 'white', 'CTA durability line is in white') // Speed Text const ctaSpeed = ctaFont.drawCalls.find(d => d.text === '剑类 - 极快的攻击速度') assert(ctaSpeed !== undefined, 'CTA speed class line "剑类 - 极快的攻击速度" exists') assert(ctaSpeed?.options?.color === 'white', 'CTA speed class line is in white') // Req Strength const ctaStr = ctaFont.drawCalls.find(d => d.text === '需要力量: 43') assert(ctaStr !== undefined, 'CTA req strength line "需要力量: 43" exists') assert(ctaStr?.options?.color === 'white', 'CTA req strength line is in white') // Req Level const ctaLvl = ctaFont.drawCalls.find(d => d.text === '需要等级: 57') assert(ctaLvl !== undefined, 'CTA req level line "需要等级: 57" exists') assert(ctaLvl?.options?.color === 'white', 'CTA req level line is in white') // All 11 Canonical CTA Stats const expectedCtaStats = [ '+1 所有技能 (+1 To All Skills)', '+40% 增加攻击速度 (+40% Increased Attack Speed)', '+255% 增强伤害 (+255% Enhanced Damage)', '增加 5-30 火焰伤害 (Adds 5-30 Fire Damage)', '7% 生命偷取 / 每次命中偷取生命 (7% Life Stolen)', '防止怪物自愈 / 防止怪物自疗 (Prevent Monster Heal)', '+6 战斗体制 (Battle Orders)', '+6 战斗指挥 (Battle Command)', '+4 战斗狂嗥 / 战嚎 (Battle Cry)', '生命回复 +12 / 生命补满 +12 (Replenish Life +12)', '30% 更佳机会取得魔法装备 (30% Better Chance of Getting Magic Items)', ] for (const statText of expectedCtaStats) { const found = ctaFont.drawCalls.find(d => d.text === statText) assert(found !== undefined, `CTA stat "${statText}" is drawn`) assert(found?.options?.color === 'blue', `CTA stat "${statText}" is in blue`) } // Sockets line const ctaSockets = ctaFont.drawCalls.find(d => d.text === '凹槽 (5)') assert(ctaSockets !== undefined, 'CTA sockets line "凹槽 (5)" exists') assert(ctaSockets?.options?.color === 'blue', 'CTA sockets line is in blue') // --------------------------------------------------------------------------- // Section 4: Starter Bag Charms (Torch, Anni, Gheed's) // --------------------------------------------------------------------------- section('4. Verification of Starter Bag Charms (Torch, Anni, Gheed\'s)') const charms = ['bag-torch', 'bag-anni', 'bag-gheeds'] for (const charmId of charms) { const pl = STARTER_BAG_ITEMS.find(p => p.item.id === charmId) assert(pl !== undefined, `Charm '${charmId}' exists in STARTER_BAG_ITEMS`) if (!pl) continue const font = createMockFont() const canvas = createMockCanvas() worldPanels.drawItemTooltip(canvas as any, { item: pl.item, x: 400, y: 300 }, font) const title = font.drawCalls[0] assert(title?.text === pl.item.nameZh, `${charmId}: Title matches '${pl.item.nameZh}'`) assert(title?.options?.color === 'gold', `${charmId}: Title is gold`) assert(font.drawCalls.some(d => d.text === pl.item.baseNameZh && d.options?.color === 'white'), `${charmId}: Base name in white`) assert(font.drawCalls.some(d => d.text === `需要等级: ${pl.item.reqLevel}` && d.options?.color === 'white'), `${charmId}: Req level in white`) for (const st of pl.item.stats) { assert(font.drawCalls.some(d => d.text === st.text && d.options?.color === 'blue'), `${charmId}: Stat "${st.text}" in blue`) } } // --------------------------------------------------------------------------- // Section 5: Dynamic Vertical Flip Threshold & Screen Clamping // --------------------------------------------------------------------------- section('5. Dynamic Vertical Flip Threshold & Boundary Clamping') const testItem: UiInventoryItem = { id: 'test-flip-item', code: 'cap', invFile: 'invcap', name: 'Cap', nameZh: '便帽', baseNameZh: '便帽', quality: 'normal', invWidth: 2, invHeight: 2, allowedSlots: ['helm'], stats: [], } // Case 1: hover.y = 200 (< 280) -> by = 224 (hover.y + 24) { const font = createMockFont() const canvas = createMockCanvas() worldPanels.drawItemTooltip(canvas as any, { item: testItem, x: 400, y: 200 }, font) const [, by, , boxH] = canvas.fillRectCalls[0]! assert(by === 224, `hover.y = 200 -> by = ${by} (expected 224)`) assert(by + boxH <= 592, `hover.y = 200 -> bottom edge within 592`) } // Case 2: hover.y = 279 (< 280) -> by = 303 (hover.y + 24) { const font = createMockFont() const canvas = createMockCanvas() worldPanels.drawItemTooltip(canvas as any, { item: testItem, x: 400, y: 279 }, font) const [, by] = canvas.fillRectCalls[0]! assert(by === 303, `hover.y = 279 -> by = ${by} (expected 303)`) } // Case 3: hover.y = 280 (>= 280) -> by < 280 (above cursor: 280 - boxH - 8) { const font = createMockFont() const canvas = createMockCanvas() worldPanels.drawItemTooltip(canvas as any, { item: testItem, x: 400, y: 280 }, font) const [, by, , boxH] = canvas.fillRectCalls[0]! assert(by < 280, `hover.y = 280 -> by = ${by} (expected < 280)`) assert(by === 280 - boxH - 8, `hover.y = 280 -> by = 280 - boxH - 8 (${280 - boxH - 8})`) } // Case 4: hover.y = 500 (>= 280) -> by < 500 (above cursor: 500 - boxH - 8) { const font = createMockFont() const canvas = createMockCanvas() worldPanels.drawItemTooltip(canvas as any, { item: testItem, x: 400, y: 500 }, font) const [, by, , boxH] = canvas.fillRectCalls[0]! assert(by < 500, `hover.y = 500 -> by = ${by} (expected < 500)`) assert(by === 500 - boxH - 8, `hover.y = 500 -> by = 500 - boxH - 8 (${500 - boxH - 8})`) } // Case 5: Viewport horizontal and vertical boundaries clamping { // Far left (hover.x = 0) const fontL = createMockFont() const canvasL = createMockCanvas() worldPanels.drawItemTooltip(canvasL as any, { item: testItem, x: 0, y: 300 }, fontL) const [bxL] = canvasL.fillRectCalls[0]! assert(bxL === 8, `hover.x = 0 clamps bx to 8 (got ${bxL})`) // Far right (hover.x = 800) const fontR = createMockFont() const canvasR = createMockCanvas() worldPanels.drawItemTooltip(canvasR as any, { item: testItem, x: 800, y: 300 }, fontR) const [bxR, , boxWR] = canvasR.fillRectCalls[0]! assert(bxR + boxWR === 792, `hover.x = 800 clamps bx + boxW to 792 (bx=${bxR}, boxW=${boxWR})`) // Far bottom (hover.y = 600) const fontB = createMockFont() const canvasB = createMockCanvas() worldPanels.drawItemTooltip(canvasB as any, { item: testItem, x: 400, y: 600 }, fontB) const [, byB, , boxHB] = canvasB.fillRectCalls[0]! assert(byB + boxHB === 592, `hover.y = 600 clamps by + boxH to 592 (by=${byB}, boxH=${boxHB})`) } // --------------------------------------------------------------------------- // Section 6: formatItemTooltip Unit Verification // --------------------------------------------------------------------------- section('6. formatItemTooltip Runeword & Quality Verification') // Test 6.1: Runeword item formatting const rawRunewordItem: Item = { id: 101, name: 'Call to Arms', base: { id: 'crs', name: 'Crystal Sword', kind: 'weapon', tags: ['sword', 'weap'], damage: 15, defense: 0, value: 5000, invWidth: 2, invHeight: 3, maxStack: 1, level: 11, mindam: 5, maxdam: 15, reqstr: 43, levelreq: 57, durability: 20, gemsockets: 5, } as any, rarity: 'runeword', level: 85, flags: {} as any, location: {} as any, stats: {}, sockets: [], nameZh: '战争召唤 (Call to Arms)', baseNameZh: '水晶剑 (Crystal Sword)', runewordRunes: "'AmnRalMalIstOhm'", speedText: '剑类 - 极快的攻击速度', rolledProps: [ { code: 'allskills', value: 1, min: 1, max: 1 }, { code: 'ias', value: 40, min: 40, max: 40 }, { code: 'dmg%', value: 255, min: 255, max: 255 }, { code: 'dmg-fire', value: 5, min: 5, max: 30 }, { code: 'lifesteal', value: 7, min: 7, max: 7 }, ], } as any const formattedRw = formatItemTooltip(rawRunewordItem, 'zh') assert(formattedRw.qualityColor === '#c8a15a', 'Runeword qualityColor is #c8a15a (gold)') assert(formattedRw.quality === '符文之语', 'Runeword quality is "符文之语"') assert(formattedRw.title === '战争召唤 (Call to Arms)', 'Runeword title is "战争召唤 (Call to Arms)"') assert(formattedRw.subTitle === '水晶剑 (Crystal Sword)', 'Runeword subTitle is "水晶剑 (Crystal Sword)"') assert(formattedRw.runewordRunes === "'AmnRalMalIstOhm'", 'Runeword runes preserved in formatItemTooltip') assert(formattedRw.speedText === '剑类 - 极快的攻击速度', 'Runeword speedText preserved in formatItemTooltip') assert(formattedRw.reqStr === 43, 'Runeword reqStr is 43') assert(formattedRw.reqLevel === 57, 'Runeword reqLevel is 57') assert(formattedRw.sockets === 5, 'Runeword sockets is 5') // Test 6.2: Socketable Rune formatting const rawRuneItem: Item = { id: 102, name: 'Ohm Rune', code: 'r27', base: { id: 'r27', name: 'Ohm Rune', kind: 'other', tags: ['rune'], damage: 0, defense: 0, value: 1000, invWidth: 1, invHeight: 1, maxStack: 1, level: 57, } as any, rarity: 'normal', level: 57, flags: {} as any, location: {} as any, stats: {}, sockets: [], } as any const formattedRune = formatItemTooltip(rawRuneItem, 'zh') assert(formattedRune.isSocketable === true, 'Rune is marked isSocketable') assert(formattedRune.lines.some(l => l.text.includes('可镶嵌进有凹槽的装备')), 'Rune has socket instruction line') assert(formattedRune.lines.some(l => l.text.includes('武器:')), 'Rune has weapons line') // --------------------------------------------------------------------------- // Section 7: Adversarial Stress Tests & Edge Cases // --------------------------------------------------------------------------- section('7. Adversarial Stress Testing & Fuzzing') // 7.1 Wide and tall item within viewport limits const wideTallItem: UiInventoryItem = { id: 'wide-tall-item', code: 'cap', invFile: 'invcap', name: 'Wide and Tall Crafted Cap of the Mammoth', nameZh: '超大巨兽之精工合成便帽 (Crafted Cap of Mammoth)', baseNameZh: '便帽 (Cap)', quality: 'craft', invWidth: 2, invHeight: 2, allowedSlots: ['helm'], stats: Array.from({ length: 15 }, (_, i) => ({ text: `+${(i + 1) * 5}% 增强防御与全抗性描述 (${i + 1})`, color: 'blue' as D2ColorCode, })), } const wideFont = createMockFont() const wideCanvas = createMockCanvas() worldPanels.drawItemTooltip(wideCanvas as any, { item: wideTallItem, x: 750, y: 150 }, wideFont) assert(wideCanvas.fillRectCalls.length === 1, 'Wide item tooltip renders without error') const [wx, wy, ww, wh] = wideCanvas.fillRectCalls[0]! assert(wx >= 8, `Wide box X left bound clamped (got ${wx} >= 8)`) assert(wx + ww <= 792, `Wide box X right bound clamped within 792 (got ${wx + ww} <= 792)`) assert(wy >= 8, `Wide box Y top bound clamped (got ${wy} >= 8)`) assert(wy + wh <= 592, `Wide box Y bottom bound clamped within 592 (got ${wy + wh} <= 592)`) // 7.1b Extreme adversarial input (300 chars, 50 stats) safety without throwing const extremeItem: UiInventoryItem = { id: 'extreme-item', code: 'cap', invFile: 'invcap', name: 'A'.repeat(300), nameZh: '长'.repeat(250), baseNameZh: '基'.repeat(200), quality: 'craft', invWidth: 2, invHeight: 2, allowedSlots: ['helm'], stats: Array.from({ length: 50 }, (_, i) => ({ text: `极限属性描述 #${i + 1}: ` + '超长文本测试'.repeat(10), color: 'blue' as D2ColorCode, })), } const extremeFont = createMockFont() const extremeCanvas = createMockCanvas() worldPanels.drawItemTooltip(extremeCanvas as any, { item: extremeItem, x: 400, y: 150 }, extremeFont) assert(extremeCanvas.fillRectCalls.length === 1, 'Extreme item tooltip renders without error') const [ex, ey, ew, eh] = extremeCanvas.fillRectCalls[0]! assert(Number.isFinite(ex) && Number.isFinite(ey) && Number.isFinite(ew) && Number.isFinite(eh), 'Extreme item produces finite numeric bounds') // 7.2 Completely bare item with zero stats / undefs const bareItem: UiInventoryItem = { id: 'bare-item', code: 'cap', invFile: 'invcap', name: 'Bare Cap', nameZh: '便帽', baseNameZh: '', quality: 'normal', invWidth: 1, invHeight: 1, allowedSlots: [], stats: [], } const bareFont = createMockFont() const bareCanvas = createMockCanvas() worldPanels.drawItemTooltip(bareCanvas as any, { item: bareItem, x: 200, y: 100 }, bareFont) assert(bareCanvas.fillRectCalls.length === 1, 'Bare item renders without error') // 7.3 Performance benchmark: 2,000 tooltips rendered const t0 = performance.now() const benchmarkFont = createMockFont() const benchmarkCanvas = createMockCanvas() for (let i = 0; i < 2000; i++) { const sampleItem = (i % 2 === 0) ? shako : cta worldPanels.drawItemTooltip( benchmarkCanvas as any, { item: sampleItem, x: (i * 17) % 800, y: (i * 23) % 600 }, benchmarkFont, ) } const elapsedMs = performance.now() - t0 assert(elapsedMs < 300, `2,000 tooltips rendered in ${elapsedMs.toFixed(2)}ms (< 300ms threshold)`) // --------------------------------------------------------------------------- // Final Summary // --------------------------------------------------------------------------- console.log(`\n======================================================`) console.log(`Empirical Verification Summary:`) console.log(`Total Assertions: ${totalAssertions}`) console.log(`Passed: ${totalAssertions - failedAssertions}`) console.log(`Failed: ${failedAssertions}`) console.log(`Verdict: ${failedAssertions === 0 ? 'APPROVE' : 'REJECT'}`) console.log(`======================================================\n`) if (failedAssertions > 0) { process.exit(1) }