feat(planner): integrate C4 RecipePicker with PanelState machine + slot actions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-08 23:23:26 +02:00
parent 178c888635
commit cbafe783e9
3 changed files with 426 additions and 56 deletions

View File

@@ -6,8 +6,10 @@ vi.mock('$env/dynamic/private', () => ({
const mockGet = vi.fn();
const mockPost = vi.fn();
const mockPatch = vi.fn();
const mockDelete = vi.fn();
vi.mock('$lib/server/api', () => ({
apiClient: () => ({ GET: mockGet, POST: mockPost })
apiClient: () => ({ GET: mockGet, POST: mockPost, PATCH: mockPatch, DELETE: mockDelete })
}));
describe('planner page — load', () => {
@@ -193,3 +195,89 @@ describe('planner page — variety score partial failure', () => {
expect(result.varietyScore).toBeNull();
});
});
describe('planner page — slot actions', () => {
let actions: any;
beforeEach(async () => {
mockGet.mockReset();
mockPost.mockReset();
mockPatch.mockReset();
mockDelete.mockReset();
vi.resetModules();
const mod = await import('./+page.server');
actions = mod.actions;
});
it('addSlot calls POST /v1/week-plans/{id}/slots and returns success with slot', async () => {
const formData = new FormData();
formData.set('planId', 'plan-1');
formData.set('slotDate', '2026-04-01');
formData.set('recipeId', 'r1');
mockPost.mockResolvedValue({ data: { id: 's1', slotDate: '2026-04-01' }, error: undefined });
const result = await actions.addSlot({
fetch: vi.fn(),
request: { formData: async () => formData }
} as any);
expect(mockPost).toHaveBeenCalledWith(
'/v1/week-plans/{id}/slots',
expect.objectContaining({
params: { path: { id: 'plan-1' } },
body: { slotDate: '2026-04-01', recipeId: 'r1' }
})
);
expect(result.success).toBe(true);
expect(result.slot?.id).toBe('s1');
});
it('addSlot returns failure when API errors', async () => {
const formData = new FormData();
formData.set('planId', 'plan-1');
formData.set('slotDate', '2026-04-01');
formData.set('recipeId', 'r1');
mockPost.mockResolvedValue({ data: undefined, error: { status: 500 } });
const result = await actions.addSlot({
fetch: vi.fn(),
request: { formData: async () => formData }
} as any);
expect(result.success).toBe(false);
});
it('updateSlot calls PATCH /v1/week-plans/{planId}/slots/{slotId} and returns success', async () => {
const formData = new FormData();
formData.set('planId', 'plan-1');
formData.set('slotId', 's1');
formData.set('recipeId', 'r2');
mockPatch.mockResolvedValue({ data: { id: 's1' }, error: undefined });
const result = await actions.updateSlot({
fetch: vi.fn(),
request: { formData: async () => formData }
} as any);
expect(mockPatch).toHaveBeenCalledWith(
'/v1/week-plans/{planId}/slots/{slotId}',
expect.objectContaining({
params: { path: { planId: 'plan-1', slotId: 's1' } },
body: { recipeId: 'r2' }
})
);
expect(result.success).toBe(true);
});
it('deleteSlot calls DELETE /v1/week-plans/{planId}/slots/{slotId} and returns success', async () => {
const formData = new FormData();
formData.set('planId', 'plan-1');
formData.set('slotId', 's1');
mockDelete.mockResolvedValue({ error: undefined });
const result = await actions.deleteSlot({
fetch: vi.fn(),
request: { formData: async () => formData }
} as any);
expect(mockDelete).toHaveBeenCalledWith(
'/v1/week-plans/{planId}/slots/{slotId}',
expect.objectContaining({
params: { path: { planId: 'plan-1', slotId: 's1' } }
})
);
expect(result.success).toBe(true);
});
});