Skip to content

Commit f3b36e6

Browse files
Merge pull request #92003 from Expensify/blimpich-fixRailUrnStationCode
Drop URN-prefixed station codes from rail trip display
2 parents dc477f5 + ad3416a commit f3b36e6

4 files changed

Lines changed: 78 additions & 21 deletions

File tree

src/components/ReportActionItem/TripDetailsView.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import StringUtils from '@libs/StringUtils';
2222
import variables from '@styles/variables';
2323
import CONST from '@src/CONST';
2424
import type {ReservationData} from '@src/libs/TripReservationUtils';
25-
import {formatAirportInfo, formatCancelledDescription, getPNRReservationDataFromTripReport, getTripReservationCode, getTripReservationIcon} from '@src/libs/TripReservationUtils';
25+
import {formatCancelledDescription, formatTransitLocationLabel, getPNRReservationDataFromTripReport, getTripReservationCode, getTripReservationIcon} from '@src/libs/TripReservationUtils';
2626
import ROUTES from '@src/ROUTES';
2727
import type {Report} from '@src/types/onyx';
2828
import type {Reservation} from '@src/types/onyx/Transaction';
@@ -102,18 +102,18 @@ function ReservationView({reservation, transactionID, tripRoomReportID, sequence
102102
<View style={[styles.flexRow, styles.alignItemsCenter, styles.gap2]}>
103103
{shouldShowArrowIcon ? (
104104
<>
105-
<Text style={[titleTextStyle, styles.lh20, shouldUseNarrowLayout && styles.flex1]}>{formatAirportInfo(reservation.start)}</Text>
105+
<Text style={[titleTextStyle, styles.lh20, shouldUseNarrowLayout && styles.flex1]}>{formatTransitLocationLabel(reservation.start)}</Text>
106106
<Icon
107107
src={expensifyIcons.ArrowRightLong}
108108
width={variables.iconSizeSmall}
109109
height={variables.iconSizeSmall}
110110
fill={theme.icon}
111111
/>
112-
<Text style={[titleTextStyle, styles.lh20, shouldUseNarrowLayout && styles.flex1]}>{formatAirportInfo(reservation.end)}</Text>
112+
<Text style={[titleTextStyle, styles.lh20, shouldUseNarrowLayout && styles.flex1]}>{formatTransitLocationLabel(reservation.end)}</Text>
113113
</>
114114
) : (
115115
<Text style={[titleTextStyle, styles.lh20, shouldUseNarrowLayout && styles.flex1]}>
116-
{formatAirportInfo(reservation.start)} {translate('common.to').toLowerCase()} {formatAirportInfo(reservation.end)}
116+
{formatTransitLocationLabel(reservation.start)} {translate('common.to').toLowerCase()} {formatTransitLocationLabel(reservation.end)}
117117
</Text>
118118
)}
119119
</View>
@@ -217,7 +217,7 @@ function TripDetailsView({tripRoomReport, shouldShowHorizontalRule, tripTransact
217217
if (!destinationReservation) {
218218
return '';
219219
}
220-
return `${translate('travel.flightTo')} ${formatAirportInfo(destinationReservation.reservation.end, true)}`;
220+
return `${translate('travel.flightTo')} ${formatTransitLocationLabel(destinationReservation.reservation.end, true)}`;
221221
}
222222
case CONST.RESERVATION_TYPE.TRAIN:
223223
if (reservations.length === 2 && firstReservation.start.shortName === lastReservation.end.shortName) {

src/libs/TripReservationUtils.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,9 @@ function getCarReservations(pnr: Pnr, travelers: PnrTraveler[]): ReservationItem
336336
return reservationList;
337337
}
338338

339+
// Drop Trainline URN-style codes so the trip UI doesn't render them as station labels.
340+
const getRailStationShortName = (code: string | undefined) => (code && !code.startsWith('urn:') ? code : '');
341+
339342
function getRailReservations(pnr: Pnr, travelers: PnrTraveler[]): ReservationItem[] {
340343
const reservationList: ReservationItem[] = [];
341344

@@ -362,13 +365,13 @@ function getRailReservations(pnr: Pnr, travelers: PnrTraveler[]): ReservationIte
362365
start: {
363366
date: leg.departAt.iso8601,
364367
longName: leg.originInfo.name,
365-
shortName: leg.originInfo.code,
368+
shortName: getRailStationShortName(leg.originInfo.code),
366369
cityName: leg.originInfo.cityName,
367370
},
368371
end: {
369372
date: leg.arriveAt.iso8601,
370373
longName: leg.destinationInfo.name,
371-
shortName: leg.destinationInfo.code,
374+
shortName: getRailStationShortName(leg.destinationInfo.code),
372375
cityName: leg.destinationInfo.cityName,
373376
},
374377
route: {
@@ -471,13 +474,13 @@ function getReservationsFromTripReport(tripReport?: Report, transactions?: Trans
471474
return [];
472475
}
473476

474-
function formatAirportInfo(reservationTimeDetails: ReservationTimeDetails, hideAirportCode = false): string {
475-
const longName = reservationTimeDetails?.longName ? `${reservationTimeDetails?.longName} ` : '';
476-
let shortName = reservationTimeDetails?.shortName ? `${reservationTimeDetails?.shortName}` : '';
477-
478-
shortName = longName && shortName ? `(${shortName})` : shortName;
479-
480-
return !hideAirportCode ? `${longName}${shortName}` : longName;
477+
function formatTransitLocationLabel(reservationTimeDetails: ReservationTimeDetails, hideShortCode = false): string {
478+
const longName = reservationTimeDetails?.longName ?? '';
479+
const shortName = reservationTimeDetails?.shortName ?? '';
480+
if (hideShortCode || !shortName) {
481+
return longName;
482+
}
483+
return longName ? `${longName} (${shortName})` : `(${shortName})`;
481484
}
482485

483486
function getPNRReservationDataFromTripReport(tripReport?: Report, transactions?: Transaction[]): ReservationPNRData[] {
@@ -560,7 +563,7 @@ export {
560563
getReservationsFromTripReport,
561564
getTripTotal,
562565
getReservationDetailsFromSequence,
563-
formatAirportInfo,
566+
formatTransitLocationLabel,
564567
getPNRReservationDataFromTripReport,
565568
getAirReservations,
566569
isPnrCancelled,

src/pages/Travel/TrainTripDetails.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import UserPills from '@components/UserPills';
77
import useLocalize from '@hooks/useLocalize';
88
import useThemeStyles from '@hooks/useThemeStyles';
99
import DateUtils from '@libs/DateUtils';
10+
import {formatTransitLocationLabel} from '@libs/TripReservationUtils';
1011
import CONST from '@src/CONST';
1112
import type {PersonalDetails} from '@src/types/onyx';
1213
import type {Reservation} from '@src/types/onyx/Transaction';
@@ -22,9 +23,7 @@ function TrainTripDetails({reservation, personalDetails}: TrainTripDetailsProps)
2223

2324
const startDate = DateUtils.getFormattedTransportDateAndHour(new Date(reservation.start.date));
2425
const endDate = DateUtils.getFormattedTransportDateAndHour(new Date(reservation.end.date));
25-
const trainRouteDescription = `${reservation.start.longName} (${reservation.start.shortName}) ${translate('common.conjunctionTo')} ${reservation.end.longName} (${
26-
reservation.end.shortName
27-
})`;
26+
const trainRouteDescription = `${formatTransitLocationLabel(reservation.start)} ${translate('common.conjunctionTo')} ${formatTransitLocationLabel(reservation.end)}`;
2827
const trainDuration = DateUtils.getFormattedDurationBetweenDates(translate, new Date(reservation.start.date), new Date(reservation.end.date));
2928

3029
const displayName = personalDetails?.displayName ?? reservation.travelerPersonalInfo?.name;
@@ -50,15 +49,15 @@ function TrainTripDetails({reservation, personalDetails}: TrainTripDetailsProps)
5049
description={translate('travel.trainDetails.departs')}
5150
descriptionTextStyle={[styles.textLabelSupporting, styles.mb1]}
5251
titleComponent={<Text style={[styles.textLarge, styles.textHeadlineH2]}>{startDate.hour}</Text>}
53-
helperText={`${reservation.start.longName} (${reservation.start.shortName})`}
52+
helperText={formatTransitLocationLabel(reservation.start)}
5453
helperTextStyle={[styles.pb3, styles.mtn2]}
5554
interactive={false}
5655
/>
5756
<MenuItemWithTopDescription
5857
description={translate('travel.trainDetails.arrives')}
5958
descriptionTextStyle={[styles.textLabelSupporting, styles.mb1]}
6059
titleComponent={<Text style={[styles.textLarge, styles.textHeadlineH2]}>{endDate.hour}</Text>}
61-
helperText={`${reservation.end.longName} (${reservation.end.shortName})`}
60+
helperText={formatTransitLocationLabel(reservation.end)}
6261
helperTextStyle={[styles.pb3, styles.mtn2]}
6362
interactive={false}
6463
/>

tests/unit/TripReservationUtilsTest.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* cspell:disable */
2-
import {getAirReservations, getPNRReservationDataFromTripReport, getReservationsFromTripReport, isPnrCancelled} from '@libs/TripReservationUtils';
2+
import {formatTransitLocationLabel, getAirReservations, getPNRReservationDataFromTripReport, getReservationsFromTripReport, isPnrCancelled} from '@libs/TripReservationUtils';
33
import CONST from '@src/CONST';
44
import type {Pnr, TripData} from '@src/types/onyx/TripData';
55
import {airReservationPnrData, airReservationTravelers} from '../data/TripAirReservationData';
@@ -2692,6 +2692,61 @@ describe('TripReservationUtils', () => {
26922692
});
26932693
});
26942694

2695+
describe('rail shortName sanitization', () => {
2696+
it('should drop a URN-formatted code from rail shortName', () => {
2697+
const railPnrWithUrnCodes = JSON.parse(JSON.stringify(railPnr)) as typeof railPnr;
2698+
const leg = railPnrWithUrnCodes.data.railPnr?.legInfos.at(0);
2699+
if (leg) {
2700+
leg.originInfo.code = 'urn:trainline:public:nloc:at000408';
2701+
leg.destinationInfo.code = 'urn:trainline:public:nloc:at001685';
2702+
}
2703+
2704+
const report = createRandomReport(1, undefined);
2705+
report.tripData = {
2706+
tripID: 'trip123',
2707+
payload: {
2708+
...basicTripData,
2709+
pnrs: [railPnrWithUrnCodes],
2710+
},
2711+
};
2712+
2713+
const result = getReservationsFromTripReport(report, []);
2714+
expect(result).toHaveLength(1);
2715+
2716+
const trainReservation = result.at(0)?.reservation;
2717+
expect(trainReservation?.type).toEqual(CONST.RESERVATION_TYPE.TRAIN);
2718+
expect(trainReservation?.start?.shortName).toEqual('');
2719+
expect(trainReservation?.end?.shortName).toEqual('');
2720+
});
2721+
2722+
it('should render only longName when shortName is empty (no trailing space, no empty parens)', () => {
2723+
expect(formatTransitLocationLabel({date: '', longName: 'Brockenhurst', shortName: '', cityName: ''})).toEqual('Brockenhurst');
2724+
});
2725+
2726+
it('should render longName with parenthesized shortName when both are present', () => {
2727+
expect(formatTransitLocationLabel({date: '', longName: 'Solana Beach station', shortName: 'SOL', cityName: ''})).toEqual('Solana Beach station (SOL)');
2728+
});
2729+
2730+
it('should preserve a clean station code in rail shortName', () => {
2731+
const report = createRandomReport(1, undefined);
2732+
report.tripData = {
2733+
tripID: 'trip123',
2734+
payload: {
2735+
...basicTripData,
2736+
pnrs: [railPnr],
2737+
},
2738+
};
2739+
2740+
const result = getReservationsFromTripReport(report, []);
2741+
expect(result).toHaveLength(1);
2742+
2743+
const trainReservation = result.at(0)?.reservation;
2744+
expect(trainReservation?.type).toEqual(CONST.RESERVATION_TYPE.TRAIN);
2745+
expect(trainReservation?.start?.shortName).toEqual('STX');
2746+
expect(trainReservation?.end?.shortName).toEqual('STY');
2747+
});
2748+
});
2749+
26952750
describe('getPNRReservationDataFromTripReport', () => {
26962751
it('should return an empty array when there are no transactions and trip payload', () => {
26972752
const report = createRandomReport(1, undefined);

0 commit comments

Comments
 (0)