anima-core 0.3.0 → 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.
- checksums.yaml +4 -4
- data/.reek.yml +27 -1
- data/CHANGELOG.md +4 -0
- data/README.md +219 -25
- data/agents/codebase-analyzer.md +88 -0
- data/agents/codebase-pattern-finder.md +83 -0
- data/agents/documentation-researcher.md +59 -0
- data/agents/thoughts-analyzer.md +102 -0
- data/agents/web-search-researcher.md +71 -0
- data/anima-core.gemspec +3 -0
- data/app/channels/session_channel.rb +76 -28
- data/app/jobs/agent_request_job.rb +24 -0
- data/app/jobs/analytical_brain_job.rb +33 -0
- data/app/jobs/count_event_tokens_job.rb +1 -1
- data/app/models/concerns/event/broadcasting.rb +20 -2
- data/app/models/event.rb +1 -1
- data/app/models/goal.rb +91 -0
- data/app/models/session.rb +347 -22
- data/config/application.rb +2 -0
- data/db/migrate/20260314075248_add_subagent_support_to_sessions.rb +6 -0
- data/db/migrate/20260314112417_add_granted_tools_to_sessions.rb +5 -0
- data/db/migrate/20260314140000_add_name_to_sessions.rb +7 -0
- data/db/migrate/20260314150000_add_viewport_event_ids_to_sessions.rb +7 -0
- data/db/migrate/20260315100000_add_active_skills_to_sessions.rb +7 -0
- data/db/migrate/20260315140843_create_goals.rb +16 -0
- data/db/migrate/20260315144837_add_completed_at_to_goals.rb +5 -0
- data/db/migrate/20260315191105_add_active_workflow_to_sessions.rb +5 -0
- data/lib/agent_loop.rb +65 -9
- data/lib/agents/definition.rb +116 -0
- data/lib/agents/registry.rb +106 -0
- data/lib/analytical_brain/runner.rb +276 -0
- data/lib/analytical_brain/tools/activate_skill.rb +52 -0
- data/lib/analytical_brain/tools/deactivate_skill.rb +43 -0
- data/lib/analytical_brain/tools/deactivate_workflow.rb +34 -0
- data/lib/analytical_brain/tools/everything_is_ready.rb +28 -0
- data/lib/analytical_brain/tools/finish_goal.rb +62 -0
- data/lib/analytical_brain/tools/read_workflow.rb +58 -0
- data/lib/analytical_brain/tools/rename_session.rb +63 -0
- data/lib/analytical_brain/tools/set_goal.rb +60 -0
- data/lib/analytical_brain/tools/update_goal.rb +60 -0
- data/lib/analytical_brain.rb +23 -0
- data/lib/anima/cli/mcp/secrets.rb +76 -0
- data/lib/anima/cli/mcp.rb +197 -0
- data/lib/anima/cli.rb +4 -0
- data/lib/anima/installer.rb +168 -0
- data/lib/anima/settings.rb +226 -0
- data/lib/anima/version.rb +1 -1
- data/lib/anima.rb +9 -0
- data/lib/credential_store.rb +103 -0
- data/lib/environment_probe.rb +232 -0
- data/lib/llm/client.rb +29 -10
- data/lib/mcp/client_manager.rb +86 -0
- data/lib/mcp/config.rb +213 -0
- data/lib/mcp/health_check.rb +77 -0
- data/lib/mcp/secrets.rb +73 -0
- data/lib/mcp/stdio_transport.rb +206 -0
- data/lib/providers/anthropic.rb +8 -7
- data/lib/shell_session.rb +11 -10
- data/lib/skills/definition.rb +97 -0
- data/lib/skills/registry.rb +105 -0
- data/lib/tools/edit.rb +3 -4
- data/lib/tools/mcp_tool.rb +114 -0
- data/lib/tools/read.rb +15 -16
- data/lib/tools/registry.rb +14 -12
- data/lib/tools/request_feature.rb +121 -0
- data/lib/tools/return_result.rb +81 -0
- data/lib/tools/spawn_specialist.rb +109 -0
- data/lib/tools/spawn_subagent.rb +111 -0
- data/lib/tools/subagent_prompts.rb +12 -0
- data/lib/tools/web_get.rb +8 -9
- data/lib/tui/app.rb +332 -43
- data/lib/tui/message_store.rb +20 -0
- data/lib/tui/screens/chat.rb +207 -20
- data/lib/workflows/definition.rb +97 -0
- data/lib/workflows/registry.rb +89 -0
- data/skills/activerecord/SKILL.md +255 -0
- data/skills/activerecord/examples/associations/association_extensions.rb +298 -0
- data/skills/activerecord/examples/associations/basic_associations.rb +118 -0
- data/skills/activerecord/examples/associations/counter_caches.rb +215 -0
- data/skills/activerecord/examples/associations/polymorphic_associations.rb +217 -0
- data/skills/activerecord/examples/associations/self_referential.rb +302 -0
- data/skills/activerecord/examples/associations/through_associations.rb +203 -0
- data/skills/activerecord/examples/basics/crud_operations.rb +209 -0
- data/skills/activerecord/examples/basics/dirty_tracking.rb +218 -0
- data/skills/activerecord/examples/basics/inheritance.rb +377 -0
- data/skills/activerecord/examples/basics/type_casting.rb +317 -0
- data/skills/activerecord/examples/callbacks/alternatives_to_callbacks.rb +447 -0
- data/skills/activerecord/examples/callbacks/conditional_callbacks.rb +353 -0
- data/skills/activerecord/examples/callbacks/lifecycle_callbacks.rb +280 -0
- data/skills/activerecord/examples/callbacks/transaction_callbacks.rb +340 -0
- data/skills/activerecord/examples/migrations/indexes_and_constraints.rb +337 -0
- data/skills/activerecord/examples/migrations/reversible_patterns.rb +403 -0
- data/skills/activerecord/examples/migrations/safe_patterns.rb +420 -0
- data/skills/activerecord/examples/migrations/schema_changes.rb +277 -0
- data/skills/activerecord/examples/querying/batch_processing.rb +226 -0
- data/skills/activerecord/examples/querying/eager_loading.rb +259 -0
- data/skills/activerecord/examples/querying/finder_methods.rb +170 -0
- data/skills/activerecord/examples/querying/optimization.rb +275 -0
- data/skills/activerecord/examples/querying/scopes.rb +260 -0
- data/skills/activerecord/examples/validations/built_in_validators.rb +277 -0
- data/skills/activerecord/examples/validations/conditional_validations.rb +288 -0
- data/skills/activerecord/examples/validations/custom_validators.rb +381 -0
- data/skills/activerecord/examples/validations/database_constraints.rb +432 -0
- data/skills/activerecord/examples/validations/validation_contexts.rb +367 -0
- data/skills/activerecord/references/associations.md +709 -0
- data/skills/activerecord/references/basics.md +622 -0
- data/skills/activerecord/references/callbacks.md +738 -0
- data/skills/activerecord/references/migrations.md +657 -0
- data/skills/activerecord/references/querying.md +655 -0
- data/skills/activerecord/references/validations.md +596 -0
- data/skills/dragonruby/SKILL.md +250 -0
- data/skills/dragonruby/examples/audio/audio_events.rb +55 -0
- data/skills/dragonruby/examples/audio/background_music.rb +29 -0
- data/skills/dragonruby/examples/audio/crossfade.rb +51 -0
- data/skills/dragonruby/examples/audio/music_controls.rb +51 -0
- data/skills/dragonruby/examples/audio/sound_effects.rb +30 -0
- data/skills/dragonruby/examples/core/coordinate_system.rb +27 -0
- data/skills/dragonruby/examples/core/hello_world.rb +24 -0
- data/skills/dragonruby/examples/core/labels.rb +22 -0
- data/skills/dragonruby/examples/core/sprites.rb +35 -0
- data/skills/dragonruby/examples/core/state_management.rb +29 -0
- data/skills/dragonruby/examples/distribution/background_pause.rb +42 -0
- data/skills/dragonruby/examples/distribution/build_workflow.sh +26 -0
- data/skills/dragonruby/examples/distribution/cvars_production.txt +16 -0
- data/skills/dragonruby/examples/distribution/game_metadata_hd.txt +23 -0
- data/skills/dragonruby/examples/distribution/game_metadata_minimal.txt +9 -0
- data/skills/dragonruby/examples/distribution/game_metadata_mobile.txt +31 -0
- data/skills/dragonruby/examples/distribution/platform_detection.rb +36 -0
- data/skills/dragonruby/examples/distribution/steam_metadata.txt +19 -0
- data/skills/dragonruby/examples/entities/collision_detection.rb +43 -0
- data/skills/dragonruby/examples/entities/entity_lifecycle.rb +68 -0
- data/skills/dragonruby/examples/entities/entity_storage.rb +38 -0
- data/skills/dragonruby/examples/entities/factory_methods.rb +45 -0
- data/skills/dragonruby/examples/entities/random_spawning.rb +50 -0
- data/skills/dragonruby/examples/game-logic/reset_patterns.rb +98 -0
- data/skills/dragonruby/examples/game-logic/save_load.rb +101 -0
- data/skills/dragonruby/examples/game-logic/scoring.rb +104 -0
- data/skills/dragonruby/examples/game-logic/state_transitions.rb +103 -0
- data/skills/dragonruby/examples/game-logic/timers.rb +87 -0
- data/skills/dragonruby/examples/input/action_triggers.rb +36 -0
- data/skills/dragonruby/examples/input/analog_movement.rb +28 -0
- data/skills/dragonruby/examples/input/controller_input.rb +28 -0
- data/skills/dragonruby/examples/input/directional_input.rb +24 -0
- data/skills/dragonruby/examples/input/keyboard_input.rb +28 -0
- data/skills/dragonruby/examples/input/mouse_click.rb +26 -0
- data/skills/dragonruby/examples/input/movement_with_bounds.rb +22 -0
- data/skills/dragonruby/examples/input/normalized_movement.rb +32 -0
- data/skills/dragonruby/examples/rendering/frame_animation.rb +32 -0
- data/skills/dragonruby/examples/rendering/labels.rb +32 -0
- data/skills/dragonruby/examples/rendering/layering.rb +51 -0
- data/skills/dragonruby/examples/rendering/solids.rb +61 -0
- data/skills/dragonruby/examples/rendering/sprites.rb +33 -0
- data/skills/dragonruby/examples/rendering/spritesheet_animation.rb +39 -0
- data/skills/dragonruby/examples/scenes/case_dispatch.rb +60 -0
- data/skills/dragonruby/examples/scenes/class_based.rb +150 -0
- data/skills/dragonruby/examples/scenes/pause_overlay.rb +100 -0
- data/skills/dragonruby/examples/scenes/safe_transitions.rb +68 -0
- data/skills/dragonruby/examples/scenes/scene_transitions.rb +98 -0
- data/skills/dragonruby/examples/scenes/send_dispatch.rb +88 -0
- data/skills/dragonruby/references/audio.md +396 -0
- data/skills/dragonruby/references/core.md +385 -0
- data/skills/dragonruby/references/distribution.md +434 -0
- data/skills/dragonruby/references/entities.md +516 -0
- data/skills/dragonruby/references/game-logic/persistence.md +386 -0
- data/skills/dragonruby/references/game-logic/state.md +389 -0
- data/skills/dragonruby/references/input.md +414 -0
- data/skills/dragonruby/references/rendering/animation.md +467 -0
- data/skills/dragonruby/references/rendering/primitives.md +403 -0
- data/skills/dragonruby/references/scenes.md +443 -0
- data/skills/draper-decorators/SKILL.md +344 -0
- data/skills/draper-decorators/examples/application_decorator.rb +61 -0
- data/skills/draper-decorators/examples/decorator_spec.rb +253 -0
- data/skills/draper-decorators/examples/model_decorator.rb +152 -0
- data/skills/draper-decorators/references/anti-patterns.md +640 -0
- data/skills/draper-decorators/references/patterns.md +507 -0
- data/skills/draper-decorators/references/testing.md +559 -0
- data/skills/gh-issue.md +182 -0
- data/skills/mcp-server/SKILL.md +177 -0
- data/skills/mcp-server/examples/dynamic_tools.rb +36 -0
- data/skills/mcp-server/examples/file_manager_tool.rb +85 -0
- data/skills/mcp-server/examples/http_client.rb +48 -0
- data/skills/mcp-server/examples/http_server.rb +97 -0
- data/skills/mcp-server/examples/rails_integration.rb +88 -0
- data/skills/mcp-server/examples/stdio_server.rb +108 -0
- data/skills/mcp-server/examples/streaming_client.rb +95 -0
- data/skills/mcp-server/references/gotchas.md +183 -0
- data/skills/mcp-server/references/prompts.md +98 -0
- data/skills/mcp-server/references/resources.md +53 -0
- data/skills/mcp-server/references/server.md +140 -0
- data/skills/mcp-server/references/tools.md +146 -0
- data/skills/mcp-server/references/transport.md +104 -0
- data/skills/ratatui-ruby/SKILL.md +315 -0
- data/skills/ratatui-ruby/references/core-concepts.md +340 -0
- data/skills/ratatui-ruby/references/events.md +387 -0
- data/skills/ratatui-ruby/references/frameworks.md +522 -0
- data/skills/ratatui-ruby/references/layout.md +423 -0
- data/skills/ratatui-ruby/references/styling.md +268 -0
- data/skills/ratatui-ruby/references/testing.md +433 -0
- data/skills/ratatui-ruby/references/widgets.md +532 -0
- data/skills/rspec/SKILL.md +340 -0
- data/skills/rspec/examples/core/basic_structure.rb +69 -0
- data/skills/rspec/examples/core/configuration.rb +126 -0
- data/skills/rspec/examples/core/hooks.rb +126 -0
- data/skills/rspec/examples/core/memoized_helpers.rb +139 -0
- data/skills/rspec/examples/core/metadata_filtering.rb +144 -0
- data/skills/rspec/examples/core/shared_examples.rb +145 -0
- data/skills/rspec/examples/factory_bot/associations.rb +314 -0
- data/skills/rspec/examples/factory_bot/build_strategies.rb +272 -0
- data/skills/rspec/examples/factory_bot/callbacks.rb +320 -0
- data/skills/rspec/examples/factory_bot/custom_construction.rb +328 -0
- data/skills/rspec/examples/factory_bot/factory_definition.rb +191 -0
- data/skills/rspec/examples/factory_bot/inheritance.rb +314 -0
- data/skills/rspec/examples/factory_bot/traits.rb +293 -0
- data/skills/rspec/examples/factory_bot/transients.rb +229 -0
- data/skills/rspec/examples/matchers/change.rb +115 -0
- data/skills/rspec/examples/matchers/collections.rb +154 -0
- data/skills/rspec/examples/matchers/comparisons.rb +79 -0
- data/skills/rspec/examples/matchers/composing.rb +155 -0
- data/skills/rspec/examples/matchers/custom_matchers.rb +197 -0
- data/skills/rspec/examples/matchers/equality.rb +58 -0
- data/skills/rspec/examples/matchers/errors.rb +136 -0
- data/skills/rspec/examples/matchers/output.rb +103 -0
- data/skills/rspec/examples/matchers/predicates.rb +87 -0
- data/skills/rspec/examples/matchers/truthiness.rb +101 -0
- data/skills/rspec/examples/matchers/types.rb +82 -0
- data/skills/rspec/examples/matchers/yield.rb +147 -0
- data/skills/rspec/examples/mocks/any_instance.rb +172 -0
- data/skills/rspec/examples/mocks/argument_matchers.rb +206 -0
- data/skills/rspec/examples/mocks/constants.rb +177 -0
- data/skills/rspec/examples/mocks/doubles.rb +139 -0
- data/skills/rspec/examples/mocks/expectations.rb +137 -0
- data/skills/rspec/examples/mocks/message_chains.rb +173 -0
- data/skills/rspec/examples/mocks/ordering.rb +144 -0
- data/skills/rspec/examples/mocks/receive_counts.rb +181 -0
- data/skills/rspec/examples/mocks/responses.rb +223 -0
- data/skills/rspec/examples/mocks/spies.rb +149 -0
- data/skills/rspec/examples/mocks/stubbing.rb +133 -0
- data/skills/rspec/examples/rails/channels.rb +250 -0
- data/skills/rspec/examples/rails/controller_specs.rb +302 -0
- data/skills/rspec/examples/rails/helper_specs.rb +245 -0
- data/skills/rspec/examples/rails/job_specs.rb +256 -0
- data/skills/rspec/examples/rails/mailer_specs.rb +228 -0
- data/skills/rspec/examples/rails/matchers.rb +374 -0
- data/skills/rspec/examples/rails/model_specs.rb +193 -0
- data/skills/rspec/examples/rails/request_specs.rb +275 -0
- data/skills/rspec/examples/rails/routing_specs.rb +276 -0
- data/skills/rspec/examples/rails/system_specs.rb +294 -0
- data/skills/rspec/examples/rails/transactions.rb +254 -0
- data/skills/rspec/examples/rails/view_specs.rb +252 -0
- data/skills/rspec/references/core.md +816 -0
- data/skills/rspec/references/factory_bot.md +641 -0
- data/skills/rspec/references/matchers.md +516 -0
- data/skills/rspec/references/mocks.md +381 -0
- data/skills/rspec/references/rails.md +528 -0
- data/templates/soul.md +40 -0
- data/workflows/commit.md +45 -0
- data/workflows/create_handoff.md +98 -0
- data/workflows/create_note.md +82 -0
- data/workflows/create_plan.md +457 -0
- data/workflows/decompose_ticket.md +109 -0
- data/workflows/feature.md +91 -0
- data/workflows/implement_plan.md +87 -0
- data/workflows/iterate_plan.md +247 -0
- data/workflows/research_codebase.md +210 -0
- data/workflows/resume_handoff.md +217 -0
- data/workflows/review_pr.md +320 -0
- data/workflows/thoughts_init.md +71 -0
- data/workflows/validate_plan.md +166 -0
- metadata +284 -1
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# RSpec Rails: Routing Specs Examples
|
|
2
|
+
# Source: rspec-rails gem features/routing_specs/
|
|
3
|
+
|
|
4
|
+
# Routing specs test Rails routes in isolation.
|
|
5
|
+
# Location: spec/routing/
|
|
6
|
+
|
|
7
|
+
# Basic route_to matching
|
|
8
|
+
RSpec.describe "Widget routes", type: :routing do
|
|
9
|
+
describe "GET /widgets" do
|
|
10
|
+
it "routes to widgets#index" do
|
|
11
|
+
expect(get: "/widgets").to route_to("widgets#index")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe "GET /widgets/:id" do
|
|
16
|
+
it "routes to widgets#show" do
|
|
17
|
+
expect(get: "/widgets/1").to route_to(
|
|
18
|
+
controller: "widgets",
|
|
19
|
+
action: "show",
|
|
20
|
+
id: "1"
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe "POST /widgets" do
|
|
26
|
+
it "routes to widgets#create" do
|
|
27
|
+
expect(post: "/widgets").to route_to("widgets#create")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "PATCH /widgets/:id" do
|
|
32
|
+
it "routes to widgets#update" do
|
|
33
|
+
expect(patch: "/widgets/1").to route_to(
|
|
34
|
+
controller: "widgets",
|
|
35
|
+
action: "update",
|
|
36
|
+
id: "1"
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe "DELETE /widgets/:id" do
|
|
42
|
+
it "routes to widgets#destroy" do
|
|
43
|
+
expect(delete: "/widgets/1").to route_to(
|
|
44
|
+
controller: "widgets",
|
|
45
|
+
action: "destroy",
|
|
46
|
+
id: "1"
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Testing nested routes
|
|
53
|
+
RSpec.describe "Comment routes", type: :routing do
|
|
54
|
+
describe "GET /posts/:post_id/comments" do
|
|
55
|
+
it "routes to comments#index" do
|
|
56
|
+
expect(get: "/posts/5/comments").to route_to(
|
|
57
|
+
controller: "comments",
|
|
58
|
+
action: "index",
|
|
59
|
+
post_id: "5"
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "POST /posts/:post_id/comments" do
|
|
65
|
+
it "routes to comments#create" do
|
|
66
|
+
expect(post: "/posts/5/comments").to route_to(
|
|
67
|
+
controller: "comments",
|
|
68
|
+
action: "create",
|
|
69
|
+
post_id: "5"
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Testing namespaced routes
|
|
76
|
+
RSpec.describe "Admin routes", type: :routing do
|
|
77
|
+
describe "GET /admin/users" do
|
|
78
|
+
it "routes to admin/users#index" do
|
|
79
|
+
expect(get: "/admin/users").to route_to("admin/users#index")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
describe "GET /admin/dashboard" do
|
|
84
|
+
it "routes to admin/dashboard#show" do
|
|
85
|
+
expect(get: "/admin/dashboard").to route_to("admin/dashboard#show")
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Testing API versioned routes
|
|
91
|
+
RSpec.describe "API v1 routes", type: :routing do
|
|
92
|
+
describe "GET /api/v1/widgets" do
|
|
93
|
+
it "routes to api/v1/widgets#index" do
|
|
94
|
+
expect(get: "/api/v1/widgets").to route_to(
|
|
95
|
+
controller: "api/v1/widgets",
|
|
96
|
+
action: "index"
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Testing custom member routes
|
|
103
|
+
RSpec.describe "Custom routes", type: :routing do
|
|
104
|
+
describe "POST /widgets/:id/publish" do
|
|
105
|
+
it "routes to widgets#publish" do
|
|
106
|
+
expect(post: "/widgets/1/publish").to route_to(
|
|
107
|
+
controller: "widgets",
|
|
108
|
+
action: "publish",
|
|
109
|
+
id: "1"
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
describe "POST /widgets/:id/archive" do
|
|
115
|
+
it "routes to widgets#archive" do
|
|
116
|
+
expect(post: "/widgets/1/archive").to route_to(
|
|
117
|
+
controller: "widgets",
|
|
118
|
+
action: "archive",
|
|
119
|
+
id: "1"
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Testing collection routes
|
|
126
|
+
RSpec.describe "Collection routes", type: :routing do
|
|
127
|
+
describe "GET /widgets/search" do
|
|
128
|
+
it "routes to widgets#search" do
|
|
129
|
+
expect(get: "/widgets/search").to route_to("widgets#search")
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
describe "POST /widgets/import" do
|
|
134
|
+
it "routes to widgets#import" do
|
|
135
|
+
expect(post: "/widgets/import").to route_to("widgets#import")
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# be_routable matcher
|
|
141
|
+
RSpec.describe "Route existence", type: :routing do
|
|
142
|
+
describe "existing routes" do
|
|
143
|
+
it "is routable to GET /widgets" do
|
|
144
|
+
expect(get: "/widgets").to be_routable
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it "is routable to POST /widgets" do
|
|
148
|
+
expect(post: "/widgets").to be_routable
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe "non-existing routes" do
|
|
153
|
+
it "is not routable to PUT /widgets" do
|
|
154
|
+
expect(put: "/widgets").not_to be_routable
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it "is not routable to DELETE /admin" do
|
|
158
|
+
expect(delete: "/admin").not_to be_routable
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Testing routes with constraints
|
|
164
|
+
RSpec.describe "Constrained routes", type: :routing do
|
|
165
|
+
describe "routes with format constraint" do
|
|
166
|
+
it "routes /widgets.json to widgets#index" do
|
|
167
|
+
expect(get: "/widgets.json").to route_to(
|
|
168
|
+
controller: "widgets",
|
|
169
|
+
action: "index",
|
|
170
|
+
format: "json"
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
describe "routes with ID constraint" do
|
|
176
|
+
it "routes numeric ID" do
|
|
177
|
+
expect(get: "/widgets/123").to be_routable
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Depending on route constraints, this might not be routable
|
|
181
|
+
# it "does not route non-numeric ID" do
|
|
182
|
+
# expect(get: "/widgets/abc").not_to be_routable
|
|
183
|
+
# end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Testing subdomain routes
|
|
188
|
+
RSpec.describe "Subdomain routes", type: :routing do
|
|
189
|
+
describe "api subdomain" do
|
|
190
|
+
before { host! "api.example.com" }
|
|
191
|
+
|
|
192
|
+
it "routes to API controller" do
|
|
193
|
+
expect(get: "/widgets").to route_to("api/widgets#index")
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
describe "admin subdomain" do
|
|
198
|
+
before { host! "admin.example.com" }
|
|
199
|
+
|
|
200
|
+
it "routes to admin controller" do
|
|
201
|
+
expect(get: "/dashboard").to route_to("admin/dashboard#show")
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Testing route helpers
|
|
207
|
+
RSpec.describe "Route helpers", type: :routing do
|
|
208
|
+
it "generates widget_path" do
|
|
209
|
+
expect(widget_path(1)).to eq("/widgets/1")
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it "generates widgets_path" do
|
|
213
|
+
expect(widgets_path).to eq("/widgets")
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
it "generates new_widget_path" do
|
|
217
|
+
expect(new_widget_path).to eq("/widgets/new")
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it "generates edit_widget_path" do
|
|
221
|
+
expect(edit_widget_path(1)).to eq("/widgets/1/edit")
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Testing shallow nested routes
|
|
226
|
+
RSpec.describe "Shallow routes", type: :routing do
|
|
227
|
+
describe "nested under parent" do
|
|
228
|
+
it "routes comments#create under posts" do
|
|
229
|
+
expect(post: "/posts/1/comments").to route_to(
|
|
230
|
+
controller: "comments",
|
|
231
|
+
action: "create",
|
|
232
|
+
post_id: "1"
|
|
233
|
+
)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
describe "shallow (non-nested)" do
|
|
238
|
+
it "routes comments#show without parent" do
|
|
239
|
+
expect(get: "/comments/5").to route_to(
|
|
240
|
+
controller: "comments",
|
|
241
|
+
action: "show",
|
|
242
|
+
id: "5"
|
|
243
|
+
)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Testing root route
|
|
249
|
+
RSpec.describe "Root route", type: :routing do
|
|
250
|
+
it "routes / to pages#home" do
|
|
251
|
+
expect(get: "/").to route_to("pages#home")
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Testing optional format
|
|
256
|
+
RSpec.describe "Format routing", type: :routing do
|
|
257
|
+
describe "with format" do
|
|
258
|
+
it "routes with JSON format" do
|
|
259
|
+
expect(get: "/widgets/1.json").to route_to(
|
|
260
|
+
controller: "widgets",
|
|
261
|
+
action: "show",
|
|
262
|
+
id: "1",
|
|
263
|
+
format: "json"
|
|
264
|
+
)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
it "routes with XML format" do
|
|
268
|
+
expect(get: "/widgets/1.xml").to route_to(
|
|
269
|
+
controller: "widgets",
|
|
270
|
+
action: "show",
|
|
271
|
+
id: "1",
|
|
272
|
+
format: "xml"
|
|
273
|
+
)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# RSpec Rails: System Specs Examples
|
|
2
|
+
# Source: rspec-rails gem features/system_specs/
|
|
3
|
+
|
|
4
|
+
# System specs are browser-based tests using Capybara.
|
|
5
|
+
# They run within database transactions by default.
|
|
6
|
+
# Location: spec/system/
|
|
7
|
+
|
|
8
|
+
# Basic system spec with rack_test driver
|
|
9
|
+
RSpec.describe "Widget management", type: :system do
|
|
10
|
+
before { driven_by(:rack_test) }
|
|
11
|
+
|
|
12
|
+
describe "creating a widget" do
|
|
13
|
+
it "creates a new widget" do
|
|
14
|
+
visit "/widgets/new"
|
|
15
|
+
|
|
16
|
+
fill_in "Name", with: "My Widget"
|
|
17
|
+
fill_in "Description", with: "A useful widget"
|
|
18
|
+
click_button "Create Widget"
|
|
19
|
+
|
|
20
|
+
expect(page).to have_text("Widget was successfully created")
|
|
21
|
+
expect(page).to have_text("My Widget")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Using headless Chrome for JavaScript testing
|
|
27
|
+
RSpec.describe "JavaScript features", type: :system do
|
|
28
|
+
before { driven_by(:selenium_chrome_headless) }
|
|
29
|
+
|
|
30
|
+
describe "dynamic form" do
|
|
31
|
+
it "shows validation errors without page reload" do
|
|
32
|
+
visit "/widgets/new"
|
|
33
|
+
click_button "Create Widget"
|
|
34
|
+
|
|
35
|
+
expect(page).to have_text("Name can't be blank")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe "modal dialog" do
|
|
40
|
+
let!(:widget) { create(:widget) }
|
|
41
|
+
|
|
42
|
+
it "confirms deletion in modal" do
|
|
43
|
+
visit "/widgets/#{widget.id}"
|
|
44
|
+
|
|
45
|
+
click_button "Delete"
|
|
46
|
+
within(".modal") do
|
|
47
|
+
click_button "Confirm"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
expect(page).to have_text("Widget was deleted")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Testing user flows
|
|
56
|
+
RSpec.describe "User registration flow", type: :system do
|
|
57
|
+
before { driven_by(:rack_test) }
|
|
58
|
+
|
|
59
|
+
it "allows new user to sign up" do
|
|
60
|
+
visit "/"
|
|
61
|
+
|
|
62
|
+
click_link "Sign Up"
|
|
63
|
+
fill_in "Email", with: "new@example.com"
|
|
64
|
+
fill_in "Password", with: "password123"
|
|
65
|
+
fill_in "Password confirmation", with: "password123"
|
|
66
|
+
click_button "Create Account"
|
|
67
|
+
|
|
68
|
+
expect(page).to have_text("Welcome! You have signed up successfully")
|
|
69
|
+
expect(page).to have_link("Sign Out")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Testing authentication
|
|
74
|
+
RSpec.describe "User sessions", type: :system do
|
|
75
|
+
before { driven_by(:rack_test) }
|
|
76
|
+
|
|
77
|
+
let(:user) { create(:user, email: "test@example.com", password: "password") }
|
|
78
|
+
|
|
79
|
+
describe "logging in" do
|
|
80
|
+
it "logs in with valid credentials" do
|
|
81
|
+
visit "/login"
|
|
82
|
+
|
|
83
|
+
fill_in "Email", with: "test@example.com"
|
|
84
|
+
fill_in "Password", with: "password"
|
|
85
|
+
click_button "Log In"
|
|
86
|
+
|
|
87
|
+
expect(page).to have_text("Logged in successfully")
|
|
88
|
+
expect(page).to have_link("Sign Out")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "shows error with invalid credentials" do
|
|
92
|
+
visit "/login"
|
|
93
|
+
|
|
94
|
+
fill_in "Email", with: "test@example.com"
|
|
95
|
+
fill_in "Password", with: "wrong"
|
|
96
|
+
click_button "Log In"
|
|
97
|
+
|
|
98
|
+
expect(page).to have_text("Invalid email or password")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
describe "logging out" do
|
|
103
|
+
before do
|
|
104
|
+
visit "/login"
|
|
105
|
+
fill_in "Email", with: "test@example.com"
|
|
106
|
+
fill_in "Password", with: "password"
|
|
107
|
+
click_button "Log In"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "logs out the user" do
|
|
111
|
+
click_link "Sign Out"
|
|
112
|
+
expect(page).to have_text("You have been logged out")
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Testing CRUD operations
|
|
118
|
+
RSpec.describe "Widget CRUD", type: :system do
|
|
119
|
+
before { driven_by(:rack_test) }
|
|
120
|
+
|
|
121
|
+
let(:user) { create(:user) }
|
|
122
|
+
|
|
123
|
+
before do
|
|
124
|
+
# Login helper
|
|
125
|
+
visit "/login"
|
|
126
|
+
fill_in "Email", with: user.email
|
|
127
|
+
fill_in "Password", with: "password"
|
|
128
|
+
click_button "Log In"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe "listing widgets" do
|
|
132
|
+
let!(:widgets) { create_list(:widget, 3, user:) }
|
|
133
|
+
|
|
134
|
+
it "displays all user widgets" do
|
|
135
|
+
visit "/widgets"
|
|
136
|
+
|
|
137
|
+
widgets.each do |widget|
|
|
138
|
+
expect(page).to have_text(widget.name)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
describe "creating a widget" do
|
|
144
|
+
it "creates with valid data" do
|
|
145
|
+
visit "/widgets/new"
|
|
146
|
+
|
|
147
|
+
fill_in "Name", with: "New Widget"
|
|
148
|
+
click_button "Create Widget"
|
|
149
|
+
|
|
150
|
+
expect(page).to have_text("Widget was successfully created")
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
describe "editing a widget" do
|
|
155
|
+
let!(:widget) { create(:widget, name: "Old Name", user:) }
|
|
156
|
+
|
|
157
|
+
it "updates the widget" do
|
|
158
|
+
visit "/widgets/#{widget.id}/edit"
|
|
159
|
+
|
|
160
|
+
fill_in "Name", with: "Updated Name"
|
|
161
|
+
click_button "Update Widget"
|
|
162
|
+
|
|
163
|
+
expect(page).to have_text("Widget was successfully updated")
|
|
164
|
+
expect(page).to have_text("Updated Name")
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
describe "deleting a widget" do
|
|
169
|
+
let!(:widget) { create(:widget, user:) }
|
|
170
|
+
|
|
171
|
+
it "removes the widget" do
|
|
172
|
+
visit "/widgets/#{widget.id}"
|
|
173
|
+
|
|
174
|
+
click_button "Delete"
|
|
175
|
+
|
|
176
|
+
expect(page).to have_text("Widget was deleted")
|
|
177
|
+
expect(page).not_to have_text(widget.name)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Testing navigation
|
|
183
|
+
RSpec.describe "Navigation", type: :system do
|
|
184
|
+
before { driven_by(:rack_test) }
|
|
185
|
+
|
|
186
|
+
it "navigates between pages" do
|
|
187
|
+
visit "/"
|
|
188
|
+
|
|
189
|
+
click_link "Widgets"
|
|
190
|
+
expect(page).to have_current_path("/widgets")
|
|
191
|
+
|
|
192
|
+
click_link "About"
|
|
193
|
+
expect(page).to have_current_path("/about")
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Testing flash messages
|
|
198
|
+
RSpec.describe "Flash messages", type: :system do
|
|
199
|
+
before { driven_by(:rack_test) }
|
|
200
|
+
|
|
201
|
+
it "shows success message" do
|
|
202
|
+
visit "/widgets/new"
|
|
203
|
+
fill_in "Name", with: "Test"
|
|
204
|
+
click_button "Create Widget"
|
|
205
|
+
|
|
206
|
+
expect(page).to have_css(".flash.notice", text: "successfully created")
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it "shows error message" do
|
|
210
|
+
visit "/widgets/new"
|
|
211
|
+
click_button "Create Widget"
|
|
212
|
+
|
|
213
|
+
expect(page).to have_css(".flash.alert")
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Testing with JavaScript (Turbo)
|
|
218
|
+
RSpec.describe "Turbo interactions", type: :system do
|
|
219
|
+
before { driven_by(:selenium_chrome_headless) }
|
|
220
|
+
|
|
221
|
+
let!(:widget) { create(:widget) }
|
|
222
|
+
|
|
223
|
+
describe "inline editing" do
|
|
224
|
+
it "updates without full page reload" do
|
|
225
|
+
visit "/widgets/#{widget.id}"
|
|
226
|
+
|
|
227
|
+
click_link "Edit"
|
|
228
|
+
fill_in "Name", with: "Updated via Turbo"
|
|
229
|
+
click_button "Save"
|
|
230
|
+
|
|
231
|
+
expect(page).to have_text("Updated via Turbo")
|
|
232
|
+
# Page was not fully reloaded
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Testing file uploads
|
|
238
|
+
RSpec.describe "File uploads", type: :system do
|
|
239
|
+
before { driven_by(:rack_test) }
|
|
240
|
+
|
|
241
|
+
it "uploads an image" do
|
|
242
|
+
visit "/widgets/new"
|
|
243
|
+
|
|
244
|
+
fill_in "Name", with: "Widget with Image"
|
|
245
|
+
attach_file "Image", Rails.root.join("spec/fixtures/files/image.png")
|
|
246
|
+
click_button "Create Widget"
|
|
247
|
+
|
|
248
|
+
expect(page).to have_css("img.widget-image")
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Testing pagination
|
|
253
|
+
RSpec.describe "Pagination", type: :system do
|
|
254
|
+
before { driven_by(:rack_test) }
|
|
255
|
+
|
|
256
|
+
let!(:widgets) { create_list(:widget, 30) }
|
|
257
|
+
|
|
258
|
+
it "paginates results" do
|
|
259
|
+
visit "/widgets"
|
|
260
|
+
|
|
261
|
+
expect(page).to have_css(".widget", count: 10)
|
|
262
|
+
expect(page).to have_link("Next")
|
|
263
|
+
|
|
264
|
+
click_link "Next"
|
|
265
|
+
|
|
266
|
+
expect(page).to have_css(".widget", count: 10)
|
|
267
|
+
expect(page).to have_link("Previous")
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Testing search
|
|
272
|
+
RSpec.describe "Search", type: :system do
|
|
273
|
+
before { driven_by(:rack_test) }
|
|
274
|
+
|
|
275
|
+
let!(:matching_widget) { create(:widget, name: "Super Widget") }
|
|
276
|
+
let!(:other_widget) { create(:widget, name: "Regular Item") }
|
|
277
|
+
|
|
278
|
+
it "filters results by search term" do
|
|
279
|
+
visit "/widgets"
|
|
280
|
+
|
|
281
|
+
fill_in "Search", with: "Super"
|
|
282
|
+
click_button "Search"
|
|
283
|
+
|
|
284
|
+
expect(page).to have_text("Super Widget")
|
|
285
|
+
expect(page).not_to have_text("Regular Item")
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Configuration in rails_helper.rb
|
|
290
|
+
# RSpec.configure do |config|
|
|
291
|
+
# config.before(type: :system) do
|
|
292
|
+
# driven_by :selenium_chrome_headless
|
|
293
|
+
# end
|
|
294
|
+
# end
|