@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.
- package/dist/api-reference/store-api/metadata.md +6 -6
- package/dist/developer/core-concepts/architecture.md +15 -19
- package/dist/developer/core-concepts/carts.md +71 -1
- package/dist/developer/core-concepts/metafields.md +0 -2
- package/dist/developer/create-spree-app/quickstart.md +22 -19
- package/dist/developer/customization/api.md +2 -2
- package/dist/developer/customization/checkout.md +117 -0
- package/dist/developer/customization/configuration.md +76 -175
- package/dist/developer/customization/decorators.md +120 -130
- package/dist/developer/customization/metadata.md +9 -9
- package/dist/developer/customization/model-preferences.md +34 -68
- package/dist/developer/customization/permissions.md +13 -28
- package/dist/developer/customization/validations.md +16 -43
- package/dist/developer/dashboard/concepts.md +4 -2
- package/dist/developer/dashboard/customization/backend.md +5 -2
- package/dist/developer/dashboard/customization/navigation.md +1 -1
- package/dist/developer/dashboard/customization/quickstart.md +1 -1
- package/dist/developer/dashboard/overview.md +2 -2
- package/dist/developer/dashboard/plugins/distributing.md +1 -1
- package/dist/developer/dashboard/public-api.md +3 -2
- package/dist/developer/dashboard/recipes/attribute-end-to-end.md +3 -3
- package/dist/developer/dashboard/recipes/custom-form-field.md +1 -1
- package/dist/developer/dashboard/recipes/page-action-button.md +2 -2
- package/dist/developer/dashboard/recipes/sidebar-widget.md +2 -2
- package/dist/developer/dashboard/slots-catalog.md +60 -0
- package/dist/developer/getting-started/quickstart.md +25 -24
- package/dist/developer/how-to/custom-promotion.md +1 -1
- package/dist/developer/how-to/custom-search-provider.md +2 -2
- package/dist/developer/how-to/custom-stock-splitter.md +2 -2
- package/dist/developer/sdk/admin/extending.md +1 -1
- package/dist/developer/sdk/admin/quickstart.md +1 -1
- package/dist/developer/sdk/admin/resources.md +1 -1
- package/dist/developer/sdk/configuration.md +1 -1
- package/dist/developer/sdk/extending.md +1 -1
- package/dist/developer/tutorial/testing.md +45 -91
- package/package.json +1 -1
|
@@ -1,129 +1,83 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Testing
|
|
3
|
-
description: Test the Brands feature
|
|
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
|
-
|
|
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
|
-
|
|
8
|
+
## The backend
|
|
9
9
|
|
|
10
|
-
|
|
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
|
-
|
|
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
|
|
27
|
-
|
|
28
|
-
## Step 2: Model specs
|
|
24
|
+
## The dashboard
|
|
29
25
|
|
|
30
|
-
|
|
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
|
-
```
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
52
|
+
for (const key of brandInvalidations()) {
|
|
53
|
+
client.invalidateQueries({ queryKey: withStoreScope(key, STORE) })
|
|
54
|
+
}
|
|
71
55
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
56
|
+
const stale = client.getQueryCache().getAll().filter((q) => q.state.isInvalidated)
|
|
57
|
+
expect(stale).toHaveLength(1)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
75
60
|
```
|
|
76
61
|
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
66
|
+
### What is not worth a unit test
|
|
82
67
|
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
104
|
+
Run what you changed, not everything:
|
|
151
105
|
|
|
152
106
|
```bash
|
|
153
|
-
spree rspec spec/models/spree/brand_spec.rb
|
|
154
|
-
|
|
107
|
+
spree rspec spec/models/spree/brand_spec.rb # the backend
|
|
108
|
+
cd apps/dashboard && pnpm test # the dashboard
|
|
155
109
|
```
|
|
156
110
|
|
|
157
|
-
|
|
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
|
|
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
|