@@ -50,7 +50,7 @@ import * as authStore from "../../../src/app/stores/auth";
5050import * as cacheStore from "../../../src/app/stores/cache" ;
5151import * as apiModule from "../../../src/app/services/api" ;
5252import { updateConfig , config } from "../../../src/app/stores/config" ;
53- import { MERGE_ORGS_KEY , OAUTH_STATE_KEY } from "../../../src/app/lib/oauth" ;
53+ import { buildOrgAccessUrl } from "../../../src/app/lib/oauth" ;
5454
5555// ── Helpers ───────────────────────────────────────────────────────────────────
5656
@@ -104,7 +104,6 @@ beforeEach(() => {
104104 selectedRepos : [ ] ,
105105 } ) ;
106106
107- // Clear sessionStorage — re-auth tests set MERGE_ORGS_KEY and OAUTH_STATE_KEY
108107 sessionStorage . clear ( ) ;
109108
110109 // Mock window.location with both reload and href
@@ -521,126 +520,93 @@ describe("SettingsPage — Theme application", () => {
521520 } ) ;
522521} ) ;
523522
524- describe ( "SettingsPage — Re-auth button" , ( ) => {
523+ describe ( "SettingsPage — Grant more orgs button" , ( ) => {
525524 it ( "renders 'Grant more orgs' button in Organizations & Repositories section" , ( ) => {
526525 renderSettings ( ) ;
527526 screen . getByRole ( "button" , { name : "Grant more orgs" } ) ;
528527 } ) ;
529528
530- it ( "clicking 'Grant more orgs' sets MERGE_ORGS_KEY in sessionStorage " , async ( ) => {
529+ it ( "clicking 'Grant more orgs' opens GitHub app settings in new tab " , async ( ) => {
531530 const user = userEvent . setup ( ) ;
532531 vi . stubEnv ( "VITE_GITHUB_CLIENT_ID" , "test-client-id" ) ;
532+ const openSpy = vi . spyOn ( window , "open" ) . mockImplementation ( ( ) => null ) ;
533533 renderSettings ( ) ;
534534 const btn = screen . getByRole ( "button" , { name : "Grant more orgs" } ) ;
535535 await user . click ( btn ) ;
536- expect ( sessionStorage . getItem ( MERGE_ORGS_KEY ) ) . toBe ( "true" ) ;
536+ expect ( openSpy ) . toHaveBeenCalledWith (
537+ buildOrgAccessUrl ( ) ,
538+ "_blank" ,
539+ "noopener" ,
540+ ) ;
537541 vi . unstubAllEnvs ( ) ;
538542 } ) ;
539543
540- it ( "clicking 'Grant more orgs' sets window.location.href to GitHub authorize URL " , async ( ) => {
544+ it ( "clicking 'Grant more orgs' registers a focus listener for auto-merge " , async ( ) => {
541545 const user = userEvent . setup ( ) ;
542546 vi . stubEnv ( "VITE_GITHUB_CLIENT_ID" , "test-client-id" ) ;
547+ vi . spyOn ( window , "open" ) . mockImplementation ( ( ) => null ) ;
548+ const addSpy = vi . spyOn ( window , "addEventListener" ) ;
543549 renderSettings ( ) ;
544550 const btn = screen . getByRole ( "button" , { name : "Grant more orgs" } ) ;
545551 await user . click ( btn ) ;
546- expect ( window . location . href ) . toContain ( "https://github.com/login/oauth/authorize" ) ;
552+ expect ( addSpy ) . toHaveBeenCalledWith ( "focus" , expect . any ( Function ) ) ;
547553 vi . unstubAllEnvs ( ) ;
548554 } ) ;
549555
550- it ( "clicking 'Grant more orgs' also sets OAUTH_STATE_KEY in sessionStorage " , async ( ) => {
556+ it ( "auto-merges new orgs when window regains focus after granting " , async ( ) => {
551557 const user = userEvent . setup ( ) ;
552558 vi . stubEnv ( "VITE_GITHUB_CLIENT_ID" , "test-client-id" ) ;
553- renderSettings ( ) ;
554- const btn = screen . getByRole ( "button" , { name : "Grant more orgs" } ) ;
555- await user . click ( btn ) ;
556- expect ( sessionStorage . getItem ( OAUTH_STATE_KEY ) ) . toBeTruthy ( ) ;
557- vi . unstubAllEnvs ( ) ;
558- } ) ;
559-
560- it ( "'Grant more orgs' button is disabled after click and re-enables after timeout" , async ( ) => {
561- vi . useFakeTimers ( ) ;
562- const user = userEvent . setup ( { advanceTimers : vi . advanceTimersByTime } ) ;
563- vi . stubEnv ( "VITE_GITHUB_CLIENT_ID" , "test-client-id" ) ;
564- renderSettings ( ) ;
565- const btn = screen . getByRole ( "button" , { name : "Grant more orgs" } ) ;
566- await user . click ( btn ) ;
567- expect ( btn . hasAttribute ( "disabled" ) ) . toBe ( true ) ;
568- vi . advanceTimersByTime ( 3000 ) ;
569- expect ( btn . hasAttribute ( "disabled" ) ) . toBe ( false ) ;
570- vi . unstubAllEnvs ( ) ;
571- vi . useRealTimers ( ) ;
572- } ) ;
573- } ) ;
574-
575- describe ( "SettingsPage — Auto-merge orgs on mount" , ( ) => {
576- it ( "calls fetchOrgs and merges new orgs when MERGE_ORGS_KEY is in sessionStorage" , async ( ) => {
577- sessionStorage . setItem ( MERGE_ORGS_KEY , "true" ) ;
559+ vi . spyOn ( window , "open" ) . mockImplementation ( ( ) => null ) ;
578560 updateConfig ( { selectedOrgs : [ "existing-org" ] } ) ;
579561 vi . mocked ( apiModule . fetchOrgs ) . mockResolvedValue ( [
580562 { login : "existing-org" , avatarUrl : "" , type : "org" } ,
581563 { login : "new-org" , avatarUrl : "" , type : "org" } ,
582564 ] ) ;
583565 renderSettings ( ) ;
566+ const btn = screen . getByRole ( "button" , { name : "Grant more orgs" } ) ;
567+ await user . click ( btn ) ;
568+ // Simulate user returning from GitHub settings tab
569+ window . dispatchEvent ( new Event ( "focus" ) ) ;
584570 await waitFor ( ( ) => {
585571 expect ( apiModule . fetchOrgs ) . toHaveBeenCalled ( ) ;
586572 } ) ;
587573 await waitFor ( ( ) => {
588574 expect ( config . selectedOrgs ) . toContain ( "new-org" ) ;
589- } ) ;
590- } ) ;
591-
592- it ( "preserves existing selectedOrgs when merging" , async ( ) => {
593- sessionStorage . setItem ( MERGE_ORGS_KEY , "true" ) ;
594- updateConfig ( { selectedOrgs : [ "existing-org" ] } ) ;
595- vi . mocked ( apiModule . fetchOrgs ) . mockResolvedValue ( [
596- { login : "existing-org" , avatarUrl : "" , type : "org" } ,
597- { login : "new-org" , avatarUrl : "" , type : "org" } ,
598- ] ) ;
599- renderSettings ( ) ;
600- await waitFor ( ( ) => {
601575 expect ( config . selectedOrgs ) . toContain ( "existing-org" ) ;
602576 } ) ;
577+ vi . unstubAllEnvs ( ) ;
603578 } ) ;
604579
605- it ( "removes MERGE_ORGS_KEY from sessionStorage after processing" , async ( ) => {
606- sessionStorage . setItem ( MERGE_ORGS_KEY , "true" ) ;
607- updateConfig ( { selectedOrgs : [ ] } ) ;
608- vi . mocked ( apiModule . fetchOrgs ) . mockResolvedValue ( [
609- { login : "new-org" , avatarUrl : "" , type : "org" } ,
610- ] ) ;
611- renderSettings ( ) ;
612- await waitFor ( ( ) => {
613- expect ( sessionStorage . getItem ( MERGE_ORGS_KEY ) ) . toBeNull ( ) ;
614- } ) ;
615- } ) ;
616-
617- it ( "does not call fetchOrgs when MERGE_ORGS_KEY is not in sessionStorage" , ( ) => {
618- // No MERGE_ORGS_KEY set
619- renderSettings ( ) ;
620- expect ( apiModule . fetchOrgs ) . not . toHaveBeenCalled ( ) ;
621- } ) ;
622-
623- it ( "silently handles fetchOrgs rejection without breaking" , async ( ) => {
624- sessionStorage . setItem ( MERGE_ORGS_KEY , "true" ) ;
580+ it ( "silently handles fetchOrgs rejection on focus without breaking" , async ( ) => {
581+ const user = userEvent . setup ( ) ;
582+ vi . stubEnv ( "VITE_GITHUB_CLIENT_ID" , "test-client-id" ) ;
583+ vi . spyOn ( window , "open" ) . mockImplementation ( ( ) => null ) ;
625584 updateConfig ( { selectedOrgs : [ "existing-org" ] } ) ;
626585 vi . mocked ( apiModule . fetchOrgs ) . mockRejectedValue ( new Error ( "Network error" ) ) ;
627586 renderSettings ( ) ;
587+ const btn = screen . getByRole ( "button" , { name : "Grant more orgs" } ) ;
588+ await user . click ( btn ) ;
589+ window . dispatchEvent ( new Event ( "focus" ) ) ;
628590 await waitFor ( ( ) => {
629591 expect ( apiModule . fetchOrgs ) . toHaveBeenCalled ( ) ;
630592 } ) ;
631- // Config unchanged — error was swallowed
632593 expect ( config . selectedOrgs ) . toEqual ( [ "existing-org" ] ) ;
594+ vi . unstubAllEnvs ( ) ;
633595 } ) ;
634596
635- it ( "skips merge when getClient returns null" , async ( ) => {
636- sessionStorage . setItem ( MERGE_ORGS_KEY , "true" ) ;
597+ it ( "skips merge on focus when getClient returns null" , async ( ) => {
598+ const user = userEvent . setup ( ) ;
599+ vi . stubEnv ( "VITE_GITHUB_CLIENT_ID" , "test-client-id" ) ;
600+ vi . spyOn ( window , "open" ) . mockImplementation ( ( ) => null ) ;
637601 const github = await import ( "../../../src/app/services/github" ) ;
638- vi . mocked ( github . getClient ) . mockReturnValueOnce ( null ) ;
602+ vi . mocked ( github . getClient ) . mockReturnValue ( null ) ;
639603 renderSettings ( ) ;
640- // MERGE_ORGS_KEY still removed synchronously before mergeNewOrgs
641- await waitFor ( ( ) => {
642- expect ( sessionStorage . getItem ( MERGE_ORGS_KEY ) ) . toBeNull ( ) ;
643- } ) ;
604+ const btn = screen . getByRole ( "button" , { name : "Grant more orgs" } ) ;
605+ await user . click ( btn ) ;
606+ window . dispatchEvent ( new Event ( "focus" ) ) ;
607+ // Give mergeNewOrgs time to bail out
608+ await new Promise ( ( r ) => setTimeout ( r , 50 ) ) ;
644609 expect ( apiModule . fetchOrgs ) . not . toHaveBeenCalled ( ) ;
610+ vi . unstubAllEnvs ( ) ;
645611 } ) ;
646612} ) ;
0 commit comments