@mohasinac/appkit 4.37.1 → 4.39.0
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/digital-code/CodeRevealPanel.d.ts +20 -1
- package/dist/_internal/client/features/digital-code/CodeRevealPanel.js +21 -1
- package/dist/_internal/client/features/digital-code/DigitalContentPoolManager.d.ts +40 -0
- package/dist/_internal/client/features/digital-code/DigitalContentPoolManager.js +67 -0
- package/dist/_internal/server/features/bundles/og.d.ts +0 -1
- package/dist/_internal/server/features/bundles/og.js +2 -18
- package/dist/_internal/server/features/catalogue/og.d.ts +0 -1
- package/dist/_internal/server/features/catalogue/og.js +1 -9
- package/dist/_internal/server/features/checkout/actions.js +18 -0
- package/dist/_internal/server/features/classified/og.d.ts +0 -1
- package/dist/_internal/server/features/classified/og.js +1 -9
- package/dist/_internal/server/features/digital-code/download.d.ts +60 -0
- package/dist/_internal/server/features/digital-code/download.js +128 -0
- package/dist/_internal/server/features/digital-code/og.d.ts +0 -1
- package/dist/_internal/server/features/digital-code/og.js +1 -9
- package/dist/_internal/server/features/digital-code/pool.d.ts +101 -0
- package/dist/_internal/server/features/digital-code/pool.js +208 -0
- package/dist/_internal/server/features/digital-code/upload.d.ts +51 -0
- package/dist/_internal/server/features/digital-code/upload.js +144 -0
- package/dist/_internal/server/features/grouped/og.d.ts +7 -4
- package/dist/_internal/server/features/grouped/og.js +7 -6
- package/dist/_internal/server/features/item-requests/og.d.ts +0 -1
- package/dist/_internal/server/features/item-requests/og.js +2 -18
- package/dist/_internal/server/features/live/og.d.ts +0 -1
- package/dist/_internal/server/features/live/og.js +1 -9
- package/dist/_internal/server/features/prize-draws/og.js +5 -10
- package/dist/_internal/server/features/products/og.js +5 -2
- package/dist/_internal/server/features/seo/sitemap.js +10 -2
- package/dist/_internal/server/features/shared/listing-adapters.d.ts +1 -2
- package/dist/_internal/server/features/shared/listing-adapters.js +5 -3
- package/dist/_internal/server/jobs/core/cleanupRtdbEvents.js +40 -0
- package/dist/_internal/server/jobs/core/countersReconcile.js +153 -40
- package/dist/_internal/server/jobs/core/onCategoryWrite.js +33 -0
- package/dist/_internal/server/jobs/core/onProductWrite.js +93 -25
- package/dist/_internal/server/notifications/send-recorder.d.ts +79 -0
- package/dist/_internal/server/notifications/send-recorder.js +126 -0
- package/dist/_internal/shared/listing-types/_registry.d.ts +31 -4
- package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/art/config.js +2 -0
- package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/auction/config.js +3 -0
- package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/classified/config.js +3 -0
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/digital-code/config.js +3 -0
- package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/live/config.js +3 -0
- package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/pre-order/config.js +2 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +3 -0
- package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/standard/config.js +2 -0
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/stickers/config.js +2 -0
- package/dist/client/observability/reportClientError.js +11 -1
- package/dist/client.d.ts +6 -0
- package/dist/client.js +10 -0
- package/dist/core/hooks/useSyncManager.js +11 -1
- package/dist/features/account/hooks/useNotifications.js +25 -5
- package/dist/features/auctions/components/AuctionBottomActions.js +10 -2
- package/dist/features/auctions/components/AuctionDetailPageView.js +12 -3
- package/dist/features/auctions/components/LiveBidPrice.js +2 -2
- package/dist/features/auctions/components/LiveMinIncrement.d.ts +1 -1
- package/dist/features/auctions/components/LiveMinIncrement.js +8 -0
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +3 -3
- package/dist/features/auctions/components/PlaceBidFormClient.js +16 -1
- package/dist/features/auctions/hooks/useAuctions.js +12 -1
- package/dist/features/auctions/hooks/useRealtimeBids.d.ts +3 -0
- package/dist/features/auctions/hooks/useRealtimeBids.js +138 -36
- package/dist/features/blog/components/BlogIndexPageView.js +10 -1
- package/dist/features/blog/repository/blog.repository.d.ts +14 -0
- package/dist/features/blog/repository/blog.repository.js +23 -1
- package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +2 -2
- package/dist/features/catalogue/components/PublicCatalogueView.js +2 -2
- package/dist/features/categories/components/BundleCollage.js +10 -5
- package/dist/features/categories/components/BundleDetailView.js +7 -3
- package/dist/features/categories/components/CategoryDetailPageView.js +1 -1
- package/dist/features/categories/components/CategoryProductsListing.js +4 -1
- package/dist/features/categories/components/CategoryTree.js +1 -1
- package/dist/features/categories/repository/categories.repository.d.ts +34 -2
- package/dist/features/categories/repository/categories.repository.js +31 -9
- package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -2
- package/dist/features/contact/email.js +36 -1
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +2 -2
- package/dist/features/homepage/components/FeaturedBundlesSection.js +4 -4
- package/dist/features/live/components/LiveItemDetailPageView.js +2 -2
- package/dist/features/orders/repository/orders.repository.d.ts +21 -1
- package/dist/features/orders/repository/orders.repository.js +26 -9
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
- package/dist/features/pre-orders/components/PreOrderActionsClient.js +2 -2
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -2
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +4 -1
- package/dist/features/products/actions/product-actions.d.ts +9 -0
- package/dist/features/products/actions/product-actions.js +11 -0
- package/dist/features/products/columns/productTableColumns.js +2 -2
- package/dist/features/products/components/AuctionsIndexListing.js +4 -1
- package/dist/features/products/components/BidHistory.js +2 -2
- package/dist/features/products/components/CompareOverlay.js +2 -2
- package/dist/features/products/components/GroupMemberPicker.js +5 -5
- package/dist/features/products/components/ListingBottomActions.js +12 -3
- package/dist/features/products/components/MakeOfferButton.js +7 -1
- package/dist/features/products/components/MarketplaceBundleCard.js +4 -4
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +2 -2
- package/dist/features/products/components/PrizeDrawCollage.js +16 -5
- package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
- package/dist/features/products/components/PrizeDrawsIndexListing.js +4 -1
- package/dist/features/products/components/PrizeRevealModal.js +2 -2
- package/dist/features/products/components/ProductDetailActions.js +10 -3
- package/dist/features/products/components/ProductFilters.js +2 -2
- package/dist/features/products/components/ProductGrid.js +5 -2
- package/dist/features/products/components/ProductsIndexListing.js +12 -1
- package/dist/features/products/components/ProductsIndexPageView.js +26 -0
- package/dist/features/products/components/ShowGroupSection.js +2 -2
- package/dist/features/products/components/SublistingCarouselSection.js +2 -2
- package/dist/features/products/constants/listing-tabs.d.ts +36 -12
- package/dist/features/products/constants/listing-tabs.js +38 -14
- package/dist/features/products/constants/sieve.d.ts +14 -0
- package/dist/features/products/constants/sieve.js +39 -0
- package/dist/features/products/repository/products.repository.d.ts +35 -0
- package/dist/features/products/repository/products.repository.js +116 -3
- package/dist/features/products/schemas/firestore.d.ts +37 -0
- package/dist/features/seller/actions/seller-actions.js +4 -13
- package/dist/features/seller/components/SellerProductShell.d.ts +7 -1
- package/dist/features/seller/components/SellerProductShell.js +11 -2
- package/dist/features/stores/api/[storeSlug]/reviews/route.d.ts +6 -1
- package/dist/features/stores/api/[storeSlug]/reviews/route.js +89 -30
- package/dist/features/stores/components/StoresIndexPageView.js +17 -1
- package/dist/features/tester/seed-data/authored/admin__users-trust.js +1 -1
- package/dist/features/tester/seed-data/authored/buying__bidding.js +5 -4
- package/dist/features/tester/seed-data/authored/buying__browsing-search.js +85 -85
- package/dist/features/tester/seed-data/authored/buying__image-tile-layout.js +2 -2
- package/dist/features/tester/seed-data/authored/buying__offers.js +1 -1
- package/dist/features/tester/seed-data/authored/buying__product-detail.js +53 -15
- package/dist/features/tester/seed-data/authored/buying__user-dashboard-extras.js +6 -6
- package/dist/features/tester/seed-data/authored/content-discovery__search.js +6 -4
- package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.js +4 -4
- package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.js +2 -2
- package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.js +4 -4
- package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.js +8 -8
- package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.js +8 -8
- package/dist/features/tester/seed-data/authored/seo__canonical-and-host.js +16 -0
- package/dist/features/tester/seed-data/authored/seo__og-images.js +17 -0
- package/dist/features/tester/seed-data/stores-tester-seed-data.js +41 -2
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +36 -6
- package/dist/features/tester/utils/phases.js +78 -12
- package/dist/features/wishlist/components/WishlistPage.js +2 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -1
- package/dist/providers/db-firebase/rtdb-paths.d.ts +7 -0
- package/dist/providers/db-firebase/rtdb-paths.js +7 -0
- package/dist/react/contexts/SessionContext.js +40 -1
- package/dist/react/hooks/useCanSeePrices.d.ts +37 -0
- package/dist/react/hooks/useCanSeePrices.js +40 -0
- package/dist/seed/addresses-seed-data.js +36 -2
- package/dist/seed/users-seed-data.js +10 -2
- package/dist/seo/json-ld.d.ts +21 -0
- package/dist/seo/json-ld.js +45 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +12 -0
- package/dist/styles.css +41 -0
- package/dist/ui/components/FilePickerButton.d.ts +32 -0
- package/dist/ui/components/FilePickerButton.js +23 -0
- package/dist/ui/components/GatedPrice.d.ts +66 -0
- package/dist/ui/components/GatedPrice.js +80 -0
- package/dist/ui/components/GatedPrice.style.css +40 -0
- package/dist/ui/components/PriceDisplay.d.ts +12 -0
- package/dist/ui/components/PriceDisplay.js +17 -17
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +8 -0
- package/package.json +1 -1
|
@@ -7,15 +7,27 @@
|
|
|
7
7
|
* export { storeReviewsGET as GET } from "@mohasinac/feat-stores";
|
|
8
8
|
* ```
|
|
9
9
|
*
|
|
10
|
-
* Aggregates approved
|
|
10
|
+
* Aggregates every approved review filed against a store, by `storeId`.
|
|
11
11
|
* Returns: reviews[], averageRating, totalReviews, ratingDistribution.
|
|
12
12
|
*
|
|
13
|
+
* It used to aggregate "up to 20 published products by itemsSold" and fan out a
|
|
14
|
+
* review query per product — and since no product carries `itemsSold`, that
|
|
15
|
+
* `orderBy` matched nothing and the endpoint returned zero reviews for every
|
|
16
|
+
* store. See the block comment at the query for the full account.
|
|
17
|
+
*
|
|
13
18
|
* Requires `db` registered in providers.config via `registerProviders()`.
|
|
14
19
|
* Collections: "stores", "products", "reviews"
|
|
15
20
|
*/
|
|
16
21
|
import { NextResponse } from "next/server.js";
|
|
17
22
|
import { getProviders } from "../../../../../contracts";
|
|
18
23
|
import { normalizeError } from "../../../../../errors/normalize";
|
|
24
|
+
/**
|
|
25
|
+
* Upper bound on the reviews scanned to build a store's aggregate. The average
|
|
26
|
+
* rating and the distribution are computed over this window, so it must be
|
|
27
|
+
* generous — but it is still a bound, because the alternative is an unbounded
|
|
28
|
+
* read on a public, cacheable endpoint (Rule #6).
|
|
29
|
+
*/
|
|
30
|
+
const STORE_REVIEW_SCAN_CAP = 500;
|
|
19
31
|
// --- GET /api/stores/[storeSlug]/reviews --------------------------------------
|
|
20
32
|
export async function GET(request, context) {
|
|
21
33
|
try {
|
|
@@ -44,40 +56,53 @@ export async function GET(request, context) {
|
|
|
44
56
|
if (!store) {
|
|
45
57
|
return NextResponse.json({ success: false, error: "Store not found" }, { status: 404 });
|
|
46
58
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
/*
|
|
60
|
+
* 🛑 REVIEWS ARE FETCHED BY `storeId`, IN ONE QUERY. Do not route this back
|
|
61
|
+
* through products.
|
|
62
|
+
*
|
|
63
|
+
* It used to select "the store's top 20 products by `itemsSold`" and then
|
|
64
|
+
* fan out one review query per product. Two independent defects, and the
|
|
65
|
+
* second hid the first:
|
|
66
|
+
*
|
|
67
|
+
* - **`orderBy("itemsSold")` silently drops every document that lacks the
|
|
68
|
+
* field**, exactly like the documented `!=` trap. Measured on production
|
|
69
|
+
* 2026-09-14: **0 of 95 products carry `itemsSold`**, so that query
|
|
70
|
+
* returned an empty array — and therefore so did the whole endpoint.
|
|
71
|
+
* Every store's reviews tab had been empty since it shipped, answering
|
|
72
|
+
* HTTP 200 with `totalReviews: 0` while 79 approved reviews sat in
|
|
73
|
+
* Firestore. Nothing errored; there was no symptom to report beyond "this
|
|
74
|
+
* store has no reviews yet", which reads as a fact about the store.
|
|
75
|
+
* - Even populated, a 20-product window is a SAMPLE, not the store: 14
|
|
76
|
+
* distinct products carry reviews here and the store has 65 listings, so
|
|
77
|
+
* an arbitrary slice would under-report the average rating and the
|
|
78
|
+
* distribution — the two numbers buyers actually judge a seller by.
|
|
79
|
+
*
|
|
80
|
+
* `ReviewDocument` carries `storeId` (74/79 here belong to this store), so
|
|
81
|
+
* the store's reviews are one equality away. This also turns 21 Firestore
|
|
82
|
+
* queries into 1, well inside Rule #6's ~3-round-trip budget.
|
|
83
|
+
*/
|
|
57
84
|
const reviewsRepo = db.getRepository("reviews");
|
|
58
|
-
const
|
|
59
|
-
.
|
|
60
|
-
filters: `productId==${p.id},status==approved`,
|
|
85
|
+
const reviewsResult = await reviewsRepo.findAll({
|
|
86
|
+
filters: `storeId==${store.id},status==approved`,
|
|
61
87
|
sort: "createdAt",
|
|
62
88
|
order: "desc",
|
|
63
|
-
perPage:
|
|
64
|
-
})
|
|
65
|
-
|
|
89
|
+
perPage: STORE_REVIEW_SCAN_CAP,
|
|
90
|
+
});
|
|
91
|
+
const allFlat = reviewsResult.data;
|
|
66
92
|
// Compute aggregate metrics from ALL reviews (unfiltered)
|
|
67
93
|
const ratingDistribution = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
|
|
68
94
|
let ratingSum = 0;
|
|
69
95
|
let totalReviews = 0;
|
|
70
|
-
for (const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
ratingDistribution[review.rating] = (ratingDistribution[review.rating] ?? 0) + 1;
|
|
75
|
-
}
|
|
96
|
+
for (const review of allFlat) {
|
|
97
|
+
totalReviews++;
|
|
98
|
+
ratingSum += review.rating;
|
|
99
|
+
ratingDistribution[review.rating] = (ratingDistribution[review.rating] ?? 0) + 1;
|
|
76
100
|
}
|
|
77
101
|
const averageRating = totalReviews > 0 ? ratingSum / totalReviews : 0;
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
102
|
+
// Titles and images are enriched further down, from only the products the
|
|
103
|
+
// reviews on the CURRENT PAGE name. Every review already carries a
|
|
104
|
+
// denormalised `productTitle`, so a miss still renders a usable card.
|
|
105
|
+
const productsRepo = db.getRepository("products");
|
|
81
106
|
// Apply sort
|
|
82
107
|
const sorted = [...allFlat].sort((a, b) => {
|
|
83
108
|
if (sort === "createdAt" || sort === "+createdAt") {
|
|
@@ -99,10 +124,20 @@ export async function GET(request, context) {
|
|
|
99
124
|
// Apply search
|
|
100
125
|
if (q) {
|
|
101
126
|
filtered = filtered.filter((r) => {
|
|
102
|
-
const
|
|
103
|
-
const
|
|
127
|
+
const rv = r;
|
|
128
|
+
const title = (rv.title ?? "").toLowerCase();
|
|
129
|
+
/*
|
|
130
|
+
* `comment` FIRST. The stored field is `comment` — verified against all
|
|
131
|
+
* 79 production review documents, none of which has a `body`. This read
|
|
132
|
+
* `body` alone, so searching a store's reviews only ever matched the
|
|
133
|
+
* title or the product name, never a word the reviewer actually wrote.
|
|
134
|
+
* `body` is kept as a fallback because it is what CLAUDE.md's reviews
|
|
135
|
+
* row and several types call it; the two spellings have drifted and only
|
|
136
|
+
* the data settles which is real.
|
|
137
|
+
*/
|
|
138
|
+
const text = (rv.comment ?? rv.body ?? "").toLowerCase();
|
|
104
139
|
const pt = (r.productTitle ?? "").toLowerCase();
|
|
105
|
-
return title.includes(q) ||
|
|
140
|
+
return title.includes(q) || text.includes(q) || pt.includes(q);
|
|
106
141
|
});
|
|
107
142
|
}
|
|
108
143
|
// Apply date range
|
|
@@ -124,7 +159,31 @@ export async function GET(request, context) {
|
|
|
124
159
|
const totalFiltered = filtered.length;
|
|
125
160
|
const totalPages = Math.max(1, Math.ceil(totalFiltered / pageSize));
|
|
126
161
|
const pageSlice = filtered.slice((page - 1) * pageSize, page * pageSize);
|
|
127
|
-
// Enrich with product title + main image
|
|
162
|
+
// Enrich with product title + main image — only the ids on this page.
|
|
163
|
+
const pageProductIds = [...new Set(pageSlice.map((r) => r.productId).filter(Boolean))];
|
|
164
|
+
/*
|
|
165
|
+
* 🛑 DOCUMENT GETS, not a filter. The generic `findAll` on this provider
|
|
166
|
+
* parses a clause as `.where(field, op, coerceValue(value))` and has no
|
|
167
|
+
* pipe-join OR handling — so `id==a|b|c` would query for the LITERAL string
|
|
168
|
+
* "a|b|c" and match nothing, silently, which is the exact defect class this
|
|
169
|
+
* whole route is being fixed for. (Pipe-joins are a `sieveQuery` feature;
|
|
170
|
+
* this repository is not that path.) `id` is also not a stored field.
|
|
171
|
+
*
|
|
172
|
+
* Bounded by `pageSize`, capped at 50 above and typically ~12.
|
|
173
|
+
*/
|
|
174
|
+
const productMap = new Map();
|
|
175
|
+
const fetched = await Promise.all(pageProductIds.map((id) => productsRepo.findById(id).catch((err) => {
|
|
176
|
+
// Named and logged, not swallowed: enrichment is genuinely optional
|
|
177
|
+
// (every review carries a denormalised productTitle), but a silent
|
|
178
|
+
// null here is indistinguishable from "that product was deleted" —
|
|
179
|
+
// Root Cause #59's shape, which is what this whole route was fixed for.
|
|
180
|
+
void normalizeError(err);
|
|
181
|
+
console.warn(`[feat-stores] products.findById failed for ${id}`);
|
|
182
|
+
return null;
|
|
183
|
+
})));
|
|
184
|
+
for (const p of fetched)
|
|
185
|
+
if (p)
|
|
186
|
+
productMap.set(p.id, p);
|
|
128
187
|
const reviewsWithProduct = pageSlice.map((review) => ({
|
|
129
188
|
...review,
|
|
130
189
|
productTitle: productMap.get(review.productId)?.title ?? review.productTitle,
|
|
@@ -13,7 +13,23 @@ export async function StoresIndexPageView({ searchParams = {} }) {
|
|
|
13
13
|
const sort = sp(searchParams, "sort") || "-createdAt";
|
|
14
14
|
const page = Number(sp(searchParams, "page")) || 1;
|
|
15
15
|
const pageSize = Number(sp(searchParams, "pageSize")) || 24;
|
|
16
|
-
|
|
16
|
+
/*
|
|
17
|
+
* 🛑 `q` MUST be read here, and passed as the third argument exactly as
|
|
18
|
+
* /api/stores does — `listStores(model, true, { search })`.
|
|
19
|
+
*
|
|
20
|
+
* It was not, so /stores?q=anything rendered the FULL store list: measured
|
|
21
|
+
* live, `?q=zzzznope` returned two real store cards and no empty state. The
|
|
22
|
+
* search box looked like it worked because a real term also returns rows —
|
|
23
|
+
* only a nonsense term exposes a filter that is not filtering.
|
|
24
|
+
*
|
|
25
|
+
* And it could not self-correct on the client: this result is handed to
|
|
26
|
+
* <StoresIndexListing initialData=...>, and every public listing hook sets
|
|
27
|
+
* `staleTime: Infinity` when given SSR data, so the unfiltered first paint is
|
|
28
|
+
* frozen for that query key. Root Cause #30 — the SSR builder has to compute
|
|
29
|
+
* the same filters the client would, because it never gets a second chance.
|
|
30
|
+
*/
|
|
31
|
+
const q = sp(searchParams, "q").trim();
|
|
32
|
+
const result = await safeRead(() => storeRepository.listStores({ page, pageSize, sorts: sort }, true, q ? { search: q } : undefined), { route: "/stores", key: "stores.listStores", fallback: null });
|
|
17
33
|
/*
|
|
18
34
|
* The API already filters this; the SSR page called the repository directly
|
|
19
35
|
* and did not, so the first paint rendered a card for `store-tester-sandbox`
|
|
@@ -213,7 +213,7 @@ export const authored = {
|
|
|
213
213
|
},
|
|
214
214
|
"checklist-admin-users-trust-support-tickets-triage-admin": {
|
|
215
215
|
roles: ["admin"],
|
|
216
|
-
startPage: "/admin/support",
|
|
216
|
+
startPage: "/admin/support-tickets",
|
|
217
217
|
steps: [
|
|
218
218
|
"Sign in as admin@letitrip.in / TempPass123!.",
|
|
219
219
|
"Open the admin support tickets surface and read every column.",
|
|
@@ -130,9 +130,10 @@ export const authored = {
|
|
|
130
130
|
endResult: "After reload the same rows appear in the same order. An empty list here immediately after placing a bid means the bid was written without a buyer reference.",
|
|
131
131
|
},
|
|
132
132
|
"checklist-buying-bidding-bid-history-auction-detail-pagination": {
|
|
133
|
-
roles: ["
|
|
133
|
+
roles: ["buyer"],
|
|
134
134
|
startPage: "/auctions/auction-beyblade-metal-lightning-l-drago",
|
|
135
135
|
steps: [
|
|
136
|
+
"Sign in as a buyer — bid amounts are hidden from signed-out visitors, so this case cannot read them as a guest.",
|
|
136
137
|
"Open /auctions/auction-beyblade-metal-lightning-l-drago.",
|
|
137
138
|
"Click the 'Bid History' control to expand it.",
|
|
138
139
|
"Read the amounts on the first page, top to bottom.",
|
|
@@ -350,16 +351,16 @@ export const authored = {
|
|
|
350
351
|
endResult: "Reloading the auction still shows ₹15,000.00 and 1 bid, and the row is still on /user/bids.",
|
|
351
352
|
},
|
|
352
353
|
"checklist-buying-bidding-place-bid-live-other-viewer": {
|
|
353
|
-
roles: ["buyer"
|
|
354
|
+
roles: ["buyer"],
|
|
354
355
|
startPage: "/auctions/auction-tester-sandbox-cycle-2",
|
|
355
356
|
steps: [
|
|
356
357
|
"In window A, sign in as rehan.sheikh@gmail.com / TempPass123! and open /auctions/auction-tester-sandbox-cycle-2.",
|
|
357
|
-
"In window B, open the same auction
|
|
358
|
+
"In window B, sign in as a DIFFERENT buyer (vivaan.kapoor@gmail.com / TempPass123!) and open the same auction; read the current bid and bid count.",
|
|
358
359
|
"In window A, click 'Place a bid', select the 'Minimum' preset at ₹15,000.00, and click 'Place Bid'.",
|
|
359
360
|
"Switch to window B and watch for up to 10 seconds without reloading.",
|
|
360
361
|
],
|
|
361
362
|
inputs: { bidAmount: 15000 },
|
|
362
|
-
expectedBehaviour: "Bid updates are relayed over SSE from the server
|
|
363
|
+
expectedBehaviour: "Bid updates are relayed over SSE from the server — the underlying realtime node is not client-readable, so the browser never reads it directly. Window B is a SECOND SIGNED-IN BUYER, not a signed-out visitor: bid figures are now hidden from guests, so a signed-out window B would show 'Sign in to see the current bid' before and after and could never demonstrate the stream at all.",
|
|
363
364
|
expectedUiState: "Window B moves to 'Current bid ₹15,000.00' and '1 bid' within about 10 seconds, with no reload and no interaction. Figures that only change on refresh mean the stream is not connected.",
|
|
364
365
|
expectedData: { currentBid: 15000, bidCount: 1 },
|
|
365
366
|
endResult: "Reloading window B shows the same figures it had already updated to.",
|
|
@@ -58,25 +58,26 @@ export const authored = {
|
|
|
58
58
|
},
|
|
59
59
|
"checklist-buying-browsing-search-auctions-show-ended-off-shows-live": {
|
|
60
60
|
roles: ["guest"],
|
|
61
|
-
startPage: "/
|
|
61
|
+
startPage: "/auctions",
|
|
62
62
|
steps: [
|
|
63
|
-
"Open /
|
|
64
|
-
"
|
|
63
|
+
"Open /auctions.",
|
|
64
|
+
"Read the availability tab bar and the grid.",
|
|
65
65
|
],
|
|
66
|
-
expectedBehaviour: "Live auctions appear on the Available tab
|
|
67
|
-
expectedUiState: "At least one auction card
|
|
66
|
+
expectedBehaviour: "Live auctions appear on the Available tab, which is selected on arrival. The page is not empty. This case used to start on /products with the Auctions chip ticked; auctions now live only on their own page.",
|
|
67
|
+
expectedUiState: "At least one live auction card is visible (e.g. 'Beyblade Burst B-100 Cho-Z Achilles' or 'Metal Fight Beyblade BB-99 Lightning L-Drago'). The middle tab reads 'Ended' and is NOT selected. The grid is not empty and no error toast appears.",
|
|
68
68
|
endResult: "Nothing is persisted.",
|
|
69
69
|
},
|
|
70
70
|
"checklist-buying-browsing-search-auctions-show-ended-with-nondefault-sort": {
|
|
71
|
-
roles: ["
|
|
72
|
-
startPage: "/
|
|
71
|
+
roles: ["buyer"],
|
|
72
|
+
startPage: "/auctions",
|
|
73
73
|
steps: [
|
|
74
|
-
"
|
|
75
|
-
"
|
|
74
|
+
"Sign in as a buyer.",
|
|
75
|
+
"Open /auctions.",
|
|
76
76
|
"Open the 'Sort by' dropdown and select 'Highest Current Bid'.",
|
|
77
|
+
"Read the order of the current-bid figures on the cards.",
|
|
77
78
|
],
|
|
78
|
-
expectedBehaviour: "Sorting by 'Highest Current Bid'
|
|
79
|
-
expectedUiState: "The grid shows live auction cards
|
|
79
|
+
expectedBehaviour: "Sorting by 'Highest Current Bid' on the Available tab still returns only live auctions, ordered by current bid descending, and the grid is not empty. The role is BUYER, not guest: 'Highest Current Bid' is a money sort and is not offered to signed-out visitors, because ordering by bid ranks every hidden amount on the page.",
|
|
80
|
+
expectedUiState: "The grid shows live auction cards whose current-bid figures descend from top-left to bottom-right. No ended auction cards are visible. No 'No products found' message appears.",
|
|
80
81
|
endResult: "Nothing is persisted.",
|
|
81
82
|
},
|
|
82
83
|
"checklist-buying-browsing-search-browse-categories": {
|
|
@@ -146,9 +147,10 @@ export const authored = {
|
|
|
146
147
|
endResult: "Nothing is persisted.",
|
|
147
148
|
},
|
|
148
149
|
"checklist-buying-browsing-search-filter-drawer-combines-correctly": {
|
|
149
|
-
roles: ["
|
|
150
|
+
roles: ["buyer"],
|
|
150
151
|
startPage: "/products",
|
|
151
152
|
steps: [
|
|
153
|
+
"Sign in as a buyer — the Price Range facet is hidden from signed-out visitors.",
|
|
152
154
|
"Open /products.",
|
|
153
155
|
"Note the initial result count.",
|
|
154
156
|
"Click 'Filters'.",
|
|
@@ -199,9 +201,10 @@ export const authored = {
|
|
|
199
201
|
endResult: "The Left-hand mode preference persists in the user's profile across sessions.",
|
|
200
202
|
},
|
|
201
203
|
"checklist-buying-browsing-search-filters": {
|
|
202
|
-
roles: ["
|
|
204
|
+
roles: ["buyer"],
|
|
203
205
|
startPage: "/products",
|
|
204
206
|
steps: [
|
|
207
|
+
"Sign in as a buyer — the Price Range facet is hidden from signed-out visitors.",
|
|
205
208
|
"Open /products.",
|
|
206
209
|
"Note the result count shown in pagination.",
|
|
207
210
|
"Click 'Filters'.",
|
|
@@ -369,19 +372,22 @@ export const authored = {
|
|
|
369
372
|
expectedUiState: "After clicking Page 2: the pagination bar highlights Page 2 and the URL contains 'page=2'. After Next: the next page loads with different cards. After Previous: the previous page loads. No page shows 'No products found' unless the total results genuinely fit on one page.",
|
|
370
373
|
endResult: "Nothing is persisted to the server. The page parameter is URL state.",
|
|
371
374
|
},
|
|
372
|
-
"checklist-buying-browsing-search-
|
|
375
|
+
"checklist-buying-browsing-search-every-listing-type-reachable-from-nav-or-products": {
|
|
373
376
|
roles: ["guest"],
|
|
374
377
|
startPage: "/products",
|
|
375
378
|
steps: [
|
|
376
|
-
"Open /products.",
|
|
377
|
-
"Click '
|
|
378
|
-
"
|
|
379
|
-
"Click '
|
|
380
|
-
"
|
|
379
|
+
"Open /products and read the main navigation bar.",
|
|
380
|
+
"Click 'Auctions' in the nav and confirm at least one auction card is shown.",
|
|
381
|
+
"Click 'Pre-Orders' in the nav and confirm at least one pre-order card is shown.",
|
|
382
|
+
"Click 'Prize Draws' in the nav and confirm at least one prize-draw card is shown.",
|
|
383
|
+
"Click 'Art & Stickers' in the nav and confirm at least one art or sticker card is shown.",
|
|
384
|
+
"Return to /products, click 'Filters', tick 'Classifieds', click 'Apply', and read the grid.",
|
|
385
|
+
"Click 'Filters', untick 'Classifieds', tick 'Digital Codes', click 'Apply', and read the grid.",
|
|
386
|
+
"Click 'Filters', untick 'Digital Codes', tick 'Live Items', click 'Apply', and read the grid.",
|
|
381
387
|
],
|
|
382
|
-
expectedBehaviour: "
|
|
383
|
-
expectedUiState: "
|
|
384
|
-
endResult: "Nothing is persisted to the server.",
|
|
388
|
+
expectedBehaviour: "All nine listing types are reachable without typing a URL. Five reach their own page from the main nav; the remaining four — Standard, Classifieds, Digital Codes, Live Items — are reached through the /products Listing type filter, because they have no nav entry of their own.",
|
|
389
|
+
expectedUiState: "Each nav click lands on the matching page with at least one real card: /auctions, /pre-orders, /prize-draws, /art. Each /products filter selection narrows the grid to that type with at least one card and no 'No products found' state — Classifieds shows 'Classified' badges, Digital Codes shows 'Digital Code' badges, Live Items shows 'Live Item' badges.",
|
|
390
|
+
endResult: "Nothing is persisted to the server. A type that can be reached from neither the nav nor the /products filter is the regression this case exists for.",
|
|
385
391
|
},
|
|
386
392
|
"checklist-buying-browsing-search-product-filter-status-labels": {
|
|
387
393
|
roles: ["admin"],
|
|
@@ -403,48 +409,45 @@ export const authored = {
|
|
|
403
409
|
steps: [
|
|
404
410
|
"Open /products.",
|
|
405
411
|
"Click 'Filters'.",
|
|
406
|
-
"Tick only '
|
|
412
|
+
"Tick only 'Classifieds' under Listing type.",
|
|
407
413
|
"Read the contents of the drawer below the Listing type checkboxes, without pressing Apply.",
|
|
408
|
-
"Click the 'Full
|
|
414
|
+
"Click the 'Full Classifieds filters →' link.",
|
|
409
415
|
"Navigate back to /products.",
|
|
410
|
-
"Click 'Filters', untick '
|
|
416
|
+
"Click 'Filters', untick 'Classifieds', tick only 'Digital Codes', and read for a similar link.",
|
|
411
417
|
"Navigate back to /products.",
|
|
412
|
-
"Click 'Filters', tick '
|
|
418
|
+
"Click 'Filters', tick 'Classifieds' and 'Digital Codes' both, and read for a link.",
|
|
413
419
|
"Navigate back to /products.",
|
|
414
420
|
"Click 'Filters', untick all, and read for a link.",
|
|
421
|
+
"Click 'Filters', tick only 'Standard', and read for a link.",
|
|
415
422
|
],
|
|
416
|
-
expectedBehaviour: "When exactly one type that has its own browse page is ticked, a 'Full <type> filters →' link appears in the drawer below the Listing type section (before pressing Apply). The link navigates to that type's dedicated page. With two types ticked or none ticked, the link
|
|
417
|
-
expectedUiState: "
|
|
423
|
+
expectedBehaviour: "When exactly one type that has its own browse page is ticked, a 'Full <type> filters →' link appears in the drawer below the Listing type section (before pressing Apply). The link navigates to that type's dedicated page, which carries facets this generic page cannot. With two types ticked or none ticked, no link appears. Standard is the exception: its 'dedicated page' IS /products, so offering a link would point the page at itself.",
|
|
424
|
+
expectedUiState: "Classifieds only: 'Full Classifieds filters →' link is visible; clicking it lands on /classified. Digital Codes only: 'Full Digital Codes filters →' link is visible. Both ticked: no link visible. None ticked: no link visible. Standard only: NO link is visible — in particular no 'Full Products filters →' link pointing back at /products.",
|
|
418
425
|
endResult: "Nothing is persisted to the server.",
|
|
419
426
|
},
|
|
420
427
|
"checklist-buying-browsing-search-product-type-chip-drives-sort-options": {
|
|
421
428
|
roles: ["guest"],
|
|
422
429
|
startPage: "/products",
|
|
423
430
|
steps: [
|
|
424
|
-
"
|
|
425
|
-
"
|
|
426
|
-
"Open the 'Sort by' dropdown
|
|
427
|
-
"
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
],
|
|
432
|
-
expectedBehaviour: "With only Auctions selected, 'Ending Soon' appears in Sort. With only Pre-Orders, 'Earliest Delivery' appears. With both selected, only shared sorts (Newest, Price, Name) remain — 'Ending Soon' and 'Earliest Delivery' are both absent.",
|
|
433
|
-
expectedUiState: "Auctions only: Sort dropdown includes 'Ending Soon'. Pre-Orders only: Sort dropdown includes 'Earliest Delivery'; 'Ending Soon' is absent. Both: Sort dropdown contains only generic options like 'Newest First', 'Price low to high', 'Price high to low'.",
|
|
431
|
+
"Sign in as a buyer (price sorts are hidden from signed-out visitors).",
|
|
432
|
+
"Open /auctions and open the 'Sort by' dropdown; read the available options.",
|
|
433
|
+
"Open /pre-orders and open the 'Sort by' dropdown; read the available options.",
|
|
434
|
+
"Open /products and open the 'Sort by' dropdown; read the available options.",
|
|
435
|
+
],
|
|
436
|
+
expectedBehaviour: "Each listing type offers the sorts that make sense for it, and only those. An auction sorts by how soon it ends; a pre-order by when it is delivered; the general catalogue by neither, because neither field means anything for a standard product. This used to be exercised by ticking Auctions and Pre-Orders on /products; those types now live only on their own pages, so the contrast is read across pages instead.",
|
|
437
|
+
expectedUiState: "/auctions: the Sort dropdown includes 'Ending Soon'. /pre-orders: it includes 'Earliest Delivery' and does NOT include 'Ending Soon'. /products: it includes neither 'Ending Soon' nor 'Earliest Delivery', offering generic options such as 'Newest First' and the two price sorts.",
|
|
434
438
|
endResult: "Nothing is persisted to the server.",
|
|
435
439
|
},
|
|
436
440
|
"checklist-buying-browsing-search-product-type-chip-sort-resets-no-empty-page": {
|
|
437
441
|
roles: ["guest"],
|
|
438
442
|
startPage: "/products",
|
|
439
443
|
steps: [
|
|
440
|
-
"Open /products.",
|
|
441
|
-
"
|
|
442
|
-
"Open
|
|
443
|
-
"
|
|
444
|
-
"Open the 'Sort by' dropdown and read the selected option.",
|
|
444
|
+
"Open /products?sort=auctionEndDate:asc directly in the address bar — an auction-only sort that the general catalogue cannot satisfy.",
|
|
445
|
+
"Read the 'Sort by' dropdown and the grid.",
|
|
446
|
+
"Open /products?listingType=auction directly in the address bar.",
|
|
447
|
+
"Read the URL the browser settles on and the page heading.",
|
|
445
448
|
],
|
|
446
|
-
expectedBehaviour: "
|
|
447
|
-
expectedUiState: "
|
|
449
|
+
expectedBehaviour: "A sort or a listing type that /products does not offer must degrade visibly, never silently. An unsupported sort falls back to the default rather than running a query Firestore rejects, which would surface as a bare empty grid with no error. A URL naming only out-of-catalogue types redirects to the page that owns them, rather than quietly showing the whole general catalogue as though the filter had been honoured.",
|
|
450
|
+
expectedUiState: "First URL: the Sort dropdown reads 'Newest First', the grid shows product cards, and there is no 'No products found' state and no error. Second URL: the browser lands on /auctions and the page heading reads Auctions — it does NOT stay on /products showing unfiltered results.",
|
|
448
451
|
endResult: "Nothing is persisted to the server.",
|
|
449
452
|
},
|
|
450
453
|
"checklist-buying-browsing-search-product-type-chips-cover-all-types": {
|
|
@@ -456,8 +459,8 @@ export const authored = {
|
|
|
456
459
|
"Count every checkbox under the 'Listing type' heading.",
|
|
457
460
|
"Read each checkbox label.",
|
|
458
461
|
],
|
|
459
|
-
expectedBehaviour: "Exactly
|
|
460
|
-
expectedUiState: "
|
|
462
|
+
expectedBehaviour: "Exactly four listing-type checkboxes are present: Standard, Classifieds, Digital Codes, Live Items. Those are the four types with no dedicated entry in the main nav, so /products is their only discovery path. Auctions, Pre-Orders, Prize Draws, Art and Stickers are NOT offered here — each owns a dedicated page reachable from the main nav. No checkbox labelled 'Bundles' is present; a bundle is a category, not a listing type.",
|
|
463
|
+
expectedUiState: "BEFORE: nine checkboxes, including Auctions, Pre-Orders, Prize Draws, Art and Stickers. AFTER: the Listing type section shows exactly four — Standard, Classifieds, Digital Codes, Live Items. No 'Auctions', 'Pre-Orders', 'Prize Draws', 'Art', 'Stickers' or 'Bundles' checkbox is present.",
|
|
461
464
|
endResult: "Nothing is persisted.",
|
|
462
465
|
},
|
|
463
466
|
"checklist-buying-browsing-search-product-type-chips-multi-select": {
|
|
@@ -466,17 +469,17 @@ export const authored = {
|
|
|
466
469
|
steps: [
|
|
467
470
|
"Open /products.",
|
|
468
471
|
"Click 'Filters'.",
|
|
469
|
-
"Tick '
|
|
470
|
-
"Tick '
|
|
472
|
+
"Tick 'Classifieds'.",
|
|
473
|
+
"Tick 'Digital Codes' without unticking 'Classifieds'.",
|
|
471
474
|
"Click 'Apply'.",
|
|
472
475
|
"Click 'Filters' again to reopen the drawer.",
|
|
473
476
|
"Read which boxes are ticked.",
|
|
474
477
|
"Click 'Close'.",
|
|
475
|
-
"Untick both '
|
|
478
|
+
"Untick both 'Classifieds' and 'Digital Codes' in the drawer.",
|
|
476
479
|
"Click 'Apply'.",
|
|
477
480
|
],
|
|
478
|
-
expectedBehaviour: "Both checkboxes remain ticked after Apply. The grid shows a mix of
|
|
479
|
-
expectedUiState: "After first Apply: the Filters badge count is 2; the grid shows cards labelled '
|
|
481
|
+
expectedBehaviour: "Both checkboxes remain ticked after Apply. The grid shows a mix of Classified and Digital Code cards. After unticking both and applying, the grid returns to showing every type the general catalogue spans.",
|
|
482
|
+
expectedUiState: "After first Apply: the Filters badge count is 2; the grid shows cards labelled 'Classified' and 'Digital Code'. Reopening drawer: both Classifieds and Digital Codes are ticked. After second Apply with none ticked: badge count returns to 0 and cards of multiple types reappear.",
|
|
480
483
|
endResult: "Nothing is persisted to the server. Filter state is URL state.",
|
|
481
484
|
},
|
|
482
485
|
"checklist-buying-browsing-search-product-type-chips-none-means-all": {
|
|
@@ -488,8 +491,8 @@ export const authored = {
|
|
|
488
491
|
"Click 'Close'.",
|
|
489
492
|
"Scan the badges on the first page of cards.",
|
|
490
493
|
],
|
|
491
|
-
expectedBehaviour: "With no Listing type filter applied the grid shows
|
|
492
|
-
expectedUiState: "At least two different type badges are visible on page 1, for example
|
|
494
|
+
expectedBehaviour: "With no Listing type filter applied the grid shows every type the GENERAL CATALOGUE spans — not every type in the system. At least two different type badges are visible on the first page, and none of them is a type that owns a dedicated nav page.",
|
|
495
|
+
expectedUiState: "At least two different type badges are visible on page 1, drawn only from Classified / Digital Code / Live Item / no-badge (Standard) — for example a 'Classified' card alongside a 'Digital Code' card. NO 'Auction', 'Pre-Order', 'Prize Draw', 'Art Print' or 'Sticker Sheet' badge appears.",
|
|
493
496
|
endResult: "Nothing is persisted.",
|
|
494
497
|
},
|
|
495
498
|
"checklist-buying-browsing-search-product-type-filter-counts-in-badge": {
|
|
@@ -498,7 +501,7 @@ export const authored = {
|
|
|
498
501
|
steps: [
|
|
499
502
|
"Open /products.",
|
|
500
503
|
"Click 'Filters'.",
|
|
501
|
-
"Tick '
|
|
504
|
+
"Tick 'Classifieds'.",
|
|
502
505
|
"Click 'Apply'.",
|
|
503
506
|
"Note the new number on the Filters badge.",
|
|
504
507
|
"Click 'Filters' again.",
|
|
@@ -506,8 +509,8 @@ export const authored = {
|
|
|
506
509
|
"Click 'Apply'.",
|
|
507
510
|
"Note the Filters badge number and the grid content.",
|
|
508
511
|
],
|
|
509
|
-
expectedBehaviour: "Ticking one Listing type box and pressing Apply increases the Filters badge by 1. 'Reset all' unticks every type box and restores the badge to 0, and the grid repopulates with
|
|
510
|
-
expectedUiState: "After ticking
|
|
512
|
+
expectedBehaviour: "Ticking one Listing type box and pressing Apply increases the Filters badge by 1. 'Reset all' unticks every type box and restores the badge to 0, and the grid repopulates with every type the general catalogue spans.",
|
|
513
|
+
expectedUiState: "After ticking Classifieds and applying: badge shows 1 (or higher if defaults add to it). After Reset all and Apply: badge shows 0 and the grid contains mixed listing types.",
|
|
511
514
|
endResult: "Nothing is persisted to the server.",
|
|
512
515
|
},
|
|
513
516
|
"checklist-buying-browsing-search-product-type-filter-lives-in-drawer": {
|
|
@@ -530,16 +533,16 @@ export const authored = {
|
|
|
530
533
|
"Open /products with no filters applied.",
|
|
531
534
|
"Note the types of cards visible in the grid.",
|
|
532
535
|
"Click 'Filters'.",
|
|
533
|
-
"Tick '
|
|
536
|
+
"Tick 'Classifieds' and watch the grid behind the open drawer.",
|
|
534
537
|
"Click 'Apply'.",
|
|
535
538
|
"Note the grid now.",
|
|
536
539
|
"Click 'Filters' again.",
|
|
537
|
-
"Tick '
|
|
540
|
+
"Tick 'Live Items'.",
|
|
538
541
|
"Click the X (Close) button on the drawer — do not click Apply.",
|
|
539
542
|
"Click 'Filters' again to reopen and read which boxes are ticked.",
|
|
540
543
|
],
|
|
541
544
|
expectedBehaviour: "Ticking a box does not change the grid until Apply is pressed. Closing with X discards pending changes — the grid and the ticked state both revert to what was applied before.",
|
|
542
|
-
expectedUiState: "While drawer is open with
|
|
545
|
+
expectedUiState: "While drawer is open with Classifieds ticked: grid behind the drawer still shows mixed types. After Apply: grid shows only Classified cards. After ticking Live Items and closing with X: grid still shows only Classified cards (Live Items was discarded). Reopening drawer: only Classifieds is ticked; Live Items is unticked.",
|
|
543
546
|
endResult: "Nothing is persisted to the server.",
|
|
544
547
|
},
|
|
545
548
|
"checklist-buying-browsing-search-product-type-selection-survives-reload": {
|
|
@@ -547,7 +550,7 @@ export const authored = {
|
|
|
547
550
|
startPage: "/products",
|
|
548
551
|
steps: [
|
|
549
552
|
"Open /products.",
|
|
550
|
-
"Click 'Filters', tick '
|
|
553
|
+
"Click 'Filters', tick 'Classifieds' and 'Live Items', click 'Apply'.",
|
|
551
554
|
"Copy the current URL from the address bar.",
|
|
552
555
|
"Open the copied URL in a new browser tab.",
|
|
553
556
|
"Click 'Filters' in the new tab and read which boxes are ticked.",
|
|
@@ -555,24 +558,22 @@ export const authored = {
|
|
|
555
558
|
"Press the browser Back button in the original tab.",
|
|
556
559
|
"Click 'Filters' in the original tab and read which boxes are ticked.",
|
|
557
560
|
],
|
|
558
|
-
expectedBehaviour: "The applied listing types are encoded in the URL. Opening the URL in a new tab shows the same filtered grid with
|
|
559
|
-
expectedUiState: "New tab: grid shows
|
|
561
|
+
expectedBehaviour: "The applied listing types are encoded in the URL. Opening the URL in a new tab shows the same filtered grid with Classifieds and Live Items ticked. Pressing Back restores the previous selection.",
|
|
562
|
+
expectedUiState: "New tab: grid shows Classified and Live Item cards; Filters drawer shows Classifieds and Live Items ticked. After Back: the previous filter state (before the Classifieds + Live Items selection) is restored.",
|
|
560
563
|
endResult: "Nothing is persisted to the server. Filter state is URL state.",
|
|
561
564
|
},
|
|
562
565
|
"checklist-buying-browsing-search-product-type-toggles-follow-selection": {
|
|
563
566
|
roles: ["guest"],
|
|
564
567
|
startPage: "/products",
|
|
565
568
|
steps: [
|
|
566
|
-
"Open /products.",
|
|
567
|
-
"Click
|
|
568
|
-
"
|
|
569
|
-
"Click that middle tab.",
|
|
570
|
-
"
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
expectedBehaviour: "With only Auctions selected, the middle tab is labelled 'Ended'. With only Standard selected, the middle tab is labelled 'Sold'. Clicking the tab in each case shows the respective unavailable items of that type.",
|
|
575
|
-
expectedUiState: "Auctions only: middle tab reads 'Ended'; clicking it shows ended auction cards. Standard only: middle tab reads 'Sold'; clicking it shows sold-out product cards.",
|
|
569
|
+
"Open /products and read the label of the middle availability tab above the grid.",
|
|
570
|
+
"Click that middle tab and read the cards.",
|
|
571
|
+
"Open /auctions and read the label of its middle availability tab.",
|
|
572
|
+
"Click that middle tab and read the cards.",
|
|
573
|
+
"Open /prize-draws and read the label of its middle availability tab.",
|
|
574
|
+
],
|
|
575
|
+
expectedBehaviour: "The middle availability tab is named for how that listing type stops being available: a product runs out, an auction ends, a prize draw closes. This used to be read by ticking Auctions on /products; auctions now live only on their own page, so the three wordings are read across the three pages.",
|
|
576
|
+
expectedUiState: "/products: the middle tab reads 'Sold'; clicking it shows sold-out product cards. /auctions: it reads 'Ended'; clicking it shows ended auction cards. /prize-draws: it reads 'Closed'. In all three the first tab is 'Available' and is selected on arrival.",
|
|
576
577
|
endResult: "Nothing is persisted to the server.",
|
|
577
578
|
},
|
|
578
579
|
"checklist-buying-browsing-search-search": {
|
|
@@ -598,15 +599,15 @@ export const authored = {
|
|
|
598
599
|
steps: [
|
|
599
600
|
"Open /products.",
|
|
600
601
|
"Type 'Beyblade' in the search field and click 'Search'.",
|
|
601
|
-
"Click 'Filters', tick '
|
|
602
|
-
"Open the 'Sort by' dropdown and select '
|
|
602
|
+
"Click 'Filters', tick 'Classifieds', click 'Apply'.",
|
|
603
|
+
"Open the 'Sort by' dropdown and select 'Name: A–Z'.",
|
|
603
604
|
"Read the URL.",
|
|
604
605
|
"Click 'Page 2' in the pagination bar if available.",
|
|
605
606
|
"Read the URL and note the result count.",
|
|
606
607
|
],
|
|
607
|
-
inputs: { query: "Beyblade", listingType: "
|
|
608
|
-
expectedBehaviour: "All three — search query, listing type filter, and sort — stay applied together. Pagination reflects the combined narrowed result count, not the unfiltered total.",
|
|
609
|
-
expectedUiState: "The URL contains the search term, the listing-type parameter, and the sort parameter together. The grid shows only
|
|
608
|
+
inputs: { query: "Beyblade", listingType: "Classifieds", sort: "Name: A–Z" },
|
|
609
|
+
expectedBehaviour: "All three — search query, listing type filter, and sort — stay applied together. Pagination reflects the combined narrowed result count, not the unfiltered total. The sort used here is deliberately a non-money one: price sorts are not offered to signed-out visitors.",
|
|
610
|
+
expectedUiState: "The URL contains the search term, the listing-type parameter, and the sort parameter together. The grid shows only Classified cards whose title includes 'Beyblade', ordered A–Z by title. Page 2 (if navigated) continues this filtered/sorted set, not the unfiltered set.",
|
|
610
611
|
endResult: "Nothing is persisted to the server. State is URL-encoded.",
|
|
611
612
|
},
|
|
612
613
|
"checklist-buying-browsing-search-show-sold-toggle-reveals-items": {
|
|
@@ -631,23 +632,22 @@ export const authored = {
|
|
|
631
632
|
endResult: "Nothing is persisted. Tab state is URL state only.",
|
|
632
633
|
},
|
|
633
634
|
"checklist-buying-browsing-search-sort-options-per-listing-type": {
|
|
634
|
-
roles: ["
|
|
635
|
+
roles: ["buyer"],
|
|
635
636
|
startPage: "/products",
|
|
636
637
|
steps: [
|
|
638
|
+
"Sign in as a buyer — price sorts are not offered to signed-out visitors.",
|
|
637
639
|
"Open /products with no type filter.",
|
|
638
640
|
"Open the 'Sort by' dropdown and select 'Price low to high'.",
|
|
639
641
|
"Note the order of cards (cheapest first).",
|
|
640
642
|
"Open the 'Sort by' dropdown and select 'Price high to low'.",
|
|
641
643
|
"Note the order of cards (most expensive first).",
|
|
642
|
-
"
|
|
643
|
-
"Open Sort and select 'Ending Soon'.",
|
|
644
|
+
"Open /auctions, open Sort and select 'Ending Soon'.",
|
|
644
645
|
"Note the order of auction cards.",
|
|
645
|
-
"
|
|
646
|
-
"Open Sort and select 'Earliest Delivery'.",
|
|
646
|
+
"Open /pre-orders, open Sort and select 'Earliest Delivery'.",
|
|
647
647
|
"Note the order of pre-order cards.",
|
|
648
648
|
],
|
|
649
|
-
expectedBehaviour: "Each sort option genuinely reorders the visible results. Price sorts change the order by price. Ending Soon reorders auctions by end date. Earliest Delivery reorders pre-orders by delivery date.",
|
|
650
|
-
expectedUiState: "Price low to high: cheapest card first (e.g. ₹149 before ₹1,299). Price high to low: most expensive first. Ending Soon: auctions with soonest end date appear first. Earliest Delivery: pre-orders with earliest expected delivery appear first.",
|
|
649
|
+
expectedBehaviour: "Each sort option genuinely reorders the visible results. Price sorts change the order by price. Ending Soon reorders auctions by end date. Earliest Delivery reorders pre-orders by delivery date. The auction and pre-order legs run on their own pages, because those types are no longer selectable on /products.",
|
|
650
|
+
expectedUiState: "Price low to high: cheapest card first (e.g. ₹149 before ₹1,299). Price high to low: most expensive first. Ending Soon on /auctions: auctions with soonest end date appear first. Earliest Delivery on /pre-orders: pre-orders with earliest expected delivery appear first.",
|
|
651
651
|
endResult: "Nothing is persisted.",
|
|
652
652
|
},
|
|
653
653
|
};
|