Skip to content

Commit

Permalink
feat: Deactivate detour flow (#2805)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshlarson authored Sep 25, 2024
1 parent 1154994 commit 27de03c
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 31 deletions.
12 changes: 7 additions & 5 deletions assets/src/components/detours/activeDetourPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react"
import React, { PropsWithChildren } from "react"
import { DetourDirection } from "../../models/detour"
import { Button, ListGroup } from "react-bootstrap"
import { Panel } from "./diversionPage"
Expand All @@ -10,15 +10,15 @@ import {
} from "../../helpers/bsIcons"
import { AffectedRoute, MissedStops } from "./detourPanelComponents"

export interface ActiveDetourPanelProps {
export interface ActiveDetourPanelProps extends PropsWithChildren {
directions?: DetourDirection[]
connectionPoints?: string[]
missedStops?: Stop[]
routeName: string
routeDescription: string
routeOrigin: string
routeDirection: string
onDeactivateDetour?: () => void
onOpenDeactivateModal?: () => void
onNavigateBack: () => void
}

Expand All @@ -30,8 +30,9 @@ export const ActiveDetourPanel = ({
routeDescription,
routeOrigin,
routeDirection,
onDeactivateDetour,
onOpenDeactivateModal,
onNavigateBack,
children,
}: ActiveDetourPanelProps) => (
<Panel as="article" className="c-diversion-panel">
<Panel.Header>
Expand Down Expand Up @@ -94,12 +95,13 @@ export const ActiveDetourPanel = ({
<Button
variant="ui-alert"
className="flex-grow-1 m-3 icon-link text-light"
onClick={onDeactivateDetour}
onClick={onOpenDeactivateModal}
>
<StopCircle />
Return to regular route
</Button>
</Panel.Body.Footer>
</Panel.Body>
{children}
</Panel>
)
32 changes: 32 additions & 0 deletions assets/src/components/detours/deactivateDetourModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from "react"
import { Button, Modal } from "react-bootstrap"

export const DeactivateDetourModal = ({
onDeactivate,
onCancel,
}: {
onDeactivate: () => void
onCancel: () => void
}) => {
return (
<Modal show animation={false}>
<Modal.Header role="heading">Return to regular route?</Modal.Header>
<Modal.Body>
Are you sure that you want to stop this detour and return to the regular
route?
</Modal.Body>
<Modal.Footer>
<Button variant="outline-primary" onClick={onCancel}>
Cancel
</Button>
<Button
variant="ui-alert"
onClick={onDeactivate}
className="text-white"
>
Confirm
</Button>
</Modal.Footer>
</Modal>
)
}
20 changes: 17 additions & 3 deletions assets/src/components/detours/diversionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { ActiveDetourPanel } from "./activeDetourPanel"
import { PastDetourPanel } from "./pastDetourPanel"
import userInTestGroup from "../../userInTestGroup"
import { ActivateDetour } from "./activateDetourModal"
import { DeactivateDetourModal } from "./deactivateDetourModal"

const displayFieldsFromRouteAndPattern = (
route: Route,
Expand Down Expand Up @@ -349,10 +350,23 @@ export const DiversionPage = ({
routeOrigin={routeOrigin ?? "??"}
routeDirection={routeDirection ?? "??"}
onNavigateBack={onConfirmClose}
onDeactivateDetour={() => {
send({ type: "detour.active.deactivate" })
onOpenDeactivateModal={() => {
send({ type: "detour.active.open-deactivate-modal" })
}}
/>
>
{snapshot.matches({
"Detour Drawing": { Active: "Deactivating" },
}) ? (
<DeactivateDetourModal
onDeactivate={() =>
send({ type: "detour.active.deactivate-modal.deactivate" })
}
onCancel={() =>
send({ type: "detour.active.deactivate-modal.cancel" })
}
/>
) : null}
</ActiveDetourPanel>
) : snapshot.matches({ "Detour Drawing": "Past" }) ? (
<PastDetourPanel />
) : null}
Expand Down
29 changes: 25 additions & 4 deletions assets/src/models/createDetourMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ export const createDetourMachine = setup({
| { type: "detour.share.activate-modal.cancel" }
| { type: "detour.share.activate-modal.back" }
| { type: "detour.share.activate-modal.activate" }
| { type: "detour.active.deactivate" }
| { type: "detour.active.open-deactivate-modal" }
| { type: "detour.active.deactivate-modal.deactivate" }
| { type: "detour.active.deactivate-modal.cancel" }
| { type: "detour.save.begin-save" }
| { type: "detour.save.set-uuid"; uuid: number },

Expand Down Expand Up @@ -599,10 +601,29 @@ export const createDetourMachine = setup({
},
},
Active: {
on: {
"detour.active.deactivate": {
target: "Past",
initial: "Reviewing",
states: {
Reviewing: {
on: {
"detour.active.open-deactivate-modal": {
target: "Deactivating",
},
},
},
Deactivating: {
on: {
"detour.active.deactivate-modal.deactivate": {
target: "Done",
},
"detour.active.deactivate-modal.cancel": {
target: "Reviewing",
},
},
},
Done: { type: "final" },
},
onDone: {
target: "Past",
},
},
Past: {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const meta = {
routeDescription: "Harvard via Allston",
routeOrigin: "from Andrew Station",
routeDirection: "Outbound",
onDeactivateDetour: undefined,
onOpenDeactivateModal: undefined,
onNavigateBack: undefined,
},
// The bootstrap CSS reset is supposed to set box-sizing: border-box by
Expand Down
12 changes: 0 additions & 12 deletions assets/tests/components/detours/diversionPage.activate.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -374,17 +374,5 @@ describe("DiversionPage activate workflow", () => {
screen.getByRole("button", { name: "Return to regular route" })
).toBeVisible()
})

test("clicking the 'Return to regular route' button shows the 'Past Detour' screen", async () => {
await diversionPageOnActiveDetourScreen()

await userEvent.click(
screen.getByRole("button", { name: "Return to regular route" })
)
expect(
screen.queryByRole("heading", { name: "Active Detour" })
).not.toBeInTheDocument()
expect(screen.getByRole("heading", { name: "Past Detour" })).toBeVisible()
})
})
})
133 changes: 133 additions & 0 deletions assets/tests/components/detours/diversionPage.deactivate.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React from "react"
import {
DiversionPage as DiversionPageDefault,
DiversionPageProps,
} from "../../../src/components/detours/diversionPage"
import { originalRouteFactory } from "../../factories/originalRouteFactory"
import { beforeEach, describe, expect, jest, test } from "@jest/globals"
import "@testing-library/jest-dom/jest-globals"
import getTestGroups from "../../../src/userTestGroups"
import { TestGroups } from "../../../src/userInTestGroup"
import { act, fireEvent, render } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import {
activateDetourButton,
originalRouteShape,
reviewDetourButton,
} from "../../testHelpers/selectors/components/detours/diversionPage"
import {
fetchDetourDirections,
fetchFinishedDetour,
fetchNearestIntersection,
fetchRoutePatterns,
fetchUnfinishedDetour,
putDetourUpdate,
} from "../../../src/api"
import { neverPromise } from "../../testHelpers/mockHelpers"
import { byRole } from "testing-library-selector"

beforeEach(() => {
jest.spyOn(global, "scrollTo").mockImplementationOnce(jest.fn())
})

const DiversionPage = (props: Partial<DiversionPageProps>) => (
<DiversionPageDefault
originalRoute={originalRouteFactory.build()}
showConfirmCloseModal={false}
onConfirmClose={() => null}
{...props}
/>
)

jest.mock("../../../src/api")
jest.mock("../../../src/userTestGroups")

beforeEach(() => {
jest.mocked(fetchDetourDirections).mockReturnValue(neverPromise())
jest.mocked(fetchUnfinishedDetour).mockReturnValue(neverPromise())
jest.mocked(fetchFinishedDetour).mockReturnValue(neverPromise())
jest.mocked(fetchNearestIntersection).mockReturnValue(neverPromise())
jest.mocked(fetchRoutePatterns).mockReturnValue(neverPromise())
jest.mocked(putDetourUpdate).mockReturnValue(neverPromise())

jest
.mocked(getTestGroups)
.mockReturnValue([TestGroups.DetoursPilot, TestGroups.DetoursList])
})

const diversionPageOnActiveDetourScreen = async (
props?: Partial<DiversionPageProps>
) => {
const { container } = render(<DiversionPage {...props} />)

act(() => {
fireEvent.click(originalRouteShape.get(container))
})
act(() => {
fireEvent.click(originalRouteShape.get(container))
})
await userEvent.click(reviewDetourButton.get())
await userEvent.click(activateDetourButton.get())
await userEvent.click(threeHoursRadio.get())
await userEvent.click(nextButton.get())
await userEvent.click(constructionRadio.get())
await userEvent.click(nextButton.get())
await userEvent.click(activateButton.get())

return { container }
}

const nextButton = byRole("button", { name: "Next" })
const activateButton = byRole("button", { name: "Activate detour" })
const threeHoursRadio = byRole("radio", { name: "3 hours" })
const constructionRadio = byRole("radio", { name: "Construction" })

const activeDetourHeading = byRole("heading", { name: "Active Detour" })
const pastDetourHeading = byRole("heading", { name: "Past Detour" })
const returnModalHeading = byRole("heading", {
name: "Return to regular route?",
})

const regularRouteButton = byRole("button", { name: "Return to regular route" })
const confirmButton = byRole("button", { name: "Confirm" })
const cancelButton = byRole("button", { name: "Cancel" })

describe("DiversionPage deactivate workflow", () => {
test("clicking the 'Return to regular route' button keeps existing headers on the screen", async () => {
await diversionPageOnActiveDetourScreen()

await userEvent.click(regularRouteButton.get())
expect(activeDetourHeading.get()).toBeVisible()

expect(pastDetourHeading.query()).not.toBeInTheDocument()
})

test("clicking the 'Return to regular route' button opens the deactivate modal", async () => {
await diversionPageOnActiveDetourScreen()

await userEvent.click(regularRouteButton.get())
expect(returnModalHeading.get()).toBeVisible()
})

test("clicking the 'Return to regular route' button from the the deactivate modal deactivates the detour", async () => {
await diversionPageOnActiveDetourScreen()

await userEvent.click(regularRouteButton.get())
await userEvent.click(confirmButton.get())

expect(activeDetourHeading.query()).not.toBeInTheDocument()
expect(pastDetourHeading.get()).toBeVisible()
})

test("clicking the 'Cancel' button from the the deactivate modal closes the modal", async () => {
await diversionPageOnActiveDetourScreen()

await userEvent.click(regularRouteButton.get())
await userEvent.click(cancelButton.get())

expect(activeDetourHeading.get()).toBeVisible()
expect(pastDetourHeading.query()).not.toBeInTheDocument()

expect(returnModalHeading.query()).not.toBeInTheDocument()
})
})
7 changes: 5 additions & 2 deletions lib/skate/detours/detours.ex
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,11 @@ defmodule Skate.Detours.Detours do
@type detour_type :: :active | :draft | :past

@spec categorize_detour(map(), integer()) :: detour_type
defp categorize_detour(%{state: %{"value" => %{"Detour Drawing" => "Active"}}}, _user_id),
do: :active
defp categorize_detour(
%{state: %{"value" => %{"Detour Drawing" => %{"Active" => _}}}},
_user_id
),
do: :active

defp categorize_detour(%{state: %{"value" => %{"Detour Drawing" => "Past"}}}, _user_id),
do: :past
Expand Down
8 changes: 4 additions & 4 deletions test/skate_web/controllers/detours_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ defmodule SkateWeb.DetoursControllerTest do
"nearestIntersection" => "Street A & Avenue B",
"uuid" => 1
},
"value" => %{"Detour Drawing" => "Active"}
"value" => %{"Detour Drawing" => %{"Active" => "Reviewing"}}
}
})

Expand Down Expand Up @@ -151,7 +151,7 @@ defmodule SkateWeb.DetoursControllerTest do
"routePattern" => %{"directionId" => 0, "headsign" => "Headsign"},
"uuid" => 1
},
"value" => %{"Detour Drawing" => "Active"}
"value" => %{"Detour Drawing" => %{"Active" => "Reviewing"}}
},
"updated_at" => _
}
Expand Down Expand Up @@ -296,7 +296,7 @@ defmodule SkateWeb.DetoursControllerTest do
"nearestIntersection" => "Street A & Avenue B",
"uuid" => 4
},
"value" => %{"Detour Drawing" => "Active"}
"value" => %{"Detour Drawing" => %{"Active" => "Reviewing"}}
}
})

Expand All @@ -314,7 +314,7 @@ defmodule SkateWeb.DetoursControllerTest do
"nearestIntersection" => "Street A & Avenue B",
"uuid" => 5
},
"value" => %{"Detour Drawing" => "Active"}
"value" => %{"Detour Drawing" => %{"Active" => "Reviewing"}}
}
})

Expand Down

0 comments on commit 27de03c

Please sign in to comment.