import { describe, expect, it } from 'vitest' import { EscPos, hr, twoCol, renderReceipt } from '@sims/printing' import type { BillLine, BillTotals } from '@sims/domain' describe('twoCol', () => { it('pads to exact width and truncates long names', () => { expect(twoCol('Atta 5kg', '270.00', 32)).toHaveLength(32) const long = twoCol('A very very long item name indeed', '1,431.00', 32) expect(long).toHaveLength(32) expect(long.endsWith('1,431.00')).toBe(true) }) it('always keeps one space between columns', () => { expect(twoCol('X'.repeat(40), '99.00', 32)).toContain(' 99.00') }) }) describe('EscPos builder', () => { it('starts with init and encodes control sequences', () => { const b = new EscPos().init().bold(true).text('HI').cut().build() expect([...b.slice(0, 2)]).toEqual([0x1b, 0x40]) expect([...b]).toContain(0x48) // 'H' expect([...b.slice(-4)]).toEqual([0x1d, 0x56, 66, 3]) }) it('emits the standard drawer-kick pulse', () => { const b = new EscPos().drawerKick().build() expect([...b]).toEqual([0x1b, 0x70, 0, 25, 250]) }) it('collapses non-ASCII to ? in text mode (raster path comes later)', () => { const b = new EscPos().text('चाय').build() expect([...b]).toEqual([0x3f, 0x3f, 0x3f]) }) }) describe('renderReceipt', () => { const line: BillLine = { itemId: 'i1', name: 'Aashirvaad Atta 5kg', hsn: '1101', qty: 1, unitCode: 'PCS', unitPricePaise: 27_000, mrpPaise: 28_500, grossPaise: 27_000, discountPaise: 0, taxablePaise: 22_881, taxRateBp: 1800, cgstPaise: 2_059, sgstPaise: 2_060, igstPaise: 0, cessPaise: 0, lineTotalPaise: 27_000, } const totals: BillTotals = { grossPaise: 27_000, discountPaise: 0, taxablePaise: 22_881, cgstPaise: 2_059, sgstPaise: 2_060, igstPaise: 0, cessPaise: 0, roundOffPaise: 0, payablePaise: 27_000, savingsVsMrpPaise: 1_500, } const doc = { storeName: 'MegaMart', docNo: 'ST1C2/26-000123', businessDate: '2026-07-09', counterCode: 'C2', cashierName: 'Ramesh', lines: [line], totals, paymentLabel: 'CASH', } it('renders a complete receipt with cut at the end', () => { const bytes = renderReceipt(doc, { width: 42, kickDrawer: false }) const text = new TextDecoder('ascii').decode(bytes) expect(text).toContain('PAYABLE') expect(text).toContain('ST1C2/26-000123') expect(text).toContain('You saved 15.00 vs MRP') expect([...bytes.slice(-4)]).toEqual([0x1d, 0x56, 66, 3]) }) it('kicks the drawer before printing only when asked', () => { const withKick = renderReceipt(doc, { width: 42, kickDrawer: true }) expect([...withKick.slice(2, 7)]).toEqual([0x1b, 0x70, 0, 25, 250]) const noKick = renderReceipt(doc, { width: 42, kickDrawer: false }) expect([...noKick.slice(2, 7)]).not.toEqual([0x1b, 0x70, 0, 25, 250]) }) it(`shows IGST instead of the split for inter-state bills`, () => { const inter = renderReceipt( { ...doc, totals: { ...totals, cgstPaise: 0, sgstPaise: 0, igstPaise: 4_119 } }, { width: 42, kickDrawer: false }, ) const text = new TextDecoder('ascii').decode(inter) expect(text).toContain('IGST') expect(text).not.toContain('CGST') }) it('prints the buyer GSTIN line for a B2B receipt', () => { const b2b = renderReceipt( { ...doc, buyerName: 'Anand Traders', buyerGstin: '27ABCDE1234F1Z0' }, { width: 42, kickDrawer: false }, ) const text = new TextDecoder('ascii').decode(b2b) expect(text).toContain('Buyer: Anand Traders') expect(text).toContain('Buyer GSTIN: 27ABCDE1234F1Z0') }) })