using Test
import Pluto
import Pluto: update_run!, update_save_run!, WorkspaceManager, ClientSession, ServerSession, Notebook, Cell
import Malt
@testset "Bonds" begin
🍭 = ServerSession()
🍭.options.evaluation.workspace_use_distributed = false
@testset "Don't write to file" begin
notebook = Notebook([
Cell("""
@bind x html""
"""),
Cell("x"),
])
update_save_run!(🍭, notebook, notebook.cells)
old_mtime = mtime(notebook.path)
setcode!(notebook.cells[2], "x #asdf")
update_save_run!(🍭, notebook, notebook.cells[2])
@test old_mtime != mtime(notebook.path)
old_mtime = mtime(notebook.path)
function set_bond_value(name, value, is_first_value=false)
notebook.bonds[name] = Dict("value" => value)
Pluto.set_bond_values_reactive(; session=🍭, notebook, bound_sym_names=[name],
is_first_values=[is_first_value],
run_async=false,
)
end
set_bond_value(:x, 1, true)
@test old_mtime == mtime(notebook.path)
set_bond_value(:x, 2, false)
@test old_mtime == mtime(notebook.path)
end
@testset "AbstractPlutoDingetjes.jl" begin
🍭.options.evaluation.workspace_use_distributed = true # because we use AbstractPlutoDingetjes
notebook = Notebook([
# 1
Cell("""
begin
import AbstractPlutoDingetjes as APD
import AbstractPlutoDingetjes.Bonds
end
"""),
# 2
Cell("""
begin
struct HTMLShower
f::Function
end
Base.show(io::IO, m::MIME"text/html", hs::HTMLShower) = hs.f(io)
end
"""),
Cell("""
APD.is_inside_pluto()
"""),
# 4
Cell("""
HTMLShower() do io
write(io, string(APD.is_inside_pluto(io)))
end
"""),
Cell("""
HTMLShower() do io
write(io, string(APD.is_supported_by_display(io, Bonds.initial_value)))
end
"""),
Cell("""
HTMLShower() do io
write(io, string(APD.is_supported_by_display(io, Bonds.transform_value)))
end
"""),
Cell("""
HTMLShower() do io
write(io, string(APD.is_supported_by_display(io, Bonds.validate_value)))
end
"""),
Cell("""
HTMLShower() do io
write(io, string(APD.is_supported_by_display(io, sqrt)))
end
"""),
# 9
Cell("""
@bind x_simple html""
"""),
Cell("""
x_simple
"""),
# 11
Cell("""
begin
struct OldSlider end
Base.show(io::IO, m::MIME"text/html", os::OldSlider) = write(io, "")
Base.get(os::OldSlider) = 1
end
"""),
Cell("""
@bind x_old OldSlider()
"""),
Cell("""
x_old
"""),
# 14
Cell("""
begin
struct NewSlider end
Base.show(io::IO, m::MIME"text/html", os::NewSlider) = write(io, "")
Bonds.initial_value(os::NewSlider) = 1
Bonds.possible_values(s::NewSlider) = [1,2,3]
end
"""),
Cell("""
@bind x_new NewSlider()
"""),
Cell("""
x_new
"""),
# 17
Cell("""
begin
struct TransformSlider end
Base.show(io::IO, m::MIME"text/html", os::TransformSlider) = write(io, "")
Bonds.initial_value(os::TransformSlider) = "x"
Bonds.possible_values(os::TransformSlider) = 1:10
Bonds.transform_value(os::TransformSlider, from_js) = repeat("x", from_js)
end
"""),
Cell("""
@bind x_transform TransformSlider()
"""),
Cell("""
x_transform
"""),
# 20
Cell("""
begin
struct BadTransformSlider end
Base.show(io::IO, m::MIME"text/html", os::BadTransformSlider) = write(io, "")
Bonds.initial_value(os::BadTransformSlider) = "x"
Bonds.possible_values(os::BadTransformSlider) = 1:10
Bonds.transform_value(os::BadTransformSlider, from_js) = error("bad")
end
"""),
Cell("""
@bind x_badtransform BadTransformSlider()
"""),
Cell("""
x_badtransform
"""),
# 23
Cell("""
count = Ref(0)
"""),
Cell("""
@bind x_counter NewSlider() #or OldSlider(), same idea
"""),
Cell("""
let
x_counter
count[] += 1
end
"""),
# 26
Cell("""
@assert x_old == 1
"""),
Cell("""
@assert x_new == 1
"""),
Cell("""
@assert x_transform == "x"
"""),
# 29
Cell("""
begin
struct PossibleValuesTest
possible_values::Any
end
Base.show(io::IO, m::MIME"text/html", ::PossibleValuesTest) = write(io, "hello")
Bonds.possible_values(pvt::PossibleValuesTest) = pvt.possible_values
end
"""),
Cell("@bind pv1 PossibleValuesTest(Bonds.NotGiven())"),
Cell("@bind pv2 PossibleValuesTest(Bonds.InfinitePossibilities())"),
Cell("@bind pv3 PossibleValuesTest([1,2,3])"),
Cell("@bind pv4 PossibleValuesTest((x+1 for x in 1:10))"),
# 34
Cell("@bind pv5 PossibleValuesTest(1:10)"),
# 35 - https://github.com/fonsp/Pluto.jl/issues/2465
Cell(""),
Cell("@bind ts2465 TransformSlider()"),
Cell("ts2465"),
])
function set_bond_value(name, value, is_first_value=false)
notebook.bonds[name] = Dict("value" => value)
Pluto.set_bond_values_reactive(; session=🍭, notebook, bound_sym_names=[name],
is_first_values=[is_first_value],
run_async=false,
)
end
# before loading AbstractPlutoDingetjes, test the default behaviour:
update_run!(🍭, notebook, notebook.cells[9:10])
@test noerror(notebook.cells[9])
@test noerror(notebook.cells[10])
@test Pluto.possible_bond_values(🍭, notebook, :x_simple) == :NotGiven
@test notebook.cells[10].output.body == "missing"
set_bond_value(:x_simple, 1, true)
@test notebook.cells[10].output.body == "1"
update_run!(🍭, notebook, notebook.cells)
@test noerror(notebook.cells[1])
@test noerror(notebook.cells[2])
@test noerror(notebook.cells[3])
@test noerror(notebook.cells[4])
@test noerror(notebook.cells[5])
@test noerror(notebook.cells[6])
@test noerror(notebook.cells[7])
@test noerror(notebook.cells[8])
@test notebook.cells[3].output.body == "true"
@test notebook.cells[4].output.body == "true"
@test notebook.cells[5].output.body == "true"
@test notebook.cells[6].output.body == "true"
@test notebook.cells[7].output.body == "false"
@test notebook.cells[8].output.body == "false"
@test noerror(notebook.cells[9])
@test noerror(notebook.cells[10])
@test noerror(notebook.cells[11])
@test noerror(notebook.cells[12])
@test noerror(notebook.cells[13])
@test noerror(notebook.cells[14])
@test noerror(notebook.cells[15])
@test noerror(notebook.cells[16])
@test noerror(notebook.cells[17])
@test noerror(notebook.cells[18])
@test noerror(notebook.cells[19])
@test noerror(notebook.cells[20])
@test noerror(notebook.cells[21])
@test noerror(notebook.cells[22])
@test noerror(notebook.cells[23])
@test noerror(notebook.cells[24])
@test noerror(notebook.cells[25])
@test noerror(notebook.cells[26])
@test noerror(notebook.cells[27])
@test noerror(notebook.cells[28])
@test noerror(notebook.cells[29])
@test noerror(notebook.cells[30])
@test noerror(notebook.cells[31])
@test noerror(notebook.cells[32])
@test noerror(notebook.cells[33])
@test noerror(notebook.cells[34])
@test length(notebook.cells) == 37
@test Pluto.possible_bond_values(🍭, notebook, :x_new) == [1,2,3]
@test_throws Exception Pluto.possible_bond_values(🍭, notebook, :asdfasdfx_new)
@test Pluto.possible_bond_values(🍭, notebook, :pv1) == :NotGiven
@test Pluto.possible_bond_values(🍭, notebook, :pv2) == :InfinitePossibilities
@test Pluto.possible_bond_values(🍭, notebook, :pv3) == [1,2,3]
@test Pluto.possible_bond_values(🍭, notebook, :pv4) == 2:11
@test Pluto.possible_bond_values(🍭, notebook, :pv5) === 1:10
@test Pluto.possible_bond_values_length(🍭, notebook, :pv1) == :NotGiven
@test Pluto.possible_bond_values_length(🍭, notebook, :pv2) == :InfinitePossibilities
@test Pluto.possible_bond_values_length(🍭, notebook, :pv3) == 3
@test Pluto.possible_bond_values_length(🍭, notebook, :pv4) == 10
@test Pluto.possible_bond_values_length(🍭, notebook, :pv5) == 10
@test notebook.cells[10].output.body == "missing"
set_bond_value(:x_simple, 1, true)
@test notebook.cells[10].output.body == "1"
@test notebook.cells[13].output.body == "1"
set_bond_value(:x_old, 1, true)
@test notebook.cells[13].output.body == "1"
set_bond_value(:x_old, 99, false)
@test notebook.cells[13].output.body == "99"
@test notebook.cells[16].output.body == "1"
set_bond_value(:x_new, 1, true)
@test notebook.cells[16].output.body == "1"
set_bond_value(:x_new, 99, false)
@test notebook.cells[16].output.body == "99"
@test notebook.cells[19].output.body == "\"x\""
set_bond_value(:x_transform, 1, true)
@test notebook.cells[19].output.body == "\"x\""
set_bond_value(:x_transform, 3, false)
@test notebook.cells[19].output.body == "\"xxx\""
@test notebook.cells[22].output.body != "missing"
@info "The following error is expected:"
set_bond_value(:x_badtransform, 1, true)
@test notebook.cells[22].output.body != "missing"
@test notebook.cells[25].output.body == "1"
set_bond_value(:x_counter, 1, true)
@test notebook.cells[25].output.body == "1"
set_bond_value(:x_counter, 7, false)
@test notebook.cells[25].output.body == "2"
# https://github.com/fonsp/Pluto.jl/issues/2465
update_run!(🍭, notebook, notebook.cells[35:37])
@test noerror(notebook.cells[35])
@test noerror(notebook.cells[36])
@test noerror(notebook.cells[37])
@test notebook.cells[37].output.body == "\"x\""
@test isempty(notebook.cells[35].code)
# this should not deregister the TransformSlider
setcode!(notebook.cells[35], notebook.cells[36].code)
setcode!(notebook.cells[36], "")
update_run!(🍭, notebook, notebook.cells[35:36])
@test noerror(notebook.cells[35])
@test noerror(notebook.cells[36])
@test notebook.cells[37].output.body == "\"x\""
set_bond_value(:ts2465, 2, false)
@test noerror(notebook.cells[35])
@test noerror(notebook.cells[36])
@test notebook.cells[37].output.body == "\"xx\""
cleanup(🍭, notebook)
🍭.options.evaluation.workspace_use_distributed = false
# test that the notebook file is runnable:
test_proc = Malt.Worker()
Malt.remote_eval_wait(test_proc, quote
import Pkg
try
Pkg.UPDATED_REGISTRY_THIS_SESSION[] = true
catch; end
Pkg.activate(mktempdir())
Pkg.add("AbstractPlutoDingetjes")
end)
@test Malt.remote_eval_fetch(test_proc, quote
include($(notebook.path))
true
end)
Malt.stop(test_proc)
end
@testset "Dependent Bound Variables" begin
🍭 = ServerSession()
🍭.options.evaluation.workspace_use_distributed = true
notebook = Notebook([
Cell(raw"""@bind x HTML("")"""),
Cell(raw"""@bind y HTML("")"""),
Cell(raw"""x"""), #3
Cell(raw"""y"""), #4
Cell(raw"""
begin
struct TransformSlider
range::AbstractRange
end
Base.show(io::IO, m::MIME"text/html", os::TransformSlider) = write(io, "")
Bonds.initial_value(os::TransformSlider) = Bonds.transform_value(os, minimum(os.range))
Bonds.possible_values(os::TransformSlider) = os.range
Bonds.transform_value(os::TransformSlider, from_js) = from_js * 2
end
"""),
Cell(raw"""begin
hello1 = 123
@bind a TransformSlider(1:10)
end"""),
Cell(raw"""begin
hello2 = 234
@bind b TransformSlider(1:a)
end"""),
Cell(raw"""a"""), #8
Cell(raw"""b"""), #9
Cell(raw"""hello1"""), #10
Cell(raw"""hello2"""), #11
Cell(raw"""using AbstractPlutoDingetjes"""),
])
update_run!(🍭, notebook, notebook.cells)
# Test the get_bond_names function
@test Pluto.get_bond_names(🍭, notebook) == Set([:a, :b, :x, :y])
function set_bond_values!(notebook:: Notebook, bonds:: Dict; is_first_value=false)
for (name, value) in bonds
notebook.bonds[name] = Dict("value" => value)
end
Pluto.set_bond_values_reactive(; session=🍭, notebook, bound_sym_names=collect(keys(bonds)), run_async=false, is_first_values=fill(is_first_value, length(bonds)))
end
@test notebook.cells[3].output.body == "missing"
@test notebook.cells[4].output.body == "missing" # no initial value defined for simple html slider (in contrast to TransformSlider)
@test notebook.cells[8].output.body == "2"
@test notebook.cells[9].output.body == "2"
@test notebook.cells[10].output.body == "123"
@test notebook.cells[11].output.body == "234"
set_bond_values!(notebook, Dict(:x => 1, :a => 1); is_first_value=true)
@test notebook.cells[3].output.body == "1"
@test notebook.cells[4].output.body == "missing" # no initial value defined for simple html slider (in contrast to TransformSlider)
@test notebook.cells[8].output.body == "2" # TransformSlider scales values *2
@test notebook.cells[9].output.body == "2"
@test notebook.cells[10].output.body == "123"
@test notebook.cells[11].output.body == "234"
set_bond_values!(notebook, Dict(:y => 1, :b => 1); is_first_value=true)
@test notebook.cells[3].output.body == "1"
@test notebook.cells[4].output.body == "1"
@test notebook.cells[8].output.body == "2"
@test notebook.cells[9].output.body == "2"
@test notebook.cells[10].output.body == "123"
@test notebook.cells[11].output.body == "234"
set_bond_values!(notebook, Dict(:x => 5))
@test notebook.cells[3].output.body == "5"
@test notebook.cells[4].output.body == "missing" # the slider object is re-defined, therefore its value is the default one
set_bond_values!(notebook, Dict(:y => 3))
@test notebook.cells[3].output.body == "5"
@test notebook.cells[4].output.body == "3"
set_bond_values!(notebook, Dict(:x => 10, :y => 5))
@test notebook.cells[3].output.body == "10"
@test notebook.cells[4].output.body == "5" # this would fail without PR #2014 - previously `y` was reset to the default value `missing`
set_bond_values!(notebook, Dict(:b => 2))
@test notebook.cells[8].output.body == "2"
@test notebook.cells[9].output.body == "4"
@test notebook.cells[10].output.body == "123"
@test notebook.cells[11].output.body == "234"
set_bond_values!(notebook, Dict(:a => 8, :b => 12))
@test notebook.cells[8].output.body == "16"
@test notebook.cells[9].output.body == "24" # this would fail without PR #2014
@test notebook.cells[10].output.body == "123"
@test notebook.cells[11].output.body == "234"
set_bond_values!(notebook, Dict(:a => 1, :b => 1))
setcode!(notebook.cells[10], "a + hello1")
setcode!(notebook.cells[11], "b + hello2")
update_run!(🍭, notebook, notebook.cells[10:11])
@test notebook.cells[10].output.body == "125"
@test notebook.cells[11].output.body == "236"
set_bond_values!(notebook, Dict(:a => 2, :b => 2))
@test notebook.cells[10].output.body == "127"
@test notebook.cells[11].output.body == "238"
set_bond_values!(notebook, Dict(:b => 3))
@test notebook.cells[10].output.body == "127"
@test notebook.cells[11].output.body == "240"
set_bond_values!(notebook, Dict(:a => 1))
@test notebook.cells[10].output.body == "125"
@test notebook.cells[11].output.body == "236" # changing a will reset b
cleanup(🍭, notebook)
end
end