Browse Source

ref(stores): Make ProjectsStore useLegacyStore compatible (#29247)

Evan Purkhiser 3 years ago
parent
commit
05194d66bf

+ 1 - 1
static/app/components/organizations/globalSelectionHeader/globalSelectionHeader.tsx

@@ -89,7 +89,7 @@ type Props = {
   className?: string;
 
   /**
-   * Slugs of projects to display in project selector (this affects the ^^^projects returned from HoC)
+   * Slugs of projects to display in project selector
    */
   specificProjectSlugs?: string[];
 

+ 13 - 17
static/app/components/organizations/globalSelectionHeader/index.tsx

@@ -5,7 +5,7 @@ import partition from 'lodash/partition';
 import ConfigStore from 'app/stores/configStore';
 import {Organization, Project} from 'app/types';
 import withOrganization from 'app/utils/withOrganization';
-import withProjectsSpecified from 'app/utils/withProjectsSpecified';
+import withProjects from 'app/utils/withProjects';
 
 import GlobalSelectionHeader from './globalSelectionHeader';
 import InitializeGlobalSelectionHeader from './initializeGlobalSelectionHeader';
@@ -35,27 +35,25 @@ function GlobalSelectionHeaderContainer({
   forceProject,
   shouldForceProject,
   skipLoadLastUsed,
+  specificProjectSlugs,
   showAbsolute,
   ...props
 }: Props) {
-  function getProjects() {
-    const {isSuperuser} = ConfigStore.get('user');
-    const isOrgAdmin = organization.access.includes('org:admin');
+  const {isSuperuser} = ConfigStore.get('user');
+  const isOrgAdmin = organization.access.includes('org:admin');
 
-    const [memberProjects, nonMemberProjects] = partition(
-      projects,
-      project => project.isMember
-    );
+  const specifiedProjects = specificProjectSlugs
+    ? projects.filter(project => specificProjectSlugs.includes(project.slug))
+    : projects;
 
-    if (isSuperuser || isOrgAdmin) {
-      return [memberProjects, nonMemberProjects];
-    }
+  const [memberProjects, otherProjects] = partition(
+    specifiedProjects,
+    project => project.isMember
+  );
 
-    return [memberProjects, []];
-  }
+  const nonMemberProjects = isSuperuser || isOrgAdmin ? otherProjects : [];
 
   const enforceSingleProject = !organization.features.includes('global-views');
-  const [memberProjects, nonMemberProjects] = getProjects();
 
   // We can initialize before ProjectsStore is fully loaded if we don't need to enforce single project.
   return (
@@ -93,6 +91,4 @@ function GlobalSelectionHeaderContainer({
   );
 }
 
-export default withOrganization(
-  withProjectsSpecified(withRouter(GlobalSelectionHeaderContainer))
-);
+export default withOrganization(withProjects(withRouter(GlobalSelectionHeaderContainer)));

+ 10 - 9
static/app/stores/projectsStore.tsx

@@ -4,6 +4,8 @@ import ProjectActions from 'app/actions/projectActions';
 import TeamActions from 'app/actions/teamActions';
 import {Project, Team} from 'app/types';
 
+import {CommonStoreInterface} from './types';
+
 type State = {
   projects: Project[];
   loading: boolean;
@@ -19,7 +21,7 @@ type Internals = {
   loading: boolean;
 };
 
-type ProjectsStoreInterface = {
+type ProjectsStoreInterface = CommonStoreInterface<State> & {
   init(): void;
   reset(): void;
   loadInitialData(projects: Project[]): void;
@@ -31,10 +33,9 @@ type ProjectsStoreInterface = {
   onRemoveTeam(teamSlug: string, projectSlug: string): void;
   onAddTeam(team: Team, projectSlug: string): void;
   removeTeamFromProject(teamSlug: string, project: Project): void;
+  isLoading(): boolean;
   getWithTeam(teamSlug: string): Project[];
   getAll(): Project[];
-  getBySlugs(slug: string[]): Project[];
-  getState(slugs?: string[]): State;
   getById(id?: string): Project | undefined;
   getBySlug(slug?: string): Project | undefined;
 };
@@ -193,6 +194,10 @@ const storeConfig: Reflux.StoreDefinition & Internals & ProjectsStoreInterface =
     return this.getAll().filter(({teams}) => teams.find(({slug}) => slug === teamSlug));
   },
 
+  isLoading() {
+    return this.loading;
+  },
+
   getAll() {
     return Object.values(this.itemsById).sort((a, b) => a.slug.localeCompare(b.slug));
   },
@@ -205,13 +210,9 @@ const storeConfig: Reflux.StoreDefinition & Internals & ProjectsStoreInterface =
     return this.getAll().find(project => project.slug === slug);
   },
 
-  getBySlugs(slugs: string[]) {
-    return this.getAll().filter(project => slugs.includes(project.slug));
-  },
-
-  getState(slugs?: string[]): State {
+  getState() {
     return {
-      projects: slugs ? this.getBySlugs(slugs) : this.getAll(),
+      projects: this.getAll(),
       loading: this.loading,
     };
   },

+ 2 - 1
static/app/utils/projects.tsx

@@ -450,7 +450,8 @@ async function fetchProjects(
   }
 
   if (allProjects) {
-    const {loading, projects} = ProjectsStore.getState();
+    const projects = ProjectsStore.getAll();
+    const loading = ProjectsStore.isLoading();
     // If the projects store is loaded then return all projects from the store
     if (!loading) {
       return {

+ 9 - 2
static/app/utils/withProjects.tsx

@@ -26,14 +26,21 @@ function withProjects<P extends InjectedProjectsProps>(
   > {
     static displayName = `withProjects(${getDisplayName(WrappedComponent)})`;
 
-    state: State = ProjectsStore.getState();
+    state: State = {
+      projects: ProjectsStore.getAll(),
+      loading: ProjectsStore.isLoading(),
+    };
 
     componentWillUnmount() {
       this.unsubscribe();
     }
 
     unsubscribe = ProjectsStore.listen(
-      () => this.setState(ProjectsStore.getState()),
+      () =>
+        this.setState({
+          projects: ProjectsStore.getAll(),
+          loading: ProjectsStore.isLoading(),
+        }),
       undefined
     );
 

+ 0 - 66
static/app/utils/withProjectsSpecified.tsx

@@ -1,66 +0,0 @@
-import * as React from 'react';
-import isEqual from 'lodash/isEqual';
-
-import ProjectsStore from 'app/stores/projectsStore';
-import {Project} from 'app/types';
-import getDisplayName from 'app/utils/getDisplayName';
-
-type Props = {
-  projects?: Project[];
-  specificProjectSlugs?: string[];
-};
-
-type InjectedProjectsProps = {
-  loadingProjects: boolean;
-} & Props;
-
-type State = {
-  projects: Project[];
-  loading: boolean;
-};
-
-/**
- * Higher order component that takes specificProjectSlugs and provides list of that projects from ProjectsStore
- */
-function withProjectsSpecified<P extends InjectedProjectsProps>(
-  WrappedComponent: React.ComponentType<P>
-) {
-  class WithProjectsSpecified extends React.Component<
-    Props & Omit<P, keyof InjectedProjectsProps>,
-    State
-  > {
-    static displayName = `withProjectsSpecified(${getDisplayName(WrappedComponent)})`;
-
-    state = ProjectsStore.getState(this.props.specificProjectSlugs);
-
-    static getDerivedStateFromProps(nextProps: Readonly<Props>): State {
-      return ProjectsStore.getState(nextProps.specificProjectSlugs);
-    }
-
-    componentWillUnmount() {
-      this.unsubscribe();
-    }
-
-    unsubscribe = ProjectsStore.listen(() => {
-      const storeState = ProjectsStore.getState(this.props.specificProjectSlugs);
-
-      if (!isEqual(this.state, storeState)) {
-        this.setState(storeState);
-      }
-    }, undefined);
-
-    render() {
-      return (
-        <WrappedComponent
-          {...(this.props as P)}
-          projects={this.state.projects as Project[]}
-          loadingProjects={this.state.loading}
-        />
-      );
-    }
-  }
-
-  return WithProjectsSpecified;
-}
-
-export default withProjectsSpecified;

+ 109 - 104
tests/js/spec/components/organizations/globalSelectionHeader.spec.jsx

@@ -59,10 +59,8 @@ describe('GlobalSelectionHeader', function () {
 
   beforeEach(function () {
     MockApiClient.clearMockResponses();
-    jest.spyOn(ProjectsStore, 'getState').mockImplementation(() => ({
-      projects: organization.projects,
-      loading: false,
-    }));
+    jest.spyOn(ProjectsStore, 'getAll').mockImplementation(() => organization.projects);
+    jest.spyOn(ProjectsStore, 'isLoading').mockImplementation(() => false);
 
     getItem.mockImplementation(() => null);
     MockApiClient.addMockResponse({
@@ -231,10 +229,8 @@ describe('GlobalSelectionHeader', function () {
         params: {orgId: 'org-slug'},
       },
     });
-    jest.spyOn(ProjectsStore, 'getState').mockImplementation(() => ({
-      projects: initialData.projects,
-      loading: false,
-    }));
+    jest.spyOn(ProjectsStore, 'getAll').mockImplementation(() => initialData.projects);
+    jest.spyOn(ProjectsStore, 'isLoading').mockImplementation(() => false);
 
     wrapper = mountWithTheme(
       <GlobalSelectionHeader
@@ -387,7 +383,9 @@ describe('GlobalSelectionHeader', function () {
         features: ['global-views'],
       },
       router: {
-        params: {orgId: 'org-slug'}, // we need this to be set to make sure org in context is same as current org in URL
+        // we need this to be set to make sure org in context is same as
+        // current org in URL
+        params: {orgId: 'org-slug'},
       },
     });
 
@@ -421,7 +419,9 @@ describe('GlobalSelectionHeader', function () {
         features: ['global-views'],
       },
       router: {
-        params: {orgId: 'org-slug'}, // we need this to be set to make sure org in context is same as current org in URL
+        // we need this to be set to make sure org in context is same as
+        // current org in URL
+        params: {orgId: 'org-slug'},
         location: {query: {project: [1, 2]}},
       },
     });
@@ -445,7 +445,9 @@ describe('GlobalSelectionHeader', function () {
         features: ['global-views'],
       },
       router: {
-        params: {orgId: 'org-slug'}, // we need this to be set to make sure org in context is same as current org in URL
+        // we need this to be set to make sure org in context is same as
+        // current org in URL
+        params: {orgId: 'org-slug'},
         location: {query: {project: [1, 2]}},
       },
     });
@@ -469,7 +471,9 @@ describe('GlobalSelectionHeader', function () {
         features: ['global-views'],
       },
       router: {
-        params: {orgId: 'org-slug'}, // we need this to be set to make sure org in context is same as current org in URL
+        // we need this to be set to make sure org in context is same as
+        // current org in URL
+        params: {orgId: 'org-slug'},
         location: {query: {}},
       },
     });
@@ -487,32 +491,43 @@ describe('GlobalSelectionHeader', function () {
    * GSH: (no global-views)
    * - mounts with no state from router
    *   - params org id === org.slug
-   * - updateProjects should not be called (enforceSingleProject should not be called)
-   * - componentDidUpdate with loadingProjects === true, and pass in list of projects (via projects store)
-   * - enforceProject should be called and updateProjects() called with the new project
+   *
+   * - updateProjects should not be called (enforceSingleProject should not be
+   *   called)
+   *
+   * - componentDidUpdate with loadingProjects === true, and pass in list of
+   *   projects (via projects store)
+   *
+   * - enforceProject should be called and updateProjects() called with the new
+   *   project
    *   - variation:
    *     - params.orgId !== org.slug (e.g. just switched orgs)
    *
    * When switching orgs when not in Issues view, the issues view gets rendered
    * with params.orgId !== org.slug
-   * Global selection header gets unmounted and mounted, and in this case nothing should be done
-   * until it gets updated and params.orgId === org.slug
+   *
+   * Global selection header gets unmounted and mounted, and in this case
+   * nothing should be done until it gets updated and params.orgId === org.slug
    *
    * Separate issue:
-   * IssuesList ("child view") renders before a single project is enforced, will require refactoring
-   * views so that they depend on GSH enforcing a single project first IF they don't have required feature
-   * (and no project id in URL).
+   *
+   * IssuesList ("child view") renders before a single project is enforced,
+   * will require refactoring views so that they depend on GSH enforcing a
+   * single project first IF they don't have required feature (and no project id
+   * in URL).
    */
   describe('Single project selection mode', function () {
     it('does not do anything while organization is switching in single project', async function () {
       const initialData = initializeOrg({
         organization: {slug: 'old-org-slug'},
         router: {
-          params: {orgId: 'org-slug'}, // we need this to be set to make sure org in context is same as current org in URL
+          // we need this to be set to make sure org in context is same as
+          // current org in URL
+          params: {orgId: 'org-slug'},
           location: {query: {project: [1]}},
         },
       });
-      ProjectsStore.getState.mockRestore();
+      ProjectsStore.isLoading.mockRestore();
       ProjectsStore.getAll.mockRestore();
 
       MockApiClient.addMockResponse({
@@ -520,8 +535,8 @@ describe('GlobalSelectionHeader', function () {
         body: [],
       });
 
-      // This can happen when you switch organization so params.orgId !== the current org in context
-      // In this case params.orgId = 'org-slug'
+      // This can happen when you switch organization so params.orgId !== the
+      // current org in context In this case params.orgId = 'org-slug'
       wrapper = mountWithTheme(
         <GlobalSelectionHeader organization={initialData.organization} />,
         initialData.routerContext
@@ -540,8 +555,8 @@ describe('GlobalSelectionHeader', function () {
         body: updatedOrganization,
       });
 
-      // Eventually OrganizationContext will fetch org details for `org-slug` and update `organization` prop
-      // emulate fetchOrganizationDetails
+      // Eventually OrganizationContext will fetch org details for `org-slug`
+      // and update `organization` prop emulate fetchOrganizationDetails
       OrganizationActions.update(updatedOrganization);
       wrapper.setContext({
         organization: updatedOrganization,
@@ -565,7 +580,9 @@ describe('GlobalSelectionHeader', function () {
     it('selects first project if more than one is requested', function () {
       const initializationObj = initializeOrg({
         router: {
-          params: {orgId: 'org-slug'}, // we need this to be set to make sure org in context is same as current org in URL
+          // we need this to be set to make sure org in context is same as
+          // current org in URL
+          params: {orgId: 'org-slug'},
           location: {query: {project: [1, 2]}},
         },
       });
@@ -585,9 +602,9 @@ describe('GlobalSelectionHeader', function () {
     it('selects first project if none (i.e. all) is requested', async function () {
       const project = TestStubs.Project({id: '3'});
       const org = TestStubs.Organization({projects: [project]});
-      jest
-        .spyOn(ProjectsStore, 'getState')
-        .mockImplementation(() => ({projects: org.projects, loading: false}));
+
+      jest.spyOn(ProjectsStore, 'getAll').mockImplementation(() => org.projects);
+      jest.spyOn(ProjectsStore, 'isLoading').mockImplementation(() => false);
 
       const initializationObj = initializeOrg({
         organization: org,
@@ -626,16 +643,15 @@ describe('GlobalSelectionHeader', function () {
           location: {query: {}},
         },
       });
-      jest.spyOn(ProjectsStore, 'getState').mockImplementation(() => ({
-        projects: initialData.organization.projects,
-        loadingProjects: false,
-      }));
+
+      jest.spyOn(ProjectsStore, 'getAll').mockImplementation(() => initialData.projects);
+      jest.spyOn(ProjectsStore, 'isLoading').mockImplementation(() => false);
 
       wrapper = mountWithTheme(
         <GlobalSelectionHeader
           organization={initialData.organization}
           shouldForceProject
-          forceProject={initialData.organization.projects[0]}
+          forceProject={initialData.projects[0]}
           showIssueStreamLink
         />,
         initialData.routerContext
@@ -684,10 +700,11 @@ describe('GlobalSelectionHeader', function () {
       };
 
       beforeEach(function () {
-        jest.spyOn(ProjectsStore, 'getState').mockImplementation(() => ({
-          projects: initialData.organization.projects,
-          loading: false,
-        }));
+        jest
+          .spyOn(ProjectsStore, 'getAll')
+          .mockImplementation(() => initialData.projects);
+        jest.spyOn(ProjectsStore, 'isLoading').mockImplementation(() => false);
+
         initialData.router.push.mockClear();
         initialData.router.replace.mockClear();
       });
@@ -705,26 +722,25 @@ describe('GlobalSelectionHeader', function () {
       });
 
       it('appends projectId to URL when `forceProject` becomes available (async)', async function () {
-        const mockProjectsStoreState = {
-          projects: [],
-          loading: true,
-        };
-
-        jest
-          .spyOn(ProjectsStore, 'getState')
-          .mockImplementation(() => mockProjectsStoreState);
+        jest.spyOn(ProjectsStore, 'getAll').mockImplementation(() => []);
+        jest.spyOn(ProjectsStore, 'isLoading').mockImplementation(() => true);
 
         // forceProject generally starts undefined
         createWrapper({shouldForceProject: true});
 
         // load the projects
-        mockProjectsStoreState.projects = initialData.organization.projects;
-        mockProjectsStoreState.loading = false;
+        jest
+          .spyOn(ProjectsStore, 'getAll')
+          .mockImplementation(() => initialData.projects);
+        jest.spyOn(ProjectsStore, 'isLoading').mockImplementation(() => false);
 
         wrapper.setProps({
-          forceProject: initialData.organization.projects[1],
+          forceProject: initialData.projects[1],
         });
 
+        // Force the withProjects HoC to re-render
+        ProjectsStore.trigger();
+
         wrapper.update();
 
         expect(initialData.router.replace).toHaveBeenLastCalledWith({
@@ -751,7 +767,7 @@ describe('GlobalSelectionHeader', function () {
           },
         });
         wrapper.setProps({
-          forceProject: initialData.organization.projects[1],
+          forceProject: initialData.projects[1],
         });
 
         wrapper.update();
@@ -763,7 +779,7 @@ describe('GlobalSelectionHeader', function () {
         // forceProject generally starts undefined
         createWrapper({
           shouldForceProject: true,
-          forceProject: initialData.organization.projects[1],
+          forceProject: initialData.projects[1],
         });
 
         wrapper.update();
@@ -787,9 +803,7 @@ describe('GlobalSelectionHeader', function () {
           params: {orgId: 'org-slug'},
         },
       });
-      jest
-        .spyOn(ProjectsStore, 'getAll')
-        .mockImplementation(() => initialData.organization.projects);
+      jest.spyOn(ProjectsStore, 'getAll').mockImplementation(() => initialData.projects);
 
       const createWrapper = props => {
         wrapper = mountWithTheme(
@@ -812,7 +826,7 @@ describe('GlobalSelectionHeader', function () {
         // forceProject generally starts undefined
         createWrapper({
           shouldForceProject: true,
-          forceProject: initialData.organization.projects[1],
+          forceProject: initialData.projects[1],
         });
 
         wrapper.update();
@@ -856,10 +870,11 @@ describe('GlobalSelectionHeader', function () {
       };
 
       beforeEach(function () {
-        jest.spyOn(ProjectsStore, 'getState').mockImplementation(() => ({
-          projects: initialData.organization.projects,
-          loading: false,
-        }));
+        jest
+          .spyOn(ProjectsStore, 'getAll')
+          .mockImplementation(() => initialData.projects);
+        jest.spyOn(ProjectsStore, 'isLoading').mockImplementation(() => false);
+
         initialData.router.push.mockClear();
         initialData.router.replace.mockClear();
       });
@@ -874,50 +889,46 @@ describe('GlobalSelectionHeader', function () {
       });
 
       it('does not append projectId to URL when `loadingProjects` changes and finishes loading', async function () {
-        const mockProjectsStoreState = {
-          projects: [],
-          loading: true,
-        };
-
-        jest
-          .spyOn(ProjectsStore, 'getState')
-          .mockImplementation(() => mockProjectsStoreState);
+        jest.spyOn(ProjectsStore, 'getAll').mockImplementation(() => []);
+        jest.spyOn(ProjectsStore, 'isLoading').mockImplementation(() => true);
 
         createWrapper();
 
         // load the projects
-        mockProjectsStoreState.projects = initialData.organization.projects;
-        mockProjectsStoreState.loading = false;
+        jest
+          .spyOn(ProjectsStore, 'getAll')
+          .mockImplementation(() => initialData.projects);
+        jest.spyOn(ProjectsStore, 'isLoading').mockImplementation(() => false);
 
-        wrapper.update();
+        wrapper.setProps({
+          forceProject: initialData.projects[1],
+        });
+
+        // Force the withProjects HoC to re-render
+        ProjectsStore.trigger();
 
         expect(initialData.router.replace).not.toHaveBeenCalled();
       });
 
       it('appends projectId to URL when `forceProject` becomes available (async)', async function () {
-        const mockProjectsStoreState = {
-          projects: [],
-          loading: true,
-        };
-
-        jest
-          .spyOn(ProjectsStore, 'getState')
-          .mockImplementation(() => mockProjectsStoreState);
+        jest.spyOn(ProjectsStore, 'getAll').mockImplementation(() => []);
+        jest.spyOn(ProjectsStore, 'isLoading').mockImplementation(() => true);
 
         // forceProject generally starts undefined
         createWrapper({shouldForceProject: true});
 
-        await tick();
-
         // load the projects
-        mockProjectsStoreState.projects = initialData.organization.projects;
-        mockProjectsStoreState.loading = false;
+        jest
+          .spyOn(ProjectsStore, 'getAll')
+          .mockImplementation(() => initialData.projects);
+        jest.spyOn(ProjectsStore, 'isLoading').mockImplementation(() => false);
 
         wrapper.setProps({
-          forceProject: initialData.organization.projects[1],
+          forceProject: initialData.projects[1],
         });
 
-        wrapper.update();
+        // Force the withProjects HoC to re-render
+        ProjectsStore.trigger();
 
         expect(initialData.router.replace).toHaveBeenLastCalledWith({
           pathname: undefined,
@@ -937,7 +948,7 @@ describe('GlobalSelectionHeader', function () {
         await tick();
 
         wrapper.setProps({
-          forceProject: initialData.organization.projects[1],
+          forceProject: initialData.projects[1],
         });
 
         wrapper.update();
@@ -953,9 +964,7 @@ describe('GlobalSelectionHeader', function () {
       memberProject = TestStubs.Project({id: '3', isMember: true});
       nonMemberProject = TestStubs.Project({id: '4', isMember: false});
       initialData = initializeOrg({
-        organization: {
-          projects: [memberProject, nonMemberProject],
-        },
+        projects: [memberProject, nonMemberProject],
         router: {
           location: {query: {}},
           params: {
@@ -964,10 +973,8 @@ describe('GlobalSelectionHeader', function () {
         },
       });
 
-      jest.spyOn(ProjectsStore, 'getState').mockImplementation(() => ({
-        projects: initialData.organization.projects,
-        loading: false,
-      }));
+      jest.spyOn(ProjectsStore, 'getAll').mockImplementation(() => initialData.projects);
+      jest.spyOn(ProjectsStore, 'isLoading').mockImplementation(() => false);
 
       wrapper = mountWithTheme(
         <GlobalSelectionHeader organization={initialData.organization} />,
@@ -1007,7 +1014,7 @@ describe('GlobalSelectionHeader', function () {
       wrapper = mountWithTheme(
         <GlobalSelectionHeader
           organization={initialData.organization}
-          projects={initialData.organization.projects}
+          projects={initialData.projects}
         />,
         initialData.routerContext
       );
@@ -1033,7 +1040,7 @@ describe('GlobalSelectionHeader', function () {
       wrapper = mountWithTheme(
         <GlobalSelectionHeader
           organization={initialData.organization}
-          projects={initialData.organization.projects}
+          projects={initialData.projects}
         />,
         initialData.routerContext
       );
@@ -1058,7 +1065,7 @@ describe('GlobalSelectionHeader', function () {
       wrapper = mountWithTheme(
         <GlobalSelectionHeader
           organization={initialData.organization}
-          projects={initialData.organization.projects}
+          projects={initialData.projects}
         />,
         initialData.routerContext
       );
@@ -1084,7 +1091,7 @@ describe('GlobalSelectionHeader', function () {
       wrapper = mountWithTheme(
         <GlobalSelectionHeader
           organization={initialData.organization}
-          projects={initialData.organization.projects}
+          projects={initialData.projects}
         />,
         changeQuery(initialData.routerContext, {project: -1})
       );
@@ -1118,17 +1125,15 @@ describe('GlobalSelectionHeader', function () {
     });
 
     beforeEach(function () {
-      jest.spyOn(ProjectsStore, 'getState').mockImplementation(() => ({
-        projects: initialData.organization.projects,
-        loading: false,
-      }));
+      jest.spyOn(ProjectsStore, 'getAll').mockImplementation(() => initialData.projects);
+      jest.spyOn(ProjectsStore, 'isLoading').mockImplementation(() => false);
     });
 
     it('shows IconProject when no projects are selected', async function () {
       wrapper = mountWithTheme(
         <GlobalSelectionHeader
           organization={initialData.organization}
-          projects={initialData.organization.projects}
+          projects={initialData.projects}
         />,
         changeQuery(initialData.routerContext, {project: -1})
       );
@@ -1148,7 +1153,7 @@ describe('GlobalSelectionHeader', function () {
       wrapper = mountWithTheme(
         <GlobalSelectionHeader
           organization={initialData.organization}
-          projects={initialData.organization.projects}
+          projects={initialData.projects}
         />,
         changeQuery(initialData.routerContext, {project: 1})
       );
@@ -1169,7 +1174,7 @@ describe('GlobalSelectionHeader', function () {
       wrapper = mountWithTheme(
         <GlobalSelectionHeader
           organization={initialData.organization}
-          projects={initialData.organization.projects}
+          projects={initialData.projects}
         />,
         initialData.routerContext
       );

+ 2 - 4
tests/js/spec/components/suggestProjectCTA.spec.jsx

@@ -9,10 +9,8 @@ jest.mock('app/actionCreators/modal');
 function generateWrapperAndSetMocks(inputProps, mobileEventResp, promptResp) {
   const projects = inputProps?.projects ?? [TestStubs.Project({platform: 'javascript'})];
 
-  jest.spyOn(ProjectsStore, 'getState').mockImplementation(() => ({
-    projects,
-    loading: false,
-  }));
+  jest.spyOn(ProjectsStore, 'getAll').mockImplementation(() => projects);
+  jest.spyOn(ProjectsStore, 'isLoading').mockImplementation(() => false);
 
   const organization = TestStubs.Organization();