Skip to content

Commit c9a7c52

Browse files
committed
E2E: Should mark stops as TrunkLine on Line details page
1 parent 5b99fe0 commit c9a7c52

7 files changed

Lines changed: 267 additions & 3 deletions

File tree

cypress/datasets/base.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export const stopCoordinatesByLabel = {
5151
};
5252

5353
export const buildStopsOnInfraLinks = (
54-
testInfraLinkIds: UUID[],
54+
testInfraLinkIds: ReadonlyArray<UUID>,
5555
): StopInsertInput[] => [
5656
// Stops along test route 901 Outbound
5757
{
@@ -413,7 +413,7 @@ const stopsInJourneyPattern: StopInJourneyPatternInsertInput[] = [
413413
];
414414

415415
export const buildInfraLinksAlongRoute = (
416-
infrastructureLinkIds: UUID[],
416+
infrastructureLinkIds: ReadonlyArray<UUID>,
417417
): InfraLinkAlongRouteInsertInput[] => [
418418
{
419419
route_id: routes[0].route_id,

cypress/e2e/editRoute.cy.ts

Lines changed: 173 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,37 @@
11
import {
22
Priority,
33
RouteDirectionEnum,
4+
RouteInsertInput,
45
} from '@hsl/jore4-test-db-manager/dist/CypressSpecExports';
6+
import identity from 'lodash/identity';
7+
import range from 'lodash/range';
58
import { DateTime } from 'luxon';
69
import {
710
buildInfraLinksAlongRoute,
811
buildStopsOnInfraLinks,
912
getClonedBaseDbResources,
1013
testInfraLinkExternalIds,
1114
} from '../datasets/base';
15+
import { getClonedBaseStopRegistryData } from '../datasets/stopRegistry';
1216
import { Tag } from '../enums';
1317
import {
18+
BasicDetailsViewCard,
1419
ConfirmationDialog,
1520
EditRoutePage,
1621
LineChangeHistory,
1722
LineDetailsPage,
23+
LineForm,
24+
LineRouteList,
1825
RouteRow,
26+
StopsNeedingUpdateModal,
1927
TerminusNameInputs,
2028
Toast,
2129
ValidityPeriodForm,
2230
} from '../pageObjects';
2331
import { UUID } from '../types';
24-
import { SupportedResources, insertToDbHelper } from '../utils';
32+
import { SupportedResources, insertToDbHelper, mapAt } from '../utils';
2533
import { expectGraphQLCallToSucceed } from '../utils/assertions';
34+
import { InsertedStopRegistryIds } from './utils';
2635

2736
describe('Route editing', { tags: [Tag.Routes] }, () => {
2837
let dbResources: SupportedResources;
@@ -338,4 +347,167 @@ describe('Route editing', { tags: [Tag.Routes] }, () => {
338347
);
339348
});
340349
});
350+
351+
describe('Trunk Line bus Route', () => {
352+
const { lineRouteListItem } = LineRouteList;
353+
const { routeRow, routeStopListItem } = lineRouteListItem;
354+
355+
function buildTestData(infraLinkIds: ReadonlyArray<UUID>) {
356+
const stops = buildStopsOnInfraLinks(infraLinkIds);
357+
const infraLinksAlongRoute = buildInfraLinksAlongRoute(infraLinkIds);
358+
359+
return {
360+
...getClonedBaseDbResources(),
361+
stops,
362+
infraLinksAlongRoute,
363+
};
364+
}
365+
366+
function initTrunkLineTest(
367+
editResources: (
368+
resources: ReturnType<typeof buildTestData>,
369+
) => SupportedResources = identity,
370+
) {
371+
cy.task<UUID[]>(
372+
'getInfrastructureLinkIdsByExternalIds',
373+
testInfraLinkExternalIds,
374+
)
375+
.then(buildTestData)
376+
.then(editResources)
377+
.then((testResources) => {
378+
cy.task('resetDbs');
379+
insertToDbHelper(testResources);
380+
381+
cy.task<InsertedStopRegistryIds>(
382+
'insertStopRegistryData',
383+
getClonedBaseStopRegistryData(),
384+
);
385+
386+
cy.setupTests();
387+
cy.mockLogin();
388+
});
389+
}
390+
391+
function setRouteToDraft(route: RouteInsertInput): RouteInsertInput {
392+
return { ...route, priority: Priority.Draft };
393+
}
394+
395+
function setLineTypeToTrunkLineType() {
396+
LineDetailsPage.getEditLineButton().click();
397+
LineForm.selectLineType('Runkolinja');
398+
LineForm.save();
399+
}
400+
401+
function addExcludedStopToRoute() {
402+
LineRouteList.getShowUnusedStopsSwitch().click();
403+
404+
LineRouteList.getNthLineRouteListItem(1).within(() => {
405+
routeRow.getToggleAccordionButton().click();
406+
407+
lineRouteListItem
408+
.getNthRouteStopListItem(4)
409+
.should('contain', 'E2E010')
410+
.and('contain', 'Ei reitin käytössä');
411+
412+
// Add E2E010 to the route
413+
lineRouteListItem.getNthRouteStopListItem(4).within(() => {
414+
routeStopListItem.getStopActionsDropdown().click();
415+
cy.withinHeadlessPortal(() =>
416+
routeStopListItem.stopActionsDropdown
417+
.getAddStopToRouteButton()
418+
.click(),
419+
);
420+
});
421+
});
422+
}
423+
424+
function assertStopsAreTrunkLine(areTrunkLine: boolean) {
425+
const expectedContent = areTrunkLine ? 'Runkolinjapysäkki' : '-';
426+
427+
LineRouteList.getNthLineRouteListItem(1).within(() => {
428+
lineRouteListItem
429+
.getNthRouteStopListItem(0)
430+
.within(() => lineRouteListItem.getLabel().click());
431+
});
432+
433+
BasicDetailsViewCard.getStopType()
434+
.shouldBeVisible()
435+
.shouldHaveText(expectedContent);
436+
cy.go(-1);
437+
438+
LineRouteList.getNthLineRouteListItem(1).within(() => {
439+
routeRow.getToggleAccordionButton().click();
440+
441+
lineRouteListItem
442+
.getNthRouteStopListItem(4)
443+
.within(() => lineRouteListItem.getLabel().click());
444+
});
445+
446+
BasicDetailsViewCard.getStopType()
447+
.shouldBeVisible()
448+
.shouldHaveText(expectedContent);
449+
}
450+
451+
it('Should not update stop Trunk Line status for Draft route', () => {
452+
cy.section('Init test data', () => {
453+
initTrunkLineTest((resources) => ({
454+
...resources,
455+
routes: mapAt(resources.routes, {
456+
0: setRouteToDraft,
457+
1: setRouteToDraft,
458+
}),
459+
}));
460+
});
461+
462+
LineDetailsPage.visit(baseDbResources.lines[0].line_id);
463+
464+
cy.section('Change line type to Trunk Line', () => {
465+
setLineTypeToTrunkLineType();
466+
LineForm.checkLineSubmitSuccess();
467+
});
468+
469+
LineDetailsPage.getShowDraftsButton().click();
470+
471+
cy.section('Toggle a stop onto a Trunk Line', () => {
472+
addExcludedStopToRoute();
473+
Toast.expectSuccessToast('Reitti tallennettu');
474+
});
475+
476+
cy.section('Assert stop Trunk Line status', () =>
477+
assertStopsAreTrunkLine(false),
478+
);
479+
});
480+
481+
it('Should update stop Trunk Line status for non Draft route', () => {
482+
cy.section('Init test data', () => initTrunkLineTest());
483+
484+
LineDetailsPage.visit(baseDbResources.lines[0].line_id);
485+
486+
cy.section('Change line type to Trunk Line', () => {
487+
setLineTypeToTrunkLineType();
488+
489+
StopsNeedingUpdateModal.getModal().shouldBeVisible();
490+
range(1, 10).forEach((i) =>
491+
StopsNeedingUpdateModal.getStopLink(`E2E00${i}`).shouldBeVisible(),
492+
);
493+
StopsNeedingUpdateModal.getConfirmButton().click();
494+
495+
LineForm.checkLineSubmitSuccess();
496+
});
497+
498+
cy.section('Toggle a stop onto a Trunk Line', () => {
499+
addExcludedStopToRoute();
500+
501+
StopsNeedingUpdateModal.getModal().shouldBeVisible();
502+
StopsNeedingUpdateModal.getStopLink('E2E010').shouldBeVisible();
503+
StopsNeedingUpdateModal.getConfirmButton().click();
504+
505+
Toast.expectSuccessToast('Reitti tallennettu');
506+
});
507+
508+
cy.section('Assert stop Trunk Line status', () =>
509+
assertStopsAreTrunkLine(true),
510+
);
511+
});
512+
});
341513
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { createSimplePageObject } from '../createSimplePageObject';
2+
3+
export const StopsNeedingUpdateModal = createSimplePageObject(
4+
'StopsNeedingUpdateModal',
5+
[
6+
'StopsNeedingUpdateModal::modal',
7+
'StopsNeedingUpdateModal::cancelButton',
8+
'StopsNeedingUpdateModal::confirmButton',
9+
],
10+
(base) => ({
11+
...base,
12+
getStopLink: (publicCode: string) =>
13+
cy.get(
14+
`[data-testid^="StopsNeedingUpdateModal::stopLink::${publicCode}"]`,
15+
),
16+
}),
17+
);

cypress/pageObjects/routes-and-lines/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ export * from './RouteStopListItem';
1111
export * from './RoutesAndLinesPage';
1212
export * from './SearchContainer';
1313
export * from './SearchResultsPage';
14+
export * from './StopsNeedingUpdateModal';
1415
export * from './line-details-page';

cypress/pageObjects/routes-and-lines/line-details-page/LineRouteListItem.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,8 @@ export class LineRouteListItem {
1313
static getNthRouteStopListItem(nth: number) {
1414
return LineRouteListItem.getRouteStopListItems().eq(nth);
1515
}
16+
17+
static getLabel() {
18+
return cy.getByTestId('RouteStopListItem::label');
19+
}
1620
}

cypress/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './db';
22
export * from './time';
3+
export * from './mapAt';

cypress/utils/mapAt.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
type Mapper<T, R> = (item: T) => R;
2+
type Mappers<T, R> = { readonly [key: number]: Mapper<T, R> };
3+
4+
function mapSingleAt<T, R = T>(
5+
array: ReadonlyArray<T>,
6+
index: number,
7+
mapper: Mapper<T, R>,
8+
): Array<T | R> {
9+
if (index >= array.length) {
10+
throw new RangeError(
11+
`Trying to map index ${index} of an Array with length of ${array.length}!`,
12+
);
13+
}
14+
15+
const item = array[index];
16+
return (array as Array<T | R>).with(index, mapper(item));
17+
}
18+
19+
function mapMultipleAt<T, R = T>(
20+
array: ReadonlyArray<T>,
21+
mappers: Mappers<T, R>,
22+
): Array<T | R> {
23+
const indexes = Object.keys(mappers).map(Number);
24+
25+
const invalidIndexes = indexes.filter((i) => i >= array.length);
26+
if (invalidIndexes.length) {
27+
throw new RangeError(
28+
`Trying to map indexes ${invalidIndexes} of an Array with length of ${array.length}!`,
29+
);
30+
}
31+
32+
const copy: Array<T | R> = new Array(array.length);
33+
for (let i = 0; i < array.length; i += 1) {
34+
if (i in mappers) {
35+
copy[i] = mappers[i](array[i]);
36+
} else {
37+
copy[i] = array[i];
38+
}
39+
}
40+
41+
return copy;
42+
}
43+
44+
export function mapAt<T, R = T>(
45+
array: ReadonlyArray<T>,
46+
index: number,
47+
mapper: (item: T) => R,
48+
): Array<T | R>;
49+
export function mapAt<T, R = T>(
50+
array: ReadonlyArray<T>,
51+
mappers: { readonly [key: number]: (item: T) => R },
52+
): Array<T | R>;
53+
export function mapAt<T, R = T>(
54+
array: ReadonlyArray<T>,
55+
indexOrMappers: number | Mappers<T, R>,
56+
mapper?: Mapper<T, R>,
57+
): Array<T | R> {
58+
if (mapper && typeof indexOrMappers === 'number') {
59+
return mapSingleAt(array, indexOrMappers, mapper);
60+
}
61+
62+
if (typeof indexOrMappers === 'object' && indexOrMappers !== null) {
63+
return mapMultipleAt(array, indexOrMappers);
64+
}
65+
66+
throw new TypeError(
67+
'mapAt called with invalid parameters! Known Fn contract: (array,number,Mapper) | (array,Mappers)',
68+
);
69+
}

0 commit comments

Comments
 (0)