anima-core 0.2.1 → 1.0.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.
Files changed (280) hide show
  1. checksums.yaml +4 -4
  2. data/.reek.yml +27 -1
  3. data/CHANGELOG.md +19 -0
  4. data/README.md +213 -43
  5. data/agents/codebase-analyzer.md +88 -0
  6. data/agents/codebase-pattern-finder.md +83 -0
  7. data/agents/documentation-researcher.md +59 -0
  8. data/agents/thoughts-analyzer.md +102 -0
  9. data/agents/web-search-researcher.md +71 -0
  10. data/anima-core.gemspec +3 -0
  11. data/app/channels/session_channel.rb +195 -45
  12. data/app/decorators/user_message_decorator.rb +16 -5
  13. data/app/jobs/agent_request_job.rb +55 -2
  14. data/app/jobs/analytical_brain_job.rb +33 -0
  15. data/app/jobs/count_event_tokens_job.rb +15 -4
  16. data/app/models/concerns/event/broadcasting.rb +81 -0
  17. data/app/models/event.rb +20 -1
  18. data/app/models/goal.rb +91 -0
  19. data/app/models/session.rb +366 -21
  20. data/config/application.rb +2 -0
  21. data/config/initializers/event_subscribers.rb +0 -1
  22. data/config/routes.rb +0 -6
  23. data/db/migrate/20260313010000_add_status_to_events.rb +8 -0
  24. data/db/migrate/20260313020000_add_processing_to_sessions.rb +7 -0
  25. data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
  26. data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
  27. data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
  28. data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
  29. data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
  30. data/db/migrate/20260315140843_create_goals.rb +16 -0
  31. data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
  32. data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
  33. data/lib/agent_loop.rb +65 -6
  34. data/lib/agents/definition.rb +116 -0
  35. data/lib/agents/registry.rb +106 -0
  36. data/lib/analytical_brain/runner.rb +276 -0
  37. data/lib/analytical_brain/tools/activate_skill.rb +52 -0
  38. data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
  39. data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
  40. data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
  41. data/lib/analytical_brain/tools/finish_goal.rb +62 -0
  42. data/lib/analytical_brain/tools/read_workflow.rb +58 -0
  43. data/lib/analytical_brain/tools/rename_session.rb +63 -0
  44. data/lib/analytical_brain/tools/set_goal.rb +60 -0
  45. data/lib/analytical_brain/tools/update_goal.rb +60 -0
  46. data/lib/analytical_brain.rb +23 -0
  47. data/lib/anima/cli/mcp/secrets.rb +76 -0
  48. data/lib/anima/cli/mcp.rb +197 -0
  49. data/lib/anima/cli.rb +5 -40
  50. data/lib/anima/installer.rb +168 -0
  51. data/lib/anima/settings.rb +226 -0
  52. data/lib/anima/version.rb +1 -1
  53. data/lib/anima.rb +9 -0
  54. data/lib/credential_store.rb +103 -0
  55. data/lib/environment_probe.rb +232 -0
  56. data/lib/events/subscribers/persister.rb +1 -0
  57. data/lib/events/user_message.rb +17 -0
  58. data/lib/llm/client.rb +29 -10
  59. data/lib/mcp/client_manager.rb +86 -0
  60. data/lib/mcp/config.rb +213 -0
  61. data/lib/mcp/health_check.rb +77 -0
  62. data/lib/mcp/secrets.rb +73 -0
  63. data/lib/mcp/stdio_transport.rb +206 -0
  64. data/lib/providers/anthropic.rb +11 -20
  65. data/lib/shell_session.rb +11 -10
  66. data/lib/skills/definition.rb +97 -0
  67. data/lib/skills/registry.rb +105 -0
  68. data/lib/tools/edit.rb +226 -0
  69. data/lib/tools/mcp_tool.rb +114 -0
  70. data/lib/tools/read.rb +151 -0
  71. data/lib/tools/registry.rb +14 -12
  72. data/lib/tools/request_feature.rb +121 -0
  73. data/lib/tools/return_result.rb +81 -0
  74. data/lib/tools/spawn_specialist.rb +109 -0
  75. data/lib/tools/spawn_subagent.rb +111 -0
  76. data/lib/tools/subagent_prompts.rb +12 -0
  77. data/lib/tools/web_get.rb +8 -9
  78. data/lib/tools/write.rb +86 -0
  79. data/lib/tui/app.rb +985 -26
  80. data/lib/tui/cable_client.rb +69 -31
  81. data/lib/tui/message_store.rb +103 -8
  82. data/lib/tui/screens/chat.rb +293 -45
  83. data/lib/workflows/definition.rb +97 -0
  84. data/lib/workflows/registry.rb +89 -0
  85. data/skills/activerecord/SKILL.md +255 -0
  86. data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
  87. data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
  88. data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
  89. data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
  90. data/skills/activerecord/examples/associations/self_referential.rb +302 -0
  91. data/skills/activerecord/examples/associations/through_associations.rb +203 -0
  92. data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
  93. data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
  94. data/skills/activerecord/examples/basics/inheritance.rb +377 -0
  95. data/skills/activerecord/examples/basics/type_casting.rb +317 -0
  96. data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
  97. data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
  98. data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
  99. data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
  100. data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
  101. data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
  102. data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
  103. data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
  104. data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
  105. data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
  106. data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
  107. data/skills/activerecord/examples/querying/optimization.rb +275 -0
  108. data/skills/activerecord/examples/querying/scopes.rb +260 -0
  109. data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
  110. data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
  111. data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
  112. data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
  113. data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
  114. data/skills/activerecord/references/associations.md +709 -0
  115. data/skills/activerecord/references/basics.md +622 -0
  116. data/skills/activerecord/references/callbacks.md +738 -0
  117. data/skills/activerecord/references/migrations.md +657 -0
  118. data/skills/activerecord/references/querying.md +655 -0
  119. data/skills/activerecord/references/validations.md +596 -0
  120. data/skills/dragonruby/SKILL.md +250 -0
  121. data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
  122. data/skills/dragonruby/examples/audio/background_music.rb +29 -0
  123. data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
  124. data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
  125. data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
  126. data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
  127. data/skills/dragonruby/examples/core/hello_world.rb +24 -0
  128. data/skills/dragonruby/examples/core/labels.rb +22 -0
  129. data/skills/dragonruby/examples/core/sprites.rb +35 -0
  130. data/skills/dragonruby/examples/core/state_management.rb +29 -0
  131. data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
  132. data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
  133. data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
  134. data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
  135. data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
  136. data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
  137. data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
  138. data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
  139. data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
  140. data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
  141. data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
  142. data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
  143. data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
  144. data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
  145. data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
  146. data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
  147. data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
  148. data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
  149. data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
  150. data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
  151. data/skills/dragonruby/examples/input/controller_input.rb +28 -0
  152. data/skills/dragonruby/examples/input/directional_input.rb +24 -0
  153. data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
  154. data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
  155. data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
  156. data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
  157. data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
  158. data/skills/dragonruby/examples/rendering/labels.rb +32 -0
  159. data/skills/dragonruby/examples/rendering/layering.rb +51 -0
  160. data/skills/dragonruby/examples/rendering/solids.rb +61 -0
  161. data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
  162. data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
  163. data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
  164. data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
  165. data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
  166. data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
  167. data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
  168. data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
  169. data/skills/dragonruby/references/audio.md +396 -0
  170. data/skills/dragonruby/references/core.md +385 -0
  171. data/skills/dragonruby/references/distribution.md +434 -0
  172. data/skills/dragonruby/references/entities.md +516 -0
  173. data/skills/dragonruby/references/game-logic/persistence.md +386 -0
  174. data/skills/dragonruby/references/game-logic/state.md +389 -0
  175. data/skills/dragonruby/references/input.md +414 -0
  176. data/skills/dragonruby/references/rendering/animation.md +467 -0
  177. data/skills/dragonruby/references/rendering/primitives.md +403 -0
  178. data/skills/dragonruby/references/scenes.md +443 -0
  179. data/skills/draper-decorators/SKILL.md +344 -0
  180. data/skills/draper-decorators/examples/application_decorator.rb +61 -0
  181. data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
  182. data/skills/draper-decorators/examples/model_decorator.rb +152 -0
  183. data/skills/draper-decorators/references/anti-patterns.md +640 -0
  184. data/skills/draper-decorators/references/patterns.md +507 -0
  185. data/skills/draper-decorators/references/testing.md +559 -0
  186. data/skills/gh-issue.md +182 -0
  187. data/skills/mcp-server/SKILL.md +177 -0
  188. data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
  189. data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
  190. data/skills/mcp-server/examples/http_client.rb +48 -0
  191. data/skills/mcp-server/examples/http_server.rb +97 -0
  192. data/skills/mcp-server/examples/rails_integration.rb +88 -0
  193. data/skills/mcp-server/examples/stdio_server.rb +108 -0
  194. data/skills/mcp-server/examples/streaming_client.rb +95 -0
  195. data/skills/mcp-server/references/gotchas.md +183 -0
  196. data/skills/mcp-server/references/prompts.md +98 -0
  197. data/skills/mcp-server/references/resources.md +53 -0
  198. data/skills/mcp-server/references/server.md +140 -0
  199. data/skills/mcp-server/references/tools.md +146 -0
  200. data/skills/mcp-server/references/transport.md +104 -0
  201. data/skills/ratatui-ruby/SKILL.md +315 -0
  202. data/skills/ratatui-ruby/references/core-concepts.md +340 -0
  203. data/skills/ratatui-ruby/references/events.md +387 -0
  204. data/skills/ratatui-ruby/references/frameworks.md +522 -0
  205. data/skills/ratatui-ruby/references/layout.md +423 -0
  206. data/skills/ratatui-ruby/references/styling.md +268 -0
  207. data/skills/ratatui-ruby/references/testing.md +433 -0
  208. data/skills/ratatui-ruby/references/widgets.md +532 -0
  209. data/skills/rspec/SKILL.md +340 -0
  210. data/skills/rspec/examples/core/basic_structure.rb +69 -0
  211. data/skills/rspec/examples/core/configuration.rb +126 -0
  212. data/skills/rspec/examples/core/hooks.rb +126 -0
  213. data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
  214. data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
  215. data/skills/rspec/examples/core/shared_examples.rb +145 -0
  216. data/skills/rspec/examples/factory_bot/associations.rb +314 -0
  217. data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
  218. data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
  219. data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
  220. data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
  221. data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
  222. data/skills/rspec/examples/factory_bot/traits.rb +293 -0
  223. data/skills/rspec/examples/factory_bot/transients.rb +229 -0
  224. data/skills/rspec/examples/matchers/change.rb +115 -0
  225. data/skills/rspec/examples/matchers/collections.rb +154 -0
  226. data/skills/rspec/examples/matchers/comparisons.rb +79 -0
  227. data/skills/rspec/examples/matchers/composing.rb +155 -0
  228. data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
  229. data/skills/rspec/examples/matchers/equality.rb +58 -0
  230. data/skills/rspec/examples/matchers/errors.rb +136 -0
  231. data/skills/rspec/examples/matchers/output.rb +103 -0
  232. data/skills/rspec/examples/matchers/predicates.rb +87 -0
  233. data/skills/rspec/examples/matchers/truthiness.rb +101 -0
  234. data/skills/rspec/examples/matchers/types.rb +82 -0
  235. data/skills/rspec/examples/matchers/yield.rb +147 -0
  236. data/skills/rspec/examples/mocks/any_instance.rb +172 -0
  237. data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
  238. data/skills/rspec/examples/mocks/constants.rb +177 -0
  239. data/skills/rspec/examples/mocks/doubles.rb +139 -0
  240. data/skills/rspec/examples/mocks/expectations.rb +137 -0
  241. data/skills/rspec/examples/mocks/message_chains.rb +173 -0
  242. data/skills/rspec/examples/mocks/ordering.rb +144 -0
  243. data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
  244. data/skills/rspec/examples/mocks/responses.rb +223 -0
  245. data/skills/rspec/examples/mocks/spies.rb +149 -0
  246. data/skills/rspec/examples/mocks/stubbing.rb +133 -0
  247. data/skills/rspec/examples/rails/channels.rb +250 -0
  248. data/skills/rspec/examples/rails/controller_specs.rb +302 -0
  249. data/skills/rspec/examples/rails/helper_specs.rb +245 -0
  250. data/skills/rspec/examples/rails/job_specs.rb +256 -0
  251. data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
  252. data/skills/rspec/examples/rails/matchers.rb +374 -0
  253. data/skills/rspec/examples/rails/model_specs.rb +193 -0
  254. data/skills/rspec/examples/rails/request_specs.rb +275 -0
  255. data/skills/rspec/examples/rails/routing_specs.rb +276 -0
  256. data/skills/rspec/examples/rails/system_specs.rb +294 -0
  257. data/skills/rspec/examples/rails/transactions.rb +254 -0
  258. data/skills/rspec/examples/rails/view_specs.rb +252 -0
  259. data/skills/rspec/references/core.md +816 -0
  260. data/skills/rspec/references/factory_bot.md +641 -0
  261. data/skills/rspec/references/matchers.md +516 -0
  262. data/skills/rspec/references/mocks.md +381 -0
  263. data/skills/rspec/references/rails.md +528 -0
  264. data/templates/soul.md +40 -0
  265. data/workflows/commit.md +45 -0
  266. data/workflows/create_handoff.md +98 -0
  267. data/workflows/create_note.md +82 -0
  268. data/workflows/create_plan.md +457 -0
  269. data/workflows/decompose_ticket.md +109 -0
  270. data/workflows/feature.md +91 -0
  271. data/workflows/implement_plan.md +87 -0
  272. data/workflows/iterate_plan.md +247 -0
  273. data/workflows/research_codebase.md +210 -0
  274. data/workflows/resume_handoff.md +217 -0
  275. data/workflows/review_pr.md +320 -0
  276. data/workflows/thoughts_init.md +71 -0
  277. data/workflows/validate_plan.md +166 -0
  278. metadata +290 -3
  279. data/app/controllers/api/sessions_controller.rb +0 -25
  280. data/lib/events/subscribers/action_cable_bridge.rb +0 -59
@@ -0,0 +1,254 @@
1
+ # RSpec Rails: Transactions and Database Examples
2
+ # Source: rspec-rails gem features/Transactions.md
3
+
4
+ # Database transactions in RSpec Rails.
5
+ # Each example runs in a transaction that's rolled back after.
6
+ # Location: spec/rails_helper.rb configuration
7
+
8
+ # Default transactional behavior
9
+ RSpec.describe Widget, type: :model do
10
+ describe "transaction isolation" do
11
+ it "has no widgets initially" do
12
+ expect(Widget.count).to eq(0)
13
+ end
14
+
15
+ it "has one widget after creating" do
16
+ Widget.create!(name: "Test")
17
+ expect(Widget.count).to eq(1)
18
+ end
19
+
20
+ it "has no widgets again (previous was rolled back)" do
21
+ expect(Widget.count).to eq(0)
22
+ end
23
+ end
24
+ end
25
+
26
+ # Using let! for setup within transaction
27
+ RSpec.describe "let! with transactions" do
28
+ let!(:widget) { create(:widget) }
29
+
30
+ it "widget exists in first example" do
31
+ expect(Widget.count).to eq(1)
32
+ expect(Widget.first).to eq(widget)
33
+ end
34
+
35
+ it "widget exists in second example (recreated)" do
36
+ # let! runs before each example
37
+ expect(Widget.count).to eq(1)
38
+ end
39
+ end
40
+
41
+ # before(:example) vs before(:context)
42
+ RSpec.describe "before hooks and transactions" do
43
+ describe "before(:example)" do
44
+ before(:example) { create(:widget, name: "Example Widget") }
45
+
46
+ it "creates widget for each example" do
47
+ expect(Widget.count).to eq(1)
48
+ end
49
+
50
+ it "has fresh widget in next example" do
51
+ expect(Widget.count).to eq(1)
52
+ expect(Widget.first.name).to eq("Example Widget")
53
+ end
54
+ end
55
+
56
+ describe "before(:context)" do
57
+ # WARNING: Data created in before(:context) persists across examples
58
+ # and is NOT rolled back. Must clean up manually.
59
+
60
+ before(:context) do
61
+ @context_widget = Widget.create!(name: "Context Widget")
62
+ end
63
+
64
+ after(:context) do
65
+ @context_widget.destroy
66
+ end
67
+
68
+ before(:example) do
69
+ # Must reload to avoid stale data within transaction
70
+ @context_widget.reload
71
+ end
72
+
73
+ it "uses shared context widget" do
74
+ expect(@context_widget.name).to eq("Context Widget")
75
+ end
76
+
77
+ it "also sees the context widget" do
78
+ expect(Widget.find(@context_widget.id)).to be_present
79
+ end
80
+ end
81
+ end
82
+
83
+ # Testing transactions explicitly
84
+ RSpec.describe "Explicit transaction testing" do
85
+ describe "rollback behavior" do
86
+ it "rolls back on error" do
87
+ expect {
88
+ Widget.transaction do
89
+ Widget.create!(name: "Will be rolled back")
90
+ raise ActiveRecord::Rollback
91
+ end
92
+ }.not_to change(Widget, :count)
93
+ end
94
+
95
+ it "commits without error" do
96
+ expect {
97
+ Widget.transaction do
98
+ Widget.create!(name: "Will be committed")
99
+ end
100
+ }.to change(Widget, :count).by(1)
101
+ end
102
+ end
103
+ end
104
+
105
+ # Configuration example (for rails_helper.rb)
106
+ RSpec.describe "Transaction configuration" do
107
+ # Enable transactional fixtures (default)
108
+ # RSpec.configure do |config|
109
+ # config.use_transactional_fixtures = true
110
+ # end
111
+
112
+ it "demonstrates transactional isolation" do
113
+ widget = create(:widget)
114
+ expect(Widget.count).to eq(1)
115
+
116
+ # After this test, widget won't exist
117
+ end
118
+ end
119
+
120
+ # Database Cleaner for non-transactional scenarios
121
+ # (e.g., JavaScript tests with Selenium)
122
+ RSpec.describe "Database Cleaner setup", skip: "documentation only" do
123
+ # Configuration example:
124
+ #
125
+ # RSpec.configure do |config|
126
+ # config.before(:suite) do
127
+ # DatabaseCleaner.strategy = :transaction
128
+ # DatabaseCleaner.clean_with(:truncation)
129
+ # end
130
+ #
131
+ # config.around(:each) do |example|
132
+ # DatabaseCleaner.cleaning do
133
+ # example.run
134
+ # end
135
+ # end
136
+ #
137
+ # # Use truncation for JS tests
138
+ # config.before(:each, js: true) do
139
+ # DatabaseCleaner.strategy = :truncation
140
+ # end
141
+ #
142
+ # config.after(:each, js: true) do
143
+ # DatabaseCleaner.strategy = :transaction
144
+ # end
145
+ # end
146
+ end
147
+
148
+ # Testing database constraints
149
+ RSpec.describe "Database constraints" do
150
+ describe "unique constraint" do
151
+ let!(:widget) { create(:widget, name: "Unique") }
152
+
153
+ it "raises error on duplicate" do
154
+ expect {
155
+ create(:widget, name: "Unique")
156
+ }.to raise_error(ActiveRecord::RecordNotUnique)
157
+ end
158
+ end
159
+
160
+ describe "foreign key constraint" do
161
+ let(:widget) { create(:widget) }
162
+ let!(:component) { create(:component, widget:) }
163
+
164
+ it "prevents deletion of referenced record" do
165
+ expect {
166
+ widget.destroy!
167
+ }.to raise_error(ActiveRecord::InvalidForeignKey)
168
+ end
169
+ end
170
+ end
171
+
172
+ # Testing with specific database features
173
+ RSpec.describe "Database-specific testing" do
174
+ describe "locking" do
175
+ let!(:widget) { create(:widget, count: 0) }
176
+
177
+ it "uses pessimistic locking" do
178
+ Widget.transaction do
179
+ locked = Widget.lock.find(widget.id)
180
+ locked.update!(count: locked.count + 1)
181
+ end
182
+
183
+ expect(widget.reload.count).to eq(1)
184
+ end
185
+ end
186
+
187
+ describe "advisory locks" do
188
+ it "obtains and releases lock", skip: "database-specific" do
189
+ Widget.with_advisory_lock("test_lock") do
190
+ # Critical section
191
+ expect(Widget.advisory_lock_exists?("test_lock")).to be true
192
+ end
193
+ end
194
+ end
195
+ end
196
+
197
+ # Testing seeds
198
+ RSpec.describe "Database seeds", type: :model do
199
+ # Usually seeds are loaded once in test setup
200
+ # Here's how to test seed data
201
+
202
+ describe "seed data" do
203
+ before(:context) do
204
+ # Load seeds if needed
205
+ # Rails.application.load_seed
206
+ end
207
+
208
+ it "creates default categories" do
209
+ expect(Category.count).to be > 0
210
+ end
211
+
212
+ it "creates admin user" do
213
+ expect(User.find_by(role: "admin")).to be_present
214
+ end
215
+ end
216
+ end
217
+
218
+ # Testing database migrations
219
+ RSpec.describe "Migration testing", skip: "run separately" do
220
+ describe "up migration" do
221
+ it "adds new column" do
222
+ # Assuming a pending migration adds 'status' column
223
+ ActiveRecord::Migrator.up(
224
+ ActiveRecord::Migrator.migrations_paths,
225
+ 20240101000000
226
+ )
227
+
228
+ expect(Widget.column_names).to include("status")
229
+ end
230
+ end
231
+
232
+ describe "down migration" do
233
+ it "removes column" do
234
+ ActiveRecord::Migrator.down(
235
+ ActiveRecord::Migrator.migrations_paths,
236
+ 20240101000000
237
+ )
238
+
239
+ expect(Widget.column_names).not_to include("status")
240
+ end
241
+ end
242
+ end
243
+
244
+ # Parallel testing considerations
245
+ RSpec.describe "Parallel test isolation" do
246
+ # Each parallel worker has its own database
247
+ # Transactions work within each worker
248
+
249
+ it "works in parallel environment" do
250
+ # Create data - won't conflict with other workers
251
+ widget = create(:widget)
252
+ expect(widget).to be_persisted
253
+ end
254
+ end
@@ -0,0 +1,252 @@
1
+ # RSpec Rails: View Specs Examples
2
+ # Source: rspec-rails gem features/view_specs/
3
+
4
+ # View specs test templates in isolation.
5
+ # Three-step pattern: assign, render, assert.
6
+ # Location: spec/views/
7
+
8
+ # Basic view spec
9
+ RSpec.describe "widgets/index", type: :view do
10
+ let(:widgets) { create_list(:widget, 2, :with_names) }
11
+
12
+ before { assign(:widgets, widgets) }
13
+
14
+ it "displays all widgets" do
15
+ render
16
+
17
+ widgets.each do |widget|
18
+ expect(rendered).to include(widget.name)
19
+ end
20
+ end
21
+ end
22
+
23
+ # Testing specific elements
24
+ RSpec.describe "widgets/show", type: :view do
25
+ let(:widget) { create(:widget, name: "Slicer", price: 99) }
26
+
27
+ before { assign(:widget, widget) }
28
+
29
+ it "displays the widget name" do
30
+ render
31
+ expect(rendered).to match(/Slicer/)
32
+ end
33
+
34
+ it "displays the widget price" do
35
+ render
36
+ expect(rendered).to include("$99")
37
+ end
38
+ end
39
+
40
+ # Explicit template rendering
41
+ RSpec.describe "rendering the widget template", type: :view do
42
+ let(:widget) { create(:widget, name: "Dicer") }
43
+
44
+ before { assign(:widget, widget) }
45
+
46
+ it "renders with explicit template path" do
47
+ render template: "widgets/widget"
48
+ expect(rendered).to include("Dicer")
49
+ end
50
+ end
51
+
52
+ # Rendering with layouts
53
+ RSpec.describe "widgets/widget", type: :view do
54
+ let(:widget) { create(:widget, name: "Blender") }
55
+
56
+ before { assign(:widget, widget) }
57
+
58
+ context "with application layout" do
59
+ it "includes header from layout" do
60
+ render template: "widgets/widget", layout: "layouts/application"
61
+
62
+ expect(rendered).to include("Application Header")
63
+ expect(rendered).to include("Blender")
64
+ end
65
+ end
66
+
67
+ context "with admin layout" do
68
+ it "includes admin navigation" do
69
+ render template: "widgets/widget", layout: "layouts/admin"
70
+
71
+ expect(rendered).to include("Admin Navigation")
72
+ end
73
+ end
74
+ end
75
+
76
+ # Testing partials
77
+ RSpec.describe "widgets/_widget", type: :view do
78
+ let(:widget) { create(:widget, name: "Grinder") }
79
+
80
+ it "renders the widget partial with locals" do
81
+ render partial: "widgets/widget", locals: { widget: }
82
+
83
+ expect(rendered).to include("Grinder")
84
+ end
85
+ end
86
+
87
+ # Testing collection rendering
88
+ RSpec.describe "widgets/_widget", type: :view do
89
+ let(:widgets) { build_list(:widget, 3) }
90
+
91
+ it "renders each widget in collection" do
92
+ render partial: "widgets/widget", collection: widgets
93
+
94
+ widgets.each do |widget|
95
+ expect(rendered).to include(widget.name)
96
+ end
97
+ end
98
+ end
99
+
100
+ # Stubbing view helpers
101
+ RSpec.describe "secrets/index", type: :view do
102
+ context "when user is admin" do
103
+ before { allow(view).to receive(:admin?).and_return(true) }
104
+
105
+ it "displays admin section" do
106
+ render
107
+ expect(rendered).to include("Secret admin area")
108
+ end
109
+ end
110
+
111
+ context "when user is not admin" do
112
+ before { allow(view).to receive(:admin?).and_return(false) }
113
+
114
+ it "hides admin section" do
115
+ render
116
+ expect(rendered).not_to include("Secret admin area")
117
+ end
118
+ end
119
+ end
120
+
121
+ # Stubbing current_user
122
+ RSpec.describe "dashboard/index", type: :view do
123
+ let(:user) { build(:user, name: "John") }
124
+
125
+ before { allow(view).to receive(:current_user).and_return(user) }
126
+
127
+ it "greets the current user" do
128
+ render
129
+ expect(rendered).to include("Welcome, John")
130
+ end
131
+ end
132
+
133
+ # Testing forms
134
+ RSpec.describe "widgets/new", type: :view do
135
+ before { assign(:widget, Widget.new) }
136
+
137
+ it "renders the new widget form" do
138
+ render
139
+
140
+ expect(rendered).to have_selector("form[action='/widgets'][method='post']")
141
+ expect(rendered).to have_selector("input[name='widget[name]']")
142
+ expect(rendered).to have_selector("input[type='submit']")
143
+ end
144
+ end
145
+
146
+ # Testing edit forms with existing record
147
+ RSpec.describe "widgets/edit", type: :view do
148
+ let(:widget) { create(:widget, name: "Existing Widget") }
149
+
150
+ before { assign(:widget, widget) }
151
+
152
+ it "populates form with widget values" do
153
+ render
154
+
155
+ expect(rendered).to have_selector("form[action='/widgets/#{widget.id}']")
156
+ expect(rendered).to have_selector("input[name='widget[name]'][value='Existing Widget']")
157
+ end
158
+ end
159
+
160
+ # Testing conditional content
161
+ RSpec.describe "widgets/show", type: :view do
162
+ let(:widget) { create(:widget, :published) }
163
+
164
+ before { assign(:widget, widget) }
165
+
166
+ context "when widget is published" do
167
+ it "shows published badge" do
168
+ render
169
+ expect(rendered).to include("Published")
170
+ end
171
+ end
172
+
173
+ context "when widget is draft" do
174
+ let(:widget) { create(:widget, :draft) }
175
+
176
+ it "shows draft badge" do
177
+ render
178
+ expect(rendered).to include("Draft")
179
+ end
180
+ end
181
+ end
182
+
183
+ # Testing links
184
+ RSpec.describe "widgets/index", type: :view do
185
+ let(:widgets) { create_list(:widget, 2) }
186
+
187
+ before { assign(:widgets, widgets) }
188
+
189
+ it "links to each widget" do
190
+ render
191
+
192
+ widgets.each do |widget|
193
+ expect(rendered).to have_link(widget.name, href: widget_path(widget))
194
+ end
195
+ end
196
+
197
+ it "links to new widget" do
198
+ render
199
+ expect(rendered).to have_link("New Widget", href: new_widget_path)
200
+ end
201
+ end
202
+
203
+ # Using Capybara matchers in view specs
204
+ RSpec.describe "widgets/show", type: :view do
205
+ let(:widget) { create(:widget, name: "Test Widget", description: "A great widget") }
206
+
207
+ before { assign(:widget, widget) }
208
+
209
+ it "renders widget details" do
210
+ render
211
+
212
+ expect(rendered).to have_css("h1", text: "Test Widget")
213
+ expect(rendered).to have_css("p.description", text: "A great widget")
214
+ end
215
+ end
216
+
217
+ # Testing empty states
218
+ RSpec.describe "widgets/index", type: :view do
219
+ context "with no widgets" do
220
+ before { assign(:widgets, []) }
221
+
222
+ it "shows empty state message" do
223
+ render
224
+ expect(rendered).to include("No widgets found")
225
+ end
226
+ end
227
+ end
228
+
229
+ # Testing content_for blocks
230
+ RSpec.describe "widgets/show", type: :view do
231
+ let(:widget) { create(:widget, name: "Widget") }
232
+
233
+ before { assign(:widget, widget) }
234
+
235
+ it "sets page title via content_for" do
236
+ render
237
+
238
+ expect(view.content_for(:title)).to eq("Widget")
239
+ end
240
+ end
241
+
242
+ # Access view helpers defined in the application
243
+ RSpec.describe "widgets/show", type: :view do
244
+ let(:widget) { create(:widget, price: 1000) }
245
+
246
+ before { assign(:widget, widget) }
247
+
248
+ it "formats currency using helper" do
249
+ render
250
+ expect(rendered).to include("$1,000.00")
251
+ end
252
+ end