// Page order is the one thing here that costs money to get wrong: a run of // badge cards with the backs one card out looks perfect until someone reads // one. These tests pin the order and the CSV contract. import { describe, it, expect } from 'vitest' import { parseCsv, splitCsvLine, buildPages, MAX_LABELS } from './labelPages' const sides = pages => pages.map(page => page.side).join(' ') const contents = pages => pages.map(page => page.content || '-').join(' ') describe('splitCsvLine', () => { it('keeps a comma that is inside quotes', () => { expect(splitCsvLine('WJ-0001,"Bay 12, press",2')).toEqual(['WJ-0001', 'Bay 12, press', '2']) }) it('unescapes a doubled quote', () => { expect(splitCsvLine('x,"the ""big"" one"')).toEqual(['x', 'the "big" one']) }) }) describe('parseCsv', () => { it('reads a named header in any column order', () => { const rows = parseCsv('copies,label,content\n3,Line 1,WJ-0001') expect(rows).toEqual([ { content: 'WJ-0001', label: 'Line 1', copies: 3, back: '', backlabel: '' }, ]) }) it('falls back to positional columns with no header', () => { const rows = parseCsv('WJ-0001,Line 1,2,WJ-0001-B,Reverse') expect(rows[0]).toEqual({ content: 'WJ-0001', label: 'Line 1', copies: 2, back: 'WJ-0001-B', backlabel: 'Reverse', }) }) it('skips a row with no content rather than printing a blank sticker', () => { expect(parseCsv('content,label\n,orphan\nWJ-0002,real')).toHaveLength(1) }) it('defaults copies to 1 when it is missing or junk', () => { expect(parseCsv('content,copies\nA,\nB,zero')[0].copies).toBe(1) expect(parseCsv('content,copies\nA,\nB,zero')[1].copies).toBe(1) }) }) describe('buildPages without a back side', () => { it('expands copies and emits fronts only', () => { const { pages, backs } = buildPages([{ content: 'A', label: 'a', copies: 3 }]) expect(sides(pages)).toBe('front front front') expect(backs).toHaveLength(0) }) it('caps cards, not pages, and says how many it dropped', () => { const { fronts, dropped } = buildPages([{ content: 'A', copies: MAX_LABELS + 5 }]) expect(fronts).toHaveLength(MAX_LABELS) expect(dropped).toBe(5) }) }) describe('buildPages with a back side', () => { const rows = [ { content: 'A', label: 'a', copies: 2, back: 'A2', backlabel: 'a2' }, { content: 'B', label: 'b', copies: 1, back: 'B2', backlabel: 'b2' }, ] it('interleaves front and back, which is what a duplex driver takes', () => { const { pages } = buildPages(rows, { back: { enabled: true, source: 'column', order: 'interleave' } }) expect(sides(pages)).toBe('front back front back front back') expect(contents(pages)).toBe('A A2 A A2 B B2') }) it('groups every front then every back, for a flip-and-reload printer', () => { const { pages } = buildPages(rows, { back: { enabled: true, source: 'column', order: 'grouped' } }) expect(sides(pages)).toBe('front front front back back back') // Same card order in both halves, or the flipped stack pairs up wrong. expect(contents(pages)).toBe('A A B A2 A2 B2') }) it('still emits a back for a row that left the back column empty', () => { const { pages } = buildPages( [{ content: 'A', copies: 1 }, { content: 'B', copies: 1, back: 'B2' }], { back: { enabled: true, source: 'column', order: 'interleave' } }) // A page count that changes per row would shift every later back onto the // wrong front. expect(sides(pages)).toBe('front back front back') expect(contents(pages)).toBe('A - B B2') }) it('repeats the front payload when the back is the same code', () => { const { backs } = buildPages(rows, { back: { enabled: true, source: 'code' } }) expect(contents(backs)).toBe('A A B') }) it('puts the same fixed text on every back and no code', () => { const { backs } = buildPages(rows, { back: { enabled: true, source: 'text', text: 'Property of GE' } }) expect(backs.every(page => page.content === '')).toBe(true) expect(backs.every(page => page.label === 'Property of GE')).toBe(true) }) it('leaves a blank back entirely empty but still counted', () => { const { pages, backs } = buildPages(rows, { back: { enabled: true, source: 'blank' } }) expect(pages).toHaveLength(6) expect(backs.every(page => !page.content && !page.label)).toBe(true) }) it('falls back to the fixed text when a row has no back label', () => { const { backs } = buildPages([{ content: 'A', copies: 1, back: 'A2' }], { back: { enabled: true, source: 'column', text: 'Return to IT' } }) expect(backs[0].label).toBe('Return to IT') }) })