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
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
@doc """
@ -113,7 +113,7 @@ defmodule LiveBook.Session do
@spec insert_cell(id(), Section.id(), non_neg_integer(), Cell.type()) ::
:ok
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
@doc """
@ -121,7 +121,7 @@ defmodule LiveBook.Session do
"""
@spec delete_section(id(), Section.id()) :: :ok
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
@doc """
@ -129,7 +129,7 @@ defmodule LiveBook.Session do
"""
@spec delete_cell(id(), Cell.id()) :: :ok
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
@doc """
@ -137,7 +137,7 @@ defmodule LiveBook.Session do
"""
@spec move_cell(id(), Cell.id(), integer()) :: :ok
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
@doc """
@ -145,7 +145,7 @@ defmodule LiveBook.Session do
"""
@spec queue_cell_evaluation(id(), Cell.id()) :: :ok
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
@doc """
@ -153,7 +153,7 @@ defmodule LiveBook.Session do
"""
@spec cancel_cell_evaluation(id(), Cell.id()) :: :ok
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
@doc """
@ -161,7 +161,7 @@ defmodule LiveBook.Session do
"""
@spec set_notebook_name(id(), String.t()) :: :ok
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
@doc """
@ -169,15 +169,15 @@ defmodule LiveBook.Session do
"""
@spec set_section_name(id(), Section.id(), String.t()) :: :ok
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
@doc """
Asynchronously sends a cell delta to apply to the server.
"""
@spec apply_cell_delta(id(), pid(), Cell.id(), Delta.t(), Data.cell_revision()) :: :ok
def apply_cell_delta(session_id, client_pid, cell_id, delta, revision) do
GenServer.cast(name(session_id), {:apply_cell_delta, client_pid, cell_id, delta, revision})
@spec apply_cell_delta(id(), Cell.id(), Delta.t(), Data.cell_revision()) :: :ok
def apply_cell_delta(session_id, cell_id, delta, revision) do
GenServer.cast(name(session_id), {:apply_cell_delta, self(), cell_id, delta, revision})
end
@doc """
@ -185,9 +185,9 @@ defmodule LiveBook.Session do
This helps to remove old deltas that are no longer necessary.
"""
@spec report_cell_revision(id(), pid(), Cell.id(), Data.cell_revision()) :: :ok
def report_cell_revision(session_id, client_pid, cell_id, revision) do
GenServer.cast(name(session_id), {:report_cell_revision, client_pid, cell_id, revision})
@spec report_cell_revision(id(), Cell.id(), Data.cell_revision()) :: :ok
def report_cell_revision(session_id, cell_id, revision) do
GenServer.cast(name(session_id), {:report_cell_revision, self(), cell_id, revision})
end
@doc """
@ -198,7 +198,7 @@ defmodule LiveBook.Session do
"""
@spec connect_runtime(id(), Runtime.t()) :: :ok
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
@doc """
@ -208,7 +208,7 @@ defmodule LiveBook.Session do
"""
@spec disconnect_runtime(id()) :: :ok
def disconnect_runtime(session_id) do
GenServer.cast(name(session_id), :disconnect_runtime)
GenServer.cast(name(session_id), {:disconnect_runtime, self()})
end
@doc """
@ -216,7 +216,7 @@ defmodule LiveBook.Session do
"""
@spec set_path(id(), String.t() | nil) :: :ok
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
@doc """
@ -301,37 +301,37 @@ defmodule LiveBook.Session do
end
@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
operation = {:insert_section, index, Utils.random_id()}
operation = {:insert_section, client_pid, index, Utils.random_id()}
{:noreply, handle_operation(state, operation)}
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
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)}
end
def handle_cast({:delete_section, section_id}, state) do
operation = {:delete_section, section_id}
def handle_cast({:delete_section, client_pid, section_id}, state) do
operation = {:delete_section, client_pid, section_id}
{:noreply, handle_operation(state, operation)}
end
def handle_cast({:delete_cell, cell_id}, state) do
operation = {:delete_cell, cell_id}
def handle_cast({:delete_cell, client_pid, cell_id}, state) do
operation = {:delete_cell, client_pid, cell_id}
{:noreply, handle_operation(state, operation)}
end
def handle_cast({:move_cell, cell_id, offset}, state) do
operation = {:move_cell, cell_id, offset}
def handle_cast({:move_cell, client_pid, cell_id, offset}, state) do
operation = {:move_cell, client_pid, cell_id, offset}
{:noreply, handle_operation(state, operation)}
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
{:ok, state} ->
operation = {:queue_cell_evaluation, cell_id}
operation = {:queue_cell_evaluation, client_pid, cell_id}
{:noreply, handle_operation(state, operation)}
{:error, error} ->
@ -340,18 +340,18 @@ defmodule LiveBook.Session do
end
end
def handle_cast({:cancel_cell_evaluation, cell_id}, state) do
operation = {:cancel_cell_evaluation, cell_id}
def handle_cast({:cancel_cell_evaluation, client_pid, cell_id}, state) do
operation = {:cancel_cell_evaluation, client_pid, cell_id}
{:noreply, handle_operation(state, operation)}
end
def handle_cast({:set_notebook_name, name}, state) do
operation = {:set_notebook_name, name}
def handle_cast({:set_notebook_name, client_pid, name}, state) do
operation = {:set_notebook_name, client_pid, name}
{:noreply, handle_operation(state, operation)}
end
def handle_cast({:set_section_name, section_id, name}, state) do
operation = {:set_section_name, section_id, name}
def handle_cast({:set_section_name, client_pid, section_id, name}, state) do
operation = {:set_section_name, client_pid, section_id, name}
{:noreply, handle_operation(state, operation)}
end
@ -365,7 +365,7 @@ defmodule LiveBook.Session do
{:noreply, handle_operation(state, operation)}
end
def handle_cast({:connect_runtime, runtime}, state) do
def handle_cast({:connect_runtime, client_pid, runtime}, state) do
if state.data.runtime do
Runtime.disconnect(state.data.runtime)
end
@ -374,18 +374,18 @@ defmodule LiveBook.Session do
{:noreply,
%{state | runtime_monitor_ref: runtime_monitor_ref}
|> handle_operation({:set_runtime, runtime})}
|> handle_operation({:set_runtime, client_pid, runtime})}
end
def handle_cast(:disconnect_runtime, state) do
def handle_cast({:disconnect_runtime, client_pid}, state) do
Runtime.disconnect(state.data.runtime)
{:noreply,
%{state | runtime_monitor_ref: nil}
|> handle_operation({:set_runtime, nil})}
|> handle_operation({:set_runtime, client_pid, nil})}
end
def handle_cast({:set_path, path}, state) do
def handle_cast({:set_path, client_pid, path}, state) do
if path do
FileGuard.lock(path, self())
else
@ -397,7 +397,7 @@ defmodule LiveBook.Session do
FileGuard.unlock(state.data.path)
end
{:noreply, handle_operation(state, {:set_path, path})}
{:noreply, handle_operation(state, {:set_path, client_pid, path})}
{:error, :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,
%{state | runtime_monitor_ref: nil}
|> handle_operation({:set_runtime, nil})}
|> handle_operation({:set_runtime, self(), nil})}
end
def handle_info({:DOWN, _, :process, pid, _}, state) do
@ -437,12 +437,12 @@ defmodule LiveBook.Session do
end
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)}
end
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)}
end
@ -546,7 +546,7 @@ defmodule LiveBook.Session do
{:ok,
%{state | runtime_monitor_ref: runtime_monitor_ref}
|> handle_operation({:set_runtime, runtime})}
|> handle_operation({:set_runtime, self(), runtime})}
end
end
@ -558,7 +558,7 @@ defmodule LiveBook.Session do
case File.write(state.data.path, content) do
:ok ->
handle_operation(state, :mark_as_not_dirty)
handle_operation(state, {:mark_as_not_dirty, self()})
{:error, 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()
# 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 ::
{:insert_section, index(), Section.id()}
| {:insert_cell, Section.id(), index(), Cell.type(), Cell.id()}
| {:delete_section, Section.id()}
| {:delete_cell, Cell.id()}
| {:move_cell, Cell.id(), offset :: integer()}
| {:queue_cell_evaluation, Cell.id()}
| {:add_cell_evaluation_stdout, Cell.id(), String.t()}
| {:add_cell_evaluation_response, Cell.id(), Evaluator.evaluation_response()}
| {:cancel_cell_evaluation, Cell.id()}
| {:set_notebook_name, String.t()}
| {:set_section_name, Section.id(), String.t()}
{:insert_section, pid(), index(), Section.id()}
| {:insert_cell, pid(), Section.id(), index(), Cell.type(), Cell.id()}
| {:delete_section, pid(), Section.id()}
| {:delete_cell, pid(), Cell.id()}
| {:move_cell, pid(), Cell.id(), offset :: integer()}
| {:queue_cell_evaluation, pid(), Cell.id()}
| {:add_cell_evaluation_stdout, pid(), Cell.id(), String.t()}
| {:add_cell_evaluation_response, pid(), Cell.id(), Evaluator.evaluation_response()}
| {:cancel_cell_evaluation, pid(), Cell.id()}
| {:set_notebook_name, pid(), String.t()}
| {:set_section_name, pid(), Section.id(), String.t()}
| {:client_join, pid()}
| {:client_leave, pid()}
| {:apply_cell_delta, pid(), Cell.id(), Delta.t(), cell_revision()}
| {:report_cell_revision, pid(), Cell.id(), cell_revision()}
| {:set_runtime, Runtime.t() | nil}
| {:set_path, String.t() | nil}
| :mark_as_not_dirty
| {:set_runtime, pid(), Runtime.t() | nil}
| {:set_path, pid(), String.t() | nil}
| {:mark_as_not_dirty, pid()}
@type action ::
{: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
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}
data
@ -155,7 +163,7 @@ defmodule LiveBook.Session.Data do
|> wrap_ok()
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
cell = %{Cell.new(type) | id: id}
@ -167,7 +175,7 @@ defmodule LiveBook.Session.Data do
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
data
|> with_actions()
@ -177,7 +185,7 @@ defmodule LiveBook.Session.Data do
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
case data.cell_infos[cell.id].evaluation_status do
:evaluating ->
@ -205,7 +213,7 @@ defmodule LiveBook.Session.Data do
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),
true <- offset != 0 do
data
@ -218,7 +226,7 @@ defmodule LiveBook.Session.Data do
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),
:elixir <- cell.type,
:ready <- data.cell_infos[cell.id].evaluation_status do
@ -233,7 +241,7 @@ defmodule LiveBook.Session.Data do
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),
:evaluating <- data.cell_infos[cell.id].evaluation_status do
data
@ -245,7 +253,7 @@ defmodule LiveBook.Session.Data do
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),
:evaluating <- data.cell_infos[cell.id].evaluation_status do
data
@ -260,7 +268,7 @@ defmodule LiveBook.Session.Data do
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
case data.cell_infos[cell.id].evaluation_status do
:evaluating ->
@ -284,7 +292,7 @@ defmodule LiveBook.Session.Data do
end
end
def apply_operation(data, {:set_notebook_name, name}) do
def apply_operation(data, {:set_notebook_name, _client_pid, name}) do
data
|> with_actions()
|> set_notebook_name(name)
@ -292,7 +300,7 @@ defmodule LiveBook.Session.Data do
|> wrap_ok()
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
data
|> with_actions()
@ -353,7 +361,7 @@ defmodule LiveBook.Session.Data do
end
end
def apply_operation(data, {:set_runtime, runtime}) do
def apply_operation(data, {:set_runtime, _client_pid, runtime}) do
data
|> with_actions()
|> set!(runtime: runtime)
@ -361,7 +369,7 @@ defmodule LiveBook.Session.Data do
|> wrap_ok()
end
def apply_operation(data, {:set_path, path}) do
def apply_operation(data, {:set_path, _client_pid, path}) do
data
|> with_actions()
|> set!(path: path)
@ -369,7 +377,7 @@ defmodule LiveBook.Session.Data do
|> wrap_ok()
end
def apply_operation(data, :mark_as_not_dirty) do
def apply_operation(data, {:mark_as_not_dirty, _client_pid}) do
data
|> with_actions()
|> set_dirty(false)

View file

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

View file

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

View file

@ -11,54 +11,59 @@ defmodule LiveBook.SessionTest do
describe "insert_section/2" do
test "sends an insert opreation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
Session.insert_section(session_id, 0)
assert_receive {:operation, {:insert_section, 0, _id}}
assert_receive {:operation, {:insert_section, ^pid, 0, _id}}
end
end
describe "insert_cell/4" do
test "sends an insert opreation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
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)
assert_receive {:operation, {:insert_cell, ^section_id, 0, :elixir, _id}}
assert_receive {:operation, {:insert_cell, ^pid, ^section_id, 0, :elixir, _id}}
end
end
describe "delete_section/2" do
test "sends a delete opreation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
{section_id, _cell_id} = insert_section_and_cell(session_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
describe "delete_cell/2" do
test "sends a delete opreation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
{_section_id, cell_id} = insert_section_and_cell(session_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
describe "queue_cell_evaluation/2" do
test "sends a queue evaluation operation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
{_section_id, cell_id} = insert_section_and_cell(session_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
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)
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
describe "cancel_cell_evaluation/2" do
test "sends a cancel evaluation operation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
{_section_id, cell_id} = insert_section_and_cell(session_id)
queue_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
describe "set_notebook_name/2" do
test "sends a notebook name update operation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
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
describe "set_section_name/3" do
test "sends a section name update operation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
{section_id, _cell_id} = insert_section_and_cell(session_id)
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
describe "apply_cell_delta/5" do
test "sends a cell delta operation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
{_section_id, cell_id} = insert_section_and_cell(session_id)
from = self()
delta = Delta.new() |> Delta.insert("cats")
revision = 1
Session.apply_cell_delta(session_id, from, cell_id, delta, revision)
assert_receive {:operation, {:apply_cell_delta, ^from, ^cell_id, ^delta, ^revision}}
Session.apply_cell_delta(session_id, cell_id, delta, revision)
assert_receive {:operation, {:apply_cell_delta, ^pid, ^cell_id, ^delta, ^revision}}
end
end
describe "connect_runtime/2" do
test "sends a runtime update operation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
{:ok, runtime} = LiveBookTest.Runtime.SingleEvaluator.init()
Session.connect_runtime(session_id, runtime)
assert_receive {:operation, {:set_runtime, ^runtime}}
assert_receive {:operation, {:set_runtime, ^pid, ^runtime}}
end
end
describe "disconnect_runtime/1" do
test "sends a runtime update operation to subscribers", %{session_id: session_id} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
Session.disconnect_runtime(session_id)
assert_receive {:operation, {:set_runtime, nil}}
assert_receive {:operation, {:set_runtime, ^pid, nil}}
end
end
@ -145,11 +155,12 @@ defmodule LiveBook.SessionTest do
test "sends a path update operation to subscribers",
%{session_id: session_id, tmp_dir: tmp_dir} do
Phoenix.PubSub.subscribe(LiveBook.PubSub, "sessions:#{session_id}")
pid = self()
path = Path.join(tmp_dir, "notebook.livemd")
Session.set_path(session_id, path)
assert_receive {:operation, {:set_path, ^path}}
assert_receive {:operation, {:set_path, ^pid, ^path}}
end
@tag :tmp_dir
@ -181,7 +192,7 @@ defmodule LiveBook.SessionTest do
Session.save(session_id)
assert_receive {:operation, :mark_as_not_dirty}
assert_receive {:operation, {:mark_as_not_dirty, _}}
assert File.exists?(path)
assert File.read!(path) =~ "My notebook"
end
@ -234,7 +245,7 @@ defmodule LiveBook.SessionTest do
Session.queue_cell_evaluation(session_id, cell_id)
# 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
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
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
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"}
end
@ -267,15 +278,15 @@ defmodule LiveBook.SessionTest do
defp insert_section_and_cell(session_id) do
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)
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}
end
defp queue_evaluation(session_id, cell_id) do
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

View file

@ -197,7 +197,7 @@ defmodule LiveBookWeb.SessionLiveTest do
%{notebook: %{sections: [%{cells: [cell]}]}} = Session.get_data(session_id)
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
end