Fix new cell/section focus (#66)

* Add pid to data operations and use that in post-operation hooks

* Fix tests
This commit is contained in:
Jonatan Kłosko 2021-03-02 08:50:31 +01:00 committed by GitHub
parent 228c279cea
commit 90a7b599df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 367 additions and 334 deletions

View file

@ -104,7 +104,7 @@ defmodule LiveBook.Session do
""" """
@spec insert_section(id(), non_neg_integer()) :: :ok @spec insert_section(id(), non_neg_integer()) :: :ok
def insert_section(session_id, index) do def insert_section(session_id, index) do
GenServer.cast(name(session_id), {:insert_section, index}) GenServer.cast(name(session_id), {:insert_section, self(), index})
end end
@doc """ @doc """
@ -113,7 +113,7 @@ defmodule LiveBook.Session do
@spec insert_cell(id(), Section.id(), non_neg_integer(), Cell.type()) :: @spec insert_cell(id(), Section.id(), non_neg_integer(), Cell.type()) ::
:ok :ok
def insert_cell(session_id, section_id, index, type) do def insert_cell(session_id, section_id, index, type) do
GenServer.cast(name(session_id), {:insert_cell, section_id, index, type}) GenServer.cast(name(session_id), {:insert_cell, self(), section_id, index, type})
end end
@doc """ @doc """
@ -121,7 +121,7 @@ defmodule LiveBook.Session do
""" """
@spec delete_section(id(), Section.id()) :: :ok @spec delete_section(id(), Section.id()) :: :ok
def delete_section(session_id, section_id) do def delete_section(session_id, section_id) do
GenServer.cast(name(session_id), {:delete_section, section_id}) GenServer.cast(name(session_id), {:delete_section, self(), section_id})
end end
@doc """ @doc """
@ -129,7 +129,7 @@ defmodule LiveBook.Session do
""" """
@spec delete_cell(id(), Cell.id()) :: :ok @spec delete_cell(id(), Cell.id()) :: :ok
def delete_cell(session_id, cell_id) do def delete_cell(session_id, cell_id) do
GenServer.cast(name(session_id), {:delete_cell, cell_id}) GenServer.cast(name(session_id), {:delete_cell, self(), cell_id})
end end
@doc """ @doc """
@ -137,7 +137,7 @@ defmodule LiveBook.Session do
""" """
@spec move_cell(id(), Cell.id(), integer()) :: :ok @spec move_cell(id(), Cell.id(), integer()) :: :ok
def move_cell(session_id, cell_id, offset) do def move_cell(session_id, cell_id, offset) do
GenServer.cast(name(session_id), {:move_cell, cell_id, offset}) GenServer.cast(name(session_id), {:move_cell, self(), cell_id, offset})
end end
@doc """ @doc """
@ -145,7 +145,7 @@ defmodule LiveBook.Session do
""" """
@spec queue_cell_evaluation(id(), Cell.id()) :: :ok @spec queue_cell_evaluation(id(), Cell.id()) :: :ok
def queue_cell_evaluation(session_id, cell_id) do def queue_cell_evaluation(session_id, cell_id) do
GenServer.cast(name(session_id), {:queue_cell_evaluation, cell_id}) GenServer.cast(name(session_id), {:queue_cell_evaluation, self(), cell_id})
end end
@doc """ @doc """
@ -153,7 +153,7 @@ defmodule LiveBook.Session do
""" """
@spec cancel_cell_evaluation(id(), Cell.id()) :: :ok @spec cancel_cell_evaluation(id(), Cell.id()) :: :ok
def cancel_cell_evaluation(session_id, cell_id) do def cancel_cell_evaluation(session_id, cell_id) do
GenServer.cast(name(session_id), {:cancel_cell_evaluation, cell_id}) GenServer.cast(name(session_id), {:cancel_cell_evaluation, self(), cell_id})
end end
@doc """ @doc """
@ -161,7 +161,7 @@ defmodule LiveBook.Session do
""" """
@spec set_notebook_name(id(), String.t()) :: :ok @spec set_notebook_name(id(), String.t()) :: :ok
def set_notebook_name(session_id, name) do def set_notebook_name(session_id, name) do
GenServer.cast(name(session_id), {:set_notebook_name, name}) GenServer.cast(name(session_id), {:set_notebook_name, self(), name})
end end
@doc """ @doc """
@ -169,15 +169,15 @@ defmodule LiveBook.Session do
""" """
@spec set_section_name(id(), Section.id(), String.t()) :: :ok @spec set_section_name(id(), Section.id(), String.t()) :: :ok
def set_section_name(session_id, section_id, name) do def set_section_name(session_id, section_id, name) do
GenServer.cast(name(session_id), {:set_section_name, section_id, name}) GenServer.cast(name(session_id), {:set_section_name, self(), section_id, name})
end end
@doc """ @doc """
Asynchronously sends a cell delta to apply to the server. Asynchronously sends a cell delta to apply to the server.
""" """
@spec apply_cell_delta(id(), pid(), Cell.id(), Delta.t(), Data.cell_revision()) :: :ok @spec apply_cell_delta(id(), Cell.id(), Delta.t(), Data.cell_revision()) :: :ok
def apply_cell_delta(session_id, client_pid, cell_id, delta, revision) do def apply_cell_delta(session_id, cell_id, delta, revision) do
GenServer.cast(name(session_id), {:apply_cell_delta, client_pid, cell_id, delta, revision}) GenServer.cast(name(session_id), {:apply_cell_delta, self(), cell_id, delta, revision})
end end
@doc """ @doc """
@ -185,9 +185,9 @@ defmodule LiveBook.Session do
This helps to remove old deltas that are no longer necessary. This helps to remove old deltas that are no longer necessary.
""" """
@spec report_cell_revision(id(), pid(), Cell.id(), Data.cell_revision()) :: :ok @spec report_cell_revision(id(), Cell.id(), Data.cell_revision()) :: :ok
def report_cell_revision(session_id, client_pid, cell_id, revision) do def report_cell_revision(session_id, cell_id, revision) do
GenServer.cast(name(session_id), {:report_cell_revision, client_pid, cell_id, revision}) GenServer.cast(name(session_id), {:report_cell_revision, self(), cell_id, revision})
end end
@doc """ @doc """
@ -198,7 +198,7 @@ defmodule LiveBook.Session do
""" """
@spec connect_runtime(id(), Runtime.t()) :: :ok @spec connect_runtime(id(), Runtime.t()) :: :ok
def connect_runtime(session_id, runtime) do def connect_runtime(session_id, runtime) do
GenServer.cast(name(session_id), {:connect_runtime, runtime}) GenServer.cast(name(session_id), {:connect_runtime, self(), runtime})
end end
@doc """ @doc """
@ -208,7 +208,7 @@ defmodule LiveBook.Session do
""" """
@spec disconnect_runtime(id()) :: :ok @spec disconnect_runtime(id()) :: :ok
def disconnect_runtime(session_id) do def disconnect_runtime(session_id) do
GenServer.cast(name(session_id), :disconnect_runtime) GenServer.cast(name(session_id), {:disconnect_runtime, self()})
end end
@doc """ @doc """
@ -216,7 +216,7 @@ defmodule LiveBook.Session do
""" """
@spec set_path(id(), String.t() | nil) :: :ok @spec set_path(id(), String.t() | nil) :: :ok
def set_path(session_id, path) do def set_path(session_id, path) do
GenServer.cast(name(session_id), {:set_path, path}) GenServer.cast(name(session_id), {:set_path, self(), path})
end end
@doc """ @doc """
@ -301,37 +301,37 @@ defmodule LiveBook.Session do
end end
@impl true @impl true
def handle_cast({:insert_section, index}, state) do def handle_cast({:insert_section, client_pid, index}, state) do
# Include new id in the operation, so it's reproducible # Include new id in the operation, so it's reproducible
operation = {:insert_section, index, Utils.random_id()} operation = {:insert_section, client_pid, index, Utils.random_id()}
{:noreply, handle_operation(state, operation)} {:noreply, handle_operation(state, operation)}
end end
def handle_cast({:insert_cell, section_id, index, type}, state) do def handle_cast({:insert_cell, client_pid, section_id, index, type}, state) do
# Include new id in the operation, so it's reproducible # Include new id in the operation, so it's reproducible
operation = {:insert_cell, section_id, index, type, Utils.random_id()} operation = {:insert_cell, client_pid, section_id, index, type, Utils.random_id()}
{:noreply, handle_operation(state, operation)} {:noreply, handle_operation(state, operation)}
end end
def handle_cast({:delete_section, section_id}, state) do def handle_cast({:delete_section, client_pid, section_id}, state) do
operation = {:delete_section, section_id} operation = {:delete_section, client_pid, section_id}
{:noreply, handle_operation(state, operation)} {:noreply, handle_operation(state, operation)}
end end
def handle_cast({:delete_cell, cell_id}, state) do def handle_cast({:delete_cell, client_pid, cell_id}, state) do
operation = {:delete_cell, cell_id} operation = {:delete_cell, client_pid, cell_id}
{:noreply, handle_operation(state, operation)} {:noreply, handle_operation(state, operation)}
end end
def handle_cast({:move_cell, cell_id, offset}, state) do def handle_cast({:move_cell, client_pid, cell_id, offset}, state) do
operation = {:move_cell, cell_id, offset} operation = {:move_cell, client_pid, cell_id, offset}
{:noreply, handle_operation(state, operation)} {:noreply, handle_operation(state, operation)}
end end
def handle_cast({:queue_cell_evaluation, cell_id}, state) do def handle_cast({:queue_cell_evaluation, client_pid, cell_id}, state) do
case ensure_runtime(state) do case ensure_runtime(state) do
{:ok, state} -> {:ok, state} ->
operation = {:queue_cell_evaluation, cell_id} operation = {:queue_cell_evaluation, client_pid, cell_id}
{:noreply, handle_operation(state, operation)} {:noreply, handle_operation(state, operation)}
{:error, error} -> {:error, error} ->
@ -340,18 +340,18 @@ defmodule LiveBook.Session do
end end
end end
def handle_cast({:cancel_cell_evaluation, cell_id}, state) do def handle_cast({:cancel_cell_evaluation, client_pid, cell_id}, state) do
operation = {:cancel_cell_evaluation, cell_id} operation = {:cancel_cell_evaluation, client_pid, cell_id}
{:noreply, handle_operation(state, operation)} {:noreply, handle_operation(state, operation)}
end end
def handle_cast({:set_notebook_name, name}, state) do def handle_cast({:set_notebook_name, client_pid, name}, state) do
operation = {:set_notebook_name, name} operation = {:set_notebook_name, client_pid, name}
{:noreply, handle_operation(state, operation)} {:noreply, handle_operation(state, operation)}
end end
def handle_cast({:set_section_name, section_id, name}, state) do def handle_cast({:set_section_name, client_pid, section_id, name}, state) do
operation = {:set_section_name, section_id, name} operation = {:set_section_name, client_pid, section_id, name}
{:noreply, handle_operation(state, operation)} {:noreply, handle_operation(state, operation)}
end end
@ -365,7 +365,7 @@ defmodule LiveBook.Session do
{:noreply, handle_operation(state, operation)} {:noreply, handle_operation(state, operation)}
end end
def handle_cast({:connect_runtime, runtime}, state) do def handle_cast({:connect_runtime, client_pid, runtime}, state) do
if state.data.runtime do if state.data.runtime do
Runtime.disconnect(state.data.runtime) Runtime.disconnect(state.data.runtime)
end end
@ -374,18 +374,18 @@ defmodule LiveBook.Session do
{:noreply, {:noreply,
%{state | runtime_monitor_ref: runtime_monitor_ref} %{state | runtime_monitor_ref: runtime_monitor_ref}
|> handle_operation({:set_runtime, runtime})} |> handle_operation({:set_runtime, client_pid, runtime})}
end end
def handle_cast(:disconnect_runtime, state) do def handle_cast({:disconnect_runtime, client_pid}, state) do
Runtime.disconnect(state.data.runtime) Runtime.disconnect(state.data.runtime)
{:noreply, {:noreply,
%{state | runtime_monitor_ref: nil} %{state | runtime_monitor_ref: nil}
|> handle_operation({:set_runtime, nil})} |> handle_operation({:set_runtime, client_pid, nil})}
end end
def handle_cast({:set_path, path}, state) do def handle_cast({:set_path, client_pid, path}, state) do
if path do if path do
FileGuard.lock(path, self()) FileGuard.lock(path, self())
else else
@ -397,7 +397,7 @@ defmodule LiveBook.Session do
FileGuard.unlock(state.data.path) FileGuard.unlock(state.data.path)
end end
{:noreply, handle_operation(state, {:set_path, path})} {:noreply, handle_operation(state, {:set_path, client_pid, path})}
{:error, :already_in_use} -> {:error, :already_in_use} ->
broadcast_error(state.session_id, "failed to set new path because it is already in use") broadcast_error(state.session_id, "failed to set new path because it is already in use")
@ -422,7 +422,7 @@ defmodule LiveBook.Session do
{:noreply, {:noreply,
%{state | runtime_monitor_ref: nil} %{state | runtime_monitor_ref: nil}
|> handle_operation({:set_runtime, nil})} |> handle_operation({:set_runtime, self(), nil})}
end end
def handle_info({:DOWN, _, :process, pid, _}, state) do def handle_info({:DOWN, _, :process, pid, _}, state) do
@ -437,12 +437,12 @@ defmodule LiveBook.Session do
end end
def handle_info({:evaluation_stdout, cell_id, string}, state) do def handle_info({:evaluation_stdout, cell_id, string}, state) do
operation = {:add_cell_evaluation_stdout, cell_id, string} operation = {:add_cell_evaluation_stdout, self(), cell_id, string}
{:noreply, handle_operation(state, operation)} {:noreply, handle_operation(state, operation)}
end end
def handle_info({:evaluation_response, cell_id, response}, state) do def handle_info({:evaluation_response, cell_id, response}, state) do
operation = {:add_cell_evaluation_response, cell_id, response} operation = {:add_cell_evaluation_response, self(), cell_id, response}
{:noreply, handle_operation(state, operation)} {:noreply, handle_operation(state, operation)}
end end
@ -546,7 +546,7 @@ defmodule LiveBook.Session do
{:ok, {:ok,
%{state | runtime_monitor_ref: runtime_monitor_ref} %{state | runtime_monitor_ref: runtime_monitor_ref}
|> handle_operation({:set_runtime, runtime})} |> handle_operation({:set_runtime, self(), runtime})}
end end
end end
@ -558,7 +558,7 @@ defmodule LiveBook.Session do
case File.write(state.data.path, content) do case File.write(state.data.path, content) do
:ok -> :ok ->
handle_operation(state, :mark_as_not_dirty) handle_operation(state, {:mark_as_not_dirty, self()})
{:error, reason} -> {:error, reason} ->
broadcast_error(state.session_id, "failed to save notebook - #{reason}") broadcast_error(state.session_id, "failed to save notebook - #{reason}")

View file

@ -62,25 +62,33 @@ defmodule LiveBook.Session.Data do
@type index :: non_neg_integer() @type index :: non_neg_integer()
# Note that all operations carry the pid of whatever
# process originated the operation. Some operations
# like :apply_cell_delta and :report_cell_revision
# require the pid to be a registered client, as in these
# cases it's necessary for the operation to be properly applied.
# For other operations the pid can represent an arbitrary process
# and is passed for informative purposes only.
@type operation :: @type operation ::
{:insert_section, index(), Section.id()} {:insert_section, pid(), index(), Section.id()}
| {:insert_cell, Section.id(), index(), Cell.type(), Cell.id()} | {:insert_cell, pid(), Section.id(), index(), Cell.type(), Cell.id()}
| {:delete_section, Section.id()} | {:delete_section, pid(), Section.id()}
| {:delete_cell, Cell.id()} | {:delete_cell, pid(), Cell.id()}
| {:move_cell, Cell.id(), offset :: integer()} | {:move_cell, pid(), Cell.id(), offset :: integer()}
| {:queue_cell_evaluation, Cell.id()} | {:queue_cell_evaluation, pid(), Cell.id()}
| {:add_cell_evaluation_stdout, Cell.id(), String.t()} | {:add_cell_evaluation_stdout, pid(), Cell.id(), String.t()}
| {:add_cell_evaluation_response, Cell.id(), Evaluator.evaluation_response()} | {:add_cell_evaluation_response, pid(), Cell.id(), Evaluator.evaluation_response()}
| {:cancel_cell_evaluation, Cell.id()} | {:cancel_cell_evaluation, pid(), Cell.id()}
| {:set_notebook_name, String.t()} | {:set_notebook_name, pid(), String.t()}
| {:set_section_name, Section.id(), String.t()} | {:set_section_name, pid(), Section.id(), String.t()}
| {:client_join, pid()} | {:client_join, pid()}
| {:client_leave, pid()} | {:client_leave, pid()}
| {:apply_cell_delta, pid(), Cell.id(), Delta.t(), cell_revision()} | {:apply_cell_delta, pid(), Cell.id(), Delta.t(), cell_revision()}
| {:report_cell_revision, pid(), Cell.id(), cell_revision()} | {:report_cell_revision, pid(), Cell.id(), cell_revision()}
| {:set_runtime, Runtime.t() | nil} | {:set_runtime, pid(), Runtime.t() | nil}
| {:set_path, String.t() | nil} | {:set_path, pid(), String.t() | nil}
| :mark_as_not_dirty | {:mark_as_not_dirty, pid()}
@type action :: @type action ::
{:start_evaluation, Cell.t(), Section.t()} {:start_evaluation, Cell.t(), Section.t()}
@ -145,7 +153,7 @@ defmodule LiveBook.Session.Data do
@spec apply_operation(t(), operation()) :: {:ok, t(), list(action())} | :error @spec apply_operation(t(), operation()) :: {:ok, t(), list(action())} | :error
def apply_operation(data, operation) def apply_operation(data, operation)
def apply_operation(data, {:insert_section, index, id}) do def apply_operation(data, {:insert_section, _client_pid, index, id}) do
section = %{Section.new() | id: id} section = %{Section.new() | id: id}
data data
@ -155,7 +163,7 @@ defmodule LiveBook.Session.Data do
|> wrap_ok() |> wrap_ok()
end end
def apply_operation(data, {:insert_cell, section_id, index, type, id}) do def apply_operation(data, {:insert_cell, _client_pid, section_id, index, type, id}) do
with {:ok, _section} <- Notebook.fetch_section(data.notebook, section_id) do with {:ok, _section} <- Notebook.fetch_section(data.notebook, section_id) do
cell = %{Cell.new(type) | id: id} cell = %{Cell.new(type) | id: id}
@ -167,7 +175,7 @@ defmodule LiveBook.Session.Data do
end end
end end
def apply_operation(data, {:delete_section, id}) do def apply_operation(data, {:delete_section, _client_pid, id}) do
with {:ok, section} <- Notebook.fetch_section(data.notebook, id) do with {:ok, section} <- Notebook.fetch_section(data.notebook, id) do
data data
|> with_actions() |> with_actions()
@ -177,7 +185,7 @@ defmodule LiveBook.Session.Data do
end end
end end
def apply_operation(data, {:delete_cell, id}) do def apply_operation(data, {:delete_cell, _client_pid, id}) do
with {:ok, cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id) do with {:ok, cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id) do
case data.cell_infos[cell.id].evaluation_status do case data.cell_infos[cell.id].evaluation_status do
:evaluating -> :evaluating ->
@ -205,7 +213,7 @@ defmodule LiveBook.Session.Data do
end end
end end
def apply_operation(data, {:move_cell, id, offset}) do def apply_operation(data, {:move_cell, _client_pid, id, offset}) do
with {:ok, cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id), with {:ok, cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id),
true <- offset != 0 do true <- offset != 0 do
data data
@ -218,7 +226,7 @@ defmodule LiveBook.Session.Data do
end end
end end
def apply_operation(data, {:queue_cell_evaluation, id}) do def apply_operation(data, {:queue_cell_evaluation, _client_pid, id}) do
with {:ok, cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id), with {:ok, cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id),
:elixir <- cell.type, :elixir <- cell.type,
:ready <- data.cell_infos[cell.id].evaluation_status do :ready <- data.cell_infos[cell.id].evaluation_status do
@ -233,7 +241,7 @@ defmodule LiveBook.Session.Data do
end end
end end
def apply_operation(data, {:add_cell_evaluation_stdout, id, string}) do def apply_operation(data, {:add_cell_evaluation_stdout, _client_pid, id, string}) do
with {:ok, cell, _} <- Notebook.fetch_cell_and_section(data.notebook, id), with {:ok, cell, _} <- Notebook.fetch_cell_and_section(data.notebook, id),
:evaluating <- data.cell_infos[cell.id].evaluation_status do :evaluating <- data.cell_infos[cell.id].evaluation_status do
data data
@ -245,7 +253,7 @@ defmodule LiveBook.Session.Data do
end end
end end
def apply_operation(data, {:add_cell_evaluation_response, id, response}) do def apply_operation(data, {:add_cell_evaluation_response, _client_pid, id, response}) do
with {:ok, cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id), with {:ok, cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id),
:evaluating <- data.cell_infos[cell.id].evaluation_status do :evaluating <- data.cell_infos[cell.id].evaluation_status do
data data
@ -260,7 +268,7 @@ defmodule LiveBook.Session.Data do
end end
end end
def apply_operation(data, {:cancel_cell_evaluation, id}) do def apply_operation(data, {:cancel_cell_evaluation, _client_pid, id}) do
with {:ok, cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id) do with {:ok, cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id) do
case data.cell_infos[cell.id].evaluation_status do case data.cell_infos[cell.id].evaluation_status do
:evaluating -> :evaluating ->
@ -284,7 +292,7 @@ defmodule LiveBook.Session.Data do
end end
end end
def apply_operation(data, {:set_notebook_name, name}) do def apply_operation(data, {:set_notebook_name, _client_pid, name}) do
data data
|> with_actions() |> with_actions()
|> set_notebook_name(name) |> set_notebook_name(name)
@ -292,7 +300,7 @@ defmodule LiveBook.Session.Data do
|> wrap_ok() |> wrap_ok()
end end
def apply_operation(data, {:set_section_name, section_id, name}) do def apply_operation(data, {:set_section_name, _client_pid, section_id, name}) do
with {:ok, section} <- Notebook.fetch_section(data.notebook, section_id) do with {:ok, section} <- Notebook.fetch_section(data.notebook, section_id) do
data data
|> with_actions() |> with_actions()
@ -353,7 +361,7 @@ defmodule LiveBook.Session.Data do
end end
end end
def apply_operation(data, {:set_runtime, runtime}) do def apply_operation(data, {:set_runtime, _client_pid, runtime}) do
data data
|> with_actions() |> with_actions()
|> set!(runtime: runtime) |> set!(runtime: runtime)
@ -361,7 +369,7 @@ defmodule LiveBook.Session.Data do
|> wrap_ok() |> wrap_ok()
end end
def apply_operation(data, {:set_path, path}) do def apply_operation(data, {:set_path, _client_pid, path}) do
data data
|> with_actions() |> with_actions()
|> set!(path: path) |> set!(path: path)
@ -369,7 +377,7 @@ defmodule LiveBook.Session.Data do
|> wrap_ok() |> wrap_ok()
end end
def apply_operation(data, :mark_as_not_dirty) do def apply_operation(data, {:mark_as_not_dirty, _client_pid}) do
data data
|> with_actions() |> with_actions()
|> set_dirty(false) |> set_dirty(false)

View file

@ -261,7 +261,7 @@ defmodule LiveBookWeb.SessionLive do
socket socket
) do ) do
delta = Delta.from_compressed(delta) delta = Delta.from_compressed(delta)
Session.apply_cell_delta(socket.assigns.session_id, self(), cell_id, delta, revision) Session.apply_cell_delta(socket.assigns.session_id, cell_id, delta, revision)
{:noreply, socket} {:noreply, socket}
end end
@ -271,7 +271,7 @@ defmodule LiveBookWeb.SessionLive do
%{"cell_id" => cell_id, "revision" => revision}, %{"cell_id" => cell_id, "revision" => revision},
socket socket
) do ) do
Session.report_cell_revision(socket.assigns.session_id, self(), cell_id, revision) Session.report_cell_revision(socket.assigns.session_id, cell_id, revision)
{:noreply, socket} {:noreply, socket}
end end
@ -421,20 +421,34 @@ defmodule LiveBookWeb.SessionLive do
def handle_info(_message, socket), do: {:noreply, socket} def handle_info(_message, socket), do: {:noreply, socket}
defp after_operation(socket, _prev_socket, {:insert_section, _index, section_id}) do defp after_operation(socket, _prev_socket, {:insert_section, client_pid, _index, section_id}) do
if client_pid == self() do
assign(socket, selected_section_id: section_id) assign(socket, selected_section_id: section_id)
else
socket
end
end end
defp after_operation(socket, _prev_socket, {:delete_section, _section_id}) do defp after_operation(socket, _prev_socket, {:delete_section, _client_pid, section_id}) do
if section_id == socket.assigns.selected_section_id do
assign(socket, selected_section_id: nil) assign(socket, selected_section_id: nil)
else
socket
end
end end
defp after_operation(socket, _prev_socket, {:insert_cell, _, _, _, cell_id}) do defp after_operation(socket, _prev_socket, {:insert_cell, client_pid, _, _, _, cell_id}) do
{:ok, cell, _section} = Notebook.fetch_cell_and_section(socket.assigns.data.notebook, cell_id) if client_pid == self() do
{:ok, cell, _section} =
Notebook.fetch_cell_and_section(socket.assigns.data.notebook, cell_id)
focus_cell(socket, cell, insert_mode: true) focus_cell(socket, cell, insert_mode: true)
else
socket
end
end end
defp after_operation(socket, prev_socket, {:delete_cell, cell_id}) do defp after_operation(socket, prev_socket, {:delete_cell, _client_pid, cell_id}) do
if cell_id == socket.assigns.focused_cell_id do if cell_id == socket.assigns.focused_cell_id do
case Notebook.fetch_cell_sibling(prev_socket.assigns.data.notebook, cell_id, 1) do case Notebook.fetch_cell_sibling(prev_socket.assigns.data.notebook, cell_id, 1) do
{:ok, next_cell} -> {:ok, next_cell} ->
@ -460,8 +474,8 @@ defmodule LiveBookWeb.SessionLive do
Enum.reduce(actions, socket, &handle_action(&2, &1)) Enum.reduce(actions, socket, &handle_action(&2, &1))
end end
defp handle_action(socket, {:broadcast_delta, from, cell, delta}) do defp handle_action(socket, {:broadcast_delta, client_pid, cell, delta}) do
if from == self() do if client_pid == self() do
push_event(socket, "cell_acknowledgement:#{cell.id}", %{}) push_event(socket, "cell_acknowledgement:#{cell.id}", %{})
else else
push_event(socket, "cell_delta:#{cell.id}", %{delta: Delta.to_compressed(delta)}) push_event(socket, "cell_delta:#{cell.id}", %{delta: Delta.to_compressed(delta)})

View file

@ -26,10 +26,10 @@ defmodule LiveBook.Session.DataTest do
end end
describe "apply_operation/2 given :insert_section" do describe "apply_operation/2 given :insert_section" do
test "adds new section to notebook and session info" do test "adds new section to notebook and section info" do
data = Data.new() data = Data.new()
operation = {:insert_section, 0, "s1"} operation = {:insert_section, self(), 0, "s1"}
assert {:ok, assert {:ok,
%{ %{
@ -44,17 +44,17 @@ defmodule LiveBook.Session.DataTest do
describe "apply_operation/2 given :insert_cell" do describe "apply_operation/2 given :insert_cell" do
test "returns an error given invalid section id" do test "returns an error given invalid section id" do
data = Data.new() data = Data.new()
operation = {:insert_cell, "nonexistent", 0, :elixir, "c1"} operation = {:insert_cell, self(), "nonexistent", 0, :elixir, "c1"}
assert :error = Data.apply_operation(data, operation) assert :error = Data.apply_operation(data, operation)
end end
test "insert_cell adds new cell to notebook and cell info" do test "insert_cell adds new cell to notebook and cell info" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"} {:insert_section, self(), 0, "s1"}
]) ])
operation = {:insert_cell, "s1", 0, :elixir, "c1"} operation = {:insert_cell, self(), "s1", 0, :elixir, "c1"}
assert {:ok, assert {:ok,
%{ %{
@ -73,10 +73,10 @@ defmodule LiveBook.Session.DataTest do
data = data =
data_after_operations!([ data_after_operations!([
{:client_join, client_pid}, {:client_join, client_pid},
{:insert_section, 0, "s1"} {:insert_section, self(), 0, "s1"}
]) ])
operation = {:insert_cell, "s1", 0, :elixir, "c1"} operation = {:insert_cell, self(), "s1", 0, :elixir, "c1"}
assert {:ok, assert {:ok,
%{ %{
@ -88,17 +88,17 @@ defmodule LiveBook.Session.DataTest do
describe "apply_operation/2 given :delete_section" do describe "apply_operation/2 given :delete_section" do
test "returns an error given invalid section id" do test "returns an error given invalid section id" do
data = Data.new() data = Data.new()
operation = {:delete_section, "nonexistent"} operation = {:delete_section, self(), "nonexistent"}
assert :error = Data.apply_operation(data, operation) assert :error = Data.apply_operation(data, operation)
end end
test "removes the section from notebook and session info, adds to deleted sections" do test "removes the section from notebook and section info, adds to deleted sections" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"} {:insert_section, self(), 0, "s1"}
]) ])
operation = {:delete_section, "s1"} operation = {:delete_section, self(), "s1"}
empty_map = %{} empty_map = %{}
assert {:ok, assert {:ok,
@ -115,21 +115,21 @@ defmodule LiveBook.Session.DataTest do
describe "apply_operation/2 given :delete_cell" do describe "apply_operation/2 given :delete_cell" do
test "returns an error given invalid cell id" do test "returns an error given invalid cell id" do
data = Data.new() data = Data.new()
operation = {:delete_cell, "nonexistent"} operation = {:delete_cell, self(), "nonexistent"}
assert :error = Data.apply_operation(data, operation) assert :error = Data.apply_operation(data, operation)
end end
test "if the cell is evaluating, cencels section evaluation" do test "if the cell is evaluating, cencels section evaluation" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, "c2"} {:queue_cell_evaluation, self(), "c2"}
]) ])
operation = {:delete_cell, "c1"} operation = {:delete_cell, self(), "c1"}
assert {:ok, assert {:ok,
%{ %{
@ -138,14 +138,14 @@ defmodule LiveBook.Session.DataTest do
}, _actions} = Data.apply_operation(data, operation) }, _actions} = Data.apply_operation(data, operation)
end end
test "removes the cell from notebook and session info, adds to deleted cells" do test "removes the cell from notebook and section info, adds to deleted cells" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"} {:insert_cell, self(), "s1", 0, :elixir, "c1"}
]) ])
operation = {:delete_cell, "c1"} operation = {:delete_cell, self(), "c1"}
empty_map = %{} empty_map = %{}
assert {:ok, assert {:ok,
@ -161,14 +161,14 @@ defmodule LiveBook.Session.DataTest do
test "unqueues the cell if it's queued for evaluation" do test "unqueues the cell if it's queued for evaluation" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, "c2"} {:queue_cell_evaluation, self(), "c2"}
]) ])
operation = {:delete_cell, "c2"} operation = {:delete_cell, self(), "c2"}
assert {:ok, assert {:ok,
%{ %{
@ -179,17 +179,17 @@ defmodule LiveBook.Session.DataTest do
test "marks evaluated child cells as stale" do test "marks evaluated child cells as stale" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
# Evaluate both cells # Evaluate both cells
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, "c1", {:ok, [1, 2, 3]}}, {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}},
{:queue_cell_evaluation, "c2"}, {:queue_cell_evaluation, self(), "c2"},
{:add_cell_evaluation_response, "c2", {:ok, [1, 2, 3]}} {:add_cell_evaluation_response, self(), "c2", {:ok, [1, 2, 3]}}
]) ])
operation = {:delete_cell, "c1"} operation = {:delete_cell, self(), "c1"}
assert {:ok, assert {:ok,
%{ %{
@ -200,11 +200,11 @@ defmodule LiveBook.Session.DataTest do
test "returns forget evaluation action" do test "returns forget evaluation action" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"} {:insert_cell, self(), "s1", 0, :elixir, "c1"}
]) ])
operation = {:delete_cell, "c1"} operation = {:delete_cell, self(), "c1"}
assert {:ok, _data, [{:forget_evaluation, %{id: "c1"}, %{id: "s1"}}]} = assert {:ok, _data, [{:forget_evaluation, %{id: "c1"}, %{id: "s1"}}]} =
Data.apply_operation(data, operation) Data.apply_operation(data, operation)
@ -214,42 +214,42 @@ defmodule LiveBook.Session.DataTest do
describe "apply_operation/2 given :move_cell" do describe "apply_operation/2 given :move_cell" do
test "returns an error given invalid cell id" do test "returns an error given invalid cell id" do
data = Data.new() data = Data.new()
operation = {:move_cell, "nonexistent", 1} operation = {:move_cell, self(), "nonexistent", 1}
assert :error = Data.apply_operation(data, operation) assert :error = Data.apply_operation(data, operation)
end end
test "returns an error given no offset" do test "returns an error given no offset" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"} {:insert_cell, self(), "s1", 0, :elixir, "c1"}
]) ])
operation = {:move_cell, "c1", 0} operation = {:move_cell, self(), "c1", 0}
assert :error = Data.apply_operation(data, operation) assert :error = Data.apply_operation(data, operation)
end end
test "given negative offset moves the cell and marks relevant cells as stale" do test "given negative offset moves the cell and marks relevant cells as stale" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
# Add cells # Add cells
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:insert_cell, "s1", 2, :elixir, "c3"}, {:insert_cell, self(), "s1", 2, :elixir, "c3"},
{:insert_cell, "s1", 3, :elixir, "c4"}, {:insert_cell, self(), "s1", 3, :elixir, "c4"},
# Evaluate cells # Evaluate cells
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, "c1", {:ok, nil}}, {:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
{:queue_cell_evaluation, "c2"}, {:queue_cell_evaluation, self(), "c2"},
{:add_cell_evaluation_response, "c2", {:ok, nil}}, {:add_cell_evaluation_response, self(), "c2", {:ok, nil}},
{:queue_cell_evaluation, "c3"}, {:queue_cell_evaluation, self(), "c3"},
{:add_cell_evaluation_response, "c3", {:ok, nil}}, {:add_cell_evaluation_response, self(), "c3", {:ok, nil}},
{:queue_cell_evaluation, "c4"}, {:queue_cell_evaluation, self(), "c4"},
{:add_cell_evaluation_response, "c4", {:ok, nil}} {:add_cell_evaluation_response, self(), "c4", {:ok, nil}}
]) ])
operation = {:move_cell, "c3", -1} operation = {:move_cell, self(), "c3", -1}
assert {:ok, assert {:ok,
%{ %{
@ -272,24 +272,24 @@ defmodule LiveBook.Session.DataTest do
test "given positive offset moves the cell and marks relevant cells as stale" do test "given positive offset moves the cell and marks relevant cells as stale" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
# Add cells # Add cells
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:insert_cell, "s1", 2, :elixir, "c3"}, {:insert_cell, self(), "s1", 2, :elixir, "c3"},
{:insert_cell, "s1", 3, :elixir, "c4"}, {:insert_cell, self(), "s1", 3, :elixir, "c4"},
# Evaluate cells # Evaluate cells
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, "c1", {:ok, nil}}, {:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
{:queue_cell_evaluation, "c2"}, {:queue_cell_evaluation, self(), "c2"},
{:add_cell_evaluation_response, "c2", {:ok, nil}}, {:add_cell_evaluation_response, self(), "c2", {:ok, nil}},
{:queue_cell_evaluation, "c3"}, {:queue_cell_evaluation, self(), "c3"},
{:add_cell_evaluation_response, "c3", {:ok, nil}}, {:add_cell_evaluation_response, self(), "c3", {:ok, nil}},
{:queue_cell_evaluation, "c4"}, {:queue_cell_evaluation, self(), "c4"},
{:add_cell_evaluation_response, "c4", {:ok, nil}} {:add_cell_evaluation_response, self(), "c4", {:ok, nil}}
]) ])
operation = {:move_cell, "c2", 1} operation = {:move_cell, self(), "c2", 1}
assert {:ok, assert {:ok,
%{ %{
@ -312,16 +312,16 @@ defmodule LiveBook.Session.DataTest do
test "moving a markdown cell does not change validity" do test "moving a markdown cell does not change validity" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
# Add cells # Add cells
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :markdown, "c2"}, {:insert_cell, self(), "s1", 1, :markdown, "c2"},
# Evaluate the Elixir cell # Evaluate the Elixir cell
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, "c1", {:ok, nil}} {:add_cell_evaluation_response, self(), "c1", {:ok, nil}}
]) ])
operation = {:move_cell, "c2", -1} operation = {:move_cell, self(), "c2", -1}
assert {:ok, assert {:ok,
%{ %{
@ -334,16 +334,16 @@ defmodule LiveBook.Session.DataTest do
test "affected queued cell is unqueued" do test "affected queued cell is unqueued" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
# Add cells # Add cells
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
# Evaluate the Elixir cell # Evaluate the Elixir cell
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, "c2"} {:queue_cell_evaluation, self(), "c2"}
]) ])
operation = {:move_cell, "c2", -1} operation = {:move_cell, self(), "c2", -1}
assert {:ok, assert {:ok,
%{ %{
@ -356,19 +356,19 @@ defmodule LiveBook.Session.DataTest do
test "does not invalidate the moved cell if the order of Elixir cells stays the same" do test "does not invalidate the moved cell if the order of Elixir cells stays the same" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
# Add cells # Add cells
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :markdown, "c2"}, {:insert_cell, self(), "s1", 1, :markdown, "c2"},
{:insert_cell, "s1", 2, :elixir, "c3"}, {:insert_cell, self(), "s1", 2, :elixir, "c3"},
# Evaluate cells # Evaluate cells
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, "c1", {:ok, nil}}, {:add_cell_evaluation_response, self(), "c1", {:ok, nil}},
{:queue_cell_evaluation, "c3"}, {:queue_cell_evaluation, self(), "c3"},
{:add_cell_evaluation_response, "c3", {:ok, nil}} {:add_cell_evaluation_response, self(), "c3", {:ok, nil}}
]) ])
operation = {:move_cell, "c1", 1} operation = {:move_cell, self(), "c1", 1}
assert {:ok, assert {:ok,
%{ %{
@ -383,41 +383,41 @@ defmodule LiveBook.Session.DataTest do
describe "apply_operation/2 given :queue_cell_evaluation" do describe "apply_operation/2 given :queue_cell_evaluation" do
test "returns an error given invalid cell id" do test "returns an error given invalid cell id" do
data = Data.new() data = Data.new()
operation = {:queue_cell_evaluation, "nonexistent"} operation = {:queue_cell_evaluation, self(), "nonexistent"}
assert :error = Data.apply_operation(data, operation) assert :error = Data.apply_operation(data, operation)
end end
test "returns an error given non-elixir cell" do test "returns an error given non-elixir cell" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :markdown, "c1"} {:insert_cell, self(), "s1", 0, :markdown, "c1"}
]) ])
operation = {:queue_cell_evaluation, "c1"} operation = {:queue_cell_evaluation, self(), "c1"}
assert :error = Data.apply_operation(data, operation) assert :error = Data.apply_operation(data, operation)
end end
test "returns an error for an evaluating cell" do test "returns an error for an evaluating cell" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:queue_cell_evaluation, "c1"} {:queue_cell_evaluation, self(), "c1"}
]) ])
operation = {:queue_cell_evaluation, "c1"} operation = {:queue_cell_evaluation, self(), "c1"}
assert :error = Data.apply_operation(data, operation) assert :error = Data.apply_operation(data, operation)
end end
test "marks the cell as evaluating if the corresponding section is idle" do test "marks the cell as evaluating if the corresponding section is idle" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"} {:insert_cell, self(), "s1", 0, :elixir, "c1"}
]) ])
operation = {:queue_cell_evaluation, "c1"} operation = {:queue_cell_evaluation, self(), "c1"}
assert {:ok, assert {:ok,
%{ %{
@ -429,11 +429,11 @@ defmodule LiveBook.Session.DataTest do
test "returns start evaluation action if the corresponding section is idle" do test "returns start evaluation action if the corresponding section is idle" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"} {:insert_cell, self(), "s1", 0, :elixir, "c1"}
]) ])
operation = {:queue_cell_evaluation, "c1"} operation = {:queue_cell_evaluation, self(), "c1"}
assert {:ok, _data, [{:start_evaluation, %{id: "c1"}, %{id: "s1"}}]} = assert {:ok, _data, [{:start_evaluation, %{id: "c1"}, %{id: "s1"}}]} =
Data.apply_operation(data, operation) Data.apply_operation(data, operation)
@ -442,13 +442,13 @@ defmodule LiveBook.Session.DataTest do
test "marks the cell as queued if the corresponding section is already evaluating" do test "marks the cell as queued if the corresponding section is already evaluating" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:queue_cell_evaluation, "c1"} {:queue_cell_evaluation, self(), "c1"}
]) ])
operation = {:queue_cell_evaluation, "c2"} operation = {:queue_cell_evaluation, self(), "c2"}
assert {:ok, assert {:ok,
%{ %{
@ -462,12 +462,12 @@ defmodule LiveBook.Session.DataTest do
test "updates the cell outputs" do test "updates the cell outputs" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:queue_cell_evaluation, "c1"} {:queue_cell_evaluation, self(), "c1"}
]) ])
operation = {:add_cell_evaluation_stdout, "c1", "Hello!"} operation = {:add_cell_evaluation_stdout, self(), "c1", "Hello!"}
assert {:ok, assert {:ok,
%{ %{
@ -484,13 +484,13 @@ defmodule LiveBook.Session.DataTest do
test "merges consecutive stdout results" do test "merges consecutive stdout results" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_stdout, "c1", "Hello"} {:add_cell_evaluation_stdout, self(), "c1", "Hello"}
]) ])
operation = {:add_cell_evaluation_stdout, "c1", " amigo!"} operation = {:add_cell_evaluation_stdout, self(), "c1", " amigo!"}
assert {:ok, assert {:ok,
%{ %{
@ -509,12 +509,12 @@ defmodule LiveBook.Session.DataTest do
test "updates the cell outputs" do test "updates the cell outputs" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:queue_cell_evaluation, "c1"} {:queue_cell_evaluation, self(), "c1"}
]) ])
operation = {:add_cell_evaluation_response, "c1", {:ok, [1, 2, 3]}} operation = {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}}
assert {:ok, assert {:ok,
%{ %{
@ -531,13 +531,13 @@ defmodule LiveBook.Session.DataTest do
test "marks the cell as evaluated" do test "marks the cell as evaluated" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:queue_cell_evaluation, "c1"} {:queue_cell_evaluation, self(), "c1"}
]) ])
operation = {:add_cell_evaluation_response, "c1", {:ok, [1, 2, 3]}} operation = {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}}
assert {:ok, assert {:ok,
%{ %{
@ -549,14 +549,14 @@ defmodule LiveBook.Session.DataTest do
test "marks next queued cell in this section as evaluating if there is one" do test "marks next queued cell in this section as evaluating if there is one" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, "c2"} {:queue_cell_evaluation, self(), "c2"}
]) ])
operation = {:add_cell_evaluation_response, "c1", {:ok, [1, 2, 3]}} operation = {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}}
assert {:ok, assert {:ok,
%{ %{
@ -570,12 +570,12 @@ defmodule LiveBook.Session.DataTest do
test "if parent cells are not executed, marks them for evaluation first" do test "if parent cells are not executed, marks them for evaluation first" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"} {:insert_cell, self(), "s1", 1, :elixir, "c2"}
]) ])
operation = {:queue_cell_evaluation, "c2"} operation = {:queue_cell_evaluation, self(), "c2"}
assert {:ok, assert {:ok,
%{ %{
@ -592,19 +592,19 @@ defmodule LiveBook.Session.DataTest do
test "marks evaluated child cells as stale" do test "marks evaluated child cells as stale" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
# Evaluate both cells # Evaluate both cells
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, "c1", {:ok, [1, 2, 3]}}, {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}},
{:queue_cell_evaluation, "c2"}, {:queue_cell_evaluation, self(), "c2"},
{:add_cell_evaluation_response, "c2", {:ok, [1, 2, 3]}}, {:add_cell_evaluation_response, self(), "c2", {:ok, [1, 2, 3]}},
# Queue the first cell again # Queue the first cell again
{:queue_cell_evaluation, "c1"} {:queue_cell_evaluation, self(), "c1"}
]) ])
operation = {:add_cell_evaluation_response, "c1", {:ok, [1, 2, 3]}} operation = {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}}
assert {:ok, assert {:ok,
%{ %{
@ -616,34 +616,34 @@ defmodule LiveBook.Session.DataTest do
describe "apply_operation/2 given :cancel_cell_evaluation" do describe "apply_operation/2 given :cancel_cell_evaluation" do
test "returns an error given invalid cell id" do test "returns an error given invalid cell id" do
data = Data.new() data = Data.new()
operation = {:cancel_cell_evaluation, "nonexistent"} operation = {:cancel_cell_evaluation, self(), "nonexistent"}
assert :error = Data.apply_operation(data, operation) assert :error = Data.apply_operation(data, operation)
end end
test "returns an error for an evaluated cell" do test "returns an error for an evaluated cell" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:add_cell_evaluation_response, "c1", {:ok, [1, 2, 3]}} {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}}
]) ])
operation = {:cancel_cell_evaluation, "c1"} operation = {:cancel_cell_evaluation, self(), "c1"}
assert :error = Data.apply_operation(data, operation) assert :error = Data.apply_operation(data, operation)
end end
test "if the cell is evaluating, clears the corresponding section evaluation and the queue" do test "if the cell is evaluating, clears the corresponding section evaluation and the queue" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, "c2"} {:queue_cell_evaluation, self(), "c2"}
]) ])
operation = {:cancel_cell_evaluation, "c1"} operation = {:cancel_cell_evaluation, self(), "c1"}
assert {:ok, assert {:ok,
%{ %{
@ -660,14 +660,14 @@ defmodule LiveBook.Session.DataTest do
test "if the cell is evaluating, returns stop evaluation action" do test "if the cell is evaluating, returns stop evaluation action" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, "c2"} {:queue_cell_evaluation, self(), "c2"}
]) ])
operation = {:cancel_cell_evaluation, "c1"} operation = {:cancel_cell_evaluation, self(), "c1"}
assert {:ok, _data, [{:stop_evaluation, %{id: "s1"}}]} = assert {:ok, _data, [{:stop_evaluation, %{id: "s1"}}]} =
Data.apply_operation(data, operation) Data.apply_operation(data, operation)
@ -676,14 +676,14 @@ defmodule LiveBook.Session.DataTest do
test "if the cell is queued, unqueues it" do test "if the cell is queued, unqueues it" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, "c2"} {:queue_cell_evaluation, self(), "c2"}
]) ])
operation = {:cancel_cell_evaluation, "c2"} operation = {:cancel_cell_evaluation, self(), "c2"}
assert {:ok, assert {:ok,
%{ %{
@ -695,16 +695,16 @@ defmodule LiveBook.Session.DataTest do
test "if the cell is queued, unqueues dependent cells that are also queued" do test "if the cell is queued, unqueues dependent cells that are also queued" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:insert_cell, "s1", 2, :elixir, "c3"}, {:insert_cell, self(), "s1", 2, :elixir, "c3"},
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, "c2"}, {:queue_cell_evaluation, self(), "c2"},
{:queue_cell_evaluation, "c3"} {:queue_cell_evaluation, self(), "c3"}
]) ])
operation = {:cancel_cell_evaluation, "c2"} operation = {:cancel_cell_evaluation, self(), "c2"}
assert {:ok, assert {:ok,
%{ %{
@ -718,7 +718,7 @@ defmodule LiveBook.Session.DataTest do
test "updates notebook name with the given string" do test "updates notebook name with the given string" do
data = Data.new() data = Data.new()
operation = {:set_notebook_name, "Cat's guide to life"} operation = {:set_notebook_name, self(), "Cat's guide to life"}
assert {:ok, %{notebook: %{name: "Cat's guide to life"}}, []} = assert {:ok, %{notebook: %{name: "Cat's guide to life"}}, []} =
Data.apply_operation(data, operation) Data.apply_operation(data, operation)
@ -728,17 +728,17 @@ defmodule LiveBook.Session.DataTest do
describe "apply_operation/2 given :set_section_name" do describe "apply_operation/2 given :set_section_name" do
test "returns an error given invalid cell id" do test "returns an error given invalid cell id" do
data = Data.new() data = Data.new()
operation = {:set_section_name, "nonexistent", "Chapter 1"} operation = {:set_section_name, self(), "nonexistent", "Chapter 1"}
assert :error = Data.apply_operation(data, operation) assert :error = Data.apply_operation(data, operation)
end end
test "updates section name with the given string" do test "updates section name with the given string" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"} {:insert_section, self(), 0, "s1"}
]) ])
operation = {:set_section_name, "s1", "Cat's guide to life"} operation = {:set_section_name, self(), "s1", "Cat's guide to life"}
assert {:ok, %{notebook: %{sections: [%{name: "Cat's guide to life"}]}}, []} = assert {:ok, %{notebook: %{sections: [%{name: "Cat's guide to life"}]}}, []} =
Data.apply_operation(data, operation) Data.apply_operation(data, operation)
@ -771,8 +771,8 @@ defmodule LiveBook.Session.DataTest do
data = data =
data_after_operations!([ data_after_operations!([
{:client_join, client1_pid}, {:client_join, client1_pid},
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:apply_cell_delta, client1_pid, "c1", delta1, 1} {:apply_cell_delta, client1_pid, "c1", delta1, 1}
]) ])
@ -814,8 +814,8 @@ defmodule LiveBook.Session.DataTest do
data_after_operations!([ data_after_operations!([
{:client_join, client1_pid}, {:client_join, client1_pid},
{:client_join, client2_pid}, {:client_join, client2_pid},
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:apply_cell_delta, client1_pid, "c1", delta1, 1} {:apply_cell_delta, client1_pid, "c1", delta1, 1}
]) ])
@ -846,8 +846,8 @@ defmodule LiveBook.Session.DataTest do
test "returns an error given non-joined client pid" do test "returns an error given non-joined client pid" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"} {:insert_cell, self(), "s1", 0, :elixir, "c1"}
]) ])
delta = Delta.new() |> Delta.insert("cats") delta = Delta.new() |> Delta.insert("cats")
@ -859,8 +859,8 @@ defmodule LiveBook.Session.DataTest do
data = data =
data_after_operations!([ data_after_operations!([
{:client_join, self()}, {:client_join, self()},
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"} {:insert_cell, self(), "s1", 0, :elixir, "c1"}
]) ])
delta = Delta.new() |> Delta.insert("cats") delta = Delta.new() |> Delta.insert("cats")
@ -873,8 +873,8 @@ defmodule LiveBook.Session.DataTest do
data = data =
data_after_operations!([ data_after_operations!([
{:client_join, self()}, {:client_join, self()},
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"} {:insert_cell, self(), "s1", 0, :elixir, "c1"}
]) ])
delta = Delta.new() |> Delta.insert("cats") delta = Delta.new() |> Delta.insert("cats")
@ -901,8 +901,8 @@ defmodule LiveBook.Session.DataTest do
data_after_operations!([ data_after_operations!([
{:client_join, client1_pid}, {:client_join, client1_pid},
{:client_join, client2_pid}, {:client_join, client2_pid},
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:apply_cell_delta, client1_pid, "c1", delta1, 1} {:apply_cell_delta, client1_pid, "c1", delta1, 1}
]) ])
@ -930,8 +930,8 @@ defmodule LiveBook.Session.DataTest do
data_after_operations!([ data_after_operations!([
{:client_join, client1_pid}, {:client_join, client1_pid},
{:client_join, client2_pid}, {:client_join, client2_pid},
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:apply_cell_delta, client1_pid, "c1", delta1, 1} {:apply_cell_delta, client1_pid, "c1", delta1, 1}
]) ])
@ -950,8 +950,8 @@ defmodule LiveBook.Session.DataTest do
data = data =
data_after_operations!([ data_after_operations!([
{:client_join, client_pid}, {:client_join, client_pid},
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"} {:insert_cell, self(), "s1", 0, :elixir, "c1"}
]) ])
delta = Delta.new() |> Delta.insert("cats") delta = Delta.new() |> Delta.insert("cats")
@ -971,8 +971,8 @@ defmodule LiveBook.Session.DataTest do
data_after_operations!([ data_after_operations!([
{:client_join, client1_pid}, {:client_join, client1_pid},
{:client_join, client2_pid}, {:client_join, client2_pid},
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"} {:insert_cell, self(), "s1", 0, :elixir, "c1"}
]) ])
delta = Delta.new() |> Delta.insert("cats") delta = Delta.new() |> Delta.insert("cats")
@ -1003,8 +1003,8 @@ defmodule LiveBook.Session.DataTest do
data = data =
data_after_operations!([ data_after_operations!([
{:client_join, client1_pid}, {:client_join, client1_pid},
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:apply_cell_delta, client1_pid, "c1", Delta.new(insert: "cats"), 1} {:apply_cell_delta, client1_pid, "c1", Delta.new(insert: "cats"), 1}
]) ])
@ -1016,8 +1016,8 @@ defmodule LiveBook.Session.DataTest do
data = data =
data_after_operations!([ data_after_operations!([
{:client_join, self()}, {:client_join, self()},
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"} {:insert_cell, self(), "s1", 0, :elixir, "c1"}
]) ])
operation = {:report_cell_revision, self(), "c1", 1} operation = {:report_cell_revision, self(), "c1", 1}
@ -1034,8 +1034,8 @@ defmodule LiveBook.Session.DataTest do
data_after_operations!([ data_after_operations!([
{:client_join, client1_pid}, {:client_join, client1_pid},
{:client_join, client2_pid}, {:client_join, client2_pid},
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:apply_cell_delta, client1_pid, "c1", delta1, 1} {:apply_cell_delta, client1_pid, "c1", delta1, 1}
]) ])
@ -1059,7 +1059,7 @@ defmodule LiveBook.Session.DataTest do
{:ok, runtime} = LiveBookTest.Runtime.SingleEvaluator.init() {:ok, runtime} = LiveBookTest.Runtime.SingleEvaluator.init()
operation = {:set_runtime, runtime} operation = {:set_runtime, self(), runtime}
assert {:ok, %{runtime: ^runtime}, []} = Data.apply_operation(data, operation) assert {:ok, %{runtime: ^runtime}, []} = Data.apply_operation(data, operation)
end end
@ -1068,22 +1068,22 @@ defmodule LiveBook.Session.DataTest do
data = data =
data_after_operations!([ data_after_operations!([
# First section with evaluating and queued cells # First section with evaluating and queued cells
{:insert_section, 0, "s1"}, {:insert_section, self(), 0, "s1"},
{:insert_cell, "s1", 0, :elixir, "c1"}, {:insert_cell, self(), "s1", 0, :elixir, "c1"},
{:insert_cell, "s1", 1, :elixir, "c2"}, {:insert_cell, self(), "s1", 1, :elixir, "c2"},
{:queue_cell_evaluation, "c1"}, {:queue_cell_evaluation, self(), "c1"},
{:queue_cell_evaluation, "c2"}, {:queue_cell_evaluation, self(), "c2"},
# Second section with evaluating and queued cells # Second section with evaluating and queued cells
{:insert_section, 1, "s2"}, {:insert_section, self(), 1, "s2"},
{:insert_cell, "s2", 0, :elixir, "c3"}, {:insert_cell, self(), "s2", 0, :elixir, "c3"},
{:insert_cell, "s2", 1, :elixir, "c4"}, {:insert_cell, self(), "s2", 1, :elixir, "c4"},
{:queue_cell_evaluation, "c3"}, {:queue_cell_evaluation, self(), "c3"},
{:queue_cell_evaluation, "c4"} {:queue_cell_evaluation, self(), "c4"}
]) ])
{:ok, runtime} = LiveBookTest.Runtime.SingleEvaluator.init() {:ok, runtime} = LiveBookTest.Runtime.SingleEvaluator.init()
operation = {:set_runtime, runtime} operation = {:set_runtime, self(), runtime}
assert {:ok, assert {:ok,
%{ %{
@ -1104,7 +1104,7 @@ defmodule LiveBook.Session.DataTest do
describe "apply_operation/2 given :set_path" do describe "apply_operation/2 given :set_path" do
test "updates data with the given path" do test "updates data with the given path" do
data = Data.new() data = Data.new()
operation = {:set_path, "path"} operation = {:set_path, self(), "path"}
assert {:ok, %{path: "path"}, []} = Data.apply_operation(data, operation) assert {:ok, %{path: "path"}, []} = Data.apply_operation(data, operation)
end end
@ -1114,10 +1114,10 @@ defmodule LiveBook.Session.DataTest do
test "sets dirty flag to false" do test "sets dirty flag to false" do
data = data =
data_after_operations!([ data_after_operations!([
{:insert_section, 0, "s1"} {:insert_section, self(), 0, "s1"}
]) ])
operation = :mark_as_not_dirty operation = {:mark_as_not_dirty, self()}
assert {:ok, %{dirty: false}, []} = Data.apply_operation(data, operation) assert {:ok, %{dirty: false}, []} = Data.apply_operation(data, operation)
end end

View file

@ -11,54 +11,59 @@ defmodule LiveBook.SessionTest do
describe "insert_section/2" do describe "insert_section/2" do
test "sends an insert opreation to subscribers", %{session_id: session_id} do test "sends an insert opreation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
Session.insert_section(session_id, 0) Session.insert_section(session_id, 0)
assert_receive {:operation, {:insert_section, 0, _id}} assert_receive {:operation, {:insert_section, ^pid, 0, _id}}
end end
end end
describe "insert_cell/4" do describe "insert_cell/4" do
test "sends an insert opreation to subscribers", %{session_id: session_id} do test "sends an insert opreation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
Session.insert_section(session_id, 0) Session.insert_section(session_id, 0)
assert_receive {:operation, {:insert_section, 0, section_id}} assert_receive {:operation, {:insert_section, ^pid, 0, section_id}}
Session.insert_cell(session_id, section_id, 0, :elixir) Session.insert_cell(session_id, section_id, 0, :elixir)
assert_receive {:operation, {:insert_cell, ^section_id, 0, :elixir, _id}} assert_receive {:operation, {:insert_cell, ^pid, ^section_id, 0, :elixir, _id}}
end end
end end
describe "delete_section/2" do describe "delete_section/2" do
test "sends a delete opreation to subscribers", %{session_id: session_id} do test "sends a delete opreation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
{section_id, _cell_id} = insert_section_and_cell(session_id) {section_id, _cell_id} = insert_section_and_cell(session_id)
Session.delete_section(session_id, section_id) Session.delete_section(session_id, section_id)
assert_receive {:operation, {:delete_section, ^section_id}} assert_receive {:operation, {:delete_section, ^pid, ^section_id}}
end end
end end
describe "delete_cell/2" do describe "delete_cell/2" do
test "sends a delete opreation to subscribers", %{session_id: session_id} do test "sends a delete opreation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
{_section_id, cell_id} = insert_section_and_cell(session_id) {_section_id, cell_id} = insert_section_and_cell(session_id)
Session.delete_cell(session_id, cell_id) Session.delete_cell(session_id, cell_id)
assert_receive {:operation, {:delete_cell, ^cell_id}} assert_receive {:operation, {:delete_cell, ^pid, ^cell_id}}
end end
end end
describe "queue_cell_evaluation/2" do describe "queue_cell_evaluation/2" do
test "sends a queue evaluation operation to subscribers", %{session_id: session_id} do test "sends a queue evaluation operation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
{_section_id, cell_id} = insert_section_and_cell(session_id) {_section_id, cell_id} = insert_section_and_cell(session_id)
Session.queue_cell_evaluation(session_id, cell_id) Session.queue_cell_evaluation(session_id, cell_id)
assert_receive {:operation, {:queue_cell_evaluation, ^cell_id}} assert_receive {:operation, {:queue_cell_evaluation, ^pid, ^cell_id}}
end end
test "triggers evaluation and sends update operation once it finishes", test "triggers evaluation and sends update operation once it finishes",
@ -68,75 +73,80 @@ defmodule LiveBook.SessionTest do
{_section_id, cell_id} = insert_section_and_cell(session_id) {_section_id, cell_id} = insert_section_and_cell(session_id)
Session.queue_cell_evaluation(session_id, cell_id) Session.queue_cell_evaluation(session_id, cell_id)
assert_receive {:operation, {:add_cell_evaluation_response, ^cell_id, _}} assert_receive {:operation, {:add_cell_evaluation_response, _, ^cell_id, _}}
end end
end end
describe "cancel_cell_evaluation/2" do describe "cancel_cell_evaluation/2" do
test "sends a cancel evaluation operation to subscribers", %{session_id: session_id} do test "sends a cancel evaluation operation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
{_section_id, cell_id} = insert_section_and_cell(session_id) {_section_id, cell_id} = insert_section_and_cell(session_id)
queue_evaluation(session_id, cell_id) queue_evaluation(session_id, cell_id)
Session.cancel_cell_evaluation(session_id, cell_id) Session.cancel_cell_evaluation(session_id, cell_id)
assert_receive {:operation, {:cancel_cell_evaluation, ^cell_id}} assert_receive {:operation, {:cancel_cell_evaluation, ^pid, ^cell_id}}
end end
end end
describe "set_notebook_name/2" do describe "set_notebook_name/2" do
test "sends a notebook name update operation to subscribers", %{session_id: session_id} do test "sends a notebook name update operation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
Session.set_notebook_name(session_id, "Cat's guide to life") Session.set_notebook_name(session_id, "Cat's guide to life")
assert_receive {:operation, {:set_notebook_name, "Cat's guide to life"}} assert_receive {:operation, {:set_notebook_name, ^pid, "Cat's guide to life"}}
end end
end end
describe "set_section_name/3" do describe "set_section_name/3" do
test "sends a section name update operation to subscribers", %{session_id: session_id} do test "sends a section name update operation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
{section_id, _cell_id} = insert_section_and_cell(session_id) {section_id, _cell_id} = insert_section_and_cell(session_id)
Session.set_section_name(session_id, section_id, "Chapter 1") Session.set_section_name(session_id, section_id, "Chapter 1")
assert_receive {:operation, {:set_section_name, ^section_id, "Chapter 1"}} assert_receive {:operation, {:set_section_name, ^pid, ^section_id, "Chapter 1"}}
end end
end end
describe "apply_cell_delta/5" do describe "apply_cell_delta/5" do
test "sends a cell delta operation to subscribers", %{session_id: session_id} do test "sends a cell delta operation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
{_section_id, cell_id} = insert_section_and_cell(session_id) {_section_id, cell_id} = insert_section_and_cell(session_id)
from = self()
delta = Delta.new() |> Delta.insert("cats") delta = Delta.new() |> Delta.insert("cats")
revision = 1 revision = 1
Session.apply_cell_delta(session_id, from, cell_id, delta, revision) Session.apply_cell_delta(session_id, cell_id, delta, revision)
assert_receive {:operation, {:apply_cell_delta, ^from, ^cell_id, ^delta, ^revision}} assert_receive {:operation, {:apply_cell_delta, ^pid, ^cell_id, ^delta, ^revision}}
end end
end end
describe "connect_runtime/2" do describe "connect_runtime/2" do
test "sends a runtime update operation to subscribers", %{session_id: session_id} do test "sends a runtime update operation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
{:ok, runtime} = LiveBookTest.Runtime.SingleEvaluator.init() {:ok, runtime} = LiveBookTest.Runtime.SingleEvaluator.init()
Session.connect_runtime(session_id, runtime) Session.connect_runtime(session_id, runtime)
assert_receive {:operation, {:set_runtime, ^runtime}} assert_receive {:operation, {:set_runtime, ^pid, ^runtime}}
end end
end end
describe "disconnect_runtime/1" do describe "disconnect_runtime/1" do
test "sends a runtime update operation to subscribers", %{session_id: session_id} do test "sends a runtime update operation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
Session.disconnect_runtime(session_id) Session.disconnect_runtime(session_id)
assert_receive {:operation, {:set_runtime, nil}} assert_receive {:operation, {:set_runtime, ^pid, nil}}
end end
end end
@ -145,11 +155,12 @@ defmodule LiveBook.SessionTest do
test "sends a path update operation to subscribers", test "sends a path update operation to subscribers",
%{session_id: session_id, tmp_dir: tmp_dir} do %{session_id: session_id, tmp_dir: tmp_dir} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}") Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
path = Path.join(tmp_dir, "notebook.livemd") path = Path.join(tmp_dir, "notebook.livemd")
Session.set_path(session_id, path) Session.set_path(session_id, path)
assert_receive {:operation, {:set_path, ^path}} assert_receive {:operation, {:set_path, ^pid, ^path}}
end end
@tag :tmp_dir @tag :tmp_dir
@ -181,7 +192,7 @@ defmodule LiveBook.SessionTest do
Session.save(session_id) Session.save(session_id)
assert_receive {:operation, :mark_as_not_dirty} assert_receive {:operation, {:mark_as_not_dirty, _}}
assert File.exists?(path) assert File.exists?(path)
assert File.read!(path) =~ "My notebook" assert File.read!(path) =~ "My notebook"
end end
@ -234,7 +245,7 @@ defmodule LiveBook.SessionTest do
Session.queue_cell_evaluation(session_id, cell_id) Session.queue_cell_evaluation(session_id, cell_id)
# Give it a bit more time as this involves starting a system process. # Give it a bit more time as this involves starting a system process.
assert_receive {:operation, {:add_cell_evaluation_response, ^cell_id, _}}, 1000 assert_receive {:operation, {:add_cell_evaluation_response, _, ^cell_id, _}}, 1000
end end
test "if the runtime node goes down, notifies the subscribers" do test "if the runtime node goes down, notifies the subscribers" do
@ -246,12 +257,12 @@ defmodule LiveBook.SessionTest do
# Wait for the runtime to be set # Wait for the runtime to be set
Session.connect_runtime(session_id, runtime) Session.connect_runtime(session_id, runtime)
assert_receive {:operation, {:set_runtime, ^runtime}} assert_receive {:operation, {:set_runtime, _, ^runtime}}
# Terminate the other node, the session should detect that # Terminate the other node, the session should detect that
Node.spawn(runtime.node, System, :halt, []) Node.spawn(runtime.node, System, :halt, [])
assert_receive {:operation, {:set_runtime, nil}} assert_receive {:operation, {:set_runtime, _, nil}}
assert_receive {:info, "runtime node terminated unexpectedly"} assert_receive {:info, "runtime node terminated unexpectedly"}
end end
@ -267,15 +278,15 @@ defmodule LiveBook.SessionTest do
defp insert_section_and_cell(session_id) do defp insert_section_and_cell(session_id) do
Session.insert_section(session_id, 0) Session.insert_section(session_id, 0)
assert_receive {:operation, {:insert_section, 0, section_id}} assert_receive {:operation, {:insert_section, _, 0, section_id}}
Session.insert_cell(session_id, section_id, 0, :elixir) Session.insert_cell(session_id, section_id, 0, :elixir)
assert_receive {:operation, {:insert_cell, ^section_id, 0, :elixir, cell_id}} assert_receive {:operation, {:insert_cell, _, ^section_id, 0, :elixir, cell_id}}
{section_id, cell_id} {section_id, cell_id}
end end
defp queue_evaluation(session_id, cell_id) do defp queue_evaluation(session_id, cell_id) do
Session.queue_cell_evaluation(session_id, cell_id) Session.queue_cell_evaluation(session_id, cell_id)
assert_receive {:operation, {:add_cell_evaluation_response, ^cell_id, _}} assert_receive {:operation, {:add_cell_evaluation_response, _, ^cell_id, _}}
end end
end end

View file

@ -197,7 +197,7 @@ defmodule LiveBookWeb.SessionLiveTest do
%{notebook: %{sections: [%{cells: [cell]}]}} = Session.get_data(session_id) %{notebook: %{sections: [%{cells: [cell]}]}} = Session.get_data(session_id)
delta = Delta.new(insert: content) delta = Delta.new(insert: content)
Session.apply_cell_delta(session_id, self(), cell.id, delta, 1) Session.apply_cell_delta(session_id, cell.id, delta, 1)
cell.id cell.id
end end