The Lazy Programmer's Intro to LiveView: Chapter 7
Tags: elixir, phoenix, liveview
Published: 2023-06-18 15:07:42

## Conceding points to the winner

Sometimes you will lose and that's okay. Just don't forget to give your opponent 3 points for their victory. In this chapter, we will explore how one user could give 3 points to another if they lose to them. In case you haven't already, create a second account on your app.

## Dynamic interactions with LiveView

So far we haven't created any sort of user interaction besides boring links from one page to another. Let's make our user LiveView more powerful. What we need to do is create an 'I lost to this person' button that when pressed gives them 3 points. Let's start with the render function. Update your `lib/champions_web/live/user_live/show.ex` render function to:

```diff
@impl true
def render(assigns) do
  ~H"""
  <.header>
    User <%= @user.id %>
    <:subtitle>This is a player on this app.</:subtitle>
  </.header>

  <.list>
    <:item title="Email"><%= @user.email %></:item>
    <:item title="Points"><%= @user.points %></:item>
  </.list>

+ <div class="my-4">
+   <.button type="button" phx-click="concede_loss">I lost to this person</.button>
+ </div>

  <.back navigate={~p"/users"}>Back to users</.back>
  """
end
```

We just added a Phoenix `<.button>` component and it contains an attribute called `phx-click`. Clicking this button will trigger an event on our LiveView but there's a catch: we are not ready for this event yet. Press that button right now and your terminal will show the following error:

```
[error] GenServer #PID<0.3550.0> terminating
** (UndefinedFunctionError) function ChampionsWeb.UserLive.Show.handle_event/3 is undefined or private
    ChampionsWeb.UserLive.Show.handle_event("concede_loss", %{"value" => ""}, #Phoenix.LiveView.Socket<id: "phx-F2mNT8Nhl-Qz2gxH", endpoint: ChampionsWeb.Endpoint, view: ChampionsWeb.UserLive.Show, parent_pid: nil, root_pid: #PID<0.3550.0>, router: ChampionsWeb.Router, assigns: %{__changed__: %{}, current_user: #Champions.Accounts.User<__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 1, email: "lubien@example.com", confirmed_at: ~N[2023-06-17 13:55:41], points: 0, inserted_at: ~N[2023-06-17 13:53:56], updated_at: ~N[2023-06-17 13:55:41], ...>, flash: %{}, live_action: :show, page_title: "Showing user enemy@example.com", user: #Champions.Accounts.User<__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 2, email: "enemy@example.com", confirmed_at: nil, points: 0, inserted_at: ~N[2023-06-18 10:28:49], updated_at: ~N[2023-06-18 10:28:49], ...>}, transport_pid: #PID<0.3544.0>, ...>)
    (phoenix_live_view 0.19.2) lib/phoenix_live_view/channel.ex:401: anonymous fn/3 in Phoenix.LiveView.Channel.view_handle_event/3
    (telemetry 1.2.1) /Users/lubien/workspace/champions/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
    (phoenix_live_view 0.19.2) lib/phoenix_live_view/channel.ex:221: Phoenix.LiveView.Channel.handle_info/2
    (stdlib 3.17.2) gen_server.erl:695: :gen_server.try_dispatch/4
    (stdlib 3.17.2) gen_server.erl:771: :gen_server.handle_msg/6
    (stdlib 3.17.2) proc_lib.erl:236: :proc_lib.wake_up/3
Last message: %Phoenix.Socket.Message{topic: "lv:phx-F2mNT8Nhl-Qz2gxH", event: "event", payload: %{"event" => "concede_loss", "type" => "click", "value" => %{"value" => ""}}, ref: "12", join_ref: "4"}
State: %{components: {%{}, %{}, 1}, join_ref: "4", serializer: Phoenix.Socket.V2.JSONSerializer, socket: #Phoenix.LiveView.Socket<id: "phx-F2mNT8Nhl-Qz2gxH", endpoint: ChampionsWeb.Endpoint, view: ChampionsWeb.UserLive.Show, parent_pid: nil, root_pid: #PID<0.3550.0>, router: ChampionsWeb.Router, assigns: %{__changed__: %{}, current_user: #Champions.Accounts.User<__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 1, email: "lubien@example.com", confirmed_at: ~N[2023-06-17 13:55:41], points: 0, inserted_at: ~N[2023-06-17 13:53:56], updated_at: ~N[2023-06-17 13:55:41], ...>, flash: %{}, live_action: :show, page_title: "Showing user enemy@example.com", user: #Champions.Accounts.User<__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 2, email: "enemy@example.com", confirmed_at: nil, points: 0, inserted_at: ~N[2023-06-18 10:28:49], updated_at: ~N[2023-06-18 10:28:49], ...>}, transport_pid: #PID<0.3544.0>, ...>, topic: "lv:phx-F2mNT8Nhl-Qz2gxH", upload_names: %{}, upload_pids: %{}}
```

That's a lot to unpack but bear with me. I like Phoenix errors because they tell you a lot once you understand how to read them. I'd like to teach you that right now so let's break down it bit by bit:

## Learning how to interpret a LiveView event error

First of all, the first line tells you something very important: `[error] GenServer #PID<0.3550.0> terminating`. That means this error was bad, our LiveView page crashed and had to be restarted. That's not a great experience so you definitely want to fix this.

On line 2 you can already see a good hint at the issue: `UndefinedFunctionError`. That name says all: LiveView expected a certain function to be defined but it wasn't. Keep reading that line and we can see that `function ChampionsWeb.UserLive.Show.handle_event/3 is undefined or private` tells us that we forgot to create a `handle_event` function that takes 3 arguments or we mistakenly created it as a private function using `defp`. Well you didn't forget it since you're learning yet, I just wanted you to experience this cool error message though.

But say you've never heard of `handle_event/3` , what do I need to do with that function? Line 3 gives you exactly what Phoenix tried passing to that function so you can pretty much get started with that: `ChampionsWeb.UserLive.Show.handle_event("concede_loss", %{"value" => ""}, #Phoenix.LiveView.Socket<id: "phx-F2mNT8Nhl-Qz2gxH"...>)`. Do note that `Phoenix.LiveView.Socket` contains a lot of information so it can be threatening to read the whole piece but in time you will learn to ignore it and focus on the other arguments, I even cut it for you already in this paragraph.

## Creating our `handle_event/3`

[handle_event/3](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveComponent.html#c:handle_event/3) is a new LiveView callback you should learn about. It is triggered when one of your LiveView events from the render function is called such as `phx-click`. In this case, we created a `phx-click="concede_loss"` so our event name will be `concede_loss`. The second argument with contains values, we will ignore that for now, and last but not least there's the socket as the third argument. Did you notice that all callbacks have the socket as the last argument? That's because all callbacks are just functions that manage what is inside the socket so it makes sense for it to be available.

```elixir
@impl true
def handle_event("concede_loss", _value, socket) do
  {:noreply, socket}
end
```

The code above is the minimal event handler needed to receive our custom event and do nothing. `handle_event/3` must return either `{:noreply, socket}` or `{:reply, map, socket}`. For now, we will focus on the `:noreply` case. Now that we have an event handler we need to modify it to get the user being shown on the page and add 3 points to them. Fortunately `socket`  contains a property called `assigns` that has all assigns. Since this page has a `user` assigns, `socket.assigns.user` is readily available to us.

```elixir
@impl true
def handle_event("concede_loss", _value, socket) do
  user = socket.assigns.user
  {:ok, updated_user} = Accounts.update_user_points(user, user.points + 3)
  {:noreply, assign(socket, :user, updated_user)}
end
```

That's it! All we had to do was get the `user` assign and run that function we created back in the early chapters of this project. Don't forget to reassign the user so the LiveView updates immediately without a page refresh needed to show the new points.

## Conceding a draw

I've also told you when this series started that draws would give both players 1 point. You can probably guess 90% of what needs to be done by copying and pasting what you just learned so let's start with that. We will create a `concede_draw` event that's a carbon copy of `concede_loss`.

```diff
  @impl true
  def handle_event("concede_loss", _value, socket) do
    user = socket.assigns.user
    {:ok, updated_user} = Accounts.update_user_points(user, user.points + 3)
    {:noreply, assign(socket, :user, updated_user)}
  end
+
+ def handle_event("concede_draw", _value, socket) do
+   user = socket.assigns.user
+   {:ok, updated_user} = Accounts.update_user_points(user, user.points + 1)
+   {:noreply, assign(socket, :user, updated_user)}
+ end

  @impl true
  def render(assigns) do
    ~H"""
    <.header>
      User <%= @user.id %>
      <:subtitle>This is a player on this app.</:subtitle>
    </.header>

    <.list>
      <:item title="Email"><%= @user.email %></:item>
      <:item title="Points"><%= @user.points %></:item>
    </.list>

    <div class="my-4">
      <.button type="button" phx-click="concede_loss">I lost to this person</.button>
+     <.button type="button" phx-click="concede_draw">Declare draw match</.button>
    </div>

    <.back navigate={~p"/users"}>Back to users</.back>
    """
  end
```

Do note we didn't add `@impl true` on the second `handle_event/3` clause because Elixir will already infer that from the first one. How can I know which user am I on my LiveView so I can add 1 point to myself? Back when we generated our auth code Phoenix added something called LiveSession, which we will explore later, but the important bit is that it adds an assign called `current_user` on your socket, so you can use it on callbacks and on your render functions, we even used it to show your points on the navbar.

```diff
def handle_event("concede_draw", _value, socket) do
+ my_user = socket.assigns.current_user
  user = socket.assigns.user
  {:ok, updated_user} = Accounts.update_user_points(user, user.points + 1)
+ {:ok, updated_my_user} = Accounts.update_user_points(my_user, my_user.points + 1)
- {:noreply, assign(socket, :user, updated_user)}
+ {:noreply,
+   socket
+   |> assign(:user, updated_user)
+   |> assign(:current_user, updated_my_user)
+ }
end
```

Amazing, we already have two main features of our app up and running. But if you pay attention to your navbar you will notice that your points aren't increasing but if you refresh the page your new points will show up. What is happening with our layout?

## Difference between `root.html.heex` and `app.html.heex` layout

The `root.html.heex` layout is [meant to be rendered only once](https://hexdocs.pm/phoenix_live_view/live-layouts.html) and never update again with one single exception: updating page titles. That means that in order for us to see live updates to our navbar we must move our navbar logic to `app.html.heex` which is LiveView-aware. That's quite simple actually. Remove this entire `ul` from your `root.html.heex`:

```diff
<!DOCTYPE html>
<html lang="en" class="[scrollbar-gutter:stable]">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="csrf-token" content={get_csrf_token()} />
    <.live_title suffix=" · Phoenix Framework">
      <%= assigns[:page_title] || "Champions" %>
    </.live_title>
    <link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
    <script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
    </script>
  </head>
  <body class="bg-white antialiased">
-   <ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
-     <%= if @current_user do %>
-       <li class="text-[0.8125rem] leading-6 text-zinc-900">
-         <%= @current_user.email %>
-         (<%= @current_user.points %> points)
-       </li>
-       <li>
-         <.link
-           href={~p"/users/settings"}
-           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
-         >
-           Settings
-         </.link>
-       </li>
-       <li>
-         <.link
-           href={~p"/users/log_out"}
-           method="delete"
-           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
-         >
-           Log out
-         </.link>
-       </li>
-     <% else %>
-       <li>
-         <.link
-           href={~p"/users/register"}
-           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
-         >
-           Register
-         </.link>
-       </li>
-       <li>
-         <.link
-           href={~p"/users/log_in"}
-           class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
-         >
-           Log in
-         </.link>
-       </li>
-     <% end %>
-   </ul>
    <%= @inner_content %>
  </body>
</html>
```

Then add it to the top of your `app.html.heex`:

```diff
+<ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
+<%= if @current_user do %>
+  <li class="text-[0.8125rem] leading-6 text-zinc-900">
+    <%= @current_user.email %>
+    (<%= @current_user.points %> points)
+  </li>
+  <li>
+    <.link
+      href={~p"/users/settings"}
+      class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+    >
+      Settings
+    </.link>
+  </li>
+  <li>
+    <.link
+      href={~p"/users/log_out"}
+      method="delete"
+      class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+    >
+      Log out
+    </.link>
+  </li>
+<% else %>
+  <li>
+    <.link
+      href={~p"/users/register"}
+      class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+    >
+      Register
+    </.link>
+  </li>
+  <li>
+    <.link
+      href={~p"/users/log_in"}
+      class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
+    >
+      Log in
+    </.link>
+  </li>
+<% end %>
+</ul>
<header class="px-4 sm:px-6 lg:px-8">
... rest of the things here
```

Now your points should be updated each time you click on the draw button! But here's a catch: we just broke our tests! If you run `mix test` you will spot two errors. They're both related to the fact that the login and register tests send you to the root path `/` and expect your navbar to be there but the homepage explicitly says it doesn't want to use `app.html.heex` on `lib/champions_web/controllers/page_controller.ex` by `render(conn, :home, layout: false)`.

That's okay and since we won't be caring for that page in the future the solution is quite simple: let's change those tests to send users to the `/users` page. Locate and change this on `test/champions_web/controllers/user_session_controller_test.exs`

```diff
# Now do a logged in request and assert on the menu
-conn = get(conn, ~p"/")
+conn = get(conn, ~p"/users")
response = html_response(conn, 200)
assert response =~ user.email
assert response =~ ~p"/users/settings"
assert response =~ ~p"/users/log_out"
```

Do the same thing on `test/champions_web/live/user_registration_live_test.exs`:

```diff
# Now do a logged in request and assert on the menu
-conn = get(conn, "/")
+conn = get(conn, "/users")
response = html_response(conn, 200)
assert response =~ email
assert response =~ "Settings"
assert response =~ "Log out"
```

Your `mix test` should be running just fine now!

## Preventing unauthorized users from using these buttons

Right now if you open an anonymous tab and hit those buttons you will have pleasant surprises. Hitting the concede loss button will work just fine even though I'm not a player. Hitting the concede draw button will crash our LiveView because there's no `current_user` assign. Let's fix that.

```elixir
@impl true
def handle_event("concede_loss", _value, socket) do
  socket =
    if socket.assigns.current_user do
      user = socket.assigns.user
      {:ok, updated_user} = Accounts.update_user_points(user, user.points + 3)
      assign(socket, :user, updated_user)
    else
      put_flash(socket, :error, "You must be a player to do that!")
    end

  {:noreply, socket}
end

def handle_event("concede_draw", _value, socket) do
  socket =
    if my_user = socket.assigns.current_user do
      user = socket.assigns.user
      {:ok, updated_user} = Accounts.update_user_points(user, user.points + 1)
      {:ok, updated_my_user} = Accounts.update_user_points(my_user, my_user.points + 1)
      socket
      |> assign(:user, updated_user)
      |> assign(:current_user, updated_my_user)
    else
      put_flash(socket, :error, "You must be a player to do that!")
    end

  {:noreply, socket}
end
```

This time a simple `if` expression could solve our problems. Even if our user shouldn't be able to do some events we still need to return `{:noreply, socket}` but in case they're unauthorized we will use [put_flash/3](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#put_flash/3) to show a pretty error message. Now let's hide these buttons from those who shouldn't see them:

```diff
@impl true
def render(assigns) do
  ~H"""
  <.header>
    User <%= @user.id %>
    <:subtitle>This is a player on this app.</:subtitle>
  </.header>

  <.list>
    <:item title="Email"><%= @user.email %></:item>
    <:item title="Points"><%= @user.points %></:item>
  </.list>

- <div class="my-4">
+ <div :if={@current_user} class="my-4">
    <.button type="button" phx-click="concede_loss">I lost to this person</.button>
    <.button type="button" phx-click="concede_draw">Declare draw match</.button>
  </div>

  <.back navigate={~p"/users"}>Back to users</.back>
  """
end
```

HEEx nodes can use `:if={condition}` to show elements, pretty handy. At this point, you could be asking yourself 'Why bother validating in the events if we are not going to show these buttons?' And that's a really good question. The thing about LiveView events is that there's no real magic on the HTML side, a `<.button phx-click="concede_loss">` is just a regular HTML button like `<button phx-click="concede_loss>`. If your users are clever to figure that out they could 👻 haxx your system! Don't believe me? Paste the HTML below on an unauthenticated tab on `/users/:id` using your browser dev tools:

```
<button type="button" phx-click="concede_loss">
  Hacking to the Gate 🔥
</button>
```

## How to prevent a user from giving points to himself infinitely

Right now you can also go to your own page and concede losses/draws to yourself and you get all the points. That's not right so let's fix this!

```elixir
@impl true
def handle_event("concede_loss", _value, socket) do
  socket =
    case {socket.assigns.current_user, socket.assigns.user} do
      {nil, _user} ->
        put_flash(socket, :error, "You must be a player to do that!")

      {%{id: my_id}, %{id: other_id}} when my_id == other_id ->
        put_flash(socket, :error, "You can't give points to yourself")

      {_my_user, user} ->
        {:ok, updated_user} = Accounts.update_user_points(user, user.points + 3)
        assign(socket, :user, updated_user)
    end

  {:noreply, socket}
end

def handle_event("concede_draw", _value, socket) do
  socket =
    case {socket.assigns.current_user, socket.assigns.user} do
      {nil, _user} ->
        put_flash(socket, :error, "You must be a player to do that!")

      {%{id: my_id}, %{id: other_id}} when my_id == other_id ->
        put_flash(socket, :error, "You can't give points to yourself")

      {my_user, user} ->
        {:ok, updated_user} = Accounts.update_user_points(user, user.points + 1)
        {:ok, updated_my_user} = Accounts.update_user_points(my_user, my_user.points + 1)
        socket
        |> assign(:user, updated_user)
        |> assign(:current_user, updated_my_user)
    end

  {:noreply, socket}
end
```

This time we changed our `if` to a pattern match to compare `{my_user, user}` so we can prevent both `my_user` being `nil` and `my_user` and `user` being the same person. As for the HEEx change:

```diff
-<div :if={@current_user} class="my-4">
+<div :if={@current_user && @current_user.id != @user.id} class="my-4">
```

That should cover all cases.

## Don't forget the tests

How do we test a LiveView with a button click? How do we test whether being authenticated or not and whether I'm the same user on that page or not? In all tests that required a user so far we did this:

```elixir
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 =~ "This is a player on this app"
    assert html =~ user.email
  end
end
```

The `setup [:create_user]` will create a user for us and pass it under our context at line 4 so we can immediately go to its page. `:create_user` does not authenticates us. If we change that to `setup [:register_and_log_in_user]` it will authenticate us and our context user will be ourselves so we can use this to go to our own page. Let's add a test for that. Go to `test/champions_web/live/user_live_test.exs` and add this suite:

```elixir
describe "Authenticated Show" do
  setup [:register_and_log_in_user]

  test "displays my own user but no action buttons", %{conn: conn, user: user} do
    {:ok, _show_live, html} = live(conn, ~p"/users/#{user}")

    assert html =~ "This is a player on this app"
    assert html =~ user.email
    refute html =~ "I lost to this person"
    refute html =~ "Declare draw match"
  end
end
```

Now we should also be able to go to another user's page and see those buttons. Inside that `describe` block add this test:

```
test "displays another user with action buttons", %{conn: conn, user: _user} do
  other_user = user_fixture()
  {:ok, _show_live, html} = live(conn, ~p"/users/#{other_user}")

  assert html =~ "This is a player on this app"
  assert html =~ other_user.email
  assert html =~ "I lost to this person"
  assert html =~ "Declare draw match"
end
```

Now we should also verify that unauthenticated users can't see those buttons. Luckly we already have a `describe` for that! Here's how the full suite for this page ended up as:

```diff
describe "Authenticated Show" do
  setup [:register_and_log_in_user]

  test "displays my own user but no action buttons", %{conn: conn, user: user} do
    {:ok, _show_live, html} = live(conn, ~p"/users/#{user}")

    assert html =~ "This is a player on this app"
    assert html =~ user.email
    refute html =~ "I lost to this person"
    refute html =~ "Declare draw match"
  end

  test "displays another user with action buttons", %{conn: conn, user: _user} do
    other_user = user_fixture()
    {:ok, _show_live, html} = live(conn, ~p"/users/#{other_user}")

    assert html =~ "This is a player on this app"
    assert html =~ other_user.email
    assert html =~ "I lost to this person"
    assert html =~ "Declare draw match"
  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 =~ "This is a player on this app"
    assert html =~ user.email
+   refute html =~ "I lost to this person"
+   refute html =~ "Declare draw match"
  end
end
```

But we forgot to test our actions! Before we do that we are going to do two small HTML tweaks that will make our testing live easier. We are going to add tags to help us find the number of points users have inside our HTML. Inside `show.ex` change this line:

```diff
-<:item title="Points"><%= @user.points %></:item>
+<:item title="Points"><span data-points><%= @user.points %></span></:item>
```

And inside `app.html.heex` change this line:

```
<li class="text-[0.8125rem] leading-6 text-zinc-900">
  <%= @current_user.email %>
- (<%= @current_user.points %> points)
+ <span data-my-points>(<%= @current_user.points %> points)</span>
</li>
```

First, add an alias to `Champions.Accounts` on the top of our `user_live_test.exs` file. We are going to be using it to check the state of things in the database.

```diff
defmodule ChampionsWeb.UserLiveTest do
  use ChampionsWeb.ConnCase

+ alias Champions.Accounts
  import Phoenix.LiveViewTest
  import Champions.AccountsFixtures
...
```

From now on we will add tests to "Authenticated Show". Here's how you can trigger a click action from LiveView:

```elixir
test "concede 3 points when I lose to another player", %{conn: conn, user: _user} do
  other_user = user_fixture()
  {:ok, show_live, _html} = live(conn, ~p"/users/#{other_user}")

  assert other_user.points == 0
  assert show_live |> element("button", "I lost to this person") |> render_click()
  assert element(show_live, "span[data-points]") |> render() =~ "3"
  assert Accounts.get_user!(other_user.id).points == 3
end
```

This time we use new helpers from [LiveViewTest](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html). First, we need to locate the button so we use [element/3](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#element/3) to locate a `<button>` with a certain text. After that, we use [render_click/2](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#render_click/2) to trigger our event. After that, we use `element/3` again on our LiveView to find the `span[data-points]` we just added to see if the value 3 appears there, this ensures our LiveView is updating the UI. The last test line gets the latest state of the other user and asserts it should be 3. Time to add the draw match test:

```elixir
test "concede 1 point to each user when there's a draw match", %{conn: conn, user: user} do
  other_user = user_fixture()
  {:ok, show_live, _html} = live(conn, ~p"/users/#{other_user}")

  assert user.points == 0
  assert other_user.points == 0
  assert show_live |> element("button", "Declare draw match") |> render_click()
  assert element(show_live, "span[data-my-points]") |> render() =~ "1"
  assert element(show_live, "span[data-points]") |> render() =~ "1"
  assert Accounts.get_user!(user.id).points == 1
  assert Accounts.get_user!(other_user.id).points == 1
end
```

Very similar to the previous test but we add checks for the logged-in user too. At this point, we should be very confident our tests cover the important bits of our user interactions. Now let's spend a bit of our time on optimization.

## Refactoring because we can

Now that we have unit tests that ensure our system work we have room for some refactoring without having the trouble of manual testing every step of the way. Whenever we change something just run `mix test` and you should be good to go. I've saved some cool things just for now.

The first thing we can notice is that both `handle_event/3` have the same check for users being logged in and users being different. We can use pattern matching to make that simpler! Let's start with one:

```diff
@impl true
+def handle_event(_event_name, _value, %{assigns: %{current_user: nil}} = socket) do
+ {:noreply, put_flash(socket, :error, "You must be a player to do that!")}
+end
+
def handle_event("concede_loss", _value, socket) do
  socket =
    case {socket.assigns.current_user, socket.assigns.user} do
-     {nil, _user} ->
-       put_flash(socket, :error, "You must be a player to do that!")
-
      {%{id: my_id}, %{id: other_id}} when my_id == other_id ->
        put_flash(socket, :error, "You can't give points to yourself")

      {_my_user, user} ->
        {:ok, updated_user} = Accounts.update_user_points(user, user.points + 3)
        assign(socket, :user, updated_user)
    end

  {:noreply, socket}
end

def handle_event("concede_draw", _value, socket) do
  socket =
    case {socket.assigns.current_user, socket.assigns.user} do
-     {nil, _user} ->
-       put_flash(socket, :error, "You must be a player to do that!")
-
      {%{id: my_id}, %{id: other_id}} when my_id == other_id ->
        put_flash(socket, :error, "You can't give points to yourself")

      {my_user, user} ->
        {:ok, updated_user} = Accounts.update_user_points(user, user.points + 1)
        {:ok, updated_my_user} = Accounts.update_user_points(my_user, my_user.points + 1)
        socket
        |> assign(:user, updated_user)
        |> assign(:current_user, updated_my_user)
    end

  {:noreply, socket}
end
```

We added a clause for any `handle_event/3` on the top that ignores the `_event_name` and matches if `current_user` is `nil`. If it is, stop everything and `put_flash` that error message. With that simple addition, we already traded 4 duplicated lines with 3 lines that should be easier to read. It's not really about the line count, it's all about being able to easily see that this first clause has an important meaning for all the events below. Let's do the same for the other error condition. Here's the final product:

```elixir
@impl true
def handle_event(_event_name, _value, %{assigns: %{current_user: nil}} = socket) do
  {:noreply, put_flash(socket, :error, "You must be a player to do that!")}
end

def handle_event(_event_name, _value, %{assigns: %{current_user: current_user, user: user}} = socket)
 when current_user.id == user.id do
 {:noreply, put_flash(socket, :error, "You can't give points to yourself")}
end

def handle_event("concede_loss", _value, %{assigns: %{user: user}} = socket) do
  {:ok, updated_user} = Accounts.update_user_points(user, user.points + 3)
  {:noreply, assign(socket, :user, updated_user)}
end

def handle_event("concede_draw", _value, %{assigns: %{current_user: current_user, user: user}} = socket) do
  {:ok, updated_user} = Accounts.update_user_points(user, user.points + 1)
  {:ok, updated_my_user} = Accounts.update_user_points(current_user, current_user.points + 1)
  {:noreply,
    socket
    |> assign(:user, updated_user)
    |> assign(:current_user, updated_my_user)
  }
end
```

## Summary

- `phx-click` works for sending events from HTML to your LiveView.
- LiveView events are caught by `handle_event/3` which takes 3 arguments: event name, value, and the socket.
- Being able to read LiveView errors will help you creating your applications.
- You can access assigns from `socket.assigns` including `current_user` to get data about yourself if you're logged in.
- `root.html.heex` never updates based on assigns except for `page_title`. `app.html.heex` will be updated if they use assigns.
- Dead Views and LiveViews can opt out of using `app.html.heex` if they need.
- To verify that your user is logged in, check for `socket.assigns.current_user`.
- You can add `:if={condition}` to HTML tags to make HEEx render or not them.
- You should not trust hiding buttons from unauthorized users directly on HEEx, you should validate your events callbacks.
- You can use `put_flash/3` to send messages to your user.
- LiveView comes with useful helper functions for tests ranging from getting elements on the screen to clicking buttons.
- You can create `handle_event/3` clauses to handle errors for all events below them b simply ignoring the event name.
- You can keep your mind at easy refactoring your code if you have a good battery of tests. `mix test` can be your best friend.