Browse lessons

Testing Upload Previews

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"
                >
                  &times;
                </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