こんにちは!
プログラミング未経験文系出身、Elixirの国に迷い込んだ?!見習いアルケミストのaliceと申します。
今回はPhoenix.LiveViewTestについて学んだことをまとめます。
Phoenix.LiveViewTestとは?
ElixirのWebアプリケーションフレームワークであるPhoenixのライブラリである、LiveViewのライフサイクルを利用した自動テストの仕組み。結合テストの自動化が可能になります。
実行環境
Windows 11 Home(バージョン 21H2, OS ビルド 22000.1219)
WSL2 Ubuntu22.04
Elixir 1.14.1
Erlang 25.0.4
Phoenix installer v1.7.0-rc.0
Phoenix.LiveViewTestを試してみた!
Phoenixプロジェクトを作成する
いつも通りPhoenixプロジェクトを作ります。
$mix phx.new dec19 --database sqlite3
$cd dec19
$mix ecto.create
mix test.watchを導入する
毎回mix testを実行するのは面倒なので、テストを自動実行するライブラリであるmix test.watchを導入します。
https://hex.pm/packages/mix_test_watch にアクセスし、mix.exs用のmix.test.watchをコピーしてきます。
mix.exsのdepsに貼り付けます。
defmodule Dec19.MixProject do
use Mix.Project
def project do
[
app: :dec19,
version: "0.1.0",
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps()
]
end
# Configuration for the OTP application.
#
# Type `mix help compile.app` for more information.
def application do
[
mod: {Dec19.Application, []},
extra_applications: [:logger, :runtime_tools]
]
end
# Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
# Specifies your project dependencies.
#
# Type `mix help deps` for examples and options.
defp deps do
[
{:phoenix, "~> 1.7.0-rc.0", override: true},
{:phoenix_ecto, "~> 4.4"},
{:ecto_sql, "~> 3.6"},
{:ecto_sqlite3, ">= 0.0.0"},
{:phoenix_html, "~> 3.0"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_view, "~> 0.18.3"},
{:heroicons, "~> 0.5"},
{:floki, ">= 0.30.0", only: :test},
{:phoenix_live_dashboard, "~> 0.7.2"},
{:esbuild, "~> 0.5", runtime: Mix.env() == :dev},
{:tailwind, "~> 0.1.8", runtime: Mix.env() == :dev},
{:swoosh, "~> 1.3"},
{:finch, "~> 0.13"},
{:telemetry_metrics, "~> 0.6"},
{:telemetry_poller, "~> 1.0"},
{:gettext, "~> 0.20"},
{:jason, "~> 1.2"},
{:plug_cowboy, "~> 2.5"},
+ {:mix_test_watch, "~> 1.1"}
]
end
# Aliases are shortcuts or tasks specific to the current project.
# For example, to install project dependencies and perform other setup tasks, run:
#
# $ mix setup
#
# See the documentation for `Mix` for more info on aliases.
defp aliases do
[
setup: ["deps.get", "ecto.setup"],
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"],
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
"assets.deploy": ["tailwind default --minify", "esbuild default --minify", "phx.digest"]
]
end
end
ライブラリをとってきます。
$ mix deps.get
追加したライブラリの動作確認をします。
$ mix test.watch
PhoenixプロジェクトをLiveView化する
LiveViewを導入します。
$ mix phx.gen.live Accounts User users name:string
routerにpathを追加します。
defmodule Dec19Web.Router do
use Dec19Web, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {Dec19Web.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", Dec19Web do
pipe_through :browser
+ live "/users", UserLive.Index, :index
+ live "/users/new", UserLive.Index, :new
+ live "/users/:id/edit", UserLive.Index, :edit
+
+ live "/users/:id", UserLive.Show, :show
+ live "/users/:id/show/edit", UserLive.Show, :edit
get "/", PageController, :home
end
# Other scopes may use custom stacks.
# scope "/api", Dec19Web do
# pipe_through :api
# end
# Enable LiveDashboard and Swoosh mailbox preview in development
if Application.compile_env(:dec19, :dev_routes) do
# If you want to use the LiveDashboard in production, you should put
# it behind authentication and allow only admins to access it.
# If your application does not have an admins-only section yet,
# you can use Plug.BasicAuth to set up some basic authentication
# as long as you are also using SSL (which you should anyway).
import Phoenix.LiveDashboard.Router
scope "/dev" do
pipe_through :browser
live_dashboard "/dashboard", metrics: Dec19Web.Telemetry
forward "/mailbox", Plug.Swoosh.MailboxPreview
end
end
end
マイグレーションします。
$ mix ecto.migrate
LiveViewTestの動きを検証してみる
まずはmix test.watchを起動
$ mix test.watch
Running tests...
.............
Finished in 0.3 seconds (0.1s async, 0.2s sync)
18 tests, 0 failures
エンドポイント/usersのページに文字列"hogehoge"を加えて、それが存在することを確認してみましょう。
※以下ソースコード中のシギル~pはphoenix1.7以降の新機能です。詳しくは@the_haigo さんのこちらの記事をご覧ください。
<.header>
Listing Users
<:actions>
<.link patch={~p"/users/new"}>
<.button>New User</.button>
</.link>
</:actions>
</.header>
+ <h1>hogehoge</h1>
<.table id="users" rows={@users} row_click={&JS.navigate(~p"/users/#{&1}")}>
<:col :let={user} label="Name"><%= user.name %></:col>
<:action :let={user}>
<div class="sr-only">
<.link navigate={~p"/users/#{user}"}>Show</.link>
</div>
<.link patch={~p"/users/#{user}/edit"}>Edit</.link>
</:action>
<:action :let={user}>
<.link phx-click={JS.push("delete", value: %{id: user.id})} data-confirm="Are you sure?">
Delete
</.link>
</:action>
</.table>
<.modal
:if={@live_action in [:new, :edit]}
id="user-modal"
show
on_cancel={JS.navigate(~p"/users")}
>
<.live_component
module={Dec19Web.UserLive.FormComponent}
id={@user.id || :new}
title={@page_title}
action={@live_action}
user={@user}
navigate={~p"/users"}
/>
</.modal>
・変数index_liveは公式ドキュメントのいうところのViewとよばれるもの。
・=~演算子はテキストベースの一致演算子。そのまま使用すると部分一致検索である点に注意。
defmodule Dec19Web.UserLiveTest do
use Dec19Web.ConnCase
import Phoenix.LiveViewTest
import Dec19.AccountsFixtures
@create_attrs %{name: "some name"}
@update_attrs %{name: "some updated name"}
@invalid_attrs %{name: nil}
defp create_user(_) do
user = user_fixture()
%{user: user}
end
describe "Index" do
setup [:create_user]
test "lists all users", %{conn: conn, user: user} do
+ {:ok, index_live, html} = live(conn, ~p"/users")
assert html =~ "Listing Users"
assert html =~ user.name
+ assert index_live |> has_element?("h1", "hogehoge")
end
test "saves new user", %{conn: conn} do
{:ok, index_live, _html} = live(conn, ~p"/users")
assert index_live |> element("a", "New User") |> render_click() =~
"New User"
assert_patch(index_live, ~p"/users/new")
assert index_live
|> form("#user-form", user: @invalid_attrs)
|> render_change() =~ "can't be blank"
{:ok, _, html} =
index_live
|> form("#user-form", user: @create_attrs)
|> render_submit()
|> follow_redirect(conn, ~p"/users")
assert html =~ "User created successfully"
assert html =~ "some name"
end
test "updates user in listing", %{conn: conn, user: user} do
{:ok, index_live, _html} = live(conn, ~p"/users")
assert index_live |> element("#users-#{user.id} a", "Edit") |> render_click() =~
"Edit User"
assert_patch(index_live, ~p"/users/#{user}/edit")
assert index_live
|> form("#user-form", user: @invalid_attrs)
|> render_change() =~ "can't be blank"
{:ok, _, html} =
index_live
|> form("#user-form", user: @update_attrs)
|> render_submit()
|> follow_redirect(conn, ~p"/users")
assert html =~ "User updated successfully"
assert html =~ "some updated name"
end
test "deletes user in listing", %{conn: conn, user: user} do
{:ok, index_live, _html} = live(conn, ~p"/users")
assert index_live |> element("#users-#{user.id} a", "Delete") |> render_click()
refute has_element?(index_live, "#user-#{user.id}")
end
end
describe "Show" do
setup [:create_user]
test "displays user", %{conn: conn, user: user} do
{:ok, _show_live, html} = live(conn, ~p"/users/#{user}")
assert html =~ "Show User"
assert html =~ user.name
end
test "updates user within modal", %{conn: conn, user: user} do
{:ok, show_live, _html} = live(conn, ~p"/users/#{user}")
assert show_live |> element("a", "Edit") |> render_click() =~
"Edit User"
assert_patch(show_live, ~p"/users/#{user}/show/edit")
assert show_live
|> form("#user-form", user: @invalid_attrs)
|> render_change() =~ "can't be blank"
{:ok, _, html} =
show_live
|> form("#user-form", user: @update_attrs)
|> render_submit()
|> follow_redirect(conn, ~p"/users/#{user}")
assert html =~ "User updated successfully"
assert html =~ "some updated name"
end
end
end
テストが1件増え、かつ成功していることが確認できました。
$ mix test.watch
Running tests...
..........
Finished in 0.3 seconds (0.1s async, 0.2s sync)
19 tests, 0 failures
(余談)mix test / mix test.watchを、特定のモジュール・テストだけ実行したい場合
特定のモジュールの場合→オプションに<テストモジュール(相対パス)>を渡す。
$ mix test.watch test/dec19_web/live/user_live_test.exs
Running tests...
......
Finished in 0.3 seconds (0.00s async, 0.3s sync)
6 tests, 0 failures
特定のテストの場合→オプションに <テストモジュール(相対パス)>:<開始行> を渡す。
$ mix test.watch mix test.watch test/dec19_web/live/user_live_test.exs:27
Running tests...
......
Finished in 0.3 seconds (0.00s async, 0.3s sync)
6 tests, 0 failures
(余談)IO.inspect()のデバッグ結果を全表示したいとき
IO.inspect()の引数に limit: :infinity, printable_limit: :infinity を渡す。
普通にIO.inspect()した場合
test "saves new user", %{conn: conn} do
{:ok, index_live, _html} = live(conn, ~p"/users")
assert index_live
|> element("a", "New User1")
|> render_click()
|> IO.inspect() =~
"新規作成"
結果行が途中で...と切れて最後まで見えない。
Running tests...
Excluding tags: [:test]
Including tags: [line: "27"]
"<header class=\"px-4 sm:px-6 lg:px-8\"><div class=\"flex items-center justify-between border-b border-zinc-100 py-3\"><div class=\"flex items-center gap-4\"><a href=\"/\"><svg viewbox=\"0 0 71 48\" class=\"h-6\" aria-hidden=\"true\"><path d=\"m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.043.03a2.96 2.96 0 0 0 .04-.029c-.038-.117-.107-.12-.197-.054l.122.107c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728.374.388.763.768 1.182 1.106 1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zm17.29-19.32c0-.023.001-.045.003-.068l-.006.006.006-.006-.036-.004.021.018.012.053Zm-20 14.744a7.61 7.61 0 0 0-.072-.041.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zm-.072-.041-.008-.034-.008.01.008-.01-.022-.006.005.026.024.014Z\" fill=\"#FD4F00\"></path></svg></a><p class=\"rounded-full bg-brand/5 px-2 text-[0.8125rem] font-medium leading-6 text-brand\">\n v1.7\n </p></div><div class=\"flex items-center gap-4\"><a href=\"https://twitter.com/elixirphoenix\" class=\"text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:text-zinc-700\">\n @elixirphoenix\n </a><a href=\"https://github.com/phoenixframework/phoenix\" class=\"text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:text-zinc-700\">\n GitHub\n </a><a href=\"https://hexdocs.pm/phoenix/overview.html\" class=\"rounded-lg bg-zinc-100 px-2 py-1 text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:bg-zinc-200/80 active:text-zinc-900/70\">\n Get Started <span aria-hidden=\"true\">→</s" <> ...
.
Finished in 0.3 seconds (0.00s async, 0.3s sync)
6 tests, 0 failures, 5 excluded
IO.inspect()の引数に limit: :infinity, printable_limit: :infinity を渡す場合
test "saves new user", %{conn: conn} do
{:ok, index_live, _html} = live(conn, ~p"/users")
assert index_live
|> element("a", "New User1")
|> render_click()
+ |> IO.inspect(limit: :infinity, printable_limit: :infinity) =~
"新規作成"
結果行が全部見える(代わりに出力が長くなる)
alice@okadanopc:~/code/practice/dec19$ mix test.watch test/dec19_web/live/user_live_test.exs:27
Running tests...
Excluding tags: [:test]
Including tags: [line: "27"]
"<header class=\"px-4 sm:px-6 lg:px-8\"><div class=\"flex items-center justify-between border-b border-zinc-100 py-3\"><div class=\"flex items-center gap-4\"><a href=\"/\"><svg viewbox=\"0 0 71 48\" class=\"h-6\" aria-hidden=\"true\"><path d=\"m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.043.03a2.96 2.96 0 0 0 .04-.029c-.038-.117-.107-.12-.197-.054l.122.107c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728.374.388.763.768 1.182 1.106 1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zm17.29-19.32c0-.023.001-.045.003-.068l-.006.006.006-.006-.036-.004.021.018.012.053Zm-20 14.744a7.61 7.61 0 0 0-.072-.041.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zm-.072-.041-.008-.034-.008.01.008-.01-.022-.006.005.026.024.014Z\" fill=\"#FD4F00\"></path></svg></a><p class=\"rounded-full bg-brand/5 px-2 text-[0.8125rem] font-medium leading-6 text-brand\">\n v1.7\n </p></div><div class=\"flex items-center gap-4\"><a href=\"https://twitter.com/elixirphoenix\" class=\"text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:text-zinc-700\">\n @elixirphoenix\n </a><a href=\"https://github.com/phoenixframework/phoenix\" class=\"text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:text-zinc-700\">\n GitHub\n </a><a href=\"https://hexdocs.pm/phoenix/overview.html\" class=\"rounded-lg bg-zinc-100 px-2 py-1 text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:bg-zinc-200/80 active:text-zinc-900/70\">\n Get Started <span aria-hidden=\"true\">→</span></a></div></div></header><main class=\"px-4 py-20 sm:px-6 lg:px-8\"><div class=\"mx-auto max-w-2xl\"><div id=\"disconnected\" phx-click=\"[["push",{"event":"lv:clear-flash","value":{"key":"error"}}],["hide",{"time":200,"to":"#flash","transition":[["transition-all","transform","ease-in","duration-200"],["opacity-100","translate-y-0","sm:scale-100"],["opacity-0","translate-y-4","sm:translate-y-0","sm:scale-95"]]}]]\" role=\"alert\" class=\"fixed hidden top-2 right-2 w-80 sm:w-96 z-50 rounded-lg p-3 shadow-md shadow-zinc-900/5 ring-1 bg-rose-50 p-3 text-rose-900 shadow-md ring-rose-500 fill-rose-900\" phx-connected=\"[["hide",{"time":200,"to":"#disconnected","transition":[["transition-all","transform","ease-in","duration-200"],["opacity-100","translate-y-0","sm:scale-100"],["opacity-0","translate-y-4","sm:translate-y-0","sm:scale-95"]]}]]\" phx-disconnected=\"[["show",{"display":null,"time":200,"to":"#disconnected","transition":[["transition-all","transform","ease-out","duration-300"],["opacity-0","translate-y-4","sm:translate-y-0","sm:scale-95"],["opacity-100","translate-y-0","sm:scale-100"]]}]]\"><p class=\"flex items-center gap-1.5 text-[0.8125rem] font-semibold leading-6\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" class=\"h-4 w-4\" fill=\"currentColor\" viewbox=\"0 0 20 20\"><path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z\" clip-rule=\"evenodd\"></path></svg>\n We can't find the internet\n </p><p class=\"mt-2 text-[0.8125rem] leading-5\">\n Attempting to reconnect <svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" class=\"ml-1 w-3 h-3 inline animate-spin\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" viewbox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99\"></path></svg></p></div><header class=\"flex items-center justify-between gap-6\"><div><h1 class=\"text-lg font-semibold leading-8 text-zinc-800\">\n \n Listing Users\n \n </h1></div><div class=\"flex-none\"><a href=\"/users/new\" data-phx-link=\"patch\" data-phx-link-state=\"push\"><button class=\"phx-submit-loading:opacity-75 rounded-lg bg-zinc-900 hover:bg-zinc-700 py-2 px-3 text-sm font-semibold leading-6 text-white active:text-white/80 \">\n New User1\n</button></a></div></header><h1>hogehoge</h1><div id=\"users\" class=\"overflow-y-auto px-4 sm:overflow-visible sm:px-0\"><table class=\"mt-11 w-[40rem] sm:w-full\"><thead class=\"text-left text-[0.8125rem] leading-6 text-zinc-500\"><tr><th class=\"p-0 pb-4 pr-6 font-normal\">Name</th><th class=\"relative p-0 pb-4\"><span class=\"sr-only\">Actions</span></th></tr></thead><tbody class=\"relative divide-y divide-zinc-100 border-t border-zinc-200 text-sm leading-6 text-zinc-700\"><tr id=\"users-1\" class=\"relative group hover:bg-zinc-50\"><td phx-click=\"[["navigate",{"href":"/users/1","replace":false}]]\" class=\"p-0 hover:cursor-pointer\"><div><span class=\"absolute h-full w-4 top-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl\"></span><span class=\"absolute h-full w-4 top-0 -right-4 group-hover:bg-zinc-50 sm:rounded-r-xl\"></span></div><div class=\"block py-4 pr-6\"><span class=\"relative font-semibold text-zinc-900\">\n some name\n </span></div></td><td class=\"p-0 w-14\"><div class=\"relative whitespace-nowrap py-4 text-right text-sm font-medium\"><span class=\"relative ml-4 font-semibold leading-6 text-zinc-900 hover:text-zinc-700\"><div class=\"sr-only\"><a href=\"/users/1\" data-phx-link=\"redirect\" data-phx-link-state=\"push\">Show</a></div><a href=\"/users/1/edit\" data-phx-link=\"patch\" data-phx-link-state=\"push\">Edit</a></span><span class=\"relative ml-4 font-semibold leading-6 text-zinc-900 hover:text-zinc-700\"><a href=\"#\" data-confirm=\"Are you sure?\" phx-click=\"[["push",{"event":"delete","value":{"id":1}}]]\">\n Delete\n </a></span></div></td></tr></tbody></table></div><div id=\"user-modal\" phx-mounted=\"[["show",{"display":null,"time":200,"to":"#user-modal","transition":[[],[],[]]}],["show",{"display":null,"time":200,"to":"#user-modal-bg","transition":[["transition-all","transform","ease-out","duration-300"],["opacity-0"],["opacity-100"]]}],["show",{"display":null,"time":200,"to":"#user-modal-container","transition":[["transition-all","transform","ease-out","duration-300"],["opacity-0","translate-y-4","sm:translate-y-0","sm:scale-95"],["opacity-100","translate-y-0","sm:scale-100"]]}],["add_class",{"names":["overflow-hidden"],"time":200,"to":"body","transition":[[],[],[]]}],["focus_first",{"to":"#user-modal-content"}]]\" phx-remove=\"[["hide",{"time":200,"to":"#user-modal-bg","transition":[["transition-all","transform","ease-in","duration-200"],["opacity-100"],["opacity-0"]]}],["hide",{"time":200,"to":"#user-modal-container","transition":[["transition-all","transform","ease-in","duration-200"],["opacity-100","translate-y-0","sm:scale-100"],["opacity-0","translate-y-4","sm:translate-y-0","sm:scale-95"]]}],["hide",{"time":200,"to":"#user-modal","transition":[["block"],["block"],["hidden"]]}],["remove_class",{"names":["overflow-hidden"],"time":200,"to":"body","transition":[[],[],[]]}],["pop_focus",{}]]\" class=\"relative z-50 hidden\"><div id=\"user-modal-bg\" class=\"fixed inset-0 bg-zinc-50/90 transition-opacity\" aria-hidden=\"true\"></div><div class=\"fixed inset-0 overflow-y-auto\" aria-labelledby=\"user-modal-title\" aria-describedby=\"user-modal-description\" role=\"dialog\" aria-modal=\"true\" tabindex=\"0\"><div class=\"flex min-h-full items-center justify-center\"><div class=\"w-full max-w-3xl p-4 sm:p-6 lg:py-8\"><div id=\"user-modal-container\" phx-hook=\"Phoenix.FocusWrap\" class=\"hidden relative rounded-2xl bg-white p-14 shadow-lg shadow-zinc-700/10 ring-1 ring-zinc-700/10 transition\" phx-click-away=\"[["navigate",{"href":"/users","replace":false}],["hide",{"time":200,"to":"#user-modal-bg","transition":[["transition-all","transform","ease-in","duration-200"],["opacity-100"],["opacity-0"]]}],["hide",{"time":200,"to":"#user-modal-container","transition":[["transition-all","transform","ease-in","duration-200"],["opacity-100","translate-y-0","sm:scale-100"],["opacity-0","translate-y-4","sm:translate-y-0","sm:scale-95"]]}],["hide",{"time":200,"to":"#user-modal","transition":[["block"],["block"],["hidden"]]}],["remove_class",{"names":["overflow-hidden"],"time":200,"to":"body","transition":[[],[],[]]}],["pop_focus",{}]]\" phx-key=\"escape\" phx-mounted=\"[["show",{"display":null,"time":200,"to":"#user-modal","transition":[[],[],[]]}],["show",{"display":null,"time":200,"to":"#user-modal-bg","transition":[["transition-all","transform","ease-out","duration-300"],["opacity-0"],["opacity-100"]]}],["show",{"display":null,"time":200,"to":"#user-modal-container","transition":[["transition-all","transform","ease-out","duration-300"],["opacity-0","translate-y-4","sm:translate-y-0","sm:scale-95"],["opacity-100","translate-y-0","sm:scale-100"]]}],["add_class",{"names":["overflow-hidden"],"time":200,"to":"body","transition":[[],[],[]]}],["focus_first",{"to":"#user-modal-content"}]]\" phx-window-keydown=\"[["navigate",{"href":"/users","replace":false}],["hide",{"time":200,"to":"#user-modal-bg","transition":[["transition-all","transform","ease-in","duration-200"],["opacity-100"],["opacity-0"]]}],["hide",{"time":200,"to":"#user-modal-container","transition":[["transition-all","transform","ease-in","duration-200"],["opacity-100","translate-y-0","sm:scale-100"],["opacity-0","translate-y-4","sm:translate-y-0","sm:scale-95"]]}],["hide",{"time":200,"to":"#user-modal","transition":[["block"],["block"],["hidden"]]}],["remove_class",{"names":["overflow-hidden"],"time":200,"to":"body","transition":[[],[],[]]}],["pop_focus",{}]]\"><span id=\"user-modal-container-start\" tabindex=\"0\" aria-hidden=\"true\"></span><div class=\"absolute top-6 right-5\"><button phx-click=\"[["navigate",{"href":"/users","replace":false}],["hide",{"time":200,"to":"#user-modal-bg","transition":[["transition-all","transform","ease-in","duration-200"],["opacity-100"],["opacity-0"]]}],["hide",{"time":200,"to":"#user-modal-container","transition":[["transition-all","transform","ease-in","duration-200"],["opacity-100","translate-y-0","sm:scale-100"],["opacity-0","translate-y-4","sm:translate-y-0","sm:scale-95"]]}],["hide",{"time":200,"to":"#user-modal","transition":[["block"],["block"],["hidden"]]}],["remove_class",{"names":["overflow-hidden"],"time":200,"to":"body","transition":[[],[],[]]}],["pop_focus",{}]]\" type=\"button\" class=\"-m-3 flex-none p-3 opacity-20 hover:opacity-40\" aria-label=\"close\"><svg xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" class=\"h-5 w-5 stroke-current\" fill=\"currentColor\" viewbox=\"0 0 24 24\"><path fill-rule=\"evenodd\" d=\"M5.47 5.47a.75.75 0 011.06 0L12 10.94l5.47-5.47a.75.75 0 111.06 1.06L13.06 12l5.47 5.47a.75.75 0 11-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 01-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 010-1.06z\" clip-rule=\"evenodd\"></path></svg></button></div><div id=\"user-modal-content\"><div data-phx-component=\"1\"><header class=\"\"><div><h1 class=\"text-lg font-semibold leading-8 text-zinc-800\">\n \n New User\n 新規作成\n \n </h1><p class=\"mt-2 text-sm leading-6 text-zinc-600\">\n Use this form to manage user records in your database.\n </p></div><div class=\"flex-none\"></div></header><form method=\"post\" errors=\"\" id=\"user-form\" phx-change=\"validate\" phx-submit=\"save\" phx-target=\"1\"><div class=\"space-y-8 bg-white mt-10\"><div phx-feedback-for=\"user[name]\"><label for=\"user-form_name\" class=\"block text-sm font-semibold leading-6 text-zinc-800\">\n name\n</label><input type=\"text\" name=\"user[name]\" id=\"user-form_name\" class=\"border-zinc-300 focus:border-zinc-400 focus:ring-zinc-800/5 mt-2 block w-full rounded-lg border-zinc-300 py-[7px] px-[11px] text-zinc-900 focus:outline-none focus:ring-4 sm:text-sm sm:leading-6 phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 phx-no-feedback:focus:ring-zinc-800/5\"/></div><div class=\"mt-2 flex items-center justify-between gap-6\"><button class=\"phx-submit-loading:opacity-75 rounded-lg bg-zinc-900 hover:bg-zinc-700 py-2 px-3 text-sm font-semibold leading-6 text-white active:text-white/80 \" phx-disable-with=\"Saving...\">\n Save User\n</button></div></div></form></div></div><span id=\"user-modal-container-end\" tabindex=\"0\" aria-hidden=\"true\"></span></div></div></div></div></div></div></main>"
.
Finished in 0.2 seconds (0.00s async, 0.2s sync)
6 tests, 0 failures, 5 excluded
~Elixirの国のご案内~
※Elixirって何ぞや?と思ったらこちらもどぞ。未来がぎゅっと詰まった、Elixirは今年で生まれて10周年です
We Are The Alchemists, my friends!1
Elixirコミュニティは本当に優しくて温かい人たちばかり!
私が挫折せずにいられるのもこの恵まれた環境のおかげです。
まずは気軽に話しかけてみてください。2
-
@torifukukaiouさんのAwesomeな名言をお借りしました。Elixirコミュニティを一言で表すと、これに尽きます。 ↩