├── .containers.example.yml ├── .dockerignore ├── .github ├── FUNDING.yml └── workflows │ ├── codeql-analysis.yml │ ├── prettier.yml │ ├── standardrb.yml │ └── tests.yml ├── .gitignore ├── .prettierignore ├── .standard.yml ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── Gemfile ├── Guardfile ├── MIT-LICENSE ├── README.md ├── Rakefile ├── app ├── assets │ ├── builds │ │ └── @turbo-boost │ │ │ ├── commands.js │ │ │ └── commands.js.map │ └── images │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── hopsoft.webp │ │ ├── turbo-boost-logo-dark-bg.webp │ │ ├── turbo-boost-logo.afdesign │ │ ├── turbo-boost-logo.webp │ │ └── turbo-boost-mark.afdesign ├── controllers │ └── concerns │ │ └── turbo_boost │ │ └── commands │ │ └── controller.rb ├── helpers │ └── turbo_boost │ │ └── commands │ │ └── application_helper.rb └── javascript │ ├── activity.js │ ├── confirmation.js │ ├── delegates.js │ ├── drivers │ ├── form.js │ ├── frame.js │ ├── index.js │ ├── method.js │ └── window.js │ ├── elements.js │ ├── events.js │ ├── headers.js │ ├── index.js │ ├── invoker.js │ ├── lifecycle.js │ ├── logger.js │ ├── renderer.js │ ├── schema.js │ ├── state │ ├── index.js │ ├── observable.js │ ├── page.js │ └── storage.js │ ├── turbo.js │ ├── urls.js │ ├── uuids.js │ └── version.js ├── bin ├── build.mjs ├── docker │ └── run │ │ ├── local │ │ └── remote ├── fly │ ├── bash │ └── console ├── loc ├── rails └── standardize ├── docker-compose.yml ├── fly.toml ├── lib └── turbo_boost │ ├── commands.rb │ └── commands │ ├── attribute_hydration.rb │ ├── attribute_set.rb │ ├── command.rb │ ├── command_callbacks.rb │ ├── command_validator.rb │ ├── controller_pack.rb │ ├── engine.rb │ ├── errors.rb │ ├── http_status_codes.rb │ ├── middlewares │ ├── entry_middleware.rb │ └── exit_middleware.rb │ ├── patches.rb │ ├── patches │ └── action_view_helpers_tag_helper_tag_builder_patch.rb │ ├── responder.rb │ ├── runner.rb │ ├── sanitizer.rb │ ├── state.rb │ ├── state_store.rb │ ├── token_validator.rb │ └── version.rb ├── package-lock.json ├── package.json ├── prettier.config.js ├── test ├── application_system_test_case.rb ├── attribute_hydration_test.rb ├── attribute_set_test.rb ├── dummy │ ├── Procfile.dev │ ├── Rakefile │ ├── app │ │ ├── assets │ │ │ ├── builds │ │ │ │ ├── .keep │ │ │ │ └── tailwind.css │ │ │ ├── config │ │ │ │ └── manifest.js │ │ │ ├── images │ │ │ │ └── .keep │ │ │ └── stylesheets │ │ │ │ ├── application.css │ │ │ │ ├── application.tailwind.css │ │ │ │ ├── pico.css │ │ │ │ ├── pygments.css │ │ │ │ └── tailwind │ │ │ │ └── base.css │ │ ├── channels │ │ │ └── application_cable │ │ │ │ ├── channel.rb │ │ │ │ └── connection.rb │ │ ├── commands │ │ │ ├── application_command.rb │ │ │ ├── decrement_count_command.rb │ │ │ ├── drivers │ │ │ │ ├── form │ │ │ │ │ ├── allow_controller_action_command.rb │ │ │ │ │ └── prevent_controller_action_command.rb │ │ │ │ ├── frame │ │ │ │ │ ├── allow_controller_action_command.rb │ │ │ │ │ └── prevent_controller_action_command.rb │ │ │ │ ├── method │ │ │ │ │ ├── allow_controller_action_command.rb │ │ │ │ │ └── prevent_controller_action_command.rb │ │ │ │ └── window │ │ │ │ │ ├── allow_controller_action_command.rb │ │ │ │ │ └── prevent_controller_action_command.rb │ │ │ ├── increment_count_command.rb │ │ │ ├── remember_attributes_command.rb │ │ │ └── reset_count_command.rb │ │ ├── controllers │ │ │ ├── application_controller.rb │ │ │ ├── basic_commands_controller.rb │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ ├── features_controller.rb │ │ │ ├── installations_controller.rb │ │ │ ├── sponsors_controller.rb │ │ │ ├── tests_controller.rb │ │ │ └── todos_controller.rb │ │ ├── helpers │ │ │ ├── application_helper.rb │ │ │ ├── basic_commands_helper.rb │ │ │ ├── code_helper.rb │ │ │ ├── features_helper.rb │ │ │ ├── installations_helper.rb │ │ │ ├── sponsors_helper.rb │ │ │ ├── svgs_helper.rb │ │ │ ├── tailwind_helper.rb │ │ │ ├── tests_helper.rb │ │ │ └── todos_helper.rb │ │ ├── javascript │ │ │ ├── application.js │ │ │ ├── controllers │ │ │ │ ├── index.js │ │ │ │ ├── scroll_into_view_controller.js │ │ │ │ └── view_mode_controller.js │ │ │ ├── tests │ │ │ │ └── index.js │ │ │ └── turbo-boost │ │ │ │ └── index.js │ │ ├── jobs │ │ │ └── application_job.rb │ │ ├── mailers │ │ │ └── application_mailer.rb │ │ ├── models │ │ │ ├── application_record.rb │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ ├── current.rb │ │ │ └── user.rb │ │ └── views │ │ │ ├── alerts │ │ │ ├── _alert.html.erb │ │ │ ├── _info.html.erb │ │ │ ├── _success.html.erb │ │ │ └── _warning.html.erb │ │ │ ├── basic_commands │ │ │ ├── @stylesheet.css │ │ │ ├── _demo.html.erb │ │ │ ├── _no_frame.html.erb │ │ │ ├── _no_frame_files.sh.erb │ │ │ ├── _turbo_frame.html.erb │ │ │ ├── _turbo_frame_files.sh.erb │ │ │ ├── _with_turbo_frame.html.erb │ │ │ ├── _without_turbo_frame.html.erb │ │ │ ├── show.html.erb │ │ │ ├── show.turbo_boost.erb │ │ │ └── show.turbo_stream.erb │ │ │ ├── breadcrumbs │ │ │ ├── _breadcrumb.html.erb │ │ │ ├── _breadcrumbs.html.erb │ │ │ ├── _button.html.erb │ │ │ └── _divider.html.erb │ │ │ ├── cards │ │ │ └── _sponsor.html.erb │ │ │ ├── examples │ │ │ └── _example.html.erb │ │ │ ├── features │ │ │ ├── @stylesheet.css │ │ │ ├── _feature.html.erb │ │ │ └── index.html.erb │ │ │ ├── icons │ │ │ ├── bootstrap │ │ │ │ └── _mastodon.html.erb │ │ │ ├── flowbite │ │ │ │ ├── _chart_mixed_dollar.html.erb │ │ │ │ ├── _github.html.erb │ │ │ │ ├── _twitter.html.erb │ │ │ │ └── _youtube.html.erb │ │ │ └── heroicons │ │ │ │ ├── _academic_cap.html.erb │ │ │ │ ├── _adjustments_horizontal.html.erb │ │ │ │ ├── _arrow_down_on_square_stack.html.erb │ │ │ │ ├── _arrow_left.html.erb │ │ │ │ ├── _arrow_path.html.erb │ │ │ │ ├── _arrow_right.html.erb │ │ │ │ ├── _at_symbol.html.erb │ │ │ │ ├── _banknotes.html.erb │ │ │ │ ├── _bars_3.html.erb │ │ │ │ ├── _beaker.html.erb │ │ │ │ ├── _bell.html.erb │ │ │ │ ├── _book_open.html.erb │ │ │ │ ├── _calculator.html.erb │ │ │ │ ├── _check.html.erb │ │ │ │ ├── _check_badge.html.erb │ │ │ │ ├── _check_circle.html.erb │ │ │ │ ├── _chevron_down.html.erb │ │ │ │ ├── _chevron_right.html.erb │ │ │ │ ├── _chevron_up.html.erb │ │ │ │ ├── _circle_stack.html.erb │ │ │ │ ├── _clipboard_document.html.erb │ │ │ │ ├── _cloud_arrow_up.html.erb │ │ │ │ ├── _code_bracket.html.erb │ │ │ │ ├── _code_bracket_square.html.erb │ │ │ │ ├── _command_line.html.erb │ │ │ │ ├── _credit_card.html.erb │ │ │ │ ├── _cursor_arrow_rays.html.erb │ │ │ │ ├── _cursor_arrow_ripple.html.erb │ │ │ │ ├── _document_text.html.erb │ │ │ │ ├── _exclamation.html.erb │ │ │ │ ├── _exclamation_triangle.html.erb │ │ │ │ ├── _face_smile.html.erb │ │ │ │ ├── _globe_americas.html.erb │ │ │ │ ├── _hand_thumb_up.html.erb │ │ │ │ ├── _home.html.erb │ │ │ │ ├── _information_circle.html.erb │ │ │ │ ├── _library.html.erb │ │ │ │ ├── _light_bulb.html.erb │ │ │ │ ├── _lightning_bolt.html.erb │ │ │ │ ├── _minus.html.erb │ │ │ │ ├── _moon.html.erb │ │ │ │ ├── _play_circle.html.erb │ │ │ │ ├── _plus.html.erb │ │ │ │ ├── _puzzle_piece.html.erb │ │ │ │ ├── _rectangle_group.html.erb │ │ │ │ ├── _rectangle_stack.html.erb │ │ │ │ ├── _refresh.html.erb │ │ │ │ ├── _rocket_launch.html.erb │ │ │ │ ├── _server_stack.html.erb │ │ │ │ ├── _shield_check.html.erb │ │ │ │ ├── _signal.html.erb │ │ │ │ ├── _sparkles.html.erb │ │ │ │ ├── _square_3_stack_3d.html.erb │ │ │ │ ├── _squares_plus.html.erb │ │ │ │ ├── _sun.html.erb │ │ │ │ ├── _variable.html.erb │ │ │ │ ├── _x_circle.html.erb │ │ │ │ └── _x_mark.html.erb │ │ │ ├── installations │ │ │ ├── show.html.erb │ │ │ └── source_code │ │ │ │ ├── _application.js.erb │ │ │ │ ├── _gemfile.rb.erb │ │ │ │ ├── _importmap.rb.erb │ │ │ │ ├── _importmap_pin.sh.erb │ │ │ │ ├── _npm_install.sh.erb │ │ │ │ └── _package.json.erb │ │ │ ├── instructions │ │ │ ├── @stylesheet.css │ │ │ └── _instruction.html.erb │ │ │ ├── layouts │ │ │ ├── application.html.erb │ │ │ ├── mailer.html.erb │ │ │ ├── mailer.text.erb │ │ │ └── pico.html.erb │ │ │ ├── navbars │ │ │ ├── _apps.html.erb │ │ │ ├── _apps_toggle.html.erb │ │ │ ├── _brand.html.erb │ │ │ ├── _mode_toggle.html.erb │ │ │ ├── _navbar.html.erb │ │ │ ├── _notifications.html.erb │ │ │ ├── _notifications_toggle.html.erb │ │ │ ├── _search.html.erb │ │ │ ├── _search_toggle.html.erb │ │ │ ├── _sidebar_toggle.html.erb │ │ │ ├── _user_menu.html.erb │ │ │ └── _user_menu_toggle.html.erb │ │ │ ├── partials │ │ │ └── _details.html.erb │ │ │ ├── sidebars │ │ │ ├── _button.html.erb │ │ │ ├── _footer.html.erb │ │ │ ├── _link.html.erb │ │ │ ├── _sidebar.html.erb │ │ │ └── _sponsor.html.erb │ │ │ ├── sponsors │ │ │ ├── _clickfunnels.html.erb │ │ │ └── index.html.erb │ │ │ ├── svgs │ │ │ └── _svg.html.erb │ │ │ ├── tests │ │ │ ├── drivers │ │ │ │ ├── _footer.html.erb │ │ │ │ ├── _form.html.erb │ │ │ │ ├── _form.turbo_stream.erb │ │ │ │ ├── _frame.html.erb │ │ │ │ ├── _frame.turbo_stream.erb │ │ │ │ ├── _method.html.erb │ │ │ │ ├── _method.turbo_stream.erb │ │ │ │ ├── _window.html.erb │ │ │ │ └── _window.turbo_stream.erb │ │ │ ├── index.html.erb │ │ │ ├── show.html.erb │ │ │ └── show.turbo_stream.erb │ │ │ ├── todos │ │ │ └── index.html.erb │ │ │ └── tooltips │ │ │ └── _tooltip.html.erb │ ├── bin │ │ ├── dev │ │ ├── importmap │ │ ├── rails │ │ ├── rake │ │ └── setup │ ├── config.ru │ ├── config │ │ ├── application.rb │ │ ├── boot.rb │ │ ├── cable.yml │ │ ├── credentials.yml.enc │ │ ├── database.yml │ │ ├── environment.rb │ │ ├── environments │ │ │ ├── development.rb │ │ │ ├── production.rb │ │ │ └── test.rb │ │ ├── importmap.rb │ │ ├── initializers │ │ │ ├── content_security_policy.rb │ │ │ ├── filter_parameter_logging.rb │ │ │ ├── inflections.rb │ │ │ ├── local_cache.rb │ │ │ ├── migrations.rb │ │ │ ├── permissions_policy.rb │ │ │ ├── pry.rb │ │ │ └── turbo_boost.rb │ │ ├── locales │ │ │ └── en.yml │ │ ├── puma.rb │ │ ├── routes.rb │ │ ├── routes │ │ │ ├── demos.rb │ │ │ ├── docs.rb │ │ │ ├── marketing.rb │ │ │ └── tests.rb │ │ ├── storage.yml │ │ └── tailwind.config.js │ ├── db │ │ ├── migrate │ │ │ └── 20221230164522_create_users.rb │ │ └── schema.rb │ ├── lib │ │ └── assets │ │ │ └── .keep │ ├── log │ │ └── .keep │ ├── public │ │ ├── 404.html │ │ ├── 422.html │ │ ├── 500.html │ │ ├── apple-touch-icon-precomposed.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ └── favicon.ico │ └── vendor │ │ └── javascript │ │ ├── .keep │ │ ├── @hotwired--stimulus.js │ │ ├── @hotwired--turbo-rails.js │ │ ├── @hotwired--turbo.js │ │ ├── @rails--actioncable--src.js │ │ ├── @rails--actioncable.js │ │ ├── @turbo-boost--streams.js │ │ └── stimulus.js ├── system │ ├── basic_commands │ │ ├── decrement_frame_test.rb │ │ ├── decrement_no_frame_test.rb │ │ ├── increment_frame_test.rb │ │ ├── increment_no_frame_test.rb │ │ ├── reset_frame_test.rb │ │ └── reset_no_frame_test.rb │ ├── tests │ │ └── drivers │ │ │ ├── form_test.rb │ │ │ ├── frame_test.rb │ │ │ ├── method_test.rb │ │ │ └── window_test.rb │ └── turbo_boost_globals_test.rb ├── test_helper.rb ├── turbo_boost │ └── commands │ │ ├── command_validator_test.rb │ │ ├── state_store_test.rb │ │ └── state_test.rb └── turbo_boost_commands_test.rb └── turbo_boost-commands.gemspec /.containers.example.yml: -------------------------------------------------------------------------------- 1 | --- 2 | organization: 3 | name: turbo_boost 4 | 5 | app: 6 | name: turbo_boost-commands 7 | directory: /path/to/turbo_boost-commands 8 | 9 | docker: 10 | directory: /path/to/turbo_boost-commands 11 | compose_files: 12 | - /path/to/turbo_boost-commands/docker-compose.yml 13 | default_service: web 14 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # flyctl launch added from .gitignore 2 | **/*.gem 3 | **/*.key 4 | **/Gemfile.lock 5 | .bundle 6 | .env 7 | doc 8 | fly.toml 9 | log/*.log 10 | node_modules 11 | pkg 12 | tags 13 | test/dummy/db/*.sqlite3 14 | test/dummy/db/*.sqlite3-* 15 | test/dummy/log/*.log 16 | test/dummy/public/assets 17 | test/dummy/storage 18 | test/dummy/tmp 19 | tmp 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: hopsoft 2 | -------------------------------------------------------------------------------- /.github/workflows/prettier.yml: -------------------------------------------------------------------------------- 1 | name: Prettier 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - '*' 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | prettier: 13 | name: Prettier Check Action 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Setup Node 19 | uses: actions/setup-node@v4 20 | with: 21 | version: '20.x' 22 | cache: 'npm' 23 | 24 | - name: NPM Install 25 | run: 'npm install --legacy-peer-deps' 26 | 27 | - name: Run Prettier 28 | run: 'npx prettier --check --fail-on-errors .' 29 | 30 | -------------------------------------------------------------------------------- /.github/workflows/standardrb.yml: -------------------------------------------------------------------------------- 1 | name: StandardRB 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - '*' 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | standard: 13 | name: StandardRB Check Action 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | ruby-version: ['3.3'] 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Set up Ruby ${{ matrix.ruby-version }} 22 | uses: ruby/setup-ruby@v1 23 | with: 24 | ruby-version: ${{ matrix.ruby-version }} 25 | bundler-cache: true 26 | 27 | - name: Run StandardRB 28 | run: bundle exec standardrb --format progress 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.key 3 | *.metafile.json 4 | .byebug_history 5 | .containers.yml 6 | .playwright* 7 | .pnp.* 8 | .yarn* 9 | /.bundle/ 10 | /doc/ 11 | /log/*.log* 12 | /node_modules/ 13 | /pkg/ 14 | /test/dummy/db/*.sqlite3 15 | /test/dummy/db/*.sqlite3-* 16 | /test/dummy/log/*.log 17 | /test/dummy/public/assets/ 18 | /test/dummy/storage/ 19 | /test/dummy/tmp/ 20 | /tmp/ 21 | /vendor 22 | Gemfile.lock 23 | test/dummy/app/javascript/@turbo-boost 24 | test/dummy/log/*.log* 25 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md 2 | *.yml 3 | builds 4 | test/dummy/vendor 5 | -------------------------------------------------------------------------------- /.standard.yml: -------------------------------------------------------------------------------- 1 | ruby_version: 3.0 2 | format: progress 3 | parallel: true 4 | 5 | ignore: 6 | - test/dummy/bin/* 7 | - test/dummy/db/schema.rb 8 | 9 | Lint/RescueException: 10 | Enabled: false 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.3.2-slim-bullseye 2 | 3 | RUN apt-get -y update && \ 4 | apt-get -y --no-install-recommends install \ 5 | build-essential \ 6 | curl \ 7 | git \ 8 | htop \ 9 | libjemalloc2 \ 10 | sqlite3 \ 11 | tzdata 12 | 13 | RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - 14 | RUN apt-get -y --no-install-recommends install nodejs 15 | 16 | RUN apt-get clean 17 | RUN gem update --system 18 | RUN bundle config set --local clean 'true' 19 | RUN bundle config set --local gems.force true 20 | 21 | RUN mkdir -p /mnt/external/node_modules /mnt/external/gems /mnt/external/database 22 | 23 | COPY . /app 24 | WORKDIR /app 25 | CMD bin/docker/run/remote 26 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 5 | 6 | # Specify your gem's dependencies in turbo_boost-commands.gemspec. 7 | gemspec 8 | 9 | gem "dockerfile-rails", ">= 1.3", group: :development 10 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # SEE: https://github.com/guard/guard#readme 2 | 3 | guard :minitest, spring: false, all_on_start: false, all_after_pass: false do 4 | watch(/.*\.(rb|erb)/o) do |m| 5 | # TODO: configure 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Nate Hopkins (hopsoft) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/setup" 4 | 5 | APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__) 6 | 7 | load "rails/tasks/engine.rake" 8 | load "rails/tasks/statistics.rake" 9 | require "bundler/gem_tasks" 10 | 11 | desc "Run tests" 12 | task :test, [:file] do |_, args| 13 | command = (ARGV.length > 1) ? 14 | "bin/rails test #{ARGV[1..].join(" ")}" : 15 | "bin/rails test:all" 16 | puts command 17 | exec command 18 | end 19 | 20 | task default: :test 21 | -------------------------------------------------------------------------------- /app/assets/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/app/assets/images/apple-touch-icon.png -------------------------------------------------------------------------------- /app/assets/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/app/assets/images/favicon-16x16.png -------------------------------------------------------------------------------- /app/assets/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/app/assets/images/favicon-32x32.png -------------------------------------------------------------------------------- /app/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/app/assets/images/favicon.ico -------------------------------------------------------------------------------- /app/assets/images/hopsoft.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/app/assets/images/hopsoft.webp -------------------------------------------------------------------------------- /app/assets/images/turbo-boost-logo-dark-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/app/assets/images/turbo-boost-logo-dark-bg.webp -------------------------------------------------------------------------------- /app/assets/images/turbo-boost-logo.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/app/assets/images/turbo-boost-logo.afdesign -------------------------------------------------------------------------------- /app/assets/images/turbo-boost-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/app/assets/images/turbo-boost-logo.webp -------------------------------------------------------------------------------- /app/assets/images/turbo-boost-mark.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/app/assets/images/turbo-boost-mark.afdesign -------------------------------------------------------------------------------- /app/controllers/concerns/turbo_boost/commands/controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TurboBoost::Commands::Controller 4 | extend ActiveSupport::Concern 5 | 6 | included do 7 | before_action -> { turbo_boost.runner.run } 8 | after_action -> { turbo_boost.runner.flush } 9 | helper_method :turbo_boost 10 | end 11 | 12 | def turbo_boost 13 | @turbo_boost ||= TurboBoost::Commands::ControllerPack.new(self) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/helpers/turbo_boost/commands/application_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TurboBoost::Commands::ApplicationHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/javascript/activity.js: -------------------------------------------------------------------------------- 1 | const active = {} 2 | 3 | function add(payload) { 4 | active[payload.id] = payload 5 | } 6 | 7 | function remove(id) { 8 | delete active[id] 9 | } 10 | 11 | export default { 12 | add, 13 | remove, 14 | get commands() { 15 | return [...Object.values(active)] 16 | }, 17 | get length() { 18 | return Object.keys(active).length 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/javascript/confirmation.js: -------------------------------------------------------------------------------- 1 | import { commandEvents } from './events' 2 | import schema from './schema' 3 | 4 | const confirmation = { 5 | method: message => Promise.resolve(confirm(message)) 6 | } 7 | 8 | const isTurboMethod = event => event.detail.driver === 'method' 9 | 10 | const isTurboForm = event => { 11 | if (event.detail.driver !== 'form') return false 12 | 13 | const element = event.target 14 | const frame = element.closest('turbo-frame') 15 | const target = element.closest(`[${schema.frameAttribute}]`) 16 | return !!(frame || target) 17 | } 18 | 19 | const shouldDelegate = event => isTurboMethod(event) || isTurboForm(event) 20 | 21 | document.addEventListener(commandEvents.start, async event => { 22 | const message = event.target.getAttribute(schema.confirmAttribute) 23 | if (!message) return 24 | 25 | event.detail.confirmation = true 26 | 27 | if (shouldDelegate(event)) return // delegate confirmation handling to Turbo 28 | 29 | const proceed = await confirmation.method(message) 30 | if (!proceed) event.preventDefault() 31 | }) 32 | 33 | export default confirmation 34 | -------------------------------------------------------------------------------- /app/javascript/delegates.js: -------------------------------------------------------------------------------- 1 | let events = [] 2 | let eventListener 3 | 4 | function register(eventName, selectors) { 5 | const match = events.find(evt => evt.name === eventName) 6 | if (match) events.splice(events.indexOf(match), 1) 7 | events = [{ name: eventName, selectors }, ...events] 8 | 9 | document.removeEventListener(eventName, eventListener, true) 10 | document.addEventListener(eventName, eventListener, true) 11 | 12 | return { ...events.find(evt => evt.name === eventName) } 13 | } 14 | 15 | function getRegisteredEventForElement(element) { 16 | return events.find(evt => 17 | evt.selectors.find(selector => Array.from(document.querySelectorAll(selector)).find(el => el === element)) 18 | ) 19 | } 20 | 21 | function isRegisteredForElement(eventName, element) { 22 | const evt = getRegisteredEventForElement(element) 23 | return evt && evt.name === eventName 24 | } 25 | 26 | export default { 27 | register, 28 | isRegisteredForElement, 29 | get events() { 30 | return [...events] 31 | }, 32 | set handler(fn) { 33 | eventListener = fn 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/javascript/drivers/form.js: -------------------------------------------------------------------------------- 1 | const invokeCommand = (form, payload = {}) => { 2 | const input = form.querySelector('input[name="turbo_boost_command"]') || document.createElement('input') 3 | input.type = 'hidden' 4 | input.name = 'turbo_boost_command' 5 | input.value = JSON.stringify(payload) 6 | if (!form.contains(input)) form.appendChild(input) 7 | } 8 | 9 | export default { invokeCommand } 10 | -------------------------------------------------------------------------------- /app/javascript/drivers/frame.js: -------------------------------------------------------------------------------- 1 | import { invoke } from '../invoker' 2 | 3 | const invokeCommand = (_, payload) => invoke(payload) 4 | 5 | export default { invokeCommand } 6 | -------------------------------------------------------------------------------- /app/javascript/drivers/method.js: -------------------------------------------------------------------------------- 1 | let activeElement 2 | let activePayload 3 | 4 | const reset = () => { 5 | activeElement = null 6 | activePayload = null 7 | } 8 | 9 | const invokeCommand = (element, payload = {}) => { 10 | activeElement = element 11 | activePayload = payload 12 | } 13 | 14 | const amendForm = form => { 15 | try { 16 | if (!activeElement) return 17 | if (form.getAttribute('method') !== activeElement.dataset.turboMethod) return 18 | if (form.getAttribute('action') !== activeElement.href) return 19 | 20 | const input = form.querySelector('input[name="turbo_boost_command"]') || document.createElement('input') 21 | input.type = 'hidden' 22 | input.name = 'turbo_boost_command' 23 | input.value = JSON.stringify(activePayload) 24 | if (!form.contains(input)) form.appendChild(input) 25 | } finally { 26 | reset() // ensure reset 27 | } 28 | } 29 | 30 | document.addEventListener('submit', event => amendForm(event.target), true) 31 | 32 | export default { invokeCommand } 33 | -------------------------------------------------------------------------------- /app/javascript/drivers/window.js: -------------------------------------------------------------------------------- 1 | import { invoke } from '../invoker' 2 | 3 | const invokeCommand = (_, payload = {}) => invoke(payload) 4 | 5 | export default { invokeCommand } 6 | -------------------------------------------------------------------------------- /app/javascript/elements.js: -------------------------------------------------------------------------------- 1 | import schema from './schema' 2 | 3 | function findClosestCommand(element) { 4 | return element.closest(`[${schema.commandAttribute}]`) 5 | } 6 | 7 | function findClosestFrameWithSource(element) { 8 | return ( 9 | element.closest('turbo-frame[src]') || 10 | element.closest('turbo-frame[data-turbo-frame-src]') || 11 | element.closest('turbo-frame') 12 | ) 13 | } 14 | 15 | function assignElementValueToPayload(element, payload = {}) { 16 | if (element.tagName.toLowerCase() !== 'select') return (payload.value = element.value || null) 17 | 18 | if (!element.multiple) return (payload.value = element.options[element.selectedIndex].value) 19 | 20 | payload.values = Array.from(element.options).reduce((memo, option) => { 21 | if (option.selected) memo.push(option.value) 22 | return memo 23 | }, []) 24 | } 25 | 26 | function buildAttributePayload(element) { 27 | const payload = Array.from(element.attributes).reduce((memo, attr) => { 28 | let value = attr.value 29 | memo[attr.name] = value 30 | return memo 31 | }, {}) 32 | 33 | payload.tag = element.tagName 34 | payload.checked = !!element.checked 35 | payload.disabled = !!element.disabled 36 | assignElementValueToPayload(element, payload) 37 | 38 | // reduce payload size to keep URL length smaller 39 | delete payload.class 40 | delete payload.action 41 | delete payload.href 42 | delete payload[schema.commandAttribute] 43 | delete payload[schema.frameAttribute] 44 | 45 | return payload 46 | } 47 | 48 | export default { 49 | buildAttributePayload, 50 | findClosestCommand, 51 | findClosestFrameWithSource 52 | } 53 | -------------------------------------------------------------------------------- /app/javascript/events.js: -------------------------------------------------------------------------------- 1 | export const commandEvents = { 2 | start: 'turbo-boost:command:start', 3 | success: 'turbo-boost:command:success', 4 | finish: 'turbo-boost:command:finish', 5 | abort: 'turbo-boost:command:abort', 6 | clientError: 'turbo-boost:command:client-error', 7 | serverError: 'turbo-boost:command:server-error' 8 | } 9 | 10 | export const stateEvents = { 11 | stateChange: 'turbo-boost:state:change', 12 | stateInitialize: 'turbo-boost:state:initialize' 13 | } 14 | 15 | export const turboEvents = { 16 | frameLoad: 'turbo:frame-load', 17 | load: 'turbo:load' 18 | } 19 | 20 | export function dispatch(name, target, options = {}) { 21 | return new Promise(resolve => { 22 | options = options || {} 23 | options.detail = options.detail || {} 24 | target = target || document 25 | const evt = new CustomEvent(name, { ...options, bubbles: true }) 26 | target.dispatchEvent(evt) 27 | resolve(evt) 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /app/javascript/headers.js: -------------------------------------------------------------------------------- 1 | const RESPONSE_HEADER = 'TurboBoost-Command' 2 | 3 | const types = { 4 | boost: 'text/vnd.turbo-boost.html', 5 | stream: 'text/vnd.turbo-stream.html', 6 | html: 'text/html', 7 | xhtml: 'application/xhtml+xml', 8 | json: 'application/json' 9 | } 10 | 11 | // Prepares request headers for TurboBoost Command invocations 12 | const prepare = (headers = {}) => { 13 | headers = { ...headers } 14 | 15 | // Assign Accept values 16 | const accepts = (headers['Accept'] || '') 17 | .split(',') 18 | .map(val => val.trim()) 19 | .filter(val => val.length) 20 | 21 | accepts.unshift(types.boost, types.stream, types.html, types.xhtml) 22 | headers['Accept'] = [...new Set(accepts)].join(', ') 23 | 24 | // Assign Content-Type (Commands POST JSON via fetch/XHR) 25 | headers['Content-Type'] = types.json 26 | 27 | // Assign X-Requested-With for XHR detection 28 | headers['X-Requested-With'] = 'XMLHttpRequest' 29 | 30 | return headers 31 | } 32 | 33 | // Tokenizes the 'TurboBoost-Command' HTTP response header value 34 | const tokenize = value => { 35 | if (value) { 36 | const [name, status, strategy] = value.split(', ') 37 | return { name, status, strategy } 38 | } 39 | 40 | return {} 41 | } 42 | 43 | export default { prepare, tokenize, RESPONSE_HEADER } 44 | -------------------------------------------------------------------------------- /app/javascript/invoker.js: -------------------------------------------------------------------------------- 1 | import headers from './headers' 2 | import lifecycle from './lifecycle' 3 | import urls from './urls' 4 | import { dispatch } from './events' 5 | import { render } from './renderer' 6 | 7 | const parseError = error => { 8 | const message = `Unexpected error performing a TurboBoost Command! ${error.message}` 9 | dispatch(lifecycle.events.clientError, document, { detail: { message, error } }, true) 10 | } 11 | 12 | const parseAndRenderResponse = response => { 13 | const { strategy } = headers.tokenize(response.headers.get(headers.RESPONSE_HEADER)) 14 | response.text().then(content => render(strategy, content)) 15 | } 16 | 17 | const invoke = (payload = {}) => { 18 | try { 19 | fetch(urls.commandInvocationURL.href, { 20 | method: 'POST', 21 | headers: headers.prepare({}), 22 | body: JSON.stringify(payload) 23 | }) 24 | .then(parseAndRenderResponse) 25 | .catch(parseError) 26 | } catch (error) { 27 | parseError(error) 28 | } 29 | } 30 | 31 | export { invoke } 32 | -------------------------------------------------------------------------------- /app/javascript/lifecycle.js: -------------------------------------------------------------------------------- 1 | import activity from './activity' 2 | import { dispatch, commandEvents } from './events' 3 | 4 | function finish(event) { 5 | setTimeout(() => dispatch(commandEvents.finish, event.target, { detail: event.detail })) 6 | } 7 | 8 | const events = [commandEvents.abort, commandEvents.serverError, commandEvents.success] 9 | events.forEach(name => addEventListener(name, finish)) 10 | addEventListener(commandEvents.finish, event => activity.remove(event.detail.id), true) 11 | 12 | export default { events: commandEvents } 13 | -------------------------------------------------------------------------------- /app/javascript/renderer.js: -------------------------------------------------------------------------------- 1 | const append = content => { 2 | document.body.insertAdjacentHTML('beforeend', content) 3 | } 4 | 5 | const replace = content => { 6 | const parser = new DOMParser() 7 | const doc = parser.parseFromString(content, 'text/html') 8 | const head = document.querySelector('head') 9 | const body = document.querySelector('body') 10 | const newHead = doc.querySelector('head') 11 | const newBody = doc.querySelector('body') 12 | if (head && newHead) TurboBoost?.Streams?.morph?.method(head, newHead) 13 | if (body && newBody) TurboBoost?.Streams?.morph?.method(body, newBody) 14 | } 15 | 16 | // TODO: dispatch events after append/replace so we can apply page state 17 | export const render = (strategy, content) => { 18 | if (strategy && content) { 19 | if (strategy.match(/^Append$/i)) return append(content) 20 | if (strategy.match(/^Replace$/i)) return replace(content) 21 | } 22 | } 23 | 24 | export default { render } 25 | -------------------------------------------------------------------------------- /app/javascript/schema.js: -------------------------------------------------------------------------------- 1 | const schema = { 2 | // attributes 3 | frameAttribute: 'data-turbo-frame', 4 | methodAttribute: 'data-turbo-method', 5 | commandAttribute: 'data-turbo-command', 6 | confirmAttribute: 'data-turbo-confirm', 7 | stateAttributesAttribute: 'data-turbo-boost-state-attributes' 8 | } 9 | 10 | export default { ...schema } 11 | -------------------------------------------------------------------------------- /app/javascript/state/index.js: -------------------------------------------------------------------------------- 1 | // TODO: Move State to its own library 2 | import observable from './observable' 3 | import page from './page' 4 | import storage from './storage' 5 | import { dispatch, stateEvents } from '../events' 6 | 7 | const key = 'TurboBoost::State' 8 | const stub = { pages: {}, signed: null, unsigned: {} } 9 | 10 | let signed = null // signed state 11 | let unsigned = {} // unsigned state (optimistic) 12 | 13 | const restore = () => { 14 | const saved = { ...stub, ...storage.find(key) } 15 | signed = saved.signed 16 | unsigned = observable(saved.unsigned) 17 | saved.pages[location.pathname] = saved.pages[location.pathname] || {} 18 | page.restoreState(saved.pages[location.pathname]) 19 | } 20 | 21 | const save = () => { 22 | const saved = { ...stub, ...storage.find(key) } 23 | const fresh = { 24 | signed: signed || saved.signed, 25 | unsigned: { ...saved.unsigned, ...unsigned }, 26 | pages: { ...saved.pages } 27 | } 28 | 29 | // update the current page's state entry 30 | const pageKey = location.pathname 31 | const pageState = page.buildState() 32 | Object.values(pageState).length ? (fresh.pages[pageKey] = pageState) : delete fresh.pages[pageKey] 33 | 34 | storage.save(key, fresh) 35 | } 36 | 37 | const initialize = json => { 38 | const state = { ...stub, ...JSON.parse(json) } 39 | signed = state.signed 40 | unsigned = observable(state.unsigned) 41 | save() 42 | dispatch(stateEvents.stateInitialize, document, { detail: unsigned }) 43 | } 44 | 45 | // setup 46 | addEventListener('DOMContentLoaded', restore) 47 | addEventListener('turbo:morph', restore) 48 | addEventListener('turbo:render', restore) 49 | addEventListener('turbo:before-fetch-request', save) 50 | addEventListener('beforeunload', save) 51 | 52 | export default { 53 | initialize, 54 | buildPageState: page.buildState, 55 | get signed() { 56 | return signed 57 | }, 58 | get unsigned() { 59 | return unsigned 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/javascript/state/observable.js: -------------------------------------------------------------------------------- 1 | import { dispatch, stateEvents as events } from '../events' 2 | 3 | let head 4 | 5 | function observable(object, parent = null) { 6 | if (!object || typeof object !== 'object') return object 7 | 8 | const proxy = new Proxy(object, { 9 | deleteProperty(target, key) { 10 | delete target[key] 11 | dispatch(events.stateChange, document, { detail: { state: head } }) 12 | return true 13 | }, 14 | 15 | set(target, key, value, _receiver) { 16 | target[key] = observable(value, this) 17 | dispatch(events.stateChange, document, { detail: { state: head } }) 18 | return true 19 | } 20 | }) 21 | 22 | if (Array.isArray(object)) { 23 | object.forEach((value, index) => (object[index] = observable(value, proxy))) 24 | } else if (typeof object === 'object') { 25 | for (const [key, value] of Object.entries(object)) object[key] = observable(value, proxy) 26 | } 27 | 28 | if (!parent) head = proxy 29 | return proxy 30 | } 31 | 32 | export default observable 33 | -------------------------------------------------------------------------------- /app/javascript/state/page.js: -------------------------------------------------------------------------------- 1 | import schema from '../schema.js' 2 | 3 | const updateElement = (id, attribute, value, attempts = 1) => { 4 | if (attempts > 20) return 5 | const element = document.getElementById(id) 6 | if (element?.isConnected) return element.setAttribute(attribute, value) 7 | setTimeout(() => updateElement(id, attribute, value, attempts + 1), attempts * 5) 8 | } 9 | 10 | const buildState = () => { 11 | const elements = Array.from(document.querySelectorAll(`[id][${schema.stateAttributesAttribute}]`)) 12 | return elements.reduce((memo, element) => { 13 | const attributes = JSON.parse(element.getAttribute(schema.stateAttributesAttribute)) 14 | if (element.id) { 15 | const stateAttributes = attributes.reduce((acc, name) => { 16 | if (element.hasAttribute(name)) acc[name] = element.getAttribute(name) || name 17 | return acc 18 | }, {}) 19 | if (Object.values(stateAttributes).length) memo[element.id] = stateAttributes 20 | } 21 | return memo 22 | }, {}) 23 | } 24 | 25 | const restoreState = (state = {}) => { 26 | for (const [id, attributes] of Object.entries(state)) { 27 | for (const [attribute, value] of Object.entries(attributes)) updateElement(id, attribute, value) 28 | } 29 | } 30 | 31 | export default { 32 | buildState, 33 | restoreState 34 | } 35 | -------------------------------------------------------------------------------- /app/javascript/state/storage.js: -------------------------------------------------------------------------------- 1 | function save(name, value) { 2 | if (typeof value !== 'object') value = {} 3 | return localStorage.setItem(String(name), JSON.stringify(value)) 4 | } 5 | 6 | function find(name) { 7 | const stored = localStorage.getItem(String(name)) 8 | return stored ? JSON.parse(stored) : {} 9 | } 10 | 11 | export default { save, find } 12 | -------------------------------------------------------------------------------- /app/javascript/turbo.js: -------------------------------------------------------------------------------- 1 | import headers from './headers' 2 | import { render } from './renderer' 3 | 4 | const frameSources = {} 5 | 6 | // fires after receiving a turbo HTTP response 7 | addEventListener('turbo:before-fetch-response', event => { 8 | const frame = event.target.closest('turbo-frame') 9 | if (frame?.id && frame?.src) frameSources[frame.id] = frame.src 10 | 11 | const { fetchResponse: response } = event.detail 12 | const header = response.header(headers.RESPONSE_HEADER) 13 | 14 | if (!header) return 15 | 16 | // We'll take it from here Hotwire... 17 | event.preventDefault() 18 | const { strategy } = headers.tokenize(header) 19 | response.responseHTML.then(content => render(strategy, content)) 20 | }) 21 | 22 | // fires when a frame element is navigated and finishes loading 23 | addEventListener('turbo:frame-load', event => { 24 | const frame = event.target.closest('turbo-frame') 25 | frame.dataset.src = frameSources[frame.id] || frame.src || frame.dataset.src 26 | delete frameSources[frame.id] 27 | }) 28 | -------------------------------------------------------------------------------- /app/javascript/urls.js: -------------------------------------------------------------------------------- 1 | const buildURL = path => { 2 | const a = document.createElement('a') 3 | a.href = path 4 | return new URL(a) 5 | } 6 | 7 | export default { 8 | get commandInvocationURL() { 9 | return buildURL('/turbo-boost-command-invocation') 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/javascript/uuids.js: -------------------------------------------------------------------------------- 1 | function v4() { 2 | return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => 3 | (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16) 4 | ) 5 | } 6 | 7 | export default { v4 } 8 | -------------------------------------------------------------------------------- /app/javascript/version.js: -------------------------------------------------------------------------------- 1 | export default '0.3.2' 2 | -------------------------------------------------------------------------------- /bin/build.mjs: -------------------------------------------------------------------------------- 1 | import * as esbuild from 'esbuild' 2 | import fs from 'fs' 3 | 4 | const debug = process.argv.includes('--debug') 5 | 6 | const context = await esbuild.context({ 7 | entryPoints: ['app/javascript/index.js'], 8 | external: ['@hotwired/turbo'], 9 | bundle: true, 10 | format: 'esm', 11 | logLevel: 'debug', 12 | metafile: true, 13 | minify: !debug, 14 | outfile: 'app/assets/builds/@turbo-boost/commands.js', 15 | sourcemap: true, 16 | target: ['chrome79', 'edge44', 'es2020', 'firefox71', 'opera65', 'safari13'] 17 | }) 18 | 19 | const watch = process.argv.includes('--watch') 20 | 21 | if (watch) { 22 | context.logLevel = 'verbose' 23 | await context.watch() 24 | } else { 25 | const result = await context.rebuild() 26 | const metafile = 'app/assets/builds/@turbo-boost/commands.metafile.json' 27 | 28 | fs.writeFile(metafile, JSON.stringify(result.metafile), ex => { 29 | if (ex) { 30 | console.error('Build failed!❗️') 31 | conosle.error(ex) 32 | } else { 33 | const fileSize = fs.statSync('app/assets/builds/@turbo-boost/commands.js').size 34 | const fileSizeInKB = Math.round(fileSize / 1024) 35 | const message = [ 36 | 'Build succeeded! 🚀', 37 | `|- Metafile saved to ... → ${metafile} (${fileSizeInKB}KB)`, 38 | '|- Analyze the bundle at → https://esbuild.github.io/analyze/' 39 | ] 40 | console.log(message.join('\n')) 41 | } 42 | context.dispose() 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /bin/docker/run/local: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | shopt -s globstar 4 | 5 | # ============================================================================================================ 6 | # Jemalloc - SEE: https://jemalloc.net 7 | # ============================================================================================================ 8 | jemalloc="/usr/lib/x86_64-linux-gnu/libjemalloc.so.2" 9 | if [ "$(uname -m)" = "x86_64" ] && [ -f "$jemalloc" ]; then 10 | export LD_PRELOAD=$jemalloc 11 | fi 12 | 13 | jemalloc="/usr/lib/aarch64-linux-gnu/libjemalloc.so.2" 14 | if [ "$(uname -m)" = "aarch64" ] && [ -f "$jemalloc" ]; then 15 | export LD_PRELOAD=$jemalloc 16 | fi 17 | 18 | 19 | # ============================================================================================================ 20 | # Dependencies 21 | # ============================================================================================================ 22 | npm install 23 | # if [ ! -f "/mnt/external/.playwright" ]; then 24 | yes | npx playwright@latest install chromium --with-deps 25 | # touch /mnt/external/.playwright 26 | # fi 27 | bundle install 28 | 29 | 30 | # ============================================================================================================ 31 | # Prepare & Run the Application 32 | # ============================================================================================================ 33 | cd test/dummy 34 | rm -rf tmp/pids/* 35 | bin/rails db:create db:migrate db:seed 36 | bin/rails server --binding=0.0.0.0 --port=3000 37 | -------------------------------------------------------------------------------- /bin/docker/run/remote: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | # ============================================================================================================ 5 | # Jemalloc - SEE: https://jemalloc.net 6 | # ============================================================================================================ 7 | jemalloc="/usr/lib/x86_64-linux-gnu/libjemalloc.so.2" 8 | if [ "$(uname -m)" = "x86_64" ] && [ -f "$jemalloc" ]; then 9 | export LD_PRELOAD=$jemalloc 10 | fi 11 | 12 | jemalloc="/usr/lib/aarch64-linux-gnu/libjemalloc.so.2" 13 | if [ "$(uname -m)" = "aarch64" ] && [ -f "$jemalloc" ]; then 14 | export LD_PRELOAD=$jemalloc 15 | fi 16 | 17 | 18 | # ============================================================================================================ 19 | # Dependencies 20 | # ============================================================================================================ 21 | npm install 22 | bundle install 23 | 24 | 25 | # ============================================================================================================ 26 | # Prepare & Run the Application 27 | # ============================================================================================================ 28 | cd test/dummy 29 | rm -rf tmp/pids/* 30 | bin/rails db:create db:migrate db:seed 31 | bin/rails assets:clean 32 | bin/rails server --binding=0.0.0.0 --port=3000 33 | -------------------------------------------------------------------------------- /bin/fly/bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | fly ssh console -a turbo-boost-streams --pty -C "/bin/bash" 4 | -------------------------------------------------------------------------------- /bin/fly/console: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | fly ssh console -a turbo-boost-commands --pty -C "/app/test/dummy/bin/rails c" 4 | -------------------------------------------------------------------------------- /bin/loc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cloc --exclude-dir=assets app lib 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails gems 3 | # installed from the root of your application. 4 | 5 | ENGINE_ROOT = File.expand_path("..", __dir__) 6 | ENGINE_PATH = File.expand_path("../lib/turbo_boost/commands/engine", __dir__) 7 | APP_PATH = File.expand_path("../test/dummy/config/application", __dir__) 8 | 9 | # Set up gems listed in the Gemfile. 10 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 11 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) 12 | 13 | require "rails/all" 14 | require "rails/engine/commands" 15 | -------------------------------------------------------------------------------- /bin/standardize: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | shopt -s globstar 4 | bundle exec magic_frozen_string_literal 5 | bundle exec standardrb --fix 6 | npx prettier --write . 7 | cd test/dummy && bin/rails tailwindcss:clobber tailwindcss:build && cd - 8 | -------------------------------------------------------------------------------- /fly.toml: -------------------------------------------------------------------------------- 1 | # fly.toml app configuration file generated for turbo-boost-commands on 2023-05-23T22:00:07-06:00 2 | # 3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 | # 5 | 6 | app = "turbo-boost-commands" 7 | primary_region = "den" 8 | 9 | [env] 10 | DATABASE_URL = "sqlite3:///mnt/external/database/development.sqlite3" 11 | GEM_HOME = "/mnt/external/gems" 12 | RAILS_ENV = "development" 13 | RAILS_LOG_TO_STDOUT = "true" 14 | RAILS_MAX_THREADS = "6" 15 | RAILS_RELATIVE_URL_ROOT = "/@turbo-boost/commands" 16 | RAILS_SERVE_STATIC_FILES = "true" 17 | WEB_CONCURRENCY = "2" 18 | YARN_CACHE_FOLDER = "/mnt/external/yarn/.cache" 19 | 20 | [[mounts]] 21 | source = "turbo_boost_commands_volume" 22 | destination = "/mnt/external" 23 | 24 | [http_service] 25 | internal_port = 3000 26 | force_https = true 27 | auto_start_machines = true 28 | auto_stop_machines = false 29 | 30 | [[statics]] 31 | guest_path = "/app" 32 | url_prefix = "/" 33 | -------------------------------------------------------------------------------- /lib/turbo_boost/commands.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "turbo_boost/commands/engine" 4 | -------------------------------------------------------------------------------- /lib/turbo_boost/commands/command_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TurboBoost::Commands::CommandValidator 4 | def initialize(command, method_name) 5 | @command = command 6 | @method_name = method_name.to_sym 7 | end 8 | 9 | attr_reader :command, :method_name 10 | 11 | def validate 12 | valid_class? && valid_method? 13 | end 14 | alias_method :valid?, :validate 15 | 16 | def validate! 17 | message = "`#{command.class.name}` is not a subclass of `#{TurboBoost::Commands::Command.name}`!" 18 | raise TurboBoost::Commands::InvalidClassError.new(message, command: command.class) unless valid_class? 19 | 20 | message = "`#{command.class.name}` does not define the public method `#{method_name}`!" 21 | raise TurboBoost::Commands::InvalidMethodError.new(message, command: command.class) unless valid_method? 22 | 23 | true 24 | end 25 | 26 | private 27 | 28 | def command_ancestors 29 | range = 0..(command.class.ancestors.index(TurboBoost::Commands::Command).to_i - 1) 30 | command.class.ancestors.[](range) || [] 31 | end 32 | 33 | def valid_class? 34 | command.class.ancestors.include? TurboBoost::Commands::Command 35 | end 36 | 37 | def valid_method? 38 | return false unless valid_class? 39 | return false unless command_ancestors.any? { |a| a.public_instance_methods(false).any? method_name } 40 | 41 | method = command.class.public_instance_method(method_name) 42 | method&.parameters&.none? 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/turbo_boost/commands/controller_pack.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "runner" 4 | 5 | class TurboBoost::Commands::ControllerPack 6 | include TurboBoost::Commands::AttributeHydration 7 | 8 | def initialize(controller) 9 | @runner = TurboBoost::Commands::Runner.new(controller) 10 | @command = runner.command_instance 11 | end 12 | 13 | attr_reader :runner, :command 14 | 15 | delegate( 16 | :command_aborted?, 17 | :command_errored?, 18 | :command_performed?, 19 | :command_performing?, 20 | :command_requested?, 21 | :command_succeeded?, 22 | :state, 23 | to: :runner 24 | ) 25 | 26 | # DEPRECATED: This method will removed in a future release 27 | def controller 28 | ActiveSupport::Deprecation.warn "This method will removed in a future release." 29 | runner.controller 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/turbo_boost/commands/errors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TurboBoost::Commands 4 | class StateError < StandardError 5 | end 6 | 7 | class CommandError < StandardError 8 | def initialize(*messages, command:, http_status: :internal_server_error, cause: nil) 9 | @cause = cause 10 | @command = command 11 | @http_status_code = TurboBoost::Commands.http_status_code(http_status) 12 | 13 | messages.prepend "HTTP #{http_status_code} #{TurboBoost::Commands::HTTP_STATUS_CODES[http_status_code]}" 14 | messages << cause.message if cause 15 | messages << location 16 | 17 | super(messages.uniq.select(&:present?).join("; ")) 18 | end 19 | 20 | attr_reader :cause, :command, :http_status_code 21 | 22 | def location 23 | line = (cause&.backtrace || []).first.to_s 24 | line.[](/[^\/]+\.rb:\d+/i) 25 | end 26 | end 27 | 28 | class InvalidTokenError < CommandError 29 | end 30 | 31 | class InvalidClassError < CommandError 32 | end 33 | 34 | class InvalidMethodError < CommandError 35 | end 36 | 37 | class InvalidElementError < CommandError 38 | end 39 | 40 | class AbortError < CommandError 41 | def initialize(*messages, **kwargs) 42 | messages.prepend "TurboBoost Command intentionally aborted via `throw` in a `[before,after,around]_command` lifecycle callback!" 43 | super(messages, http_status: :abort_turbo_boost_command, **kwargs) 44 | end 45 | end 46 | 47 | class PerformError < CommandError 48 | def initialize(*messages, **kwargs) 49 | messages.prepend "Unexpected error encountered while performing a TurboBoost Command!" 50 | super(messages, http_status: :internal_server_error, **kwargs) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/turbo_boost/commands/http_status_codes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TurboBoost::Commands 4 | HTTP_ABORT_STATUS_CODE = 285 # I-285 is the most congested highway in the US (traffic jams → halt/abort) 5 | 6 | HTTP_STATUS_CODES = Rack::Utils::HTTP_STATUS_CODES.merge( 7 | HTTP_ABORT_STATUS_CODE => "Abort TurboBoost Command" 8 | ).freeze 9 | 10 | def self.http_status_code(value) 11 | return value.to_i unless value.is_a?(String) || value.is_a?(Symbol) 12 | return value.to_i if value.match?(/\A\d+\z/) 13 | 14 | case value.to_sym 15 | when :abort_turbo_boost_command then HTTP_ABORT_STATUS_CODE 16 | else Rack::Utils::SYMBOL_TO_STATUS_CODE[value.to_sym] 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/turbo_boost/commands/patches.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TurboBoost::Commands::Patches 4 | end 5 | 6 | require_relative "patches/action_view_helpers_tag_helper_tag_builder_patch" 7 | -------------------------------------------------------------------------------- /lib/turbo_boost/commands/patches/action_view_helpers_tag_helper_tag_builder_patch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../attribute_hydration" 4 | 5 | module TurboBoost::Commands::Patches::ActionViewHelpersTagHelperTagBuilderPatch 6 | def tag_options(options, ...) 7 | options = turbo_boost&.state&.tag_options(options) || options 8 | options = TurboBoost::Commands::AttributeHydration.dehydrate(options) 9 | super 10 | end 11 | 12 | private 13 | 14 | def turbo_boost 15 | return nil unless @view_context.respond_to?(:turbo_boost) 16 | @view_context.turbo_boost 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/turbo_boost/commands/responder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TurboBoost::Commands::Responder 4 | attr_accessor :headers 5 | 6 | def initialize 7 | @headers = HashWithIndifferentAccess.new 8 | @body = Set.new 9 | end 10 | 11 | def body 12 | @body.join.html_safe 13 | end 14 | 15 | def add_header(key, value) 16 | headers[key.to_s.downcase] = value.to_s 17 | end 18 | 19 | def add_content(content) 20 | @body << sanitizer.sanitize(content) + "\n" 21 | end 22 | 23 | private 24 | 25 | def sanitizer 26 | TurboBoost::Commands::Sanitizer.instance 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/turbo_boost/commands/sanitizer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TurboBoost::Commands::Sanitizer 4 | include Singleton 5 | include ActionView::Helpers::SanitizeHelper 6 | 7 | attr_reader :scrubber 8 | 9 | def sanitize(value) 10 | super(value.to_s, scrubber: scrubber) 11 | end 12 | 13 | private 14 | 15 | def initialize 16 | @scrubber = Loofah::Scrubber.new do |node| 17 | node.remove if node.name == "script" 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/turbo_boost/commands/state_store.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TurboBoost::Commands::StateStore < ActiveSupport::Cache::MemoryStore 4 | include Enumerable 5 | 6 | class NoCoder 7 | def dump(value) 8 | value 9 | end 10 | 11 | def load(value) 12 | value 13 | end 14 | end 15 | 16 | SGID_PURPOSE = name.dup.freeze 17 | 18 | def initialize(payload = {}) 19 | super(coder: NoCoder.new, compress: false, expires_in: 1.day, size: 16.kilobytes) 20 | 21 | begin 22 | payload = case payload 23 | when SignedGlobalID then URI::UID.from_sgid(payload, for: SGID_PURPOSE)&.decode 24 | when GlobalID then URI::UID.from_gid(payload)&.decode 25 | when String then URI::UID.from_sgid(payload, for: SGID_PURPOSE)&.decode || URI::UID.from_gid(payload, for: SGID_PURPOSE)&.decode 26 | else payload 27 | end 28 | rescue => error 29 | Rails.logger.error "Failed to decode URI::UID when creating a TurboBoost::Commands::StateStore! #{error.message}" 30 | payload = {} 31 | end 32 | 33 | merge! payload 34 | end 35 | 36 | alias_method :[], :read 37 | alias_method :[]=, :write 38 | 39 | def to_h 40 | @data 41 | .each_with_object({}) { |(key, entry), memo| memo[key] = entry.value } 42 | .with_indifferent_access 43 | end 44 | 45 | delegate :dig, :each, to: :to_h 46 | 47 | def merge!(other = {}) 48 | other.to_h.each { |key, val| write key, val } 49 | self 50 | end 51 | 52 | def to_uid 53 | cleanup 54 | URI::UID.build to_h, include_blank: false 55 | end 56 | 57 | def to_gid 58 | to_uid.to_gid 59 | end 60 | 61 | def to_gid_param 62 | to_gid.to_param 63 | end 64 | 65 | def to_sgid 66 | to_uid.to_sgid for: SGID_PURPOSE, expires_in: 1.day 67 | end 68 | 69 | def to_sgid_param 70 | to_sgid.to_param 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/turbo_boost/commands/token_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TurboBoost::Commands::TokenValidator 4 | def initialize(command, method_name) 5 | @command = command 6 | @method_name = method_name 7 | end 8 | 9 | attr_reader :command, :method_name 10 | delegate :controller, to: :command 11 | delegate :session, to: :controller 12 | 13 | def validate 14 | return true unless TurboBoost::Commands.config.protect_from_forgery 15 | tokens.any? { |token| valid_token? token } 16 | end 17 | 18 | alias_method :valid?, :validate 19 | 20 | def validate! 21 | return true if valid? 22 | 23 | message = <<~MSG 24 | `#{command.class.name}##{method_name}` invoked with an invalid authenticity token! 25 | 26 | Verify that your page includes `<%= csrf_meta_tags %>` in the header. 27 | 28 | If the problem persists, you can disable forgery protection with `TurboBoost::Commands.config.protect_from_forgery = false` 29 | MSG 30 | 31 | raise TurboBoost::Commands::InvalidTokenError.new(message, command: command.class) 32 | end 33 | 34 | private 35 | 36 | def tokens 37 | list = Set.new.tap do |set| 38 | set.add command.params[:csrf_token] 39 | set.add controller.request.x_csrf_token 40 | set.add controller.params[controller.class.request_forgery_protection_token] 41 | end 42 | 43 | list.select(&:present?).to_a 44 | end 45 | 46 | def valid_token?(token) 47 | # TODO: Update to use Rails' public API 48 | controller.send :valid_authenticity_token?, session, token 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/turbo_boost/commands/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TurboBoost 4 | module Commands 5 | VERSION = "0.3.2" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@turbo-boost/commands", 3 | "version": "0.3.2", 4 | "description": "Commands to help you build robust reactive applications with Rails & Hotwire.", 5 | "keywords": [ 6 | "hotwire", 7 | "hotwired", 8 | "rails", 9 | "turbo", 10 | "turbo-boost" 11 | ], 12 | "main": "app/assets/builds/@turbo-boost/commands.js", 13 | "files": [ 14 | "app/assets/builds/@turbo-boost/*{.js,.map}" 15 | ], 16 | "repository": "https://github.com/hopsoft/turbo_boost-commands", 17 | "author": "Nate Hopkins (hopsoft) ", 18 | "license": "MIT", 19 | "dependencies": { 20 | "debounced": "^1.0.0" 21 | }, 22 | "peerDependencies": { 23 | "@hotwired/turbo-rails": ">= 7.2.0", 24 | "@turbo-boost/streams": ">= 0.1.11" 25 | }, 26 | "devDependencies": { 27 | "@tailwindcss/aspect-ratio": "^0.4.2", 28 | "@tailwindcss/forms": "^0.5.7", 29 | "@tailwindcss/typography": "^0.5.13", 30 | "esbuild": "^0.21.5", 31 | "flowbite": "1.7.0", 32 | "playwright": "^1.44.0", 33 | "prettier": "^3.3.2", 34 | "prettier-plugin-tailwindcss": "^0.6.4", 35 | "tailwindcss": "^3.4.4" 36 | }, 37 | "scripts": { 38 | "build": "node bin/build.mjs", 39 | "build:watch": "node bin/build.mjs --watch" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSameLine: true, 4 | bracketSpacing: true, 5 | embeddedLanguageFormatting: 'auto', 6 | htmlWhitespaceSensitivity: 'css', 7 | insertPragma: false, 8 | jsxSingleQuote: false, 9 | plugins: ['prettier-plugin-tailwindcss'], 10 | printWidth: 110, 11 | proseWrap: 'preserve', 12 | quoteProps: 'as-needed', 13 | requirePragma: false, 14 | semi: false, 15 | singleAttributePerLine: false, 16 | singleQuote: true, 17 | tabWidth: 2, 18 | tailwindConfig: './test/dummy/config/tailwind.config.js', 19 | trailingComma: 'none', 20 | useTabs: false, 21 | vueIndentScriptAndStyle: false 22 | } 23 | -------------------------------------------------------------------------------- /test/attribute_set_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class AttributeSetTest < ActiveSupport::TestCase 6 | include TurboBoost::Commands::AttributeHydration 7 | 8 | test "prefix selection" do 9 | attributes = {test_a: "included", b: "excluded"} 10 | attrs = TurboBoost::Commands::AttributeSet.new(attributes, prefix: :test) 11 | assert_equal "included", attrs.a 12 | assert_nil attrs.b 13 | end 14 | 15 | test "presence check" do 16 | attributes = {test_a: "value", test_b: ""} 17 | attrs = TurboBoost::Commands::AttributeSet.new(attributes, prefix: :test) 18 | assert attrs.a? 19 | refute attrs.b? 20 | end 21 | 22 | test "boolean type coercion" do 23 | attributes = {test_a: "true", test_b: "false"} 24 | attrs = TurboBoost::Commands::AttributeSet.new(attributes, prefix: :test) 25 | assert attrs.a? 26 | assert_equal true, attrs.a 27 | refute attrs.b? 28 | assert_equal false, attrs.b 29 | end 30 | 31 | test "integer type coercion" do 32 | attributes = {test_a: "54872"} 33 | attrs = TurboBoost::Commands::AttributeSet.new(attributes, prefix: :test) 34 | assert attrs.a? 35 | assert attrs.a.is_a? Numeric 36 | assert_equal 54872, attrs.a 37 | end 38 | 39 | test "type coercion with number containg a leading 0 remains a string" do 40 | attributes = {test: "00000"} 41 | attrs = TurboBoost::Commands::AttributeSet.new(attributes) 42 | assert attrs.test? 43 | assert attrs.test.is_a? String 44 | assert_equal "00000", attrs.test 45 | end 46 | 47 | test "implicit hydration" do 48 | attributes = {test_a: "value", data: {locals: {user: User.first}}}.with_indifferent_access 49 | dehydrated = dehydrate(attributes) 50 | attrs = TurboBoost::Commands::AttributeSet.new(dehydrated) 51 | assert_equal attributes, attrs.to_h 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/dummy/Procfile.dev: -------------------------------------------------------------------------------- 1 | web: bin/rails server -p 3000 2 | css: bin/rails tailwindcss:watch 3 | -------------------------------------------------------------------------------- /test/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Add your own tasks in files placed in lib/tasks ending in .rake, 4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 5 | 6 | require_relative "config/application" 7 | 8 | Rails.application.load_tasks 9 | -------------------------------------------------------------------------------- /test/dummy/app/assets/builds/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/test/dummy/app/assets/builds/.keep -------------------------------------------------------------------------------- /test/dummy/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | //= link_tree ../../javascript .js 4 | //= link_tree ../../../../../app/assets/images 5 | //= link_tree ../../../vendor/javascript .js 6 | //= link_tree ../builds 7 | -------------------------------------------------------------------------------- /test/dummy/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/test/dummy/app/assets/images/.keep -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); 2 | @import 'pico'; 3 | @import 'pygments'; 4 | 5 | html.dark .light-mode { 6 | display: none; 7 | } 8 | 9 | html.light .dark-mode { 10 | display: none; 11 | } 12 | 13 | body.busy, 14 | body.busy * { 15 | cursor: wait !important; 16 | } 17 | -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/application.tailwind.css: -------------------------------------------------------------------------------- 1 | @import './tailwind/base'; 2 | @import '../../views/basic_commands/@stylesheet'; 3 | @import '../../views/features/@stylesheet'; 4 | @import '../../views/instructions/@stylesheet'; 5 | 6 | @tailwind base; 7 | @tailwind components; 8 | @tailwind utilities; 9 | @tailwind variants; 10 | -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/pico.css: -------------------------------------------------------------------------------- 1 | @import 'https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css'; 2 | 3 | [aria-busy='true']::before { 4 | display: none !important; 5 | } 6 | -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/tailwind/base.css: -------------------------------------------------------------------------------- 1 | @layer base { 2 | code { 3 | @apply rounded bg-gray-100 px-1 py-0.5 text-sm dark:bg-gray-800; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/dummy/app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Channel < ActionCable::Channel::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/dummy/app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Connection < ActionCable::Connection::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/dummy/app/commands/application_command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationCommand < TurboBoost::Commands::Command 4 | # Abort a command from a before callback by invoking `throw :abort` 5 | # before_command { throw :abort } 6 | 7 | # NOTE: Callbacks use throw/catch to manage control flow which means 8 | # this is not a real error per se, but you can intercept with rescue_from 9 | rescue_from TurboBoost::Commands::AbortError do |error| 10 | # do something... 11 | end 12 | 13 | # Setup an error handler with `on_error` 14 | rescue_from TurboBoost::Commands::PerformError do |error| 15 | # do something... 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/dummy/app/commands/decrement_count_command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DecrementCountCommand < ApplicationCommand 4 | def perform 5 | Current.user.decrement! :count 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/commands/drivers/form/allow_controller_action_command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Drivers 4 | module Form 5 | class AllowControllerActionCommand < ApplicationCommand 6 | after_command -> { transfer_instance_variables controller } 7 | 8 | def perform 9 | # store count in the state 10 | key = "#{self.class.name}/count" 11 | state[key] = state[key].to_i + 1 12 | 13 | @message = "#{self.class.name.demodulize} invoked #{state[key]} times" 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/dummy/app/commands/drivers/form/prevent_controller_action_command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Drivers 4 | module Form 5 | class PreventControllerActionCommand < ApplicationCommand 6 | prevent_controller_action 7 | 8 | def perform 9 | Current.template = "tests/drivers/_form.turbo_stream.erb" 10 | 11 | # store count in the state 12 | key = "#{self.class.name}/count" 13 | state[key] = state[key].to_i + 1 14 | 15 | streams << render( 16 | partial: "/tests/drivers/form", 17 | formats: [:turbo_stream], 18 | assigns: {message: "#{self.class.name.demodulize} invoked #{state[key]} times"} 19 | ) 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/dummy/app/commands/drivers/frame/allow_controller_action_command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Drivers 4 | module Frame 5 | class AllowControllerActionCommand < ApplicationCommand 6 | after_command -> { transfer_instance_variables controller } 7 | 8 | def perform 9 | # store count in the state 10 | key = "#{self.class.name}/count" 11 | state[key] = state[key].to_i + 1 12 | 13 | @message = "#{self.class.name.demodulize} invoked #{state[key]} times" 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/dummy/app/commands/drivers/frame/prevent_controller_action_command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Drivers 4 | module Frame 5 | class PreventControllerActionCommand < ApplicationCommand 6 | prevent_controller_action 7 | 8 | def perform 9 | Current.template = "tests/drivers/frame/_turbo_stream.html.erb" 10 | 11 | # store count in the state 12 | key = "#{self.class.name}/count" 13 | state[key] = state[key].to_i + 1 14 | 15 | streams << render( 16 | partial: "/tests/drivers/frame", 17 | formats: [:turbo_stream], 18 | assigns: {message: "#{self.class.name.demodulize} invoked #{state[key]} times"} 19 | ) 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/dummy/app/commands/drivers/method/allow_controller_action_command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Drivers 4 | module Method 5 | class AllowControllerActionCommand < ApplicationCommand 6 | after_command -> { transfer_instance_variables controller } 7 | 8 | def perform 9 | # store count in the state 10 | key = "#{self.class.name}/count" 11 | state[key] = state[key].to_i + 1 12 | 13 | @message = "#{self.class.name.demodulize} invoked #{state[key]} times" 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/dummy/app/commands/drivers/method/prevent_controller_action_command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Drivers 4 | module Method 5 | class PreventControllerActionCommand < ApplicationCommand 6 | prevent_controller_action 7 | 8 | def perform 9 | Current.template = "tests/drivers/_method.html.erb" 10 | 11 | # store count in the state 12 | key = "#{self.class.name}/count" 13 | state[key] = state[key].to_i + 1 14 | 15 | streams << render( 16 | partial: "/tests/drivers/method", 17 | formats: [:turbo_stream], 18 | assigns: {message: "#{self.class.name.demodulize} invoked #{state[key]} times"} 19 | ) 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/dummy/app/commands/drivers/window/allow_controller_action_command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Drivers 4 | module Window 5 | class AllowControllerActionCommand < ApplicationCommand 6 | before_command -> { throw :abort }, only: :perform_with_abort 7 | after_command -> { transfer_instance_variables controller } 8 | 9 | def perform 10 | # store count in the state 11 | key = "#{self.class.name}/count" 12 | state[key] = state[key].to_i + 1 13 | 14 | @message = "#{self.class.name.demodulize} invoked #{state[key]} times" 15 | end 16 | 17 | before_command -> { throw :abort }, only: :perform_with_abort 18 | def perform_with_abort 19 | end 20 | 21 | def perform_with_error 22 | raise NotImplementedError, "Intentional error for testing!" 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/dummy/app/commands/drivers/window/prevent_controller_action_command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Drivers 4 | module Window 5 | class PreventControllerActionCommand < ApplicationCommand 6 | prevent_controller_action 7 | 8 | def perform 9 | Current.template = "tests/drivers/window/_turbo_stream.html.erb" 10 | 11 | # store count in the state 12 | key = "#{self.class.name}/count" 13 | state[key] = state[key].to_i + 1 14 | 15 | streams << render(partial: "/tests/drivers/window", formats: [:turbo_stream], 16 | assigns: {message: "#{self.class.name.demodulize} invoked #{state[key]} times"}) 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/dummy/app/commands/increment_count_command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class IncrementCountCommand < ApplicationCommand 4 | def perform 5 | Current.user.increment! :count 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/commands/remember_attributes_command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RememberAttributesCommand < ApplicationCommand 4 | prevent_controller_action 5 | 6 | def perform 7 | element.data.attributes.each do |name| 8 | value = element[name] 9 | value = name if value.blank? && element.include?(name) 10 | remember name, value 11 | end 12 | end 13 | 14 | private 15 | 16 | def remember(name, value) 17 | return unless element.id 18 | data = state[element.id] ||= {} 19 | data[name] = value 20 | state[element.id] = data 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/dummy/app/commands/reset_count_command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ResetCountCommand < ApplicationCommand 4 | def perform 5 | Current.user.update count: 0 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationController < ActionController::Base 4 | prepend_before_action :init_current 5 | after_action :cleanup 6 | 7 | # An example of how to override state with data stored on the server 8 | # Simply return a Hash of state data 9 | # Could be fetched from Redis, Postgres, etc... 10 | # 11 | # IMPORTANT: Server state should be scoped to the visitor 12 | # 13 | # Rembember to persist this state during/after the controller and action have performed 14 | # turbo_boost_state do # Example 15 | # # {example: SecureRandom.hex} 16 | 17 | # # Too much data 18 | # # 1000.times.each_with_object({}) do |index, memo| 19 | # # memo[SecureRandom.hex] = index 20 | # # end 21 | # end 22 | 23 | private 24 | 25 | def init_current 26 | session[:forced_load] = true unless session.loaded? # forces session to load (required for automated testing) 27 | Current.user ||= User.find_or_create_by(session_id: session.id.to_s) 28 | 29 | # Determine which view template will be rendered 30 | Current.template = lookup_context.find_template( 31 | action_name, # ..................................... the base name of the template to find 32 | self.class._prefixes, # ............................ directories to search for the template 33 | false, # ........................................... partial (we are looking for the template not a partial) 34 | [], # .............................................. keys (locals that would be passed to the template) 35 | formats: request.accepts.map(&:symbol).compact # ... the template formats accepted by the client 36 | )&.short_identifier 37 | rescue => error 38 | Rails.logger.error "Error in ApplicationController#init_current! #{error.message}" 39 | end 40 | 41 | def cleanup 42 | return unless rand(10) == 0 43 | User.where(updated_at: ..1.day.ago).delete_all 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/basic_commands_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class BasicCommandsController < ApplicationController 4 | # GET /basic_command 5 | def show 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/test/dummy/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /test/dummy/app/controllers/features_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class FeaturesController < ApplicationController 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/installations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class InstallationsController < ApplicationController 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/sponsors_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SponsorsController < ApplicationController 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/tests_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TestsController < ApplicationController 4 | layout "pico" 5 | 6 | def create 7 | Current.template = "tests/drivers/_form.turbo_stream.erb" 8 | render turbo_stream: turbo_stream.replace("form-driver-test", partial: "tests/drivers/form") 9 | end 10 | 11 | def destroy 12 | Current.template = "tests/drivers/_method.turbo_stream.erb" 13 | render turbo_stream: turbo_stream.replace("method-driver-test", partial: "tests/drivers/method") 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/todos_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TodosController < ApplicationController 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationHelper 4 | DOCS_CONTROLLERS = HashWithIndifferentAccess.new( 5 | installations: true 6 | ).freeze 7 | 8 | DEMOS_CONTROLLERS = HashWithIndifferentAccess.new( 9 | basic_commands: true 10 | ).freeze 11 | 12 | def controller_action 13 | "#{controller_name}##{action_name}" 14 | end 15 | 16 | def controller_action?(name) 17 | controller_action == name.to_s 18 | end 19 | 20 | def docs_controller? 21 | return true if controller_name == "todos" && params[:doc] 22 | DOCS_CONTROLLERS[controller_name] 23 | end 24 | 25 | def docs_action?(name) 26 | docs_controller? && action_name == name.to_s 27 | end 28 | 29 | def demos_controller? 30 | return true if controller_name == "todos" && params[:demo] 31 | DEMOS_CONTROLLERS[controller_name] 32 | end 33 | 34 | def element_id(*args) 35 | args.push(object_id).join("-").parameterize 36 | end 37 | 38 | def attribute(id, name) 39 | turbo_boost.state[id][name] if turbo_boost.state[id] 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/basic_commands_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module BasicCommandsHelper 4 | def padded_number(count) 5 | prefix = "-" if count.negative? 6 | value = count.abs.to_s.rjust(4, "0") 7 | "#{prefix}#{value}" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/code_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rouge" 4 | 5 | module CodeHelper 6 | def read_source(path, erb: false) 7 | cache_key = "#{path}/#{File.mtime(Rails.root.join(path)).iso8601}" 8 | source = Rails.cache.fetch(cache_key) { File.read(Rails.root.join(path)) } 9 | return source unless erb 10 | ERB.new(source).result(binding) 11 | end 12 | 13 | def render_source(source, language:) 14 | formatter = Rouge::Formatters::HTML.new 15 | 16 | lexer = case language 17 | when :bash, :sh, :shell then Rouge::Lexers::Shell.new 18 | when :erb then Rouge::Lexers::ERB.new 19 | when :htm, :html then Rouge::Lexers::HTML.new 20 | when :js, :javascript then Rouge::Lexers::Javascript.new 21 | when :json then Rouge::Lexers::JSON.new 22 | when :rb, :ruby then Rouge::Lexers::Ruby.new 23 | end 24 | 25 | formatter.format(lexer.lex(source)).html_safe 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/features_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module FeaturesHelper 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/installations_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module InstallationsHelper 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/sponsors_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SponsorsHelper 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/svgs_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SvgsHelper 4 | def svg_fill(local_assigns) 5 | local_assigns[:solid] ? "currentColor" : "none" 6 | end 7 | 8 | def svg_stroke(local_assigns) 9 | local_assigns[:solid] ? nil : "currentColor" 10 | end 11 | 12 | def svg_view_box(local_assigns) 13 | "0 0 24 24" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/tests_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TestsHelper 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/todos_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module TodosHelper 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/javascript/application.js: -------------------------------------------------------------------------------- 1 | // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails 2 | 3 | import '@hotwired/turbo-rails' 4 | import 'flowbite' 5 | import './turbo-boost' 6 | import './controllers' 7 | import './tests' 8 | -------------------------------------------------------------------------------- /test/dummy/app/javascript/controllers/index.js: -------------------------------------------------------------------------------- 1 | import { Application } from '@hotwired/stimulus' 2 | 3 | const application = Application.start() 4 | 5 | import ScrollIntoViewController from './controllers/scroll_into_view_controller' 6 | application.register('scroll-into-view', ScrollIntoViewController) 7 | 8 | import ViewModeController from './controllers/view_mode_controller' 9 | application.register('view-mode', ViewModeController) 10 | -------------------------------------------------------------------------------- /test/dummy/app/javascript/controllers/scroll_into_view_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from '@hotwired/stimulus' 2 | 3 | export default class extends Controller { 4 | connect() { 5 | this.scrollLater() 6 | } 7 | 8 | scroll(event) { 9 | const options = { 10 | behavior: 'instant', 11 | block: 'start' 12 | } 13 | 14 | this.element.scrollIntoView(options) 15 | this.scrollTop = this.scrollTop - this.navbar.offsetHeight 16 | } 17 | 18 | scrollLater(wait = 100) { 19 | setTimeout(this.scroll.bind(this), wait) 20 | } 21 | 22 | get scrollTop() { 23 | return document.documentElement.scrollTop 24 | } 25 | 26 | set scrollTop(value) { 27 | return (document.documentElement.scrollTop = value) 28 | } 29 | 30 | get navbar() { 31 | return document.getElementById('navbar') 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/dummy/app/javascript/controllers/view_mode_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from '@hotwired/stimulus' 2 | 3 | export default class extends Controller { 4 | static targets = ['moon', 'sun'] 5 | 6 | connect() { 7 | this.save(this.mode || this.defaultMode) 8 | } 9 | 10 | toggle() { 11 | if (this.mode === 'dark') return this.save('light') 12 | this.save('dark') 13 | } 14 | 15 | save(value) { 16 | document.documentElement.classList.remove('light', 'dark') 17 | document.documentElement.classList.add(value) 18 | 19 | if (value === 'dark') { 20 | this.sunTarget.classList.remove('hidden') 21 | this.moonTarget.classList.add('hidden') 22 | } else { 23 | this.moonTarget.classList.remove('hidden') 24 | this.sunTarget.classList.add('hidden') 25 | } 26 | 27 | return localStorage.setItem('view-mode', value) 28 | } 29 | 30 | get mode() { 31 | return localStorage.getItem('view-mode') 32 | } 33 | 34 | get defaultMode() { 35 | let mode = 'light' 36 | if (matchMedia('(prefers-color-scheme: dark)').matches) mode = 'dark' 37 | return mode 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/dummy/app/javascript/turbo-boost/index.js: -------------------------------------------------------------------------------- 1 | import '@turbo-boost/streams' 2 | import '@turbo-boost/commands' 3 | 4 | TurboBoost.Commands.logger.level = 'debug' 5 | -------------------------------------------------------------------------------- /test/dummy/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationJob < ActiveJob::Base 4 | # Automatically retry jobs that encountered a deadlock 5 | # retry_on ActiveRecord::Deadlocked 6 | 7 | # Most jobs are safe to ignore if the underlying records are no longer available 8 | # discard_on ActiveJob::DeserializationError 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationMailer < ActionMailer::Base 4 | default from: "from@example.com" 5 | layout "mailer" 6 | end 7 | -------------------------------------------------------------------------------- /test/dummy/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationRecord < ActiveRecord::Base 4 | primary_abstract_class 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/test/dummy/app/models/concerns/.keep -------------------------------------------------------------------------------- /test/dummy/app/models/current.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Current < ActiveSupport::CurrentAttributes 4 | attribute :user, :template 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/app/models/user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class User < ApplicationRecord 4 | # extends .................................................................................................. 5 | 6 | # includes ................................................................................................. 7 | 8 | # constants ................................................................................................ 9 | 10 | # class methods ............................................................................................ 11 | class << self 12 | end 13 | 14 | # relationships ............................................................................................ 15 | 16 | # validations .............................................................................................. 17 | validates :session_id, presence: true 18 | validates :count, numericality: true 19 | 20 | # callbacks (caution: side effects) ........................................................................ 21 | 22 | # scopes (composable queries) .............................................................................. 23 | 24 | # additional config (accepts_nested_attribute_for, etc.) ................................................... 25 | 26 | # public instance methods .................................................................................. 27 | 28 | # protected instance methods ............................................................................... 29 | 30 | # private instance methods ................................................................................. 31 | end 32 | -------------------------------------------------------------------------------- /test/dummy/app/views/alerts/_alert.html.erb: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /test/dummy/app/views/alerts/_info.html.erb: -------------------------------------------------------------------------------- 1 | <%= render("alerts/alert", 2 | **local_assigns.merge( 3 | type: "Information", 4 | heroicon: :information_circle, 5 | class: token_list("bg-blue-50 text-blue-800 dark:bg-gray-800 dark:text-blue-400", local_assigns[:class]) 6 | )) { yield if block_given? } %> 7 | -------------------------------------------------------------------------------- /test/dummy/app/views/alerts/_success.html.erb: -------------------------------------------------------------------------------- 1 | <%= render("alerts/alert", 2 | **local_assigns.merge( 3 | type: "Success", 4 | heroicon: :exclamation_triangle, 5 | class: token_list("text-green-800 bg-green-50 dark:bg-gray-800 dark:text-green-400", local_assigns[:class]) 6 | )) { yield if block_given? } %> 7 | -------------------------------------------------------------------------------- /test/dummy/app/views/alerts/_warning.html.erb: -------------------------------------------------------------------------------- 1 | <%= render("alerts/alert", 2 | **local_assigns.merge( 3 | type: "Warning", 4 | heroicon: :exclamation_triangle, 5 | class: token_list("bg-yellow-50 text-yellow-800 dark:bg-gray-800 dark:text-yellow-300", local_assigns[:class]) 6 | )) { yield if block_given? } %> 7 | -------------------------------------------------------------------------------- /test/dummy/app/views/basic_commands/_demo.html.erb: -------------------------------------------------------------------------------- 1 |

Counter

2 | 3 |

4 | In this demo, the counter value or "state" is managed by the User model. 5 | It is persisted to the database and used when rendering the page. 6 |

7 | 8 | <%= padded_number Current.user.count %> 9 | 10 | <% cache do %> 11 |
12 | 15 | 16 | 19 | 20 | 23 |
24 | <% end %> 25 | -------------------------------------------------------------------------------- /test/dummy/app/views/basic_commands/_no_frame.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | <%= render "basic_commands/demo" %> 5 |
6 | 7 | 8 | -------------------------------------------------------------------------------- /test/dummy/app/views/basic_commands/_no_frame_files.sh.erb: -------------------------------------------------------------------------------- 1 | ├── app 2 | │ ├── commands 3 | │ │ ├── decrement_count_command.rb 4 | │ │ ├── increment_count_command.rb 5 | │ │ └── reset_count_command.rb 6 | │ │ 7 | │ ├── controllers 8 | │ │ └── basic_commands_controller.rb 9 | │ │ 10 | │ ├── models 11 | │ │ └── user.rb 12 | │ │ 13 | │ ├── views 14 | │ │ └── basic_commands 15 | │ │ ├── _demo.html.erb 16 | │ │ ├── show.html.erb 17 | │ │ └── show.turbo_boost.erb 18 | -------------------------------------------------------------------------------- /test/dummy/app/views/basic_commands/_turbo_frame.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= turbo_frame_tag "basic_command-turbo-frame", src: basic_command_path(format: :turbo_stream) %> 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/dummy/app/views/basic_commands/_turbo_frame_files.sh.erb: -------------------------------------------------------------------------------- 1 | ├── app 2 | │ ├── commands 3 | │ │ ├── decrement_count_command.rb 4 | │ │ ├── increment_count_command.rb 5 | │ │ └── reset_count_command.rb 6 | │ │ 7 | │ ├── controllers 8 | │ │ └── basic_commands_controller.rb 9 | │ │ 10 | │ ├── models 11 | │ │ └── user.rb 12 | │ │ 13 | │ ├── views 14 | │ │ └── basic_commands 15 | │ │ ├── _demo.html.erb 16 | │ │ ├── show.html.erb 17 | │ │ └── show.turbo_stream.erb 18 | -------------------------------------------------------------------------------- /test/dummy/app/views/basic_commands/show.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :breadcrumbs do %> 2 | <%= render "breadcrumbs/divider" %> 3 | <%= render "breadcrumbs/breadcrumb", label: "Demos" %> 4 | <%= render "breadcrumbs/divider" %> 5 | <%= render "breadcrumbs/breadcrumb", label: "Basic Command", active: true %> 6 | <% end %> 7 | 8 | <% cache do %> 9 |
10 |

11 | Basic Command 12 |

13 |

14 | Let's create a basic Command that inrements and decrements a number. 15 | It's simple and contrived but will demonstrate some high level concepts. 16 |

17 | <%= render "alerts/info", message: "Be sure to open the browser's network tab and watch HTTP activity as you increment and decrement the number." %> 18 |
19 | 20 | <%= render "basic_commands/with_turbo_frame" %> 21 | <%= render "basic_commands/without_turbo_frame" %> 22 | <%= render "cards/sponsor" %> 23 | <% end %> 24 | -------------------------------------------------------------------------------- /test/dummy/app/views/basic_commands/show.turbo_boost.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_stream.update "basic_command-no-frame" do %> 2 | <%= render "basic_commands/demo" %> 3 | <% end %> 4 | 5 | 6 | <%= turbo_stream.update "basic_command-turbo-frame" do %> 7 | <%= render "basic_commands/demo" %> 8 | <% end %> 9 | -------------------------------------------------------------------------------- /test/dummy/app/views/basic_commands/show.turbo_stream.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_stream.update "basic_command-turbo-frame" do %> 2 | <%= render "basic_commands/demo" %> 3 | <% end %> 4 | 5 | 6 | <%= turbo_stream.update "basic_command-no-frame" do %> 7 | <%= render "basic_commands/demo" %> 8 | <% end %> 9 | -------------------------------------------------------------------------------- /test/dummy/app/views/breadcrumbs/_breadcrumb.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= link_to local_assigns[:href], class: token_list( 3 | "text-sm font-medium hover:text-blue-600 md:ml-2 dark:hover:text-white", 4 | token_list( 5 | "text-gray-700 dark:text-gray-400": !local_assigns[:active], 6 | "text-gray-900 dark:text-gray-200 underline underline-offset-2 decoration-dotted decoration-gray-600 dark:decoration-gray-500": local_assigns[:active], 7 | "pointer-events-none": local_assigns[:href].blank?, 8 | ), 9 | local_assigns[:class]) do %> 10 | <%= local_assigns[:label] || yield %> 11 | <% end %> 12 |
  • 13 | -------------------------------------------------------------------------------- /test/dummy/app/views/breadcrumbs/_breadcrumbs.html.erb: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /test/dummy/app/views/breadcrumbs/_button.html.erb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/test/dummy/app/views/breadcrumbs/_button.html.erb -------------------------------------------------------------------------------- /test/dummy/app/views/breadcrumbs/_divider.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= render "icons/heroicons/chevron_right", solid: true, class: "w-3 h-3 text-gray-400" %> 3 |
  • 4 | -------------------------------------------------------------------------------- /test/dummy/app/views/cards/_sponsor.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 |
    Was this helpful?
    5 |
    6 |

    7 | I hope TurboBoost helps you get better at Hotwire while teaching you some cool new tricks... 8 | because this stuff is fun and addictive. 9 | Check out the test/dummy app on GitHub to see how this site was made, 10 | and please consider supporting my efforts. 11 |

    12 | 14 | Sponsor TurboBoost 15 | <%= render "icons/heroicons/rocket_launch", class: "w-4 h-4 ml-2 opacity-60", solid: true %> 16 | 17 |
    18 |
    19 | -------------------------------------------------------------------------------- /test/dummy/app/views/examples/_example.html.erb: -------------------------------------------------------------------------------- 1 | <%= tag.code local_assigns[:path] || source_path, class: "ml-2 px-2 rounded-t-lg text-gray-400 rounded-b-none bg-white dark:text-gray-400/60 dark:bg-gray-700 border border-b-0 dark:border-black" -%> 2 | <%= tag.div id: local_assigns[:id] || SecureRandom.alphanumeric(10), class: "highlight text-sm #{local_assigns[:class]}" do -%> 3 | <% if local_assigns[:source] %> 4 | <%= tag.pre render_source(source, language: language), class: "-mt-px overflow-scroll p-5 rounded-lg border dark:border-black" %> 5 | <% elsif local_assigns[:source_path] %> 6 | <%= tag.pre render_source(read_source(source_path, erb: local_assigns[:erb]), language: language), class: "-mt-px overflow-scroll p-5 rounded-lg border dark:border-black" %> 7 | <% end %> 8 | <% end -%> 9 | -------------------------------------------------------------------------------- /test/dummy/app/views/features/@stylesheet.css: -------------------------------------------------------------------------------- 1 | @layer components { 2 | .feature p em { 3 | @apply not-italic text-accent-500 dark:text-accent-300; 4 | } 5 | .feature p i { 6 | @apply not-italic text-gray-700 underline decoration-gray-600 decoration-dotted underline-offset-4 dark:text-gray-300 dark:decoration-gray-400; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/dummy/app/views/features/_feature.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | <%= render "icons/heroicons/#{icon}", solid: true, class: "h-5 w-5 text-primary-600 dark:text-primary-300 lg:h-6 lg:w-6" %> 4 |
    5 |

    6 | <%= heading %> 7 |

    8 |

    9 | <%= local_assigns[:content] || yield %> 10 |

    11 |
    12 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/bootstrap/_mastodon.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/flowbite/_github.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/flowbite/_twitter.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/flowbite/_youtube.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_academic_cap.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | 4 | 5 | <% end %> 6 | 7 | <%= content_for :outline, flush: true do %> 8 | 9 | <% end %> 10 | 11 | <%= render "svgs/svg", **local_assigns %> 12 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_adjustments_horizontal.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_arrow_down_on_square_stack.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | 4 | <% end %> 5 | 6 | <%= content_for :outline, flush: true do %> 7 | 8 | <% end %> 9 | 10 | <%= render "svgs/svg", **local_assigns %> 11 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_arrow_left.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_arrow_path.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_arrow_right.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_at_symbol.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_banknotes.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | 4 | 5 | <% end %> 6 | 7 | <%= content_for :outline, flush: true do %> 8 | 9 | <% end %> 10 | 11 | <%= render "svgs/svg", **local_assigns %> 12 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_bars_3.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_beaker.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_bell.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_book_open.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_check.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_check_badge.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_check_circle.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_chevron_down.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_chevron_right.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_chevron_up.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_circle_stack.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | 4 | 5 | 6 | <% end %> 7 | 8 | <%= content_for :outline, flush: true do %> 9 | 10 | <% end %> 11 | 12 | <%= render "svgs/svg", **local_assigns %> 13 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_clipboard_document.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | 4 | <% end %> 5 | 6 | <%= content_for :outline, flush: true do %> 7 | 8 | <% end %> 9 | 10 | <%= render "svgs/svg", **local_assigns %> 11 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_cloud_arrow_up.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_code_bracket.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_code_bracket_square.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_command_line.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_credit_card.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | 4 | <% end %> 5 | 6 | <%= content_for :outline, flush: true do %> 7 | 8 | <% end %> 9 | 10 | <%= render "svgs/svg", **local_assigns %> 11 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_cursor_arrow_rays.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_cursor_arrow_ripple.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_document_text.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | 4 | <% end %> 5 | 6 | <%= content_for :outline, flush: true do %> 7 | 8 | <% end %> 9 | 10 | <%= render "svgs/svg", **local_assigns %> 11 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_exclamation.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_exclamation_triangle.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_face_smile.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_globe_americas.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_hand_thumb_up.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_home.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | 4 | <% end %> 5 | 6 | <%= content_for :outline, flush: true do %> 7 | 8 | <% end %> 9 | 10 | <%= render "svgs/svg", **local_assigns %> 11 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_information_circle.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_library.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | 4 | 5 | <% end %> 6 | 7 | <%= content_for :outline, flush: true do %> 8 | 9 | <% end %> 10 | 11 | <%= render "svgs/svg", **local_assigns %> 12 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_light_bulb.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | 4 | <% end %> 5 | 6 | <%= content_for :outline, flush: true do %> 7 | 8 | <% end %> 9 | 10 | <%= render "svgs/svg", **local_assigns %> 11 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_lightning_bolt.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_minus.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_moon.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_play_circle.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | 8 | <% end %> 9 | 10 | <%= render "svgs/svg", **local_assigns %> 11 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_plus.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_rectangle_group.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_rectangle_stack.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_refresh.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_rocket_launch.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | 4 | <% end %> 5 | 6 | <%= content_for :outline, flush: true do %> 7 | 8 | <% end %> 9 | 10 | <%= render "svgs/svg", **local_assigns %> 11 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_server_stack.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | 4 | <% end %> 5 | 6 | <%= content_for :outline, flush: true do %> 7 | 8 | <% end %> 9 | 10 | <%= render "svgs/svg", **local_assigns %> 11 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_shield_check.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_signal.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_sparkles.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_square_3_stack_3d.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | 4 | 5 | <% end %> 6 | 7 | <%= content_for :outline, flush: true do %> 8 | 9 | <% end %> 10 | 11 | <%= render "svgs/svg", **local_assigns %> 12 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_squares_plus.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_sun.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_variable.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_x_circle.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/icons/heroicons/_x_mark.html.erb: -------------------------------------------------------------------------------- 1 | <%= content_for :solid, flush: true do %> 2 | 3 | <% end %> 4 | 5 | <%= content_for :outline, flush: true do %> 6 | 7 | <% end %> 8 | 9 | <%= render "svgs/svg", **local_assigns %> 10 | -------------------------------------------------------------------------------- /test/dummy/app/views/installations/source_code/_application.js.erb: -------------------------------------------------------------------------------- 1 | import '@hotwired/turbo-rails' 2 | import '@turbo-boost/streams' // <-- add this line 3 | import '@turbo-boost/commands' // <-- add this line 4 | 5 | // set the log level [optional] 6 | // 7 | // - unknown (default) 8 | // - debug 9 | // - info 10 | // - warn 11 | // - error 12 | // 13 | TurboBoost.Commands.logger.level = 'debug' // <-- add this line [optional] 14 | -------------------------------------------------------------------------------- /test/dummy/app/views/installations/source_code/_gemfile.rb.erb: -------------------------------------------------------------------------------- 1 | gem "turbo_boost-commands", "~> <%= TurboBoost::Commands::VERSION %>" 2 | -------------------------------------------------------------------------------- /test/dummy/app/views/installations/source_code/_importmap.rb.erb: -------------------------------------------------------------------------------- 1 | pin "@turbo-boost/streams", to: "https://ga.jspm.io/npm:@turbo-boost/streams@<%= TurboBoost::Streams::VERSION %>/app/javascript/turbo_boost-streams.js" 2 | pin "@turbo-boost/commands", to: "https://ga.jspm.io/npm:@turbo-boost/commands@<%= TurboBoost::Commands::VERSION %>/app/javascript/turbo_boost-commands.js" 3 | -------------------------------------------------------------------------------- /test/dummy/app/views/installations/source_code/_importmap_pin.sh.erb: -------------------------------------------------------------------------------- 1 | bin/importmap pin add @turbo-boost/streams@$(bundle show turbo_boost-streams | ruby -ne 'puts $_.split(/-/).last') 2 | bin/importmap pin @turbo-boost/commands@$(bundle show turbo_boost-commands | ruby -ne 'puts $_.split(/-/).last') 3 | -------------------------------------------------------------------------------- /test/dummy/app/views/installations/source_code/_npm_install.sh.erb: -------------------------------------------------------------------------------- 1 | npm install @turbo-boost/streams@$(bundle show turbo_boost-streams | ruby -ne 'puts $_.split(/-/).last') 2 | npm install @turbo-boost/commands@$(bundle show turbo_boost-commands | ruby -ne 'puts $_.split(/-/).last') 3 | -------------------------------------------------------------------------------- /test/dummy/app/views/installations/source_code/_package.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@turbo-boost/streams": "^<%= TurboBoost::Streams::VERSION %>", 4 | "@turbo-boost/commands": "^<%= TurboBoost::Commands::VERSION %>" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/dummy/app/views/instructions/@stylesheet.css: -------------------------------------------------------------------------------- 1 | @layer components { 2 | .instruction > p { 3 | @apply my-2 text-base font-normal text-gray-500 dark:text-gray-400 sm:text-lg; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/dummy/app/views/instructions/_instruction.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 | <%= step.to_s.rjust(2, "0") %> 5 | 6 |

    7 | <%= heading %> 8 |

    9 |
    10 | <%= local_assigns[:content] || yield %> 11 |
    12 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TurboBoost Commands 5 | 6 | <%= csrf_meta_tags %> 7 | <%= csp_meta_tag %> 8 | 9 | <%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %> 10 | <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> 11 | <%= javascript_importmap_tags %> 12 | 13 | 14 | 15 | <% cache params.to_s do %> 16 | <%= render "navbars/navbar" %> 17 | <%= render "sidebars/sidebar" %> 18 | <% end %> 19 |
    20 | <%= render "breadcrumbs/breadcrumbs" %> 21 | <%= yield %> 22 |
    23 | 24 | 25 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/pico.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TurboBoost Commands 5 | 6 | <%= csrf_meta_tags %> 7 | <%= csp_meta_tag %> 8 | 9 | <%= stylesheet_link_tag "pico", "data-turbo-track": "reload" %> 10 | <%= javascript_importmap_tags %> 11 | 12 | 13 | 14 |
    15 |
    16 |

    TurboBoost Commands

    17 |
    18 | 19 |
    20 | <%= yield %> 21 |
    22 | 23 |
    24 | © 2024 Hopsoft. All rights reserved. 25 |
    26 |
    27 | 28 | 29 | -------------------------------------------------------------------------------- /test/dummy/app/views/navbars/_apps_toggle.html.erb: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /test/dummy/app/views/navbars/_brand.html.erb: -------------------------------------------------------------------------------- 1 | <%= link_to root_path, class: "flex items-center justify-between mr-4" do %> 2 | <%= image_tag "turbo-boost-logo.webp", class: "h-9 light-mode", alt: "TurboBoost Logo" %> 3 | <%= image_tag "turbo-boost-logo-dark-bg.webp", class: "h-9 dark-mode", alt: "TurboBoost Logo" %> 4 | 5 | Commands 6 | v<%= TurboBoost::Commands::VERSION %> 7 | 8 | <% end %> 9 | -------------------------------------------------------------------------------- /test/dummy/app/views/navbars/_mode_toggle.html.erb: -------------------------------------------------------------------------------- 1 | 6 | 7 | <%= render "tooltips/tooltip", id: "view-mode-tooltip", content: "Light / Dark mode" %> 8 | -------------------------------------------------------------------------------- /test/dummy/app/views/navbars/_navbar.html.erb: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /test/dummy/app/views/navbars/_notifications_toggle.html.erb: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /test/dummy/app/views/navbars/_search.html.erb: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /test/dummy/app/views/navbars/_search_toggle.html.erb: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /test/dummy/app/views/navbars/_sidebar_toggle.html.erb: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /test/dummy/app/views/navbars/_user_menu_toggle.html.erb: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /test/dummy/app/views/partials/_details.html.erb: -------------------------------------------------------------------------------- 1 | <%= tag.details id: id, data: { testid: id }, turbo_boost: { remember: [:open] } do %> 2 | <%= yield %> 3 | <% end %> 4 | -------------------------------------------------------------------------------- /test/dummy/app/views/sidebars/_button.html.erb: -------------------------------------------------------------------------------- 1 | <%= button_tag type: "button", value: value, 2 | class: token_list("flex w-full items-center rounded-lg p-2 font-medium hover:bg-gray-100 dark:hover:bg-gray-700", 3 | "text-gray-900 dark:text-white": !active, 4 | "text-primary-700 dark:text-primary-300": active 5 | ), 6 | aria: { controls: element_id(:sidebar, value), expanded: active }, 7 | data: { collapse_toggle: element_id(:sidebar, value) } do %> 8 | <%= render "icons/heroicons/#{heroicon}", solid: true, 9 | class: token_list("h-6 w-6 mr-3", 10 | "text-gray-500 dark:text-gray-400": !active, 11 | "text-primary-700 dark:text-primary-300": active 12 | ) %> 13 | 14 | <%= value.to_s.titleize %> 15 | 16 | <% if active %> 17 | <%= render "icons/heroicons/chevron_down", solid: true, 18 | class: token_list("h-6 w-6 mr-3", 19 | "text-gray-500 dark:text-gray-400": !active, 20 | "text-primary-700 dark:text-primary-300": active 21 | ) %> 22 | <% else %> 23 | <%= render "icons/heroicons/chevron_right", solid: true, 24 | class: token_list("h-6 w-6 mr-3", 25 | "text-gray-500 dark:text-gray-400": !active, 26 | "text-primary-700 dark:text-primary-300": active 27 | ) %> 28 | <% end %> 29 | <% end %> 30 | 31 | <%= tag.ul id: element_id(:sidebar, value), class: token_list("space-y-2 ml-3 opacity-70", hidden: !active) do %> 32 | <%= yield %> 33 | <% end %> 34 | -------------------------------------------------------------------------------- /test/dummy/app/views/sidebars/_link.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= link_to href, 3 | class: token_list("flex items-center rounded-lg p-2 text-base text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700", local_assigns[:class], 4 | "bg-gray-300 dark:bg-gray-500": active 5 | ) do %> 6 | <%= render "icons/heroicons/#{icon}", solid: true, 7 | class: token_list("mr-3 h-6 w-6 text-gray-500 dark:text-gray-400", "text-gray-600 dark:text-gray-300": active) %> 8 | <%= local_assigns[:label] || yield %> 9 | <% end %> 10 |
  • 11 | -------------------------------------------------------------------------------- /test/dummy/app/views/sidebars/_sponsor.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    <%= image_tag "hopsoft.webp", class: "h-14 w-14 rounded-full", alt: "hopsoft" %>
    5 |
    Want more demos, better docs, and faster bug fixes?
    6 | 13 |
    14 |
    15 | -------------------------------------------------------------------------------- /test/dummy/app/views/sponsors/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :breadcrumbs do %> 2 | <%= render "breadcrumbs/divider" %> 3 | <%= render "breadcrumbs/breadcrumb", label: "Sponsors", active: true %> 4 | <% end %> 5 | 6 | <% cache do %> 7 |
    8 |
    9 |

    10 | Corporate Sponsors 11 |

    12 |

    13 | Organizations that give back to the Ruby on Rails community by sponsoring TurboBoost's ongoing development. 14 |

    15 |
    16 | 17 | 23 | 24 |
    25 | <%= render "sponsors/clickfunnels" %> 26 |
    27 |
    28 | <% end %> 29 | -------------------------------------------------------------------------------- /test/dummy/app/views/svgs/_svg.html.erb: -------------------------------------------------------------------------------- 1 | <%= tag.svg xmlns: "http://www.w3.org/2000/svg", class: token_list(local_assigns[:class]), data: local_assigns[:data], 2 | fill: svg_fill(local_assigns), stroke: svg_stroke(local_assigns), viewBox: svg_view_box(local_assigns) do %> 3 | <% if content_for? :solid %> 4 | <%= yield :solid %> 5 | <% elsif content_for? :outline %> 6 | <%= yield :outline %> 7 | <% end %> 8 | <% end %> 9 | -------------------------------------------------------------------------------- /test/dummy/app/views/tests/drivers/_footer.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | 3 | Message → 4 | <% if @message %> 5 | <%= tag.ins @message, data: data %> 6 | <% else %> 7 | <%= tag.del "...", data: data %> 8 | <% end %> 9 |
    10 | Request Format → <%= request.format %> 11 |
    12 | View Template → <%= Current.template %> 13 |
    14 |
    15 | -------------------------------------------------------------------------------- /test/dummy/app/views/tests/drivers/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_frame_tag "form-driver-test", data: { src: test_path(:form) } do %> 2 | <%= render "partials/details", id: :form_driver, data: { testid: :form_driver, turbo_boost: request.format.turbo_boost? } do %> 3 | 4 | Form 5 | → app/javascript/drivers/form.js 6 | 7 | 8 |
    9 |
    10 | <%= form_with url: tests_path, data: { turbo_command: "Drivers::Form::PreventControllerActionCommand" } do |form| %> 11 | 15 | <% end %> 16 |
    17 | 18 |
    19 | <%= form_with url: tests_path, data: { turbo_command: "Drivers::Form::AllowControllerActionCommand" } do |form| %> 20 | 24 | <% end %> 25 |
    26 | 27 | <%= render "/tests/drivers/footer", data: { testid: :form_driver_message } %> 28 |
    29 | <% end %> 30 | <% end %> 31 | -------------------------------------------------------------------------------- /test/dummy/app/views/tests/drivers/_form.turbo_stream.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_stream.replace "form-driver-test" do %> 2 | <%= render partial: "/tests/drivers/form", formats: [:html] %> 3 | <% end %> 4 | -------------------------------------------------------------------------------- /test/dummy/app/views/tests/drivers/_frame.html.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_frame_tag "frame-driver-test", data: { src: test_path(:frame) } do %> 2 | <%= render "partials/details", id: :frame_driver, data: { testid: :frame_driver, turbo_boost: request.format.turbo_boost? } do %> 3 | 4 | Frame 5 | → app/javascript/drivers/frame.js 6 | 7 | 8 |
    9 |
    10 | 14 |
    15 | 16 |
    17 | 25 |
    26 | 27 | <%= render "/tests/drivers/footer", data: { testid: :frame_driver_message } %> 28 |
    29 | <% end %> 30 | <% end %> 31 | -------------------------------------------------------------------------------- /test/dummy/app/views/tests/drivers/_frame.turbo_stream.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_stream.replace "frame-driver-test" do %> 2 | <%= render partial: "/tests/drivers/frame", formats: [:html] %> 3 | <% end %> 4 | -------------------------------------------------------------------------------- /test/dummy/app/views/tests/drivers/_method.html.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_frame_tag "method-driver-test", data: { src: test_path(:method) } do %> 2 | <%= render "partials/details", id: :method_driver, data: { testid: :method_driver, turbo_boost: request.format.turbo_boost? } do %> 3 | 4 | Method 5 | → app/javascript/drivers/method.js 6 | 7 | 8 |
    9 |
    10 | <%= link_to test_path(:method), role: "button", style: "width:100%", 11 | data: { testid: :method_driver_prevent, turbo_method: "delete", turbo_command: "Drivers::Method::PreventControllerActionCommand" } do %> 12 |
    Click to Invoke ➜ Drivers::Method::PreventControllerActionCommand
    13 | Uses the Append rendering strategy 14 | <% end %> 15 |
    16 | 17 |
    18 | <%= link_to test_path(:method), role: "button", style: "width:100%", 19 | data: { testid: :method_driver_allow, turbo_method: "delete", turbo_command: "Drivers::Method::AllowControllerActionCommand" } do %> 20 |
    Click to Invoke ➜ Drivers::Method::AllowControllerActionCommand
    21 | Uses the Append rendering strategy 22 | <% end %> 23 |
    24 | 25 | <%= render "/tests/drivers/footer", data: { testid: :method_driver_message } %> 26 |
    27 | <% end %> 28 | <% end %> 29 | -------------------------------------------------------------------------------- /test/dummy/app/views/tests/drivers/_method.turbo_stream.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_stream.replace "method-driver-test" do %> 2 | <%= render partial: "/tests/drivers/method", formats: [:html] %> 3 | <% end %> 4 | -------------------------------------------------------------------------------- /test/dummy/app/views/tests/drivers/_window.html.erb: -------------------------------------------------------------------------------- 1 | <%= render "partials/details", id: :window_driver, data: { testid: :window_driver, turbo_boost: request.format.turbo_boost? } do %> 2 | 3 | Window 4 | → app/javascript/drivers/window.js 5 | 6 | 7 |
    8 |
    9 | 13 |
    14 | 15 |
    16 | 20 |
    21 | 22 |
    23 | 26 |
    27 | 28 |
    29 | 32 |
    33 | 34 | <%= render "/tests/drivers/footer", data: { testid: :window_driver_message } %> 35 |
    36 | <% end %> 37 | -------------------------------------------------------------------------------- /test/dummy/app/views/tests/drivers/_window.turbo_stream.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_stream.replace "window_driver" do %> 2 | <%= render partial: "/tests/drivers/window", formats: [:html] %> 3 | <% end %> 4 | -------------------------------------------------------------------------------- /test/dummy/app/views/tests/index.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Drivers

    4 | 5 | <%= turbo_frame_tag "form-driver-test", src: test_path(:form) %> 6 |
    7 | <%= turbo_frame_tag "frame-driver-test", src: test_path(:frame) %> 8 |
    9 | <%= turbo_frame_tag "method-driver-test", src: test_path(:method) %> 10 |
    11 |
    <%= render "/tests/drivers/window" %>
    12 | 13 |
    14 |
    15 | -------------------------------------------------------------------------------- /test/dummy/app/views/tests/show.html.erb: -------------------------------------------------------------------------------- 1 | <%= render "/tests/drivers/#{params[:id]}" %> 2 | -------------------------------------------------------------------------------- /test/dummy/app/views/tests/show.turbo_stream.erb: -------------------------------------------------------------------------------- 1 | <%= render "/tests/drivers/#{params[:id]}" %> 2 | -------------------------------------------------------------------------------- /test/dummy/app/views/tooltips/_tooltip.html.erb: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/dummy/bin/dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if ! command -v foreman &> /dev/null 4 | then 5 | echo "Installing foreman..." 6 | gem install foreman 7 | fi 8 | 9 | foreman start -f Procfile.dev 10 | -------------------------------------------------------------------------------- /test/dummy/bin/importmap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../config/application" 4 | require "importmap/commands" 5 | -------------------------------------------------------------------------------- /test/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /test/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /test/dummy/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | end 34 | -------------------------------------------------------------------------------- /test/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require_relative "config/environment" 6 | 7 | # Add support for reverse proxying this app at a given path 8 | # NOTE: RAILS_RELATIVE_URL_ROOT is set in fly.toml 9 | # SEE: https://gist.github.com/ebeigarts/5450422 10 | map ENV["RAILS_RELATIVE_URL_ROOT"] || "/" do 11 | run Rails.application 12 | Rails.application.load_server 13 | end 14 | -------------------------------------------------------------------------------- /test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "boot" 4 | 5 | require "rails/all" 6 | 7 | # Require the gems listed in Gemfile, including any gems 8 | # you've limited to :test, :development, or :production. 9 | Bundler.require(*Rails.groups) 10 | require "turbo_boost/commands" 11 | require "importmap-rails" 12 | require "sprockets/railtie" 13 | 14 | module Dummy 15 | class Application < Rails::Application 16 | config.load_defaults Rails::VERSION::STRING.to_f 17 | 18 | # Configuration for the application, engines, and railties goes here. 19 | # 20 | # These settings can be overridden in specific environments using the files 21 | # in config/environments, which are processed later. 22 | # 23 | # config.time_zone = "Central Time (US & Canada)" 24 | # config.eager_load_paths << Rails.root.join("extras") 25 | 26 | config.middleware.use Rack::Deflater 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__) 5 | 6 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) 7 | $LOAD_PATH.unshift File.expand_path("../../../lib", __dir__) 8 | -------------------------------------------------------------------------------- /test/dummy/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: dummy_production 11 | -------------------------------------------------------------------------------- /test/dummy/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | IVwwCVA27kHPxBUh047rTHvckeOAl+2ZgpABRE9Upe0k/R3X+yLK6SP1tsujoBjDWobpwxFstdu+3maJXMRZxKvCFVjkAXNGyZVc7iK6h0U8iKCe5FmAJz3CLs892/SsW6+cL0/t5M+RKejrgEItyZa/FbH5d2vzAYxb67tUawTahN1FW6XbzSIMfTIzwPFx2Pzmy5yBetW/35dqBlrkqZjxngU/EVgJloAyD101dhL5s3gZo8XGV6/gLqPD14L3APYP6gB7+9pZERQcF7XeHHc3vuLKM/XtiCkjpd7+rx/V63dJ+Zr32ThVbSYYjQJZricNMrFayZaBW/G4/NhyAu5KVfm9sFQ7a7M68Xo60US0oLHzqINlDZGVB1VLYyX2v20PNfhnvVnrsOwJvHzm1yOY+J7iMq5hHFXX--qZfkzKGcHWpRJ4AW--A+Lno6XBe6Svp5vWrGtehw== -------------------------------------------------------------------------------- /test/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: sqlite3 3 | timeout: 5000 4 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 5 | database: <%= ENV["DATABASE_URL"] || "db/#{Rails.env}.sqlite3" %> 6 | 7 | development: 8 | <<: *default 9 | 10 | test: 11 | <<: *default 12 | 13 | production: 14 | <<: *default 15 | -------------------------------------------------------------------------------- /test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative "application" 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /test/dummy/config/importmap.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | pin "@hotwired/stimulus", to: "@hotwired--stimulus.js" # @3.2.2 4 | pin "@hotwired/turbo", to: "@hotwired--turbo.js" # @8.0.4 5 | pin "@hotwired/turbo-rails", to: "@hotwired--turbo-rails.js" # @8.0.4 6 | pin "@rails/actioncable/src", to: "@rails--actioncable--src.js" # @7.1.3 7 | pin "@turbo-boost/streams", to: "@turbo-boost--streams.js" # @0.1.11 8 | 9 | # NOTE: The following libs stop working if we allow Rails to vendor them 10 | pin "flowbite", to: "https://cdnjs.cloudflare.com/ajax/libs/flowbite/1.7.0/flowbite.turbo.min.js" 11 | 12 | # HACK: This allows us to pin a lib above Rails.root in the dummy app 13 | # It's goofy but works 14 | FileUtils.rm_f Rails.root.join("app/javascript/@turbo-boost") 15 | FileUtils.ln_s Rails.root.join("../../app/assets/builds/@turbo-boost"), Rails.root.join("app/javascript/@turbo-boost") 16 | pin "@turbo-boost/commands", to: "@turbo-boost/commands.js" 17 | 18 | pin "application", preload: true 19 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Define an application-wide content security policy. 6 | # See the Securing Rails Applications Guide for more information: 7 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 8 | 9 | # Rails.application.configure do 10 | # config.content_security_policy do |policy| 11 | # policy.default_src :self, :https 12 | # policy.font_src :self, :https, :data 13 | # policy.img_src :self, :https, :data 14 | # policy.object_src :none 15 | # policy.script_src :self, :https 16 | # policy.style_src :self, :https 17 | # # Specify URI for violation reports 18 | # # policy.report_uri "/csp-violation-report-endpoint" 19 | # end 20 | # 21 | # # Generate session nonces for permitted importmap and inline scripts 22 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 23 | # config.content_security_policy_nonce_directives = %w(script-src) 24 | # 25 | # # Report violations without enforcing the policy. 26 | # # config.content_security_policy_report_only = true 27 | # end 28 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Configure parameters to be filtered from the log file. Use this to limit dissemination of 6 | # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported 7 | # notations and behaviors. 8 | Rails.application.config.filter_parameters += [ 9 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 10 | ] 11 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Add new inflection rules using the following format. Inflections 6 | # are locale specific, and you may define rules for as many different 7 | # locales as you wish. All of these examples are active by default: 8 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 9 | # inflect.plural /^(ox)$/i, "\\1en" 10 | # inflect.singular /^(ox)en/i, "\\1" 11 | # inflect.irregular "person", "people" 12 | # inflect.uncountable %w( fish sheep ) 13 | # end 14 | 15 | # These inflection rules are supported but not enabled by default: 16 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 17 | # inflect.acronym "RESTful" 18 | # end 19 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/local_cache.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module LocalCache 4 | def local_cache 5 | ActiveSupport::Cache::MemoryStore.new( 6 | expires_in: 1.minute, 7 | size: 32.megabytes 8 | ) 9 | end 10 | end 11 | 12 | Rails.extend LocalCache 13 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/migrations.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.config.tap do |config| 4 | config.active_record.maintain_test_schema = false 5 | 6 | config.after_initialize do 7 | ActiveRecord::Base.connection.migration_context.tap do |context| 8 | if context.migrations.blank? 9 | path = Rails.root.join("db/migrate").to_s 10 | context.migrations_paths << path unless context.migrations_paths.include?(path) 11 | end 12 | context.migrate 13 | context.migrations_paths.clear 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Define an application-wide HTTP permissions policy. For further 4 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 5 | # 6 | # Rails.application.config.permissions_policy do |f| 7 | # f.camera :none 8 | # f.gyroscope :none 9 | # f.microphone :none 10 | # f.usb :none 11 | # f.fullscreen :self 12 | # f.payment :self, "https://secure.example.com" 13 | # end 14 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/pry.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | AmazingPrint.pry! if defined?(AmazingPrint) && defined?(Pry) 4 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/turbo_boost.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # NOTE: this line is only required by the test/dummy app, regular apps will not need this 4 | require "turbo_boost/streams" 5 | 6 | # Configuration can be accessed multiple ways. 7 | # All options return the same configuration object. 8 | # 9 | # - TurboBoost::Commands.config 10 | # - Rails.application.config.turbo_boost_commands 11 | # 12 | # Options: 13 | # - alert_on_abort, opt-(in/out) of alerting on abort (true, *false, "development", "test", "production") 14 | # - alert_on_error, opt-(in/out) of alerting on error (true, *false, "development", "test", "production") 15 | # - precompile_assets, opt-(in/out) of precompiling assets (*true, false) 16 | # - protect_from_forgery, opt-(in/out) of forgery protection (*true, false) 17 | # - raise_on_invalid_command, opt-(in/out) of raising an error if invalid command requested (true, false, *"development", "test", "production") 18 | # - resolve_state, opt-(in/out) of state resolution (true, *false) 19 | # - verify_client, opt-(in/out) of verifying the client browser (*true, false) 20 | # 21 | TurboBoost::Commands.config.tap do |config| 22 | config.alert_on_abort = "development" 23 | config.alert_on_error = "development" 24 | config.precompile_assets = true 25 | config.protect_from_forgery = true 26 | config.raise_on_invalid_command = "development" 27 | config.resolve_state = false 28 | config.verify_client = true 29 | end 30 | -------------------------------------------------------------------------------- /test/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # "true": "foo" 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: 'Hello world' 34 | -------------------------------------------------------------------------------- /test/dummy/config/puma.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Puma can serve each request in a thread from an internal thread pool. 4 | # The `threads` method setting takes two numbers: a minimum and maximum. 5 | # Any libraries that use thread pools should be configured to match 6 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 7 | # and maximum; this matches the default thread size of Active Record. 8 | # 9 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5) 10 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 11 | threads min_threads_count, max_threads_count 12 | 13 | # Specifies the `worker_timeout` threshold that Puma will use to wait before 14 | # terminating a worker in development environments. 15 | # 16 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" 17 | 18 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 19 | # 20 | port ENV.fetch("PORT", 3000) 21 | 22 | # Specifies the `environment` that Puma will run in. 23 | # 24 | environment ENV.fetch("RAILS_ENV", "development") 25 | 26 | # Specifies the `pidfile` that Puma will use. 27 | pidfile ENV.fetch("PIDFILE", "tmp/pids/server.pid") 28 | 29 | # Specifies the number of `workers` to boot in clustered mode. 30 | # Workers are forked web server processes. If using threads and workers together 31 | # the concurrency of the application would be max `threads` * `workers`. 32 | # Workers do not work on JRuby or Windows (both of which do not support 33 | # processes). 34 | # 35 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 36 | 37 | # Use the `preload_app!` method when specifying a `workers` number. 38 | # This directive tells Puma to first boot the application and load code 39 | # before forking the application. This takes advantage of Copy On Write 40 | # process behavior so workers use less memory. 41 | # 42 | # preload_app! 43 | 44 | # Allow puma to be restarted by `bin/rails restart` command. 45 | plugin :tmp_restart 46 | -------------------------------------------------------------------------------- /test/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.routes.draw do 4 | get "health", to: ->(_env) { [204, {}, [""]] } 5 | 6 | draw :marketing 7 | draw :docs 8 | draw :demos 9 | draw :tests 10 | 11 | root "features#index" 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/config/routes/demos.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | resource :basic_command, only: [:show] 4 | -------------------------------------------------------------------------------- /test/dummy/config/routes/docs.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | resource :installations, only: [:show] 4 | -------------------------------------------------------------------------------- /test/dummy/config/routes/marketing.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | resources :features, only: [:index] 4 | resources :todos, only: [:index] 5 | resources :sponsors, only: [:index] 6 | -------------------------------------------------------------------------------- /test/dummy/config/routes/tests.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | resources :tests, only: [:create, :destroy, :index, :show] 4 | -------------------------------------------------------------------------------- /test/dummy/config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 9 | # amazon: 10 | # service: S3 11 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 12 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 13 | # region: us-east-1 14 | # bucket: your_own_bucket-<%= Rails.env %> 15 | 16 | # Remember not to checkin your GCS keyfile to a repository 17 | # google: 18 | # service: GCS 19 | # project: your_project 20 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 21 | # bucket: your_own_bucket-<%= Rails.env %> 22 | 23 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 24 | # microsoft: 25 | # service: AzureStorage 26 | # storage_account_name: your_account_name 27 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 28 | # container: your_container_name-<%= Rails.env %> 29 | 30 | # mirror: 31 | # service: Mirror 32 | # primary: local 33 | # mirrors: [ amazon, google, microsoft ] 34 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20221230164522_create_users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateUsers < ActiveRecord::Migration[7.0] 4 | def change 5 | create_table :users do |t| 6 | t.string :session_id, null: false 7 | t.integer :count, null: false, default: 0 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is auto-generated from the current state of the database. Instead 4 | # of editing this file, please use the migrations feature of Active Record to 5 | # incrementally modify your database, and then regenerate this schema definition. 6 | # 7 | # This file is the source Rails uses to define your schema when running `bin/rails 8 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 9 | # be faster and is potentially less error prone than running all of your 10 | # migrations from scratch. Old migrations may fail to apply correctly if those 11 | # migrations use external dependencies or application code. 12 | # 13 | # It's strongly recommended that you check this file into your version control system. 14 | 15 | ActiveRecord::Schema[7.1].define(version: 2022_12_30_164522) do 16 | create_table "users", force: :cascade do |t| 17 | t.string "session_id", null: false 18 | t.integer "count", default: 0, null: false 19 | t.datetime "created_at", null: false 20 | t.datetime "updated_at", null: false 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /test/dummy/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/test/dummy/lib/assets/.keep -------------------------------------------------------------------------------- /test/dummy/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/test/dummy/log/.keep -------------------------------------------------------------------------------- /test/dummy/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/test/dummy/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /test/dummy/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/test/dummy/public/apple-touch-icon.png -------------------------------------------------------------------------------- /test/dummy/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/test/dummy/public/favicon-16x16.png -------------------------------------------------------------------------------- /test/dummy/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/test/dummy/public/favicon-32x32.png -------------------------------------------------------------------------------- /test/dummy/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/test/dummy/public/favicon.ico -------------------------------------------------------------------------------- /test/dummy/vendor/javascript/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hopsoft/turbo_boost-commands/73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5/test/dummy/vendor/javascript/.keep -------------------------------------------------------------------------------- /test/system/basic_commands/decrement_frame_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "application_system_test_case" 4 | 5 | class DecrementFrameTest < ApplicationSystemTestCase 6 | PARENT_SELECTOR = "#basic_command-turbo-frame" 7 | 8 | test "decrement once" do 9 | page.goto basic_command_url 10 | user = User.last 11 | 12 | assert_equal 0, user.count 13 | assert_equal "0000", page.wait_for_selector("code[role='counter']").inner_text 14 | 15 | trigger = page.wait_for_selector("[data-turbo-command='DecrementCountCommand']") 16 | trigger.click 17 | wait_for_detach trigger 18 | 19 | assert_equal(-1, user.reload.count) 20 | assert_equal "-0001", page.wait_for_selector("code[role='counter']").inner_text 21 | end 22 | 23 | test "decrement 3 times" do 24 | page.goto basic_command_url 25 | user = User.last 26 | 27 | assert_equal 0, user.count 28 | assert_equal "0000", page.wait_for_selector("code[role='counter']").inner_text 29 | 30 | 3.times do 31 | trigger = page.wait_for_selector("[data-turbo-command='DecrementCountCommand']") 32 | trigger.click 33 | wait_for_detach trigger 34 | end 35 | 36 | assert_equal(-3, user.reload.count) 37 | assert_equal "-0003", page.wait_for_selector("code[role='counter']").inner_text 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/system/basic_commands/decrement_no_frame_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "application_system_test_case" 4 | 5 | class DecrementNoFrameTest < ApplicationSystemTestCase 6 | PARENT_SELECTOR = "#basic_command-no-frame" 7 | 8 | test "decrement once" do 9 | page.goto basic_command_url 10 | user = User.last 11 | 12 | assert_equal 0, user.count 13 | assert_equal "0000", page.wait_for_selector("code[role='counter']").inner_text 14 | 15 | trigger = page.wait_for_selector("[data-turbo-command='DecrementCountCommand']") 16 | trigger.click 17 | wait_for_detach trigger 18 | 19 | assert_equal(-1, user.reload.count) 20 | assert_equal "-0001", page.wait_for_selector("code[role='counter']").inner_text 21 | end 22 | 23 | test "decrement 3 times" do 24 | page.goto basic_command_url 25 | user = User.last 26 | 27 | assert_equal 0, user.reload.count 28 | assert_equal "0000", page.wait_for_selector("code[role='counter']").inner_text 29 | 30 | 3.times do 31 | trigger = page.wait_for_selector("[data-turbo-command='DecrementCountCommand']") 32 | trigger.click 33 | wait_for_detach trigger 34 | end 35 | 36 | assert_equal(-3, user.reload.count) 37 | assert_equal "-0003", page.wait_for_selector("#basic_command-no-frame code[role='counter']").inner_text 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/system/basic_commands/increment_frame_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "application_system_test_case" 4 | 5 | class IncrementFrameTest < ApplicationSystemTestCase 6 | PARENT_SELECTOR = "#basic_command-turbo-frame" 7 | 8 | test "increment once" do 9 | page.goto basic_command_url 10 | user = User.last 11 | 12 | assert_equal 0, user.count 13 | assert_equal "0000", page.wait_for_selector("code[role='counter']").inner_text 14 | 15 | trigger = page.wait_for_selector("[data-turbo-command='IncrementCountCommand']") 16 | trigger.click 17 | wait_for_detach trigger 18 | 19 | assert_equal 1, user.reload.count 20 | assert_equal "0001", page.wait_for_selector("code[role='counter']").inner_text 21 | end 22 | 23 | test "increment 3 times" do 24 | page.goto basic_command_url 25 | user = User.last 26 | 27 | assert_equal 0, user.count 28 | assert_equal "0000", page.wait_for_selector("code[role='counter']").inner_text 29 | 30 | 3.times do 31 | trigger = page.wait_for_selector("[data-turbo-command='IncrementCountCommand']") 32 | trigger.click 33 | wait_for_detach trigger 34 | end 35 | 36 | assert_equal 3, user.reload.count 37 | assert_equal "0003", page.wait_for_selector("code[role='counter']").inner_text 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/system/basic_commands/increment_no_frame_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "application_system_test_case" 4 | 5 | class IncrementNoFrameTest < ApplicationSystemTestCase 6 | PARENT_SELECTOR = "#basic_command-no-frame" 7 | 8 | test "increment once" do 9 | page.goto basic_command_url 10 | user = User.last 11 | 12 | assert_equal 0, user.count 13 | assert_equal "0000", page.wait_for_selector("code[role='counter']").inner_text 14 | 15 | trigger = page.wait_for_selector("[data-turbo-command='IncrementCountCommand']") 16 | trigger.click 17 | wait_for_detach trigger 18 | 19 | assert_equal 1, user.reload.count 20 | assert_equal "0001", page.wait_for_selector("code[role='counter']").inner_text 21 | end 22 | 23 | test "increment 3 times" do 24 | page.goto basic_command_url 25 | user = User.last 26 | 27 | assert_equal 0, user.reload.count 28 | assert_equal "0000", page.wait_for_selector("code[role='counter']").inner_text 29 | 30 | 3.times do 31 | trigger = page.wait_for_selector("[data-turbo-command='IncrementCountCommand']") 32 | trigger.click 33 | wait_for_detach trigger 34 | end 35 | 36 | assert_equal 3, user.reload.count 37 | assert_equal "0003", page.wait_for_selector("code[role='counter']").inner_text 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/system/turbo_boost_globals_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "application_system_test_case" 4 | 5 | class TurboBoostGlobalsTest < ApplicationSystemTestCase 6 | test "turbo boost commands loaded and configured" do 7 | page.goto basic_command_url 8 | page.wait_for_timeout 100 # TODO: change to page.expect_event("turbo-boost:command:success") 9 | 10 | assert_equal "object", js("typeof TurboBoost") 11 | assert_equal "object", js("typeof TurboBoost.Commands") 12 | assert_equal "object", js("typeof TurboBoost.Commands.schema") 13 | assert js("Array.isArray(TurboBoost.Commands.eventDelegates)") 14 | assert js("Array.isArray(TurboBoost.Commands.eventDelegates.find(e => e.name === 'change').selectors)") 15 | assert js("Array.isArray(TurboBoost.Commands.eventDelegates.find(e => e.name === 'click').selectors)") 16 | assert js("Array.isArray(TurboBoost.Commands.eventDelegates.find(e => e.name === 'submit').selectors)") 17 | assert_equal "function", js("typeof TurboBoost.Commands.registerEventDelegate") 18 | assert_equal "object", js("typeof TurboBoost.Commands.events") 19 | assert_equal "object", js("typeof TurboBoost.State") 20 | assert_equal "object", js("typeof TurboBoost.Commands.logger") 21 | assert_equal "debug", js("TurboBoost.Commands.logger.level") 22 | assert_equal TurboBoost::Commands::VERSION, js("TurboBoost.Commands.VERSION") 23 | end 24 | 25 | test "turbo boost state" do 26 | page.goto basic_command_url 27 | page.wait_for_timeout 100 # TODO: change to page.expect_event("turbo-boost:command:success") 28 | 29 | assert_equal "object", js("typeof TurboBoost.State.current") 30 | assert_equal "function", js("typeof TurboBoost.State.initialize") 31 | js("TurboBoost.State.initialize(JSON.stringify({ unsigned: { example: 'value' }}))") 32 | 33 | assert_equal "value", js("TurboBoost.State.current.example") 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Configure Rails Environment 4 | ENV["RAILS_ENV"] = "test" 5 | 6 | require "amazing_print" 7 | require "pry-byebug" 8 | require "minitest/reporters" 9 | Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new 10 | 11 | require_relative "../test/dummy/config/environment" 12 | # migrations_dir = File.expand_path("dummy/db/migrate", __dir__) 13 | # ActiveRecord::MigrationContext.new(migrations_dir).migrate 14 | ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)] 15 | ActiveRecord::Migrator.migrations_paths << File.expand_path("../db/migrate", __dir__) 16 | require "rails/test_help" 17 | -------------------------------------------------------------------------------- /test/turbo_boost_commands_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TurboBoostCommandsTest < ActiveSupport::TestCase 6 | test "it has a version number" do 7 | assert TurboBoost::Commands::VERSION 8 | end 9 | end 10 | --------------------------------------------------------------------------------