From ba954257e8940f55ba3e686f14b3ae4f7f38d27e Mon Sep 17 00:00:00 2001 From: Anthony Shull Date: Mon, 6 Jan 2025 10:21:44 -0600 Subject: [PATCH 01/36] first pass at swapping in new trip planner --- .../scenarios/plan-a-trip-from-homepage.js | 2 +- integration/scenarios/plan-a-trip.js | 24 +- .../controllers/places_controller.ex | 20 +- .../controllers/trip_plan_controller.ex | 606 +++---- lib/dotcom_web/controllers/vote_controller.ex | 130 +- lib/dotcom_web/live/trip_planner.ex | 2 +- lib/dotcom_web/router.ex | 13 +- .../templates/fare/_nearby_locations.html.eex | 2 +- .../templates/layout/live.html.heex | 3 + lib/dotcom_web/templates/vote/show.html.heex | 4 +- lib/dotcom_web/views/vote_view.ex | 3 +- .../controllers/trip_plan_controller_test.exs | 1436 ++++++++--------- 12 files changed, 1125 insertions(+), 1120 deletions(-) create mode 100644 lib/dotcom_web/templates/layout/live.html.heex 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/places_controller.ex b/lib/dotcom_web/controllers/places_controller.ex index e997284c7c..931e0b2346 100644 --- a/lib/dotcom_web/controllers/places_controller.ex +++ b/lib/dotcom_web/controllers/places_controller.ex @@ -155,16 +155,16 @@ defmodule DotcomWeb.PlacesController do Map.take(map, [:latitude, :longitude]) end - vote_params = - case map do - %{formatted: formatted} -> - map - |> Map.take([:latitude, :longitude]) - |> Map.put(:address, formatted) + # vote_params = + # case map do + # %{formatted: formatted} -> + # map + # |> Map.take([:latitude, :longitude]) + # |> Map.put(:address, formatted) - _ -> - %{} - end + # _ -> + # %{} + # end map |> Map.put_new(:urls, %{ @@ -177,7 +177,7 @@ defmodule DotcomWeb.PlacesController do params ), "transit-near-me" => transit_near_me_path(DotcomWeb.Endpoint, :index, params), - "vote" => vote_path(DotcomWeb.Endpoint, :show, vote_params) + # "vote" => vote_path(DotcomWeb.Endpoint, :show, vote_params) }) end diff --git a/lib/dotcom_web/controllers/trip_plan_controller.ex b/lib/dotcom_web/controllers/trip_plan_controller.ex index cc60b210af..1fac9a0593 100644 --- a/lib/dotcom_web/controllers/trip_plan_controller.ex +++ b/lib/dotcom_web/controllers/trip_plan_controller.ex @@ -3,307 +3,307 @@ defmodule DotcomWeb.TripPlanController do 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 + # 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..e887c9acea 100644 --- a/lib/dotcom_web/controllers/vote_controller.ex +++ b/lib/dotcom_web/controllers/vote_controller.ex @@ -3,81 +3,81 @@ defmodule DotcomWeb.VoteController do Handles rendering the vote widget """ - use DotcomWeb, :controller + # use DotcomWeb, :controller - import DotcomWeb.ViewHelpers, only: [cms_static_page_path: 2] + # import DotcomWeb.ViewHelpers, only: [cms_static_page_path: 2] - plug(:meta_description) - plug(:clear_polling_results) + # plug(:meta_description) + # plug(:clear_polling_results) - def show( - conn, - %{"address" => address, "latitude" => latitude, "longitude" => longitude} = _params - ) do - google_api_key = Application.get_env(:dotcom, :google_api_key) + # def show( + # conn, + # %{"address" => address, "latitude" => latitude, "longitude" => longitude} = _params + # ) do + # google_api_key = Application.get_env(:dotcom, :google_api_key) - response = - Req.get("https://www.googleapis.com/civicinfo/v2/voterinfo", - params: [ - key: google_api_key, - electionId: "9000", - address: address - ] - ) + # response = + # Req.get("https://www.googleapis.com/civicinfo/v2/voterinfo", + # params: [ + # key: google_api_key, + # electionId: "9000", + # address: address + # ] + # ) - conn = - case response do - {:ok, %{body: %{"pollingLocations" => [polling_location | _]}}} -> - polling_location_name = Recase.to_title(polling_location["address"]["locationName"]) + # conn = + # case response do + # {:ok, %{body: %{"pollingLocations" => [polling_location | _]}}} -> + # polling_location_name = Recase.to_title(polling_location["address"]["locationName"]) - params = %{ - "plan" => %{ - "from_latitude" => latitude, - "from_longitude" => longitude, - "from" => address, - "to_latitude" => polling_location["latitude"], - "to_longitude" => polling_location["longitude"], - "to" => polling_location_name - } - } + # params = %{ + # "plan" => %{ + # "from_latitude" => latitude, + # "from_longitude" => longitude, + # "from" => address, + # "to_latitude" => polling_location["latitude"], + # "to_longitude" => polling_location["longitude"], + # "to" => polling_location_name + # } + # } - conn - |> assign(:polling_location, polling_location) - |> assign(:polling_location_name, polling_location_name) - |> assign(:trip_plan_path, trip_plan_path(DotcomWeb.Endpoint, :index, params)) + # conn + # |> assign(:polling_location, polling_location) + # |> assign(:polling_location_name, polling_location_name) + # |> assign(:trip_plan_path, trip_plan_path(DotcomWeb.Endpoint, :index, params)) - _ -> - conn |> assign(:polling_error, true) - end + # _ -> + # conn |> assign(:polling_error, true) + # end - conn - |> assign(:should_scroll, true) - |> assign(:breadcrumbs, [ - Breadcrumb.build("Take the T to Vote", cms_static_page_path(conn, "/vote")) - ]) - |> render("show.html") - end + # conn + # |> assign(:should_scroll, true) + # |> assign(:breadcrumbs, [ + # Breadcrumb.build("Take the T to Vote", cms_static_page_path(conn, "/vote")) + # ]) + # |> render("show.html") + # end - def show(conn, _params) do - conn - |> assign(:breadcrumbs, [ - Breadcrumb.build("Vote", cms_static_page_path(conn, "/vote")) - ]) - |> render("show.html") - end + # def show(conn, _params) do + # conn + # |> assign(:breadcrumbs, [ + # Breadcrumb.build("Vote", cms_static_page_path(conn, "/vote")) + # ]) + # |> render("show.html") + # end - defp meta_description(conn, _) do - conn - |> assign( - :meta_description, - "Tuesday, November 5 is the last day to vote in the 2024 general election. Use the T to get to your polling location." - ) - end + # defp meta_description(conn, _) do + # conn + # |> assign( + # :meta_description, + # "Tuesday, November 5 is the last day to vote in the 2024 general election. Use the T to get to your polling location." + # ) + # end - defp clear_polling_results(conn, _) do - conn - |> assign(:polling_location, nil) - |> assign(:polling_error, false) - |> assign(:should_scroll, false) - end + # defp clear_polling_results(conn, _) do + # conn + # |> assign(:polling_location, nil) + # |> assign(:polling_error, false) + # |> assign(:should_scroll, false) + # end end diff --git a/lib/dotcom_web/live/trip_planner.ex b/lib/dotcom_web/live/trip_planner.ex index d547406698..a12d37ee14 100644 --- a/lib/dotcom_web/live/trip_planner.ex +++ b/lib/dotcom_web/live/trip_planner.ex @@ -70,7 +70,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 1fadd9a87b..dc5a814892 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) @@ -242,7 +237,7 @@ defmodule DotcomWeb.Router do post("/search/query", SearchController, :query) post("/search/click", SearchController, :click) get("/bus-stop-changes", BusStopChangeController, :show) - get("/vote", VoteController, :show) + # get("/vote", VoteController, :show) end scope "/", DotcomWeb do @@ -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) live("/trip-planner/from/:place", Live.TripPlanner, :from) live("/trip-planner/to/:place", Live.TripPlanner, :to) 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 @@
- + Plan your trip
@@ -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/lib/dotcom_web/views/vote_view.ex b/lib/dotcom_web/views/vote_view.ex index e05f122a07..f13877a231 100644 --- a/lib/dotcom_web/views/vote_view.ex +++ b/lib/dotcom_web/views/vote_view.ex @@ -2,5 +2,6 @@ defmodule DotcomWeb.VoteView do @moduledoc """ View for the vote widget """ - use DotcomWeb, :view + + # use DotcomWeb, :view end diff --git a/test/dotcom_web/controllers/trip_plan_controller_test.exs b/test/dotcom_web/controllers/trip_plan_controller_test.exs index 0e2cb30203..547fe4b681 100644 --- a/test/dotcom_web/controllers/trip_plan_controller_test.exs +++ b/test/dotcom_web/controllers/trip_plan_controller_test.exs @@ -1,720 +1,720 @@ 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 + # 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 From 7c9577155f42d45e17a7e7dbabb3dac9c8ae72bc Mon Sep 17 00:00:00 2001 From: Anthony Shull Date: Mon, 6 Jan 2025 10:36:22 -0600 Subject: [PATCH 02/36] restore and remove --- .../controllers/places_controller.ex | 20 +- .../controllers/trip_plan_controller.ex | 309 -------- lib/dotcom_web/controllers/vote_controller.ex | 130 ++-- lib/dotcom_web/router.ex | 2 +- .../templates/layout/live.html.heex | 2 +- lib/dotcom_web/views/vote_view.ex | 3 +- .../controllers/trip_plan_controller_test.exs | 720 ------------------ 7 files changed, 78 insertions(+), 1108 deletions(-) delete mode 100644 lib/dotcom_web/controllers/trip_plan_controller.ex delete mode 100644 test/dotcom_web/controllers/trip_plan_controller_test.exs diff --git a/lib/dotcom_web/controllers/places_controller.ex b/lib/dotcom_web/controllers/places_controller.ex index 931e0b2346..e997284c7c 100644 --- a/lib/dotcom_web/controllers/places_controller.ex +++ b/lib/dotcom_web/controllers/places_controller.ex @@ -155,16 +155,16 @@ defmodule DotcomWeb.PlacesController do Map.take(map, [:latitude, :longitude]) end - # vote_params = - # case map do - # %{formatted: formatted} -> - # map - # |> Map.take([:latitude, :longitude]) - # |> Map.put(:address, formatted) + vote_params = + case map do + %{formatted: formatted} -> + map + |> Map.take([:latitude, :longitude]) + |> Map.put(:address, formatted) - # _ -> - # %{} - # end + _ -> + %{} + end map |> Map.put_new(:urls, %{ @@ -177,7 +177,7 @@ defmodule DotcomWeb.PlacesController do params ), "transit-near-me" => transit_near_me_path(DotcomWeb.Endpoint, :index, params), - # "vote" => vote_path(DotcomWeb.Endpoint, :show, vote_params) + "vote" => vote_path(DotcomWeb.Endpoint, :show, vote_params) }) end 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 1fac9a0593..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 e887c9acea..0283175d76 100644 --- a/lib/dotcom_web/controllers/vote_controller.ex +++ b/lib/dotcom_web/controllers/vote_controller.ex @@ -3,81 +3,81 @@ defmodule DotcomWeb.VoteController do Handles rendering the vote widget """ - # use DotcomWeb, :controller + use DotcomWeb, :controller - # import DotcomWeb.ViewHelpers, only: [cms_static_page_path: 2] + import DotcomWeb.ViewHelpers, only: [cms_static_page_path: 2] - # plug(:meta_description) - # plug(:clear_polling_results) + plug(:meta_description) + plug(:clear_polling_results) - # def show( - # conn, - # %{"address" => address, "latitude" => latitude, "longitude" => longitude} = _params - # ) do - # google_api_key = Application.get_env(:dotcom, :google_api_key) + def show( + conn, + %{"address" => address, "latitude" => latitude, "longitude" => longitude} = _params + ) do + google_api_key = Application.get_env(:dotcom, :google_api_key) - # response = - # Req.get("https://www.googleapis.com/civicinfo/v2/voterinfo", - # params: [ - # key: google_api_key, - # electionId: "9000", - # address: address - # ] - # ) + response = + Req.get("https://www.googleapis.com/civicinfo/v2/voterinfo", + params: [ + key: google_api_key, + electionId: "9000", + address: address + ] + ) - # conn = - # case response do - # {:ok, %{body: %{"pollingLocations" => [polling_location | _]}}} -> - # polling_location_name = Recase.to_title(polling_location["address"]["locationName"]) + conn = + case response do + {:ok, %{body: %{"pollingLocations" => [polling_location | _]}}} -> + polling_location_name = Recase.to_title(polling_location["address"]["locationName"]) - # params = %{ - # "plan" => %{ - # "from_latitude" => latitude, - # "from_longitude" => longitude, - # "from" => address, - # "to_latitude" => polling_location["latitude"], - # "to_longitude" => polling_location["longitude"], - # "to" => polling_location_name - # } - # } + params = %{ + "plan" => %{ + "from_latitude" => latitude, + "from_longitude" => longitude, + "from" => address, + "to_latitude" => polling_location["latitude"], + "to_longitude" => polling_location["longitude"], + "to" => polling_location_name + } + } - # conn - # |> assign(:polling_location, polling_location) - # |> assign(:polling_location_name, polling_location_name) - # |> assign(:trip_plan_path, trip_plan_path(DotcomWeb.Endpoint, :index, params)) + conn + |> assign(:polling_location, polling_location) + |> assign(:polling_location_name, polling_location_name) + |> assign(:trip_plan_path, "/trip-planner#{URI.encode_query(params)}") - # _ -> - # conn |> assign(:polling_error, true) - # end + _ -> + conn |> assign(:polling_error, true) + end - # conn - # |> assign(:should_scroll, true) - # |> assign(:breadcrumbs, [ - # Breadcrumb.build("Take the T to Vote", cms_static_page_path(conn, "/vote")) - # ]) - # |> render("show.html") - # end + conn + |> assign(:should_scroll, true) + |> assign(:breadcrumbs, [ + Breadcrumb.build("Take the T to Vote", cms_static_page_path(conn, "/vote")) + ]) + |> render("show.html") + end - # def show(conn, _params) do - # conn - # |> assign(:breadcrumbs, [ - # Breadcrumb.build("Vote", cms_static_page_path(conn, "/vote")) - # ]) - # |> render("show.html") - # end + def show(conn, _params) do + conn + |> assign(:breadcrumbs, [ + Breadcrumb.build("Vote", cms_static_page_path(conn, "/vote")) + ]) + |> render("show.html") + end - # defp meta_description(conn, _) do - # conn - # |> assign( - # :meta_description, - # "Tuesday, November 5 is the last day to vote in the 2024 general election. Use the T to get to your polling location." - # ) - # end + defp meta_description(conn, _) do + conn + |> assign( + :meta_description, + "Tuesday, November 5 is the last day to vote in the 2024 general election. Use the T to get to your polling location." + ) + end - # defp clear_polling_results(conn, _) do - # conn - # |> assign(:polling_location, nil) - # |> assign(:polling_error, false) - # |> assign(:should_scroll, false) - # end + defp clear_polling_results(conn, _) do + conn + |> assign(:polling_location, nil) + |> assign(:polling_error, false) + |> assign(:should_scroll, false) + end end diff --git a/lib/dotcom_web/router.ex b/lib/dotcom_web/router.ex index dc5a814892..f76dd2ff9b 100644 --- a/lib/dotcom_web/router.ex +++ b/lib/dotcom_web/router.ex @@ -237,7 +237,7 @@ defmodule DotcomWeb.Router do post("/search/query", SearchController, :query) post("/search/click", SearchController, :click) get("/bus-stop-changes", BusStopChangeController, :show) - # get("/vote", VoteController, :show) + get("/vote", VoteController, :show) end scope "/", DotcomWeb do diff --git a/lib/dotcom_web/templates/layout/live.html.heex b/lib/dotcom_web/templates/layout/live.html.heex index d4b5ad37e3..01eded9c98 100644 --- a/lib/dotcom_web/templates/layout/live.html.heex +++ b/lib/dotcom_web/templates/layout/live.html.heex @@ -1,3 +1,3 @@
{@inner_content} -
\ No newline at end of file + diff --git a/lib/dotcom_web/views/vote_view.ex b/lib/dotcom_web/views/vote_view.ex index f13877a231..e05f122a07 100644 --- a/lib/dotcom_web/views/vote_view.ex +++ b/lib/dotcom_web/views/vote_view.ex @@ -2,6 +2,5 @@ defmodule DotcomWeb.VoteView do @moduledoc """ View for the vote widget """ - - # use DotcomWeb, :view + use DotcomWeb, :view end 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 547fe4b681..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 From 27a6b128cfd6a75072d0ae07eeb9a8628ab3a8b1 Mon Sep 17 00:00:00 2001 From: Anthony Shull Date: Mon, 6 Jan 2025 10:46:30 -0600 Subject: [PATCH 03/36] fix vote paths --- lib/dotcom_web/templates/vote/show.html.heex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dotcom_web/templates/vote/show.html.heex b/lib/dotcom_web/templates/vote/show.html.heex index 67f41d4321..337089fca1 100644 --- a/lib/dotcom_web/templates/vote/show.html.heex +++ b/lib/dotcom_web/templates/vote/show.html.heex @@ -42,7 +42,7 @@ {@polling_location["address"]["city"]}, {@polling_location["address"]["state"]} {@polling_location["address"]["zip"]} - + Plan your trip @@ -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. From 2dbebe2a672015e94db57b18474c699c8233cfe1 Mon Sep 17 00:00:00 2001 From: Anthony Shull Date: Mon, 6 Jan 2025 11:08:07 -0600 Subject: [PATCH 04/36] tests --- test/dotcom_web/live/trip_planner_test.exs | 34 +++++++--------------- test/dotcom_web/router_test.exs | 5 ---- 2 files changed, 11 insertions(+), 28 deletions(-) diff --git a/test/dotcom_web/live/trip_planner_test.exs b/test/dotcom_web/live/trip_planner_test.exs index 7c4093c397..5ed69eb21d 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 @@ -197,7 +185,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 @@ -233,7 +221,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 @@ -241,7 +229,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() @@ -255,7 +243,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) @@ -287,7 +275,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) @@ -321,7 +309,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) @@ -352,7 +340,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) @@ -371,7 +359,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 @@ -383,7 +371,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 @@ -396,7 +384,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) From 59415c7e66bcfb572b3f1bb7c8d5ef4352b01588 Mon Sep 17 00:00:00 2001 From: Anthony Shull Date: Mon, 6 Jan 2025 11:28:27 -0600 Subject: [PATCH 05/36] remove the smoke test --- cypress/e2e/smoke.cy.js | 24 ------------------------ 1 file changed, 24 deletions(-) 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(); From 4f8a0cc9bba93d8499f875d706044d57edebfe11 Mon Sep 17 00:00:00 2001 From: Anthony Shull Date: Tue, 7 Jan 2025 10:13:49 -0600 Subject: [PATCH 06/36] add banner --- lib/dotcom_web/templates/layout/root.html.eex | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/dotcom_web/templates/layout/root.html.eex b/lib/dotcom_web/templates/layout/root.html.eex index 5b8e525634..3315487e7b 100644 --- a/lib/dotcom_web/templates/layout/root.html.eex +++ b/lib/dotcom_web/templates/layout/root.html.eex @@ -74,6 +74,17 @@ <% end %> + <%= if assigns[:search_header?] do %> <%= render "_searchbar.html", assigns %> <% end %> From fbdaf9c84a8da3049ae83961aa947d10b5ef671e Mon Sep 17 00:00:00 2001 From: Anthony Shull Date: Tue, 7 Jan 2025 10:17:39 -0600 Subject: [PATCH 07/36] remove preview from admin page --- lib/dotcom_web/live/admin.ex | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/dotcom_web/live/admin.ex b/lib/dotcom_web/live/admin.ex index b14b290b56..39b674ce5e 100644 --- a/lib/dotcom_web/live/admin.ex +++ b/lib/dotcom_web/live/admin.ex @@ -12,11 +12,6 @@ defmodule DotcomWeb.Live.Admin do 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." - }, %{ url: Helpers.live_path(socket, DotcomWeb.Live.SystemStatus), title: "System Status Widget Preview", From 43c9479e2997e795db3ca988a67106238be4af01 Mon Sep 17 00:00:00 2001 From: Anthony Shull Date: Thu, 16 Jan 2025 21:12:04 -0600 Subject: [PATCH 08/36] tests --- lib/dotcom_web/router.ex | 2 +- test/dotcom_web/live/trip_planner_test.exs | 477 ++++++--------------- 2 files changed, 123 insertions(+), 356 deletions(-) diff --git a/lib/dotcom_web/router.ex b/lib/dotcom_web/router.ex index f76dd2ff9b..aa8e7b2c17 100644 --- a/lib/dotcom_web/router.ex +++ b/lib/dotcom_web/router.ex @@ -271,7 +271,7 @@ defmodule DotcomWeb.Router do scope "/preview", DotcomWeb do import Phoenix.LiveView.Router - pipe_through([:browser, :browser_live, :basic_auth_readonly]) + pipe_through([:browser, :browser_live]) live_session :system_status, layout: {DotcomWeb.LayoutView, :preview} do live "/system-status", Live.SystemStatus diff --git a/test/dotcom_web/live/trip_planner_test.exs b/test/dotcom_web/live/trip_planner_test.exs index 5ed69eb21d..61a4e03ebf 100644 --- a/test/dotcom_web/live/trip_planner_test.exs +++ b/test/dotcom_web/live/trip_planner_test.exs @@ -9,384 +9,151 @@ defmodule DotcomWeb.Live.TripPlannerTest do setup :verify_on_exit! - defp stub_otp_results(itineraries) do - expect(OpenTripPlannerClient.Mock, :plan, fn _ -> - {:ok, %OpenTripPlannerClient.Plan{itineraries: itineraries}} - end) + setup %{conn: conn} do + {:ok, view, html} = live(conn, ~p"/preview/trip-planner") - # For certain routes, Dotcom.TripPlan.Alerts.mode_entities/1 is called to help fetch the associated alerts - stub(MBTA.Api.Mock, :get_json, fn "/trips/" <> id, [] -> - %JsonApi{ - data: [ - Test.Support.Factories.MBTA.Api.build(:trip_item, %{id: id}) - ] - } - end) + %{html: html, view: view} end - defp stub_populated_otp_results do - itineraries = TripPlannerFactory.build_list(3, :otp_itinerary) + @valid_params %{ + "from" => %{ + "latitude" => Faker.Address.latitude(), + "longitude" => Faker.Address.longitude() + }, + "to" => %{ + "latitude" => Faker.Address.latitude(), + "longitude" => Faker.Address.longitude() + } + } + + # MOUNT - stub_otp_results(itineraries) + # INPUTS + test "setting 'from' places a pin on the map", %{view: view} do + # Setup + params = Map.take(@valid_params, ["from"]) + + # Exercise + view |> element("form") |> render_change(%{"input_form" => params}) + + # Verify + document = render(view) |> Floki.parse_document!() + + assert Floki.get_by_id(document, "mbta-metro-pin-0") end - # For a list of headsigns, create a bunch of itineraries that would be grouped - # by the Trip Planner's logic - defp grouped_itineraries_from_headsigns([initial_headsign | _] = headsigns) do - # Only MBTA transit legs show the headsigns right now, so ensure the - # generated legs are MBTA-only - base_leg = - Factory.build(:transit_leg, %{ - agency: Factory.build(:agency, %{name: "MBTA"}), - route: - Factory.build(:route, %{gtfs_id: "mbta-ma-us:internal", type: Faker.Util.pick(0..4)}), - trip: - Factory.build(:trip, %{ - direction_id: Faker.Util.pick(["0", "1"]), - trip_headsign: initial_headsign - }) - }) - - base_itinerary = Factory.build(:itinerary, legs: [base_leg]) - - headsigns - |> Enum.with_index() - |> Enum.map(fn {headsign, index} -> - leg = update_in(base_leg, [:trip, :trip_headsign], fn _ -> headsign end) - - %{ - base_itinerary - | legs: [leg], - start: Timex.shift(base_itinerary.start, minutes: 10 * index) - } - end) + test "setting 'to' places a pin on the map", %{view: view} do + # Setup + params = Map.take(@valid_params, ["to"]) + + # Exercise + view |> element("form") |> render_change(%{"input_form" => params}) + + # Verify + document = render(view) |> Floki.parse_document!() + + assert Floki.get_by_id(document, "mbta-metro-pin-1") end - describe "Trip Planner" do - setup %{conn: conn} do - {:ok, view, html} = - conn - |> live(~p"/trip-planner") - - %{html: html, view: view} - end - - # test "toggles the date input when changing from 'now'", %{html: html, view: view} do - # end - - test "summarizes the selected modes", %{view: view, html: html} do - assert html =~ "All modes" - - html = - view - |> element("form") - |> render_change(%{ - _target: ["input_form", "modes"], - input_form: %{modes: %{RAIL: true, SUBWAY: true, FERRY: true, BUS: false}} - }) - - assert html =~ "Commuter Rail, Subway, and Ferry" - - html = - view - |> element("form") - |> render_change(%{ - _target: ["input_form", "modes"], - input_form: %{modes: %{SUBWAY: true, BUS: false, RAIL: false, FERRY: false}} - }) - - assert html =~ "Subway Only" - - html = - view - |> element("form") - |> render_change(%{ - _target: ["input_form", "modes"], - input_form: %{modes: %{SUBWAY: true, BUS: true, RAIL: false, FERRY: false}} - }) - - assert html =~ "Subway and Bus" - end - - # test "shows errors on form submit", %{view: view} do - # end - - # test "pushes updated location to the map", %{view: view} do - # end + test "selecting a time other than 'now' shows the datepicker", %{view: view} do + # Setup + expect(MBTA.Api.Mock, :get_json, fn _, _ -> {:ok, %{}} end) + + params = %{ + "datetime_type" => "depart_at" + } + + # Exercise + view |> element("form") |> render_change(%{"input_form" => params}) + + # Verify + document = render(view) |> Floki.parse_document!() + + assert Floki.get_by_id(document, "date-picker") end - describe "Trip Planner location validations" do - setup %{conn: conn} do - [username: username, password: password] = - Application.get_env(:dotcom, DotcomWeb.Router)[:basic_auth_readonly] - - %{ - conn: - conn - |> put_req_header("authorization", "Basic " <> Base.encode64("#{username}:#{password}")) - } - end - - test "shows error if origin and destination are the same", %{conn: conn} do - latitude = Faker.Address.latitude() - longitude = Faker.Address.longitude() - - params = %{ - "plan" => %{ - "from_latitude" => "#{latitude}", - "from_longitude" => "#{longitude}", - "to_latitude" => "#{latitude}", - "to_longitude" => "#{longitude}" - } - } - - {:ok, view, _html} = - conn - |> live(~p"/preview/trip-planner?#{params}") - - assert render_async(view) =~ - "Please select a destination at a different location from the origin." - end - - test "does not show errors if origin or destination are missing", %{conn: conn} do - {:ok, view, _html} = - conn - |> live(~p"/preview/trip-planner") - - refute render_async(view) =~ "Please specify an origin location." - refute render_async(view) =~ "Please add a destination." - end + test "selecting 'now' after selecting another time hides the datepicker", %{view: view} do + # Setup + expect(MBTA.Api.Mock, :get_json, fn _, _ -> {:ok, %{}} end) + + open_params = %{ + "datetime_type" => "depart_at" + } + + closed_params = %{ + "datetime_type" => "now" + } + + # Exercise + view |> element("form") |> render_change(%{"input_form" => open_params}) + view |> element("form") |> render_change(%{"input_form" => closed_params}) + + # Verify + document = render(view) |> Floki.parse_document!() + + refute Floki.get_by_id(document, "date-picker") end - describe "Trip Planner with no results" do - setup %{conn: conn} do - [username: username, password: password] = - Application.get_env(:dotcom, DotcomWeb.Router)[:basic_auth_readonly] - - %{ - conn: - put_req_header( - conn, - "authorization", - "Basic " <> Base.encode64("#{username}:#{password}") - ) - } - end - - test "shows 'No trips found' text", %{conn: conn} do - params = %{ - "plan" => %{ - "from_latitude" => "#{Faker.Address.latitude()}", - "from_longitude" => "#{Faker.Address.longitude()}", - "to_latitude" => "#{Faker.Address.latitude()}", - "to_longitude" => "#{Faker.Address.longitude()}" - } - } - - stub_otp_results([]) - - {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") - - assert render_async(view) =~ "No trips found" - end + test "setting 'from' and 'to' to the same location shows an error message", %{view: view} do + # Setup + params = Map.put(@valid_params, "to", Map.get(@valid_params, "from")) + + # Exercise + view |> element("form") |> render_change(%{"input_form" => params}) + + # Verify + html = render(view) + + assert html =~ "Please select a destination at a different location from the origin." end - describe "Trip Planner with results" do - setup %{conn: conn} do - [username: username, password: password] = - Application.get_env(:dotcom, DotcomWeb.Router)[:basic_auth_readonly] - - stub(Stops.Repo.Mock, :get, fn _ -> - Test.Support.Factories.Stops.Stop.build(:stop) - end) - - %{ - conn: - put_req_header( - conn, - "authorization", - "Basic " <> Base.encode64("#{username}:#{password}") - ), - params: %{ - "plan" => %{ - "from_latitude" => "#{Faker.Address.latitude()}", - "from_longitude" => "#{Faker.Address.longitude()}", - "to_latitude" => "#{Faker.Address.latitude()}", - "to_longitude" => "#{Faker.Address.longitude()}" - } - } - } - end - - test "starts out with no 'View All Options' button", %{conn: conn, params: params} do - stub_populated_otp_results() - - {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") - - refute render_async(view) =~ "View All Options" - end - - test "clicking 'Details' button opens details view", %{conn: conn, params: params} do - stub_populated_otp_results() - - {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") - - render_async(view) - view |> element("button[phx-value-index=\"0\"]", "Details") |> render_click() - - assert render_async(view) =~ "View All Options" - end - - test "clicking 'View All Options' button from details view closes it", %{ - conn: conn, - params: params - } do - stub_populated_otp_results() - - {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") - - render_async(view) - - view |> element("button[phx-value-index=\"0\"]", "Details") |> render_click() - view |> element("button", "View All Options") |> render_click() - - refute render_async(view) =~ "View All Options" - end - - test "'Depart At' buttons toggle which itinerary to show", %{ - conn: conn, - params: params - } do - trip_headsign_1 = "Headsign1" - trip_headsign_2 = "Headsign2" - - expect(OpenTripPlannerClient.Mock, :plan, fn _ -> - {:ok, - %OpenTripPlannerClient.Plan{ - itineraries: grouped_itineraries_from_headsigns([trip_headsign_1, trip_headsign_2]) - }} - end) - - stub(MBTA.Api.Mock, :get_json, fn "/trips/" <> id, [] -> - %JsonApi{ - data: [ - Test.Support.Factories.MBTA.Api.build(:trip_item, %{id: id}) - ] - } - end) - - {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") - - render_async(view) - - view |> element("button", "Details") |> render_click() - - assert render_async(view) =~ trip_headsign_1 - refute render_async(view) =~ trip_headsign_2 - - view |> element("#itinerary-detail-departure-times button:last-child") |> render_click() - - assert render_async(view) =~ trip_headsign_2 - refute render_async(view) =~ trip_headsign_1 - end - - test "'Depart At' buttons don't appear if there would only be one", %{ - conn: conn, - params: params - } do - expect(OpenTripPlannerClient.Mock, :plan, fn _ -> - {:ok, - %OpenTripPlannerClient.Plan{ - itineraries: TripPlannerFactory.build_list(1, :otp_itinerary) - }} - end) - - stub(MBTA.Api.Mock, :get_json, fn "/trips/" <> id, [] -> - %JsonApi{ - data: [ - Test.Support.Factories.MBTA.Api.build(:trip_item, %{id: id}) - ] - } - end) - - {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") + test "using valid params shows results", %{view: view} do + # Setup + expect(OpenTripPlannerClient.Mock, :plan, fn _ -> + itineraries = TripPlannerFactory.build_list(1, :otp_itinerary) + + {:ok, %OpenTripPlannerClient.Plan{itineraries: itineraries}} + end) + + # Exercise + view |> element("form") |> render_change(%{"input_form" => @valid_params}) - render_async(view) + # Verify + document = render(view) |> Floki.parse_document!() - view |> element("button", "Details") |> render_click() + assert Floki.get_by_id(document, "trip-planner-results") + end - refute view |> element("#itinerary-detail-departure-times") |> has_element?() - end + test "an OTP connection error shows up as an error message", %{view: view} do + # Setup + expect(OpenTripPlannerClient.Mock, :plan, fn _ -> + {:error, %Req.TransportError{reason: :econnrefused}} + end) - test "'Depart At' button state is not preserved when leaving details view", %{ - conn: conn, - params: params - } do - trip_headsign_1 = "Headsign1" - trip_headsign_2 = "Headsign2" + # Exercise + view |> element("form") |> render_change(%{"input_form" => @valid_params}) - expect(OpenTripPlannerClient.Mock, :plan, fn _ -> - {:ok, - %OpenTripPlannerClient.Plan{ - itineraries: grouped_itineraries_from_headsigns([trip_headsign_1, trip_headsign_2]) - }} - end) - - stub(MBTA.Api.Mock, :get_json, fn "/trips/" <> id, [] -> - %JsonApi{ - data: [ - Test.Support.Factories.MBTA.Api.build(:trip_item, %{id: id}) - ] - } - end) - - {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") + html = render(view) - render_async(view) - - view |> element("button", "Details") |> render_click() - view |> element("#itinerary-detail-departure-times button:last-child") |> render_click() - view |> element("button", "View All Options") |> render_click() - view |> element("button", "Details") |> render_click() - - assert render_async(view) =~ trip_headsign_1 - refute render_async(view) =~ trip_headsign_2 - end - - test "displays 'No trips found.' if given an empty list of itineraries", %{ - conn: conn, - params: params - } do - stub_otp_results([]) - - {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") - - assert render_async(view) =~ "No trips found." - end - - test "displays error message from the Open Trip Planner client", %{conn: conn, params: params} do - error_message = Faker.Lorem.sentence() + assert html =~ "Cannot connect to OpenTripPlanner. Please try again later." + end + + test "an OTP error shows up as an error message", %{view: view} do + # Setup + error = Faker.Company.bullshit() + + expect(OpenTripPlannerClient.Mock, :plan, fn _ -> + {:error, [%{message: error}]} + end) - expect(OpenTripPlannerClient.Mock, :plan, fn _ -> - {:error, [%OpenTripPlannerClient.Error{message: error_message}]} - end) + # Exercise + view |> element("form") |> render_change(%{"input_form" => @valid_params}) - {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") + # Verify + html = render(view) - assert render_async(view) =~ error_message - end + open_browser(view) - test "does not display 'No trips found.' if there's another error", %{ - conn: conn, - params: params - } do - expect(OpenTripPlannerClient.Mock, :plan, fn _ -> - {:error, [%OpenTripPlannerClient.Error{message: Faker.Lorem.sentence()}]} - end) - - {:ok, view, _html} = live(conn, ~p"/trip-planner?#{params}") - - refute render_async(view) =~ "No trips found." - end + assert html =~ error end end From 141fea181b51f412f0d4adf0e85a21f91d06209f Mon Sep 17 00:00:00 2001 From: Anthony Shull Date: Fri, 17 Jan 2025 13:42:03 -0600 Subject: [PATCH 09/36] tests and factories --- .../trip_planner/itinerary_detail.ex | 46 +++ .../components/trip_planner/results.ex | 2 + .../trip_planner/results_summary.ex | 4 +- test/dotcom_web/live/trip_planner_test.exs | 289 ++++++++++++------ .../factories/trip_planner/trip_planner.ex | 185 ++++++----- 5 files changed, 344 insertions(+), 182 deletions(-) diff --git a/lib/dotcom_web/components/trip_planner/itinerary_detail.ex b/lib/dotcom_web/components/trip_planner/itinerary_detail.ex index 9254f00c70..cd32d5551b 100644 --- a/lib/dotcom_web/components/trip_planner/itinerary_detail.ex +++ b/lib/dotcom_web/components/trip_planner/itinerary_detail.ex @@ -14,6 +14,52 @@ defmodule DotcomWeb.Components.TripPlanner.ItineraryDetail do alias Dotcom.TripPlan.Alerts def itinerary_detail(assigns) do +<<<<<<< HEAD +======= + itinerary_group = + Enum.at(assigns.results.itinerary_groups, assigns.results.itinerary_group_selection || 0) + + itinerary = Enum.at(itinerary_group.itineraries, assigns.results.itinerary_selection || 0) + + assigns = %{ + itinerary: itinerary, + itinerary_selection: assigns.results.itinerary_selection, + itineraries: itinerary_group.itineraries + } + + ~H""" +
+ <.depart_at_buttons itineraries={@itineraries} itinerary_selection={@itinerary_selection} /> + <.specific_itinerary_detail itinerary={@itinerary} /> +
+ """ + end + + defp depart_at_buttons(assigns) do + ~H""" +
1}> +
+

Depart at

+
+ <.button + :for={{itinerary, index} <- Enum.with_index(@itineraries)} + type="button" + class={if(@itinerary_selection == index, do: "bg-brand-primary-lightest")} + size="small" + variant="secondary" + phx-click="select_itinerary" + phx-value-index={index} + data-test={"itinerary_detail:#{index}"} + > + {Timex.format!(itinerary.start, "%-I:%M%p", :strftime) |> String.downcase()} + +
+
+ """ + end + + defp specific_itinerary_detail(assigns) do +>>>>>>> 5fc47ec7 (tests and factories) assigns = assigns |> assign_new(:alerts, fn -> Alerts.from_itinerary(assigns.itinerary) end) diff --git a/lib/dotcom_web/components/trip_planner/results.ex b/lib/dotcom_web/components/trip_planner/results.ex index b2a346a58c..ae0e00fb0e 100644 --- a/lib/dotcom_web/components/trip_planner/results.ex +++ b/lib/dotcom_web/components/trip_planner/results.ex @@ -22,6 +22,7 @@ defmodule DotcomWeb.Components.TripPlanner.Results do
0 && @results.itinerary_group_selection} class="h-min w-full mb-3.5" + data-test={"results:itinerary_group:selected:#{@results.itinerary_group_selection}"} >