diff --git a/packages/react/src/modules/guide/components/Toolbar/V2/useInspectGuideClientStore.ts b/packages/react/src/modules/guide/components/Toolbar/V2/useInspectGuideClientStore.ts index c85f15596..2736606f5 100644 --- a/packages/react/src/modules/guide/components/Toolbar/V2/useInspectGuideClientStore.ts +++ b/packages/react/src/modules/guide/components/Toolbar/V2/useInspectGuideClientStore.ts @@ -197,17 +197,30 @@ const inferSelectOneByTypeReturnStatus = ( }; const inferSelectAllByTypeReturnStatus = ( - _guide: KnockGuide, - _snapshot: StoreStateSnapshot, - _stage: KnockGuideClientGroupStage, - _query: SelectionResultByQuery, + guide: KnockGuide, + snapshot: StoreStateSnapshot, + stage: KnockGuideClientGroupStage, + query: SelectionResultByQuery, ): SelectableStatusPresent["status"] => { - // If queried with useGuides/selectGuides (i.e. can return multiple guides), - // then we need to look up the actual query results to figure out how this - // guide may or may not be included in the result. + const result = query.type!.all!; + if (result.size === 0) { + return "queried"; + } - // TODO: Placeholder to follow up. - return "returned"; + const guides = [...result.values()]; + const first = guides[0]!; + + // If the first selected guide is unthrottled or resolved, then we should + // have at minimum one guide to return, and potentially more based on whether + // we are throttling currently and which other guides are unthrottled. + if (first.bypass_global_group_limit || first.key === stage.resolved) { + if (snapshot.throttled) { + return guide.bypass_global_group_limit ? "returned" : "throttled"; + } + return "returned"; + } + + return "queried"; }; const inferSelectReturnStatus = ( diff --git a/packages/react/test/guide/Toolbar/V2/useInspectGuideClientStore.test.ts b/packages/react/test/guide/Toolbar/V2/useInspectGuideClientStore.test.ts index c78ba59d0..4dc9f92de 100644 --- a/packages/react/test/guide/Toolbar/V2/useInspectGuideClientStore.test.ts +++ b/packages/react/test/guide/Toolbar/V2/useInspectGuideClientStore.test.ts @@ -659,7 +659,7 @@ describe("useInspectGuideClientStore", () => { expect(annotated.annotation.selectable.status).toBe("queried"); }); - test("selectable is 'returned' when queried by type (selectAll)", () => { + test("selectable is 'returned' when queried by type (selectAll) and guide is resolved", () => { const guide = makeGuide({ key: "g1", type: "banner" }); mockGroupStage = { status: "closed", @@ -681,10 +681,215 @@ describe("useInspectGuideClientStore", () => { const result = renderInspect()!; const annotated = result.guides[0] as AnnotatedGuide; - // selectAll placeholder always returns "returned" expect(annotated.annotation.selectable.status).toBe("returned"); }); + test("selectAll: returns 'queried' when result is empty", () => { + const guide = makeGuide({ key: "g1", type: "banner" }); + mockGroupStage = { + status: "closed", + ordered: ["g1"], + resolved: "g1", + timeoutId: null, + results: { + type: { + banner: { + all: makeSelectionResult([]), + }, + }, + }, + }; + setSnapshot({ + guideGroups: [makeGuideGroup(["g1"])], + guides: { g1: guide }, + }); + + const result = renderInspect()!; + const annotated = result.guides[0] as AnnotatedGuide; + expect(annotated.annotation.selectable.status).toBe("queried"); + }); + + test("selectAll: returns 'returned' when first guide is unthrottled and not throttling", () => { + const guide = makeGuide({ key: "g1", type: "banner" }); + mockGroupStage = { + status: "closed", + ordered: ["g1"], + resolved: "other", + timeoutId: null, + results: { + type: { + banner: { + all: makeSelectionResult([ + [0, { key: "g1", bypass_global_group_limit: true }], + ]), + }, + }, + }, + }; + setSnapshot({ + guideGroups: [makeGuideGroup(["g1"])], + guides: { g1: guide }, + }); + + const result = renderInspect()!; + const annotated = result.guides[0] as AnnotatedGuide; + expect(annotated.annotation.selectable.status).toBe("returned"); + }); + + test("selectAll: returns 'returned' when throttled but guide bypasses group limit", () => { + mockCheckStateIfThrottled.mockReturnValue(true); + const guide = makeGuide({ + key: "g1", + type: "banner", + bypass_global_group_limit: true, + }); + mockGroupStage = { + status: "closed", + ordered: ["g1"], + resolved: "g1", + timeoutId: null, + results: { + type: { + banner: { + all: makeSelectionResult([[0, { key: "g1" }]]), + }, + }, + }, + }; + setSnapshot({ + guideGroups: [makeGuideGroup(["g1"])], + guides: { g1: guide }, + }); + + const result = renderInspect()!; + const annotated = result.guides[0] as AnnotatedGuide; + expect(annotated.annotation.selectable.status).toBe("returned"); + }); + + test("selectAll: returns 'throttled' when throttled and guide does not bypass group limit", () => { + mockCheckStateIfThrottled.mockReturnValue(true); + const guide = makeGuide({ + key: "g1", + type: "banner", + bypass_global_group_limit: false, + }); + mockGroupStage = { + status: "closed", + ordered: ["g1"], + resolved: "g1", + timeoutId: null, + results: { + type: { + banner: { + all: makeSelectionResult([[0, { key: "g1" }]]), + }, + }, + }, + }; + setSnapshot({ + guideGroups: [makeGuideGroup(["g1"])], + guides: { g1: guide }, + }); + + const result = renderInspect()!; + const annotated = result.guides[0] as AnnotatedGuide; + expect(annotated.annotation.selectable.status).toBe("throttled"); + }); + + test("selectAll: returns 'throttled' when first guide is unthrottled, throttled, and current guide is throttled", () => { + mockCheckStateIfThrottled.mockReturnValue(true); + const guide = makeGuide({ + key: "g1", + type: "banner", + bypass_global_group_limit: false, + }); + mockGroupStage = { + status: "closed", + ordered: ["g1"], + resolved: "other", + timeoutId: null, + results: { + type: { + banner: { + all: makeSelectionResult([ + [0, { key: "first", bypass_global_group_limit: true }], + [1, { key: "g1", bypass_global_group_limit: false }], + ]), + }, + }, + }, + }; + setSnapshot({ + guideGroups: [makeGuideGroup(["g1"])], + guides: { g1: guide }, + }); + + const result = renderInspect()!; + const annotated = result.guides[0] as AnnotatedGuide; + expect(annotated.annotation.selectable.status).toBe("throttled"); + }); + + test("selectAll: returns 'returned' when first guide is unthrottled, throttled, and current guide bypasses group limit", () => { + mockCheckStateIfThrottled.mockReturnValue(true); + const guide = makeGuide({ + key: "g1", + type: "banner", + bypass_global_group_limit: true, + }); + mockGroupStage = { + status: "closed", + ordered: ["g1"], + resolved: "other", + timeoutId: null, + results: { + type: { + banner: { + all: makeSelectionResult([ + [0, { key: "first", bypass_global_group_limit: true }], + [1, { key: "g1", bypass_global_group_limit: true }], + ]), + }, + }, + }, + }; + setSnapshot({ + guideGroups: [makeGuideGroup(["g1"])], + guides: { g1: guide }, + }); + + const result = renderInspect()!; + const annotated = result.guides[0] as AnnotatedGuide; + expect(annotated.annotation.selectable.status).toBe("returned"); + }); + + test("selectAll: returns 'queried' when first guide is neither resolved nor unthrottled", () => { + const guide = makeGuide({ key: "g1", type: "banner" }); + mockGroupStage = { + status: "closed", + ordered: ["g1"], + resolved: "other", + timeoutId: null, + results: { + type: { + banner: { + all: makeSelectionResult([ + [0, { key: "first", bypass_global_group_limit: false }], + [1, { key: "g1" }], + ]), + }, + }, + }, + }; + setSnapshot({ + guideGroups: [makeGuideGroup(["g1"])], + guides: { g1: guide }, + }); + + const result = renderInspect()!; + const annotated = result.guides[0] as AnnotatedGuide; + expect(annotated.annotation.selectable.status).toBe("queried"); + }); + test("key-based query takes precedence over type-based query", () => { const guide = makeGuide({ key: "g1", type: "banner" }); // Stage has both key and type results, key should take precedence