diff --git a/.gitignore b/.gitignore index 99bdf5e0..c26154f1 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ phoenix_test-*.tar # Temporary files, for example, from tests. /tmp/ + +/priv/static/assets/ +.envrc diff --git a/config/test.exs b/config/test.exs index df1fddd5..5b51537d 100644 --- a/config/test.exs +++ b/config/test.exs @@ -2,4 +2,26 @@ import Config config :phoenix_test, :endpoint, PhoenixTest.Endpoint -config :logger, level: :warning +config :phoenix_test, PhoenixTest.Endpoint, + server: true, + http: [port: 4000], + live_view: [signing_salt: "112345678212345678312345678412"], + secret_key_base: String.duplicate("57689", 50) + +config :logger, level: :error + +config :wallaby, + otp_app: :phoenix_test, + js_logger: nil, + chromedriver: [ + headless: System.get_env("WALLABY_HEADLESS", "t") in ["t", "true"], + binary: System.get_env("CHROME_EXECUTABLE", "") + ] + +config :esbuild, + version: "0.17.11", + default: [ + args: ~w(js/app.js --bundle --target=es2017 --outdir=../../priv/static/assets), + cd: Path.expand("../test/assets", __DIR__), + env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} + ] diff --git a/lib/phoenix_test.ex b/lib/phoenix_test.ex index 69fddf78..ac525b96 100644 --- a/lib/phoenix_test.ex +++ b/lib/phoenix_test.ex @@ -196,7 +196,6 @@ defmodule PhoenixTest do import Phoenix.ConnTest - alias PhoenixTest.Assertions alias PhoenixTest.Driver @endpoint Application.compile_env(:phoenix_test, :endpoint) @@ -211,7 +210,12 @@ defmodule PhoenixTest do LiveView or a static view. You don't need to worry about which type of page you're visiting. """ - def visit(conn, path) do + + def visit(%Plug.Conn{assigns: %{phoenix_test_js: true}} = conn, path) do + PhoenixTest.Wallaby.build(conn, path) + end + + def visit(%Plug.Conn{} = conn, path) do case get(conn, path) do %{assigns: %{live_module: _}} = conn -> PhoenixTest.Live.build(conn) @@ -225,6 +229,32 @@ defmodule PhoenixTest do end end + @doc """ + Helper to run tests with the Javascript (Wallaby) driver via `@tag :js`. + + ```elixir + setup %{conn: conn} = context do + conn = if context[:js], do: with_js_driver(conn), else: conn + %{conn: conn} + end + + @tag :js + test "with wallaby", %{conn: conn} do + visit(conn, "/path") + end + + test "without wallaby", %{conn: conn} do + visit(conn, "/path") + end + ``` + + You can also use `@describetag :js` and `@moduletag :js`. + Refer to the [ExUnit docs](https://hexdocs.pm/ex_unit/1.16.3/ExUnit.Case.html#module-tags) for more details. + """ + def with_js_driver(%Plug.Conn{} = conn) do + Plug.Conn.assign(conn, :phoenix_test_js, true) + end + @doc """ Clicks a link with given text and performs the action. @@ -691,7 +721,7 @@ defmodule PhoenixTest do assert_has(session, "#user") ``` """ - defdelegate assert_has(session, selector), to: Assertions + defdelegate assert_has(session, selector), to: Driver @doc """ Assert helper to ensure an element with given CSS selector and options. @@ -734,7 +764,7 @@ defmodule PhoenixTest do assert_has(session, ".posts", at: 2, text: "Hello") ``` """ - defdelegate assert_has(session, selector, opts), to: Assertions + defdelegate assert_has(session, selector, opts), to: Driver @doc """ Opposite of `assert_has/2` helper. Verifies that element with @@ -754,7 +784,7 @@ defmodule PhoenixTest do refute_has(session, "#user") ``` """ - defdelegate refute_has(session, selector), to: Assertions + defdelegate refute_has(session, selector), to: Driver @doc """ Opposite of `assert_has/3` helper. Verifies that element with @@ -795,7 +825,7 @@ defmodule PhoenixTest do refute_has(session, ".posts", at: 2, text: "Hello") ``` """ - defdelegate refute_has(session, selector, opts), to: Assertions + defdelegate refute_has(session, selector, opts), to: Driver @doc """ Assert helper to verify current request path. Takes an optional `query_params` @@ -822,12 +852,12 @@ defmodule PhoenixTest do |> assert_path("/users", query_params: %{name: "frodo"}) ``` """ - defdelegate assert_path(session, path), to: Assertions + defdelegate assert_path(session, path), to: Driver @doc """ Same as `assert_path/2` but takes an optional `query_params` map. """ - defdelegate assert_path(session, path, opts), to: Assertions + defdelegate assert_path(session, path, opts), to: Driver @doc """ Verifies current request path is NOT the one provided. Takes an optional @@ -854,11 +884,11 @@ defmodule PhoenixTest do |> refute_path("/users", query_params: %{name: "frodo"}) ``` """ - defdelegate refute_path(session, path), to: Assertions + defdelegate refute_path(session, path), to: Driver @doc """ Same as `refute_path/2` but takes an optional `query_params` for more specific refutation. """ - defdelegate refute_path(session, path, opts), to: Assertions + defdelegate refute_path(session, path, opts), to: Driver end diff --git a/lib/phoenix_test/assertions.ex b/lib/phoenix_test/assertions.ex index a2fd8d55..0bf6ffa8 100644 --- a/lib/phoenix_test/assertions.ex +++ b/lib/phoenix_test/assertions.ex @@ -189,7 +189,7 @@ defmodule PhoenixTest.Assertions do end def assert_path(session, path) do - uri = URI.parse(session.current_path) + uri = URI.parse(PhoenixTest.Driver.current_path(session)) if uri.path == path do assert true @@ -215,7 +215,7 @@ defmodule PhoenixTest.Assertions do defp assert_query_params(session, params) do params = Utils.stringify_keys_and_values(params) - uri = URI.parse(session.current_path) + uri = URI.parse(PhoenixTest.Driver.current_path(session)) query_params = uri.query && URI.decode_query(uri.query) if query_params == params do @@ -234,7 +234,7 @@ defmodule PhoenixTest.Assertions do end def refute_path(session, path) do - uri = URI.parse(session.current_path) + uri = URI.parse(PhoenixTest.Driver.current_path(session)) if uri.path == path do msg = """ @@ -258,7 +258,7 @@ defmodule PhoenixTest.Assertions do defp refute_query_params(session, params) do params = Utils.stringify_keys_and_values(params) - uri = URI.parse(session.current_path) + uri = URI.parse(PhoenixTest.Driver.current_path(session)) query_params = uri.query && URI.decode_query(uri.query) if query_params == params do diff --git a/lib/phoenix_test/driver.ex b/lib/phoenix_test/driver.ex index 078a7a55..3e666776 100644 --- a/lib/phoenix_test/driver.ex +++ b/lib/phoenix_test/driver.ex @@ -16,4 +16,14 @@ defprotocol PhoenixTest.Driver do def unwrap(session, fun) def open_browser(session) def open_browser(session, open_fun) + def current_path(session) + + def assert_has(session, selector) + def assert_has(session, selector, opts) + def refute_has(session, selector) + def refute_has(session, selector, opts) + def assert_path(session, path) + def assert_path(session, path, opts) + def refute_path(session, path) + def refute_path(session, path, opts) end diff --git a/lib/phoenix_test/field.ex b/lib/phoenix_test/field.ex index dd980d24..63b233b3 100644 --- a/lib/phoenix_test/field.ex +++ b/lib/phoenix_test/field.ex @@ -7,8 +7,8 @@ defmodule PhoenixTest.Field do alias PhoenixTest.Query alias PhoenixTest.Utils - @enforce_keys ~w[source_raw label id name value selector]a - defstruct ~w[source_raw label id name value selector]a + @enforce_keys ~w[source_raw label id name value selector raw]a + defstruct ~w[source_raw label id name value selector raw]a def find_input!(html, label) do field = Query.find_by_label!(html, label) @@ -22,7 +22,8 @@ defmodule PhoenixTest.Field do id: id, name: name, value: value, - selector: Element.build_selector(field) + selector: Element.build_selector(field), + raw: field } end @@ -67,7 +68,8 @@ defmodule PhoenixTest.Field do id: id, name: name, value: value, - selector: Element.build_selector(field) + selector: Element.build_selector(field), + raw: field } end @@ -83,7 +85,8 @@ defmodule PhoenixTest.Field do id: id, name: name, value: value, - selector: Element.build_selector(field) + selector: Element.build_selector(field), + raw: field } end @@ -92,7 +95,7 @@ defmodule PhoenixTest.Field do id = Html.attribute(field, "id") name = Html.attribute(field, "name") - hidden_input = Query.find!(html, "input[type='hidden'][name=#{name}]") + hidden_input = Query.find!(html, "input[type='hidden'][name='#{name}']") value = Html.attribute(hidden_input, "value") %__MODULE__{ @@ -101,7 +104,8 @@ defmodule PhoenixTest.Field do id: id, name: name, value: value, - selector: Element.build_selector(field) + selector: Element.build_selector(field), + raw: field } end diff --git a/lib/phoenix_test/form.ex b/lib/phoenix_test/form.ex index 926f3171..213ea657 100644 --- a/lib/phoenix_test/form.ex +++ b/lib/phoenix_test/form.ex @@ -61,6 +61,7 @@ defmodule PhoenixTest.Form do @checked_radio_buttons "input:not([disabled])[type=radio][checked=checked][value]" @checked_checkboxes "input:not([disabled])[type=checkbox][checked=checked][value]" @pre_filled_text_inputs "input:not([disabled])[type=text][value]" + @pre_filled_number_inputs "input:not([disabled])[type=number][value]" @pre_filled_default_text_inputs "input:not([disabled]):not([type])[value]" defp form_data(form) do @@ -69,6 +70,7 @@ defmodule PhoenixTest.Form do |> put_form_data(@checked_radio_buttons, form) |> put_form_data(@checked_checkboxes, form) |> put_form_data(@pre_filled_text_inputs, form) + |> put_form_data(@pre_filled_number_inputs, form) |> put_form_data(@pre_filled_default_text_inputs, form) |> put_form_data_select(form) end @@ -78,9 +80,9 @@ defmodule PhoenixTest.Form do form |> Html.all(selector) |> Enum.map(&to_form_field/1) - |> Enum.reduce(%{}, fn value, acc -> Map.merge(acc, value) end) + |> Enum.reduce(%{}, fn value, acc -> DeepMerge.deep_merge(acc, value) end) - Map.merge(form_data, input_fields) + DeepMerge.deep_merge(form_data, input_fields) end defp put_form_data_select(form_data, form) do @@ -88,18 +90,18 @@ defmodule PhoenixTest.Form do form |> Html.all("select") |> Enum.reduce(%{}, fn select, acc -> - multiple = Html.attribute(select, "multiple") == "multiple" + multiple = !is_nil(Html.attribute(select, "multiple")) case {Html.all(select, "option"), multiple, Html.all(select, "option[selected]")} do {[], _, _} -> acc - {_, false, [only_selected]} -> Map.merge(acc, to_form_field(select, only_selected)) - {_, true, [_ | _] = all_selected} -> Map.merge(acc, to_form_field(select, all_selected)) - {[first | _], false, _} -> Map.merge(acc, to_form_field(select, first)) - {[first | _], true, _} -> Map.merge(acc, to_form_field(select, [first])) + {_, false, [only_selected]} -> DeepMerge.deep_merge(acc, to_form_field(select, only_selected)) + {_, true, [_ | _] = all_selected} -> DeepMerge.deep_merge(acc, to_form_field(select, all_selected)) + {[first | _], false, _} -> DeepMerge.deep_merge(acc, to_form_field(select, first)) + {_, true, _} -> DeepMerge.deep_merge(acc, to_form_field(select, [])) end end) - Map.merge(form_data, selects) + DeepMerge.deep_merge(form_data, selects) end def put_button_data(form, nil), do: form diff --git a/lib/phoenix_test/live.ex b/lib/phoenix_test/live.ex index 0c792ff0..5dc1bd3a 100644 --- a/lib/phoenix_test/live.ex +++ b/lib/phoenix_test/live.ex @@ -20,6 +20,8 @@ defmodule PhoenixTest.Live do %__MODULE__{view: view, conn: conn, current_path: current_path} end + def current_path(session), do: session.current_path + defp append_query_string(path, ""), do: path defp append_query_string(path, query), do: path <> "?" <> query @@ -300,6 +302,7 @@ defmodule PhoenixTest.Live do end defimpl PhoenixTest.Driver, for: PhoenixTest.Live do + alias PhoenixTest.Assertions alias PhoenixTest.Live defdelegate render_page_title(session), to: Live @@ -318,4 +321,14 @@ defimpl PhoenixTest.Driver, for: PhoenixTest.Live do defdelegate open_browser(session), to: Live defdelegate open_browser(session, open_fun), to: Live defdelegate unwrap(session, fun), to: Live + defdelegate current_path(session), to: Live + + defdelegate assert_has(session, selector), to: Assertions + defdelegate assert_has(session, selector, opts), to: Assertions + defdelegate refute_has(session, selector), to: Assertions + defdelegate refute_has(session, selector, opts), to: Assertions + defdelegate assert_path(session, path), to: Assertions + defdelegate assert_path(session, path, opts), to: Assertions + defdelegate refute_path(session, path), to: Assertions + defdelegate refute_path(session, path, opts), to: Assertions end diff --git a/lib/phoenix_test/static.ex b/lib/phoenix_test/static.ex index af4e1478..8bc971c3 100644 --- a/lib/phoenix_test/static.ex +++ b/lib/phoenix_test/static.ex @@ -22,6 +22,8 @@ defmodule PhoenixTest.Static do %__MODULE__{conn: conn, current_path: current_path} end + def current_path(session), do: session.current_path + defp append_query_string(path, ""), do: path defp append_query_string(path, query), do: path <> "?" <> query @@ -269,6 +271,7 @@ defmodule PhoenixTest.Static do end defimpl PhoenixTest.Driver, for: PhoenixTest.Static do + alias PhoenixTest.Assertions alias PhoenixTest.Static defdelegate render_page_title(session), to: Static @@ -287,4 +290,14 @@ defimpl PhoenixTest.Driver, for: PhoenixTest.Static do defdelegate open_browser(session), to: Static defdelegate open_browser(session, open_fun), to: Static defdelegate unwrap(session, fun), to: Static + defdelegate current_path(session), to: Static + + defdelegate assert_has(session, selector), to: Assertions + defdelegate assert_has(session, selector, opts), to: Assertions + defdelegate refute_has(session, selector), to: Assertions + defdelegate refute_has(session, selector, opts), to: Assertions + defdelegate assert_path(session, path), to: Assertions + defdelegate assert_path(session, path, opts), to: Assertions + defdelegate refute_path(session, path), to: Assertions + defdelegate refute_path(session, path, opts), to: Assertions end diff --git a/lib/phoenix_test/wallaby.ex b/lib/phoenix_test/wallaby.ex new file mode 100644 index 00000000..26d9afc1 --- /dev/null +++ b/lib/phoenix_test/wallaby.ex @@ -0,0 +1,261 @@ +defmodule PhoenixTest.Wallaby do + @moduledoc false + + import Wallaby.Browser + + alias ExUnit.AssertionError + alias PhoenixTest.Button + alias PhoenixTest.Field + alias PhoenixTest.Html + alias PhoenixTest.OpenBrowser + alias PhoenixTest.Query + + require Wallaby.Browser + + @endpoint Application.compile_env(:phoenix_test, :endpoint) + + defstruct session: nil, within: :none, last_field_query: :none + + def build(_conn, path) do + {:ok, session} = Wallaby.start_session() + Wallaby.Browser.visit(session, path) + %__MODULE__{session: session} + end + + def current_path(%{session: session}) do + uri = session |> current_url() |> URI.parse() + append_query_string(uri.path, uri.query) + end + + def append_query_string(path, nil), do: path + def append_query_string(path, query), do: path <> "?" <> query + + def render_page_title(session) do + session + |> render_html() + |> Query.find("title") + |> case do + {:found, element} -> Html.text(element) + _ -> nil + end + end + + def render_html(%{session: session, within: within}) do + html = Wallaby.Browser.page_source(session) + + case within do + :none -> html + selector -> html |> Query.find!(selector) |> Html.raw() + end + end + + def click_link(session, selector \\ "a", text) do + click(session, selector, text) + end + + def click_button(session, selector \\ "button", text) do + belongs_to_form? = + session + |> render_html() + |> Button.find!(selector, text) + |> Button.belongs_to_form?() + + session + |> click(selector, text) + |> Map.update!(:last_field_query, &if(belongs_to_form?, do: :none, else: &1)) + end + + defp click(session, selector, text) do + Wallaby.Browser.click(session.session, Wallaby.Query.css(selector, text: text)) + + session + end + + def within(session, selector, fun) when is_function(fun, 1) do + session + |> Map.put(:within, selector) + |> fun.() + |> Map.put(:within, :none) + end + + def fill_in(session, label, with: value) do + field = Field.find_input!(render_html(session), label) + query = query(session, label, &Field.find_input!/2) + + case Html.attribute(field.raw, "type") do + # Set via JS to avoid locale format issues + type when type in ~w(date datetime-local time week) -> + js = """ + el = document.querySelector('#{field.selector}'); + el.value = '#{value}'; + """ + + session.session + |> Wallaby.Browser.execute_script(js) + |> Wallaby.Browser.find(query) + |> Wallaby.Element.click() + + _other -> + Wallaby.Browser.clear(session.session, query) + unless is_nil(value), do: Wallaby.Browser.fill_in(session.session, query, with: value) + end + + session + |> Map.put(:last_field_query, query) + |> trigger_phx_change_validations(query) + end + + def select(session, options, from: label) do + query = query(session, label, &Field.find_input!/2) + + # Deselect other options first? + for option <- List.wrap(options) do + session.session + |> Wallaby.Browser.find(query) + |> Wallaby.Browser.set_value(Wallaby.Query.css("option", text: option), :selected) + end + + Map.put(session, :last_field_query, query) + end + + def check(session, label) do + query = query(session, label, &Field.find_checkbox!/2) + Wallaby.Browser.set_value(session.session, query, :selected) + + Map.put(session, :last_field_query, query) + end + + def uncheck(session, label) do + query = query(session, label, &Field.find_checkbox!/2) + Wallaby.Browser.set_value(session.session, query, :unselected) + + Map.put(session, :last_field_query, query) + end + + def choose(session, label) do + query = query(session, label, &Field.find_input!/2) + Wallaby.Browser.click(session.session, query) + + Map.put(session, :last_field_query, query) + end + + def submit(session) do + case session.last_field_query do + :none -> raise no_active_form_error() + query -> Wallaby.Browser.send_keys(session.session, query, [:enter]) + end + + Map.put(session, :last_field_query, :none) + end + + def open_browser(session, open_fun \\ &OpenBrowser.open_with_system_cmd/1) do + path = Path.join([System.tmp_dir!(), "phx-test#{System.unique_integer([:monotonic])}.html"]) + + html = + session.session + |> Wallaby.Browser.page_source() + |> Floki.parse_document!() + |> Floki.traverse_and_update(&OpenBrowser.prefix_static_paths(&1, @endpoint)) + |> Floki.raw_html() + + File.write!(path, html) + + open_fun.(path) + + session + end + + def unwrap(session, fun) when is_function(fun, 1) do + fun.(session.session) + session + end + + defp query(session, label, find_fun) do + # Use PhoenixTest.Field queries for label exact-match semantics + html = render_html(session) + field = find_fun.(html, label) + + within = + case session.within do + :none -> "" + selector -> selector + end + + selector = "#{within} #{field.selector}" + Query.find!(html, selector) + Wallaby.Query.css(selector) + end + + defp trigger_phx_change_validations(session, query) do + if has?(session.session, query) do + Wallaby.Browser.send_keys(session.session, query, [:tab]) + end + + session + end + + defp no_active_form_error do + %ArgumentError{ + message: "There's no active form. Fill in a form with `fill_in`, `select`, etc." + } + end +end + +defimpl PhoenixTest.Driver, for: PhoenixTest.Wallaby do + alias ExUnit.AssertionError + alias PhoenixTest.Assertions + alias PhoenixTest.Wallaby + + def render_page_title(session), do: map_errors(fn -> Wallaby.render_page_title(session) end) + def render_html(session), do: map_errors(fn -> Wallaby.render_html(session) end) + def click_link(session, text), do: map_errors(fn -> Wallaby.click_link(session, text) end) + def click_link(session, selector, text), do: map_errors(fn -> Wallaby.click_link(session, selector, text) end) + def click_button(session, text), do: map_errors(fn -> Wallaby.click_button(session, text) end) + def click_button(session, selector, text), do: map_errors(fn -> Wallaby.click_button(session, selector, text) end) + def within(session, selector, fun), do: map_errors(fn -> Wallaby.within(session, selector, fun) end) + def fill_in(session, label, attrs), do: map_errors(fn -> Wallaby.fill_in(session, label, attrs) end) + def select(session, option, attrs), do: map_errors(fn -> Wallaby.select(session, option, attrs) end) + def check(session, label), do: map_errors(fn -> Wallaby.check(session, label) end) + def uncheck(session, label), do: map_errors(fn -> Wallaby.uncheck(session, label) end) + def choose(session, label), do: map_errors(fn -> Wallaby.choose(session, label) end) + def submit(session), do: map_errors(fn -> Wallaby.submit(session) end) + def open_browser(session), do: map_errors(fn -> Wallaby.open_browser(session) end) + def open_browser(session, open_fun), do: map_errors(fn -> Wallaby.open_browser(session, open_fun) end) + def unwrap(session, fun), do: map_errors(fn -> Wallaby.unwrap(session, fun) end) + def current_path(session), do: map_errors(fn -> Wallaby.current_path(session) end) + + def assert_has(session, selector), do: retry(fn -> Assertions.assert_has(session, selector) end) + def assert_has(session, selector, opts), do: retry(fn -> Assertions.assert_has(session, selector, opts) end) + def refute_has(session, selector), do: retry(fn -> Assertions.refute_has(session, selector) end) + def refute_has(session, selector, opts), do: retry(fn -> Assertions.refute_has(session, selector, opts) end) + def assert_path(session, path), do: retry(fn -> Assertions.assert_path(session, path) end) + def assert_path(session, path, opts), do: retry(fn -> Assertions.assert_path(session, path, opts) end) + def refute_path(session, path), do: retry(fn -> Assertions.refute_path(session, path) end) + def refute_path(session, path, opts), do: retry(fn -> Assertions.refute_path(session, path, opts) end) + + defp map_errors(fun) do + fun.() + rescue + e -> + raise ArgumentError, e.message + end + + defp retry(fun, timeout_ms \\ 300, interval_ms \\ 10) do + now = DateTime.to_unix(DateTime.utc_now(), :millisecond) + timeout_at = DateTime.utc_now() |> DateTime.add(timeout_ms, :millisecond) |> DateTime.to_unix(:millisecond) + retry(fun, now, timeout_at, interval_ms) + end + + defp retry(fun, now, timeout_at, _interval_ms) when now >= timeout_at do + fun.() + end + + defp retry(fun, _now, timeout_at, interval_ms) do + fun.() + rescue + AssertionError -> + Process.sleep(interval_ms) + now = DateTime.to_unix(DateTime.utc_now(), :millisecond) + retry(fun, now, timeout_at, interval_ms) + end +end diff --git a/mix.exs b/mix.exs index 6466e1a8..ceb33bc4 100644 --- a/mix.exs +++ b/mix.exs @@ -21,7 +21,13 @@ defmodule PhoenixTest.MixProject do package: package(), name: "PhoenixTest", source_url: @source_url, - docs: docs() + docs: docs(), + aliases: aliases(), + preferred_cli_env: [ + setup: :test, + "assets.setup": :test, + "assets.build": :test + ] ] end @@ -38,10 +44,13 @@ defmodule PhoenixTest.MixProject do {:ex_doc, "~> 0.31", only: :dev, runtime: false}, {:deep_merge, "~> 1.0"}, {:floki, ">= 0.30.0"}, + {:esbuild, "~> 0.8", only: :test, runtime: false}, {:jason, "~> 1.4"}, {:phoenix, "~> 1.7.10"}, {:phoenix_live_view, "~> 0.20.1"}, - {:styler, "~> 0.11", only: [:dev, :test], runtime: false} + {:wallaby, "~> 0.30.6", runtime: false, only: :test}, + {:styler, "~> 0.11", only: [:dev, :test], runtime: false}, + {:plug_cowboy, "~> 2.7", only: :test, runtime: false} ] end @@ -64,4 +73,13 @@ defmodule PhoenixTest.MixProject do ] ] end + + defp aliases do + [ + setup: ["deps.get", "assets.setup", "assets.build"], + "assets.setup": ["esbuild.install --if-missing"], + "assets.build": ["esbuild default"], + test: ["assets.build", "test"] + ] + end end diff --git a/mix.lock b/mix.lock index 2ca45df2..0ec35f41 100644 --- a/mix.lock +++ b/mix.lock @@ -1,24 +1,42 @@ %{ "castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "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"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, + "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"}, "ex_doc": {:hex, :ex_doc, "0.32.1", "21e40f939515373bcdc9cffe65f3b3543f05015ac6c3d01d991874129d173420", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5142c9db521f106d61ff33250f779807ed2a88620e472ac95dc7d59c380113da"}, "floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"}, + "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"}, + "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"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [: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", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, + "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.7.10", "02189140a61b2ce85bb633a9b6fd02dff705a5f1596869547aeb2b2b95edd729", [: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.6", [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", "cf784932e010fd736d656d7fead6a584a4498efefe5b8227e9f383bf15bb79d0"}, "phoenix_html": {:hex, :phoenix_html, "4.0.0", "4857ec2edaccd0934a923c2b0ba526c44a173c86b847e8db725172e9e51d11d6", [:mix], [], "hexpm", "cee794a052f243291d92fa3ccabcb4c29bb8d236f655fb03bcbdc3a8214b8d13"}, "phoenix_live_view": {:hex, :phoenix_live_view, "0.20.3", "8b6406bc0a451f295407d7acff7f234a6314be5bbe0b3f90ed82b07f50049878", [:mix], [{: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", "a8e4385e05618b424779f894ed2df97d3c7518b7285fcd11979077ae6226466b"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "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"}, "plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [: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", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"}, "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, + "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "styler": {:hex, :styler, "0.11.9", "2595393b94e660cd6e8b582876337cc50ff047d184ccbed42fdad2bfd5d78af5", [:mix], [], "hexpm", "8b7806ba1fdc94d0a75127c56875f91db89b75117fcc67572661010c13e1f259"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "tesla": {:hex, :tesla, "1.9.0", "8c22db6a826e56a087eeb8cdef56889731287f53feeb3f361dec5d4c8efb6f14", [: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", "7c240c67e855f7e63e795bf16d6b3f5115a81d1f44b7fe4eadbf656bae0fef8a"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, + "wallaby": {:hex, :wallaby, "0.30.6", "7dc4c1213f3b52c4152581d126632bc7e06892336d3a0f582853efeeabd45a71", [: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", "50950c1d968549b54c20e16175c68c7fc0824138e2bb93feb11ef6add8eb23d4"}, + "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.5", "9dfeee8269b27e958a65b3e235b7e447769f66b5b5925385f5a569269164a210", [: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", "4b977ba4a01918acbf77045ff88de7f6972c2a009213c515a445c48f224ffce9"}, } diff --git a/test/assets/js/app.js b/test/assets/js/app.js new file mode 100644 index 00000000..2aa5bf2a --- /dev/null +++ b/test/assets/js/app.js @@ -0,0 +1,8 @@ +// Include phoenix_html to handle method=PUT/DELETE in forms and buttons. +import "phoenix_html" +import {Socket} from "phoenix" +import {LiveSocket} from "phoenix_live_view" + +let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}, hooks: {SomeHook: {}, SomeOtherHook: {}}}) +liveSocket.connect() diff --git a/test/phoenix_test/assertions_test.exs b/test/phoenix_test/assertions_test.exs index 31eae704..e8014948 100644 --- a/test/phoenix_test/assertions_test.exs +++ b/test/phoenix_test/assertions_test.exs @@ -6,19 +6,24 @@ defmodule PhoenixTest.AssertionsTest do import PhoenixTest.TestHelpers alias ExUnit.AssertionError + alias PhoenixTest.Live - setup do - %{conn: Phoenix.ConnTest.build_conn()} + require PhoenixTest.TestHelpers + + setup context do + conn = Phoenix.ConnTest.build_conn() + conn = if context[:js], do: with_js_driver(conn), else: conn + %{conn: conn} end describe "assert_has/2" do - test "succeeds if single element is found with CSS selector", %{conn: conn} do + test_also_with_js "succeeds if single element is found with CSS selector", %{conn: conn} do conn |> visit("/page/index") |> assert_has("[data-role='title']") end - test "raises an error if the element cannot be found at all", %{conn: conn} do + test_also_with_js "raises an error if the element cannot be found at all", %{conn: conn} do conn = visit(conn, "/page/index") msg = ~r/Could not find any elements with selector "#nonexistent-id"/ @@ -28,25 +33,25 @@ defmodule PhoenixTest.AssertionsTest do end end - test "succeeds if element searched is title (Static)", %{conn: conn} do + test_also_with_js "succeeds if element searched is title (Static)", %{conn: conn} do conn |> visit("/page/index") |> assert_has("title") end - test "succeeds if element searched is title (Live)", %{conn: conn} do + test_also_with_js "succeeds if element searched is title (Live)", %{conn: conn} do conn |> visit("/live/index") |> assert_has("title") end - test "succeeds if more than one element matches selector", %{conn: conn} do + test_also_with_js "succeeds if more than one element matches selector", %{conn: conn} do conn |> visit("/page/index") |> assert_has("li") end - test "takes in input helper in assertion", %{conn: conn} do + test_also_with_js "takes in input helper in assertion", %{conn: conn} do conn |> visit("/page/index") |> assert_has(input(type: "text", label: "User Name")) @@ -54,7 +59,7 @@ defmodule PhoenixTest.AssertionsTest do end describe "assert_has/3" do - test "succeeds if single element is found with CSS selector and text (Static)", %{conn: conn} do + test_also_with_js "succeeds if single element is found with CSS selector and text (Static)", %{conn: conn} do conn |> visit("/page/index") |> assert_has("h1", text: "Main page") @@ -63,7 +68,7 @@ defmodule PhoenixTest.AssertionsTest do |> assert_has("[data-role='title']", text: "Main page") end - test "succeeds if single element is found with CSS selector and text (Live)", %{conn: conn} do + test_also_with_js "succeeds if single element is found with CSS selector and text (Live)", %{conn: conn} do conn |> visit("/live/index") |> assert_has("h1", text: "LiveView main page") @@ -72,7 +77,7 @@ defmodule PhoenixTest.AssertionsTest do |> assert_has("[data-role='title']", text: "LiveView main page") end - test "succeeds if more than one element matches selector but text narrows it down", %{ + test_also_with_js "succeeds if more than one element matches selector but text narrows it down", %{ conn: conn } do conn @@ -80,25 +85,25 @@ defmodule PhoenixTest.AssertionsTest do |> assert_has("li", text: "Aragorn") end - test "succeeds if more than one element matches selector and text", %{conn: conn} do + test_also_with_js "succeeds if more than one element matches selector and text", %{conn: conn} do conn |> visit("/page/index") |> assert_has(".multiple_links", text: "Multiple links") end - test "succeeds if text difference is only a matter of truncation", %{conn: conn} do + test_also_with_js "succeeds if text difference is only a matter of truncation", %{conn: conn} do conn |> visit("/page/index") |> assert_has(".has_extra_space", text: "Has extra space") end - test "succeeds when a non-200 status code is returned", %{conn: conn} do + test_also_with_js "succeeds when a non-200 status code is returned", %{conn: conn} do conn |> visit("/page/unauthorized") |> assert_has("h1", text: "Unauthorized") end - test "raises an error if the element cannot be found at all", %{conn: conn} do + test_also_with_js "raises an error if the element cannot be found at all", %{conn: conn} do conn = visit(conn, "/page/index") msg = ~r/Could not find any elements with selector "#nonexistent-id"/ @@ -108,7 +113,7 @@ defmodule PhoenixTest.AssertionsTest do end end - test "raises error if element cannot be found but selector matches other elements", %{ + test_also_with_js "raises error if element cannot be found but selector matches other elements", %{ conn: conn } do conn = visit(conn, "/page/index") @@ -129,25 +134,25 @@ defmodule PhoenixTest.AssertionsTest do end end - test "can be used to assert on page title (Static)", %{conn: conn} do + test_also_with_js "can be used to assert on page title (Static)", %{conn: conn} do conn |> visit("/page/index") |> assert_has("title", text: "PhoenixTest is the best!") end - test "can be used to assert on page title (Live)", %{conn: conn} do + test_also_with_js "can be used to assert on page title (Live)", %{conn: conn} do conn |> visit("/live/index") |> assert_has("title", text: "PhoenixTest is the best!") end - test "can assert title's exactness", %{conn: conn} do + test_also_with_js "can assert title's exactness", %{conn: conn} do conn |> visit("/live/index") |> assert_has("title", text: "PhoenixTest is the best!", exact: true) end - test "raises if title does not match expected value (Static)", %{conn: conn} do + test_also_with_js "raises if title does not match expected value (Static)", %{conn: conn} do msg = ignore_whitespace(""" Expected title to be "Not the title" but got "PhoenixTest is the best!" @@ -160,7 +165,7 @@ defmodule PhoenixTest.AssertionsTest do end end - test "raises if title does not match expected value (Live)", %{conn: conn} do + test_also_with_js "raises if title does not match expected value (Live)", %{conn: conn} do msg = ignore_whitespace(""" Expected title to be "Not the title" but got "PhoenixTest is the best!" @@ -173,8 +178,8 @@ defmodule PhoenixTest.AssertionsTest do end end - test "raises if title is contained but is not exactly the same as expected (with exact=true)", - %{conn: conn} do + test_also_with_js "raises if title is contained but is not exactly the same as expected (with exact=true)", + %{conn: conn} do msg = ignore_whitespace(""" Expected title to be "PhoenixTest" but got "PhoenixTest is the best!" @@ -187,7 +192,7 @@ defmodule PhoenixTest.AssertionsTest do end end - test "raises error if element cannot be found and selector matches a nested structure", %{ + test_also_with_js "raises error if element cannot be found and selector matches a nested structure", %{ conn: conn } do conn = visit(conn, "/page/index") @@ -216,7 +221,7 @@ defmodule PhoenixTest.AssertionsTest do end end - test "accepts a `count` option", %{conn: conn} do + test_also_with_js "accepts a `count` option", %{conn: conn} do conn |> visit("/page/index") |> assert_has(".multiple_links", count: 2) @@ -225,7 +230,7 @@ defmodule PhoenixTest.AssertionsTest do |> assert_has("h1", text: "Main page", count: 1) end - test "raises an error if count is more than expected count", %{conn: conn} do + test_also_with_js "raises an error if count is more than expected count", %{conn: conn} do session = visit(conn, "/page/index") msg = @@ -240,7 +245,7 @@ defmodule PhoenixTest.AssertionsTest do end end - test "raises an error if count is less than expected count", %{conn: conn} do + test_also_with_js "raises an error if count is less than expected count", %{conn: conn} do session = visit(conn, "/page/index") msg = @@ -255,14 +260,14 @@ defmodule PhoenixTest.AssertionsTest do end end - test "accepts an `exact` option to match text exactly", %{conn: conn} do + test_also_with_js "accepts an `exact` option to match text exactly", %{conn: conn} do conn |> visit("/page/index") |> assert_has("h1", text: "Main", exact: false) |> assert_has("h1", text: "Main page", exact: true) end - test "raises if `exact` text doesn't match", %{conn: conn} do + test_also_with_js "raises if `exact` text doesn't match", %{conn: conn} do msg = ignore_whitespace(""" Could not find any elements with selector "h1" and text "Main". @@ -281,13 +286,13 @@ defmodule PhoenixTest.AssertionsTest do end end - test "accepts an `at` option to assert on a specific element", %{conn: conn} do + test_also_with_js "accepts an `at` option to assert on a specific element", %{conn: conn} do conn |> visit("/page/index") |> assert_has("#multiple-items li", at: 2, text: "Legolas") end - test "raises if it cannot find element at `at` position", %{conn: conn} do + test_also_with_js "raises if it cannot find element at `at` position", %{conn: conn} do msg = ignore_whitespace(""" Could not find any elements with selector "#multiple-items li" and text "Aragorn" at position 2 @@ -302,28 +307,28 @@ defmodule PhoenixTest.AssertionsTest do end describe "refute_has/2" do - test "succeeds if no element is found with CSS selector (Static)", %{conn: conn} do + test_also_with_js "succeeds if no element is found with CSS selector (Static)", %{conn: conn} do conn |> visit("/page/index") |> refute_has("#some-invalid-id") |> refute_has("[data-role='invalid-role']") end - test "succeeds if no element is found with CSS selector (Live)", %{conn: conn} do + test_also_with_js "succeeds if no element is found with CSS selector (Live)", %{conn: conn} do conn |> visit("/live/index") |> refute_has("#some-invalid-id") |> refute_has("[data-role='invalid-role']") end - test "can refute presence of title (Static)", %{conn: conn} do + test_also_with_js "can refute presence of title (Static)", %{conn: conn} do conn |> visit("/page/index_no_layout") |> refute_has("title") |> refute_has("#something-else-to-test-pipe") end - test "accepts a `count` option", %{conn: conn} do + test_also_with_js "accepts a `count` option", %{conn: conn} do conn |> visit("/page/index") |> refute_has("h1", count: 2) @@ -332,7 +337,7 @@ defmodule PhoenixTest.AssertionsTest do |> refute_has(".multiple_links", text: "Multiple links", count: 1) end - test "raises if element is found", %{conn: conn} do + test_also_with_js "raises if element is found", %{conn: conn} do msg = ignore_whitespace(""" Expected not to find any elements with selector "h1". @@ -351,7 +356,7 @@ defmodule PhoenixTest.AssertionsTest do end end - test "raises if title is found", %{conn: conn} do + test_also_with_js "raises if title is found", %{conn: conn} do msg = ignore_whitespace(""" Expected title not to be present but found: "PhoenixTest is the best!" @@ -364,7 +369,7 @@ defmodule PhoenixTest.AssertionsTest do end end - test "raises an error if multiple elements are found", %{conn: conn} do + test_also_with_js "raises an error if multiple elements are found", %{conn: conn} do conn = visit(conn, "/page/index") msg = @@ -379,7 +384,7 @@ defmodule PhoenixTest.AssertionsTest do end end - test "raises if there is one element and count is 1", %{conn: conn} do + test_also_with_js "raises if there is one element and count is 1", %{conn: conn} do conn = visit(conn, "/page/index") msg = @@ -392,7 +397,7 @@ defmodule PhoenixTest.AssertionsTest do end end - test "raises if there are the same number of elements as refuted", %{conn: conn} do + test_also_with_js "raises if there are the same number of elements as refuted", %{conn: conn} do conn = visit(conn, "/page/index") msg = @@ -409,27 +414,27 @@ defmodule PhoenixTest.AssertionsTest do end describe "refute_has/3" do - test "can be used to refute on page title (Static)", %{conn: conn} do + test_also_with_js "can be used to refute on page title (Static)", %{conn: conn} do conn |> visit("/page/index") |> refute_has("title", text: "Not the title") |> refute_has("title", text: "Not this title either") end - test "can be used to refute on page title (Live)", %{conn: conn} do + test_also_with_js "can be used to refute on page title (Live)", %{conn: conn} do conn |> visit("/live/index") |> refute_has("title", text: "Not the title") |> refute_has("title", text: "Not this title either") end - test "can be used to refute page title's exactness", %{conn: conn} do + test_also_with_js "can be used to refute page title's exactness", %{conn: conn} do conn |> visit("/live/index") |> refute_has("title", text: "PhoenixTest is the", exact: true) end - test "raises if title matches value (Static)", %{conn: conn} do + test_also_with_js "raises if title matches value (Static)", %{conn: conn} do msg = ignore_whitespace(""" Expected title not to be "PhoenixTest is the best!" @@ -442,7 +447,7 @@ defmodule PhoenixTest.AssertionsTest do end end - test "raises if title matches value (Live)", %{conn: conn} do + test_also_with_js "raises if title matches value (Live)", %{conn: conn} do msg = ignore_whitespace(""" Expected title not to be "PhoenixTest is the best!" @@ -455,7 +460,7 @@ defmodule PhoenixTest.AssertionsTest do end end - test "succeeds if no element is found with CSS selector and text (Static)", %{conn: conn} do + test_also_with_js "succeeds if no element is found with CSS selector and text (Static)", %{conn: conn} do conn |> visit("/page/index") |> refute_has("h1", text: "Not main page") @@ -464,7 +469,7 @@ defmodule PhoenixTest.AssertionsTest do |> refute_has("#title", text: "Not main page") end - test "succeeds if no element is found with CSS selector and text (Live)", %{conn: conn} do + test_also_with_js "succeeds if no element is found with CSS selector and text (Live)", %{conn: conn} do conn |> visit("/live/index") |> refute_has("h1", text: "Not main page") @@ -473,7 +478,7 @@ defmodule PhoenixTest.AssertionsTest do |> refute_has("#title", text: "Not main page") end - test "raises an error if one element is found", %{conn: conn} do + test_also_with_js "raises an error if one element is found", %{conn: conn} do conn = visit(conn, "/page/index") msg = @@ -492,7 +497,7 @@ defmodule PhoenixTest.AssertionsTest do end end - test "raises an error if multiple elements are found", %{conn: conn} do + test_also_with_js "raises an error if multiple elements are found", %{conn: conn} do conn = visit(conn, "/page/index") msg = @@ -515,13 +520,13 @@ defmodule PhoenixTest.AssertionsTest do end end - test "accepts an `exact` option to match text exactly", %{conn: conn} do + test_also_with_js "accepts an `exact` option to match text exactly", %{conn: conn} do conn |> visit("/page/index") |> refute_has("h1", text: "Main", exact: true) end - test "raises if `exact` text makes refutation false", %{conn: conn} do + test_also_with_js "raises if `exact` text makes refutation false", %{conn: conn} do msg = ignore_whitespace(""" Expected not to find any elements with selector "h1" and text "Main". @@ -540,13 +545,13 @@ defmodule PhoenixTest.AssertionsTest do end end - test "accepts an `at` option to refute on a specific element", %{conn: conn} do + test_also_with_js "accepts an `at` option to refute on a specific element", %{conn: conn} do conn |> visit("/page/index") |> refute_has("#multiple-items li", at: 2, text: "Aragorn") end - test "raises if it finds element at `at` position", %{conn: conn} do + test_also_with_js "raises if it finds element at `at` position", %{conn: conn} do msg = ignore_whitespace(""" Expected not to find any elements with selector "#multiple-items li" and text "Legolas" at position 2 @@ -567,58 +572,58 @@ defmodule PhoenixTest.AssertionsTest do end describe "assert_path" do - test "asserts the session's current path" do - session = %{current_path: "/page/index"} + test_also_with_js "asserts the session's current path" do + session = %Live{current_path: "/page/index"} assert_path(session, "/page/index") end - test "asserts query params are the same" do - session = %{current_path: "/page/index?hello=world"} + test_also_with_js "asserts query params are the same" do + session = %Live{current_path: "/page/index?hello=world"} assert_path(session, "/page/index", query_params: %{"hello" => "world"}) end - test "order of query params does not matter" do - session = %{current_path: "/page/index?hello=world&foo=bar"} + test_also_with_js "order of query params does not matter" do + session = %Live{current_path: "/page/index?hello=world&foo=bar"} assert_path(session, "/page/index", query_params: %{"foo" => "bar", "hello" => "world"}) end - test "raises helpful error if path doesn't match" do + test_also_with_js "raises helpful error if path doesn't match" do msg = ignore_whitespace(""" Expected path to be "/page/not-index" but got "/page/index" """) assert_raise AssertionError, msg, fn -> - session = %{current_path: "/page/index"} + session = %Live{current_path: "/page/index"} assert_path(session, "/page/not-index") end end - test "raises helpful error if path doesn't have query params" do + test_also_with_js "raises helpful error if path doesn't have query params" do msg = ignore_whitespace(""" Expected query params to be "details=true&foo=bar" but got nil """) assert_raise AssertionError, msg, fn -> - session = %{current_path: "/page/index"} + session = %Live{current_path: "/page/index"} assert_path(session, "/page/index", query_params: %{foo: "bar", details: true}) end end - test "raises helpful error if query params don't match" do + test_also_with_js "raises helpful error if query params don't match" do msg = ignore_whitespace(""" Expected query params to be "goodbye=world&hi=bye" but got "hello=world&hi=bye" """) assert_raise AssertionError, msg, fn -> - session = %{current_path: "/page/index?hello=world&hi=bye"} + session = %Live{current_path: "/page/index?hello=world&hi=bye"} assert_path(session, "/page/index", query_params: %{"goodbye" => "world", "hi" => "bye"}) end @@ -626,39 +631,39 @@ defmodule PhoenixTest.AssertionsTest do end describe "refute_path" do - test "refute the given path is the current path" do - session = %{current_path: "/page/index"} + test_also_with_js "refute the given path is the current path" do + session = %Live{current_path: "/page/index"} refute_path(session, "/page/page_2") end - test "refutes query params are the same" do - session = %{current_path: "/page/index?hello=world"} + test_also_with_js "refutes query params are the same" do + session = %Live{current_path: "/page/index?hello=world"} refute_path(session, "/page/index", query_params: %{"hello" => "not-world"}) end - test "raises helpful error if path matches" do + test_also_with_js "raises helpful error if path matches" do msg = ignore_whitespace(""" Expected path not to be "/page/index" """) assert_raise AssertionError, msg, fn -> - session = %{current_path: "/page/index"} + session = %Live{current_path: "/page/index"} refute_path(session, "/page/index") end end - test "raises helpful error if query params MATCH" do + test_also_with_js "raises helpful error if query params MATCH" do msg = ignore_whitespace(""" Expected query params not to be "hello=world&hi=bye" """) assert_raise AssertionError, msg, fn -> - session = %{current_path: "/page/index?hello=world&hi=bye"} + session = %Live{current_path: "/page/index?hello=world&hi=bye"} refute_path(session, "/page/index", query_params: %{"hello" => "world", "hi" => "bye"}) end diff --git a/test/phoenix_test/live_test.exs b/test/phoenix_test/live_test.exs index 62af34ed..4ce11a56 100644 --- a/test/phoenix_test/live_test.exs +++ b/test/phoenix_test/live_test.exs @@ -7,8 +7,12 @@ defmodule PhoenixTest.LiveTest do alias PhoenixTest.Driver - setup do - %{conn: Phoenix.ConnTest.build_conn()} + require PhoenixTest.TestHelpers + + setup context do + conn = Phoenix.ConnTest.build_conn() + conn = if context[:js], do: with_js_driver(conn), else: conn + %{conn: conn} end describe "render_page_title/1" do @@ -42,25 +46,26 @@ defmodule PhoenixTest.LiveTest do end describe "visit/2" do - test "navigates to given LiveView page", %{conn: conn} do + test_also_with_js "navigates to given LiveView page", %{conn: conn} do conn |> visit("/live/index") |> assert_has("h1", text: "LiveView main page") end - test "follows redirects", %{conn: conn} do + test_also_with_js "follows redirects", %{conn: conn} do conn |> visit("/live/redirect_on_mount/redirect") |> assert_has("h1", text: "LiveView main page") end - test "follows push redirects (push navigate)", %{conn: conn} do + test_also_with_js "follows push redirects (push navigate)", %{conn: conn} do conn |> visit("/live/redirect_on_mount/push_navigate") |> assert_has("h1", text: "LiveView main page") end - test "raises error if route doesn't exist", %{conn: conn} do + @tag :skip + test_also_with_js "raises error if route doesn't exist", %{conn: conn} do assert_raise Phoenix.Router.NoRouteError, fn -> visit(conn, "/live/non_route") end @@ -68,59 +73,59 @@ defmodule PhoenixTest.LiveTest do end describe "click_link/2" do - test "follows 'navigate' links", %{conn: conn} do + test_also_with_js "follows 'navigate' links", %{conn: conn} do conn |> visit("/live/index") |> click_link("Navigate link") |> assert_has("h1", text: "LiveView page 2") end - test "follows navigation that subsequently redirect", %{conn: conn} do + test_also_with_js "follows navigation that subsequently redirect", %{conn: conn} do conn |> visit("/live/index") |> click_link("Navigate (and redirect back) link") |> assert_has("h1", text: "LiveView main page") end - test "accepts click_link with selector", %{conn: conn} do + test_also_with_js "accepts click_link with selector", %{conn: conn} do conn |> visit("/live/index") |> click_link("a", "Navigate link") |> assert_has("h1", text: "LiveView page 2") end - test "handles patches to current view", %{conn: conn} do + test_also_with_js "handles patches to current view", %{conn: conn} do conn |> visit("/live/index") |> click_link("Patch link") |> assert_has("h2", text: "LiveView main page details") end - test "handles navigation to a non-liveview", %{conn: conn} do + test_also_with_js "handles navigation to a non-liveview", %{conn: conn} do conn |> visit("/live/index") |> click_link("Navigate to non-liveview") |> assert_has("h1", text: "Main page") end - test "raises error when there are multiple links with same text", %{conn: conn} do - assert_raise ArgumentError, ~r/2 of them matched the text filter/, fn -> + test_also_with_js "raises error when there are multiple links with same text", %{conn: conn} do + assert_raise ArgumentError, ~r/ 2 /, fn -> conn |> visit("/live/index") |> click_link("Multiple links") end end - test "raises an error when link element can't be found with given text", %{conn: conn} do - assert_raise ArgumentError, ~r/elements but none matched the text filter "No link"/, fn -> + test_also_with_js "raises an error when link element can't be found with given text", %{conn: conn} do + assert_raise ArgumentError, fn -> conn |> visit("/live/index") |> click_link("No link") end end - test "raises an error when there are no links on the page", %{conn: conn} do - assert_raise ArgumentError, ~r/selector "a" did not return any element/, fn -> + test_also_with_js "raises an error when there are no links on the page", %{conn: conn} do + assert_raise ArgumentError, fn -> conn |> visit("/live/page_2") |> click_link("No link") @@ -129,7 +134,7 @@ defmodule PhoenixTest.LiveTest do end describe "click_button/2" do - test "handles a `phx-click` button", %{conn: conn} do + test_also_with_js "handles a `phx-click` button", %{conn: conn} do conn |> visit("/live/index") |> click_button("Show tab") @@ -156,7 +161,7 @@ defmodule PhoenixTest.LiveTest do refute PhoenixTest.ActiveForm.active?(session.active_form) end - test "includes name and value if specified", %{conn: conn} do + test_also_with_js "includes name and value if specified", %{conn: conn} do conn |> visit("/live/index") |> fill_in("User Name", with: "Aragorn") @@ -164,16 +169,17 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "user:no-phx-change-form-button: save") end - test "includes default data if form is untouched", %{conn: conn} do + test_also_with_js "includes default data if form is untouched", %{conn: conn} do conn |> visit("/live/index") |> click_button("Save Full Form") |> assert_has("#form-data", text: "admin: off") |> assert_has("#form-data", text: "contact: mail") + |> assert_has("#form-data", text: "level: 7") |> assert_has("#form-data", text: "full_form_button: save") end - test "can click button that does not submit form after filling form", %{conn: conn} do + test_also_with_js "can click button that does not submit form after filling form", %{conn: conn} do conn |> visit("/live/index") |> fill_in("Email", with: "some@example.com") @@ -181,7 +187,7 @@ defmodule PhoenixTest.LiveTest do |> refute_has("#form-data", text: "email: some@example.com") end - test "submits owner form if button isn't nested inside form", %{conn: conn} do + test_also_with_js "submits owner form if button isn't nested inside form", %{conn: conn} do conn |> visit("/live/index") |> within("#owner-form", fn session -> @@ -191,7 +197,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "name: Aragorn") end - test "follows form's redirect to live page", %{conn: conn} do + test_also_with_js "follows form's redirect to live page", %{conn: conn} do conn |> visit("/live/index") |> within("#redirect-form", &fill_in(&1, "Name", with: "Aragorn")) @@ -199,7 +205,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("h1", text: "LiveView page 2") end - test "follows form's redirect to static page", %{conn: conn} do + test_also_with_js "follows form's redirect to static page", %{conn: conn} do conn |> visit("/live/index") |> within("#redirect-form-to-static", &fill_in(&1, "Name", with: "Aragorn")) @@ -207,7 +213,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("h1", text: "Main page") end - test "submits regular (non phx-submit) form", %{conn: conn} do + test_also_with_js "submits regular (non phx-submit) form", %{conn: conn} do conn |> visit("/live/index") |> within("#non-liveview-form", &fill_in(&1, "Name", with: "Aragorn")) @@ -229,7 +235,7 @@ defmodule PhoenixTest.LiveTest do end end - test "raises an error when there are no buttons on page", %{conn: conn} do + test_also_with_js "raises an error when there are no buttons on page", %{conn: conn} do msg = ~r/Could not find element with selector "button" and text "Show tab"/ assert_raise ArgumentError, msg, fn -> @@ -251,7 +257,7 @@ defmodule PhoenixTest.LiveTest do end end - test "raises an error if active form but can't find button", %{conn: conn} do + test_also_with_js "raises an error if active form but can't find button", %{conn: conn} do msg = ~r/Could not find element with selector "button" and text "No button"/ assert_raise ArgumentError, msg, fn -> @@ -267,7 +273,7 @@ defmodule PhoenixTest.LiveTest do end describe "within/3" do - test "scopes assertions within selector", %{conn: conn} do + test_also_with_js "scopes assertions within selector", %{conn: conn} do conn |> visit("/live/index") |> assert_has("button", text: "Reset") @@ -276,16 +282,17 @@ defmodule PhoenixTest.LiveTest do end) end - test "scopes further form actions within a selector", %{conn: conn} do + test_also_with_js "scopes further form actions within a selector", %{conn: conn} do conn |> visit("/live/index") |> within("#email-form", fn session -> fill_in(session, "Email", with: "someone@example.com") end) - |> assert_has(input(label: "Email", value: "someone@example.com")) + |> click_button("Save Email") + |> assert_has("#form-data", text: "email: someone@example.com") end - test "raises when data is not in scoped HTML", %{conn: conn} do + test_also_with_js "raises when data is not in scoped HTML", %{conn: conn} do assert_raise ArgumentError, ~r/Could not find element with label "User Name"/, fn -> conn |> visit("/live/index") @@ -297,6 +304,7 @@ defmodule PhoenixTest.LiveTest do end describe "fill_in/3" do + # JS: Browser does not update `input.value` attribute when filling in field test "fills in a single text field based on the label", %{conn: conn} do conn |> visit("/live/index") @@ -304,7 +312,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has(input(label: "Email", value: "someone@example.com")) end - test "can fill input with `nil` to override existing value", %{conn: conn} do + test_also_with_js "can fill input with `nil` to override existing value", %{conn: conn} do conn |> visit("/live/index") |> within("#pre-rendered-data-form", fn session -> @@ -313,7 +321,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "input's value is empty") end - test "can fill-in complex form fields", %{conn: conn} do + test_also_with_js "can fill-in complex form fields", %{conn: conn} do conn |> visit("/live/index") |> fill_in("First Name", with: "Aragorn") @@ -325,22 +333,25 @@ defmodule PhoenixTest.LiveTest do ) end - test "works in 'nested' forms", %{conn: conn} do + test_also_with_js "works in 'nested' forms", %{conn: conn} do conn |> visit("/live/index") |> fill_in("User Name", with: "Aragorn") |> click_button("Save Nested Form") |> assert_has("#form-data", text: "user:name: Aragorn") + |> assert_has("#form-data", text: "user:payer: off") + |> assert_has("#form-data", text: "user:role: El Jefe") end - test "triggers phx-change validations", %{conn: conn} do + test_also_with_js "triggers phx-change validations", %{conn: conn} do conn |> visit("/live/index") + |> fill_in("Email", with: "someone@example.com") |> fill_in("Email", with: nil) |> assert_has("#form-errors", text: "Errors present") end - test "does not trigger phx-change event if one isn't present", %{conn: conn} do + test_also_with_js "does not trigger phx-change event if one isn't present", %{conn: conn} do session = visit(conn, "/live/index") starting_html = Driver.render_html(session) @@ -353,14 +364,14 @@ defmodule PhoenixTest.LiveTest do assert starting_html == ending_html end - test "follows redirects on phx-change", %{conn: conn} do + test_also_with_js "follows redirects on phx-change", %{conn: conn} do conn |> visit("/live/index") |> fill_in("Email with redirect", with: "someone@example.com") |> assert_has("h1", text: "LiveView page 2") end - test "can be used to submit form", %{conn: conn} do + test_also_with_js "can be used to submit form", %{conn: conn} do conn |> visit("/live/index") |> fill_in("Email", with: "someone@example.com") @@ -368,15 +379,15 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "email: someone@example.com") end - test "can be combined with other forms' fill_ins (without pollution)", %{conn: conn} do + test_also_with_js "can be combined with other forms' fill_ins (without pollution)", %{conn: conn} do conn |> visit("/live/index") - |> fill_in("Country", with: "Bolivia") - |> fill_in("City", with: "La Paz") - |> assert_has("#form-data", text: "Bolivia: La Paz") + |> select("Argentina", from: "Country") + |> fill_in("City", with: "Cordoba") + |> assert_has("#form-data", text: "Argentina: Cordoba") end - test "raises an error when element can't be found with label", %{conn: conn} do + test_also_with_js "raises an error when element can't be found with label", %{conn: conn} do msg = ~r/Could not find element with label "Non-existent Email Label"./ assert_raise ArgumentError, msg, fn -> @@ -386,7 +397,7 @@ defmodule PhoenixTest.LiveTest do end end - test "raises an error when label is found but no corresponding input is found", %{conn: conn} do + test_also_with_js "raises an error when label is found but no corresponding input is found", %{conn: conn} do msg = ~r/Found label but could not find corresponding element with matching `id`./ assert_raise ArgumentError, msg, fn -> @@ -398,14 +409,14 @@ defmodule PhoenixTest.LiveTest do end describe "select/3" do - test "selects given option for a label", %{conn: conn} do + test_also_with_js "selects given option for a label", %{conn: conn} do conn |> visit("/live/index") |> select("Elf", from: "Race") |> assert_has("#full-form option[value='elf']") end - test "works in 'nested' forms", %{conn: conn} do + test_also_with_js "works in 'nested' forms", %{conn: conn} do conn |> visit("/live/index") |> select("False", from: "User Admin") @@ -413,7 +424,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "user:admin: false") end - test "can be used to submit form", %{conn: conn} do + test_also_with_js "can be used to submit form", %{conn: conn} do conn |> visit("/live/index") |> select("Elf", from: "Race") @@ -421,7 +432,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "race: elf") end - test "works for multiple select", %{conn: conn} do + test_also_with_js "works for multiple select", %{conn: conn} do conn |> visit("/live/index") |> select("Elf", from: "Race") @@ -432,7 +443,7 @@ defmodule PhoenixTest.LiveTest do end describe "check/2" do - test "checks a checkbox", %{conn: conn} do + test_also_with_js "checks a checkbox", %{conn: conn} do conn |> visit("/live/index") |> check("Admin") @@ -440,7 +451,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "admin: on") end - test "can check an unchecked checkbox", %{conn: conn} do + test_also_with_js "can check an unchecked checkbox", %{conn: conn} do conn |> visit("/live/index") |> uncheck("Admin") @@ -449,17 +460,25 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "admin: on") end - test "handle checkbox name with '?'", %{conn: conn} do + test_also_with_js "handle checkbox name with '?'", %{conn: conn} do conn |> visit("/live/index") |> check("Subscribe") |> click_button("Save Full Form") |> assert_has("#form-data", text: "subscribe?: on") end + + test "works in 'nested' forms", %{conn: conn} do + conn + |> visit("/live/index") + |> check("Payer") + |> click_button("Save Nested Form") + |> assert_has("#form-data", text: "user:payer: on") + end end describe "uncheck/2" do - test "sends the default value (in hidden input)", %{conn: conn} do + test_also_with_js "sends the default value (in hidden input)", %{conn: conn} do conn |> visit("/live/index") |> uncheck("Admin") @@ -467,7 +486,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "admin: off") end - test "can uncheck a previous check/2 in the test", %{conn: conn} do + test_also_with_js "can uncheck a previous check/2 in the test", %{conn: conn} do conn |> visit("/live/index") |> check("Admin") @@ -475,10 +494,19 @@ defmodule PhoenixTest.LiveTest do |> click_button("Save Full Form") |> assert_has("#form-data", text: "admin: off") end + + test "works in 'nested' forms", %{conn: conn} do + conn + |> visit("/live/index") + |> check("Payer") + |> uncheck("Payer") + |> click_button("Save Nested Form") + |> assert_has("#form-data", text: "user:payer: off") + end end describe "choose/2" do - test "chooses an option in radio button", %{conn: conn} do + test_also_with_js "chooses an option in radio button", %{conn: conn} do conn |> visit("/live/index") |> choose("Email Choice") @@ -486,7 +514,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "contact: email") end - test "uses the default 'checked' if present", %{conn: conn} do + test_also_with_js "uses the default 'checked' if present", %{conn: conn} do conn |> visit("/live/index") |> click_button("Save Full Form") @@ -495,7 +523,7 @@ defmodule PhoenixTest.LiveTest do end describe "filling out full form with field functions" do - test "populates all fields", %{conn: conn} do + test_also_with_js "populates all fields", %{conn: conn} do conn |> visit("/live/index") |> fill_in("First Name", with: "Legolas") @@ -513,7 +541,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "notes: Woodland Elf") end - test "populates all fields in nested forms", %{conn: conn} do + test_also_with_js "populates all fields in nested forms", %{conn: conn} do conn |> visit("/live/index") |> fill_in("User Name", with: "Legolas") @@ -525,7 +553,7 @@ defmodule PhoenixTest.LiveTest do end describe "submit/1" do - test "submits a pre-filled form via phx-submit", %{conn: conn} do + test_also_with_js "submits a pre-filled form via phx-submit", %{conn: conn} do conn |> visit("/live/index") |> fill_in("Email", with: "some@example.com") @@ -533,7 +561,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "email: some@example.com") end - test "includes pre-rendered data", %{conn: conn} do + test_also_with_js "includes pre-rendered data", %{conn: conn} do conn |> visit("/live/index") |> fill_in("First Name", with: "Aragorn") @@ -543,7 +571,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "contact: mail") end - test "includes the first button's name and value if present", %{conn: conn} do + test_also_with_js "includes the first button's name and value if present", %{conn: conn} do conn |> visit("/live/index") |> fill_in("First Name", with: "Aragorn") @@ -551,7 +579,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "full_form_button: save") end - test "can submit form without button", %{conn: conn} do + test_also_with_js "can submit form without button", %{conn: conn} do conn |> visit("/live/index") |> fill_in("Country of Origin", with: "Arnor") @@ -559,7 +587,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "country: Arnor") end - test "follows form's redirect to live page", %{conn: conn} do + test_also_with_js "follows form's redirect to live page", %{conn: conn} do conn |> visit("/live/index") |> within("#redirect-form", fn session -> @@ -570,7 +598,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("h1", text: "LiveView page 2") end - test "follows form's redirect to static page", %{conn: conn} do + test_also_with_js "follows form's redirect to static page", %{conn: conn} do conn |> visit("/live/index") |> within("#redirect-form-to-static", fn session -> @@ -581,7 +609,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("h1", text: "Main page") end - test "submits regular (non phx-submit) form", %{conn: conn} do + test_also_with_js "submits regular (non phx-submit) form", %{conn: conn} do conn |> visit("/live/index") |> within("#non-liveview-form", fn session -> @@ -593,7 +621,7 @@ defmodule PhoenixTest.LiveTest do |> assert_has("#form-data", text: "button: save") end - test "raises an error if there's no active form", %{conn: conn} do + test_also_with_js "raises an error if there's no active form", %{conn: conn} do message = ~r/There's no active form. Fill in a form with `fill_in`, `select`, etc./ assert_raise ArgumentError, message, fn -> @@ -662,17 +690,17 @@ defmodule PhoenixTest.LiveTest do end end - describe "session.current_path" do + describe "current_path" do test "it is set on visit", %{conn: conn} do session = visit(conn, "/live/index") - assert session.current_path == "/live/index" + assert PhoenixTest.Driver.current_path(session) == "/live/index" end test "it is set on visit with query string", %{conn: conn} do session = visit(conn, "/live/index?foo=bar") - assert session.current_path == "/live/index?foo=bar" + assert PhoenixTest.Driver.current_path(session) == "/live/index?foo=bar" end test "it is updated on href navigation", %{conn: conn} do @@ -681,7 +709,7 @@ defmodule PhoenixTest.LiveTest do |> visit("/live/index") |> click_link("Navigate to non-liveview") - assert session.current_path == "/page/index?details=true&foo=bar" + assert PhoenixTest.Driver.current_path(session) == "/page/index?details=true&foo=bar" end test "it is updated on live navigation", %{conn: conn} do @@ -690,7 +718,7 @@ defmodule PhoenixTest.LiveTest do |> visit("/live/index") |> click_link("Navigate link") - assert session.current_path == "/live/page_2?details=true&foo=bar" + assert PhoenixTest.Driver.current_path(session) == "/live/page_2?details=true&foo=bar" end test "it is updated on live patching", %{conn: conn} do @@ -699,7 +727,7 @@ defmodule PhoenixTest.LiveTest do |> visit("/live/index") |> click_link("Patch link") - assert session.current_path == "/live/index?details=true&foo=bar" + assert PhoenixTest.Driver.current_path(session) == "/live/index?details=true&foo=bar" end test "it is updated on push navigation", %{conn: conn} do @@ -708,7 +736,7 @@ defmodule PhoenixTest.LiveTest do |> visit("/live/index") |> click_button("Button with push navigation") - assert session.current_path == "/live/page_2?foo=bar" + assert PhoenixTest.Driver.current_path(session) == "/live/page_2?foo=bar" end test "it is updated on push patch", %{conn: conn} do @@ -717,7 +745,7 @@ defmodule PhoenixTest.LiveTest do |> visit("/live/index") |> click_button("Button with push patch") - assert session.current_path == "/live/index?foo=bar" + assert PhoenixTest.Driver.current_path(session) == "/live/index?foo=bar" end end end diff --git a/test/phoenix_test/static_test.exs b/test/phoenix_test/static_test.exs index 47c03307..7456430d 100644 --- a/test/phoenix_test/static_test.exs +++ b/test/phoenix_test/static_test.exs @@ -4,12 +4,16 @@ defmodule PhoenixTest.StaticTest do import PhoenixTest import PhoenixTest.TestHelpers - setup do - %{conn: Phoenix.ConnTest.build_conn()} + require PhoenixTest.TestHelpers + + setup context do + conn = Phoenix.ConnTest.build_conn() + conn = if context[:js], do: with_js_driver(conn), else: conn + %{conn: conn} end describe "render_page_title/1" do - test "renders the page title", %{conn: conn} do + test_also_with_js "renders the page title", %{conn: conn} do title = conn |> visit("/page/index") @@ -18,7 +22,7 @@ defmodule PhoenixTest.StaticTest do assert title == "PhoenixTest is the best!" end - test "renders nil if there's no page title", %{conn: conn} do + test_also_with_js "renders nil if there's no page title", %{conn: conn} do title = conn |> visit("/page/index_no_layout") @@ -29,19 +33,20 @@ defmodule PhoenixTest.StaticTest do end describe "visit/2" do - test "navigates to given static page", %{conn: conn} do + test_also_with_js "navigates to given static page", %{conn: conn} do conn |> visit("/page/index") |> assert_has("h1", text: "Main page") end - test "follows redirects", %{conn: conn} do + test_also_with_js "follows redirects", %{conn: conn} do conn |> visit("/page/redirect_to_static") |> assert_has("h1", text: "Main page") end - test "raises error if route doesn't exist", %{conn: conn} do + @tag :skip + test_also_with_js "raises error if route doesn't exist", %{conn: conn} do assert_raise Phoenix.Router.NoRouteError, fn -> visit(conn, "/non_route") end @@ -49,35 +54,35 @@ defmodule PhoenixTest.StaticTest do end describe "click_link/2" do - test "follows link's path", %{conn: conn} do + test_also_with_js "follows link's path", %{conn: conn} do conn |> visit("/page/index") |> click_link("Page 2") |> assert_has("h1", text: "Page 2") end - test "follows link that subsequently redirects", %{conn: conn} do + test_also_with_js "follows link that subsequently redirects", %{conn: conn} do conn |> visit("/page/index") |> click_link("Navigate away and redirect back") |> assert_has("h1", text: "Main page") end - test "accepts selector for link", %{conn: conn} do + test_also_with_js "accepts selector for link", %{conn: conn} do conn |> visit("/page/index") |> click_link("a", "Page 2") |> assert_has("h1", text: "Page 2") end - test "handles navigation to a LiveView", %{conn: conn} do + test_also_with_js "handles navigation to a LiveView", %{conn: conn} do conn |> visit("/page/index") |> click_link("To LiveView!") |> assert_has("h1", text: "LiveView main page") end - test "handles form submission via `data-method` & `data-to` attributes", %{conn: conn} do + test_also_with_js "handles form submission via `data-method` & `data-to` attributes", %{conn: conn} do conn |> visit("/page/index") |> click_link("Data-method Delete") @@ -114,24 +119,24 @@ defmodule PhoenixTest.StaticTest do end end - test "raises error when there are multiple links with same text", %{conn: conn} do - assert_raise ArgumentError, ~r/Found more than one element with selector/, fn -> + test_also_with_js "raises error when there are multiple links with same text", %{conn: conn} do + assert_raise ArgumentError, fn -> conn |> visit("/page/index") |> click_link("Multiple links") end end - test "raises an error when link element can't be found with given text", %{conn: conn} do - assert_raise ArgumentError, ~r/Could not find element with selector/, fn -> + test_also_with_js "raises an error when link element can't be found with given text", %{conn: conn} do + assert_raise ArgumentError, fn -> conn |> visit("/page/index") |> click_link("No link") end end - test "raises an error when there are no links on the page", %{conn: conn} do - assert_raise ArgumentError, ~r/Could not find element with selector/, fn -> + test_also_with_js "raises an error when there are no links on the page", %{conn: conn} do + assert_raise ArgumentError, fn -> conn |> visit("/page/page_2") |> click_link("No link") @@ -140,35 +145,35 @@ defmodule PhoenixTest.StaticTest do end describe "click_button/2" do - test "handles a button that defaults to GET", %{conn: conn} do + test_also_with_js "handles a button that defaults to GET", %{conn: conn} do conn |> visit("/page/index") |> click_button("Get record") |> assert_has("h1", text: "Record received") end - test "accepts selector for button", %{conn: conn} do + test_also_with_js "accepts selector for button", %{conn: conn} do conn |> visit("/page/index") |> click_button("button", "Get record") |> assert_has("h1", text: "Record received") end - test "handles a button clicks when button PUTs data (hidden input)", %{conn: conn} do + test_also_with_js "handles a button clicks when button PUTs data (hidden input)", %{conn: conn} do conn |> visit("/page/index") |> click_button("Mark as active") |> assert_has("h1", text: "Record updated") end - test "handles a button clicks when button DELETEs data (hidden input)", %{conn: conn} do + test_also_with_js "handles a button clicks when button DELETEs data (hidden input)", %{conn: conn} do conn |> visit("/page/index") |> click_button("Delete record") |> assert_has("h1", text: "Record deleted") end - test "can submit forms with input type submit", %{conn: conn} do + test_also_with_js "can submit forms with input type submit", %{conn: conn} do conn |> visit("/page/index") |> fill_in("Email", with: "sample@example.com") @@ -176,7 +181,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "email: sample@example.com") end - test "can handle clicking button that does not submit form after filling a form", %{conn: conn} do + test_also_with_js "can handle clicking button that does not submit form after filling a form", %{conn: conn} do conn |> visit("/page/index") |> fill_in("Email", with: "some@example.com") @@ -184,7 +189,7 @@ defmodule PhoenixTest.StaticTest do |> refute_has("#form-data", text: "email: some@example.com") end - test "submits owner form if button isn't nested inside form", %{conn: conn} do + test_also_with_js "submits owner form if button isn't nested inside form", %{conn: conn} do conn |> visit("/page/index") |> within("#owner-form", fn session -> @@ -194,14 +199,14 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "name: Aragorn") end - test "can handle redirects to a LiveView", %{conn: conn} do + test_also_with_js "can handle redirects to a LiveView", %{conn: conn} do conn |> visit("/page/index") |> click_button("Post and Redirect") |> assert_has("h1", text: "LiveView main page") end - test "handles form submission via `data-method` & `data-to` attributes", %{conn: conn} do + test_also_with_js "handles form submission via `data-method` & `data-to` attributes", %{conn: conn} do conn |> visit("/page/index") |> click_button("Data-method Delete") @@ -228,7 +233,7 @@ defmodule PhoenixTest.StaticTest do refute PhoenixTest.ActiveForm.active?(session.active_form) end - test "includes name and value if specified", %{conn: conn} do + test_also_with_js "includes name and value if specified", %{conn: conn} do conn |> visit("/page/index") |> fill_in("User Name", with: "Aragorn") @@ -236,7 +241,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "user:save-button: nested-form-save") end - test "can handle clicking button that does not submit form after fill_in", %{conn: conn} do + test_also_with_js "can handle clicking button that does not submit form after fill_in", %{conn: conn} do conn |> visit("/page/index") |> fill_in("Email", with: "some@example.com") @@ -244,12 +249,13 @@ defmodule PhoenixTest.StaticTest do |> refute_has("#form-data", text: "email: some@example.com") end - test "includes default data if form is untouched", %{conn: conn} do + test_also_with_js "includes default data if form is untouched", %{conn: conn} do conn |> visit("/page/index") |> click_button("Save Full Form") |> assert_has("#form-data", text: "admin: off") |> assert_has("#form-data", text: "contact: mail") + |> assert_has("#form-data", text: "level: 7") |> assert_has("#form-data", text: "full_form_button: save") end @@ -283,7 +289,7 @@ defmodule PhoenixTest.StaticTest do end end - test "raises an error when there are no buttons on page", %{conn: conn} do + test_also_with_js "raises an error when there are no buttons on page", %{conn: conn} do msg = ~r/Could not find element with selector "button" and text "Show tab"/ assert_raise ArgumentError, msg, fn -> @@ -293,7 +299,7 @@ defmodule PhoenixTest.StaticTest do end end - test "raises an error if can't find button", %{conn: conn} do + test_also_with_js "raises an error if can't find button", %{conn: conn} do msg = ~r/Could not find element with selector "button" and text "No button"/ assert_raise ArgumentError, msg, fn -> @@ -316,7 +322,7 @@ defmodule PhoenixTest.StaticTest do end describe "within/3" do - test "scopes assertions within selector", %{conn: conn} do + test_also_with_js "scopes assertions within selector", %{conn: conn} do conn |> visit("/page/index") |> assert_has("button", text: "Get record") @@ -325,7 +331,7 @@ defmodule PhoenixTest.StaticTest do end) end - test "scopes further form actions within a selector", %{conn: conn} do + test_also_with_js "scopes further form actions within a selector", %{conn: conn} do conn |> visit("/page/index") |> within("#email-form", fn session -> @@ -336,7 +342,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "email: someone@example.com") end - test "raises when data is not in scoped HTML", %{conn: conn} do + test_also_with_js "raises when data is not in scoped HTML", %{conn: conn} do assert_raise ArgumentError, ~r/Could not find element with label "User Name"/, fn -> conn |> visit("/page/index") @@ -348,7 +354,7 @@ defmodule PhoenixTest.StaticTest do end describe "fill_in/3" do - test "fills in a single text field based on the label", %{conn: conn} do + test_also_with_js "fills in a single text field based on the label", %{conn: conn} do conn |> visit("/page/index") |> fill_in("Email", with: "someone@example.com") @@ -356,7 +362,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "email: someone@example.com") end - test "can fill input with `nil` to override existing value", %{conn: conn} do + test_also_with_js "can fill input with `nil` to override existing value", %{conn: conn} do conn |> visit("/page/index") |> fill_in("Pre Rendered Input", with: nil) @@ -364,7 +370,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "input's value is empty") end - test "can fill-in complex form fields", %{conn: conn} do + test_also_with_js "can fill-in complex form fields", %{conn: conn} do conn |> visit("/page/index") |> fill_in("First Name", with: "Aragorn") @@ -376,15 +382,18 @@ defmodule PhoenixTest.StaticTest do ) end - test "works in 'nested' forms", %{conn: conn} do + test_also_with_js "works in 'nested' forms", %{conn: conn} do conn |> visit("/page/index") |> fill_in("User Name", with: "Aragorn") |> click_button("Save Nested Form") |> assert_has("#form-data", text: "user:name: Aragorn") + |> assert_has("#form-data", text: "user:admin: true") + |> assert_has("#form-data", text: "user:payer: off") + |> assert_has("#form-data", text: "user:role: El Jefe") end - test "can be combined with other forms' fill_ins (without pollution)", %{conn: conn} do + test_also_with_js "can be combined with other forms' fill_ins (without pollution)", %{conn: conn} do conn |> visit("/page/index") |> fill_in("First Name", with: "Aragorn") @@ -394,7 +403,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "user:name: Legolas") end - test "raises an error when element can't be found with label", %{conn: conn} do + test_also_with_js "raises an error when element can't be found with label", %{conn: conn} do msg = ~r/Could not find element with label "Non-existent Email Label"./ assert_raise ArgumentError, msg, fn -> @@ -404,7 +413,7 @@ defmodule PhoenixTest.StaticTest do end end - test "raises an error when label is found but no corresponding input is found", %{conn: conn} do + test_also_with_js "raises an error when label is found but no corresponding input is found", %{conn: conn} do msg = ~r/Found label but could not find corresponding element with matching `id`./ assert_raise ArgumentError, msg, fn -> @@ -416,7 +425,7 @@ defmodule PhoenixTest.StaticTest do end describe "select/3" do - test "selects given option for a label", %{conn: conn} do + test_also_with_js "selects given option for a label", %{conn: conn} do conn |> visit("/page/index") |> select("Elf", from: "Race") @@ -424,7 +433,14 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "race: elf") end - test "works in 'nested' forms", %{conn: conn} do + test_also_with_js "picks first by default", %{conn: conn} do + conn + |> visit("/page/index") + |> click_button("Save Full Form") + |> assert_has("#form-data", text: "race: human") + end + + test_also_with_js "works in 'nested' forms", %{conn: conn} do conn |> visit("/page/index") |> select("False", from: "User Admin") @@ -432,17 +448,24 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "user:admin: false") end - test "handles multi select", %{conn: conn} do + test_also_with_js "handles multi select", %{conn: conn} do conn |> visit("/page/index") |> select(["Elf", "Dwarf"], from: "Race 2") |> click_button("Save Full Form") |> assert_has("#form-data", text: "race_2: [elf,dwarf]") end + + test "honors empty default for multi select", %{conn: conn} do + conn + |> visit("/page/index") + |> click_button("Save Full Form") + |> assert_has("#form-data", text: "race_2: []") + end end describe "check/3" do - test "checks a checkbox", %{conn: conn} do + test_also_with_js "checks a checkbox", %{conn: conn} do conn |> visit("/page/index") |> check("Admin (boolean)") @@ -450,7 +473,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "admin_boolean: true") end - test "sets checkbox value as 'on' by default", %{conn: conn} do + test_also_with_js "sets checkbox value as 'on' by default", %{conn: conn} do conn |> visit("/page/index") |> check("Admin") @@ -458,7 +481,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "admin: on") end - test "can check an unchecked checkbox", %{conn: conn} do + test_also_with_js "can check an unchecked checkbox", %{conn: conn} do conn |> visit("/page/index") |> uncheck("Admin") @@ -467,7 +490,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "admin: on") end - test "handle checkbox name with '?'", %{conn: conn} do + test_also_with_js "handle checkbox name with '?'", %{conn: conn} do conn |> visit("/page/index") |> check("Subscribe") @@ -477,7 +500,7 @@ defmodule PhoenixTest.StaticTest do end describe "uncheck/3" do - test "sends the default value (in hidden input)", %{conn: conn} do + test_also_with_js "sends the default value (in hidden input)", %{conn: conn} do conn |> visit("/page/index") |> uncheck("Admin") @@ -485,7 +508,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "admin: off") end - test "can uncheck a previous check/2 in the test", %{conn: conn} do + test_also_with_js "can uncheck a previous check/2 in the test", %{conn: conn} do conn |> visit("/page/index") |> check("Admin") @@ -496,7 +519,7 @@ defmodule PhoenixTest.StaticTest do end describe "choose/2" do - test "chooses an option in radio button", %{conn: conn} do + test_also_with_js "chooses an option in radio button", %{conn: conn} do conn |> visit("/page/index") |> choose("Email Choice") @@ -504,7 +527,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "contact: email") end - test "uses the default 'checked' if present", %{conn: conn} do + test_also_with_js "uses the default 'checked' if present", %{conn: conn} do conn |> visit("/page/index") |> click_button("Save Full Form") @@ -513,7 +536,7 @@ defmodule PhoenixTest.StaticTest do end describe "filling out full form with field functions" do - test "populates all fields", %{conn: conn} do + test_also_with_js "populates all fields", %{conn: conn} do conn |> visit("/page/index") |> fill_in("First Name", with: "Legolas") @@ -531,7 +554,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "notes: Woodland Elf") end - test "populates all fields in nested forms", %{conn: conn} do + test_also_with_js "populates all fields in nested forms", %{conn: conn} do conn |> visit("/page/index") |> fill_in("User Name", with: "Legolas") @@ -543,7 +566,7 @@ defmodule PhoenixTest.StaticTest do end describe "submit/1" do - test "submits form even if no submit is present (acts as )", %{conn: conn} do + test_also_with_js "submits form even if no submit is present (acts as )", %{conn: conn} do conn |> visit("/page/index") |> within("#no-submit-button-form", fn session -> @@ -554,8 +577,8 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "name: Aragorn") end - test "includes pre-rendered data (input value, selected option, checked checkbox, checked radio button)", - %{conn: conn} do + test_also_with_js "includes pre-rendered data (input value, selected option, checked checkbox, checked radio button)", + %{conn: conn} do conn |> visit("/page/index") |> fill_in("First Name", with: "Aragorn") @@ -564,7 +587,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "race: human") end - test "includes the first button's name and value if present", %{conn: conn} do + test_also_with_js "includes the first button's name and value if present", %{conn: conn} do conn |> visit("/page/index") |> fill_in("First Name", with: "Aragorn") @@ -572,7 +595,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "full_form_button: save") end - test "can submit form without button", %{conn: conn} do + test_also_with_js "can submit form without button", %{conn: conn} do conn |> visit("/page/index") |> fill_in("Country of Origin", with: "Arnor") @@ -580,7 +603,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "country: Arnor") end - test "can handle redirects", %{conn: conn} do + test_also_with_js "can handle redirects", %{conn: conn} do conn |> visit("/page/index") |> within("#no-submit-button-and-redirect", fn session -> @@ -591,7 +614,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("h1", text: "LiveView main page") end - test "handles when form PUTs data through hidden input", %{conn: conn} do + test_also_with_js "handles when form PUTs data through hidden input", %{conn: conn} do conn |> visit("/page/index") |> within("#update-form", fn session -> @@ -602,7 +625,7 @@ defmodule PhoenixTest.StaticTest do |> assert_has("#form-data", text: "name: Aragorn") end - test "handles a button clicks when button DELETEs data (hidden input)", %{conn: conn} do + test_also_with_js "handles a button clicks when button DELETEs data (hidden input)", %{conn: conn} do conn |> visit("/page/index") |> click_button("Delete record") @@ -626,7 +649,7 @@ defmodule PhoenixTest.StaticTest do assert content = File.read!(path) assert content =~ - ~r[] + ~r[] assert content =~ "" assert content =~ "body { font-size: 12px; }" @@ -643,7 +666,7 @@ defmodule PhoenixTest.StaticTest do %{open_fun: open_fun} end - test "opens the browser ", %{conn: conn, open_fun: open_fun} do + test_also_with_js "opens the browser ", %{conn: conn, open_fun: open_fun} do conn |> visit("/page/index") |> open_browser(open_fun) @@ -677,35 +700,35 @@ defmodule PhoenixTest.StaticTest do end end - describe "session.current_path" do - test "it is set on visit", %{conn: conn} do + describe "current_path" do + test_also_with_js "it is set on visit", %{conn: conn} do session = visit(conn, "/page/index") - assert session.current_path == "/page/index" + assert PhoenixTest.Driver.current_path(session) == "/page/index" end - test "it includes query string if available", %{conn: conn} do + test_also_with_js "it includes query string if available", %{conn: conn} do session = visit(conn, "/page/index?foo=bar") - assert session.current_path == "/page/index?foo=bar" + assert PhoenixTest.Driver.current_path(session) == "/page/index?foo=bar" end - test "it is updated on href navigation", %{conn: conn} do + test_also_with_js "it is updated on href navigation", %{conn: conn} do session = conn |> visit("/page/index") |> click_link("Page 2") - assert session.current_path == "/page/page_2?foo=bar" + assert PhoenixTest.Driver.current_path(session) == "/page/page_2?foo=bar" end - test "it is updated on redirects", %{conn: conn} do + test_also_with_js "it is updated on redirects", %{conn: conn} do session = conn |> visit("/page/index") |> click_link("Navigate away and redirect back") - assert session.current_path == "/page/index" + assert PhoenixTest.Driver.current_path(session) == "/page/index" end end end diff --git a/test/phoenix_test/wallaby_test.exs b/test/phoenix_test/wallaby_test.exs new file mode 100644 index 00000000..e86dd1ae --- /dev/null +++ b/test/phoenix_test/wallaby_test.exs @@ -0,0 +1,75 @@ +defmodule PhoenixTest.WallabyTest do + use ExUnit.Case, async: true + + import PhoenixTest + + @moduletag :js + + setup context do + conn = Phoenix.ConnTest.build_conn() + conn = if context[:js], do: with_js_driver(conn), else: conn + %{conn: conn} + end + + describe "unwrap" do + test "provides an escape hatch that gives access to the underlying view", %{conn: conn} do + conn + |> visit("/live/index") + |> unwrap(fn session -> + session + |> Wallaby.Browser.fill_in(Wallaby.Query.text_field("Notes"), with: "Wow") + |> Wallaby.Browser.click(Wallaby.Query.button("Save Full Form")) + end) + |> assert_has("#form-data", text: "notes: Wow") + end + end + + describe "assert_has/3 title" do + test "asserts on page title", %{conn: conn} do + conn + |> visit("/live/index") + |> assert_has("title", text: "PhoenixTest is the best!") + end + + test "asserts on updated page title", %{conn: conn} do + conn + |> visit("/live/index") + |> click_button("Change page title") + |> assert_has("title", text: "Title changed!") + end + + test "refutes missing page title", %{conn: conn} do + conn + |> visit("/live/index_no_layout") + |> refute_has("title") + end + end + + describe "assert_path" do + test "it asserts on visit", %{conn: conn} do + conn + |> visit("/live/index") + |> assert_path("/live/index") + end + + test "it asserts on visit with query string", %{conn: conn} do + conn + |> visit("/live/index?foo=bar") + |> assert_path("/live/index", query_params: %{foo: "bar"}) + end + + test "it asserts on href navigation", %{conn: conn} do + conn + |> visit("/live/index") + |> click_link("Navigate to non-liveview") + |> assert_path("/page/index", query_params: %{details: true, foo: "bar"}) + end + + test "it asserts on live navigation", %{conn: conn} do + conn + |> visit("/live/index") + |> click_link("Navigate link") + |> assert_path("/live/page_2", query_params: %{details: true, foo: "bar"}) + end + end +end diff --git a/test/support/endpoint.ex b/test/support/endpoint.ex index ab555e4a..a6d3f5bc 100644 --- a/test/support/endpoint.ex +++ b/test/support/endpoint.ex @@ -1,42 +1,15 @@ -defmodule PhoenixTest.EndpointOverridable do - @moduledoc false - defmacro __before_compile__(_env) do - quote do - @parsers Plug.Parsers.init( - parsers: [:urlencoded, :multipart, :json], - pass: ["*/*"], - json_decoder: Phoenix.json_library() - ) - - defoverridable call: 2 - - def call(conn, _) do - %{conn | secret_key_base: config(:secret_key_base)} - |> Plug.Parsers.call(@parsers) - |> Plug.Conn.put_private(:phoenix_endpoint, __MODULE__) - |> PhoenixTest.Router.call([]) - end - end - end -end - defmodule PhoenixTest.Endpoint do + @moduledoc false use Phoenix.Endpoint, otp_app: :phoenix_test - @before_compile PhoenixTest.EndpointOverridable socket("/live", Phoenix.LiveView.Socket) - defoverridable url: 0, script_name: 0, config: 1, config: 2, static_path: 1 - def url, do: "http://localhost:4000" - def script_name, do: [] - def static_path(path), do: "/static" <> path - def config(:live_view), do: [signing_salt: "112345678212345678312345678412"] - def config(:secret_key_base), do: String.duplicate("57689", 50) - def config(:cache_static_manifest_latest), do: Process.get(:cache_static_manifest_latest) - def config(:otp_app), do: :phoenix_test - def config(:pubsub_server), do: Phoenix.LiveView.PubSub - def config(:render_errors), do: [view: __MODULE__] - def config(:static_url), do: [path: "/static"] - def config(which), do: super(which) - def config(which, default), do: super(which, default) + plug Plug.Static, at: "/", from: :phoenix_test + + plug Plug.Parsers, + parsers: [:urlencoded, :multipart], + pass: ["*/*"] + + plug Plug.MethodOverride + plug PhoenixTest.Router end diff --git a/test/support/error_view.ex b/test/support/error_view.ex new file mode 100644 index 00000000..68cd5d3f --- /dev/null +++ b/test/support/error_view.ex @@ -0,0 +1,9 @@ +defmodule PhoenixTest.ErrorView do + def render(template, %{conn: conn}) do + unless conn.private.phoenix_endpoint do + raise "no endpoint in error view" + end + + "#{template} from Phoenix.ErrorView" + end +end diff --git a/test/support/index_live.ex b/test/support/index_live.ex index b63beb52..6b3b054a 100644 --- a/test/support/index_live.ex +++ b/test/support/index_live.ex @@ -105,12 +105,19 @@ defmodule PhoenixTest.IndexLive do + + + + + + + @@ -131,6 +138,9 @@ defmodule PhoenixTest.IndexLive do + + + + + + + + + + @@ -153,6 +163,9 @@ defmodule PhoenixTest.PageView do + + + - diff --git a/test/support/test_helpers.ex b/test/support/test_helpers.ex index a5906dc7..0bdeaf6c 100644 --- a/test/support/test_helpers.ex +++ b/test/support/test_helpers.ex @@ -1,6 +1,8 @@ defmodule PhoenixTest.TestHelpers do @moduledoc false + require ExUnit.Case + @doc """ Converts a multi-line string into a whitespace-forgiving regex """ @@ -12,4 +14,15 @@ defmodule PhoenixTest.TestHelpers do |> Enum.map_join("\n", fn s -> "\\s*" <> s <> "\\s*" end) |> Regex.compile!([:dotall]) end + + defmacro test_also_with_js(message, var \\ quote(do: _), contents) do + quote location: :keep do + tags = Module.get_attribute(__MODULE__, :tag) + ExUnit.Case.test(unquote(message), unquote(var), unquote(contents)) + + for tag <- tags, do: @tag(tag) + @tag :js + ExUnit.Case.test(unquote(message) <> " (JS)", unquote(var), unquote(contents)) + end + end end diff --git a/test/test_helper.exs b/test/test_helper.exs index f63cefb9..5a68d347 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,6 @@ ExUnit.start() -Application.put_env(:phoenix_test, PhoenixTest.Endpoint, []) {:ok, _} = PhoenixTest.Endpoint.start_link() + +{:ok, _} = Application.ensure_all_started(:wallaby) +Application.put_env(:wallaby, :base_url, PhoenixTest.Endpoint.url())