feat(recipes): image upload, fix save 500, HelloFresh seed data #53
@@ -67,6 +67,13 @@ export const actions: Actions = {
|
||||
return fail(400, { error: 'Ungültige Formulardaten' });
|
||||
}
|
||||
|
||||
const filteredIngredients = (
|
||||
parsedIngredients as { name: string; quantity: string; unit: string }[]
|
||||
).filter((ing) => ing.name?.trim() && Number(ing.quantity) > 0);
|
||||
|
||||
if (!filteredIngredients.length)
|
||||
return fail(422, { error: 'Mindestens eine gültige Zutat ist erforderlich' });
|
||||
|
||||
const api = apiClient(fetch);
|
||||
const { error: apiError } = await api.PUT('/v1/recipes/{id}', {
|
||||
params: { path: { id: params.id } },
|
||||
@@ -76,14 +83,12 @@ export const actions: Actions = {
|
||||
cookTimeMin: cookTimeMin ? Number(cookTimeMin) || null : null,
|
||||
effort,
|
||||
heroImageUrl,
|
||||
ingredients: (parsedIngredients as { name: string; quantity: string; unit: string }[])
|
||||
.filter((ing) => ing.name?.trim() && Number(ing.quantity) > 0)
|
||||
.map((ing, i) => ({
|
||||
newIngredientName: ing.name.trim(),
|
||||
quantity: Number(ing.quantity),
|
||||
unit: ing.unit || '',
|
||||
sortOrder: i
|
||||
})),
|
||||
ingredients: filteredIngredients.map((ing, i) => ({
|
||||
newIngredientName: ing.name.trim(),
|
||||
quantity: Number(ing.quantity),
|
||||
unit: ing.unit || '',
|
||||
sortOrder: i
|
||||
})),
|
||||
steps: (parsedSteps as string[])
|
||||
.filter((s) => s?.trim())
|
||||
.map((s, i) => ({ stepNumber: i + 1, instruction: s.trim() })),
|
||||
|
||||
@@ -84,7 +84,7 @@ describe('edit recipe page — update action', () => {
|
||||
name: 'Test Rezept',
|
||||
effort: 'easy',
|
||||
tagIds: ['t1'],
|
||||
ingredientsJson: '[]',
|
||||
ingredientsJson: JSON.stringify([{ name: 'Spaghetti', quantity: 200, unit: 'g' }]),
|
||||
stepsJson: '[]',
|
||||
...overrides
|
||||
};
|
||||
@@ -236,6 +236,24 @@ describe('edit recipe page — update action', () => {
|
||||
expect(body.ingredients[0].newIngredientName).toBe('Spaghetti');
|
||||
});
|
||||
|
||||
it('returns fail(422) when all ingredients filter to empty after quantity check', async () => {
|
||||
const result = await actions.update({
|
||||
request: {
|
||||
formData: async () =>
|
||||
makeFormData({
|
||||
ingredientsJson: JSON.stringify([
|
||||
{ name: 'Salt', quantity: 0, unit: 'tsp' },
|
||||
{ name: '', quantity: 100, unit: 'g' }
|
||||
])
|
||||
})
|
||||
},
|
||||
fetch: vi.fn(),
|
||||
params: { id: 'r1' }
|
||||
} as any);
|
||||
expect(result.status).toBe(422);
|
||||
expect(result.data.error).toMatch(/zutat/i);
|
||||
});
|
||||
|
||||
it('returns fail(500) when API returns error', async () => {
|
||||
mockPut.mockResolvedValue({ error: { status: 500 } });
|
||||
const result = await actions.update({
|
||||
|
||||
@@ -40,6 +40,13 @@ export const actions: Actions = {
|
||||
return fail(400, { error: 'Ungültige Formulardaten' });
|
||||
}
|
||||
|
||||
const filteredIngredients = (
|
||||
parsedIngredients as { name: string; quantity: string; unit: string }[]
|
||||
).filter((ing) => ing.name?.trim() && Number(ing.quantity) > 0);
|
||||
|
||||
if (!filteredIngredients.length)
|
||||
return fail(422, { error: 'Mindestens eine gültige Zutat ist erforderlich' });
|
||||
|
||||
const api = apiClient(fetch);
|
||||
const { error: apiError } = await api.POST('/v1/recipes', {
|
||||
body: {
|
||||
@@ -48,14 +55,12 @@ export const actions: Actions = {
|
||||
cookTimeMin: cookTimeMin ? Number(cookTimeMin) || null : null,
|
||||
effort,
|
||||
heroImageUrl,
|
||||
ingredients: (parsedIngredients as { name: string; quantity: string; unit: string }[])
|
||||
.filter((ing) => ing.name?.trim() && Number(ing.quantity) > 0)
|
||||
.map((ing, i) => ({
|
||||
newIngredientName: ing.name.trim(),
|
||||
quantity: Number(ing.quantity),
|
||||
unit: ing.unit || '',
|
||||
sortOrder: i
|
||||
})),
|
||||
ingredients: filteredIngredients.map((ing, i) => ({
|
||||
newIngredientName: ing.name.trim(),
|
||||
quantity: Number(ing.quantity),
|
||||
unit: ing.unit || '',
|
||||
sortOrder: i
|
||||
})),
|
||||
steps: (parsedSteps as string[])
|
||||
.filter((s) => s?.trim())
|
||||
.map((s, i) => ({ stepNumber: i + 1, instruction: s.trim() })),
|
||||
|
||||
@@ -62,7 +62,7 @@ describe('new recipe page — create action', () => {
|
||||
name: 'Test Rezept',
|
||||
effort: 'easy',
|
||||
tagIds: ['t1'],
|
||||
ingredientsJson: '[]',
|
||||
ingredientsJson: JSON.stringify([{ name: 'Spaghetti', quantity: 200, unit: 'g' }]),
|
||||
stepsJson: '[]',
|
||||
...overrides
|
||||
};
|
||||
@@ -189,6 +189,23 @@ describe('new recipe page — create action', () => {
|
||||
expect(body.ingredients[0].newIngredientName).toBe('Spaghetti');
|
||||
});
|
||||
|
||||
it('returns fail(422) when all ingredients filter to empty after quantity check', async () => {
|
||||
const result = await actions.create({
|
||||
request: {
|
||||
formData: async () =>
|
||||
makeFormData({
|
||||
ingredientsJson: JSON.stringify([
|
||||
{ name: 'Salt', quantity: 0, unit: 'tsp' },
|
||||
{ name: '', quantity: 100, unit: 'g' }
|
||||
])
|
||||
})
|
||||
},
|
||||
fetch: vi.fn()
|
||||
} as any);
|
||||
expect(result.status).toBe(422);
|
||||
expect(result.data.error).toMatch(/zutat/i);
|
||||
});
|
||||
|
||||
it('returns fail(500) when API returns error', async () => {
|
||||
mockPost.mockResolvedValue({ error: { status: 500 } });
|
||||
const result = await actions.create({
|
||||
|
||||
Reference in New Issue
Block a user