@@ -2260,6 +2260,116 @@ describe("KnockGuideClient", () => {
22602260 expect ( result ) . toBeUndefined ( ) ;
22612261 } ) ;
22622262
2263+ test ( "returns an archived guide when focusedGuideKeys includes it" , ( ) => {
2264+ const archivedGuide = {
2265+ ...mockGuideThree ,
2266+ steps : [
2267+ {
2268+ ...mockStep ,
2269+ message : {
2270+ ...mockStep . message ,
2271+ archived_at : new Date ( ) . toISOString ( ) ,
2272+ } ,
2273+ } ,
2274+ ] ,
2275+ } ;
2276+
2277+ const stateWithArchivedGuide = {
2278+ guideGroups : [ mockDefaultGroup ] ,
2279+ guideGroupDisplayLogs : { } ,
2280+ guides : {
2281+ ...mockGuides ,
2282+ [ mockGuideThree . key ] : archivedGuide ,
2283+ } ,
2284+ ineligibleGuides : { } ,
2285+ previewGuides : { } ,
2286+ queries : { } ,
2287+ location : undefined ,
2288+ counter : 0 ,
2289+ debug : {
2290+ focusedGuideKeys : { [ mockGuideThree . key ] : true as const } ,
2291+ } ,
2292+ } ;
2293+
2294+ const client = new KnockGuideClient ( mockKnock , channelId ) ;
2295+ const result = client [ "_selectGuide" ] ( stateWithArchivedGuide , {
2296+ key : mockGuideThree . key ,
2297+ } ) ;
2298+
2299+ // Should return the focused guide even though it's archived
2300+ expect ( result ! . key ) . toBe ( "system_status" ) ;
2301+ expect ( result ! . steps [ 0 ] ! . message . archived_at ) . toBeTruthy ( ) ;
2302+ } ) ;
2303+
2304+ test ( "returns only focused guides when focusedGuideKeys is set" , ( ) => {
2305+ const stateWithGuides = {
2306+ guideGroups : [ mockDefaultGroup ] ,
2307+ guideGroupDisplayLogs : { } ,
2308+ guides : mockGuides ,
2309+ ineligibleGuides : { } ,
2310+ previewGuides : { } ,
2311+ queries : { } ,
2312+ location : undefined ,
2313+ counter : 0 ,
2314+ debug : {
2315+ focusedGuideKeys : { [ mockGuideTwo . key ] : true as const } ,
2316+ } ,
2317+ } ;
2318+
2319+ const client = new KnockGuideClient ( mockKnock , channelId ) ;
2320+ const result = client [ "_selectGuide" ] ( stateWithGuides ) ;
2321+
2322+ // Should return the focused guide
2323+ expect ( result ! . key ) . toBe ( "feature_tour" ) ;
2324+ } ) ;
2325+
2326+ test ( "doesn't return a guide not in focusedGuideKeys" , ( ) => {
2327+ const stateWithGuides = {
2328+ guideGroups : [ mockDefaultGroup ] ,
2329+ guideGroupDisplayLogs : { } ,
2330+ guides : mockGuides ,
2331+ ineligibleGuides : { } ,
2332+ previewGuides : { } ,
2333+ queries : { } ,
2334+ location : undefined ,
2335+ counter : 0 ,
2336+ debug : {
2337+ focusedGuideKeys : { [ mockGuideTwo . key ] : true as const } ,
2338+ } ,
2339+ } ;
2340+
2341+ const client = new KnockGuideClient ( mockKnock , channelId ) ;
2342+ const result = client [ "_selectGuide" ] ( stateWithGuides , {
2343+ key : mockGuideOne . key ,
2344+ } ) ;
2345+
2346+ // Guide one is not in focusedGuideKeys, so should not be returned
2347+ expect ( result ) . toBeUndefined ( ) ;
2348+ } ) ;
2349+
2350+ test ( "falls through to normal filtering when focusedGuideKeys is empty object" , ( ) => {
2351+ const stateWithGuides = {
2352+ guideGroups : [ mockDefaultGroup ] ,
2353+ guideGroupDisplayLogs : { } ,
2354+ guides : mockGuides ,
2355+ ineligibleGuides : { } ,
2356+ previewGuides : { } ,
2357+ queries : { } ,
2358+ location : undefined ,
2359+ counter : 0 ,
2360+ debug : {
2361+ focusedGuideKeys : { } ,
2362+ } ,
2363+ } ;
2364+
2365+ const client = new KnockGuideClient ( mockKnock , channelId ) ;
2366+ const result = client [ "_selectGuide" ] ( stateWithGuides ) ;
2367+
2368+ // Empty focusedGuideKeys should not filter — normal selection applies
2369+ expect ( result ) . toBeDefined ( ) ;
2370+ expect ( result ! . key ) . toBe ( "feature_tour" ) ;
2371+ } ) ;
2372+
22632373 test ( "does not return a guide inside a throttle window " , ( ) => {
22642374 const stateWithGuides = {
22652375 guideGroups : [
@@ -4217,6 +4327,152 @@ describe("KnockGuideClient", () => {
42174327 expect ( result ) . toBeDefined ( ) ;
42184328 expect ( result ! . key ) . toBe ( "onboarding" ) ;
42194329 } ) ;
4330+
4331+ test ( "returns focused guide in closed stage even when throttled" , ( ) => {
4332+ const stateWithGuides = {
4333+ guideGroups : [ throttleDefaultGroup ] ,
4334+ guideGroupDisplayLogs : {
4335+ default : new Date ( ) . toISOString ( ) ,
4336+ } ,
4337+ guides : { onboarding : mockGuide } ,
4338+ ineligibleGuides : { } ,
4339+ previewGuides : { } ,
4340+ queries : { } ,
4341+ location : undefined ,
4342+ counter : 0 ,
4343+ debug : {
4344+ focusedGuideKeys : { onboarding : true as const } ,
4345+ } ,
4346+ } ;
4347+
4348+ const client = new KnockGuideClient ( mockKnock , channelId ) ;
4349+
4350+ // Set up a closed stage with the guide resolved
4351+ client [ "stage" ] = {
4352+ status : "closed" ,
4353+ ordered : [ "onboarding" ] ,
4354+ resolved : "onboarding" ,
4355+ results : { } ,
4356+ timeoutId : null ,
4357+ } ;
4358+
4359+ // Focused guides bypass throttle in closed stage
4360+ const result = client . selectGuide ( stateWithGuides , {
4361+ key : "onboarding" ,
4362+ } ) ;
4363+ expect ( result ) . toBeDefined ( ) ;
4364+ expect ( result ! . key ) . toBe ( "onboarding" ) ;
4365+ } ) ;
4366+
4367+ test ( "returns focused guide in patch stage even when throttled" , ( ) => {
4368+ const stateWithGuides = {
4369+ guideGroups : [ throttleDefaultGroup ] ,
4370+ guideGroupDisplayLogs : {
4371+ default : new Date ( ) . toISOString ( ) ,
4372+ } ,
4373+ guides : { onboarding : mockGuide } ,
4374+ ineligibleGuides : { } ,
4375+ previewGuides : { } ,
4376+ queries : { } ,
4377+ location : undefined ,
4378+ counter : 0 ,
4379+ debug : {
4380+ focusedGuideKeys : { onboarding : true as const } ,
4381+ } ,
4382+ } ;
4383+
4384+ const client = new KnockGuideClient ( mockKnock , channelId ) ;
4385+
4386+ // Set up a closed stage then patch it
4387+ client [ "stage" ] = {
4388+ status : "closed" ,
4389+ ordered : [ "onboarding" ] ,
4390+ resolved : "onboarding" ,
4391+ results : { } ,
4392+ timeoutId : null ,
4393+ } ;
4394+ client [ "patchClosedGroupStage" ] ( ) ;
4395+
4396+ expect ( client . getStage ( ) ! . status ) . toBe ( "patch" ) ;
4397+
4398+ // Focused guides bypass throttle in patch stage
4399+ const result = client . selectGuide ( stateWithGuides , {
4400+ key : "onboarding" ,
4401+ } ) ;
4402+ expect ( result ) . toBeDefined ( ) ;
4403+ expect ( result ! . key ) . toBe ( "onboarding" ) ;
4404+ } ) ;
4405+
4406+ test ( "returns focused guide in patch stage even when not the resolved guide" , ( ) => {
4407+ const stateWithGuides = {
4408+ guideGroups : [ throttleDefaultGroup ] ,
4409+ guideGroupDisplayLogs : { } ,
4410+ guides : { onboarding : mockGuide } ,
4411+ ineligibleGuides : { } ,
4412+ previewGuides : { } ,
4413+ queries : { } ,
4414+ location : undefined ,
4415+ counter : 0 ,
4416+ debug : {
4417+ focusedGuideKeys : { onboarding : true as const } ,
4418+ } ,
4419+ } ;
4420+
4421+ const client = new KnockGuideClient ( mockKnock , channelId ) ;
4422+
4423+ // Set up a closed stage where a DIFFERENT guide was resolved
4424+ client [ "stage" ] = {
4425+ status : "closed" ,
4426+ ordered : [ "onboarding" ] ,
4427+ resolved : "some_other_guide" ,
4428+ results : { } ,
4429+ timeoutId : null ,
4430+ } ;
4431+ client [ "patchClosedGroupStage" ] ( ) ;
4432+
4433+ expect ( client . getStage ( ) ! . status ) . toBe ( "patch" ) ;
4434+
4435+ // Focused guides bypass the resolved check in patch stage
4436+ const result = client . selectGuide ( stateWithGuides , {
4437+ key : "onboarding" ,
4438+ } ) ;
4439+ expect ( result ) . toBeDefined ( ) ;
4440+ expect ( result ! . key ) . toBe ( "onboarding" ) ;
4441+ } ) ;
4442+
4443+ test ( "returns focused guide in closed stage even when not the resolved guide" , ( ) => {
4444+ const stateWithGuides = {
4445+ guideGroups : [ throttleDefaultGroup ] ,
4446+ guideGroupDisplayLogs : { } ,
4447+ guides : { onboarding : mockGuide } ,
4448+ ineligibleGuides : { } ,
4449+ previewGuides : { } ,
4450+ queries : { } ,
4451+ location : undefined ,
4452+ counter : 0 ,
4453+ debug : {
4454+ focusedGuideKeys : { onboarding : true as const } ,
4455+ } ,
4456+ } ;
4457+
4458+ const client = new KnockGuideClient ( mockKnock , channelId ) ;
4459+
4460+ // Set up a closed stage where a DIFFERENT guide was resolved
4461+ client [ "stage" ] = {
4462+ status : "closed" ,
4463+ ordered : [ "onboarding" ] ,
4464+ resolved : "some_other_guide" ,
4465+ results : { } ,
4466+ timeoutId : null ,
4467+ } ;
4468+
4469+ // Focused guides bypass the resolved check in closed stage
4470+ const result = client . selectGuide ( stateWithGuides , {
4471+ key : "onboarding" ,
4472+ } ) ;
4473+ expect ( result ) . toBeDefined ( ) ;
4474+ expect ( result ! . key ) . toBe ( "onboarding" ) ;
4475+ } ) ;
42204476 } ) ;
42214477
42224478 describe ( "setDebug" , ( ) => {
0 commit comments