import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; const applyItemUpsertMock = vi.fn(); const previewItemUpsertMock = vi.fn(); const printItemLabelMock = vi.fn(); const LABEL_DRAFT_STORAGE_KEY = 'lonc.labels.draft'; let localStorageState; let localStorageMock; function createWindowStorageMock(initialState = {}) { const state = new Map(Object.entries(initialState)); const localStorage = { getItem: vi.fn((key) => (state.has(key) ? state.get(key) : null)), setItem: vi.fn((key, value) => { state.set(key, String(value)); }), removeItem: vi.fn((key) => { state.delete(key); }), }; vi.stubGlobal('window', { localStorage }); return { state, localStorage }; } function readStoredLabelDraft() { const raw = localStorageState.get(LABEL_DRAFT_STORAGE_KEY); return raw ? JSON.parse(raw) : null; } vi.mock('../../../src/api/stock.js', () => ({ applyItemUpsert: (...args) => applyItemUpsertMock(...args), previewItemUpsert: (...args) => previewItemUpsertMock(...args), searchItemDefinitions: vi.fn(async () => []), })); vi.mock('../../../src/api/labels.js', () => ({ previewLabel: vi.fn(async () => ({ objectUrl: 'blob:preview' })), printItemLabel: (...args) => printItemLabelMock(...args), formatPrintErrorMessage: (error) => error?.message || 'Printing failed.', })); vi.mock('../../../src/api/locations.js', () => ({ fetchLocations: vi.fn(async () => ({ flat: [], tree: [] })), })); const { labelCreatePageData } = await import('../../../src/features/labels/label-create-page.js'); describe('label create upsert-first submit', () => { beforeEach(() => { vi.useFakeTimers(); vi.setSystemTime(new Date('2026-04-12T12:00:00Z')); applyItemUpsertMock.mockReset(); previewItemUpsertMock.mockReset(); printItemLabelMock.mockReset(); const storageMock = createWindowStorageMock(); localStorageState = storageMock.state; localStorageMock = storageMock.localStorage; }); afterEach(() => { vi.useRealTimers(); vi.unstubAllGlobals(); }); it('defaults print checkbox to enabled', () => { const data = labelCreatePageData({ isConnected: false, activeKitchen: { id: 1 }, addAlert: vi.fn(), }); expect(data.printLabelOnSave).toBe(true); }); it('restores a fresh enveloped draft when inactivity is below 30 minutes', () => { localStorageState.set( LABEL_DRAFT_STORAGE_KEY, JSON.stringify({ form: { name: 'Draft yogurt', productionDate: '2026-04-11', stockType: 'descriptive', }, savedAt: Date.now() - (29 * 60 * 1000), }), ); const data = labelCreatePageData({ isConnected: false, activeKitchen: { id: 1 }, addAlert: vi.fn(), }); expect(data.form.name).toBe('Draft yogurt'); expect(data.form.productionDate).toBe('2026-04-11'); expect(data.form.stockType).toBe('descriptive'); }); it('drops stale enveloped drafts at 30 minutes inactivity and loads a clean form', () => { localStorageState.set( LABEL_DRAFT_STORAGE_KEY, JSON.stringify({ form: { name: 'Old draft', description: 'Should be removed', productionDate: '2026-04-10', stockType: 'measured', quantity: '4', uom: 'kg', }, savedAt: Date.now() - (30 * 60 * 1000), }), ); const data = labelCreatePageData({ isConnected: false, activeKitchen: { id: 1 }, addAlert: vi.fn(), }); expect(data.form.name).toBe(''); expect(data.form.description).toBe(''); expect(data.form.stockType).toBe('binary'); expect(data.form.quantity).toBe(''); expect(data.form.uom).toBe('g'); expect(data.form.productionDate).toBe('2026-04-12'); }); it('keeps draft when day changes but inactivity stays below 30 minutes', () => { vi.setSystemTime(new Date('2026-04-12T00:10:00Z')); localStorageState.set( LABEL_DRAFT_STORAGE_KEY, JSON.stringify({ form: { name: 'Day-changed draft', productionDate: '2026-04-11', }, savedAt: Date.now() - (10 * 60 * 1000), }), ); const data = labelCreatePageData({ isConnected: false, activeKitchen: { id: 1 }, addAlert: vi.fn(), }); expect(data.form.name).toBe('Day-changed draft'); expect(data.form.productionDate).toBe('2026-04-11'); }); it('loads legacy plain-object drafts without forcing discard', () => { localStorageState.set( LABEL_DRAFT_STORAGE_KEY, JSON.stringify({ name: 'Legacy draft', productionDate: '2026-04-05', }), ); const data = labelCreatePageData({ isConnected: false, activeKitchen: { id: 1 }, addAlert: vi.fn(), }); expect(data.form.name).toBe('Legacy draft'); expect(data.form.productionDate).toBe('2026-04-05'); }); it('writes enveloped draft payload from persist and reset save paths', () => { const data = labelCreatePageData({ isConnected: false, activeKitchen: { id: 1 }, addAlert: vi.fn(), }); data.form = { ...data.form, name: 'Persisted entry', search: 'temp search value', }; data.persistDraft(); const persistedDraft = readStoredLabelDraft(); expect(persistedDraft.savedAt).toBe(Date.now()); expect(persistedDraft.form.name).toBe('Persisted entry'); expect(persistedDraft.form.search).toBe(''); vi.setSystemTime(new Date('2026-04-12T12:05:00Z')); data.form.name = 'Reset me'; data.$refs = {}; data.reset(false); const resetDraft = readStoredLabelDraft(); expect(resetDraft.savedAt).toBe(Date.now()); expect(resetDraft.form.name).toBe(''); expect(resetDraft.form.productionDate).toBe('2026-04-12'); }); it('builds upsert payload with selected template uuid', () => { const store = { isConnected: false, activeKitchen: { id: 7 }, addAlert: vi.fn(), }; const data = labelCreatePageData(store); data.form = { ...data.form, itemUuidB64: 'uuid-template-1', name: 'Beans', description: 'Dry beans', stockType: 'measured', quantity: '2', uom: 'kg', level: '', productionDate: '2026-04-10', expirationDate: '2026-08-10', locationId: '', identifierCode: '12345', }; const payload = data.buildUpsertPayload(); expect(payload.uuid_b64).toBe('uuid-template-1'); expect(payload.identifier_code).toBe('12345'); expect(payload.item.name).toBe('Beans'); expect(payload.item.quantity_initial).toBe(2); }); it('create uses applyItemUpsert and sets operation-aware success message', async () => { applyItemUpsertMock.mockResolvedValueOnce({ operation: 'update', item: { name: 'Rice', uuid_b64: 'uuid-rice-1' }, }); printItemLabelMock.mockResolvedValueOnce(null); const addAlert = vi.fn(); const store = { isConnected: false, activeKitchen: { id: 3 }, addAlert, }; const data = labelCreatePageData(store); data.validateBeforeSubmit = () => true; data.form = { ...data.form, name: 'Rice', stockType: 'binary', locationId: '', productionDate: '2026-04-10', itemUuidB64: 'uuid-rice-1', }; await data.create(); expect(applyItemUpsertMock).toHaveBeenCalledTimes(1); expect(applyItemUpsertMock.mock.calls[0][1].uuid_b64).toBe('uuid-rice-1'); expect(printItemLabelMock).toHaveBeenCalledWith(store, 'uuid-rice-1'); expect(data.successMessage).toBe('Rice was updated successfully.'); expect(addAlert).toHaveBeenCalledWith({ type: 'success', message: 'Rice was updated successfully.', }); const savedDraft = readStoredLabelDraft(); expect(savedDraft).toMatchObject({ form: { name: 'Rice', }, }); expect(savedDraft.savedAt).toBeTypeOf('number'); }); it('create shows parsed print issue warning when printing fails', async () => { applyItemUpsertMock.mockResolvedValueOnce({ operation: 'create', item: { name: 'Beans', uuid_b64: 'uuid-beans-1' }, }); printItemLabelMock.mockRejectedValueOnce(new Error('Printer is unavailable.')); const addAlert = vi.fn(); const store = { isConnected: false, activeKitchen: { id: 3 }, addAlert, }; const data = labelCreatePageData(store); data.validateBeforeSubmit = () => true; data.form = { ...data.form, name: 'Beans', stockType: 'binary', locationId: '', productionDate: '2026-04-10', itemUuidB64: '', }; await data.create(); expect(data.printIssue).toBe('Printer is unavailable.'); expect(addAlert).toHaveBeenCalledWith({ type: 'warning', message: 'Beans was created, but printing has an issue: Printer is unavailable.', }); expect(localStorageMock.setItem).toHaveBeenCalled(); }); });