diff --git a/src/ComponentPackages.jl b/src/ComponentPackages.jl index 24b1c37..6e53ee4 100644 --- a/src/ComponentPackages.jl +++ b/src/ComponentPackages.jl @@ -43,6 +43,17 @@ module ComponentPackages ) end + function components_js_sources(prefix = ""; debug=false) + type = debug ? :dev : :prod + result = [] + + for p in values(_components_packages) + for script in p.scripts[type] + push!(result, "$(prefix)_dash-component-suites/$(p.package_name)/$(script)") + end + end + return result + end diff --git a/src/Dash.jl b/src/Dash.jl index 26024e3..962f19c 100644 --- a/src/Dash.jl +++ b/src/Dash.jl @@ -15,9 +15,11 @@ export dash, Component, Front, @use, <|, @callid_str, CallbackId, callback!, run_server, PreventUpdate, no_update, @wildprop ComponentPackages.@reg_components() +include("utils.jl") +include("config.jl") include("app.jl") +include("index_page.jl") include("handlers.jl") -include("utils.jl") @doc """ @@ -91,4 +93,5 @@ function run_server(app::DashApp, host = HTTP.Sockets.localhost, port = 8080; de HTTP.serve(handler, host, port) end + end # module \ No newline at end of file diff --git a/src/app.jl b/src/app.jl index b2381f1..5adf7ad 100644 --- a/src/app.jl +++ b/src/app.jl @@ -1,5 +1,25 @@ const IdProp = Tuple{Symbol, Symbol} +const default_index = """ + + + {%metas%} + {%title%} + {%favicon%} + {%css%} + + + {%app_entry%} + + +""" + + + struct CallbackId state ::Vector{IdProp} input ::Vector{IdProp} @@ -34,33 +54,43 @@ mutable struct Layout component::Union{Nothing, Component} end +const ExternalSrcType = Union{String, Dict{String, String}} + +struct DashConfig + external_stylesheets ::Vector{ExternalSrcType} + external_scripts ::Vector{ExternalSrcType} + url_base_pathname ::Union{String, Nothing} #TODO This looks unused + requests_pathname_prefix ::String + routes_pathname_prefix ::String + assets_folder ::String + assets_url_path ::String + assets_ignore ::String + serve_locally ::Bool + suppress_callback_exceptions ::Bool + eager_loading ::Bool + meta_tags ::Vector{Dict{String, String}} + index_string ::Union{String, Nothing} + assets_external_path ::Union{String, Nothing} + include_assets_files ::Bool + show_undo_redo ::Bool +end + """ struct DashApp <: Any -Representation of Dash application +Representation of Dash application. + +Not meant to be constructed directly, use `dash` function instead. """ struct DashApp name ::String + config ::DashConfig layout ::Layout callbacks ::Dict{Symbol, Callback} - external_stylesheets ::Vector{String} - external_scripts ::Vector{String} - url_base_pathname ::String - assets_folder ::String - callable_components ::Dict{Symbol, Component} - function DashApp(name::String; - external_stylesheets ::Vector{String} = Vector{String}(), - external_scripts ::Vector{String} = Vector{String}(), - url_base_pathname="/", - assets_folder::String = "assets") - - new(name, Layout(nothing), Dict{Symbol, Callback}(), - external_stylesheets, external_scripts, - url_base_pathname, assets_folder, - Dict{Symbol, Component}() - - ) - end + callable_components ::Dict{Symbol, Component} + + DashApp(name::String, config::DashConfig) = new(name, config, Layout(nothing), Dict{Symbol, Callback}(), Dict{Symbol, Component}()) + end function layout!(app::DashApp, component::Component) @@ -78,59 +108,190 @@ function Base.getproperty(app::DashApp, name::Symbol) name == :layout ? getlayout(app) : Base.getfield(app, name) end - + """ - dash(name::String; external_stylesheets ::Vector{String} = Vector{String}(), url_base_pathname::String="/") - dash(layout_maker::Function, name::String; external_stylesheets ::Vector{String} = Vector{String}(), url_base_pathname::String="/") + dash(name::String; + external_stylesheets, + external_scripts, + url_base_pathname, + requests_pathname_prefix, + routes_pathname_prefix, + assets_folder, + assets_url_path, + assets_ignore, + serve_locally, + suppress_callback_exceptions, + eager_loading , + meta_tags, + index_string, + assets_external_path, + include_assets_files, + show_undo_redo + ) -Construct a dash app using callback for layout creation +Construct a dash app # Arguments -- `layout_maker::Function` - function for layout creation. Must has signature ()::Component -- `name::String` - Dashboard name -- `external_stylesheets::Vector{String} = Vector{String}()` - vector of external css urls -- `external_scripts::Vector{String} = Vector{String}()` - vector of external js scripts urls -- `url_base_pathname::String="/"` - base url path for dashboard, default "/" +- `name::String` - The name of your application - `assets_folder::String` - a path, relative to the current working directory, -for extra files to be used in the browser. Default `"assets"` + for extra files to be used in the browser. Default ``'assets'``. -# Examples -```jldoctest -julia> app = dash("Test") do - html_div() do - html_h1("Test Dashboard") - end -end -``` -""" +- `assets_url_path::String` - The local urls for assets will be: + ``requests_pathname_prefix * assets_url_path * "/" * asset_path`` + where ``asset_path`` is the path to a file inside ``assets_folder``. + Default ``'assets'`. + + +- `assets_ignore::String` - [IN DEVELOPMENT] A regex, as a string to pass to ``Regex``, for + assets to omit from immediate loading. Ignored files will still be + served if specifically requested. You cannot use this to prevent access + to sensitive files. + :type assets_ignore: string + +- `assets_external_path::String` - [IN DEVELOPMENT] an absolute URL from which to load assets. + Use with ``serve_locally=false``. Dash can still find js and css to + automatically load if you also keep local copies in your assets + folder that Dash can index, but external serving can improve + performance and reduce load on the Dash server. + + +- `include_assets_files::Bool` - [IN DEVELOPMENT] Default ``true``, set to ``False`` to prevent + immediate loading of any assets. Assets will still be served if + specifically requested. You cannot use this to prevent access + to sensitive files. + + +- `url_base_pathname::String`: A local URL prefix to use app-wide. + Default ``nothing``. Both `requests_pathname_prefix` and + `routes_pathname_prefix` default to `url_base_pathname`. + + +- `requests_pathname_prefix::String`: A local URL prefix for file requests. + Defaults to `url_base_pathname`, and must end with + `routes_pathname_prefix` + + +- `routes_pathname_prefix::String`: A local URL prefix for JSON requests. + Defaults to ``url_base_pathname``, and must start and end + with ``'/'``. + +- `serve_locally`: [IN DEVELOPMENT] If ``true`` (default), assets and dependencies + (Dash and Component js and css) will be served from local URLs. + If ``false`` we will use CDN links where available. + +- `meta_tags::Vector{Dict{String, String}}`: html tags to be added to the index page. + Each dict should have the attributes and values for one tag, eg: + ``Dict("name"=>"description", "content" => "My App")`` + +- `index_string::String`: Override the standard Dash index page. + Must contain the correct insertion markers to interpolate various + content into it depending on the app config and components used. + See https://dash.plotly.com/external-resources for details. + + +- `external_scripts::Vector`: Additional JS files to load with the page. + Each entry can be a String (the URL) or a Dict{String, String} with ``src`` (the URL) + and optionally other ``""" - end, "\n") - - app_entry = """ -
-
- Loading... -
-
- """ - config = ( - url_base_pathname = "$(app.url_base_pathname)", - requests_pathname_prefix = "$(app.url_base_pathname)", - ui = true, - props_check = debug, - show_undo_redo = false - ) - - scripts = ComponentPackages.components_js_include(app.url_base_pathname, debug = debug) - """ - - - $(join(metas, "")) - $(title) - $(css) - - - $(app_entry) - - - """ -end function dependencies_json(app::DashApp) id_prop_named(p::IdProp) = (id = p[1], property = p[2]) @@ -108,8 +63,9 @@ function process_callback(app::DashApp, body::String) end function process_assets(app::DashApp, path) - assets_path = "$(app.url_base_pathname)assets/" - filename = joinpath(app.assets_folder, replace(path, assets_path=>"")) + assets_path = "$(app.config.routes_pathname_prefix)" * strip(app.config.assets_url_path, '/') * "/" + + filename = joinpath(app.config.assets_folder, replace(path, assets_path=>"")) try return HTTP.Response(200, [], body = read(filename)) catch @@ -119,22 +75,24 @@ end function make_handler(app::DashApp; debug::Bool = false) - function (req::HTTP.Request) + index_string::String = index_page(app, debug = debug) + + return function (req::HTTP.Request) uri = HTTP.URI(req.target) - ComponentPackages.@register_js_sources(uri.path, app.url_base_pathname) - if uri.path == "$(app.url_base_pathname)" - return HTTP.Response(200, index_page(app, debug = debug)) + ComponentPackages.@register_js_sources(uri.path, app.config.routes_pathname_prefix) + if uri.path == "$(app.config.routes_pathname_prefix)" + return HTTP.Response(200, index_string) end - if uri.path == "$(app.url_base_pathname)_dash-layout" + if uri.path == "$(app.config.routes_pathname_prefix)_dash-layout" return HTTP.Response(200, ["Content-Type" => "application/json"], body = JSON2.write(app.layout)) end - if uri.path == "$(app.url_base_pathname)_dash-dependencies" + if uri.path == "$(app.config.routes_pathname_prefix)_dash-dependencies" return HTTP.Response(200, ["Content-Type" => "application/json"], body = dependencies_json(app)) end - if startswith(uri.path, "$(app.url_base_pathname)assets/") + if startswith(uri.path, "$(app.config.routes_pathname_prefix)assets/") return process_assets(app, uri.path) end - if uri.path == "$(app.url_base_pathname)_dash-update-component" && req.method == "POST" + if uri.path == "$(app.config.routes_pathname_prefix)_dash-update-component" && req.method == "POST" try return HTTP.Response(200, ["Content-Type" => "application/json"], body = JSON2.write( @@ -151,4 +109,5 @@ function make_handler(app::DashApp; debug::Bool = false) end return HTTP.Response(404) end + end \ No newline at end of file diff --git a/src/index_page.jl b/src/index_page.jl new file mode 100644 index 0000000..1336949 --- /dev/null +++ b/src/index_page.jl @@ -0,0 +1,72 @@ +make_css_tag(url::String) = """""" +make_css_tag(dict::Dict{String, String}) = format_tag("link", dict, opened = true) + +make_script_tag(url::String) = """""" +make_script_tag(dict::Dict{String, String}) = format_tag("script", dict) + +function metas_html(app::DashApp) + meta_tags = app.config.meta_tags + has_ie_compat = any(meta_tags) do tag + get(tag, "http-equiv", "") == "X-UA-Compatible" + end + has_charset = any(tag -> haskey(tag, "charset"), meta_tags) + + result = String[] + !has_ie_compat && push!(result, "") + !has_charset && push!(result, "") + + append!(result, format_tag.("meta", meta_tags, opened = true)) + return join(result, "\n ") + +end + +css_html(app::DashApp) = join(map(make_css_tag, app.config.external_stylesheets), "\n ") + +app_entry_html() = """ +
+
+ Loading... +
+
+""" + +function config_html(app::DashApp; debug = false) + config = ( + url_base_pathname = app.config.url_base_pathname, + requests_pathname_prefix = app.config.requests_pathname_prefix, + ui = true, + props_check = debug, + show_undo_redo = app.config.show_undo_redo, + suppress_callback_exceptions = app.config.suppress_callback_exceptions + ) + return """""" +end + + + +function scripts_html(app::DashApp; debug = false) + scripts = app.config.external_scripts + append!(scripts, + ComponentPackages.components_js_sources(app.config.requests_pathname_prefix, debug = debug) + ) + return join(map(make_script_tag, scripts), "\n ") +end + +renderer_html() = """""" + +favicon_html(app::DashApp) = "" + + +function index_page(app::DashApp; debug = false) + + return interpolate_string(app.config.index_string, + metas = metas_html(app), + title = app.name, + favicon = favicon_html(app), + css = css_html(app), + app_entry = app_entry_html(), + config = config_html(app, debug = debug), + scripts = scripts_html(app, debug = debug), + renderer = renderer_html() + ) +end diff --git a/src/utils.jl b/src/utils.jl index e0a2b6d..53d3e94 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,4 +1,28 @@ using MacroTools + +function format_tag(name ::String, attributes::Dict{String, String}, inner::String = ""; opened = false, closed = false) + attrs_string = join( + ["$k=\"$v\"" for (k, v) in attributes], + " " + ) + tag = "<$name $attrs_string" + if closed + tag *= "/>" + elseif opened + tag *= ">" + else + tag *= ">$inner" + end +end + +function interpolate_string(s::String; kwargs...) + result = s + for (k, v) in kwargs + result = replace(result, "{%$(k)%}" => v) + end + return result +end + macro wildprop(e) if e.head != :(=) || length(e.args) != 2 || !(e.args[1] isa AbstractString) error("expected AbstractString = value") @@ -24,6 +48,7 @@ function parse_props(s) end end + """ @callid_str" @@ -45,4 +70,4 @@ macro callid_str(s) output = parse_props(strip(m[:output])) state = isnothing(m[:state]) ? Vector{IdProp}() : parse_props(strip(m[:state])) return CallbackId(state, input, output) -end \ No newline at end of file +end diff --git a/test/config_functional.jl b/test/config_functional.jl new file mode 100644 index 0000000..b53b6ab --- /dev/null +++ b/test/config_functional.jl @@ -0,0 +1,207 @@ +using Test +using Dash +using HTTP + + +@testset "external_stylesheets" begin + app = dash("test app") + index_page = Dash.index_page(app) + + @test isnothing(findfirst("link rel=\"stylesheet\"", index_page)) + + app = dash("test app"; external_stylesheets = ["https://test.css"]) + index_page = Dash.index_page(app) + @test !isnothing(findfirst("", index_page)) + + app = dash("test app"; external_stylesheets = [ + "https://test.css", + Dict("href" => "https://test2.css", "rel" => "stylesheet") + ] + ) + index_page = Dash.index_page(app) + + @test !isnothing(findfirst("", index_page)) + @test !isnothing(findfirst("href=\"https://test2.css\"", index_page)) + +end + +@testset "external_scripts" begin + + + app = dash("test app"; external_scripts = ["https://test.js"]) + index_page = Dash.index_page(app) + + @test !isnothing(findfirst("""""", index_page)) + + app = dash("test app"; external_scripts = [ + "https://test.js", + Dict("src" => "https://test2.js", "crossorigin" => "anonymous") + ]) + index_page = Dash.index_page(app) + + @test !isnothing(findfirst("""""", index_page)) + + @test !isnothing(findfirst("""""", index_page)) + +end + +@testset "url paths" begin + app = dash("test app"; requests_pathname_prefix = "/reg/prefix/", routes_pathname_prefix = "/prefix/") + index_page = Dash.index_page(app) + + @test !isnothing(findfirst("""requests_pathname_prefix":"/reg/prefix/""", index_page)) + handler = Dash.make_handler(app) + request = HTTP.Request("GET", "/prefix/") + response = handler(request) + @test response.status == 200 + + request = HTTP.Request("GET", "/prefix/_dash-layout") + response = handler(request) + @test response.status == 200 + + request = HTTP.Request("GET", "/prefix/_dash-dependencies") + response = handler(request) + @test response.status == 200 + +end + +@testset "assets paths" begin + app = dash("test app") + res = Dash.process_assets(app, "/assets/test.png") + @test res.status == 200 + res = Dash.process_assets(app, "/assets/test3.png") + @test res.status == 404 + res = Dash.process_assets(app, "/images/test.png") + @test res.status == 404 + + app = dash("test app", url_base_pathname = "/test/") + res = Dash.process_assets(app, "/assets/test.png") + @test res.status == 404 + res = Dash.process_assets(app, "/test/assets/test.png") + @test res.status == 200 + res = Dash.process_assets(app, "/images/test.png") + @test res.status == 404 + + app = dash("test app", assets_url_path = "ass") + res = Dash.process_assets(app, "/ass/test.png") + @test res.status == 200 + res = Dash.process_assets(app, "/ass/test3.png") + @test res.status == 404 + res = Dash.process_assets(app, "/assets/test3.png") + @test res.status == 404 + res = Dash.process_assets(app, "/images/test.png") + @test res.status == 404 + + app = dash("test app", assets_folder = "images") + res = Dash.process_assets(app, "/assets/test.png") + @test res.status == 404 + res = Dash.process_assets(app, "/assets/test_images.png") + @test res.status == 200 + res = Dash.process_assets(app, "/images/test.png") + @test res.status == 404 +end + +@testset "suppress_callback_exceptions" begin + app = dash("test app") + index_page = Dash.index_page(app) + @test !isnothing(findfirst("\"suppress_callback_exceptions\":false", index_page)) + @test isnothing(findfirst("\"suppress_callback_exceptions\":true", index_page)) + + app = dash("test app", suppress_callback_exceptions = true) + index_page = Dash.index_page(app) + @test isnothing(findfirst("\"suppress_callback_exceptions\":false", index_page)) + @test !isnothing(findfirst("\"suppress_callback_exceptions\":true", index_page)) +end + +@testset "meta_tags" begin + app = dash("test app") + index_page = Dash.index_page(app) + + @test !isnothing( + findfirst( + "", + index_page) + ) + + @test !isnothing( + findfirst( + "", + index_page) + ) + + app = dash("test app", meta_tags = [Dict("type" => "tst", "rel" => "r")]) + index_page = Dash.index_page(app) + + @test !isnothing( + findfirst( + "", + index_page) + ) + + @test !isnothing( + findfirst( + "", + index_page) + ) + + @test !isnothing( + findfirst( + Dash.format_tag("meta", Dict("type" => "tst", "rel" => "r"), opened = true), + index_page) + ) + + app = dash("test app", meta_tags = [Dict("charset" => "Win1251"), Dict("type" => "tst", "rel" => "r")]) + index_page = Dash.index_page(app) + + @test isnothing( + findfirst( + "", + index_page) + ) + @test !isnothing( + findfirst( + "", + index_page) + ) + + @test !isnothing( + findfirst( + Dash.format_tag("meta", Dict("type" => "tst", "rel" => "r"), opened = true), + index_page) + ) + + app = dash("test app", meta_tags = [Dict("http-equiv" => "X-UA-Compatible", "content" => "IE"), Dict("type" => "tst", "rel" => "r")]) + index_page = Dash.index_page(app) + @test isnothing( + findfirst( + "", + index_page) + ) + @test !isnothing( + findfirst( + Dash.format_tag("meta", Dict("http-equiv" => "X-UA-Compatible", "content" => "IE"), opened = true), + index_page) + ) + +end + +@testset "index_string" begin + index_string = "test test test, {%metas%},{%title%},{%favicon%},{%css%},{%app_entry%},{%config%},{%scripts%},{%renderer%}" + app = dash("test", index_string = index_string) + index_page = Dash.index_page(app) + @test startswith(index_page, "test test test,") + +end + +@testset "show_undo_redo" begin + + app = dash("test") + + index_page = Dash.index_page(app) + @test !isnothing(findfirst("\"show_undo_redo\":false", index_page)) + + app = dash("test", show_undo_redo = true) + + index_page = Dash.index_page(app) + @test !isnothing(findfirst("\"show_undo_redo\":true", index_page)) +end \ No newline at end of file diff --git a/test/core.jl b/test/core.jl index f45b453..a2129e6 100644 --- a/test/core.jl +++ b/test/core.jl @@ -51,18 +51,6 @@ end end -@testset "Dash creation" begin - app = dash("test app"; external_stylesheets=["https://test.css"], url_base_pathname = "/") do - html_div(id = "test-div") - end - @test app.name == "test app" - @test app.external_stylesheets == ["https://test.css"] - @test app.url_base_pathname == "/" - @test app.layout.type == "Div" - @test app.layout.props[:id] == "test-div" - @test length(app.callable_components) == 1 - @test haskey(app.callable_components, Symbol("test-div")) -end @testset "callid" begin id = callid"id1.prop1 => id2.prop2" @@ -278,7 +266,7 @@ end html_img(src = "assets/test.png") end end - @test app.assets_folder == "assets" + @test app.config.assets_folder == joinpath(pwd(),"assets") handler = Dash.make_handler(app) request = HTTP.Request("GET", "/assets/test.png") response = handler(request) diff --git a/test/dash_creation.jl b/test/dash_creation.jl new file mode 100644 index 0000000..42c95d2 --- /dev/null +++ b/test/dash_creation.jl @@ -0,0 +1,122 @@ +using Test +using Dash + + +@testset "default args" begin + app = dash("test app") + @test app.name == "test app" + @test isempty(app.config.external_stylesheets) + @test isempty(app.config.external_scripts) + #@test app.config.url_base_pathname == "/" + @test app.config.requests_pathname_prefix == "/" + @test app.config.routes_pathname_prefix == "/" + @test app.config.assets_folder == joinpath(pwd(), "assets") + @test app.config.assets_url_path == "assets" + @test app.config.assets_ignore == "" + + @test app.config.serve_locally == true + @test app.config.suppress_callback_exceptions == false + @test app.config.eager_loading == false + + @test isempty(app.config.meta_tags) + @test app.config.index_string == Dash.default_index + @test app.config.assets_external_path == nothing + + @test app.config.include_assets_files == true + @test app.config.show_undo_redo == false +end + +@testset "setted args" begin + app = dash("test app"; external_stylesheets=["https://test.css"]) + @test app.config.external_stylesheets == ["https://test.css"] + + app = dash("test app"; external_stylesheets=[Dict("url" => "https://test.css", "integrity" => "integrity")]) + @test app.config.external_stylesheets == [ + Dict("url" => "https://test.css", "integrity" => "integrity") + ] + + app = dash("test app"; external_scripts = ["http://test.js"]) + @test app.config.external_scripts == ["http://test.js"] + + app = dash("test app"; external_scripts=[Dict("url" => "https://test.js", "integrity" => "integrity")]) + @test app.config.external_scripts == [ + Dict("url" => "https://test.js", "integrity" => "integrity") + ] + + app = dash("test app"; url_base_pathname = "/base/") + @test app.config.url_base_pathname == "/base/" + @test app.config.requests_pathname_prefix == "/base/" + @test app.config.routes_pathname_prefix == "/base/" + + app = dash("test app"; requests_pathname_prefix = "/prefix/") + @test app.config.requests_pathname_prefix == "/prefix/" + @test app.config.routes_pathname_prefix == "/" + + app = dash("test app"; routes_pathname_prefix = "/prefix/") + @test app.config.requests_pathname_prefix == "/prefix/" + @test app.config.routes_pathname_prefix == "/prefix/" + + app = dash("test app"; requests_pathname_prefix = "/reg/prefix/", routes_pathname_prefix = "/prefix/") + + @test app.config.requests_pathname_prefix == "/reg/prefix/" + @test app.config.routes_pathname_prefix == "/prefix/" + + @test_throws ErrorException app = dash("test app"; url_base_pathname = "/base") + @test_throws ErrorException app = dash("test app"; url_base_pathname = "base/") + @test_throws ErrorException app = dash("test app"; url_base_pathname = "/", routes_pathname_prefix = "/prefix/") + @test_throws ErrorException app = dash("test app"; requests_pathname_prefix = "/prefix") + @test_throws ErrorException app = dash("test app"; routes_pathname_prefix = "/prefix") + @test_throws ErrorException app = dash("test app"; requests_pathname_prefix = "prefix/") + @test_throws ErrorException app = dash("test app"; routes_pathname_prefix = "prefix/") + @test_throws ErrorException app = dash("test app"; requests_pathname_prefix = "/reg/prefix/", routes_pathname_prefix = "/ix/") + + + app = dash("test app"; assets_folder = "images") + @test app.config.assets_folder == joinpath(pwd(), "images") + + app = dash("test app"; assets_url_path = "/images") + @test app.config.assets_url_path == "images" + + app = dash("test app"; assets_ignore = "ignore") + @test app.config.assets_ignore == "ignore" + + app = dash("test app"; serve_locally = false) + @test app.config.serve_locally == false + + app = dash("test app"; suppress_callback_exceptions = true) + @test app.config.suppress_callback_exceptions == true + + app = dash("test app"; eager_loading = false) + @test app.config.eager_loading == false + + app = dash("test app"; meta_tags = [Dict(["name"=>"test", "content" => "content"])]) + @test app.config.meta_tags == [Dict(["name"=>"test", "content" => "content"])] + + app = dash("test app"; index_string = "") + @test app.config.index_string == "" + + app = dash("test app"; assets_external_path = "external") + @test app.config.assets_external_path == "external" + + app = dash("test app"; include_assets_files = true) + @test app.config.include_assets_files == true + + app = dash("test app"; show_undo_redo = false) + @test app.config.show_undo_redo == false + +end + + +@testset "old Dash creation" begin + app = dash("test app"; external_stylesheets=["https://test.css"], url_base_pathname = "/") do + html_div(id = "test-div") + end + @test app.name == "test app" + @test app.config.external_stylesheets == ["https://test.css"] + @test app.config.url_base_pathname == "/" + @test app.layout.type == "Div" + @test app.layout.props[:id] == "test-div" + @test length(app.callable_components) == 1 + @test haskey(app.callable_components, Symbol("test-div")) +end + diff --git a/test/dev.jl b/test/dev.jl index 0d9e6ec..01bb4ac 100644 --- a/test/dev.jl +++ b/test/dev.jl @@ -11,6 +11,8 @@ styles = ( ), ) + + app = dash("Dash Layout", external_stylesheets=external_stylesheets) app.layout = html_div() do dcc_graph( diff --git a/test/images/test_images.png b/test/images/test_images.png new file mode 100644 index 0000000..83f519a Binary files /dev/null and b/test/images/test_images.png differ diff --git a/test/runtests.jl b/test/runtests.jl index 099cb39..1a6391f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,3 +1,6 @@ +include("dash_creation.jl") +include("config_functional.jl") +include("utils.jl") include("core.jl") include("types.jl") #include("dev.jl") \ No newline at end of file diff --git a/test/utils.jl b/test/utils.jl new file mode 100644 index 0000000..95451b5 --- /dev/null +++ b/test/utils.jl @@ -0,0 +1,19 @@ +using Test +using Dash +@testset "interpolate_string" begin + test_str = """ + {%head%} + blah blah blah blah blah blah blah blah blah blah + {%middle%} + da da da da da da da da da da da da da {%da%} da + end + """ + inter = Dash.interpolate_string(test_str, head="hd", middle = :mmmm, da = 10) + @test inter == """ + hd + blah blah blah blah blah blah blah blah blah blah + mmmm + da da da da da da da da da da da da da 10 da + end + """ +end