@mohasinac/appkit 4.34.1 → 4.35.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/lottery/LotteryAdminEditView.js +32 -2
- package/dist/features/tester/components/TesterChecklistStepRow.d.ts +9 -1
- package/dist/features/tester/components/TesterChecklistStepRow.js +31 -2
- package/dist/features/tester/components/TesterHubView.js +8 -0
- package/dist/features/tester/schemas/firestore.d.ts +109 -0
- package/dist/features/tester/schemas/firestore.js +14 -0
- package/dist/features/tester/seed-data/_money-flows.d.ts +23 -0
- package/dist/features/tester/seed-data/_money-flows.js +430 -0
- package/dist/features/tester/seed-data/authored/_types.d.ts +33 -0
- package/dist/features/tester/seed-data/authored/_types.js +35 -0
- package/dist/features/tester/seed-data/authored/account-auth__profile-settings.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/account-auth__profile-settings.js +207 -0
- package/dist/features/tester/seed-data/authored/account-auth__signup-login.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/account-auth__signup-login.js +298 -0
- package/dist/features/tester/seed-data/authored/account-auth__testing-program--admin.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/account-auth__testing-program--admin.js +33 -0
- package/dist/features/tester/seed-data/authored/account-auth__testing-program.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/account-auth__testing-program.js +138 -0
- package/dist/features/tester/seed-data/authored/addresses__address-filters.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/addresses__address-filters.js +36 -0
- package/dist/features/tester/seed-data/authored/addresses__postal-lookup.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/addresses__postal-lookup.js +75 -0
- package/dist/features/tester/seed-data/authored/addresses__postal-validation.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/addresses__postal-validation.js +72 -0
- package/dist/features/tester/seed-data/authored/addresses__state-picker.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/addresses__state-picker.js +54 -0
- package/dist/features/tester/seed-data/authored/addresses__unban-request.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/addresses__unban-request.js +39 -0
- package/dist/features/tester/seed-data/authored/admin__bans-and-trust.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__bans-and-trust.js +226 -0
- package/dist/features/tester/seed-data/authored/admin__blog-faqs.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__blog-faqs.js +82 -0
- package/dist/features/tester/seed-data/authored/admin__bulk-actions.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__bulk-actions.js +177 -0
- package/dist/features/tester/seed-data/authored/admin__bundles.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__bundles.js +97 -0
- package/dist/features/tester/seed-data/authored/admin__buyer-data-admin.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__buyer-data-admin.js +232 -0
- package/dist/features/tester/seed-data/authored/admin__catalog-listings.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__catalog-listings.js +306 -0
- package/dist/features/tester/seed-data/authored/admin__category-brand-authoring.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__category-brand-authoring.js +158 -0
- package/dist/features/tester/seed-data/authored/admin__classifieds-digitalcodes-live.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__classifieds-digitalcodes-live.js +78 -0
- package/dist/features/tester/seed-data/authored/admin__content-deletes.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__content-deletes.js +261 -0
- package/dist/features/tester/seed-data/authored/admin__content-marketing.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__content-marketing.js +151 -0
- package/dist/features/tester/seed-data/authored/admin__coupons.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__coupons.js +214 -0
- package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.js +160 -0
- package/dist/features/tester/seed-data/authored/admin__media-watermark.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__media-watermark.js +104 -0
- package/dist/features/tester/seed-data/authored/admin__orders-fulfillment.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__orders-fulfillment.js +350 -0
- package/dist/features/tester/seed-data/authored/admin__prize-draws-lotteries.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__prize-draws-lotteries.js +105 -0
- package/dist/features/tester/seed-data/authored/admin__site-system.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__site-system.js +590 -0
- package/dist/features/tester/seed-data/authored/admin__uncovered-admin-pages.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__uncovered-admin-pages.js +235 -0
- package/dist/features/tester/seed-data/authored/admin__users-trust.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/admin__users-trust.js +569 -0
- package/dist/features/tester/seed-data/authored/buying__bidding.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__bidding.js +417 -0
- package/dist/features/tester/seed-data/authored/buying__browsing-search.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__browsing-search.js +653 -0
- package/dist/features/tester/seed-data/authored/buying__buying-checkout.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__buying-checkout.js +533 -0
- package/dist/features/tester/seed-data/authored/buying__buying-coupons.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__buying-coupons.js +379 -0
- package/dist/features/tester/seed-data/authored/buying__cart.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__cart.js +1171 -0
- package/dist/features/tester/seed-data/authored/buying__image-tile-layout.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__image-tile-layout.js +140 -0
- package/dist/features/tester/seed-data/authored/buying__my-orders.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__my-orders.js +257 -0
- package/dist/features/tester/seed-data/authored/buying__offers.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__offers.js +1111 -0
- package/dist/features/tester/seed-data/authored/buying__order-status-lifecycle.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__order-status-lifecycle.js +252 -0
- package/dist/features/tester/seed-data/authored/buying__product-detail.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__product-detail.js +387 -0
- package/dist/features/tester/seed-data/authored/buying__return-request.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__return-request.js +152 -0
- package/dist/features/tester/seed-data/authored/buying__reviews-pagination.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__reviews-pagination.js +242 -0
- package/dist/features/tester/seed-data/authored/buying__reviews.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__reviews.js +113 -0
- package/dist/features/tester/seed-data/authored/buying__user-dashboard-extras.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__user-dashboard-extras.js +283 -0
- package/dist/features/tester/seed-data/authored/buying__user-dashboard-navigation.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__user-dashboard-navigation.js +167 -0
- package/dist/features/tester/seed-data/authored/buying__user-uncovered-pages.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__user-uncovered-pages.js +90 -0
- package/dist/features/tester/seed-data/authored/buying__wishlist-history.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__wishlist-history.js +344 -0
- package/dist/features/tester/seed-data/authored/community-support__public-profile.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/community-support__public-profile.js +53 -0
- package/dist/features/tester/seed-data/authored/community-support__support-tickets.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/community-support__support-tickets.js +76 -0
- package/dist/features/tester/seed-data/authored/content-discovery__blog.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__blog.js +93 -0
- package/dist/features/tester/seed-data/authored/content-discovery__category-brand-relations.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__category-brand-relations.js +149 -0
- package/dist/features/tester/seed-data/authored/content-discovery__coupons.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__coupons.js +121 -0
- package/dist/features/tester/seed-data/authored/content-discovery__event-participation.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__event-participation.js +207 -0
- package/dist/features/tester/seed-data/authored/content-discovery__events.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__events.js +318 -0
- package/dist/features/tester/seed-data/authored/content-discovery__faq-help.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__faq-help.js +65 -0
- package/dist/features/tester/seed-data/authored/content-discovery__notifications.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__notifications.js +147 -0
- package/dist/features/tester/seed-data/authored/content-discovery__search.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__search.js +332 -0
- package/dist/features/tester/seed-data/authored/cta-layout__checkout-bottom-bar.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/cta-layout__checkout-bottom-bar.js +53 -0
- package/dist/features/tester/seed-data/authored/cta-layout__dialog-footers.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/cta-layout__dialog-footers.js +52 -0
- package/dist/features/tester/seed-data/authored/cta-layout__editor-action-bar.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/cta-layout__editor-action-bar.js +35 -0
- package/dist/features/tester/seed-data/authored/cta-layout__navbar-ctas.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/cta-layout__navbar-ctas.js +141 -0
- package/dist/features/tester/seed-data/authored/cta-layout__product-bottom-bar.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/cta-layout__product-bottom-bar.js +35 -0
- package/dist/features/tester/seed-data/authored/design-ux__back-to-top-button.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/design-ux__back-to-top-button.js +95 -0
- package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/design-ux__carousel-arrow-bounds.js +191 -0
- package/dist/features/tester/seed-data/authored/design-ux__dashboard-layout.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/design-ux__dashboard-layout.js +158 -0
- package/dist/features/tester/seed-data/authored/design-ux__footer-theme.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/design-ux__footer-theme.js +106 -0
- package/dist/features/tester/seed-data/authored/design-ux__form-validation-errors.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/design-ux__form-validation-errors.js +88 -0
- package/dist/features/tester/seed-data/authored/design-ux__general-design.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/design-ux__general-design.js +204 -0
- package/dist/features/tester/seed-data/authored/design-ux__hand-mode-layout.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/design-ux__hand-mode-layout.js +301 -0
- package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/design-ux__homepage-carousels.js +407 -0
- package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/design-ux__status-badge-legibility.js +214 -0
- package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/design-ux__sticky-cta-bar.js +275 -0
- package/dist/features/tester/seed-data/authored/index.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/index.js +237 -0
- package/dist/features/tester/seed-data/authored/page-wiring__data-loss.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/page-wiring__data-loss.js +102 -0
- package/dist/features/tester/seed-data/authored/page-wiring__detail-pages.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/page-wiring__detail-pages.js +50 -0
- package/dist/features/tester/seed-data/authored/page-wiring__drawer-pages.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/page-wiring__drawer-pages.js +66 -0
- package/dist/features/tester/seed-data/authored/page-wiring__reachability.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +115 -0
- package/dist/features/tester/seed-data/authored/public-pages__auth-error-pages.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/public-pages__auth-error-pages.js +136 -0
- package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +69 -0
- package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/public-pages__core-listing-pages.js +263 -0
- package/dist/features/tester/seed-data/authored/public-pages__help-how-it-works.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/public-pages__help-how-it-works.js +102 -0
- package/dist/features/tester/seed-data/authored/public-pages__help-scams-guides-subpages.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/public-pages__help-scams-guides-subpages.js +234 -0
- package/dist/features/tester/seed-data/authored/public-pages__legal-policy-pages.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/public-pages__legal-policy-pages.js +126 -0
- package/dist/features/tester/seed-data/authored/public-pages__stores-sellers-directories.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/public-pages__stores-sellers-directories.js +255 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__employee-permissions.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__employee-permissions.js +59 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__header-search.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +88 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__settings-deep-links.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__settings-deep-links.js +51 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__sidebar-search.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__sidebar-search.js +35 -0
- package/dist/features/tester/seed-data/authored/selling__become-seller.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__become-seller.js +94 -0
- package/dist/features/tester/seed-data/authored/selling__final-sale-authoring.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__final-sale-authoring.js +107 -0
- package/dist/features/tester/seed-data/authored/selling__listing-a-product.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__listing-a-product.js +285 -0
- package/dist/features/tester/seed-data/authored/selling__listing-lifecycle.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__listing-lifecycle.js +326 -0
- package/dist/features/tester/seed-data/authored/selling__listing-type-fields-roundtrip.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__listing-type-fields-roundtrip.js +136 -0
- package/dist/features/tester/seed-data/authored/selling__media-limits.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__media-limits.js +128 -0
- package/dist/features/tester/seed-data/authored/selling__sectionised-forms.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__sectionised-forms.js +167 -0
- package/dist/features/tester/seed-data/authored/selling__seller-analytics-payouts.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__seller-analytics-payouts.js +117 -0
- package/dist/features/tester/seed-data/authored/selling__seller-bids-bundles-filters.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__seller-bids-bundles-filters.js +88 -0
- package/dist/features/tester/seed-data/authored/selling__seller-catalog-org.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__seller-catalog-org.js +151 -0
- package/dist/features/tester/seed-data/authored/selling__seller-custom-brands.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__seller-custom-brands.js +105 -0
- package/dist/features/tester/seed-data/authored/selling__seller-guide.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__seller-guide.js +33 -0
- package/dist/features/tester/seed-data/authored/selling__seller-listing-types.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__seller-listing-types.js +347 -0
- package/dist/features/tester/seed-data/authored/selling__seller-marketing-extras.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__seller-marketing-extras.js +166 -0
- package/dist/features/tester/seed-data/authored/selling__seller-ops-comms.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__seller-ops-comms.js +89 -0
- package/dist/features/tester/seed-data/authored/selling__seller-orders.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__seller-orders.js +195 -0
- package/dist/features/tester/seed-data/authored/selling__seller-shipping-payouts-setup.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__seller-shipping-payouts-setup.js +180 -0
- package/dist/features/tester/seed-data/authored/selling__store-dashboard-navigation.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__store-dashboard-navigation.js +164 -0
- package/dist/features/tester/seed-data/authored/selling__store-uncovered-pages.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__store-uncovered-pages.js +116 -0
- package/dist/features/tester/seed-data/authored/seo__canonical-and-host.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/seo__canonical-and-host.js +111 -0
- package/dist/features/tester/seed-data/authored/seo__og-images.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/seo__og-images.js +91 -0
- package/dist/features/tester/seed-data/authored/seo__page-metadata.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/seo__page-metadata.js +122 -0
- package/dist/features/tester/seed-data/authored/seo__sitemap-and-robots.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/seo__sitemap-and-robots.js +105 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +744 -1
- package/dist/seed/carousel-slides-seed-data.js +1 -1
- package/dist/seed/products-standard-seed-data.js +2 -2
- package/dist/seed/reviews-seed-data.js +6 -1
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +6 -0
|
@@ -96,8 +96,38 @@ export function LotteryAdminEditView({ eventId, initialData, onSubmit, }) {
|
|
|
96
96
|
* `audit-code-quality`'s DEEP_NESTING rule exists to stop.
|
|
97
97
|
*/
|
|
98
98
|
function SlotTable({ slots, showPrice, onSlotsChange, }) {
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Next number is max+1, not length+1.
|
|
101
|
+
*
|
|
102
|
+
* Now that removeSlot preserves numbering, `slots.length + 1` collides: delete
|
|
103
|
+
* slot 1 of 25 and you hold 24 slots numbered 2..25, so length+1 is 25 — a
|
|
104
|
+
* duplicate, which the write schema rejects outright (slot numbers must be
|
|
105
|
+
* unique). Deriving from the highest number in use is stable under any gaps.
|
|
106
|
+
*/
|
|
107
|
+
const addSlot = () => onSlotsChange([
|
|
108
|
+
...slots,
|
|
109
|
+
{ slotNumber: slots.reduce((max, s) => Math.max(max, s.slotNumber), 0) + 1, name: "", price: 0 },
|
|
110
|
+
]);
|
|
111
|
+
/**
|
|
112
|
+
* 🛑 Remove the slot. Do NOT renumber the survivors.
|
|
113
|
+
*
|
|
114
|
+
* `slotNumber` is the stable identity a booking is attached to, and renumbering
|
|
115
|
+
* silently defeated the server's own protection. `mergeLotteryConfig` refuses a
|
|
116
|
+
* save that drops a BOOKED slot by checking `isBooked && !incoming.has(slotNumber)`
|
|
117
|
+
* — but this function used to renumber the remaining slots to `i + 1`, so deleting
|
|
118
|
+
* slot 1 of 25 sent back numbers 1..24. Every booked number (1–5) still *appeared*
|
|
119
|
+
* in that set, the check found nothing to refuse, and the save went through.
|
|
120
|
+
*
|
|
121
|
+
* The damage was worse than the deletion: the merge re-attaches bookings by
|
|
122
|
+
* slotNumber, so the buyer of the deleted slot 1 had their booking moved onto
|
|
123
|
+
* whatever prize now occupied number 1. That is precisely the hazard the schema
|
|
124
|
+
* header describes — "hands slot 7's buyer the prize that used to be slot 8".
|
|
125
|
+
*
|
|
126
|
+
* Numbers may therefore be non-contiguous after a delete (2..25), which the write
|
|
127
|
+
* schema allows: it requires slot numbers to be UNIQUE, not gapless, and totalSlots
|
|
128
|
+
* is derived from slots.length rather than from the highest number.
|
|
129
|
+
*/
|
|
130
|
+
const removeSlot = (idx) => onSlotsChange(slots.filter((_, i) => i !== idx));
|
|
101
131
|
const updateSlot = (idx, field, value) => onSlotsChange(slots.map((s, i) => i === idx
|
|
102
132
|
? {
|
|
103
133
|
...s,
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import type { TesterAnswer } from "../schemas/firestore";
|
|
2
|
+
import type { TesterAnswer, TesterCaseRole } from "../schemas/firestore";
|
|
3
3
|
export interface TesterChecklistStepItem {
|
|
4
4
|
checklistItemId: string;
|
|
5
5
|
label: string;
|
|
6
6
|
description?: string;
|
|
7
7
|
href?: string;
|
|
8
|
+
roles?: TesterCaseRole[];
|
|
9
|
+
startPage?: string;
|
|
10
|
+
steps?: string[];
|
|
11
|
+
inputs?: Record<string, string | number | boolean>;
|
|
12
|
+
expectedBehaviour?: string;
|
|
13
|
+
expectedUiState?: string;
|
|
14
|
+
expectedData?: Record<string, string | number | boolean>;
|
|
15
|
+
endResult?: string;
|
|
8
16
|
answer: TesterAnswer | null;
|
|
9
17
|
comment?: string;
|
|
10
18
|
screenshotUrl?: string;
|
|
@@ -1,10 +1,34 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
|
-
import { Anchor, Button, Div, FieldTextarea, Row, Stack, Text } from "../../../ui";
|
|
4
|
+
import { Anchor, Button, Details, Div, FieldTextarea, Li, Ol, Row, Stack, Summary, Text } from "../../../ui";
|
|
5
5
|
import { MediaUploadField } from "../../media/upload/MediaUploadField";
|
|
6
6
|
import { useMediaUpload } from "../../media/hooks/useMedia";
|
|
7
7
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
8
|
+
/** Human-facing labels for the role chip. `seller` is specifically the listing's OWNER. */
|
|
9
|
+
const ROLE_LABEL = {
|
|
10
|
+
guest: "Signed out",
|
|
11
|
+
buyer: "As a buyer",
|
|
12
|
+
seller: "As the owning seller",
|
|
13
|
+
admin: "As an admin",
|
|
14
|
+
employee: "As an employee",
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* `inputs` / `expectedData` as a two-column list.
|
|
18
|
+
*
|
|
19
|
+
* Rendered as key/value rather than pretty-printed JSON because the reader is a
|
|
20
|
+
* person about to type these into a form — braces and quotes are noise, and a
|
|
21
|
+
* value they have to mentally unescape is a value they will get wrong.
|
|
22
|
+
*
|
|
23
|
+
* Renders nothing at all when the case has no such data, which is the common case:
|
|
24
|
+
* a read-only page enters nothing and has nothing checkable behind the screen.
|
|
25
|
+
*/
|
|
26
|
+
function KeyValueBlock({ label, data, }) {
|
|
27
|
+
const entries = Object.entries(data ?? {});
|
|
28
|
+
if (!entries.length)
|
|
29
|
+
return null;
|
|
30
|
+
return (_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", children: label }), entries.map(([k, v]) => (_jsxs(Row, { gap: "xs", align: "baseline", wrap: true, children: [_jsx(Text, { size: "sm", color: "muted", children: k }), _jsx(Text, { size: "sm", weight: "medium", numeric: typeof v === "number", children: String(v) })] }, k)))] }));
|
|
31
|
+
}
|
|
8
32
|
export function TesterChecklistStepRow({ item, testerDisplayName, onAnswer, onSaveNote, }) {
|
|
9
33
|
const [expanded, setExpanded] = React.useState(false);
|
|
10
34
|
const [comment, setComment] = React.useState(item.comment ?? "");
|
|
@@ -25,5 +49,10 @@ export function TesterChecklistStepRow({ item, testerDisplayName, onAnswer, onSa
|
|
|
25
49
|
setSaving(false);
|
|
26
50
|
}
|
|
27
51
|
};
|
|
28
|
-
return (_jsxs(Stack, { gap: "xs", padding: "sm", border: "default", rounded: "md", children: [_jsxs(Row, { gap: "sm", justify: "between", align: "center", wrap: true, children: [_jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsx(Text, { weight: "medium", children: item.label }), item.description && (_jsx(Text, { size: "xs", color: "muted", children: item.description })), item.href && (_jsx(Anchor, { href: item.href, tone: "brand", underline: "hover", target: "_blank", rel: "noopener noreferrer", children: "Go test this \u2192" }))] }), _jsxs(Row, { gap: "xs", children: [_jsx(Button, { type: "button", size: "sm", variant: item.answer === "yes" ? "primary" : "outline", onClick: () => onAnswer(item.checklistItemId, "yes"), children: "Yes" }), _jsx(Button, { type: "button", size: "sm", variant: item.answer === "no" ? "danger" : "outline", onClick: () => onAnswer(item.checklistItemId, "no"), children: "No" }), _jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => setExpanded((v) => !v), children: expanded ? "Hide note" : "Add note" })] })] }),
|
|
52
|
+
return (_jsxs(Stack, { gap: "xs", padding: "sm", border: "default", rounded: "md", children: [_jsxs(Row, { gap: "sm", justify: "between", align: "center", wrap: true, children: [_jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsxs(Row, { gap: "xs", align: "center", wrap: true, children: [item.roles?.map((r) => (_jsx(Text, { size: "xs", weight: "semibold", color: "muted", children: ROLE_LABEL[r] }, r))), _jsx(Text, { weight: "medium", children: item.label })] }), item.description && (_jsx(Text, { size: "xs", color: "muted", children: item.description })), item.href && (_jsx(Anchor, { href: item.href, tone: "brand", underline: "hover", target: "_blank", rel: "noopener noreferrer", children: "Go test this \u2192" }))] }), _jsxs(Row, { gap: "xs", children: [_jsx(Button, { type: "button", size: "sm", variant: item.answer === "yes" ? "primary" : "outline", onClick: () => onAnswer(item.checklistItemId, "yes"), children: "Yes" }), _jsx(Button, { type: "button", size: "sm", variant: item.answer === "no" ? "danger" : "outline", onClick: () => onAnswer(item.checklistItemId, "no"), children: "No" }), _jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => setExpanded((v) => !v), children: expanded ? "Hide note" : "Add note" })] })] }), (item.steps?.length ||
|
|
53
|
+
item.expectedBehaviour ||
|
|
54
|
+
item.expectedUiState ||
|
|
55
|
+
item.endResult ||
|
|
56
|
+
item.inputs ||
|
|
57
|
+
item.expectedData) && (_jsxs(Details, { tone: "card", padding: "sm", defaultOpen: false, children: [_jsx(Summary, { children: item.steps?.length ? `Steps (${item.steps.length})` : "Expected result" }), _jsxs(Stack, { gap: "sm", children: [item.startPage && (_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", children: "Start" }), _jsx(Text, { size: "sm", children: item.startPage })] })), item.steps?.length ? (_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", children: "Steps" }), _jsx(Ol, { marker: "decimal", indent: "md", spacing: "tight", size: "sm", children: item.steps.map((step, i) => (_jsx(Li, { children: step }, i))) })] })) : null, _jsx(KeyValueBlock, { label: "Enter exactly", data: item.inputs }), item.expectedBehaviour && (_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", children: "Expected behaviour" }), _jsx(Text, { size: "sm", children: item.expectedBehaviour })] })), item.expectedUiState && (_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", children: "Expected UI state" }), _jsx(Text, { size: "sm", children: item.expectedUiState })] })), _jsx(KeyValueBlock, { label: "Values that must be correct", data: item.expectedData }), item.endResult && (_jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "xs", weight: "semibold", color: "muted", children: "After reload" }), _jsx(Text, { size: "sm", children: item.endResult })] }))] })] })), expanded && (_jsx(Div, { padding: "t-sm", border: "default", className: "border-t", children: _jsxs(Stack, { gap: "sm", children: [_jsx(FieldTextarea, { name: `comment-${item.checklistItemId}`, label: "Comment", value: comment, onChange: setComment, placeholder: "What did you notice? Colors, styles, readability, bugs..." }), _jsx(MediaUploadField, { label: "Screenshot (optional)", value: screenshotUrl, onChange: setScreenshotUrl, onUpload: handleUpload, kind: "image" }), _jsx(Row, { children: _jsx(Button, { type: "button", action: ACTIONS.TESTER["save-note"], isLoading: saving, onClick: handleSave }) })] }) }))] }));
|
|
29
58
|
}
|
|
@@ -25,6 +25,14 @@ function TesterChecklistPageSection({ page, testerDisplayName, onAnswer, onSaveN
|
|
|
25
25
|
label: item.label,
|
|
26
26
|
description: item.description,
|
|
27
27
|
href: item.href,
|
|
28
|
+
roles: item.roles,
|
|
29
|
+
startPage: item.startPage,
|
|
30
|
+
steps: item.steps,
|
|
31
|
+
inputs: item.inputs,
|
|
32
|
+
expectedBehaviour: item.expectedBehaviour,
|
|
33
|
+
expectedUiState: item.expectedUiState,
|
|
34
|
+
expectedData: item.expectedData,
|
|
35
|
+
endResult: item.endResult,
|
|
28
36
|
answer: item.answer,
|
|
29
37
|
comment: item.comment,
|
|
30
38
|
screenshotUrl: item.screenshotUrl,
|
|
@@ -13,6 +13,101 @@ export interface TesterChecklistItemDocument extends BaseDocument {
|
|
|
13
13
|
pageLabel: string;
|
|
14
14
|
label: string;
|
|
15
15
|
description?: string;
|
|
16
|
+
/**
|
|
17
|
+
* WHICH ROLES this case affects. Not decoration — it changes what "correct" means.
|
|
18
|
+
*
|
|
19
|
+
* The same page is a different page per role: /products/<slug> shows a buyer
|
|
20
|
+
* Add to Cart and Make Offer, shows the OWNING seller Edit listing and no Make
|
|
21
|
+
* Offer (you cannot bid on your own listing), shows an admin moderation
|
|
22
|
+
* controls, and shows a guest a sign-in prompt. So `expectedUiState` is
|
|
23
|
+
* meaningless without this, and a case omitting it is really three untested
|
|
24
|
+
* cases wearing one label.
|
|
25
|
+
*
|
|
26
|
+
* 🛑 A LIST, because the interesting content is usually the DIFFERENCE between
|
|
27
|
+
* roles — "the owning seller must NOT see Make Offer where a buyer does" is one
|
|
28
|
+
* case, not two, and splitting it loses the comparison that is the whole point.
|
|
29
|
+
* Where roles differ, `expectedBehaviour` and `expectedUiState` say so in prose
|
|
30
|
+
* ("Buyer: … Owning seller: …"). Deliberately NOT a parallel per-role structure:
|
|
31
|
+
* that would be four more fields to keep in sync for something a sentence
|
|
32
|
+
* expresses perfectly well.
|
|
33
|
+
*
|
|
34
|
+
* Also drives which session the automated harness browses with.
|
|
35
|
+
*/
|
|
36
|
+
roles?: TesterCaseRole[];
|
|
37
|
+
/** WHERE the tester begins. One unambiguous URL, unprefixed (/products, not /en/products). */
|
|
38
|
+
startPage?: string;
|
|
39
|
+
/**
|
|
40
|
+
* WHAT the tester does. One action per entry, concrete fixtures named, literal
|
|
41
|
+
* values ("Enter 780", not "enter an amount below the listed price") so two
|
|
42
|
+
* runs are comparable and a regression can be spotted by diffing.
|
|
43
|
+
*
|
|
44
|
+
* 🛑 No assertions in here — they belong in the three expectation fields. A
|
|
45
|
+
* step reading "click Save and confirm it worked" is two things wearing one
|
|
46
|
+
* number, and the confirming half silently goes unchecked.
|
|
47
|
+
*
|
|
48
|
+
* Length varies with the case. "This page renders" is one honest step; a
|
|
49
|
+
* multi-role negotiation is fifteen. There is no target, and padding toward one
|
|
50
|
+
* produces the mechanical "Open X. Verify Y." shape that fills the field while
|
|
51
|
+
* encoding nothing the label did not already say.
|
|
52
|
+
*/
|
|
53
|
+
steps?: string[];
|
|
54
|
+
/**
|
|
55
|
+
* The exact data the tester enters or selects, as flat key/value pairs.
|
|
56
|
+
*
|
|
57
|
+
* 🛑 WHY THIS IS NOT JUST PROSE
|
|
58
|
+
*
|
|
59
|
+
* "Enter 780, the listed price is 999" reads fine and still lets two testers type
|
|
60
|
+
* two different numbers, and gives an automated runner nothing to consume but a
|
|
61
|
+
* sentence. The steps say WHAT TO DO; this says WITH WHAT — so the procedure is
|
|
62
|
+
* repeatable by construction and a runner can fill a form from it directly.
|
|
63
|
+
*
|
|
64
|
+
* Flat and scalar on purpose: nested objects invite a shape nobody validates, and
|
|
65
|
+
* every real value here is a string, number or boolean.
|
|
66
|
+
*
|
|
67
|
+
* Omitted entirely by a case that enters nothing — a read-only page has no inputs.
|
|
68
|
+
*/
|
|
69
|
+
inputs?: Record<string, string | number | boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* What the system must DO — the functional outcome, including side effects no
|
|
72
|
+
* screen shows (a notification sent, stock decremented, an order created).
|
|
73
|
+
*
|
|
74
|
+
* Kept apart from `expectedUiState` because the gap between them is a real bug
|
|
75
|
+
* class: the write succeeds and the screen never updates.
|
|
76
|
+
*/
|
|
77
|
+
expectedBehaviour?: string;
|
|
78
|
+
/**
|
|
79
|
+
* What the tester must SEE — exact on-screen text and control states.
|
|
80
|
+
*
|
|
81
|
+
* Quote the screen ("the button reads 'Offer sent'"), never "the UI updates
|
|
82
|
+
* correctly", which cannot be checked against a screenshot. This is what makes
|
|
83
|
+
* the mandatory screenshot reviewable: the image is compared to a written
|
|
84
|
+
* claim instead of to a guess about what "works" looked like.
|
|
85
|
+
*/
|
|
86
|
+
expectedUiState?: string;
|
|
87
|
+
/**
|
|
88
|
+
* The values that must be CORRECT afterwards, as flat key/value pairs.
|
|
89
|
+
*
|
|
90
|
+
* `expectedUiState` settles what the screen looks like; this settles whether the
|
|
91
|
+
* data behind it is right. The two are different questions, and a screen can be
|
|
92
|
+
* entirely plausible over a wrong number — which is most of this codebase's bug
|
|
93
|
+
* history, where a defect returns HTTP 200 with believable rows.
|
|
94
|
+
*
|
|
95
|
+
* Machine-readable so a runner asserts against it rather than re-reading English,
|
|
96
|
+
* and so "it looked right" becomes a comparison.
|
|
97
|
+
*
|
|
98
|
+
* Omitted by a case with nothing checkable behind the screen.
|
|
99
|
+
*/
|
|
100
|
+
expectedData?: Record<string, string | number | boolean>;
|
|
101
|
+
/**
|
|
102
|
+
* What is still true after a RELOAD — the persistence oracle.
|
|
103
|
+
*
|
|
104
|
+
* Several of the worst bugs here returned a normal 200, rendered
|
|
105
|
+
* optimistically, and wrote nothing (the grouped-listing rename, the dropped
|
|
106
|
+
* address landmark). Reloading is the only thing that catches them. Where a
|
|
107
|
+
* case genuinely persists nothing, say so explicitly rather than leaving the
|
|
108
|
+
* field to mean two different things.
|
|
109
|
+
*/
|
|
110
|
+
endResult?: string;
|
|
16
111
|
href?: string;
|
|
17
112
|
order: number;
|
|
18
113
|
phase: number;
|
|
@@ -48,6 +143,20 @@ export interface BugHunterLeaderboardEntry {
|
|
|
48
143
|
}
|
|
49
144
|
export declare function slugifyChecklistLabel(label: string): string;
|
|
50
145
|
export declare function createChecklistItemId(groupKey: string, pageKey: string, label: string): string;
|
|
146
|
+
/**
|
|
147
|
+
* Whose point of view a checklist case is written from.
|
|
148
|
+
*
|
|
149
|
+
* `guest` is a real role, not the absence of one — a signed-out visitor sees a
|
|
150
|
+
* sign-in prompt where a buyer sees a wishlist heart, and that path had never
|
|
151
|
+
* been exercised before this field existed.
|
|
152
|
+
*
|
|
153
|
+
* `seller` means the seller who OWNS the listing under test. A different
|
|
154
|
+
* seller looking at someone else's listing is a `buyer` for that case — the
|
|
155
|
+
* distinction matters because the owning seller is precisely who must NOT see
|
|
156
|
+
* Make Offer.
|
|
157
|
+
*/
|
|
158
|
+
export declare const TESTER_CASE_ROLES: readonly ["guest", "buyer", "seller", "admin", "employee"];
|
|
159
|
+
export type TesterCaseRole = (typeof TESTER_CASE_ROLES)[number];
|
|
51
160
|
export type TesterAnswer = "yes" | "no";
|
|
52
161
|
export type TesterFeedbackStatus = "new" | "reviewed";
|
|
53
162
|
export interface TesterChecklistResponseDocument extends BaseDocument {
|
|
@@ -42,6 +42,20 @@ export function slugifyChecklistLabel(label) {
|
|
|
42
42
|
export function createChecklistItemId(groupKey, pageKey, label) {
|
|
43
43
|
return generateChecklistItemId({ groupKey, pageKey, label });
|
|
44
44
|
}
|
|
45
|
+
// --- Checklist response (tester answers) --------------------------------------
|
|
46
|
+
/**
|
|
47
|
+
* Whose point of view a checklist case is written from.
|
|
48
|
+
*
|
|
49
|
+
* `guest` is a real role, not the absence of one — a signed-out visitor sees a
|
|
50
|
+
* sign-in prompt where a buyer sees a wishlist heart, and that path had never
|
|
51
|
+
* been exercised before this field existed.
|
|
52
|
+
*
|
|
53
|
+
* `seller` means the seller who OWNS the listing under test. A different
|
|
54
|
+
* seller looking at someone else's listing is a `buyer` for that case — the
|
|
55
|
+
* distinction matters because the owning seller is precisely who must NOT see
|
|
56
|
+
* Make Offer.
|
|
57
|
+
*/
|
|
58
|
+
export const TESTER_CASE_ROLES = ["guest", "buyer", "seller", "admin", "employee"];
|
|
45
59
|
export const TESTER_CHECKLIST_RESPONSE_COLLECTION = "testerChecklistResponses";
|
|
46
60
|
export const TESTER_CHECKLIST_RESPONSE_INDEXED_FIELDS = [
|
|
47
61
|
"testerId",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { TesterCaseRole } from "../schemas";
|
|
2
|
+
interface MoneyFlowCase {
|
|
3
|
+
key: string;
|
|
4
|
+
label: string;
|
|
5
|
+
/** Every role the case affects; per-role detail goes inline in the expectations. */
|
|
6
|
+
roles: TesterCaseRole[];
|
|
7
|
+
startPage?: string;
|
|
8
|
+
steps: string[];
|
|
9
|
+
inputs?: Record<string, string | number | boolean>;
|
|
10
|
+
expectedBehaviour: string;
|
|
11
|
+
expectedUiState: string;
|
|
12
|
+
expectedData?: Record<string, string | number | boolean>;
|
|
13
|
+
endResult: string;
|
|
14
|
+
href?: string;
|
|
15
|
+
}
|
|
16
|
+
interface MoneyFlowPage {
|
|
17
|
+
pageKey: string;
|
|
18
|
+
pageLabel: string;
|
|
19
|
+
href?: string;
|
|
20
|
+
cases: MoneyFlowCase[];
|
|
21
|
+
}
|
|
22
|
+
export declare const moneyFlowsPages: MoneyFlowPage[];
|
|
23
|
+
export {};
|