import { transformApiResult, fetchCruiseCards } from '../helpers'; import { ApiCruiseResult } from '../types'; const mockApiResult: ApiCruiseResult = { post_id: 34246, cruise_id: 2074350, cruise_title: 'Mediterranean from Barcelona', cruise_line: { name: 'MSC Cruises', link: 'https://example.com/cruise-lines/msc-cruises/', logo: 'https://example.com/msc-logo.png' }, ship: { name: 'MSC Seaside', link: 'https://example.com/ships/msc-seaside/', image: 'https://example.com/ship-image.jpg' }, departs: '2025-10-21', ends: '2025-10-28', date_label: 'Oct 21 - Oct 28, 2025', destination: ['Mediterranean'], duration: 7, itinerary: ['Barcelona', 'Naples', 'Rome', 'Marseille'], prices: { cheapest_price: 2000, inside: 'Enquire now', outside: 'Enquire now', balcony: '2000', suite: 'Enquire now' }, was_prices: { inside: 0, outside: 0, balcony: 2500, suite: 0 }, link: 'https://example.com/cruises/mediterranean-from-barcelona', campaigns: ['Special Offer'], extras: { date: 'Oct 2025 - Oct 2025', duration: 'Seven Days', destination: 'Mediterranean', info: 'Seven nights onboard a four-star ship' } }; describe('transformApiResult', () => { it('transforms API result to TripCardApiData format', () => { const result = transformApiResult(mockApiResult); expect(result).toEqual({ cruise_id: '2074350', image: 'https://example.com/ship-image.jpg', offer_label: 'Special Offer', headline: 'Mediterranean from Barcelona', date: 'Oct 2025 - Oct 2025', duration: 'Seven Days', destination: 'Mediterranean', ship: 'MSC Seaside', original_price: '£2,500', price: '£2,000', logo: 'https://example.com/msc-logo.png', logo_url: 'https://example.com/cruise-lines/msc-cruises/', logo_name: 'MSC Cruises', cta_url: 'https://example.com/cruises/mediterranean-from-barcelona', cta_text: 'View Itinerary' }); }); it('formats price as "Enquire now" when price is 0', () => { const resultWithZeroPrice = { ...mockApiResult, prices: { ...mockApiResult.prices, cheapest_price: 0 } }; const result = transformApiResult(resultWithZeroPrice); expect(result.price).toBe('Enquire now'); }); it('formats price as "Enquire now" when price is "Enquire now"', () => { const resultWithEnquire = { ...mockApiResult, prices: { ...mockApiResult.prices, cheapest_price: 'Enquire now' } }; const result = transformApiResult(resultWithEnquire); expect(result.price).toBe('Enquire now'); }); it('formats numeric price with £ symbol', () => { const result = transformApiResult(mockApiResult); expect(result.price).toBe('£2,000'); }); it('returns undefined original_price when was_price is 0', () => { const resultWithZeroWasPrice = { ...mockApiResult, was_prices: { ...mockApiResult.was_prices, balcony: 0 } }; const result = transformApiResult(resultWithZeroWasPrice); expect(result.original_price).toBeUndefined(); }); it('returns undefined original_price when was_price is "Enquire now"', () => { const resultWithEnquireWasPrice = { ...mockApiResult, was_prices: { ...mockApiResult.was_prices, balcony: 0 }, prices: { ...mockApiResult.prices, cheapest_price: 'Enquire now', balcony: 'Enquire now' } }; const result = transformApiResult(resultWithEnquireWasPrice); expect(result.original_price).toBeUndefined(); }); it('returns undefined offer_label when no campaigns', () => { const resultWithNoCampaigns = { ...mockApiResult, campaigns: [] }; const result = transformApiResult(resultWithNoCampaigns); expect(result.offer_label).toBeUndefined(); }); it('uses data from extras object', () => { const result = transformApiResult(mockApiResult); expect(result.date).toBe('Oct 2025 - Oct 2025'); expect(result.duration).toBe('Seven Days'); }); it('handles string prices correctly', () => { const resultWithStringPrice = { ...mockApiResult, prices: { ...mockApiResult.prices, cheapest_price: '£1500pp' } }; const result = transformApiResult(resultWithStringPrice); expect(result.price).toBe('£1500pp'); }); }); describe('fetchCruiseCards', () => { beforeEach(() => { global.fetch = jest.fn(); }); afterEach(() => { jest.resetAllMocks(); }); it('filters out offers with past departure dates', async () => { const futureDate = new Date(); futureDate.setFullYear(futureDate.getFullYear() + 1); const futureDateString = futureDate.toISOString().split('T')[0]; const validOffer: ApiCruiseResult = { ...mockApiResult, cruise_id: 1, departs: futureDateString // Future date }; const pastOffer: ApiCruiseResult = { ...mockApiResult, cruise_id: 2, departs: '2024-01-01' // Past date }; const mockResponse = { results: [validOffer, pastOffer] }; (global.fetch as jest.Mock).mockResolvedValueOnce({ ok: true, json: async () => mockResponse }); const result = await fetchCruiseCards([1, 2]); // Should only include the valid offer with future date expect(result).toHaveLength(1); expect(result[0]).toBeDefined(); expect(result[0].cruise_id).toBe('1'); }); it('filters out offers with invalid date format', async () => { const futureDate = new Date(); futureDate.setFullYear(futureDate.getFullYear() + 1); const futureDateString = futureDate.toISOString().split('T')[0]; const validOffer: ApiCruiseResult = { ...mockApiResult, cruise_id: 1, departs: futureDateString }; const invalidDateOffer: ApiCruiseResult = { ...mockApiResult, cruise_id: 2, departs: 'invalid-date' }; const mockResponse = { results: [validOffer, invalidDateOffer] }; (global.fetch as jest.Mock).mockResolvedValueOnce({ ok: true, json: async () => mockResponse }); const result = await fetchCruiseCards([1, 2]); expect(result).toHaveLength(1); expect(result[0]).toBeDefined(); expect(result[0].cruise_id).toBe('1'); }); it('includes offers departing today', async () => { const today = new Date(); const todayString = today.toISOString().split('T')[0]; const todayOffer: ApiCruiseResult = { ...mockApiResult, cruise_id: 1, departs: todayString }; const mockResponse = { results: [todayOffer] }; (global.fetch as jest.Mock).mockResolvedValueOnce({ ok: true, json: async () => mockResponse }); const result = await fetchCruiseCards([1]); expect(result).toHaveLength(1); expect(result[0]).toBeDefined(); expect(result[0].cruise_id).toBe('1'); }); });