144 lines
4.5 KiB
JavaScript
144 lines
4.5 KiB
JavaScript
|
|
import { describe, expect, it, vi } from 'vitest';
|
||
|
|
|
||
|
|
const listKitchenChangesMock = vi.fn();
|
||
|
|
const getStockEntryMock = vi.fn();
|
||
|
|
const fetchLocationsMock = vi.fn();
|
||
|
|
|
||
|
|
vi.mock('../../../src/api/stock.js', () => ({
|
||
|
|
listKitchenChanges: (...args) => listKitchenChangesMock(...args),
|
||
|
|
getStockEntry: (...args) => getStockEntryMock(...args),
|
||
|
|
}));
|
||
|
|
|
||
|
|
vi.mock('../../../src/api/locations.js', () => ({
|
||
|
|
fetchLocations: (...args) => fetchLocationsMock(...args),
|
||
|
|
}));
|
||
|
|
|
||
|
|
const { dashboardPageData, renderDashboardPage } = await import('../../../src/features/dashboard/dashboard-page.js');
|
||
|
|
|
||
|
|
describe('features/dashboard/dashboard-page', () => {
|
||
|
|
it('renders dashboard with recent changes section', () => {
|
||
|
|
const html = renderDashboardPage();
|
||
|
|
expect(html).toContain('Recent changes');
|
||
|
|
expect(html).toContain('x-data="dashboardPage()"');
|
||
|
|
expect(html).toContain('Saved means the backend created or updated a record.');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('loads recent changes on init and renders item-focused state lines', async () => {
|
||
|
|
listKitchenChangesMock.mockResolvedValueOnce({
|
||
|
|
since: null,
|
||
|
|
nextCursor: null,
|
||
|
|
changes: [{
|
||
|
|
type: 'item',
|
||
|
|
action: 'upsert',
|
||
|
|
timestamp: '2026-04-10T10:00:00Z',
|
||
|
|
item: {
|
||
|
|
uuid_b64: 'u1',
|
||
|
|
name: 'Rice',
|
||
|
|
stock_type: 'measured',
|
||
|
|
quantity: 3,
|
||
|
|
uom_symbol: 'kg',
|
||
|
|
level: 'good',
|
||
|
|
expire_date: '2026-04-21',
|
||
|
|
location_initial_uuid_b64: 'loc1',
|
||
|
|
},
|
||
|
|
}],
|
||
|
|
});
|
||
|
|
fetchLocationsMock.mockResolvedValueOnce({
|
||
|
|
flat: [{ uuid_b64: 'loc1', pathLabel: 'Pantry / Shelf A' }],
|
||
|
|
});
|
||
|
|
|
||
|
|
const store = {
|
||
|
|
isConnected: true,
|
||
|
|
setActiveKitchen: vi.fn(),
|
||
|
|
addAlert: vi.fn(),
|
||
|
|
};
|
||
|
|
const data = dashboardPageData(store);
|
||
|
|
|
||
|
|
await data.init();
|
||
|
|
|
||
|
|
expect(listKitchenChangesMock).toHaveBeenCalledWith(store, { limit: 10 });
|
||
|
|
expect(data.recentChanges).toHaveLength(1);
|
||
|
|
expect(data.changesState.error).toBe('');
|
||
|
|
expect(data.changeHeadline(data.recentChanges[0])).toBe('Item saved: Rice');
|
||
|
|
expect(data.changeStateLine(data.recentChanges[0])).toContain('Quantity: 3 kg');
|
||
|
|
expect(data.changeStateLine(data.recentChanges[0])).toContain('Location: Pantry / Shelf A');
|
||
|
|
expect(getStockEntryMock).not.toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('resolves stock event item context via item lookup when needed', async () => {
|
||
|
|
listKitchenChangesMock.mockResolvedValueOnce({
|
||
|
|
since: null,
|
||
|
|
nextCursor: null,
|
||
|
|
changes: [{
|
||
|
|
type: 'stock',
|
||
|
|
action: 'upsert',
|
||
|
|
timestamp: '2026-04-10T10:00:00Z',
|
||
|
|
stock: {
|
||
|
|
item_uuid_b64: 'item-uuid-1',
|
||
|
|
quantity: 0.5,
|
||
|
|
uom_symbol: 'kg',
|
||
|
|
level: 'some',
|
||
|
|
location_uuid_b64: 'loc2',
|
||
|
|
},
|
||
|
|
}],
|
||
|
|
});
|
||
|
|
getStockEntryMock.mockResolvedValueOnce({
|
||
|
|
uuid_b64: 'item-uuid-1',
|
||
|
|
name: 'Flour',
|
||
|
|
stock_type: 'measured',
|
||
|
|
expire_date: '2026-05-02',
|
||
|
|
});
|
||
|
|
fetchLocationsMock.mockResolvedValueOnce({
|
||
|
|
flat: [{ uuid_b64: 'loc2', pathLabel: 'Pantry / Bin 2' }],
|
||
|
|
});
|
||
|
|
|
||
|
|
const data = dashboardPageData({
|
||
|
|
isConnected: true,
|
||
|
|
setActiveKitchen: vi.fn(),
|
||
|
|
addAlert: vi.fn(),
|
||
|
|
});
|
||
|
|
|
||
|
|
await data.refreshChanges();
|
||
|
|
|
||
|
|
expect(data.changeHeadline(data.recentChanges[0])).toBe('Stock saved: Flour');
|
||
|
|
expect(data.changeStateLine(data.recentChanges[0])).toContain('Quantity: 0.5 kg');
|
||
|
|
expect(data.changeStateLine(data.recentChanges[0])).toContain('Level: Some');
|
||
|
|
expect(data.changeStateLine(data.recentChanges[0])).toContain('Location: Pantry / Bin 2');
|
||
|
|
expect(getStockEntryMock).toHaveBeenCalledWith(expect.anything(), 'item-uuid-1');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('keeps empty state when API returns no changes', async () => {
|
||
|
|
listKitchenChangesMock.mockResolvedValueOnce({
|
||
|
|
since: null,
|
||
|
|
nextCursor: null,
|
||
|
|
changes: [],
|
||
|
|
});
|
||
|
|
fetchLocationsMock.mockResolvedValueOnce({ flat: [] });
|
||
|
|
|
||
|
|
const data = dashboardPageData({
|
||
|
|
isConnected: true,
|
||
|
|
setActiveKitchen: vi.fn(),
|
||
|
|
addAlert: vi.fn(),
|
||
|
|
});
|
||
|
|
|
||
|
|
await data.refreshChanges();
|
||
|
|
|
||
|
|
expect(data.recentChanges).toEqual([]);
|
||
|
|
expect(data.changesState.error).toBe('');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('captures refresh errors in async state', async () => {
|
||
|
|
listKitchenChangesMock.mockRejectedValueOnce(new Error('Feed unavailable'));
|
||
|
|
const data = dashboardPageData({
|
||
|
|
isConnected: true,
|
||
|
|
setActiveKitchen: vi.fn(),
|
||
|
|
addAlert: vi.fn(),
|
||
|
|
});
|
||
|
|
|
||
|
|
await data.refreshChanges();
|
||
|
|
|
||
|
|
expect(data.changesState.error).toBe('Feed unavailable');
|
||
|
|
expect(data.recentChanges).toEqual([]);
|
||
|
|
});
|
||
|
|
});
|