diff --git a/cypress/e2e/smoke.cy.js b/cypress/e2e/smoke.cy.js index 7416723d32..3de6db722a 100644 --- a/cypress/e2e/smoke.cy.js +++ b/cypress/e2e/smoke.cy.js @@ -152,30 +152,6 @@ describe("passes smoke test", () => { } }); - it("trip planner", () => { - cy.visit("/trip-planner"); - - // reverses the inputs - cy.get("#from").type("A"); - cy.get("#to").type("B"); - cy.get("#trip-plan-reverse-control").click(); - cy.get("#from").should("have.value", "B"); - cy.get("#to").should("have.value", "A"); - - // opens the date picker - cy.contains("#trip-plan-datepicker").should("not.exist"); - cy.get('label[for="arrive"]').click(); - cy.get("#trip-plan-datepicker"); - - // shortcut /from/ - marker A prepopulated - cy.visit("/trip-planner/from/North+Station"); - cy.get('img.leaflet-marker-icon[src="/icon-svg/icon-map-pin-a.svg"]'); - - // shortcut /to/ - marker B prepopulated - cy.visit("/trip-planner/to/North+Station"); - cy.get('img.leaflet-marker-icon[src="/icon-svg/icon-map-pin-b.svg"]'); - }); - it("alerts page", () => { cy.visit("/alerts"); cy.contains(".m-alerts__mode-buttons a", "Bus").click(); diff --git a/integration/scenarios/plan-a-trip-from-homepage.js b/integration/scenarios/plan-a-trip-from-homepage.js index b4a241e98f..92bb0c83e7 100644 --- a/integration/scenarios/plan-a-trip-from-homepage.js +++ b/integration/scenarios/plan-a-trip-from-homepage.js @@ -29,7 +29,7 @@ exports.scenario = async ({ page, baseURL }) => { await expect .poll(async () => - page.locator("div.m-trip-plan-results__itinerary").count(), + page.locator("section#trip-planner-results").count(), ) .toBeGreaterThan(0); }; diff --git a/integration/scenarios/plan-a-trip.js b/integration/scenarios/plan-a-trip.js index 4473a476b1..fde2eb62be 100644 --- a/integration/scenarios/plan-a-trip.js +++ b/integration/scenarios/plan-a-trip.js @@ -3,29 +3,35 @@ const { expect } = require("@playwright/test"); exports.scenario = async ({ page, baseURL }) => { await page.goto(`${baseURL}/trip-planner`); - await page.locator("input#from").pressSequentially("North Station"); + await expect( + page.getByRole("heading", { name: "Trip Planner" }), + ).toBeVisible(); + + await page.locator("fieldset#trip-planner-locations-from input[type='search']").pressSequentially("North Station"); await page.waitForSelector( - "div#from-autocomplete-results span.c-search-bar__-dropdown-menu", + "ul.aa-List", ); await page.keyboard.press("ArrowDown"); await page.keyboard.press("Enter"); - await page.locator("input#to").pressSequentially("South Station"); + // The A location pin. + await page.waitForSelector("#mbta-metro-pin-0"); + + await page.locator("fieldset#trip-planner-locations-to input[type='search']").pressSequentially("South Station"); await page.waitForSelector( - "div#to-autocomplete-results span.c-search-bar__-dropdown-menu", + "ul.aa-List", ); await page.keyboard.press("ArrowDown"); await page.keyboard.press("Enter"); - await page.locator("button#trip-plan__submit").click(); + // The B location pin. + await page.waitForSelector("#mbta-metro-pin-1"); - await expect( - page.getByRole("heading", { name: "Trip Planner" }), - ).toBeVisible(); + await page.getByText("Get trip suggestions").click(); await expect .poll(async () => - page.locator("div.m-trip-plan-results__itinerary").count(), + page.locator("section#trip-planner-results").count(), ) .toBeGreaterThan(0); }; diff --git a/lib/dotcom_web/controllers/trip_plan_controller.ex b/lib/dotcom_web/controllers/trip_plan_controller.ex deleted file mode 100644 index cc60b210af..0000000000 --- a/lib/dotcom_web/controllers/trip_plan_controller.ex +++ /dev/null @@ -1,309 +0,0 @@ -defmodule DotcomWeb.TripPlanController do - @moduledoc """ - Controller for trip plans. - """ - - use DotcomWeb, :controller - - require Logger - - alias Dotcom.TripPlan.{ - Itinerary, - ItineraryRowList, - Leg, - NamedPosition, - PersonalDetail, - Query, - RelatedLink, - TransitDetail - } - - alias Dotcom.TripPlan.Map, as: TripPlanMap - alias Routes.Route - - @location_service Application.compile_env!(:dotcom, :location_service) - - @type route_map :: %{optional(Route.id_t()) => Route.t()} - @type route_mapper :: (Route.id_t() -> Route.t() | nil) - - plug(:assign_initial_map) - plug(:breadcrumbs) - plug(:modes) - plug(:wheelchair) - plug(:meta_description) - plug(:assign_params) - - def index(conn, %{"plan" => %{"to" => _to, "from" => _fr} = plan}) do - conn - |> assign(:expanded, conn.query_params["expanded"]) - |> render_plan(plan) - end - - def index(conn, _params) do - render(conn, :index) - end - - def from(conn, %{"plan" => _plan} = params) do - redirect(conn, to: trip_plan_path(conn, :index, Map.delete(params, "address"))) - end - - def from(conn, %{ - "address" => address - }) do - if String.match?(address, ~r/^(\-?\d+(\.\d+)?),(\-?\d+(\.\d+)?),.*$/) do - [latitude, longitude, name] = String.split(address, ",", parts: 3) - # Avoid extra geocode call, just use these coordinates - destination = %NamedPosition{ - latitude: String.to_float(latitude), - longitude: String.to_float(longitude), - name: name, - stop: nil - } - - do_from(conn, destination) - else - updated_address = check_address(address) - - case @location_service.geocode(updated_address) do - {:ok, [geocoded_from | _]} -> - do_from(conn, NamedPosition.new(geocoded_from)) - - _ -> - # redirect to the initial index page - redirect(conn, to: trip_plan_path(conn, :index)) - end - end - end - - defp do_from(conn, destination) do - # build a default query with a pre-filled 'from' field: - query = %Query{ - from: destination, - to: {:error, :unknown}, - time: {:error, :unknown} - } - - now = Util.now() - - # build map information for a single leg with the 'from' field: - map_data = - TripPlanMap.itinerary_map([ - %Leg{ - from: destination, - to: nil, - mode: %PersonalDetail{}, - start: now, - stop: now - } - ]) - - %{markers: [marker]} = map_data - from_marker = %{marker | id: "B"} - map_info_for_from_destination = %{map_data | markers: [from_marker]} - - conn - |> assign(:query, query) - |> assign(:map_data, map_info_for_from_destination) - |> render(:index) - end - - def to(conn, %{"plan" => _plan} = params) do - redirect(conn, to: trip_plan_path(conn, :index, Map.delete(params, "address"))) - end - - def to(conn, %{ - "address" => address - }) do - if String.match?(address, ~r/^(\-?\d+(\.\d+)?),(\-?\d+(\.\d+)?),.*$/) do - [latitude, longitude, name] = String.split(address, ",", parts: 3) - # Avoid extra geocode call, just use these coordinates - destination = %NamedPosition{ - latitude: String.to_float(latitude), - longitude: String.to_float(longitude), - name: name, - stop: nil - } - - do_to(conn, destination) - else - updated_address = check_address(address) - - case @location_service.geocode(updated_address) do - {:ok, [geocoded_to | _]} -> - do_to(conn, NamedPosition.new(geocoded_to)) - - _ -> - # redirect to the initial index page - redirect(conn, to: trip_plan_path(conn, :index)) - end - end - end - - defp do_to(conn, destination) do - # build a default query with a pre-filled 'to' field: - query = %Query{ - to: destination, - time: {:error, :unknown}, - from: {:error, :unknown} - } - - now = Util.now() - - # build map information for a single leg with the 'to' field: - map_data = - TripPlanMap.itinerary_map([ - %Leg{ - from: nil, - to: destination, - mode: %PersonalDetail{}, - start: now, - stop: now - } - ]) - - %{markers: [marker]} = map_data - to_marker = %{marker | id: "B"} - map_info_for_to_destination = %{map_data | markers: [to_marker]} - - conn - |> assign(:query, query) - |> assign(:map_data, map_info_for_to_destination) - |> render(:index) - end - - defp assign_params(conn, _) do - conn - |> assign(:chosen_date_time, conn.params["plan"]["date_time"]) - |> assign(:chosen_time, conn.params["plan"]["time"]) - end - - @spec check_address(String.t()) :: String.t() - defp check_address(address) do - # address can be a String containing "lat,lon" so we check for that case - - [lat, lon] = - case String.split(address, ",", parts: 2) do - [lat, lon] -> [lat, lon] - _ -> ["error", "error"] - end - - if Float.parse(lat) == :error || Float.parse(lon) == :error do - address - else - {parsed_lat, _} = Float.parse(lat) - {parsed_lon, _} = Float.parse(lon) - - case @location_service.reverse_geocode(parsed_lat, parsed_lon) do - {:ok, [first | _]} -> - first.formatted - - _ -> - "#{lat}, #{lon}" - end - end - end - - defp get_route(link) do - if is_bitstring(link.text) do - link.text - else - link.text |> List.to_string() - end - end - - defp filter_duplicate_links(related_links) do - Enum.map(related_links, fn x -> Enum.uniq_by(x, fn y -> get_route(y) end) end) - end - - @spec render_plan(Plug.Conn.t(), map) :: Plug.Conn.t() - defp render_plan(conn, plan_params) do - query = - Query.from_query( - plan_params, - now: conn.assigns.date_time, - end_of_rating: Map.get(conn.assigns, :end_of_rating, Schedules.Repo.end_of_rating()) - ) - - itineraries = - query - |> Query.get_itineraries() - - itinerary_row_lists = itinerary_row_lists(itineraries, plan_params) - - conn - |> render( - query: query, - itineraries: itineraries, - plan_error: MapSet.to_list(query.errors), - routes: Enum.map(itineraries, &routes_for_itinerary(&1)), - itinerary_maps: Enum.map(itineraries, &TripPlanMap.itinerary_map(&1)), - related_links: - filter_duplicate_links(Enum.map(itineraries, &RelatedLink.links_for_itinerary(&1))), - itinerary_row_lists: itinerary_row_lists - ) - end - - @spec itinerary_row_lists([Itinerary.t()], map) :: [ItineraryRowList.t()] - defp itinerary_row_lists(itineraries, plan) do - Enum.map(itineraries, &ItineraryRowList.from_itinerary(&1, to_and_from(plan))) - end - - @spec assign_initial_map(Plug.Conn.t(), any()) :: Plug.Conn.t() - def assign_initial_map(conn, _opts) do - conn - |> assign(:map_data, TripPlanMap.initial_map_data()) - end - - @spec modes(Plug.Conn.t(), Keyword.t()) :: Plug.Conn.t() - def modes(%Plug.Conn{params: %{"plan" => %{"modes" => modes}}} = conn, _) do - assign( - conn, - :modes, - Map.new(modes, fn {mode, active?} -> {String.to_existing_atom(mode), active? === "true"} end) - ) - end - - def modes(%Plug.Conn{} = conn, _) do - assign( - conn, - :modes, - %{subway: true, bus: true, commuter_rail: true, ferry: true} - ) - end - - @spec breadcrumbs(Plug.Conn.t(), Keyword.t()) :: Plug.Conn.t() - defp breadcrumbs(conn, _) do - assign(conn, :breadcrumbs, [Breadcrumb.build("Trip Planner")]) - end - - @spec wheelchair(Plug.Conn.t(), Keyword.t()) :: Plug.Conn.t() - def wheelchair(%Plug.Conn{params: %{"plan" => plan_params}} = conn, _) do - assign(conn, :wheelchair, get_in(plan_params, ["wheelchair"]) === "true") - end - - # Initialize to checked state for trip plan accessibility - def wheelchair(%Plug.Conn{} = conn, _) do - assign(conn, :wheelchair, true) - end - - @spec routes_for_itinerary(Itinerary.t()) :: [Route.t()] - defp routes_for_itinerary(itinerary) do - itinerary.legs - |> Enum.filter(&match?(%TransitDetail{}, &1.mode)) - |> Enum.map(& &1.mode.route) - end - - @spec to_and_from(map) :: [to: String.t() | nil, from: String.t() | nil] - def to_and_from(plan) do - [to: Map.get(plan, "to"), from: Map.get(plan, "from")] - end - - defp meta_description(conn, _) do - conn - |> assign( - :meta_description, - "Plan a trip on public transit in the Greater Boston region with directions " <> - "and suggestions based on real-time data." - ) - end -end diff --git a/lib/dotcom_web/controllers/vote_controller.ex b/lib/dotcom_web/controllers/vote_controller.ex index c16b493ba0..0283175d76 100644 --- a/lib/dotcom_web/controllers/vote_controller.ex +++ b/lib/dotcom_web/controllers/vote_controller.ex @@ -44,7 +44,7 @@ defmodule DotcomWeb.VoteController do conn |> assign(:polling_location, polling_location) |> assign(:polling_location_name, polling_location_name) - |> assign(:trip_plan_path, trip_plan_path(DotcomWeb.Endpoint, :index, params)) + |> assign(:trip_plan_path, "/trip-planner#{URI.encode_query(params)}") _ -> conn |> assign(:polling_error, true) diff --git a/lib/dotcom_web/live/admin.ex b/lib/dotcom_web/live/admin.ex index 8ae633fe2a..4202009b75 100644 --- a/lib/dotcom_web/live/admin.ex +++ b/lib/dotcom_web/live/admin.ex @@ -11,11 +11,6 @@ defmodule DotcomWeb.Live.Admin do url: Helpers.live_path(socket, DotcomWeb.Live.Admin.TripPlanFeedback), title: "Trip Planner Feedback", description: "Find and download the latest comments and votes." - }, - %{ - url: Helpers.live_path(socket, DotcomWeb.Live.TripPlanner), - title: "Trip Planner Preview", - description: "WIP on the trip planner rewrite." } ] )} diff --git a/lib/dotcom_web/live/trip_planner.ex b/lib/dotcom_web/live/trip_planner.ex index 1b7c2bc651..7029b32a96 100644 --- a/lib/dotcom_web/live/trip_planner.ex +++ b/lib/dotcom_web/live/trip_planner.ex @@ -62,7 +62,7 @@ defmodule DotcomWeb.Live.TripPlanner do """ def render(assigns) do ~H""" -

Trip Planner Preview

+

Trip Planner

<.input_form class="mb-4" changeset={@input_form.changeset} />
diff --git a/lib/dotcom_web/router.ex b/lib/dotcom_web/router.ex index e993fcf159..f53865d67a 100644 --- a/lib/dotcom_web/router.ex +++ b/lib/dotcom_web/router.ex @@ -227,11 +227,6 @@ defmodule DotcomWeb.Router do get("/style-guide/:section/:subpage", StyleGuideController, :show) get("/transit-near-me", TransitNearMeController, :index) resources("/alerts", AlertController, only: [:index, :show]) - get("/trip-planner", TripPlanController, :index) - get("/trip-planner/from/", Redirector, to: "/trip-planner") - get("/trip-planner/from/:address", TripPlanController, :from) - get("/trip-planner/to/", Redirector, to: "/trip-planner") - get("/trip-planner/to/:address", TripPlanController, :to) delete("/trip-planner/feedback", TripPlan.Feedback, :delete) post("/trip-planner/feedback", TripPlan.Feedback, :put) get("/customer-support", CustomerSupportController, :index) @@ -263,11 +258,11 @@ defmodule DotcomWeb.Router do end end - scope "/preview", DotcomWeb do + scope "/", DotcomWeb do import Phoenix.LiveView.Router - pipe_through([:browser, :browser_live, :basic_auth_readonly]) + pipe_through([:browser, :browser_live]) - live_session :rider, layout: {DotcomWeb.LayoutView, :preview} do + live_session :rider, layout: {DotcomWeb.LayoutView, :live} do live("/trip-planner", Live.TripPlanner) end end diff --git a/lib/dotcom_web/templates/fare/_nearby_locations.html.eex b/lib/dotcom_web/templates/fare/_nearby_locations.html.eex index e09fc7fe47..750250fcaa 100644 --- a/lib/dotcom_web/templates/fare/_nearby_locations.html.eex +++ b/lib/dotcom_web/templates/fare/_nearby_locations.html.eex @@ -36,7 +36,7 @@
<% end %> + <%= if assigns[:search_header?] do %> <%= render "_searchbar.html", assigns %> <% end %> diff --git a/lib/dotcom_web/templates/vote/show.html.heex b/lib/dotcom_web/templates/vote/show.html.heex index 44db0cfd62..337089fca1 100644 --- a/lib/dotcom_web/templates/vote/show.html.heex +++ b/lib/dotcom_web/templates/vote/show.html.heex @@ -56,7 +56,7 @@ Secretary of State's official site to find your polling place, and then use the - + MBTA Trip Planner to plan your trip. diff --git a/test/dotcom_web/controllers/trip_plan_controller_test.exs b/test/dotcom_web/controllers/trip_plan_controller_test.exs deleted file mode 100644 index 0e2cb30203..0000000000 --- a/test/dotcom_web/controllers/trip_plan_controller_test.exs +++ /dev/null @@ -1,720 +0,0 @@ -defmodule DotcomWeb.TripPlanControllerTest do - use DotcomWeb.ConnCase, async: true - - alias Dotcom.TripPlan.{Itinerary, PersonalDetail, Query, TransitDetail} - alias Fares.Fare - - import Test.Support.Factories.LocationService.LocationService - - doctest DotcomWeb.TripPlanController - - import Mox - - @system_time "2017-01-01T12:20:00-05:00" - @morning %{ - "year" => "2017", - "month" => "1", - "day" => "2", - "hour" => "9", - "minute" => "30", - "am_pm" => "AM" - } - @afternoon %{ - "year" => "2017", - "month" => "1", - "day" => "2", - "hour" => "5", - "minute" => "30", - "am_pm" => "PM" - } - @after_hours %{ - "year" => "2017", - "month" => "1", - "day" => "2", - "hour" => "3", - "minute" => "00", - "am_pm" => "AM" - } - @modes %{"subway" => "true", "commuter_rail" => "true", "bus" => "false", "ferry" => "false"} - - @good_params %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "from address", - "to" => "to address", - "date_time" => @afternoon, - "time" => "depart", - "modes" => @modes, - "wheelchair" => "true" - } - } - - @bad_params %{ - "date_time" => @system_time, - "plan" => %{"from" => "no results", "to" => "too many results", "date_time" => @afternoon} - } - - setup :verify_on_exit! - - setup do - stub(MBTA.Api.Mock, :get_json, fn "/schedules/", [route: "Red", date: "1970-01-01"] -> - {:error, - [ - %JsonApi.Error{ - code: "no_service", - source: %{ - "parameter" => "date" - }, - detail: "The current rating does not describe service on that date.", - meta: %{ - "end_date" => "2024-06-15", - "start_date" => "2024-05-10", - "version" => "Spring 2024, 2024-05-17T21:10:15+00:00, version D" - } - } - ]} - end) - - stub(OpenTripPlannerClient.Mock, :plan, fn _from, _to, _opts -> - {:ok, %OpenTripPlannerClient.Plan{itineraries: []}} - end) - - stub(LocationService.Mock, :geocode, fn name -> - {:ok, build_list(2, :address, %{formatted: name})} - end) - - stub(Stops.Repo.Mock, :get_parent, fn _ -> - %Stops.Stop{} - end) - - cache = Application.get_env(:dotcom, :cache) - cache.flush() - - conn = default_conn() - - end_of_rating = - @system_time - |> Timex.parse!("{ISO:Extended}") - |> Timex.shift(months: 3) - |> DateTime.to_date() - - {:ok, conn: assign(conn, :end_of_rating, end_of_rating)} - end - - describe "index without params" do - test "renders index.html", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :index)) - assert html_response(conn, 200) =~ "Trip Planner" - end - - test "assigns initial map data", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :index)) - assert conn.assigns.map_data - end - - test "sets a custom meta description", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :index)) - assert conn.assigns.meta_description - end - end - - describe "index with params" do - test "renders the query plan", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :index, @good_params)) - response = html_response(conn, 200) - assert response =~ "Trip Planner" - assert %Query{} = conn.assigns.query - assert conn.assigns.itineraries - assert conn.assigns.routes - assert conn.assigns.itinerary_maps - assert conn.assigns.related_links - end - - test "uses current location to render a query plan", %{conn: conn} do - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "Your current location", - "from_latitude" => "42.3428", - "from_longitude" => "-71.0857", - "to" => "to address", - "to_latitude" => "", - "to_longitude" => "", - "date_time" => @morning, - "modes" => @modes - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - - assert html_response(conn, 200) =~ "Trip Planner" - assert %Query{} = conn.assigns.query - end - - test "sets hidden inputs for lat/lng", %{conn: conn} do - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "from address", - "from_latitude" => "1", - "from_longitude" => "2", - "to" => "to address", - "to_latitude" => "3", - "to_longitude" => "4", - "date_time" => @morning, - "modes" => @modes - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - - resp = html_response(conn, 200) - assert from_latitude = Floki.find(resp, "#from_latitude") - assert from_longitude = Floki.find(resp, "#from_longitude") - assert to_latitude = Floki.find(resp, "#to_latitude") - assert to_longitude = Floki.find(resp, "#to_longitude") - assert List.first(Floki.attribute(from_latitude, "value")) == "1.0" - assert List.first(Floki.attribute(from_longitude, "value")) == "2.0" - assert List.first(Floki.attribute(to_latitude, "value")) == "3.0" - assert List.first(Floki.attribute(to_longitude, "value")) == "4.0" - end - - test "assigns.mode is a map of parsed mode state", %{conn: conn} do - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "Your current location", - "from_latitude" => "42.3428", - "from_longitude" => "-71.0857", - "to" => "to address", - "to_latitude" => "", - "to_longitude" => "", - "date_time" => @morning, - "modes" => @modes - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - - assert html_response(conn, 200) =~ "Trip Planner" - assert conn.assigns.modes == %{subway: true, commuter_rail: true, bus: false, ferry: false} - assert %Query{} = conn.assigns.query - end - - test "assigns.wheelchair uses value provided in params", %{conn: conn} do - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "Your current location", - "from_latitude" => "42.3428", - "from_longitude" => "-71.0857", - "to" => "to address", - "to_latitude" => "", - "to_longitude" => "", - "date_time" => @morning, - "modes" => @modes, - "wheelchair" => "true" - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - - assert html_response(conn, 200) =~ "Trip Planner" - assert conn.assigns.wheelchair == true - end - - test "can use the old date time format", %{conn: conn} do - old_dt_format = Map.delete(@afternoon, "am_pm") - - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "from_address", - "from_latitude" => "", - "from_longitude" => "", - "to" => "to address", - "to_latitude" => "", - "to_longitude" => "", - "date_time" => old_dt_format, - "mode" => @modes - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - assert html_response(conn, 200) - end - - test "each map url has a path color", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :index, @good_params)) - - for {map_data, static_map} <- conn.assigns.itinerary_maps do - assert static_map =~ "color" - - for path <- map_data.polylines do - assert path.color - end - end - end - - test "renders a geocoding error", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :index, @bad_params)) - response = html_response(conn, 200) - assert response =~ "Trip Planner" - # shows error styling on location input with "required" label - assert response =~ "(Required)" - assert %Query{} = conn.assigns.query - end - - test "assigns maps for each itinerary", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :index, @good_params)) - assert conn.assigns.itinerary_maps - end - - test "gets routes from each itinerary", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :index, @good_params)) - assert conn.assigns.routes - - for routes_for_itinerary <- conn.assigns.routes do - assert length(routes_for_itinerary) > 0 - end - end - - test "assigns an ItineraryRowList for each itinerary", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :index, @good_params)) - assert conn.assigns.itinerary_row_lists - end - - test "adds fare data to each transit leg of each itinerary", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :index, @good_params)) - - assert Enum.all?(conn.assigns.itineraries, fn itinerary -> - Enum.all?(itinerary.legs, fn leg -> - match?(%PersonalDetail{}, leg.mode) || - match?( - %TransitDetail{ - fares: %{ - highest_one_way_fare: %Fares.Fare{}, - lowest_one_way_fare: %Fares.Fare{}, - reduced_one_way_fare: %Fares.Fare{} - } - }, - leg.mode - ) - end) - end) - end - - test "returns all nil fares when there is not enough information", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :index, @good_params)) - - for itinerary <- conn.assigns.itineraries do - for leg <- itinerary.legs do - if Dotcom.TripPlan.Leg.transit?(leg) do - assert leg.mode.fares == %{ - highest_one_way_fare: nil, - lowest_one_way_fare: nil, - reduced_one_way_fare: nil - } - end - end - end - end - - test "adds monthly pass data to each itinerary", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :index, @good_params)) - - assert Enum.all?(conn.assigns.itineraries, fn itinerary -> - %Itinerary{passes: %{base_month_pass: %Fare{}, recommended_month_pass: %Fare{}}} = - itinerary - end) - end - - test "renders an error if longitude and latitude from both addresses are the same", %{ - conn: conn - } do - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from_latitude" => "90", - "to_latitude" => "90", - "from_longitude" => "50", - "to_longitude" => "50", - "date_time" => @afternoon, - "from" => "from St", - "to" => "from Street" - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - assert conn.assigns.plan_error == [:same_address] - assert html_response(conn, 200) - assert html_response(conn, 200) =~ "two different locations" - end - - test "doesn't render an error if longitudes and latitudes are unique", %{conn: conn} do - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from_latitude" => "90", - "to_latitude" => "90.5", - "from_longitude" => "50.5", - "to_longitude" => "50", - "date_time" => @afternoon, - "from" => "from St", - "to" => "from Street" - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - assert conn.assigns.plan_error == [] - assert html_response(conn, 200) - end - - test "renders an error if to and from address are the same", %{conn: conn} do - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "from", - "to" => "from", - "date_time" => @afternoon - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - assert conn.assigns.plan_error == [:same_address] - assert html_response(conn, 200) - assert html_response(conn, 200) =~ "two different locations" - end - - test "doesn't render an error if to and from address are unique", %{conn: conn} do - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "from", - "to" => "to", - "date_time" => @afternoon - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - assert conn.assigns.plan_error == [] - assert html_response(conn, 200) - end - - test "handles empty lat/lng", %{conn: conn} do - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "from", - "to" => "from", - "to_latitude" => "", - "to_longitude" => "", - "from_latitude" => "", - "from_longitude" => "", - "date_time" => @afternoon - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - assert conn.assigns.plan_error == [:same_address] - assert html_response(conn, 200) - assert html_response(conn, 200) =~ "two different locations" - end - - test "bad date input: fictional day", %{conn: conn} do - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "from address", - "to" => "to address", - "date_time" => %{@morning | "month" => "6", "day" => "31"} - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - response = html_response(conn, 200) - assert response =~ "Date is not valid" - end - - test "bad date input: partial input", %{conn: conn} do - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "from address", - "to" => "to address", - "date_time" => %{@morning | "month" => ""} - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - response = html_response(conn, 200) - assert response =~ "Date is not valid" - end - - test "bad date input: corrupt day", %{conn: conn} do - date_input = %{ - "year" => "A", - "month" => "B", - "day" => "C", - "hour" => "D", - "minute" => "E", - "am_pm" => "PM" - } - - params = %{ - "date_time" => @system_time, - "plan" => %{"from" => "from address", "to" => "to address", "date_time" => date_input} - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - response = html_response(conn, 200) - assert response =~ "Date is not valid" - end - - test "bad date input: too far in future", %{conn: conn} do - end_date = Timex.shift(Schedules.Repo.end_of_rating(), days: 1) - - end_date_as_params = %{ - "month" => Integer.to_string(end_date.month), - "day" => Integer.to_string(end_date.day), - "year" => Integer.to_string(end_date.year), - "hour" => "12", - "minute" => "15", - "am_pm" => "PM" - } - - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "from address", - "to" => "to address", - "date_time" => end_date_as_params, - "time" => "depart" - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - response = html_response(conn, 200) - assert Map.get(conn.assigns, :plan_error) == [:too_future] - assert response =~ "Date is too far in the future" - - expected = - [:too_future] - |> DotcomWeb.TripPlanView.plan_error_description() - |> IO.iodata_to_binary() - - assert response =~ expected - end - - test "bad date input: date in past", %{conn: conn} do - past_date = - @system_time - |> Timex.parse!("{ISO:Extended}") - |> Timex.shift(days: -10) - - past_date_as_params = %{ - "month" => Integer.to_string(past_date.month), - "day" => Integer.to_string(past_date.day), - "year" => Integer.to_string(past_date.year), - "hour" => "12", - "minute" => "15", - "am_pm" => "PM" - } - - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "from address", - "to" => "to address", - "date_time" => past_date_as_params, - "time" => "depart" - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - response = html_response(conn, 200) - assert Map.get(conn.assigns, :plan_error) == [:past] - assert response =~ "Date is in the past" - end - - test "handles missing date and time params, using today's values if they are missing", - %{conn: conn} do - wrong_datetime_params = %{ - "year" => "2017", - "day" => "2", - "hour" => "9", - "am_pm" => "AM" - } - - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "from address", - "to" => "to address", - "date_time" => wrong_datetime_params - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - assert html_response(conn, 200) - end - - test "handles non-existing date and time params, using today's values", - %{conn: conn} do - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "from address", - "to" => "to address", - "date_time" => %{} - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - assert html_response(conn, 200) - end - - test "does not need to default date and time params as they are present", - %{conn: conn} do - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "from address", - "to" => "to address", - "date_time" => @morning - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - assert html_response(conn, 200) - end - - test "good date input: date within service date of end of rating", %{conn: conn} do - # after midnight but before end of service on last day of rating - # should still be inside of the rating - - date = Timex.shift(conn.assigns.end_of_rating, days: 1) - - date_params = %{ - "month" => Integer.to_string(date.month), - "day" => Integer.to_string(date.day), - "year" => Integer.to_string(date.year), - "hour" => "12", - "minute" => "15", - "am_pm" => "AM" - } - - params = %{ - "date_time" => @system_time, - "plan" => %{"from" => "from address", "to" => "to address", "date_time" => date_params} - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - - response = html_response(conn, 200) - assert Map.get(conn.assigns, :plan_error) == [] - refute response =~ "Date is too far in the future" - refute response =~ "Date is not valid" - end - - test "hour and minute are processed correctly when provided as single digits", %{conn: conn} do - params = %{ - "date_time" => @system_time, - "plan" => %{ - "from" => "from address", - "to" => "to address", - "date_time" => %{@after_hours | "hour" => "1", "minute" => "1"}, - "time" => "depart" - } - } - - conn = get(conn, trip_plan_path(conn, :index, params)) - response = html_response(conn, 200) - assert Map.get(conn.assigns, :plan_error) == [] - refute response =~ "Date is not valid" - end - end - - describe "/from/ address path" do - test "gets a valid address in the 'from' field", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :from, "Boston Common")) - - assert conn.assigns.query.from.name == "Boston Common" - end - - test "uses expected values when addres is formatted latitutde,longitude,stopName", %{ - conn: conn - } do - conn = get(conn, trip_plan_path(conn, :from, "42.395428,-71.142483,Cobbs Corner, Canton")) - - assert html_response(conn, 200) - assert conn.assigns.query.from.name == "Cobbs Corner, Canton" - assert conn.assigns.query.from.latitude == 42.395428 - assert conn.assigns.query.from.longitude == -71.142483 - end - - test "is unable to get address so it redirects to index", %{conn: conn} do - expect(LocationService.Mock, :geocode, fn _ -> - {:error, :something} - end) - - conn = get(conn, trip_plan_path(conn, :from, "Atlantis")) - assert html_response(conn, 302) =~ "/trip-planner" - end - - test "when 'plan' is part of the parameters, it redirects to the usual trip planner", %{ - conn: conn - } do - plan_params = %{"plan" => %{"from" => "from address", "to" => "to address"}} - - conn = - get( - conn, - trip_plan_path(conn, :from, "Address", plan_params) - ) - - assert redirected_to(conn) == trip_plan_path(conn, :index, plan_params) - end - end - - describe "/to/ address path" do - test "gets a valid address in the 'to' field", %{conn: conn} do - conn = get(conn, trip_plan_path(conn, :to, "Boston Common")) - assert conn.assigns.query.to.name == "Boston Common" - end - - test "uses expected values when address is formatted latitutde,longitude,stopName", %{ - conn: conn - } do - conn = get(conn, trip_plan_path(conn, :to, "42.395428,-71.142483,Cobbs Corner, Canton")) - - assert html_response(conn, 200) - assert conn.assigns.query.to.name == "Cobbs Corner, Canton" - assert conn.assigns.query.to.latitude == 42.395428 - assert conn.assigns.query.to.longitude == -71.142483 - end - - test "is unable to get address so it redirects to index", %{conn: conn} do - expect(LocationService.Mock, :geocode, fn _ -> - {:error, :something} - end) - - conn = get(conn, trip_plan_path(conn, :to, "Atlantis")) - assert html_response(conn, 302) =~ "/trip-planner" - end - - test "when 'plan' is part of the parameters, it redirects to the usual trip planner", %{ - conn: conn - } do - plan_params = %{"plan" => %{"from" => "from address", "to" => "to address"}} - - conn = - get( - conn, - trip_plan_path(conn, :to, "Address", plan_params) - ) - - assert redirected_to(conn) == trip_plan_path(conn, :index, plan_params) - end - end -end diff --git a/test/dotcom_web/live/trip_planner_test.exs b/test/dotcom_web/live/trip_planner_test.exs index dedbc30e6f..f0617ef1d2 100644 --- a/test/dotcom_web/live/trip_planner_test.exs +++ b/test/dotcom_web/live/trip_planner_test.exs @@ -62,23 +62,11 @@ defmodule DotcomWeb.Live.TripPlannerTest do end) end - test "Preview version behind basic auth", %{conn: conn} do - conn = get(conn, ~p"/preview/trip-planner") - - {_header_name, header_value} = List.keyfind(conn.resp_headers, "www-authenticate", 0) - assert conn.status == 401 - assert header_value =~ "Basic" - end - describe "Trip Planner" do setup %{conn: conn} do - [username: username, password: password] = - Application.get_env(:dotcom, DotcomWeb.Router)[:basic_auth_readonly] - {:ok, view, html} = conn - |> put_req_header("authorization", "Basic " <> Base.encode64("#{username}:#{password}")) - |> live(~p"/preview/trip-planner") + |> live(~p"/trip-planner") %{html: html, view: view} end @@ -154,7 +142,7 @@ defmodule DotcomWeb.Live.TripPlannerTest do stub_otp_results([]) - {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}") + {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") assert render_async(view) =~ "No trips found" end @@ -190,7 +178,7 @@ defmodule DotcomWeb.Live.TripPlannerTest do test "starts out with no 'View All Options' button", %{conn: conn, params: params} do stub_populated_otp_results() - {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}") + {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") refute render_async(view) =~ "View All Options" end @@ -198,7 +186,7 @@ defmodule DotcomWeb.Live.TripPlannerTest do test "clicking 'Details' button opens details view", %{conn: conn, params: params} do stub_populated_otp_results() - {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}") + {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") render_async(view) view |> element("button[phx-value-index=\"0\"]", "Details") |> render_click() @@ -212,7 +200,7 @@ defmodule DotcomWeb.Live.TripPlannerTest do } do stub_populated_otp_results() - {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}") + {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") render_async(view) @@ -244,7 +232,7 @@ defmodule DotcomWeb.Live.TripPlannerTest do } end) - {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}") + {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") render_async(view) @@ -278,7 +266,7 @@ defmodule DotcomWeb.Live.TripPlannerTest do } end) - {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}") + {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") render_async(view) @@ -309,7 +297,7 @@ defmodule DotcomWeb.Live.TripPlannerTest do } end) - {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}") + {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") render_async(view) @@ -328,7 +316,7 @@ defmodule DotcomWeb.Live.TripPlannerTest do } do stub_otp_results([]) - {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}") + {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") assert render_async(view) =~ "No trips found." end @@ -340,7 +328,7 @@ defmodule DotcomWeb.Live.TripPlannerTest do {:error, [%OpenTripPlannerClient.Error{message: error_message}]} end) - {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}") + {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") assert render_async(view) =~ error_message end @@ -353,7 +341,7 @@ defmodule DotcomWeb.Live.TripPlannerTest do {:error, [%OpenTripPlannerClient.Error{message: Faker.Lorem.sentence()}]} end) - {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}") + {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") refute render_async(view) =~ "No trips found." end diff --git a/test/dotcom_web/router_test.exs b/test/dotcom_web/router_test.exs index 9fdb9bd782..2caf6891d4 100644 --- a/test/dotcom_web/router_test.exs +++ b/test/dotcom_web/router_test.exs @@ -114,11 +114,6 @@ defmodule Phoenix.Router.RoutingTest do assert redirected_to(conn, 301) == "/betterbus-440s" end - test "trip planner with 'to' but without an address", %{conn: conn} do - conn = get(conn, "/trip-planner/to/") - assert redirected_to(conn, 301) == "/trip-planner" - end - test "redirect to canonical host securely", %{conn: conn} do System.put_env("HOST", @canonical_host)