@mohasinac/appkit 4.42.1 → 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/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/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
|
@@ -10,6 +10,17 @@
|
|
|
10
10
|
* A page with no nav entry and a nav entry with no page are the two halves of one
|
|
11
11
|
* defect, and both ship as "half done" without anything failing.
|
|
12
12
|
*
|
|
13
|
+
* 🛑 Two fixtures pinned on 2026-09-29. Exactly ONE named carousel is seeded
|
|
14
|
+
* — carousel-hero-default, 'Homepage Hero', status active — so 'the first
|
|
15
|
+
* carousel' is that one, and its status toggles between exactly two values,
|
|
16
|
+
* active and draft. The 'Edit carousel' control lives on the DETAIL page
|
|
17
|
+
* /admin/carousels/{id}, not on the list, which only links to each row.
|
|
18
|
+
*
|
|
19
|
+
* Verified before asserting: /admin/events/new really does derive its type list
|
|
20
|
+
* from ALL_EVENT_TYPES now, so Lottery is in the picker and the first case
|
|
21
|
+
* should pass. Its own file still carries the note about the local copy that
|
|
22
|
+
* omitted it, which is why lottery events could once only come from the seeder.
|
|
23
|
+
*
|
|
13
24
|
* @tag domain:tester
|
|
14
25
|
* @tag layer:seed
|
|
15
26
|
* @tag pattern:none
|
|
@@ -29,7 +40,7 @@ export const authored = {
|
|
|
29
40
|
"Open /admin/lotteries and open the new event's slot editor.",
|
|
30
41
|
"Add three slots numbered 1, 2 and 3, give each a name, and save.",
|
|
31
42
|
"Sign out and sign in as rehan.sheikh@gmail.com / TempPass123!.",
|
|
32
|
-
"Open the new
|
|
43
|
+
"Open /lottery/{the new event id} and pull slot 1.",
|
|
33
44
|
],
|
|
34
45
|
inputs: { eventTitle: "QA Event lottery-created-without-seeding", slotCount: 3, pulledSlot: 1 },
|
|
35
46
|
expectedBehaviour: "A lottery can be built entirely through the UI. The event and its slots are edited in different places on purpose — the slot editor has no fields for title, dates, status or media, so a lottery created there alone would have no dates, and the draw window is measured from them.",
|
|
@@ -42,18 +53,24 @@ export const authored = {
|
|
|
42
53
|
startPage: "/admin/carousels",
|
|
43
54
|
steps: [
|
|
44
55
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
45
|
-
"Open /admin/carousels and read the list.",
|
|
46
|
-
"Open
|
|
47
|
-
"Change
|
|
56
|
+
"Open /admin/carousels and read the list — one carousel is seeded, 'Homepage Hero', with status active.",
|
|
57
|
+
"Open /admin/carousels/carousel-hero-default and find its 'Edit carousel' control. It is on this detail page, not on the list.",
|
|
58
|
+
"Change the name to 'QA Carousel can-be-renamed' and the status from active to draft.",
|
|
48
59
|
"Save.",
|
|
49
|
-
"RELOAD /admin/carousels and read the name and status.",
|
|
50
|
-
"Set
|
|
60
|
+
"RELOAD /admin/carousels and read the name and status in the row.",
|
|
61
|
+
"Set the name back to 'Homepage Hero' and the status back to active, then reload once more to confirm.",
|
|
51
62
|
],
|
|
52
|
-
inputs: {
|
|
63
|
+
inputs: {
|
|
64
|
+
carousel: "carousel-hero-default",
|
|
65
|
+
originalName: "Homepage Hero",
|
|
66
|
+
newName: "QA Carousel can-be-renamed",
|
|
67
|
+
statusBefore: "active",
|
|
68
|
+
statusAfter: "draft",
|
|
69
|
+
},
|
|
53
70
|
expectedBehaviour: "A named carousel can be edited after creation. The group editor was create-only, so a carousel's name was fixed for its entire life with no path to change it — and /admin/carousels rendered the flat slide editor rather than a list of named carousels at all.",
|
|
54
|
-
expectedUiState: "The list page shows named carousels, not a bare slide editor. 'Edit carousel' exists, opens with the current values, and after saving and reloading the
|
|
71
|
+
expectedUiState: "The list page shows named carousels, not a bare slide editor. 'Edit carousel' exists on the detail page, opens with the current values, and after saving and reloading the list row reads 'QA Carousel can-be-renamed' with status draft. Status has exactly two values here — active and draft — so a picker offering more is itself worth noting.",
|
|
55
72
|
expectedData: { editControlPresent: true },
|
|
56
|
-
endResult: "The name
|
|
73
|
+
endResult: "The name reads 'Homepage Hero' and the status is active again. Other cases read this carousel by that seeded name, and it is the only one there is.",
|
|
57
74
|
},
|
|
58
75
|
"checklist-page-wiring-reachability-grouped-listing-members-editable-from-its-own-page": {
|
|
59
76
|
roles: ["seller"],
|
|
@@ -73,7 +90,7 @@ export const authored = {
|
|
|
73
90
|
member1: "product-beyblade-burst-valkyrie",
|
|
74
91
|
member2: "product-beyblade-x-wizard-arrow",
|
|
75
92
|
minActiveMembers: 2,
|
|
76
|
-
coverImage: "
|
|
93
|
+
coverImage: "/test-media/sample-image.png",
|
|
77
94
|
},
|
|
78
95
|
expectedBehaviour: "Members are chosen while the group is being created, in the same form. productIds was hardcoded to an empty array, so a group could only ever be created empty and populated from somewhere else — and neither the minimum-active-members nor the cover-image field had an input anywhere in the app.",
|
|
79
96
|
expectedUiState: "All three controls are present on the create form. After saving and reloading, the group holds both products, a minimum of 2, and the uploaded cover image. A group that saves with zero members is the failure.",
|
|
@@ -84,15 +101,15 @@ export const authored = {
|
|
|
84
101
|
roles: ["guest"],
|
|
85
102
|
startPage: "/",
|
|
86
103
|
steps: [
|
|
87
|
-
"
|
|
104
|
+
"Signed out, open /.",
|
|
88
105
|
"Click every link in the public header navigation in turn, reading each destination and going back.",
|
|
89
106
|
"Open the sidebar support links and click each one, reading each destination.",
|
|
90
107
|
"Scroll to the footer and click every link in every column, reading each destination.",
|
|
91
108
|
"Write down any link that lands on a not-found page, an empty shell, or back on the homepage.",
|
|
92
109
|
],
|
|
93
|
-
expectedBehaviour: "Every public-facing link resolves to a real page with content. The nav audit only ever checked the three portal sidebars, so not one of these
|
|
94
|
-
expectedUiState: "All
|
|
95
|
-
expectedData: { brokenLinkCount: 0 },
|
|
110
|
+
expectedBehaviour: "Every public-facing link resolves to a real page with content. The nav audit only ever checked the three portal sidebars, so not one of these 62 hrefs was covered by anything — a rename anywhere in the public surface rotted silently.",
|
|
111
|
+
expectedUiState: "All 62 destinations render real content. None shows a 404, none renders chrome with an empty body, and none silently redirects back to / — a redirect to the homepage looks like a working link and is not one.",
|
|
112
|
+
expectedData: { brokenLinkCount: 0, linksChecked: 62 },
|
|
96
113
|
endResult: "Read-only; nothing persists. Record every broken href in the comment, not just the first — one report naming all of them is worth more than a verdict.",
|
|
97
114
|
},
|
|
98
115
|
"checklist-page-wiring-reachability-user-tester-hub-reachable-from-user-sidebar": {
|
|
@@ -42,11 +42,12 @@ export const authored = {
|
|
|
42
42
|
"Open /auth/forgot-password in a private window with no session.",
|
|
43
43
|
"Click 'Send reset link' with the field empty and read where the error appears.",
|
|
44
44
|
"Type not-an-email and click 'Send reset link'.",
|
|
45
|
-
"Type divya.funko@gmail.com and click 'Send reset link'.",
|
|
46
|
-
"
|
|
45
|
+
"Type divya.funko@gmail.com and click 'Send reset link'. Read only the CONFIRMATION here — the generic 'if an account exists' message is the whole point of this step and needs no inbox.",
|
|
46
|
+
"🛑 DO NOT COMPLETE A RESET FOR A SEEDED PERSONA. The harness cannot open divya's inbox — both check-inbox.mjs and the emailEvents recorder key on TESTER_EMAIL_ID — and a reset that half-completes leaves a PRESERVE-tier login changed with nothing to restore it from, breaking every other case that signs in as her. For the link half, register a throwaway account on the harness mailbox using a plus-address and request the reset for THAT.",
|
|
47
|
+
"Capture a timestamp, request the link for the throwaway address, then run: node tester/scripts/check-inbox.mjs --since <that number> --subject \"reset\". Open the link it reports and read the page it lands on.",
|
|
47
48
|
"Submit the reset form with two different passwords typed into its two fields.",
|
|
48
49
|
"Set both fields to QaAuthPages654! and submit.",
|
|
49
|
-
"
|
|
50
|
+
"No restore is needed: the password changed belongs to a throwaway account, not to a seeded persona. Note in your comment that the account remains — the purge script matches only qa-signup+…@letitrip-qa.test, so a Gmail plus-address is not swept automatically.",
|
|
50
51
|
],
|
|
51
52
|
inputs: {
|
|
52
53
|
email: "divya.funko@gmail.com",
|
|
@@ -61,8 +62,8 @@ export const authored = {
|
|
|
61
62
|
roles: ["guest"],
|
|
62
63
|
startPage: "/auth/register",
|
|
63
64
|
steps: [
|
|
64
|
-
"Open /auth/register in a private window and create an account
|
|
65
|
-
"
|
|
65
|
+
"Open /auth/register in a private window and create an account on the HARNESS mailbox using a plus-address — no seeded persona can receive testable email, since both instruments key on TESTER_EMAIL_ID — with TestPass123! as the password.",
|
|
66
|
+
"Capture a timestamp before registering, then find the verification email: node tester/scripts/check-inbox.mjs --since <that number> --subject \"verify\". Read the exit code — 0 arrived, 1 did not (a defect), 2 could not open the mailbox (a harness problem, answer null).",
|
|
66
67
|
"Open the verification link and read the page it lands on.",
|
|
67
68
|
"Open /user and look for an unverified-email warning.",
|
|
68
69
|
"Open the same verification link a second time and read the page.",
|
|
@@ -100,7 +101,7 @@ export const authored = {
|
|
|
100
101
|
],
|
|
101
102
|
inputs: { productId: "product-beyblade-burst-valkyrie", price: 199, paymentMethod: "Cash on Delivery" },
|
|
102
103
|
expectedBehaviour: "The success page reads the real order rather than whatever the checkout had in memory, so it survives a reload. It is also order-scoped data, so a signed-out visitor with the URL must not be shown someone's delivery address.",
|
|
103
|
-
expectedUiState: "The page names an order number, '
|
|
104
|
+
expectedUiState: "The page names an order number, 'Beyblade Burst Valkyrie' at ₹1,899.00 — the product the steps actually add, the delivery address and a total. After the reload it shows the same. In the private window it does not render the order details — it asks for sign-in or reports not-found.",
|
|
104
105
|
expectedData: { orderTotal: 199 },
|
|
105
106
|
endResult: "The order exists in /user/orders. A success page that goes blank on reload was rendering from checkout state and not from the order.",
|
|
106
107
|
},
|
|
@@ -25,12 +25,21 @@ export const authored = {
|
|
|
25
25
|
"Open /bug-hunters in a private window with no session.",
|
|
26
26
|
"Read the rows from the top and note each name and confirmed-bug count.",
|
|
27
27
|
"Check that the counts descend from the first row to the last.",
|
|
28
|
-
|
|
28
|
+
/*
|
|
29
|
+
* 'Mock User 3' — the demo fixture's bugHunterName, matching
|
|
30
|
+
* bugHunterId "user-yugi-muto". This said 'Mock User 18', and there is no
|
|
31
|
+
* such persona: the seed runs Mock User 1–17 plus two Mock Employees.
|
|
32
|
+
*
|
|
33
|
+
* So the tester looked for a name that cannot be on the board, found
|
|
34
|
+
* nothing, and the case failed for a reason that is not a bug — while
|
|
35
|
+
* the leaderboard's real behaviour went unchecked.
|
|
36
|
+
*/
|
|
37
|
+
"Look for 'Mock User 3' and read the count beside it.",
|
|
29
38
|
"Look for the Claude tester account by name anywhere in the list.",
|
|
30
39
|
],
|
|
31
|
-
inputs: { seededHunter: "Mock User
|
|
40
|
+
inputs: { seededHunter: "Mock User 3", seededBugCount: 1 },
|
|
32
41
|
expectedBehaviour: "The board ranks testers by confirmed-bug count, highest first, and is fed by the demo fixture under Admin (Testing) → Bug Hunter Rewards. The bot account is excluded because it carries isBot — credit belongs to people, and a runner working the whole checklist would otherwise sit at the top forever.",
|
|
33
|
-
expectedUiState: "'Mock User
|
|
42
|
+
expectedUiState: "'Mock User 3' appears with 1 confirmed bug. Counts never increase as you read down the list. The Claude tester account does not appear at all.",
|
|
34
43
|
expectedData: { botAccountOnLeaderboard: 0 },
|
|
35
44
|
endResult: "Read-only; nothing persists. A bot row on the board is a fail even if the ordering is otherwise correct.",
|
|
36
45
|
},
|
|
@@ -39,7 +48,7 @@ export const authored = {
|
|
|
39
48
|
startPage: "/bug-hunters",
|
|
40
49
|
steps: [
|
|
41
50
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
42
|
-
"Open /admin/tester-feedback and find the confirmed bug credited to 'Mock User
|
|
51
|
+
"Open /admin/tester-feedback and find the confirmed bug credited to 'Mock User 3'.",
|
|
43
52
|
"Un-confirm it, so no confirmed bugs remain.",
|
|
44
53
|
"Open /bug-hunters in a private window with no session.",
|
|
45
54
|
"Read the page.",
|
|
@@ -47,7 +56,7 @@ export const authored = {
|
|
|
47
56
|
"Reload /bug-hunters in the private window.",
|
|
48
57
|
],
|
|
49
58
|
expectedBehaviour: "With nothing to rank the page shows a named empty state rather than a blank container or an error. Zero rows is a normal condition for this page on a new install, not a failure.",
|
|
50
|
-
expectedUiState: "With no confirmed bugs the page reads 'No confirmed bugs yet' or equivalent, inside the normal site chrome. It is not a blank white page, not an error boundary, and not a heading with nothing under it. After re-confirming, 'Mock User
|
|
59
|
+
expectedUiState: "With no confirmed bugs the page reads 'No confirmed bugs yet' or equivalent, inside the normal site chrome. It is not a blank white page, not an error boundary, and not a heading with nothing under it. After re-confirming, 'Mock User 3' is back.",
|
|
51
60
|
expectedData: { emptyStateShown: true },
|
|
52
61
|
endResult: "The confirmation is restored by the final step, so the leaderboard-loads case above still has its fixture. Leaving it un-confirmed silently breaks that case.",
|
|
53
62
|
},
|
|
@@ -40,7 +40,7 @@ export const authored = {
|
|
|
40
40
|
startPage: "/",
|
|
41
41
|
steps: [
|
|
42
42
|
"Open / on a device or emulator with WhatsApp installed.",
|
|
43
|
-
"Find the 'Join the Community'
|
|
43
|
+
"Find the WhatsApp community button — its default label is 'Join our WhatsApp community', not 'Join the Community' — and read the URL it points at.",
|
|
44
44
|
"Tap it and read what WhatsApp shows.",
|
|
45
45
|
"Open /contact and find its WhatsApp link.",
|
|
46
46
|
"Read that URL and compare it with the first.",
|
|
@@ -83,14 +83,14 @@ export const authored = {
|
|
|
83
83
|
steps: [
|
|
84
84
|
"Open /about in a private window with no session.",
|
|
85
85
|
"Scroll to the 'Our Values' section.",
|
|
86
|
-
"Count the values listed.",
|
|
86
|
+
"Count the values listed, and read whether each has a second smaller paragraph under its heading.",
|
|
87
87
|
"Read each one for a second, smaller paragraph beneath its heading.",
|
|
88
88
|
"Find the 'How we hold ourselves to this →' link and click it.",
|
|
89
89
|
"Read the address bar.",
|
|
90
90
|
],
|
|
91
91
|
inputs: { expectedValueCount: 6 },
|
|
92
92
|
expectedBehaviour: "The section carries six values, each with a subtitle paragraph, and links onward to the Ethics page — where the commitments are actually detailed. Values with no onward link are a statement with nothing behind it.",
|
|
93
|
-
expectedUiState: "Six values are shown, not three
|
|
93
|
+
expectedUiState: "Six values are shown, not three, each with a second smaller paragraph under its heading, and the link lands on /ethics. 🛑 EXPECT THREE WITHOUT SUBTITLES ON A FRESHLY-SEEDED ENVIRONMENT, and record that as a CONTENT gap rather than a UI failure: appkit/src/seed/site-settings-seed-data.ts ships three valueItems and none of them carries a `detail` field, so it predates this expansion. aboutContent lives in siteSettings, which is PRESERVE-tier and never reseeded, so production holds whatever an admin last saved and may well show six. Report the count you actually see and which environment you saw it in — the link and the subtitle rendering are separately testable and the link should work either way.",
|
|
94
94
|
expectedData: { valueCount: 6, ethicsHref: "/ethics" },
|
|
95
95
|
endResult: "Read-only; nothing persists.",
|
|
96
96
|
},
|
|
@@ -100,7 +100,7 @@ export const authored = {
|
|
|
100
100
|
steps: [
|
|
101
101
|
"Open /how-emi-works signed out and read the whole page.",
|
|
102
102
|
"Write down the eligibility threshold it states and the worked example it gives.",
|
|
103
|
-
"
|
|
103
|
+
"Switch identity before continuing: this case is roles [guest] and the harness gives a guest batch NO session at all, so copy tester/.tester-runs/session-buyer.json over session.json and CLOSE THE BROWSER first — the MCP reads that file only at context creation. Then sign in as rehan.sheikh@gmail.com / TempPass123! and put live-golden-retriever-puppy in the cart. That fixture is priced at 25,000, comfortably over the seeded EMI minOrderValue of 10,000, so EMI should be offered; a cheaper item would show no EMI for the right reason and read as a failure.",
|
|
104
104
|
"Reach the payment step and read the instalment figures offered.",
|
|
105
105
|
"Compare them against the page's worked example.",
|
|
106
106
|
"Empty the cart, add product-beyblade-burst-valkyrie instead, and check EMI is NOT offered below the threshold.",
|
|
@@ -129,9 +129,11 @@ export const authored = {
|
|
|
129
129
|
roles: ["buyer", "admin"],
|
|
130
130
|
startPage: "/scams/report",
|
|
131
131
|
steps: [
|
|
132
|
+
"START AS THE BUYER. This case lists roles [buyer, admin] and the harness resolves that to the ADMIN session — admin wins whenever it is present — so the batch opens as the wrong person for this first half. Copy tester/.tester-runs/session-buyer.json over session.json and CLOSE THE BROWSER before continuing; the MCP reads that file only when the context is created.",
|
|
132
133
|
"Sign in as rehan.sheikh@gmail.com / TempPass123! and submit a report with the description 'QA scam report routing probe — checklist case.'",
|
|
134
|
+
"Switch identity before continuing: copy tester/.tester-runs/session-admin.json over tester/.tester-runs/session.json and CLOSE THE BROWSER first — the MCP server reads that file when the context is created, so copying it over an open browser changes nothing and the next steps run as the previous person.",
|
|
133
135
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
134
|
-
"Find that report
|
|
136
|
+
"Find that report at /admin/scammers — the scam-report queue — and if it is not there, check /admin/reports before concluding it did not route.",
|
|
135
137
|
"Open it and read the description and the reporter.",
|
|
136
138
|
"Check it can be actioned — accepted, rejected or turned into a registry entry.",
|
|
137
139
|
"Remove the QA report if the interface allows.",
|
|
@@ -162,7 +164,7 @@ export const authored = {
|
|
|
162
164
|
steps: [
|
|
163
165
|
"Open /seller-guide signed out and read its content.",
|
|
164
166
|
"Note whether it reads as marketing for prospective sellers or as a dashboard manual.",
|
|
165
|
-
"
|
|
167
|
+
"Do NOT sign in — this case is signed out by design and the harness gives a guest batch no session at all. The comparison against the seller-dashboard hub at /store/guide belongs to selling/seller-guide, which runs as the seller; note here only that /seller-guide is a DIFFERENT, public page and not that hub.",
|
|
166
168
|
"Read that content.",
|
|
167
169
|
"Compare the two.",
|
|
168
170
|
"Check neither is a copy of the other.",
|
|
@@ -108,13 +108,13 @@ export const authored = {
|
|
|
108
108
|
startPage: "/admin/site",
|
|
109
109
|
steps: [
|
|
110
110
|
"Open /ethics in a private window and read the first paragraph.",
|
|
111
|
-
"Sign in as admin@letitrip.in / TempPass123
|
|
112
|
-
"Open /admin/site?tab=legal and find the 'Our Ethics' textarea.",
|
|
111
|
+
"Sign in as admin@letitrip.in / TempPass123!. This case lists roles [admin, guest] and resolves to ADMIN, so the batch already opens signed in; the guest reads below can be done in a private window, or by swapping tester/.tester-runs/session-guest.json over session.json and closing the browser between each switch.",
|
|
112
|
+
"Open /admin/site?tab=legal and find the 'Our Ethics' textarea. WRITE DOWN exactly what it contains before typing — the seed ships legalPages.ethics as an empty string, but siteSettings is PRESERVE-tier and never reseeded, so on a long-lived environment an admin may have authored real policy copy there.",
|
|
113
113
|
"Type <p>QA Ethics policy-admin-html-override</p> into it.",
|
|
114
114
|
"Save.",
|
|
115
115
|
"Return to the private window and reload /ethics immediately.",
|
|
116
116
|
"Read the first paragraph.",
|
|
117
|
-
"Go back to the admin textarea
|
|
117
|
+
"Go back to the admin textarea and RESTORE exactly what step 3 recorded — which on a fresh seed means clearing it, since the field ships empty, but on any other environment means putting the original copy back. Clearing a field that held real policy text is not a restore. Save again.",
|
|
118
118
|
"Reload /ethics in the private window once more.",
|
|
119
119
|
],
|
|
120
120
|
inputs: { override: "<p>QA Ethics policy-admin-html-override</p>" },
|
|
@@ -24,7 +24,7 @@ export const authored = {
|
|
|
24
24
|
roles: ["guest"],
|
|
25
25
|
startPage: "/",
|
|
26
26
|
steps: [
|
|
27
|
-
"Open the homepage and scroll to the footer.",
|
|
27
|
+
"Open the homepage and scroll to the footer. Of the six indexes, THREE are redirect shims into the catalogue (/classified, /digital-codes, /live -> /products?listingType=…) and three are real pages (/lottery, /sellers, /brands). A footer link that lands on /products carrying a query has worked; judge each by where it ends up, not by whether the URL changed.",
|
|
28
28
|
"Read the Shop column and list every link in it.",
|
|
29
29
|
"Click Classifieds and confirm where it lands.",
|
|
30
30
|
"Go back and repeat for Digital Codes, Live Items, Lotteries, Brands and Verified Sellers.",
|
|
@@ -38,7 +38,7 @@ export const authored = {
|
|
|
38
38
|
roles: ["guest"],
|
|
39
39
|
startPage: "/classified",
|
|
40
40
|
steps: [
|
|
41
|
-
"Open /classified.",
|
|
41
|
+
"Open /classified. 🛑 IT REDIRECTS to /products?listingType=classified, and that IS the wiring this page exists to confirm — the per-type browse indexes were folded into the one catalogue with a type filter, so landing on /products with a query is the design, not a broken link. Everything below is read on that filtered catalogue.",
|
|
42
42
|
"Read the badge on each of the first six cards.",
|
|
43
43
|
"Open the first card and read the badge on its detail page.",
|
|
44
44
|
"Note the URL the card led to.",
|
|
@@ -52,7 +52,7 @@ export const authored = {
|
|
|
52
52
|
roles: ["guest"],
|
|
53
53
|
startPage: "/digital-codes",
|
|
54
54
|
steps: [
|
|
55
|
-
"Open /digital-codes.",
|
|
55
|
+
"Open /digital-codes. It redirects to /products?listingType=digital-code — same folding as /classified.",
|
|
56
56
|
"Read the badge on each of the first six cards.",
|
|
57
57
|
"Open the first card and confirm the URL it leads to.",
|
|
58
58
|
"Read whether the detail page states how many codes remain.",
|
|
@@ -66,7 +66,7 @@ export const authored = {
|
|
|
66
66
|
roles: ["guest"],
|
|
67
67
|
startPage: "/live",
|
|
68
68
|
steps: [
|
|
69
|
-
"Open /live.",
|
|
69
|
+
"Open /live. It redirects to /products?listingType=live — same folding as /classified.",
|
|
70
70
|
"Read the badge on every card shown.",
|
|
71
71
|
"Open the first card and confirm the URL.",
|
|
72
72
|
"Read whether the detail page shows the species and jurisdiction fields a live item carries.",
|
|
@@ -128,7 +128,7 @@ export const authored = {
|
|
|
128
128
|
startPage: "/classified",
|
|
129
129
|
steps: [
|
|
130
130
|
"Open a private window with no session.",
|
|
131
|
-
"Open each of /classified, /digital-codes, /live, /lottery, /brands and /sellers in turn.",
|
|
131
|
+
"Open each of /classified, /digital-codes, /live, /lottery, /brands and /sellers in turn. The first three redirect into /products?listingType=… — expected, and not a sign the gate broke navigation — while the last three are real pages.",
|
|
132
132
|
"On each, confirm cards render and note what appears where a price would be.",
|
|
133
133
|
"Open one listing detail page from a price-bearing index.",
|
|
134
134
|
"Read the price area on that detail page.",
|
|
@@ -23,7 +23,7 @@ export const authored = {
|
|
|
23
23
|
roles: ["guest"],
|
|
24
24
|
startPage: "/stores",
|
|
25
25
|
steps: [
|
|
26
|
-
"Open /stores and click through to Beyblade Arena.",
|
|
26
|
+
"Open /stores and click through to Beyblade Arena. The store URL /stores/store-beyblade-arena redirects to /stores/store-beyblade-arena/products — that redirect IS the default-tab behaviour this case is checking, so landing on a /products URL is the pass condition rather than a surprise.",
|
|
27
27
|
"Note which tab is selected on arrival.",
|
|
28
28
|
"Count the listings shown and read the total the pager reports.",
|
|
29
29
|
"Open two of the cards and read which store each says it belongs to.",
|
|
@@ -52,9 +52,9 @@ export const authored = {
|
|
|
52
52
|
},
|
|
53
53
|
"checklist-public-pages-store-tabs-empty-tab-says-empty": {
|
|
54
54
|
roles: ["guest"],
|
|
55
|
-
startPage: "/stores/store-
|
|
55
|
+
startPage: "/stores/store-letitrip-official",
|
|
56
56
|
steps: [
|
|
57
|
-
"Open Beyblade Arena and
|
|
57
|
+
"Open /stores/store-letitrip-official and work through all twelve tabs in turn: Products, Auctions, Pre-orders, Prize Draws, Classified, Digital Codes, Live, Art, Bundles, Coupons, Reviews, About. 🛑 USE THIS STORE, NOT Beyblade Arena: the official store carries SIX listings and every one is a prize draw, so eleven of its twelve tabs are genuinely empty. Beyblade Arena has stock in every single type, so it has no empty tab to find and the case cannot be performed there at all.",
|
|
58
58
|
"Find one that lists nothing.",
|
|
59
59
|
"Wait for it to finish loading before judging it.",
|
|
60
60
|
"Read what it shows.",
|
|
@@ -85,8 +85,8 @@ export const authored = {
|
|
|
85
85
|
roles: ["guest"],
|
|
86
86
|
startPage: "/stores/store-letitrip-official",
|
|
87
87
|
steps: [
|
|
88
|
-
"Open /stores/store-letitrip-official —
|
|
89
|
-
"Work through
|
|
88
|
+
"Open /stores/store-letitrip-official — SIX listings, all of them prize draws, which is what makes a leak obvious: any card on any other tab belongs to someone else, and the Prize Draws tab should hold exactly six.",
|
|
89
|
+
"Work through all twelve tabs and read the store name on each card: Products, Auctions, Pre-orders, Prize Draws, Classified, Digital Codes, Live, Art, Bundles, Coupons, Reviews, About.",
|
|
90
90
|
"Note any card naming a different store.",
|
|
91
91
|
"Open one card and confirm on its detail page which store it belongs to.",
|
|
92
92
|
],
|
|
@@ -180,10 +180,10 @@ export const authored = {
|
|
|
180
180
|
},
|
|
181
181
|
"checklist-public-pages-stores-sellers-directories-store-tab-empty-state-not-error": {
|
|
182
182
|
roles: ["guest"],
|
|
183
|
-
startPage: "/stores/store-
|
|
183
|
+
startPage: "/stores/store-letitrip-official",
|
|
184
184
|
steps: [
|
|
185
|
-
"Open /stores/store-
|
|
186
|
-
"Open every tab offered and find one that is genuinely empty.",
|
|
185
|
+
"Open /stores/store-letitrip-official in a private window with no session. 🛑 NOT store-vintage-vault-co, which this case used to name: that store is SUSPENDED with isPublic false, and getStoreBySlug returns null unless status is ACTIVE and isPublic is true — so its public page calls notFound(). A tester would meet a 404 and have to judge an empty state on a page that does not exist.",
|
|
186
|
+
"Open every tab offered and find one that is genuinely empty. This store carries SIX listings, all prize draws, so eleven of its twelve tabs are empty and one is not — which gives both halves of the comparison.",
|
|
187
187
|
"Read exactly what is shown in that empty tab.",
|
|
188
188
|
"Wait 10 seconds and read it again.",
|
|
189
189
|
"Check for any error toast anywhere on the page.",
|
|
@@ -19,6 +19,38 @@
|
|
|
19
19
|
* @tag consumers:seed-data/authored/index.ts
|
|
20
20
|
* @tag sideEffects:none
|
|
21
21
|
*/
|
|
22
|
+
/*
|
|
23
|
+
* Measured on 2026-09-29 with `node scripts/diff-employee-sidebar.mjs`, which
|
|
24
|
+
* exists for exactly this and computes what every preset sees:
|
|
25
|
+
*
|
|
26
|
+
* total 90 items across 12 groups (the case said 91)
|
|
27
|
+
* blog_poster sees 5
|
|
28
|
+
* trust_and_safety sees 24
|
|
29
|
+
* maintenance_employee sees 77 (the most permissive preset)
|
|
30
|
+
* 20 presets in all, and the SMALLEST is 5
|
|
31
|
+
*
|
|
32
|
+
* The nine groups the first case names as vanishing for the blog employee are
|
|
33
|
+
* exactly the nine the script reports: Finance, Procurement, Catalog, Testing,
|
|
34
|
+
* Site, Events, Trust & Safety, System, Maintenance. That is a strong
|
|
35
|
+
* confirmation, so the case is kept as written apart from the count.
|
|
36
|
+
*
|
|
37
|
+
* 🛑 THE SECOND CASE ASKED FOR 20 SIGN-IN CYCLES to prove something the repo
|
|
38
|
+
* already computes. Setting each preset as admin, signing in as the employee
|
|
39
|
+
* and counting, twenty times over, is most of a session; and the answer is in
|
|
40
|
+
* the script above, where the smallest preset is 5 and no preset reaches zero.
|
|
41
|
+
* It now checks two presets by hand and defers the sweep to the script, which
|
|
42
|
+
* is the difference between a case a tester will run and one they will skip.
|
|
43
|
+
*
|
|
44
|
+
* Two things the script itself reports and neither case mentioned:
|
|
45
|
+
*
|
|
46
|
+
* — 45 distinct preset permissions gate NO sidebar entry at all; they gate
|
|
47
|
+
* routes and actions instead. So a preset can differ from another in
|
|
48
|
+
* permissions and not differ in sidebar count, and identical counts
|
|
49
|
+
* between two presets are not automatically the bug.
|
|
50
|
+
* — An employee with hand-picked `permissions[]` rather than a preset is NOT
|
|
51
|
+
* covered by any of this. That is a real coverage gap, recorded here
|
|
52
|
+
* rather than silently left out.
|
|
53
|
+
*/
|
|
22
54
|
export const authored = {
|
|
23
55
|
"checklist-search-and-nav-employee-permissions-employee-sees-only-their-permissions": {
|
|
24
56
|
roles: ["employee", "admin"],
|
|
@@ -33,9 +65,9 @@ export const authored = {
|
|
|
33
65
|
"Sign out and sign in as admin@letitrip.in / TempPass123!, then open /admin and count the entries.",
|
|
34
66
|
],
|
|
35
67
|
inputs: { blogEmployee: "employee-blog@letitrip.in", trustEmployee: "employee-trust@letitrip.in" },
|
|
36
|
-
expectedBehaviour: "Nav filtering keys on each item's own id to look up its required permission.
|
|
37
|
-
expectedUiState: "The blog employee sees
|
|
38
|
-
expectedData: {
|
|
68
|
+
expectedBehaviour: "Nav filtering keys on each item's own id to look up its required permission. `filterNavItems` opens with `if (!item.id) return true;` and no item in the config had one — so all 90 entries passed for everybody and every requiredPermission was decorative. A group left with no visible children is removed entirely rather than rendering an empty heading.",
|
|
69
|
+
expectedUiState: "The blog employee sees FIVE entries, not 90. Finance, Procurement, Catalog, Testing, Site, Events, Trust & Safety and System and Maintenance are absent as HEADINGS, not present-and-empty. The trust employee sees 24. The admin sees all 90 across 12 groups. Three genuinely different counts is the pass; three identical counts is the exact bug — and 90 for all three is the specific bug this replaced.",
|
|
70
|
+
expectedData: { blogEntries: 5, trustEntries: 24, adminEntries: 90, adminGroups: 12 },
|
|
39
71
|
endResult: "Read-only; nothing persists. Comparing two presets against each other AND against admin is what distinguishes real filtering from a filter that happens to hide the same fixed set from everyone.",
|
|
40
72
|
},
|
|
41
73
|
"checklist-search-and-nav-employee-permissions-employee-sidebar-never-empty": {
|
|
@@ -44,16 +76,18 @@ export const authored = {
|
|
|
44
76
|
steps: [
|
|
45
77
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
46
78
|
"Open /admin/team and open the editor for employee-blog@letitrip.in.",
|
|
47
|
-
"Read every permission preset the editor offers and
|
|
48
|
-
"Set the account to the
|
|
79
|
+
"Read every permission preset the editor offers and count them — there are 20.",
|
|
80
|
+
"Set the account to blog_poster, the smallest preset, and save.",
|
|
49
81
|
"Sign out, sign in as employee-blog@letitrip.in / TempPass123!, open /admin and count the sidebar entries.",
|
|
50
|
-
"
|
|
51
|
-
"
|
|
82
|
+
"Sign back in as admin, set the account to maintenance_employee, the most permissive preset, and save.",
|
|
83
|
+
"Sign in as the employee again and count the entries.",
|
|
84
|
+
"Sign back in as admin and set the account back to blog_poster as the last step.",
|
|
85
|
+
"For the remaining 18 presets, run `node scripts/diff-employee-sidebar.mjs` rather than signing in 18 more times — it computes every preset's count from the same config the sidebar reads.",
|
|
52
86
|
],
|
|
53
87
|
inputs: { employee: "employee-blog@letitrip.in" },
|
|
54
|
-
expectedBehaviour: "No preset produces a completely blank sidebar. At minimum every employee reaches the dashboard itself, so the portal is navigable from the moment they sign in.",
|
|
55
|
-
expectedUiState: "
|
|
56
|
-
expectedData: {
|
|
57
|
-
endResult: "The account is back on
|
|
88
|
+
expectedBehaviour: "No preset produces a completely blank sidebar. At minimum every employee reaches the dashboard itself, so the portal is navigable from the moment they sign in. Measured: the smallest of the 20 presets is blog_poster at 5 entries and the largest is maintenance_employee at 77, so the floor is well clear of zero.",
|
|
89
|
+
expectedUiState: "Both presets checked by hand show entries — 5 for blog_poster and 77 for maintenance_employee — and the script reports no preset below 5. A blank sidebar is the failure: an employee cannot tell it from a broken account and will report it as one, which is a support ticket the permission system caused rather than prevented.",
|
|
90
|
+
expectedData: { blogPosterEntries: 5, maintenanceEmployeeEntries: 77, presetCount: 20 },
|
|
91
|
+
endResult: "The account is back on blog_poster, which the case above depends on. Leaving it on another preset silently breaks that case's expected count of exactly 5.",
|
|
58
92
|
},
|
|
59
93
|
};
|
|
@@ -18,30 +18,70 @@
|
|
|
18
18
|
* @tag consumers:seed-data/authored/index.ts
|
|
19
19
|
* @tag sideEffects:none
|
|
20
20
|
*/
|
|
21
|
+
/*
|
|
22
|
+
* 🛑 THIS IS THE SIDEBAR SEARCH, NOT THE HEADER SEARCH (corrected 2026-09-29).
|
|
23
|
+
*
|
|
24
|
+
* All four cases said to open / and click into the header search. Two things
|
|
25
|
+
* are wrong with that. The public header's Search is a CATALOGUE search: it
|
|
26
|
+
* carries a resource-type dropdown and navigates to a listing URL with ?q=,
|
|
27
|
+
* so it never returns a settings toggle or an admin screen. And on / there is
|
|
28
|
+
* no admin sidebar at all.
|
|
29
|
+
*
|
|
30
|
+
* The search these cases describe is `useSidebarSearch`, mounted in each
|
|
31
|
+
* portal's sidebar, and its own header states exactly what they assert:
|
|
32
|
+
*
|
|
33
|
+
* "refund found nothing, because the screen is called Payouts. postcode
|
|
34
|
+
* found nothing, because the field is called PIN code. maintenance found
|
|
35
|
+
* nothing, because that toggle lives inside Site Settings."
|
|
36
|
+
*
|
|
37
|
+
* and the ranking rule they turn on:
|
|
38
|
+
*
|
|
39
|
+
* "A label hit beats a keyword hit beats a description hit, so typing
|
|
40
|
+
* orders puts Orders above the guide that merely mentions orders."
|
|
41
|
+
*
|
|
42
|
+
* Groups keep their declared order deliberately, so ranking is WITHIN a group.
|
|
43
|
+
* A case expecting one flat list ranked end to end would misread that.
|
|
44
|
+
*
|
|
45
|
+
* 🛑 AND THERE IS A NAV GROUP TITLED 'Maintenance', with Overview, Server
|
|
46
|
+
* Errors, Client Errors and Function Errors under it. The first case claimed
|
|
47
|
+
* no nav item is called Maintenance and that a label-matching search returns
|
|
48
|
+
* nothing for the word. Both are false, and the corrected case uses that: the
|
|
49
|
+
* query must return BOTH the Maintenance group and the Site Settings entry
|
|
50
|
+
* whose keywords carry 'maintenance mode', which is the harder half.
|
|
51
|
+
*
|
|
52
|
+
* The Maintenance mode toggle really is on the Branding tab, verified at
|
|
53
|
+
* AdminSiteSettingsView's `setting-maintenance-mode` anchor. But that is tab
|
|
54
|
+
* TWO of TWENTY, not one of nineteen.
|
|
55
|
+
*
|
|
56
|
+
* Case 4's filter is by CONSTRUCTION rather than by a permission check — each
|
|
57
|
+
* portal's sidebar is built from its own nav groups, so a buyer's sidebar has
|
|
58
|
+
* no admin items to surface. That is a stronger guarantee than filtering, and
|
|
59
|
+
* the case now says so instead of implying a runtime check it does not make.
|
|
60
|
+
*/
|
|
21
61
|
export const authored = {
|
|
22
62
|
"checklist-search-and-nav-header-search-search-finds-maintenance-toggle": {
|
|
23
63
|
roles: ["admin"],
|
|
24
|
-
startPage: "/",
|
|
64
|
+
startPage: "/admin",
|
|
25
65
|
steps: [
|
|
26
66
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
27
|
-
"Open / and click into the
|
|
67
|
+
"Open /admin and click into the sidebar search box.",
|
|
28
68
|
"Type maintenance.",
|
|
29
69
|
"Read the results.",
|
|
30
|
-
"
|
|
31
|
-
"Read which tab of Site Settings
|
|
70
|
+
"Read whether BOTH the Maintenance nav group and the Site Settings entry are returned, then click the Site Settings entry.",
|
|
71
|
+
"Read which tab of Site Settings opens and whether the Maintenance mode toggle is on screen.",
|
|
32
72
|
],
|
|
33
73
|
inputs: { query: "maintenance" },
|
|
34
|
-
expectedBehaviour: "Search reaches individual SETTINGS, not just screens.
|
|
35
|
-
expectedUiState: "
|
|
36
|
-
expectedData: {
|
|
74
|
+
expectedBehaviour: "Search reaches individual SETTINGS, not just screens. There IS a nav group called Maintenance, so the word finds those pages on its label alone — the harder half is the Site Settings entry, which matches only through its keywords, and whose Maintenance mode toggle sits on tab two of twenty with no way to find it but hunting.",
|
|
75
|
+
expectedUiState: "The results include the Maintenance nav group AND the Site Settings entry. Opening Site Settings lands on the Branding tab with the Maintenance mode toggle on screen. Landing on the About tab, which is the first of twenty, with the toggle somewhere below is a FAIL rather than a partial pass. Returning only the Maintenance pages and not Site Settings means keyword matching is not running — that is the finding this case exists for.",
|
|
76
|
+
expectedData: { siteSettingsReturned: true, maintenanceGroupReturned: true },
|
|
37
77
|
endResult: "Read-only; do not flip the toggle. Turning on maintenance mode would take the site down for every other case in the run.",
|
|
38
78
|
},
|
|
39
79
|
"checklist-search-and-nav-header-search-search-finds-by-what-it-does": {
|
|
40
80
|
roles: ["admin"],
|
|
41
|
-
startPage: "/",
|
|
81
|
+
startPage: "/admin",
|
|
42
82
|
steps: [
|
|
43
83
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
44
|
-
"Open / and click into the
|
|
84
|
+
"Open /admin and click into the sidebar search box.",
|
|
45
85
|
"Type refund and read the results.",
|
|
46
86
|
"Clear the box, type postcode and read the results.",
|
|
47
87
|
"Clear the box, type cod and read the results.",
|
|
@@ -55,33 +95,33 @@ export const authored = {
|
|
|
55
95
|
},
|
|
56
96
|
"checklist-search-and-nav-header-search-search-ranks-exact-first": {
|
|
57
97
|
roles: ["admin"],
|
|
58
|
-
startPage: "/",
|
|
98
|
+
startPage: "/admin",
|
|
59
99
|
steps: [
|
|
60
100
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
61
|
-
"Open / and click into the
|
|
101
|
+
"Open /admin and click into the sidebar search box.",
|
|
62
102
|
"Type orders.",
|
|
63
103
|
"Read the results in order from the top.",
|
|
64
104
|
],
|
|
65
105
|
inputs: { query: "orders" },
|
|
66
|
-
expectedBehaviour: "An exact label match outranks
|
|
67
|
-
expectedUiState: "
|
|
106
|
+
expectedBehaviour: "An exact label match outranks a keyword hit, which outranks a description hit. With no ranking the results fall into declaration order and the thing actually called Orders can land well down the list. Ranking is WITHIN each group — groups keep their declared order on purpose, so that the sidebar does not rearrange itself under the reader while they type.",
|
|
107
|
+
expectedUiState: "Within its own group, the entry named Orders is first. Guides and settings whose descriptions merely mention orders rank below it, inside their own groups. Orders appearing below a description-only match IN THE SAME GROUP is the failure, even though it is present. Do not expect one flat list — groups are not reordered by score.",
|
|
68
108
|
expectedData: { exactMatchPosition: 1 },
|
|
69
109
|
endResult: "Read-only; nothing persists.",
|
|
70
110
|
},
|
|
71
111
|
"checklist-search-and-nav-header-search-search-never-shows-what-you-cannot-reach": {
|
|
72
112
|
roles: ["buyer"],
|
|
73
|
-
startPage: "/",
|
|
113
|
+
startPage: "/user",
|
|
74
114
|
steps: [
|
|
75
115
|
"Sign in as rehan.sheikh@gmail.com / TempPass123!, an ordinary buyer.",
|
|
76
|
-
"Open / and click into the
|
|
116
|
+
"Open /user and click into the sidebar search box — a buyer has a user sidebar, not an admin one.",
|
|
77
117
|
"Type users and read every result.",
|
|
78
118
|
"Clear the box, type payouts and read every result.",
|
|
79
119
|
"Clear the box, type maintenance and read every result.",
|
|
80
120
|
"Clear the box, type orders and read every result.",
|
|
81
121
|
],
|
|
82
122
|
inputs: { query1: "users", query2: "payouts", query3: "maintenance", query4: "orders" },
|
|
83
|
-
expectedBehaviour: "
|
|
84
|
-
expectedUiState: "'users', 'payouts' and 'maintenance' return no admin entries at all — not greyed out, not present-but-unclickable, absent. 'orders' still returns the buyer's OWN orders page, which
|
|
123
|
+
expectedBehaviour: "A buyer can never surface an admin entry, and the reason is stronger than filtering: each portal builds its sidebar from its OWN nav groups, so there are no admin items in a buyer's list to match against. The labels alone would be a site map of the admin panel, so absence by construction is the right answer — hiding the destination behind a 403 would not be.",
|
|
124
|
+
expectedUiState: "'users', 'payouts' and 'maintenance' return no admin entries at all — not greyed out, not present-but-unclickable, absent. 'orders' still returns the buyer's OWN orders page, which is the control: a search returning nothing for everything would pass the first three for the wrong reason.",
|
|
85
125
|
expectedData: { adminResultsForBuyer: 0 },
|
|
86
126
|
endResult: "Read-only; nothing persists. The fourth query is the control — a search that returned nothing for everything would pass the first three for the wrong reason.",
|
|
87
127
|
},
|