@mohasinac/appkit 4.42.0 ā 4.42.2
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/client/features/layout/DashboardLayoutClient.js +2 -30
- 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/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/layout/AppLayoutShell.js +2 -2
- package/dist/features/layout/BottomNavLayout.d.ts +9 -3
- package/dist/features/layout/BottomNavLayout.js +9 -3
- 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 +8 -6
- 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 +4 -4
- 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 +10 -9
- 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 +126 -24
- 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
|
@@ -38,15 +38,24 @@ export const moneyFlowsPages = [
|
|
|
38
38
|
steps: [
|
|
39
39
|
STEP_SIGNIN_BUYER,
|
|
40
40
|
"Open /products/product-beyblade-burst-valkyrie.",
|
|
41
|
+
"Confirm the signed-in account is ash@pokemonpalace.in and NOT rehan.sheikh@gmail.com. The seeded pending 780 on this product belongs to rehan, who is also the harness default buyer, so running this case as rehan trips the guard that allows only one active offer per listing and Make Offer is refused ā which reads as a broken control rather than as the wrong identity.",
|
|
41
42
|
"Click 'Make Offer'.",
|
|
42
|
-
"Enter
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
"Enter 815 as the offer amount. NOT 780: the seed already ships offer-yugi-burst-valkyrie-pending, a pending 780 from a different buyer on this same product, and two pending rows at the same amount on the same listing are tellable apart only by buyer name.",
|
|
44
|
+
/*
|
|
45
|
+
* Named `QA note buyer-makes-offer`, not `QA offer offer-to-purchase-ā¦`.
|
|
46
|
+
* R7 reads `offer-ā¦` as a FIXTURE CITATION wherever it appears in a
|
|
47
|
+
* step ā including inside a name the tester is being told to type ā so
|
|
48
|
+
* the page-qualified key read as an offer id that does not exist. The
|
|
49
|
+
* fixture reference documents this exact trap; renaming the record is
|
|
50
|
+
* cheaper than weakening the rule.
|
|
51
|
+
*/
|
|
52
|
+
"Enter the note 'QA note buyer-makes-offer'.",
|
|
53
|
+
"Click the primary button. It reads 'Send offer of ā¹815.00' ā the label carries the amount, so it doubles as a check that what you typed reached the button. The label is interpolated, so it is not a fixed string to search for.",
|
|
45
54
|
"Open /user/offers.",
|
|
46
55
|
],
|
|
47
|
-
expectedBehaviour: "An offer is created for
|
|
48
|
-
expectedUiState: "The offer modal closes and a success confirmation appears. /user/offers lists a row for the product with amount ā¹
|
|
49
|
-
endResult: "After reloading /user/offers the row is still listed at ā¹
|
|
56
|
+
expectedBehaviour: "An offer is created for 815 against product-beyblade-burst-valkyrie, owned by the signed-in buyer, and the owning seller receives an offer-received notification.",
|
|
57
|
+
expectedUiState: "The offer modal closes and a success confirmation appears. /user/offers lists a row for the product with amount ā¹815 and status Pending. The page does NOT read 'No offers yet'. Only THIS buyer's offers appear here, so the seeded 780 from another buyer must not be listed.",
|
|
58
|
+
endResult: "After reloading /user/offers the row is still listed at ā¹815 with status Pending.",
|
|
50
59
|
},
|
|
51
60
|
{
|
|
52
61
|
key: "duplicate-offer-refused",
|
|
@@ -54,15 +63,15 @@ export const moneyFlowsPages = [
|
|
|
54
63
|
roles: ["buyer"],
|
|
55
64
|
startPage: "/products/product-beyblade-burst-valkyrie",
|
|
56
65
|
steps: [
|
|
57
|
-
"Sign in as the buyer ash@pokemonpalace.in, holding the pending
|
|
66
|
+
"Sign in as the buyer ash@pokemonpalace.in, holding the pending 815 offer from the previous case.",
|
|
58
67
|
"Open /products/product-beyblade-burst-valkyrie.",
|
|
59
68
|
"Click 'Make Offer'.",
|
|
60
69
|
"Enter 800 as the offer amount.",
|
|
61
|
-
"Click '
|
|
70
|
+
"Click the primary button, which now reads 'Send offer of ā¹800.00'.",
|
|
62
71
|
],
|
|
63
|
-
expectedBehaviour: "The second offer is rejected. No new offer document is created; the existing
|
|
72
|
+
expectedBehaviour: "The second offer is rejected. No new offer document is created; the existing 815 offer is untouched.",
|
|
64
73
|
expectedUiState: "An error states that an active offer already exists on this listing. The amount field keeps the entered 800 rather than clearing silently.",
|
|
65
|
-
endResult: "After reloading /user/offers there is still exactly ONE pending row for this product, at ā¹
|
|
74
|
+
endResult: "After reloading /user/offers there is still exactly ONE pending row for this product, at ā¹815 ā not two, and not one at ā¹800. The rule is per buyer per listing, so the other buyer's seeded 780 is unaffected and is not visible here anyway.",
|
|
66
75
|
},
|
|
67
76
|
{
|
|
68
77
|
key: "seller-sees-and-accepts",
|
|
@@ -72,7 +81,7 @@ export const moneyFlowsPages = [
|
|
|
72
81
|
steps: [
|
|
73
82
|
"Sign in as tyson@beybladearena.in / TempPass123!, who owns store-beyblade-arena.",
|
|
74
83
|
"Open /store/offers.",
|
|
75
|
-
"Find the row for offer-yugi-burst-valkyrie-pending
|
|
84
|
+
"Find the row for offer-yugi-burst-valkyrie-pending ā product-beyblade-burst-valkyrie, offered ā¹780 against a listed ā¹999, from buyer Mock User 3. If the previous cases ran there is also an ā¹815 row from a different buyer on the same product; accept the ā¹780 one.",
|
|
76
85
|
"Open the row actions menu.",
|
|
77
86
|
"Click 'Accept'.",
|
|
78
87
|
"Confirm in the dialog.",
|
|
@@ -190,6 +199,7 @@ export const moneyFlowsPages = [
|
|
|
190
199
|
startPage: "/user/notifications",
|
|
191
200
|
steps: [
|
|
192
201
|
"Sign in as the buyer who won auction-money-flows-closing.",
|
|
202
|
+
"š WAIT FOR SETTLEMENT FIRST. The auction closes three minutes after the batch starts, but `auctionSettlement` is a scheduled function running every 15 minutes ā so the win is not processed the moment the clock runs out. Nothing in this case or the two after it can be judged until it has run; an empty list before then is the schedule, not a defect.",
|
|
193
203
|
"Open the notifications bell in the header.",
|
|
194
204
|
"Find the auction-won notification.",
|
|
195
205
|
"Read its text.",
|
|
@@ -204,19 +214,19 @@ export const moneyFlowsPages = [
|
|
|
204
214
|
key: "won-auction-is-payable",
|
|
205
215
|
label: "š A won auction can actually be paid for",
|
|
206
216
|
roles: ["buyer"],
|
|
207
|
-
startPage: "/
|
|
217
|
+
startPage: "/cart",
|
|
208
218
|
steps: [
|
|
209
219
|
"Sign in as the buyer who won auction-money-flows-closing.",
|
|
210
|
-
"Open /user/orders.",
|
|
211
|
-
"Locate the won auction.",
|
|
212
|
-
"Click
|
|
220
|
+
"Open /cart, NOT /user/orders. A win does not create an order ā settlement adds a LOCKED CART LINE, and the buyer pays through the ordinary checkout in the auction lane. An order only exists after payment.",
|
|
221
|
+
"Locate the won auction line and read its price.",
|
|
222
|
+
"Click through to checkout from that line.",
|
|
213
223
|
"Read the amount due.",
|
|
214
224
|
"Proceed as far as the payment method selection.",
|
|
215
225
|
],
|
|
216
226
|
expectedBehaviour: "A reachable payment path exists and the amount due equals the winning bid of ā¹16,000.",
|
|
217
|
-
expectedUiState: "The
|
|
218
|
-
endResult: "Stop at the payment method step. After reloading /
|
|
219
|
-
href: "/
|
|
227
|
+
expectedUiState: "The cart line reads ā¹16,000.00 and checkout is reachable from it, arriving at a payment method selection. Root Cause #60 is exactly this failing ā settlement once wrote an ORDER with no payment route at all, so 'the record exists' is NOT a pass; a payment step must be reachable. A line that is present but leads nowhere is the same defect wearing a different shape.",
|
|
228
|
+
endResult: "Stop at the payment method step and place nothing. After reloading /cart the locked line is still there at ā¹16,000.00.",
|
|
229
|
+
href: "/cart",
|
|
220
230
|
},
|
|
221
231
|
{
|
|
222
232
|
key: "won-auction-line-is-locked",
|
|
@@ -365,7 +375,7 @@ export const moneyFlowsPages = [
|
|
|
365
375
|
roles: ["buyer"],
|
|
366
376
|
startPage: "/checkout",
|
|
367
377
|
steps: [
|
|
368
|
-
"Sign in as admin@letitrip.in / TempPass123!, open /admin/site, switch to the Shipping tab, and confirm 'PhonePe (online card/UPI) enabled' is ON.",
|
|
378
|
+
"Sign in as admin@letitrip.in / TempPass123!, open /admin/site, switch to the Shipping tab, and confirm the toggle reading 'PhonePe (online card/UPI) enabled ā disabled by default, manual payment is the default' is ON. It is OFF on a fresh install, which is what that label is telling you.",
|
|
369
379
|
STEP_SIGNIN_BUYER,
|
|
370
380
|
"Open /products/product-beyblade-burst-valkyrie (ā¹999) and click 'Add to cart'.",
|
|
371
381
|
"Open /checkout and complete the address step.",
|
|
@@ -443,7 +453,8 @@ export const moneyFlowsPages = [
|
|
|
443
453
|
roles: ["buyer"],
|
|
444
454
|
startPage: "/checkout",
|
|
445
455
|
steps: [
|
|
446
|
-
"Sign in as admin@letitrip.in / TempPass123
|
|
456
|
+
"Sign in as admin@letitrip.in / TempPass123! and confirm PhonePe is enabled on Site Settings, Shipping tab.",
|
|
457
|
+
"š The out-of-stock policy is NOT an admin setting ā there is no Checkout tab in Site Settings, and no control by the name this step used to give. It is a BUYER choice made at checkout, under the heading 'If an item goes out of stock', offering \"Ship what's available and refund the rest\" and \"Cancel my whole order\". The first is already the default, so nothing needs changing; just confirm it is selected when you reach checkout.",
|
|
447
458
|
STEP_SIGNIN_BUYER,
|
|
448
459
|
"Add product-beyblade-burst-valkyrie (ā¹999, stock available) and product-beyblade-original-dranzer-s (ā¹1,499) to the cart.",
|
|
449
460
|
"Open /checkout, complete the address step, and click 'Pay Online (PhonePe)'.",
|
|
@@ -537,13 +548,13 @@ export const moneyFlowsPages = [
|
|
|
537
548
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
538
549
|
"Open /admin/bundles and start a new bundle.",
|
|
539
550
|
"Name it 'QA cross-store bundle'.",
|
|
540
|
-
"Add product-beyblade-burst-valkyrie (store-beyblade-arena)
|
|
541
|
-
"
|
|
551
|
+
"Add THREE members, because the form refuses fewer: product-beyblade-burst-valkyrie and product-beyblade-x-wizard-arrow (both store-beyblade-arena), plus prizedraw-beyblade-mystery-box (store-letitrip-official) ā a different store.",
|
|
552
|
+
"š Two members is not enough to reach the cross-store check at all. BUNDLE_MIN_ITEMS is 3, and a two-member save is refused with 'Select at least 3 products (currently 2).' ā a refusal for the wrong reason, which reads as a pass if the message is not read.",
|
|
542
553
|
"Set the bundle price to 1000 and click Save.",
|
|
543
554
|
],
|
|
544
555
|
inputs: { bundleName: "QA cross-store bundle", bundlePrice: 1000 },
|
|
545
556
|
expectedBehaviour: "The save is refused because an order belongs to exactly one seller, and a bundle spanning two stores would produce an order containing another seller's product ā with no notification, no shipping resolution and no payout for that second seller. The refusal is at save time, not at add-to-cart time, so bundles already stored keep working.",
|
|
546
|
-
expectedUiState: "An error names the
|
|
557
|
+
expectedUiState: "An error names how many sellers the members span, or at least states that a bundle cannot span sellers. Read the message: a minimum-members error means the cross-store guard never ran. The editor stays open with the entered members intact rather than clearing.",
|
|
547
558
|
endResult: "After reloading /admin/bundles there is no bundle named 'QA cross-store bundle'.",
|
|
548
559
|
href: "/admin/bundles",
|
|
549
560
|
},
|
|
@@ -60,7 +60,15 @@ export const authored = {
|
|
|
60
60
|
bio: "Tester bio update for QA.",
|
|
61
61
|
},
|
|
62
62
|
expectedBehaviour: "The save succeeds and both fields are written to the user document. The header account menu picks up the new display name too, since it reads the same field.",
|
|
63
|
-
expectedUiState:
|
|
63
|
+
expectedUiState:
|
|
64
|
+
/*
|
|
65
|
+
* 'Mock User 3', not 'Rehan Sheikh'. Every seeded persona's displayName
|
|
66
|
+
* is "Mock User N" ā the human-looking names live only in the email
|
|
67
|
+
* addresses ā so 'Rehan Sheikh' occurs nowhere in the seed and the
|
|
68
|
+
* revert half of this assertion could never fail. A save that silently
|
|
69
|
+
* reverted would still have passed.
|
|
70
|
+
*/
|
|
71
|
+
"After reload, Profile Details shows the display name 'QA Profile account-auth-profile-settings-edit-profile' and the bio 'Tester bio update for QA.'. Neither field has reverted to 'Mock User 3' (user-yugi-muto's seeded display name) or to an empty bio.",
|
|
64
72
|
expectedData: {
|
|
65
73
|
displayName: "QA Profile account-auth-profile-settings-edit-profile",
|
|
66
74
|
bio: "Tester bio update for QA.",
|
|
@@ -112,8 +120,19 @@ export const authored = {
|
|
|
112
120
|
"Sign in as rehan.sheikh@gmail.com / TempPass123!.",
|
|
113
121
|
"Open /user/settings.",
|
|
114
122
|
"Click the 'Notifications' tab.",
|
|
115
|
-
|
|
116
|
-
|
|
123
|
+
/*
|
|
124
|
+
* Both switches named with their SECTION, because the panel renders two
|
|
125
|
+
* independent lists ā 'Notification channels' (Email / WhatsApp / SMS) and
|
|
126
|
+
* the per-type list (Offers, Promotions, Reviews, ā¦) ā and 'the Email
|
|
127
|
+
* switch' alone does not say which list to look in.
|
|
128
|
+
*
|
|
129
|
+
* The channels section is CONDITIONAL: it renders only when an admin has
|
|
130
|
+
* enabled at least one external channel in Site Settings. Its absence is
|
|
131
|
+
* therefore a precondition that was not met, not a defect ā which is why
|
|
132
|
+
* the step says to record it rather than to hunt for the control.
|
|
133
|
+
*/
|
|
134
|
+
"Set the 'Email' switch under 'Notification channels' to off. If that whole section is absent, record it and skip to the next step ā an admin has disabled every external channel, which is a precondition, not a bug.",
|
|
135
|
+
"Set the 'Promotions' switch in the per-type list to off.",
|
|
117
136
|
"Click 'Save preferences'.",
|
|
118
137
|
"Reload the page.",
|
|
119
138
|
"Click the 'Notifications' tab.",
|
|
@@ -19,18 +19,23 @@ export const authored = {
|
|
|
19
19
|
startPage: "/user/addresses",
|
|
20
20
|
steps: [
|
|
21
21
|
"Sign in as rehan.sheikh@gmail.com / TempPass123!.",
|
|
22
|
-
"Open /user/addresses
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
22
|
+
"Open /user/addresses. No address is seeded for this buyer, so create two ā one labelled 'Home' at '7 Test Lane', Indore, Madhya Pradesh, 452010, and one labelled 'Office' at '8 Test Lane', Bengaluru, Karnataka, 560001.",
|
|
23
|
+
"Back on /user/addresses, read the controls above the list: a Search input and a Label select. There is no filter drawer and no filter badge.",
|
|
24
|
+
"Type Indore in Search and read which addresses remain.",
|
|
25
|
+
"Clear Search, type zzzznope, and read the count.",
|
|
26
|
+
"Clear Search. Choose Office in the Label select and read which addresses remain.",
|
|
27
|
+
"Set the Label select back to 'All labels' and confirm both return.",
|
|
28
|
+
"Finally type Office into Search, with the Label select on All labels, and read the result.",
|
|
29
29
|
],
|
|
30
|
-
inputs: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
inputs: {
|
|
31
|
+
firstLabel: "Home",
|
|
32
|
+
secondLabel: "Office",
|
|
33
|
+
searchHit: "Indore",
|
|
34
|
+
searchMiss: "zzzznope",
|
|
35
|
+
},
|
|
36
|
+
expectedBehaviour: "Both controls narrow the list they sit above, and both are in-memory over the already-fetched addresses rather than a refetch. Search matches the name, street, city, state and postal code; the Label select matches the label, which Search deliberately does not cover. A control that changes nothing is the failure this case exists for.",
|
|
37
|
+
expectedUiState: "Searching Indore leaves the Indore address and hides the Bengaluru one. 'zzzznope' leaves NONE ā that is the control that separates filtering from returning everything. Choosing Office in the Label select leaves only the Office address, and 'All labels' restores both.\n\nš Typing Office into SEARCH returns nothing, and that is CORRECT: search covers name, street, city, state and postal code, not the label. Do not record it as broken search. The Label select does not appear at all until at least one address has a label.",
|
|
38
|
+
expectedData: { searchMissCount: 0, labelFilteredCount: 1 },
|
|
39
|
+
endResult: "Both controls are cleared. Delete the two addresses afterwards ā the addresses collection is PRESERVE tier and is never wiped between runs, so anything left here accumulates forever."
|
|
35
40
|
},
|
|
36
41
|
};
|
|
@@ -28,11 +28,12 @@ export const authored = {
|
|
|
28
28
|
"Leave the City and State fields empty.",
|
|
29
29
|
"Type 560001 in the PIN code field.",
|
|
30
30
|
"Wait one second without typing anything else.",
|
|
31
|
-
"Read the City and State
|
|
31
|
+
"Read the City field and the 'State / region' picker, then read the rest of the form.",
|
|
32
|
+
"If neither filled, open the network panel and find the api.zippopotam.us request before judging.",
|
|
32
33
|
],
|
|
33
|
-
inputs: { country: "India", pincode: "560001" },
|
|
34
|
+
inputs: { country: "India", pincode: "560001", expectedState: "Karnataka" },
|
|
34
35
|
expectedBehaviour: "A complete PIN code triggers one debounced lookup that fills only City and State. Nothing else on the form moves ā a lookup that also rewrites the street or the label is doing more than it was asked and the user has no way to undo it.",
|
|
35
|
-
expectedUiState: "Within about half a second City and State are filled
|
|
36
|
+
expectedUiState: "Within about half a second City and 'State / region' are filled for 560001; the state reads Karnataka. Record the city string rather than asserting one ā it is whatever Zippopotam returns for that code. Every other field holds exactly what it held before, including any name or street already typed. No spinner is left running and no error appears.",
|
|
36
37
|
expectedData: { pincode: "560001" },
|
|
37
38
|
endResult: "Nothing is saved unless the form is submitted. Two dead modules once carried the shape of a lookup that had been removed, which is why this looked built long before it was.",
|
|
38
39
|
},
|
|
@@ -50,7 +51,7 @@ export const authored = {
|
|
|
50
51
|
],
|
|
51
52
|
inputs: { country: "India", city: "Whitefield", pincode: "560001" },
|
|
52
53
|
expectedBehaviour: "Autofill only ever lands in an EMPTY field. A lookup that corrects deliberate typing produces a bug the user cannot report ā they simply watch their own text disappear and assume they mistyped.",
|
|
53
|
-
expectedUiState: "City still reads 'Whitefield' after the lookup completes. State, which was empty, has been filled. Nothing flashes the typed value away and back.",
|
|
54
|
+
expectedUiState: "City still reads 'Whitefield' after the lookup completes. 'State / region', which was empty, has been filled. Nothing flashes the typed value away and back.",
|
|
54
55
|
expectedData: { city: "Whitefield" },
|
|
55
56
|
endResult: "Nothing persists until the form is submitted. 'Whitefield' is chosen because it is a real Bengaluru locality that the 560001 lookup will not return ā a city name the lookup agrees with would prove nothing.",
|
|
56
57
|
},
|
|
@@ -63,12 +64,12 @@ export const authored = {
|
|
|
63
64
|
"Select India as the country.",
|
|
64
65
|
"Type 999999 in the PIN code field.",
|
|
65
66
|
"Wait two seconds and read the whole form.",
|
|
66
|
-
"Type 'QA Address lookup-miss' as the name, '3 Test Lane' as the street
|
|
67
|
+
"Type 'QA Address lookup-miss' as the name, '3 Test Lane' as the street and 'Indore' as the city, then SELECT 'Madhya Pradesh' from the 'State / region' picker ā for India it is a 36-option list, not a text box.",
|
|
67
68
|
"Save the address.",
|
|
68
69
|
],
|
|
69
70
|
inputs: { country: "India", pincode: "999999", city: "Indore", state: "Madhya Pradesh" },
|
|
70
71
|
expectedBehaviour: "An unknown but well-formed code resolves to nothing and the form carries on. The code is still valid ā six digits ā so it must save; only the convenience lookup missed.",
|
|
71
|
-
expectedUiState: "No error banner, no red PIN code field, no disabled Save. City and State
|
|
72
|
+
expectedUiState: "No error banner, no red PIN code field, no disabled Save. City stays empty and editable and the 'State / region' picker stays on its placeholder, and the typed and selected values are accepted. The address saves with PIN code 999999, which matches the IN postal pattern of six digits not starting with zero.",
|
|
72
73
|
expectedData: { pincode: "999999", saved: true },
|
|
73
74
|
endResult: "The address exists in /user/addresses after a reload. Delete it afterwards. Treating a miss as a validation failure would block every address in the many areas the lookup does not cover.",
|
|
74
75
|
},
|
|
@@ -21,7 +21,7 @@ export const authored = {
|
|
|
21
21
|
"Sign in as rehan.sheikh@gmail.com / TempPass123!.",
|
|
22
22
|
"Open /user/addresses/new.",
|
|
23
23
|
"Select India as the country.",
|
|
24
|
-
"Type 'QA Address letters-rejected' as the name, '4 Test Lane' as the street
|
|
24
|
+
"Type 'QA Address letters-rejected' as the name, '4 Test Lane' as the street and 'Indore' as the city, then SELECT 'Madhya Pradesh' from the 'State / region' picker.",
|
|
25
25
|
"Type abcdef in the PIN code field.",
|
|
26
26
|
"Click Save.",
|
|
27
27
|
"Read where the error appears.",
|
|
@@ -40,7 +40,7 @@ export const authored = {
|
|
|
40
40
|
"Open /user/addresses/new.",
|
|
41
41
|
"Select Canada as the country.",
|
|
42
42
|
"Read the label on the postal code field.",
|
|
43
|
-
"Type K1A 0B1 in that field
|
|
43
|
+
"Type K1A 0B1 in that field, then fill the name 'QA Address country-rule', the street '5 Test Lane' and the city 'Ottawa', and SELECT 'Ontario' from the 'State / region' picker ā Canada has one too.",
|
|
44
44
|
"Click Save.",
|
|
45
45
|
"Open the address for editing, switch the country to India, and type K1A 0B1 in the PIN code field.",
|
|
46
46
|
"Type 560001 instead and read the field.",
|
|
@@ -63,8 +63,14 @@ export const authored = {
|
|
|
63
63
|
"Click Save.",
|
|
64
64
|
"Open the address for editing, replace the postal code with ABC-1234, and save again.",
|
|
65
65
|
],
|
|
66
|
-
inputs: {
|
|
67
|
-
|
|
66
|
+
inputs: {
|
|
67
|
+
country: "United Arab Emirates",
|
|
68
|
+
firstCode: "00000",
|
|
69
|
+
secondCode: "ABC-1234",
|
|
70
|
+
postalMin: 3,
|
|
71
|
+
postalMax: 12,
|
|
72
|
+
},
|
|
73
|
+
expectedBehaviour: "For a country with no known format any code of 3 to 12 characters is accepted, which is the POSTAL_MIN and POSTAL_MAX fallback in the one shared validator. A postal rule exists to catch a typo, never to refuse a real address whose pattern we simply do not have ā and an EMPTY code is still refused, because a blank is not a postal code in any country.",
|
|
68
74
|
expectedUiState: "Both codes save without an error. The field is not marked red and Save is never disabled. What must not happen is India's six-digit rule being applied by default to a country it does not describe.",
|
|
69
75
|
expectedData: { bothCodesAccepted: true },
|
|
70
76
|
endResult: "The address holds ABC-1234 after a reload. Delete it afterwards.",
|
|
@@ -21,15 +21,15 @@ export const authored = {
|
|
|
21
21
|
"Sign in as rehan.sheikh@gmail.com / TempPass123!.",
|
|
22
22
|
"Open /user/addresses/new.",
|
|
23
23
|
"Select India as the country.",
|
|
24
|
-
"Click the State field and try to type free text into it.",
|
|
24
|
+
"Click the 'State / region' field and try to type free text into it.",
|
|
25
25
|
"Type karn in the picker's search box and read the options offered.",
|
|
26
26
|
"Select Karnataka.",
|
|
27
27
|
"Change the country to United Arab Emirates.",
|
|
28
|
-
"Click
|
|
28
|
+
"Click 'State / region' again and try to type free text.",
|
|
29
29
|
],
|
|
30
30
|
inputs: { country: "India", stateSearch: "karn", state: "Karnataka" },
|
|
31
31
|
expectedBehaviour: "Where a country's regions are enumerated, State is a searchable picker; where they are not, it is a plain text box. The picker is what stops one column holding 'Karnataka', 'karnataka' and 'KA' depending on which screen created the row ā which is what happened while this form and the seller drawer wrote free text into the same field the admin editor filled from a 36-option list.",
|
|
32
|
-
expectedUiState: "With India selected, State cannot be typed into freely and searching 'karn' narrows a list of India's 36 states and union territories to Karnataka. With United Arab Emirates selected
|
|
32
|
+
expectedUiState: "With India selected, 'State / region' cannot be typed into freely and searching 'karn' narrows a list of India's 36 states and union territories to Karnataka. With United Arab Emirates selected the same label becomes an ordinary text input that accepts anything, because the UAE has no subdivisions in the registry.",
|
|
33
33
|
expectedData: { indiaStateOptionCount: 36 },
|
|
34
34
|
endResult: "Nothing persists unless the form is submitted. A picker offering 28 or 29 options is missing the union territories.",
|
|
35
35
|
},
|
|
@@ -40,15 +40,20 @@ export const authored = {
|
|
|
40
40
|
"Sign in as rehan.sheikh@gmail.com / TempPass123!.",
|
|
41
41
|
"Open /user/addresses/new.",
|
|
42
42
|
"Select India as the country.",
|
|
43
|
-
"Select Karnataka in the State picker.",
|
|
43
|
+
"Select Karnataka in the 'State / region' picker.",
|
|
44
44
|
"Change the country to Canada.",
|
|
45
|
-
"Read the State field.",
|
|
45
|
+
"Read the 'State / region' field, and count the options it now offers.",
|
|
46
46
|
"Change the country back to India and read it again.",
|
|
47
47
|
],
|
|
48
|
-
inputs: {
|
|
48
|
+
inputs: {
|
|
49
|
+
firstCountry: "India",
|
|
50
|
+
state: "Karnataka",
|
|
51
|
+
secondCountry: "Canada",
|
|
52
|
+
canadaOptionCount: 13,
|
|
53
|
+
},
|
|
49
54
|
expectedBehaviour: "Changing the country clears the state, because a state only means something inside the country it belongs to. An address carrying Karnataka under Canada is a row nothing can group, rate for shipping, or tax correctly.",
|
|
50
|
-
expectedUiState: "After switching to Canada
|
|
51
|
-
expectedData: { stateAfterCountryChange: "" },
|
|
55
|
+
expectedUiState: "After switching to Canada 'State / region' is empty and offers 13 Canadian options, its 10 provinces and 3 territories ā an emptied field still listing India's 36 would mean the clear ran and the option source did not. Switching back to India leaves it empty too: the clear fires on every country change and does not restore 'Karnataka', which would reintroduce the mismatch it exists to prevent.",
|
|
56
|
+
expectedData: { stateAfterCountryChange: "", canadaOptionCount: 13 },
|
|
52
57
|
endResult: "Nothing persists unless the form is submitted. A retained 'Karnataka' under Canada is the failure.",
|
|
53
58
|
},
|
|
54
59
|
};
|
|
@@ -13,27 +13,65 @@
|
|
|
13
13
|
* @tag consumers:seed-data/authored/index.ts
|
|
14
14
|
* @tag sideEffects:none
|
|
15
15
|
*/
|
|
16
|
+
/*
|
|
17
|
+
* Verified against the drawer on 2026-09-29, and every assertion the case
|
|
18
|
+
* makes is correct: the trigger reads 'Request Unban' and renders only when
|
|
19
|
+
* the address is banned; the drawer's field is 'Reason for unban request';
|
|
20
|
+
* its submit is 'Submit request' and is DELIBERATELY not disabled on an empty
|
|
21
|
+
* note, with a source comment saying why; and addressUnbanRequestSchema
|
|
22
|
+
* carries a .min(20) whose message is
|
|
23
|
+
*
|
|
24
|
+
* 'Explain what happened ā at least 20 characters. This is all the reviewer sees.'
|
|
25
|
+
*
|
|
26
|
+
* So the 20 the case names is real, and so is the error text.
|
|
27
|
+
*
|
|
28
|
+
* š WHAT IS MISSING IS THE PRECONDITION. This buyer has no addresses at all,
|
|
29
|
+
* and no seeded address is banned, so 'find an address showing a banned state'
|
|
30
|
+
* finds nothing. Worse, there is no UI path to ban one: ban-address is a row
|
|
31
|
+
* action on the Banned Addresses queue, which is fed only by
|
|
32
|
+
* GET /api/admin/addresses?banStatus=..., and its chips are banned,
|
|
33
|
+
* unban_requested and suspicious. The All chip sends no banStatus and the
|
|
34
|
+
* route then returns an empty list ā so that queue can only act on rows
|
|
35
|
+
* already in it, and nothing in the product puts a clean address there.
|
|
36
|
+
*
|
|
37
|
+
* The one real producer is hardBanCascade, which bans every address belonging
|
|
38
|
+
* to a hard-banned user. But /unban reverses exactly that via
|
|
39
|
+
* unbanAutoForOwner, so lifting the user's ban clears the address too. The two
|
|
40
|
+
* states this case needs together ā an ACTIVE buyer with a BANNED address ā
|
|
41
|
+
* cannot be reached from the UI at all.
|
|
42
|
+
*
|
|
43
|
+
* So the case attempts the setup, RECORDS the gap, and runs the real
|
|
44
|
+
* assertions only if a banned address does exist. Recorded in OUTOFSCOPE.
|
|
45
|
+
*/
|
|
16
46
|
export const authored = {
|
|
17
47
|
"checklist-addresses-unban-request-empty-note-says-why": {
|
|
18
48
|
roles: ["buyer"],
|
|
19
49
|
startPage: "/user/addresses",
|
|
20
50
|
steps: [
|
|
21
|
-
"Sign in as
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
51
|
+
"Sign in as admin@letitrip.in / TempPass123! and open the Banned Addresses queue.",
|
|
52
|
+
"Select the All chip and read how many rows are listed, then try the Banned, Unban Requested and Suspicious chips in turn.",
|
|
53
|
+
"Record whether ANY route in the product lets an admin ban a single address on an otherwise-active account ā if none does, this case cannot be set up, and that is the finding.",
|
|
54
|
+
"If a banned address belonging to rehan.sheikh@gmail.com does exist, continue. Otherwise stop here and answer no, quoting what each chip returned.",
|
|
55
|
+
"Sign in as rehan.sheikh@gmail.com / TempPass123! and open /user/addresses.",
|
|
56
|
+
"Find the banned address and click 'Request Unban'.",
|
|
57
|
+
"Leave 'Reason for unban request' completely empty and click 'Submit request'. The button must be clickable, not greyed out.",
|
|
58
|
+
"Read where the message appears.",
|
|
59
|
+
"Type 'Too short' and click 'Submit request' again.",
|
|
60
|
+
"Type 'This address was banned in error; it is my registered home address.' and submit.",
|
|
28
61
|
],
|
|
29
62
|
inputs: {
|
|
30
63
|
shortNote: "Too short",
|
|
31
64
|
validNote: "This address was banned in error; it is my registered home address.",
|
|
32
65
|
minimumLength: 20,
|
|
66
|
+
trigger: "Request Unban",
|
|
67
|
+
field: "Reason for unban request",
|
|
68
|
+
submit: "Submit request",
|
|
33
69
|
},
|
|
34
|
-
expectedBehaviour: "Submitting with an empty or too-short note fails loudly
|
|
35
|
-
expectedUiState: "The
|
|
36
|
-
expectedData: { minimumNoteLength: 20 },
|
|
37
|
-
endResult: "
|
|
70
|
+
expectedBehaviour: "Submitting with an empty or too-short note fails loudly ON THE FIELD and names the 20-character minimum. The note is the entire case a reviewer reads, so a request without one is not a request ā but a greyed-out button with a bare return in its handler tells the user nothing about why, which is what this replaced.",
|
|
71
|
+
expectedUiState: "The submit button is enabled even with the box empty. Pressing it puts an error on the note field naming the minimum length; the schema message reads 'Explain what happened ā at least 20 characters. This is all the reviewer sees.' 'Too short' produces the same error. The full sentence is accepted and a confirmation appears.\n\nš If no banned address exists, answer NO for the precondition rather than null: there is no UI path to ban one, which is a product gap and not a missing test channel.",
|
|
72
|
+
expectedData: { minimumNoteLength: 20, submitEnabledWhenEmpty: true },
|
|
73
|
+
endResult: "If the request was submitted, the address reads as unban-requested and carries the full note. A silently disabled button that does nothing when pressed is the failure ā the user cannot tell it from a broken page.",
|
|
74
|
+
needsReview: true,
|
|
75
|
+
reviewNote: "The assertions are verified correct against the drawer and its schema. What is unsettled is whether an admin should be able to ban a single address on an otherwise-active account at all; today they cannot, so this case has no reachable setup.",
|
|
38
76
|
},
|
|
39
77
|
};
|
|
@@ -31,6 +31,8 @@ export const authored = {
|
|
|
31
31
|
"Press Save.",
|
|
32
32
|
"Reload the page and open the same coupon again.",
|
|
33
33
|
"Read the discount value, the usage limits and the validity dates.",
|
|
34
|
+
"Restore the description by deleting the word you appended, then save again. coupons is SEED_OWNED so a reseed would fix it either way, but a run does not reseed ā three earlier runs left this text reading 'roundtrip roundtrip roundtrip', and a later case reading this description would be reading what this one wrote.",
|
|
35
|
+
"Note the editor keeps Limits and Validity in COLLAPSIBLE sections. After the reload they are closed, so their fields are absent from the page rather than empty ā read those four numbers off the coupon card in the list, which renders them directly, or expand the sections again before judging.",
|
|
34
36
|
],
|
|
35
37
|
inputs: { couponCode: "ARENA25", descriptionSuffix: "roundtrip" },
|
|
36
38
|
expectedBehaviour: "Editing one field writes that field and leaves the rest as they were. A handler that merges a partial validity or restrictions object wholesale wipes whatever the caller did not resend ā and a handler that never calls the repository at all returns a 200 that echoes the submission back, which is indistinguishable from a save.",
|
|
@@ -48,7 +50,8 @@ export const authored = {
|
|
|
48
50
|
"Append the sentence: Edited during a round-trip check.",
|
|
49
51
|
"Press Save.",
|
|
50
52
|
"Reload and reopen the same FAQ.",
|
|
51
|
-
"Open the public /faqs page and find the same question.",
|
|
53
|
+
"Open the public /faqs page and find the same question. Answers are collapsed accordions ā click the question to expand it before reading.",
|
|
54
|
+
"Remove the sentence you appended and save again. Append it with the KEYBOARD at the caret, never by filling the field: this is a contenteditable holding real HTML, and a fill replaces the whole answer with plain text, destroying five bold runs on a buyer-facing page.",
|
|
52
55
|
],
|
|
53
56
|
inputs: { faqSlug: "coupons-and-discounts", appended: "Edited during a round-trip check." },
|
|
54
57
|
expectedBehaviour: "The answer is a structured value, not a bare string. A create path that wraps it and an update path that writes it flat produce a document every reader expects to be an object and finds is a string ā which renders as nothing, or as raw markup, on the public page.",
|
|
@@ -61,12 +64,13 @@ export const authored = {
|
|
|
61
64
|
startPage: "/admin/categories",
|
|
62
65
|
steps: [
|
|
63
66
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
64
|
-
"Open /categories/x-starters as a visitor in a second tab and write down its product count.",
|
|
65
|
-
"In the first tab open /admin/categories and find X Starters.",
|
|
67
|
+
"Open /categories/category-x-starters as a visitor in a second tab and write down its product count. Note the FULL id ā /categories/x-starters serves 'Category Not Found'; every category id carries the category- prefix.",
|
|
68
|
+
"In the first tab open /admin/categories and find Starter Sets ā that is the display name of category-x-starters; there is no category called 'X Starters'.",
|
|
66
69
|
"Write down its parent and how many children it has.",
|
|
67
70
|
"Open its Edit action, change ONLY its description, and press Save.",
|
|
68
71
|
"Reload the admin list and reopen it.",
|
|
69
72
|
"Reload the public category page in the second tab.",
|
|
73
|
+
"Restore the description by removing the word you appended, then save again. Three of the cases on this page append a marker and this one accumulated 'roundtrip' from an earlier run ā categories is SEED_OWNED, but a run does not reseed.",
|
|
70
74
|
],
|
|
71
75
|
inputs: { categorySlug: "x-starters" },
|
|
72
76
|
expectedBehaviour: "A rename or a description edit touches that field alone. The hierarchy fields ā parent, children, ancestors, tier ā are derived and must never be rewritten from form state, and the product count is derived from a trigger rather than typed.",
|
|
@@ -82,11 +86,11 @@ export const authored = {
|
|
|
82
86
|
"Open /admin/users and find the account rehan.sheikh@gmail.com.",
|
|
83
87
|
"Open its Edit action, turn BOTH tester flags on, and save ā no seeded account carries them, so the case has to set up the field it is about.",
|
|
84
88
|
"Reopen the same account and confirm both flags read as on.",
|
|
85
|
-
"
|
|
89
|
+
"WRITE DOWN the bio exactly as it stands, then change ONLY the bio ā append the word roundtrip.",
|
|
86
90
|
"Press Save.",
|
|
87
91
|
"Reload the list and reopen the same account.",
|
|
88
92
|
"Read the tester flags again.",
|
|
89
|
-
"Turn both tester flags back off
|
|
93
|
+
"Turn both tester flags back off AND restore the bio to what you wrote down, then save. users is PRESERVE-tier ā never wiped, never reseeded ā so an appended word left behind is permanent, and after a few runs the bio reads roundtrip roundtrip roundtrip.",
|
|
90
94
|
],
|
|
91
95
|
inputs: { account: "rehan.sheikh@gmail.com", bioSuffix: "roundtrip" },
|
|
92
96
|
expectedBehaviour: "An editor seeded from a LIST row can only send back what that row carried. If the list serializer omits a field the update schema accepts, the editor state defaults to false and the save rewrites it ā so editing any other field silently strips it. That is why the check is on a field the form did not touch.",
|
|
@@ -101,7 +105,7 @@ export const authored = {
|
|
|
101
105
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
102
106
|
"Open /admin/stores and find Beyblade Arena.",
|
|
103
107
|
"Note whether it is marked Verified and whether it is Featured.",
|
|
104
|
-
"
|
|
108
|
+
"Note the admin notes as they stand, then open its Edit action and change ONLY the admin notes ā append the word roundtrip. Restore them before you finish. Unlike the user bio in the sibling case, stores is SEED_OWNED and a reseed would put this back either way ā so this is hygiene rather than damage control, and it keeps a later case from reading notes this one wrote.",
|
|
105
109
|
"Press Save.",
|
|
106
110
|
"Reload the list.",
|
|
107
111
|
"Read the Verified and Featured markers again.",
|