@mohasinac/appkit 4.42.1 → 4.42.3
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/_internal/server/features/offers/adapters.d.ts +29 -7
- package/dist/_internal/server/features/offers/adapters.js +14 -3
- package/dist/_internal/server/features/orders/adapters.js +54 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.js +1 -0
- package/dist/features/account/components/UserOrdersView.js +30 -2
- package/dist/features/account/hooks/useAddresses.d.ts +21 -6
- package/dist/features/account/hooks/useAddresses.js +1 -1
- package/dist/features/addresses/hooks/usePostalLookup.js +84 -12
- package/dist/features/admin/components/AdminCategoriesView.js +37 -2
- package/dist/features/admin/components/AdminOffersView.js +2 -2
- package/dist/features/admin/components/AdminSupportTicketsView.js +27 -1
- package/dist/features/admin/constants/filter-tabs.d.ts +25 -0
- package/dist/features/admin/constants/filter-tabs.js +30 -0
- package/dist/features/admin/hooks/useAdminListingData.d.ts +35 -0
- package/dist/features/admin/hooks/useAdminListingData.js +45 -9
- package/dist/features/cart/schemas/index.d.ts +15 -0
- package/dist/features/cart/schemas/index.js +2 -0
- package/dist/features/cart/types/index.d.ts +22 -0
- package/dist/features/categories/components/CategoriesIndexListing.js +19 -1
- package/dist/features/categories/components/CategoriesIndexPageView.js +14 -1
- package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +8 -0
- package/dist/features/classified/components/ClassifiedDetailPageView.js +1 -0
- package/dist/features/events/actions/event-actions.d.ts +13 -0
- package/dist/features/events/actions/event-actions.js +75 -1
- package/dist/features/events/repository/event-entry.repository.d.ts +17 -0
- package/dist/features/events/repository/event-entry.repository.js +36 -0
- package/dist/features/homepage/components/BrandsSection.js +1 -1
- package/dist/features/homepage/components/CustomerReviewsSection.js +1 -1
- package/dist/features/homepage/components/FeaturedBundlesSection.js +1 -1
- package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
- package/dist/features/media/upload/MediaUploadField.js +21 -3
- package/dist/features/orders/components/OrdersList.js +2 -1
- package/dist/features/orders/index.d.ts +1 -0
- package/dist/features/orders/index.js +1 -0
- package/dist/features/orders/types/index.d.ts +2 -0
- package/dist/features/orders/utils/order-ref.d.ts +45 -0
- package/dist/features/orders/utils/order-ref.js +58 -0
- package/dist/features/products/components/MakeOfferButton.d.ts +18 -1
- package/dist/features/products/components/MakeOfferButton.js +23 -1
- package/dist/features/products/components/ProductDetailPageView.d.ts +5 -0
- package/dist/features/products/components/ProductDetailPageView.js +3 -2
- package/dist/features/products/repository/products.repository.js +29 -2
- package/dist/features/seller/actions/offer-actions.js +32 -0
- package/dist/features/seller/components/QuickProductForm.js +15 -1
- package/dist/features/seller/components/SellerBidsView.js +3 -1
- package/dist/features/seller/components/SellerOffersView.js +2 -2
- package/dist/features/seller/components/SellerOrdersView.js +49 -6
- package/dist/features/seller/components/SellerProductsCards.js +23 -1
- package/dist/features/seller/components/SellerProductsView.js +27 -2
- package/dist/features/seller/hooks/useSellerListingData.d.ts +0 -1
- package/dist/features/seller/hooks/useSellerListingData.js +17 -19
- package/dist/features/shell/SectionForm.js +33 -2
- package/dist/features/tester/seed-data/_happy-path.d.ts +23 -0
- package/dist/features/tester/seed-data/_happy-path.js +710 -0
- package/dist/features/tester/seed-data/_money-flows.js +34 -23
- package/dist/features/tester/seed-data/authored/account-auth__profile-settings.js +22 -3
- package/dist/features/tester/seed-data/authored/addresses__address-filters.js +17 -12
- package/dist/features/tester/seed-data/authored/addresses__postal-lookup.js +7 -6
- package/dist/features/tester/seed-data/authored/addresses__postal-validation.js +10 -4
- package/dist/features/tester/seed-data/authored/addresses__state-picker.js +13 -8
- package/dist/features/tester/seed-data/authored/addresses__unban-request.js +49 -11
- package/dist/features/tester/seed-data/authored/admin__admin-detail-round-trips.js +10 -6
- package/dist/features/tester/seed-data/authored/admin__bans-and-trust.js +71 -32
- package/dist/features/tester/seed-data/authored/admin__blog-faqs.js +80 -31
- package/dist/features/tester/seed-data/authored/admin__bug-hunter-rewards.js +62 -25
- package/dist/features/tester/seed-data/authored/admin__bulk-actions.js +61 -31
- package/dist/features/tester/seed-data/authored/admin__bundles.js +84 -37
- package/dist/features/tester/seed-data/authored/admin__buyer-data-admin.js +90 -57
- package/dist/features/tester/seed-data/authored/admin__catalog-listings.js +38 -26
- package/dist/features/tester/seed-data/authored/admin__category-brand-authoring.js +47 -28
- package/dist/features/tester/seed-data/authored/admin__classifieds-digitalcodes-live.js +118 -31
- package/dist/features/tester/seed-data/authored/admin__content-deletes.js +30 -24
- package/dist/features/tester/seed-data/authored/admin__content-marketing.js +83 -46
- package/dist/features/tester/seed-data/authored/admin__coupons.js +8 -8
- package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.js +9 -9
- package/dist/features/tester/seed-data/authored/admin__firebase-function-effects.js +19 -4
- package/dist/features/tester/seed-data/authored/admin__media-watermark.js +77 -33
- package/dist/features/tester/seed-data/authored/admin__orders-fulfillment.js +5 -5
- package/dist/features/tester/seed-data/authored/admin__prize-draws-lotteries.js +29 -5
- package/dist/features/tester/seed-data/authored/admin__site-system.js +45 -23
- package/dist/features/tester/seed-data/authored/admin__uncovered-admin-pages.js +53 -28
- package/dist/features/tester/seed-data/authored/admin__users-trust.js +20 -20
- package/dist/features/tester/seed-data/authored/buying__bidding.js +19 -2
- package/dist/features/tester/seed-data/authored/buying__browsing-search.js +20 -5
- package/dist/features/tester/seed-data/authored/buying__buying-checkout.js +28 -10
- package/dist/features/tester/seed-data/authored/buying__cart.js +10 -3
- package/dist/features/tester/seed-data/authored/buying__image-tile-layout.js +0 -16
- package/dist/features/tester/seed-data/authored/buying__my-orders.js +27 -5
- package/dist/features/tester/seed-data/authored/buying__offers.js +22 -7
- package/dist/features/tester/seed-data/authored/buying__order-detail-actions.js +3 -3
- package/dist/features/tester/seed-data/authored/buying__product-detail.js +35 -4
- package/dist/features/tester/seed-data/authored/buying__return-request.js +19 -6
- package/dist/features/tester/seed-data/authored/buying__reviews-pagination.js +36 -18
- package/dist/features/tester/seed-data/authored/buying__reviews.js +16 -1
- package/dist/features/tester/seed-data/authored/buying__user-dashboard-navigation.js +2 -1
- package/dist/features/tester/seed-data/authored/buying__user-uncovered-pages.js +16 -1
- package/dist/features/tester/seed-data/authored/community-support__public-profile.js +3 -2
- package/dist/features/tester/seed-data/authored/community-support__support-tickets.js +4 -4
- package/dist/features/tester/seed-data/authored/content-discovery__blog.js +19 -2
- package/dist/features/tester/seed-data/authored/content-discovery__category-brand-relations.js +4 -4
- package/dist/features/tester/seed-data/authored/content-discovery__category-counts-and-rollup.js +5 -5
- package/dist/features/tester/seed-data/authored/content-discovery__coupons.js +41 -6
- package/dist/features/tester/seed-data/authored/content-discovery__event-detail-subroutes.js +63 -8
- package/dist/features/tester/seed-data/authored/content-discovery__event-participation.js +29 -29
- package/dist/features/tester/seed-data/authored/content-discovery__events.js +2 -2
- package/dist/features/tester/seed-data/authored/content-discovery__faq-help.js +23 -1
- package/dist/features/tester/seed-data/authored/content-discovery__notifications.js +6 -6
- package/dist/features/tester/seed-data/authored/content-discovery__search.js +1 -1
- package/dist/features/tester/seed-data/authored/content-discovery__store-reviews-aggregate.js +1 -1
- package/dist/features/tester/seed-data/authored/cta-layout__checkout-bottom-bar.js +31 -6
- package/dist/features/tester/seed-data/authored/cta-layout__dialog-footers.js +34 -10
- package/dist/features/tester/seed-data/authored/cta-layout__editor-action-bar.js +35 -8
- package/dist/features/tester/seed-data/authored/cta-layout__navbar-ctas.js +31 -9
- package/dist/features/tester/seed-data/authored/cta-layout__product-bottom-bar.js +29 -3
- package/dist/features/tester/seed-data/authored/design-ux__back-to-top-button.js +1 -1
- package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.js +10 -10
- package/dist/features/tester/seed-data/authored/design-ux__dashboard-layout.js +3 -3
- package/dist/features/tester/seed-data/authored/design-ux__footer-theme.js +2 -2
- package/dist/features/tester/seed-data/authored/design-ux__form-validation-errors.js +2 -2
- package/dist/features/tester/seed-data/authored/design-ux__general-design.js +4 -4
- package/dist/features/tester/seed-data/authored/design-ux__hand-mode-layout.js +1 -1
- package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.js +8 -8
- package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.js +2 -2
- package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.js +2 -2
- package/dist/features/tester/seed-data/authored/page-wiring__data-loss.js +39 -16
- package/dist/features/tester/seed-data/authored/page-wiring__detail-pages.js +43 -8
- package/dist/features/tester/seed-data/authored/page-wiring__drawer-pages.js +40 -14
- package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +31 -14
- package/dist/features/tester/seed-data/authored/public-pages__auth-error-pages.js +7 -6
- package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +14 -5
- package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.js +3 -3
- package/dist/features/tester/seed-data/authored/public-pages__help-how-it-works.js +1 -1
- package/dist/features/tester/seed-data/authored/public-pages__help-scams-guides-subpages.js +4 -2
- package/dist/features/tester/seed-data/authored/public-pages__legal-policy-pages.js +3 -3
- package/dist/features/tester/seed-data/authored/public-pages__newly-wired-browse-indexes.js +5 -5
- package/dist/features/tester/seed-data/authored/public-pages__store-tabs.js +5 -5
- package/dist/features/tester/seed-data/authored/public-pages__stores-sellers-directories.js +3 -3
- package/dist/features/tester/seed-data/authored/search-and-nav__employee-permissions.js +45 -11
- package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -17
- package/dist/features/tester/seed-data/authored/search-and-nav__settings-deep-links.js +36 -5
- package/dist/features/tester/seed-data/authored/search-and-nav__sidebar-search.js +29 -4
- package/dist/features/tester/seed-data/authored/selling__become-seller.js +23 -0
- package/dist/features/tester/seed-data/authored/selling__final-sale-authoring.js +14 -13
- package/dist/features/tester/seed-data/authored/selling__listing-a-product.js +14 -9
- package/dist/features/tester/seed-data/authored/selling__listing-edit-roundtrip.js +34 -7
- package/dist/features/tester/seed-data/authored/selling__listing-lifecycle.js +17 -7
- package/dist/features/tester/seed-data/authored/selling__listing-type-fields-roundtrip.js +9 -9
- package/dist/features/tester/seed-data/authored/selling__media-limits.js +9 -4
- package/dist/features/tester/seed-data/authored/selling__product-upload-details.js +3 -3
- package/dist/features/tester/seed-data/authored/selling__quick-add-minimum-details.js +3 -3
- package/dist/features/tester/seed-data/authored/selling__sectionised-forms.js +25 -1
- package/dist/features/tester/seed-data/authored/selling__seller-analytics-payouts.js +27 -6
- package/dist/features/tester/seed-data/authored/selling__seller-bids-bundles-filters.js +19 -1
- package/dist/features/tester/seed-data/authored/selling__seller-catalog-org.js +63 -22
- package/dist/features/tester/seed-data/authored/selling__seller-custom-brands.js +9 -5
- package/dist/features/tester/seed-data/authored/selling__seller-guide.js +7 -6
- package/dist/features/tester/seed-data/authored/selling__seller-listing-types.js +3 -2
- package/dist/features/tester/seed-data/authored/selling__seller-marketing-extras.js +18 -18
- package/dist/features/tester/seed-data/authored/selling__seller-ops-comms.js +13 -12
- package/dist/features/tester/seed-data/authored/selling__seller-orders-at-scale.js +19 -3
- package/dist/features/tester/seed-data/authored/selling__seller-orders.js +22 -4
- package/dist/features/tester/seed-data/authored/selling__seller-shipping-payouts-setup.js +20 -8
- package/dist/features/tester/seed-data/authored/selling__store-dashboard-navigation.js +4 -4
- package/dist/features/tester/seed-data/authored/selling__store-uncovered-pages.js +2 -2
- package/dist/features/tester/seed-data/authored/seo__canonical-and-host.js +5 -5
- package/dist/features/tester/seed-data/authored/seo__og-images.js +1 -1
- package/dist/features/tester/seed-data/authored/seo__page-metadata.js +7 -7
- package/dist/features/tester/seed-data/authored/seo__sitemap-and-robots.js +3 -3
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +123 -21
- package/dist/features/tester/utils/phases.d.ts +17 -0
- package/dist/features/tester/utils/phases.js +29 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/seo/json-ld.d.ts +23 -0
- package/dist/seo/json-ld.js +3 -2
- package/dist/server.d.ts +1 -0
- package/dist/server.js +2 -0
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/PaginatedSelect.js +0 -0
- package/package.json +1 -1
|
@@ -12,6 +12,29 @@
|
|
|
12
12
|
* keeps its layout height while invisible over-reports the tier on every page that
|
|
13
13
|
* has no bar, pushing floating controls up the screen site-wide.
|
|
14
14
|
*
|
|
15
|
+
* 🛑 BOTH RECURRING SHAPES ARE PRESENT TODAY, and three cases were pointed at a
|
|
16
|
+
* surface where neither can be observed (checked 2026-09-29).
|
|
17
|
+
*
|
|
18
|
+
* `BulkActionItem` is `{ id, label, variant, onClick }`. There is NO `action`
|
|
19
|
+
* ActionDef on it, so Rule #7's auto-confirmation — which works by resolving
|
|
20
|
+
* `ActionDef.confirmation` inside `<Button action={…}>` — never runs here.
|
|
21
|
+
* `BulkActionBar.handleApply` calls `selectedAction?.onClick()` directly, and
|
|
22
|
+
* `variant: "danger"` only adds `--danger` styling to the trigger and the
|
|
23
|
+
* option. So **bulk Cancel on /admin/orders cancels every selected order with
|
|
24
|
+
* no dialog**, which is exactly the failure this file's own header describes.
|
|
25
|
+
*
|
|
26
|
+
* And every bulk handler is a fire-and-forget loop —
|
|
27
|
+
* `for (const rowId of selection.selectedIds) void handleQuickStatus(rowId, …)`
|
|
28
|
+
* then `clearSelection()` — so there is no aggregated outcome anywhere: no
|
|
29
|
+
* succeeded count, no failed count, and no way to name which rows failed. Two
|
|
30
|
+
* cases asked for those counts; they now expect their absence and say so.
|
|
31
|
+
*
|
|
32
|
+
* 🛑 /admin/products HAS NO DESTRUCTIVE BULK ACTION. Its preset is
|
|
33
|
+
* [FEATURE, PROMOTE, SALE] — three boolean flags — so the confirmation case
|
|
34
|
+
* cannot start there. It now starts on /admin/orders, whose preset carries
|
|
35
|
+
* CANCEL. Note those three are TOGGLES (`!row[field]`), so running "Feature"
|
|
36
|
+
* over a mixed selection UN-features the rows that were already featured.
|
|
37
|
+
*
|
|
15
38
|
* @tag domain:tester
|
|
16
39
|
* @tag layer:seed
|
|
17
40
|
* @tag pattern:none
|
|
@@ -53,47 +76,54 @@ export const authored = {
|
|
|
53
76
|
},
|
|
54
77
|
"checklist-admin-bulk-actions-bulk-destructive-confirms": {
|
|
55
78
|
roles: ["admin"],
|
|
56
|
-
startPage: "/admin/
|
|
79
|
+
startPage: "/admin/orders",
|
|
57
80
|
steps: [
|
|
58
81
|
SIGN_IN_ADMIN,
|
|
59
|
-
"Open /admin/products and
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
82
|
+
"Open /admin/products and read its bulk actions with three rows selected — they are Feature, Promote and Sale, and NONE of them is destructive, so there is nothing to confirm here.",
|
|
83
|
+
"Open /admin/orders and select ONE order that is already cancelled, so that running the action cannot destroy anything.",
|
|
84
|
+
"Open the bulk action picker and read the options — Mark Shipped, Mark Delivered and Cancel.",
|
|
85
|
+
"Choose Cancel and apply it. Watch carefully for a confirmation dialog BEFORE anything commits.",
|
|
86
|
+
"Record whether a dialog appeared at all, and what the bar did immediately afterwards.",
|
|
87
|
+
"Open /admin/users, select one row, and check whether its Delete bulk action confirms.",
|
|
65
88
|
],
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
89
|
+
inputs: { productBulkActions: "Feature, Promote, Sale", orderBulkActions: "Mark Shipped, Mark Delivered, Cancel" },
|
|
90
|
+
expectedBehaviour: "Every destructive bulk action should confirm first, naming the count. Bulk is where selections are largest, so an unconfirmed destructive action is irreversible across the most rows an admin ever touches at once.",
|
|
91
|
+
expectedUiState: "🛑 EXPECT NO CONFIRMATION, AND RECORD IT AS A FAILURE. `BulkActionItem` carries no ActionDef, so the registry's confirmation never resolves; the bar calls the handler directly and `variant: \"danger\"` only colours the control. Select an ALREADY-CANCELLED order so that proving this costs nothing. Say for each surface whether a dialog appeared.",
|
|
92
|
+
expectedData: { confirmationShownOnOrdersCancel: false },
|
|
93
|
+
endResult: "Nothing that mattered was destroyed — the only order actioned was already cancelled. Do not test this with a live order.",
|
|
69
94
|
},
|
|
70
95
|
"checklist-admin-bulk-actions-bulk-action-reports-result": {
|
|
71
96
|
roles: ["admin"],
|
|
72
97
|
startPage: "/admin/products",
|
|
73
98
|
steps: [
|
|
74
99
|
SIGN_IN_ADMIN,
|
|
75
|
-
"Open /admin/products and
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"Check the
|
|
100
|
+
"Open /admin/products and note which of three rows are already Featured — the bulk action is a TOGGLE, so this matters.",
|
|
101
|
+
"Select three rows that are all NOT featured, so the toggle moves them the same way.",
|
|
102
|
+
"Run the Feature bulk action.",
|
|
103
|
+
"Read whatever message appears when it completes, and record the exact wording.",
|
|
104
|
+
"Check whether the three rows show the change without a manual reload.",
|
|
105
|
+
"Re-select the same three and run Feature again to put them back.",
|
|
80
106
|
],
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
107
|
+
inputs: { action: "Feature", rowCount: 3 },
|
|
108
|
+
expectedBehaviour: "The result should name the counts. A bare 'Done' after an action over N rows tells the admin nothing about whether it was N — and a partial success reported as an unqualified success is how rows silently stay unchanged.",
|
|
109
|
+
expectedUiState: "🛑 EXPECT NO COUNTS, AND RECORD IT AS A FAILURE. The handler is a fire-and-forget loop over the selected ids followed by clearSelection(), so nothing collects an outcome: there is no succeeded count, no failed count, and no aggregated message to read. Record what IS shown — per-row toast, nothing at all, or a bare success — and whether the rows updated in place. 🛑 Select rows that are all in the same starting state: Feature inverts each row independently, so a mixed selection un-features the ones that were already featured.",
|
|
110
|
+
expectedData: { succeededCountShown: false, failedCountShown: false },
|
|
111
|
+
endResult: "The three rows are back to not-featured. Running the same toggle twice restores them, which is only true because they all started the same way.",
|
|
84
112
|
},
|
|
85
113
|
"checklist-admin-bulk-actions-bulk-partial-failure-named": {
|
|
86
114
|
roles: ["admin"],
|
|
87
115
|
startPage: "/admin/products",
|
|
88
116
|
steps: [
|
|
89
117
|
SIGN_IN_ADMIN,
|
|
90
|
-
"Open /admin/
|
|
91
|
-
"Run
|
|
92
|
-
"Read the result message.",
|
|
93
|
-
"Check the rows that
|
|
94
|
-
"
|
|
118
|
+
"Open /admin/orders and build a selection mixing rows the action can apply to with rows it cannot — one PENDING order alongside one already CANCELLED and one already DELIVERED.",
|
|
119
|
+
"Run Mark Shipped over that selection.",
|
|
120
|
+
"Read the result message and record the exact wording.",
|
|
121
|
+
"Check whether the rows that could not be shipped are NAMED, merely counted, or not mentioned at all.",
|
|
122
|
+
"Reload and read each of the three rows' status to find out what actually happened to each.",
|
|
123
|
+
"Restore any order whose status this changed.",
|
|
95
124
|
],
|
|
96
|
-
|
|
125
|
+
inputs: { action: "Mark Shipped", mixedStatuses: "pending + cancelled + delivered" },
|
|
126
|
+
expectedBehaviour: "Failures should be named. 'Two rows failed' out of a selection of twenty leaves the admin re-checking twenty rows by hand to find which two — and the usual next step is to run the action again on everything, which is worse. Naming nothing at all is worse still: the admin cannot even tell that anything failed.",
|
|
97
127
|
expectedUiState: "The failed rows are identified by title or id, with a reason. A bare failure count is the finding.",
|
|
98
128
|
endResult: "Restore whatever succeeded.",
|
|
99
129
|
},
|
|
@@ -149,14 +179,14 @@ export const authored = {
|
|
|
149
179
|
steps: [
|
|
150
180
|
SIGN_IN_ADMIN,
|
|
151
181
|
"Open /admin/offers and select two rows.",
|
|
152
|
-
"Read
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"Record any action that only cleared the selection.",
|
|
182
|
+
"Read what the bulk bar offers — the correct answer here is NOTHING.",
|
|
183
|
+
"If any bulk action IS offered, run it, reload, and check whether the records actually changed.",
|
|
184
|
+
"Open a single offer's row menu and confirm Cancel is there instead, and that it demands a reason.",
|
|
156
185
|
],
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
186
|
+
inputs: { expectedBulkActionCount: 0 },
|
|
187
|
+
expectedBehaviour: "Every offered action does what its label says — and where it cannot, it is not offered. A destructively-labelled bulk action once existed here whose handler only cleared the selection: it read as working, cancelled nothing, and an admin using it believed a batch of offers had been cancelled. It was DELETED rather than wired, because cancelling an offer requires a reason and one shared reason across a mixed selection is worse audit data than no bulk action at all.",
|
|
188
|
+
expectedUiState: "No bulk actions are offered on this page. That absence is the pass. A re-appeared bulk Cancel is the finding — whether it works or not — and per-offer Cancel is still available from the row menu, where it can ask for a reason.",
|
|
189
|
+
endResult: "Nothing is changed; no offer is cancelled.",
|
|
160
190
|
},
|
|
161
191
|
"checklist-admin-bulk-actions-bulk-users-actions": {
|
|
162
192
|
roles: ["admin"],
|
|
@@ -11,6 +11,33 @@
|
|
|
11
11
|
* that mirror, so a write path that updates the rule and forgets the mirror
|
|
12
12
|
* produces a bundle that renders as "0 items" with no error anywhere.
|
|
13
13
|
*
|
|
14
|
+
* 🛑 THREE MEMBERS MINIMUM, AND EVERY CASE HERE ASKED FOR TWO (fixed 2026-09-29).
|
|
15
|
+
*
|
|
16
|
+
* BUNDLE_MIN_ITEMS is 3 (bundle-config.ts:10) and bundle-form.ts's superRefine
|
|
17
|
+
* enforces it with "Select at least 3 products (currently 2)." So the create
|
|
18
|
+
* case could never save, and the two cases that READ that bundle were testing
|
|
19
|
+
* something which had never been created — three verdicts of "could not test"
|
|
20
|
+
* for a rule the editor states on its own field label.
|
|
21
|
+
*
|
|
22
|
+
* Every member is now a real seeded id with its real price, so the undiscounted
|
|
23
|
+
* total is arithmetic a tester can check rather than a number to trust.
|
|
24
|
+
*
|
|
25
|
+
* 🛑 EVERY STANDARD LISTING IN THE CATALOGUE BELONGS TO store-beyblade-arena.
|
|
26
|
+
* The only other seller with stock is store-letitrip-official, whose six
|
|
27
|
+
* listings are all prize draws — so the cross-store refusal is exercised with
|
|
28
|
+
* one of those. The guard reads storeId and does not care about listing type.
|
|
29
|
+
* (CLAUDE.md claims product-tester-crossstore-a/b are seeded for exactly this;
|
|
30
|
+
* they are not, anywhere — recorded in OUTOFSCOPE.)
|
|
31
|
+
*
|
|
32
|
+
* 🛑 THE SLUG IS DERIVED AND THEREFORE KNOWABLE: the route slugifies the name
|
|
33
|
+
* and prefixes "bundle-", so 'QA Bundle admin-create' is always
|
|
34
|
+
* /bundles/bundle-qa-bundle-admin-create. The cases used to say "/bundles/{slug}"
|
|
35
|
+
* without saying how to learn it.
|
|
36
|
+
*
|
|
37
|
+
* audit-tester-created-id: bundle-qa-bundle-admin-create — created by case 1 step 2
|
|
38
|
+
* and visited by cases 1-3. It is not a seed fixture and must not be one: the point
|
|
39
|
+
* is to exercise the CREATE path, and a pre-seeded bundle would skip it.
|
|
40
|
+
*
|
|
14
41
|
* @tag domain:tester
|
|
15
42
|
* @tag layer:seed
|
|
16
43
|
* @tag pattern:none
|
|
@@ -24,56 +51,69 @@ export const authored = {
|
|
|
24
51
|
startPage: "/admin/bundles",
|
|
25
52
|
steps: [
|
|
26
53
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
27
|
-
"Open /admin/bundles and
|
|
28
|
-
"
|
|
29
|
-
"
|
|
54
|
+
"Open /admin/bundles/new and set Name to 'QA Bundle admin-create'.",
|
|
55
|
+
"Leave the member-source select on its static option, add exactly TWO members — product-beyblade-burst-valkyrie and product-beyblade-x-wizard-arrow — set 'Bundle price (₹)' to 1500, and try to save.",
|
|
56
|
+
"Read the refusal: the minimum is three, and the field label states the range.",
|
|
57
|
+
"Add product-beyblade-x-knife-shinobi (₹949) as the third member and save.",
|
|
30
58
|
"RELOAD the editor and read the members, the price and the original total.",
|
|
31
|
-
"Open /bundles/
|
|
32
|
-
"
|
|
59
|
+
"Open /bundles/bundle-qa-bundle-admin-create — the slug is the name slugified under a 'bundle-' prefix — and read the members, price and discount badge.",
|
|
60
|
+
"Back in the editor add prizedraw-beyblade-mystery-box, which belongs to store-letitrip-official, as a fourth member, save, and read what happens.",
|
|
61
|
+
"Remove that fourth member and save, leaving the bundle at its three arena members.",
|
|
33
62
|
],
|
|
34
63
|
inputs: {
|
|
35
64
|
name: "QA Bundle admin-create",
|
|
36
65
|
member1: "product-beyblade-burst-valkyrie",
|
|
37
66
|
member2: "product-beyblade-x-wizard-arrow",
|
|
67
|
+
member3: "product-beyblade-x-knife-shinobi",
|
|
68
|
+
crossStoreMember: "prizedraw-beyblade-mystery-box",
|
|
38
69
|
bundlePrice: 1500,
|
|
39
|
-
memberTotal:
|
|
70
|
+
memberTotal: 2847,
|
|
40
71
|
},
|
|
41
|
-
expectedBehaviour: "
|
|
42
|
-
expectedUiState: "
|
|
43
|
-
expectedData: { memberCount:
|
|
44
|
-
endResult: "
|
|
72
|
+
expectedBehaviour: "A bundle holds at least three members, stores its locked price, and derives the members' undiscounted total SERVER-SIDE — the editor never sends that total, and it is what drives the discount badge, so a bundle without it shows a price and no saving. Members must come from one store: the store id is the order-splitting and payout key, so a cross-store bundle would produce an order belonging to one seller while holding another seller's products.",
|
|
73
|
+
expectedUiState: "The two-member save is refused inline with 'Select at least 3 products (currently 2).' — not a silent no-op and not a generic banner. With the third added, the reload lists all three, a bundle price of ₹1,500 against an original total of ₹2,847, and the public page shows the three members with a discount badge of roughly 47% off. The cross-store save is refused with a message naming how many sellers the members span, not a 500.",
|
|
74
|
+
expectedData: { memberCount: 3, bundlePrice: 1500, bundleOriginalTotal: 2847 },
|
|
75
|
+
endResult: "The bundle survives with its three arena members — the next two cases read it. The cross-store member is NOT part of it.",
|
|
45
76
|
},
|
|
46
77
|
"checklist-admin-bundles-bundle-stock-sync": {
|
|
47
78
|
roles: ["admin", "seller", "guest"],
|
|
48
79
|
startPage: "/admin/bundles",
|
|
49
80
|
steps: [
|
|
50
|
-
"
|
|
51
|
-
"Sign in as tyson@beybladearena.in / TempPass123
|
|
52
|
-
"Reload
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"Reload
|
|
81
|
+
"Open /bundles/bundle-qa-bundle-admin-create signed out and note that it offers purchase.",
|
|
82
|
+
"Sign in as tyson@beybladearena.in / TempPass123!, open /store/products, edit product-beyblade-x-wizard-arrow, and set its stock quantity from 12 to 0.",
|
|
83
|
+
"Reload /bundles/bundle-qa-bundle-admin-create and read its availability.",
|
|
84
|
+
"Sign in as admin@letitrip.in / TempPass123!, open /admin/bundles and read the bundle's row.",
|
|
85
|
+
"Sign back in as tyson@beybladearena.in and set that product's stock back to 12.",
|
|
86
|
+
"Reload /bundles/bundle-qa-bundle-admin-create and read its availability again.",
|
|
56
87
|
],
|
|
57
|
-
inputs: { member: "product-beyblade-x-wizard-arrow" },
|
|
88
|
+
inputs: { member: "product-beyblade-x-wizard-arrow", stockBefore: 12, stockDuring: 0 },
|
|
58
89
|
expectedBehaviour: "A bundle is all-or-nothing, so one member going out of stock makes the whole bundle unavailable. That is kept in sync by a hook on the member's stock change rather than computed per request — which is what makes the reverse direction worth testing too: restoring the stock must restore the bundle.",
|
|
59
|
-
expectedUiState: "With the member at zero the bundle reads as unavailable and
|
|
60
|
-
|
|
90
|
+
expectedUiState: "With the member at zero the bundle reads as unavailable and offers no purchase control. After the stock is restored it becomes purchasable again. A bundle still offering purchase with a zero-stock member is the failure; so is one that stays unavailable after the stock returns.",
|
|
91
|
+
expectedData: { stockAfterRestore: 12 },
|
|
92
|
+
endResult: "The member's stock is back at 12 and the bundle is purchasable. Both directions matter — a sync that only ever disables is half a sync, and leaving the stock at zero silently breaks the next case.",
|
|
61
93
|
},
|
|
62
94
|
"checklist-admin-bundles-bundle-edit-delete": {
|
|
63
95
|
roles: ["admin", "guest"],
|
|
64
96
|
startPage: "/admin/bundles",
|
|
65
97
|
steps: [
|
|
66
98
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
67
|
-
"Open
|
|
68
|
-
"Change ONLY
|
|
69
|
-
"RELOAD and compare every field against what was written down.",
|
|
70
|
-
"
|
|
71
|
-
"Open
|
|
72
|
-
"
|
|
99
|
+
"Open /admin/bundles, open 'QA Bundle admin-create' for editing, and write down every field — name, description, price, the three members, cover image, brand and active.",
|
|
100
|
+
"Change ONLY 'Bundle price (₹)' to 1400 and save.",
|
|
101
|
+
"RELOAD the editor and compare every field against what was written down.",
|
|
102
|
+
"SWAP one member rather than dropping one: remove product-beyblade-x-knife-shinobi (₹949), add product-beyblade-burst-regalia-genesis (₹1,399), keeping three. Save, RELOAD, and read the member list and the original total.",
|
|
103
|
+
"Open /bundles/bundle-qa-bundle-admin-create and read the members and the discount badge.",
|
|
104
|
+
"Back in the editor click the delete control, confirm, and check the bundle is gone from /admin/bundles and from /bundles.",
|
|
73
105
|
],
|
|
74
|
-
inputs: {
|
|
75
|
-
|
|
76
|
-
|
|
106
|
+
inputs: {
|
|
107
|
+
priceBefore: 1500,
|
|
108
|
+
priceAfter: 1400,
|
|
109
|
+
memberRemoved: "product-beyblade-x-knife-shinobi",
|
|
110
|
+
memberAdded: "product-beyblade-burst-regalia-genesis",
|
|
111
|
+
totalBefore: 2847,
|
|
112
|
+
totalAfter: 3297,
|
|
113
|
+
},
|
|
114
|
+
expectedBehaviour: "An edit writes back only what changed, and changing the member list recomputes the undiscounted total server-side — a stale total makes the discount badge claim a saving that no longer exists. A SWAP rather than a bare removal is what makes this readable: the count stays at the legal minimum of three while the total must move, so a total that did not change cannot be excused as the member rule having been left alone.",
|
|
115
|
+
expectedUiState: "After the price edit only the price differs — every other field matches what was written down. After the swap the list holds three members and the original total reads ₹3,297 rather than ₹2,847, so the badge's saving shrinks. After the delete the bundle is absent from /admin/bundles and /bundles, and its own URL no longer renders it.",
|
|
116
|
+
expectedData: { memberCountAfterSwap: 3, bundleOriginalTotalAfterSwap: 3297 },
|
|
77
117
|
endResult: "The bundle is deleted. A public page still rendering a deleted bundle is a cache or projection failure worth naming.",
|
|
78
118
|
},
|
|
79
119
|
"checklist-admin-bundles-bundle-brand-picker": {
|
|
@@ -81,17 +121,24 @@ export const authored = {
|
|
|
81
121
|
startPage: "/admin/bundles",
|
|
82
122
|
steps: [
|
|
83
123
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
84
|
-
"Open /admin/bundles
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
124
|
+
"Open /admin/bundles/new, set Name to 'QA Bundle brand-picker', and add THREE members from Beyblade Arena — product-beyblade-burst-valkyrie, product-beyblade-x-wizard-arrow and product-beyblade-x-knife-shinobi. Fewer than three is refused.",
|
|
125
|
+
"Set 'Bundle price (₹)' to 2000.",
|
|
126
|
+
"Find the 'Brand' select and read what it offers.",
|
|
127
|
+
"Select Beyblade and save.",
|
|
128
|
+
"RELOAD the editor and read the brand.",
|
|
129
|
+
"Open /brands/brand-beyblade signed out and find the bundle in its Bundles tab.",
|
|
130
|
+
"Back in the editor set Brand to 'No specific brand', save, reload the brand page, and check the bundle has left that tab.",
|
|
90
131
|
"Delete the bundle.",
|
|
91
132
|
],
|
|
92
|
-
inputs: {
|
|
133
|
+
inputs: {
|
|
134
|
+
name: "QA Bundle brand-picker",
|
|
135
|
+
brand: "Beyblade",
|
|
136
|
+
clearedTo: "No specific brand",
|
|
137
|
+
bundlePrice: 2000,
|
|
138
|
+
},
|
|
93
139
|
expectedBehaviour: "A bundle's own brand tag is a DIFFERENT field from any brand filter inside a dynamic member rule — the tag says which brand the bundle belongs to regardless of how its members were resolved, and it is what the brand page's Bundles tab filters on. Not every bundle needs one; a genuinely cross-brand bundle leaves it unset.",
|
|
94
|
-
expectedUiState: "The
|
|
95
|
-
|
|
140
|
+
expectedUiState: "The select offers the real seeded brands plus a 'No specific brand' option — not a free-text box and not an empty list. With Beyblade set the bundle appears in that brand's Bundles tab; with it cleared it leaves. Worth watching: two of the three members are tagged Takara-Tomy rather than Beyblade, so a bundle still appearing under Beyblade after the tag is cleared means the tab is filtering on the MEMBERS instead of the tag.",
|
|
141
|
+
expectedData: { brandAfterReload: "Beyblade" },
|
|
142
|
+
endResult: "The bundle is deleted by the final step, so /brands/brand-beyblade's Bundles tab holds only the seeded bundles again.",
|
|
96
143
|
},
|
|
97
144
|
};
|
|
@@ -13,6 +13,26 @@
|
|
|
13
13
|
* returns a per-user summary only, so the honest fix was to route to the owning
|
|
14
14
|
* user rather than widen the payload.
|
|
15
15
|
*
|
|
16
|
+
* 🛑 /admin/addresses IS AN OWNER-SCOPED LOOKUP, NOT A LISTING (fixed 2026-09-29).
|
|
17
|
+
*
|
|
18
|
+
* It renders `AdminAddressBookView`: an 'Owner type' select (user | store), an
|
|
19
|
+
* 'Owner ID' text field placeholdered `user-...` / `store-...`, and a Search
|
|
20
|
+
* button. There are no columns, no sort dropdown, no filter drawer and no
|
|
21
|
+
* free-text search of addresses — and nothing at all is shown until an owner id
|
|
22
|
+
* is searched. Three cases here described a listing with owner-type filters and
|
|
23
|
+
* a sort default; none of that exists on this page.
|
|
24
|
+
*
|
|
25
|
+
* The banned-address queue those descriptions actually fit is a DIFFERENT
|
|
26
|
+
* route, `ROUTES.ADMIN.BANNED_ADDRESSES` ("Banned Addresses"), backed by
|
|
27
|
+
* `AdminAddressesView` — similar name, different page. Worth stating because
|
|
28
|
+
* reading the plausibly-named component instead of following the route is how
|
|
29
|
+
* this was nearly mis-audited.
|
|
30
|
+
*
|
|
31
|
+
* Its editor DOES exist, at /admin/addresses/new and /admin/addresses/[id]/edit,
|
|
32
|
+
* and per its own header the postal code is now validated per country by schema
|
|
33
|
+
* rather than by `length === 6` — so 'abcdef' should be REJECTED, and the CRUD
|
|
34
|
+
* case expects a pass.
|
|
35
|
+
*
|
|
16
36
|
* @tag domain:tester
|
|
17
37
|
* @tag layer:seed
|
|
18
38
|
* @tag pattern:none
|
|
@@ -26,7 +46,7 @@ export const authored = {
|
|
|
26
46
|
startPage: "/admin/carts",
|
|
27
47
|
steps: [
|
|
28
48
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
29
|
-
"Open
|
|
49
|
+
"Open /admin/carts and read every column.",
|
|
30
50
|
"Check each row identifies whose cart it is and how many items it holds.",
|
|
31
51
|
"Read whether guest carts are distinguishable from signed-in ones.",
|
|
32
52
|
"Use every filter and sort offered, confirming each changes the rows.",
|
|
@@ -43,14 +63,16 @@ export const authored = {
|
|
|
43
63
|
startPage: "/admin/carts",
|
|
44
64
|
steps: [
|
|
45
65
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
46
|
-
"Open
|
|
47
|
-
"Click the row and read what opens.",
|
|
48
|
-
"Read
|
|
49
|
-
"Read
|
|
50
|
-
"
|
|
66
|
+
"Open /admin/carts and find a row whose item count is greater than one.",
|
|
67
|
+
"Click the row and read what opens — it should be a 'Cart Details' modal.",
|
|
68
|
+
"Read its badge: a guest cart reads 'Guest' and a signed-in one reads 'Authenticated'.",
|
|
69
|
+
"Read its fields — Session or User, Last updated, Cart ID.",
|
|
70
|
+
"Read the 'Items in cart' section and check each item carries a title, a quantity and a price.",
|
|
71
|
+
"Close and open a different row the same way.",
|
|
51
72
|
],
|
|
73
|
+
inputs: { modalTitle: "Cart Details", itemsHeading: "Items in cart" },
|
|
52
74
|
expectedBehaviour: "A row opens to show the cart's items. The list already computes the item count from the items array and then discards it, so the data needed to render the detail is present at list time — this is a UI wiring gap rather than a missing query, and the fix is a detail view rather than a wider payload.",
|
|
53
|
-
expectedUiState: "Clicking a row opens
|
|
75
|
+
expectedUiState: "Clicking a row opens the 'Cart Details' modal showing each item with its title, quantity and price under 'Items in cart'. A row that does nothing on click, or opens a modal whose items section is empty for a cart the list counted as non-empty, is the dead-end failure this page family exists to close.",
|
|
54
76
|
endResult: "Read-only; close without acting.",
|
|
55
77
|
},
|
|
56
78
|
"checklist-admin-buyer-data-admin-wishlists-admin-view": {
|
|
@@ -58,7 +80,7 @@ export const authored = {
|
|
|
58
80
|
startPage: "/admin/wishlists",
|
|
59
81
|
steps: [
|
|
60
82
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
61
|
-
"Open
|
|
83
|
+
"Open /admin/wishlists and read every column.",
|
|
62
84
|
"Check each row names the owning user and the number of items.",
|
|
63
85
|
"Read whether the item titles themselves are shown in the list.",
|
|
64
86
|
"Use every filter and sort offered.",
|
|
@@ -74,15 +96,15 @@ export const authored = {
|
|
|
74
96
|
startPage: "/admin/wishlists",
|
|
75
97
|
steps: [
|
|
76
98
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
77
|
-
"Open
|
|
78
|
-
"Read
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"Read where that lands.",
|
|
99
|
+
"Open /admin/wishlists and click a row.",
|
|
100
|
+
"Read the URL it lands on — it should be that row's owner under /admin/users/{userId}, not a wishlist detail.",
|
|
101
|
+
"Go back, open /admin/history, and click a row there.",
|
|
102
|
+
"Read the URL that lands on.",
|
|
82
103
|
"From the user page reached, check the wishlist or history is readable in context.",
|
|
83
104
|
],
|
|
105
|
+
inputs: { expectedDestination: "/admin/users/{userId}" },
|
|
84
106
|
expectedBehaviour: "These two rows route to the owning user rather than to a per-record detail, and that is the correct answer rather than a shortcut: their list queries return only a per-user summary, so a detail view would have to fetch separately, and the user page is where the rest of that person's data already is.",
|
|
85
|
-
expectedUiState: "Both rows
|
|
107
|
+
expectedUiState: "Both rows navigate to /admin/users/{userId} for that row's owner. Neither is inert. From there the wishlist or history contents are reachable. A row that does nothing at all is the failure; a row routing to the user is a pass. A row whose owner id is missing, so the click does nothing, is also a failure — the navigation is guarded on it.",
|
|
86
108
|
endResult: "Read-only; close without acting.",
|
|
87
109
|
},
|
|
88
110
|
"checklist-admin-buyer-data-admin-history-admin-view": {
|
|
@@ -90,7 +112,7 @@ export const authored = {
|
|
|
90
112
|
startPage: "/admin/history",
|
|
91
113
|
steps: [
|
|
92
114
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
93
|
-
"Open
|
|
115
|
+
"Open /admin/history and read every column.",
|
|
94
116
|
"Check each row names the owning user and the number of entries.",
|
|
95
117
|
"Read the highest entry count in the list.",
|
|
96
118
|
"Use every filter and sort offered.",
|
|
@@ -107,17 +129,17 @@ export const authored = {
|
|
|
107
129
|
startPage: "/admin/notifications",
|
|
108
130
|
steps: [
|
|
109
131
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
110
|
-
"Open
|
|
132
|
+
"Open /admin/notifications and read every column.",
|
|
111
133
|
"Read every type filter offered and select each, noting any that returns nothing.",
|
|
112
134
|
"For each empty filter, check the unfiltered list for rows of that type.",
|
|
113
|
-
"Open a row's
|
|
135
|
+
"Open a row menu's 'View details' action and read the full body and payload in the modal it opens.",
|
|
114
136
|
"Read whether the recipient is identified.",
|
|
115
137
|
"Search zzzznope and read the count.",
|
|
116
138
|
],
|
|
117
139
|
inputs: { nonsenseQuery: "zzzznope" },
|
|
118
|
-
expectedBehaviour: "Every type chip names a value notifications actually carry. The type union is a single runtime list, and for a long time the filters were built from a nine-value copy
|
|
119
|
-
expectedUiState: "Every type filter returns its own rows or is empty with none unfiltered either. A row opens to show the full body and payload rather than only the title. 'zzzznope' returns none.",
|
|
120
|
-
expectedData: { nonsenseResultCount: 0 },
|
|
140
|
+
expectedBehaviour: "Every type chip names a value notifications actually carry, and the union has THIRTY values today. The type union is a single runtime list, and for a long time the filters were built from a nine-value copy — most types were unfilterable and two could not be allow-listed for any channel at all. Count the chips against thirty rather than against the seeded data, which covers only ten of them.",
|
|
141
|
+
expectedUiState: "Every type filter returns its own rows or is empty with none unfiltered either. A row opens to show the full body and payload rather than only the title. 'zzzznope' returns none. A chip MISSING for a real type is as much a finding as a chip that matches nothing — record the count you saw.",
|
|
142
|
+
expectedData: { nonsenseResultCount: 0, typeChipCount: 30 },
|
|
121
143
|
endResult: "Read-only. A chip empty while matching rows sit unfiltered is the finding, named by type.",
|
|
122
144
|
},
|
|
123
145
|
"checklist-admin-buyer-data-admin-reviews-admin-view": {
|
|
@@ -125,7 +147,7 @@ export const authored = {
|
|
|
125
147
|
startPage: "/admin/reviews",
|
|
126
148
|
steps: [
|
|
127
149
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
128
|
-
"Open /admin/reviews and read every column, including any review images.",
|
|
150
|
+
"Open /admin/reviews and read every column, including any review images. Roughly 15-20% of the 79 seeded reviews carry one.",
|
|
129
151
|
"Check each image tile renders rather than showing a placeholder icon.",
|
|
130
152
|
"Read the search box's placeholder for how it matches.",
|
|
131
153
|
"Type a reviewer's FULL name and read the results.",
|
|
@@ -144,16 +166,21 @@ export const authored = {
|
|
|
144
166
|
startPage: "/admin/addresses",
|
|
145
167
|
steps: [
|
|
146
168
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
147
|
-
"Open the
|
|
148
|
-
"Read whether
|
|
149
|
-
"
|
|
150
|
-
"Read how
|
|
151
|
-
"
|
|
152
|
-
"
|
|
169
|
+
"Open /admin/addresses and read what the page offers before any input — an 'Owner type' select, an 'Owner ID' field and a Search button.",
|
|
170
|
+
"Read whether anything is listed before a search is run.",
|
|
171
|
+
"Leave Owner type on 'user', enter user-admin-letitrip, and click Search.",
|
|
172
|
+
"Read every field shown for each address returned, and record exactly how much of the name and phone number is displayed.",
|
|
173
|
+
"Switch Owner type to 'store', enter store-beyblade-arena, and Search.",
|
|
174
|
+
"Enter zzzznope as the Owner ID and Search.",
|
|
153
175
|
],
|
|
154
|
-
inputs: {
|
|
155
|
-
|
|
156
|
-
|
|
176
|
+
inputs: {
|
|
177
|
+
userOwner: "user-admin-letitrip",
|
|
178
|
+
storeOwner: "store-beyblade-arena",
|
|
179
|
+
nonsenseOwner: "zzzznope",
|
|
180
|
+
},
|
|
181
|
+
expectedBehaviour: "Both buyer delivery and store pickup addresses live in one collection discriminated by owner type, and this page reaches them one OWNER at a time rather than as a browsable list — which is why the owner type is an input rather than a filter. Names, phones and street lines are PII-encrypted at rest, so how much this page decrypts and displays is worth recording rather than assuming.",
|
|
182
|
+
expectedUiState: "Before a search the page lists nothing, which is correct here rather than an empty-state bug. A searched owner returns their addresses; a nonsense owner id returns 'No addresses found for this owner.' rather than an error or the full collection. Record exactly how much of each name and phone is shown.\n\nNote what this page is NOT: there are no columns, no sort dropdown, no filter drawer and no free-text address search. A case expecting those is describing the Banned Addresses queue, which is a different route.",
|
|
183
|
+
expectedData: { listedBeforeSearch: 0, nonsenseOwnerResultCount: 0 },
|
|
157
184
|
endResult: "Read-only; nothing persists beyond the URL.",
|
|
158
185
|
},
|
|
159
186
|
"checklist-admin-buyer-data-admin-store-addresses-admin-row-opens-detail": {
|
|
@@ -161,27 +188,28 @@ export const authored = {
|
|
|
161
188
|
startPage: "/admin/addresses",
|
|
162
189
|
steps: [
|
|
163
190
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
164
|
-
"Open
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
-
"
|
|
168
|
-
"
|
|
191
|
+
"Open /admin/addresses, search Owner type 'store' with store-beyblade-arena, and read the card rendered for each address.",
|
|
192
|
+
"Record which fields the card shows — label, full name, street, city, state, postal code, phone — and specifically whether the LANDMARK appears.",
|
|
193
|
+
"Check whether the card is openable at all: a link, a click target or an edit affordance leading to /admin/addresses/{id}/edit.",
|
|
194
|
+
"Search Owner type 'user' with user-admin-letitrip and read the same for a buyer-owned address.",
|
|
195
|
+
"Open /admin/addresses/{one of those ids}/edit directly and compare the fields there against what the card showed.",
|
|
169
196
|
],
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
197
|
+
inputs: { storeOwner: "store-beyblade-arena", userOwner: "user-admin-letitrip" },
|
|
198
|
+
expectedBehaviour: "An admin can read every stored field of an address, for both owner types. The landmark is the field worth naming: a form that cannot express it sends undefined and overwrites, so a surface that never shows it hides whether the value survived.",
|
|
199
|
+
expectedUiState: "Both owner types render cards carrying the stored fields. The editor at /admin/addresses/{id}/edit shows every field including the landmark. Record any field the editor has and the card does not — a card that omits the landmark while the editor can write it is the gap this case is for.",
|
|
200
|
+
endResult: "Read-only; close the editor without saving.",
|
|
173
201
|
},
|
|
174
202
|
"checklist-admin-buyer-data-admin-addresses-crud-admin": {
|
|
175
203
|
roles: ["admin"],
|
|
176
204
|
startPage: "/admin/addresses",
|
|
177
205
|
steps: [
|
|
178
206
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
179
|
-
"Open
|
|
180
|
-
"Save,
|
|
181
|
-
"Edit ONLY the street to '14 Test Lane', save,
|
|
207
|
+
"Open /admin/addresses/new and create one for Owner type 'user', Owner ID user-admin-letitrip: label 'QA Address admin-crud', '13 Test Lane', Indore, Madhya Pradesh, 452010, landmark 'Beside the arena'.",
|
|
208
|
+
"Save, then find it again via /admin/addresses searching that owner, open /admin/addresses/{its id}/edit, and read every field including the landmark.",
|
|
209
|
+
"Edit ONLY the street to '14 Test Lane', save, REOPEN the editor, and read every field again.",
|
|
182
210
|
"Type abcdef into the postal code field and attempt to save.",
|
|
183
|
-
"Read where the error appears.",
|
|
184
|
-
"
|
|
211
|
+
"Read where the error appears — on the field, or as a banner, or not at all.",
|
|
212
|
+
"Restore the postal code, then delete the address and search that owner again to confirm it is gone.",
|
|
185
213
|
],
|
|
186
214
|
inputs: {
|
|
187
215
|
name: "QA Address admin-crud",
|
|
@@ -191,7 +219,7 @@ export const authored = {
|
|
|
191
219
|
badPostcode: "abcdef",
|
|
192
220
|
},
|
|
193
221
|
expectedBehaviour: "Create, edit and delete persist and an edit to one field leaves the rest alone. The postal code is validated by FORMAT rather than by length: a length-only rule accepts 'abcdef' as a valid six-character Indian PIN code, and one admin route did exactly that.",
|
|
194
|
-
expectedUiState: "After each
|
|
222
|
+
expectedUiState: "After each reopen every value holds, including the landmark after the street edit — a landmark that vanishes when only the street changed is the overwrite this case is for. 'abcdef' is rejected ON THE POSTAL CODE FIELD before any request is sent, not returned as a server error with nothing marked. This half is expected to PASS: the form validates per country by schema now, where it once checked only that the value was six characters long — which 'abcdef' satisfies.",
|
|
195
223
|
endResult: "The address is deleted by the final step.",
|
|
196
224
|
},
|
|
197
225
|
"checklist-admin-buyer-data-admin-stores-admin-view": {
|
|
@@ -200,15 +228,20 @@ export const authored = {
|
|
|
200
228
|
steps: [
|
|
201
229
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
202
230
|
"Open /admin/stores and read every column and status chip.",
|
|
203
|
-
"Select each status chip in turn and read the rows returned.",
|
|
204
|
-
"
|
|
205
|
-
"
|
|
206
|
-
"RELOAD and read
|
|
231
|
+
"Select each status chip in turn and read the rows returned — the seed carries store-blader-bazaar as pending and store-vintage-vault-co as suspended, so neither chip should be empty.",
|
|
232
|
+
"Open store-letitrip-official — verified and featured — for editing and write down its verified flag, its featured flag, its capabilities and its admin notes.",
|
|
233
|
+
"Change ONLY its description and save.",
|
|
234
|
+
"RELOAD and read all four of those back.",
|
|
207
235
|
"Restore the description.",
|
|
208
236
|
],
|
|
237
|
+
inputs: {
|
|
238
|
+
store: "store-letitrip-official",
|
|
239
|
+
pendingStore: "store-blader-bazaar",
|
|
240
|
+
suspendedStore: "store-vintage-vault-co",
|
|
241
|
+
},
|
|
209
242
|
expectedBehaviour: "🛑 This is the highest-severity case on the page. The list serializer once omitted the verified, featured, capabilities and notes fields, so the editor seeded them as undefined and the save handler sent those defaults back unconditionally — saving ANY unrelated field silently un-verified the store and reset its capabilities. The seed carries a pending and a suspended store precisely so those chips are not permanently empty.",
|
|
210
|
-
expectedUiState: "Every status chip returns rows
|
|
211
|
-
expectedData: { verifiedPreserved: true },
|
|
243
|
+
expectedUiState: "Every status chip returns rows; the pending and suspended chips have a seeded store each. After the description save and reload the store is STILL verified, still featured, its capabilities are unchanged and its admin notes are intact. A store that quietly lost its verified badge is the failure, and the save reported success. This is expected to PASS — the list serializer now carries all five of those fields — but it is the case worth re-running after any change to that route, because the failure is silent and destructive.",
|
|
244
|
+
expectedData: { verifiedPreserved: true, featuredPreserved: true, capabilitiesPreserved: true },
|
|
212
245
|
endResult: "The description is restored and the store's flags are as they were. Re-read every flag after the reload, not just the one that was edited.",
|
|
213
246
|
},
|
|
214
247
|
"checklist-admin-buyer-data-admin-admin-store-detail-page": {
|
|
@@ -216,14 +249,14 @@ export const authored = {
|
|
|
216
249
|
startPage: "/admin/stores",
|
|
217
250
|
steps: [
|
|
218
251
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
219
|
-
"Open /admin/stores and open
|
|
220
|
-
"Write down every field label it shows.",
|
|
221
|
-
"
|
|
222
|
-
"
|
|
223
|
-
"
|
|
224
|
-
"
|
|
225
|
-
"Read whether the store's WhatsApp access token appears anywhere on either surface.",
|
|
252
|
+
"Open /admin/stores and open store-beyblade-arena's detail panel from the list.",
|
|
253
|
+
"Write down every field label it shows, then close the panel.",
|
|
254
|
+
"Open /admin/stores/store-beyblade-arena/view directly.",
|
|
255
|
+
"Write down every field label there and compare the two lists.",
|
|
256
|
+
"Reload that page and confirm it loads the same store rather than 404ing or resetting.",
|
|
257
|
+
"Search both surfaces, and the page source of the standalone one, for a WhatsApp access token.",
|
|
226
258
|
],
|
|
259
|
+
inputs: { store: "store-beyblade-arena", standalonePage: "/admin/stores/{id}/view" },
|
|
227
260
|
expectedBehaviour: "The panel and the page render from one shared content component, so their fields match by construction. The store document's WhatsApp token is decrypted on every read, so any surface that does not deliberately project it is handling plaintext — an admin page is allowed to show more than a public one, but not the raw credential.",
|
|
228
261
|
expectedUiState: "Both surfaces show the same fields and the standalone page survives a reload. The access token is masked or absent on both. A field in one and not the other means a second copy of the content component has appeared.",
|
|
229
262
|
expectedData: { rawTokenShown: 0 },
|