When dealing with LiveView uploads, there are three separate things we can test:
- Uploading images, displaying previews, cancelling an upload, and showing errors (client-side)
- Submitting the upload and storing the images locally (direct-to-server)
- Storing the images in an external location (direct-to-cloud)
We’ll cover the first one in this lesson. We’ll handle the rest in the next lessons.
AlbumLive
If you look at your router, you’ll see we have a couple of AlbumLive LiveViews:
live_session :album do
live "/albums", AlbumLive.Index
live "/albums/:id", AlbumLive.Show
end
When we visit /albums we can see:
- A form where we can upload photos
- Image previews after selecting files
- Cancel uploads
- Upload validations (like too many uploads)
- Form validations (name cannot be blank)
In this lesson, we’ll test the client-side interactions.
AlbumLive.Index
defmodule RangerWeb.AlbumLive.Index do
use RangerWeb, :live_view
import Phoenix.Naming, only: [humanize: 1]
alias Ranger.{Album, Repo}
def render(assigns) do
~H"""
<div class="max-w-lg mx-auto space-y-6">
<section phx-drop-target={@uploads.photos.ref}>
<div class="space-y-2 border border-4 border-blue-100 p-8 rounded-md">
<.form
:let={f}
for={@changeset}
id="upload-form"
phx-change="validate"
phx-submit="save"
class="flex flex-col space-y-6"
>
<.input field={{f, :name}} label="Name" />
<.live_file_input upload={@uploads.photos} />
<button type="submit">Upload</button>
</.form>
</div>
</section>
<%= if !Enum.empty?(@uploads.photos.entries) do %>
<section>
<h2>Photo Previews</h2>
<%= for err <- upload_errors(@uploads.photos) do %>
<p><%= humanize(err) %></p>
<% end %>
<%= for entry <- @uploads.photos.entries do %>
<figure>
<div class="text-right">
<button
type="button"
data-role="cancel-upload"
phx-click="cancel-upload"
phx-value-ref={entry.ref}
aria-label="cancel"
>
×
</button>
</div>
<.live_img_preview data-role="image-preview" entry={entry} class="rounded-md" />
</figure>
<% end %>
</section>
<% end %>
</div>
"""
end
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(:changeset, Album.changeset(%{}))
|> allow_upload(:photos, accept: ~w(.jpg .jpeg .png), max_entries: 2)}
end
def handle_event("validate", %{"album" => params}, socket) do
changeset =
params
|> Album.changeset()
|> Map.put(:action, :validate)
{:noreply, assign(socket, :changeset, changeset)}
end
def handle_event("cancel-upload", %{"ref" => ref}, socket) do
{:noreply, cancel_upload(socket, :photos, ref)}
end
end
Testing AlbumLive
Open up album_live/index_test.exs.
Testing upload previews
defmodule RangerWeb.AlbumLive.IndexDoneTest do
use RangerWeb.ConnCase, async: true
import Phoenix.LiveViewTest
test "user can see preview of picture to be uploaded", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/albums")
view
|> file_input("#upload-form", :photos, [
%{
name: "moria-door.png",
content: File.read!("test/support/images/moria-door.png"),
type: "image/png"
}
])
|> render_upload("moria-door.png")
assert has_element?(view, "[data-role='image-preview']")
end
end
Testing cancelling uploads
test "user can cancel upload", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/albums")
view
|> upload("moria-door.png")
|> cancel_upload()
refute has_element?(view, "[data-role='image-preview']")
end
defp cancel_upload(view) do
view
|> element("[data-role='cancel-upload']")
|> render_click()
end
defp upload(view, filename) do
view
|> file_input("#upload-form", :photos, [
%{
name: filename,
content: File.read!("test/support/images/#{filename}"),
type: "image/png"
}
])
|> render_upload(filename)
view
end
Testing upload errors
test "user sees error when uploading too many files", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/albums")
view
|> upload("moria-door.png")
|> upload("moria-door.png")
|> upload("moria-door.png")
assert render(view) =~ "Too many files"
end
Is phx-change covered?
If you comment out handle_event("validate", ...), previews can break in the browser while tests still pass.
Two approaches:
1) Add a test that triggers normal form validation:
test "user sees error when not including album name", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/albums")
view
|> form("#upload-form", %{album: %{name: nil}})
|> render_change()
assert has_element?(view, "p", "can't be blank")
end
2) Trigger render_change inside your upload helper after render_upload/2.
Resources
file_input/4: https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#file_input/4render_upload/3: https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#render_upload/3live_file_input/2: https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html#live_file_input/2live_img_preview/2: https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Helpers.html#live_img_preview/2cancel_upload/3: https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#cancel_upload/3