rails_claude_skills 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/ISSUE_TEMPLATE/bug_report.yml +134 -0
- data/.github/ISSUE_TEMPLATE/config.yml +11 -0
- data/.github/ISSUE_TEMPLATE/feature_request.yml +129 -0
- data/.github/ISSUE_TEMPLATE/question.yml +90 -0
- data/.github/dependabot.yml +19 -0
- data/.github/workflows/ci.yml +77 -0
- data/.github/workflows/release.yml +66 -0
- data/.rubocop.yml +52 -0
- data/CHANGELOG.md +94 -0
- data/CLAUDE.md +332 -0
- data/CODE_OF_CONDUCT.md +134 -0
- data/CONTRIBUTING.md +580 -0
- data/LICENSE.txt +21 -0
- data/README.md +544 -0
- data/Rakefile +8 -0
- data/lib/generators/claude/agent/agent_generator.rb +71 -0
- data/lib/generators/claude/agent/templates/agent.md.tt +62 -0
- data/lib/generators/claude/command/command_generator.rb +50 -0
- data/lib/generators/claude/command/templates/command.md.tt +28 -0
- data/lib/generators/claude/commands_library/create-pr.md +27 -0
- data/lib/generators/claude/commands_library/dbchange.md +19 -0
- data/lib/generators/claude/commands_library/quality.md +20 -0
- data/lib/generators/claude/commands_library/stimulus.md +19 -0
- data/lib/generators/claude/commands_library/turbo-feature.md +17 -0
- data/lib/generators/claude/install/install_generator.rb +211 -0
- data/lib/generators/claude/install/templates/README.md.tt +59 -0
- data/lib/generators/claude/install/templates/USAGE +28 -0
- data/lib/generators/claude/install/templates/agents/api-dev.md.tt +46 -0
- data/lib/generators/claude/install/templates/agents/fullstack-dev.md.tt +48 -0
- data/lib/generators/claude/install/templates/agents/rails-developer.md.tt +40 -0
- data/lib/generators/claude/install/templates/settings.local.json.tt +13 -0
- data/lib/generators/claude/rule/rule_generator.rb +175 -0
- data/lib/generators/claude/rule/templates/rule.md.tt +7 -0
- data/lib/generators/claude/rules_library/code-style.md +37 -0
- data/lib/generators/claude/rules_library/database.md +47 -0
- data/lib/generators/claude/rules_library/hotwire.md +56 -0
- data/lib/generators/claude/rules_library/security.md +54 -0
- data/lib/generators/claude/rules_library/testing.md +47 -0
- data/lib/generators/claude/skill/skill_generator.rb +196 -0
- data/lib/generators/claude/skill/templates/SKILL.md.tt +27 -0
- data/lib/generators/claude/skills_library/create-task-files/SKILL.md +311 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/bug.md +60 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/epic.md +47 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/issue.md +45 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/user-story.md +57 -0
- data/lib/generators/claude/skills_library/minitest-testing/SKILL.md +398 -0
- data/lib/generators/claude/skills_library/minitest-testing/references/examples.md +889 -0
- data/lib/generators/claude/skills_library/plan-feature/SKILL.md +253 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/SKILL.md +1041 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/references/api-documentation.md +422 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/references/serialization.md +456 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/SKILL.md +191 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/advanced.md +331 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/api-auth.md +266 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/omniauth.md +194 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/SKILL.md +603 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/api-authorization.md +543 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/complex-permissions.md +572 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/multi-tenancy.md +373 -0
- data/lib/generators/claude/skills_library/rails-controllers/SKILL.md +514 -0
- data/lib/generators/claude/skills_library/rails-debugging/SKILL.md +260 -0
- data/lib/generators/claude/skills_library/rails-deployment/SKILL.md +437 -0
- data/lib/generators/claude/skills_library/rails-deployment/references/examples.md +901 -0
- data/lib/generators/claude/skills_library/rails-hotwire/SKILL.md +367 -0
- data/lib/generators/claude/skills_library/rails-jobs/MISSION_CONTROL_SETUP.md +639 -0
- data/lib/generators/claude/skills_library/rails-jobs/SKILL.md +704 -0
- data/lib/generators/claude/skills_library/rails-mailers/SKILL.md +549 -0
- data/lib/generators/claude/skills_library/rails-models/SKILL.md +379 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/SKILL.md +622 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/api-pagination.md +523 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/custom-themes.md +498 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/performance.md +478 -0
- data/lib/generators/claude/skills_library/rails-views/SKILL.md +508 -0
- data/lib/generators/claude/skills_library/refine-requirements/SKILL.md +226 -0
- data/lib/generators/claude/skills_library/refine-requirements/references/examples.md +344 -0
- data/lib/generators/claude/skills_library/refine-requirements/references/reference.md +298 -0
- data/lib/generators/claude/skills_library/rspec-testing/SKILL.md +572 -0
- data/lib/generators/claude/skills_library/rspec-testing/references/better_specs_guide.md +273 -0
- data/lib/generators/claude/skills_library/rspec-testing/references/thoughtbot_patterns.md +407 -0
- data/lib/generators/claude/skills_library/tailwindcss/SKILL.md +371 -0
- data/lib/generators/claude/views/views_generator.rb +113 -0
- data/lib/rails_claude_skills/railtie.rb +16 -0
- data/lib/rails_claude_skills/version.rb +5 -0
- data/lib/rails_claude_skills.rb +27 -0
- data/sig/rails_claude_skills.rbs +4 -0
- metadata +199 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Better Specs - RSpec Best Practices
|
|
2
|
+
|
|
3
|
+
## Describe Blocks
|
|
4
|
+
|
|
5
|
+
Use Ruby documentation conventions when naming describe blocks:
|
|
6
|
+
- `.method_name` for class methods
|
|
7
|
+
- `#method_name` for instance methods
|
|
8
|
+
|
|
9
|
+
**Example:**
|
|
10
|
+
```ruby
|
|
11
|
+
describe '.authenticate' do
|
|
12
|
+
# tests for class method
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe '#admin?' do
|
|
16
|
+
# tests for instance method
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Context Blocks
|
|
21
|
+
|
|
22
|
+
Organize tests with contexts using descriptive language:
|
|
23
|
+
- Start descriptions with "when," "with," or "without"
|
|
24
|
+
- Groups related behaviors and improves readability
|
|
25
|
+
|
|
26
|
+
**Example:**
|
|
27
|
+
```ruby
|
|
28
|
+
context 'when logged in' do
|
|
29
|
+
it { is_expected.to respond_with 200 }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context 'with valid parameters' do
|
|
33
|
+
# tests for valid scenarios
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context 'without authentication' do
|
|
37
|
+
# tests for unauthorized scenarios
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## It Blocks
|
|
42
|
+
|
|
43
|
+
Keep test descriptions concise—ideally under 40 characters. Split longer descriptions into contexts instead.
|
|
44
|
+
|
|
45
|
+
Use third-person present tense without "should":
|
|
46
|
+
|
|
47
|
+
**Good:**
|
|
48
|
+
```ruby
|
|
49
|
+
it 'does not change timings' do
|
|
50
|
+
it 'creates a new project' do
|
|
51
|
+
it 'redirects to the dashboard' do
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Bad:**
|
|
55
|
+
```ruby
|
|
56
|
+
it 'should not change timings' do
|
|
57
|
+
it 'should create a new project' do
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Single Expectations
|
|
61
|
+
|
|
62
|
+
Isolated unit tests should contain one expectation per test. This makes tests:
|
|
63
|
+
- Easier to understand
|
|
64
|
+
- Easier to debug when they fail
|
|
65
|
+
- More maintainable
|
|
66
|
+
|
|
67
|
+
For slower, non-isolated tests (database, external services), multiple expectations are acceptable for performance reasons.
|
|
68
|
+
|
|
69
|
+
**Good (unit test):**
|
|
70
|
+
```ruby
|
|
71
|
+
it 'validates presence of name' do
|
|
72
|
+
project = Project.new(name: nil)
|
|
73
|
+
expect(project).not_to be_valid
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'adds error message for missing name' do
|
|
77
|
+
project = Project.new(name: nil)
|
|
78
|
+
project.valid?
|
|
79
|
+
expect(project.errors[:name]).to include("can't be blank")
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Acceptable (integration/system test):**
|
|
84
|
+
```ruby
|
|
85
|
+
it 'creates a project and redirects' do
|
|
86
|
+
expect do
|
|
87
|
+
post :create, params: {project: valid_attributes}
|
|
88
|
+
end.to change(Project, :count).by(1)
|
|
89
|
+
|
|
90
|
+
expect(response).to redirect_to(Project.last)
|
|
91
|
+
expect(flash[:notice]).to eq('Project created successfully')
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Test All Cases
|
|
96
|
+
|
|
97
|
+
Cover valid, edge, and invalid scenarios. Test "all the possible inputs."
|
|
98
|
+
|
|
99
|
+
**Example:**
|
|
100
|
+
```ruby
|
|
101
|
+
describe 'validations' do
|
|
102
|
+
it 'validates presence of name'
|
|
103
|
+
it 'validates length of name'
|
|
104
|
+
it 'validates uniqueness of name'
|
|
105
|
+
it 'allows valid names'
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Expect vs Should Syntax
|
|
110
|
+
|
|
111
|
+
Always use `expect()` syntax on new projects (not `should`):
|
|
112
|
+
|
|
113
|
+
**Good:**
|
|
114
|
+
```ruby
|
|
115
|
+
expect(response).to respond_with_content_type(:json)
|
|
116
|
+
expect(user).to be_valid
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Bad (deprecated):**
|
|
120
|
+
```ruby
|
|
121
|
+
response.should respond_with_content_type(:json)
|
|
122
|
+
user.should be_valid
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
For one-line expectations, use `is_expected.to`:
|
|
126
|
+
|
|
127
|
+
**Good:**
|
|
128
|
+
```ruby
|
|
129
|
+
it { is_expected.to be_valid }
|
|
130
|
+
it { is_expected.to respond_with 422 }
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Subject Usage
|
|
134
|
+
|
|
135
|
+
Use `subject {}` to DRY up multiple related tests:
|
|
136
|
+
|
|
137
|
+
**Good:**
|
|
138
|
+
```ruby
|
|
139
|
+
subject { assigns('message') }
|
|
140
|
+
|
|
141
|
+
it { is_expected.to match /pattern/ }
|
|
142
|
+
it { is_expected.to be_present }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**When not to use subject:**
|
|
146
|
+
- Avoid using `subject` explicitly inside `it` blocks
|
|
147
|
+
- If you need to name it, use `let` instead
|
|
148
|
+
|
|
149
|
+
## Let vs Before
|
|
150
|
+
|
|
151
|
+
Prefer `let` over `before` blocks for variable assignment. Variables defined with `let`:
|
|
152
|
+
- Are lazy loaded (only evaluated when referenced)
|
|
153
|
+
- Are cached during each test
|
|
154
|
+
- Make dependencies explicit
|
|
155
|
+
|
|
156
|
+
Use `let!` when you need immediate evaluation (before the test runs).
|
|
157
|
+
|
|
158
|
+
**Good:**
|
|
159
|
+
```ruby
|
|
160
|
+
let(:resource) { create :device }
|
|
161
|
+
let(:user) { create :user }
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**When to use before:**
|
|
165
|
+
- Setting up global test state
|
|
166
|
+
- Configuring mocks/stubs
|
|
167
|
+
- Database cleanup
|
|
168
|
+
|
|
169
|
+
**Example:**
|
|
170
|
+
```ruby
|
|
171
|
+
before do
|
|
172
|
+
# Freeze time for consistent test results
|
|
173
|
+
freeze_time
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Mocking Strategy
|
|
178
|
+
|
|
179
|
+
"Do not (over)use mocks and test real behavior when possible."
|
|
180
|
+
|
|
181
|
+
Test actual application flow rather than stubbed interactions when feasible. Mocks are useful for:
|
|
182
|
+
- External services
|
|
183
|
+
- Slow operations
|
|
184
|
+
- Testing error conditions
|
|
185
|
+
|
|
186
|
+
But prefer real objects for:
|
|
187
|
+
- Simple collaborators
|
|
188
|
+
- Fast operations
|
|
189
|
+
- Core business logic
|
|
190
|
+
|
|
191
|
+
## Data Creation
|
|
192
|
+
|
|
193
|
+
Create only necessary test data. Use `create_list` sparingly.
|
|
194
|
+
|
|
195
|
+
**Good:**
|
|
196
|
+
```ruby
|
|
197
|
+
let(:project) { create(:project) }
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Avoid:**
|
|
201
|
+
```ruby
|
|
202
|
+
let(:projects) { create_list(:project, 50) } # Usually unnecessary
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Factories Over Fixtures
|
|
206
|
+
|
|
207
|
+
Use FactoryBot instead of fixtures. Factories:
|
|
208
|
+
- Are easier to understand and maintain
|
|
209
|
+
- Reduce coupling between tests
|
|
210
|
+
- Make test data explicit
|
|
211
|
+
- Are easier to modify
|
|
212
|
+
|
|
213
|
+
**Example:**
|
|
214
|
+
```ruby
|
|
215
|
+
# spec/factories/projects.rb
|
|
216
|
+
FactoryBot.define do
|
|
217
|
+
factory :project do
|
|
218
|
+
name { "Heart Rate Monitor" }
|
|
219
|
+
device_description { "A medical device..." }
|
|
220
|
+
|
|
221
|
+
trait :class_ii do
|
|
222
|
+
fda_class { :class_ii_confirmed }
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Shared Examples
|
|
229
|
+
|
|
230
|
+
Eliminate test duplication using shared examples, particularly for controller tests:
|
|
231
|
+
|
|
232
|
+
**Definition:**
|
|
233
|
+
```ruby
|
|
234
|
+
RSpec.shared_examples 'a listable resource' do
|
|
235
|
+
it 'returns success' do
|
|
236
|
+
expect(response).to have_http_status(:success)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
it 'assigns resources' do
|
|
240
|
+
expect(assigns(:resources)).to be_present
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Usage:**
|
|
246
|
+
```ruby
|
|
247
|
+
describe 'GET #index' do
|
|
248
|
+
it_behaves_like 'a listable resource'
|
|
249
|
+
it_behaves_like 'a paginable resource'
|
|
250
|
+
end
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Integration Testing
|
|
254
|
+
|
|
255
|
+
Focus on integration and model tests rather than controller tests. "Test what you see" using Capybara and RSpec.
|
|
256
|
+
|
|
257
|
+
Integration tests:
|
|
258
|
+
- Cover all use cases
|
|
259
|
+
- Run fast with proper setup
|
|
260
|
+
- Test actual user flows
|
|
261
|
+
- Catch more real bugs
|
|
262
|
+
|
|
263
|
+
## HTTP Stubbing
|
|
264
|
+
|
|
265
|
+
Stub external API calls using WebMock or VCR rather than relying on real services.
|
|
266
|
+
|
|
267
|
+
**Example:**
|
|
268
|
+
```ruby
|
|
269
|
+
before do
|
|
270
|
+
stub_request(:get, "https://api.example.com/data")
|
|
271
|
+
.to_return(status: 200, body: '{"status":"ok"}')
|
|
272
|
+
end
|
|
273
|
+
```
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
# Thoughtbot RSpec Patterns
|
|
2
|
+
|
|
3
|
+
## Syntax & Expectations
|
|
4
|
+
|
|
5
|
+
### Use Modern RSpec Syntax
|
|
6
|
+
- Use RSpec's `expect` syntax (not `should`)
|
|
7
|
+
- Use RSpec's `allow` syntax for method stubs (not `stub`)
|
|
8
|
+
- Prefer `eq` over `==` in RSpec assertions
|
|
9
|
+
- Use `not_to` instead of `to_not` in expectations
|
|
10
|
+
|
|
11
|
+
**Examples:**
|
|
12
|
+
```ruby
|
|
13
|
+
# Good
|
|
14
|
+
expect(user.name).to eq('John')
|
|
15
|
+
expect(response).not_to be_nil
|
|
16
|
+
allow(service).to receive(:call).and_return(result)
|
|
17
|
+
|
|
18
|
+
# Bad
|
|
19
|
+
user.name.should == 'John'
|
|
20
|
+
response.should_not be_nil
|
|
21
|
+
service.stub(:call).and_return(result)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Capybara Matchers
|
|
25
|
+
Prefer the `have_css` matcher to the `have_selector` matcher in Capybara assertions:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
# Good
|
|
29
|
+
expect(page).to have_css('.success-message')
|
|
30
|
+
|
|
31
|
+
# Less preferred
|
|
32
|
+
expect(page).to have_selector('.success-message')
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Test Structure
|
|
36
|
+
|
|
37
|
+
### Separate Test Phases
|
|
38
|
+
Separate setup, exercise, verification, and teardown phases with newlines:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
it 'creates a new project' do
|
|
42
|
+
# Setup
|
|
43
|
+
user = create(:user)
|
|
44
|
+
attributes = {name: 'Test Project'}
|
|
45
|
+
|
|
46
|
+
# Exercise
|
|
47
|
+
project = Project.create(attributes)
|
|
48
|
+
|
|
49
|
+
# Verification
|
|
50
|
+
expect(project).to be_persisted
|
|
51
|
+
expect(project.name).to eq('Test Project')
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Single Level of Abstraction
|
|
56
|
+
Use a single level of abstraction within `it` examples:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
# Good
|
|
60
|
+
it 'notifies the user' do
|
|
61
|
+
perform_action
|
|
62
|
+
expect_notification_sent
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Bad - mixing abstraction levels
|
|
66
|
+
it 'notifies the user' do
|
|
67
|
+
click_button 'Submit'
|
|
68
|
+
expect(ActionMailer::Base.deliveries.last.to).to eq([user.email])
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### One Test Per Execution Path
|
|
73
|
+
Use an `it` example or test method for each execution path through the method.
|
|
74
|
+
|
|
75
|
+
## What to Avoid
|
|
76
|
+
|
|
77
|
+
### Don't Test Private Methods
|
|
78
|
+
- Never use the `private` keyword in specs
|
|
79
|
+
- Don't test private methods
|
|
80
|
+
- Test public interface and let private methods be covered indirectly
|
|
81
|
+
|
|
82
|
+
### Avoid Let and Let!
|
|
83
|
+
Extract helper methods instead:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
# Good
|
|
87
|
+
def create_authenticated_user
|
|
88
|
+
user = create(:user)
|
|
89
|
+
sign_in(user)
|
|
90
|
+
user
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'shows dashboard' do
|
|
94
|
+
user = create_authenticated_user
|
|
95
|
+
visit dashboard_path
|
|
96
|
+
expect(page).to have_content(user.name)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Avoid
|
|
100
|
+
let!(:user) { create(:user) }
|
|
101
|
+
before { sign_in(user) }
|
|
102
|
+
|
|
103
|
+
it 'shows dashboard' do
|
|
104
|
+
visit dashboard_path
|
|
105
|
+
expect(page).to have_content(user.name)
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Avoid Subject
|
|
110
|
+
Avoid using `subject` explicitly inside of an RSpec `it` block:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
# Good
|
|
114
|
+
subject { user.name }
|
|
115
|
+
it { is_expected.to eq('John') }
|
|
116
|
+
|
|
117
|
+
# Avoid
|
|
118
|
+
it 'has correct name' do
|
|
119
|
+
expect(subject).to eq('John')
|
|
120
|
+
end
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Avoid Instance Variables
|
|
124
|
+
Don't use instance variables in tests:
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
# Good
|
|
128
|
+
let(:user) { create(:user) }
|
|
129
|
+
|
|
130
|
+
# Avoid
|
|
131
|
+
before { @user = create(:user) }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Avoid Other Constructs
|
|
135
|
+
- Avoid `its`, `specify`, and `before` in RSpec (prefer explicit tests)
|
|
136
|
+
- Avoid `any_instance` in rspec-mocks and mocha; prefer dependency injection
|
|
137
|
+
|
|
138
|
+
### Skip Boolean Equality Checks
|
|
139
|
+
Use predicate methods and matchers instead:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
# Good
|
|
143
|
+
expect(user).to be_valid
|
|
144
|
+
expect(project).to be_persisted
|
|
145
|
+
|
|
146
|
+
# Avoid
|
|
147
|
+
expect(user.valid?).to eq(true)
|
|
148
|
+
expect(project.persisted?).to be_truthy
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Mocking & Stubbing
|
|
152
|
+
|
|
153
|
+
### Use Stubs and Spies, Not Mocks
|
|
154
|
+
- Use stubs and spies (not mocks) in isolated tests
|
|
155
|
+
- Use assertions about state for incoming messages
|
|
156
|
+
- Use stubs and spies to assert you sent outgoing messages
|
|
157
|
+
|
|
158
|
+
**Example:**
|
|
159
|
+
```ruby
|
|
160
|
+
# Good - stub
|
|
161
|
+
allow(service).to receive(:call).and_return(result)
|
|
162
|
+
|
|
163
|
+
# Good - spy
|
|
164
|
+
service = spy('service')
|
|
165
|
+
controller.notify(service)
|
|
166
|
+
expect(service).to have_received(:call)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Disable Real HTTP Requests
|
|
170
|
+
Use `WebMock.disable_net_connect!` to prevent real HTTP requests to external services.
|
|
171
|
+
|
|
172
|
+
Use a Fake to stub requests to external services:
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
class FakeGitHubAPI
|
|
176
|
+
def initialize(stubs = {})
|
|
177
|
+
@stubs = stubs
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def get_user(username)
|
|
181
|
+
@stubs.fetch(username) { default_user }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
|
|
186
|
+
def default_user
|
|
187
|
+
{name: 'Test User', email: 'test@example.com'}
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Acceptance/System Tests
|
|
193
|
+
|
|
194
|
+
### Use Specific Selectors
|
|
195
|
+
- Use the most specific selectors available
|
|
196
|
+
- Don't locate elements with CSS selectors or `[id]` attributes
|
|
197
|
+
- Use accessible names and descriptions to locate elements
|
|
198
|
+
- Interact with form controls, buttons, and links by accessible names
|
|
199
|
+
|
|
200
|
+
**Good:**
|
|
201
|
+
```ruby
|
|
202
|
+
click_button 'Create Project'
|
|
203
|
+
fill_in 'Project Name', with: 'Test Device'
|
|
204
|
+
click_link 'Settings'
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Avoid:**
|
|
208
|
+
```ruby
|
|
209
|
+
find('#create-project-btn').click
|
|
210
|
+
find('.project-name-input').set('Test Device')
|
|
211
|
+
find('a[href="/settings"]').click
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Don't Assert on Classes or Data Attributes
|
|
215
|
+
- Don't assert an element's state with `[class]` or `[data-*]` attributes
|
|
216
|
+
- Use WAI-ARIA States and Properties when asserting an element's state
|
|
217
|
+
- Prefer implicit semantics and built-in attributes over WAI-ARIA
|
|
218
|
+
|
|
219
|
+
**Good:**
|
|
220
|
+
```ruby
|
|
221
|
+
expect(page).to have_css('button[disabled]')
|
|
222
|
+
expect(page).to have_css('[aria-hidden="false"]')
|
|
223
|
+
expect(page).to have_content('Success message')
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Avoid:**
|
|
227
|
+
```ruby
|
|
228
|
+
expect(page).to have_css('.opacity-100')
|
|
229
|
+
expect(page).to have_css('.bg-red-500')
|
|
230
|
+
expect(page).to have_css('[data-visible="true"]')
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Avoid Meaningless Descriptions
|
|
234
|
+
Avoid `it` block descriptions that add no information:
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
# Avoid
|
|
238
|
+
it 'successfully creates project' do
|
|
239
|
+
|
|
240
|
+
# Good
|
|
241
|
+
it 'creates project and redirects to project page' do
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Avoid repetitive descriptions between `describe` and `it` blocks:
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
# Avoid
|
|
248
|
+
describe 'creating a project' do
|
|
249
|
+
it 'creates a project' do
|
|
250
|
+
|
|
251
|
+
# Good
|
|
252
|
+
describe 'project creation' do
|
|
253
|
+
it 'redirects to the new project' do
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### System Spec Organization
|
|
257
|
+
- Use file names like `user_changes_password_spec.rb` (role_action format)
|
|
258
|
+
- Store system specs in `spec/system` directory
|
|
259
|
+
- Place helper methods in a top-level `System` module
|
|
260
|
+
- Use only one `describe` block per system spec file
|
|
261
|
+
|
|
262
|
+
**Example:**
|
|
263
|
+
```ruby
|
|
264
|
+
# spec/system/user_creates_project_spec.rb
|
|
265
|
+
require 'rails_helper'
|
|
266
|
+
|
|
267
|
+
RSpec.describe 'User creates project' do
|
|
268
|
+
it 'creates a new project' do
|
|
269
|
+
# test implementation
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Unit Tests
|
|
275
|
+
|
|
276
|
+
### Imperative Descriptions
|
|
277
|
+
Don't prefix descriptions with "should"; use imperative mood:
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
# Good
|
|
281
|
+
it 'validates presence of name' do
|
|
282
|
+
|
|
283
|
+
# Bad
|
|
284
|
+
it 'should validate presence of name' do
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Use Subject Blocks
|
|
288
|
+
Use `subject` blocks to define objects for use in one-line specs:
|
|
289
|
+
|
|
290
|
+
```ruby
|
|
291
|
+
subject { Project.new(name: 'Test') }
|
|
292
|
+
|
|
293
|
+
it { is_expected.to be_valid }
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Method Documentation Conventions
|
|
297
|
+
- Use `.method` to describe class methods
|
|
298
|
+
- Use `#method` to describe instance methods
|
|
299
|
+
|
|
300
|
+
```ruby
|
|
301
|
+
describe '.find_by_name' do
|
|
302
|
+
# class method tests
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
describe '#save' do
|
|
306
|
+
# instance method tests
|
|
307
|
+
end
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Context for Preconditions
|
|
311
|
+
Use `context` to describe testing preconditions:
|
|
312
|
+
|
|
313
|
+
```ruby
|
|
314
|
+
context 'when user is admin' do
|
|
315
|
+
# tests for admin users
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
context 'with valid parameters' do
|
|
319
|
+
# tests for valid scenarios
|
|
320
|
+
end
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Test Organization
|
|
324
|
+
- Group tests by method using `describe '#method_name'`
|
|
325
|
+
- Maintain single, top-level `describe ClassName` block
|
|
326
|
+
- Order tests matching class definition: validations, associations, methods
|
|
327
|
+
|
|
328
|
+
**Example:**
|
|
329
|
+
```ruby
|
|
330
|
+
RSpec.describe Project do
|
|
331
|
+
describe 'validations' do
|
|
332
|
+
# validation tests
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
describe 'associations' do
|
|
336
|
+
# association tests
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
describe '#save' do
|
|
340
|
+
# instance method tests
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
describe '.find_active' do
|
|
344
|
+
# class method tests
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Factories
|
|
350
|
+
|
|
351
|
+
### Factory Organization
|
|
352
|
+
Organize `factories.rb`:
|
|
353
|
+
1. Sequences
|
|
354
|
+
2. Traits
|
|
355
|
+
3. Factory definitions
|
|
356
|
+
|
|
357
|
+
Order factory attributes:
|
|
358
|
+
1. Implicit associations first
|
|
359
|
+
2. Explicit attributes
|
|
360
|
+
3. Child factories (alphabetical within sections)
|
|
361
|
+
|
|
362
|
+
Sort factory definitions alphabetically.
|
|
363
|
+
|
|
364
|
+
**Example:**
|
|
365
|
+
```ruby
|
|
366
|
+
FactoryBot.define do
|
|
367
|
+
# Sequences
|
|
368
|
+
sequence :email do |n|
|
|
369
|
+
"user-#{n}@example.com"
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Factories (alphabetically)
|
|
373
|
+
factory :project do
|
|
374
|
+
# Associations (implicit)
|
|
375
|
+
tenant
|
|
376
|
+
created_by factory: %i[user]
|
|
377
|
+
|
|
378
|
+
# Attributes (alphabetical)
|
|
379
|
+
device_description { "A medical device..." }
|
|
380
|
+
fda_class { :class_ii_assumed }
|
|
381
|
+
name { "Heart Rate Monitor" }
|
|
382
|
+
software_safety_class { :to_be_determined }
|
|
383
|
+
|
|
384
|
+
# Traits (alphabetically)
|
|
385
|
+
trait :class_ii do
|
|
386
|
+
fda_class { :class_ii_confirmed }
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
trait :with_github_repo do
|
|
390
|
+
github_repo_owner { "organization" }
|
|
391
|
+
github_repo_name { "awesome-repo" }
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Integration Testing
|
|
398
|
+
|
|
399
|
+
### Test the Entire App
|
|
400
|
+
Use integration tests to execute the entire app stack, including:
|
|
401
|
+
- Database operations
|
|
402
|
+
- Background jobs
|
|
403
|
+
- External service interactions (stubbed)
|
|
404
|
+
- Full request/response cycle
|
|
405
|
+
|
|
406
|
+
### Background Jobs
|
|
407
|
+
Test background jobs with appropriate matchers for your job processor (Sidekiq, DelayedJob, etc.).
|