├── .dir-locals.el ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── continuous-deployment-workflow.yml │ └── continuous-integration-workflow.yml ├── .gitignore ├── .idea └── codeStyleSettings.xml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── doc └── intro.md ├── examples └── todomvc │ ├── .gitignore │ ├── README.md │ ├── karma.conf.js │ ├── package-lock.json │ ├── package.json │ ├── project.clj │ ├── resources │ └── public │ │ ├── index.html │ │ └── todos.css │ ├── src │ ├── deps.cljs │ └── todomvc │ │ ├── core.cljs │ │ ├── db.cljs │ │ ├── events.cljs │ │ ├── subs.cljs │ │ └── views.cljs │ └── test │ └── todomvc │ ├── README.md │ └── core_test.cljs ├── karma.conf.js ├── package-lock.json ├── package.json ├── project.clj ├── src ├── day8 │ └── re_frame │ │ └── test.cljc └── deps.cljs ├── test-resources └── logback-test.xml └── test └── day8 └── re_frame └── test_reframe ├── macros.cljc ├── runner.cljs └── test_test.cljc /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((clojure-mode 5 | (eval define-clojure-indent 6 | (wait-for '(:defn)) 7 | (async '(:defn))))) 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: mike-thompson-day8 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Set update schedule for GitHub Actions 2 | 3 | version: 2 4 | updates: 5 | 6 | - package-ecosystem: "github-actions" 7 | directory: "/" 8 | schedule: 9 | # Check for updates to GitHub Actions every week 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /.github/workflows/continuous-deployment-workflow.yml: -------------------------------------------------------------------------------- 1 | name: cd 2 | on: 3 | push: 4 | tags: 5 | - "v[0-9]+.[0-9]+.[0-9]+*" 6 | 7 | jobs: 8 | test: 9 | name: Test 10 | runs-on: ubuntu-20.04 11 | container: 12 | # Source: https://github.com/day8/dockerfile-for-dev-ci-image 13 | image: ghcr.io/day8/dockerfile-for-dev-ci-image/chrome-56:2 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Maven cache 17 | uses: actions/cache@v4 18 | with: 19 | path: /root/.m2/repository 20 | key: ${{ runner.os }}-maven-${{ hashFiles('project.clj') }} 21 | restore-keys: | 22 | ${{ runner.os }}-maven- 23 | - name: npm cache 24 | uses: actions/cache@v4 25 | with: 26 | path: ~/.npm 27 | key: ${{ runner.os }}-npm-${{ hashFiles('project.clj') }}-${{ hashFiles('**/deps.cljs') }} 28 | restore-keys: | 29 | ${{ runner.os }}-npm- 30 | - name: shadow-cljs compiler cache 31 | uses: actions/cache@v4 32 | with: 33 | path: .shadow-cljs 34 | key: ${{ runner.os }}-shadow-cljs-${{ github.sha }} 35 | restore-keys: | 36 | ${{ runner.os }}-shadow-cljs- 37 | - run: lein ci 38 | - name: Slack notification 39 | uses: homoluctus/slatify@v3.0.0 40 | if: failure() || cancelled() 41 | with: 42 | type: ${{ job.status }} 43 | job_name: re-frame-test Tests 44 | channel: '#oss-robots' 45 | url: ${{ secrets.SLACK_WEBHOOK }} 46 | commit: true 47 | token: ${{ secrets.GITHUB_TOKEN }} 48 | release: 49 | name: Release 50 | needs: test 51 | runs-on: ubuntu-20.04 52 | container: 53 | # Source: https://github.com/day8/dockerfile-for-dev-ci-image 54 | image: ghcr.io/day8/dockerfile-for-dev-ci-image/chrome-56:2 55 | steps: 56 | - uses: actions/checkout@v4 57 | - name: Maven cache 58 | uses: actions/cache@v4 59 | with: 60 | path: /root/.m2/repository 61 | key: ${{ runner.os }}-maven-${{ hashFiles('project.clj') }} 62 | restore-keys: | 63 | ${{ runner.os }}-maven- 64 | - name: Run lein release 65 | run: | 66 | CLOJARS_USERNAME=${{ secrets.CLOJARS_USERNAME }} CLOJARS_TOKEN=${{ secrets.CLOJARS_TOKEN }} GITHUB_USERNAME=${{ github.actor }} GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} lein release 67 | # This creates a 'GitHub Release' from the tag and includes link to the CHANGELOG 68 | # at that point in the Git history. We do not use draft or prerelease features as 69 | # we always want the latest release to show in the right hand column of the project 70 | # page regardless of if it is a stable release. 71 | - name: Create GitHub Release 72 | uses: actions/create-release@v1 73 | env: 74 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 75 | with: 76 | tag_name: ${{ github.ref }} 77 | release_name: ${{ github.ref }} 78 | body: | 79 | [Changelog](https://github.com/day8/re-frame-test/blob/${{ github.ref }}/CHANGELOG.md) 80 | draft: false 81 | prerelease: false 82 | - name: Slack notification 83 | uses: homoluctus/slatify@v3.0.0 84 | if: always() 85 | with: 86 | type: ${{ job.status }} 87 | job_name: re-frame-test Deployment 88 | channel: '#oss-robots' 89 | url: ${{ secrets.SLACK_WEBHOOK }} 90 | commit: true 91 | token: ${{ secrets.GITHUB_TOKEN }} 92 | -------------------------------------------------------------------------------- /.github/workflows/continuous-integration-workflow.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: [push] 3 | 4 | jobs: 5 | test: 6 | name: Test 7 | runs-on: ubuntu-latest 8 | container: 9 | # Source: https://github.com/day8/dockerfile-for-dev-ci-image 10 | image: ghcr.io/day8/dockerfile-for-dev-ci-image/chrome-56:2 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Maven cache 14 | uses: actions/cache@v4 15 | with: 16 | path: /root/.m2/repository 17 | key: ${{ runner.os }}-maven-${{ hashFiles('project.clj') }} 18 | restore-keys: | 19 | ${{ runner.os }}-maven- 20 | - name: npm cache 21 | uses: actions/cache@v4 22 | with: 23 | path: ~/.npm 24 | key: ${{ runner.os }}-npm-${{ hashFiles('project.clj') }}-${{ hashFiles('**/deps.cljs') }} 25 | restore-keys: | 26 | ${{ runner.os }}-npm- 27 | - name: shadow-cljs compiler cache 28 | uses: actions/cache@v4 29 | with: 30 | path: .shadow-cljs 31 | key: ${{ runner.os }}-shadow-cljs-${{ github.sha }} 32 | restore-keys: | 33 | ${{ runner.os }}-shadow-cljs- 34 | - run: lein ci 35 | - name: Slack notification 36 | uses: homoluctus/slatify@v3.0.0 37 | if: failure() || cancelled() 38 | with: 39 | type: ${{ job.status }} 40 | job_name: re-frame-test Tests 41 | channel: '#oss-robots' 42 | url: ${{ secrets.SLACK_WEBHOOK }} 43 | commit: true 44 | token: ${{ secrets.GITHUB_TOKEN }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /run 3 | /node_modules 4 | /.shadow-cljs/ 5 | /shadow-cljs.edn 6 | /classes 7 | /checkouts 8 | pom.xml 9 | pom.xml.asc 10 | *.jar 11 | *.class 12 | /.lein-* 13 | /.nrepl-port 14 | .hgignore 15 | .hg/ 16 | .idea/* 17 | !/.idea/codeStyleSettings.xml 18 | *.iml 19 | -------------------------------------------------------------------------------- /.idea/codeStyleSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [Unreleased] 5 | ### Changed 6 | - Add a new arity to `make-widget-async` to provide a different widget shape. 7 | - Migrate to [shadow-cljs](https://shadow-cljs.github.io/docs/UsersGuide.html) and 8 | [lein-shadow](https://gitlab.com/nikperic/lein-shadow) 9 | - Replace `*test-context*` dynvar of atoms with a `test-context*` atom of pure 10 | data. 11 | 12 | ## [0.1.1] - 2016-07-25 13 | ### Changed 14 | - Documentation on how to make the widgets. 15 | 16 | ### Removed 17 | - `make-widget-sync` - we're all async, all the time. 18 | 19 | ### Fixed 20 | - Fixed widget maker to keep working when daylight savings switches over. 21 | 22 | ## 0.1.0 - 2016-07-25 23 | ### Added 24 | - Files from the new template. 25 | - Widget maker public API - `make-widget-sync`. 26 | 27 | [Unreleased]: https://github.com/your-name/re-frame-test/compare/0.1.1...HEAD 28 | [0.1.1]: https://github.com/your-name/re-frame-test/compare/0.1.0...0.1.1 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Mike Thompson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 7 | [![Clojars Project](https://img.shields.io/clojars/v/day8.re-frame/test?style=for-the-badge&logo=clojure&logoColor=fff)](https://clojars.org/day8.re-frame/test) 8 | [![GitHub issues](https://img.shields.io/github/issues-raw/day8/re-frame-test?style=for-the-badge&logo=github)](https://github.com/day8/re-frame-test/issues) 9 | [![License](https://img.shields.io/github/license/day8/re-frame-test?style=for-the-badge)](LICENSE) 10 | 11 | # re-frame-test 12 | 13 | This library provides utilities 14 | for testing [re-frame applications](https://github.com/day8/re-frame). 15 | 16 | These utilities: 17 | - allow you to use `subscribe` in your tests 18 | - allow you to use `dispatch` in your tests 19 | - allow you to run tests on both the JVM and JS platforms 20 | - allow you to create "end to end" integration tests, involving backend servers 21 | 22 | For context, please be sure to read the 23 | [basic testing tutorial](https://github.com/day8/re-frame/blob/master/docs/Testing.md) 24 | in the main re-frame docs before going any further. 25 | 26 | This library primarily supports the testing of Event Handlers, but Subscription Handlers 27 | get to come along for the ride. 28 | 29 | ## Quick Start Guide 30 | 31 | ### Step 1. Add Dependency 32 | 33 | Add the following project dependency:
34 | [![Clojars Project](https://img.shields.io/clojars/v/day8.re-frame/test.svg)](https://clojars.org/day8.re-frame/test) 35 | 36 | Requires re-frame >= "1.1.1" 37 | 38 | ### Step 2. Registration And Use 39 | 40 | In the namespace where you register your tests, perhaps called `tests.cljs`, you have 2 things to do. 41 | 42 | **First**, add this require to the `ns`: 43 | ```clj 44 | (ns app.tests 45 | (:require 46 | ... 47 | [day8.re-frame.test :as rf-test] ;; <-- add this 48 | ...)) 49 | ``` 50 | 51 | **Second**, Define or Modify some tests. 52 | ```Clojure 53 | (deftest init 54 | (rf-test/run-test-sync ;; <-- add this 55 | ;; with the above macro this becomes a dispatch-sync 56 | ;; and app-db is isolated between tests 57 | (rf/dispatch [:initialise-db]) 58 | ;; Define subscriptions to the app state 59 | (let [showing (rf/subscribe [:showing])] 60 | ;;Assert the initial state 61 | (is (= :all @showing))))) 62 | ``` 63 | 64 | ## How It Works 65 | 66 | `re-frame-test` provides two macros which dovetail with `cljs.test`. 67 | 68 | ### run-test-sync 69 | 70 | Execute `body` as a test, where each `dispatch` call is executed 71 | synchronously (via `dispatch-sync`), and any subsequent dispatches which are 72 | caused by that dispatch are also fully handled/executed prior to control flow 73 | returning to your test. 74 | 75 | Think of it as though every `dispatch` in your app had been magically 76 | turned into `dispatch-sync`, and re-frame had lifted the restriction that says 77 | you can't call `dispatch-sync` from within an event handler. 78 | 79 | This macro is applicable for most events that do not run async behaviour within the 80 | event. 81 | 82 | From the todomvc example: 83 | 84 | ```Clojure 85 | (defn test-fixtures 86 | [] 87 | ;; change this coeffect to make tests start with nothing 88 | (rf/reg-cofx 89 | :local-store-todos 90 | (fn [cofx _] 91 | "Read in todos from localstore, and process into a map we can merge into app-db." 92 | (assoc cofx :local-store-todos 93 | (sorted-map))))) 94 | ``` 95 | 96 | Define some test-fixtures. In this case we have to ignore the localstore 97 | in the tests. 98 | 99 | ```Clojure 100 | (deftest basic--sync 101 | (rf-test/run-test-sync 102 | (test-fixtures) 103 | (rf/dispatch [:initialise-db]) 104 | ``` 105 | 106 | Use the `run-test-sync` macro to construct the tests and initialise the app state. 107 | Note that, the `dispatch` will be handled before the following code is executed, 108 | effectively turning it into a `dispatch-sync`. Also any changes to the database 109 | and registrations will be rolled back at the termination of the test, therefore 110 | our fixtures are run within the `run-test-sync` macro. 111 | 112 | ```Clojure 113 | ;; Define subscriptions to the app state 114 | (let [showing (rf/subscribe [:showing]) 115 | sorted-todos (rf/subscribe [:sorted-todos]) 116 | todos (rf/subscribe [:todos]) 117 | visible-todos (rf/subscribe [:visible-todos]) 118 | all-complete? (rf/subscribe [:all-complete?]) 119 | completed-count (rf/subscribe [:completed-count]) 120 | footer-counts (rf/subscribe [:footer-counts])] 121 | 122 | ;;Assert the initial state 123 | (is (= :all @showing)) 124 | (is (empty? @sorted-todos)) 125 | (is (empty? @todos)) 126 | (is (empty? @visible-todos)) 127 | (is (= false (boolean @all-complete?))) 128 | (is (= 0 @completed-count)) 129 | (is (= [0 0] @footer-counts)) 130 | 131 | ;;Dispatch the event that you want to test, remember that `re-frame-test` 132 | ;;will process this event immediately. 133 | (rf/dispatch [:add-todo "write first test"]) 134 | 135 | ;;Test that the dispatch has mutated the state in the way that we expect. 136 | (is (= 1 (count @todos) (count @visible-todos) (count @sorted-todos))) 137 | (is (= 0 @completed-count)) 138 | (is (= [1 0] @footer-counts)) 139 | (is (= {:id 1, :title "write first test", :done false} 140 | (first @todos))) 141 | ``` 142 | 143 | ### run-test-async 144 | 145 | This macro is applicable for events that do run some async behaviour 146 | (usually outside or re-frame, e.g. an http request) within the event. 147 | 148 | Run `body` as an async re-frame test. The async nature means you'll need to 149 | use `wait-for` any time you want to make any assertions that should be true 150 | *after* an event has been handled. It's assumed that there will be at least 151 | one `wait-for` in the body of your test (otherwise you don't need this macro 152 | at all). 153 | 154 | Note: unlike regular ClojureScript `cljs.test/async` tests, `wait-for` takes 155 | care of calling `(done)` for you: you don't need to do anything specific to 156 | handle the fact that your test is asynchronous, other than make sure that all 157 | your assertions happen with `wait-for` blocks. 158 | 159 | From the todomvc example: 160 | 161 | ```Clojure 162 | (deftest basic--async 163 | (rf-test/run-test-async 164 | (test-fixtures) 165 | (rf/dispatch-sync [:initialise-db]) 166 | ``` 167 | 168 | Use the `run-test-async` macro to construct the tests and initialise the app state 169 | note that the `dispatch-sync` must be used as this macro does not run the dispatch 170 | immediately like `run-test-sync`. Also any changes to the database 171 | and registrations will be rolled back at the termination of the test, therefore 172 | our fixtures are run within the `run-test-async` macro. 173 | 174 | ```Clojure 175 | ;;Define subscriptions to the app state 176 | (let [showing (rf/subscribe [:showing]) 177 | sorted-todos (rf/subscribe [:sorted-todos]) 178 | todos (rf/subscribe [:todos]) 179 | visible-todos (rf/subscribe [:visible-todos]) 180 | all-complete? (rf/subscribe [:all-complete?]) 181 | completed-count (rf/subscribe [:completed-count]) 182 | footer-counts (rf/subscribe [:footer-counts])] 183 | 184 | ;;Assert the initial state 185 | (is (= :all @showing)) 186 | (is (empty? @sorted-todos)) 187 | (is (empty? @todos)) 188 | (is (empty? @visible-todos)) 189 | (is (= 0 @completed-count)) 190 | 191 | ;;Dispatch the event that you want to test, remember 192 | ;;that re-frame will not process this event immediately, 193 | ;;and need to use the `wait-for` macro to continue the tests. 194 | (rf/dispatch [:add-todo "write first test"]) 195 | 196 | 197 | ;;Wait for the `:add-todo` event to be dispatched 198 | ;;(note, in the use of this macro we would typically wait for 199 | ;;an event that had been triggered by the successful return of 200 | ;;the async event). 201 | (rf-test/wait-for [:add-todo-finished] 202 | 203 | ;;Test that the dispatch has mutated the state in the way 204 | ;;that we expect. 205 | (is (= [{:id 1, :title "write first test", :done false}] @todos)) 206 | ``` 207 | 208 | Here we have assumed that the `:add-todo` event will make some sort of async 209 | call which will in turn generate an `add-todo-finished` event when it has finished. 210 | This is not actually the case in the example code. 211 | 212 | ## Running the CLJS tests with Karma 213 | 214 | You will need `npm`, with: 215 | 216 | ```console 217 | $ npm install -g karma karma-cli karma-cljs-test karma-junit-reporter karma-chrome-launcher 218 | ``` 219 | 220 | And you will need Chrome. 221 | 222 | 223 | ## License 224 | 225 | Copyright (c) 2016 Mike Thompson 226 | 227 | Distributed under the The MIT License (MIT). 228 | -------------------------------------------------------------------------------- /doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to re-frame-test 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /examples/todomvc/.gitignore: -------------------------------------------------------------------------------- 1 | /resources/public/js 2 | /target 3 | /node_modules/ 4 | /.shadow-cljs/ 5 | /shadow-cljs.edn 6 | -------------------------------------------------------------------------------- /examples/todomvc/README.md: -------------------------------------------------------------------------------- 1 | # TodoMVC done with re-frame 2 | 3 | A [re-frame](https://github.com/day8/re-frame) implementation of [TodoMVC](http://todomvc.com/). 4 | 5 | 6 | ## Setup And Run 7 | 8 | 1. Install [Leiningen](http://leiningen.org/) (plus Java). 9 | 10 | 2. Get the re-frame repo 11 | ``` 12 | git clone https://github.com/day8/re-frame.git 13 | ``` 14 | 15 | 3. cd to the right example directory 16 | ``` 17 | cd re-frame/examples/todomvc 18 | ``` 19 | 20 | 4. Clean build 21 | ``` 22 | lein do clean, figwheel 23 | ``` 24 | 25 | 5. Run 26 | You'll have to wait for step 4 to do its compile, but then: 27 | ``` 28 | open http://localhost:3450 29 | ``` 30 | 31 | ## run the karma tests 32 | 33 | 1. get karma installed 34 | ``` 35 | lein npm install 36 | npm install -g karma-cli 37 | ``` 38 | 39 | 2. compile the code 40 | ``` 41 | lein karma-auto 42 | ``` 43 | 44 | 2. run the karma cli 45 | ``` 46 | karma start 47 | ``` 48 | 49 | 50 | 51 | ## Compile an optimised version 52 | 53 | 1. Compile 54 | ``` 55 | lein prod-once 56 | ``` 57 | 58 | 2. Open the following in your browser 59 | ``` 60 | resources/public/index.html 61 | ``` 62 | 63 | 64 | ## Exploring The Code 65 | 66 | From the re-frame readme: 67 | ``` 68 | To build a re-frame app, you: 69 | - design your app's data structure (data layer) 70 | - write and register subscription functions (query layer) 71 | - write Reagent component functions (view layer) 72 | - write and register event handler functions (control layer and/or state transition layer) 73 | ``` 74 | 75 | In `src`, there's a matching set of files (each small): 76 | ``` 77 | src 78 | ├── core.cljs <--- entry point, plus history 79 | ├── db.cljs <--- data related (data layer) 80 | ├── subs.cljs <--- subscription handlers (query layer) 81 | ├── views.cljs <--- reagent components (view layer) 82 | └── events.cljs <--- event handlers (control/update layer) 83 | ``` 84 | 85 | ## Notes 86 | 87 | Various: 88 | - The [official reagent example](https://github.com/reagent-project/reagent/tree/master/examples/todomvc) 89 | - Look at the [re-frame Wiki](https://github.com/day8/re-frame/wiki) 90 | -------------------------------------------------------------------------------- /examples/todomvc/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | var junitOutputDir = process.env.CIRCLE_TEST_REPORTS || "target/junit" 3 | 4 | config.set({ 5 | browsers: ['ChromeHeadless'], 6 | basePath: 'target', 7 | files: ['karma-test.js'], 8 | frameworks: ['cljs-test'], 9 | plugins: [ 10 | 'karma-cljs-test', 11 | 'karma-chrome-launcher', 12 | 'karma-junit-reporter' 13 | ], 14 | colors: true, 15 | logLevel: config.LOG_INFO, 16 | client: { 17 | args: ['shadow.test.karma.init'], 18 | singleRun: true 19 | }, 20 | 21 | // the default configuration 22 | junitReporter: { 23 | outputDir: junitOutputDir + '/karma', // results will be saved as outputDir/browserName.xml 24 | outputFile: undefined, // if included, results will be saved as outputDir/browserName/outputFile 25 | suite: '' // suite will become the package name attribute in xml testsuite element 26 | } 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /examples/todomvc/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-re-frame", 3 | "requires": true, 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "@types/color-name": { 7 | "version": "1.1.1", 8 | "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", 9 | "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", 10 | "dev": true 11 | }, 12 | "accepts": { 13 | "version": "1.3.7", 14 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 15 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 16 | "dev": true, 17 | "requires": { 18 | "mime-types": "~2.1.24", 19 | "negotiator": "0.6.2" 20 | } 21 | }, 22 | "after": { 23 | "version": "0.8.2", 24 | "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", 25 | "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", 26 | "dev": true 27 | }, 28 | "ansi-regex": { 29 | "version": "5.0.0", 30 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 31 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", 32 | "dev": true 33 | }, 34 | "ansi-styles": { 35 | "version": "4.2.1", 36 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", 37 | "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", 38 | "dev": true, 39 | "requires": { 40 | "@types/color-name": "^1.1.1", 41 | "color-convert": "^2.0.1" 42 | } 43 | }, 44 | "anymatch": { 45 | "version": "3.1.1", 46 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", 47 | "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", 48 | "dev": true, 49 | "requires": { 50 | "normalize-path": "^3.0.0", 51 | "picomatch": "^2.0.4" 52 | } 53 | }, 54 | "arraybuffer.slice": { 55 | "version": "0.0.7", 56 | "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", 57 | "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", 58 | "dev": true 59 | }, 60 | "asn1.js": { 61 | "version": "5.4.1", 62 | "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", 63 | "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", 64 | "dev": true, 65 | "requires": { 66 | "bn.js": "^4.0.0", 67 | "inherits": "^2.0.1", 68 | "minimalistic-assert": "^1.0.0", 69 | "safer-buffer": "^2.1.0" 70 | }, 71 | "dependencies": { 72 | "bn.js": { 73 | "version": "4.11.9", 74 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", 75 | "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", 76 | "dev": true 77 | } 78 | } 79 | }, 80 | "assert": { 81 | "version": "1.5.0", 82 | "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", 83 | "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", 84 | "dev": true, 85 | "requires": { 86 | "object-assign": "^4.1.1", 87 | "util": "0.10.3" 88 | }, 89 | "dependencies": { 90 | "inherits": { 91 | "version": "2.0.1", 92 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", 93 | "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", 94 | "dev": true 95 | }, 96 | "util": { 97 | "version": "0.10.3", 98 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", 99 | "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", 100 | "dev": true, 101 | "requires": { 102 | "inherits": "2.0.1" 103 | } 104 | } 105 | } 106 | }, 107 | "async-limiter": { 108 | "version": "1.0.1", 109 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", 110 | "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", 111 | "dev": true 112 | }, 113 | "backo2": { 114 | "version": "1.0.2", 115 | "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", 116 | "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", 117 | "dev": true 118 | }, 119 | "balanced-match": { 120 | "version": "1.0.0", 121 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 122 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 123 | "dev": true 124 | }, 125 | "base64-arraybuffer": { 126 | "version": "0.1.5", 127 | "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", 128 | "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", 129 | "dev": true 130 | }, 131 | "base64-js": { 132 | "version": "1.3.1", 133 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", 134 | "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", 135 | "dev": true 136 | }, 137 | "base64id": { 138 | "version": "2.0.0", 139 | "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", 140 | "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", 141 | "dev": true 142 | }, 143 | "better-assert": { 144 | "version": "1.0.2", 145 | "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", 146 | "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", 147 | "dev": true, 148 | "requires": { 149 | "callsite": "1.0.0" 150 | } 151 | }, 152 | "binary-extensions": { 153 | "version": "2.1.0", 154 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", 155 | "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", 156 | "dev": true 157 | }, 158 | "blob": { 159 | "version": "0.0.5", 160 | "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", 161 | "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", 162 | "dev": true 163 | }, 164 | "bn.js": { 165 | "version": "5.1.3", 166 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", 167 | "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", 168 | "dev": true 169 | }, 170 | "body-parser": { 171 | "version": "1.19.0", 172 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 173 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 174 | "dev": true, 175 | "requires": { 176 | "bytes": "3.1.0", 177 | "content-type": "~1.0.4", 178 | "debug": "2.6.9", 179 | "depd": "~1.1.2", 180 | "http-errors": "1.7.2", 181 | "iconv-lite": "0.4.24", 182 | "on-finished": "~2.3.0", 183 | "qs": "6.7.0", 184 | "raw-body": "2.4.0", 185 | "type-is": "~1.6.17" 186 | } 187 | }, 188 | "brace-expansion": { 189 | "version": "1.1.11", 190 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 191 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 192 | "dev": true, 193 | "requires": { 194 | "balanced-match": "^1.0.0", 195 | "concat-map": "0.0.1" 196 | } 197 | }, 198 | "braces": { 199 | "version": "3.0.2", 200 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 201 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 202 | "dev": true, 203 | "requires": { 204 | "fill-range": "^7.0.1" 205 | } 206 | }, 207 | "brorand": { 208 | "version": "1.1.0", 209 | "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", 210 | "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", 211 | "dev": true 212 | }, 213 | "browserify-aes": { 214 | "version": "1.2.0", 215 | "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", 216 | "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", 217 | "dev": true, 218 | "requires": { 219 | "buffer-xor": "^1.0.3", 220 | "cipher-base": "^1.0.0", 221 | "create-hash": "^1.1.0", 222 | "evp_bytestokey": "^1.0.3", 223 | "inherits": "^2.0.1", 224 | "safe-buffer": "^5.0.1" 225 | } 226 | }, 227 | "browserify-cipher": { 228 | "version": "1.0.1", 229 | "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", 230 | "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", 231 | "dev": true, 232 | "requires": { 233 | "browserify-aes": "^1.0.4", 234 | "browserify-des": "^1.0.0", 235 | "evp_bytestokey": "^1.0.0" 236 | } 237 | }, 238 | "browserify-des": { 239 | "version": "1.0.2", 240 | "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", 241 | "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", 242 | "dev": true, 243 | "requires": { 244 | "cipher-base": "^1.0.1", 245 | "des.js": "^1.0.0", 246 | "inherits": "^2.0.1", 247 | "safe-buffer": "^5.1.2" 248 | } 249 | }, 250 | "browserify-rsa": { 251 | "version": "4.0.1", 252 | "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", 253 | "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", 254 | "dev": true, 255 | "requires": { 256 | "bn.js": "^4.1.0", 257 | "randombytes": "^2.0.1" 258 | }, 259 | "dependencies": { 260 | "bn.js": { 261 | "version": "4.11.9", 262 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", 263 | "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", 264 | "dev": true 265 | } 266 | } 267 | }, 268 | "browserify-sign": { 269 | "version": "4.2.1", 270 | "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", 271 | "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", 272 | "dev": true, 273 | "requires": { 274 | "bn.js": "^5.1.1", 275 | "browserify-rsa": "^4.0.1", 276 | "create-hash": "^1.2.0", 277 | "create-hmac": "^1.1.7", 278 | "elliptic": "^6.5.3", 279 | "inherits": "^2.0.4", 280 | "parse-asn1": "^5.1.5", 281 | "readable-stream": "^3.6.0", 282 | "safe-buffer": "^5.2.0" 283 | }, 284 | "dependencies": { 285 | "inherits": { 286 | "version": "2.0.4", 287 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 288 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 289 | "dev": true 290 | }, 291 | "readable-stream": { 292 | "version": "3.6.0", 293 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 294 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 295 | "dev": true, 296 | "requires": { 297 | "inherits": "^2.0.3", 298 | "string_decoder": "^1.1.1", 299 | "util-deprecate": "^1.0.1" 300 | } 301 | } 302 | } 303 | }, 304 | "browserify-zlib": { 305 | "version": "0.2.0", 306 | "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", 307 | "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", 308 | "dev": true, 309 | "requires": { 310 | "pako": "~1.0.5" 311 | } 312 | }, 313 | "buffer": { 314 | "version": "4.9.2", 315 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", 316 | "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", 317 | "dev": true, 318 | "requires": { 319 | "base64-js": "^1.0.2", 320 | "ieee754": "^1.1.4", 321 | "isarray": "^1.0.0" 322 | }, 323 | "dependencies": { 324 | "isarray": { 325 | "version": "1.0.0", 326 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 327 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 328 | "dev": true 329 | } 330 | } 331 | }, 332 | "buffer-xor": { 333 | "version": "1.0.3", 334 | "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", 335 | "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", 336 | "dev": true 337 | }, 338 | "builtin-status-codes": { 339 | "version": "3.0.0", 340 | "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", 341 | "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", 342 | "dev": true 343 | }, 344 | "bytes": { 345 | "version": "3.1.0", 346 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 347 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", 348 | "dev": true 349 | }, 350 | "callsite": { 351 | "version": "1.0.0", 352 | "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", 353 | "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", 354 | "dev": true 355 | }, 356 | "camelcase": { 357 | "version": "5.3.1", 358 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 359 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", 360 | "dev": true 361 | }, 362 | "chokidar": { 363 | "version": "3.4.2", 364 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", 365 | "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", 366 | "dev": true, 367 | "requires": { 368 | "anymatch": "~3.1.1", 369 | "braces": "~3.0.2", 370 | "fsevents": "~2.1.2", 371 | "glob-parent": "~5.1.0", 372 | "is-binary-path": "~2.1.0", 373 | "is-glob": "~4.0.1", 374 | "normalize-path": "~3.0.0", 375 | "readdirp": "~3.4.0" 376 | } 377 | }, 378 | "cipher-base": { 379 | "version": "1.0.4", 380 | "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", 381 | "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", 382 | "dev": true, 383 | "requires": { 384 | "inherits": "^2.0.1", 385 | "safe-buffer": "^5.0.1" 386 | } 387 | }, 388 | "cliui": { 389 | "version": "6.0.0", 390 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", 391 | "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", 392 | "dev": true, 393 | "requires": { 394 | "string-width": "^4.2.0", 395 | "strip-ansi": "^6.0.0", 396 | "wrap-ansi": "^6.2.0" 397 | } 398 | }, 399 | "color-convert": { 400 | "version": "2.0.1", 401 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 402 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 403 | "dev": true, 404 | "requires": { 405 | "color-name": "~1.1.4" 406 | } 407 | }, 408 | "color-name": { 409 | "version": "1.1.4", 410 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 411 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 412 | "dev": true 413 | }, 414 | "colors": { 415 | "version": "1.4.0", 416 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 417 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", 418 | "dev": true 419 | }, 420 | "component-bind": { 421 | "version": "1.0.0", 422 | "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", 423 | "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", 424 | "dev": true 425 | }, 426 | "component-emitter": { 427 | "version": "1.2.1", 428 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 429 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", 430 | "dev": true 431 | }, 432 | "component-inherit": { 433 | "version": "0.0.3", 434 | "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", 435 | "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", 436 | "dev": true 437 | }, 438 | "concat-map": { 439 | "version": "0.0.1", 440 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 441 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 442 | "dev": true 443 | }, 444 | "connect": { 445 | "version": "3.7.0", 446 | "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", 447 | "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", 448 | "dev": true, 449 | "requires": { 450 | "debug": "2.6.9", 451 | "finalhandler": "1.1.2", 452 | "parseurl": "~1.3.3", 453 | "utils-merge": "1.0.1" 454 | } 455 | }, 456 | "console-browserify": { 457 | "version": "1.2.0", 458 | "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", 459 | "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", 460 | "dev": true 461 | }, 462 | "constants-browserify": { 463 | "version": "1.0.0", 464 | "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", 465 | "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", 466 | "dev": true 467 | }, 468 | "content-type": { 469 | "version": "1.0.4", 470 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 471 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", 472 | "dev": true 473 | }, 474 | "cookie": { 475 | "version": "0.3.1", 476 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 477 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", 478 | "dev": true 479 | }, 480 | "core-util-is": { 481 | "version": "1.0.2", 482 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 483 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 484 | "dev": true 485 | }, 486 | "create-ecdh": { 487 | "version": "4.0.4", 488 | "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", 489 | "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", 490 | "dev": true, 491 | "requires": { 492 | "bn.js": "^4.1.0", 493 | "elliptic": "^6.5.3" 494 | }, 495 | "dependencies": { 496 | "bn.js": { 497 | "version": "4.11.9", 498 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", 499 | "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", 500 | "dev": true 501 | } 502 | } 503 | }, 504 | "create-hash": { 505 | "version": "1.2.0", 506 | "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", 507 | "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", 508 | "dev": true, 509 | "requires": { 510 | "cipher-base": "^1.0.1", 511 | "inherits": "^2.0.1", 512 | "md5.js": "^1.3.4", 513 | "ripemd160": "^2.0.1", 514 | "sha.js": "^2.4.0" 515 | } 516 | }, 517 | "create-hmac": { 518 | "version": "1.1.7", 519 | "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", 520 | "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", 521 | "dev": true, 522 | "requires": { 523 | "cipher-base": "^1.0.3", 524 | "create-hash": "^1.1.0", 525 | "inherits": "^2.0.1", 526 | "ripemd160": "^2.0.0", 527 | "safe-buffer": "^5.0.1", 528 | "sha.js": "^2.4.8" 529 | } 530 | }, 531 | "crypto-browserify": { 532 | "version": "3.12.0", 533 | "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", 534 | "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", 535 | "dev": true, 536 | "requires": { 537 | "browserify-cipher": "^1.0.0", 538 | "browserify-sign": "^4.0.0", 539 | "create-ecdh": "^4.0.0", 540 | "create-hash": "^1.1.0", 541 | "create-hmac": "^1.1.0", 542 | "diffie-hellman": "^5.0.0", 543 | "inherits": "^2.0.1", 544 | "pbkdf2": "^3.0.3", 545 | "public-encrypt": "^4.0.0", 546 | "randombytes": "^2.0.0", 547 | "randomfill": "^1.0.3" 548 | } 549 | }, 550 | "custom-event": { 551 | "version": "1.0.1", 552 | "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", 553 | "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", 554 | "dev": true 555 | }, 556 | "date-format": { 557 | "version": "3.0.0", 558 | "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", 559 | "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==", 560 | "dev": true 561 | }, 562 | "debug": { 563 | "version": "2.6.9", 564 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 565 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 566 | "dev": true, 567 | "requires": { 568 | "ms": "2.0.0" 569 | } 570 | }, 571 | "decamelize": { 572 | "version": "1.2.0", 573 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 574 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 575 | "dev": true 576 | }, 577 | "depd": { 578 | "version": "1.1.2", 579 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 580 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", 581 | "dev": true 582 | }, 583 | "des.js": { 584 | "version": "1.0.1", 585 | "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", 586 | "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", 587 | "dev": true, 588 | "requires": { 589 | "inherits": "^2.0.1", 590 | "minimalistic-assert": "^1.0.0" 591 | } 592 | }, 593 | "di": { 594 | "version": "0.0.1", 595 | "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", 596 | "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", 597 | "dev": true 598 | }, 599 | "diffie-hellman": { 600 | "version": "5.0.3", 601 | "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", 602 | "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", 603 | "dev": true, 604 | "requires": { 605 | "bn.js": "^4.1.0", 606 | "miller-rabin": "^4.0.0", 607 | "randombytes": "^2.0.0" 608 | }, 609 | "dependencies": { 610 | "bn.js": { 611 | "version": "4.11.9", 612 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", 613 | "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", 614 | "dev": true 615 | } 616 | } 617 | }, 618 | "dom-serialize": { 619 | "version": "2.2.1", 620 | "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", 621 | "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", 622 | "dev": true, 623 | "requires": { 624 | "custom-event": "~1.0.0", 625 | "ent": "~2.2.0", 626 | "extend": "^3.0.0", 627 | "void-elements": "^2.0.0" 628 | } 629 | }, 630 | "domain-browser": { 631 | "version": "1.2.0", 632 | "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", 633 | "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", 634 | "dev": true 635 | }, 636 | "ee-first": { 637 | "version": "1.1.1", 638 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 639 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", 640 | "dev": true 641 | }, 642 | "elliptic": { 643 | "version": "6.5.3", 644 | "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", 645 | "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", 646 | "dev": true, 647 | "requires": { 648 | "bn.js": "^4.4.0", 649 | "brorand": "^1.0.1", 650 | "hash.js": "^1.0.0", 651 | "hmac-drbg": "^1.0.0", 652 | "inherits": "^2.0.1", 653 | "minimalistic-assert": "^1.0.0", 654 | "minimalistic-crypto-utils": "^1.0.0" 655 | }, 656 | "dependencies": { 657 | "bn.js": { 658 | "version": "4.11.9", 659 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", 660 | "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", 661 | "dev": true 662 | } 663 | } 664 | }, 665 | "emoji-regex": { 666 | "version": "8.0.0", 667 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 668 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 669 | "dev": true 670 | }, 671 | "encodeurl": { 672 | "version": "1.0.2", 673 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 674 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", 675 | "dev": true 676 | }, 677 | "engine.io": { 678 | "version": "3.4.2", 679 | "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.2.tgz", 680 | "integrity": "sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg==", 681 | "dev": true, 682 | "requires": { 683 | "accepts": "~1.3.4", 684 | "base64id": "2.0.0", 685 | "cookie": "0.3.1", 686 | "debug": "~4.1.0", 687 | "engine.io-parser": "~2.2.0", 688 | "ws": "^7.1.2" 689 | }, 690 | "dependencies": { 691 | "debug": { 692 | "version": "4.1.1", 693 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 694 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 695 | "dev": true, 696 | "requires": { 697 | "ms": "^2.1.1" 698 | } 699 | }, 700 | "ms": { 701 | "version": "2.1.2", 702 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 703 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 704 | "dev": true 705 | } 706 | } 707 | }, 708 | "engine.io-client": { 709 | "version": "3.4.3", 710 | "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.3.tgz", 711 | "integrity": "sha512-0NGY+9hioejTEJCaSJZfWZLk4FPI9dN+1H1C4+wj2iuFba47UgZbJzfWs4aNFajnX/qAaYKbe2lLTfEEWzCmcw==", 712 | "dev": true, 713 | "requires": { 714 | "component-emitter": "~1.3.0", 715 | "component-inherit": "0.0.3", 716 | "debug": "~4.1.0", 717 | "engine.io-parser": "~2.2.0", 718 | "has-cors": "1.1.0", 719 | "indexof": "0.0.1", 720 | "parseqs": "0.0.5", 721 | "parseuri": "0.0.5", 722 | "ws": "~6.1.0", 723 | "xmlhttprequest-ssl": "~1.5.4", 724 | "yeast": "0.1.2" 725 | }, 726 | "dependencies": { 727 | "component-emitter": { 728 | "version": "1.3.0", 729 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", 730 | "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", 731 | "dev": true 732 | }, 733 | "debug": { 734 | "version": "4.1.1", 735 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 736 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 737 | "dev": true, 738 | "requires": { 739 | "ms": "^2.1.1" 740 | } 741 | }, 742 | "ms": { 743 | "version": "2.1.2", 744 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 745 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 746 | "dev": true 747 | }, 748 | "ws": { 749 | "version": "6.1.4", 750 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", 751 | "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", 752 | "dev": true, 753 | "requires": { 754 | "async-limiter": "~1.0.0" 755 | } 756 | } 757 | } 758 | }, 759 | "engine.io-parser": { 760 | "version": "2.2.0", 761 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz", 762 | "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==", 763 | "dev": true, 764 | "requires": { 765 | "after": "0.8.2", 766 | "arraybuffer.slice": "~0.0.7", 767 | "base64-arraybuffer": "0.1.5", 768 | "blob": "0.0.5", 769 | "has-binary2": "~1.0.2" 770 | } 771 | }, 772 | "ent": { 773 | "version": "2.2.0", 774 | "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", 775 | "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", 776 | "dev": true 777 | }, 778 | "escape-html": { 779 | "version": "1.0.3", 780 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 781 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", 782 | "dev": true 783 | }, 784 | "eventemitter3": { 785 | "version": "4.0.7", 786 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 787 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", 788 | "dev": true 789 | }, 790 | "events": { 791 | "version": "3.2.0", 792 | "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", 793 | "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", 794 | "dev": true 795 | }, 796 | "evp_bytestokey": { 797 | "version": "1.0.3", 798 | "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", 799 | "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", 800 | "dev": true, 801 | "requires": { 802 | "md5.js": "^1.3.4", 803 | "safe-buffer": "^5.1.1" 804 | } 805 | }, 806 | "extend": { 807 | "version": "3.0.2", 808 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 809 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", 810 | "dev": true 811 | }, 812 | "fill-range": { 813 | "version": "7.0.1", 814 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 815 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 816 | "dev": true, 817 | "requires": { 818 | "to-regex-range": "^5.0.1" 819 | } 820 | }, 821 | "finalhandler": { 822 | "version": "1.1.2", 823 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 824 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 825 | "dev": true, 826 | "requires": { 827 | "debug": "2.6.9", 828 | "encodeurl": "~1.0.2", 829 | "escape-html": "~1.0.3", 830 | "on-finished": "~2.3.0", 831 | "parseurl": "~1.3.3", 832 | "statuses": "~1.5.0", 833 | "unpipe": "~1.0.0" 834 | } 835 | }, 836 | "find-up": { 837 | "version": "4.1.0", 838 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 839 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 840 | "dev": true, 841 | "requires": { 842 | "locate-path": "^5.0.0", 843 | "path-exists": "^4.0.0" 844 | } 845 | }, 846 | "flatted": { 847 | "version": "2.0.2", 848 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", 849 | "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", 850 | "dev": true 851 | }, 852 | "follow-redirects": { 853 | "version": "1.13.0", 854 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", 855 | "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", 856 | "dev": true 857 | }, 858 | "fs-extra": { 859 | "version": "8.1.0", 860 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", 861 | "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", 862 | "dev": true, 863 | "requires": { 864 | "graceful-fs": "^4.2.0", 865 | "jsonfile": "^4.0.0", 866 | "universalify": "^0.1.0" 867 | } 868 | }, 869 | "fs.realpath": { 870 | "version": "1.0.0", 871 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 872 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 873 | "dev": true 874 | }, 875 | "fsevents": { 876 | "version": "2.1.3", 877 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", 878 | "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", 879 | "dev": true, 880 | "optional": true 881 | }, 882 | "get-caller-file": { 883 | "version": "2.0.5", 884 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 885 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 886 | "dev": true 887 | }, 888 | "glob": { 889 | "version": "7.1.6", 890 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 891 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 892 | "dev": true, 893 | "requires": { 894 | "fs.realpath": "^1.0.0", 895 | "inflight": "^1.0.4", 896 | "inherits": "2", 897 | "minimatch": "^3.0.4", 898 | "once": "^1.3.0", 899 | "path-is-absolute": "^1.0.0" 900 | } 901 | }, 902 | "glob-parent": { 903 | "version": "5.1.1", 904 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", 905 | "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", 906 | "dev": true, 907 | "requires": { 908 | "is-glob": "^4.0.1" 909 | } 910 | }, 911 | "graceful-fs": { 912 | "version": "4.2.4", 913 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", 914 | "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", 915 | "dev": true 916 | }, 917 | "has-binary2": { 918 | "version": "1.0.3", 919 | "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", 920 | "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", 921 | "dev": true, 922 | "requires": { 923 | "isarray": "2.0.1" 924 | } 925 | }, 926 | "has-cors": { 927 | "version": "1.1.0", 928 | "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", 929 | "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", 930 | "dev": true 931 | }, 932 | "hash-base": { 933 | "version": "3.1.0", 934 | "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", 935 | "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", 936 | "dev": true, 937 | "requires": { 938 | "inherits": "^2.0.4", 939 | "readable-stream": "^3.6.0", 940 | "safe-buffer": "^5.2.0" 941 | }, 942 | "dependencies": { 943 | "inherits": { 944 | "version": "2.0.4", 945 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 946 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 947 | "dev": true 948 | }, 949 | "readable-stream": { 950 | "version": "3.6.0", 951 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 952 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 953 | "dev": true, 954 | "requires": { 955 | "inherits": "^2.0.3", 956 | "string_decoder": "^1.1.1", 957 | "util-deprecate": "^1.0.1" 958 | } 959 | } 960 | } 961 | }, 962 | "hash.js": { 963 | "version": "1.1.7", 964 | "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", 965 | "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", 966 | "dev": true, 967 | "requires": { 968 | "inherits": "^2.0.3", 969 | "minimalistic-assert": "^1.0.1" 970 | } 971 | }, 972 | "hmac-drbg": { 973 | "version": "1.0.1", 974 | "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", 975 | "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", 976 | "dev": true, 977 | "requires": { 978 | "hash.js": "^1.0.3", 979 | "minimalistic-assert": "^1.0.0", 980 | "minimalistic-crypto-utils": "^1.0.1" 981 | } 982 | }, 983 | "http-errors": { 984 | "version": "1.7.2", 985 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 986 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 987 | "dev": true, 988 | "requires": { 989 | "depd": "~1.1.2", 990 | "inherits": "2.0.3", 991 | "setprototypeof": "1.1.1", 992 | "statuses": ">= 1.5.0 < 2", 993 | "toidentifier": "1.0.0" 994 | } 995 | }, 996 | "http-proxy": { 997 | "version": "1.18.1", 998 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 999 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 1000 | "dev": true, 1001 | "requires": { 1002 | "eventemitter3": "^4.0.0", 1003 | "follow-redirects": "^1.0.0", 1004 | "requires-port": "^1.0.0" 1005 | } 1006 | }, 1007 | "https-browserify": { 1008 | "version": "1.0.0", 1009 | "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", 1010 | "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", 1011 | "dev": true 1012 | }, 1013 | "iconv-lite": { 1014 | "version": "0.4.24", 1015 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 1016 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1017 | "dev": true, 1018 | "requires": { 1019 | "safer-buffer": ">= 2.1.2 < 3" 1020 | } 1021 | }, 1022 | "ieee754": { 1023 | "version": "1.1.13", 1024 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 1025 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", 1026 | "dev": true 1027 | }, 1028 | "indexof": { 1029 | "version": "0.0.1", 1030 | "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", 1031 | "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", 1032 | "dev": true 1033 | }, 1034 | "inflight": { 1035 | "version": "1.0.6", 1036 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1037 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 1038 | "dev": true, 1039 | "requires": { 1040 | "once": "^1.3.0", 1041 | "wrappy": "1" 1042 | } 1043 | }, 1044 | "inherits": { 1045 | "version": "2.0.3", 1046 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 1047 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 1048 | "dev": true 1049 | }, 1050 | "is-binary-path": { 1051 | "version": "2.1.0", 1052 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 1053 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 1054 | "dev": true, 1055 | "requires": { 1056 | "binary-extensions": "^2.0.0" 1057 | } 1058 | }, 1059 | "is-extglob": { 1060 | "version": "2.1.1", 1061 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1062 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 1063 | "dev": true 1064 | }, 1065 | "is-fullwidth-code-point": { 1066 | "version": "3.0.0", 1067 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1068 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 1069 | "dev": true 1070 | }, 1071 | "is-glob": { 1072 | "version": "4.0.1", 1073 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 1074 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 1075 | "dev": true, 1076 | "requires": { 1077 | "is-extglob": "^2.1.1" 1078 | } 1079 | }, 1080 | "is-number": { 1081 | "version": "7.0.0", 1082 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1083 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1084 | "dev": true 1085 | }, 1086 | "isarray": { 1087 | "version": "2.0.1", 1088 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", 1089 | "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", 1090 | "dev": true 1091 | }, 1092 | "isbinaryfile": { 1093 | "version": "4.0.6", 1094 | "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz", 1095 | "integrity": "sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==", 1096 | "dev": true 1097 | }, 1098 | "isexe": { 1099 | "version": "2.0.0", 1100 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1101 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 1102 | "dev": true 1103 | }, 1104 | "js-tokens": { 1105 | "version": "4.0.0", 1106 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1107 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 1108 | }, 1109 | "jsonfile": { 1110 | "version": "4.0.0", 1111 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", 1112 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", 1113 | "dev": true, 1114 | "requires": { 1115 | "graceful-fs": "^4.1.6" 1116 | } 1117 | }, 1118 | "karma": { 1119 | "version": "5.2.3", 1120 | "resolved": "https://registry.npmjs.org/karma/-/karma-5.2.3.tgz", 1121 | "integrity": "sha512-tHdyFADhVVPBorIKCX8A37iLHxc6RBRphkSoQ+MLKdAtFn1k97tD8WUGi1KlEtDZKL3hui0qhsY9HXUfSNDYPQ==", 1122 | "dev": true, 1123 | "requires": { 1124 | "body-parser": "^1.19.0", 1125 | "braces": "^3.0.2", 1126 | "chokidar": "^3.4.2", 1127 | "colors": "^1.4.0", 1128 | "connect": "^3.7.0", 1129 | "di": "^0.0.1", 1130 | "dom-serialize": "^2.2.1", 1131 | "glob": "^7.1.6", 1132 | "graceful-fs": "^4.2.4", 1133 | "http-proxy": "^1.18.1", 1134 | "isbinaryfile": "^4.0.6", 1135 | "lodash": "^4.17.19", 1136 | "log4js": "^6.2.1", 1137 | "mime": "^2.4.5", 1138 | "minimatch": "^3.0.4", 1139 | "qjobs": "^1.2.0", 1140 | "range-parser": "^1.2.1", 1141 | "rimraf": "^3.0.2", 1142 | "socket.io": "^2.3.0", 1143 | "source-map": "^0.6.1", 1144 | "tmp": "0.2.1", 1145 | "ua-parser-js": "0.7.22", 1146 | "yargs": "^15.3.1" 1147 | } 1148 | }, 1149 | "karma-chrome-launcher": { 1150 | "version": "3.1.0", 1151 | "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", 1152 | "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", 1153 | "dev": true, 1154 | "requires": { 1155 | "which": "^1.2.1" 1156 | } 1157 | }, 1158 | "karma-cljs-test": { 1159 | "version": "0.1.0", 1160 | "resolved": "https://registry.npmjs.org/karma-cljs-test/-/karma-cljs-test-0.1.0.tgz", 1161 | "integrity": "sha1-y4YF7w4R+ab20o9Wul298m84mSM=", 1162 | "dev": true 1163 | }, 1164 | "karma-junit-reporter": { 1165 | "version": "1.2.0", 1166 | "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-1.2.0.tgz", 1167 | "integrity": "sha1-T5xAzt+xo5X4rvh2q/lhiZF8Y5Y=", 1168 | "dev": true, 1169 | "requires": { 1170 | "path-is-absolute": "^1.0.0", 1171 | "xmlbuilder": "8.2.2" 1172 | } 1173 | }, 1174 | "locate-path": { 1175 | "version": "5.0.0", 1176 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 1177 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 1178 | "dev": true, 1179 | "requires": { 1180 | "p-locate": "^4.1.0" 1181 | } 1182 | }, 1183 | "lodash": { 1184 | "version": "4.17.20", 1185 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", 1186 | "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", 1187 | "dev": true 1188 | }, 1189 | "log4js": { 1190 | "version": "6.3.0", 1191 | "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", 1192 | "integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==", 1193 | "dev": true, 1194 | "requires": { 1195 | "date-format": "^3.0.0", 1196 | "debug": "^4.1.1", 1197 | "flatted": "^2.0.1", 1198 | "rfdc": "^1.1.4", 1199 | "streamroller": "^2.2.4" 1200 | }, 1201 | "dependencies": { 1202 | "debug": { 1203 | "version": "4.2.0", 1204 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", 1205 | "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", 1206 | "dev": true, 1207 | "requires": { 1208 | "ms": "2.1.2" 1209 | } 1210 | }, 1211 | "ms": { 1212 | "version": "2.1.2", 1213 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1214 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1215 | "dev": true 1216 | } 1217 | } 1218 | }, 1219 | "loose-envify": { 1220 | "version": "1.4.0", 1221 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 1222 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 1223 | "requires": { 1224 | "js-tokens": "^3.0.0 || ^4.0.0" 1225 | } 1226 | }, 1227 | "md5.js": { 1228 | "version": "1.3.5", 1229 | "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", 1230 | "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", 1231 | "dev": true, 1232 | "requires": { 1233 | "hash-base": "^3.0.0", 1234 | "inherits": "^2.0.1", 1235 | "safe-buffer": "^5.1.2" 1236 | } 1237 | }, 1238 | "media-typer": { 1239 | "version": "0.3.0", 1240 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1241 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", 1242 | "dev": true 1243 | }, 1244 | "miller-rabin": { 1245 | "version": "4.0.1", 1246 | "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", 1247 | "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", 1248 | "dev": true, 1249 | "requires": { 1250 | "bn.js": "^4.0.0", 1251 | "brorand": "^1.0.1" 1252 | }, 1253 | "dependencies": { 1254 | "bn.js": { 1255 | "version": "4.11.9", 1256 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", 1257 | "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", 1258 | "dev": true 1259 | } 1260 | } 1261 | }, 1262 | "mime": { 1263 | "version": "2.4.6", 1264 | "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", 1265 | "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", 1266 | "dev": true 1267 | }, 1268 | "mime-db": { 1269 | "version": "1.44.0", 1270 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 1271 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", 1272 | "dev": true 1273 | }, 1274 | "mime-types": { 1275 | "version": "2.1.27", 1276 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 1277 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 1278 | "dev": true, 1279 | "requires": { 1280 | "mime-db": "1.44.0" 1281 | } 1282 | }, 1283 | "minimalistic-assert": { 1284 | "version": "1.0.1", 1285 | "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", 1286 | "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", 1287 | "dev": true 1288 | }, 1289 | "minimalistic-crypto-utils": { 1290 | "version": "1.0.1", 1291 | "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", 1292 | "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", 1293 | "dev": true 1294 | }, 1295 | "minimatch": { 1296 | "version": "3.0.4", 1297 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1298 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1299 | "dev": true, 1300 | "requires": { 1301 | "brace-expansion": "^1.1.7" 1302 | } 1303 | }, 1304 | "ms": { 1305 | "version": "2.0.0", 1306 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1307 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 1308 | "dev": true 1309 | }, 1310 | "negotiator": { 1311 | "version": "0.6.2", 1312 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 1313 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", 1314 | "dev": true 1315 | }, 1316 | "node-libs-browser": { 1317 | "version": "2.2.1", 1318 | "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", 1319 | "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", 1320 | "dev": true, 1321 | "requires": { 1322 | "assert": "^1.1.1", 1323 | "browserify-zlib": "^0.2.0", 1324 | "buffer": "^4.3.0", 1325 | "console-browserify": "^1.1.0", 1326 | "constants-browserify": "^1.0.0", 1327 | "crypto-browserify": "^3.11.0", 1328 | "domain-browser": "^1.1.1", 1329 | "events": "^3.0.0", 1330 | "https-browserify": "^1.0.0", 1331 | "os-browserify": "^0.3.0", 1332 | "path-browserify": "0.0.1", 1333 | "process": "^0.11.10", 1334 | "punycode": "^1.2.4", 1335 | "querystring-es3": "^0.2.0", 1336 | "readable-stream": "^2.3.3", 1337 | "stream-browserify": "^2.0.1", 1338 | "stream-http": "^2.7.2", 1339 | "string_decoder": "^1.0.0", 1340 | "timers-browserify": "^2.0.4", 1341 | "tty-browserify": "0.0.0", 1342 | "url": "^0.11.0", 1343 | "util": "^0.11.0", 1344 | "vm-browserify": "^1.0.1" 1345 | } 1346 | }, 1347 | "normalize-path": { 1348 | "version": "3.0.0", 1349 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1350 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1351 | "dev": true 1352 | }, 1353 | "object-assign": { 1354 | "version": "4.1.1", 1355 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1356 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 1357 | }, 1358 | "object-component": { 1359 | "version": "0.0.3", 1360 | "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", 1361 | "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", 1362 | "dev": true 1363 | }, 1364 | "on-finished": { 1365 | "version": "2.3.0", 1366 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1367 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1368 | "dev": true, 1369 | "requires": { 1370 | "ee-first": "1.1.1" 1371 | } 1372 | }, 1373 | "once": { 1374 | "version": "1.4.0", 1375 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1376 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1377 | "dev": true, 1378 | "requires": { 1379 | "wrappy": "1" 1380 | } 1381 | }, 1382 | "os-browserify": { 1383 | "version": "0.3.0", 1384 | "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", 1385 | "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", 1386 | "dev": true 1387 | }, 1388 | "p-limit": { 1389 | "version": "2.3.0", 1390 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 1391 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 1392 | "dev": true, 1393 | "requires": { 1394 | "p-try": "^2.0.0" 1395 | } 1396 | }, 1397 | "p-locate": { 1398 | "version": "4.1.0", 1399 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 1400 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 1401 | "dev": true, 1402 | "requires": { 1403 | "p-limit": "^2.2.0" 1404 | } 1405 | }, 1406 | "p-try": { 1407 | "version": "2.2.0", 1408 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 1409 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 1410 | "dev": true 1411 | }, 1412 | "pako": { 1413 | "version": "1.0.11", 1414 | "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", 1415 | "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", 1416 | "dev": true 1417 | }, 1418 | "parse-asn1": { 1419 | "version": "5.1.6", 1420 | "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", 1421 | "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", 1422 | "dev": true, 1423 | "requires": { 1424 | "asn1.js": "^5.2.0", 1425 | "browserify-aes": "^1.0.0", 1426 | "evp_bytestokey": "^1.0.0", 1427 | "pbkdf2": "^3.0.3", 1428 | "safe-buffer": "^5.1.1" 1429 | } 1430 | }, 1431 | "parseqs": { 1432 | "version": "0.0.5", 1433 | "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", 1434 | "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", 1435 | "dev": true, 1436 | "requires": { 1437 | "better-assert": "~1.0.0" 1438 | } 1439 | }, 1440 | "parseuri": { 1441 | "version": "0.0.5", 1442 | "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", 1443 | "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", 1444 | "dev": true, 1445 | "requires": { 1446 | "better-assert": "~1.0.0" 1447 | } 1448 | }, 1449 | "parseurl": { 1450 | "version": "1.3.3", 1451 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1452 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1453 | "dev": true 1454 | }, 1455 | "path-browserify": { 1456 | "version": "0.0.1", 1457 | "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", 1458 | "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", 1459 | "dev": true 1460 | }, 1461 | "path-exists": { 1462 | "version": "4.0.0", 1463 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 1464 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 1465 | "dev": true 1466 | }, 1467 | "path-is-absolute": { 1468 | "version": "1.0.1", 1469 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1470 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1471 | "dev": true 1472 | }, 1473 | "pbkdf2": { 1474 | "version": "3.1.1", 1475 | "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", 1476 | "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", 1477 | "dev": true, 1478 | "requires": { 1479 | "create-hash": "^1.1.2", 1480 | "create-hmac": "^1.1.4", 1481 | "ripemd160": "^2.0.1", 1482 | "safe-buffer": "^5.0.1", 1483 | "sha.js": "^2.4.8" 1484 | } 1485 | }, 1486 | "picomatch": { 1487 | "version": "2.2.2", 1488 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", 1489 | "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", 1490 | "dev": true 1491 | }, 1492 | "process": { 1493 | "version": "0.11.10", 1494 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 1495 | "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", 1496 | "dev": true 1497 | }, 1498 | "process-nextick-args": { 1499 | "version": "2.0.1", 1500 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 1501 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 1502 | "dev": true 1503 | }, 1504 | "prop-types": { 1505 | "version": "15.7.2", 1506 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", 1507 | "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", 1508 | "requires": { 1509 | "loose-envify": "^1.4.0", 1510 | "object-assign": "^4.1.1", 1511 | "react-is": "^16.8.1" 1512 | } 1513 | }, 1514 | "public-encrypt": { 1515 | "version": "4.0.3", 1516 | "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", 1517 | "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", 1518 | "dev": true, 1519 | "requires": { 1520 | "bn.js": "^4.1.0", 1521 | "browserify-rsa": "^4.0.0", 1522 | "create-hash": "^1.1.0", 1523 | "parse-asn1": "^5.0.0", 1524 | "randombytes": "^2.0.1", 1525 | "safe-buffer": "^5.1.2" 1526 | }, 1527 | "dependencies": { 1528 | "bn.js": { 1529 | "version": "4.11.9", 1530 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", 1531 | "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", 1532 | "dev": true 1533 | } 1534 | } 1535 | }, 1536 | "punycode": { 1537 | "version": "1.4.1", 1538 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 1539 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", 1540 | "dev": true 1541 | }, 1542 | "qjobs": { 1543 | "version": "1.2.0", 1544 | "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", 1545 | "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", 1546 | "dev": true 1547 | }, 1548 | "qs": { 1549 | "version": "6.7.0", 1550 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 1551 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", 1552 | "dev": true 1553 | }, 1554 | "querystring": { 1555 | "version": "0.2.0", 1556 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 1557 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", 1558 | "dev": true 1559 | }, 1560 | "querystring-es3": { 1561 | "version": "0.2.1", 1562 | "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", 1563 | "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", 1564 | "dev": true 1565 | }, 1566 | "randombytes": { 1567 | "version": "2.1.0", 1568 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 1569 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 1570 | "dev": true, 1571 | "requires": { 1572 | "safe-buffer": "^5.1.0" 1573 | } 1574 | }, 1575 | "randomfill": { 1576 | "version": "1.0.4", 1577 | "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", 1578 | "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", 1579 | "dev": true, 1580 | "requires": { 1581 | "randombytes": "^2.0.5", 1582 | "safe-buffer": "^5.1.0" 1583 | } 1584 | }, 1585 | "range-parser": { 1586 | "version": "1.2.1", 1587 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1588 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1589 | "dev": true 1590 | }, 1591 | "raw-body": { 1592 | "version": "2.4.0", 1593 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 1594 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 1595 | "dev": true, 1596 | "requires": { 1597 | "bytes": "3.1.0", 1598 | "http-errors": "1.7.2", 1599 | "iconv-lite": "0.4.24", 1600 | "unpipe": "1.0.0" 1601 | } 1602 | }, 1603 | "react": { 1604 | "version": "16.13.0", 1605 | "resolved": "https://registry.npmjs.org/react/-/react-16.13.0.tgz", 1606 | "integrity": "sha512-TSavZz2iSLkq5/oiE7gnFzmURKZMltmi193rm5HEoUDAXpzT9Kzw6oNZnGoai/4+fUnm7FqS5dwgUL34TujcWQ==", 1607 | "requires": { 1608 | "loose-envify": "^1.1.0", 1609 | "object-assign": "^4.1.1", 1610 | "prop-types": "^15.6.2" 1611 | } 1612 | }, 1613 | "react-dom": { 1614 | "version": "16.13.0", 1615 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.0.tgz", 1616 | "integrity": "sha512-y09d2c4cG220DzdlFkPTnVvGTszVvNpC73v+AaLGLHbkpy3SSgvYq8x0rNwPJ/Rk/CicTNgk0hbHNw1gMEZAXg==", 1617 | "requires": { 1618 | "loose-envify": "^1.1.0", 1619 | "object-assign": "^4.1.1", 1620 | "prop-types": "^15.6.2", 1621 | "scheduler": "^0.19.0" 1622 | } 1623 | }, 1624 | "react-is": { 1625 | "version": "16.13.1", 1626 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", 1627 | "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" 1628 | }, 1629 | "readable-stream": { 1630 | "version": "2.3.7", 1631 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 1632 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 1633 | "dev": true, 1634 | "requires": { 1635 | "core-util-is": "~1.0.0", 1636 | "inherits": "~2.0.3", 1637 | "isarray": "~1.0.0", 1638 | "process-nextick-args": "~2.0.0", 1639 | "safe-buffer": "~5.1.1", 1640 | "string_decoder": "~1.1.1", 1641 | "util-deprecate": "~1.0.1" 1642 | }, 1643 | "dependencies": { 1644 | "isarray": { 1645 | "version": "1.0.0", 1646 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 1647 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 1648 | "dev": true 1649 | }, 1650 | "safe-buffer": { 1651 | "version": "5.1.2", 1652 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1653 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 1654 | "dev": true 1655 | }, 1656 | "string_decoder": { 1657 | "version": "1.1.1", 1658 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1659 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1660 | "dev": true, 1661 | "requires": { 1662 | "safe-buffer": "~5.1.0" 1663 | } 1664 | } 1665 | } 1666 | }, 1667 | "readdirp": { 1668 | "version": "3.4.0", 1669 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", 1670 | "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", 1671 | "dev": true, 1672 | "requires": { 1673 | "picomatch": "^2.2.1" 1674 | } 1675 | }, 1676 | "readline-sync": { 1677 | "version": "1.4.10", 1678 | "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", 1679 | "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", 1680 | "dev": true 1681 | }, 1682 | "require-directory": { 1683 | "version": "2.1.1", 1684 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 1685 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", 1686 | "dev": true 1687 | }, 1688 | "require-main-filename": { 1689 | "version": "2.0.0", 1690 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 1691 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", 1692 | "dev": true 1693 | }, 1694 | "requires-port": { 1695 | "version": "1.0.0", 1696 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 1697 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", 1698 | "dev": true 1699 | }, 1700 | "rfdc": { 1701 | "version": "1.1.4", 1702 | "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz", 1703 | "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==", 1704 | "dev": true 1705 | }, 1706 | "rimraf": { 1707 | "version": "3.0.2", 1708 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 1709 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 1710 | "dev": true, 1711 | "requires": { 1712 | "glob": "^7.1.3" 1713 | } 1714 | }, 1715 | "ripemd160": { 1716 | "version": "2.0.2", 1717 | "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", 1718 | "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", 1719 | "dev": true, 1720 | "requires": { 1721 | "hash-base": "^3.0.0", 1722 | "inherits": "^2.0.1" 1723 | } 1724 | }, 1725 | "safe-buffer": { 1726 | "version": "5.2.1", 1727 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1728 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1729 | "dev": true 1730 | }, 1731 | "safer-buffer": { 1732 | "version": "2.1.2", 1733 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1734 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1735 | "dev": true 1736 | }, 1737 | "scheduler": { 1738 | "version": "0.19.1", 1739 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", 1740 | "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", 1741 | "requires": { 1742 | "loose-envify": "^1.1.0", 1743 | "object-assign": "^4.1.1" 1744 | } 1745 | }, 1746 | "set-blocking": { 1747 | "version": "2.0.0", 1748 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1749 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", 1750 | "dev": true 1751 | }, 1752 | "setimmediate": { 1753 | "version": "1.0.5", 1754 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 1755 | "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", 1756 | "dev": true 1757 | }, 1758 | "setprototypeof": { 1759 | "version": "1.1.1", 1760 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 1761 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", 1762 | "dev": true 1763 | }, 1764 | "sha.js": { 1765 | "version": "2.4.11", 1766 | "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", 1767 | "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", 1768 | "dev": true, 1769 | "requires": { 1770 | "inherits": "^2.0.1", 1771 | "safe-buffer": "^5.0.1" 1772 | } 1773 | }, 1774 | "shadow-cljs": { 1775 | "version": "2.11.4", 1776 | "resolved": "https://registry.npmjs.org/shadow-cljs/-/shadow-cljs-2.11.4.tgz", 1777 | "integrity": "sha512-sIc1MfN/JsGenbFkDXs0+iVrKTSzAE5DzBFuUGxHc4LbcAJ9GWRQlAeE0WRT3fOCQQOlxeBLrxlZ6WiUjKlQgg==", 1778 | "dev": true, 1779 | "requires": { 1780 | "node-libs-browser": "^2.2.1", 1781 | "readline-sync": "^1.4.7", 1782 | "shadow-cljs-jar": "1.3.2", 1783 | "source-map-support": "^0.4.15", 1784 | "which": "^1.3.1", 1785 | "ws": "^3.0.0" 1786 | }, 1787 | "dependencies": { 1788 | "safe-buffer": { 1789 | "version": "5.1.2", 1790 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1791 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 1792 | "dev": true 1793 | }, 1794 | "ws": { 1795 | "version": "3.3.3", 1796 | "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", 1797 | "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", 1798 | "dev": true, 1799 | "requires": { 1800 | "async-limiter": "~1.0.0", 1801 | "safe-buffer": "~5.1.0", 1802 | "ultron": "~1.1.0" 1803 | } 1804 | } 1805 | } 1806 | }, 1807 | "shadow-cljs-jar": { 1808 | "version": "1.3.2", 1809 | "resolved": "https://registry.npmjs.org/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz", 1810 | "integrity": "sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg==", 1811 | "dev": true 1812 | }, 1813 | "socket.io": { 1814 | "version": "2.3.0", 1815 | "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", 1816 | "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", 1817 | "dev": true, 1818 | "requires": { 1819 | "debug": "~4.1.0", 1820 | "engine.io": "~3.4.0", 1821 | "has-binary2": "~1.0.2", 1822 | "socket.io-adapter": "~1.1.0", 1823 | "socket.io-client": "2.3.0", 1824 | "socket.io-parser": "~3.4.0" 1825 | }, 1826 | "dependencies": { 1827 | "debug": { 1828 | "version": "4.1.1", 1829 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 1830 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 1831 | "dev": true, 1832 | "requires": { 1833 | "ms": "^2.1.1" 1834 | } 1835 | }, 1836 | "ms": { 1837 | "version": "2.1.2", 1838 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1839 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1840 | "dev": true 1841 | } 1842 | } 1843 | }, 1844 | "socket.io-adapter": { 1845 | "version": "1.1.2", 1846 | "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", 1847 | "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", 1848 | "dev": true 1849 | }, 1850 | "socket.io-client": { 1851 | "version": "2.3.0", 1852 | "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", 1853 | "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", 1854 | "dev": true, 1855 | "requires": { 1856 | "backo2": "1.0.2", 1857 | "base64-arraybuffer": "0.1.5", 1858 | "component-bind": "1.0.0", 1859 | "component-emitter": "1.2.1", 1860 | "debug": "~4.1.0", 1861 | "engine.io-client": "~3.4.0", 1862 | "has-binary2": "~1.0.2", 1863 | "has-cors": "1.1.0", 1864 | "indexof": "0.0.1", 1865 | "object-component": "0.0.3", 1866 | "parseqs": "0.0.5", 1867 | "parseuri": "0.0.5", 1868 | "socket.io-parser": "~3.3.0", 1869 | "to-array": "0.1.4" 1870 | }, 1871 | "dependencies": { 1872 | "debug": { 1873 | "version": "4.1.1", 1874 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 1875 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 1876 | "dev": true, 1877 | "requires": { 1878 | "ms": "^2.1.1" 1879 | } 1880 | }, 1881 | "ms": { 1882 | "version": "2.1.2", 1883 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1884 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1885 | "dev": true 1886 | }, 1887 | "socket.io-parser": { 1888 | "version": "3.3.0", 1889 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", 1890 | "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", 1891 | "dev": true, 1892 | "requires": { 1893 | "component-emitter": "1.2.1", 1894 | "debug": "~3.1.0", 1895 | "isarray": "2.0.1" 1896 | }, 1897 | "dependencies": { 1898 | "debug": { 1899 | "version": "3.1.0", 1900 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 1901 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 1902 | "dev": true, 1903 | "requires": { 1904 | "ms": "2.0.0" 1905 | } 1906 | }, 1907 | "ms": { 1908 | "version": "2.0.0", 1909 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1910 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 1911 | "dev": true 1912 | } 1913 | } 1914 | } 1915 | } 1916 | }, 1917 | "socket.io-parser": { 1918 | "version": "3.4.1", 1919 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", 1920 | "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", 1921 | "dev": true, 1922 | "requires": { 1923 | "component-emitter": "1.2.1", 1924 | "debug": "~4.1.0", 1925 | "isarray": "2.0.1" 1926 | }, 1927 | "dependencies": { 1928 | "debug": { 1929 | "version": "4.1.1", 1930 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 1931 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 1932 | "dev": true, 1933 | "requires": { 1934 | "ms": "^2.1.1" 1935 | } 1936 | }, 1937 | "ms": { 1938 | "version": "2.1.2", 1939 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1940 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1941 | "dev": true 1942 | } 1943 | } 1944 | }, 1945 | "source-map": { 1946 | "version": "0.6.1", 1947 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1948 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 1949 | "dev": true 1950 | }, 1951 | "source-map-support": { 1952 | "version": "0.4.18", 1953 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", 1954 | "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", 1955 | "dev": true, 1956 | "requires": { 1957 | "source-map": "^0.5.6" 1958 | }, 1959 | "dependencies": { 1960 | "source-map": { 1961 | "version": "0.5.7", 1962 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 1963 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", 1964 | "dev": true 1965 | } 1966 | } 1967 | }, 1968 | "statuses": { 1969 | "version": "1.5.0", 1970 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1971 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", 1972 | "dev": true 1973 | }, 1974 | "stream-browserify": { 1975 | "version": "2.0.2", 1976 | "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", 1977 | "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", 1978 | "dev": true, 1979 | "requires": { 1980 | "inherits": "~2.0.1", 1981 | "readable-stream": "^2.0.2" 1982 | } 1983 | }, 1984 | "stream-http": { 1985 | "version": "2.8.3", 1986 | "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", 1987 | "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", 1988 | "dev": true, 1989 | "requires": { 1990 | "builtin-status-codes": "^3.0.0", 1991 | "inherits": "^2.0.1", 1992 | "readable-stream": "^2.3.6", 1993 | "to-arraybuffer": "^1.0.0", 1994 | "xtend": "^4.0.0" 1995 | } 1996 | }, 1997 | "streamroller": { 1998 | "version": "2.2.4", 1999 | "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz", 2000 | "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==", 2001 | "dev": true, 2002 | "requires": { 2003 | "date-format": "^2.1.0", 2004 | "debug": "^4.1.1", 2005 | "fs-extra": "^8.1.0" 2006 | }, 2007 | "dependencies": { 2008 | "date-format": { 2009 | "version": "2.1.0", 2010 | "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", 2011 | "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", 2012 | "dev": true 2013 | }, 2014 | "debug": { 2015 | "version": "4.2.0", 2016 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", 2017 | "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", 2018 | "dev": true, 2019 | "requires": { 2020 | "ms": "2.1.2" 2021 | } 2022 | }, 2023 | "ms": { 2024 | "version": "2.1.2", 2025 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 2026 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 2027 | "dev": true 2028 | } 2029 | } 2030 | }, 2031 | "string-width": { 2032 | "version": "4.2.0", 2033 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", 2034 | "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", 2035 | "dev": true, 2036 | "requires": { 2037 | "emoji-regex": "^8.0.0", 2038 | "is-fullwidth-code-point": "^3.0.0", 2039 | "strip-ansi": "^6.0.0" 2040 | } 2041 | }, 2042 | "string_decoder": { 2043 | "version": "1.3.0", 2044 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 2045 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 2046 | "dev": true, 2047 | "requires": { 2048 | "safe-buffer": "~5.2.0" 2049 | } 2050 | }, 2051 | "strip-ansi": { 2052 | "version": "6.0.0", 2053 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 2054 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 2055 | "dev": true, 2056 | "requires": { 2057 | "ansi-regex": "^5.0.0" 2058 | } 2059 | }, 2060 | "timers-browserify": { 2061 | "version": "2.0.11", 2062 | "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", 2063 | "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", 2064 | "dev": true, 2065 | "requires": { 2066 | "setimmediate": "^1.0.4" 2067 | } 2068 | }, 2069 | "tmp": { 2070 | "version": "0.2.1", 2071 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", 2072 | "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", 2073 | "dev": true, 2074 | "requires": { 2075 | "rimraf": "^3.0.0" 2076 | } 2077 | }, 2078 | "to-array": { 2079 | "version": "0.1.4", 2080 | "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", 2081 | "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", 2082 | "dev": true 2083 | }, 2084 | "to-arraybuffer": { 2085 | "version": "1.0.1", 2086 | "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", 2087 | "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", 2088 | "dev": true 2089 | }, 2090 | "to-regex-range": { 2091 | "version": "5.0.1", 2092 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 2093 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 2094 | "dev": true, 2095 | "requires": { 2096 | "is-number": "^7.0.0" 2097 | } 2098 | }, 2099 | "toidentifier": { 2100 | "version": "1.0.0", 2101 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 2102 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", 2103 | "dev": true 2104 | }, 2105 | "tty-browserify": { 2106 | "version": "0.0.0", 2107 | "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", 2108 | "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", 2109 | "dev": true 2110 | }, 2111 | "type-is": { 2112 | "version": "1.6.18", 2113 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 2114 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 2115 | "dev": true, 2116 | "requires": { 2117 | "media-typer": "0.3.0", 2118 | "mime-types": "~2.1.24" 2119 | } 2120 | }, 2121 | "ua-parser-js": { 2122 | "version": "0.7.22", 2123 | "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz", 2124 | "integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q==", 2125 | "dev": true 2126 | }, 2127 | "ultron": { 2128 | "version": "1.1.1", 2129 | "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", 2130 | "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", 2131 | "dev": true 2132 | }, 2133 | "universalify": { 2134 | "version": "0.1.2", 2135 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", 2136 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", 2137 | "dev": true 2138 | }, 2139 | "unpipe": { 2140 | "version": "1.0.0", 2141 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 2142 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", 2143 | "dev": true 2144 | }, 2145 | "url": { 2146 | "version": "0.11.0", 2147 | "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", 2148 | "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", 2149 | "dev": true, 2150 | "requires": { 2151 | "punycode": "1.3.2", 2152 | "querystring": "0.2.0" 2153 | }, 2154 | "dependencies": { 2155 | "punycode": { 2156 | "version": "1.3.2", 2157 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 2158 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", 2159 | "dev": true 2160 | } 2161 | } 2162 | }, 2163 | "util": { 2164 | "version": "0.11.1", 2165 | "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", 2166 | "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", 2167 | "dev": true, 2168 | "requires": { 2169 | "inherits": "2.0.3" 2170 | } 2171 | }, 2172 | "util-deprecate": { 2173 | "version": "1.0.2", 2174 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 2175 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 2176 | "dev": true 2177 | }, 2178 | "utils-merge": { 2179 | "version": "1.0.1", 2180 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 2181 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", 2182 | "dev": true 2183 | }, 2184 | "vm-browserify": { 2185 | "version": "1.1.2", 2186 | "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", 2187 | "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", 2188 | "dev": true 2189 | }, 2190 | "void-elements": { 2191 | "version": "2.0.1", 2192 | "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", 2193 | "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", 2194 | "dev": true 2195 | }, 2196 | "which": { 2197 | "version": "1.3.1", 2198 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 2199 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 2200 | "dev": true, 2201 | "requires": { 2202 | "isexe": "^2.0.0" 2203 | } 2204 | }, 2205 | "which-module": { 2206 | "version": "2.0.0", 2207 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 2208 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", 2209 | "dev": true 2210 | }, 2211 | "wrap-ansi": { 2212 | "version": "6.2.0", 2213 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", 2214 | "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", 2215 | "dev": true, 2216 | "requires": { 2217 | "ansi-styles": "^4.0.0", 2218 | "string-width": "^4.1.0", 2219 | "strip-ansi": "^6.0.0" 2220 | } 2221 | }, 2222 | "wrappy": { 2223 | "version": "1.0.2", 2224 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2225 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 2226 | "dev": true 2227 | }, 2228 | "ws": { 2229 | "version": "7.3.1", 2230 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", 2231 | "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", 2232 | "dev": true 2233 | }, 2234 | "xmlbuilder": { 2235 | "version": "8.2.2", 2236 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", 2237 | "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=", 2238 | "dev": true 2239 | }, 2240 | "xmlhttprequest-ssl": { 2241 | "version": "1.5.5", 2242 | "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", 2243 | "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", 2244 | "dev": true 2245 | }, 2246 | "xtend": { 2247 | "version": "4.0.2", 2248 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 2249 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 2250 | "dev": true 2251 | }, 2252 | "y18n": { 2253 | "version": "4.0.0", 2254 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", 2255 | "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", 2256 | "dev": true 2257 | }, 2258 | "yargs": { 2259 | "version": "15.4.1", 2260 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", 2261 | "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", 2262 | "dev": true, 2263 | "requires": { 2264 | "cliui": "^6.0.0", 2265 | "decamelize": "^1.2.0", 2266 | "find-up": "^4.1.0", 2267 | "get-caller-file": "^2.0.1", 2268 | "require-directory": "^2.1.1", 2269 | "require-main-filename": "^2.0.0", 2270 | "set-blocking": "^2.0.0", 2271 | "string-width": "^4.2.0", 2272 | "which-module": "^2.0.0", 2273 | "y18n": "^4.0.0", 2274 | "yargs-parser": "^18.1.2" 2275 | } 2276 | }, 2277 | "yargs-parser": { 2278 | "version": "18.1.3", 2279 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", 2280 | "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", 2281 | "dev": true, 2282 | "requires": { 2283 | "camelcase": "^5.0.0", 2284 | "decamelize": "^1.2.0" 2285 | } 2286 | }, 2287 | "yeast": { 2288 | "version": "0.1.2", 2289 | "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", 2290 | "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", 2291 | "dev": true 2292 | } 2293 | } 2294 | } 2295 | -------------------------------------------------------------------------------- /examples/todomvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-re-frame", 3 | "devDependencies": { 4 | "karma": "5.2.3", 5 | "karma-chrome-launcher": "3.1.0", 6 | "karma-cljs-test": "0.1.0", 7 | "karma-junit-reporter": "1.2.0", 8 | "shadow-cljs": "2.11.4" 9 | }, 10 | "dependencies": { 11 | "react": "16.13.0", 12 | "react-dom": "16.13.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/todomvc/project.clj: -------------------------------------------------------------------------------- 1 | (defproject todomvc-re-frame "lein-git-inject/version" 2 | 3 | :dependencies [[org.clojure/clojure "1.10.1"] 4 | [org.clojure/clojurescript "1.10.773" 5 | :exclusions [com.google.javascript/closure-compiler-unshaded 6 | org.clojure/google-closure-library]] 7 | [thheller/shadow-cljs "2.11.4"] 8 | [reagent "0.10.0"] 9 | [re-frame "1.1.1"] 10 | [binaryage/devtools "1.0.2"] 11 | [day8.re-frame/test "0.1.5"] 12 | [secretary "1.2.3"]] 13 | 14 | :plugins [[day8/lein-git-inject "0.0.14"] 15 | [lein-shadow "0.3.1"] 16 | [lein-shell "0.5.0"]] 17 | 18 | :middleware [leiningen.git-inject/middleware] 19 | 20 | :shadow-cljs {:nrepl {:port 8777} 21 | 22 | :builds {:client {:target :browser 23 | :output-dir "resources/public/js" 24 | :modules {:client {:init-fn todomvc.core/main}} 25 | :dev {:compiler-options {:external-config {:devtools/config {:features-to-install [:formatters :hints]}}}} 26 | :devtools {:http-root "resources/public" 27 | :http-port 8280}} 28 | :karma-test 29 | {:target :karma 30 | :ns-regexp "-test$" 31 | :output-to "target/karma-test.js"}}} 32 | 33 | :aliases {"watch" ["with-profile" "dev" "shadow" "watch" "client"] 34 | "ci" ["do" 35 | ["shadow" "compile" "karma-test"] 36 | ["shell" "karma" "start" "--single-run" "--reporters" "junit,dots"]]} 37 | 38 | :jvm-opts ^:replace ["-Xms256m" "-Xmx2g"] 39 | 40 | :clean-targets ^{:protect false} ["resources/public/js" "target"]) 41 | -------------------------------------------------------------------------------- /examples/todomvc/resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Reframe Todomvc 6 | 7 | 8 | 9 |
10 |
11 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /examples/todomvc/resources/public/todos.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-font-smoothing: antialiased; 21 | font-smoothing: antialiased; 22 | } 23 | 24 | body { 25 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 26 | line-height: 1.4em; 27 | background: #f5f5f5; 28 | color: #4d4d4d; 29 | min-width: 230px; 30 | max-width: 550px; 31 | margin: 0 auto; 32 | -webkit-font-smoothing: antialiased; 33 | -moz-font-smoothing: antialiased; 34 | font-smoothing: antialiased; 35 | font-weight: 300; 36 | } 37 | 38 | button, 39 | input[type="checkbox"] { 40 | outline: none; 41 | } 42 | 43 | .hidden { 44 | display: none; 45 | } 46 | 47 | #todoapp { 48 | background: #fff; 49 | margin: 130px 0 40px 0; 50 | position: relative; 51 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 52 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 53 | } 54 | 55 | #todoapp input::-webkit-input-placeholder { 56 | font-style: italic; 57 | font-weight: 300; 58 | color: #e6e6e6; 59 | } 60 | 61 | #todoapp input::-moz-placeholder { 62 | font-style: italic; 63 | font-weight: 300; 64 | color: #e6e6e6; 65 | } 66 | 67 | #todoapp input::input-placeholder { 68 | font-style: italic; 69 | font-weight: 300; 70 | color: #e6e6e6; 71 | } 72 | 73 | #todoapp h1 { 74 | position: absolute; 75 | top: -155px; 76 | width: 100%; 77 | font-size: 100px; 78 | font-weight: 100; 79 | text-align: center; 80 | color: rgba(175, 47, 47, 0.15); 81 | -webkit-text-rendering: optimizeLegibility; 82 | -moz-text-rendering: optimizeLegibility; 83 | text-rendering: optimizeLegibility; 84 | } 85 | 86 | #new-todo, 87 | .edit { 88 | position: relative; 89 | margin: 0; 90 | width: 100%; 91 | font-size: 24px; 92 | font-family: inherit; 93 | font-weight: inherit; 94 | line-height: 1.4em; 95 | border: 0; 96 | outline: none; 97 | color: inherit; 98 | padding: 6px; 99 | border: 1px solid #999; 100 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 101 | box-sizing: border-box; 102 | -webkit-font-smoothing: antialiased; 103 | -moz-font-smoothing: antialiased; 104 | font-smoothing: antialiased; 105 | } 106 | 107 | #new-todo { 108 | padding: 16px 16px 16px 60px; 109 | border: none; 110 | background: rgba(0, 0, 0, 0.003); 111 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 112 | } 113 | 114 | #main { 115 | position: relative; 116 | z-index: 2; 117 | border-top: 1px solid #e6e6e6; 118 | } 119 | 120 | label[for='toggle-all'] { 121 | display: none; 122 | } 123 | 124 | #toggle-all { 125 | position: absolute; 126 | top: -55px; 127 | left: -12px; 128 | width: 60px; 129 | height: 34px; 130 | text-align: center; 131 | border: none; /* Mobile Safari */ 132 | } 133 | 134 | #toggle-all:before { 135 | content: '❯'; 136 | font-size: 22px; 137 | color: #e6e6e6; 138 | padding: 10px 27px 10px 27px; 139 | } 140 | 141 | #toggle-all:checked:before { 142 | color: #737373; 143 | } 144 | 145 | #todo-list { 146 | margin: 0; 147 | padding: 0; 148 | list-style: none; 149 | } 150 | 151 | #todo-list li { 152 | position: relative; 153 | font-size: 24px; 154 | border-bottom: 1px solid #ededed; 155 | } 156 | 157 | #todo-list li:last-child { 158 | border-bottom: none; 159 | } 160 | 161 | #todo-list li.editing { 162 | border-bottom: none; 163 | padding: 0; 164 | } 165 | 166 | #todo-list li.editing .edit { 167 | display: block; 168 | width: 506px; 169 | padding: 13px 17px 12px 17px; 170 | margin: 0 0 0 43px; 171 | } 172 | 173 | #todo-list li.editing .view { 174 | display: none; 175 | } 176 | 177 | #todo-list li .toggle { 178 | text-align: center; 179 | width: 40px; 180 | /* auto, since non-WebKit browsers doesn't support input styling */ 181 | height: auto; 182 | position: absolute; 183 | top: 0; 184 | bottom: 0; 185 | margin: auto 0; 186 | border: none; /* Mobile Safari */ 187 | -webkit-appearance: none; 188 | appearance: none; 189 | } 190 | 191 | #todo-list li .toggle:after { 192 | content: url('data:image/svg+xml;utf8,'); 193 | } 194 | 195 | #todo-list li .toggle:checked:after { 196 | content: url('data:image/svg+xml;utf8,'); 197 | } 198 | 199 | #todo-list li label { 200 | white-space: pre-line; 201 | word-break: break-all; 202 | padding: 15px 60px 15px 15px; 203 | margin-left: 45px; 204 | display: block; 205 | line-height: 1.2; 206 | transition: color 0.4s; 207 | } 208 | 209 | #todo-list li.completed label { 210 | color: #d9d9d9; 211 | text-decoration: line-through; 212 | } 213 | 214 | #todo-list li .destroy { 215 | display: none; 216 | position: absolute; 217 | top: 0; 218 | right: 10px; 219 | bottom: 0; 220 | width: 40px; 221 | height: 40px; 222 | margin: auto 0; 223 | font-size: 30px; 224 | color: #cc9a9a; 225 | margin-bottom: 11px; 226 | transition: color 0.2s ease-out; 227 | } 228 | 229 | #todo-list li .destroy:hover { 230 | color: #af5b5e; 231 | } 232 | 233 | #todo-list li .destroy:after { 234 | content: '×'; 235 | } 236 | 237 | #todo-list li:hover .destroy { 238 | display: block; 239 | } 240 | 241 | #todo-list li .edit { 242 | display: none; 243 | } 244 | 245 | #todo-list li.editing:last-child { 246 | margin-bottom: -1px; 247 | } 248 | 249 | #footer { 250 | color: #777; 251 | padding: 10px 15px; 252 | height: 20px; 253 | text-align: center; 254 | border-top: 1px solid #e6e6e6; 255 | } 256 | 257 | #footer:before { 258 | content: ''; 259 | position: absolute; 260 | right: 0; 261 | bottom: 0; 262 | left: 0; 263 | height: 50px; 264 | overflow: hidden; 265 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 266 | 0 8px 0 -3px #f6f6f6, 267 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 268 | 0 16px 0 -6px #f6f6f6, 269 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 270 | } 271 | 272 | #todo-count { 273 | float: left; 274 | text-align: left; 275 | } 276 | 277 | #todo-count strong { 278 | font-weight: 300; 279 | } 280 | 281 | #filters { 282 | margin: 0; 283 | padding: 0; 284 | list-style: none; 285 | position: absolute; 286 | right: 0; 287 | left: 0; 288 | } 289 | 290 | #filters li { 291 | display: inline; 292 | } 293 | 294 | #filters li a { 295 | color: inherit; 296 | margin: 3px; 297 | padding: 3px 7px; 298 | text-decoration: none; 299 | border: 1px solid transparent; 300 | border-radius: 3px; 301 | } 302 | 303 | #filters li a.selected, 304 | #filters li a:hover { 305 | border-color: rgba(175, 47, 47, 0.1); 306 | } 307 | 308 | #filters li a.selected { 309 | border-color: rgba(175, 47, 47, 0.2); 310 | } 311 | 312 | #clear-completed, 313 | html #clear-completed:active { 314 | float: right; 315 | position: relative; 316 | line-height: 20px; 317 | text-decoration: none; 318 | cursor: pointer; 319 | position: relative; 320 | } 321 | 322 | #clear-completed:hover { 323 | text-decoration: underline; 324 | } 325 | 326 | #info { 327 | margin: 65px auto 0; 328 | color: #bfbfbf; 329 | font-size: 10px; 330 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 331 | text-align: center; 332 | } 333 | 334 | #info p { 335 | line-height: 1; 336 | } 337 | 338 | #info a { 339 | color: inherit; 340 | text-decoration: none; 341 | font-weight: 400; 342 | } 343 | 344 | #info a:hover { 345 | text-decoration: underline; 346 | } 347 | 348 | /* 349 | Hack to remove background from Mobile Safari. 350 | Can't use it globally since it destroys checkboxes in Firefox 351 | */ 352 | @media screen and (-webkit-min-device-pixel-ratio:0) { 353 | #toggle-all, 354 | #todo-list li .toggle { 355 | background: none; 356 | } 357 | 358 | #todo-list li .toggle { 359 | height: 40px; 360 | } 361 | 362 | #toggle-all { 363 | -webkit-transform: rotate(90deg); 364 | transform: rotate(90deg); 365 | -webkit-appearance: none; 366 | appearance: none; 367 | } 368 | } 369 | 370 | @media (max-width: 430px) { 371 | #footer { 372 | height: 50px; 373 | } 374 | 375 | #filters { 376 | bottom: 10px; 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /examples/todomvc/src/deps.cljs: -------------------------------------------------------------------------------- 1 | {:npm-dev-deps {"shadow-cljs" "2.11.4" 2 | "karma" "5.2.3" 3 | "karma-chrome-launcher" "3.1.0" 4 | "karma-cljs-test" "0.1.0" 5 | "karma-junit-reporter" "1.2.0"}} 6 | -------------------------------------------------------------------------------- /examples/todomvc/src/todomvc/core.cljs: -------------------------------------------------------------------------------- 1 | (ns todomvc.core 2 | (:require-macros [secretary.core :refer [defroute]]) 3 | (:require [goog.events :as events] 4 | [reagent.core :as reagent] 5 | [re-frame.core :refer [dispatch dispatch-sync]] 6 | [secretary.core :as secretary] 7 | [todomvc.events] 8 | [todomvc.subs] 9 | [todomvc.views] 10 | [devtools.core :as devtools]) 11 | (:import [goog History] 12 | [goog.history EventType])) 13 | 14 | 15 | ;; -- Debugging aids ---------------------------------------------------------- 16 | (devtools/install!) ;; we love https://github.com/binaryage/cljs-devtools 17 | (enable-console-print!) ;; so that println writes to `console.log` 18 | 19 | ;; -- Routes and History ------------------------------------------------------ 20 | ;; Although we use the secretary library below, that's mostly a historical 21 | ;; accident. You might also consider using: 22 | ;; - https://github.com/DomKM/silk 23 | ;; - https://github.com/juxt/bidi 24 | ;; We don't have a strong opinion. 25 | ;; 26 | 27 | ;(defroute "/" [] (dispatch [:set-showing :all])) ;; for some reason this breaks the tests 28 | (defroute "/:filter" [filter] (dispatch [:set-showing (keyword filter)])) 29 | 30 | (def history 31 | (doto (History.) 32 | (events/listen EventType.NAVIGATE 33 | (fn [event] (secretary/dispatch! (.-token event)))) 34 | (.setEnabled true))) 35 | 36 | 37 | ;; -- Entry Point ------------------------------------------------------------- 38 | ;; Within ../../resources/public/index.html you'll see this code 39 | ;; window.onload = function () { 40 | ;; todomvc.core.main(); 41 | ;; } 42 | ;; So this is the entry function that kicks off the app once the HTML is loaded. 43 | ;; 44 | (defn ^:export main 45 | [] 46 | ;; Put an initial value into app-db. 47 | ;; The event handler for `:initialise-db` can be found in `events.cljs` 48 | ;; Using the sync version of dispatch means that value is in 49 | ;; place before we go onto the next step. 50 | (dispatch-sync [:initialise-db]) 51 | 52 | ;; Render the UI into the HTML's
element 53 | ;; The view function `todomvc.views/todo-app` is the 54 | ;; root view for the entire UI. 55 | (reagent/render [todomvc.views/todo-app] ;; 56 | (.getElementById js/document "app"))) 57 | 58 | -------------------------------------------------------------------------------- /examples/todomvc/src/todomvc/db.cljs: -------------------------------------------------------------------------------- 1 | (ns todomvc.db 2 | (:require 3 | [cljs.reader] 4 | [clojure.spec.alpha :as s] 5 | [re-frame.core :as re-frame])) 6 | 7 | 8 | ;; -- Spec -------------------------------------------------------------------- 9 | ;; 10 | ;; This is a clojure.spec specification for the value in app-db. It is like a 11 | ;; Schema. See: http://clojure.org/guides/spec 12 | ;; 13 | ;; The value in app-db should always match this spec. Only event handlers 14 | ;; can change the value in app-db so, after each event handler 15 | ;; has run, we re-check app-db for correctness (compliance with the Schema). 16 | ;; 17 | ;; How is this done? Look in events.cljs and you'll notice that all handers 18 | ;; have an "after" interceptor which does the spec re-check. 19 | ;; 20 | ;; None of this is strictly necessary. It could be omitted. But we find it 21 | ;; good practice. 22 | 23 | (s/def ::id int?) 24 | (s/def ::title string?) 25 | (s/def ::done boolean?) 26 | (s/def ::todo (s/keys :req-un [::id ::title ::done])) 27 | (s/def ::todos (s/and ;; should use the :kind kw to s/map-of (not supported yet) 28 | (s/map-of ::id ::todo) ;; in this map, each todo is keyed by its :id 29 | #(sorted? %) ;; is a sorted-map (not just a map) 30 | )) 31 | (s/def ::showing ;; what todos are shown to the user? 32 | #{:all ;; all todos are shown 33 | :active ;; only todos whose :done is false 34 | :done ;; only todos whose :done is true 35 | }) 36 | (s/def ::db (s/keys :req-un [::todos ::showing])) 37 | 38 | ;; -- Default app-db Value --------------------------------------------------- 39 | ;; 40 | ;; When the application first starts, this will be the value put in app-db 41 | ;; Unless, of course, there are todos in the LocalStore (see further below) 42 | ;; Look in `core.cljs` for "(dispatch-sync [:initialise-db])" 43 | ;; 44 | 45 | (def default-value ;; what gets put into app-db by default. 46 | {:todos (sorted-map) ;; an empty list of todos. Use the (int) :id as the key 47 | :showing :all}) ;; show all todos 48 | 49 | 50 | ;; -- Local Storage ---------------------------------------------------------- 51 | ;; 52 | ;; Part of the todomvc challenge is to store todos in LocalStorage, and 53 | ;; on app startup, reload the todos from when the program was last run. 54 | ;; But the challenge stipulates to NOT load the setting for the "showing" 55 | ;; filter. Just the todos. 56 | ;; 57 | 58 | (def ls-key "todos-reframe") ;; localstore key 59 | 60 | (defn localstore->todos 61 | "Read in todos from localstore, and process into a map we can merge into app-db." 62 | [] 63 | (some->> (.getItem js/localStorage ls-key) 64 | (cljs.reader/read-string) ;; stored as an EDN map. 65 | (into (sorted-map)) ;; map -> sorted-map 66 | (hash-map :todos))) ;; access via the :todos key 67 | 68 | (defn todos->local-store 69 | "Puts todos into localStorage" 70 | [todos _] 71 | (.setItem js/localStorage ls-key (str todos))) ;; sorted-map writen as an EDN map 72 | 73 | ;; register a coeffect handler which will load a value from localstore 74 | ;; To see it used look in events.clj at the event handler for `:initialise-db` 75 | (re-frame/reg-cofx 76 | :local-store-todos 77 | (fn [cofx _] 78 | "Read in todos from localstore, and process into a map we can merge into app-db." 79 | (assoc cofx :local-store-todos 80 | (into (sorted-map) 81 | (some->> (.getItem js/localStorage ls-key) 82 | (cljs.reader/read-string) ;; stored as an EDN map. 83 | ))))) 84 | -------------------------------------------------------------------------------- /examples/todomvc/src/todomvc/events.cljs: -------------------------------------------------------------------------------- 1 | (ns todomvc.events 2 | (:require 3 | [todomvc.db :refer [default-value todos->local-store]] 4 | [re-frame.core :refer [reg-event-db reg-event-fx inject-cofx path trim-v 5 | after debug]] 6 | [clojure.spec.alpha :as s])) 7 | 8 | 9 | ;; -- Interceptors -------------------------------------------------------------- 10 | ;; 11 | 12 | (defn check-and-throw 13 | "throw an exception if db doesn't match the spec." 14 | [a-spec db _] 15 | (when-not (s/valid? a-spec db) 16 | (throw (ex-info (str "spec check failed: " (s/explain-str a-spec db)) {})))) 17 | 18 | ;; Event handlers change state, that's their job. But what happens if there's 19 | ;; a bug which corrupts app state in some subtle way? This interceptor is run after 20 | ;; each event handler has finished, and it checks app-db against a spec. This 21 | ;; helps us detect event handler bugs early. 22 | (def check-spec-interceptor (after (partial check-and-throw :todomvc.db/db))) 23 | 24 | ;; this interceptor stores todos into local storage 25 | ;; we attach it to each event handler which could update todos 26 | (def ->local-store (after todos->local-store)) 27 | 28 | ;; Each event handler can have its own set of interceptors (middleware) 29 | ;; But we use the same set of interceptors for all event habdlers related 30 | ;; to manipulating todos. 31 | ;; A chain of interceptors is a vector. 32 | (def todo-interceptors [check-spec-interceptor ;; ensure the spec is still valid 33 | (path :todos) ;; 1st param to handler will be the value from this path 34 | ->local-store ;; write todos to localstore 35 | (when ^boolean js/goog.DEBUG debug) ;; look in your browser console for debug logs 36 | trim-v]) ;; removes first (event id) element from the event vec 37 | 38 | 39 | ;; -- Helpers ----------------------------------------------------------------- 40 | 41 | (defn allocate-next-id 42 | "Returns the next todo id. 43 | Assumes todos are sorted. 44 | Returns one more than the current largest id." 45 | [todos] 46 | ((fnil inc 0) (last (keys todos)))) 47 | 48 | 49 | ;; -- Event Handlers ---------------------------------------------------------- 50 | 51 | ;; usage: (dispatch [:initialise-db]) 52 | (reg-event-fx ;; on app startup, create initial state 53 | :initialise-db ;; event id being handled 54 | [(inject-cofx :local-store-todos) ;; obtain todos from localstore 55 | check-spec-interceptor] ;; after the event handler runs, check that app-db matches the spec 56 | (fn [{:keys [db local-store-todos]} _] ;; the handler being registered 57 | {:db (assoc default-value :todos local-store-todos)})) ;; all hail the new state 58 | 59 | 60 | ;; usage: (dispatch [:set-showing :active]) 61 | (reg-event-db ;; this handler changes the todo filter 62 | :set-showing ;; event-id 63 | 64 | ;; this chain of two interceptors wrap the handler 65 | [#_check-spec-interceptor (path :showing) trim-v] 66 | 67 | ;; The event handler 68 | ;; Because of the path interceptor above, the 1st parameter to 69 | ;; the handler below won't be the entire 'db', and instead will 70 | ;; be the value at a certain path within db, namely :showing. 71 | ;; Also, the use of the 'trim-v' interceptor means we can omit 72 | ;; the leading underscore from the 2nd parameter (event vector). 73 | (fn [old-keyword [new-filter-kw]] ;; handler 74 | new-filter-kw)) ;; return new state for the path 75 | 76 | 77 | ;; usage: (dispatch [:add-todo "Finish comments"]) 78 | (reg-event-db ;; given the text, create a new todo 79 | :add-todo 80 | 81 | ;; The standard set of interceptors, defined above, which we 82 | ;; apply to all todos-modifiing event handlers. Looks after 83 | ;; writing todos to local store, etc. 84 | todo-interceptors 85 | 86 | ;; The event handler function. 87 | ;; The "path" interceptor in `todo-interceptors` means 1st parameter is :todos 88 | (fn [todos [text]] 89 | (let [id (allocate-next-id todos)] 90 | (assoc todos id {:id id :title text :done false})))) 91 | 92 | 93 | (reg-event-db 94 | :toggle-done 95 | todo-interceptors 96 | (fn [todos [id]] 97 | (update-in todos [id :done] not))) 98 | 99 | 100 | (reg-event-db 101 | :save 102 | todo-interceptors 103 | (fn [todos [id title]] 104 | (assoc-in todos [id :title] title))) 105 | 106 | 107 | (reg-event-db 108 | :delete-todo 109 | todo-interceptors 110 | (fn [todos [id]] 111 | (dissoc todos id))) 112 | 113 | 114 | (reg-event-db 115 | :clear-completed 116 | todo-interceptors 117 | (fn [todos _] 118 | (->> (vals todos) ;; find the ids of all todos where :done is true 119 | (filter :done) 120 | (map :id) 121 | (reduce dissoc todos)))) ;; now delete these ids 122 | 123 | 124 | (reg-event-db 125 | :complete-all-toggle 126 | todo-interceptors 127 | (fn [todos _] 128 | (let [new-done (not-every? :done (vals todos))] ;; work out: toggle true or false? 129 | (reduce #(assoc-in %1 [%2 :done] new-done) 130 | todos 131 | (keys todos))))) 132 | -------------------------------------------------------------------------------- /examples/todomvc/src/todomvc/subs.cljs: -------------------------------------------------------------------------------- 1 | (ns todomvc.subs 2 | (:require [re-frame.core :refer [reg-sub subscribe]])) 3 | 4 | ;; ------------------------------------------------------------------------------------- 5 | ;; Layer 2 (see the Subscriptions Infographic for meaning) 6 | ;; 7 | (reg-sub 8 | :showing 9 | (fn [db _] ;; db is the (map) value in app-db 10 | (:showing db))) ;; I repeat: db is a value. Not a ratom. And this fn does not return a reaction, just a value. 11 | 12 | ;; that `fn` is a pure function 13 | 14 | ;; Next, the registration of a similar handler is done in two steps. 15 | ;; First, we `defn` a pure handler function. Then, we use `reg-sub` to register it. 16 | ;; Two steps. This is different to that first registration, above, which was done in one step. 17 | (defn sorted-todos 18 | [db _] 19 | (:todos db)) 20 | (reg-sub :sorted-todos sorted-todos) 21 | 22 | ;; ------------------------------------------------------------------------------------- 23 | ;; Layer 3 (see the infographic for meaning) 24 | ;; 25 | ;; A subscription handler is a function which is re-run when its input signals 26 | ;; change. Each time it is rerun, it produces a new output (return value). 27 | ;; 28 | ;; In the simple case, app-db is the only input signal, as was the case in the two 29 | ;; simple subscriptions above. But many subscriptions are not directly dependent on 30 | ;; app-db, and instead, depend on a value derived from app-db. 31 | ;; 32 | ;; Such handlers represent "intermediate nodes" in a signal graph. New values emanate 33 | ;; from app-db, and flow out through a signal graph, into and out of these intermediate 34 | ;; nodes, before a leaf subscription delivers data into views which render data as hiccup. 35 | ;; 36 | ;; When writing and registering the handler for an intermediate node, you must nominate 37 | ;; one or more input signals (typically one or two). 38 | ;; 39 | ;; reg-sub allows you to supply: 40 | ;; 41 | ;; 1. a function which returns the input signals. It can return either a single signal or 42 | ;; a vector of signals, or a map of where the values are the signals. 43 | ;; 44 | ;; 2. a function which does the computation. It takes input values and produces a new 45 | ;; derived value. 46 | ;; 47 | ;; In the two simple examples at the top, we only supplied the 2nd of these functions. 48 | ;; But now we are dealing with intermediate nodes, we'll need to provide both fns. 49 | ;; 50 | (reg-sub 51 | :todos 52 | 53 | ;; This function returns the input signals. 54 | ;; In this case, it returns a single signal. 55 | ;; Although not required in this example, it is called with two paramters 56 | ;; being the two values supplied in the originating `(subscribe X Y)`. 57 | ;; X will be the query vector and Y is an advanced feature and out of scope 58 | ;; for this explanation. 59 | (fn [query-v] 60 | (subscribe [:sorted-todos])) ;; returns a single input signal 61 | 62 | ;; This 2nd fn does the computation. Data values in, derived data out. 63 | ;; It is the same as the two simple subscription handlers up at the top. 64 | ;; Except they took the value in app-db as their first argument and, instead, 65 | ;; this function takes the value delivered by another input signal, supplied by the 66 | ;; function above: (subscribe [:sorted-todos]) 67 | ;; 68 | ;; Subscription handlers can take 3 parameters: 69 | ;; - the input signals (a single item, a vector or a map) 70 | ;; - the query vector supplied to query-v (the query vector argument 71 | ;; to the "subscribe") and the 3rd one is for advanced cases, out of scope for this discussion. 72 | (fn [sorted-todos query-v] 73 | (vals sorted-todos))) 74 | 75 | ;; So here we define the handler for another intermediate node. 76 | ;; This time the computation involves two input signals. 77 | ;; As a result note: 78 | ;; - the first function (which returns the signals, returns a 2-vector) 79 | ;; - the second function (which is the computation, destructures this 2-vector as its first parameter) 80 | (reg-sub 81 | :visible-todos 82 | 83 | ;; signal function 84 | ;; returns a vector of two input signals 85 | (fn [query-v _] 86 | [(subscribe [:todos]) 87 | (subscribe [:showing])]) 88 | 89 | ;; computation function 90 | (fn [[todos showing] _] ;; that 1st parameter is a 2-vector of values 91 | (let [filter-fn (case showing 92 | :active (complement :done) 93 | :done :done 94 | :all identity)] 95 | (filter filter-fn todos)))) 96 | 97 | ;; ------------------------------------------------------------------------------------- 98 | ;; Hey, wait on!! 99 | ;; 100 | ;; How did those two simple registrations at the top work? 101 | ;; We only supplied one function in those registrations, not two? 102 | ;; Very observant of you, I'm glad you asked. 103 | ;; When the signal-returning-fn is omitted, re-sub provides a default, 104 | ;; and it looks like this: 105 | ;; (fn [_ _] re-frame.db/app-db) 106 | ;; It returns one signal, and that signal is app-db itself. 107 | ;; 108 | ;; So the two simple registrations at the top didn't need to provide a signal-fn, 109 | ;; because they operated only on the value in app-db, supplied as 'db' in the 1st arguement. 110 | 111 | ;; ------------------------------------------------------------------------------------- 112 | ;; SUGAR ? 113 | ;; Now for some syntactic sugar... 114 | ;; The purpose of the sugar is to remove boilerplate noise. To distill to the essential 115 | ;; in 90% of cases. 116 | ;; Because it is so common to nominate 1 or more input signals, 117 | ;; reg-sub provides some macro sugar so you can nominate a very minimal 118 | ;; vector of input signals. The 1st function is not needed. 119 | ;; Here is the example above rewritten using the sugar. 120 | #_(reg-sub 121 | :visible-todos 122 | :<- [:todos] 123 | :<- [:showing] 124 | (fn [[todos showing] _] 125 | (let [filter-fn (case showing 126 | :active (complement :done) 127 | :done :done 128 | :all identity)] 129 | (filter filter-fn todos)))) 130 | 131 | 132 | (reg-sub 133 | :all-complete? 134 | :<- [:todos] 135 | (fn [todos _] 136 | (seq todos))) 137 | 138 | (reg-sub 139 | :completed-count 140 | :<- [:todos] 141 | (fn [todos _] 142 | (count (filter :done todos)))) 143 | 144 | (reg-sub 145 | :footer-counts 146 | :<- [:todos] 147 | :<- [:completed-count] 148 | (fn [[todos completed] _] 149 | [(- (count todos) completed) completed])) 150 | -------------------------------------------------------------------------------- /examples/todomvc/src/todomvc/views.cljs: -------------------------------------------------------------------------------- 1 | (ns todomvc.views 2 | (:require [reagent.core :as reagent] 3 | [re-frame.core :refer [subscribe dispatch]])) 4 | 5 | 6 | (defn todo-input [{:keys [title on-save on-stop]}] 7 | (let [val (reagent/atom title) 8 | stop #(do (reset! val "") 9 | (when on-stop (on-stop))) 10 | save #(let [v (-> @val str clojure.string/trim)] 11 | (when (seq v) (on-save v)) 12 | (stop))] 13 | (fn [props] 14 | [:input (merge props 15 | {:type "text" 16 | :value @val 17 | :auto-focus true 18 | :on-blur save 19 | :on-change #(reset! val (-> % .-target .-value)) 20 | :on-key-down #(case (.-which %) 21 | 13 (save) 22 | 27 (stop) 23 | nil)})]))) 24 | 25 | 26 | (defn todo-item 27 | [] 28 | (let [editing (reagent/atom false)] 29 | (fn [{:keys [id done title]}] 30 | [:li {:class (str (when done "completed ") 31 | (when @editing "editing"))} 32 | [:div.view 33 | [:input.toggle 34 | {:type "checkbox" 35 | :checked done 36 | :on-change #(dispatch [:toggle-done id])}] 37 | [:label 38 | {:on-double-click #(reset! editing true)} 39 | title] 40 | [:button.destroy 41 | {:on-click #(dispatch [:delete-todo id])}]] 42 | (when @editing 43 | [todo-input 44 | {:class "edit" 45 | :title title 46 | :on-save #(dispatch [:save id %]) 47 | :on-stop #(reset! editing false)}])]))) 48 | 49 | 50 | (defn task-list 51 | [] 52 | (let [visible-todos @(subscribe [:visible-todos]) 53 | all-complete? @(subscribe [:all-complete?])] 54 | [:section#main 55 | [:input#toggle-all 56 | {:type "checkbox" 57 | :checked all-complete? 58 | :on-change #(dispatch [:complete-all-toggle (not all-complete?)])}] 59 | [:label 60 | {:for "toggle-all"} 61 | "Mark all as complete"] 62 | [:ul#todo-list 63 | (for [todo visible-todos] 64 | ^{:key (:id todo)} [todo-item todo])]])) 65 | 66 | 67 | (defn footer-controls 68 | [] 69 | (let [[active done] @(subscribe [:footer-counts]) 70 | showing @(subscribe [:showing]) 71 | a-fn (fn [filter-kw txt] 72 | [:a {:class (when (= filter-kw showing) "selected") 73 | :href (str "#/" (name filter-kw))} txt])] 74 | [:footer#footer 75 | [:span#todo-count 76 | [:strong active] " " (case active 1 "item" "items") " left"] 77 | [:ul#filters 78 | [:li (a-fn :all "All")] 79 | [:li (a-fn :active "Active")] 80 | [:li (a-fn :done "Completed")]] 81 | (when (pos? done) 82 | [:button#clear-completed {:on-click #(dispatch [:clear-completed])} 83 | "Clear completed"])])) 84 | 85 | 86 | (defn task-entry 87 | [] 88 | [:header#header 89 | [:h1 "todos"] 90 | [todo-input 91 | {:id "new-todo" 92 | :placeholder "What needs to be done?" 93 | :on-save #(dispatch [:add-todo %])}]]) 94 | 95 | 96 | (defn todo-app 97 | [] 98 | [:div 99 | [:section#todoapp 100 | [task-entry] 101 | (when (seq @(subscribe [:todos])) 102 | [task-list]) 103 | [footer-controls]] 104 | [:footer#info 105 | [:p "Double-click to edit a todo"]]]) 106 | -------------------------------------------------------------------------------- /examples/todomvc/test/todomvc/README.md: -------------------------------------------------------------------------------- 1 | See the main [README.md](../../../README.md) 2 | -------------------------------------------------------------------------------- /examples/todomvc/test/todomvc/core_test.cljs: -------------------------------------------------------------------------------- 1 | (ns todomvc.core-test 2 | (:require [cljs.test :refer-macros [deftest is]] 3 | [day8.re-frame.test :as rf-test] 4 | [re-frame.core :as rf] 5 | todomvc.db 6 | todomvc.events 7 | todomvc.subs)) 8 | 9 | (defn test-fixtures 10 | [] 11 | ;; change this coeffect to make tests start with nothing 12 | (rf/reg-cofx 13 | :local-store-todos 14 | (fn [cofx _] 15 | "Read in todos from localstore, and process into a map we can merge into app-db." 16 | (assoc cofx :local-store-todos 17 | (sorted-map))))) 18 | 19 | (deftest basic--sync 20 | (rf-test/run-test-sync 21 | (test-fixtures) 22 | (rf/dispatch [:initialise-db]) 23 | 24 | ;; Define subscriptions to the app state 25 | (let [showing (rf/subscribe [:showing]) 26 | sorted-todos (rf/subscribe [:sorted-todos]) 27 | todos (rf/subscribe [:todos]) 28 | visible-todos (rf/subscribe [:visible-todos]) 29 | all-complete? (rf/subscribe [:all-complete?]) 30 | completed-count (rf/subscribe [:completed-count]) 31 | footer-counts (rf/subscribe [:footer-counts])] 32 | 33 | ;;Assert the initial state 34 | (is (= :all @showing)) 35 | (is (empty? @sorted-todos)) 36 | (is (empty? @todos)) 37 | (is (empty? @visible-todos)) 38 | (is (= false (boolean @all-complete?))) 39 | (is (= 0 @completed-count)) 40 | (is (= [0 0] @footer-counts)) 41 | 42 | ;;Dispatch the event that you want to test, remember that `re-frame-test` 43 | ;;will process this event immediately. 44 | (rf/dispatch [:add-todo "write first test"]) 45 | 46 | ;;Test that the dispatch has mutated the state in the way that we expect. 47 | (is (= 1 (count @todos) (count @visible-todos) (count @sorted-todos))) 48 | (is (= 0 @completed-count)) 49 | (is (= [1 0] @footer-counts)) 50 | (is (= {:id 1, :title "write first test", :done false} 51 | (first @todos))) 52 | 53 | (rf/dispatch [:add-todo "write second teXt"]) 54 | (is (= 2 (count @todos) (count @visible-todos) (count @sorted-todos))) 55 | (is (= 0 @completed-count)) 56 | (is (= [2 0] @footer-counts)) 57 | (is (= {:id 2, :title "write second teXt", :done false} 58 | (second @todos))) 59 | 60 | (rf/dispatch [:save 2 "write second test"]) 61 | (is (= 2 (count @todos) (count @visible-todos) (count @sorted-todos))) 62 | (is (= 0 @completed-count)) 63 | (is (= [2 0] @footer-counts)) 64 | (is (= {:id 2, :title "write second test", :done false} 65 | (second @todos))) 66 | 67 | (rf/dispatch [:toggle-done 1]) 68 | (is (= 2 (count @todos) (count @visible-todos) (count @sorted-todos))) 69 | (is (= 1 @completed-count)) 70 | (is (= [1 1] @footer-counts)) 71 | (is (= {:id 1, :title "write first test", :done true} 72 | (first @todos))) 73 | 74 | (rf/dispatch [:set-showing :active]) 75 | (is (= :active @showing)) 76 | (is (= 2 (count @todos) (count @sorted-todos))) 77 | (is (= 1 (count @visible-todos))) 78 | (is (= 1 @completed-count)) 79 | (is (= [1 1] @footer-counts)) 80 | (is (= {:id 2, :title "write second test", :done false} 81 | (first @visible-todos))) 82 | 83 | (rf/dispatch [:set-showing :done]) 84 | (is (= :done @showing)) 85 | (is (= 2 (count @todos) (count @sorted-todos))) 86 | (is (= 1 (count @visible-todos))) 87 | (is (= 1 @completed-count)) 88 | (is (= [1 1] @footer-counts)) 89 | (is (= {:id 1, :title "write first test", :done true} 90 | (first @visible-todos))) 91 | 92 | (rf/dispatch [:toggle-done 2]) 93 | (is (= true (boolean @all-complete?)))))) 94 | 95 | 96 | 97 | (deftest basic--async 98 | (rf-test/run-test-async 99 | (test-fixtures) 100 | (rf/dispatch-sync [:initialise-db]) 101 | 102 | ;;Define subscriptions to the app state 103 | (let [showing (rf/subscribe [:showing]) 104 | sorted-todos (rf/subscribe [:sorted-todos]) 105 | todos (rf/subscribe [:todos]) 106 | visible-todos (rf/subscribe [:visible-todos]) 107 | all-complete? (rf/subscribe [:all-complete?]) 108 | completed-count (rf/subscribe [:completed-count]) 109 | footer-counts (rf/subscribe [:footer-counts])] 110 | 111 | ;;Assert the initial state 112 | (is (= :all @showing)) 113 | (is (empty? @sorted-todos)) 114 | (is (empty? @todos)) 115 | (is (empty? @visible-todos)) 116 | (is (= 0 @completed-count)) 117 | 118 | ;;Dispatch the event that you want to test, remember 119 | ;;that re-frame will not process this event immediately, 120 | ;;and need to use the `wait-for` macro to continue the tests. 121 | (rf/dispatch [:add-todo "write first test"]) 122 | 123 | 124 | ;;Wait for the `:add-todo` event to be dispatched 125 | ;;(note, in the use of this macro we would typically wait for 126 | ;;an event that had been triggered by the successful return of 127 | ;;the async event). 128 | (rf-test/wait-for [:add-todo] 129 | 130 | ;;Test that the dispatch has mutated the state in the way 131 | ;;that we expect. 132 | (is (= [{:id 1, :title "write first test", :done false}] @todos)) 133 | 134 | (rf/dispatch [:add-todo "write second teXt"]) 135 | (rf-test/wait-for [:add-todo] 136 | (is (= 2 (count @todos) (count @visible-todos) (count @sorted-todos))) 137 | (is (= 0 @completed-count)) 138 | (is (= [2 0] @footer-counts)) 139 | (is (= {:id 2, :title "write second teXt", :done false} 140 | (second @todos))) 141 | 142 | (rf/dispatch [:save 2 "write second test"]) 143 | (rf-test/wait-for [:save] 144 | (is (= "write second test" (:title (second @todos)))) 145 | 146 | (rf/dispatch [:toggle-done 1]) 147 | (rf-test/wait-for [:toggle-done] 148 | (is (= true (:done (first @todos)))) 149 | (rf/dispatch [:set-showing :active]) 150 | (rf-test/wait-for [:set-showing] 151 | (is (= :active @showing)) 152 | (is (= 1 (count @visible-todos))) 153 | (is (= 1 @completed-count)) 154 | (is (= {:id 2, :title "write second test", :done false} 155 | (first @visible-todos))) 156 | (rf/dispatch [:set-showing :done]) 157 | (rf-test/wait-for [:set-showing] 158 | (is (= :done @showing)) 159 | (is (= 1 (count @visible-todos))) 160 | (is (= {:id 1, :title "write first test", :done true} 161 | (first @visible-todos)))))))))))) 162 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | var junitOutputDir = process.env.CIRCLE_TEST_REPORTS || "target/junit" 3 | 4 | config.set({ 5 | browsers: ['ChromeHeadless'], 6 | basePath: 'target', 7 | files: ['karma-test.js'], 8 | frameworks: ['cljs-test'], 9 | plugins: [ 10 | 'karma-cljs-test', 11 | 'karma-chrome-launcher', 12 | 'karma-junit-reporter' 13 | ], 14 | colors: true, 15 | logLevel: config.LOG_INFO, 16 | client: { 17 | args: ['shadow.test.karma.init'], 18 | singleRun: true 19 | }, 20 | 21 | // the default configuration 22 | junitReporter: { 23 | outputDir: junitOutputDir + '/karma', // results will be saved as outputDir/browserName.xml 24 | outputFile: undefined, // if included, results will be saved as outputDir/browserName/outputFile 25 | suite: '' // suite will become the package name attribute in xml testsuite element 26 | } 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "day8.re-frame.test", 3 | "devDependencies": { 4 | "karma": "5.2.3", 5 | "karma-chrome-launcher": "3.1.0", 6 | "karma-cljs-test": "0.1.0", 7 | "karma-junit-reporter": "2.0.1", 8 | "shadow-cljs": "2.11.4" 9 | }, 10 | "dependencies": { 11 | "react": "16.13.0", 12 | "react-dom": "16.13.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject day8.re-frame/test "lein-git-inject/version" 2 | :description "re-frame testing tools" 3 | :url "https://github.com/day8/re-frame-test" 4 | :license {:name "MIT" 5 | :url "https://opensource.org/licenses/MIT"} 6 | 7 | :dependencies [[org.clojure/clojure "1.10.1" :scope "provided"] 8 | [org.clojure/clojurescript "1.10.773" :scope "provided" 9 | :exclusions [com.google.javascript/closure-compiler-unshaded 10 | org.clojure/google-closure-library 11 | org.clojure/google-closure-library-third-party]] 12 | [thheller/shadow-cljs "2.11.4" :scope "provided"] 13 | [re-frame "1.1.1"]] 14 | 15 | :plugins [[day8/lein-git-inject "0.0.14"] 16 | [lein-shadow "0.3.1"] 17 | [lein-shell "0.5.0"]] 18 | 19 | :middleware [leiningen.git-inject/middleware] 20 | 21 | :profiles {:dev {:dependencies [[ch.qos.logback/logback-classic "1.2.3"]] 22 | :resource-paths ["test-resources"]}} 23 | 24 | :release-tasks [["deploy" "clojars"]] 25 | 26 | :deploy-repositories [["clojars" {:sign-releases false 27 | :url "https://clojars.org/repo" 28 | :username :env/CLOJARS_USERNAME 29 | :password :env/CLOJARS_TOKEN}]] 30 | 31 | :jvm-opts ["-Xmx1g"] 32 | 33 | :clean-targets [:target-path "run/compiled"] 34 | 35 | :shadow-cljs {:nrepl {:port 8777} 36 | 37 | :builds {:browser-test 38 | {:target :browser-test 39 | :ns-regexp "-test$" 40 | :test-dir "resources/public/js/test" 41 | :devtools {:http-root "resources/public/js/test" 42 | :http-port 8290}} 43 | 44 | :karma-test 45 | {:target :karma 46 | :ns-regexp "-test$" 47 | :output-to "target/karma-test.js"}}} 48 | 49 | :aliases {"watch" ["with-profile" "dev" "do" 50 | ["clean"] 51 | ["shadow" "watch" "browser-test" "karma-test"]] 52 | "ci" ["do" 53 | ["clean"] 54 | ["shadow" "compile" "karma-test"] 55 | ["shell" "karma" "start" "--single-run" "--reporters" "junit,dots"]]}) 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/day8/re_frame/test.cljc: -------------------------------------------------------------------------------- 1 | (ns day8.re-frame.test 2 | #?(:cljs (:require-macros day8.re-frame.test)) 3 | (:require #?(:cljs [cljs.test :as test] 4 | :clj [clojure.test :as test]) 5 | [re-frame.core :as rf] 6 | [re-frame.router :as rf.router] 7 | [re-frame.db :as rf.db] 8 | [re-frame.interop :as rf.int]) 9 | #?(:clj (:import [java.util.concurrent Executors TimeUnit]))) 10 | 11 | 12 | ;;;; 13 | ;;;; General test utils 14 | ;;;; 15 | 16 | (defn- dequeue! 17 | "Dequeue an item from a persistent queue which is stored as the value in 18 | queue-atom. Returns the item, and updates the atom with the new queue 19 | value. If the queue is empty, does not alter it and returns nil." 20 | [queue-atom] 21 | (let [queue @queue-atom] 22 | (when (seq queue) 23 | (if (compare-and-set! queue-atom queue (pop queue)) 24 | (peek queue) 25 | (recur queue-atom))))) 26 | 27 | 28 | 29 | ;;;; 30 | ;;;; Async tests 31 | ;;;; 32 | 33 | (def ^:dynamic *test-timeout* 5000) 34 | 35 | (def test-context* 36 | "`test-context*` is used to communicate internal details of the test between 37 | `run-test-async*` and `wait-for*`." 38 | (atom nil)) 39 | 40 | (defn set-test-context 41 | [tc] 42 | (reset! test-context* tc)) 43 | 44 | (defn clear-test-context 45 | [] 46 | (reset! test-context* nil)) 47 | 48 | #?(:clj 49 | (defmacro with-temp-re-frame-state 50 | "Run `body`, but discard whatever effects it may have on re-frame's internal 51 | state (by resetting `app-db` and re-frame's various different types of 52 | handlers after `body` has run). 53 | 54 | Note: you *can't* use this macro to clean up a JS async test, since the macro 55 | will perform the cleanup before your async code actually has a chance to run. 56 | `run-test-async` will automatically do this cleanup for you." 57 | [& body] 58 | `(let [restore-fn# (rf/make-restore-fn)] 59 | (try 60 | ~@body 61 | (finally 62 | (clear-test-context) 63 | (restore-fn#)))))) 64 | 65 | (defn run-test-async* [f] 66 | (let [initial-test-context {:wait-for-depth 0 67 | :max-wait-for-depth 0 68 | :now-waiting-for nil}] 69 | #?(:clj (with-temp-re-frame-state 70 | (let [done-promise (promise) 71 | executor (Executors/newSingleThreadExecutor) 72 | fail-ex (atom nil) 73 | initial-test-context (assoc initial-test-context 74 | :done 75 | #(deliver done-promise ::done))] 76 | (with-redefs [rf.int/executor executor] 77 | ;; Execute the test code itself on the same thread as the 78 | ;; re-frame event handlers run, so that we accurately 79 | ;; simulate the single-threaded JS environment and also so 80 | ;; that we don't have to worry about making the JVM 81 | ;; implementation of the re-frame EventQueue thread-safe. 82 | (rf.int/next-tick #(try 83 | (set-test-context initial-test-context) 84 | (f) 85 | (catch Throwable t 86 | (reset! fail-ex t)))) 87 | (let [result (deref done-promise *test-timeout* ::timeout)] 88 | (.shutdown executor) 89 | (when-not (.awaitTermination executor 5 TimeUnit/SECONDS) 90 | (throw (ex-info (str "Couldn't cleanly shut down the re-frame event queue's " 91 | "executor. Possibly this could result in a polluted " 92 | "`app-db` for other tests. Probably it means you're " 93 | "doing something very strange in an event handler. " 94 | "(Catching InterruptedException, for a start.)") 95 | {}))) 96 | (if-let [ex @fail-ex] 97 | (throw ex) 98 | (test/is (not= ::timeout result) 99 | (str "Test timed out after " *test-timeout* "ms" 100 | (when-let [ev (:now-waiting-for @test-context*)] 101 | (str ", waiting for " (pr-str ev) "."))))))))) 102 | 103 | :cljs (test/async 104 | done 105 | (let [restore-fn (rf/make-restore-fn)] 106 | (set-test-context 107 | (assoc initial-test-context 108 | :done 109 | (fn [] 110 | (clear-test-context) 111 | (restore-fn) 112 | (done)))) 113 | (f)))))) 114 | 115 | 116 | (defmacro run-test-async 117 | "Run `body` as an async re-frame test. The async nature means you'll need to 118 | use `wait-for` any time you want to make any assertions that should be true 119 | *after* an event has been handled. It's assumed that there will be at least 120 | one `wait-for` in the body of your test (otherwise you don't need this macro 121 | at all). 122 | 123 | Note: unlike regular ClojureScript `cljs.test/async` tests, `wait-for` takes 124 | care of calling `(done)` for you: you don't need to do anything specific to 125 | handle the fact that your test is asynchronous, other than make sure that all 126 | your assertions happen with `wait-for` blocks. 127 | 128 | This macro will automatically clean up any changes to re-frame state made 129 | within the test body, as per `with-temp-re-frame-state` (except that the way 130 | it's done here *does* work for async tests, whereas that macro used by itself 131 | doesn't)." 132 | [& body] 133 | `(run-test-async* (fn [] ~@body))) 134 | 135 | 136 | (defn- as-callback-pred 137 | "Interprets the acceptable input values for `wait-for`'s `ok-ids` and 138 | `failure-ids` params to produce a predicate function on an event. See 139 | `wait-for` for details." 140 | [callback-pred] 141 | (when callback-pred 142 | (cond (or (set? callback-pred) 143 | (vector? callback-pred)) (fn [event] 144 | (some (fn [pred] (pred event)) 145 | (map as-callback-pred (seq callback-pred)))) 146 | (fn? callback-pred) callback-pred 147 | (keyword? callback-pred) (fn [[event-id _]] 148 | (= callback-pred event-id)) 149 | :else (throw 150 | (ex-info (str (pr-str callback-pred) 151 | " isn't an event predicate") 152 | {:callback-pred callback-pred}))))) 153 | 154 | 155 | (defn wait-for* 156 | "This function is an implementation detail: in your async tests (within a 157 | `run-test-async`), you should use the `wait-for` macro instead. (For 158 | synchronous tests within `run-test-sync`, you don't need this capability at 159 | all.) 160 | 161 | Installs `callback` as a re-frame post-event callback handler, called as soon 162 | as any event matching `ok-ids` is handled. Aborts the test as a failure if 163 | any event matching `failure-ids` is handled. 164 | 165 | Since this is intended for use in asynchronous tests: it will return 166 | immediately after installing the callback -- it doesn't *actually* wait. 167 | 168 | Note that `wait-for*` tracks whether, during your callback, you call 169 | `wait-for*` again. If you *don't*, then, given the way asynchronous tests 170 | work, your test must necessarily be finished. So `wait-for*` will 171 | call `(done)` for you." 172 | [ok-ids failure-ids callback] 173 | ;; `:wait-for-depth` and `:max-wait-for-depth` are used together to track how 174 | ;; "deep" we are in callback functions as the test progresses. We increment 175 | ;; `:max-wait-for-depth` before installing a post-event callback handler, then 176 | ;; after the event later occurs and the callback handler subsequently runs, we 177 | ;; check whether it has been incremented further (indicating another 178 | ;; `wait-for*` callback handler has been installed). If it *hasn't*, since 179 | ;; `wait-for*` only makes sense in a tail position, this means the test is 180 | ;; complete, and we can call `(done)`, saving the test author the trouble of 181 | ;; passing `done` through every single callback. 182 | (let [{done :done 183 | wait-for-depth :max-wait-for-depth 184 | :as test-context} (swap! test-context* update :max-wait-for-depth inc)] 185 | 186 | (let [ok-pred (as-callback-pred ok-ids) 187 | fail-pred (as-callback-pred failure-ids) 188 | cb-id (gensym "wait-for-cb-fn")] 189 | (rf/add-post-event-callback cb-id (#?(:cljs fn :clj bound-fn) [event _] 190 | ;; (taoensso.timbre/warn "post-event-callback: " event) 191 | (cond (and fail-pred 192 | (not (test/is (not (fail-pred event)) 193 | "Received failure event"))) 194 | (do 195 | (rf/remove-post-event-callback cb-id) 196 | (swap! test-context* assoc :now-waiting-for nil) 197 | (done)) 198 | 199 | (ok-pred event) 200 | (do 201 | (rf/remove-post-event-callback cb-id) 202 | (swap! test-context* assoc :now-waiting-for nil) 203 | (callback event) 204 | (when (= wait-for-depth 205 | (:max-wait-for-depth @test-context*)) 206 | ;; `callback` has completed with no `wait-for*` 207 | ;; calls, so we're not waiting for anything 208 | ;; further. Given that `wait-for*` calls are 209 | ;; only valid in tail position, the test must 210 | ;; now be finished. 211 | (done))) 212 | 213 | ;; Test is not interested this event, but we still 214 | ;; need to wait for the one we *are* interested in. 215 | :else 216 | nil))) 217 | (swap! test-context* assoc :now-waiting-for ok-ids)))) 218 | 219 | 220 | (defmacro wait-for 221 | "Execute `body` once an event identified by the predicate(s) `ids` has been handled. 222 | 223 | `ids` and `failure-ids` are means to identify an event. Normally, each would 224 | be a simple keyword or a set of keywords. If an event with event-id of (or 225 | in) `ids` is handled, the test will continue by executing the body. If an 226 | event with an event-id of (or in) `failure-ids` is handled, the test will 227 | abort and fail. 228 | 229 | IMPORTANT NOTE: due to the way async tests in re-frame work, code you want 230 | executed after the event you're waiting for has to happen in the `body` of the 231 | `wait-for` (in an implicit callback), not just lexically after the the 232 | `wait-for` call. In practice, this means `wait-for` must always be in a tail 233 | position. 234 | 235 | Eg: 236 | (run-test-async 237 | (dispatch [:get-user 2]) 238 | (wait-for [#{:got-user} #{:no-such-user :system-unavailable} event] 239 | (is (= (:username @(subscribe [:user])) \"johnny\"))) 240 | ;; Don't put code here, it will run *before* the event you're waiting 241 | ;; for. 242 | ) 243 | 244 | Acceptable inputs for `ids` and `failure-ids` are: 245 | - `:some-event-id` => matches an event with that ID 246 | 247 | - `#{:some-event-id :other-event-id}` => matches an event with any of the 248 | given IDs 249 | 250 | - `[:some-event-id :other-event-id]` => ditto (checks in order) 251 | 252 | - `(fn [event] ,,,) => uses the function as a predicate 253 | 254 | - `[(fn [event] ,,,) (fn [event] ,,,)]` => tries each predicate in turn, 255 | matching an event which matches 256 | at least one predicate 257 | 258 | - `#{:some-event-id (fn [event] ,,,)}` => tries each 259 | 260 | Note that because we're liberal about whether you supply `failure-ids` and/or 261 | `event-sym`, if you do choose to supply only one, and you want that one to be 262 | `event-sym`, you can't supply it as a destructuring form (because we can't 263 | disambiguate that from a vector of `failure-ids`). You can just supply `nil` 264 | as `failure-ids` in this case, and then you'll be able to destructure." 265 | [[ids failure-ids event-sym :as argv] & body] 266 | (let [[failure-ids event-sym] (case (count argv) 267 | 3 [failure-ids event-sym] 268 | 2 (if (symbol? (second argv)) 269 | [nil (second argv)] 270 | [(second argv) (gensym "event")]) 271 | 1 [nil (gensym "event")] 272 | 0 (throw (ex-info "wait-for needs to know what to wait for!" 273 | {})))] 274 | `(wait-for* ~ids ~failure-ids (fn [~event-sym] ~@body)))) 275 | 276 | 277 | 278 | ;;;; 279 | ;;;; Sync tests 280 | ;;;; 281 | 282 | (def ^{:dynamic true, :private true} *handling* false) 283 | 284 | (defn run-test-sync* [f] 285 | (day8.re-frame.test/with-temp-re-frame-state 286 | ;; Bypass the actual re-frame EventQueue and use a local alternative over 287 | ;; which we have full control. 288 | (let [my-queue (atom rf.int/empty-queue) 289 | new-dispatch (fn [argv] 290 | (swap! my-queue conj argv) 291 | (when-not *handling* 292 | (binding [*handling* true] 293 | (loop [] 294 | (when-let [queue-head (dequeue! my-queue)] 295 | (rf.router/dispatch-sync queue-head) 296 | (recur))))))] 297 | (with-redefs [rf/dispatch new-dispatch 298 | rf.router/dispatch new-dispatch] 299 | (f))))) 300 | 301 | 302 | (defmacro run-test-sync 303 | "Execute `body` as a test, where each `dispatch` call is executed 304 | synchronously (via `dispatch-sync`), and any subsequent dispatches which are 305 | caused by that dispatch are also fully handled/executed prior to control flow 306 | returning to your test. 307 | 308 | Think of it kind of as though every `dispatch` in your app had been magically 309 | turned into `dispatch-sync`, and re-frame had lifted the restriction that says 310 | you can't call `dispatch-sync` from within an event handler. 311 | 312 | Note that this is *not* achieved with blocking. It relies on you not doing 313 | anything asynchronous (such as an actual AJAX call or `js/setTimeout`) 314 | directly in your event handlers. In a real app running in the real browser, 315 | of course that won't apply, so this might seem useless at first. But if 316 | you're a well-behaved re-framer, all of your asynchronous stuff (which is by 317 | definition side-effecty) will happen in effectful event handlers installed 318 | with `reg-fx`. Which works very nicely: in your tests, install an alternative 319 | version of those effectful event handlers which behaves synchronously. For 320 | maximum coolness, you might want to consider running your tests on the JVM and 321 | installing a `reg-fx` handler which actually invokes your JVM Clojure 322 | server-side Ring handler where your in-browser code would make an AJAX call." 323 | [& body] 324 | `(run-test-sync* (fn [] ~@body))) 325 | -------------------------------------------------------------------------------- /src/deps.cljs: -------------------------------------------------------------------------------- 1 | {:npm-dev-deps {"shadow-cljs" "2.11.4" 2 | "karma" "5.2.3" 3 | "karma-chrome-launcher" "3.1.0" 4 | "karma-cljs-test" "0.1.0" 5 | "karma-junit-reporter" "2.0.1"}} 6 | -------------------------------------------------------------------------------- /test-resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/day8/re_frame/test_reframe/macros.cljc: -------------------------------------------------------------------------------- 1 | (ns day8.re-frame.test-reframe.macros 2 | #?(:cljs (:require-macros day8.re-frame.test-reframe.macros)) 3 | #?(:cljs (:require [cljs.test]))) 4 | 5 | 6 | (def ^:dynamic *captured-test-output* nil) 7 | 8 | (defn report [m] 9 | (swap! *captured-test-output* (fnil conj []) m)) 10 | 11 | 12 | #?(:cljs 13 | (do 14 | (defmethod cljs.test/report [::custom :begin-test-var] [m] (report m)) 15 | (defmethod cljs.test/report [::custom :pass] [m] (report m)) 16 | (defmethod cljs.test/report [::custom :fail] [m] (report m)) 17 | (defmethod cljs.test/report [::custom :error] [m] (report m)) 18 | (defmethod cljs.test/report [::custom :end-test-var] [m] (report m)))) 19 | 20 | 21 | #?(:clj 22 | (defmacro assert-captured-test-results--jvm 23 | [assert-fn & body] 24 | (let [captured-test (gensym "captured-test_")] 25 | `(do 26 | (clojure.test/deftest ~captured-test 27 | ~@body) 28 | 29 | (let [captured-test-output# (atom [])] 30 | (try 31 | (binding [*captured-test-output* captured-test-output# 32 | clojure.test/report report] 33 | (~captured-test)) 34 | (finally 35 | (alter-meta! (var ~captured-test) dissoc :test) 36 | (ns-unmap *ns* '~captured-test))) 37 | 38 | (~assert-fn @captured-test-output#)))))) 39 | 40 | 41 | #?(:cljs 42 | (defn wrap-cljs-async-test-result [test-result run-after-complete] 43 | (reify 44 | cljs.test/IAsyncTest 45 | cljs.core/IFn 46 | (-invoke [_ done] 47 | ;; (done) calls `(cljs.test/report {:type :end-test-var, ...})`. Then 48 | ;; if there are other tests being run in the same test 'block', they'll 49 | ;; continue after that. Here, because we invoke the test (below) via 50 | ;; `(captured-test#)`, we know that there are no other tests in the 51 | ;; (captured) test's block, so we can safely execute `(done)` before 52 | ;; continuing with some other work, without worrying that `(done)` 53 | ;; might trigger a whole subsequent test or three. 54 | (test-result (fn [] 55 | (done) 56 | (run-after-complete))) 57 | ::async)))) 58 | 59 | 60 | #?(:clj 61 | (defmacro assert-captured-test-results--js 62 | [assert-fn & body] 63 | (let [captured-test (gensym "captured-test_")] 64 | `(do 65 | (let [captured-test-output# (atom []) 66 | ;; If the captured test is an async test, then our capturing test 67 | ;; will also have to be async. The capturing test will have to 68 | ;; invoke its `done` function only when the captured test's `done` 69 | ;; function is invoked. The capturing test puts its `done` 70 | ;; function in this atom so that the captured test can be made to 71 | ;; call it. 72 | capturing-test-done-fn# (atom nil) 73 | previous-test-env# cljs.test/*current-env* 74 | ;; The complete-fn# executes the actual assertion, after cleaning 75 | ;; back up to remove the temporary test reporting capture 76 | ;; mechanism. This happens either just after the test (for a 77 | ;; non-async test), or in the `done` continuation (for an async 78 | ;; test). 79 | complete-fn# (fn [] 80 | (set! *captured-test-output* nil) 81 | (set! cljs.test/*current-env* previous-test-env#) 82 | (~assert-fn @captured-test-output#))] 83 | 84 | (cljs.test/deftest ~captured-test 85 | (let [test-result# (do ~@body)] 86 | (if (cljs.test/async? test-result#) 87 | ;; The `done` continuation invoked here finishes the capturing 88 | ;; test, and will then continue on to execute all the other 89 | ;; tests that the user has asked for. So anything we want to do 90 | ;; on completion of the captured test has to be done before the 91 | ;; capturing test's `done` is invoked. 92 | (wrap-cljs-async-test-result test-result# (fn [] 93 | (complete-fn#) 94 | (@capturing-test-done-fn#))) 95 | ::not-async))) 96 | 97 | (try 98 | (set! cljs.test/*current-env* (assoc previous-test-env# :reporter ::custom)) 99 | (set! *captured-test-output* captured-test-output#) 100 | 101 | (let [result# (~captured-test)] 102 | (if (= ::async result#) 103 | ;; If it's an async test, then the `complete-fn#` will get 104 | ;; called in the captured test's `done` continuation, thanks to 105 | ;; the use of `wrap-cljs-async-test-result` in the `deftest` 106 | ;; itself (just above), so we don't call it here. 107 | (cljs.test/async done# 108 | (reset! capturing-test-done-fn# done#)) 109 | (complete-fn#))) 110 | (finally 111 | ;; `ns-unmap` on ClojureScript is a macro which can only take quoted 112 | ;; symbols, so we can't do the same in JS as we do on the JVM. 113 | (alter-meta! ~captured-test dissoc :test)))))))) 114 | 115 | 116 | #?(:clj 117 | (defmacro assert-captured-test-results 118 | "Execute `body` as a test within a context where `clojure.test` or `cljs.test` 119 | will use a reporting function that -- instead of reporting test success or 120 | failure as per normal -- will just capture (and subsequently return) the calls 121 | made to the report function, for subsequent inspection. 122 | 123 | Used in the tests with a `body` representing a test we might expect a user 124 | of this library to write, to assert that running that test produces the 125 | correct output from clojure.test. 126 | 127 | Note that test which captures an async test in JS will correctly 128 | macro-expand into a `cljs.test/async` test, but as a result, such a test will 129 | need to be in a tail position. 130 | 131 | Don't forget to say hi to the turtles on the way down..." 132 | [assert-fn & body] 133 | (let [cljs-env? (boolean (:ns &env))] 134 | (if cljs-env? 135 | `(assert-captured-test-results--js ~assert-fn ~@body) 136 | `(assert-captured-test-results--jvm ~assert-fn ~@body))))) 137 | -------------------------------------------------------------------------------- /test/day8/re_frame/test_reframe/runner.cljs: -------------------------------------------------------------------------------- 1 | (ns day8.re-frame.test-reframe.runner 2 | (:require [jx.reporter.karma :as karma :include-macros true] 3 | [day8.re-frame.test-reframe.test-test])) 4 | 5 | (enable-console-print!) 6 | 7 | (defn ^:export run-karma [karma] 8 | (karma/run-tests 9 | karma 10 | 'day8.re-frame.test-reframe.test-test)) 11 | -------------------------------------------------------------------------------- /test/day8/re_frame/test_reframe/test_test.cljc: -------------------------------------------------------------------------------- 1 | (ns day8.re-frame.test-reframe.test-test 2 | (:require #?(:cljs [cljs.test :refer-macros [deftest is]] 3 | :clj [clojure.test :refer [deftest is]]) 4 | #?(:cljs [day8.re-frame.test-reframe.macros :refer-macros [assert-captured-test-results]] 5 | :clj [day8.re-frame.test-reframe.macros :refer [assert-captured-test-results]]) 6 | [day8.re-frame.test :as rf-test] 7 | [re-frame.core :as rf] 8 | re-frame.db 9 | re-frame.registrar)) 10 | 11 | 12 | (deftest temp-re-frame-state 13 | ;; If only there were some sort of macro we could use to handle this cleanup 14 | ;; for us and prevent our various tests from interfering with one another via 15 | ;; their side effects... 16 | (letfn [(cleanup! [] 17 | (reset! re-frame.db/app-db {}) 18 | (re-frame.subs/clear-all-handlers!) 19 | (rf/clear-fx :do-ajax) 20 | (rf/clear-event :inc) 21 | (rf/clear-event :ajax))] 22 | (cleanup!) 23 | (try 24 | (let [ajax (atom 0)] 25 | ;; Set up common handlers for the whole test. 26 | (rf/reg-event-db :inc (fn do-inc [db _] (update db :counter (fnil inc 0)))) 27 | (rf/reg-fx :do-ajax (fn [_] (swap! ajax inc))) 28 | (rf/reg-event-fx :ajax (fn [_ _] {:do-ajax nil})) 29 | 30 | (rf/reg-sub :counter (fn [db _] (:counter db))) 31 | (rf/reg-sub :dead (fn [db _] (or (:dead db) false))) 32 | 33 | (let [counter (rf/subscribe [:counter]) 34 | dead (rf/subscribe [:dead])] 35 | ;; Do some work prior to snapshotting the state, so we have something 36 | ;; other than {} to restore. 37 | (is (= nil @counter)) 38 | (rf/dispatch-sync [:inc]) 39 | (is (= 1 @counter)) 40 | 41 | (rf-test/with-temp-re-frame-state ; BEGIN TRANSACTION 42 | (rf/dispatch-sync [:inc]) 43 | (is (= 2 @counter)) 44 | 45 | (rf/dispatch-sync [:ajax]) 46 | (is (= 1 @ajax)) 47 | 48 | (rf/reg-event-db :die (fn [db _] (assoc db :dead true))) 49 | (rf/dispatch-sync [:die]) 50 | (is (= true @dead))) ; ROLLBACK TRANSACTION 51 | 52 | (is (= 1 @counter)) 53 | (rf/dispatch-sync [:inc]) 54 | (is (= 2 @counter)) 55 | 56 | (rf/dispatch-sync [:ajax]) 57 | (is (= 2 @ajax)) 58 | 59 | (is (= false @dead)) 60 | ;; This event handler no longer exists, so this should be a no-op. 61 | (rf/dispatch-sync [:die]) 62 | (is (= false @dead)))) 63 | (finally 64 | (cleanup!))))) 65 | 66 | 67 | 68 | ;; In the tests below, the `assert-captured-test-results` macro takes the first 69 | ;; argument (a function) and calls it with the captured test results of running 70 | ;; the remainder of the macro call as the body of a `deftest` in a re-frame 71 | ;; application's test code. Our tests here are asserting against the expected 72 | ;; test reports that the user would see from their tests. 73 | 74 | 75 | ;;;; 76 | ;;;; Sync tests 77 | ;;;; 78 | 79 | (deftest run-test-sync--basic-flow 80 | (assert-captured-test-results 81 | (fn [results] 82 | (is (= (map #(select-keys % [:type :expected]) results) 83 | [{:type :begin-test-var} 84 | {:type :pass, :expected '(= "world" @hello-reaction)} 85 | {:type :fail, :expected '(= "nope" (:goodbye @db-reaction))} ; Failure noted below. 86 | {:type :pass, :expected '(= "world" @hello-reaction)} 87 | {:type :pass, :expected '(= {:hello "world", :goodbye "bugs"} @db-reaction)} 88 | {:type :end-test-var}]))) 89 | 90 | (rf-test/with-temp-re-frame-state 91 | (rf/reg-event-db :initialise-db (fn [_ _] {:hello "world"})) 92 | (rf/reg-event-db :update-db (fn [db [_ arg]] (assoc db :goodbye arg))) 93 | (rf/reg-sub :hello-sub (fn [db _] (:hello db))) 94 | (rf/reg-sub :db-sub (fn [db _] db)) 95 | 96 | (let [hello-reaction (rf/subscribe [:hello-sub]) 97 | db-reaction (rf/subscribe [:db-sub])] 98 | 99 | (rf-test/run-test-sync 100 | (rf/dispatch [:initialise-db]) 101 | (is (= "world" @hello-reaction)) 102 | (is (= "nope" (:goodbye @db-reaction))) ; Not true, reports failure. 103 | 104 | (rf/dispatch [:update-db "bugs"]) 105 | (is (= "world" @hello-reaction)) 106 | (is (= {:hello "world", :goodbye "bugs"} @db-reaction))))))) 107 | 108 | 109 | (deftest run-test-sync--event-handler-dispatches-event 110 | (assert-captured-test-results 111 | (fn [results] 112 | (is (= (map #(select-keys % [:type :expected]) results) 113 | [{:type :begin-test-var} 114 | {:type :pass, :expected '(= true @success)} 115 | {:type :end-test-var}]))) 116 | 117 | (rf-test/with-temp-re-frame-state 118 | (rf/reg-event-ctx :do-stuff 119 | (fn [ctx] 120 | ;; In the real world, you should use effectful event 121 | ;; handlers to `dispatch` as a result of an event, but the 122 | ;; net result here is the same. 123 | (rf/dispatch [:do-more-stuff]) 124 | ctx)) 125 | (rf/reg-event-db :do-more-stuff 126 | (fn [db _] (assoc db :success true))) 127 | 128 | (rf/reg-sub :success (fn [db _] (:success db))) 129 | 130 | (let [success (rf/subscribe [:success])] 131 | (rf-test/run-test-sync 132 | (rf/dispatch [:do-stuff]) 133 | (is (= true @success))))))) 134 | 135 | (deftest run-test-sync--event-handler-dispatches-event-cofx 136 | (assert-captured-test-results 137 | (fn [results] 138 | (is (= (map #(select-keys % [:type :expected]) results) 139 | [{:type :begin-test-var} 140 | {:type :pass, :expected '(= true @success)} 141 | {:type :end-test-var}]))) 142 | 143 | (rf-test/with-temp-re-frame-state 144 | (rf/reg-event-fx :do-stuff 145 | (fn [{:keys [db] :as cofx} event] 146 | ;; Dispatch an event using cofx 147 | {:dispatch [:do-more-stuff]})) 148 | (rf/reg-event-db :do-more-stuff 149 | (fn [db _] (assoc db :success true))) 150 | 151 | (rf/reg-sub :success (fn [db _] (:success db))) 152 | 153 | (let [success (rf/subscribe [:success])] 154 | (rf-test/run-test-sync 155 | (rf/dispatch [:do-stuff]) 156 | (is (= true @success))))))) 157 | 158 | (deftest run-test-sync--error-in-event-handler 159 | (assert-captured-test-results 160 | (fn [results] 161 | (is (= (map #(select-keys % [:type :expected]) results) 162 | [{:type :begin-test-var} 163 | {:type :pass, :expected '(= true (boolean "Did get here."))} 164 | {:type :error, :expected nil} 165 | {:type :end-test-var}]))) 166 | 167 | (rf-test/with-temp-re-frame-state 168 | (rf/reg-event-ctx :do-stuff 169 | (fn [ctx] 170 | (rf/dispatch [:do-more-stuff]) 171 | ctx)) 172 | (rf/reg-event-db :do-more-stuff 173 | (fn [db _] 174 | (throw (ex-info "Whoops!" {:expected true})))) 175 | 176 | (rf-test/run-test-sync 177 | (is (= true (boolean "Did get here."))) 178 | (rf/dispatch [:do-stuff]) ; Synchronously explodes. 179 | (is (= true (boolean "Not gonna get here..."))))))) 180 | 181 | 182 | ;;;; 183 | ;;;; Async tests 184 | ;;;; 185 | 186 | (deftest run-test-async--basic-flow 187 | (assert-captured-test-results 188 | (fn [results] 189 | (is (= (map #(select-keys % [:type :expected]) results) 190 | [{:type :begin-test-var} 191 | {:type :pass, :expected '(= "world" @hello-reaction)} 192 | {:type :fail, :expected '(= "nope" (:goodbye @db-reaction))} ; Failure noted above. 193 | {:type :pass, :expected '(= "world" @hello-reaction)} 194 | {:type :pass, :expected '(= {:hello "world", :goodbye "bugs"} @db-reaction)} 195 | ;; This "not timeout" is always reported on the JVM for passing 196 | ;; async tests. 197 | #?(:clj {:type :pass, :expected '(not= ::rf-test/timeout result)}) 198 | {:type :end-test-var}]))) 199 | 200 | (rf-test/run-test-async 201 | (rf/reg-event-db :initialise-db (fn [_ _] {:hello "world"})) 202 | (rf/reg-event-db :update-db (fn [db [_ arg]] (assoc db :goodbye arg))) 203 | (rf/reg-sub :hello-sub (fn [db _] (:hello db))) 204 | (rf/reg-sub :db-sub (fn [db _] db)) 205 | 206 | (let [hello-reaction (rf/subscribe [:hello-sub]) 207 | db-reaction (rf/subscribe [:db-sub])] 208 | (rf/dispatch [:initialise-db]) 209 | (rf-test/wait-for [:initialise-db] 210 | (is (= "world" @hello-reaction)) 211 | (is (= "nope" (:goodbye @db-reaction))) ; Not true, reports failure. 212 | 213 | (rf/dispatch [:update-db "bugs"]) 214 | (rf-test/wait-for [:update-db] 215 | (is (= "world" @hello-reaction)) 216 | (is (= {:hello "world", :goodbye "bugs"} @db-reaction)))))))) 217 | 218 | 219 | (deftest run-test-async--with-actual-asynchrony 220 | (assert-captured-test-results 221 | (fn [results] 222 | (is (= (map #(select-keys % [:type :expected]) results) 223 | [{:type :begin-test-var} 224 | {:type :pass, :expected '(= :continue event-id)} 225 | {:type :pass, :expected '(= "event-arg" event-arg)} 226 | {:type :pass, :expected '(= true @success)} 227 | #?(:clj {:type :pass, :expected '(not= ::rf-test/timeout result)}) 228 | {:type :end-test-var}]))) 229 | 230 | (rf-test/run-test-async 231 | (rf/reg-event-ctx :async 232 | (fn [ctx] 233 | #?(:cljs (js/setTimeout #(rf/dispatch [:continue "event-arg"]) 234 | 50) 235 | :clj (.start (Thread. #(do 236 | (Thread/sleep 50) 237 | (rf/dispatch [:continue "event-arg"]))))) 238 | ctx)) 239 | (rf/reg-event-db :continue (fn [db _] 240 | (assoc db :success true))) 241 | 242 | (rf/reg-sub :success (fn [db _] (:success db))) 243 | 244 | (let [success (rf/subscribe [:success])] 245 | (rf/dispatch [:async]) 246 | ;; Additionally tests the event binding form. 247 | (rf-test/wait-for [:continue nil [event-id event-arg]] 248 | (is (= :continue event-id)) 249 | (is (= "event-arg" event-arg)) 250 | (is (= true @success))))))) 251 | 252 | 253 | ;; JVM-only because `assert-captured-test-results` is complicated enough as it 254 | ;; is, without additionally handling async test timeouts in JS! 255 | #?(:clj 256 | (deftest run-test-async--test-times-out 257 | (assert-captured-test-results 258 | (fn [results] 259 | (is (= (map #(select-keys % [:type :expected]) results) 260 | [{:type :begin-test-var} 261 | {:type :pass, :expected '(= true (boolean "Did get here."))} 262 | #?(:clj {:type :fail, :expected '(not= ::rf-test/timeout result)}) 263 | {:type :end-test-var}]))) 264 | 265 | (binding [rf-test/*test-timeout* 100] 266 | (rf-test/run-test-async 267 | ;; As per previous test, but :async event doesn't actually 268 | ;; ever cause :continue to be dispatched, so we time out 269 | ;; waiting. 270 | (rf/reg-event-ctx :async (fn [ctx] ctx)) 271 | (rf/reg-event-db :continue (fn [db _] 272 | (assoc db :success true))) 273 | 274 | (rf/dispatch [:async]) 275 | (is (= true (boolean "Did get here."))) 276 | (rf-test/wait-for [:continue] 277 | (is (= true (boolean "Not gonna get here..."))))))))) 278 | 279 | 280 | (deftest run-test-async--test-dispatches-failure-event 281 | (assert-captured-test-results 282 | (fn [results] 283 | (is (= (map #(select-keys % [:type :expected]) results) 284 | [{:type :begin-test-var} 285 | {:type :pass, :expected '(not (fail-pred event))} ; The :async event. 286 | {:type :fail, :expected '(not (fail-pred event))} ; The :stop event. 287 | #?(:clj {:type :pass, :expected '(not= ::rf-test/timeout result)}) 288 | {:type :end-test-var}]))) 289 | 290 | (rf-test/run-test-async 291 | (rf/reg-event-ctx :async 292 | (fn [ctx] 293 | #?(:cljs (js/setTimeout #(rf/dispatch [:stop]) 50) 294 | :clj (.start (Thread. #(do 295 | (Thread/sleep 50) 296 | (rf/dispatch [:stop]))))) 297 | ctx)) 298 | 299 | (rf/dispatch [:async]) 300 | (rf-test/wait-for [:continue :stop] 301 | (is (= true "Not gonna get here...")))))) 302 | 303 | 304 | ;; JVM-only because in JS, the error will not be captured and will terminate the 305 | ;; test run. 306 | #?(:clj 307 | (deftest run-test-async--error-in-event-handler 308 | (assert-captured-test-results 309 | (fn [results] 310 | (is (= (map #(select-keys % [:type :expected]) results) 311 | ;; Note you don't actually get the error from the event handler here, 312 | ;; even though it threw an exception. It'll get printed to the console, 313 | ;; but since it happens in a different thread, from the test's point of 314 | ;; view, all you see is a timeout. This is one advantage of 315 | ;; synchronous tests. 316 | [{:type :begin-test-var} 317 | {:type :fail, :expected '(not= ::rf-test/timeout result)} 318 | {:type :end-test-var}]))) 319 | 320 | (binding [rf-test/*test-timeout* 100] 321 | (rf-test/run-test-async 322 | (rf/reg-event-ctx :async 323 | (fn [ctx] 324 | #?(:cljs (js/setTimeout #(rf/dispatch [:continue]) 50) 325 | :clj (.start (Thread. #(do 326 | (Thread/sleep 50) 327 | (rf/dispatch [:continue]))))) 328 | ctx)) 329 | (rf/reg-event-db :continue 330 | (fn [db _] 331 | (throw (ex-info (str "Whoops! (Not really, we threw this exception " 332 | "deliberately for testing purposes, and the fact that " 333 | "you're seeing it here on the console doesn't actually " 334 | "indicate a test failure.)") 335 | {:foo :bar})))) 336 | 337 | (rf/dispatch [:async]) 338 | (rf-test/wait-for [:continue nil event] 339 | (is (= [:continue] event)))))))) 340 | --------------------------------------------------------------------------------