Skip to content

Commit

Permalink
feat: add trip summary a11y label (#52)
Browse files Browse the repository at this point in the history
* feat: add trip summary a11y label

Reused the code for the trip summary from the app. Need to clean up usage of helper functions, usage of types etc.

* refacor: use existing function for translated transport mode

* refactor: update import path

* test: add tests for `tripSummary`

* test: fix tests failing due to time zone
  • Loading branch information
adriansberg authored Nov 3, 2023
1 parent e32956b commit 9420c68
Show file tree
Hide file tree
Showing 9 changed files with 768 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/components/typography/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const screenReaderPause = '.\n';
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,5 @@ export type Notice = z.infer<typeof noticeSchema>;
export type Situation = z.infer<typeof situationSchema>;
export type TripData = z.infer<typeof tripSchema>;
export type TripPattern = z.infer<typeof tripPatternSchema>;
export type Leg = z.infer<typeof legSchema>;
export type Quay = z.infer<typeof quaySchema>;
84 changes: 84 additions & 0 deletions src/page-modules/assistant/trip/__tests__/trip.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { TripPattern } from '../../server/journey-planner/validators';

export const tripFixture: TripPattern = {
expectedStartTime: '2023-01-01T00:00:00+01:00',
expectedEndTime: '2023-01-01T01:00:00+01:00',
duration: 3600,
walkDistance: 0,
legs: [
{
mode: 'bus',
distance: 1,
duration: 1,
aimedStartTime: '2023-01-01T00:00:00+01:00',
aimedEndTime: '2023-01-01T01:00:00+01:00',
expectedEndTime: '2023-01-01T01:00:00+01:00',
expectedStartTime: '2023-01-01T00:00:00+01:00',
realtime: false,
transportSubmode: 'regionalBus',
line: {
id: 'ATB:Line:1',
name: 'Line',
transportSubmode: 'regionalBus',
publicCode: '1',
flexibleLineType: null,
notices: [],
},
fromEstimatedCall: {
aimedDepartureTime: '2023-01-01T00:00:00+01:00',
expectedDepartureTime: '2023-01-01T01:00:00+01:00',
destinationDisplay: {
frontText: 'Destination',
},
quay: {
publicCode: '',
name: 'Quay',
},
notices: [],
},
situations: [],
fromPlace: {
name: 'From',
longitude: 0,
latitude: 0,
quay: {
publicCode: '1',
name: 'From',
id: 'NSR:Quay:1',
situations: [],
},
},
toPlace: {
name: '2',
longitude: 0,
latitude: 0,
quay: {
publicCode: '2',
name: '2',
id: 'NSR:Quay:2',
situations: [],
},
},
serviceJourney: {
id: 'ATB:ServiceJourney:1',
notices: [],
journeyPattern: {
notices: [],
},
},
rentedBike: null,
interchangeTo: null,
pointsOnLink: {
points:
'yscbKgbqfAaJzl@oEx^??oAdKi@zDc@hBaAjC_@t@aA`BeUn]kApBg@pAo@jCa@bCWlCEt@e@tJ??sBpe@WlEQ|Bs@bGq@vD??_DpMsAhF{@dCy@dBgDtG??gA`Cw@rBc@jB[vBQvBEpB@~Bp@nM??LjEL`P\\da@J|EThGlAfV??TdFH`D?fC_Av_@CzB??AnCJhe@??@|RG`ZBtFP`Ij@bM??DfC?zBGxCUdD_@dDm@jDa@|Ai@`B}ArDw@`Ce@xBKt@oAhLiAjIUhECtCF~BTjD^pCPz@bBxGJb@FET\\??U]GDj@xDThCFlBD~C?xFCzAKjBOnAMx@cAtEiBxK]zAcB`G]bBs@fFY`C??y@bIc@nFMnCa@xLQdDIdAS~AQfASx@sAtEkAtEgArDwF~Qg@vA{@zAeE|FgFjGwA~Bk@lAa@`Aa@lAa@|Am@bDe@hD_@jF_AtNM|AOtA_@vBWjAaBbG??gFfRk@dC_@~BKrAGhBAnCD`BPbCx@dFTjBThCH`BD|BBxMRlOA~BIbC_@fFq@vGo@fEgChOk@zCu@xCeDfL{Ir_@k@pCm@vB}A`Hq@|DUjBMjBKdDA`CJvLHlGFpBHjBv@hLF~ABxB?vAGrCY~FW|CeB|PWvCOzDQ~MS|FYlE}@dJQ~CGnEBhLCtEe@z\\KxEKtCYnEa@`EYbC_@tBoAfF{@pEKI??nl@t~KCA]xEWtFG|BI|G@rKD`GH~BPjBnAzHRrBd@`HVxBPfAXjA|AtFd@hBPhARbBLzBFvC?`AEtAMrAS`A[|@_IzQc@pAw@fDoGdZeAzF[jCQxBO~BIlCoAns@??CzCBvCNbFJpBpArPHdBFhB@tBElAq@tLObEaAhl@D@JTFV@\\A^??Mp@SGKxFCnDFvDFnAj@pIF~C?x@ElBKpBMlA{CfWWpCEz@MCONCPE|@???lA\\F?|@HvDZzFHdA^~Bt@nDtAtH~@~FtA~JLrALrBHhD@vBh@rmBEjGu@|i@C|B@`CN`FLrCZvGV|D`@fFtF~n@P`CHnBJfDDfC@vCEfCEvAOdCQjBw@~F??sDxWs@zGMfBI~BEfFHzG??h@zPH~ALlAT`BVfA^rA`BdEp@pBxCdLf@~Az@vBpDjHd@v@j@t@nDhD|AjBvAxBvDtG~FvI|@z@x@d@|HbDvA~@v@t@t@`AbCbEdAvB`A`CbAnCfBzFjAjEh@~B`AfFbA|G]d@Lt@??PfAHHRMl@jG^pFjEbq@TlDL`D??ZpJD`EAlE}@nlCEjBGzAQnBUpB[lB[xA[`AGQwgBx{DJHePvaB{@nJyAlRW[EU??DTVZcEhi@??Q~B]xGOdEc@rQc@rTO~OAtCDhIx@`a@??P~YF~DJjE~@zY`@pKd@dKx@lMbDj`@J`BRGFFB\\@tBA?aOrgCLHM`DGlCCbFDjDHbCJrB`@`F\\zCbAnGr@hFp@xG`@hFLhCJbDFrD@~B?~BEdCeBb_@QdE[fR??C`BA|FFjL?dBGfESbE_@zDsBjN[nDQvDGbE]BIzA??BbAd@S@n@^vJn@jLl@rH~@jI|ArKzA`IR|@v@|CrB|F^vA^bCNzBFnBDpD`@x]I?jStnC??yTngEEFEWMYqBzNyApLsB`RcBvPsAtOqIpkAk@rJUpFMzEIdEErICh@?v@RfJ@nBM^Aj@@PLb@DFFrBA|D@p@Dv@~@pMHjADJn@hH`@pFJzCFjEOdLIjN@rBCrIBdLIlCCFq@LaDG??kKW@xH??Jhm@p@AAxCDFj@?',
length: 530,
},
intermediateEstimatedCalls: [],
authority: {
id: 'ATB:Authority:2',
},
serviceJourneyEstimatedCalls: null,
datedServiceJourney: null,
},
],
};
212 changes: 212 additions & 0 deletions src/page-modules/assistant/trip/__tests__/trip.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import { cleanup, render, screen } from '@testing-library/react';
import mockRouter from 'next-router-mock';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { createDynamicRouteParser } from 'next-router-mock/dynamic-routes';
import { tripFixture } from './trip.fixture';
import {
AppCookiesProvider,
AppCookiesProviderProps,
} from '@atb/modules/cookies/cookies-context';
import React from 'react';
import {
AppLanguageProvider,
Language,
useTranslation,
} from '@atb/translations';
import { tripSummary } from '../utils';
import { formatToClock } from '@atb/utils/date';

afterEach(function () {
cleanup();
});

vi.mock('next/router', () => require('next-router-mock'));

mockRouter.useParser(createDynamicRouteParser(['/assistant']));

type RenderProps = { providerProps?: AppCookiesProviderProps };
const customRender = (
ui: React.ReactNode,
{
providerProps = {
initialCookies: {
darkmode: false,
language: 'no',
},
},
...renderOptions
}: RenderProps,
) => {
return render(
<AppCookiesProvider {...providerProps}>
<AppLanguageProvider serverAcceptLanguage="">{ui}</AppLanguageProvider>
</AppCookiesProvider>,
renderOptions,
);
};

describe('trip', function () {
describe('trip summary', function () {
it('should create correct summary', () => {
const Test = function () {
const { t, language } = useTranslation();

const summary = tripSummary(tripFixture, t, language, false, 1);
return (
<div data-testid="test-id" aria-label={summary}>
{summary}
</div>
);
};

customRender(<Test />, {});

const ariaLabel = screen
.getByTestId('test-id')
.getAttribute('aria-label');

const startTime = formatToClock(
tripFixture.expectedStartTime,
Language.Norwegian,
);
const endTime = formatToClock(
tripFixture.expectedEndTime,
Language.Norwegian,
);

const expected =
`Reiseresultat 1.\nBuss fra From 1.\nBussnummer 1.\nKlokken ${startTime}.\nIngen bytter.\nTotalt 0 meter å gå.` +
`\nStart klokken ${startTime}, ankomst klokken ${endTime}. Total reisetid 1 time.`;

expect(ariaLabel).toBe(expected);
});

it('should create summary in English', () => {
const Test = function () {
const { t, language } = useTranslation();

const summary = tripSummary(tripFixture, t, language, false, 1);
return (
<div data-testid="test-id" aria-label={summary}>
{summary}
</div>
);
};

customRender(<Test />, {
providerProps: {
initialCookies: {
darkmode: false,
language: 'en-US',
},
},
});

const ariaLabel = screen
.getByTestId('test-id')
.getAttribute('aria-label');

const startTime = formatToClock(
tripFixture.expectedStartTime,
Language.Norwegian,
);
const endTime = formatToClock(
tripFixture.expectedEndTime,
Language.Norwegian,
);

const expected =
`Result number 1.\nBus from From 1.\nBus number 1.\nAt ${startTime}.\nNo transfers.\nTotal of 0 meters to walk.` +
`\nStart time ${startTime}, arrival time ${endTime}. Total travel time 1 hour`;

expect(ariaLabel).toBe(expected);
});

it('should create summary in nynorsk', () => {
const Test = function () {
const { t, language } = useTranslation();

const summary = tripSummary(tripFixture, t, language, false, 1);
return (
<div data-testid="test-id" aria-label={summary}>
{summary}
</div>
);
};

customRender(<Test />, {
providerProps: {
initialCookies: {
darkmode: false,
language: 'nn',
},
},
});

const ariaLabel = screen
.getByTestId('test-id')
.getAttribute('aria-label');

const startTime = formatToClock(
tripFixture.expectedStartTime,
Language.Norwegian,
);
const endTime = formatToClock(
tripFixture.expectedEndTime,
Language.Norwegian,
);

const expected =
`Reiseresultat 1.\nBuss frå From 1.\nBussnummer 1.\nKlokka ${startTime}.\nIngen bytter.\nTotalt 0 meter å gå.` +
`\nStart klokka ${startTime}, framkomst klokka ${endTime}. Total reisetid 1 time.`;

expect(ariaLabel).toBe(expected);
});

it('should create summary with trip in the past', () => {
const Test = function () {
const { t, language } = useTranslation();

const summary = tripSummary(tripFixture, t, language, true, 1);
return (
<div data-testid="test-id" aria-label={summary}>
{summary}
</div>
);
};

customRender(<Test />, {});

const ariaLabel = screen
.getByTestId('test-id')
.getAttribute('aria-label');

const expected = 'Passert reise';

expect(ariaLabel).toContain(expected);
});

it('should create summary with correct result number', () => {
const Test = function () {
const { t, language } = useTranslation();

const summary = tripSummary(tripFixture, t, language, true, 5);
return (
<div data-testid="test-id" aria-label={summary}>
{summary}
</div>
);
};

customRender(<Test />, {});

const ariaLabel = screen
.getByTestId('test-id')
.getAttribute('aria-label');

const expected = 'Reiseresultat 5';

expect(ariaLabel).toContain(expected);
});
});
});
15 changes: 14 additions & 1 deletion src/page-modules/assistant/trip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
formatLocaleTime,
formatToSimpleDate,
formatToWeekday,
isInPast,
parseIfNeeded,
} from '@atb/utils/date';
import {
Expand Down Expand Up @@ -31,6 +32,7 @@ import { NonTransitTrip } from '../non-transit-pill';
import { isSameDay } from 'date-fns';
import { capitalize } from 'lodash';
import { TripPatternHeader } from '@atb/page-modules/assistant/trip/trip-pattern-header';
import { tripSummary } from '@atb/page-modules/assistant/trip/utils';
import EmptySearchResults from '@atb/components/empty-search-results';

export type TripProps = {
Expand Down Expand Up @@ -76,6 +78,7 @@ export default function Trip({
/>
);
}

return (
<>
<div className={style.tripResults}>
Expand All @@ -90,6 +93,7 @@ export default function Trip({
))}
</div>
)}

{tripPatterns.map((tripPattern, i) => (
<div key={`tripPattern-${tripPattern.expectedStartTime}-${i}`}>
<DayLabel
Expand All @@ -99,6 +103,7 @@ export default function Trip({
<TripPattern
tripPattern={tripPattern}
delay={tripPattern.transitionDelay}
index={i}
/>
</div>
))}
Expand Down Expand Up @@ -172,9 +177,10 @@ function useTripPatterns(initialTrip: TripData, departureMode: DepartureMode) {
type TripPatternProps = {
tripPattern: TripPattern;
delay: number;
index: number;
};

function TripPattern({ tripPattern, delay }: TripPatternProps) {
function TripPattern({ tripPattern, delay, index }: TripPatternProps) {
const { t, language } = useTranslation();

return (
Expand All @@ -187,6 +193,13 @@ function TripPattern({ tripPattern, delay }: TripPatternProps) {
transition={{
delay, // staggerChildren on parent only works first render
}}
aria-label={tripSummary(
tripPattern,
t,
language,
isInPast(tripPattern.legs[0].expectedStartTime),
index + 1,
)}
>
<TripPatternHeader tripPattern={tripPattern} />

Expand Down
Loading

0 comments on commit 9420c68

Please sign in to comment.