diff --git a/assets/src/components/detours/diversionPage.tsx b/assets/src/components/detours/diversionPage.tsx index 7b69e59ee..342fb78d8 100644 --- a/assets/src/components/detours/diversionPage.tsx +++ b/assets/src/components/detours/diversionPage.tsx @@ -144,7 +144,9 @@ export const DiversionPage = ({ routeOrigin, , "Turn-by-Turn Directions:", - ...(extendedDirections?.map((v) => v.instruction) ?? []), + ...(editedDirections + ? [editedDirections] + : extendedDirections?.map((v) => v.instruction) ?? []), , "Connection Points:", connectionPoints?.start?.name ?? "N/A", diff --git a/assets/tests/components/detours/diversionPage.test.tsx b/assets/tests/components/detours/diversionPage.test.tsx index 1ed2f44dd..83f466486 100644 --- a/assets/tests/components/detours/diversionPage.test.tsx +++ b/assets/tests/components/detours/diversionPage.test.tsx @@ -1335,6 +1335,34 @@ describe("DiversionPage", () => { expect(input.value).toBe(startText + "\nHello World!") }) + test("Edited detour text is reflected in the copy button", async () => { + const { container } = render() + + act(() => { + fireEvent.click(originalRouteShape.get(container)) + }) + + act(() => { + fireEvent.click(originalRouteShape.get(container)) + }) + + await userEvent.click(reviewDetourButton.get()) + + const input = screen.getByRole("textbox") as HTMLTextAreaElement + + const startText = `From null` + + await userEvent.type(input, "\nHello World!") + + await userEvent.click(screen.getByRole("button", { name: "Copy details" })) + + await waitFor(() => + expect(window.navigator.clipboard.readText()).resolves.toMatch( + startText + "\nHello World!" + ) + ) + }) + test("Attempting to close the page calls the onClose callback", async () => { const onClose = jest.fn() diff --git a/lib/skate/open_route_service_api.ex b/lib/skate/open_route_service_api.ex index c214d6857..9ac9f17b6 100644 --- a/lib/skate/open_route_service_api.ex +++ b/lib/skate/open_route_service_api.ex @@ -24,10 +24,10 @@ defmodule Skate.OpenRouteServiceAPI do ], directions: [ %{ - instruction: "Turn right onto 1st Avenue" + instruction: "R - 1st Avenue" }, %{ - instruction: "Turn left onto 2nd Place" + instruction: "L - 2nd Place" } ] } @@ -86,14 +86,9 @@ defmodule Skate.OpenRouteServiceAPI do directions: segments |> Enum.flat_map(& &1["steps"]) - |> Enum.filter(fn %{"type" => type} -> - map_type(type) not in [:goal, :depart, :straight, :error] - end) - |> Enum.map( - &%{ - instruction: &1["instruction"] - } - ) + |> Enum.map(fn attrs -> Map.update!(attrs, "type", &map_type/1) end) + |> Enum.map(&Skate.OpenRouteServiceAPI.DirectionsFormatter.MBTA.English.format/1) + |> Enum.reject(&is_nil/1) }} end diff --git a/lib/skate/open_route_service_api/directions_formatter/MBTA/english.ex b/lib/skate/open_route_service_api/directions_formatter/MBTA/english.ex new file mode 100644 index 000000000..870fc349f --- /dev/null +++ b/lib/skate/open_route_service_api/directions_formatter/MBTA/english.ex @@ -0,0 +1,84 @@ +defmodule Skate.OpenRouteServiceAPI.DirectionsFormatter.MBTA.English do + @moduledoc """ + Formats Directions from Open Route Service into MBTA English specific Directions Shorthand. + """ + + @doc """ + Formats a Open Route Service Direction Instruction Map into + - a Instruction, as a `Map` with a single `:instruction` key + - or `nil`, which represents discarded instructions + """ + def format(%{"type" => type, "name" => name} = attrs) do + case format_instruction(type, name, attrs) do + nil -> nil + instruction -> %{instruction: instruction} + end + end + + # Converts ORS Instructions into string form, or `nil` + # Ignore noisy instructions + defp format_instruction(type, _name, _) + when type in [:goal, :depart, :straight, :error], + do: nil + + # ORS uses `-` as a value when a direction doesn't have a name + # Reject instructions without a name + defp format_instruction(_type, "-", _), + do: nil + + defp format_instruction(:left, name, _), + do: "L - #{name}" + + defp format_instruction(:right, name, _), + do: "R - #{name}" + + defp format_instruction(:slight_left, name, _), + do: "Slight L - #{name}" + + defp format_instruction(:slight_right, name, _), + do: "Slight R - #{name}" + + defp format_instruction(:keep_left, name, _), + do: "Keep L - #{name}" + + defp format_instruction(:keep_right, name, _), + do: "Keep R - #{name}" + + defp format_instruction(:sharp_left, name, _), + do: "L - #{name}" + + defp format_instruction(:sharp_right, name, _), + do: "R - #{name}" + + # There does not seem to be many `exit_roundabout` instructions, only `enter` + defp format_instruction(:enter_roundabout, name, %{"exit_number" => exit_number}), + do: "Roundabout - #{exit_number}#{ordinal_indicator(exit_number)} exit, #{name}" + + # Fallback case, return the original instruction from ORS if we do not have an override + defp format_instruction(_, _, %{"instruction" => ors_instruction}), do: ors_instruction + + # English Specific Ordinal Indicators + # > https://en.wikipedia.org/wiki/Ordinal_indicator#English + # > -st is used with numbers ending in 1 + defp ordinal_indicator(1), do: "st" + + # > -nd is used with numbers ending in 2 + defp ordinal_indicator(2), do: "nd" + + # > -rd is used with numbers ending in 3 + defp ordinal_indicator(3), do: "rd" + + # > As an exception to the above rules, numbers ending with 11, 12, and 13 use -th + # `11st, 12nd, 13rd`, do not exist + defp ordinal_indicator(n) when 11 <= n and n <= 13, do: "th" + + # If the number is over 100, we need to check the last two digits for `11-13` + defp ordinal_indicator(n) when n >= 100, do: ordinal_indicator(rem(n, 100)) + + # If the number is over 10, but is not covered by the `11-13` condition above, + # We need to check the last digit for `1..3` + defp ordinal_indicator(n) when n >= 10, do: ordinal_indicator(rem(n, 10)) + + # > -th is used for all other numbers + defp ordinal_indicator(_), do: "th" +end diff --git a/test/skate/open_route_service_api_test.exs b/test/skate/open_route_service_api_test.exs index a9162dcf4..d3d7897d8 100644 --- a/test/skate/open_route_service_api_test.exs +++ b/test/skate/open_route_service_api_test.exs @@ -84,15 +84,18 @@ defmodule Skate.OpenRouteServiceAPITest do "steps" => [ %{ "instruction" => "not-in-results", + "name" => "not-in-results", # Depart "type" => 11 }, %{ "instruction" => "1", + "name" => "A Street", "type" => 1 }, %{ "instruction" => "not-in-results", + "name" => "not-in-results", # Goal "type" => 10 } @@ -102,16 +105,19 @@ defmodule Skate.OpenRouteServiceAPITest do "steps" => [ %{ "instruction" => "not-in-results", + "name" => "not-in-results", # Depart "type" => 11 }, %{ "instruction" => "not-in-results", + "name" => "not-in-results", # Straight "type" => 6 }, %{ "instruction" => "2", + "name" => "B Drive", "type" => 0 } ] @@ -124,8 +130,8 @@ defmodule Skate.OpenRouteServiceAPITest do assert {:ok, %DirectionsResponse{ directions: [ - %{instruction: "1"}, - %{instruction: "2"} + %{instruction: "R - A Street"}, + %{instruction: "L - B Drive"} ] }} = Skate.OpenRouteServiceAPI.directions([ diff --git a/test/skate_web/controllers/detour_route_controller_test.exs b/test/skate_web/controllers/detour_route_controller_test.exs index 7c864d32f..cb8a1ca65 100644 --- a/test/skate_web/controllers/detour_route_controller_test.exs +++ b/test/skate_web/controllers/detour_route_controller_test.exs @@ -66,11 +66,13 @@ defmodule SkateWeb.DetourRouteControllerTest do "steps" => [ %{ "instruction" => "1", - "type" => 1 + "type" => 1, + "name" => "First Street" }, %{ "instruction" => "2", - "type" => 0 + "type" => 0, + "name" => "Second Street" } ] }, @@ -78,7 +80,8 @@ defmodule SkateWeb.DetourRouteControllerTest do "steps" => [ %{ "instruction" => "3", - "type" => 2 + "type" => 2, + "name" => "Third Street" } ] } @@ -94,9 +97,9 @@ defmodule SkateWeb.DetourRouteControllerTest do assert %{ "data" => %{ "directions" => [ - %{"instruction" => "1"}, - %{"instruction" => "2"}, - %{"instruction" => "3"} + %{"instruction" => "R - First Street"}, + %{"instruction" => "L - Second Street"}, + %{"instruction" => "L - Third Street"} ] } } = diff --git a/test/skate_web/controllers/detours_controller_test.exs b/test/skate_web/controllers/detours_controller_test.exs index a604487d7..df6672f26 100644 --- a/test/skate_web/controllers/detours_controller_test.exs +++ b/test/skate_web/controllers/detours_controller_test.exs @@ -786,11 +786,13 @@ defmodule SkateWeb.DetoursControllerTest do "steps" => [ %{ "instruction" => "1", - "type" => 1 + "type" => 1, + "name" => "A" }, %{ "instruction" => "2", - "type" => 0 + "type" => 0, + "name" => "B" } ] }, @@ -798,7 +800,8 @@ defmodule SkateWeb.DetoursControllerTest do "steps" => [ %{ "instruction" => "3", - "type" => 2 + "type" => 2, + "name" => "C" } ] } @@ -836,9 +839,9 @@ defmodule SkateWeb.DetoursControllerTest do "data" => %{ "detour_shape" => %{ "directions" => [ - %{"instruction" => "1"}, - %{"instruction" => "2"}, - %{"instruction" => "3"} + %{"instruction" => "R - A"}, + %{"instruction" => "L - B"}, + %{"instruction" => "L - C"} ] } }