@spree/docs 0.1.239 → 0.1.241

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 (36) hide show
  1. package/dist/api-reference/store-api/metadata.md +6 -6
  2. package/dist/developer/core-concepts/architecture.md +15 -19
  3. package/dist/developer/core-concepts/carts.md +71 -1
  4. package/dist/developer/core-concepts/metafields.md +0 -2
  5. package/dist/developer/create-spree-app/quickstart.md +22 -19
  6. package/dist/developer/customization/api.md +2 -2
  7. package/dist/developer/customization/checkout.md +117 -0
  8. package/dist/developer/customization/configuration.md +76 -175
  9. package/dist/developer/customization/decorators.md +120 -130
  10. package/dist/developer/customization/metadata.md +9 -9
  11. package/dist/developer/customization/model-preferences.md +34 -68
  12. package/dist/developer/customization/permissions.md +13 -28
  13. package/dist/developer/customization/validations.md +16 -43
  14. package/dist/developer/dashboard/concepts.md +4 -2
  15. package/dist/developer/dashboard/customization/backend.md +5 -2
  16. package/dist/developer/dashboard/customization/navigation.md +1 -1
  17. package/dist/developer/dashboard/customization/quickstart.md +1 -1
  18. package/dist/developer/dashboard/overview.md +2 -2
  19. package/dist/developer/dashboard/plugins/distributing.md +1 -1
  20. package/dist/developer/dashboard/public-api.md +3 -2
  21. package/dist/developer/dashboard/recipes/attribute-end-to-end.md +3 -3
  22. package/dist/developer/dashboard/recipes/custom-form-field.md +1 -1
  23. package/dist/developer/dashboard/recipes/page-action-button.md +2 -2
  24. package/dist/developer/dashboard/recipes/sidebar-widget.md +2 -2
  25. package/dist/developer/dashboard/slots-catalog.md +60 -0
  26. package/dist/developer/getting-started/quickstart.md +25 -24
  27. package/dist/developer/how-to/custom-promotion.md +1 -1
  28. package/dist/developer/how-to/custom-search-provider.md +2 -2
  29. package/dist/developer/how-to/custom-stock-splitter.md +2 -2
  30. package/dist/developer/sdk/admin/extending.md +1 -1
  31. package/dist/developer/sdk/admin/quickstart.md +1 -1
  32. package/dist/developer/sdk/admin/resources.md +1 -1
  33. package/dist/developer/sdk/configuration.md +1 -1
  34. package/dist/developer/sdk/extending.md +1 -1
  35. package/dist/developer/tutorial/testing.md +45 -91
  36. package/package.json +1 -1
@@ -1,129 +1,83 @@
1
1
  ---
2
2
  title: Testing
3
- description: Test the Brands feature at every layer — model and API specs with RSpec, dashboard tests with Vitest, and one end-to-end Playwright pass.
3
+ description: Test the Brands feature — running the generated backend specs, unit-testing the dashboard logic you wrote, and one Playwright pass through the browser.
4
4
  ---
5
5
 
6
- Each layer gets the test that suits it: fast backend specs for the model and both APIs, component tests for the dashboard, and one end-to-end pass proving the whole path works.
6
+ Most of the backend is generated, so most of the testing worth doing is on the code you wrote yourself — which is mostly in the dashboard. This chapter covers the backend briefly, spends longer on the dashboard, and ends with one browser pass proving the layers are actually connected.
7
7
 
8
- Your project arrives with testing already configured — RSpec, FactoryBot, and Spree's own test helpers, including every factory core ships. You can write a spec against `create(:product)` without setting anything up.
8
+ ## The backend
9
9
 
10
- ## Step 1: Run what the generator wrote
11
-
12
- The generator in step 1 already wrote a factory and a controller spec per API side, so this step is mostly filling in rather than starting over:
10
+ Your project arrives with a fully configured testing environment for backend testing. The generator wrote all the files needed for running API tests:
13
11
 
14
12
  ```text
15
- server/spec/factories/spree/brand_factory.rb
16
13
  server/spec/controllers/spree/api/v3/store/brands_controller_spec.rb
17
14
  server/spec/controllers/spree/api/v3/admin/brands_controller_spec.rb
18
15
  ```
19
16
 
20
- Run them:
17
+ You can now run them, a directory or a single file at a time:
21
18
 
22
19
  ```bash
23
20
  spree rspec spec/controllers/spree/api/v3/
21
+ spree rspec spec/models/spree/brand_spec.rb
24
22
  ```
25
23
 
26
- The generated factory is the starting point for every other spec — always build records through factories rather than creating them directly, so a change to the model does not mean editing fifty specs.
27
-
28
- ## Step 2: Model specs
24
+ ## The dashboard
29
25
 
30
- Test what you wrote, not what the framework already guarantees. A presence validation on a required column needs no spec; the association behavior you chose does:
26
+ This is where most of your own code lives, so it is where most of your tests belong. Vitest and Playwright come configured, so there is nothing to set up:
31
27
 
32
- ```ruby server/spec/models/spree/brand_spec.rb
33
- RSpec.describe Spree::Brand, type: :model do
34
- it 'nullifies the reference instead of deleting products' do
35
- brand = create(:brand)
36
- product = create(:product, brand: brand)
37
-
38
- brand.destroy
39
-
40
- expect(product.reload.brand_id).to be_nil
41
- expect(product).to be_persisted
42
- end
43
- end
28
+ ```bash
29
+ cd apps/dashboard && pnpm test
44
30
  ```
45
31
 
46
- Prefer `build` over `create` when a record does not need to hit the database — it is markedly faster across a suite.
47
-
48
- ## Step 3: Controller specs
49
-
50
- Controller specs are where edge cases belong: authorization, scoping, filtering, error shapes. Always `render_views`, so a broken serializer fails the spec rather than passing silently:
32
+ ### What is worth a unit test
51
33
 
52
- ```ruby server/spec/controllers/spree/api/v3/store/brands_controller_spec.rb
53
- RSpec.describe Spree::Api::V3::Store::BrandsController, type: :controller do
54
- render_views
34
+ Not components. Rendering a card to assert it shows a brand name tests React, not your feature, and breaks every time the markup moves. Test the **logic between the UI and the API** — the parts with decisions in them.
55
35
 
56
- it 'exposes public fields only' do
57
- create(:brand, name: 'Wilson')
36
+ Query keys are the clearest example. Every key must be scoped to the current store, or one store's rows leak into another after a switch. That is a rule a test can hold you to:
58
37
 
59
- get :index, params: { format: :json }
38
+ ```ts apps/dashboard/src/brands/client.test.ts
39
+ import { withStoreScope } from '@spree/dashboard'
40
+ import { QueryClient } from '@tanstack/react-query'
41
+ import { describe, expect, it } from 'vitest'
42
+ import { brandInvalidations } from './client'
60
43
 
61
- record = json_response['data'].first
62
- expect(record['id']).to start_with('brand_')
63
- expect(record).not_to have_key('created_at')
64
- end
44
+ const STORE = 'store_abc123'
65
45
 
66
- it 'filters by name' do
67
- create(:brand, name: 'Wilson')
68
- create(:brand, name: 'Penn')
46
+ describe('brand cache invalidation', () => {
47
+ it('refreshes the brand list without touching another store', () => {
48
+ const client = new QueryClient()
49
+ client.setQueryData(withStoreScope(['brands'], STORE), [])
50
+ client.setQueryData(withStoreScope(['brands'], 'store_other'), [])
69
51
 
70
- get :index, params: { q: { name_cont: 'wil' }, format: :json }
52
+ for (const key of brandInvalidations()) {
53
+ client.invalidateQueries({ queryKey: withStoreScope(key, STORE) })
54
+ }
71
55
 
72
- expect(json_response['data'].map { |b| b['name'] }).to eq(['Wilson'])
73
- end
74
- end
56
+ const stale = client.getQueryCache().getAll().filter((q) => q.state.isInvalidated)
57
+ expect(stale).toHaveLength(1)
58
+ })
59
+ })
75
60
  ```
76
61
 
77
- That second example is worth keeping: it proves the Ransack allowlist from step 1 actually permits `name_cont`. Remove `name` from `whitelisted_ransackable_attributes` and this spec fails, which is exactly what you want.
62
+ Driving a real `QueryClient` means the assertion goes through TanStack's own prefix matching rather than a hand-rolled key comparison — so it stays true if the matching rules change.
78
63
 
79
- > **NOTE:** Specs run against a store the suite sets up for you, available as `@default_store`. Use it rather than creating another store marked as the default — two of them in one run produce failures that surface far from their cause.
64
+ The same applies to anything else with a decision in it: a function mapping form values to an API payload, a permission predicate deciding whether a nav entry shows, a filter translating table state into Ransack params. Each is a pure function, each is a two-line test.
80
65
 
81
- ## Step 4: Dashboard tests
66
+ ### What is not worth a unit test
82
67
 
83
- The dashboard is a TypeScript app, so its code tests with Vitest:
68
+ - **Components.** Asserting a card renders text is testing React.
69
+ - **`BrandsClient.list` itself.** It is a one-line wrapper around `adminClient.request`. A test would mock the thing it wraps and assert the mock was called — proving nothing about whether the endpoint exists.
70
+ - **Anything the compiler already catches.** A wrong prop type is a build failure, not a test case.
84
71
 
85
- ```bash
86
- cd apps/dashboard && pnpm test
87
- ```
88
-
89
- Test the client wrappers and any logic in your pages. Mock the HTTP layer rather than the client module, so the test exercises the real request path:
90
-
91
- ```ts apps/dashboard/src/brands/client.test.ts
92
- import { http, HttpResponse } from 'msw'
93
- import { setupServer } from 'msw/node'
94
- import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'
95
- import { BrandsClient } from './client'
96
-
97
- const server = setupServer(
98
- http.get('*/api/v3/admin/brands', () =>
99
- HttpResponse.json({ data: [{ id: 'brand_k5nR8xLq', name: 'Wilson' }] }),
100
- ),
101
- )
102
-
103
- beforeAll(() => server.listen())
104
- afterEach(() => server.resetHandlers())
105
- afterAll(() => server.close())
106
-
107
- describe('BrandsClient', () => {
108
- it('lists brands from the admin endpoint', async () => {
109
- const result = await BrandsClient.list({ per_page: 10 })
110
-
111
- expect(result.data).toHaveLength(1)
112
- expect(result.data[0].name).toBe('Wilson')
113
- })
114
- })
115
- ```
72
+ That second one is worth dwelling on: the question "does `/brands` actually return brands" cannot be answered by a unit test at all. It needs the real API, which is the next section.
116
73
 
117
- Mocking the HTTP layer rather than the client module means the test exercises the real request path — the URL, the params and the response shape — so a wrong endpoint fails here instead of in the browser.
118
-
119
- ## Step 5: One end-to-end pass
74
+ ## End to end, through the browser
120
75
 
121
76
  Everything above tests a layer in isolation. One Playwright spec proves the layers are actually connected — a brand created through the Admin API reaches the dashboard screen you built:
122
77
 
123
78
  ```ts apps/dashboard/e2e/brands.spec.ts
124
79
  import { expect, test } from '@playwright/test'
125
- import { createBrand } from './helpers/api'
126
- import { login } from './helpers/login'
80
+ import { createBrand, login } from './helpers'
127
81
 
128
82
  test('a brand appears on the brands screen', async ({ page }) => {
129
83
  const name = `Wilson ${Date.now()}`
@@ -147,21 +101,21 @@ Two conventions matter here:
147
101
 
148
102
  ## What to run before you push
149
103
 
150
- Run the specs for what you changed rather than the whole suite:
104
+ Run what you changed, not everything:
151
105
 
152
106
  ```bash
153
- spree rspec spec/models/spree/brand_spec.rb
154
- spree rspec spec/controllers/spree/api/v3/
107
+ spree rspec spec/models/spree/brand_spec.rb # the backend
108
+ cd apps/dashboard && pnpm test # the dashboard
155
109
  ```
156
110
 
157
- Pass a file, a directory, or a single example with a line number (`spec/models/spree/brand_spec.rb:12`). Keep the full run for continuous integration, where waiting costs you nothing.
111
+ Keep the full suite and the end-to-end pass for continuous integration, where waiting costs you nothing.
158
112
 
159
113
  ## You're done
160
114
 
161
115
  You have built one feature through every layer of Spree:
162
116
 
163
117
  - A model with a Store API and an Admin API, generated in one command
164
- - A dashboard plugin giving staff a place to manage it
118
+ - A dashboard screen giving staff a place to manage it
165
119
  - Storefront pages rendering it through typed SDK calls
166
120
  - Lifecycle events other systems react to
167
121
  - Tests at each layer, plus one pass across all of them
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spree/docs",
3
- "version": "0.1.239",
3
+ "version": "0.1.241",
4
4
  "description": "Spree Commerce developer documentation for AI agents and local reference",
5
5
  "type": "module",
6
6
  "license": "CC-BY-4.0",