310 lines
8.8 KiB
JavaScript
310 lines
8.8 KiB
JavaScript
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();
|
|
});
|
|
});
|