├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ └── new_custom_action.md
├── dependabot.yml
└── workflows
│ ├── codeql.yml
│ └── tests.yml
├── .gitignore
├── .node-version
├── .npmignore
├── .prettierrc.json
├── LICENSE
├── README.md
├── assets
├── hero-dark.png
├── hero.png
├── icon.png
└── logo.png
├── package.json
├── playground
├── index.html
├── index.js
├── mock
│ └── routes.js
├── mock_example.html
├── package.json
├── turbo_frame_static_file.html
├── vite.config.js
└── yarn.lock
├── rollup.config.mjs
├── src
├── actions.ts
├── actions
│ ├── attributes.ts
│ ├── browser.ts
│ ├── debug.ts
│ ├── deprecated.ts
│ ├── document.ts
│ ├── document
│ │ └── cookie_string_builder.ts
│ ├── dom.ts
│ ├── events.ts
│ ├── form.ts
│ ├── history.ts
│ ├── notification.ts
│ ├── storage.ts
│ ├── turbo.ts
│ ├── turbo_frame.ts
│ └── turbo_progress_bar.ts
├── index.ts
├── proxy.ts
└── utils.ts
├── test
├── attributes
│ ├── add_css_class.test.js
│ ├── remove_attribute.test.js
│ ├── remove_css_class.test.js
│ ├── replace_css_class.test.js
│ ├── set_attribute.test.js
│ ├── set_dataset_attribute.test.js
│ ├── set_property.test.js
│ ├── set_value.test.js
│ ├── toggle_attribute.test.js
│ └── toggle_css_class.test.js
├── browser
│ ├── scroll_into_view.test.js
│ └── set_title.test.js
├── debug
│ └── console_log.test.js
├── deprecated
│ └── invoke.test.js
├── document
│ ├── set_cookie.test.js
│ └── set_cookie_item.test.js
├── events
│ └── dispatch_event.test.js
├── fixtures
│ ├── frame1.html
│ ├── frame2.html
│ ├── page1.html
│ ├── page2.html
│ └── page3.html
├── form
│ └── reset_form.test.js
├── history
│ ├── history_back.test.js
│ ├── history_forward.test.js
│ └── push_state.test.js
├── notifications
│ └── notification.test.js
├── storage
│ ├── clear_storage.test.js
│ ├── remove_storage_item.test.js
│ └── set_storage_item.test.js
├── test_helpers.js
├── turbo
│ ├── redirect_to.test.js
│ └── turbo_clear_cache.test.js
├── turbo_frame
│ ├── reload.test.js
│ └── set_src.test.js
├── turbo_progress_bar
│ ├── turbo_progress_bar_hide.test.js
│ ├── turbo_progress_bar_set_value.test.js
│ └── turbo_progress_bar_show.test.js
└── utils
│ ├── tokenize.test.js
│ └── typecast.test.js
├── tsconfig.json
├── types
└── .keep
├── web-test-runner.config.mjs
└── yarn.lock
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/**/*.*
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "plugins": [
5 | "@typescript-eslint",
6 | "prettier"
7 | ],
8 | "extends": [
9 | "eslint:recommended",
10 | "plugin:@typescript-eslint/eslint-recommended",
11 | "plugin:@typescript-eslint/recommended",
12 | "prettier"
13 | ],
14 | "rules": {
15 | "prettier/prettier": ["error"],
16 | "@typescript-eslint/no-explicit-any": "off",
17 | "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
18 | },
19 | "overrides": [
20 | {
21 | "files": [ "test/**/*.test.js" ],
22 | "globals": {
23 | "describe": true,
24 | "context": true,
25 | "it": true,
26 | "beforeEach": true,
27 | "afterEach": true,
28 | "Turbo": true,
29 | "TurboPowerLocation": true
30 | }
31 | }
32 | ],
33 | "env": {
34 | "browser": true,
35 | "node": true
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | playground/**/* linguist-vendored
2 | test/**/*.js linguist-vendored
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/new_custom_action.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: New Custom Action
3 | about: Suggest a custom action which we should consider to implement
4 | title: 'Implement [action name] action'
5 | labels: new custom action
6 | ---
7 |
8 | **Action signature:**
9 | ```ruby
10 | turbo_stream.[action_name](targets, [arguments], **attributes)
11 | ```
12 |
13 | **Example Turbo Stream Element:**
14 | ```html
15 |
16 | ```
17 |
18 | **Action content:**
19 | ```js
20 | target.[action_name]([arguments])
21 | ```
22 |
23 | **Reference:**
24 | * [add links to implementation details/docs/additional details]
25 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "npm"
9 | directory: "/"
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 | schedule:
9 | - cron: "49 0 * * 6"
10 |
11 | jobs:
12 | analyze:
13 | name: Analyze
14 | runs-on: ubuntu-latest
15 | permissions:
16 | actions: read
17 | contents: read
18 | security-events: write
19 |
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | language: [ javascript ]
24 |
25 | steps:
26 | - name: Checkout
27 | uses: actions/checkout@v3
28 |
29 | - name: Initialize CodeQL
30 | uses: github/codeql-action/init@v2
31 | with:
32 | languages: ${{ matrix.language }}
33 | queries: +security-and-quality
34 |
35 | - name: Autobuild
36 | uses: github/codeql-action/autobuild@v2
37 |
38 | - name: Perform CodeQL Analysis
39 | uses: github/codeql-action/analyze@v2
40 | with:
41 | category: "/language:${{ matrix.language }}"
42 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - '*'
7 |
8 | push:
9 | branches:
10 | - main
11 |
12 | jobs:
13 | test:
14 | name: JavaScript Test Action
15 | runs-on: ubuntu-latest
16 | strategy:
17 | matrix:
18 | node: [20, 22, 23, 24]
19 |
20 | steps:
21 | - uses: actions/checkout@master
22 |
23 | - name: Setup Node v${{ matrix.node }}
24 | uses: actions/setup-node@v3
25 | with:
26 | node-version: ${{ matrix.node }}
27 | cache: 'yarn'
28 |
29 | - name: Yarn install
30 | run: yarn install --frozen-lockfile
31 |
32 | - name: Run JavaScript Tests
33 | run: yarn build
34 |
35 | - name: Run JavaScript Tests
36 | run: yarn test
37 |
38 | - name: Lint
39 | run: yarn lint
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.tgz
2 | *.tsbuildinfo
3 | *.log
4 | *~
5 |
6 | /dist
7 | node_modules/
8 | coverage/
9 |
10 | yarn-error.log
11 |
12 | .DS_Store
13 | .rollup.cache
14 | .tool-versions
15 |
--------------------------------------------------------------------------------
/.node-version:
--------------------------------------------------------------------------------
1 | 20.9.0
2 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .babelrc
2 | .babelrc.js
3 | .DS_Store
4 | .gitignore
5 | .yarn.lock
6 |
7 | *.log
8 | *.tsbuildinfo
9 | *.tgz
10 |
11 | README.md
12 | rollup.config.js
13 | web-test-runner.config.mjs
14 | tsconfig.json
15 | webpack.config.js
16 | yarn-error.log
17 | *~
18 |
19 | /.git
20 | /.github
21 | /.gitattributes
22 |
23 | /node_modules
24 | /playground
25 | /src
26 | /test
27 | /coverage
28 | /assets
29 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": false,
3 | "printWidth": 120,
4 | "semi": false
5 | }
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Marco Roth
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | TurboPower
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | ## Getting Started
27 |
28 | `turbo_power` is a power-pack for Turbo Streams. It provides Turbo Streams with a bunch of new actions and additionally adds the `morph` action from [`turbo-morph`](https://github.com/marcoroth/turbo-morph).
29 |
30 | **Note:** Requires Turbo **7.2+**
31 |
32 | ## Getting Started
33 |
34 | ```bash
35 | yarn add turbo_power
36 | ```
37 |
38 | ```diff
39 | // application.js
40 | import * as Turbo from '@hotwired/turbo'
41 |
42 | +import TurboPower from 'turbo_power'
43 | +TurboPower.initialize(Turbo.StreamActions)
44 | ```
45 |
46 | ## Getting Started with Rails
47 |
48 | Checkout the instructions in the [`turbo_power-rails`](https://github.com/marcoroth/turbo_power-rails) repo.
49 |
50 | ## Getting Started with Django
51 |
52 | Checkout the doc of the [`django-turbo-helper`](https://github.com/rails-inspire-django/django-turbo-helper) repo.
53 |
54 | ## Custom Actions
55 |
56 | ### DOM Actions
57 |
58 | * `turbo_stream.graft(target, parent, **attributes)`
59 | * [`turbo_stream.morph(target, html = nil, **attributes, &block)`](https://github.com/marcoroth/turbo-morph)
60 | * `turbo_stream.inner_html(target, html = nil, **attributes, &block)`
61 | * `turbo_stream.insert_adjacent_html(target, html = nil, position: 'beforeend', **attributes, &block)`
62 | * `turbo_stream.insert_adjacent_text(target, text, position: 'beforebegin', **attributes)`
63 | * `turbo_stream.outer_html(target, html = nil, **attributes, &block)`
64 | * `turbo_stream.text_content(target, text, **attributes)`
65 | * `turbo_stream.set_meta(name, content)`
66 |
67 |
68 | ### Attribute Actions
69 |
70 | * `turbo_stream.add_css_class(target, classes, **attributes)`
71 | * `turbo_stream.remove_attribute(target, attribute, **attributes)`
72 | * `turbo_stream.remove_css_class(target, classes, **attributes)`
73 | * `turbo_stream.set_attribute(target, attribute, value, **attributes)`
74 | * `turbo_stream.set_dataset_attribute(target, attribute, value, **attributes)`
75 | * `turbo_stream.set_property(target, property, value, **attributes)`
76 | * `turbo_stream.set_style(target, name, value, **attributes)`
77 | * `turbo_stream.set_styles(target, styles, **attributes)`
78 | * `turbo_stream.set_value(target, value, **attributes)`
79 | * `turbo_stream.toggle_attribute(target, attribute, force, **attributes)`
80 | * `turbo_stream.toggle_css_class(target, classes, **attributes)`
81 | * `turbo_stream.replace_css_class(target, from, to, **attributes)`
82 |
83 |
84 | ### Event Actions
85 |
86 | * `turbo_stream.dispatch_event(target, name, detail: {}, **attributes)`
87 |
88 |
89 | ### Form Actions
90 |
91 | * `turbo_stream.reset_form(target, **attributes)`
92 |
93 |
94 | ### Storage Actions
95 |
96 | * `turbo_stream.clear_storage(type, **attributes)`
97 | * `turbo_stream.clear_local_storage(**attributes)`
98 | * `turbo_stream.clear_session_storage(**attributes)`
99 | * `turbo_stream.remove_storage_item(key, type, **attributes)`
100 | * `turbo_stream.remove_local_storage_item(key, **attributes)`
101 | * `turbo_stream.remove_session_storage_item(key, **attributes)`
102 | * `turbo_stream.set_storage_item(key, value, type, **attributes)`
103 | * `turbo_stream.set_local_storage_item(key, value, **attributes)`
104 | * `turbo_stream.set_session_storage_item(key, value, **attributes)`
105 |
106 |
107 | ### Browser Actions
108 |
109 | * `turbo_stream.reload(**attributes)`
110 | * `turbo_stream.scroll_into_view(**attributes)`
111 | * `turbo_stream.scroll_into_view(targets)`
112 | * `turbo_stream.scroll_into_view(targets, align_to_top)`
113 | * `turbo_stream.scroll_into_view(targets, behavior:, block:, inline:)`
114 | * `turbo_stream.set_focus(target, **attributes)`
115 | * `turbo_stream.set_title(title, **attributes)`
116 |
117 |
118 | ### Document Actions
119 |
120 | * `turbo_stream.set_cookie(cookie, **attributes)`
121 | * `turbo_stream.set_cookie_item(key, value, **attributes)`
122 |
123 |
124 | ### Browser History Actions
125 |
126 | * `turbo_stream.history_back(**attributes)`
127 | * `turbo_stream.history_forward(**attributes)`
128 | * `turbo_stream.history_go(delta, **attributes)`
129 | * `turbo_stream.push_state(url, title = nil, state = nil, **attributes)`
130 | * `turbo_stream.replace_state(url, title = nil, state = nil, **attributes)`
131 |
132 |
133 | ### Debug Actions
134 |
135 | * `turbo_stream.console_log(message, level = :log)`
136 | * `turbo_stream.console_table(data, columns)`
137 |
138 |
139 | ### Notification Actions
140 |
141 | * `turbo_stream.notification(title, **options)`
142 |
143 |
144 | ### Turbo Actions
145 |
146 | * `turbo_stream.redirect_to(url, turbo_action = nil, turbo_frame = nil, **attributes)`
147 | * `turbo_stream.turbo_clear_cache()`
148 |
149 |
150 | ### Turbo Progress Bar Actions
151 |
152 | * `turbo_stream.turbo_progress_bar_show()`
153 | * `turbo_stream.turbo_progress_bar_hide()`
154 | * `turbo_stream.turbo_progress_bar_set_value(value)`
155 |
156 |
157 | ### Turbo Frame Actions
158 |
159 | * `turbo_stream.turbo_frame_reload(frame_id)`
160 | * `turbo_stream.turbo_frame_set_src(frame_id, src)`
161 |
162 |
163 |
164 | ## Previous Art
165 |
166 | TurboPower is heavily inspired by [CableReady](https://github.com/stimulusreflex/cable_ready) and its operations. This library aims to bring the same level of operation-diversity to Turbo Streams.
167 |
168 | ## Development
169 |
170 | To run the test runner:
171 |
172 | ```plain
173 | yarn build
174 | yarn test
175 | ```
176 |
177 | ## Acknowledgments
178 |
179 | `turbo_power` is [MIT-licensed](LICENSE) open-source software from [Marco Roth](https://github.com/marcoroth).
180 |
181 | `turbo-morph` is [MIT-licensed](https://github.com/marcoroth/turbo-morph/blob/master/LICENSE) open-source software from [Marco Roth](https://github.com/marcoroth).
182 |
183 | Turbo is [MIT-licensed](https://github.com/hotwired/turbo/blob/main/MIT-LICENSE) open-source software from [Basecamp](https://basecamp.com/).
184 |
--------------------------------------------------------------------------------
/assets/hero-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcoroth/turbo_power/4219232851e19de3a4aa0d63f76c496fdfc141af/assets/hero-dark.png
--------------------------------------------------------------------------------
/assets/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcoroth/turbo_power/4219232851e19de3a4aa0d63f76c496fdfc141af/assets/hero.png
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcoroth/turbo_power/4219232851e19de3a4aa0d63f76c496fdfc141af/assets/icon.png
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcoroth/turbo_power/4219232851e19de3a4aa0d63f76c496fdfc141af/assets/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "turbo_power",
3 | "version": "0.7.1",
4 | "description": "Power-pack for Turbo",
5 | "main": "dist/turbo_power.js",
6 | "module": "dist/turbo_power.js",
7 | "unpkg": "dist/turbo_power.umd.js",
8 | "types": "dist/types/index.d.ts",
9 | "author": "Marco Roth",
10 | "license": "MIT",
11 | "repository": "https://github.com/marcoroth/turbo_power",
12 | "sideEffects": false,
13 | "scripts": {
14 | "start": "cd playground && yarn start",
15 | "prebuild": "yarn clean",
16 | "build": "tsc --noEmit false --declaration true --emitDeclarationOnly true --outDir dist/types && rollup -c",
17 | "watch": "rollup -wc",
18 | "dev": "concurrently 'yarn run watch' 'yarn run start'",
19 | "clean": "rimraf dist",
20 | "prerelease": "yarn build",
21 | "test": "web-test-runner test/**/*.test.js",
22 | "lint": "eslint .",
23 | "format": "yarn lint --fix"
24 | },
25 | "devDependencies": {
26 | "@hotwired/turbo": "^7.2.5",
27 | "@open-wc/testing": "^4.0.0",
28 | "@rollup/plugin-node-resolve": "^16.0.1",
29 | "@rollup/plugin-terser": "^0.4.0",
30 | "@rollup/plugin-typescript": "^12.1.0",
31 | "@typescript-eslint/eslint-plugin": "^7.0.0",
32 | "@typescript-eslint/parser": "^6.18.0",
33 | "@web/test-runner": "^0.20.0",
34 | "concurrently": "^9.0.0",
35 | "eslint": "^8.56.0",
36 | "eslint-config-prettier": "^10.0.1",
37 | "eslint-plugin-prettier": "^5.1.2",
38 | "prettier": "^3.1.1",
39 | "rimraf": "^5.0.1",
40 | "rollup": "^4.1.5",
41 | "rollup-plugin-filesize": "^10.0.0",
42 | "sinon": "^20.0.0",
43 | "sourcemap": "^0.1.0",
44 | "tslib": "^2.6.2",
45 | "turbo-morph": "^0.2.0",
46 | "typescript": "^5.3.3"
47 | },
48 | "peerDependencies": {
49 | "@hotwired/turbo": ">= 7.2"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/playground/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | TurboPower Playground
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/playground/index.js:
--------------------------------------------------------------------------------
1 | import * as Turbo from "@hotwired/turbo"
2 | import * as TurboPower from "turbo_power"
3 |
4 | TurboPower.initialize(Turbo.StreamActions)
5 |
--------------------------------------------------------------------------------
/playground/mock/routes.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | url: /\/turbo-frame-[\d]/,
4 | method: "get",
5 | rawResponse: async (req, res) => {
6 | // let _reqbody = ""
7 | await new Promise((resolve) => {
8 | req.on("data", (_chunk) => {
9 | // _reqbody += _chunk
10 | })
11 | req.on("end", () => resolve(undefined))
12 | })
13 | res.setHeader("Content-Type", "text/html")
14 | res.statusCode = 200
15 | res.end(`Dynamic Turbo Frame ${res.req.url}`)
16 | },
17 | },
18 | {
19 | url: /\/turbo-stream-[\w+]/,
20 | method: "get",
21 | rawResponse: async (req, res) => {
22 | // let _reqbody = ""
23 | await new Promise((resolve) => {
24 | req.on("data", (_chunk) => {
25 | // _reqbody += _chunk
26 | })
27 | req.on("end", () => resolve(undefined))
28 | })
29 | const kindValue = res.req.url.split("turbo-stream-")[1]
30 | res.setHeader("Content-Type", "text/vnd.turbo-stream.html; charset=utf-8")
31 | res.statusCode = 200
32 | const htmlResponse = (kind) =>
33 | ({
34 | frame_src:
35 | '',
36 | frame_reload: '',
37 | inner_html:
38 | 'INNER HTML action
',
39 | })[kind]
40 | res.end(htmlResponse(kindValue))
41 | },
42 | },
43 | ]
44 |
--------------------------------------------------------------------------------
/playground/mock_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | TurboPower Playground
5 |
6 |
7 |
8 |
9 |
23 |
24 |
25 | Main page
26 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/playground/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "turbo_power-playground",
3 | "private": true,
4 | "version": "0.1.0",
5 | "description": "A playground for TurboPower",
6 | "license": "MIT",
7 | "repository": "https://github.com/marcoroth/turbo_power",
8 | "dependencies": {
9 | "@hotwired/turbo": "^7.2.4",
10 | "turbo_power": "link:../"
11 | },
12 | "scripts": {
13 | "start": "yarn run dev",
14 | "dev": "vite",
15 | "build": "vite build",
16 | "preview": "vite preview"
17 | },
18 | "devDependencies": {
19 | "mockjs": "^1.1.0",
20 | "vite": "^4.5.14",
21 | "vite-plugin-mock": "^2.9.6"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/playground/turbo_frame_static_file.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Static Frame
4 |
7 |
8 |
--------------------------------------------------------------------------------
/playground/vite.config.js:
--------------------------------------------------------------------------------
1 | import { viteMockServe } from "vite-plugin-mock"
2 | import path from "path"
3 |
4 | export default {
5 | resolve: {
6 | alias: {
7 | turbo_power: path.resolve(__dirname, "../dist/index"),
8 | },
9 | },
10 | plugins: [
11 | viteMockServe({
12 | mockPath: "mock",
13 | localEnabled: true,
14 | }),
15 | ],
16 | }
17 |
--------------------------------------------------------------------------------
/playground/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@esbuild/android-arm64@0.18.20":
6 | version "0.18.20"
7 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622"
8 | integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==
9 |
10 | "@esbuild/android-arm@0.18.20":
11 | version "0.18.20"
12 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682"
13 | integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==
14 |
15 | "@esbuild/android-x64@0.18.20":
16 | version "0.18.20"
17 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2"
18 | integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==
19 |
20 | "@esbuild/darwin-arm64@0.18.20":
21 | version "0.18.20"
22 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1"
23 | integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==
24 |
25 | "@esbuild/darwin-x64@0.18.20":
26 | version "0.18.20"
27 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d"
28 | integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==
29 |
30 | "@esbuild/freebsd-arm64@0.18.20":
31 | version "0.18.20"
32 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54"
33 | integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==
34 |
35 | "@esbuild/freebsd-x64@0.18.20":
36 | version "0.18.20"
37 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e"
38 | integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==
39 |
40 | "@esbuild/linux-arm64@0.18.20":
41 | version "0.18.20"
42 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0"
43 | integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==
44 |
45 | "@esbuild/linux-arm@0.18.20":
46 | version "0.18.20"
47 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0"
48 | integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==
49 |
50 | "@esbuild/linux-ia32@0.18.20":
51 | version "0.18.20"
52 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7"
53 | integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==
54 |
55 | "@esbuild/linux-loong64@0.18.20":
56 | version "0.18.20"
57 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d"
58 | integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==
59 |
60 | "@esbuild/linux-mips64el@0.18.20":
61 | version "0.18.20"
62 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231"
63 | integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==
64 |
65 | "@esbuild/linux-ppc64@0.18.20":
66 | version "0.18.20"
67 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb"
68 | integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==
69 |
70 | "@esbuild/linux-riscv64@0.18.20":
71 | version "0.18.20"
72 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6"
73 | integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==
74 |
75 | "@esbuild/linux-s390x@0.18.20":
76 | version "0.18.20"
77 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071"
78 | integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==
79 |
80 | "@esbuild/linux-x64@0.18.20":
81 | version "0.18.20"
82 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338"
83 | integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==
84 |
85 | "@esbuild/netbsd-x64@0.18.20":
86 | version "0.18.20"
87 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1"
88 | integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==
89 |
90 | "@esbuild/openbsd-x64@0.18.20":
91 | version "0.18.20"
92 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae"
93 | integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==
94 |
95 | "@esbuild/sunos-x64@0.18.20":
96 | version "0.18.20"
97 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d"
98 | integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==
99 |
100 | "@esbuild/win32-arm64@0.18.20":
101 | version "0.18.20"
102 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9"
103 | integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==
104 |
105 | "@esbuild/win32-ia32@0.18.20":
106 | version "0.18.20"
107 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102"
108 | integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==
109 |
110 | "@esbuild/win32-x64@0.18.20":
111 | version "0.18.20"
112 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d"
113 | integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==
114 |
115 | "@hotwired/turbo@^7.2.4":
116 | version "7.2.4"
117 | resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-7.2.4.tgz#0d35541be32cfae3b4f78c6ab9138f5b21f28a21"
118 | integrity sha512-c3xlOroHp/cCZHDOuLp6uzQYEbvXBUVaal0puXoGJ9M8L/KHwZ3hQozD4dVeSN9msHWLxxtmPT1TlCN7gFhj4w==
119 |
120 | "@nodelib/fs.scandir@2.1.5":
121 | version "2.1.5"
122 | resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
123 | integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
124 | dependencies:
125 | "@nodelib/fs.stat" "2.0.5"
126 | run-parallel "^1.1.9"
127 |
128 | "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
129 | version "2.0.5"
130 | resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
131 | integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
132 |
133 | "@nodelib/fs.walk@^1.2.3":
134 | version "1.2.8"
135 | resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
136 | integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
137 | dependencies:
138 | "@nodelib/fs.scandir" "2.1.5"
139 | fastq "^1.6.0"
140 |
141 | "@rollup/plugin-node-resolve@^13.0.4":
142 | version "13.3.0"
143 | resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz#da1c5c5ce8316cef96a2f823d111c1e4e498801c"
144 | integrity sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==
145 | dependencies:
146 | "@rollup/pluginutils" "^3.1.0"
147 | "@types/resolve" "1.17.1"
148 | deepmerge "^4.2.2"
149 | is-builtin-module "^3.1.0"
150 | is-module "^1.0.0"
151 | resolve "^1.19.0"
152 |
153 | "@rollup/pluginutils@^3.1.0":
154 | version "3.1.0"
155 | resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
156 | integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
157 | dependencies:
158 | "@types/estree" "0.0.39"
159 | estree-walker "^1.0.1"
160 | picomatch "^2.2.2"
161 |
162 | "@types/estree@0.0.39":
163 | version "0.0.39"
164 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
165 | integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
166 |
167 | "@types/mockjs@^1.0.4":
168 | version "1.0.7"
169 | resolved "https://registry.yarnpkg.com/@types/mockjs/-/mockjs-1.0.7.tgz#3a0f1bc3f286ae2891d9592422529268665c88d3"
170 | integrity sha512-OCxXz6hEaJOVpRwuJMiVY5a6LtJcih+br9gwB/Q8ooOBikvk5FpBQ31OlNimXo3EqKha1Z7PFBni+q9m+8NCWg==
171 |
172 | "@types/node@*":
173 | version "18.11.3"
174 | resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.3.tgz#78a6d7ec962b596fc2d2ec102c4dd3ef073fea6a"
175 | integrity sha512-fNjDQzzOsZeKZu5NATgXUPsaFaTxeRgFXoosrHivTl8RGeV733OLawXsGfEk9a8/tySyZUyiZ6E8LcjPFZ2y1A==
176 |
177 | "@types/resolve@1.17.1":
178 | version "1.17.1"
179 | resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
180 | integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==
181 | dependencies:
182 | "@types/node" "*"
183 |
184 | ansi-styles@^4.1.0:
185 | version "4.3.0"
186 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
187 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
188 | dependencies:
189 | color-convert "^2.0.1"
190 |
191 | anymatch@~3.1.2:
192 | version "3.1.2"
193 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
194 | integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
195 | dependencies:
196 | normalize-path "^3.0.0"
197 | picomatch "^2.0.4"
198 |
199 | binary-extensions@^2.0.0:
200 | version "2.2.0"
201 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
202 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
203 |
204 | braces@^3.0.3, braces@~3.0.2:
205 | version "3.0.3"
206 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
207 | integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
208 | dependencies:
209 | fill-range "^7.1.1"
210 |
211 | builtin-modules@^3.3.0:
212 | version "3.3.0"
213 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
214 | integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==
215 |
216 | chalk@^4.1.2:
217 | version "4.1.2"
218 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
219 | integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
220 | dependencies:
221 | ansi-styles "^4.1.0"
222 | supports-color "^7.1.0"
223 |
224 | chokidar@^3.5.2:
225 | version "3.5.3"
226 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
227 | integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
228 | dependencies:
229 | anymatch "~3.1.2"
230 | braces "~3.0.2"
231 | glob-parent "~5.1.2"
232 | is-binary-path "~2.1.0"
233 | is-glob "~4.0.1"
234 | normalize-path "~3.0.0"
235 | readdirp "~3.6.0"
236 | optionalDependencies:
237 | fsevents "~2.3.2"
238 |
239 | color-convert@^2.0.1:
240 | version "2.0.1"
241 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
242 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
243 | dependencies:
244 | color-name "~1.1.4"
245 |
246 | color-name@~1.1.4:
247 | version "1.1.4"
248 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
249 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
250 |
251 | commander@*:
252 | version "9.4.1"
253 | resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd"
254 | integrity sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==
255 |
256 | connect@^3.7.0:
257 | version "3.7.0"
258 | resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8"
259 | integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==
260 | dependencies:
261 | debug "2.6.9"
262 | finalhandler "1.1.2"
263 | parseurl "~1.3.3"
264 | utils-merge "1.0.1"
265 |
266 | debug@2.6.9:
267 | version "2.6.9"
268 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
269 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
270 | dependencies:
271 | ms "2.0.0"
272 |
273 | debug@^4.3.2:
274 | version "4.3.4"
275 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
276 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
277 | dependencies:
278 | ms "2.1.2"
279 |
280 | deepmerge@^4.2.2:
281 | version "4.2.2"
282 | resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
283 | integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
284 |
285 | ee-first@1.1.1:
286 | version "1.1.1"
287 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
288 | integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
289 |
290 | encodeurl@~1.0.2:
291 | version "1.0.2"
292 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
293 | integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
294 |
295 | esbuild@0.11.3:
296 | version "0.11.3"
297 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.11.3.tgz#b57165b907be4ffba651f6450538ce8d8c1d5eb0"
298 | integrity sha512-BzVRHcCtFepjS9WcqRjqoIxLqgpK21a8J4Zi4msSGxDxiXVO1IbcqT1KjhdDDnJxKfe7bvzZrvMEX+bVO0Elcw==
299 |
300 | esbuild@^0.18.10:
301 | version "0.18.20"
302 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6"
303 | integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==
304 | optionalDependencies:
305 | "@esbuild/android-arm" "0.18.20"
306 | "@esbuild/android-arm64" "0.18.20"
307 | "@esbuild/android-x64" "0.18.20"
308 | "@esbuild/darwin-arm64" "0.18.20"
309 | "@esbuild/darwin-x64" "0.18.20"
310 | "@esbuild/freebsd-arm64" "0.18.20"
311 | "@esbuild/freebsd-x64" "0.18.20"
312 | "@esbuild/linux-arm" "0.18.20"
313 | "@esbuild/linux-arm64" "0.18.20"
314 | "@esbuild/linux-ia32" "0.18.20"
315 | "@esbuild/linux-loong64" "0.18.20"
316 | "@esbuild/linux-mips64el" "0.18.20"
317 | "@esbuild/linux-ppc64" "0.18.20"
318 | "@esbuild/linux-riscv64" "0.18.20"
319 | "@esbuild/linux-s390x" "0.18.20"
320 | "@esbuild/linux-x64" "0.18.20"
321 | "@esbuild/netbsd-x64" "0.18.20"
322 | "@esbuild/openbsd-x64" "0.18.20"
323 | "@esbuild/sunos-x64" "0.18.20"
324 | "@esbuild/win32-arm64" "0.18.20"
325 | "@esbuild/win32-ia32" "0.18.20"
326 | "@esbuild/win32-x64" "0.18.20"
327 |
328 | escape-html@~1.0.3:
329 | version "1.0.3"
330 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
331 | integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
332 |
333 | estree-walker@^1.0.1:
334 | version "1.0.1"
335 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
336 | integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
337 |
338 | fast-glob@^3.2.7:
339 | version "3.2.12"
340 | resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
341 | integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
342 | dependencies:
343 | "@nodelib/fs.stat" "^2.0.2"
344 | "@nodelib/fs.walk" "^1.2.3"
345 | glob-parent "^5.1.2"
346 | merge2 "^1.3.0"
347 | micromatch "^4.0.4"
348 |
349 | fastq@^1.6.0:
350 | version "1.13.0"
351 | resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
352 | integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==
353 | dependencies:
354 | reusify "^1.0.4"
355 |
356 | fill-range@^7.1.1:
357 | version "7.1.1"
358 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
359 | integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
360 | dependencies:
361 | to-regex-range "^5.0.1"
362 |
363 | finalhandler@1.1.2:
364 | version "1.1.2"
365 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
366 | integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
367 | dependencies:
368 | debug "2.6.9"
369 | encodeurl "~1.0.2"
370 | escape-html "~1.0.3"
371 | on-finished "~2.3.0"
372 | parseurl "~1.3.3"
373 | statuses "~1.5.0"
374 | unpipe "~1.0.0"
375 |
376 | fsevents@~2.3.2:
377 | version "2.3.2"
378 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
379 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
380 |
381 | function-bind@^1.1.1:
382 | version "1.1.1"
383 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
384 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
385 |
386 | glob-parent@^5.1.2, glob-parent@~5.1.2:
387 | version "5.1.2"
388 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
389 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
390 | dependencies:
391 | is-glob "^4.0.1"
392 |
393 | has-flag@^4.0.0:
394 | version "4.0.0"
395 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
396 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
397 |
398 | has@^1.0.3:
399 | version "1.0.3"
400 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
401 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
402 | dependencies:
403 | function-bind "^1.1.1"
404 |
405 | is-binary-path@~2.1.0:
406 | version "2.1.0"
407 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
408 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
409 | dependencies:
410 | binary-extensions "^2.0.0"
411 |
412 | is-builtin-module@^3.1.0:
413 | version "3.2.0"
414 | resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.0.tgz#bb0310dfe881f144ca83f30100ceb10cf58835e0"
415 | integrity sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==
416 | dependencies:
417 | builtin-modules "^3.3.0"
418 |
419 | is-core-module@^2.9.0:
420 | version "2.9.0"
421 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
422 | integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
423 | dependencies:
424 | has "^1.0.3"
425 |
426 | is-extglob@^2.1.1:
427 | version "2.1.1"
428 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
429 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
430 |
431 | is-glob@^4.0.1, is-glob@~4.0.1:
432 | version "4.0.3"
433 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
434 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
435 | dependencies:
436 | is-extglob "^2.1.1"
437 |
438 | is-module@^1.0.0:
439 | version "1.0.0"
440 | resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
441 | integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==
442 |
443 | is-number@^7.0.0:
444 | version "7.0.0"
445 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
446 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
447 |
448 | merge2@^1.3.0:
449 | version "1.4.1"
450 | resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
451 | integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
452 |
453 | micromatch@^4.0.4:
454 | version "4.0.8"
455 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
456 | integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
457 | dependencies:
458 | braces "^3.0.3"
459 | picomatch "^2.3.1"
460 |
461 | mockjs@^1.1.0:
462 | version "1.1.0"
463 | resolved "https://registry.yarnpkg.com/mockjs/-/mockjs-1.1.0.tgz#e6a0c378e91906dbaff20911cc0273b3c7d75b06"
464 | integrity sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==
465 | dependencies:
466 | commander "*"
467 |
468 | ms@2.0.0:
469 | version "2.0.0"
470 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
471 | integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
472 |
473 | ms@2.1.2:
474 | version "2.1.2"
475 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
476 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
477 |
478 | nanoid@^3.3.8:
479 | version "3.3.8"
480 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
481 | integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
482 |
483 | normalize-path@^3.0.0, normalize-path@~3.0.0:
484 | version "3.0.0"
485 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
486 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
487 |
488 | on-finished@~2.3.0:
489 | version "2.3.0"
490 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
491 | integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==
492 | dependencies:
493 | ee-first "1.1.1"
494 |
495 | parseurl@~1.3.3:
496 | version "1.3.3"
497 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
498 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
499 |
500 | path-parse@^1.0.7:
501 | version "1.0.7"
502 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
503 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
504 |
505 | path-to-regexp@^6.2.0:
506 | version "6.3.0"
507 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4"
508 | integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==
509 |
510 | picocolors@^1.1.1:
511 | version "1.1.1"
512 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
513 | integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
514 |
515 | picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
516 | version "2.3.1"
517 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
518 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
519 |
520 | postcss@^8.4.27:
521 | version "8.5.1"
522 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.1.tgz#e2272a1f8a807fafa413218245630b5db10a3214"
523 | integrity sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==
524 | dependencies:
525 | nanoid "^3.3.8"
526 | picocolors "^1.1.1"
527 | source-map-js "^1.2.1"
528 |
529 | queue-microtask@^1.2.2:
530 | version "1.2.3"
531 | resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
532 | integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
533 |
534 | readdirp@~3.6.0:
535 | version "3.6.0"
536 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
537 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
538 | dependencies:
539 | picomatch "^2.2.1"
540 |
541 | resolve@^1.19.0:
542 | version "1.22.1"
543 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
544 | integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
545 | dependencies:
546 | is-core-module "^2.9.0"
547 | path-parse "^1.0.7"
548 | supports-preserve-symlinks-flag "^1.0.0"
549 |
550 | reusify@^1.0.4:
551 | version "1.0.4"
552 | resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
553 | integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
554 |
555 | rollup@^3.27.1:
556 | version "3.29.5"
557 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.5.tgz#8a2e477a758b520fb78daf04bca4c522c1da8a54"
558 | integrity sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==
559 | optionalDependencies:
560 | fsevents "~2.3.2"
561 |
562 | run-parallel@^1.1.9:
563 | version "1.2.0"
564 | resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
565 | integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
566 | dependencies:
567 | queue-microtask "^1.2.2"
568 |
569 | source-map-js@^1.2.1:
570 | version "1.2.1"
571 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
572 | integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
573 |
574 | statuses@~1.5.0:
575 | version "1.5.0"
576 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
577 | integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
578 |
579 | supports-color@^7.1.0:
580 | version "7.2.0"
581 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
582 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
583 | dependencies:
584 | has-flag "^4.0.0"
585 |
586 | supports-preserve-symlinks-flag@^1.0.0:
587 | version "1.0.0"
588 | resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
589 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
590 |
591 | to-regex-range@^5.0.1:
592 | version "5.0.1"
593 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
594 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
595 | dependencies:
596 | is-number "^7.0.0"
597 |
598 | "turbo_power@link:..":
599 | version "0.7.1"
600 |
601 | unpipe@~1.0.0:
602 | version "1.0.0"
603 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
604 | integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
605 |
606 | utils-merge@1.0.1:
607 | version "1.0.1"
608 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
609 | integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
610 |
611 | vite-plugin-mock@^2.9.6:
612 | version "2.9.6"
613 | resolved "https://registry.yarnpkg.com/vite-plugin-mock/-/vite-plugin-mock-2.9.6.tgz#04dd23de6baa052faa5b9ad317514c90d6205e25"
614 | integrity sha512-/Rm59oPppe/ncbkSrUuAxIQihlI2YcBmnbR4ST1RA2VzM1C0tEQc1KlbQvnUGhXECAGTaQN2JyasiwXP6EtKgg==
615 | dependencies:
616 | "@rollup/plugin-node-resolve" "^13.0.4"
617 | "@types/mockjs" "^1.0.4"
618 | chalk "^4.1.2"
619 | chokidar "^3.5.2"
620 | connect "^3.7.0"
621 | debug "^4.3.2"
622 | esbuild "0.11.3"
623 | fast-glob "^3.2.7"
624 | path-to-regexp "^6.2.0"
625 |
626 | vite@^4.5.14:
627 | version "4.5.14"
628 | resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.14.tgz#2e652bc1d898265d987d6543ce866ecd65fa4086"
629 | integrity sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==
630 | dependencies:
631 | esbuild "^0.18.10"
632 | postcss "^8.4.27"
633 | rollup "^3.27.1"
634 | optionalDependencies:
635 | fsevents "~2.3.2"
636 |
--------------------------------------------------------------------------------
/rollup.config.mjs:
--------------------------------------------------------------------------------
1 | import resolve from "@rollup/plugin-node-resolve"
2 | import typescript from "@rollup/plugin-typescript"
3 | import terser from "@rollup/plugin-terser"
4 | import filesize from "rollup-plugin-filesize"
5 | import { readFileSync } from "fs"
6 |
7 | const json = JSON.parse(readFileSync("./package.json"))
8 | const banner = `/*\n * TurboPower ${json.version}\n */`
9 |
10 | const pretty = () => {
11 | return terser({
12 | mangle: false,
13 | compress: false,
14 | format: {
15 | comments: "all",
16 | beautify: true,
17 | indent_level: 2
18 | }
19 | })
20 | }
21 |
22 | export default [
23 | {
24 | input: "src/index.ts",
25 | external: ["@hotwired/turbo"],
26 | output: [
27 | {
28 | name: "TurboPower",
29 | file: "dist/turbo_power.umd.js",
30 | format: "umd",
31 | banner,
32 | globals: {
33 | "@hotwired/turbo": "Turbo",
34 | },
35 | },
36 | {
37 | file: "dist/turbo_power.js",
38 | format: "es",
39 | banner,
40 | },
41 | ],
42 | plugins: [
43 | resolve(),
44 | typescript(),
45 | pretty(),
46 | filesize(),
47 | ],
48 | watch: {
49 | include: "src/**",
50 | },
51 | },
52 | ]
53 |
--------------------------------------------------------------------------------
/src/actions.ts:
--------------------------------------------------------------------------------
1 | import { TurboStreamActions } from "@hotwired/turbo"
2 |
3 | import * as Attributes from "./actions/attributes"
4 | import * as Browser from "./actions/browser"
5 | import * as Debug from "./actions/debug"
6 | import * as Deprecated from "./actions/deprecated"
7 | import * as Document from "./actions/document"
8 | import * as DOM from "./actions/dom"
9 | import * as Events from "./actions/events"
10 | import * as Form from "./actions/form"
11 | import * as History from "./actions/history"
12 | import * as Notification from "./actions/notification"
13 | import * as Storage from "./actions/storage"
14 | import * as Turbo from "./actions/turbo"
15 | import * as TurboProgressBar from "./actions/turbo_progress_bar"
16 | import * as TurboFrame from "./actions/turbo_frame"
17 |
18 | export * from "./actions/attributes"
19 | export * from "./actions/browser"
20 | export * from "./actions/debug"
21 | export * from "./actions/deprecated"
22 | export * from "./actions/document"
23 | export * from "./actions/dom"
24 | export * from "./actions/events"
25 | export * from "./actions/form"
26 | export * from "./actions/history"
27 | export * from "./actions/notification"
28 | export * from "./actions/storage"
29 | export * from "./actions/turbo"
30 | export * from "./actions/turbo_progress_bar"
31 | export * from "./actions/turbo_frame"
32 |
33 | export function register(streamActions: TurboStreamActions) {
34 | Attributes.registerAttributesActions(streamActions)
35 | Browser.registerBrowserActions(streamActions)
36 | Debug.registerDebugActions(streamActions)
37 | Deprecated.registerDeprecatedActions(streamActions)
38 | Document.registerDocumentActions(streamActions)
39 | DOM.registerDOMActions(streamActions)
40 | Events.registerEventsActions(streamActions)
41 | Form.registerFormActions(streamActions)
42 | History.registerHistoryActions(streamActions)
43 | Notification.registerNotificationActions(streamActions)
44 | Storage.registerStorageActions(streamActions)
45 | Turbo.registerTurboActions(streamActions)
46 | TurboProgressBar.registerTurboProgressBarActions(streamActions)
47 | TurboFrame.registerTurboFrameActions(streamActions)
48 | }
49 |
--------------------------------------------------------------------------------
/src/actions/attributes.ts:
--------------------------------------------------------------------------------
1 | import { StreamElement, TurboStreamActions } from "@hotwired/turbo"
2 |
3 | import { camelize, typecast, tokenize } from "../utils"
4 |
5 | type TargetElement = { [key: string]: any }
6 |
7 | export function add_css_class(this: StreamElement) {
8 | const classes = tokenize(this.getAttribute("classes"))
9 |
10 | if (classes.length > 0) {
11 | this.targetElements.forEach((element: Element) => element.classList.add(...classes))
12 | } else {
13 | console.warn(`[TurboPower] no "classes" provided for Turbo Streams operation "add_css_class"`)
14 | }
15 | }
16 |
17 | export function remove_attribute(this: StreamElement) {
18 | const attribute = this.getAttribute("attribute")
19 |
20 | if (attribute) {
21 | this.targetElements.forEach((element: Element) => element.removeAttribute(attribute))
22 | } else {
23 | console.warn(`[TurboPower] no "attribute" provided for Turbo Streams operation "remove_attribute"`)
24 | }
25 | }
26 |
27 | export function remove_css_class(this: StreamElement) {
28 | const classes = tokenize(this.getAttribute("classes"))
29 |
30 | if (classes.length > 0) {
31 | this.targetElements.forEach((element: Element) => element.classList.remove(...classes))
32 | } else {
33 | console.warn(`[TurboPower] no "classes" provided for Turbo Streams operation "remove_css_class"`)
34 | }
35 | }
36 |
37 | export function set_attribute(this: StreamElement) {
38 | const attribute = this.getAttribute("attribute")
39 | const value = this.getAttribute("value") || ""
40 |
41 | if (attribute) {
42 | this.targetElements.forEach((element: Element) => element.setAttribute(attribute, value))
43 | } else {
44 | console.warn(`[TurboPower] no "attribute" provided for Turbo Streams operation "set_attribute"`)
45 | }
46 | }
47 |
48 | export function toggle_attribute(this: StreamElement): void {
49 | const attribute: string | null = this.getAttribute("attribute")
50 | const force: string | null = this.getAttribute("force")
51 |
52 | if (!attribute) {
53 | console.warn(`[TurboPower] no "attribute" provided for Turbo Streams operation "toggle_attribute"`)
54 | return
55 | }
56 |
57 | const toggleForce: boolean | undefined = force === null ? undefined : force.toLowerCase() === "true"
58 |
59 | this.targetElements.forEach((element: HTMLElement): void => {
60 | element.toggleAttribute(attribute, toggleForce)
61 | })
62 | }
63 |
64 | export function set_dataset_attribute(this: StreamElement) {
65 | const attribute = this.getAttribute("attribute")
66 | const value = this.getAttribute("value") || ""
67 |
68 | if (attribute) {
69 | this.targetElements.forEach((element: HTMLElement) => (element.dataset[camelize(attribute)] = value))
70 | } else {
71 | console.warn(`[TurboPower] no "attribute" provided for Turbo Streams operation "set_dataset_attribute"`)
72 | }
73 | }
74 |
75 | export function set_property(this: StreamElement) {
76 | const name = this.getAttribute("name")
77 | const value = typecast(this.getAttribute("value") || "")
78 |
79 | if (name) {
80 | this.targetElements.forEach((element: TargetElement) => (element[name] = value))
81 | } else {
82 | console.error(`[TurboPower] no "name" provided for Turbo Streams operation "set_property"`)
83 | }
84 | }
85 |
86 | export function set_style(this: StreamElement) {
87 | const name = this.getAttribute("name")
88 | const value = this.getAttribute("value") || ""
89 |
90 | if (name) {
91 | this.targetElements.forEach((element: TargetElement) => (element.style[name] = value))
92 | } else {
93 | console.error(`[TurboPower] no "name" provided for Turbo Streams operation "set_style"`)
94 | }
95 | }
96 |
97 | export function set_styles(this: StreamElement) {
98 | const styles = this.getAttribute("styles") || ""
99 |
100 | this.targetElements.forEach((element: HTMLElement) => element.setAttribute("style", styles))
101 | }
102 |
103 | export function set_value(this: StreamElement) {
104 | const value = this.getAttribute("value") || ""
105 |
106 | this.targetElements.forEach((element: HTMLInputElement) => (element.value = value))
107 | }
108 |
109 | export function toggle_css_class(this: StreamElement) {
110 | const classes = tokenize(this.getAttribute("classes"))
111 |
112 | if (classes.length > 0) {
113 | this.targetElements.forEach((element: Element) => {
114 | classes.forEach((className: string) => element.classList.toggle(className))
115 | })
116 | } else {
117 | console.warn(`[TurboPower] no "classes" provided for Turbo Streams operation "toggle_css_class"`)
118 | }
119 | }
120 |
121 | export function replace_css_class(this: StreamElement) {
122 | const from = this.getAttribute("from") || ""
123 | const to = this.getAttribute("to") || ""
124 |
125 | if (from && to) {
126 | this.targetElements.forEach((element: HTMLElement) => {
127 | const wasReplaced = element.classList.replace(from, to)
128 |
129 | if (!wasReplaced) {
130 | console.warn(
131 | `[TurboPower] The "${from}" CSS class provided in the "from" attribute for the "replace_css_class" action was not found on the target element. No replacements made.`,
132 | element,
133 | )
134 | }
135 | })
136 | } else {
137 | console.warn(`[TurboPower] no "from" or "to" class provided for Turbo Streams operation "replace_css_class"`)
138 | }
139 | }
140 |
141 | export function registerAttributesActions(streamActions: TurboStreamActions) {
142 | streamActions.add_css_class = add_css_class
143 | streamActions.remove_css_class = remove_css_class
144 | streamActions.remove_attribute = remove_attribute
145 | streamActions.set_attribute = set_attribute
146 | streamActions.toggle_attribute = toggle_attribute
147 | streamActions.set_dataset_attribute = set_dataset_attribute
148 | streamActions.set_property = set_property
149 | streamActions.set_style = set_style
150 | streamActions.set_styles = set_styles
151 | streamActions.set_value = set_value
152 | streamActions.toggle_css_class = toggle_css_class
153 | streamActions.replace_css_class = replace_css_class
154 | }
155 |
--------------------------------------------------------------------------------
/src/actions/browser.ts:
--------------------------------------------------------------------------------
1 | import { StreamElement, TurboStreamActions } from "@hotwired/turbo"
2 |
3 | export function reload(this: StreamElement) {
4 | window.location.reload()
5 | }
6 |
7 | export function scroll_into_view(this: StreamElement) {
8 | const alignToTop = this.getAttribute("align-to-top")
9 | const block = this.getAttribute("block") as ScrollLogicalPosition
10 | const inline = this.getAttribute("inline") as ScrollLogicalPosition
11 | const behavior = this.getAttribute("behavior") as ScrollBehavior
12 |
13 | if (alignToTop) {
14 | this.targetElements.forEach((element: Element) => element.scrollIntoView(alignToTop === "true"))
15 | } else if (block || inline || behavior) {
16 | const options: ScrollIntoViewOptions = {}
17 |
18 | if (block) options.block = block
19 | if (inline) options.inline = inline
20 | if (behavior) options.behavior = behavior
21 |
22 | this.targetElements.forEach((element: Element) => element.scrollIntoView(options))
23 | } else {
24 | this.targetElements.forEach((element: Element) => element.scrollIntoView())
25 | }
26 | }
27 |
28 | export function set_focus(this: StreamElement) {
29 | this.targetElements.forEach((element: HTMLElement) => element.focus())
30 | }
31 |
32 | export function set_title(this: StreamElement) {
33 | const title = this.getAttribute("title") || ""
34 | let titleElement = document.head.querySelector("title")
35 |
36 | if (!titleElement) {
37 | titleElement = document.createElement("title")
38 | document.head.appendChild(titleElement)
39 | }
40 |
41 | titleElement.textContent = title
42 | }
43 |
44 | export function registerBrowserActions(streamActions: TurboStreamActions) {
45 | streamActions.reload = reload
46 | streamActions.scroll_into_view = scroll_into_view
47 | streamActions.set_focus = set_focus
48 | streamActions.set_title = set_title
49 | }
50 |
--------------------------------------------------------------------------------
/src/actions/debug.ts:
--------------------------------------------------------------------------------
1 | import { StreamElement, TurboStreamActions } from "@hotwired/turbo"
2 |
3 | // turbo_stream.console_log(message, level = :log)
4 | export function console_log(this: StreamElement) {
5 | type ConsoleLevel = "log" | "info" | "warn" | "debug" | "error"
6 |
7 | const message = this.getAttribute("message")
8 | const level = (this.getAttribute("level") || "log") as ConsoleLevel
9 |
10 | console[level](message)
11 | }
12 |
13 | // turbo_stream.console_table(data, columns)
14 | export function console_table(this: StreamElement) {
15 | const data = JSON.parse(this.getAttribute("data") || "[]")
16 | const columns = JSON.parse(this.getAttribute("columns") || "[]")
17 |
18 | console.table(data, columns)
19 | }
20 |
21 | export function registerDebugActions(streamActions: TurboStreamActions) {
22 | streamActions.console_log = console_log
23 | streamActions.console_table = console_table
24 | }
25 |
--------------------------------------------------------------------------------
/src/actions/deprecated.ts:
--------------------------------------------------------------------------------
1 | import { StreamElement, TurboStreamActions } from "@hotwired/turbo"
2 |
3 | export function invoke(this: StreamElement) {
4 | console.warn(
5 | "[TurboPower] The `invoke` Turbo Stream Action was removed from TurboPower. If you'd like to continue using this action please use the successor library instead. Read more here: https://github.com/hopsoft/turbo_boost-streams",
6 | )
7 | }
8 |
9 | export function registerDeprecatedActions(streamActions: TurboStreamActions) {
10 | if (!streamActions.invoke) {
11 | streamActions.invoke = invoke
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/actions/document.ts:
--------------------------------------------------------------------------------
1 | import { StreamElement, TurboStreamActions } from "@hotwired/turbo"
2 | import { CookieStringBuilder } from "./document/cookie_string_builder"
3 |
4 | export function set_cookie(this: StreamElement) {
5 | const cookie = this.getAttribute("cookie") || ""
6 |
7 | document.cookie = cookie
8 | }
9 |
10 | export function set_cookie_item(this: StreamElement) {
11 | const cookieStringBuilder = new CookieStringBuilder(this)
12 | document.cookie = cookieStringBuilder.build()
13 | }
14 |
15 | export function registerDocumentActions(streamActions: TurboStreamActions) {
16 | streamActions.set_cookie = set_cookie
17 | streamActions.set_cookie_item = set_cookie_item
18 | }
19 |
--------------------------------------------------------------------------------
/src/actions/document/cookie_string_builder.ts:
--------------------------------------------------------------------------------
1 | import { StreamElement } from "@hotwired/turbo"
2 |
3 | type MappingRow = [string, string, boolean]
4 |
5 | export class CookieStringBuilder {
6 | ATTRIBUTE_TO_COOKIE_KEY_MAPPING: MappingRow[] = [
7 | ["domain", "Domain", false],
8 | ["path", "Path", false],
9 | ["expires", "Expires", false],
10 | ["max-age", "Max-Age", false],
11 | ["http-only", "HttpOnly", true],
12 | ["secure", "Secure", true],
13 | ["same-site", "SameSite", false],
14 | ]
15 |
16 | streamElement: StreamElement
17 |
18 | constructor(streamElement: StreamElement) {
19 | this.streamElement = streamElement
20 | }
21 |
22 | build(): string {
23 | let cookieString = `${this.streamElement.getAttribute("name")}=${this.streamElement.getAttribute("value")}`
24 |
25 | this.ATTRIBUTE_TO_COOKIE_KEY_MAPPING.forEach(([streamElementAttribute, cookieKey, isBooleanAttribute]) => {
26 | const cookieValue = this.streamElement.getAttribute(streamElementAttribute)
27 |
28 | if (cookieValue !== null) {
29 | const cookieKeyPair = isBooleanAttribute ? cookieKey : `${cookieKey}=${cookieValue}`
30 | cookieString = `${cookieString}; ${cookieKeyPair}`
31 | }
32 | })
33 |
34 | return cookieString
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/actions/dom.ts:
--------------------------------------------------------------------------------
1 | import { StreamElement, TurboStreamActions } from "@hotwired/turbo"
2 |
3 | export function graft(this: StreamElement) {
4 | const selector = this.getAttribute("parent")
5 |
6 | if (selector) {
7 | const parent = document.querySelector(selector)
8 |
9 | if (parent) {
10 | this.targetElements.forEach((element: Element) => parent.appendChild(element))
11 | } else {
12 | console.error(
13 | `[TurboPower] couldn't find parent element using selector "${selector}" for Turbo Streams operation "graft"`,
14 | )
15 | }
16 | } else {
17 | console.error(`[TurboPower] no "parent" selector provided for Turbo Streams operation "graft"`)
18 | }
19 | }
20 |
21 | export function inner_html(this: StreamElement) {
22 | const html = this.templateContent.textContent || ""
23 |
24 | this.targetElements.forEach((element: Element) => (element.innerHTML = html))
25 | }
26 |
27 | export function insert_adjacent_html(this: StreamElement) {
28 | const position = (this.getAttribute("position") || "beforebegin") as InsertPosition
29 | const html = this.templateContent.textContent || ""
30 |
31 | this.targetElements.forEach((element: Element) => element.insertAdjacentHTML(position, html))
32 | }
33 |
34 | export function insert_adjacent_text(this: StreamElement) {
35 | const position = (this.getAttribute("position") || "beforebegin") as InsertPosition
36 | const message = this.getAttribute("text") || ""
37 |
38 | this.targetElements.forEach((element: Element) => element.insertAdjacentText(position, message))
39 | }
40 |
41 | export function outer_html(this: StreamElement) {
42 | const html = this.templateContent.textContent || ""
43 |
44 | this.targetElements.forEach((element: Element) => (element.outerHTML = html))
45 | }
46 |
47 | export function set_meta(this: StreamElement) {
48 | const name = this.getAttribute("name")
49 | const content = this.getAttribute("content") || ""
50 |
51 | if (name) {
52 | let meta: HTMLMetaElement | null = document.head.querySelector(`meta[name='${name}']`)
53 |
54 | if (!meta) {
55 | meta = document.createElement("meta")
56 | meta.name = name
57 | document.head.appendChild(meta)
58 | }
59 |
60 | meta.content = content
61 | } else {
62 | console.error(`[TurboPower] no "name" provided for Turbo Streams operation "set_meta"`)
63 | }
64 | }
65 |
66 | export function text_content(this: StreamElement) {
67 | const text = this.getAttribute("text") || ""
68 |
69 | this.targetElements.forEach((element: Element) => (element.textContent = text))
70 | }
71 |
72 | export function registerDOMActions(streamActions: TurboStreamActions) {
73 | streamActions.graft = graft
74 | streamActions.inner_html = inner_html
75 | streamActions.insert_adjacent_html = insert_adjacent_html
76 | streamActions.insert_adjacent_text = insert_adjacent_text
77 | streamActions.outer_html = outer_html
78 | streamActions.text_content = text_content
79 | streamActions.set_meta = set_meta
80 | }
81 |
--------------------------------------------------------------------------------
/src/actions/events.ts:
--------------------------------------------------------------------------------
1 | import { StreamElement, TurboStreamActions } from "@hotwired/turbo"
2 |
3 | export function dispatch_event(this: StreamElement) {
4 | const name = this.getAttribute("name")
5 | let template = null
6 |
7 | try {
8 | template = this.templateContent.textContent
9 | } catch (e) {
10 | // default to empty object
11 | }
12 |
13 | try {
14 | const detail = template ? JSON.parse(template) : {}
15 |
16 | if (name) {
17 | const options = { bubbles: true, cancelable: true, detail }
18 | const event = new CustomEvent(name, options)
19 |
20 | this.targetElements.forEach((element: Element) => element.dispatchEvent(event))
21 | } else {
22 | console.warn(`[TurboPower] no "name" provided for Turbo Streams operation "dispatch_event"`)
23 | }
24 | } catch (error: any) {
25 | console.error(
26 | `[TurboPower] error proccessing provided "detail" in "" ("${template}") for Turbo Streams operation "dispatch_event".`,
27 | `Error: "${error.message}"`,
28 | )
29 | }
30 | }
31 |
32 | export function registerEventsActions(streamActions: TurboStreamActions) {
33 | streamActions.dispatch_event = dispatch_event
34 | }
35 |
--------------------------------------------------------------------------------
/src/actions/form.ts:
--------------------------------------------------------------------------------
1 | import { StreamElement, TurboStreamActions } from "@hotwired/turbo"
2 |
3 | export function reset_form(this: StreamElement) {
4 | this.targetElements.forEach((form: HTMLFormElement) => form.reset())
5 | }
6 |
7 | export function registerFormActions(streamActions: TurboStreamActions) {
8 | streamActions.reset_form = reset_form
9 | }
10 |
--------------------------------------------------------------------------------
/src/actions/history.ts:
--------------------------------------------------------------------------------
1 | import { StreamElement, TurboStreamActions } from "@hotwired/turbo"
2 |
3 | export function push_state(this: StreamElement) {
4 | const url = this.getAttribute("url")
5 | const state = this.getAttribute("state")
6 | const title = this.getAttribute("title") || ""
7 |
8 | window.history.pushState(state, title, url)
9 | }
10 |
11 | export function replace_state(this: StreamElement) {
12 | const url = this.getAttribute("url")
13 | const state = this.getAttribute("state")
14 | const title = this.getAttribute("title") || ""
15 |
16 | window.history.replaceState(state, title, url)
17 | }
18 |
19 | export function history_back(this: StreamElement) {
20 | window.history.back()
21 | }
22 |
23 | export function history_forward(this: StreamElement) {
24 | window.history.forward()
25 | }
26 |
27 | export function history_go(this: StreamElement) {
28 | const delta = Number(this.getAttribute("delta")) || 0
29 | window.history.go(delta)
30 | }
31 |
32 | export function registerHistoryActions(streamActions: TurboStreamActions) {
33 | streamActions.push_state = push_state
34 | streamActions.replace_state = replace_state
35 | streamActions.history_back = history_back
36 | streamActions.history_go = history_go
37 | }
38 |
--------------------------------------------------------------------------------
/src/actions/notification.ts:
--------------------------------------------------------------------------------
1 | import { StreamElement, TurboStreamActions } from "@hotwired/turbo"
2 | import { camelize, typecast } from "../utils"
3 |
4 | const PERMITTED_ATTRIBUTES = [
5 | "dir",
6 | "lang",
7 | "badge",
8 | "body",
9 | "tag",
10 | "icon",
11 | "image",
12 | "data",
13 | "vibrate",
14 | "renotify",
15 | "require-interaction",
16 | "actions",
17 | "silent",
18 | ]
19 |
20 | const createNotification = (streamElement: StreamElement) => {
21 | const title = streamElement.getAttribute("title") || ""
22 |
23 | const attributes = Array.from(streamElement.attributes)
24 | .filter((attribute) => PERMITTED_ATTRIBUTES.includes(attribute.name))
25 | .map((attribute) => [camelize(attribute.name), typecast(attribute.value)])
26 |
27 | const options = Object.fromEntries(attributes)
28 |
29 | new Notification(title, options)
30 | }
31 |
32 | export function notification(this: StreamElement) {
33 | if (!window.Notification) {
34 | alert("This browser does not support desktop notification")
35 | } else if (Notification.permission === "granted") {
36 | createNotification(this)
37 | } else if (Notification.permission !== "denied") {
38 | Notification.requestPermission().then((permission) => {
39 | if (permission === "granted") {
40 | createNotification(this)
41 | }
42 | })
43 | }
44 | }
45 |
46 | export function registerNotificationActions(streamActions: TurboStreamActions) {
47 | streamActions.notification = notification
48 | }
49 |
--------------------------------------------------------------------------------
/src/actions/storage.ts:
--------------------------------------------------------------------------------
1 | import { StreamElement, TurboStreamActions } from "@hotwired/turbo"
2 |
3 | function storage(type: string | null): Storage {
4 | return type === "session" ? window.sessionStorage : window.localStorage
5 | }
6 |
7 | export function clear_storage(this: StreamElement) {
8 | const type = this.getAttribute("type")
9 |
10 | storage(type).clear()
11 | }
12 |
13 | export function set_storage_item(this: StreamElement) {
14 | const key = this.getAttribute("key")
15 | const value = this.getAttribute("value") || ""
16 | const type = this.getAttribute("type")
17 |
18 | if (key) {
19 | storage(type).setItem(key, value)
20 | } else {
21 | console.warn(`[TurboPower] no "key" provided for Turbo Streams operation "set_storage_item"`)
22 | }
23 | }
24 |
25 | export function remove_storage_item(this: StreamElement) {
26 | const key = this.getAttribute("key")
27 | const type = this.getAttribute("type")
28 |
29 | if (key) {
30 | storage(type).removeItem(key)
31 | } else {
32 | console.warn(`[TurboPower] no "key" provided for Turbo Streams operation "remove_storage_item"`)
33 | }
34 | }
35 |
36 | export function registerStorageActions(streamActions: TurboStreamActions) {
37 | streamActions.clear_storage = clear_storage
38 | streamActions.set_storage_item = set_storage_item
39 | streamActions.remove_storage_item = remove_storage_item
40 | }
41 |
--------------------------------------------------------------------------------
/src/actions/turbo.ts:
--------------------------------------------------------------------------------
1 | import { StreamElement, TurboStreamActions } from "@hotwired/turbo"
2 | import { Action } from "@hotwired/turbo/dist/types/core/types"
3 | import { VisitOptions } from "@hotwired/turbo/dist/types/core/drive/visit"
4 | import Proxy from "../proxy"
5 |
6 | export function redirect_to(this: StreamElement) {
7 | const url = this.getAttribute("url") || "/"
8 | const turboAction = (this.getAttribute("turbo-action") || "advance") as Action
9 | const turboFrame = this.getAttribute("turbo-frame")
10 | const turbo = this.getAttribute("turbo") !== "false"
11 | const options: Partial = {
12 | action: turboAction,
13 | }
14 |
15 | if (turboFrame) {
16 | options.frame = turboFrame
17 | }
18 |
19 | if (turbo && window.Turbo) {
20 | window.Turbo.visit(url, options)
21 | } else {
22 | Proxy.location.assign(url)
23 | }
24 | }
25 |
26 | export function turbo_clear_cache() {
27 | window.Turbo.cache.clear()
28 | }
29 |
30 | export function registerTurboActions(streamActions: TurboStreamActions) {
31 | streamActions.redirect_to = redirect_to
32 | streamActions.turbo_clear_cache = turbo_clear_cache
33 | }
34 |
--------------------------------------------------------------------------------
/src/actions/turbo_frame.ts:
--------------------------------------------------------------------------------
1 | import { StreamElement, TurboStreamActions, FrameElement } from "@hotwired/turbo"
2 |
3 | export function turbo_frame_reload(this: StreamElement) {
4 | this.targetElements.forEach((element: FrameElement) => element.reload())
5 | }
6 |
7 | export function turbo_frame_set_src(this: StreamElement) {
8 | const src = this.getAttribute("src")
9 | this.targetElements.forEach((element: FrameElement) => (element.src = src))
10 | }
11 |
12 | export function registerTurboFrameActions(streamActions: TurboStreamActions) {
13 | streamActions.turbo_frame_reload = turbo_frame_reload
14 | streamActions.turbo_frame_set_src = turbo_frame_set_src
15 | }
16 |
--------------------------------------------------------------------------------
/src/actions/turbo_progress_bar.ts:
--------------------------------------------------------------------------------
1 | import { StreamElement, TurboStreamActions } from "@hotwired/turbo"
2 |
3 | export function turbo_progress_bar_set_value(this: StreamElement) {
4 | const value = this.getAttribute("value") || 0
5 |
6 | window.Turbo.navigator.adapter.progressBar.setValue(Number(value))
7 | }
8 |
9 | export function turbo_progress_bar_show(this: StreamElement) {
10 | window.Turbo.navigator.adapter.progressBar.show()
11 | }
12 |
13 | export function turbo_progress_bar_hide(this: StreamElement) {
14 | window.Turbo.navigator.adapter.progressBar.hide()
15 | }
16 |
17 | export function registerTurboProgressBarActions(streamActions: TurboStreamActions) {
18 | streamActions.turbo_progress_bar_set_value = turbo_progress_bar_set_value
19 | streamActions.turbo_progress_bar_show = turbo_progress_bar_show
20 | streamActions.turbo_progress_bar_hide = turbo_progress_bar_hide
21 | }
22 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as Turbo from "@hotwired/turbo"
2 | import { TurboStreamAction, TurboStreamActions } from "@hotwired/turbo"
3 | import { BrowserAdapter } from "@hotwired/turbo/dist/types/core/native/browser_adapter"
4 |
5 | import * as TurboMorph from "turbo-morph"
6 | import * as Actions from "./actions"
7 | import * as Utils from "./utils"
8 |
9 | export * as Actions from "./actions"
10 | export * as Utils from "./utils"
11 |
12 | declare global {
13 | interface Window {
14 | Turbo: typeof Turbo & {
15 | navigator: {
16 | adapter: BrowserAdapter
17 | }
18 | }
19 | }
20 | }
21 |
22 | export function initialize(streamActions: TurboStreamActions) {
23 | TurboMorph.initialize(streamActions)
24 | Actions.register(streamActions)
25 | }
26 |
27 | export function register(name: string, action: TurboStreamAction, streamActions: TurboStreamActions) {
28 | streamActions[name] = action
29 | }
30 |
31 | export default {
32 | initialize,
33 | register,
34 | Actions,
35 | Utils,
36 | }
37 |
--------------------------------------------------------------------------------
/src/proxy.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | interface Window {
3 | TurboPowerLocation: typeof window.location
4 | }
5 | }
6 |
7 | export default {
8 | get location() {
9 | return window.TurboPowerLocation || window.location
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | export function camelize(value: string) {
2 | return value.replace(/(?:[_-])([a-z0-9])/g, (_, char) => char.toUpperCase())
3 | }
4 |
5 | export function capitalize(value: string) {
6 | return value.charAt(0).toUpperCase() + value.slice(1)
7 | }
8 |
9 | export function dasherize(value: string) {
10 | return value.replace(/([A-Z])/g, (_, char) => `-${char.toLowerCase()}`)
11 | }
12 |
13 | export function tokenize(value: string | null) {
14 | return value ? value.match(/[^\s]+/g) || [] : []
15 | }
16 |
17 | export function typecast(value: string) {
18 | try {
19 | return JSON.parse(value)
20 | } catch (e) {
21 | return value
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/attributes/add_css_class.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { html, fixture, assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("add_css_class")
6 |
7 | describe("add_css_class", () => {
8 | context("warnings", () => {
9 | afterEach(() => {
10 | sinon.restore()
11 | })
12 |
13 | it("should do nothing and print warning if no classes were provided", async () => {
14 | const fake = sinon.replace(console, "warn", sinon.fake())
15 | const expectedWarning = '[TurboPower] no "classes" provided for Turbo Streams operation "add_css_class"'
16 |
17 | await fixture('')
18 |
19 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
20 | assert.equal(fake.callCount, 0)
21 |
22 | await executeStream('')
23 |
24 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
25 | assert.equal(fake.callCount, 1)
26 | assert.equal(fake.firstArg, expectedWarning)
27 | })
28 |
29 | it('should do nothing and print warning if "classes" attribute is missing', async () => {
30 | const fake = sinon.replace(console, "warn", sinon.fake())
31 | const expectedWarning = '[TurboPower] no "classes" provided for Turbo Streams operation "add_css_class"'
32 |
33 | await fixture('')
34 |
35 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
36 | assert.equal(fake.callCount, 0)
37 |
38 | await executeStream('')
39 |
40 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
41 | assert.equal(fake.callCount, 1)
42 | assert.equal(fake.firstArg, expectedWarning)
43 | })
44 | })
45 |
46 | context("target", () => {
47 | it("should add one css class", async () => {
48 | await fixture('')
49 |
50 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
51 |
52 | await executeStream('')
53 |
54 | assert.equal(document.querySelector("#element").getAttribute("class"), "one")
55 | })
56 |
57 | it("should add one css class with existing class", async () => {
58 | await fixture('')
59 |
60 | assert.equal(document.querySelector("#element").getAttribute("class"), "one")
61 |
62 | await executeStream('')
63 |
64 | assert.equal(document.querySelector("#element").getAttribute("class"), "one two")
65 | })
66 |
67 | it("should add multiple css classes", async () => {
68 | await fixture('')
69 |
70 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
71 |
72 | await executeStream(
73 | '',
74 | )
75 |
76 | assert.equal(document.querySelector("#element").getAttribute("class"), "one two three")
77 | })
78 |
79 | it("should add multiple css classes with exisiting classes", async () => {
80 | await fixture(html` `)
81 |
82 | assert.equal(document.querySelector("#element").getAttribute("class"), "already present classes")
83 |
84 | await executeStream(
85 | '',
86 | )
87 |
88 | assert.equal(document.querySelector("#element").getAttribute("class"), "already present classes one two three")
89 | })
90 | })
91 |
92 | context("targets", () => {
93 | it("should add one css class", async () => {
94 | await fixture(html`
95 |
96 |
97 |
98 | `)
99 |
100 | assert.equal(document.querySelector("#one").getAttribute("class"), null)
101 | assert.equal(document.querySelector("#two").getAttribute("class"), null)
102 | assert.equal(document.querySelector("#three").getAttribute("class"), null)
103 |
104 | await executeStream('')
105 |
106 | assert.equal(document.querySelector("#one").getAttribute("class"), "one")
107 | assert.equal(document.querySelector("#two").getAttribute("class"), "one")
108 | assert.equal(document.querySelector("#three").getAttribute("class"), "one")
109 | })
110 |
111 | it("should add one css class with existing class", async () => {
112 | await fixture(html`
113 |
114 |
115 |
116 | `)
117 |
118 | assert.equal(document.querySelector("#one").getAttribute("class"), "one")
119 | assert.equal(document.querySelector("#two").getAttribute("class"), "one")
120 | assert.equal(document.querySelector("#three").getAttribute("class"), "one")
121 |
122 | await executeStream('')
123 |
124 | assert.equal(document.querySelector("#one").getAttribute("class"), "one two")
125 | assert.equal(document.querySelector("#two").getAttribute("class"), "one two")
126 | assert.equal(document.querySelector("#three").getAttribute("class"), "one two")
127 | })
128 |
129 | it("should add multiple css classes", async () => {
130 | await fixture(html`
131 |
132 |
133 |
134 | `)
135 |
136 | assert.equal(document.querySelector("#one").getAttribute("class"), null)
137 | assert.equal(document.querySelector("#two").getAttribute("class"), null)
138 | assert.equal(document.querySelector("#three").getAttribute("class"), null)
139 |
140 | await executeStream('')
141 |
142 | assert.equal(document.querySelector("#one").getAttribute("class"), "one two three")
143 | assert.equal(document.querySelector("#two").getAttribute("class"), "one two three")
144 | assert.equal(document.querySelector("#three").getAttribute("class"), "one two three")
145 | })
146 |
147 | it("should add multiple css classes with exisiting classes", async () => {
148 | await fixture(html`
149 |
150 |
151 |
152 | `)
153 |
154 | assert.equal(document.querySelector("#one").getAttribute("class"), "already present classes")
155 | assert.equal(document.querySelector("#two").getAttribute("class"), "already present classes")
156 | assert.equal(document.querySelector("#three").getAttribute("class"), "already present classes")
157 |
158 | await executeStream('')
159 |
160 | assert.equal(document.querySelector("#one").getAttribute("class"), "already present classes one two three")
161 | assert.equal(document.querySelector("#two").getAttribute("class"), "already present classes one two three")
162 | assert.equal(document.querySelector("#three").getAttribute("class"), "already present classes one two three")
163 | })
164 | })
165 | })
166 |
--------------------------------------------------------------------------------
/test/attributes/remove_attribute.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { html, fixture, assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("remove_attribute")
6 |
7 | describe("remove_attribute", () => {
8 | context("warnings", () => {
9 | afterEach(() => {
10 | sinon.restore()
11 | })
12 |
13 | it("should do nothing and print warning if attribute is empty", async () => {
14 | const fake = sinon.replace(console, "warn", sinon.fake())
15 | const expectedWarning = '[TurboPower] no "attribute" provided for Turbo Streams operation "remove_attribute"'
16 |
17 | await fixture('')
18 |
19 | assert.equal(fake.callCount, 0)
20 |
21 | await executeStream('')
22 |
23 | assert.equal(fake.callCount, 1)
24 | assert.equal(fake.firstArg, expectedWarning)
25 | })
26 |
27 | it('should do nothing and print warning if "attribute" attribute is missing', async () => {
28 | const fake = sinon.replace(console, "warn", sinon.fake())
29 | const expectedWarning = '[TurboPower] no "attribute" provided for Turbo Streams operation "remove_attribute"'
30 |
31 | await fixture('')
32 |
33 | assert.equal(fake.callCount, 0)
34 |
35 | await executeStream('')
36 |
37 | assert.equal(fake.callCount, 1)
38 | assert.equal(fake.firstArg, expectedWarning)
39 | })
40 | })
41 |
42 | context("target", () => {
43 | it("should remove attribute", async () => {
44 | await fixture(html``)
45 |
46 | assert.equal(document.querySelector("#element").getAttribute("my-attribute"), "previous-value")
47 |
48 | await executeStream(
49 | '',
50 | )
51 |
52 | assert.equal(document.querySelector("#element").getAttribute("my-attribute"), null)
53 | })
54 | })
55 |
56 | context("targets", () => {
57 | it("should remove attribute", async () => {
58 | await fixture(html`
59 |
60 |
61 |
62 | `)
63 |
64 | assert.equal(document.querySelector("#element1").getAttribute("my-attribute"), "previous-value")
65 | assert.equal(document.querySelector("#element2").getAttribute("my-attribute"), "previous-value")
66 | assert.equal(document.querySelector("#element3").getAttribute("my-attribute"), "previous-value")
67 |
68 | await executeStream(
69 | '',
70 | )
71 |
72 | assert.equal(document.querySelector("#element1").getAttribute("my-attribute"), null)
73 | assert.equal(document.querySelector("#element2").getAttribute("my-attribute"), null)
74 | assert.equal(document.querySelector("#element3").getAttribute("my-attribute"), null)
75 | })
76 | })
77 | })
78 |
--------------------------------------------------------------------------------
/test/attributes/remove_css_class.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { html, fixture, assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("remove_css_class")
6 |
7 | describe("remove_css_class", () => {
8 | context("warnings", () => {
9 | afterEach(() => {
10 | sinon.restore()
11 | })
12 |
13 | it("should do nothing and print warning if no classes were provided", async () => {
14 | const fake = sinon.replace(console, "warn", sinon.fake())
15 | const expectedWarning = '[TurboPower] no "classes" provided for Turbo Streams operation "remove_css_class"'
16 |
17 | await fixture('')
18 |
19 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
20 | assert.equal(fake.callCount, 0)
21 |
22 | await executeStream('')
23 |
24 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
25 | assert.equal(fake.callCount, 1)
26 | assert.equal(fake.firstArg, expectedWarning)
27 | })
28 |
29 | it('should do nothing and print warning if "classes" attribute is missing', async () => {
30 | const fake = sinon.replace(console, "warn", sinon.fake())
31 | const expectedWarning = '[TurboPower] no "classes" provided for Turbo Streams operation "remove_css_class"'
32 |
33 | await fixture('')
34 |
35 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
36 | assert.equal(fake.callCount, 0)
37 |
38 | await executeStream('')
39 |
40 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
41 | assert.equal(fake.callCount, 1)
42 | assert.equal(fake.firstArg, expectedWarning)
43 | })
44 | })
45 |
46 | context("target", () => {
47 | it("should remove one css class", async () => {
48 | await fixture(html` `)
49 |
50 | assert.equal(document.querySelector("#element").getAttribute("class"), "one two")
51 |
52 | await executeStream('')
53 |
54 | assert.equal(document.querySelector("#element").getAttribute("class"), "two")
55 | })
56 |
57 | it("should remove last exisiting css class", async () => {
58 | await fixture(html` `)
59 |
60 | assert.equal(document.querySelector("#element").getAttribute("class"), "one")
61 |
62 | await executeStream('')
63 |
64 | assert.equal(document.querySelector("#element").getAttribute("class"), "")
65 | })
66 |
67 | it("should remove multiple css classes", async () => {
68 | await fixture(html` `)
69 |
70 | assert.equal(document.querySelector("#element").getAttribute("class"), "one two three")
71 |
72 | await executeStream('')
73 |
74 | assert.equal(document.querySelector("#element").getAttribute("class"), "three")
75 | })
76 |
77 | it("should remove all remaining css classes", async () => {
78 | await fixture(html` `)
79 |
80 | assert.equal(document.querySelector("#element").getAttribute("class"), "one two three")
81 |
82 | await executeStream(
83 | '',
84 | )
85 |
86 | assert.equal(document.querySelector("#element").getAttribute("class"), "")
87 | })
88 | })
89 |
90 | context("targets", () => {
91 | it("should remove one css class", async () => {
92 | await fixture(html`
93 |
94 |
95 |
96 | `)
97 |
98 | assert.equal(document.querySelector("#element1").getAttribute("class"), "one two")
99 | assert.equal(document.querySelector("#element2").getAttribute("class"), "one two")
100 | assert.equal(document.querySelector("#element3").getAttribute("class"), "one two")
101 |
102 | await executeStream('')
103 |
104 | assert.equal(document.querySelector("#element1").getAttribute("class"), "two")
105 | assert.equal(document.querySelector("#element2").getAttribute("class"), "two")
106 | assert.equal(document.querySelector("#element3").getAttribute("class"), "two")
107 | })
108 |
109 | it("should remove last exisiting css class", async () => {
110 | await fixture(html`
111 |
112 |
113 |
114 | `)
115 |
116 | assert.equal(document.querySelector("#element1").getAttribute("class"), "one")
117 | assert.equal(document.querySelector("#element2").getAttribute("class"), "one")
118 | assert.equal(document.querySelector("#element3").getAttribute("class"), "one")
119 |
120 | await executeStream('')
121 |
122 | assert.equal(document.querySelector("#element1").getAttribute("class"), "")
123 | assert.equal(document.querySelector("#element2").getAttribute("class"), "")
124 | assert.equal(document.querySelector("#element3").getAttribute("class"), "")
125 | })
126 |
127 | it("should remove multiple css classes", async () => {
128 | await fixture(html`
129 |
130 |
131 |
132 | `)
133 |
134 | assert.equal(document.querySelector("#element1").getAttribute("class"), "one two three")
135 | assert.equal(document.querySelector("#element2").getAttribute("class"), "one two three")
136 | assert.equal(document.querySelector("#element3").getAttribute("class"), "one two three")
137 |
138 | await executeStream('')
139 |
140 | assert.equal(document.querySelector("#element1").getAttribute("class"), "three")
141 | assert.equal(document.querySelector("#element2").getAttribute("class"), "three")
142 | assert.equal(document.querySelector("#element3").getAttribute("class"), "three")
143 | })
144 |
145 | it("should remove all remaining css classes", async () => {
146 | await fixture(html`
147 |
148 |
149 |
150 | `)
151 |
152 | assert.equal(document.querySelector("#element1").getAttribute("class"), "one two three")
153 | assert.equal(document.querySelector("#element2").getAttribute("class"), "one two three")
154 | assert.equal(document.querySelector("#element3").getAttribute("class"), "one two three")
155 |
156 | await executeStream(
157 | '',
158 | )
159 |
160 | assert.equal(document.querySelector("#element1").getAttribute("class"), "")
161 | assert.equal(document.querySelector("#element2").getAttribute("class"), "")
162 | assert.equal(document.querySelector("#element3").getAttribute("class"), "")
163 | })
164 | })
165 | })
166 |
--------------------------------------------------------------------------------
/test/attributes/replace_css_class.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { fixture, assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("replace_css_class")
6 |
7 | describe("replace_css_class", () => {
8 | context("warnings", () => {
9 | afterEach(() => {
10 | sinon.restore()
11 | })
12 |
13 | it('should do nothing and print warning if no "from" were provided', async () => {
14 | const fake = sinon.replace(console, "warn", sinon.fake())
15 |
16 | await fixture('')
17 |
18 | const expectedWarning =
19 | '[TurboPower] no "from" or "to" class provided for Turbo Streams operation "replace_css_class"'
20 |
21 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
22 | assert.equal(fake.callCount, 0)
23 |
24 | await executeStream('')
25 |
26 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
27 | assert.equal(fake.callCount, 1)
28 | assert.equal(fake.firstArg, expectedWarning)
29 | })
30 |
31 | it('should do nothing and print warning if "from" attribute is missing', async () => {
32 | const fake = sinon.replace(console, "warn", sinon.fake())
33 | const expectedWarning =
34 | '[TurboPower] no "from" or "to" class provided for Turbo Streams operation "replace_css_class"'
35 |
36 | await fixture('')
37 |
38 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
39 | assert.equal(fake.callCount, 0)
40 |
41 | await executeStream('')
42 |
43 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
44 | assert.equal(fake.callCount, 1)
45 | assert.equal(fake.firstArg, expectedWarning)
46 | })
47 |
48 | it('should do nothing if "from" attribute is not present on element', async () => {
49 | const fake = sinon.replace(console, "warn", sinon.fake())
50 |
51 | const element = await fixture('')
52 |
53 | assert.equal(element.getAttribute("class"), "")
54 | assert.equal(fake.callCount, 0)
55 |
56 | await executeStream(
57 | '',
58 | )
59 |
60 | assert.equal(element.getAttribute("class"), "")
61 | assert.equal(fake.callCount, 1)
62 | assert.equal(
63 | fake.firstArg,
64 | `[TurboPower] The "one" CSS class provided in the "from" attribute for the "replace_css_class" action was not found on the target element. No replacements made.`,
65 | )
66 | assert.equal(fake.lastArg, element)
67 | })
68 | })
69 |
70 | context("target", () => {
71 | it("should replace the css class", async () => {
72 | const element = await fixture('')
73 |
74 | assert.equal(element.getAttribute("class"), "background-blue")
75 |
76 | await executeStream(
77 | '',
78 | )
79 |
80 | assert.equal(element.getAttribute("class"), "background-red")
81 | })
82 | })
83 |
84 | context("targets", () => {
85 | it("should replace the css class", async () => {
86 | await fixture(`
87 |
88 |
89 |
90 | `)
91 |
92 | assert.equal(document.querySelector("#one").getAttribute("class"), "background-blue")
93 | assert.equal(document.querySelector("#two").getAttribute("class"), "background-blue")
94 | assert.equal(document.querySelector("#three").getAttribute("class"), "background-blue")
95 |
96 | await executeStream(
97 | '',
98 | )
99 |
100 | assert.equal(document.querySelector("#one").getAttribute("class"), "background-red")
101 | assert.equal(document.querySelector("#two").getAttribute("class"), "background-red")
102 | assert.equal(document.querySelector("#three").getAttribute("class"), "background-red")
103 | })
104 | })
105 | })
106 |
--------------------------------------------------------------------------------
/test/attributes/set_attribute.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { html, fixture, assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("set_attribute")
6 |
7 | describe("set_attribute", () => {
8 | context("warnings", () => {
9 | afterEach(() => {
10 | sinon.restore()
11 | })
12 |
13 | it("should do nothing and print warning if attribute is empty", async () => {
14 | const fake = sinon.replace(console, "warn", sinon.fake())
15 |
16 | await fixture('')
17 |
18 | const expectedWarning = '[TurboPower] no "attribute" provided for Turbo Streams operation "set_attribute"'
19 |
20 | assert.equal(fake.callCount, 0)
21 |
22 | await executeStream('')
23 |
24 | assert.equal(fake.callCount, 1)
25 | assert.equal(fake.firstArg, expectedWarning)
26 | })
27 |
28 | it('should do nothing and print warning if "attribute" attribute is missing', async () => {
29 | const fake = sinon.replace(console, "warn", sinon.fake())
30 |
31 | await fixture('')
32 |
33 | const expectedWarning = '[TurboPower] no "attribute" provided for Turbo Streams operation "set_attribute"'
34 |
35 | assert.equal(fake.callCount, 0)
36 |
37 | await executeStream('')
38 |
39 | assert.equal(fake.callCount, 1)
40 | assert.equal(fake.firstArg, expectedWarning)
41 | })
42 | })
43 |
44 | context("target", () => {
45 | it("should set attribute with value", async () => {
46 | await fixture('')
47 |
48 | assert.equal(document.querySelector("#element").getAttribute("my-attribute"), null)
49 |
50 | await executeStream(
51 | '',
52 | )
53 |
54 | assert.equal(document.querySelector("#element").getAttribute("my-attribute"), "my-value")
55 | })
56 |
57 | it("should set attribute with empty value", async () => {
58 | await fixture('')
59 |
60 | assert.equal(document.querySelector("#element").getAttribute("my-attribute"), "previous-value")
61 |
62 | await executeStream(
63 | '',
64 | )
65 |
66 | assert.equal(document.querySelector("#element").getAttribute("my-attribute"), "")
67 | })
68 |
69 | it("should override previous attribute value with empty value", async () => {
70 | await fixture(html` `)
71 |
72 | assert.equal(document.querySelector("#element").getAttribute("my-attribute"), "previous-value")
73 |
74 | await executeStream(
75 | '',
76 | )
77 |
78 | assert.equal(document.querySelector("#element").getAttribute("my-attribute"), "")
79 | })
80 |
81 | it('should override previous attribute value with missing "value" attribute', async () => {
82 | await fixture(html` `)
83 |
84 | assert.equal(document.querySelector("#element").getAttribute("my-attribute"), "previous-value")
85 |
86 | await executeStream(
87 | '',
88 | )
89 |
90 | assert.equal(document.querySelector("#element").getAttribute("my-attribute"), "")
91 | })
92 | })
93 |
94 | context("targets", () => {
95 | it("should set attribute with value", async () => {
96 | await fixture(html`
97 |
98 |
99 |
100 | `)
101 |
102 | assert.equal(document.querySelector("#element1").getAttribute("my-attribute"), null)
103 | assert.equal(document.querySelector("#element2").getAttribute("my-attribute"), null)
104 | assert.equal(document.querySelector("#element3").getAttribute("my-attribute"), null)
105 |
106 | await executeStream(
107 | '',
108 | )
109 |
110 | assert.equal(document.querySelector("#element1").getAttribute("my-attribute"), "my-value")
111 | assert.equal(document.querySelector("#element2").getAttribute("my-attribute"), "my-value")
112 | assert.equal(document.querySelector("#element3").getAttribute("my-attribute"), "my-value")
113 | })
114 |
115 | it("should set attribute with empty value", async () => {
116 | await fixture(html`
117 |
118 |
119 |
120 | `)
121 |
122 | assert.equal(document.querySelector("#element1").getAttribute("my-attribute"), "previous-value")
123 | assert.equal(document.querySelector("#element2").getAttribute("my-attribute"), "previous-value")
124 | assert.equal(document.querySelector("#element3").getAttribute("my-attribute"), "previous-value")
125 |
126 | await executeStream(
127 | '',
128 | )
129 |
130 | assert.equal(document.querySelector("#element1").getAttribute("my-attribute"), "")
131 | assert.equal(document.querySelector("#element2").getAttribute("my-attribute"), "")
132 | assert.equal(document.querySelector("#element3").getAttribute("my-attribute"), "")
133 | })
134 |
135 | it("should override previous attribute value with empty value", async () => {
136 | await fixture(html`
137 |
138 |
139 |
140 | `)
141 |
142 | assert.equal(document.querySelector("#element1").getAttribute("my-attribute"), "previous-value")
143 | assert.equal(document.querySelector("#element2").getAttribute("my-attribute"), "previous-value")
144 | assert.equal(document.querySelector("#element3").getAttribute("my-attribute"), "previous-value")
145 |
146 | await executeStream(
147 | '',
148 | )
149 |
150 | assert.equal(document.querySelector("#element1").getAttribute("my-attribute"), "")
151 | assert.equal(document.querySelector("#element2").getAttribute("my-attribute"), "")
152 | assert.equal(document.querySelector("#element3").getAttribute("my-attribute"), "")
153 | })
154 |
155 | it('should override previous attribute value with missing "value" attribute', async () => {
156 | await fixture(html`
157 |
158 |
159 |
160 | `)
161 |
162 | assert.equal(document.querySelector("#element1").getAttribute("my-attribute"), "previous-value")
163 | assert.equal(document.querySelector("#element2").getAttribute("my-attribute"), "previous-value")
164 | assert.equal(document.querySelector("#element3").getAttribute("my-attribute"), "previous-value")
165 |
166 | await executeStream('')
167 |
168 | assert.equal(document.querySelector("#element1").getAttribute("my-attribute"), "")
169 | assert.equal(document.querySelector("#element2").getAttribute("my-attribute"), "")
170 | assert.equal(document.querySelector("#element3").getAttribute("my-attribute"), "")
171 | })
172 | })
173 | })
174 |
--------------------------------------------------------------------------------
/test/attributes/set_dataset_attribute.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { html, fixture, assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("set_dataset_attribute")
6 |
7 | describe("set_dataset_attribute", () => {
8 | context("warnings", () => {
9 | afterEach(() => {
10 | sinon.restore()
11 | })
12 |
13 | it("should do nothing and print warning if attribute is empty", async () => {
14 | const fake = sinon.replace(console, "warn", sinon.fake())
15 | const expectedWarning = '[TurboPower] no "attribute" provided for Turbo Streams operation "set_dataset_attribute"'
16 |
17 | await fixture('')
18 |
19 | assert.equal(fake.callCount, 0)
20 |
21 | await executeStream('')
22 |
23 | assert.equal(fake.callCount, 1)
24 | assert.equal(fake.firstArg, expectedWarning)
25 | })
26 |
27 | it('should do nothing and print warning if "attribute" attribute is missing', async () => {
28 | const fake = sinon.replace(console, "warn", sinon.fake())
29 | const expectedWarning = '[TurboPower] no "attribute" provided for Turbo Streams operation "set_dataset_attribute"'
30 |
31 | await fixture('')
32 |
33 | assert.equal(fake.callCount, 0)
34 |
35 | await executeStream('')
36 |
37 | assert.equal(fake.callCount, 1)
38 | assert.equal(fake.firstArg, expectedWarning)
39 | })
40 | })
41 |
42 | context("target", () => {
43 | it("should set data attribute with value", async () => {
44 | await fixture('')
45 |
46 | assert.equal(document.querySelector("#element").dataset.attribute, null)
47 |
48 | await executeStream(
49 | '',
50 | )
51 |
52 | assert.equal(document.querySelector("#element").dataset.attribute, "my-value")
53 | })
54 |
55 | it("should set data attribute with empty value", async () => {
56 | await fixture('')
57 |
58 | assert.equal(document.querySelector("#element").dataset.attribute, "previous-value")
59 |
60 | await executeStream(
61 | '',
62 | )
63 |
64 | assert.equal(document.querySelector("#element").dataset.attribute, "")
65 | })
66 |
67 | it("should override previous data attribute value with empty value", async () => {
68 | await fixture(html` `)
69 |
70 | assert.equal(document.querySelector("#element").dataset.attribute, "previous-value")
71 |
72 | await executeStream(
73 | '',
74 | )
75 |
76 | assert.equal(document.querySelector("#element").dataset.attribute, "")
77 | })
78 |
79 | it('should override previous data attribute value with missing "value" attribute', async () => {
80 | await fixture(html` `)
81 |
82 | assert.equal(document.querySelector("#element").dataset.attribute, "previous-value")
83 |
84 | await executeStream(
85 | '',
86 | )
87 |
88 | assert.equal(document.querySelector("#element").dataset.attribute, "")
89 | })
90 |
91 | it("should set camel cased data attribute", async () => {
92 | await fixture('')
93 |
94 | assert.equal(document.querySelector("#element").dataset.camelcasedAttribute, null)
95 |
96 | await executeStream(
97 | '',
98 | )
99 |
100 | assert.equal(document.querySelector("#element").dataset.camelcasedAttribute, "camelcasedValue")
101 | })
102 |
103 | it("should set dashed data attribute", async () => {
104 | await fixture('')
105 |
106 | assert.equal(document.querySelector("#element").dataset.dashedAttribute, null)
107 |
108 | await executeStream(
109 | '',
110 | )
111 |
112 | assert.equal(document.querySelector("#element").dataset.dashedAttribute, "dashed-value")
113 | })
114 | })
115 |
116 | context("targets", () => {
117 | it("should set data attribute with value", async () => {
118 | await fixture(html`
119 |
120 |
121 |
122 | `)
123 |
124 | assert.equal(document.querySelector("#element1").dataset.attribute, null)
125 | assert.equal(document.querySelector("#element2").dataset.attribute, null)
126 | assert.equal(document.querySelector("#element3").dataset.attribute, null)
127 |
128 | await executeStream(
129 | '',
130 | )
131 |
132 | assert.equal(document.querySelector("#element1").dataset.attribute, "my-value")
133 | assert.equal(document.querySelector("#element2").dataset.attribute, "my-value")
134 | assert.equal(document.querySelector("#element3").dataset.attribute, "my-value")
135 | })
136 |
137 | it("should set data attribute with empty value", async () => {
138 | await fixture(html`
139 |
140 |
141 |
142 | `)
143 |
144 | assert.equal(document.querySelector("#element1").dataset.attribute, "previous-value")
145 | assert.equal(document.querySelector("#element2").dataset.attribute, "previous-value")
146 | assert.equal(document.querySelector("#element3").dataset.attribute, "previous-value")
147 |
148 | await executeStream(
149 | '',
150 | )
151 |
152 | assert.equal(document.querySelector("#element1").dataset.attribute, "")
153 | assert.equal(document.querySelector("#element2").dataset.attribute, "")
154 | assert.equal(document.querySelector("#element3").dataset.attribute, "")
155 | })
156 |
157 | it("should override previous data attribute value with empty value", async () => {
158 | await fixture(html`
159 |
160 |
161 |
162 | `)
163 |
164 | assert.equal(document.querySelector("#element1").dataset.attribute, "previous-value")
165 | assert.equal(document.querySelector("#element2").dataset.attribute, "previous-value")
166 | assert.equal(document.querySelector("#element3").dataset.attribute, "previous-value")
167 |
168 | await executeStream(
169 | '',
170 | )
171 |
172 | assert.equal(document.querySelector("#element1").dataset.attribute, "")
173 | assert.equal(document.querySelector("#element2").dataset.attribute, "")
174 | assert.equal(document.querySelector("#element3").dataset.attribute, "")
175 | })
176 |
177 | it('should override previous data attribute value with missing "value" attribute', async () => {
178 | await fixture(html`
179 |
180 |
181 |
182 | `)
183 |
184 | assert.equal(document.querySelector("#element1").dataset.attribute, "previous-value")
185 | assert.equal(document.querySelector("#element2").dataset.attribute, "previous-value")
186 | assert.equal(document.querySelector("#element3").dataset.attribute, "previous-value")
187 |
188 | await executeStream(
189 | '',
190 | )
191 |
192 | assert.equal(document.querySelector("#element1").dataset.attribute, "")
193 | assert.equal(document.querySelector("#element2").dataset.attribute, "")
194 | assert.equal(document.querySelector("#element3").dataset.attribute, "")
195 | })
196 |
197 | it("should set camel cased data attribute", async () => {
198 | await fixture(html`
199 |
200 |
201 |
202 | `)
203 |
204 | assert.equal(document.querySelector("#element1").dataset.camelcasedAttribute, null)
205 | assert.equal(document.querySelector("#element2").dataset.camelcasedAttribute, null)
206 | assert.equal(document.querySelector("#element3").dataset.camelcasedAttribute, null)
207 |
208 | await executeStream(
209 | '',
210 | )
211 |
212 | assert.equal(document.querySelector("#element1").dataset.camelcasedAttribute, "camelcasedValue")
213 | assert.equal(document.querySelector("#element2").dataset.camelcasedAttribute, "camelcasedValue")
214 | assert.equal(document.querySelector("#element3").dataset.camelcasedAttribute, "camelcasedValue")
215 | })
216 |
217 | it("should set dashed data attribute", async () => {
218 | await fixture(html`
219 |
220 |
221 |
222 | `)
223 |
224 | assert.equal(document.querySelector("#element1").dataset.dashedAttribute, null)
225 | assert.equal(document.querySelector("#element2").dataset.dashedAttribute, null)
226 | assert.equal(document.querySelector("#element3").dataset.dashedAttribute, null)
227 |
228 | await executeStream(
229 | '',
230 | )
231 |
232 | assert.equal(document.querySelector("#element1").dataset.dashedAttribute, "dashed-value")
233 | assert.equal(document.querySelector("#element2").dataset.dashedAttribute, "dashed-value")
234 | assert.equal(document.querySelector("#element3").dataset.dashedAttribute, "dashed-value")
235 | })
236 | })
237 | })
238 |
--------------------------------------------------------------------------------
/test/attributes/set_property.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { fixture, assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("set_property")
6 |
7 | describe("set_property", () => {
8 | context("errors", () => {
9 | afterEach(() => {
10 | sinon.restore()
11 | })
12 |
13 | it('should do nothing and print warning if "name" attribute is empty', async () => {
14 | const fake = sinon.replace(console, "error", sinon.fake())
15 | const expectedError = '[TurboPower] no "name" provided for Turbo Streams operation "set_property"'
16 |
17 | await fixture('')
18 |
19 | assert.equal(fake.callCount, 0)
20 |
21 | await executeStream('')
22 |
23 | assert.equal(fake.callCount, 1)
24 | assert.equal(fake.firstArg, expectedError)
25 | })
26 |
27 | it('should do nothing and print warning if "name" attribute is missing', async () => {
28 | const fake = sinon.replace(console, "error", sinon.fake())
29 | const expectedError = '[TurboPower] no "name" provided for Turbo Streams operation "set_property"'
30 |
31 | await fixture('')
32 |
33 | assert.equal(fake.callCount, 0)
34 |
35 | await executeStream('')
36 |
37 | assert.equal(fake.callCount, 1)
38 | assert.equal(fake.firstArg, expectedError)
39 | })
40 | })
41 |
42 | context("application", () => {
43 | it("should check a checkbox and then uncheck it", async () => {
44 | await fixture('')
45 |
46 | assert.equal(document.querySelector("#element").checked, false)
47 |
48 | await executeStream(
49 | '',
50 | )
51 |
52 | assert.equal(document.querySelector("#element").checked, true)
53 |
54 | await executeStream(
55 | '',
56 | )
57 |
58 | assert.equal(document.querySelector("#element").checked, false)
59 | })
60 |
61 | it("should set the title and then blank it out", async () => {
62 | await fixture('link')
63 |
64 | assert.equal(document.querySelector("#element").title, "")
65 |
66 | const value = "test title for an A tag"
67 | await executeStream(
68 | ``,
69 | )
70 |
71 | assert.equal(document.querySelector("#element").title, value)
72 |
73 | await executeStream('')
74 |
75 | assert.equal(document.querySelector("#element").title, "")
76 | })
77 | })
78 | })
79 |
--------------------------------------------------------------------------------
/test/attributes/set_value.test.js:
--------------------------------------------------------------------------------
1 | import { html, fixture, assert } from "@open-wc/testing"
2 | import { executeStream, registerAction } from "../test_helpers"
3 |
4 | registerAction("set_value")
5 |
6 | describe("set_value", () => {
7 | context("target", () => {
8 | it("should set value", async () => {
9 | await fixture('')
10 |
11 | assert.equal(document.querySelector("#input").value, "")
12 |
13 | await executeStream('')
14 |
15 | assert.equal(document.querySelector("#input").value, "my-value")
16 | })
17 |
18 | it("should set attribute with empty value", async () => {
19 | await fixture('')
20 |
21 | assert.equal(document.querySelector("#input").value, "previous-value")
22 |
23 | await executeStream('')
24 |
25 | assert.equal(document.querySelector("#input").value, "")
26 | })
27 |
28 | it("should override previous value with empty value", async () => {
29 | await fixture('')
30 |
31 | assert.equal(document.querySelector("#input").value, "previous-value")
32 |
33 | await executeStream('')
34 |
35 | assert.equal(document.querySelector("#input").value, "")
36 | })
37 |
38 | it('should override previous value with missing "value" attribute', async () => {
39 | await fixture(html``)
40 |
41 | assert.equal(document.querySelector("#input").value, "previous-value")
42 |
43 | await executeStream('')
44 |
45 | assert.equal(document.querySelector("#input").value, "")
46 | })
47 | })
48 |
49 | context("targets", () => {
50 | it("should set value", async () => {
51 | await fixture(html`
52 |
53 |
54 |
55 | `)
56 |
57 | assert.equal(document.querySelector("#input1").value, "")
58 | assert.equal(document.querySelector("#input2").value, "")
59 | assert.equal(document.querySelector("#input3").value, "")
60 |
61 | await executeStream('')
62 |
63 | assert.equal(document.querySelector("#input1").value, "my-value")
64 | assert.equal(document.querySelector("#input2").value, "my-value")
65 | assert.equal(document.querySelector("#input3").value, "my-value")
66 | })
67 |
68 | it("should set attribute with empty value", async () => {
69 | await fixture(html`
70 |
71 |
72 |
73 | `)
74 |
75 | assert.equal(document.querySelector("#input1").value, "previous-value")
76 | assert.equal(document.querySelector("#input2").value, "previous-value")
77 | assert.equal(document.querySelector("#input3").value, "previous-value")
78 |
79 | await executeStream('')
80 |
81 | assert.equal(document.querySelector("#input1").value, "")
82 | assert.equal(document.querySelector("#input2").value, "")
83 | assert.equal(document.querySelector("#input3").value, "")
84 | })
85 |
86 | it("should override previous value with empty value", async () => {
87 | await fixture(html`
88 |
89 |
90 |
91 | `)
92 |
93 | assert.equal(document.querySelector("#input1").value, "previous-value")
94 | assert.equal(document.querySelector("#input2").value, "previous-value")
95 | assert.equal(document.querySelector("#input3").value, "previous-value")
96 |
97 | await executeStream('')
98 |
99 | assert.equal(document.querySelector("#input1").value, "")
100 | assert.equal(document.querySelector("#input2").value, "")
101 | assert.equal(document.querySelector("#input3").value, "")
102 | })
103 |
104 | it('should override previous value with missing "value" attribute', async () => {
105 | await fixture(html`
106 |
107 |
108 |
109 | `)
110 |
111 | assert.equal(document.querySelector("#input1").value, "previous-value")
112 | assert.equal(document.querySelector("#input2").value, "previous-value")
113 | assert.equal(document.querySelector("#input3").value, "previous-value")
114 |
115 | await executeStream('')
116 |
117 | assert.equal(document.querySelector("#input1").value, "")
118 | assert.equal(document.querySelector("#input2").value, "")
119 | assert.equal(document.querySelector("#input3").value, "")
120 | })
121 | })
122 | })
123 |
--------------------------------------------------------------------------------
/test/attributes/toggle_attribute.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { html, fixture, assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("toggle_attribute")
6 |
7 | describe("toggle_attribute", () => {
8 | context("warnings", () => {
9 | afterEach(() => {
10 | sinon.restore()
11 | })
12 |
13 | it("should do nothing and print warning if attribute is empty", async () => {
14 | const fake = sinon.replace(console, "warn", sinon.fake())
15 | const expectedWarning = '[TurboPower] no "attribute" provided for Turbo Streams operation "toggle_attribute"'
16 |
17 | await fixture('')
18 |
19 | assert.equal(fake.callCount, 0)
20 |
21 | await executeStream('')
22 |
23 | assert.equal(fake.callCount, 1)
24 | assert.equal(fake.firstArg, expectedWarning)
25 | })
26 |
27 | it('should do nothing and print warning if "attribute" attribute is missing', async () => {
28 | const fake = sinon.replace(console, "warn", sinon.fake())
29 | const expectedWarning = '[TurboPower] no "attribute" provided for Turbo Streams operation "toggle_attribute"'
30 |
31 | await fixture('')
32 |
33 | assert.equal(fake.callCount, 0)
34 |
35 | await executeStream('')
36 |
37 | assert.equal(fake.callCount, 1)
38 | assert.equal(fake.firstArg, expectedWarning)
39 | })
40 | })
41 |
42 | context("target", () => {
43 | it("adds the attribute if not present", async () => {
44 | const element = await fixture(html``)
45 | assert.isFalse(element.hasAttribute("my-attribute"))
46 | await executeStream(
47 | '',
48 | )
49 | assert.isTrue(element.hasAttribute("my-attribute"))
50 | })
51 |
52 | it("removes attribute if present", async () => {
53 | const element = await fixture(html``)
54 | assert.isTrue(element.hasAttribute("my-attribute"))
55 | await executeStream(
56 | '',
57 | )
58 | assert.isFalse(element.hasAttribute("my-attribute"))
59 | })
60 |
61 | it("should add attribute if force=true and attribute not present", async () => {
62 | const element = await fixture(html``)
63 | assert.isFalse(element.hasAttribute("my-attribute"))
64 | await executeStream(
65 | '',
66 | )
67 | assert.isTrue(element.hasAttribute("my-attribute"))
68 | })
69 |
70 | it("should keep attribute if force=true", async () => {
71 | const element = await fixture(html``)
72 | assert.isTrue(element.hasAttribute("my-attribute"))
73 | await executeStream(
74 | '',
75 | )
76 | assert.isTrue(element.hasAttribute("my-attribute"))
77 | })
78 |
79 | it("should remove attribute if force=false and attribute present", async () => {
80 | const element = await fixture(html``)
81 | assert.isTrue(element.hasAttribute("my-attribute"))
82 | await executeStream(
83 | '',
84 | )
85 | assert.isFalse(element.hasAttribute("my-attribute"))
86 | })
87 |
88 | it("should not add attribute if force=false and attribute not present", async () => {
89 | const element = await fixture(html``)
90 | assert.isFalse(element.hasAttribute("my-attribute"))
91 | await executeStream(
92 | '',
93 | )
94 | assert.isFalse(element.hasAttribute("my-attribute"))
95 | })
96 | })
97 |
98 | context("targets", () => {
99 | it("adds the attribute if not present", async () => {
100 | const container = await fixture(html`
101 |
106 | `)
107 | await executeStream(
108 | '',
109 | )
110 | const elements = container.querySelectorAll("div")
111 | elements.forEach((element) => {
112 | assert.isTrue(element.hasAttribute("my-attribute"))
113 | })
114 | })
115 |
116 | it("removes attribute if present", async () => {
117 | const container = await fixture(html`
118 |
123 | `)
124 | await executeStream(
125 | '',
126 | )
127 | const elements = container.querySelectorAll("div")
128 | elements.forEach((element) => {
129 | assert.isFalse(element.hasAttribute("my-attribute"))
130 | })
131 | })
132 |
133 | it("should act as add attribute if force=true", async () => {
134 | const container = await fixture(html`
135 |
140 | `)
141 | await executeStream(
142 | '',
143 | )
144 | const elements = container.querySelectorAll("div")
145 | elements.forEach((element) => {
146 | assert.isTrue(element.hasAttribute("my-attribute"))
147 | })
148 | })
149 |
150 | it("should act as remove attribute if force=false", async () => {
151 | const container = await fixture(html`
152 |
157 | `)
158 | await executeStream(
159 | '',
160 | )
161 | const elements = container.querySelectorAll("div")
162 | elements.forEach((element) => {
163 | assert.isFalse(element.hasAttribute("my-attribute"))
164 | })
165 | })
166 | })
167 | })
168 |
--------------------------------------------------------------------------------
/test/attributes/toggle_css_class.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { fixture, assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("toggle_css_class")
6 |
7 | describe("toggle_css_class", () => {
8 | context("warnings", () => {
9 | afterEach(() => {
10 | sinon.restore()
11 | })
12 | it("should do nothing and print warning if no classes were provided", async () => {
13 | const fake = sinon.replace(console, "warn", sinon.fake())
14 | const expectedWarning = '[TurboPower] no "classes" provided for Turbo Streams operation "toggle_css_class"'
15 |
16 | await fixture('')
17 |
18 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
19 | assert.equal(fake.callCount, 0)
20 |
21 | await executeStream('')
22 |
23 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
24 | assert.equal(fake.callCount, 1)
25 | assert.equal(fake.firstArg, expectedWarning)
26 | })
27 |
28 | it('should do nothing and print warning if "classes" attribute is missing', async () => {
29 | const fake = sinon.replace(console, "warn", sinon.fake())
30 | const expectedWarning = '[TurboPower] no "classes" provided for Turbo Streams operation "toggle_css_class"'
31 |
32 | await fixture('')
33 |
34 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
35 | assert.equal(fake.callCount, 0)
36 |
37 | await executeStream('')
38 |
39 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
40 | assert.equal(fake.callCount, 1)
41 | assert.equal(fake.firstArg, expectedWarning)
42 | })
43 | })
44 |
45 | context("target", () => {
46 | it("should toggle one css class", async () => {
47 | await fixture('')
48 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
49 |
50 | await executeStream('')
51 | assert.equal(document.querySelector("#element").getAttribute("class"), "one")
52 |
53 | await executeStream('')
54 | assert.equal(document.querySelector("#element").getAttribute("class"), "")
55 | })
56 |
57 | it("should toggle one css class with existing class", async () => {
58 | await fixture('')
59 | assert.equal(document.querySelector("#element").getAttribute("class"), "one")
60 |
61 | await executeStream('')
62 | assert.equal(document.querySelector("#element").getAttribute("class"), "")
63 |
64 | await executeStream('')
65 | assert.equal(document.querySelector("#element").getAttribute("class"), "one")
66 | })
67 |
68 | it("should toggle multiple css classes", async () => {
69 | await fixture('')
70 | assert.equal(document.querySelector("#element").getAttribute("class"), null)
71 |
72 | await executeStream('')
73 | assert.equal(document.querySelector("#element").getAttribute("class"), "one two")
74 |
75 | await executeStream('')
76 | assert.equal(document.querySelector("#element").getAttribute("class"), "")
77 | })
78 |
79 | it("should toggle multiple css classes with existing class", async () => {
80 | await fixture('')
81 | assert.equal(document.querySelector("#element").getAttribute("class"), "one")
82 |
83 | await executeStream('')
84 | assert.equal(document.querySelector("#element").getAttribute("class"), "two")
85 |
86 | await executeStream('')
87 | assert.equal(document.querySelector("#element").getAttribute("class"), "one")
88 | })
89 | })
90 | })
91 |
--------------------------------------------------------------------------------
/test/browser/scroll_into_view.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { assert, fixture, html } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("scroll_into_view")
6 |
7 | describe("scroll_into_view", () => {
8 | afterEach(() => {
9 | sinon.restore()
10 | })
11 |
12 | context("no options", () => {
13 | it("calls scrollIntoView() on element", async () => {
14 | const element = await fixture(html``)
15 | const fake = sinon.replace(element, "scrollIntoView", sinon.fake())
16 |
17 | assert.equal(fake.callCount, 0)
18 |
19 | await executeStream(``)
20 |
21 | assert.equal(fake.callCount, 1)
22 | assert.equal(fake.firstArg, undefined)
23 | })
24 | })
25 |
26 | context("with align-to-top option", () => {
27 | it("calls with true", async () => {
28 | const element = await fixture(html``)
29 | const fake = sinon.replace(element, "scrollIntoView", sinon.fake())
30 |
31 | assert.equal(fake.callCount, 0)
32 |
33 | await executeStream(
34 | ``,
35 | )
36 |
37 | assert.equal(fake.callCount, 1)
38 | assert.equal(fake.firstArg, true)
39 | })
40 |
41 | it("calls with false", async () => {
42 | const element = await fixture(html``)
43 | const fake = sinon.replace(element, "scrollIntoView", sinon.fake())
44 |
45 | assert.equal(fake.callCount, 0)
46 |
47 | await executeStream(
48 | ``,
49 | )
50 |
51 | assert.equal(fake.callCount, 1)
52 | assert.equal(fake.firstArg, false)
53 | })
54 | })
55 |
56 | context("with block option", () => {
57 | it("calls scrollIntoView() on element", async () => {
58 | const element = await fixture(html``)
59 | const fake = sinon.replace(element, "scrollIntoView", sinon.fake())
60 |
61 | assert.equal(fake.callCount, 0)
62 |
63 | await executeStream(``)
64 |
65 | assert.equal(fake.callCount, 1)
66 | assert.deepEqual(fake.firstArg, { block: "end" })
67 | })
68 | })
69 |
70 | context("with inline option", () => {
71 | it("calls scrollIntoView() on element", async () => {
72 | const element = await fixture(html``)
73 | const fake = sinon.replace(element, "scrollIntoView", sinon.fake())
74 |
75 | assert.equal(fake.callCount, 0)
76 |
77 | await executeStream(``)
78 |
79 | assert.equal(fake.callCount, 1)
80 | assert.deepEqual(fake.firstArg, { inline: "nearest" })
81 | })
82 | })
83 |
84 | context("with behavior option", () => {
85 | it("calls scrollIntoView() on element", async () => {
86 | const element = await fixture(html``)
87 | const fake = sinon.replace(element, "scrollIntoView", sinon.fake())
88 |
89 | assert.equal(fake.callCount, 0)
90 |
91 | await executeStream(``)
92 |
93 | assert.equal(fake.callCount, 1)
94 | assert.deepEqual(fake.firstArg, { behavior: "smooth" })
95 | })
96 | })
97 |
98 | context("with block, inline and behavior option", () => {
99 | it("calls scrollIntoView() on element", async () => {
100 | const element = await fixture(html``)
101 | const fake = sinon.replace(element, "scrollIntoView", sinon.fake())
102 |
103 | assert.equal(fake.callCount, 0)
104 |
105 | await executeStream(
106 | ``,
107 | )
108 |
109 | assert.equal(fake.callCount, 1)
110 | assert.deepEqual(fake.firstArg, { behavior: "smooth", block: "end", inline: "nearest" })
111 | })
112 | })
113 |
114 | context("with align-to-top, block, inline and behavior option", () => {
115 | it("uses align-to-top and ignroes remaining options", async () => {
116 | const element = await fixture(html``)
117 | const fake = sinon.replace(element, "scrollIntoView", sinon.fake())
118 |
119 | assert.equal(fake.callCount, 0)
120 |
121 | await executeStream(
122 | ``,
123 | )
124 |
125 | assert.equal(fake.callCount, 1)
126 | assert.equal(fake.firstArg, false)
127 | })
128 | })
129 | })
130 |
--------------------------------------------------------------------------------
/test/browser/set_title.test.js:
--------------------------------------------------------------------------------
1 | import { html, fixture, assert } from "@open-wc/testing"
2 | import { executeStream, registerAction } from "../test_helpers"
3 |
4 | registerAction("set_title")
5 |
6 | describe("set_title", () => {
7 | afterEach(() => {
8 | document.querySelector("title")?.remove()
9 | })
10 |
11 | context("with title element present", () => {
12 | it("should set title", async () => {
13 | await fixture(html`
14 |
15 |
16 | Title
17 |
18 |
19 | `)
20 |
21 | assert.equal(document.querySelector("title").textContent, "Title")
22 |
23 | await executeStream('')
24 |
25 | assert.equal(document.querySelector("title").textContent, "My Title")
26 | })
27 |
28 | it('should set title with empty "title" attribute', async () => {
29 | await fixture(html`
30 |
31 |
32 | Title
33 |
34 |
35 | `)
36 |
37 | assert.equal(document.querySelector("title").textContent, "Title")
38 |
39 | await executeStream('')
40 |
41 | assert.equal(document.querySelector("title").textContent, "")
42 | })
43 |
44 | it('should set empty title with missing "title" attribute', async () => {
45 | await fixture(html`
46 |
47 |
48 | Title
49 |
50 |
51 | `)
52 |
53 | assert.equal(document.querySelector("title").textContent, "Title")
54 |
55 | await executeStream('')
56 |
57 | assert.equal(document.querySelector("title").textContent, "")
58 | })
59 | })
60 |
61 | context("with no title element", () => {
62 | it("should set title", async () => {
63 | await fixture(html`
64 |
65 |
66 |
67 | `)
68 |
69 | assert.equal(document.querySelector("title"), null)
70 |
71 | await executeStream('')
72 |
73 | assert.equal(document.querySelector("title").textContent, "My Title")
74 | })
75 |
76 | it('should set title with empty "title" attribute', async () => {
77 | await fixture(html`
78 |
79 |
80 |
81 | `)
82 |
83 | assert.equal(document.querySelector("title"), null)
84 |
85 | await executeStream('')
86 |
87 | assert.equal(document.querySelector("title").textContent, "")
88 | })
89 |
90 | it('should set empty title with missing "title" attribute', async () => {
91 | await fixture(html`
92 |
93 |
94 |
95 | `)
96 |
97 | assert.equal(document.querySelector("title"), null)
98 |
99 | await executeStream('')
100 |
101 | assert.equal(document.querySelector("title").textContent, "")
102 | })
103 | })
104 | })
105 |
--------------------------------------------------------------------------------
/test/debug/console_log.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("console_log")
6 |
7 | describe("console_log", () => {
8 | afterEach(() => {
9 | sinon.restore()
10 | })
11 |
12 | context("no `log` level", () => {
13 | it("should log message", async () => {
14 | const fake = sinon.replace(console, "log", sinon.fake())
15 |
16 | await executeStream(``)
17 |
18 | assert.equal(fake.firstArg, "Message")
19 | })
20 | })
21 |
22 | context('with level="log"', () => {
23 | it("should log message", async () => {
24 | const fake = sinon.replace(console, "log", sinon.fake())
25 |
26 | await executeStream(``)
27 |
28 | assert.equal(fake.firstArg, "Message")
29 | })
30 | })
31 |
32 | context('with level="warn"', () => {
33 | it("should log message", async () => {
34 | const fake = sinon.replace(console, "warn", sinon.fake())
35 |
36 | await executeStream(``)
37 |
38 | assert.equal(fake.firstArg, "Warning")
39 | })
40 | })
41 |
42 | context('with level="info"', () => {
43 | it("should log message", async () => {
44 | const fake = sinon.replace(console, "info", sinon.fake())
45 |
46 | await executeStream(``)
47 |
48 | assert.equal(fake.firstArg, "Information")
49 | })
50 | })
51 |
52 | context('with level="debug"', () => {
53 | it("should log message", async () => {
54 | const fake = sinon.replace(console, "debug", sinon.fake())
55 |
56 | await executeStream(``)
57 |
58 | assert.equal(fake.firstArg, "Debug")
59 | })
60 | })
61 |
62 | context('with level="error"', () => {
63 | it("should log message", async () => {
64 | const fake = sinon.replace(console, "error", sinon.fake())
65 |
66 | await executeStream(``)
67 |
68 | assert.equal(fake.firstArg, "Error")
69 | })
70 | })
71 | })
72 |
--------------------------------------------------------------------------------
/test/deprecated/invoke.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("invoke")
6 |
7 | describe("invoke", () => {
8 | it("show deprecation warning", async () => {
9 | const fake = sinon.replace(console, "warn", sinon.fake())
10 | const expectedWarning =
11 | "[TurboPower] The `invoke` Turbo Stream Action was removed from TurboPower. If you'd like to continue using this action please use the successor library instead. Read more here: https://github.com/hopsoft/turbo_boost-streams"
12 |
13 | assert.equal(fake.callCount, 0)
14 |
15 | await executeStream('')
16 |
17 | assert.equal(fake.callCount, 1)
18 | assert.equal(fake.firstArg, expectedWarning)
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/test/document/set_cookie.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("set_cookie")
6 |
7 | function documentCookieFake() {
8 | return sinon.replaceSetter(document, "cookie", sinon.fake())
9 | }
10 |
11 | describe("set_cookie", () => {
12 | afterEach(() => {
13 | sinon.restore()
14 | })
15 |
16 | it("should set the cookie", async () => {
17 | const fake = documentCookieFake()
18 |
19 | await executeStream(
20 | ``,
23 | )
24 |
25 | assert.equal(fake.firstArg, "foo=bar")
26 | assert.equal(fake.callCount, 1)
27 | })
28 |
29 | it("should set the cookie even with invalid values", async () => {
30 | const fake = documentCookieFake()
31 |
32 | await executeStream(
33 | ``,
36 | )
37 |
38 | assert.equal(fake.firstArg, "foo=bar; whatever;")
39 | assert.equal(fake.callCount, 1)
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/test/document/set_cookie_item.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("set_cookie_item")
6 |
7 | function documentCookieFake() {
8 | return sinon.replaceSetter(document, "cookie", sinon.fake())
9 | }
10 |
11 | describe("set_cookie_item", () => {
12 | afterEach(() => {
13 | sinon.restore()
14 | })
15 |
16 | it("should set the key value pair", async () => {
17 | const fake = documentCookieFake()
18 |
19 | await executeStream(
20 | ``,
24 | )
25 |
26 | assert.equal(fake.firstArg, "foo=bar")
27 | assert.equal(fake.callCount, 1)
28 | })
29 |
30 | it("should set Domain", async () => {
31 | const fake = documentCookieFake()
32 |
33 | await executeStream(
34 | ``,
39 | )
40 |
41 | assert.equal(fake.firstArg, "foo=bar; Domain=example.com")
42 | assert.equal(fake.callCount, 1)
43 | })
44 |
45 | it("should set Path", async () => {
46 | const fake = documentCookieFake()
47 |
48 | await executeStream(
49 | ``,
54 | )
55 |
56 | assert.equal(fake.firstArg, "foo=bar; Path=/path")
57 | assert.equal(fake.callCount, 1)
58 | })
59 |
60 | it("should set Expires", async () => {
61 | const fake = documentCookieFake()
62 |
63 | await executeStream(
64 | ``,
69 | )
70 |
71 | assert.equal(fake.firstArg, "foo=bar; Expires=Thu, 31 Oct 2021 07:28:00 GMT")
72 | assert.equal(fake.callCount, 1)
73 | })
74 |
75 | it("should set Max-Age", async () => {
76 | const fake = documentCookieFake()
77 |
78 | await executeStream(
79 | ``,
84 | )
85 |
86 | assert.equal(fake.firstArg, "foo=bar; Max-Age=75")
87 | assert.equal(fake.callCount, 1)
88 | })
89 |
90 | it("should set HTTPOnly", async () => {
91 | const fake = documentCookieFake()
92 |
93 | await executeStream(
94 | ``,
99 | )
100 |
101 | assert.equal(fake.firstArg, "foo=bar; HttpOnly")
102 | assert.equal(fake.callCount, 1)
103 | })
104 |
105 | it("should set Secure", async () => {
106 | const fake = documentCookieFake()
107 |
108 | await executeStream(
109 | ``,
114 | )
115 |
116 | assert.equal(fake.firstArg, "foo=bar; Secure")
117 | assert.equal(fake.callCount, 1)
118 | })
119 |
120 | it("should set SameSite", async () => {
121 | const fake = documentCookieFake()
122 |
123 | await executeStream(
124 | ``,
129 | )
130 |
131 | assert.equal(fake.firstArg, "foo=bar; SameSite=strict")
132 | assert.equal(fake.callCount, 1)
133 | })
134 |
135 | it("should set everything", async () => {
136 | const fake = documentCookieFake()
137 |
138 | await executeStream(
139 | ``,
151 | )
152 |
153 | assert.equal(
154 | fake.firstArg,
155 | "foo=bar; Domain=example.com; Path=/path; Expires=Thu, 31 Oct 2021 07:28:00 GMT; Max-Age=15; HttpOnly; Secure; SameSite=strict",
156 | )
157 | assert.equal(fake.callCount, 1)
158 | })
159 | })
160 |
--------------------------------------------------------------------------------
/test/events/dispatch_event.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { html, fixture, assert, oneEvent } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("dispatch_event")
6 |
7 | describe("dispatch_event", () => {
8 | context("warnings", () => {
9 | afterEach(() => {
10 | sinon.restore()
11 | })
12 |
13 | it("should do nothing and print warning if attribute is empty", async () => {
14 | const fake = sinon.replace(console, "warn", sinon.fake())
15 |
16 | await fixture('')
17 |
18 | assert.equal(fake.callCount, 0)
19 |
20 | await executeStream('')
21 |
22 | assert.equal(fake.callCount, 1)
23 | assert.equal(fake.firstArg, '[TurboPower] no "name" provided for Turbo Streams operation "dispatch_event"')
24 | })
25 |
26 | it('should do nothing and print warning if "attribute" attribute is missing', async () => {
27 | const fake = sinon.replace(console, "warn", sinon.fake())
28 |
29 | await fixture('')
30 |
31 | assert.equal(fake.callCount, 0)
32 |
33 | await executeStream('')
34 |
35 | assert.equal(fake.callCount, 1)
36 | assert.equal(fake.firstArg, '[TurboPower] no "name" provided for Turbo Streams operation "dispatch_event"')
37 | })
38 |
39 | it("should error out when detail is not valid json", async () => {
40 | const fake = sinon.replace(console, "error", sinon.fake())
41 |
42 | await fixture('')
43 |
44 | assert.equal(fake.callCount, 0)
45 |
46 | await executeStream(
47 | '{ this is not valid }',
48 | )
49 |
50 | assert.equal(fake.callCount, 1)
51 | assert.equal(
52 | fake.firstArg,
53 | `[TurboPower] error proccessing provided "detail" in "" ("{ this is not valid }") for Turbo Streams operation "dispatch_event".`,
54 | )
55 | })
56 | })
57 |
58 | context("target", () => {
59 | it("should dispatch event", async () => {
60 | const element = await fixture('')
61 |
62 | setTimeout(() =>
63 | executeStream(''),
64 | )
65 |
66 | const { detail } = await oneEvent(element, "my:event")
67 |
68 | assert.deepEqual(detail, {})
69 | })
70 |
71 | it("should dispatch event with empty detail", async () => {
72 | const element = await fixture('')
73 |
74 | setTimeout(() =>
75 | executeStream(
76 | '',
77 | ),
78 | )
79 |
80 | const { detail } = await oneEvent(element, "my:event")
81 |
82 | assert.deepEqual(detail, {})
83 | })
84 |
85 | it("should dispatch event with empty object in detail", async () => {
86 | const element = await fixture('')
87 |
88 | setTimeout(() =>
89 | executeStream(
90 | '{}',
91 | ),
92 | )
93 |
94 | const { detail } = await oneEvent(element, "my:event")
95 |
96 | assert.deepEqual(detail, {})
97 | })
98 |
99 | it("should dispatch event with detail", async () => {
100 | const element = await fixture('')
101 |
102 | const eventDetail = '{"number":1,"string":"string value","nested":{"deep":"value","boolean":true}}'
103 |
104 | setTimeout(() =>
105 | executeStream(
106 | `${eventDetail}`,
107 | ),
108 | )
109 |
110 | const { detail } = await oneEvent(element, "my:event")
111 |
112 | assert.deepEqual(detail, {
113 | number: 1,
114 | string: "string value",
115 | nested: {
116 | deep: "value",
117 | boolean: true,
118 | },
119 | })
120 | })
121 |
122 | it("should bubble up the DOM tree", async () => {
123 | await fixture(html`
124 |
129 | `)
130 |
131 | const outerParent = document.querySelector("#outer-parent")
132 |
133 | setTimeout(() =>
134 | executeStream(''),
135 | )
136 |
137 | const { detail } = await oneEvent(outerParent, "my:event")
138 |
139 | assert.deepEqual(detail, {})
140 | })
141 | })
142 |
143 | context("targets", () => {
144 | it("should dispatch event", async () => {
145 | await fixture(html`
146 |
147 |
148 |
149 | `)
150 |
151 | const element1 = document.querySelector("#element1")
152 | const element2 = document.querySelector("#element2")
153 | const element3 = document.querySelector("#element3")
154 |
155 | setTimeout(() =>
156 | executeStream(''),
157 | )
158 |
159 | const listener1 = oneEvent(element1, "my:event")
160 | const listener2 = oneEvent(element2, "my:event")
161 | const listener3 = oneEvent(element3, "my:event")
162 |
163 | const [event1, event2, event3] = await Promise.all([listener1, listener2, listener3])
164 |
165 | assert.deepEqual(event1.detail, {})
166 | assert.deepEqual(event2.detail, {})
167 | assert.deepEqual(event3.detail, {})
168 | })
169 |
170 | it("should dispatch event with empty detail", async () => {
171 | await fixture(html`
172 |
173 |
174 |
175 | `)
176 |
177 | const element1 = document.querySelector("#element1")
178 | const element2 = document.querySelector("#element2")
179 | const element3 = document.querySelector("#element3")
180 |
181 | setTimeout(() =>
182 | executeStream(
183 | '',
184 | ),
185 | )
186 |
187 | const listener1 = oneEvent(element1, "my:event")
188 | const listener2 = oneEvent(element2, "my:event")
189 | const listener3 = oneEvent(element3, "my:event")
190 |
191 | const [event1, event2, event3] = await Promise.all([listener1, listener2, listener3])
192 |
193 | assert.deepEqual(event1.detail, {})
194 | assert.deepEqual(event2.detail, {})
195 | assert.deepEqual(event3.detail, {})
196 | })
197 |
198 | it("should dispatch event with empty object in detail", async () => {
199 | await fixture(html`
200 |
201 |
202 |
203 | `)
204 |
205 | const element1 = document.querySelector("#element1")
206 | const element2 = document.querySelector("#element2")
207 | const element3 = document.querySelector("#element3")
208 |
209 | setTimeout(() =>
210 | executeStream(
211 | '{}',
212 | ),
213 | )
214 |
215 | const listener1 = oneEvent(element1, "my:event")
216 | const listener2 = oneEvent(element2, "my:event")
217 | const listener3 = oneEvent(element3, "my:event")
218 |
219 | const [event1, event2, event3] = await Promise.all([listener1, listener2, listener3])
220 |
221 | assert.deepEqual(event1.detail, {})
222 | assert.deepEqual(event2.detail, {})
223 | assert.deepEqual(event3.detail, {})
224 | })
225 |
226 | it("should dispatch event with detail", async () => {
227 | await fixture(html`
228 |
229 |
230 |
231 | `)
232 |
233 | const element1 = document.querySelector("#element1")
234 | const element2 = document.querySelector("#element2")
235 | const element3 = document.querySelector("#element3")
236 |
237 | const eventDetail = '{"number":1,"string":"string value","nested":{"deep":"value","boolean":true}}'
238 | const expectedDetail = {
239 | number: 1,
240 | string: "string value",
241 | nested: {
242 | deep: "value",
243 | boolean: true,
244 | },
245 | }
246 |
247 | setTimeout(() =>
248 | executeStream(
249 | `${eventDetail}`,
250 | ),
251 | )
252 |
253 | const listener1 = oneEvent(element1, "my:event")
254 | const listener2 = oneEvent(element2, "my:event")
255 | const listener3 = oneEvent(element3, "my:event")
256 |
257 | const [event1, event2, event3] = await Promise.all([listener1, listener2, listener3])
258 |
259 | assert.deepEqual(event1.detail, expectedDetail)
260 | assert.deepEqual(event2.detail, expectedDetail)
261 | assert.deepEqual(event3.detail, expectedDetail)
262 | })
263 | })
264 | })
265 |
--------------------------------------------------------------------------------
/test/fixtures/frame1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Frame 1
4 |
5 |
8 |
9 |
--------------------------------------------------------------------------------
/test/fixtures/frame2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Frame 2
4 |
5 |
--------------------------------------------------------------------------------
/test/fixtures/page1.html:
--------------------------------------------------------------------------------
1 | Page 1
--------------------------------------------------------------------------------
/test/fixtures/page2.html:
--------------------------------------------------------------------------------
1 | Page 2
--------------------------------------------------------------------------------
/test/fixtures/page3.html:
--------------------------------------------------------------------------------
1 | Page 3
--------------------------------------------------------------------------------
/test/form/reset_form.test.js:
--------------------------------------------------------------------------------
1 | import { html, fixture, assert } from "@open-wc/testing"
2 | import { executeStream, registerAction } from "../test_helpers"
3 |
4 | registerAction("reset_form")
5 |
6 | describe("reset_form", () => {
7 | context("target", () => {
8 | it("should reset form", async () => {
9 | await fixture(html`
10 |
14 | `)
15 |
16 | const input = document.querySelector("#input")
17 | const textarea = document.querySelector("#textarea")
18 |
19 | assert.equal(input.value, "")
20 | assert.equal(textarea.value, "")
21 |
22 | input.value = "123"
23 | textarea.value = "Text"
24 |
25 | assert.equal(input.value, "123")
26 | assert.equal(textarea.value, "Text")
27 |
28 | await executeStream('')
29 |
30 | assert.equal(input.value, "")
31 | assert.equal(textarea.value, "")
32 | })
33 | })
34 |
35 | context("targets", () => {
36 | it("should reset forms", async () => {
37 | await fixture(html`
38 |
42 |
43 |
47 | `)
48 |
49 | const input1 = document.querySelector("#input1")
50 | const input2 = document.querySelector("#input2")
51 | const textarea1 = document.querySelector("#textarea1")
52 | const textarea2 = document.querySelector("#textarea2")
53 |
54 | assert.equal(input1.value, "")
55 | assert.equal(input2.value, "456")
56 | assert.equal(textarea1.value, "")
57 | assert.equal(textarea2.value, "Text2")
58 |
59 | input1.value = "123"
60 | input2.value = "789"
61 | textarea1.value = "Text3"
62 | textarea2.value = "Text4"
63 |
64 | assert.equal(input1.value, "123")
65 | assert.equal(input2.value, "789")
66 | assert.equal(textarea1.value, "Text3")
67 | assert.equal(textarea2.value, "Text4")
68 |
69 | await executeStream('')
70 |
71 | assert.equal(input1.value, "")
72 | assert.equal(input2.value, "456")
73 | assert.equal(textarea1.value, "")
74 | assert.equal(textarea2.value, "Text2")
75 | })
76 | })
77 | })
78 |
--------------------------------------------------------------------------------
/test/history/history_back.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("history_back")
6 |
7 | describe("history_back", () => {
8 | afterEach(() => {
9 | sinon.restore()
10 | })
11 |
12 | it("should go back in history", async () => {
13 | const fake = sinon.replace(window.history, "back", sinon.fake())
14 |
15 | assert.equal(fake.callCount, 0)
16 |
17 | await executeStream(``)
18 |
19 | assert.equal(fake.callCount, 1)
20 | assert.equal(fake.firstArg, undefined)
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/test/history/history_forward.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("history_forward")
6 |
7 | describe("history_forward", () => {
8 | afterEach(() => {
9 | sinon.restore()
10 | })
11 |
12 | it("should go forward in history", async () => {
13 | const fake = sinon.replace(window.history, "forward", sinon.fake())
14 |
15 | assert.equal(fake.callCount, 0)
16 |
17 | await executeStream(``)
18 |
19 | assert.equal(fake.callCount, 1)
20 | assert.equal(fake.firstArg, undefined)
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/test/history/push_state.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("push_state")
6 |
7 | describe("push_state", () => {
8 | afterEach(() => {
9 | sinon.restore()
10 | })
11 |
12 | context("all attributes", () => {
13 | it("should push item to history object", async () => {
14 | const fake = sinon.replace(window.history, "pushState", sinon.fake())
15 |
16 | await executeStream(
17 | ``,
18 | )
19 |
20 | assert.equal(fake.callCount, 1)
21 | assert.deepEqual(fake.firstCall.args, ["state", "title", `${window.location.origin}/new-state`])
22 | })
23 | })
24 |
25 | context("title attribute", () => {
26 | it('should push item to history object with missing "title" attribute', async () => {
27 | const fake = sinon.replace(window.history, "pushState", sinon.fake())
28 |
29 | await executeStream(
30 | ``,
31 | )
32 |
33 | assert.equal(fake.callCount, 1)
34 | assert.deepEqual(fake.firstCall.args, ["state", "", `${window.location.origin}/new-state`])
35 | })
36 |
37 | it('should push item to history object with missing empty "title" attribute', async () => {
38 | const fake = sinon.replace(window.history, "pushState", sinon.fake())
39 |
40 | await executeStream(
41 | ``,
42 | )
43 |
44 | assert.equal(fake.callCount, 1)
45 | assert.deepEqual(fake.firstCall.args, ["state", "", `${window.location.origin}/new-state`])
46 | })
47 | })
48 |
49 | context("state attribute", () => {
50 | it('should push item to history object with missing "state" attribute', async () => {
51 | const fake = sinon.replace(window.history, "pushState", sinon.fake())
52 |
53 | await executeStream(
54 | ``,
55 | )
56 |
57 | assert.equal(fake.callCount, 1)
58 | assert.deepEqual(fake.firstCall.args, [null, "title", `${window.location.origin}/new-state`])
59 | })
60 |
61 | it('should push item to history object with missing empty "state" attribute', async () => {
62 | const fake = sinon.replace(window.history, "pushState", sinon.fake())
63 |
64 | await executeStream(
65 | ``,
66 | )
67 |
68 | assert.equal(fake.callCount, 1)
69 | assert.deepEqual(fake.firstCall.args, ["", "title", `${window.location.origin}/new-state`])
70 | })
71 | })
72 |
73 | context("url attribute", () => {
74 | it('should push item to history object with missing "url" attribute', async () => {
75 | const fake = sinon.replace(window.history, "pushState", sinon.fake())
76 |
77 | await executeStream('')
78 |
79 | assert.equal(fake.callCount, 1)
80 | assert.deepEqual(fake.firstCall.args, ["state", "title", null])
81 | })
82 |
83 | it('should push item to history object with missing empty "url" attribute', async () => {
84 | const fake = sinon.replace(window.history, "pushState", sinon.fake())
85 |
86 | await executeStream('')
87 |
88 | assert.equal(fake.callCount, 1)
89 | assert.deepEqual(fake.firstCall.args, ["state", "title", ""])
90 | })
91 | })
92 |
93 | context("no attributes", () => {
94 | it("should push item to history object with no attributes", async () => {
95 | const fake = sinon.replace(window.history, "pushState", sinon.fake())
96 |
97 | await executeStream('')
98 |
99 | assert.equal(fake.callCount, 1)
100 | assert.deepEqual(fake.firstCall.args, [null, "", null])
101 | })
102 |
103 | it("should push item to history object with empty attributes", async () => {
104 | const fake = sinon.replace(window.history, "pushState", sinon.fake())
105 |
106 | await executeStream('')
107 |
108 | assert.equal(fake.callCount, 1)
109 | assert.deepEqual(fake.firstCall.args, ["", "", ""])
110 | })
111 | })
112 | })
113 |
--------------------------------------------------------------------------------
/test/notifications/notification.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("notification")
6 |
7 | describe("notification", () => {
8 | afterEach(() => {
9 | sinon.restore()
10 | })
11 |
12 | context("Notification is not supported", () => {
13 | it("alerts with a message", async () => {
14 | const stub = sinon.stub(window, "Notification").value(undefined)
15 | const mock = sinon
16 | .mock(window)
17 | .expects("alert")
18 | .once()
19 | .withArgs("This browser does not support desktop notification")
20 |
21 | await executeStream(``)
22 | assert.equal(stub.callCount, 0)
23 | mock.verify()
24 | })
25 | })
26 |
27 | context("requestPermission is required", () => {
28 | it("permission is granted", async () => {
29 | const stub = sinon.stub(window, "Notification")
30 | sinon.stub(Notification, "permission").value("default")
31 | sinon.stub(Notification, "requestPermission").resolves("granted")
32 |
33 | await executeStream(``)
34 |
35 | assert.equal(stub.callCount, 1)
36 | })
37 |
38 | it("permission is denied", async () => {
39 | const stub = sinon.stub(window, "Notification")
40 | sinon.stub(Notification, "permission").value("default")
41 | sinon.stub(Notification, "requestPermission").resolves("denied")
42 |
43 | await executeStream(``)
44 |
45 | assert.equal(stub.callCount, 0)
46 | })
47 | })
48 |
49 | context("permission was denied", () => {
50 | it("creates no notification", async () => {
51 | const stub = sinon.stub(window, "Notification")
52 | sinon.stub(Notification, "permission").value("denied")
53 |
54 | await executeStream(``)
55 |
56 | assert.equal(stub.callCount, 0)
57 | })
58 | })
59 |
60 | context("permission was granted", () => {
61 | it("creates the notification with title only", async () => {
62 | const stub = sinon.stub(window, "Notification")
63 | sinon.stub(Notification, "permission").value("granted")
64 |
65 | await executeStream(
66 | ``,
70 | )
71 |
72 | assert.equal(stub.callCount, 1)
73 | assert.equal(stub.firstCall.firstArg, "May I have your attention...")
74 | assert.equal(stub.firstCall.secondArg, null)
75 | })
76 |
77 | it("creates the notification with title and options", async () => {
78 | const stub = sinon.stub(window, "Notification")
79 | sinon.stub(Notification, "permission").value("granted")
80 |
81 | await executeStream(
82 | ``,
99 | )
100 |
101 | assert.equal(stub.callCount, 1)
102 | assert.equal(stub.firstCall.firstArg, "May I have your attention...")
103 | assert.deepEqual(stub.firstCall.args[1], {
104 | dir: "ltr",
105 | lang: "EN",
106 | badge: "https://example.com/badge.png",
107 | body: "This is displayed below the title.",
108 | tag: "Demo",
109 | icon: "https://example.com/icon.png",
110 | image: "https://example.com/image.png",
111 | data: { arbitrary: "data" },
112 | vibrate: [200, 100, 200],
113 | renotify: true,
114 | requireInteraction: true,
115 | actions: [{ action: "respond", title: "Please respond", icon: "https://example.com/icon.png" }],
116 | silent: true,
117 | })
118 | })
119 |
120 | it("creates the notification with title and escaped options", async () => {
121 | const stub = sinon.stub(window, "Notification")
122 | sinon.stub(Notification, "permission").value("granted")
123 |
124 | await executeStream(
125 | ``,
141 | )
142 |
143 | assert.equal(stub.callCount, 1)
144 | assert.equal(stub.firstCall.firstArg, "May I have your attention...")
145 | assert.deepEqual(stub.firstCall.args[1], {
146 | dir: "ltr",
147 | lang: "EN",
148 | badge: "https://example.com/badge.png",
149 | body: "This is displayed below the title.",
150 | tag: "Demo",
151 | icon: "https://example.com/icon.png",
152 | image: "https://example.com/image.png",
153 | data: { arbitrary: "data" },
154 | vibrate: [200, 100, 200],
155 | renotify: true,
156 | requireInteraction: true,
157 | actions: [{ action: "respond", title: "Please respond", icon: "https://example.com/icon.png" }],
158 | silent: true,
159 | })
160 | })
161 | })
162 | })
163 |
--------------------------------------------------------------------------------
/test/storage/clear_storage.test.js:
--------------------------------------------------------------------------------
1 | import { assert } from "@open-wc/testing"
2 | import { executeStream, registerAction } from "../test_helpers"
3 |
4 | registerAction("clear_storage")
5 |
6 | describe("clear_storage", () => {
7 | beforeEach(() => {
8 | localStorage.clear()
9 | sessionStorage.clear()
10 | })
11 |
12 | afterEach(() => {
13 | localStorage.clear()
14 | sessionStorage.clear()
15 | })
16 |
17 | context("localStorage", () => {
18 | it("should clear localStorage with not defined type", async () => {
19 | localStorage.setItem("key10", "value1")
20 | localStorage.setItem("key20", "value2")
21 | sessionStorage.setItem("key30", "value3")
22 |
23 | assert.equal(localStorage.getItem("key10"), "value1")
24 | assert.equal(localStorage.getItem("key20"), "value2")
25 | assert.equal(sessionStorage.getItem("key30"), "value3")
26 |
27 | await executeStream('')
28 |
29 | assert.equal(localStorage.getItem("key10"), null)
30 | assert.equal(localStorage.getItem("key20"), null)
31 | assert.equal(sessionStorage.getItem("key30"), "value3")
32 | })
33 |
34 | it('should clear localStorage with empty "type" attribute', async () => {
35 | localStorage.setItem("key11", "value1")
36 | localStorage.setItem("key22", "value2")
37 | sessionStorage.setItem("key33", "value3")
38 |
39 | assert.equal(localStorage.getItem("key11"), "value1")
40 | assert.equal(localStorage.getItem("key22"), "value2")
41 | assert.equal(sessionStorage.getItem("key33"), "value3")
42 |
43 | await executeStream('')
44 |
45 | assert.equal(localStorage.getItem("key11"), null)
46 | assert.equal(localStorage.getItem("key22"), null)
47 | assert.equal(sessionStorage.getItem("key33"), "value3")
48 | })
49 |
50 | it('should clear localStorage with "type=local"', async () => {
51 | localStorage.setItem("key12", "value1")
52 | localStorage.setItem("key22", "value2")
53 | sessionStorage.setItem("key32", "value3")
54 |
55 | assert.equal(localStorage.getItem("key12"), "value1")
56 | assert.equal(localStorage.getItem("key22"), "value2")
57 | assert.equal(sessionStorage.getItem("key32"), "value3")
58 |
59 | await executeStream('')
60 |
61 | assert.equal(localStorage.getItem("key12"), null)
62 | assert.equal(localStorage.getItem("key22"), null)
63 | assert.equal(sessionStorage.getItem("key32"), "value3")
64 | })
65 | })
66 |
67 | context("sessionStorage", () => {
68 | it('should clear sessionStorage with "type=session"', async () => {
69 | sessionStorage.setItem("key13", "value1")
70 | sessionStorage.setItem("key23", "value2")
71 | localStorage.setItem("key33", "value3")
72 |
73 | assert.equal(sessionStorage.getItem("key13"), "value1")
74 | assert.equal(sessionStorage.getItem("key23"), "value2")
75 | assert.equal(localStorage.getItem("key33"), "value3")
76 |
77 | await executeStream('')
78 |
79 | assert.equal(sessionStorage.getItem("key13"), null)
80 | assert.equal(sessionStorage.getItem("key23"), null)
81 | assert.equal(localStorage.getItem("key33"), "value3")
82 | })
83 | })
84 | })
85 |
--------------------------------------------------------------------------------
/test/storage/remove_storage_item.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("remove_storage_item")
6 |
7 | describe("remove_storage_item", () => {
8 | beforeEach(() => {
9 | localStorage.clear()
10 | sessionStorage.clear()
11 | })
12 |
13 | afterEach(() => {
14 | localStorage.clear()
15 | sessionStorage.clear()
16 | })
17 |
18 | context("warnings", () => {
19 | afterEach(() => {
20 | sinon.restore()
21 | })
22 |
23 | it("should do nothing and print warning if attribute is empty", async () => {
24 | const fake = sinon.replace(console, "warn", sinon.fake())
25 | const expectedWarning = '[TurboPower] no "key" provided for Turbo Streams operation "remove_storage_item"'
26 |
27 | assert.equal(fake.callCount, 0)
28 |
29 | await executeStream('')
30 |
31 | assert.equal(fake.callCount, 1)
32 | assert.equal(fake.firstArg, expectedWarning)
33 | })
34 |
35 | it('should do nothing and print warning if "attribute" attribute is missing', async () => {
36 | const fake = sinon.replace(console, "warn", sinon.fake())
37 | const expectedWarning = '[TurboPower] no "key" provided for Turbo Streams operation "remove_storage_item"'
38 |
39 | assert.equal(fake.callCount, 0)
40 |
41 | await executeStream('')
42 |
43 | assert.equal(fake.callCount, 1)
44 | assert.equal(fake.firstArg, expectedWarning)
45 | })
46 | })
47 |
48 | context("localStorage", () => {
49 | it("should remove local storage item with no type", async () => {
50 | localStorage.setItem("key1", "value1")
51 | sessionStorage.setItem("key1", "value1")
52 |
53 | assert.equal(localStorage.getItem("key1"), "value1")
54 | assert.equal(sessionStorage.getItem("key1"), "value1")
55 |
56 | await executeStream('')
57 |
58 | assert.equal(localStorage.getItem("key1"), null)
59 | assert.equal(sessionStorage.getItem("key1"), "value1")
60 | })
61 |
62 | it('should remove local storage item with "type=local"', async () => {
63 | localStorage.setItem("key2", "value2")
64 | sessionStorage.setItem("key2", "value2")
65 |
66 | assert.equal(localStorage.getItem("key2"), "value2")
67 | assert.equal(sessionStorage.getItem("key2"), "value2")
68 |
69 | await executeStream('')
70 |
71 | assert.equal(localStorage.getItem("key2"), null)
72 | assert.equal(sessionStorage.getItem("key2"), "value2")
73 | })
74 | })
75 |
76 | context("sessionStorage", () => {
77 | it('should remove session storage item with "type=session"', async () => {
78 | sessionStorage.setItem("key3", "value3")
79 | localStorage.setItem("key3", "value3")
80 |
81 | assert.equal(sessionStorage.getItem("key3"), "value3")
82 | assert.equal(localStorage.getItem("key3"), "value3")
83 |
84 | await executeStream('')
85 |
86 | assert.equal(sessionStorage.getItem("key3"), null)
87 | assert.equal(localStorage.getItem("key3"), "value3")
88 | })
89 | })
90 | })
91 |
--------------------------------------------------------------------------------
/test/storage/set_storage_item.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("set_storage_item")
6 |
7 | describe("set_storage_item", () => {
8 | beforeEach(() => {
9 | localStorage.clear()
10 | sessionStorage.clear()
11 | })
12 |
13 | afterEach(() => {
14 | localStorage.clear()
15 | sessionStorage.clear()
16 | })
17 |
18 | context("warnings", () => {
19 | afterEach(() => {
20 | sinon.restore()
21 | })
22 |
23 | it("should do nothing and print warning if attribute is empty", async () => {
24 | const fake = sinon.replace(console, "warn", sinon.fake())
25 | const expectedWarning = '[TurboPower] no "key" provided for Turbo Streams operation "set_storage_item"'
26 |
27 | assert.equal(fake.callCount, 0)
28 |
29 | await executeStream('')
30 |
31 | assert.equal(fake.callCount, 1)
32 | assert.equal(fake.firstArg, expectedWarning)
33 | })
34 |
35 | it('should do nothing and print warning if "attribute" attribute is missing', async () => {
36 | const fake = sinon.replace(console, "warn", sinon.fake())
37 | const expectedWarning = '[TurboPower] no "key" provided for Turbo Streams operation "set_storage_item"'
38 |
39 | assert.equal(fake.callCount, 0)
40 |
41 | await executeStream('')
42 |
43 | assert.equal(fake.callCount, 1)
44 | assert.equal(fake.firstArg, expectedWarning)
45 | })
46 | })
47 |
48 | context("localStorage", () => {
49 | it("should set local storage item with value", async () => {
50 | assert.equal(localStorage.getItem("key1"), null)
51 | assert.equal(sessionStorage.getItem("key1"), null)
52 |
53 | await executeStream('')
54 |
55 | assert.equal(localStorage.getItem("key1"), "value1")
56 | assert.equal(sessionStorage.getItem("key1"), null)
57 | })
58 |
59 | it('should set local storage item with value and "type=local"', async () => {
60 | assert.equal(localStorage.getItem("key2"), null)
61 | assert.equal(sessionStorage.getItem("key2"), null)
62 |
63 | await executeStream(
64 | '',
65 | )
66 |
67 | assert.equal(localStorage.getItem("key2"), "value2")
68 | assert.equal(sessionStorage.getItem("key2"), null)
69 | })
70 |
71 | it("should set local storage item with empty value", async () => {
72 | assert.equal(localStorage.getItem("key3"), null)
73 | assert.equal(sessionStorage.getItem("key3"), null)
74 |
75 | await executeStream('')
76 |
77 | assert.equal(localStorage.getItem("key3"), "")
78 | assert.equal(sessionStorage.getItem("key3"), null)
79 | })
80 |
81 | it("should override previous local storage item value with empty value", async () => {
82 | localStorage.setItem("key4", "value")
83 |
84 | assert.equal(localStorage.getItem("key4"), "value")
85 | assert.equal(sessionStorage.getItem("key4"), null)
86 |
87 | await executeStream('')
88 |
89 | assert.equal(localStorage.getItem("key4"), "")
90 | assert.equal(sessionStorage.getItem("key4"), null)
91 | })
92 |
93 | it('should override previous local storage item value with missing "value" attribute', async () => {
94 | localStorage.setItem("key5", "value")
95 |
96 | assert.equal(localStorage.getItem("key5"), "value")
97 | assert.equal(sessionStorage.getItem("key5"), null)
98 |
99 | await executeStream('')
100 |
101 | assert.equal(localStorage.getItem("key5"), "")
102 | assert.equal(sessionStorage.getItem("key5"), null)
103 | })
104 | })
105 |
106 | context("sessionStorage", () => {
107 | it('should set session storage item with value and "type=session"', async () => {
108 | assert.equal(sessionStorage.getItem("key6"), null)
109 | assert.equal(localStorage.getItem("key6"), null)
110 |
111 | await executeStream(
112 | '',
113 | )
114 |
115 | assert.equal(sessionStorage.getItem("key6"), "value6")
116 | assert.equal(localStorage.getItem("key6"), null)
117 | })
118 |
119 | it("should set session storage item with empty value", async () => {
120 | assert.equal(sessionStorage.getItem("key7"), null)
121 | assert.equal(localStorage.getItem("key7"), null)
122 |
123 | await executeStream('')
124 |
125 | assert.equal(sessionStorage.getItem("key7"), "")
126 | assert.equal(localStorage.getItem("key7"), null)
127 | })
128 |
129 | it("should override previous session storage item value with empty value", async () => {
130 | sessionStorage.setItem("key8", "value")
131 |
132 | assert.equal(sessionStorage.getItem("key8"), "value")
133 | assert.equal(localStorage.getItem("key8"), null)
134 |
135 | await executeStream('')
136 |
137 | assert.equal(sessionStorage.getItem("key8"), "")
138 | assert.equal(localStorage.getItem("key8"), null)
139 | })
140 |
141 | it('should override previous session storage item value with missing "value" attribute', async () => {
142 | sessionStorage.setItem("key9", "value")
143 |
144 | assert.equal(sessionStorage.getItem("key9"), "value")
145 | assert.equal(localStorage.getItem("key9"), null)
146 |
147 | await executeStream('')
148 |
149 | assert.equal(sessionStorage.getItem("key9"), "")
150 | assert.equal(localStorage.getItem("key9"), null)
151 | })
152 | })
153 | })
154 |
--------------------------------------------------------------------------------
/test/test_helpers.js:
--------------------------------------------------------------------------------
1 | import { nextFrame } from "@open-wc/testing"
2 | import * as Turbo from "@hotwired/turbo"
3 |
4 | import TurboPower from "../"
5 |
6 | export async function executeStream(html) {
7 | document.body.insertAdjacentHTML("beforeend", html)
8 | await nextFrame()
9 | }
10 |
11 | export function registerAction(actionName) {
12 | const action = TurboPower.Actions[actionName]
13 | TurboPower.register(actionName, action, Turbo.StreamActions)
14 | }
15 |
16 | export function fixtureFile(fileName) {
17 | return `/test/fixtures/${fileName}`
18 | }
19 |
--------------------------------------------------------------------------------
/test/turbo/redirect_to.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("redirect_to")
6 |
7 | describe("redirect_to", () => {
8 | beforeEach(() => {
9 | window.TurboPowerLocation = {
10 | assign: sinon.stub(),
11 | }
12 | })
13 |
14 | afterEach(() => {
15 | sinon.restore()
16 | window.TurboPowerLocation = undefined
17 | })
18 |
19 | context("turbo attribute", () => {
20 | it("uses Turbo by default", async () => {
21 | sinon.replace(window, "Turbo", { visit: sinon.fake() })
22 |
23 | await executeStream(``)
24 |
25 | const expected = ["http://localhost:8080", { action: "advance" }]
26 |
27 | assert.equal(Turbo.visit.callCount, 1)
28 | assert.equal(TurboPowerLocation.assign.callCount, 0)
29 | assert.deepEqual(Turbo.visit.args[0], expected)
30 | })
31 |
32 | it("uses Turbo if true", async () => {
33 | sinon.replace(window, "Turbo", { visit: sinon.fake() })
34 |
35 | await executeStream(``)
36 |
37 | const expected = ["http://localhost:8080", { action: "advance" }]
38 |
39 | assert.equal(Turbo.visit.callCount, 1)
40 | assert.equal(TurboPowerLocation.assign.callCount, 0)
41 | assert.deepEqual(Turbo.visit.args[0], expected)
42 | })
43 |
44 | it("doesn't use Turbo if false", async () => {
45 | sinon.replace(window, "Turbo", { visit: sinon.fake() })
46 |
47 | await executeStream(
48 | ``,
49 | )
50 |
51 | assert.equal(Turbo.visit.callCount, 0)
52 | assert.equal(TurboPowerLocation.assign.callCount, 1)
53 | assert.deepEqual(TurboPowerLocation.assign.args[0], ["http://localhost:8080"])
54 | })
55 | })
56 |
57 | context("turbo_action attribute", () => {
58 | it("uses advance by default", async () => {
59 | sinon.replace(window, "Turbo", { visit: sinon.fake() })
60 |
61 | await executeStream(``)
62 |
63 | const expected = ["http://localhost:8080", { action: "advance" }]
64 |
65 | assert.equal(Turbo.visit.callCount, 1)
66 | assert.equal(TurboPowerLocation.assign.callCount, 0)
67 | assert.deepEqual(Turbo.visit.args[0], expected)
68 | })
69 |
70 | it("uses replace", async () => {
71 | sinon.replace(window, "Turbo", { visit: sinon.fake() })
72 |
73 | await executeStream(
74 | ``,
75 | )
76 |
77 | const expected = ["http://localhost:8080", { action: "replace" }]
78 |
79 | assert.equal(Turbo.visit.callCount, 1)
80 | assert.equal(TurboPowerLocation.assign.callCount, 0)
81 | assert.deepEqual(Turbo.visit.args[0], expected)
82 | })
83 |
84 | it("uses restore", async () => {
85 | sinon.replace(window, "Turbo", { visit: sinon.fake() })
86 |
87 | await executeStream(
88 | ``,
89 | )
90 |
91 | const expected = ["http://localhost:8080", { action: "restore" }]
92 |
93 | assert.equal(Turbo.visit.callCount, 1)
94 | assert.equal(TurboPowerLocation.assign.callCount, 0)
95 | assert.deepEqual(Turbo.visit.args[0], expected)
96 | })
97 | })
98 |
99 | context("url attribute", () => {
100 | it("uses / as fallback", async () => {
101 | sinon.replace(window, "Turbo", { visit: sinon.fake() })
102 |
103 | await executeStream(``)
104 |
105 | const expected = ["/", { action: "advance" }]
106 |
107 | assert.equal(Turbo.visit.callCount, 1)
108 | assert.equal(TurboPowerLocation.assign.callCount, 0)
109 | assert.deepEqual(Turbo.visit.args[0], expected)
110 | })
111 |
112 | it("uses / as fallback with url attribute without value", async () => {
113 | sinon.replace(window, "Turbo", { visit: sinon.fake() })
114 |
115 | await executeStream(``)
116 |
117 | const expected = ["/", { action: "advance" }]
118 |
119 | assert.equal(Turbo.visit.callCount, 1)
120 | assert.equal(TurboPowerLocation.assign.callCount, 0)
121 | assert.deepEqual(Turbo.visit.args[0], expected)
122 | })
123 |
124 | it("uses / as fallback with empty url attribute", async () => {
125 | sinon.replace(window, "Turbo", { visit: sinon.fake() })
126 |
127 | await executeStream(``)
128 |
129 | const expected = ["/", { action: "advance" }]
130 |
131 | assert.equal(Turbo.visit.callCount, 1)
132 | assert.equal(TurboPowerLocation.assign.callCount, 0)
133 | assert.deepEqual(Turbo.visit.args[0], expected)
134 | })
135 |
136 | it("uses the provided url value", async () => {
137 | sinon.replace(window, "Turbo", { visit: sinon.fake() })
138 |
139 | await executeStream(``)
140 |
141 | const expected = ["/path/to/somewhere", { action: "advance" }]
142 |
143 | assert.equal(Turbo.visit.callCount, 1)
144 | assert.equal(TurboPowerLocation.assign.callCount, 0)
145 | assert.deepEqual(Turbo.visit.args[0], expected)
146 | })
147 | })
148 |
149 | context("turbo_frame attribute", () => {
150 | it("renders the frame attribute", async () => {
151 | sinon.replace(window, "Turbo", { visit: sinon.fake() })
152 |
153 | await executeStream(
154 | ``,
155 | )
156 |
157 | const expected = ["http://localhost:8080", { action: "advance", frame: "modals" }]
158 |
159 | assert.equal(Turbo.visit.callCount, 1)
160 | assert.equal(TurboPowerLocation.assign.callCount, 0)
161 | assert.deepEqual(Turbo.visit.args[0], expected)
162 | })
163 | })
164 | })
165 |
--------------------------------------------------------------------------------
/test/turbo/turbo_clear_cache.test.js:
--------------------------------------------------------------------------------
1 | import * as Turbo from "@hotwired/turbo"
2 | import { assert, oneEvent } from "@open-wc/testing"
3 | import { executeStream, registerAction, fixtureFile } from "../test_helpers"
4 |
5 | registerAction("turbo_clear_cache")
6 |
7 | describe("turbo_clear_cache", () => {
8 | context("with history snapshots present", () => {
9 | it("should clear the snapshots entries", async () => {
10 | Turbo.visit(fixtureFile("page1.html"))
11 | await oneEvent(document, "turbo:load")
12 |
13 | Turbo.visit(fixtureFile("page2.html"))
14 | await oneEvent(document, "turbo:load")
15 |
16 | Turbo.visit(fixtureFile("page3.html"))
17 | await oneEvent(document, "turbo:load")
18 |
19 | assert.isAtLeast(Object.keys(Turbo.session.view.snapshotCache.snapshots).length, 1)
20 | await executeStream('')
21 | assert.isAtMost(Object.keys(Turbo.session.view.snapshotCache.snapshots).length, 0)
22 | })
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/test/turbo_frame/reload.test.js:
--------------------------------------------------------------------------------
1 | import { html, fixture, assert } from "@open-wc/testing"
2 | import { executeStream, registerAction, fixtureFile } from "../test_helpers"
3 |
4 | registerAction("turbo_frame_reload")
5 |
6 | describe("turbo_frame_reload", () => {
7 | it("should reload a frame", async () => {
8 | const frameFile = fixtureFile("frame1.html")
9 | await fixture(html` Loading `)
10 | let frame = document.querySelector("#frame1")
11 | await frame.loaded
12 | const timeBeforeReload = frame.querySelector("time").innerText
13 |
14 | await executeStream(``)
15 |
16 | await frame.loaded
17 | frame = document.querySelector("#frame1")
18 | const timeAfterReload = frame.querySelector("time").innerText
19 | assert.notEqual(timeBeforeReload, timeAfterReload)
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/test/turbo_frame/set_src.test.js:
--------------------------------------------------------------------------------
1 | import { html, fixture, assert } from "@open-wc/testing"
2 | import { executeStream, registerAction, fixtureFile } from "../test_helpers"
3 |
4 | registerAction("turbo_frame_set_src")
5 |
6 | describe("turbo_frame_set_src", () => {
7 | it("should change frame src with the src attribute present", async () => {
8 | await fixture(html` Loading `)
9 | let frame = document.querySelector("#frame1")
10 | await frame.loaded
11 | assert.equal(frame.querySelector("h1").innerText, "Frame 1")
12 |
13 | await executeStream(
14 | ``,
15 | )
16 |
17 | frame = document.querySelector("#frame1")
18 | await frame.loaded
19 | assert.equal(frame.querySelector("h1").innerText, "Frame 2")
20 | })
21 |
22 | it("should change frame src with the src attribute not present", async () => {
23 | await fixture(html` Loading `)
24 | let frame = document.querySelector("#frame1")
25 | assert.equal(frame.innerText, "Loading")
26 |
27 | await executeStream(
28 | ``,
29 | )
30 |
31 | frame = document.querySelector("#frame1")
32 | await frame.loaded
33 | assert.equal(frame.querySelector("h1").innerText, "Frame 2")
34 | })
35 | })
36 |
--------------------------------------------------------------------------------
/test/turbo_progress_bar/turbo_progress_bar_hide.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("turbo_progress_bar_hide")
6 |
7 | describe("turbo_progress_bar_hide", () => {
8 | afterEach(() => {
9 | sinon.restore()
10 | })
11 |
12 | it("defaults to 0", async () => {
13 | const progressBar = { hide: sinon.fake() }
14 | sinon.replace(window, "Turbo", { navigator: { adapter: { progressBar } } })
15 |
16 | await executeStream(``)
17 |
18 | assert.equal(progressBar.hide.callCount, 1)
19 | assert.deepEqual(progressBar.hide.args[0], [])
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/test/turbo_progress_bar/turbo_progress_bar_set_value.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("turbo_progress_bar_set_value")
6 |
7 | describe("turbo_progress_bar_set_value", () => {
8 | afterEach(() => {
9 | sinon.restore()
10 | })
11 |
12 | it("defaults to 0", async () => {
13 | const progressBar = { setValue: sinon.fake() }
14 | sinon.replace(window, "Turbo", { navigator: { adapter: { progressBar } } })
15 |
16 | await executeStream(``)
17 |
18 | assert.equal(progressBar.setValue.callCount, 1)
19 | assert.deepEqual(progressBar.setValue.args[0], [0])
20 | })
21 |
22 | it("works with zero value", async () => {
23 | const progressBar = { setValue: sinon.fake() }
24 | sinon.replace(window, "Turbo", { navigator: { adapter: { progressBar } } })
25 |
26 | await executeStream(``)
27 |
28 | assert.equal(progressBar.setValue.callCount, 1)
29 | assert.deepEqual(progressBar.setValue.args[0], [0])
30 | })
31 |
32 | it("works with integer values", async () => {
33 | const progressBar = { setValue: sinon.fake() }
34 | sinon.replace(window, "Turbo", { navigator: { adapter: { progressBar } } })
35 |
36 | await executeStream(``)
37 |
38 | assert.equal(progressBar.setValue.callCount, 1)
39 | assert.deepEqual(progressBar.setValue.args[0], [100])
40 | })
41 |
42 | it("works with float values", async () => {
43 | const progressBar = { setValue: sinon.fake() }
44 | sinon.replace(window, "Turbo", { navigator: { adapter: { progressBar } } })
45 |
46 | await executeStream(``)
47 |
48 | assert.equal(progressBar.setValue.callCount, 1)
49 | assert.deepEqual(progressBar.setValue.args[0], [0.5])
50 | })
51 | })
52 |
--------------------------------------------------------------------------------
/test/turbo_progress_bar/turbo_progress_bar_show.test.js:
--------------------------------------------------------------------------------
1 | import sinon from "sinon"
2 | import { assert } from "@open-wc/testing"
3 | import { executeStream, registerAction } from "../test_helpers"
4 |
5 | registerAction("turbo_progress_bar_show")
6 |
7 | describe("turbo_progress_bar_show", () => {
8 | afterEach(() => {
9 | sinon.restore()
10 | })
11 |
12 | it("defaults to 0", async () => {
13 | const progressBar = { show: sinon.fake() }
14 | sinon.replace(window, "Turbo", { navigator: { adapter: { progressBar } } })
15 |
16 | await executeStream(``)
17 |
18 | assert.equal(progressBar.show.callCount, 1)
19 | assert.deepEqual(progressBar.show.args[0], [])
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/test/utils/tokenize.test.js:
--------------------------------------------------------------------------------
1 | import { assert } from "@open-wc/testing"
2 | import { Utils } from "../../"
3 | const { tokenize } = Utils
4 |
5 | describe("utils.tokenize", () => {
6 | context("null", () => {
7 | it("returns empty array", () => {
8 | assert.deepEqual(tokenize(null), [])
9 | })
10 | })
11 |
12 | context("empty string", () => {
13 | it("returns empty array", () => {
14 | assert.deepEqual(tokenize(""), [])
15 | })
16 | })
17 |
18 | context("string with no space", () => {
19 | it("returns tokenized array", () => {
20 | assert.deepEqual(tokenize("some"), ["some"])
21 | })
22 | })
23 |
24 | context("simple string", () => {
25 | it("returns tokenized array", () => {
26 | assert.deepEqual(tokenize("some thing"), ["some", "thing"])
27 | })
28 | })
29 |
30 | context("string with muliple spaces space", () => {
31 | it("returns tokenized array without empty entries", () => {
32 | assert.deepEqual(tokenize("some thing and another"), ["some", "thing", "and", "another"])
33 | })
34 | })
35 |
36 | context("string with special characters", () => {
37 | it("returns tokenized array", () => {
38 | assert.deepEqual(tokenize("a_lot of-words with special--chars mixed__in"), [
39 | "a_lot",
40 | "of-words",
41 | "with",
42 | "special--chars",
43 | "mixed__in",
44 | ])
45 | })
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/test/utils/typecast.test.js:
--------------------------------------------------------------------------------
1 | import { assert } from "@open-wc/testing"
2 | import { Utils } from "../../"
3 | const { typecast } = Utils
4 |
5 | describe("utils.typecast", () => {
6 | context("Number", () => {
7 | it("casts integer value", () => {
8 | assert.equal(typecast("123"), 123)
9 | })
10 |
11 | it("casts float value", () => {
12 | assert.equal(typecast("123.123"), 123.123)
13 | })
14 | })
15 |
16 | context("String", () => {
17 | it("casts string", () => {
18 | assert.equal(typecast('"Hello World"'), "Hello World")
19 | })
20 | })
21 |
22 | context("Boolean", () => {
23 | it("casts true", () => {
24 | assert.equal(typecast("true"), true)
25 | })
26 |
27 | it("casts false", () => {
28 | assert.equal(typecast("false"), false)
29 | })
30 | })
31 |
32 | context("Object", () => {
33 | it("casts", () => {
34 | assert.deepEqual(typecast('{ "name": "John Doe", "age": 42, "alive": true }'), {
35 | name: "John Doe",
36 | age: 42,
37 | alive: true,
38 | })
39 | })
40 | })
41 |
42 | context("Array", () => {
43 | it("casts", () => {
44 | assert.deepEqual(typecast("[1, 2, 3, 4]"), [1, 2, 3, 4])
45 | })
46 | })
47 |
48 | context("catch", () => {
49 | it("defaults to string value on error", () => {
50 | assert.equal(typecast("Hello World"), "Hello World")
51 | assert.equal(typecast("{ abc: 123 }"), "{ abc: 123 }")
52 | assert.equal(typecast("[foo, bar, baz]"), "[foo, bar, baz]")
53 | })
54 | })
55 | })
56 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "es2019", "scripthost"],
4 | "module": "es2015",
5 | "moduleResolution": "node",
6 | "noUnusedLocals": true,
7 | "rootDir": "src",
8 | "strict": true,
9 | "target": "es2017",
10 | "removeComments": true,
11 | "outDir": "dist",
12 | "baseUrl": ".",
13 | "noEmit": false,
14 | "declaration": false,
15 | "esModuleInterop": true,
16 | "allowSyntheticDefaultImports": true,
17 | "typeRoots": [
18 | "./node_modules/@types"
19 | ]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/types/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcoroth/turbo_power/4219232851e19de3a4aa0d63f76c496fdfc141af/types/.keep
--------------------------------------------------------------------------------
/web-test-runner.config.mjs:
--------------------------------------------------------------------------------
1 | const filteredLogs = [
2 | 'Lit is in dev mode. Not recommended for production! See https://lit.dev/msg/dev-mode for more information.'
3 | ]
4 |
5 | const filterBrowserLogs = (log) => {
6 | for (const arg of log.args) {
7 | if (typeof arg === 'string' && filteredLogs.some(l => arg.includes(l))) {
8 | return false
9 | }
10 | }
11 | return true
12 | }
13 |
14 | export default {
15 | nodeResolve: true,
16 | filterBrowserLogs,
17 | concurrency: 1, // TODO: session storage tests are not supported with concurrency enabled
18 | mimeTypes: {},
19 | plugins: []
20 | }
21 |
--------------------------------------------------------------------------------