Extend dashboard tests: integrate listStockEvents mock, update stock transition handling, and enhance recent changes validation
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -1,11 +1,13 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
const listKitchenChangesMock = vi.fn();
|
const listKitchenChangesMock = vi.fn();
|
||||||
|
const listStockEventsMock = vi.fn();
|
||||||
const getStockEntryMock = vi.fn();
|
const getStockEntryMock = vi.fn();
|
||||||
const fetchLocationsMock = vi.fn();
|
const fetchLocationsMock = vi.fn();
|
||||||
|
|
||||||
vi.mock('../../../src/api/stock.js', () => ({
|
vi.mock('../../../src/api/stock.js', () => ({
|
||||||
listKitchenChanges: (...args) => listKitchenChangesMock(...args),
|
listKitchenChanges: (...args) => listKitchenChangesMock(...args),
|
||||||
|
listStockEvents: (...args) => listStockEventsMock(...args),
|
||||||
getStockEntry: (...args) => getStockEntryMock(...args),
|
getStockEntry: (...args) => getStockEntryMock(...args),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -18,6 +20,7 @@ const { dashboardPageData, renderDashboardPage } = await import('../../../src/fe
|
|||||||
describe('features/dashboard/dashboard-page', () => {
|
describe('features/dashboard/dashboard-page', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
listKitchenChangesMock.mockReset();
|
listKitchenChangesMock.mockReset();
|
||||||
|
listStockEventsMock.mockReset();
|
||||||
getStockEntryMock.mockReset();
|
getStockEntryMock.mockReset();
|
||||||
fetchLocationsMock.mockReset();
|
fetchLocationsMock.mockReset();
|
||||||
});
|
});
|
||||||
@@ -26,10 +29,11 @@ describe('features/dashboard/dashboard-page', () => {
|
|||||||
const html = renderDashboardPage();
|
const html = renderDashboardPage();
|
||||||
expect(html).toContain('Recent changes');
|
expect(html).toContain('Recent changes');
|
||||||
expect(html).toContain('x-data="dashboardPage()"');
|
expect(html).toContain('x-data="dashboardPage()"');
|
||||||
expect(html).toContain('Saved means the backend created or updated a record.');
|
expect(html).toContain('Latest item and stock updates, including used and inactive stock.');
|
||||||
|
expect(html).toContain('recent-change-list');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('loads recent changes on init and renders item-focused state lines', async () => {
|
it('loads recent changes on init and renders item-focused details', async () => {
|
||||||
listKitchenChangesMock.mockResolvedValueOnce({
|
listKitchenChangesMock.mockResolvedValueOnce({
|
||||||
since: null,
|
since: null,
|
||||||
nextCursor: null,
|
nextCursor: null,
|
||||||
@@ -62,16 +66,20 @@ describe('features/dashboard/dashboard-page', () => {
|
|||||||
|
|
||||||
await data.init();
|
await data.init();
|
||||||
|
|
||||||
expect(listKitchenChangesMock).toHaveBeenCalledWith(store, { limit: 10 });
|
expect(listKitchenChangesMock).toHaveBeenCalledWith(store, { limit: 200 });
|
||||||
expect(data.recentChanges).toHaveLength(1);
|
expect(data.recentChanges).toHaveLength(1);
|
||||||
expect(data.changesState.error).toBe('');
|
expect(data.changesState.error).toBe('');
|
||||||
expect(data.changeHeadline(data.recentChanges[0])).toBe('Item saved: Rice');
|
expect(data.changeHeadline(data.recentChanges[0])).toBe('Rice');
|
||||||
expect(data.changeStateLine(data.recentChanges[0])).toContain('Quantity: 3 kg');
|
expect(data.changeKindLabel(data.recentChanges[0])).toBe('Updated');
|
||||||
expect(data.changeStateLine(data.recentChanges[0])).toContain('Location: Pantry / Shelf A');
|
expect(data.changeSubtitle(data.recentChanges[0])).toBe('Item details were updated.');
|
||||||
|
expect(data.changeDetails(data.recentChanges[0])).toEqual(expect.arrayContaining([
|
||||||
|
{ label: 'Quantity', value: '3 kg' },
|
||||||
|
{ label: 'Location', value: 'Pantry / Shelf A' },
|
||||||
|
]));
|
||||||
expect(getStockEntryMock).not.toHaveBeenCalled();
|
expect(getStockEntryMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resolves stock event item context via item lookup when needed', async () => {
|
it('resolves stock event item context and renders stock transitions', async () => {
|
||||||
listKitchenChangesMock.mockResolvedValueOnce({
|
listKitchenChangesMock.mockResolvedValueOnce({
|
||||||
since: null,
|
since: null,
|
||||||
nextCursor: null,
|
nextCursor: null,
|
||||||
@@ -80,6 +88,7 @@ describe('features/dashboard/dashboard-page', () => {
|
|||||||
action: 'upsert',
|
action: 'upsert',
|
||||||
timestamp: '2026-04-10T10:00:00Z',
|
timestamp: '2026-04-10T10:00:00Z',
|
||||||
stock: {
|
stock: {
|
||||||
|
id: 11,
|
||||||
item_uuid_b64: 'item-uuid-1',
|
item_uuid_b64: 'item-uuid-1',
|
||||||
quantity: 0.5,
|
quantity: 0.5,
|
||||||
uom_symbol: 'kg',
|
uom_symbol: 'kg',
|
||||||
@@ -88,6 +97,10 @@ describe('features/dashboard/dashboard-page', () => {
|
|||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
listStockEventsMock.mockResolvedValueOnce([
|
||||||
|
{ id: 11, quantity: 0.5, uom_symbol: 'kg', level: 'some' },
|
||||||
|
{ id: 10, quantity: 1, uom_symbol: 'kg', level: 'good' },
|
||||||
|
]);
|
||||||
getStockEntryMock.mockResolvedValueOnce({
|
getStockEntryMock.mockResolvedValueOnce({
|
||||||
uuid_b64: 'item-uuid-1',
|
uuid_b64: 'item-uuid-1',
|
||||||
name: 'Flour',
|
name: 'Flour',
|
||||||
@@ -106,11 +119,23 @@ describe('features/dashboard/dashboard-page', () => {
|
|||||||
|
|
||||||
await data.refreshChanges();
|
await data.refreshChanges();
|
||||||
|
|
||||||
expect(data.changeHeadline(data.recentChanges[0])).toBe('Stock saved: Flour');
|
expect(data.changeHeadline(data.recentChanges[0])).toBe('Flour');
|
||||||
expect(data.changeStateLine(data.recentChanges[0])).toContain('Quantity: 0.5 kg');
|
expect(data.changeKindLabel(data.recentChanges[0])).toBe('Stock changed');
|
||||||
expect(data.changeStateLine(data.recentChanges[0])).toContain('Level: Some');
|
expect(data.changeStockTransition(data.recentChanges[0])).toEqual({
|
||||||
expect(data.changeStateLine(data.recentChanges[0])).toContain('Location: Pantry / Bin 2');
|
previous: '1 kg · Good',
|
||||||
|
current: '0.5 kg · Some',
|
||||||
|
});
|
||||||
|
expect(data.changeDetails(data.recentChanges[0])).toEqual(expect.arrayContaining([
|
||||||
|
{ label: 'Level', value: 'Some' },
|
||||||
|
{ label: 'Location', value: 'Pantry / Bin 2' },
|
||||||
|
]));
|
||||||
expect(getStockEntryMock).toHaveBeenCalledWith(expect.anything(), 'item-uuid-1');
|
expect(getStockEntryMock).toHaveBeenCalledWith(expect.anything(), 'item-uuid-1');
|
||||||
|
expect(listStockEventsMock).toHaveBeenCalledWith(expect.anything(), 'item-uuid-1', {
|
||||||
|
allowInactive: true,
|
||||||
|
limit: 50,
|
||||||
|
orderBy: 'id',
|
||||||
|
orderDir: 'desc',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('retries stock event item lookup with allowInactive after 404', async () => {
|
it('retries stock event item lookup with allowInactive after 404', async () => {
|
||||||
@@ -122,12 +147,16 @@ describe('features/dashboard/dashboard-page', () => {
|
|||||||
action: 'upsert',
|
action: 'upsert',
|
||||||
timestamp: '2026-04-10T10:00:00Z',
|
timestamp: '2026-04-10T10:00:00Z',
|
||||||
stock: {
|
stock: {
|
||||||
|
id: 12,
|
||||||
item_uuid_b64: 'item-uuid-2',
|
item_uuid_b64: 'item-uuid-2',
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
uom_symbol: 'pcs',
|
uom_symbol: 'pcs',
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
listStockEventsMock.mockResolvedValueOnce([
|
||||||
|
{ id: 12, quantity: 1, uom_symbol: 'pcs' },
|
||||||
|
]);
|
||||||
getStockEntryMock
|
getStockEntryMock
|
||||||
.mockRejectedValueOnce(Object.assign(new Error('Not found'), { status: 404 }))
|
.mockRejectedValueOnce(Object.assign(new Error('Not found'), { status: 404 }))
|
||||||
.mockResolvedValueOnce({
|
.mockResolvedValueOnce({
|
||||||
@@ -153,8 +182,12 @@ describe('features/dashboard/dashboard-page', () => {
|
|||||||
'item-uuid-2',
|
'item-uuid-2',
|
||||||
{ allowInactive: true },
|
{ allowInactive: true },
|
||||||
);
|
);
|
||||||
expect(data.changeHeadline(data.recentChanges[0])).toBe('Stock saved: Archived pasta');
|
expect(data.changeHeadline(data.recentChanges[0])).toBe('Archived pasta');
|
||||||
expect(data.changeStateLine(data.recentChanges[0])).toContain('Quantity: 1 pcs');
|
expect(data.changeKindLabel(data.recentChanges[0])).toBe('New item');
|
||||||
|
expect(data.changeStockTransition(data.recentChanges[0])).toEqual({
|
||||||
|
previous: 'Initial stock',
|
||||||
|
current: '1 pcs',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps empty state when API returns no changes', async () => {
|
it('keeps empty state when API returns no changes', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user