@spree/docs 0.1.176 → 0.1.178

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 (118) hide show
  1. package/dist/api-reference/store.yaml +48 -48
  2. package/dist/developer/cli/quickstart.md +1 -1
  3. package/dist/developer/contributing/creating-an-extension.md +2 -2
  4. package/dist/developer/core-concepts/addresses.md +3 -3
  5. package/dist/developer/core-concepts/architecture.md +4 -4
  6. package/dist/developer/core-concepts/calculators.md +2 -2
  7. package/dist/developer/core-concepts/carts.md +207 -0
  8. package/dist/developer/core-concepts/channels.md +3 -3
  9. package/dist/developer/core-concepts/customers.md +1 -1
  10. package/dist/developer/core-concepts/events.md +1 -1
  11. package/dist/developer/core-concepts/fulfillments.md +278 -0
  12. package/dist/developer/core-concepts/imports-exports.md +6 -18
  13. package/dist/developer/core-concepts/inventory.md +3 -3
  14. package/dist/developer/core-concepts/media.md +1 -1
  15. package/dist/developer/core-concepts/metafields.md +1 -1
  16. package/dist/developer/core-concepts/orders.md +92 -339
  17. package/dist/developer/core-concepts/payments.md +1 -1
  18. package/dist/developer/core-concepts/promotions.md +68 -195
  19. package/dist/developer/core-concepts/returns-exchanges-claims.md +173 -0
  20. package/dist/developer/core-concepts/store-credits-gift-cards.md +16 -18
  21. package/dist/developer/core-concepts/stores.md +1 -1
  22. package/dist/developer/core-concepts/taxes-discounts-fees.md +199 -0
  23. package/dist/developer/core-concepts/taxes.md +3 -3
  24. package/dist/developer/create-spree-app/quickstart.md +1 -1
  25. package/dist/developer/customization/api.md +36 -11
  26. package/dist/developer/customization/configuration.md +207 -37
  27. package/dist/developer/customization/decorators.md +8 -8
  28. package/dist/developer/customization/permissions.md +34 -229
  29. package/dist/developer/customization/quickstart.md +159 -108
  30. package/dist/developer/customization/validations.md +1 -1
  31. package/dist/developer/dashboard/overview.md +1 -1
  32. package/dist/developer/how-to/build-a-b2b-store.md +21 -0
  33. package/dist/developer/how-to/build-a-marketplace.md +25 -0
  34. package/dist/developer/how-to/custom-api-authentication.md +1 -1
  35. package/dist/developer/how-to/custom-delivery-rate-provider.md +242 -0
  36. package/dist/developer/how-to/custom-document-numbers.md +154 -0
  37. package/dist/developer/how-to/custom-order-routing.md +1 -1
  38. package/dist/developer/how-to/custom-payment-method.md +1 -1
  39. package/dist/developer/how-to/custom-promotion.md +76 -115
  40. package/dist/developer/how-to/custom-report.md +2 -2
  41. package/dist/developer/how-to/custom-stock-splitter.md +5 -5
  42. package/dist/developer/how-to/sell-digital-products.md +20 -0
  43. package/dist/developer/multi-tenant/quickstart.md +1 -1
  44. package/dist/developer/providers/dam.md +14 -0
  45. package/dist/developer/providers/erp.md +31 -0
  46. package/dist/developer/providers/fulfillment.md +25 -0
  47. package/dist/developer/{deployment/telemetry.md → providers/observability.md} +1 -1
  48. package/dist/developer/providers/overview.md +44 -0
  49. package/dist/developer/providers/pim.md +25 -0
  50. package/dist/developer/providers/sso.md +20 -0
  51. package/dist/developer/sdk/admin/extending.md +1 -1
  52. package/dist/developer/sdk/admin/quickstart.md +1 -1
  53. package/dist/developer/sdk/admin/resources.md +1 -1
  54. package/dist/developer/sdk/extending.md +1 -1
  55. package/dist/developer/tutorial/admin-api.md +13 -0
  56. package/dist/developer/tutorial/dashboard-plugin.md +14 -0
  57. package/dist/developer/tutorial/events.md +7 -161
  58. package/dist/developer/tutorial/introduction.md +15 -35
  59. package/dist/developer/tutorial/model.md +7 -98
  60. package/dist/developer/tutorial/store-api.md +13 -0
  61. package/dist/developer/tutorial/storefront.md +12 -0
  62. package/dist/developer/tutorial/testing.md +7 -711
  63. package/dist/developer/upgrades/5.6-to-6.0.md +2 -2
  64. package/package.json +1 -1
  65. package/dist/developer/admin/admin.md +0 -214
  66. package/dist/developer/admin/authentication.md +0 -59
  67. package/dist/developer/admin/components.md +0 -711
  68. package/dist/developer/admin/custom-css.md +0 -256
  69. package/dist/developer/admin/custom-javascript.md +0 -116
  70. package/dist/developer/admin/extending-ui.md +0 -1839
  71. package/dist/developer/admin/form-builder.md +0 -444
  72. package/dist/developer/admin/helper-methods.md +0 -531
  73. package/dist/developer/admin/navigation.md +0 -805
  74. package/dist/developer/admin/tables.md +0 -490
  75. package/dist/developer/advanced/adding_spree_to_rails_app.md +0 -92
  76. package/dist/developer/core-concepts/adjustments.md +0 -113
  77. package/dist/developer/core-concepts/reports.md +0 -208
  78. package/dist/developer/core-concepts/shipments.md +0 -307
  79. package/dist/developer/core-concepts/users.md +0 -303
  80. package/dist/developer/customization/authentication.md +0 -100
  81. package/dist/developer/customization/checkout.md +0 -202
  82. package/dist/developer/customization/emails.md +0 -18
  83. package/dist/developer/customization/routes.md +0 -24
  84. package/dist/developer/multi-vendor/installation.md +0 -61
  85. package/dist/developer/multi-vendor/quickstart.md +0 -17
  86. package/dist/developer/tutorial/admin.md +0 -206
  87. package/dist/developer/tutorial/api.md +0 -606
  88. package/dist/developer/tutorial/extending-models.md +0 -393
  89. package/dist/developer/tutorial/sdk.md +0 -170
  90. package/dist/developer/upgrades/2.0-to-2.1.md +0 -46
  91. package/dist/developer/upgrades/2.1-to-2.2.md +0 -59
  92. package/dist/developer/upgrades/2.2-to-2.3.md +0 -44
  93. package/dist/developer/upgrades/2.3-to-2.4.md +0 -42
  94. package/dist/developer/upgrades/3.0-to-3.1.md +0 -47
  95. package/dist/developer/upgrades/3.1-to-3.2.md +0 -34
  96. package/dist/developer/upgrades/3.2-to-3.3.md +0 -70
  97. package/dist/developer/upgrades/3.3-to-3.4.md +0 -36
  98. package/dist/developer/upgrades/3.4-to-3.5.md +0 -44
  99. package/dist/developer/upgrades/3.5-to-3.6.md +0 -40
  100. package/dist/developer/upgrades/3.6-to-3.7.md +0 -62
  101. package/dist/developer/upgrades/3.7-to-4.0.md +0 -152
  102. package/dist/developer/upgrades/4.0-to-4.1.md +0 -92
  103. package/dist/developer/upgrades/4.1-to-4.2.md +0 -109
  104. package/dist/developer/upgrades/4.10-to-5.0.md +0 -131
  105. package/dist/developer/upgrades/4.2-to-4.3.md +0 -100
  106. package/dist/developer/upgrades/4.3-to-4.4.md +0 -125
  107. package/dist/developer/upgrades/4.4-to-4.5.md +0 -94
  108. package/dist/developer/upgrades/4.5-to-4.6.md +0 -119
  109. package/dist/developer/upgrades/4.6-to-4.7.md +0 -39
  110. package/dist/developer/upgrades/4.8-to-4.9.md +0 -24
  111. package/dist/developer/upgrades/4.9-to-4.10.md +0 -24
  112. package/dist/developer/upgrades/4.x-to-4.8.md +0 -52
  113. package/dist/developer/upgrades/5.0-to-5.1.md +0 -28
  114. package/dist/developer/upgrades/5.1-to-5.2.md +0 -131
  115. package/dist/developer/upgrades/5.2-to-5.3.md +0 -338
  116. package/dist/developer/upgrades/5.3-to-5.4.md +0 -277
  117. package/dist/developer/upgrades/5.4-to-5.5.md +0 -301
  118. package/dist/developer/upgrades/5.5-to-5.6.md +0 -207
@@ -1,717 +1,13 @@
1
1
  ---
2
2
  title: Testing
3
- description: Write automated tests for the Brands tutorial feature using RSpec, Factory Bot, and Capybara, plus helpers from the spree_dev_tools gem.
3
+ description: Test the feature at every layer model and request specs with RSpec, dashboard plugin tests with Vitest, and an end-to-end check with Playwright.
4
4
  ---
5
5
 
6
- Automated testing is a crucial part of the development process. It helps you ensure that your code works as expected and catches bugs early.
7
- Spree uses [RSpec](https://rspec.info), [Factory Bot](https://github.com/thoughtbot/factory_bot_rails), and [Capybara](https://github.com/teamcapybara/capybara) for testing.
8
- We also provide the `spree_dev_tools` gem that helps you write Spree-specific tests.
6
+ Each layer of the feature gets the test that suits it: fast Ruby specs for the model and both API endpoints, component tests for the plugin, and one end-to-end pass proving the whole path works.
9
7
 
10
- > **INFO:** This guide assumes you've completed all previous tutorials through [API](api.md). You should have a complete `Spree::Brand` model with admin features and API endpoints.
8
+ ## What this chapter covers
11
9
 
12
- ## Setup
13
-
14
- **If your app came from [create-spree-app](../create-spree-app/quickstart.md) or [spree-starter](https://github.com/spree/spree-starter), the test environment is already set up** — RSpec, Factory Bot, Capybara, DatabaseCleaner, and the `spree_dev_tools` helpers (`stub_authorization!`, the `'API v3 Store'` shared context, Spree factories) ship preconfigured in `spec/support/`. Skip to creating the fixtures file below.
15
-
16
- <details>
17
- <summary>Setting up an app that doesn't have RSpec yet</summary>
18
-
19
- For an existing Rails app without test setup, install RSpec and the Spree test helpers:
20
-
21
-
22
- ```bash Spree CLI (Docker)
23
- spree generate rspec:install
24
- spree generate spree_dev_tools:install
25
- ```
26
-
27
- ```bash Without Spree CLI
28
- bin/rails g rspec:install
29
- bin/rails g spree_dev_tools:install
30
- ```
31
-
32
-
33
- The `spree_dev_tools` generator adds the Spree-specific helpers to `spec/support/`: authorization helpers (`stub_authorization!`), Factory Bot configuration, Capybara setup for feature tests, and more.
34
-
35
- </details>
36
-
37
-
38
- ### Create the Fixtures Directory and File
39
-
40
- When writing tests that involve file attachments (like images, PDFs, etc.), you need fixture files that your factories can use. Here's how to set them up.
41
-
42
- ```bash
43
- mkdir -p spec/fixtures/files && printf '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc\xf8\x0f\x00\x00\x01\x01\x00\x05\x18\xd8N\x00\x00\x00\x00IEND\xaeB`\x82' > spec/fixtures/files/logo.png
44
- ```
45
-
46
- ### What the generators already created
47
-
48
- If you used `spree generate api_resource` in the [API step](api.md), you already have controller specs (`spec/controllers/spree/api/v3/store/brands_controller_spec.rb` and `…/admin/brands_controller_spec.rb`) and a factory (`spec/factories/spree/brand_factory.rb`). The sections below build the same things by hand — compare as you go, and keep whichever you prefer.
49
-
50
- For the model spec, create `spec/models/spree/brand_spec.rb` — we'll fill it in below.
51
-
52
- ## Writing Factories
53
-
54
- Factories provide a convenient way to create test data. Create a factory for your Brand model:
55
-
56
- ```ruby spec/factories/spree/brand_factory.rb
57
- FactoryBot.define do
58
- factory :brand, class: Spree::Brand do
59
- sequence(:name) { |n| "Brand #{n}" }
60
- sequence(:slug) { |n| "brand-#{n}" }
61
-
62
- trait :with_description do
63
- description { '<div>A great brand for <strong>quality products</strong></div>' }
64
- end
65
-
66
- trait :with_logo do
67
- after(:create) do |brand|
68
- brand.logo.attach(
69
- io: File.new(Rails.root.join('spec', 'fixtures', 'files', 'logo.png')),
70
- filename: 'logo.png'
71
- )
72
- end
73
- end
74
-
75
- trait :with_products do
76
- transient do
77
- products_count { 3 }
78
- store { nil }
79
- end
80
-
81
- after(:create) do |brand, evaluator|
82
- store = evaluator.store || create(:store)
83
- create_list(:product, evaluator.products_count, brand: brand)
84
- end
85
- end
86
- end
87
- end
88
- ```
89
-
90
- ### Factory Usage Examples
91
-
92
- ```ruby
93
- # Basic factory
94
- brand = create(:brand)
95
-
96
- # With traits
97
- brand = create(:brand, :with_description, :with_logo)
98
-
99
- # With custom attributes
100
- brand = create(:brand, name: 'Nike')
101
-
102
- # Build without persisting (faster for unit tests)
103
- brand = build(:brand)
104
-
105
- # Create multiple records
106
- brands = create_list(:brand, 5)
107
-
108
- # With associated products
109
- brand = create(:brand, :with_products)
110
- brand = create(:brand, :with_products, products_count: 5)
111
- ```
112
-
113
- ## Writing Model Tests
114
-
115
- Model tests verify your business logic, validations, associations, and scopes.
116
-
117
- ```ruby spec/models/spree/brand_spec.rb
118
- require 'rails_helper'
119
-
120
- RSpec.describe Spree::Brand, type: :model do
121
- describe 'associations' do
122
- it 'has many products' do
123
- association = described_class.reflect_on_association(:products)
124
- expect(association.macro).to eq(:has_many)
125
- expect(association.class_name).to eq('Spree::Product')
126
- end
127
- end
128
-
129
- describe 'validations' do
130
- it 'validates presence of name' do
131
- brand = build(:brand, name: nil)
132
- expect(brand).not_to be_valid
133
- expect(brand.errors[:name]).to include("can't be blank")
134
- end
135
-
136
- describe 'slug uniqueness' do
137
- let!(:existing_brand) { create(:brand, slug: 'nike') }
138
-
139
- it 'validates uniqueness of slug' do
140
- brand = build(:brand, slug: 'nike')
141
- expect(brand).not_to be_valid
142
- expect(brand.errors[:slug]).to include('has already been taken')
143
- end
144
- end
145
- end
146
-
147
- describe 'FriendlyId' do
148
- it 'generates slug from name' do
149
- brand = create(:brand, name: 'Nike Sportswear', slug: nil)
150
- expect(brand.slug).to eq('nike-sportswear')
151
- end
152
-
153
- it 'handles duplicate names by appending UUID' do
154
- create(:brand, name: 'Nike', slug: 'nike')
155
- brand = create(:brand, name: 'Nike', slug: nil)
156
- expect(brand.slug).to match(/nike-[a-f0-9-]+/)
157
- end
158
- end
159
-
160
- describe '#image' do
161
- let(:brand) { create(:brand, :with_logo) }
162
-
163
- it 'returns logo as image for Open Graph' do
164
- expect(brand.image).to eq(brand.logo)
165
- end
166
- end
167
- end
168
- ```
169
-
170
- ### Testing Decorators
171
-
172
- When you extend core Spree models with decorators (see [Extending Core Models](extending-models.md)), test the added functionality:
173
-
174
- ```ruby spec/models/spree/product_decorator_spec.rb
175
- require 'rails_helper'
176
-
177
- RSpec.describe 'Spree::Product brand association' do
178
- let(:store) { @default_store }
179
- let(:brand) { create(:brand) }
180
- let(:product) { create(:product) }
181
-
182
- describe 'brand association' do
183
- it 'can be assigned a brand' do
184
- product.brand = brand
185
- product.save!
186
-
187
- expect(product.reload.brand).to eq(brand)
188
- end
189
-
190
- it 'is optional' do
191
- product.brand = nil
192
- expect(product).to be_valid
193
- end
194
- end
195
-
196
- describe 'brand.products' do
197
- let!(:product1) { create(:product, brand: brand) }
198
- let!(:product2) { create(:product, brand: brand) }
199
- let!(:other_product) { create(:product) }
200
-
201
- it 'returns products for the brand' do
202
- expect(brand.products).to contain_exactly(product1, product2)
203
- end
204
-
205
- it 'nullifies brand_id when brand is destroyed' do
206
- brand.destroy
207
- expect(product1.reload.brand_id).to be_nil
208
- end
209
- end
210
- end
211
- ```
212
-
213
- ## Writing Controller Tests
214
-
215
- Controller tests verify that your endpoints respond correctly and perform the expected actions.
216
-
217
- ### Store API Controller Tests
218
-
219
- Test the API endpoints we created in the [API tutorial](api.md). Store API tests use the `'API v3 Store'` shared context which sets up a store, publishable API key, and JWT tokens — it ships in the `spree_api` gem, so require `spree/api/testing_support/v3/base` at the top of the spec.
220
-
221
- ```ruby spec/controllers/spree/api/v3/store/brands_controller_spec.rb
222
- require 'rails_helper'
223
- require 'spree/api/testing_support/v3/base'
224
-
225
- RSpec.describe Spree::Api::V3::Store::BrandsController, type: :controller do
226
- render_views
227
-
228
- include_context 'API v3 Store'
229
-
230
- let!(:brand1) { create(:brand, name: 'Nike') }
231
- let!(:brand2) { create(:brand, name: 'Adidas') }
232
-
233
- before do
234
- request.headers['X-Spree-Api-Key'] = api_key.token
235
- end
236
-
237
- describe 'GET #index' do
238
- it 'returns a list of brands' do
239
- get :index
240
-
241
- expect(response).to have_http_status(:ok)
242
- expect(json_response['data'].size).to eq(2)
243
- end
244
-
245
- it 'returns brand attributes' do
246
- get :index
247
-
248
- brand_data = json_response['data'].first
249
- expect(brand_data).to include('id', 'name', 'slug')
250
- end
251
-
252
- it 'returns prefixed IDs' do
253
- get :index
254
-
255
- ids = json_response['data'].map { |b| b['id'] }
256
- ids.each { |id| expect(id).to start_with('brand_') }
257
- end
258
-
259
- it 'returns pagination metadata' do
260
- get :index, params: { page: 1, limit: 1 }
261
-
262
- expect(json_response['data'].size).to eq(1)
263
- expect(json_response['meta']).to include(
264
- 'page' => 1,
265
- 'limit' => 1,
266
- 'count' => 2,
267
- 'pages' => 2
268
- )
269
- end
270
-
271
- it 'filters by name' do
272
- get :index, params: { q: { name_cont: 'nik' } }
273
-
274
- expect(json_response['data'].size).to eq(1)
275
- expect(json_response['data'].first['name']).to eq('Nike')
276
- end
277
-
278
- it 'sorts by name' do
279
- get :index, params: { sort: 'name' }
280
-
281
- names = json_response['data'].map { |b| b['name'] }
282
- expect(names).to eq(%w[Adidas Nike])
283
- end
284
- end
285
-
286
- describe 'GET #show' do
287
- it 'returns a brand by prefixed ID' do
288
- get :show, params: { id: brand1.prefixed_id }
289
-
290
- expect(response).to have_http_status(:ok)
291
- expect(json_response['id']).to eq(brand1.prefixed_id)
292
- expect(json_response['name']).to eq('Nike')
293
- end
294
-
295
- it 'returns a brand by slug' do
296
- get :show, params: { id: brand1.slug }
297
-
298
- expect(response).to have_http_status(:ok)
299
- expect(json_response['name']).to eq('Nike')
300
- end
301
-
302
- it 'returns 404 for non-existent brand' do
303
- get :show, params: { id: 'brand_nonexistent' }
304
-
305
- expect(response).to have_http_status(:not_found)
306
- end
307
- end
308
-
309
- describe 'GET #show with logo' do
310
- let!(:brand_with_logo) { create(:brand, :with_logo) }
311
-
312
- it 'includes logo_url when logo is attached' do
313
- get :show, params: { id: brand_with_logo.prefixed_id }
314
-
315
- expect(json_response['logo_url']).to be_present
316
- end
317
- end
318
- end
319
- ```
320
-
321
- ### Testing the Product Brand Association
322
-
323
- Test that the custom Product serializer includes brand data:
324
-
325
- ```ruby spec/controllers/spree/api/v3/store/products_brand_spec.rb
326
- require 'rails_helper'
327
- require 'spree/api/testing_support/v3/base'
328
-
329
- RSpec.describe Spree::Api::V3::Store::ProductsController, type: :controller do
330
- render_views
331
-
332
- include_context 'API v3 Store'
333
-
334
- let(:brand) { create(:brand, name: 'Nike') }
335
- let!(:product) { create(:product, brand: brand, status: 'active') }
336
-
337
- before do
338
- request.headers['X-Spree-Api-Key'] = api_key.token
339
- end
340
-
341
- describe 'GET #show' do
342
- it 'includes brand_id' do
343
- get :show, params: { id: product.prefixed_id }
344
-
345
- expect(json_response['brand_id']).to eq(brand.prefixed_id)
346
- end
347
-
348
- it 'does not include brand object without expand' do
349
- get :show, params: { id: product.prefixed_id }
350
-
351
- expect(json_response).not_to have_key('brand')
352
- end
353
-
354
- it 'includes brand object with expand=brand' do
355
- get :show, params: { id: product.prefixed_id, expand: 'brand' }
356
-
357
- expect(json_response['brand']).to be_present
358
- expect(json_response['brand']['id']).to eq(brand.prefixed_id)
359
- expect(json_response['brand']['name']).to eq('Nike')
360
- end
361
- end
362
-
363
- describe 'GET #index' do
364
- it 'filters products by brand_id' do
365
- other_product = create(:product, status: 'active')
366
-
367
- get :index, params: { q: { brand_id_eq: brand.id } }
368
-
369
- ids = json_response['data'].map { |p| p['id'] }
370
- expect(ids).to include(product.prefixed_id)
371
- expect(ids).not_to include(other_product.prefixed_id)
372
- end
373
- end
374
- end
375
- ```
376
-
377
- ### Admin Controller Tests
378
-
379
- ```ruby spec/controllers/spree/admin/brands_controller_spec.rb
380
- require 'rails_helper'
381
-
382
- RSpec.describe Spree::Admin::BrandsController, type: :controller do
383
- stub_authorization!
384
- render_views
385
-
386
- describe 'GET #index' do
387
- let!(:brand1) { create(:brand, name: 'Adidas') }
388
- let!(:brand2) { create(:brand, name: 'Nike') }
389
-
390
- it 'returns a successful response' do
391
- get :index
392
- expect(response).to be_successful
393
- end
394
-
395
- it 'displays all brands' do
396
- get :index
397
- expect(response.body).to include('Adidas')
398
- expect(response.body).to include('Nike')
399
- end
400
- end
401
-
402
- describe 'GET #new' do
403
- it 'returns a successful response' do
404
- get :new
405
- expect(response).to be_successful
406
- end
407
-
408
- it 'displays the new brand form' do
409
- get :new
410
- expect(response.body).to include('brand[name]')
411
- end
412
- end
413
-
414
- describe 'POST #create' do
415
- context 'with valid params' do
416
- let(:valid_params) do
417
- { brand: { name: 'New Brand' } }
418
- end
419
-
420
- it 'creates a new brand' do
421
- expect {
422
- post :create, params: valid_params
423
- }.to change(Spree::Brand, :count).by(1)
424
- end
425
-
426
- it 'redirects to the edit page' do
427
- post :create, params: valid_params
428
- expect(response).to redirect_to(spree.edit_admin_brand_path(Spree::Brand.last))
429
- end
430
- end
431
-
432
- context 'with invalid params' do
433
- let(:invalid_params) do
434
- { brand: { name: '' } }
435
- end
436
-
437
- it 'does not create a new brand' do
438
- expect {
439
- post :create, params: invalid_params
440
- }.not_to change(Spree::Brand, :count)
441
- end
442
-
443
- it 'returns unprocessable entity status' do
444
- post :create, params: invalid_params
445
- expect(response).to have_http_status(:unprocessable_content)
446
- end
447
- end
448
- end
449
-
450
- describe 'GET #edit' do
451
- let(:brand) { create(:brand) }
452
-
453
- it 'returns a successful response' do
454
- get :edit, params: { id: brand.id }
455
- expect(response).to be_successful
456
- end
457
- end
458
-
459
- describe 'PUT #update' do
460
- let(:brand) { create(:brand, name: 'Old Name') }
461
-
462
- context 'with valid params' do
463
- it 'updates the brand' do
464
- put :update, params: { id: brand.id, brand: { name: 'New Name' } }
465
- expect(brand.reload.name).to eq('New Name')
466
- end
467
-
468
- it 'redirects to the edit page' do
469
- put :update, params: { id: brand.id, brand: { name: 'New Name' } }
470
- expect(response).to redirect_to(spree.edit_admin_brand_path(brand))
471
- end
472
- end
473
- end
474
-
475
- describe 'DELETE #destroy' do
476
- let!(:brand) { create(:brand) }
477
-
478
- it 'removes the brand from the database' do
479
- expect {
480
- delete :destroy, params: { id: brand.id }, format: :html
481
- }.to change(Spree::Brand, :count).by(-1)
482
- end
483
- end
484
- end
485
- ```
486
-
487
- ## Writing Feature Tests
488
-
489
- Feature tests (also called system tests) simulate real user interactions using Capybara.
490
-
491
- ### Admin Feature Tests
492
-
493
- ```ruby spec/features/spree/admin/brands_spec.rb
494
- require 'rails_helper'
495
-
496
- RSpec.feature 'Admin Brands', type: :feature do
497
- stub_authorization!
498
-
499
- describe 'listing brands' do
500
- let!(:brand1) { create(:brand, name: 'Nike') }
501
- let!(:brand2) { create(:brand, name: 'Adidas') }
502
-
503
- it 'displays all brands' do
504
- visit spree.admin_brands_path
505
-
506
- expect(page).to have_content('Nike')
507
- expect(page).to have_content('Adidas')
508
- end
509
- end
510
-
511
- describe 'creating a brand' do
512
- it 'creates a new brand successfully' do
513
- visit spree.admin_brands_path
514
- click_on 'New Brand'
515
-
516
- fill_in 'Name', with: 'Puma'
517
- fill_in 'Slug', with: 'puma'
518
-
519
- click_on 'Create'
520
- wait_for_turbo
521
-
522
- expect(page).to have_content('Brand "Puma" has been successfully created!')
523
- expect(Spree::Brand.find_by(name: 'Puma')).to be_present
524
- end
525
-
526
- it 'shows validation errors' do
527
- visit spree.new_admin_brand_path
528
-
529
- click_on 'Create'
530
- wait_for_turbo
531
-
532
- expect(page).to have_content("can't be blank")
533
- end
534
- end
535
-
536
- describe 'editing a brand' do
537
- let!(:brand) { create(:brand, name: 'Nike') }
538
-
539
- it 'updates the brand successfully' do
540
- visit spree.admin_brands_path
541
- click_on 'Edit'
542
-
543
- fill_in 'Name', with: 'Nike Inc.'
544
- within('#page-header') { click_button 'Update' }
545
-
546
- wait_for_turbo
547
- expect(page).to have_content('Brand "Nike Inc." has been successfully updated!')
548
- expect(brand.reload.name).to eq('Nike Inc.')
549
- end
550
- end
551
-
552
- describe 'deleting a brand' do
553
- let!(:brand) { create(:brand, name: 'Nike') }
554
-
555
- it 'removes the brand' do
556
- expect {
557
- page.driver.submit :delete, spree.admin_brand_path(brand), {}
558
- }.to change(Spree::Brand, :count).by(-1)
559
- end
560
- end
561
- end
562
- ```
563
-
564
- ## Test Helpers
565
-
566
- ### Authorization Helper
567
-
568
- Use `stub_authorization!` to bypass authorization checks in admin tests:
569
-
570
- ```ruby
571
- RSpec.describe Spree::Admin::BrandsController, type: :controller do
572
- stub_authorization! # Grants full admin access
573
-
574
- # ... your tests
575
- end
576
- ```
577
-
578
- ### wait\_for\_turbo Helper
579
-
580
- When testing with Turbo/Hotwire, use `wait_for_turbo` to ensure the page has fully loaded:
581
-
582
- ```ruby
583
- click_on 'Create'
584
- wait_for_turbo
585
- expect(page).to have_content('Success!')
586
- ```
587
-
588
- ## Running Tests
589
-
590
- ### Prepare the test database
591
-
592
- Tests run against a dedicated `spree_test` database, so your development data is never touched. Create it and load the schema once:
593
-
594
-
595
- ```bash Spree CLI (Docker)
596
- spree rails db:test:prepare
597
- ```
598
-
599
- ```bash Without Spree CLI
600
- bin/rails db:test:prepare
601
- ```
602
-
603
-
604
- You rarely need to run this again — `rails_helper.rb` re-syncs the test schema automatically when you add migrations. It's the command to reach for when the test database gets into a broken or out-of-sync state.
605
-
606
- ### Run the suite
607
-
608
- `spree rspec` runs `bundle exec rspec` inside the web container with `RAILS_ENV=test`. Everything after `rspec` is passed through, so file paths, line numbers, and flags work as usual:
609
-
610
-
611
- ```bash Spree CLI (Docker)
612
- spree rspec # all tests
613
- spree rspec spec/models/spree/brand_spec.rb # specific file
614
- spree rspec spec/models/spree/brand_spec.rb:15 # specific test
615
- spree rspec --format documentation # documentation format
616
- spree rspec spec/features/ # only feature tests
617
- ```
618
-
619
- ```bash Without Spree CLI
620
- bundle exec rspec # all tests
621
- bundle exec rspec spec/models/spree/brand_spec.rb # specific file
622
- bundle exec rspec spec/models/spree/brand_spec.rb:15 # specific test
623
- bundle exec rspec --format documentation # documentation format
624
- bundle exec rspec spec/features/ # only feature tests
625
- ```
626
-
627
-
628
- <details>
629
- <summary>Troubleshooting: DatabaseCleaner refuses a remote database URL, or tests hit the development database</summary>
630
-
631
- Projects scaffolded before July 2026 set `DATABASE_URL` in `docker-compose.dev.yml`. A URL overrides `config/database.yml` for **every** Rails environment, so in-container tests pointed at the development database and tripped DatabaseCleaner's remote-URL safeguard. Two small changes bring an older project up to date:
632
-
633
- 1. In `docker-compose.dev.yml`, replace the `DATABASE_URL` entry under `environment:` with host/username parts, so each Rails environment resolves its own database from `database.yml`:
634
-
635
- ```yaml
636
- DATABASE_HOST: postgres
637
- DATABASE_USERNAME: postgres
638
- ```
639
-
640
- 2. In `spec/rails_helper.rb`, force the test environment — the dev container bakes in `RAILS_ENV=development`:
641
-
642
- ```ruby
643
- ENV['RAILS_ENV'] = 'test'
644
- ```
645
-
646
- Then restart the stack (`Ctrl+C` and `spree dev`) so the container picks up the new environment.
647
-
648
- </details>
649
-
650
-
651
- ## Best Practices
652
-
653
-
654
- - **Use build over create** — Use `build` instead of `create` when you don't need a persisted record. It's faster because it skips database operations.
655
-
656
- - **Use let over instance variables** — Prefer `let` and `let!` over instance variables. They're lazily evaluated and scoped to each example.
657
-
658
- - **One assertion per test** — Keep tests focused on a single behavior. Use `aggregate_failures` if you need multiple assertions.
659
-
660
- - **Test behavior, not implementation** — Focus on what the code does, not how it does it. This makes tests more resilient to refactoring.
661
-
662
-
663
- ### Example: aggregate\_failures
664
-
665
- ```ruby
666
- it 'creates brand with all attributes', :aggregate_failures do
667
- brand = create(:brand, name: 'Nike')
668
-
669
- expect(brand.name).to eq('Nike')
670
- expect(brand.slug).to eq('nike')
671
- end
672
- ```
673
-
674
- ## Complete Test Suite Structure
675
-
676
- After completing this tutorial, your test structure should look like:
677
-
678
- ```
679
- spec/
680
- ├── factories/
681
- │ └── spree/
682
- │ └── brand_factory.rb
683
- ├── models/
684
- │ └── spree/
685
- │ ├── brand_spec.rb
686
- │ └── product_decorator_spec.rb
687
- ├── controllers/
688
- │ └── spree/
689
- │ ├── admin/
690
- │ │ └── brands_controller_spec.rb
691
- │ └── api/
692
- │ └── v3/
693
- │ └── store/
694
- │ ├── brands_controller_spec.rb
695
- │ └── products_brand_spec.rb
696
- ├── features/
697
- │ └── spree/
698
- │ └── admin/
699
- │ └── brands_spec.rb
700
- ├── support/
701
- │ └── (various support files)
702
- ├── rails_helper.rb
703
- └── spec_helper.rb
704
- ```
705
-
706
- ## Related Documentation
707
-
708
- * [Model Tutorial](model.md) - Creating the Brand model
709
- * [Admin Tutorial](admin.md) - Building the admin interface
710
- * [Extending Core Models](extending-models.md) - Connecting Brands to Products
711
- * [API Tutorial](api.md) - Creating Brand API endpoints
712
- * [Spree CLI](../cli/quickstart.md) - `spree rspec` and the other dev workflow commands
713
- * [RSpec Documentation](https://rspec.info/documentation/) - Official RSpec docs
714
- * [Factory Bot Documentation](https://github.com/thoughtbot/factory_bot/blob/main/GETTING_STARTED.md) - Factory Bot guide
715
-
716
-
717
- ---
10
+ - Model and factory setup
11
+ - Request specs for the Admin and Store API endpoints
12
+ - Dashboard plugin tests with Vitest
13
+ - An end-to-end Playwright spec: create an FAQ in the dashboard, see it on the storefront