@@ -74,6 +74,7 @@ describe("AvailabilityCard / DiagnosticSummaryBanner", () => {
7474 useLegacyShippingZoneStockAvailability : false ,
7575 issues : [
7676 makeIssue ( {
77+ category : "purchasability" ,
7778 id : "stock-outside-channel-warehouses" ,
7879 severity : "info" ,
7980 message : "Stranded stock" ,
@@ -102,11 +103,13 @@ describe("AvailabilityCard / DiagnosticSummaryBanner", () => {
102103 const diagnostics = baseDiagnostics ( {
103104 issues : [
104105 makeIssue ( {
106+ category : "purchasability" ,
105107 id : "stock-outside-channel-warehouses" ,
106108 severity : "info" ,
107109 message : "Stranded stock — warehouse A" ,
108110 } ) ,
109111 makeIssue ( {
112+ category : "purchasability" ,
110113 id : "stock-outside-channel-warehouses" ,
111114 severity : "info" ,
112115 message : "Stranded stock — warehouse B" ,
@@ -140,8 +143,14 @@ describe("AvailabilityCard / DiagnosticSummaryBanner", () => {
140143 const diagnostics = baseDiagnostics ( {
141144 useLegacyShippingZoneStockAvailability : true ,
142145 issues : [
143- makeIssue ( { id : "no-stock" , severity : "warning" , message : "No stock" } ) ,
144146 makeIssue ( {
147+ category : "purchasability" ,
148+ id : "no-stock" ,
149+ severity : "warning" ,
150+ message : "No stock" ,
151+ } ) ,
152+ makeIssue ( {
153+ category : "purchasability" ,
145154 id : "stock-outside-channel-warehouses" ,
146155 severity : "info" ,
147156 message : "Stranded stock" ,
@@ -207,12 +216,21 @@ describe("AvailabilityCard / StockAvailabilityModeIndicator", () => {
207216 } ) ;
208217} ) ;
209218
210- // Synthetic issue factory for UI tests. Defaults to a genuine info-level
211- // advisory (stranded stock — the only check that emits info severity) so
212- // callers using `makeIssue({ severity: "info" })` get an unambiguously
213- // realistic fixture. Tests that need warnings/errors override the id +
214- // severity explicitly.
215- const makeIssue = ( overrides : Partial < AvailabilityIssue > = { } ) : AvailabilityIssue => ( {
219+ /**
220+ * Synthetic issue factory for UI tests. `category` must be passed explicitly
221+ * — we deliberately avoid inferring it from `id` in the test layer because
222+ * that would duplicate the production category mapping (which lives in
223+ * `runAvailabilityChecks`) and silently miscategorize new issue types.
224+ *
225+ * Defaults to a genuine info-level advisory (stranded stock — the only check
226+ * that emits `info` severity in production) so callers using
227+ * `makeIssue({ category: "purchasability", severity: "info" })` get an
228+ * unambiguously realistic fixture. Tests that need warnings/errors override
229+ * the id + severity + message explicitly.
230+ */
231+ const makeIssue = (
232+ overrides : Partial < AvailabilityIssue > & Pick < AvailabilityIssue , "category" > ,
233+ ) : AvailabilityIssue => ( {
216234 id : "stock-outside-channel-warehouses" ,
217235 severity : "info" ,
218236 channelId : "channel-1" ,
@@ -236,7 +254,10 @@ describe("AvailabilityCard channel header severity gating", () => {
236254 // Arrange — direct mode, single info advisory.
237255 const diagnostics = baseDiagnostics ( {
238256 useLegacyShippingZoneStockAvailability : false ,
239- issues : [ makeIssue ( { severity : "info" } ) ] ,
257+ // Default fixture id (`stock-outside-channel-warehouses`) is a
258+ // purchasability-category check; pair the override accordingly so the
259+ // fixture stays internally consistent with production grouping.
260+ issues : [ makeIssue ( { category : "purchasability" , severity : "info" } ) ] ,
240261 hasErrors : false ,
241262 hasWarnings : false ,
242263 } ) ;
@@ -262,7 +283,14 @@ describe("AvailabilityCard channel header severity gating", () => {
262283 // Arrange — legacy mode, one warning-level issue.
263284 const diagnostics = baseDiagnostics ( {
264285 useLegacyShippingZoneStockAvailability : true ,
265- issues : [ makeIssue ( { id : "no-stock" , severity : "warning" , message : "No stock" } ) ] ,
286+ issues : [
287+ makeIssue ( {
288+ category : "purchasability" ,
289+ id : "no-stock" ,
290+ severity : "warning" ,
291+ message : "No stock" ,
292+ } ) ,
293+ ] ,
266294 hasErrors : false ,
267295 hasWarnings : true ,
268296 } ) ;
@@ -286,17 +314,32 @@ describe("AvailabilityCard channel header severity gating", () => {
286314
287315 it ( "renders a visible count and the warning icon when multiple header-worthy issues exist alongside info advisories" , ( ) => {
288316 // Arrange — two warnings + two info advisories on the same channel.
317+ // Both info advisories are stranded-stock fixtures since that's the only
318+ // check that genuinely emits info severity after Option A (no-shipping-
319+ // zones is now a warning in both modes).
289320 const diagnostics = baseDiagnostics ( {
290321 useLegacyShippingZoneStockAvailability : false ,
291322 issues : [
292- makeIssue ( { id : "no-stock" , severity : "warning" , message : "No stock" } ) ,
293- makeIssue ( { id : "no-warehouses" , severity : "warning" , message : "No warehouses" } ) ,
294323 makeIssue ( {
324+ category : "purchasability" ,
325+ id : "no-stock" ,
326+ severity : "warning" ,
327+ message : "No stock" ,
328+ } ) ,
329+ makeIssue ( {
330+ category : "purchasability" ,
331+ id : "no-warehouses" ,
332+ severity : "warning" ,
333+ message : "No warehouses" ,
334+ } ) ,
335+ makeIssue ( {
336+ category : "purchasability" ,
295337 id : "stock-outside-channel-warehouses" ,
296338 severity : "info" ,
297339 message : "Stranded stock A" ,
298340 } ) ,
299341 makeIssue ( {
342+ category : "purchasability" ,
300343 id : "stock-outside-channel-warehouses" ,
301344 severity : "info" ,
302345 message : "Stranded stock B" ,
@@ -325,9 +368,20 @@ describe("AvailabilityCard channel header severity gating", () => {
325368 const diagnostics = baseDiagnostics ( {
326369 useLegacyShippingZoneStockAvailability : true ,
327370 issues : [
328- makeIssue ( { id : "no-variants" , severity : "error" , message : "No variants" } ) ,
329- makeIssue ( { id : "no-stock" , severity : "warning" , message : "No stock" } ) ,
330371 makeIssue ( {
372+ category : "purchasability" ,
373+ id : "no-variants" ,
374+ severity : "error" ,
375+ message : "No variants" ,
376+ } ) ,
377+ makeIssue ( {
378+ category : "purchasability" ,
379+ id : "no-stock" ,
380+ severity : "warning" ,
381+ message : "No stock" ,
382+ } ) ,
383+ makeIssue ( {
384+ category : "purchasability" ,
331385 id : "stock-outside-channel-warehouses" ,
332386 severity : "info" ,
333387 message : "Info only" ,
@@ -611,3 +665,112 @@ describe("PublicApiVerificationBadge reassurance", () => {
611665 expect ( screen . queryByTestId ( "verification-reassurance" ) ) . toBeNull ( ) ;
612666 } ) ;
613667} ) ;
668+
669+ describe ( "AvailabilityChannelItem issue category sections" , ( ) => {
670+ const renderExpandedChannel = ( issues : AvailabilityIssue [ ] ) => {
671+ const diagnostics = baseDiagnostics ( {
672+ issues,
673+ hasErrors : issues . some ( i => i . severity === "error" ) ,
674+ hasWarnings : issues . some ( i => i . severity === "warning" ) ,
675+ } ) ;
676+
677+ render ( < AvailabilityCard diagnostics = { diagnostics } totalChannelsCount = { 1 } /> , {
678+ wrapper : Wrapper ,
679+ } ) ;
680+
681+ fireEvent . click ( screen . getByText ( "Default Channel" ) ) ;
682+ } ;
683+
684+ it ( "renders both category sections when issues span both purchasability and shipping" , ( ) => {
685+ renderExpandedChannel ( [
686+ makeIssue ( {
687+ category : "purchasability" ,
688+ id : "no-stock" ,
689+ severity : "warning" ,
690+ message : "No stock" ,
691+ } ) ,
692+ makeIssue ( {
693+ category : "shipping" ,
694+ id : "no-shipping-zones" ,
695+ severity : "info" ,
696+ message : "No shipping zones" ,
697+ } ) ,
698+ ] ) ;
699+
700+ const sections = screen . getAllByTestId ( "issue-category-section" ) ;
701+
702+ expect ( sections ) . toHaveLength ( 2 ) ;
703+ expect ( sections . map ( s => s . getAttribute ( "data-test-category" ) ) ) . toEqual ( [
704+ "purchasability" ,
705+ "shipping" ,
706+ ] ) ;
707+ } ) ;
708+
709+ it ( "renders only the purchasability section when no shipping issues exist" , ( ) => {
710+ renderExpandedChannel ( [
711+ makeIssue ( {
712+ category : "purchasability" ,
713+ id : "no-variants" ,
714+ severity : "error" ,
715+ message : "No variants" ,
716+ } ) ,
717+ makeIssue ( {
718+ category : "purchasability" ,
719+ id : "no-stock" ,
720+ severity : "warning" ,
721+ message : "No stock" ,
722+ } ) ,
723+ ] ) ;
724+
725+ const sections = screen . getAllByTestId ( "issue-category-section" ) ;
726+
727+ expect ( sections ) . toHaveLength ( 1 ) ;
728+ expect ( sections [ 0 ] ) . toHaveAttribute ( "data-test-category" , "purchasability" ) ;
729+ } ) ;
730+
731+ it ( "renders only the shipping section when no purchasability issues exist" , ( ) => {
732+ renderExpandedChannel ( [
733+ makeIssue ( {
734+ category : "shipping" ,
735+ id : "no-shipping-zones" ,
736+ severity : "info" ,
737+ message : "No shipping zones" ,
738+ } ) ,
739+ ] ) ;
740+
741+ const sections = screen . getAllByTestId ( "issue-category-section" ) ;
742+
743+ expect ( sections ) . toHaveLength ( 1 ) ;
744+ expect ( sections [ 0 ] ) . toHaveAttribute ( "data-test-category" , "shipping" ) ;
745+ } ) ;
746+
747+ it ( "groups multiple issues of the same category under one section" , ( ) => {
748+ renderExpandedChannel ( [
749+ makeIssue ( {
750+ category : "purchasability" ,
751+ id : "no-stock" ,
752+ severity : "warning" ,
753+ message : "No stock" ,
754+ } ) ,
755+ makeIssue ( {
756+ category : "purchasability" ,
757+ id : "stock-outside-channel-warehouses" ,
758+ severity : "info" ,
759+ message : "Stranded stock" ,
760+ } ) ,
761+ makeIssue ( {
762+ category : "purchasability" ,
763+ id : "no-warehouses" ,
764+ severity : "warning" ,
765+ message : "No warehouses" ,
766+ } ) ,
767+ ] ) ;
768+
769+ const sections = screen . getAllByTestId ( "issue-category-section" ) ;
770+
771+ expect ( sections ) . toHaveLength ( 1 ) ;
772+ expect ( sections [ 0 ] ) . toHaveAttribute ( "data-test-category" , "purchasability" ) ;
773+ // All three callouts should be inside the single purchasability section.
774+ expect ( screen . getAllByTestId ( "availability-issue-callout" ) ) . toHaveLength ( 3 ) ;
775+ } ) ;
776+ } ) ;
0 commit comments