stimulus_spec 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b3c093ad148d096d99e96b88f18c7f90814878425dc7c531c4aba4af4149cd81
4
+ data.tar.gz: 3e938ad72982b139830f45815c1cc6d4c457f9e8f60af4d1bf362a7cbb641fb6
5
+ SHA512:
6
+ metadata.gz: 50ff8b23e522757c5bc8387a27b00299db7c7f23b1134cbe6fbe204bebf005b825ba1e621546c681c47eaa6f7ec28e7418b17672e87fea7c01dcf5bea6425910
7
+ data.tar.gz: cd08d477efb9bab97f956c943f30e88ebbfc5362d73fe9ede1af2d448e0125c00fe67a6b601361abf37396a996ccdb9fe2c550d0f346dc884f8caa647e9caa00
@@ -0,0 +1,3 @@
1
+ # These are supported funding model platforms
2
+
3
+ github: [eclectic-coding]
@@ -0,0 +1,80 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ env:
14
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
15
+
16
+ jobs:
17
+ lint:
18
+ name: Lint
19
+ runs-on: ubuntu-latest
20
+
21
+ steps:
22
+ - name: Check out repository
23
+ uses: actions/checkout@v5
24
+
25
+ - name: Set up Ruby
26
+ uses: ruby/setup-ruby@v1
27
+ with:
28
+ ruby-version: "3.3"
29
+ bundler-cache: true
30
+
31
+ - name: Run Rubocop
32
+ run: bundle exec rubocop
33
+
34
+ security:
35
+ name: Security audit
36
+ runs-on: ubuntu-latest
37
+
38
+ steps:
39
+ - name: Check out repository
40
+ uses: actions/checkout@v5
41
+
42
+ - name: Set up Ruby
43
+ uses: ruby/setup-ruby@v1
44
+ with:
45
+ ruby-version: "3.3"
46
+ bundler-cache: true
47
+
48
+ - name: Run bundler-audit
49
+ run: bundle exec rake bundle:audit:update bundle:audit:check
50
+
51
+ test:
52
+ name: Ruby ${{ matrix.ruby }}
53
+ runs-on: ubuntu-latest
54
+ strategy:
55
+ fail-fast: false
56
+ matrix:
57
+ ruby:
58
+ - "3.3"
59
+ - "3.4"
60
+ - "4.0"
61
+
62
+ steps:
63
+ - name: Check out repository
64
+ uses: actions/checkout@v5
65
+
66
+ - name: Set up Ruby
67
+ uses: ruby/setup-ruby@v1
68
+ with:
69
+ ruby-version: ${{ matrix.ruby }}
70
+ bundler-cache: true
71
+
72
+ - name: Run test suite
73
+ run: bundle exec rake spec
74
+
75
+ - name: Upload coverage to Codecov
76
+ uses: codecov/codecov-action@v5
77
+ if: matrix.ruby == '3.4'
78
+ with:
79
+ token: ${{ secrets.CODECOV_TOKEN }}
80
+ files: coverage/coverage.json
@@ -0,0 +1,41 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ env:
9
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
10
+
11
+ jobs:
12
+ publish:
13
+ name: Publish to RubyGems
14
+ runs-on: ubuntu-latest
15
+ permissions:
16
+ contents: write
17
+ id-token: write
18
+
19
+ steps:
20
+ - uses: actions/checkout@v5
21
+
22
+ - uses: ruby/setup-ruby@v1
23
+ with:
24
+ ruby-version: "3.4"
25
+ bundler-cache: true
26
+
27
+ - name: Run test suite
28
+ run: bundle exec rake
29
+
30
+ - name: Create GitHub Release
31
+ env:
32
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33
+ run: |
34
+ gh release create "${{ github.ref_name }}" \
35
+ --title "${{ github.ref_name }}" \
36
+ --notes "See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md) for details." \
37
+ --verify-tag
38
+
39
+ - name: Publish to RubyGems
40
+ uses: rubygems/release-gem@v1
41
+
data/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-06-22
11
+
12
+ ### Added
13
+
14
+ - `StimulusSpec::Configuration` class with `auto_include` attribute (default `true`)
15
+ - `StimulusSpec.configure`, `.configuration`, and `.reset_configuration!` class methods
16
+ - `StimulusSpec.install_rspec_integration` — auto-includes matchers into `type: :request`, `:controller`, `:system`, and `:feature` example groups (gated on `stimulus-rails`)
17
+ - `RSpec.configure` hook at load time (guarded by `defined?(RSpec)`)
18
+ - `have_stimulus_controller(name)` matcher — asserts `[data-controller~="name"]` via Nokogiri
19
+ - `have_stimulus_action(descriptor)` matcher — full descriptor (`~=`) and shorthand without event (`*=`)
20
+ - `have_stimulus_target(controller, target)` matcher — asserts `[data-{controller}-target~="target"]`
21
+
22
+ [Unreleased]: https://github.com/eclectic-coding/stimulus_spec/compare/v0.1.0...HEAD
23
+ [0.1.0]: https://github.com/eclectic-coding/stimulus_spec/releases/tag/v0.1.0
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,65 @@
1
+ # Contributing to stimulus_spec
2
+
3
+ Bug reports and pull requests are welcome. This guide covers everything you need to get up and running.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ git clone https://github.com/eclectic-coding/stimulus_spec.git
9
+ cd stimulus_spec
10
+ bin/setup # installs gems
11
+ ```
12
+
13
+ ## Running the suite
14
+
15
+ ```bash
16
+ bundle exec rake # full suite: lint + tests (what CI runs)
17
+ bundle exec rspec # tests only
18
+ bundle exec rspec spec/path/to/foo_spec.rb # single file
19
+ bundle exec rspec spec/path/to/foo_spec.rb:42 # single example
20
+ bundle exec rubocop # lint only
21
+ ```
22
+
23
+ ## Branch workflow
24
+
25
+ Always work on a feature or chore branch — never commit directly to `main`:
26
+
27
+ ```
28
+ feat/short-description # new matchers, new API
29
+ chore/short-description # docs, tooling, dependency bumps
30
+ fix/short-description # bug fixes
31
+ ```
32
+
33
+ ## CHANGELOG
34
+
35
+ Add an entry under `## [Unreleased]` **on your branch before opening a PR**, not after merging. Use these section headers in order (omit any that have no entries):
36
+
37
+ ```markdown
38
+ ### Added
39
+ ### Changed
40
+ ### Fixed
41
+ ```
42
+
43
+ CHANGELOG entries are **user-facing only** — document changes to the public API. Pure internal refactors, docs updates, and CI fixes don't need an entry.
44
+
45
+ ## Pull requests
46
+
47
+ 1. Open a PR against `main`.
48
+ 2. All CI checks must pass: lint, security audit, and the full test matrix (Ruby 3.3–4.0).
49
+ 3. Link the relevant GitHub issue in the PR description (`Closes #N`).
50
+
51
+ ## Code conventions
52
+
53
+ - **RuboCop** enforces style — run `bundle exec rubocop -A` to auto-correct before pushing.
54
+ - New public methods need YARD `@param` and `@return` tags, and an entry in `sig/stimulus_spec.rbs`.
55
+ - Private helpers don't need YARD docs.
56
+
57
+ ## Releasing (maintainers only)
58
+
59
+ Releases are cut from `main` using the release script:
60
+
61
+ ```bash
62
+ bin/release 1.2.0
63
+ ```
64
+
65
+ This bumps the version, updates `CHANGELOG.md` and `Gemfile.lock`, commits, tags, and pushes. CI picks up the tag and publishes to RubyGems via Trusted Publishing.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Chuck Smith
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # StimulusSpec
2
+
3
+ [![CI](https://github.com/eclectic-coding/stimulus_spec/actions/workflows/main.yml/badge.svg)](https://github.com/eclectic-coding/stimulus_spec/actions/workflows/main.yml)
4
+ [![Gem Version](https://img.shields.io/gem/v/stimulus_spec)](https://rubygems.org/gems/stimulus_spec)
5
+ [![Gem Downloads](https://img.shields.io/gem/dt/stimulus_spec)](https://rubygems.org/gems/stimulus_spec)
6
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.3-ruby)](https://rubygems.org/gems/stimulus_spec)
7
+ [![codecov](https://codecov.io/gh/eclectic-coding/stimulus_spec/branch/main/graph/badge.svg)](https://codecov.io/gh/eclectic-coding/stimulus_spec)
8
+
9
+ Drop-in RSpec matchers for [hotwired/stimulus-rails](https://github.com/hotwired/stimulus-rails) — stop hand-rolling `data-controller` assertions and test your Stimulus wiring with expressive, purpose-built matchers.
10
+
11
+ - **Request/controller specs** — `have_stimulus_controller`, `have_stimulus_action`, `have_stimulus_target`
12
+ - **Auto-included** — zero setup required when `stimulus-rails` is in your bundle
13
+ - **Configurable** — disable auto-include when you need manual control
14
+
15
+ Companion gem to [turbo_rspec](https://github.com/eclectic-coding/turbo_rspec) — together they cover the full Hotwire testing stack.
16
+
17
+ ## Table of Contents
18
+
19
+ - [Installation](#installation)
20
+ - [Setup](#setup)
21
+ - [Matchers](#matchers)
22
+ - [have\_stimulus\_controller](#have_stimulus_controller)
23
+ - [have\_stimulus\_action](#have_stimulus_action)
24
+ - [have\_stimulus\_target](#have_stimulus_target)
25
+ - [Example](#example)
26
+ - [Relationship to turbo\_rspec](#relationship-to-turbo_rspec)
27
+ - [Contributing](#contributing)
28
+ - [License](#license)
29
+
30
+ ## Installation
31
+
32
+ Add to your application's `Gemfile`:
33
+
34
+ ```ruby
35
+ group :test do
36
+ gem "stimulus_spec"
37
+ end
38
+ ```
39
+
40
+ [Back to top](#stimulusspec)
41
+
42
+ ## Setup
43
+
44
+ ### Rails + stimulus-rails (automatic)
45
+
46
+ No setup needed. When `stimulus-rails` is in your bundle, `StimulusSpec::Matchers` is automatically included in `type: :request`, `:controller`, `:system`, and `:feature` example groups.
47
+
48
+ ### Manual include
49
+
50
+ For non-Rails projects or custom contexts, include the matchers explicitly:
51
+
52
+ ```ruby
53
+ # spec/spec_helper.rb
54
+ RSpec.configure do |config|
55
+ config.include StimulusSpec::Matchers
56
+ end
57
+ ```
58
+
59
+ ### Configuration
60
+
61
+ ```ruby
62
+ # spec/support/stimulus_spec.rb
63
+ StimulusSpec.configure do |config|
64
+ config.auto_include = false # disable automatic inclusion
65
+ end
66
+ ```
67
+
68
+ [Back to top](#stimulusspec)
69
+
70
+ ## Matchers
71
+
72
+ ### `have_stimulus_controller`
73
+
74
+ Assert that rendered HTML contains a `data-controller` attribute with the given controller name.
75
+
76
+ ```ruby
77
+ expect(response).to have_stimulus_controller("hello")
78
+
79
+ # Works with multiple controllers on a single element
80
+ expect(response).to have_stimulus_controller("clipboard")
81
+
82
+ # Negation
83
+ expect(response).not_to have_stimulus_controller("missing")
84
+ ```
85
+
86
+ Uses space-separated token matching (`~=`), so it works correctly when multiple controllers are declared on a single element and won't partially match.
87
+
88
+ ### `have_stimulus_action`
89
+
90
+ Assert that rendered HTML contains a `data-action` attribute with the given action descriptor.
91
+
92
+ ```ruby
93
+ # Full descriptor
94
+ expect(response).to have_stimulus_action("click->hello#greet")
95
+
96
+ # Shorthand — matches any event
97
+ expect(response).to have_stimulus_action("hello#greet")
98
+
99
+ # Negation
100
+ expect(response).not_to have_stimulus_action("hello#disconnect")
101
+ ```
102
+
103
+ ### `have_stimulus_target`
104
+
105
+ Assert that rendered HTML contains a `data-{controller}-target` attribute with the given target name.
106
+
107
+ ```ruby
108
+ expect(response).to have_stimulus_target("hello", "name")
109
+ expect(response).to have_stimulus_target("hello", "output")
110
+
111
+ # Negation
112
+ expect(response).not_to have_stimulus_target("hello", "missing")
113
+ ```
114
+
115
+ [Back to top](#stimulusspec)
116
+
117
+ ## Example
118
+
119
+ ```ruby
120
+ RSpec.describe "Search", type: :request do
121
+ describe "GET /search" do
122
+ it "wires up the search controller" do
123
+ get search_path
124
+
125
+ expect(response).to have_stimulus_controller("search")
126
+ expect(response).to have_stimulus_action("input->search#query")
127
+ expect(response).to have_stimulus_target("search", "input")
128
+ end
129
+ end
130
+ end
131
+ ```
132
+
133
+ [Back to top](#stimulusspec)
134
+
135
+ ## Relationship to turbo_rspec
136
+
137
+ [turbo_rspec](https://github.com/eclectic-coding/turbo_rspec) includes basic Stimulus matchers (`have_stimulus_controller`, `have_stimulus_action`, `have_stimulus_target`). **stimulus_spec** goes deeper with value, class, and outlet matchers, plus richer failure messages and Stimulus-specific configuration. If you only need basic controller/action/target assertions alongside your Turbo matchers, turbo_rspec has you covered. If you want comprehensive Stimulus testing, use stimulus_spec.
138
+
139
+ Both gems can coexist — they use separate namespaces and won't conflict.
140
+
141
+ [Back to top](#stimulusspec)
142
+
143
+ ## Contributing
144
+
145
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/eclectic-coding/stimulus_spec). See [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions, branch conventions, CHANGELOG requirements, and the PR checklist.
146
+
147
+ ## License
148
+
149
+ The gem is available as open source under the [MIT License](https://opensource.org/licenses/MIT).
150
+
151
+ [Back to top](#stimulusspec)
data/ROADMAP.md ADDED
@@ -0,0 +1,55 @@
1
+ # stimulus_spec Roadmap
2
+
3
+ RSpec matchers for [Stimulus](https://github.com/hotwired/stimulus-rails): controllers, actions, targets, values, classes, and outlets. The goal is to replace hand-rolled `data-controller` assertions with expressive, purpose-built matchers.
4
+
5
+ ## Guiding principles
6
+
7
+ - **Zero magic by default.** Auto-include only when it's unambiguous (Rails request specs with `stimulus-rails` present). Everything else is opt-in.
8
+ - **Fail loudly with useful output.** A cryptic failure message is a bug. Show what was expected, what was found, and the relevant HTML.
9
+ - **Stay close to Stimulus conventions.** Matcher names and arguments mirror Stimulus's `data-*` attribute conventions so the docs cross-reference naturally.
10
+ - **Pure test helper.** No Rails engine, no generators, no runtime code — just matchers you include in your specs.
11
+
12
+ ---
13
+
14
+ ## 0.2.0 — Values and Classes
15
+
16
+ - `have_stimulus_value("search", "url")` — assert `data-search-url-value` attribute exists
17
+ - `have_stimulus_value("search", "url", "/results")` — assert attribute equals expected value
18
+ - `have_stimulus_class("search", "loading")` — assert `data-search-loading-class` exists
19
+ - `have_stimulus_class("search", "loading", "opacity-50")` — assert attribute equals expected class
20
+
21
+ ---
22
+
23
+ ## 0.3.0 — Outlets and Capybara Foundation
24
+
25
+ - `have_stimulus_outlet("search", "results")` — assert `data-search-results-outlet` exists
26
+ - `have_stimulus_outlet("search", "results", "#results-list")` — assert selector value
27
+ - Capybara matchers: `have_stimulus_controller`, `have_stimulus_action`, `have_stimulus_target` using `has_css?` / `has_no_css?` with `wait: 0`
28
+ - Auto-include `StimulusSpec::Capybara::Matchers` into `type: :system` and `type: :feature` (gated on `capybara`)
29
+
30
+ ---
31
+
32
+ ## 0.4.0 — Capybara Values and Rich Failures
33
+
34
+ - Capybara `have_stimulus_value` matcher
35
+ - Enhanced failure messages across all matchers:
36
+ - List all `data-controller` values found in the document on controller mismatch
37
+ - Show actual vs expected value on value/class/outlet mismatch
38
+ - Include relevant HTML snippet for context
39
+
40
+ ---
41
+
42
+ ## 0.5.0 — Documentation and Polish
43
+
44
+ - Full YARD documentation on all public methods and classes
45
+ - `docs/cookbook.md` — common patterns: request specs, system specs, multi-controller elements, typed values, outlets
46
+ - Graceful no-op when `stimulus-rails` is not bundled (no `LoadError`)
47
+
48
+ ---
49
+
50
+ ## 1.0.0 — Stable API
51
+
52
+ - API freeze — public method signatures are part of the semver contract
53
+ - `bin/benchmark` — matcher overhead measurement
54
+ - Version spec (semver format enforced)
55
+ - `docs/migration_guide.md` — migrating from turbo_rspec's Stimulus matchers to stimulus_spec
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "rubocop/rake_task"
6
+ require "bundler/audit/task"
7
+
8
+ RSpec::Core::RakeTask.new(:spec)
9
+ RuboCop::RakeTask.new
10
+ Bundler::Audit::Task.new
11
+
12
+ task default: ["bundle:audit:update", "bundle:audit:check", :rubocop, :spec]
data/codecov.yml ADDED
@@ -0,0 +1,14 @@
1
+ comment:
2
+ layout: "reach, diff, flags, files"
3
+ behavior: default
4
+ # Only post or update the comment if the coverage drops
5
+ require_changes: "coverage_drop"
6
+
7
+ coverage:
8
+ status:
9
+ project:
10
+ default:
11
+ informational: false
12
+ patch:
13
+ default:
14
+ informational: false
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusSpec
4
+ class Configuration
5
+ attr_accessor :auto_include
6
+
7
+ def initialize
8
+ @auto_include = true
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusSpec
4
+ module Matchers
5
+ class HaveStimulusAction
6
+ def initialize(descriptor)
7
+ @descriptor = descriptor.to_s
8
+ end
9
+
10
+ def matches?(subject)
11
+ @body = extract_body(subject)
12
+ if @descriptor.include?("->")
13
+ !document.at_css("[data-action~='#{@descriptor}']").nil?
14
+ else
15
+ !document.at_css("[data-action*='#{@descriptor}']").nil?
16
+ end
17
+ end
18
+
19
+ def does_not_match?(subject)
20
+ !matches?(subject)
21
+ end
22
+
23
+ def failure_message
24
+ "expected to find an element with data-action=\"#{@descriptor}\" but found none in:\n#{@body}"
25
+ end
26
+
27
+ def failure_message_when_negated
28
+ "expected not to find an element with data-action=\"#{@descriptor}\" but found one"
29
+ end
30
+
31
+ def description
32
+ "have Stimulus action \"#{@descriptor}\""
33
+ end
34
+
35
+ private
36
+
37
+ def extract_body(subject)
38
+ subject.respond_to?(:body) ? subject.body : subject.to_s
39
+ end
40
+
41
+ def document
42
+ Nokogiri::HTML5.fragment(@body)
43
+ end
44
+ end
45
+
46
+ def have_stimulus_action(descriptor)
47
+ HaveStimulusAction.new(descriptor)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusSpec
4
+ module Matchers
5
+ class HaveStimulusController
6
+ def initialize(name)
7
+ @name = name.to_s
8
+ end
9
+
10
+ def matches?(subject)
11
+ @body = extract_body(subject)
12
+ !document.at_css("[data-controller~='#{@name}']").nil?
13
+ end
14
+
15
+ def does_not_match?(subject)
16
+ !matches?(subject)
17
+ end
18
+
19
+ def failure_message
20
+ "expected to find an element with data-controller=\"#{@name}\" but found none in:\n#{@body}"
21
+ end
22
+
23
+ def failure_message_when_negated
24
+ "expected not to find an element with data-controller=\"#{@name}\" but found one"
25
+ end
26
+
27
+ def description
28
+ "have Stimulus controller \"#{@name}\""
29
+ end
30
+
31
+ private
32
+
33
+ def extract_body(subject)
34
+ subject.respond_to?(:body) ? subject.body : subject.to_s
35
+ end
36
+
37
+ def document
38
+ Nokogiri::HTML5.fragment(@body)
39
+ end
40
+ end
41
+
42
+ def have_stimulus_controller(name)
43
+ HaveStimulusController.new(name)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusSpec
4
+ module Matchers
5
+ class HaveStimulusTarget
6
+ def initialize(controller, target)
7
+ @controller = controller.to_s
8
+ @target = target.to_s
9
+ end
10
+
11
+ def matches?(subject)
12
+ @body = extract_body(subject)
13
+ !document.at_css("[data-#{@controller}-target~='#{@target}']").nil?
14
+ end
15
+
16
+ def does_not_match?(subject)
17
+ !matches?(subject)
18
+ end
19
+
20
+ def failure_message
21
+ "expected to find an element with data-#{@controller}-target=\"#{@target}\" but found none in:\n#{@body}"
22
+ end
23
+
24
+ def failure_message_when_negated
25
+ "expected not to find an element with data-#{@controller}-target=\"#{@target}\" but found one"
26
+ end
27
+
28
+ def description
29
+ "have Stimulus target \"#{@target}\" for controller \"#{@controller}\""
30
+ end
31
+
32
+ private
33
+
34
+ def extract_body(subject)
35
+ subject.respond_to?(:body) ? subject.body : subject.to_s
36
+ end
37
+
38
+ def document
39
+ Nokogiri::HTML5.fragment(@body)
40
+ end
41
+ end
42
+
43
+ def have_stimulus_target(controller, target)
44
+ HaveStimulusTarget.new(controller, target)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ require_relative "matchers/have_stimulus_controller"
6
+ require_relative "matchers/have_stimulus_action"
7
+ require_relative "matchers/have_stimulus_target"
8
+
9
+ module StimulusSpec
10
+ module Matchers
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusSpec
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "stimulus_spec/version"
4
+ require_relative "stimulus_spec/configuration"
5
+ require_relative "stimulus_spec/matchers"
6
+
7
+ module StimulusSpec
8
+ class Error < StandardError; end
9
+
10
+ def self.configuration
11
+ @configuration ||= Configuration.new
12
+ end
13
+
14
+ def self.configure
15
+ yield configuration
16
+ end
17
+
18
+ def self.reset_configuration!
19
+ @configuration = Configuration.new
20
+ end
21
+
22
+ def self.install_rspec_integration(config)
23
+ return unless Gem.loaded_specs.key?("stimulus-rails")
24
+ return unless configuration.auto_include
25
+
26
+ %i[request controller system feature].each do |type|
27
+ config.include StimulusSpec::Matchers, type: type
28
+ end
29
+ end
30
+ end
31
+
32
+ # :nocov:
33
+ if defined?(RSpec)
34
+ RSpec.configure do |config|
35
+ StimulusSpec.install_rspec_integration(config)
36
+ end
37
+ end
38
+ # :nocov:
@@ -0,0 +1,47 @@
1
+ module StimulusSpec
2
+ VERSION: String
3
+
4
+ def self.configuration: () -> Configuration
5
+ def self.configure: () { (Configuration) -> void } -> void
6
+ def self.reset_configuration!: () -> Configuration
7
+ def self.install_rspec_integration: (untyped config) -> void
8
+
9
+ class Configuration
10
+ attr_accessor auto_include: bool
11
+
12
+ def initialize: () -> void
13
+ end
14
+
15
+ module Matchers
16
+ def have_stimulus_controller: (String name) -> HaveStimulusController
17
+ def have_stimulus_action: (String descriptor) -> HaveStimulusAction
18
+ def have_stimulus_target: (String controller, String target) -> HaveStimulusTarget
19
+
20
+ class HaveStimulusController
21
+ def initialize: (String name) -> void
22
+ def matches?: (untyped subject) -> bool
23
+ def does_not_match?: (untyped subject) -> bool
24
+ def failure_message: () -> String
25
+ def failure_message_when_negated: () -> String
26
+ def description: () -> String
27
+ end
28
+
29
+ class HaveStimulusAction
30
+ def initialize: (String descriptor) -> void
31
+ def matches?: (untyped subject) -> bool
32
+ def does_not_match?: (untyped subject) -> bool
33
+ def failure_message: () -> String
34
+ def failure_message_when_negated: () -> String
35
+ def description: () -> String
36
+ end
37
+
38
+ class HaveStimulusTarget
39
+ def initialize: (String controller, String target) -> void
40
+ def matches?: (untyped subject) -> bool
41
+ def does_not_match?: (untyped subject) -> bool
42
+ def failure_message: () -> String
43
+ def failure_message_when_negated: () -> String
44
+ def description: () -> String
45
+ end
46
+ end
47
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stimulus_spec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chuck Smith
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: nokogiri
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '1.13'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '1.13'
26
+ description: 'Drop-in RSpec matchers for hotwired/stimulus-rails: assert Stimulus
27
+ controllers, actions, targets, values, classes, and outlets in your view and system
28
+ specs. Includes Capybara matchers for live-page assertions and request-spec matchers
29
+ for rendered HTML — all auto-included with zero setup.'
30
+ email:
31
+ - eclectic-coding@users.noreply.github.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - ".github/FUNDING.yml"
37
+ - ".github/workflows/main.yml"
38
+ - ".github/workflows/publish.yml"
39
+ - CHANGELOG.md
40
+ - CONTRIBUTING.md
41
+ - LICENSE.txt
42
+ - README.md
43
+ - ROADMAP.md
44
+ - Rakefile
45
+ - codecov.yml
46
+ - lib/stimulus_spec.rb
47
+ - lib/stimulus_spec/configuration.rb
48
+ - lib/stimulus_spec/matchers.rb
49
+ - lib/stimulus_spec/matchers/have_stimulus_action.rb
50
+ - lib/stimulus_spec/matchers/have_stimulus_controller.rb
51
+ - lib/stimulus_spec/matchers/have_stimulus_target.rb
52
+ - lib/stimulus_spec/version.rb
53
+ - sig/stimulus_spec.rbs
54
+ homepage: https://github.com/eclectic-coding/stimulus_spec
55
+ licenses:
56
+ - MIT
57
+ metadata:
58
+ allowed_push_host: https://rubygems.org
59
+ homepage_uri: https://github.com/eclectic-coding/stimulus_spec
60
+ source_code_uri: https://github.com/eclectic-coding/stimulus_spec
61
+ changelog_uri: https://github.com/eclectic-coding/stimulus_spec/blob/main/CHANGELOG.md
62
+ rubygems_mfa_required: 'true'
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 3.3.0
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 3.6.9
78
+ specification_version: 4
79
+ summary: RSpec matchers for testing Stimulus controllers in Rails applications.
80
+ test_files: []