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 +7 -0
- data/.github/FUNDING.yml +3 -0
- data/.github/workflows/main.yml +80 -0
- data/.github/workflows/publish.yml +41 -0
- data/CHANGELOG.md +23 -0
- data/CONTRIBUTING.md +65 -0
- data/LICENSE.txt +21 -0
- data/README.md +151 -0
- data/ROADMAP.md +55 -0
- data/Rakefile +12 -0
- data/codecov.yml +14 -0
- data/lib/stimulus_spec/configuration.rb +11 -0
- data/lib/stimulus_spec/matchers/have_stimulus_action.rb +50 -0
- data/lib/stimulus_spec/matchers/have_stimulus_controller.rb +46 -0
- data/lib/stimulus_spec/matchers/have_stimulus_target.rb +47 -0
- data/lib/stimulus_spec/matchers.rb +12 -0
- data/lib/stimulus_spec/version.rb +5 -0
- data/lib/stimulus_spec.rb +38 -0
- data/sig/stimulus_spec.rbs +47 -0
- metadata +80 -0
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
|
data/.github/FUNDING.yml
ADDED
|
@@ -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
|
+
[](https://github.com/eclectic-coding/stimulus_spec/actions/workflows/main.yml)
|
|
4
|
+
[](https://rubygems.org/gems/stimulus_spec)
|
|
5
|
+
[](https://rubygems.org/gems/stimulus_spec)
|
|
6
|
+
[](https://rubygems.org/gems/stimulus_spec)
|
|
7
|
+
[](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,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,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: []
|