2021-03-04 05:56:28 +08:00
|
|
|
defmodule LivebookWeb.SessionLiveTest do
|
|
|
|
use LivebookWeb.ConnCase
|
2021-01-30 07:33:04 +08:00
|
|
|
|
|
|
|
import Phoenix.LiveViewTest
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
alias Livebook.{Sessions, Session, Delta, Runtime, Users, FileSystem}
|
2021-06-08 18:33:50 +08:00
|
|
|
alias Livebook.Notebook.Cell
|
2021-05-04 02:03:19 +08:00
|
|
|
alias Livebook.Users.User
|
2021-01-30 07:33:04 +08:00
|
|
|
|
|
|
|
setup do
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, session} = Sessions.create_session(notebook: Livebook.Notebook.new())
|
|
|
|
%{session: session}
|
2021-01-30 07:33:04 +08:00
|
|
|
end
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
test "disconnected and connected render", %{conn: conn, session: session} do
|
|
|
|
{:ok, view, disconnected_html} = live(conn, "/sessions/#{session.id}")
|
2021-01-30 07:33:04 +08:00
|
|
|
assert disconnected_html =~ "Untitled notebook"
|
|
|
|
assert render(view) =~ "Untitled notebook"
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "asynchronous updates" do
|
2021-09-05 01:16:01 +08:00
|
|
|
test "renders an updated notebook name", %{conn: conn, session: session} do
|
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-01-30 07:33:04 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.set_notebook_name(session.pid, "My notebook")
|
|
|
|
wait_for_session_update(session.pid)
|
2021-01-30 07:33:04 +08:00
|
|
|
|
|
|
|
assert render(view) =~ "My notebook"
|
|
|
|
end
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
test "renders a newly inserted section", %{conn: conn, session: session} do
|
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-01-30 07:33:04 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
section_id = insert_section(session.pid)
|
2021-01-30 07:33:04 +08:00
|
|
|
|
2021-02-03 02:58:06 +08:00
|
|
|
assert render(view) =~ section_id
|
2021-01-30 07:33:04 +08:00
|
|
|
end
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
test "renders an updated section name", %{conn: conn, session: session} do
|
|
|
|
section_id = insert_section(session.pid)
|
2021-01-30 07:33:04 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-01-30 07:33:04 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.set_section_name(session.pid, section_id, "My section")
|
|
|
|
wait_for_session_update(session.pid)
|
2021-01-30 07:33:04 +08:00
|
|
|
|
|
|
|
assert render(view) =~ "My section"
|
|
|
|
end
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
test "renders a newly inserted cell", %{conn: conn, session: session} do
|
|
|
|
section_id = insert_section(session.pid)
|
2021-01-30 07:33:04 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-01-30 07:33:04 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
cell_id = insert_text_cell(session.pid, section_id, :markdown)
|
2021-01-30 07:33:04 +08:00
|
|
|
|
2021-08-30 22:33:40 +08:00
|
|
|
assert render(view) =~ "cell-" <> cell_id
|
2021-01-30 07:33:04 +08:00
|
|
|
end
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
test "un-renders a deleted cell", %{conn: conn, session: session} do
|
|
|
|
section_id = insert_section(session.pid)
|
|
|
|
cell_id = insert_text_cell(session.pid, section_id, :markdown)
|
2021-01-30 07:33:04 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-01-30 07:33:04 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.delete_cell(session.pid, cell_id)
|
|
|
|
wait_for_session_update(session.pid)
|
2021-01-30 07:33:04 +08:00
|
|
|
|
2021-08-30 22:33:40 +08:00
|
|
|
refute render(view) =~ "cell-" <> cell_id
|
2021-01-30 07:33:04 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-02-04 23:01:59 +08:00
|
|
|
describe "UI events update the shared state" do
|
2021-09-05 01:16:01 +08:00
|
|
|
test "adding a new section", %{conn: conn, session: session} do
|
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-01-30 07:33:04 +08:00
|
|
|
|
|
|
|
view
|
|
|
|
|> element("button", "New section")
|
|
|
|
|> render_click()
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
assert %{notebook: %{sections: [_section]}} = Session.get_data(session.pid)
|
2021-01-30 07:33:04 +08:00
|
|
|
end
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
test "adding a new cell", %{conn: conn, session: session} do
|
|
|
|
Session.insert_section(session.pid, 0)
|
2021-01-30 07:33:04 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-01-30 07:33:04 +08:00
|
|
|
|
|
|
|
view
|
|
|
|
|> element("button", "+ Markdown")
|
|
|
|
|> render_click()
|
|
|
|
|
2021-06-08 18:33:50 +08:00
|
|
|
assert %{notebook: %{sections: [%{cells: [%Cell.Markdown{}]}]}} =
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.get_data(session.pid)
|
2021-01-30 07:33:04 +08:00
|
|
|
end
|
2021-02-03 02:58:06 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
test "queueing cell evaluation", %{conn: conn, session: session} do
|
|
|
|
section_id = insert_section(session.pid)
|
2021-10-14 04:25:33 +08:00
|
|
|
cell_id = insert_text_cell(session.pid, section_id, :elixir, "Process.sleep(50)")
|
2021-02-03 02:58:06 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-02-03 02:58:06 +08:00
|
|
|
|
|
|
|
view
|
2021-11-17 04:57:10 +08:00
|
|
|
|> element(~s{[data-element="session"]})
|
2021-03-11 22:28:18 +08:00
|
|
|
|> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id})
|
2021-02-04 23:01:59 +08:00
|
|
|
|
|
|
|
assert %{cell_infos: %{^cell_id => %{evaluation_status: :evaluating}}} =
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.get_data(session.pid)
|
2021-02-04 23:01:59 +08:00
|
|
|
end
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
test "cancelling cell evaluation", %{conn: conn, session: session} do
|
|
|
|
section_id = insert_section(session.pid)
|
|
|
|
cell_id = insert_text_cell(session.pid, section_id, :elixir, "Process.sleep(2000)")
|
2021-02-22 21:21:28 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-02-22 21:21:28 +08:00
|
|
|
|
|
|
|
view
|
2021-11-17 04:57:10 +08:00
|
|
|
|> element(~s{[data-element="session"]})
|
2021-03-11 22:28:18 +08:00
|
|
|
|> render_hook("queue_cell_evaluation", %{"cell_id" => cell_id})
|
2021-02-22 21:21:28 +08:00
|
|
|
|
|
|
|
view
|
2021-11-17 04:57:10 +08:00
|
|
|
|> element(~s{[data-element="session"]})
|
2021-03-11 22:28:18 +08:00
|
|
|
|> render_hook("cancel_cell_evaluation", %{"cell_id" => cell_id})
|
2021-02-22 21:21:28 +08:00
|
|
|
|
|
|
|
assert %{cell_infos: %{^cell_id => %{evaluation_status: :ready}}} =
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.get_data(session.pid)
|
2021-02-22 21:21:28 +08:00
|
|
|
end
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
test "inserting a cell below the given cell", %{conn: conn, session: session} do
|
|
|
|
section_id = insert_section(session.pid)
|
|
|
|
cell_id = insert_text_cell(session.pid, section_id, :elixir)
|
2021-02-04 23:01:59 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-02-04 23:01:59 +08:00
|
|
|
|
2021-02-03 02:58:06 +08:00
|
|
|
view
|
2021-11-17 04:57:10 +08:00
|
|
|
|> element(~s{[data-element="session"]})
|
2021-03-11 22:28:18 +08:00
|
|
|
|> render_hook("insert_cell_below", %{"cell_id" => cell_id, "type" => "markdown"})
|
2021-02-03 02:58:06 +08:00
|
|
|
|
2021-06-08 18:33:50 +08:00
|
|
|
assert %{notebook: %{sections: [%{cells: [_first_cell, %Cell.Markdown{}]}]}} =
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.get_data(session.pid)
|
2021-02-04 23:01:59 +08:00
|
|
|
end
|
|
|
|
|
2021-11-17 04:57:10 +08:00
|
|
|
test "inserting a cell at section start", %{conn: conn, session: session} do
|
2021-09-05 01:16:01 +08:00
|
|
|
section_id = insert_section(session.pid)
|
2021-11-17 04:57:10 +08:00
|
|
|
_cell_id = insert_text_cell(session.pid, section_id, :elixir)
|
2021-02-04 23:01:59 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-02-04 23:01:59 +08:00
|
|
|
|
|
|
|
view
|
2021-11-17 04:57:10 +08:00
|
|
|
|> element(~s{[data-element="session"]})
|
|
|
|
|> render_hook("insert_cell_below", %{"section_id" => section_id, "type" => "markdown"})
|
2021-02-04 23:01:59 +08:00
|
|
|
|
2021-06-08 18:33:50 +08:00
|
|
|
assert %{notebook: %{sections: [%{cells: [%Cell.Markdown{}, _first_cell]}]}} =
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.get_data(session.pid)
|
2021-02-03 02:58:06 +08:00
|
|
|
end
|
2021-02-04 23:01:59 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
test "deleting the given cell", %{conn: conn, session: session} do
|
|
|
|
section_id = insert_section(session.pid)
|
|
|
|
cell_id = insert_text_cell(session.pid, section_id, :elixir)
|
2021-02-04 23:01:59 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-02-04 23:01:59 +08:00
|
|
|
|
|
|
|
view
|
2021-11-17 04:57:10 +08:00
|
|
|
|> element(~s{[data-element="session"]})
|
2021-03-11 22:28:18 +08:00
|
|
|
|> render_hook("delete_cell", %{"cell_id" => cell_id})
|
2021-02-04 23:01:59 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
assert %{notebook: %{sections: [%{cells: []}]}} = Session.get_data(session.pid)
|
2021-02-04 23:01:59 +08:00
|
|
|
end
|
2021-06-26 06:53:12 +08:00
|
|
|
|
2021-11-26 01:43:42 +08:00
|
|
|
test "editing input field in cell output", %{conn: conn, session: session} do
|
2021-09-05 01:16:01 +08:00
|
|
|
section_id = insert_section(session.pid)
|
2021-11-26 01:43:42 +08:00
|
|
|
|
|
|
|
insert_cell_with_input(session.pid, section_id, %{
|
|
|
|
id: "input1",
|
|
|
|
type: :number,
|
|
|
|
label: "Name",
|
|
|
|
default: "hey"
|
|
|
|
})
|
2021-06-26 06:53:12 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-06-26 06:53:12 +08:00
|
|
|
|
|
|
|
view
|
2021-11-26 01:43:42 +08:00
|
|
|
|> element(~s/[data-element="outputs-container"] form/)
|
|
|
|
|> render_change(%{"value" => "10"})
|
|
|
|
|
|
|
|
assert %{input_values: %{"input1" => 10}} = Session.get_data(session.pid)
|
|
|
|
end
|
|
|
|
|
|
|
|
test "newlines in text input are normalized", %{conn: conn, session: session} do
|
|
|
|
section_id = insert_section(session.pid)
|
|
|
|
|
|
|
|
insert_cell_with_input(session.pid, section_id, %{
|
|
|
|
id: "input1",
|
|
|
|
type: :textarea,
|
|
|
|
label: "Name",
|
|
|
|
default: "hey"
|
|
|
|
})
|
|
|
|
|
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
|
|
|
|
|
|
|
view
|
|
|
|
|> element(~s/[data-element="outputs-container"] form/)
|
2021-06-26 06:53:12 +08:00
|
|
|
|> render_change(%{"value" => "line\r\nline"})
|
|
|
|
|
2021-11-26 01:43:42 +08:00
|
|
|
assert %{input_values: %{"input1" => "line\nline"}} = Session.get_data(session.pid)
|
2021-06-26 06:53:12 +08:00
|
|
|
end
|
2021-01-30 07:33:04 +08:00
|
|
|
end
|
|
|
|
|
2021-03-24 02:41:03 +08:00
|
|
|
describe "runtime settings" do
|
|
|
|
test "connecting to elixir standalone updates connect button to reconnect",
|
2021-09-05 01:16:01 +08:00
|
|
|
%{conn: conn, session: session} do
|
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}/settings/runtime")
|
2021-03-24 02:41:03 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
Phoenix.PubSub.subscribe(Livebook.PubSub, "sessions:#{session.id}")
|
2021-03-24 02:41:03 +08:00
|
|
|
|
2021-10-29 01:41:07 +08:00
|
|
|
view
|
|
|
|
|> element("button", "Elixir standalone")
|
|
|
|
|> render_click()
|
|
|
|
|
2021-03-24 02:41:03 +08:00
|
|
|
[elixir_standalone_view] = live_children(view)
|
|
|
|
|
|
|
|
elixir_standalone_view
|
|
|
|
|> element("button", "Connect")
|
|
|
|
|> render_click()
|
|
|
|
|
|
|
|
assert_receive {:operation, {:set_runtime, _pid, %Runtime.ElixirStandalone{} = runtime}}
|
|
|
|
|
|
|
|
page = render(view)
|
|
|
|
assert page =~ Atom.to_string(runtime.node)
|
|
|
|
assert page =~ "Reconnect"
|
|
|
|
assert page =~ "Disconnect"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-04-22 05:02:09 +08:00
|
|
|
describe "persistence settings" do
|
2021-08-14 03:17:43 +08:00
|
|
|
@tag :tmp_dir
|
2021-10-30 18:02:26 +08:00
|
|
|
test "saving to file shows the newly created file in file selector",
|
2021-09-05 01:16:01 +08:00
|
|
|
%{conn: conn, session: session, tmp_dir: tmp_dir} do
|
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}/settings/file")
|
2021-04-22 05:02:09 +08:00
|
|
|
|
2021-08-14 03:17:43 +08:00
|
|
|
assert view = find_live_child(view, "persistence")
|
|
|
|
|
2021-04-22 05:02:09 +08:00
|
|
|
path = Path.join(tmp_dir, "notebook.livemd")
|
|
|
|
|
|
|
|
view
|
2021-10-30 18:02:26 +08:00
|
|
|
|> element("button", "Choose a file")
|
2021-04-22 05:02:09 +08:00
|
|
|
|> render_click()
|
|
|
|
|
|
|
|
view
|
2021-07-30 22:24:46 +08:00
|
|
|
|> element(~s{form[phx-change="set_path"]})
|
2021-04-22 05:02:09 +08:00
|
|
|
|> render_change(%{path: path})
|
|
|
|
|
|
|
|
view
|
2021-10-30 18:02:26 +08:00
|
|
|
|> element(~s{button[phx-click="confirm_file"]}, "Choose")
|
|
|
|
|> render_click()
|
|
|
|
|
|
|
|
view
|
|
|
|
|> element(~s{button}, "Save now")
|
|
|
|
|> render_click()
|
|
|
|
|
|
|
|
view
|
|
|
|
|> element("button", "Change file")
|
2021-04-22 05:02:09 +08:00
|
|
|
|> render_click()
|
|
|
|
|
|
|
|
assert view
|
|
|
|
|> element("button", "notebook.livemd")
|
|
|
|
|> has_element?()
|
|
|
|
end
|
2021-07-30 22:24:46 +08:00
|
|
|
|
2021-08-14 03:17:43 +08:00
|
|
|
@tag :tmp_dir
|
|
|
|
test "changing output persistence updates data",
|
2021-09-05 01:16:01 +08:00
|
|
|
%{conn: conn, session: session, tmp_dir: tmp_dir} do
|
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}/settings/file")
|
2021-07-30 22:24:46 +08:00
|
|
|
|
2021-08-14 03:17:43 +08:00
|
|
|
assert view = find_live_child(view, "persistence")
|
|
|
|
|
|
|
|
path = Path.join(tmp_dir, "notebook.livemd")
|
|
|
|
|
2021-07-30 22:24:46 +08:00
|
|
|
view
|
2021-10-30 18:02:26 +08:00
|
|
|
|> element("button", "Choose a file")
|
2021-07-30 22:24:46 +08:00
|
|
|
|> render_click()
|
|
|
|
|
2021-08-14 03:17:43 +08:00
|
|
|
view
|
|
|
|
|> element(~s{form[phx-change="set_path"]})
|
|
|
|
|> render_change(%{path: path})
|
|
|
|
|
2021-10-30 18:02:26 +08:00
|
|
|
view
|
|
|
|
|> element(~s{button[phx-click="confirm_file"]}, "Choose")
|
|
|
|
|> render_click()
|
|
|
|
|
2021-07-30 22:24:46 +08:00
|
|
|
view
|
|
|
|
|> element(~s{form[phx-change="set_options"]})
|
|
|
|
|> render_change(%{persist_outputs: "true"})
|
|
|
|
|
|
|
|
view
|
2021-10-30 18:02:26 +08:00
|
|
|
|> element(~s{button}, "Save now")
|
2021-07-30 22:24:46 +08:00
|
|
|
|> render_click()
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
assert %{notebook: %{persist_outputs: true}} = Session.get_data(session.pid)
|
2021-07-30 22:24:46 +08:00
|
|
|
end
|
2021-04-22 05:02:09 +08:00
|
|
|
end
|
|
|
|
|
2021-04-21 01:34:17 +08:00
|
|
|
describe "completion" do
|
|
|
|
test "replies with nil completion reference when no runtime is started",
|
2021-09-05 01:16:01 +08:00
|
|
|
%{conn: conn, session: session} do
|
|
|
|
section_id = insert_section(session.pid)
|
|
|
|
cell_id = insert_text_cell(session.pid, section_id, :elixir, "Process.sleep(10)")
|
2021-04-21 01:34:17 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-04-21 01:34:17 +08:00
|
|
|
|
|
|
|
view
|
2021-11-17 04:57:10 +08:00
|
|
|
|> element(~s{[data-element="session"]})
|
2021-07-21 03:30:53 +08:00
|
|
|
|> render_hook("intellisense_request", %{
|
|
|
|
"cell_id" => cell_id,
|
|
|
|
"type" => "completion",
|
|
|
|
"hint" => "System.ver"
|
|
|
|
})
|
2021-04-21 01:34:17 +08:00
|
|
|
|
2021-07-21 03:30:53 +08:00
|
|
|
assert_reply view, %{"ref" => nil}
|
2021-04-21 01:34:17 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
test "replies with completion reference and then sends asynchronous response",
|
2021-09-05 01:16:01 +08:00
|
|
|
%{conn: conn, session: session} do
|
|
|
|
section_id = insert_section(session.pid)
|
|
|
|
cell_id = insert_text_cell(session.pid, section_id, :elixir, "Process.sleep(10)")
|
2021-04-21 01:34:17 +08:00
|
|
|
|
2021-05-10 20:37:38 +08:00
|
|
|
{:ok, runtime} = Livebook.Runtime.Embedded.init()
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.connect_runtime(session.pid, runtime)
|
2021-04-21 01:34:17 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-04-21 01:34:17 +08:00
|
|
|
|
|
|
|
view
|
2021-11-17 04:57:10 +08:00
|
|
|
|> element(~s{[data-element="session"]})
|
2021-07-21 03:30:53 +08:00
|
|
|
|> render_hook("intellisense_request", %{
|
|
|
|
"cell_id" => cell_id,
|
|
|
|
"type" => "completion",
|
|
|
|
"hint" => "System.ver"
|
|
|
|
})
|
2021-04-21 01:34:17 +08:00
|
|
|
|
2021-07-21 03:30:53 +08:00
|
|
|
assert_reply view, %{"ref" => ref}
|
2021-04-21 01:34:17 +08:00
|
|
|
assert ref != nil
|
|
|
|
|
2021-07-21 03:30:53 +08:00
|
|
|
assert_push_event(view, "intellisense_response", %{
|
|
|
|
"ref" => ^ref,
|
|
|
|
"response" => %{items: [%{label: "version/0"}]}
|
2021-04-21 01:34:17 +08:00
|
|
|
})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
test "forking the session", %{conn: conn, session: session} do
|
|
|
|
Session.set_notebook_name(session.pid, "My notebook")
|
|
|
|
wait_for_session_update(session.pid)
|
2021-04-22 05:02:09 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-04-22 05:02:09 +08:00
|
|
|
|
|
|
|
assert {:error, {:live_redirect, %{to: to}}} =
|
|
|
|
view
|
|
|
|
|> element("button", "Fork")
|
|
|
|
|> render_click()
|
|
|
|
|
|
|
|
assert to =~ "/sessions/"
|
|
|
|
|
|
|
|
{:ok, view, _} = live(conn, to)
|
|
|
|
assert render(view) =~ "My notebook - fork"
|
|
|
|
end
|
|
|
|
|
2021-05-04 02:03:19 +08:00
|
|
|
describe "connected users" do
|
2021-09-05 01:16:01 +08:00
|
|
|
test "lists connected users", %{conn: conn, session: session} do
|
2021-05-04 02:03:19 +08:00
|
|
|
user1 = create_user_with_name("Jake Peralta")
|
|
|
|
|
|
|
|
client_pid =
|
|
|
|
spawn_link(fn ->
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.register_client(session.pid, self(), user1)
|
2021-05-04 02:03:19 +08:00
|
|
|
|
|
|
|
receive do
|
|
|
|
:stop -> :ok
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-05-04 02:03:19 +08:00
|
|
|
|
|
|
|
assert render(view) =~ "Jake Peralta"
|
|
|
|
|
|
|
|
send(client_pid, :stop)
|
|
|
|
end
|
|
|
|
|
|
|
|
test "updates users list whenever a user joins or leaves",
|
2021-09-05 01:16:01 +08:00
|
|
|
%{conn: conn, session: session} do
|
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-05-04 02:03:19 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
Phoenix.PubSub.subscribe(Livebook.PubSub, "sessions:#{session.id}")
|
2021-05-04 02:03:19 +08:00
|
|
|
|
|
|
|
user1 = create_user_with_name("Jake Peralta")
|
|
|
|
|
|
|
|
client_pid =
|
|
|
|
spawn_link(fn ->
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.register_client(session.pid, self(), user1)
|
2021-05-04 02:03:19 +08:00
|
|
|
|
|
|
|
receive do
|
|
|
|
:stop -> :ok
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
assert_receive {:operation, {:client_join, ^client_pid, _user}}
|
|
|
|
assert render(view) =~ "Jake Peralta"
|
|
|
|
|
|
|
|
send(client_pid, :stop)
|
|
|
|
assert_receive {:operation, {:client_leave, ^client_pid}}
|
|
|
|
refute render(view) =~ "Jake Peralta"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "updates users list whenever a user changes his data",
|
2021-09-05 01:16:01 +08:00
|
|
|
%{conn: conn, session: session} do
|
2021-05-04 02:03:19 +08:00
|
|
|
user1 = create_user_with_name("Jake Peralta")
|
|
|
|
|
|
|
|
client_pid =
|
|
|
|
spawn_link(fn ->
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.register_client(session.pid, self(), user1)
|
2021-05-04 02:03:19 +08:00
|
|
|
|
|
|
|
receive do
|
|
|
|
:stop -> :ok
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
2021-05-04 02:03:19 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
Phoenix.PubSub.subscribe(Livebook.PubSub, "sessions:#{session.id}")
|
2021-05-04 02:03:19 +08:00
|
|
|
|
|
|
|
assert render(view) =~ "Jake Peralta"
|
|
|
|
|
|
|
|
Users.broadcast_change(%{user1 | name: "Raymond Holt"})
|
|
|
|
assert_receive {:operation, {:update_user, _pid, _user}}
|
|
|
|
|
|
|
|
refute render(view) =~ "Jake Peralta"
|
|
|
|
assert render(view) =~ "Raymond Holt"
|
|
|
|
|
|
|
|
send(client_pid, :stop)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-07-09 01:35:11 +08:00
|
|
|
describe "relative paths" do
|
|
|
|
test "renders an info message when the path doesn't have notebook extension",
|
2021-09-05 01:16:01 +08:00
|
|
|
%{conn: conn, session: session} do
|
|
|
|
session_path = "/sessions/#{session.id}"
|
2021-07-09 01:35:11 +08:00
|
|
|
|
|
|
|
assert {:error, {:live_redirect, %{to: ^session_path}}} =
|
2021-09-05 01:16:01 +08:00
|
|
|
result = live(conn, "/sessions/#{session.id}/document.pdf")
|
2021-07-09 01:35:11 +08:00
|
|
|
|
|
|
|
{:ok, view, _} = follow_redirect(result, conn)
|
|
|
|
assert render(view) =~ "Got unrecognised session path: document.pdf"
|
|
|
|
end
|
|
|
|
|
2021-07-11 03:49:50 +08:00
|
|
|
test "renders an info message when the session has neither original url nor path",
|
2021-09-05 01:16:01 +08:00
|
|
|
%{conn: conn, session: session} do
|
|
|
|
session_path = "/sessions/#{session.id}"
|
2021-07-09 01:35:11 +08:00
|
|
|
|
|
|
|
assert {:error, {:live_redirect, %{to: ^session_path}}} =
|
2021-09-05 01:16:01 +08:00
|
|
|
result = live(conn, "/sessions/#{session.id}/notebook.livemd")
|
2021-07-09 01:35:11 +08:00
|
|
|
|
|
|
|
{:ok, view, _} = follow_redirect(result, conn)
|
|
|
|
|
|
|
|
assert render(view) =~
|
2021-07-11 03:49:50 +08:00
|
|
|
"Cannot resolve notebook path notebook.livemd, because the current notebook has no location"
|
2021-07-09 01:35:11 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
@tag :tmp_dir
|
|
|
|
test "renders an error message when the relative notebook does not exist",
|
2021-09-05 01:16:01 +08:00
|
|
|
%{conn: conn, session: session, tmp_dir: tmp_dir} do
|
2021-08-14 03:17:43 +08:00
|
|
|
tmp_dir = FileSystem.File.local(tmp_dir <> "/")
|
|
|
|
index_file = FileSystem.File.resolve(tmp_dir, "index.livemd")
|
|
|
|
notebook_file = FileSystem.File.resolve(tmp_dir, "notebook.livemd")
|
2021-07-09 01:35:11 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.set_file(session.pid, index_file)
|
|
|
|
wait_for_session_update(session.pid)
|
2021-07-09 01:35:11 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
session_path = "/sessions/#{session.id}"
|
2021-07-09 01:35:11 +08:00
|
|
|
|
|
|
|
assert {:error, {:live_redirect, %{to: ^session_path}}} =
|
2021-09-05 01:16:01 +08:00
|
|
|
result = live(conn, "/sessions/#{session.id}/notebook.livemd")
|
2021-07-09 01:35:11 +08:00
|
|
|
|
|
|
|
{:ok, view, _} = follow_redirect(result, conn)
|
|
|
|
|
|
|
|
assert render(view) =~
|
2021-08-14 03:17:43 +08:00
|
|
|
"Cannot navigate, failed to read #{notebook_file.path}, reason: no such file or directory"
|
2021-07-09 01:35:11 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
@tag :tmp_dir
|
|
|
|
test "opens a relative notebook if it exists",
|
2021-09-05 01:16:01 +08:00
|
|
|
%{conn: conn, session: session, tmp_dir: tmp_dir} do
|
2021-08-14 03:17:43 +08:00
|
|
|
tmp_dir = FileSystem.File.local(tmp_dir <> "/")
|
|
|
|
index_file = FileSystem.File.resolve(tmp_dir, "index.livemd")
|
|
|
|
notebook_file = FileSystem.File.resolve(tmp_dir, "notebook.livemd")
|
2021-07-09 01:35:11 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.set_file(session.pid, index_file)
|
|
|
|
wait_for_session_update(session.pid)
|
2021-07-09 01:35:11 +08:00
|
|
|
|
2021-08-14 03:17:43 +08:00
|
|
|
:ok = FileSystem.File.write(notebook_file, "# Sibling notebook")
|
2021-07-09 01:35:11 +08:00
|
|
|
|
2021-07-11 03:49:50 +08:00
|
|
|
assert {:error, {:live_redirect, %{to: new_session_path}}} =
|
2021-09-05 01:16:01 +08:00
|
|
|
result = live(conn, "/sessions/#{session.id}/notebook.livemd")
|
2021-07-09 01:35:11 +08:00
|
|
|
|
|
|
|
{:ok, view, _} = follow_redirect(result, conn)
|
|
|
|
assert render(view) =~ "Sibling notebook"
|
2021-07-11 03:49:50 +08:00
|
|
|
|
|
|
|
"/sessions/" <> session_id = new_session_path
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, session} = Sessions.fetch_session(session_id)
|
|
|
|
data = Session.get_data(session.pid)
|
2021-08-14 03:17:43 +08:00
|
|
|
assert data.file == notebook_file
|
2021-07-11 03:49:50 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
@tag :tmp_dir
|
|
|
|
test "if the current session has no path, forks the relative notebook",
|
|
|
|
%{conn: conn, tmp_dir: tmp_dir} do
|
2021-08-14 03:17:43 +08:00
|
|
|
tmp_dir = FileSystem.File.local(tmp_dir <> "/")
|
|
|
|
index_file = FileSystem.File.resolve(tmp_dir, "index.livemd")
|
|
|
|
notebook_file = FileSystem.File.resolve(tmp_dir, "notebook.livemd")
|
2021-07-11 03:49:50 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, session} = Sessions.create_session(origin: {:file, index_file})
|
2021-07-11 03:49:50 +08:00
|
|
|
|
2021-08-14 03:17:43 +08:00
|
|
|
:ok = FileSystem.File.write(notebook_file, "# Sibling notebook")
|
2021-07-11 03:49:50 +08:00
|
|
|
|
|
|
|
assert {:error, {:live_redirect, %{to: new_session_path}}} =
|
2021-09-05 01:16:01 +08:00
|
|
|
result = live(conn, "/sessions/#{session.id}/notebook.livemd")
|
2021-07-11 03:49:50 +08:00
|
|
|
|
|
|
|
{:ok, view, _} = follow_redirect(result, conn)
|
|
|
|
assert render(view) =~ "Sibling notebook - fork"
|
|
|
|
|
|
|
|
"/sessions/" <> session_id = new_session_path
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, session} = Sessions.fetch_session(session_id)
|
|
|
|
data = Session.get_data(session.pid)
|
2021-08-14 03:17:43 +08:00
|
|
|
assert data.file == nil
|
|
|
|
assert data.origin == {:file, notebook_file}
|
2021-07-09 01:35:11 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
@tag :tmp_dir
|
|
|
|
test "if the notebook is already open, redirects to the session",
|
2021-09-05 01:16:01 +08:00
|
|
|
%{conn: conn, session: session, tmp_dir: tmp_dir} do
|
2021-08-14 03:17:43 +08:00
|
|
|
tmp_dir = FileSystem.File.local(tmp_dir <> "/")
|
|
|
|
index_file = FileSystem.File.resolve(tmp_dir, "index.livemd")
|
|
|
|
notebook_file = FileSystem.File.resolve(tmp_dir, "notebook.livemd")
|
2021-07-09 01:35:11 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.set_file(session.pid, index_file)
|
|
|
|
wait_for_session_update(session.pid)
|
2021-07-09 01:35:11 +08:00
|
|
|
|
2021-08-14 03:17:43 +08:00
|
|
|
:ok = FileSystem.File.write(notebook_file, "# Sibling notebook")
|
2021-07-09 01:35:11 +08:00
|
|
|
|
|
|
|
assert {:error, {:live_redirect, %{to: session_path}}} =
|
2021-09-05 01:16:01 +08:00
|
|
|
live(conn, "/sessions/#{session.id}/notebook.livemd")
|
2021-07-09 01:35:11 +08:00
|
|
|
|
|
|
|
assert {:error, {:live_redirect, %{to: ^session_path}}} =
|
2021-09-05 01:16:01 +08:00
|
|
|
live(conn, "/sessions/#{session.id}/notebook.livemd")
|
2021-07-09 01:35:11 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
@tag :tmp_dir
|
2021-09-05 01:16:01 +08:00
|
|
|
test "handles nested paths", %{conn: conn, session: session, tmp_dir: tmp_dir} do
|
2021-08-14 03:17:43 +08:00
|
|
|
tmp_dir = FileSystem.File.local(tmp_dir <> "/")
|
|
|
|
parent_file = FileSystem.File.resolve(tmp_dir, "parent.livemd")
|
|
|
|
child_dir = FileSystem.File.resolve(tmp_dir, "dir/")
|
|
|
|
child_file = FileSystem.File.resolve(child_dir, "child.livemd")
|
2021-07-09 01:35:11 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.set_file(session.pid, parent_file)
|
|
|
|
wait_for_session_update(session.pid)
|
2021-07-09 01:35:11 +08:00
|
|
|
|
2021-08-14 03:17:43 +08:00
|
|
|
:ok = FileSystem.File.write(child_file, "# Child notebook")
|
2021-07-09 01:35:11 +08:00
|
|
|
|
|
|
|
{:ok, view, _} =
|
|
|
|
conn
|
2021-09-05 01:16:01 +08:00
|
|
|
|> live("/sessions/#{session.id}/dir/child.livemd")
|
2021-07-09 01:35:11 +08:00
|
|
|
|> follow_redirect(conn)
|
|
|
|
|
|
|
|
assert render(view) =~ "Child notebook"
|
|
|
|
end
|
|
|
|
|
|
|
|
@tag :tmp_dir
|
2021-09-05 01:16:01 +08:00
|
|
|
test "handles parent paths", %{conn: conn, session: session, tmp_dir: tmp_dir} do
|
2021-08-14 03:17:43 +08:00
|
|
|
tmp_dir = FileSystem.File.local(tmp_dir <> "/")
|
|
|
|
parent_file = FileSystem.File.resolve(tmp_dir, "parent.livemd")
|
|
|
|
child_dir = FileSystem.File.resolve(tmp_dir, "dir/")
|
|
|
|
child_file = FileSystem.File.resolve(child_dir, "child.livemd")
|
2021-07-09 01:35:11 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.set_file(session.pid, child_file)
|
|
|
|
wait_for_session_update(session.pid)
|
2021-07-09 01:35:11 +08:00
|
|
|
|
2021-08-14 03:17:43 +08:00
|
|
|
:ok = FileSystem.File.write(parent_file, "# Parent notebook")
|
2021-07-09 01:35:11 +08:00
|
|
|
|
|
|
|
{:ok, view, _} =
|
|
|
|
conn
|
2021-09-05 01:16:01 +08:00
|
|
|
|> live("/sessions/#{session.id}/__parent__/parent.livemd")
|
2021-07-09 01:35:11 +08:00
|
|
|
|> follow_redirect(conn)
|
|
|
|
|
|
|
|
assert render(view) =~ "Parent notebook"
|
|
|
|
end
|
2021-07-11 03:49:50 +08:00
|
|
|
|
|
|
|
test "resolves remote URLs", %{conn: conn} do
|
|
|
|
bypass = Bypass.open()
|
|
|
|
|
|
|
|
Bypass.expect_once(bypass, "GET", "/notebook.livemd", fn conn ->
|
|
|
|
conn
|
|
|
|
|> Plug.Conn.put_resp_content_type("text/plain")
|
|
|
|
|> Plug.Conn.resp(200, "# My notebook")
|
|
|
|
end)
|
|
|
|
|
|
|
|
index_url = url(bypass.port) <> "/index.livemd"
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, session} = Sessions.create_session(origin: {:url, index_url})
|
2021-07-11 03:49:50 +08:00
|
|
|
|
|
|
|
{:ok, view, _} =
|
|
|
|
conn
|
2021-09-05 01:16:01 +08:00
|
|
|
|> live("/sessions/#{session.id}/notebook.livemd")
|
2021-07-11 03:49:50 +08:00
|
|
|
|> follow_redirect(conn)
|
|
|
|
|
|
|
|
assert render(view) =~ "My notebook"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "renders an error message if relative remote notebook cannot be loaded", %{conn: conn} do
|
|
|
|
bypass = Bypass.open()
|
|
|
|
|
|
|
|
Bypass.expect_once(bypass, "GET", "/notebook.livemd", fn conn ->
|
|
|
|
Plug.Conn.resp(conn, 500, "Error")
|
|
|
|
end)
|
|
|
|
|
|
|
|
index_url = url(bypass.port) <> "/index.livemd"
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, session} = Sessions.create_session(origin: {:url, index_url})
|
2021-07-11 03:49:50 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
session_path = "/sessions/#{session.id}"
|
2021-07-11 03:49:50 +08:00
|
|
|
|
|
|
|
assert {:error, {:live_redirect, %{to: ^session_path}}} =
|
2021-09-05 01:16:01 +08:00
|
|
|
result = live(conn, "/sessions/#{session.id}/notebook.livemd")
|
2021-07-11 03:49:50 +08:00
|
|
|
|
|
|
|
{:ok, view, _} = follow_redirect(result, conn)
|
|
|
|
assert render(view) =~ "Cannot navigate, failed to download notebook from the given URL"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "if the remote notebook is already imported, redirects to the session",
|
|
|
|
%{conn: conn, test: test} do
|
|
|
|
index_url = "http://example.com/#{test}/index.livemd"
|
|
|
|
notebook_url = "http://example.com/#{test}/notebook.livemd"
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, index_session} = Sessions.create_session(origin: {:url, index_url})
|
|
|
|
{:ok, notebook_session} = Sessions.create_session(origin: {:url, notebook_url})
|
2021-07-11 03:49:50 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
notebook_session_path = "/sessions/#{notebook_session.id}"
|
2021-07-11 03:49:50 +08:00
|
|
|
|
|
|
|
assert {:error, {:live_redirect, %{to: ^notebook_session_path}}} =
|
2021-09-05 01:16:01 +08:00
|
|
|
live(conn, "/sessions/#{index_session.id}/notebook.livemd")
|
2021-07-11 03:49:50 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
test "renders an error message if there are already multiple session imported from the relative URL",
|
|
|
|
%{conn: conn, test: test} do
|
|
|
|
index_url = "http://example.com/#{test}/index.livemd"
|
|
|
|
notebook_url = "http://example.com/#{test}/notebook.livemd"
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
{:ok, index_session} = Sessions.create_session(origin: {:url, index_url})
|
|
|
|
{:ok, _notebook_session1} = Sessions.create_session(origin: {:url, notebook_url})
|
|
|
|
{:ok, _notebook_session2} = Sessions.create_session(origin: {:url, notebook_url})
|
2021-08-14 03:17:43 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
index_session_path = "/sessions/#{index_session.id}"
|
2021-07-11 03:49:50 +08:00
|
|
|
|
|
|
|
assert {:error, {:live_redirect, %{to: ^index_session_path}}} =
|
2021-09-05 01:16:01 +08:00
|
|
|
result = live(conn, "/sessions/#{index_session.id}/notebook.livemd")
|
2021-07-11 03:49:50 +08:00
|
|
|
|
|
|
|
{:ok, view, _} = follow_redirect(result, conn)
|
|
|
|
|
|
|
|
assert render(view) =~
|
|
|
|
"Cannot navigate, because multiple sessions were found for #{notebook_url}"
|
|
|
|
end
|
2021-07-09 01:35:11 +08:00
|
|
|
end
|
|
|
|
|
2021-03-24 02:41:03 +08:00
|
|
|
# Helpers
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
defp wait_for_session_update(session_pid) do
|
2021-01-30 07:33:04 +08:00
|
|
|
# This call is synchronous, so it gives the session time
|
|
|
|
# for handling the previously sent change messages.
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.get_data(session_pid)
|
2021-01-30 07:33:04 +08:00
|
|
|
:ok
|
|
|
|
end
|
2021-02-03 02:58:06 +08:00
|
|
|
|
|
|
|
# Utils for sending session requests, waiting for the change to be applied
|
|
|
|
# and retrieving new ids if applicable.
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
defp insert_section(session_pid) do
|
|
|
|
Session.insert_section(session_pid, 0)
|
|
|
|
%{notebook: %{sections: [section]}} = Session.get_data(session_pid)
|
2021-02-03 02:58:06 +08:00
|
|
|
section.id
|
|
|
|
end
|
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
defp insert_text_cell(session_pid, section_id, type, content \\ "") do
|
|
|
|
Session.insert_cell(session_pid, section_id, 0, type)
|
|
|
|
%{notebook: %{sections: [%{cells: [cell]}]}} = Session.get_data(session_pid)
|
2021-02-03 02:58:06 +08:00
|
|
|
|
2021-05-10 20:37:38 +08:00
|
|
|
# We need to register ourselves as a client to start submitting cell deltas
|
|
|
|
user = Livebook.Users.User.new()
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.register_client(session_pid, self(), user)
|
2021-05-10 20:37:38 +08:00
|
|
|
|
2021-02-03 02:58:06 +08:00
|
|
|
delta = Delta.new(insert: content)
|
2021-09-05 01:16:01 +08:00
|
|
|
Session.apply_cell_delta(session_pid, cell.id, delta, 1)
|
2021-02-03 02:58:06 +08:00
|
|
|
|
2021-09-05 01:16:01 +08:00
|
|
|
wait_for_session_update(session_pid)
|
2021-05-10 20:37:38 +08:00
|
|
|
|
2021-02-03 02:58:06 +08:00
|
|
|
cell.id
|
|
|
|
end
|
2021-05-04 02:03:19 +08:00
|
|
|
|
2021-11-26 01:43:42 +08:00
|
|
|
defp insert_cell_with_input(session_pid, section_id, input) do
|
|
|
|
code =
|
|
|
|
quote do
|
|
|
|
send(
|
|
|
|
Process.group_leader(),
|
|
|
|
{:io_request, self(), make_ref(), {:livebook_put_output, {:input, unquote(input)}}}
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|> Macro.to_string()
|
|
|
|
|
|
|
|
cell_id = insert_text_cell(session_pid, section_id, :elixir, code)
|
|
|
|
Session.queue_cell_evaluation(session_pid, cell_id)
|
|
|
|
cell_id
|
2021-06-26 06:53:12 +08:00
|
|
|
end
|
|
|
|
|
2021-05-04 02:03:19 +08:00
|
|
|
defp create_user_with_name(name) do
|
|
|
|
{:ok, user} = User.new() |> User.change(%{"name" => name})
|
|
|
|
user
|
|
|
|
end
|
2021-07-11 03:49:50 +08:00
|
|
|
|
|
|
|
defp url(port), do: "http://localhost:#{port}"
|
2021-01-30 07:33:04 +08:00
|
|
|
end
|