1}>
@@ -121,6 +123,7 @@ defmodule DotcomWeb.Components.TripPlanner.Results do
variant="secondary"
phx-click="select_itinerary"
phx-value-index={index}
+ data-test={"itinerary_detail:#{index}"}
>
{Util.kitchen_downcase_time(time)}
diff --git a/lib/dotcom_web/components/trip_planner/results_summary.ex b/lib/dotcom_web/components/trip_planner/results_summary.ex
index 492b77f8f3..3d6b8a8d99 100644
--- a/lib/dotcom_web/components/trip_planner/results_summary.ex
+++ b/lib/dotcom_web/components/trip_planner/results_summary.ex
@@ -42,7 +42,9 @@ defmodule DotcomWeb.Components.TripPlanner.ResultsSummary do
defp results_feedback(assigns) do
~H"""
- <.feedback kind={:error}>{@results.error}
+ <.feedback kind={:error}>
+
{@results.error}
+
"""
end
diff --git a/lib/dotcom_web/components/trip_planner/transit_leg.ex b/lib/dotcom_web/components/trip_planner/transit_leg.ex
index a2641aa5a2..878ad82e20 100644
--- a/lib/dotcom_web/components/trip_planner/transit_leg.ex
+++ b/lib/dotcom_web/components/trip_planner/transit_leg.ex
@@ -107,6 +107,8 @@ defmodule DotcomWeb.Components.TripPlanner.TransitLeg do
"""
end
+ defp stop_url(_, %Stop{} = stop) when is_nil(stop.id), do: nil
+
defp stop_url(%Route{external_agency_name: nil}, %Stop{} = stop) do
~p"/stops/#{stop}"
end
diff --git a/lib/dotcom_web/controllers/trip_plan_controller.ex b/lib/dotcom_web/controllers/trip_plan_controller.ex
index cc60b210af..4becbcd6ea 100644
--- a/lib/dotcom_web/controllers/trip_plan_controller.ex
+++ b/lib/dotcom_web/controllers/trip_plan_controller.ex
@@ -5,305 +5,43 @@ defmodule DotcomWeb.TripPlanController do
use DotcomWeb, :controller
- require Logger
+ import DotcomWeb.Router.Helpers, only: [live_path: 2]
- alias Dotcom.TripPlan.{
- Itinerary,
- ItineraryRowList,
- Leg,
- NamedPosition,
- PersonalDetail,
- Query,
- RelatedLink,
- TransitDetail
- }
-
- alias Dotcom.TripPlan.Map, as: TripPlanMap
- alias Routes.Route
+ alias Dotcom.TripPlan.AntiCorruptionLayer
@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)
+ @doc """
+ When visiting /trip-planner/:from/some%20address or /trip-planner/:to/some%20address,
+ we lookup the location and redirect to the trip planner with that location encoded in the query string.
+ """
+ def location(conn, %{"direction" => direction, "query" => query})
+ when direction in ["from", "to"] do
+ case @location_service.geocode(query) do
+ {:ok, [%LocationService.Address{} = location | _]} ->
+ encoded = build_params(direction, location, query) |> AntiCorruptionLayer.encode()
+ path = live_path(conn, DotcomWeb.Live.TripPlanner)
- case @location_service.geocode(updated_address) do
- {:ok, [geocoded_from | _]} ->
- do_from(conn, NamedPosition.new(geocoded_from))
+ conn |> put_status(301) |> redirect(to: "#{path}?plan=#{encoded}") |> halt()
- _ ->
- # redirect to the initial index page
- redirect(conn, to: trip_plan_path(conn, :index))
- end
+ _ ->
+ location(conn, %{})
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 location(conn, _params) do
+ path = live_path(conn, DotcomWeb.Live.TripPlanner)
- def to(conn, %{"plan" => _plan} = params) do
- redirect(conn, to: trip_plan_path(conn, :index, Map.delete(params, "address")))
+ conn |> put_status(301) |> redirect(to: path) |> halt()
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
+ defp build_params(direction, location, query) do
+ %{
+ "#{direction}" => %{
+ "latitude" => location.latitude,
+ "longitude" => location.longitude,
+ "name" => query
}
-
- do_to(conn, destination)
- else
- updated_address = check_address(address)
-
- case @location_service.geocode(updated_address) do
- {:ok, [geocoded_to | _]} ->
- do_to(conn, NamedPosition.new(geocoded_to))
-
- _ ->
- # redirect to the initial index page
- redirect(conn, to: trip_plan_path(conn, :index))
- end
- end
- end
-
- defp do_to(conn, destination) do
- # build a default query with a pre-filled 'to' field:
- query = %Query{
- to: destination,
- time: {:error, :unknown},
- from: {:error, :unknown}
}
-
- now = Util.now()
-
- # build map information for a single leg with the 'to' field:
- map_data =
- TripPlanMap.itinerary_map([
- %Leg{
- from: nil,
- to: destination,
- mode: %PersonalDetail{},
- start: now,
- stop: now
- }
- ])
-
- %{markers: [marker]} = map_data
- to_marker = %{marker | id: "B"}
- map_info_for_to_destination = %{map_data | markers: [to_marker]}
-
- conn
- |> assign(:query, query)
- |> assign(:map_data, map_info_for_to_destination)
- |> render(:index)
- end
-
- defp assign_params(conn, _) do
- conn
- |> assign(:chosen_date_time, conn.params["plan"]["date_time"])
- |> assign(:chosen_time, conn.params["plan"]["time"])
- end
-
- @spec check_address(String.t()) :: String.t()
- defp check_address(address) do
- # address can be a String containing "lat,lon" so we check for that case
-
- [lat, lon] =
- case String.split(address, ",", parts: 2) do
- [lat, lon] -> [lat, lon]
- _ -> ["error", "error"]
- end
-
- if Float.parse(lat) == :error || Float.parse(lon) == :error do
- address
- else
- {parsed_lat, _} = Float.parse(lat)
- {parsed_lon, _} = Float.parse(lon)
-
- case @location_service.reverse_geocode(parsed_lat, parsed_lon) do
- {:ok, [first | _]} ->
- first.formatted
-
- _ ->
- "#{lat}, #{lon}"
- end
- end
- end
-
- defp get_route(link) do
- if is_bitstring(link.text) do
- link.text
- else
- link.text |> List.to_string()
- end
- end
-
- defp filter_duplicate_links(related_links) do
- Enum.map(related_links, fn x -> Enum.uniq_by(x, fn y -> get_route(y) end) end)
- end
-
- @spec render_plan(Plug.Conn.t(), map) :: Plug.Conn.t()
- defp render_plan(conn, plan_params) do
- query =
- Query.from_query(
- plan_params,
- now: conn.assigns.date_time,
- end_of_rating: Map.get(conn.assigns, :end_of_rating, Schedules.Repo.end_of_rating())
- )
-
- itineraries =
- query
- |> Query.get_itineraries()
-
- itinerary_row_lists = itinerary_row_lists(itineraries, plan_params)
-
- conn
- |> render(
- query: query,
- itineraries: itineraries,
- plan_error: MapSet.to_list(query.errors),
- routes: Enum.map(itineraries, &routes_for_itinerary(&1)),
- itinerary_maps: Enum.map(itineraries, &TripPlanMap.itinerary_map(&1)),
- related_links:
- filter_duplicate_links(Enum.map(itineraries, &RelatedLink.links_for_itinerary(&1))),
- itinerary_row_lists: itinerary_row_lists
- )
- end
-
- @spec itinerary_row_lists([Itinerary.t()], map) :: [ItineraryRowList.t()]
- defp itinerary_row_lists(itineraries, plan) do
- Enum.map(itineraries, &ItineraryRowList.from_itinerary(&1, to_and_from(plan)))
- end
-
- @spec assign_initial_map(Plug.Conn.t(), any()) :: Plug.Conn.t()
- def assign_initial_map(conn, _opts) do
- conn
- |> assign(:map_data, TripPlanMap.initial_map_data())
- end
-
- @spec modes(Plug.Conn.t(), Keyword.t()) :: Plug.Conn.t()
- def modes(%Plug.Conn{params: %{"plan" => %{"modes" => modes}}} = conn, _) do
- assign(
- conn,
- :modes,
- Map.new(modes, fn {mode, active?} -> {String.to_existing_atom(mode), active? === "true"} end)
- )
- end
-
- def modes(%Plug.Conn{} = conn, _) do
- assign(
- conn,
- :modes,
- %{subway: true, bus: true, commuter_rail: true, ferry: true}
- )
- end
-
- @spec breadcrumbs(Plug.Conn.t(), Keyword.t()) :: Plug.Conn.t()
- defp breadcrumbs(conn, _) do
- assign(conn, :breadcrumbs, [Breadcrumb.build("Trip Planner")])
- end
-
- @spec wheelchair(Plug.Conn.t(), Keyword.t()) :: Plug.Conn.t()
- def wheelchair(%Plug.Conn{params: %{"plan" => plan_params}} = conn, _) do
- assign(conn, :wheelchair, get_in(plan_params, ["wheelchair"]) === "true")
- end
-
- # Initialize to checked state for trip plan accessibility
- def wheelchair(%Plug.Conn{} = conn, _) do
- assign(conn, :wheelchair, true)
- end
-
- @spec routes_for_itinerary(Itinerary.t()) :: [Route.t()]
- defp routes_for_itinerary(itinerary) do
- itinerary.legs
- |> Enum.filter(&match?(%TransitDetail{}, &1.mode))
- |> Enum.map(& &1.mode.route)
- end
-
- @spec to_and_from(map) :: [to: String.t() | nil, from: String.t() | nil]
- def to_and_from(plan) do
- [to: Map.get(plan, "to"), from: Map.get(plan, "from")]
- end
-
- defp meta_description(conn, _) do
- conn
- |> assign(
- :meta_description,
- "Plan a trip on public transit in the Greater Boston region with directions " <>
- "and suggestions based on real-time data."
- )
end
end
diff --git a/lib/dotcom_web/controllers/vote_controller.ex b/lib/dotcom_web/controllers/vote_controller.ex
index c16b493ba0..0283175d76 100644
--- a/lib/dotcom_web/controllers/vote_controller.ex
+++ b/lib/dotcom_web/controllers/vote_controller.ex
@@ -44,7 +44,7 @@ defmodule DotcomWeb.VoteController do
conn
|> assign(:polling_location, polling_location)
|> assign(:polling_location_name, polling_location_name)
- |> assign(:trip_plan_path, trip_plan_path(DotcomWeb.Endpoint, :index, params))
+ |> assign(:trip_plan_path, "/trip-planner#{URI.encode_query(params)}")
_ ->
conn |> assign(:polling_error, true)
diff --git a/lib/dotcom_web/live/admin.ex b/lib/dotcom_web/live/admin.ex
index 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",
diff --git a/lib/dotcom_web/live/trip_planner.ex b/lib/dotcom_web/live/trip_planner.ex
index d547406698..5a4f91cf00 100644
--- a/lib/dotcom_web/live/trip_planner.ex
+++ b/lib/dotcom_web/live/trip_planner.ex
@@ -8,6 +8,7 @@ defmodule DotcomWeb.Live.TripPlanner do
use DotcomWeb, :live_view
import DotcomWeb.Components.TripPlanner.{InputForm, Results, ResultsSummary}
+ import DotcomWeb.Router.Helpers, only: [live_path: 2]
alias Dotcom.TripPlan
alias Dotcom.TripPlan.{AntiCorruptionLayer, InputForm, ItineraryGroup, ItineraryGroups}
@@ -33,22 +34,14 @@ defmodule DotcomWeb.Live.TripPlanner do
@impl true
@doc """
- Prepare the live view:
+ When the live view first loads, there are three possible scenarios:
- - Set the initial state of the live view.
- - Clean any query parameters and convert them to a changeset for the input form.
- - Then, submit the form if the changeset is valid (i.e., the user visited with valid query parameters).
+ 1. There are no query params. We go to step (2) and use the default params.
+ 2. There are query params representing the old structure of the trip planner form. We convert these, encode them, and redirect to (3).
+ 3. The new `?plan=ENCODED` query param is present. We decode it and mount the form with the decoded values.
"""
- def mount(params, _session, %{assigns: %{live_action: live_action}} = socket) do
- changeset =
- if is_atom(live_action) and is_binary(params["place"]) do
- # Handle the /to/:place or /from/:place situation
- live_action
- |> action_to_query_params(params["place"])
- |> query_params_to_changeset()
- else
- query_params_to_changeset(params)
- end
+ def mount(%{"plan" => plan}, _session, socket) when is_binary(plan) do
+ changeset = plan |> AntiCorruptionLayer.decode() |> InputForm.changeset()
new_socket =
socket
@@ -59,6 +52,14 @@ defmodule DotcomWeb.Live.TripPlanner do
{:ok, new_socket}
end
+ def mount(params, _session, socket) do
+ converted_params = AntiCorruptionLayer.convert_old_params(params)
+
+ new_socket = navigate_state(socket, converted_params)
+
+ {:ok, new_socket}
+ end
+
@impl true
@doc """
The live view is made up of four subcomponents:
@@ -70,7 +71,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} />
@@ -154,6 +155,7 @@ defmodule DotcomWeb.Live.TripPlanner do
@impl true
# Triggered every time the form changes:
#
+ # - Store the state of the form in the URL
# - Update the input form state with the new changeset
# - Update the map state with the new pins
# - Reset the results state
@@ -173,6 +175,7 @@ defmodule DotcomWeb.Live.TripPlanner do
new_socket =
socket
+ |> patch_state(params_with_datetime)
|> assign(:input_form, Map.put(@state.input_form, :changeset, changeset))
|> assign(:map, Map.put(@state.map, :pins, pins))
|> update_datepicker(params_with_datetime)
@@ -284,6 +287,7 @@ defmodule DotcomWeb.Live.TripPlanner do
@impl true
# Triggered when the user clicks the button to swap to "from" and "to" data
#
+ # - Stores the new state of the form in the URL
# - Creates a new changeset with the switched from and to values
# - Resubmits the form (which in turn updates the input form state, map, etc)
# - Dispatches a "set-query" event that is used by the AlgoliaAutocomplete
@@ -304,9 +308,13 @@ defmodule DotcomWeb.Live.TripPlanner do
|> maybe_put_change(:to, new_to)
|> maybe_put_change(:from, new_from)
+ new_changeset = Ecto.Changeset.change(changeset, changes)
+ form_params = changeset_to_params(new_changeset)
+
new_socket =
socket
- |> submit_changeset(Ecto.Changeset.change(changeset, changes))
+ |> patch_state(form_params)
+ |> submit_changeset(new_changeset)
|> push_event("set-query", changes)
{:noreply, new_socket}
@@ -327,6 +335,35 @@ defmodule DotcomWeb.Live.TripPlanner do
{:noreply, socket}
end
+ @impl true
+ # We have to handle the result of the push_patch, but we ignore it.
+ def handle_params(_params, _uri, socket) do
+ {:noreply, socket}
+ end
+
+ # When the datetime_type is "leave_at" or "arrive_by", we need to
+ # have a "datetime" indicating when we want to "leave at" or "arrive
+ # by", but because the datepicker only appears after a rider clicks
+ # on "Leave at" or "Arrive by", the actual value of "datetime"
+ # doesn't always appear in params. When that happens, we want to set
+ # "datetime" to a reasonable default.
+ defp add_datetime_if_needed(%{"datetime_type" => "now"} = params),
+ do: params |> Map.put("datetime", Timex.now("America/New_York"))
+
+ defp add_datetime_if_needed(%{"datetime" => datetime} = params) when datetime != nil, do: params
+ defp add_datetime_if_needed(params), do: params |> Map.put("datetime", nearest_5_minutes())
+
+ # Converts a changeset to a map of params that can be encoded into a URL.
+ defp changeset_to_params(%Ecto.Changeset{changes: changes}) do
+ changes
+ |> Map.update(:from, %{}, &Map.get(&1, :changes))
+ |> Map.update(:to, %{}, &Map.get(&1, :changes))
+ |> Map.update(:modes, %{}, &Map.get(&1, :changes))
+ |> Map.new(fn {k, v} -> {to_string(k), v} end)
+ |> Jason.encode!()
+ |> Jason.decode!()
+ end
+
# Run an OTP plan on the changeset data and return itinerary groups or an error.
defp get_itinerary_groups(%Ecto.Changeset{valid?: true} = changeset) do
{:ok, data} = Ecto.Changeset.apply_action(changeset, :submit)
@@ -363,7 +400,6 @@ defmodule DotcomWeb.Live.TripPlanner do
# If neither `from` nor `to` are set, we return an empty list of pins.
defp input_form_to_pins(_), do: []
-
# Get the itinerary group at the given index and convert it to a map.
defp itinerary_groups_to_itinerary_map(itinerary_groups, group_index, index) do
itinerary_groups
@@ -387,18 +423,24 @@ defmodule DotcomWeb.Live.TripPlanner do
|> TripPlan.Map.get_points()
end
- # Send an event that will get picked up by the datepicker component
- # so that the datepicker renders the correct datetime.
- #
- # Does nothing if there's no datetime in the params.
- defp update_datepicker(socket, %{"datetime" => datetime}) do
- push_event(socket, "set-datetime", %{datetime: datetime})
+ # For the from or to fields, get the underlying location data if changed
+ defp location_data_from_changeset(%Ecto.Changeset{changes: changes}) when changes != %{} do
+ changes
end
- defp update_datepicker(socket, %{}) do
- socket
+ defp location_data_from_changeset(_), do: %{}
+ # Encode params and set them in the URL while navigating to it.
+ defp navigate_state(socket, params) do
+ encoded = AntiCorruptionLayer.encode(params)
+ path = live_path(socket, __MODULE__)
+
+ push_navigate(socket, to: "#{path}?plan=#{encoded}")
end
+ # Adjust the map only if value is non-nil
+ defp maybe_put_change(changes, _, nil), do: changes
+ defp maybe_put_change(changes, key, data), do: Map.put(changes, key, data)
+
# Round the current time to the nearest 5 minutes.
defp nearest_5_minutes do
datetime = Timex.now("America/New_York")
@@ -409,44 +451,14 @@ defmodule DotcomWeb.Live.TripPlanner do
Timex.shift(datetime, minutes: added_minutes)
end
- defp action_to_query_params(action_key, action_value) do
- %{}
- |> Map.put(action_key, action_value)
- |> AntiCorruptionLayer.convert_old_action()
- end
-
- # Convert query parameters to a changeset for the input form.
- # Use an anti corruption layer to convert old query parameters to new ones.
- defp query_params_to_changeset(params) do
- %{
- "datetime_type" => "now",
- "modes" => InputForm.initial_modes()
- }
- |> Map.merge(AntiCorruptionLayer.convert_old_params(params))
- |> InputForm.changeset()
- end
-
- # Destructure the latitude and longitude from a map to a GeoJSON array.
- defp to_geojson(%{longitude: longitude, latitude: latitude}) do
- [longitude, latitude]
- end
+ # Encode params and set them in the URL while patching the browser history.
+ defp patch_state(socket, params) do
+ encoded = AntiCorruptionLayer.encode(params)
+ path = live_path(socket, __MODULE__)
- defp to_geojson(_) do
- []
+ push_patch(socket, to: "#{path}?plan=#{encoded}")
end
- # When the datetime_type is "leave_at" or "arrive_by", we need to
- # have a "datetime" indicating when we want to "leave at" or "arrive
- # by", but because the datepicker only appears after a rider clicks
- # on "Leave at" or "Arrive by", the actual value of "datetime"
- # doesn't always appear in params. When that happens, we want to set
- # "datetime" to a reasonable default.
- defp add_datetime_if_needed(%{"datetime_type" => "now"} = params),
- do: params |> Map.put("datetime", Timex.now("America/New_York"))
-
- defp add_datetime_if_needed(%{"datetime" => datetime} = params) when datetime != nil, do: params
- defp add_datetime_if_needed(params), do: params |> Map.put("datetime", nearest_5_minutes())
-
# Set an action on the changeset and submit it.
#
# - Update the input form state with the new changeset
@@ -472,14 +484,24 @@ defmodule DotcomWeb.Live.TripPlanner do
end
end
- # Adjust the map only if value is non-nil
- defp maybe_put_change(changes, _, nil), do: changes
- defp maybe_put_change(changes, key, data), do: Map.put(changes, key, data)
+ # Destructure the latitude and longitude from a map to a GeoJSON array.
+ defp to_geojson(%{longitude: longitude, latitude: latitude}) do
+ [longitude, latitude]
+ end
- # For the from or to fields, get the underlying location data if changed
- defp location_data_from_changeset(%Ecto.Changeset{changes: changes}) when changes != %{} do
- changes
+ defp to_geojson(_) do
+ []
end
- defp location_data_from_changeset(_), do: %{}
+ # Send an event that will get picked up by the datepicker component
+ # so that the datepicker renders the correct datetime.
+ #
+ # Does nothing if there's no datetime in the params.
+ defp update_datepicker(socket, %{"datetime" => datetime}) do
+ push_event(socket, "set-datetime", %{datetime: datetime})
+ end
+
+ defp update_datepicker(socket, %{}) do
+ socket
+ end
end
diff --git a/lib/dotcom_web/router.ex b/lib/dotcom_web/router.ex
index 1fadd9a87b..e6167e30d3 100644
--- a/lib/dotcom_web/router.ex
+++ b/lib/dotcom_web/router.ex
@@ -227,11 +227,9 @@ 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)
+ get("/trip-planner/:direction/:query", TripPlanController, :location)
delete("/trip-planner/feedback", TripPlan.Feedback, :delete)
post("/trip-planner/feedback", TripPlan.Feedback, :put)
get("/customer-support", CustomerSupportController, :index)
@@ -263,20 +261,18 @@ 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)
end
end
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/lib/dotcom_web/templates/fare/_nearby_locations.html.eex b/lib/dotcom_web/templates/fare/_nearby_locations.html.eex
index e09fc7fe47..750250fcaa 100644
--- a/lib/dotcom_web/templates/fare/_nearby_locations.html.eex
+++ b/lib/dotcom_web/templates/fare/_nearby_locations.html.eex
@@ -36,7 +36,7 @@
<% end %>
+
<%= if assigns[:search_header?] do %>
<%= render "_searchbar.html", assigns %>
<% end %>
diff --git a/lib/dotcom_web/templates/vote/show.html.heex b/lib/dotcom_web/templates/vote/show.html.heex
index 44db0cfd62..337089fca1 100644
--- a/lib/dotcom_web/templates/vote/show.html.heex
+++ b/lib/dotcom_web/templates/vote/show.html.heex
@@ -56,7 +56,7 @@
Secretary of State's official site
to find your polling place, and then use the
-
+
MBTA Trip Planner
to plan your trip.
diff --git a/mix.exs b/mix.exs
index 6ddbeddbe8..e8b1d1cd52 100644
--- a/mix.exs
+++ b/mix.exs
@@ -72,32 +72,31 @@ defmodule DotCom.Mixfile do
[
{:absinthe_client, "0.1.1"},
{:address_us, "0.4.3"},
- {:aws, "1.0.2"},
+ {:aws, "1.0.4"},
{:aws_credentials, "0.3.2", optional: true},
- {:castore, "1.0.9"},
+ {:castore, "1.0.11"},
{:crc, "0.10.5"},
- {:credo, "1.7.8", only: [:dev, :test]},
- {:csv, "3.2.1"},
+ {:credo, "1.7.11", only: [:dev, :test]},
+ {:csv, "3.2.2"},
{:cva, "0.2.2"},
{:decorator, "1.4.0"},
- {:dialyxir, "1.4.4", [only: [:dev, :test], runtime: false]},
+ {:dialyxir, "1.4.5", [only: [:dev, :test], runtime: false]},
{:diskusage_logger, "0.2.0"},
- {:ecto, "3.12.4"},
+ {:ecto, "3.12.5"},
{:eflame, "1.0.1", only: :dev},
{:ehmon, [github: "mbta/ehmon", only: :prod]},
- {:ex_doc, "0.34.2", only: :dev},
+ {:ex_doc, "0.36.1", only: :dev},
{:ex_machina, "2.8.0", only: [:dev, :test]},
{:ex_unit_summary, "0.1.0", only: [:dev, :test]},
- {:excoveralls, "0.18.3", only: :test},
+ {:excoveralls, "0.18.5", only: :test},
{:faker,
git: "https://github.com/elixirs/faker.git",
override: true,
branch: "master",
only: [:dev, :test]},
- # Latest 0.36.3 breaks the trip planner test
- {:floki, "0.36.2"},
+ {:floki, "0.37.0"},
{:gen_stage, "1.2.1"},
- {:gettext, "0.26.1"},
+ {:gettext, "0.26.2"},
{:hackney, "1.20.1"},
{:hammer, "6.2.1"},
{:html_sanitize_ex, "1.4.3"},
@@ -106,30 +105,29 @@ defmodule DotCom.Mixfile do
{:jason, "1.4.4", override: true},
{:logster, "1.1.1"},
{:mail, "0.3.1"},
- {:mbta_metro, "0.1.19"},
- {:mock, "0.3.8", [only: :test]},
+ {:mbta_metro, "0.1.20"},
+ {:mock, "0.3.9", [only: :test]},
{:mox, "1.2.0", [only: :test]},
+ {:msgpack, "0.8.1"},
{:nebulex, "2.6.4"},
- {:nebulex_redis_adapter, "2.4.1"},
+ {:nebulex_redis_adapter, "2.4.2"},
{
:open_trip_planner_client,
[github: "mbta/open_trip_planner_client", tag: "v0.11.3"]
},
{:parallel_stream, "1.1.0"},
- # latest version 1.7.14
- {:phoenix, "~> 1.7"},
- {:phoenix_ecto, "4.6.2"},
+ {:phoenix, "1.7.18"},
+ {:phoenix_ecto, "4.6.3"},
{:phoenix_html_helpers, "1.0.1"},
- {:phoenix_live_dashboard, "0.8.4"},
+ {:phoenix_live_dashboard, "0.8.6"},
{:phoenix_live_reload, "1.5.3", only: [:dev, :test]},
- # currently release candidate, but used in Phoenix 1.7 generator: https://github.com/phoenix-diff/phoenix-diff/blob/f320791d24bc3248fbdde557978235829313aa06/priv/data/sample-app/1.7.14/default/mix.exs#L42
- {:phoenix_live_view, "~> 1.0.0-rc.6", override: true},
+ {:phoenix_live_view, "1.0.2", override: true},
{:phoenix_pubsub, "2.1.3"},
{:phoenix_view, "~> 2.0"},
{:plug, "1.16.1"},
{:plug_cowboy, "2.7.2"},
{:poison, "6.0.0"},
- {:polyline, "1.4.0"},
+ {:polyline, "1.5.0"},
{:poolboy, "1.5.2"},
# Needed for rstar; workaround for mix local.hex bug
{:proper, "1.4.0"},
@@ -138,14 +136,14 @@ defmodule DotCom.Mixfile do
{:recase, "0.8.1"},
{:recon, "2.5.6", [only: :prod]},
{:redix, "1.5.2"},
- {:req, "0.5.6"},
+ {:req, "0.5.8"},
{:rstar, github: "armon/erl-rstar"},
- {:sentry, "10.7.1"},
+ {:sentry, "10.8.1"},
{:server_sent_event_stage, "1.2.1"},
{:sizeable, "1.0.2"},
- {:sweet_xml, "0.7.4", only: [:dev, :prod]},
+ {:sweet_xml, "0.7.5", only: [:dev, :prod]},
{:telemetry, "1.3.0", override: true},
- {:telemetry_metrics, "1.0.0", override: true},
+ {:telemetry_metrics, "1.1.0", override: true},
{:telemetry_metrics_splunk, "0.0.6-alpha"},
{:telemetry_poller, "1.1.0"},
{:telemetry_test, "0.1.2", only: [:test]},
@@ -153,7 +151,7 @@ defmodule DotCom.Mixfile do
{:typed_ecto_schema, "0.4.1"},
{:unrooted_polytree, "0.1.1"},
{:uuid, "1.1.8"},
- {:wallaby, "0.30.9", [runtime: false, only: [:dev, :test]]},
+ {:wallaby, "0.30.10", [runtime: false, only: [:dev, :test]]},
{:yaml_elixir, "2.11.0", only: [:dev]},
{:ymlr, "5.1.3", only: [:dev]}
]
diff --git a/mix.lock b/mix.lock
index 32550c5218..8212b29c21 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,46 +1,46 @@
%{
"absinthe_client": {:hex, :absinthe_client, "0.1.1", "1e778d587a27b85ecc35e4a5fedc64c85d9fdfd05395745c7af5345564dff54e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: false]}, {:slipstream, "~> 1.0", [hex: :slipstream, repo: "hexpm", optional: false]}], "hexpm", "e75a28c5bb647f485e9c03bbc3a47e7783742794bd4c10f3307a495a9e7273b6"},
"address_us": {:hex, :address_us, "0.4.3", "370d612b4fc7f00f183d3e9c87381edd801565783b7e4ad7b782c42c91e130f6", [:mix], [], "hexpm", "7eb9380ed001369174b12445bab4da5fc533434548b0217aff7fd146bd0df048"},
- "aws": {:hex, :aws, "1.0.2", "39e0844ff126662e031e4bf186a6631b3402bf39f6af639260b27a4c29fdfe1a", [:mix], [{:aws_signature, "~> 0.3", [hex: :aws_signature, repo: "hexpm", optional: false]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "0e5cb8dee3f50a81cd93820f4c68407371c3ebbf860000a92a51ecb7920e808d"},
+ "aws": {:hex, :aws, "1.0.4", "17af14644b93d8249de2d730528ab0f7289817e626eb51460f6451b2cdbb5574", [:mix], [{:aws_signature, "~> 0.3", [hex: :aws_signature, repo: "hexpm", optional: false]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.20", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "925722950c085e246ad2e346c847a3f6c8edcf41ac78dcb82b68813a797dc0e2"},
"aws_credentials": {:hex, :aws_credentials, "0.3.2", "ba2ccee4ec6dcb5426cf71830b7afd73795b1f19655f401d4401015b468fec6f", [:rebar3], [{:eini, "~> 2.2.4", [hex: :eini_beam, repo: "hexpm", optional: false]}, {:iso8601, "~> 1.3.4", [hex: :iso8601, repo: "hexpm", optional: false]}, {:jsx, "~> 3.1.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "2e748626a935a7a544647fb79d7054f38db8bf378978542c962ccbeab387387b"},
- "aws_signature": {:hex, :aws_signature, "0.3.2", "adf33bc4af00b2089b7708bf20e3246f09c639a905a619b3689f0a0a22c3ef8f", [:rebar3], [], "hexpm", "b0daf61feb4250a8ab0adea60db3e336af732ff71dd3fb22e45ae3dcbd071e44"},
+ "aws_signature": {:hex, :aws_signature, "0.3.3", "5844bee0d3cc42eefd21d236bbfaa8aa9b16e2f2b7ee79edaecb321db3fb6adf", [:rebar3], [], "hexpm", "87e8f42b8e49002aa8d0350a71d13d69ea91b9afb4ca9b526ae36db1d585c924"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
- "castore": {:hex, :castore, "1.0.9", "5cc77474afadf02c7c017823f460a17daa7908e991b0cc917febc90e466a375c", [:mix], [], "hexpm", "5ea956504f1ba6f2b4eb707061d8e17870de2bee95fb59d512872c2ef06925e7"},
+ "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
"crc": {:hex, :crc, "0.10.5", "ee12a7c056ac498ef2ea985ecdc9fa53c1bfb4e53a484d9f17ff94803707dfd8", [:mix, :rebar3], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3e673b6495a9525c5c641585af1accba59a1eb33de697bedf341e247012c2c7f"},
- "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"},
- "csv": {:hex, :csv, "3.2.1", "6d401f1ed33acb2627682a9ab6021e96d33ca6c1c6bccc243d8f7e2197d032f5", [:mix], [], "hexpm", "8f55a0524923ae49e97ff2642122a2ce7c61e159e7fe1184670b2ce847aee6c8"},
+ "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"},
+ "csv": {:hex, :csv, "3.2.2", "452f96414b39a176b7c390af6d8b78f15130dc6167fe3b836729131f515d843e", [:mix], [], "hexpm", "cbf256ff74a3fa01d9ec420d07b19c90d410ed9fe5b6d6e1bc7662edf35bc574"},
"cva": {:hex, :cva, "0.2.2", "19ff3fe93c796250f2a3946930e5be62e73019e927aa21c70fcd3c64e9b58466", [:mix], [{:phoenix_live_view, ">= 0.18.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "cb494f3df0bf4a8c1b3652d052d0c4dc5111ef6981f542cc22210f639bfbfcda"},
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
"decorator": {:hex, :decorator, "1.4.0", "a57ac32c823ea7e4e67f5af56412d12b33274661bb7640ec7fc882f8d23ac419", [:mix], [], "hexpm", "0a07cedd9083da875c7418dea95b78361197cf2bf3211d743f6f7ce39656597f"},
- "dialyxir": {:hex, :dialyxir, "1.4.4", "fb3ce8741edeaea59c9ae84d5cec75da00fa89fe401c72d6e047d11a61f65f70", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "cd6111e8017ccd563e65621a4d9a4a1c5cd333df30cebc7face8029cacb4eff6"},
+ "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"},
"diskusage_logger": {:hex, :diskusage_logger, "0.2.0", "04fc48b538fe4de43153542a71ea94f623d54707d85844123baacfceedf625c3", [:mix], [], "hexpm", "e3f2aed1b0fc4590931c089a6453a4c4eb4c945912aa97bcabcc0cff7851f34d"},
"earmark": {:hex, :earmark, "1.4.47", "7e7596b84fe4ebeb8751e14cbaeaf4d7a0237708f2ce43630cfd9065551f94ca", [:mix], [], "hexpm", "3e96bebea2c2d95f3b346a7ff22285bc68a99fbabdad9b655aa9c6be06c698f8"},
- "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
- "ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"},
+ "earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"},
+ "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"},
"eflame": {:hex, :eflame, "1.0.1", "0664d287e39eef3c413749254b3af5f4f8b00be71c1af67d325331c4890be0fc", [:mix], [], "hexpm", "e0b08854a66f9013129de0b008488f3411ae9b69b902187837f994d7a99cf04e"},
"ehmon": {:git, "https://github.com/mbta/ehmon.git", "1fb603262bd02d74a16183bd8f344dcace9d7561", []},
"eini": {:hex, :eini_beam, "2.2.4", "02143b1dce4dda4243248e7d9b3d8274b8d9f5a666445e3d868e2cce79e4ff22", [:rebar3], [], "hexpm", "12de479d144b19e09bb92ba202a7ea716739929afdf9dff01ad802e2b1508471"},
- "elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"},
+ "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
- "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"},
+ "ex_doc": {:hex, :ex_doc, "0.36.1", "4197d034f93e0b89ec79fac56e226107824adcce8d2dd0a26f5ed3a95efc36b1", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d7d26a7cf965dacadcd48f9fa7b5953d7d0cfa3b44fa7a65514427da44eafd89"},
"ex_machina": {:hex, :ex_machina, "2.8.0", "a0e847b5712065055ec3255840e2c78ef9366634d62390839d4880483be38abe", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "79fe1a9c64c0c1c1fab6c4fa5d871682cb90de5885320c187d117004627a7729"},
"ex_unit_summary": {:hex, :ex_unit_summary, "0.1.0", "7b0352afc5e6a933c805df0a539b66b392ac12ba74d8b208db7d83f77cb57049", [:mix], [], "hexpm", "8c87d0deade3657102902251d2ec60b5b94560004ce0e2c2fa5b466232716bd6"},
- "excoveralls": {:hex, :excoveralls, "0.18.3", "bca47a24d69a3179951f51f1db6d3ed63bca9017f476fe520eb78602d45f7756", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "746f404fcd09d5029f1b211739afb8fb8575d775b21f6a3908e7ce3e640724c6"},
+ "excoveralls": {:hex, :excoveralls, "0.18.5", "e229d0a65982613332ec30f07940038fe451a2e5b29bce2a5022165f0c9b157e", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "523fe8a15603f86d64852aab2abe8ddbd78e68579c8525ae765facc5eae01562"},
"expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"},
"faker": {:git, "https://github.com/elixirs/faker.git", "c217c88f5c968146ea4c5fb0120df3dcf9175ab4", [branch: "master"]},
- "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
- "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
- "floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"},
+ "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"},
+ "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"},
+ "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"},
"gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"},
- "gettext": {:hex, :gettext, "0.26.1", "38e14ea5dcf962d1fc9f361b63ea07c0ce715a8ef1f9e82d3dfb8e67e0416715", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "01ce56f188b9dc28780a52783d6529ad2bc7124f9744e571e1ee4ea88bf08734"},
+ "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"},
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
"hammer": {:hex, :hammer, "6.2.1", "5ae9c33e3dceaeb42de0db46bf505bd9c35f259c8defb03390cd7556fea67ee2", [:mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b9476d0c13883d2dc0cc72e786bac6ac28911fba7cc2e04b70ce6a6d9c4b2bdc"},
- "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"},
+ "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.4.3", "67b3d9fa8691b727317e0cc96b9b3093be00ee45419ffb221cdeee88e75d1360", [:mix], [{:mochiweb, "~> 2.15 or ~> 3.1", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm", "87748d3c4afe949c7c6eb7150c958c2bcba43fc5b2a02686af30e636b74bccb7"},
"httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
@@ -51,11 +51,11 @@
"logster": {:hex, :logster, "1.1.1", "d6fddac540dd46adde0c894024500867fe63b0043713f842c62da5815e21db10", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d18e852c430812ad1c9756998ebe46ec814c724e6eb551a512d7e3f8dee24cef"},
"mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"},
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
- "makeup_eex": {:hex, :makeup_eex, "0.1.2", "93a5ef3d28ed753215dba2d59cb40408b37cccb4a8205e53ef9b5319a992b700", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.16 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_html, "~> 0.1.0 or ~> 1.0", [hex: :makeup_html, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "6140eafb28215ad7182282fd21d9aa6dcffbfbe0eb876283bc6b768a6c57b0c3"},
+ "makeup_eex": {:hex, :makeup_eex, "1.0.0", "436d4c00204c250b17a775d64e197798aaf374627e6a4f2d3fd3074a8db61db4", [:mix], [{:makeup, "~> 1.2.1 or ~> 1.3", [hex: :makeup, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_html, "~> 0.1.0 or ~> 1.0", [hex: :makeup_html, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "3bb699bc519e4f509f1bf8a2e0ba0e08429edf3580053cd31a4f9c1bc5da86c8"},
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
"makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"},
"makeup_html": {:hex, :makeup_html, "0.1.2", "19d4050c0978a4f1618ffe43054c0049f91fe5feeb9ae8d845b5dc79c6008ae5", [:mix], [{:makeup, "~> 1.2", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b7fb9afedd617d167e6644a0430e49c1279764bfd3153da716d4d2459b0998c5"},
- "mbta_metro": {:hex, :mbta_metro, "0.1.19", "7ece3ab20b9fa1d05dbc1b6419fb5f73efce7a01060b7c4021df08a5f6c0aa81", [:mix], [{:cva, "0.2.2", [hex: :cva, repo: "hexpm", optional: false]}, {:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: false]}, {:heroicons, "0.5.6", [hex: :heroicons, repo: "hexpm", optional: true]}, {:jason, "1.4.4", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "1.7.14", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_live_view, "1.0.0-rc.6", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_storybook, "0.6.4", [hex: :phoenix_storybook, repo: "hexpm", optional: false]}, {:timex, "3.7.11", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "ed51752f4dce8e764fd664c4daa3437061b7e20136de842d896f73f8fd88e96b"},
+ "mbta_metro": {:hex, :mbta_metro, "0.1.20", "9c0ca924d4d9686534d4b9dc87faffcb100b8fb198ccbb4d6bf8abdacc6ce582", [:mix], [{:cva, "~> 0.2", [hex: :cva, repo: "hexpm", optional: false]}, {:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: false]}, {:heroicons, "~> 0.5", [hex: :heroicons, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_storybook, "~> 0.6", [hex: :phoenix_storybook, repo: "hexpm", optional: false]}, {:timex, "~> 3.7", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "82ecc7a1f800368fa2d713e48650fb2537f73e705a073a53848de4e92f6f07c5"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
@@ -63,27 +63,28 @@
"mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"},
"mint_web_socket": {:hex, :mint_web_socket, "1.0.4", "0b539116dbb3d3f861cdf5e15e269a933cb501c113a14db7001a3157d96ffafd", [:mix], [{:mint, ">= 1.4.1 and < 2.0.0-0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "027d4c5529c45a4ba0ce27a01c0f35f284a5468519c045ca15f43decb360a991"},
"mochiweb": {:hex, :mochiweb, "3.2.2", "bb435384b3b9fd1f92f2f3fe652ea644432877a3e8a81ed6459ce951e0482ad3", [:rebar3], [], "hexpm", "4114e51f1b44c270b3242d91294fe174ce1ed989100e8b65a1fab58e0cba41d5"},
- "mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"},
+ "mock": {:hex, :mock, "0.3.9", "10e44ad1f5962480c5c9b9fa779c6c63de9bd31997c8e04a853ec990a9d841af", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "9e1b244c4ca2551bb17bb8415eed89e40ee1308e0fbaed0a4fdfe3ec8a4adbd3"},
"mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"},
+ "msgpack": {:hex, :msgpack, "0.8.1", "deb35c13291eafe56ad9870374c2eaa92323dc5503d50432ebcaf47052e6d343", [:rebar3], [], "hexpm", "04d9a75bc6f4bed8627ee1e7aa9df37601f510fdc786a84fe932bb21d765565f"},
"nebulex": {:hex, :nebulex, "2.6.4", "4b00706e0e676474783d988962abf74614480e13c0a32645acb89bb32b660e09", [:mix], [{:decorator, "~> 1.4", [hex: :decorator, repo: "hexpm", optional: true]}, {:shards, "~> 1.1", [hex: :shards, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "25bdabf3fb86035c8151bba60bda20f80f96ae0261db7bd4090878ff63b03581"},
- "nebulex_redis_adapter": {:hex, :nebulex_redis_adapter, "2.4.1", "993c9c7cc4f9d982db2bcef5e761176db23ac14c574b17892d7ba6dd9e0d749c", [:mix], [{:crc, "~> 0.10", [hex: :crc, repo: "hexpm", optional: true]}, {:jchash, "~> 0.1", [hex: :jchash, repo: "hexpm", optional: true]}, {:nebulex, "~> 2.6", [hex: :nebulex, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:redix, "~> 1.5", [hex: :redix, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "180a313588c16ad850a4ef6360963312479d871dc38229171235349fd61bc629"},
+ "nebulex_redis_adapter": {:hex, :nebulex_redis_adapter, "2.4.2", "19f987e52fa5b31bf0d254736276a867820d2115834b05ee2d3f41cd62caf3a0", [:mix], [{:crc, "~> 0.10", [hex: :crc, repo: "hexpm", optional: true]}, {:jchash, "~> 0.1", [hex: :jchash, repo: "hexpm", optional: true]}, {:nebulex, "~> 2.6", [hex: :nebulex, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:redix, "~> 1.5", [hex: :redix, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b9d8e4cf9c03f019b077d7a42530f57bac6dda619ddf40820d5e276ec6c346dd"},
"nestru": {:hex, :nestru, "1.0.1", "f02321db91b898da3d598c274f2ccba2c41ec5c50c942eabe900474dbfe4bce3", [:mix], [], "hexpm", "e4fbbd6d64b1c8cb37ef590a891f0b6b17b0b880c1c5ce2ac98de02c0ad7417e"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
- "nimble_ownership": {:hex, :nimble_ownership, "1.0.0", "3f87744d42c21b2042a0aa1d48c83c77e6dd9dd357e425a038dd4b49ba8b79a1", [:mix], [], "hexpm", "7c16cc74f4e952464220a73055b557a273e8b1b7ace8489ec9d86e9ad56cb2cc"},
- "nimble_parsec": {:hex, :nimble_parsec, "1.4.1", "f41275a0354c736db4b1d255b5d2a27c91028e55c21ea3145b938e22649ffa3f", [:mix], [], "hexpm", "605e44204998f138d6e13be366c8e81af860e726c8177caf50067e1b618fe522"},
+ "nimble_ownership": {:hex, :nimble_ownership, "1.0.1", "f69fae0cdd451b1614364013544e66e4f5d25f36a2056a9698b793305c5aa3a6", [:mix], [], "hexpm", "3825e461025464f519f3f3e4a1f9b68c47dc151369611629ad08b636b73bb22d"},
+ "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"open_trip_planner_client": {:git, "https://github.com/mbta/open_trip_planner_client.git", "b127ccfcc7cdfc40bc91e30c55d41754426a7cc9", [tag: "v0.11.3"]},
"parallel_stream": {:hex, :parallel_stream, "1.1.0", "f52f73eb344bc22de335992377413138405796e0d0ad99d995d9977ac29f1ca9", [:mix], [], "hexpm", "684fd19191aedfaf387bbabbeb8ff3c752f0220c8112eb907d797f4592d6e871"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
- "phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"},
- "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.2", "3b83b24ab5a2eb071a20372f740d7118767c272db386831b2e77638c4dcc606d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "3f94d025f59de86be00f5f8c5dd7b5965a3298458d21ab1c328488be3b5fcd59"},
+ "phoenix": {:hex, :phoenix, "1.7.18", "5310c21443514be44ed93c422e15870aef254cf1b3619e4f91538e7529d2b2e4", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1797fcc82108442a66f2c77a643a62980f342bfeb63d6c9a515ab8294870004e"},
+ "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"},
"phoenix_html": {:hex, :phoenix_html, "4.2.0", "83a4d351b66f472ebcce242e4ae48af1b781866f00ef0eb34c15030d4e2069ac", [:mix], [], "hexpm", "9713b3f238d07043583a94296cc4bbdceacd3b3a6c74667f4df13971e7866ec8"},
"phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"},
- "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.4", "4508e481f791ce62ec6a096e13b061387158cbeefacca68c6c1928e1305e23ed", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "2984aae96994fbc5c61795a73b8fb58153b41ff934019cfb522343d2d3817d59"},
+ "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"},
"phoenix_live_view": {:hex, :phoenix_live_view, "1.0.2", "e7b1dd68c86326e2c45cc81da41e332cc8aa7228a7161e2c811dcd7f1dd14db1", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8a40265b0cd7d3a35f136dfa3cc048e3b198fc3718763411a78c323a44ebebee"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
- "phoenix_storybook": {:hex, :phoenix_storybook, "0.6.4", "d7bfdf6a20214251ff7453cbcb9de5f6f8a7db606f9c21846a87ba09058d8f0e", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:heroicons, "~> 0.5", [hex: :heroicons, repo: "hexpm", optional: true]}, {:jason, "~> 1.3", [hex: :jason, repo: "hexpm", optional: true]}, {:makeup_eex, "~> 0.1.0", [hex: :makeup_eex, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html_helpers, "~> 1.0", [hex: :phoenix_html_helpers, repo: "hexpm", optional: false]}, {:phoenix_live_view, "> 0.18.7", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}], "hexpm", "a63669c010e638882d287aae9c2cfd4a2c64d68e05f85403e213101283a74d3f"},
+ "phoenix_storybook": {:hex, :phoenix_storybook, "0.8.1", "911c69a1f93ee51eeac99d61caf573aa52be16443f623238315b6a97ad4f72e9", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:jason, "~> 1.3", [hex: :jason, repo: "hexpm", optional: true]}, {:makeup_eex, "~> 1.0.0", [hex: :makeup_eex, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html_helpers, "~> 1.0", [hex: :phoenix_html_helpers, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}], "hexpm", "4845c1beee54adcc1d7933fd73b1fd387a608baf3e6b78866acf1a08e72a378f"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
@@ -91,7 +92,7 @@
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"poison": {:hex, :poison, "6.0.0", "9bbe86722355e36ffb62c51a552719534257ba53f3271dacd20fbbd6621a583a", [:mix], [{:decimal, "~> 2.1", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "bb9064632b94775a3964642d6a78281c07b7be1319e0016e1643790704e739a2"},
"pollution": {:hex, :pollution, "0.9.2", "3f67542631071c99f807d2a8f9da799c07cd983c902f5357b9e1569c20a26e76", [:mix], [], "hexpm", "6399fd8ffd97dcc3d9d277f60542a234d644d7bcc0d48c8fda93d6be4801bac2"},
- "polyline": {:hex, :polyline, "1.4.0", "36666a3d010692d91d89501e13d385b6b136ef446f05814fb2e90149349d5a14", [:mix], [{:vector, "~> 1.0", [hex: :vector, repo: "hexpm", optional: false]}], "hexpm", "0e1e57497ba05f0355e23d722b03d5dc9a68d5d0c17c9f2dd5efefaa96f8960d"},
+ "polyline": {:hex, :polyline, "1.5.0", "31519f59940cd7daa733a3c5c04c58be9d3d01e7aeb81421ca20678c40814471", [:mix], [], "hexpm", "a0ab24f026c758061e354609d89cbb96d61486ac13c71e148dde15fc53f7000d"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"proper": {:hex, :proper, "1.4.0", "89a44b8c39d28bb9b4be8e4d715d534905b325470f2e0ec5e004d12484a79434", [:rebar3], [], "hexpm", "18285842185bd33efbda97d134a5cb5a0884384db36119fee0e3cfa488568cbb"},
"quixir": {:hex, :quixir, "0.9.3", "f01c37386b9e1d0526f01a8734a6d7884af294a0ec360f05c24c7171d74632bd", [:mix], [{:pollution, "~> 0.9.2", [hex: :pollution, repo: "hexpm", optional: false]}], "hexpm", "4f3a1fe7c82b767d935b3f7b94cf34b91ef78bb487ef256b303d77417fc7d589"},
@@ -100,20 +101,20 @@
"recase": {:hex, :recase, "0.8.1", "ab98cd35857a86fa5ca99036f575241d71d77d9c2ab0c39aacf1c9b61f6f7d1d", [:mix], [], "hexpm", "9fd8d63e7e43bd9ea385b12364e305778b2bbd92537e95c4b2e26fc507d5e4c2"},
"recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"},
"redix": {:hex, :redix, "1.5.2", "ab854435a663f01ce7b7847f42f5da067eea7a3a10c0a9d560fa52038fd7ab48", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:nimble_options, "~> 0.5.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "78538d184231a5d6912f20567d76a49d1be7d3fca0e1aaaa20f4df8e1142dcb8"},
- "req": {:hex, :req, "0.5.6", "8fe1eead4a085510fe3d51ad854ca8f20a622aae46e97b302f499dfb84f726ac", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cfaa8e720945d46654853de39d368f40362c2641c4b2153c886418914b372185"},
+ "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"},
"rstar": {:git, "https://github.com/armon/erl-rstar.git", "a406b2cce609029bf65b9ccfbe93a0416c0ee0cd", []},
- "sentry": {:hex, :sentry, "10.7.1", "33392222d80ccff99c503f972998d2858b4c1e5aca2219a34269b68dacba8e7d", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_ownership, "~> 0.3.0 or ~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.20", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "56291312397bf2b6afab6cf4f7aa1f27413b0eb2ceeb63b8aab2d7658aaea882"},
+ "sentry": {:hex, :sentry, "10.8.1", "aa45309785e1521416225adb16e0b4d8b957578804527f3c7babb6fefbc5e456", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_ownership, "~> 0.3.0 or ~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.20 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "495b3cdadad90ba72eef973aa3dec39b3b8b2a362fe87e2f4ef32133ac3b4097"},
"server_sent_event_stage": {:hex, :server_sent_event_stage, "1.2.1", "ede8c63496e19f039972503aa242cb4c16a301d495be7d9fdda2f952298cbf0c", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:ex_doc, "~> 0.22", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:gen_stage, "~> 1.1", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:mint, "~> 1.4", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "5b84b2c886bfa3ab38143d424eff6ec9d224e65ca576652aab762ed9d11847c3"},
"sizeable": {:hex, :sizeable, "1.0.2", "625fe06a5dad188b52121a140286f1a6ae1adf350a942cf419499ecd8a11ee29", [:mix], [], "hexpm", "4bab548e6dfba777b400ca50830a9e3a4128e73df77ab1582540cf5860601762"},
"slipstream": {:hex, :slipstream, "1.1.1", "7e56f62f1a9ee81351e3c36f57b9b187e00dc2f470e70ba46ea7ad16e80b061f", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mint_web_socket, "~> 0.2 or ~> 1.0", [hex: :mint_web_socket, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.1 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c20e420cde1654329d38ec3aa1c0e4debbd4c91ca421491e7984ad4644e638a6"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
- "sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
+ "sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
- "telemetry_metrics": {:hex, :telemetry_metrics, "1.0.0", "29f5f84991ca98b8eb02fc208b2e6de7c95f8bb2294ef244a176675adc7775df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f23713b3847286a534e005126d4c959ebcca68ae9582118ce436b521d1d47d5d"},
+ "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"},
"telemetry_metrics_splunk": {:hex, :telemetry_metrics_splunk, "0.0.6-alpha", "76812c1ece239955d1d9c7a0556fd51ccaebb89abcec357b9c9dd67e619057b4", [:mix], [{:finch, "~> 0.18", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.1", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:recase, "~> 0.8", [hex: :recase, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "2150e2112b846cf68bf604b871386d68114fc289472d9a4308146ad7b3fbeee6"},
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
"telemetry_test": {:hex, :telemetry_test, "0.1.2", "122d927567c563cf57773105fa8104ae4299718ec2cbdddcf6776562c7488072", [:mix], [{:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7bd41a49ecfd33ecd82d2c7edae19a5736f0d2150206d0ee290dcf3885d0e14d"},
- "tesla": {:hex, :tesla, "1.11.2", "24707ac48b52f72f88fc05d242b1c59a85d1ee6f16f19c312d7d3419665c9cd5", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "c549cd03aec6a7196a641689dd378b799e635eb393f689b4bd756f750c7a4014"},
+ "tesla": {:hex, :tesla, "1.13.2", "85afa342eb2ac0fee830cf649dbd19179b6b359bec4710d02a3d5d587f016910", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "960609848f1ef654c3cdfad68453cd84a5febecb6ed9fed9416e36cd9cd724f9"},
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
"typed_ecto_schema": {:hex, :typed_ecto_schema, "0.4.1", "a373ca6f693f4de84cde474a67467a9cb9051a8a7f3f615f1e23dc74b75237fa", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "85c6962f79d35bf543dd5659c6adc340fd2480cacc6f25d2cc2933ea6e8fcb3b"},
"typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"},
@@ -122,7 +123,7 @@
"unrooted_polytree": {:hex, :unrooted_polytree, "0.1.1", "95027b1619d707fcbbd8980708a50efd170782142dd3de5112e9332d4cc27fef", [:mix], [], "hexpm", "9c8143d2015526ae49c3642ca509802e4db129685a57a0ec413e66546fe0c251"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"},
"vector": {:hex, :vector, "1.1.0", "0789b5e00e9c551d8d5880acab9a8f44ed46690d083af397018bf0c7f30c1092", [:mix], [], "hexpm", "48b0a800ec88e55b12c689b09100e4c9ba41ea1befb459221c085a4e70040696"},
- "wallaby": {:hex, :wallaby, "0.30.9", "51d60682092c3c428c63b656b818e2258202b9f9a31ec37230659647ae20325b", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "62e3ccb89068b231b50ed046219022020516d44f443eebef93a19db4be95b808"},
+ "wallaby": {:hex, :wallaby, "0.30.10", "574afb8796521252daf49a4cd76a1c389d53cae5897f2d4b5f55dfae159c8e50", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "a8f89b92d8acce37a94b5dfae6075c2ef00cb3689d6333f5f36c04b381c077b2"},
"web_driver_client": {:hex, :web_driver_client, "0.2.0", "63b76cd9eb3b0716ec5467a0f8bead73d3d9612e63f7560d21357f03ad86e31a", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "83cc6092bc3e74926d1c8455f0ce927d5d1d36707b74d9a65e38c084aab0350f"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"},
diff --git a/test/dotcom/trip_plan/anti_corruption_layer_test.exs b/test/dotcom/trip_plan/anti_corruption_layer_test.exs
index b56dc6de8d..e02438de2e 100644
--- a/test/dotcom/trip_plan/anti_corruption_layer_test.exs
+++ b/test/dotcom/trip_plan/anti_corruption_layer_test.exs
@@ -1,55 +1,11 @@
defmodule Dotcom.TripPlan.AntiCorruptionLayerTest do
use ExUnit.Case
- import Dotcom.TripPlan.AntiCorruptionLayer, only: [convert_old_action: 1, convert_old_params: 1]
+ import Dotcom.TripPlan.AntiCorruptionLayer, only: [convert_old_params: 1]
import Mox
- import Test.Support.Factories.LocationService.LocationService
setup :verify_on_exit!
- describe "convert_old_action/1" do
- test "returns all defaults when no params are given" do
- assert convert_old_action(%{}) == convert_old_action(%{"plan" => %{}})
- end
-
- test "returns params representing successfully geocoded result" do
- query = Faker.Address.street_address()
- geocoded_result = build(:address)
-
- expect(LocationService.Mock, :geocode, fn ^query ->
- {:ok, [geocoded_result]}
- end)
-
- assert convert_old_action(%{from: query}) == %{
- "plan" => %{
- "from" => geocoded_result.formatted,
- "from_latitude" => geocoded_result.latitude,
- "from_longitude" => geocoded_result.longitude
- }
- }
- end
-
- test "returns all defaults when no address found" do
- query = Faker.Address.street_address()
-
- expect(LocationService.Mock, :geocode, fn _ ->
- {:ok, []}
- end)
-
- assert convert_old_action(%{from: query}) == %{"plan" => %{}}
- end
-
- test "returns all defaults for geocoding error" do
- query = Faker.Address.street_address()
-
- expect(LocationService.Mock, :geocode, fn _ ->
- {:error, :internal_error}
- end)
-
- assert convert_old_action(%{from: query}) == %{"plan" => %{}}
- end
- end
-
describe "convert_old_params/1" do
test "returns all defaults when no params are given" do
assert convert_old_params(%{}) == convert_old_params(%{"plan" => %{}})
diff --git a/test/dotcom_web/controllers/trip_plan_controller_test.exs b/test/dotcom_web/controllers/trip_plan_controller_test.exs
index 0e2cb30203..1010dcd3bd 100644
--- a/test/dotcom_web/controllers/trip_plan_controller_test.exs
+++ b/test/dotcom_web/controllers/trip_plan_controller_test.exs
@@ -1,720 +1,48 @@
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"
+ describe "location/2" do
+ test "from|to/query redirects with an encoded plan when the location is found", %{conn: conn} do
+ # Setup
+ expect(LocationService.Mock, :geocode, 2, fn _ ->
+ {:ok,
+ [
+ %LocationService.Address{
+ latitude: Faker.Address.latitude(),
+ longitude: Faker.Address.longitude()
}
- }
- ]}
- 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"}}
+ path = live_path(conn, DotcomWeb.Live.TripPlanner)
+ query = Faker.Address.street_address() |> URI.encode()
- conn =
- get(
- conn,
- trip_plan_path(conn, :from, "Address", plan_params)
- )
+ Enum.each(["from", "to"], fn direction ->
+ # Exercise
+ conn = get(conn, "#{path}/#{direction}/#{query}")
- 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
+ # Verify
+ assert redirected_to(conn, 301) =~ path <> "?plan="
+ end)
end
- test "is unable to get address so it redirects to index", %{conn: conn} do
+ test "from|to/query redirects w/out an encoded plan when no location is found", %{conn: conn} do
+ # Setup
expect(LocationService.Mock, :geocode, fn _ ->
- {:error, :something}
+ {:error, :not_found}
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"}}
+ path = live_path(conn, DotcomWeb.Live.TripPlanner)
+ direction = Faker.Util.pick(["from", "to"])
+ query = Faker.Address.street_address() |> URI.encode()
- conn =
- get(
- conn,
- trip_plan_path(conn, :to, "Address", plan_params)
- )
+ # Exercise
+ conn = get(conn, "#{path}/#{direction}/#{query}")
- assert redirected_to(conn) == trip_plan_path(conn, :index, plan_params)
+ # Verify
+ assert redirected_to(conn, 301) == path
end
end
end
diff --git a/test/dotcom_web/live/trip_planner_test.exs b/test/dotcom_web/live/trip_planner_test.exs
index 7c4093c397..2ae926dfa2 100644
--- a/test/dotcom_web/live/trip_planner_test.exs
+++ b/test/dotcom_web/live/trip_planner_test.exs
@@ -1,404 +1,377 @@
defmodule DotcomWeb.Live.TripPlannerTest do
use DotcomWeb.ConnCase, async: true
+ import DotcomWeb.Router.Helpers, only: [live_path: 2]
import Mox
import Phoenix.LiveViewTest
- alias OpenTripPlannerClient.Test.Support.Factory
- alias Test.Support.Factories.TripPlanner.TripPlanner, as: TripPlannerFactory
+ alias Dotcom.TripPlan.AntiCorruptionLayer
+ alias Test.Support.Factories.{MBTA.Api, Stops.Stop, TripPlanner.TripPlanner}
setup :verify_on_exit!
- defp stub_otp_results(itineraries) do
- expect(OpenTripPlannerClient.Mock, :plan, fn _ ->
- {:ok, %OpenTripPlannerClient.Plan{itineraries: itineraries}}
- end)
-
- # 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)
- end
+ @valid_params %{
+ "from" => %{
+ "latitude" => Faker.Address.latitude() |> Float.to_string(),
+ "longitude" => Faker.Address.longitude() |> Float.to_string(),
+ "name" => Faker.Address.street_name(),
+ "stop_id" => ""
+ },
+ "to" => %{
+ "latitude" => Faker.Address.latitude() |> Float.to_string(),
+ "longitude" => Faker.Address.longitude() |> Float.to_string(),
+ "name" => Faker.Address.street_name(),
+ "stop_id" => ""
+ }
+ }
+
+ describe "mount" do
+ test "setting no params redirects to a plan of defaults", %{conn: conn} do
+ # Setup
+ path = live_path(conn, DotcomWeb.Live.TripPlanner)
+
+ # Exercise
+ {:error, {:live_redirect, %{to: url}}} = live(conn, path)
+
+ new_params =
+ url
+ |> decode_params()
+ |> MapSet.new()
+
+ default_params = AntiCorruptionLayer.default_params() |> MapSet.new()
+
+ # Verify
+ assert MapSet.intersection(new_params, default_params) == default_params
+ end
- defp stub_populated_otp_results do
- itineraries = TripPlannerFactory.build_list(3, :otp_itinerary)
+ test "setting old params redirects to a plan of matching new params", %{conn: conn} do
+ # Setup
+ query =
+ %{
+ "plan[from]" => Kernel.get_in(@valid_params, ["from", "name"]),
+ "plan[from_latitude]" => Kernel.get_in(@valid_params, ["from", "latitude"]),
+ "plan[from_longitude]" => Kernel.get_in(@valid_params, ["from", "longitude"]),
+ "plan[to]" => Kernel.get_in(@valid_params, ["to", "name"]),
+ "plan[to_latitude]" => Kernel.get_in(@valid_params, ["to", "latitude"]),
+ "plan[to_longitude]" => Kernel.get_in(@valid_params, ["to", "longitude"])
+ }
+ |> URI.encode_query()
- stub_otp_results(itineraries)
- end
+ path = live_path(conn, DotcomWeb.Live.TripPlanner) <> "?#{query}"
- # 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)
- end
+ # Exercise
+ {:error, {:live_redirect, %{to: url}}} = live(conn, path)
+
+ new_params =
+ url
+ |> decode_params()
+ |> MapSet.new()
- test "Preview version behind basic auth", %{conn: conn} do
- conn = get(conn, ~p"/preview/trip-planner")
+ valid_params = MapSet.new(@valid_params)
- {_header_name, header_value} = List.keyfind(conn.resp_headers, "www-authenticate", 0)
- assert conn.status == 401
- assert header_value =~ "Basic"
+ # Verify
+ assert MapSet.intersection(new_params, valid_params) == valid_params
+ end
end
- describe "Trip Planner" do
+ describe "inputs" do
setup %{conn: conn} do
- [username: username, password: password] =
- Application.get_env(:dotcom, DotcomWeb.Router)[:basic_auth_readonly]
+ stub(MBTA.Api.Mock, :get_json, fn "/schedules/", _ ->
+ %JsonApi{}
+ end)
- {:ok, view, html} =
- conn
- |> put_req_header("authorization", "Basic " <> Base.encode64("#{username}:#{password}"))
- |> live(~p"/preview/trip-planner")
+ {:error, {:live_redirect, %{to: url}}} =
+ live(conn, live_path(conn, DotcomWeb.Live.TripPlanner))
- %{html: html, view: view}
- end
+ {:ok, view, _} = live(conn, url)
- # 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"
+ %{view: view}
end
- # test "shows errors on form submit", %{view: view} do
- # end
+ test "setting 'from' places a pin on the map", %{view: view} do
+ # Setup
+ params = Map.take(@valid_params, ["from"])
- # test "pushes updated location to the map", %{view: view} do
- # end
- end
+ # Exercise
+ view |> element("form") |> render_change(%{"input_form" => params})
- describe "Trip Planner location validations" do
- setup %{conn: conn} do
- [username: username, password: password] =
- Application.get_env(:dotcom, DotcomWeb.Router)[:basic_auth_readonly]
+ # Verify
+ document = render(view) |> Floki.parse_document!()
- %{
- conn:
- conn
- |> put_req_header("authorization", "Basic " <> Base.encode64("#{username}:#{password}"))
- }
+ assert Floki.get_by_id(document, "mbta-metro-pin-0")
end
- test "shows error if origin and destination are the same", %{conn: conn} do
- latitude = Faker.Address.latitude()
- longitude = Faker.Address.longitude()
+ test "setting 'to' places a pin on the map", %{view: view} do
+ # Setup
+ params = Map.take(@valid_params, ["to"])
- params = %{
- "plan" => %{
- "from_latitude" => "#{latitude}",
- "from_longitude" => "#{longitude}",
- "to_latitude" => "#{latitude}",
- "to_longitude" => "#{longitude}"
- }
- }
+ # Exercise
+ view |> element("form") |> render_change(%{"input_form" => params})
- {:ok, view, _html} =
- conn
- |> live(~p"/preview/trip-planner?#{params}")
+ # Verify
+ document = render(view) |> Floki.parse_document!()
- assert render_async(view) =~
- "Please select a destination at a different location from the origin."
+ assert Floki.get_by_id(document, "mbta-metro-pin-1")
end
- test "does not show errors if origin or destination are missing", %{conn: conn} do
- {:ok, view, _html} =
- conn
- |> live(~p"/preview/trip-planner")
+ test "swapping from/to swaps pins on the map", %{view: view} do
+ # Setup
+ stub(OpenTripPlannerClient.Mock, :plan, fn _ ->
+ {:ok, %OpenTripPlannerClient.Plan{itineraries: []}}
+ end)
- refute render_async(view) =~ "Please specify an origin location."
- refute render_async(view) =~ "Please add a destination."
- end
- end
+ # Exercise
+ view |> element("form") |> render_change(%{"input_form" => @valid_params})
- 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}")
- )
- }
+ view
+ |> element("button[phx-click='swap_direction']")
+ |> render_click()
+
+ # Verify
+ document = render(view) |> Floki.parse_document!()
+
+ pins =
+ Enum.map(["mbta-metro-pin-0", "mbta-metro-pin-1"], fn id ->
+ Floki.get_by_id(document, id)
+ |> Floki.attribute("data-coordinates")
+ |> List.first()
+ |> parse_coordinates()
+ end)
+
+ assert pins == [
+ [@valid_params["to"]["longitude"], @valid_params["to"]["latitude"]],
+ [@valid_params["from"]["longitude"], @valid_params["from"]["latitude"]]
+ ]
end
- test "shows 'No trips found' text", %{conn: conn} do
+ test "selecting a time other than 'now' shows the datepicker", %{view: view} do
+ # Setup
params = %{
- "plan" => %{
- "from_latitude" => "#{Faker.Address.latitude()}",
- "from_longitude" => "#{Faker.Address.longitude()}",
- "to_latitude" => "#{Faker.Address.latitude()}",
- "to_longitude" => "#{Faker.Address.longitude()}"
- }
+ "datetime_type" => "depart_at"
}
- stub_otp_results([])
+ # Exercise
+ view |> element("form") |> render_change(%{"input_form" => params})
- {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}")
+ # Verify
+ document = render(view) |> Floki.parse_document!()
- assert render_async(view) =~ "No trips found"
+ assert Floki.get_by_id(document, "date-picker")
end
- 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)
+ test "selecting 'now' after selecting another time hides the datepicker", %{view: view} do
+ # Setup
+ open_params = %{
+ "datetime_type" => "depart_at"
+ }
- %{
- 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()}"
- }
- }
+ closed_params = %{
+ "datetime_type" => "now"
}
- end
- test "starts out with no 'View All Options' button", %{conn: conn, params: params} do
- stub_populated_otp_results()
+ # Exercise
+ view |> element("form") |> render_change(%{"input_form" => open_params})
+ view |> element("form") |> render_change(%{"input_form" => closed_params})
- {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}")
+ # Verify
+ document = render(view) |> Floki.parse_document!()
- refute render_async(view) =~ "View All Options"
+ refute Floki.get_by_id(document, "date-picker")
end
- test "clicking 'Details' button opens details view", %{conn: conn, params: params} do
- stub_populated_otp_results()
+ 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"))
- {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}")
+ # Exercise
+ view |> element("form") |> render_change(%{"input_form" => params})
- render_async(view)
- view |> element("button[phx-value-index=\"0\"]", "Details") |> render_click()
+ # Verify
+ document = render(view)
- assert render_async(view) =~ "View All Options"
+ assert document =~ "Please select a destination at a different location from the origin."
end
- test "clicking 'View All Options' button from details view closes it", %{
- conn: conn,
- params: params
- } do
- stub_populated_otp_results()
+ 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)
- {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}")
+ # Exercise
+ view |> element("form") |> render_change(%{"input_form" => @valid_params})
- render_async(view)
+ document = 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"
+ assert document =~ "Cannot connect to OpenTripPlanner. Please try again later."
end
- test "'Depart At' buttons toggle which itinerary to show", %{
- conn: conn,
- params: params
- } do
- trip_headsign_1 = "Headsign1"
- trip_headsign_2 = "Headsign2"
+ test "an OTP error shows up as an error message", %{view: view} do
+ # Setup
+ error = Faker.Company.bullshit()
expect(OpenTripPlannerClient.Mock, :plan, fn _ ->
- {:ok,
- %OpenTripPlannerClient.Plan{
- itineraries: grouped_itineraries_from_headsigns([trip_headsign_1, trip_headsign_2])
- }}
+ {:error, [%{message: error}]}
end)
- stub(MBTA.Api.Mock, :get_json, fn "/trips/" <> id, [] ->
- %JsonApi{
- data: [
- Test.Support.Factories.MBTA.Api.build(:trip_item, %{id: id})
- ]
- }
- end)
+ # Exercise
+ view |> element("form") |> render_change(%{"input_form" => @valid_params})
- {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}")
+ # Verify
+ document = render_async(view)
+ text = document |> Floki.find("span[data-test='results-summary:error']") |> Floki.text()
- render_async(view)
+ assert text == error
+ end
+ end
- view |> element("button", "Details") |> render_click()
+ describe "results" do
+ setup %{conn: conn} do
+ stub(MBTA.Api.Mock, :get_json, fn "/trips/" <> id, [] ->
+ %JsonApi{data: [Api.build(:trip_item, %{id: id})]}
+ end)
+
+ stub(Stops.Repo.Mock, :get, fn _ ->
+ Stop.build(:stop)
+ end)
- assert render_async(view) =~ trip_headsign_1
- refute render_async(view) =~ trip_headsign_2
+ {:error, {:live_redirect, %{to: url}}} =
+ live(conn, live_path(conn, DotcomWeb.Live.TripPlanner))
- view |> element("#itinerary-detail-departure-times button:last-child") |> render_click()
+ {:ok, view, _} = live(conn, url)
- assert render_async(view) =~ trip_headsign_2
- refute render_async(view) =~ trip_headsign_1
+ %{view: view}
end
- test "'Depart At' buttons don't appear if there would only be one", %{
- conn: conn,
- params: params
- } do
+ test "using valid params shows results", %{view: view} do
+ # Setup
expect(OpenTripPlannerClient.Mock, :plan, fn _ ->
- {:ok,
- %OpenTripPlannerClient.Plan{
- itineraries: TripPlannerFactory.build_list(1, :otp_itinerary)
- }}
- end)
+ itineraries = TripPlanner.build_list(1, :otp_itinerary)
- stub(MBTA.Api.Mock, :get_json, fn "/trips/" <> id, [] ->
- %JsonApi{
- data: [
- Test.Support.Factories.MBTA.Api.build(:trip_item, %{id: id})
- ]
- }
+ {:ok, %OpenTripPlannerClient.Plan{itineraries: itineraries}}
end)
- {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}")
-
- render_async(view)
+ # Exercise
+ view |> element("form") |> render_change(%{"input_form" => @valid_params})
- view |> element("button", "Details") |> render_click()
+ # Verify
+ document = render_async(view) |> Floki.parse_document!()
- refute view |> element("#itinerary-detail-departure-times") |> has_element?()
+ assert Floki.get_by_id(document, "trip-planner-results")
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"
+ test "groupable results show up in groups", %{view: view} do
+ # Setup
+ group_count = :rand.uniform(5)
expect(OpenTripPlannerClient.Mock, :plan, fn _ ->
- {:ok,
- %OpenTripPlannerClient.Plan{
- itineraries: grouped_itineraries_from_headsigns([trip_headsign_1, trip_headsign_2])
- }}
+ itineraries = TripPlanner.groupable_otp_itineraries(group_count)
+
+ {:ok, %OpenTripPlannerClient.Plan{itineraries: itineraries}}
end)
- stub(MBTA.Api.Mock, :get_json, fn "/trips/" <> id, [] ->
- %JsonApi{
- data: [
- Test.Support.Factories.MBTA.Api.build(:trip_item, %{id: id})
- ]
- }
+ # Exercise
+ view |> element("form") |> render_change(%{"input_form" => @valid_params})
+
+ # Verify
+ document = render_async(view) |> Floki.parse_document!()
+
+ Enum.each(0..(group_count - 1), fn i ->
+ assert Floki.find(document, "div[data-test='results:itinerary_group:#{i}']") != []
end)
+ end
- {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}")
+ test "selecting a group shows the group's itineraries", %{view: view} do
+ # Setup
+ group_count = :rand.uniform(5)
- render_async(view)
+ expect(OpenTripPlannerClient.Mock, :plan, fn _ ->
+ itineraries = TripPlanner.groupable_otp_itineraries(group_count)
- 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()
+ {:ok, %OpenTripPlannerClient.Plan{itineraries: itineraries}}
+ end)
- assert render_async(view) =~ trip_headsign_1
- refute render_async(view) =~ trip_headsign_2
- end
+ selected_group = Faker.Util.pick(0..(group_count - 1))
- test "displays 'No trips found.' if given an empty list of itineraries", %{
- conn: conn,
- params: params
- } do
- stub_otp_results([])
+ # Exercise
+ view |> element("form") |> render_change(%{"input_form" => @valid_params})
+ render_async(view)
+ view |> element("button[phx-value-index='#{selected_group}']", "Details") |> render_click()
- {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}")
+ # Verify
+ document = render(view) |> Floki.parse_document!()
- assert render_async(view) =~ "No trips found."
+ assert Floki.find(
+ document,
+ "div[data-test='results:itinerary_group:selected:#{selected_group}']"
+ ) != []
end
- test "displays error message from the Open Trip Planner client", %{conn: conn, params: params} do
- error_message = Faker.Lorem.sentence()
+ test "unselecting a group shows all groups", %{view: view} do
+ group_count = :rand.uniform(5)
+ # Setup
expect(OpenTripPlannerClient.Mock, :plan, fn _ ->
- {:error, [%OpenTripPlannerClient.Error{message: error_message}]}
+ itineraries = TripPlanner.groupable_otp_itineraries(group_count)
+
+ {:ok, %OpenTripPlannerClient.Plan{itineraries: itineraries}}
end)
- {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}")
+ selected_group = Faker.Util.pick(0..(group_count - 1))
+
+ # Exercise
+ view |> element("form") |> render_change(%{"input_form" => @valid_params})
+ render_async(view)
+ view |> element("button[phx-value-index='#{selected_group}']", "Details") |> render_click()
- assert render_async(view) =~ error_message
+ view
+ |> element("button[phx-click='reset_itinerary_group']", "View All Options")
+ |> render_click()
+
+ # Verify
+ document = render(view) |> Floki.parse_document!()
+
+ assert Floki.find(
+ document,
+ "div[data-test='results:itinerary_group:selected:#{selected_group}']"
+ ) == []
end
- test "does not display 'No trips found.' if there's another error", %{
- conn: conn,
- params: params
- } do
+ test "selecting an itinerary displays it", %{view: view} do
+ # Setup
expect(OpenTripPlannerClient.Mock, :plan, fn _ ->
- {:error, [%OpenTripPlannerClient.Error{message: Faker.Lorem.sentence()}]}
+ itineraries = TripPlanner.groupable_otp_itineraries(2, 2)
+
+ {:ok, %OpenTripPlannerClient.Plan{itineraries: itineraries}}
end)
- {:ok, view, _html} = live(conn, ~p"/preview/trip-planner?#{params}")
+ # Exercise
+ view |> element("form") |> render_change(%{"input_form" => @valid_params})
+ render_async(view)
+ view |> element("button[phx-value-index='0']", "Details") |> render_click()
+ view |> element("button[data-test='itinerary_detail:1']") |> render_click()
+
+ # Verify
+ document = render(view) |> Floki.parse_document!()
- refute render_async(view) =~ "No trips found."
+ assert Floki.find(document, "div[data-test='itinerary_detail:selected:1']") != []
end
end
+
+ # Parse coordinates from data-coordinates.
+ defp parse_coordinates(string) do
+ string
+ |> String.replace(~r/\[|\]/, "")
+ |> String.split(",")
+ end
+
+ # Parse the query string from a URL and decode them into a plan.
+ defp decode_params(url) do
+ url
+ |> URI.parse()
+ |> Map.get(:query)
+ |> URI.decode_query()
+ |> Map.get("plan")
+ |> AntiCorruptionLayer.decode()
+ end
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)
diff --git a/test/support/factories/trip_planner/trip_planner.ex b/test/support/factories/trip_planner/trip_planner.ex
index bfcfe75ff6..8b448d6773 100644
--- a/test/support/factories/trip_planner/trip_planner.ex
+++ b/test/support/factories/trip_planner/trip_planner.ex
@@ -2,141 +2,154 @@ defmodule Test.Support.Factories.TripPlanner.TripPlanner do
@moduledoc """
Provides generated test data via ExMachina and Faker.
"""
+
use ExMachina
alias Dotcom.TripPlan.{NamedPosition, Parser}
alias OpenTripPlannerClient.Test.Support.Factory
- def itinerary_factory do
- Factory.build(:itinerary)
- |> limit_route_types()
- |> Parser.parse()
- end
-
- def otp_itinerary_factory do
- Factory.build(:itinerary)
- |> limit_route_types()
- end
-
- def leg_factory do
- [:walking_leg, :transit_leg]
- |> Faker.Util.pick()
- |> Factory.build()
- end
+ # FACTORIES
- def walking_leg_factory do
- Factory.build(:walking_leg)
- |> Parser.parse()
- end
-
- def transit_leg_factory do
- Factory.build(:transit_leg)
- |> limit_route_types()
+ def bus_leg_factory do
+ build(:otp_bus_leg)
|> Parser.parse()
end
- def subway_leg_factory do
+ def cr_leg_factory do
Factory.build(:transit_leg, %{
agency: Factory.build(:agency, %{name: "MBTA"}),
route:
Factory.build(:route, %{
- type: 1
+ type: 2
})
})
|> Parser.parse()
end
- def bus_leg_factory do
+ def express_bus_leg_factory do
Factory.build(:transit_leg, %{
agency: Factory.build(:agency, %{name: "MBTA"}),
route:
Factory.build(:route, %{
+ gtfs_id: "mbta-ma-us:" <> Faker.Util.pick(Fares.express()),
type: 3
})
})
|> Parser.parse()
end
- def express_bus_leg_factory do
+ def ferry_leg_factory do
+ build(:otp_ferry_leg)
+ |> Parser.parse()
+ end
+
+ def itinerary_factory do
+ Factory.build(:itinerary)
+ |> limit_route_types()
+ |> Parser.parse()
+ end
+
+ def leg_factory do
+ [:walking_leg, :transit_leg]
+ |> Faker.Util.pick()
+ |> Factory.build()
+ end
+
+ def named_position_factory do
+ %NamedPosition{
+ name: Faker.Address.city(),
+ stop: nil,
+ latitude: Faker.Address.latitude(),
+ longitude: Faker.Address.longitude()
+ }
+ end
+
+ def otp_bus_leg_factory do
Factory.build(:transit_leg, %{
agency: Factory.build(:agency, %{name: "MBTA"}),
- route:
- Factory.build(:route, %{
- gtfs_id: "mbta-ma-us:" <> Faker.Util.pick(Fares.express()),
- type: 3
- })
+ route: Factory.build(:route, %{type: 3})
})
- |> Parser.parse()
end
- def sl_rapid_leg_factory do
+ def otp_ferry_leg_factory do
Factory.build(:transit_leg, %{
agency: Factory.build(:agency, %{name: "MBTA"}),
- route:
- Factory.build(:route, %{
- gtfs_id: "mbta-ma-us:" <> Faker.Util.pick(Fares.silver_line_rapid_transit()),
- type: 3
- })
+ route: Factory.build(:route, %{type: 4})
})
- |> Parser.parse()
end
- def sl_bus_leg_factory do
+ def otp_subway_leg_factory do
Factory.build(:transit_leg, %{
agency: Factory.build(:agency, %{name: "MBTA"}),
- route:
- Factory.build(:route, %{
- gtfs_id: "mbta-ma-us:" <> Faker.Util.pick(["751", "749"]),
- type: 3
- })
+ route: Factory.build(:route, %{type: 1})
})
+ end
+
+ def otp_itinerary_factory do
+ Factory.build(:itinerary)
+ |> limit_route_types()
+ end
+
+ def personal_detail_factory do
+ Factory.build(:walking_leg)
|> Parser.parse()
+ |> Map.get(:mode)
end
- def cr_leg_factory do
+ def shuttle_leg_factory do
Factory.build(:transit_leg, %{
agency: Factory.build(:agency, %{name: "MBTA"}),
route:
Factory.build(:route, %{
- type: 2
+ type: 3,
+ desc: "Rail Replacement Bus"
})
})
|> Parser.parse()
end
- def ferry_leg_factory do
+ def sl_bus_leg_factory do
Factory.build(:transit_leg, %{
agency: Factory.build(:agency, %{name: "MBTA"}),
route:
Factory.build(:route, %{
- type: 4
+ gtfs_id: "mbta-ma-us:" <> Faker.Util.pick(["751", "749"]),
+ type: 3
})
})
|> Parser.parse()
end
- def shuttle_leg_factory do
+ def sl_rapid_leg_factory do
Factory.build(:transit_leg, %{
agency: Factory.build(:agency, %{name: "MBTA"}),
route:
Factory.build(:route, %{
- type: 3,
- desc: "Rail Replacement Bus"
+ gtfs_id: "mbta-ma-us:" <> Faker.Util.pick(Fares.silver_line_rapid_transit()),
+ type: 3
})
})
|> Parser.parse()
end
- def personal_detail_factory do
- Factory.build(:walking_leg)
- |> Parser.parse()
- |> Map.get(:mode)
- end
-
def step_factory do
Factory.build(:step)
end
+ def stop_named_position_factory do
+ %NamedPosition{
+ name: Faker.Address.street_name(),
+ stop: Test.Support.Factories.Stops.Stop.build(:stop),
+ latitude: Faker.Address.latitude(),
+ longitude: Faker.Address.longitude()
+ }
+ end
+
+ def subway_leg_factory do
+ build(:otp_subway_leg)
+ |> Parser.parse()
+ end
+
def transit_detail_factory do
Factory.build(:transit_leg)
|> limit_route_types()
@@ -144,40 +157,65 @@ defmodule Test.Support.Factories.TripPlanner.TripPlanner do
|> Map.get(:mode)
end
+ def transit_leg_factory do
+ Factory.build(:transit_leg)
+ |> limit_route_types()
+ |> Parser.parse()
+ end
+
+ def walking_leg_factory do
+ Factory.build(:walking_leg)
+ |> Parser.parse()
+ end
+
+ # NON-FACTORY FUNCTIONS
+
+ @doc """
+ Returns a list of itineraries that can be grouped.
+
+ You can pass in the number of groups you want and the number of itineraries in each group.
+ """
+ def groupable_otp_itineraries(group_count \\ 2, itinerary_count \\ 1) do
+ Enum.map(1..group_count, fn _ ->
+ otp_itineraries(itinerary_count)
+ end)
+ |> List.flatten()
+ |> Enum.shuffle()
+ end
+
+ # PRIVATE FUNCTIONS
+
# OpenTripPlannerClient supports a greater number of route_type values than
# Dotcom does! Tweak that here.
- def limit_route_types(%OpenTripPlannerClient.Schema.Itinerary{legs: legs} = itinerary) do
+ defp limit_route_types(%OpenTripPlannerClient.Schema.Itinerary{legs: legs} = itinerary) do
%OpenTripPlannerClient.Schema.Itinerary{
itinerary
| legs: Enum.map(legs, &limit_route_types/1)
}
end
- def limit_route_types(%OpenTripPlannerClient.Schema.Leg{route: route} = leg)
- when route.type > 4 do
+ defp limit_route_types(%OpenTripPlannerClient.Schema.Leg{route: route} = leg)
+ when route.type > 4 do
%OpenTripPlannerClient.Schema.Leg{
leg
| route: %OpenTripPlannerClient.Schema.Route{route | type: Faker.Util.pick([0, 1, 2, 3, 4])}
}
end
- def limit_route_types(leg), do: leg
+ defp limit_route_types(leg), do: leg
- def stop_named_position_factory do
- %NamedPosition{
- name: Faker.Address.street_name(),
- stop: Test.Support.Factories.Stops.Stop.build(:stop),
- latitude: Faker.Address.latitude(),
- longitude: Faker.Address.longitude()
- }
- end
+ # Create a number of otp itineraries with the same two random legs.
+ defp otp_itineraries(itinerary_count) do
+ [a, b, c] =
+ Enum.map(1..3, fn _ ->
+ Factory.build(:place, stop: Factory.build(:stop, gtfs_id: nil))
+ end)
- def named_position_factory do
- %NamedPosition{
- name: Faker.Address.city(),
- stop: nil,
- latitude: Faker.Address.latitude(),
- longitude: Faker.Address.longitude()
- }
+ leg_types = [:otp_bus_leg, :otp_ferry_leg, :otp_subway_leg]
+
+ a_b_leg = build(Faker.Util.pick(leg_types), from: a, to: b)
+ b_c_leg = build(Faker.Util.pick(leg_types), from: b, to: c)
+
+ build_list(itinerary_count, :otp_itinerary, legs: [a_b_leg, b_c_leg])
end
end