Derive client domain types from the shared schema contracts

Add entity/response Zod schemas to @trek/shared (place, trip, assignment, day, budget, packing, reservation), each matched against the producing server service, and re-export them from client types.ts instead of the hand-written duplicates that had drifted (name/title, amount/total_price, owner_id/user_id, cover_url/cover_image, ...). Updates the call sites and test fixtures the corrected types surfaced; type-only, no runtime behaviour change.
This commit is contained in:
Maurice
2026-05-31 15:42:39 +02:00
parent 239a68bb48
commit 3977a5ecba
52 changed files with 732 additions and 435 deletions
+7 -7
View File
@@ -43,7 +43,7 @@ describe('budgetSlice', () => {
const existing = buildBudgetItem({ trip_id: 1 });
seedStore(useTripStore, { budgetItems: [existing] });
const result = await useTripStore.getState().addBudgetItem(1, { name: 'Hotel', amount: 200 });
const result = await useTripStore.getState().addBudgetItem(1, { name: 'Hotel', total_price: 200 });
expect(result.name).toBe('Hotel');
expect(useTripStore.getState().budgetItems).toHaveLength(2);
@@ -64,7 +64,7 @@ describe('budgetSlice', () => {
describe('updateBudgetItem', () => {
it('FE-BUDGET-004: updateBudgetItem replaces item in array', async () => {
const item = buildBudgetItem({ id: 10, trip_id: 1, name: 'Old', amount: 100 });
const item = buildBudgetItem({ id: 10, trip_id: 1, name: 'Old', total_price: 100 });
seedStore(useTripStore, { budgetItems: [item] });
server.use(
@@ -74,16 +74,16 @@ describe('budgetSlice', () => {
}),
);
const result = await useTripStore.getState().updateBudgetItem(1, 10, { name: 'Updated', amount: 150 });
const result = await useTripStore.getState().updateBudgetItem(1, 10, { name: 'Updated', total_price: 150 });
expect(result.name).toBe('Updated');
expect(useTripStore.getState().budgetItems[0].name).toBe('Updated');
});
it('FE-BUDGET-005: updateBudgetItem with total_price triggers loadReservations when reservation_id present', async () => {
const item = buildBudgetItem({ id: 10, trip_id: 1, amount: 100 });
const item = buildBudgetItem({ id: 10, trip_id: 1, total_price: 100 });
const initialReservation = buildReservation({ trip_id: 1 });
const newReservation = buildReservation({ trip_id: 1, name: 'Refreshed Reservation' });
const newReservation = buildReservation({ trip_id: 1, title: 'Refreshed Reservation' });
seedStore(useTripStore, {
budgetItems: [item],
reservations: [initialReservation],
@@ -106,7 +106,7 @@ describe('budgetSlice', () => {
await new Promise(resolve => setTimeout(resolve, 50));
expect(useTripStore.getState().reservations).toHaveLength(1);
expect(useTripStore.getState().reservations[0].name).toBe('Refreshed Reservation');
expect(useTripStore.getState().reservations[0].title).toBe('Refreshed Reservation');
});
});
@@ -162,7 +162,7 @@ describe('budgetSlice', () => {
describe('toggleBudgetMemberPaid', () => {
it('FE-BUDGET-008: toggleBudgetMemberPaid updates paid status after API success', async () => {
const member = { user_id: 5, paid: false };
const member = { user_id: 5, paid: 0, username: 'dave' };
const item = buildBudgetItem({ id: 10, trip_id: 1, members: [member] });
seedStore(useTripStore, { budgetItems: [item] });
@@ -42,20 +42,20 @@ describe('reservationsSlice', () => {
describe('addReservation', () => {
it('FE-RESERV-002: addReservation prepends to reservations array', async () => {
const existing = buildReservation({ trip_id: 1, name: 'Existing' });
const existing = buildReservation({ trip_id: 1, title: 'Existing' });
seedStore(useTripStore, { reservations: [existing] });
const result = await useTripStore.getState().addReservation(1, {
name: 'New Hotel',
title: 'New Hotel',
type: 'hotel',
status: 'pending',
});
expect(result.name).toBe('New Hotel');
expect(result.title).toBe('New Hotel');
const reservations = useTripStore.getState().reservations;
expect(reservations).toHaveLength(2);
// addReservation prepends
expect(reservations[0].name).toBe('New Hotel');
expect(reservations[0].title).toBe('New Hotel');
});
it('FE-RESERV-003: addReservation on failure throws', async () => {
@@ -66,14 +66,14 @@ describe('reservationsSlice', () => {
);
await expect(
useTripStore.getState().addReservation(1, { name: 'Fail' })
useTripStore.getState().addReservation(1, { title: 'Fail' })
).rejects.toThrow();
});
});
describe('updateReservation', () => {
it('FE-RESERV-004: updateReservation replaces item in array by id', async () => {
const reservation = buildReservation({ id: 10, trip_id: 1, name: 'Old', status: 'pending' });
const reservation = buildReservation({ id: 10, trip_id: 1, title: 'Old', status: 'pending' });
seedStore(useTripStore, { reservations: [reservation] });
server.use(
@@ -83,10 +83,10 @@ describe('reservationsSlice', () => {
}),
);
const result = await useTripStore.getState().updateReservation(1, 10, { name: 'Updated Hotel' });
const result = await useTripStore.getState().updateReservation(1, 10, { title: 'Updated Hotel' });
expect(result.name).toBe('Updated Hotel');
expect(useTripStore.getState().reservations[0].name).toBe('Updated Hotel');
expect(result.title).toBe('Updated Hotel');
expect(useTripStore.getState().reservations[0].title).toBe('Updated Hotel');
});
});