react-router#MemoryRouter TypeScript Examples

The following examples show how to use react-router#MemoryRouter. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.test.tsx    From firebase-tools-ui with Apache License 2.0 6 votes vote down vote up
it('renders all emulators as "off" when error loading config', () => {
  // We don't need to show the error here because there is an app-wide
  // disconnected overlay. Just keep the layout and show everything as "off".
  const { getByTestId } = render(
    <MemoryRouter>
      <TestEmulatorConfigProvider config={null}>
        <Home />
      </TestEmulatorConfigProvider>
    </MemoryRouter>
  );

  for (const emulator of ['database', 'firestore', 'functions', 'extensions']) {
    const card = getByTestId(`emulator-info-${emulator}`);
    expect(getByText(card, 'Off')).not.toBeNull();
    expect(getByText(card, 'N/A')).not.toBeNull(); // Port is N/A
  }
});
Example #2
Source File: DebuggingMemoryRouter.ts    From json-schema-viewer with Apache License 2.0 6 votes vote down vote up
export class DebuggingMemoryRouter extends MemoryRouter {
  constructor(props: MemoryRouterProps) {
    super(props);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    (this as any).history.listen((location: any, action: any) => { // tslint:disable-line:no-any
      console.log(
        `The current URL is ${location.pathname}${location.search}${location.hash}`
      );
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      console.log(`The last navigation action was ${action}`, JSON.stringify((this as any).history, null, 2));
    });
  }
}
Example #3
Source File: CompareApiBreadcrumbs.stories.tsx    From substrate-api-explorer with Apache License 2.0 6 votes vote down vote up
storiesOf('COMPONENTS|CompareApiBreadcrumbs', module)
  .addDecorator(story => (
    <MemoryRouter initialEntries={['/']}>{story()}</MemoryRouter>
  ))
  .add('default', () => {
    const apiNameKnob = text(
      'apiName',
      'wss://kusama-rpc.polkadot.io/',
      'props'
    )
    const urlKnob = text(
      'apiexplorer.polkalert.com/compare-api/',
      'query/staking',
      'other'
    )

    return (
      <div style={{ padding: '24px' }}>
        <CompareApiBreadcrumbs
          apiName={apiNameKnob}
          match={{ url: `/compare-api/${urlKnob}` }}
        />
      </div>
    )
  })
Example #4
Source File: Layout.test.tsx    From glific-frontend with GNU Affero General Public License v3.0 6 votes vote down vote up
describe('layout testing', () => {
  it('renders the appropriate components', async () => {
    const { getByTestId } = render(
      <MockedProvider mocks={mocks}>
        <MemoryRouter>
          <Layout>Default layout</Layout>
        </MemoryRouter>
      </MockedProvider>
    );

    await waitFor(async () => await new Promise((resolve) => setTimeout(resolve, 0)));
    expect(getByTestId('navbar')).toBeInTheDocument();
    expect(getByTestId('layout')).toBeInTheDocument();
  });
});
Example #5
Source File: InfoCard.stories.tsx    From backstage with Apache License 2.0 6 votes vote down vote up
Wrapper = ({ children }: PropsWithChildren<{}>) => (
  <MemoryRouter>
    <Grid container spacing={4}>
      <Grid item xs={4}>
        {children}
      </Grid>
    </Grid>
  </MemoryRouter>
)
Example #6
Source File: Trigger.test.tsx    From glific-frontend with GNU Affero General Public License v3.0 6 votes vote down vote up
describe('trigger with hourly frequency', () => {
  const mocks = [hourlyTrigger(), ...LIST_ITEM_MOCKS, ...SearchMocks];

  const wrapper = (
    <MockedProvider mocks={mocks} addTypename={false}>
      <MemoryRouter>
        <Trigger match={{ params: { id: '1' } }} />
      </MemoryRouter>
    </MockedProvider>
  );

  test('should load trigger edit form', async () => {
    const { getByText, getByTestId } = render(wrapper);

    // loading is show initially
    expect(getByText('Loading...')).toBeInTheDocument();
    await waitFor(() => {
      const formLayout = getByTestId('formLayout');
      expect(formLayout).toHaveTextContent('hours');
    });

    await waitFor(() => {
      expect(getByText('1 AM')).toBeInTheDocument();
      expect(getByText('1 PM')).toBeInTheDocument();
    });
  });
});
Example #7
Source File: BuildTable.stories.tsx    From backstage with Apache License 2.0 6 votes vote down vote up
ErrorMessage = () => (
  <MemoryRouter>
    <BuildTable
      items={[]}
      loading={false}
      error={new Error('Failed to load builds!')}
    />
  </MemoryRouter>
)
Example #8
Source File: MainList.spec.tsx    From che-dashboard-next with Eclipse Public License 2.0 6 votes vote down vote up
describe('Navigation Main List', () => {

  function renderComponent(): RenderResult {
    return render(
      <MemoryRouter>
        <Nav
          onSelect={() => jest.fn()}
          theme="light"
        >
          <NavigationMainList activePath="" />
        </Nav>
      </MemoryRouter>
    );
  }

  it('should have correct number of main navigation items', () => {
    renderComponent();

    const navLinks = screen.getAllByRole('link');
    expect(navLinks.length).toEqual(2);
  });

  it('should have correct navigation item labels', () => {
    renderComponent();

    const navLinks = screen.getAllByRole('link');

    expect(navLinks[0]).toHaveTextContent('Get Started Page');
    expect(navLinks[1]).toHaveTextContent('Workspaces');
  });

});
Example #9
Source File: CollectionContactList.test.tsx    From glific-frontend with GNU Affero General Public License v3.0 5 votes vote down vote up
wrapper = (
  <MockedProvider mocks={mocks} addTypename={false}>
    <MemoryRouter>
      <CollectionContactList match={{ params: { id: 1 } }} title={'Default Collection'} />
    </MemoryRouter>
  </MockedProvider>
)
Example #10
Source File: GaugeCard.stories.tsx    From backstage with Apache License 2.0 5 votes vote down vote up
Wrapper = ({ children }: PropsWithChildren<{}>) => (
  <MemoryRouter>
    <Grid container spacing={2}>
      {children}
    </Grid>
  </MemoryRouter>
)
Example #11
Source File: ResetPasswordPhone.test.tsx    From glific-frontend with GNU Affero General Public License v3.0 5 votes vote down vote up
wrapper = (
  <MemoryRouter>
    <ResetPasswordPhone />
  </MemoryRouter>
)
Example #12
Source File: TabbedLayout.stories.tsx    From backstage with Apache License 2.0 5 votes vote down vote up
Wrapper = ({ children }: PropsWithChildren<{}>) => (
  <MemoryRouter>
    <Routes>
      <Route path="/*" element={<>{children}</>} />
    </Routes>
  </MemoryRouter>
)
Example #13
Source File: MainItem.spec.tsx    From che-dashboard-next with Eclipse Public License 2.0 5 votes vote down vote up
describe('Navigation Item', () => {

  let activeItem = '';
  const item: NavigationItemObject = {
    icon: <HomeIcon />,
    label: 'Home',
    to: '/home',
  };

  function renderComponent(): RenderResult {
    return render(
      <MemoryRouter>
        <NavigationMainItem
          item={item}
          activePath={activeItem}
        >
          {item.icon}
        </NavigationMainItem>
      </MemoryRouter>
    );
  }

  it('should have correct label', () => {
    renderComponent();

    const link = screen.getByRole('link');
    expect(link).toHaveTextContent('Home');
  });

  describe('activation', () => {

    it('should render not active navigation item', () => {
      renderComponent();

      const link = screen.getByRole('link');
      expect(link).not.toHaveAttribute('aria-current');
    });

    it('should render active navigation item', () => {
      activeItem = '/home';
      renderComponent();

      const link = screen.getByRole('link');
      expect(link).toHaveAttribute('aria-current');
    });

    it('should activate navigation item on props change', () => {
      activeItem = '';
      const { rerender } = renderComponent();

      activeItem = '/home';
      rerender(
        <MemoryRouter>
          <NavigationMainItem
            item={item}
            activePath={activeItem}
          >
            {item.icon}
          </NavigationMainItem>
        </MemoryRouter>
      );

      const link = screen.getByRole('link');
      expect(link).toHaveAttribute('aria-current');
    });

  });

});
Example #14
Source File: ResetPasswordConfirmOTP.test.tsx    From glific-frontend with GNU Affero General Public License v3.0 5 votes vote down vote up
wrapper = (
  <MemoryRouter>
    <ResetPasswordConfirmOTP {...defaultProps} />
  </MemoryRouter>
)
Example #15
Source File: RecentWorkflowRunsCard.test.tsx    From backstage with Apache License 2.0 4 votes vote down vote up
describe('<RecentWorkflowRunsCard />', () => {
  const entity = {
    apiVersion: 'v1',
    kind: 'Component',
    metadata: {
      name: 'software',
      annotations: {
        'github.com/project-slug': 'theorg/the-service',
      },
    },
    spec: {
      owner: 'guest',
      type: 'service',
      lifecycle: 'production',
    },
  };

  const workflowRuns = [1, 2, 3, 4, 5].map(n => ({
    id: `run-id-${n}`,
    message: `Commit message for workflow ${n}`,
    source: { branchName: `branch-${n}` },
    status: 'completed',
  }));

  beforeEach(() => {
    (useWorkflowRuns as jest.Mock).mockReturnValue([{ runs: workflowRuns }]);
  });

  afterEach(() => {
    jest.resetAllMocks();
  });

  const renderSubject = (props: RecentWorkflowRunsCardProps = {}) =>
    render(
      <ThemeProvider theme={lightTheme}>
        <MemoryRouter>
          <TestApiProvider
            apis={[
              [errorApiRef, mockErrorApi],
              [configApiRef, configApi],
            ]}
          >
            <EntityProvider entity={entity}>
              <RecentWorkflowRunsCard {...props} />
            </EntityProvider>
          </TestApiProvider>
        </MemoryRouter>
      </ThemeProvider>,
    );

  it('renders a table with a row for each workflow', async () => {
    const subject = renderSubject();

    workflowRuns.forEach(run => {
      expect(subject.getByText(run.message)).toBeInTheDocument();
    });
  });

  it('renders a workflow row correctly', async () => {
    const subject = renderSubject();
    const [run] = workflowRuns;
    expect(subject.getByText(run.message).closest('a')).toHaveAttribute(
      'href',
      `/ci-cd/${run.id}`,
    );
    expect(subject.getByText(run.source.branchName)).toBeInTheDocument();
  });

  it('requests only the required number of workflow runs', async () => {
    const limit = 3;
    renderSubject({ limit });
    expect(useWorkflowRuns).toHaveBeenCalledWith(
      expect.objectContaining({ initialPageSize: limit }),
    );
  });

  it('uses the github repo and owner from the entity annotation', async () => {
    renderSubject();
    expect(useWorkflowRuns).toHaveBeenCalledWith(
      expect.objectContaining({ owner: 'theorg', repo: 'the-service' }),
    );
  });

  it('filters workflows by branch if one is specified', async () => {
    const branch = 'master';
    renderSubject({ branch });
    expect(useWorkflowRuns).toHaveBeenCalledWith(
      expect.objectContaining({ branch }),
    );
  });

  describe('where there is an error fetching workflows', () => {
    const error = 'error getting workflows';
    beforeEach(() => {
      (useWorkflowRuns as jest.Mock).mockReturnValue([{ runs: [], error }]);
    });

    it('sends the error to the errorApi', async () => {
      renderSubject();
      expect(mockErrorApi.post).toHaveBeenCalledWith(error);
    });
  });
});
Example #16
Source File: ContactBar.test.tsx    From glific-frontend with GNU Affero General Public License v3.0 4 votes vote down vote up
describe('Menu test', () => {
  beforeEach(async () => {
    render(component);
    await waitFor(() => {
      fireEvent.click(screen.getByTestId('dropdownIcon')?.querySelector('svg'));
    });
  });

  test('it should open a menu when dropdown icon is clicked', async () => {
    expect(screen.getByText('View contact profile')).toBeInTheDocument();
  });

  test('clicking on add to collection button should open up a dialog box', async () => {
    fireEvent.click(screen.getByTestId('collectionButton'));
    await waitFor(() => {
      expect(screen.getByText('Add contact to collection')).toBeInTheDocument();
      expect(screen.getAllByText('Search')[1]).toBeInTheDocument();
      fireEvent.click(screen.getAllByText('Search')[1]);
      const button = screen.getByText('Save');
      fireEvent.click(button);
    });
  });

  test('close add to collection popup on click of cancel button', async () => {
    fireEvent.click(screen.getByTestId('collectionButton'));
    await waitFor(() => {
      expect(screen.getByText('Add contact to collection')).toBeInTheDocument();
      const button = screen.getByText('Cancel');
      fireEvent.click(button);
    });
  });

  test('clicking on Start flow should open up a dialog box', async () => {
    fireEvent.click(screen.getByTestId('flowButton'));
    await waitFor(() => {
      expect(screen.getAllByText('Select flow')[0]).toBeInTheDocument();
    });
    await waitFor(() => {
      const button = screen.getByText('Start');
      fireEvent.click(button);
    });
  });

  test('close start a flow popup on click of cancel button', async () => {
    fireEvent.click(screen.getByTestId('flowButton'));
    await waitFor(() => {
      const button = screen.getByText('Cancel');
      fireEvent.click(button);
    });
  });

  test('check Chats option is present if screen size is less than 768', async () => {
    // // Change the viewport to 500px.
    global.innerWidth = 500;
    // // Trigger the window resize event.
    global.dispatchEvent(new Event('resize'));
    expect(screen.getByText('UnselectedDark.svg')).toBeInTheDocument();
  });

  test('clicking on clear chat button should open up a dialog box', async () => {
    fireEvent.click(screen.getByTestId('clearChatButton'));

    await waitFor(() => {
      expect(
        screen.getByText('Are you sure you want to clear all conversation for this contact?')
      ).toBeInTheDocument();
      // click on cancel
      fireEvent.click(screen.getByTestId('ok-button'));
    });
  });

  test('close clear conversation popup on click of cancel', async () => {
    fireEvent.click(screen.getByTestId('clearChatButton'));

    await waitFor(() => {
      expect(
        screen.getByText('Are you sure you want to clear all conversation for this contact?')
      ).toBeInTheDocument();
    });
    // click on cancel
    fireEvent.click(screen.getByTestId('cancel-button'));
  });

  test('clicking on block button should open up a dialog box', async () => {
    fireEvent.click(screen.getByTestId('blockButton'));

    await waitFor(() => {
      expect(screen.getByText('Do you want to block this contact')).toBeInTheDocument();
    });

    fireEvent.click(screen.getByText('Cancel'));
  });

  test('block a contact', async () => {
    fireEvent.click(screen.getByTestId('blockButton'));

    await waitFor(() => {
      expect(screen.getByText('Do you want to block this contact')).toBeInTheDocument();
    });
    fireEvent.click(screen.getByText('Confirm'));
  });

  test('view contact profile', async () => {
    fireEvent.click(screen.getByTestId('viewProfile'));
  });

  const componentWithBspStatusNone = (
    <MockedProvider mocks={mocks} addTypename={false}>
      <MemoryRouter>
        <ContactBar {...propsWithBspStatusNone} />
      </MemoryRouter>
    </MockedProvider>
  );

  test('select flow should be blocked when Bsp Status is none', async () => {
    cleanup();
    const { getByTestId } = render(componentWithBspStatusNone);
    await waitFor(() => {
      fireEvent.click(getByTestId('dropdownIcon')?.querySelector('svg'));
    });
    await waitFor(() => {
      expect(getByTestId('disabledFlowButton')).toBeInTheDocument();
    });
  });
});
Example #17
Source File: RecentList.spec.tsx    From che-dashboard-next with Eclipse Public License 2.0 4 votes vote down vote up
describe('Navigation Recent List', () => {

  const workspaces: che.Workspace[] = [
    {
      id: 'wksp-1',
      devfile: {
        metadata: {
          name: 'wksp-1'
        }
      },
      attributes: {
        updated: 1,
      } as any,
    } as che.Workspace,
    {
      id: 'wksp-2',
      devfile: {
        metadata: {
          name: 'wksp-2'
        }
      },
      attributes: {
        updated: 2,
      } as any,
    } as che.Workspace,
    {
      id: 'wksp-3',
      devfile: {
        metadata: {
          name: 'wksp-3'
        }
      },
      attributes: {
        updated: 3,
      } as any,
    } as che.Workspace,
  ];

  function renderComponent(store: Store, workspaces: che.Workspace[]): RenderResult {
    return render(
      <Provider store={store}>
        <MemoryRouter>
          <Nav
            onSelect={() => jest.fn()}
            theme="light"
          >
            <NavigationRecentList workspaces={workspaces} activePath="" />
          </Nav>
        </MemoryRouter>
      </Provider>
    );
  }

  it('should have correct number of main navigation items', () => {
    const store = createFakeStore(workspaces);
    renderComponent(store, workspaces);

    const navLinks = screen.getAllByRole('link');
    expect(navLinks.length).toEqual(workspaces.length + 1);
  });

  it('should have correct navigation item labels', () => {
    const store = createFakeStore(workspaces);
    renderComponent(store, workspaces);

    const navLinks = screen.getAllByRole('link');

    expect(navLinks[0]).toHaveTextContent('Create Workspace');
    expect(navLinks[1]).toHaveTextContent('wksp-1');
    expect(navLinks[2]).toHaveTextContent('wksp-2');
    expect(navLinks[3]).toHaveTextContent('wksp-3');
  });

  it('should correctly handle workspaces order', () => {
    const store = createFakeStore(workspaces);
    const { rerender } = renderComponent(store, workspaces);

    // change workspaces order
    [workspaces[0], workspaces[2]] = [workspaces[2], workspaces[0]];
    rerender(
      <Provider store={store}>
        <MemoryRouter>
          <Nav
            onSelect={() => jest.fn()}
            theme="light"
          >
            <NavigationRecentList workspaces={workspaces} activePath="" />
          </Nav>
        </MemoryRouter>
      </Provider>
    );

    const navLinks = screen.getAllByRole('link');

    expect(navLinks[0]).toHaveTextContent('Create Workspace');
    expect(navLinks[1]).toHaveTextContent('wksp-3');
    expect(navLinks[2]).toHaveTextContent('wksp-2');
    expect(navLinks[3]).toHaveTextContent('wksp-1');
  });

});
Example #18
Source File: appWrappers.tsx    From backstage with Apache License 2.0 4 votes vote down vote up
/**
 * Creates a Wrapper component that wraps a component inside a Backstage test app,
 * providing a mocked theme and app context, along with mocked APIs.
 *
 * @param options - Additional options for the rendering.
 * @public
 */
export function createTestAppWrapper(
  options: TestAppOptions = {},
): (props: { children: ReactNode }) => JSX.Element {
  const { routeEntries = ['/'] } = options;
  const boundRoutes = new Map<ExternalRouteRef, RouteRef>();

  const app = createSpecializedApp({
    apis: mockApis,
    defaultApis,
    // Bit of a hack to make sure that the default config loader isn't used
    // as that would force every single test to wait for config loading.
    configLoader: false as unknown as undefined,
    components: {
      Progress,
      BootErrorPage,
      NotFoundErrorPage,
      ErrorBoundaryFallback,
      Router: ({ children }) => (
        <MemoryRouter initialEntries={routeEntries} children={children} />
      ),
    },
    icons: mockIcons,
    plugins: [],
    themes: [
      {
        id: 'light',
        title: 'Test App Theme',
        variant: 'light',
        Provider: ({ children }) => (
          <ThemeProvider theme={lightTheme}>
            <CssBaseline>{children}</CssBaseline>
          </ThemeProvider>
        ),
      },
    ],
    bindRoutes: ({ bind }) => {
      for (const [externalRef, absoluteRef] of boundRoutes) {
        bind(
          { ref: externalRef },
          {
            ref: absoluteRef,
          },
        );
      }
    },
  });

  const routeElements = Object.entries(options.mountedRoutes ?? {}).map(
    ([path, routeRef]) => {
      const Page = () => <div>Mounted at {path}</div>;

      // Allow external route refs to be bound to paths as well, for convenience.
      // We work around it by creating and binding an absolute ref to the external one.
      if (isExternalRouteRef(routeRef)) {
        const absoluteRef = createRouteRef({ id: 'id' });
        boundRoutes.set(routeRef, absoluteRef);
        attachComponentData(Page, 'core.mountPoint', absoluteRef);
      } else {
        attachComponentData(Page, 'core.mountPoint', routeRef);
      }
      return <Route key={path} path={path} element={<Page />} />;
    },
  );

  const AppProvider = app.getProvider();
  const AppRouter = app.getRouter();

  const TestAppWrapper = ({ children }: { children: ReactNode }) => (
    <AppProvider>
      <AppRouter>
        <NoRender>{routeElements}</NoRender>
        {/* The path of * here is needed to be set as a catch all, so it will render the wrapper element
         *  and work with nested routes if they exist too */}
        <Routes>
          <Route path="/*" element={<>{children}</>} />
        </Routes>
      </AppRouter>
    </AppProvider>
  );

  return TestAppWrapper;
}
Example #19
Source File: SideNav.test.tsx    From kfp-tekton-backend with Apache License 2.0 4 votes vote down vote up
describe('SideNav', () => {
  let tree: ReactWrapper | ShallowWrapper;

  const consoleErrorSpy = jest.spyOn(console, 'error');
  const buildInfoSpy = jest.spyOn(Apis, 'getBuildInfo');
  const checkHubSpy = jest.spyOn(Apis, 'isJupyterHubAvailable');
  const clusterNameSpy = jest.spyOn(Apis, 'getClusterName');
  const projectIdSpy = jest.spyOn(Apis, 'getProjectId');
  const localStorageHasKeySpy = jest.spyOn(LocalStorage, 'hasKey');
  const localStorageIsCollapsedSpy = jest.spyOn(LocalStorage, 'isNavbarCollapsed');

  beforeEach(() => {
    jest.clearAllMocks();

    consoleErrorSpy.mockImplementation(() => null);

    buildInfoSpy.mockImplementation(() => ({
      apiServerCommitHash: 'd3c4add0a95e930c70a330466d0923827784eb9a',
      apiServerReady: true,
      buildDate: 'Wed Jan 9 19:40:24 UTC 2019',
      frontendCommitHash: '8efb2fcff9f666ba5b101647e909dc9c6889cecb',
    }));
    checkHubSpy.mockImplementation(() => ({ ok: true }));
    clusterNameSpy.mockImplementation(() => Promise.reject('Error when fetching cluster name'));
    projectIdSpy.mockImplementation(() => Promise.reject('Error when fetching project ID'));

    localStorageHasKeySpy.mockImplementation(() => false);
    localStorageIsCollapsedSpy.mockImplementation(() => false);
  });

  afterEach(async () => {
    // unmount() should be called before resetAllMocks() in case any part of the unmount life cycle
    // depends on mocks/spies
    await tree.unmount();
    jest.resetAllMocks();
    (window as any).innerWidth = wideWidth;
  });

  it('renders expanded state', () => {
    localStorageHasKeySpy.mockImplementationOnce(() => false);
    (window as any).innerWidth = wideWidth;
    tree = shallow(<SideNav page={RoutePage.PIPELINES} {...defaultProps} />);
    expect(tree).toMatchSnapshot();
  });

  it('renders collapsed state', () => {
    localStorageHasKeySpy.mockImplementationOnce(() => false);
    (window as any).innerWidth = narrowWidth;
    tree = shallow(<SideNav page={RoutePage.PIPELINES} {...defaultProps} />);
    expect(tree).toMatchSnapshot();
  });

  it('renders Pipelines as active page', () => {
    tree = shallow(<SideNav page={RoutePage.PIPELINES} {...defaultProps} />);
    expect(tree).toMatchSnapshot();
  });

  it('renders Pipelines as active when on PipelineDetails page', () => {
    tree = shallow(<SideNav page={RoutePage.PIPELINE_DETAILS} {...defaultProps} />);
    expect(tree).toMatchSnapshot();
  });

  it('renders experiments as active page', () => {
    tree = shallow(<SideNav page={RoutePage.EXPERIMENTS} {...defaultProps} />);
    expect(tree).toMatchSnapshot();
  });

  it('renders experiments as active when on ExperimentDetails page', () => {
    tree = shallow(<SideNav page={RoutePage.EXPERIMENT_DETAILS} {...defaultProps} />);
    expect(tree).toMatchSnapshot();
  });

  it('renders experiments as active page when on NewExperiment page', () => {
    tree = shallow(<SideNav page={RoutePage.NEW_EXPERIMENT} {...defaultProps} />);
    expect(tree).toMatchSnapshot();
  });

  it('renders experiments as active page when on Compare page', () => {
    tree = shallow(<SideNav page={RoutePage.COMPARE} {...defaultProps} />);
    expect(tree).toMatchSnapshot();
  });

  it('renders experiments as active page when on AllRuns page', () => {
    tree = shallow(<SideNav page={RoutePage.RUNS} {...defaultProps} />);
    expect(tree).toMatchSnapshot();
  });

  it('renders experiments as active page when on RunDetails page', () => {
    tree = shallow(<SideNav page={RoutePage.RUN_DETAILS} {...defaultProps} />);
    expect(tree).toMatchSnapshot();
  });

  it('renders experiments as active page when on RecurringRunDetails page', () => {
    tree = shallow(<SideNav page={RoutePage.RECURRING_RUN} {...defaultProps} />);
    expect(tree).toMatchSnapshot();
  });

  it('renders experiments as active page when on NewRun page', () => {
    tree = shallow(<SideNav page={RoutePage.NEW_RUN} {...defaultProps} />);
    expect(tree).toMatchSnapshot();
  });

  it('show jupyterhub link if accessible', () => {
    tree = shallow(<SideNav page={RoutePage.COMPARE} {...defaultProps} />);
    tree.setState({ jupyterHubAvailable: true });
    expect(tree).toMatchSnapshot();
  });

  it('collapses if collapse state is true localStorage', () => {
    localStorageIsCollapsedSpy.mockImplementationOnce(() => true);
    localStorageHasKeySpy.mockImplementationOnce(() => true);

    (window as any).innerWidth = wideWidth;
    tree = shallow(<SideNav page={RoutePage.COMPARE} {...defaultProps} />);
    expect(isCollapsed(tree)).toBe(true);
  });

  it('expands if collapse state is false in localStorage', () => {
    localStorageIsCollapsedSpy.mockImplementationOnce(() => false);
    localStorageHasKeySpy.mockImplementationOnce(() => true);

    tree = shallow(<SideNav page={RoutePage.COMPARE} {...defaultProps} />);
    expect(isCollapsed(tree)).toBe(false);
  });

  it('collapses if no collapse state in localStorage, and window is too narrow', () => {
    localStorageIsCollapsedSpy.mockImplementationOnce(() => false);
    localStorageHasKeySpy.mockImplementationOnce(() => false);

    (window as any).innerWidth = narrowWidth;
    tree = shallow(<SideNav page={RoutePage.COMPARE} {...defaultProps} />);
    expect(isCollapsed(tree)).toBe(true);
  });

  it('expands if no collapse state in localStorage, and window is wide', () => {
    localStorageIsCollapsedSpy.mockImplementationOnce(() => false);
    localStorageHasKeySpy.mockImplementationOnce(() => false);

    (window as any).innerWidth = wideWidth;
    tree = shallow(<SideNav page={RoutePage.COMPARE} {...defaultProps} />);
    expect(isCollapsed(tree)).toBe(false);
  });

  it('collapses if no collapse state in localStorage, and window goes from wide to narrow', () => {
    localStorageIsCollapsedSpy.mockImplementationOnce(() => false);
    localStorageHasKeySpy.mockImplementationOnce(() => false);

    (window as any).innerWidth = wideWidth;
    tree = shallow(<SideNav page={RoutePage.COMPARE} {...defaultProps} />);
    expect(isCollapsed(tree)).toBe(false);

    (window as any).innerWidth = narrowWidth;
    const resizeEvent = new Event('resize');
    window.dispatchEvent(resizeEvent);
    expect(isCollapsed(tree)).toBe(true);
  });

  it('expands if no collapse state in localStorage, and window goes from narrow to wide', () => {
    localStorageIsCollapsedSpy.mockImplementationOnce(() => false);
    localStorageHasKeySpy.mockImplementationOnce(() => false);

    (window as any).innerWidth = narrowWidth;
    tree = shallow(<SideNav page={RoutePage.COMPARE} {...defaultProps} />);
    expect(isCollapsed(tree)).toBe(true);

    (window as any).innerWidth = wideWidth;
    const resizeEvent = new Event('resize');
    window.dispatchEvent(resizeEvent);
    expect(isCollapsed(tree)).toBe(false);
  });

  it('saves state in localStorage if chevron is clicked', () => {
    localStorageIsCollapsedSpy.mockImplementationOnce(() => false);
    localStorageHasKeySpy.mockImplementationOnce(() => false);
    const spy = jest.spyOn(LocalStorage, 'saveNavbarCollapsed');

    (window as any).innerWidth = narrowWidth;
    tree = shallow(<SideNav page={RoutePage.COMPARE} {...defaultProps} />);
    expect(isCollapsed(tree)).toBe(true);

    tree.find('WithStyles(IconButton)').simulate('click');
    expect(spy).toHaveBeenCalledWith(false);
  });

  it('does not collapse if collapse state is saved in localStorage, and window resizes', () => {
    localStorageIsCollapsedSpy.mockImplementationOnce(() => false);
    localStorageHasKeySpy.mockImplementationOnce(() => true);

    (window as any).innerWidth = wideWidth;
    tree = shallow(<SideNav page={RoutePage.COMPARE} {...defaultProps} />);
    expect(isCollapsed(tree)).toBe(false);

    (window as any).innerWidth = narrowWidth;
    const resizeEvent = new Event('resize');
    window.dispatchEvent(resizeEvent);
    expect(isCollapsed(tree)).toBe(false);
  });

  it('populates the display build information using the response from the healthz endpoint', async () => {
    const buildInfo = {
      apiServerCommitHash: '0a7b9e38f2b9bcdef4bbf3234d971e1635b50cd5',
      apiServerTagName: '1.0.0',
      apiServerReady: true,
      buildDate: 'Tue Oct 23 14:23:53 UTC 2018',
      frontendCommitHash: '302e93ce99099173f387c7e0635476fe1b69ea98',
      frontendTagName: '1.0.0-rc1',
    };
    buildInfoSpy.mockImplementationOnce(() => buildInfo);

    tree = shallow(<SideNav page={RoutePage.PIPELINES} {...defaultProps} />);
    await TestUtils.flushPromises();
    expect(tree).toMatchSnapshot();

    expect(tree.state('displayBuildInfo')).toEqual({
      tagName: buildInfo.apiServerTagName,
      commitHash: buildInfo.apiServerCommitHash.substring(0, 7),
      commitUrl:
        'https://www.github.com/kubeflow/pipelines/commit/' + buildInfo.apiServerCommitHash,
      date: new Date(buildInfo.buildDate).toLocaleDateString(),
    });
  });

  it('populates the cluster information from context', async () => {
    const clusterName = 'some-cluster-name';
    const projectId = 'some-project-id';

    clusterNameSpy.mockImplementationOnce(() => Promise.resolve(clusterName));
    projectIdSpy.mockImplementationOnce(() => Promise.resolve(projectId));
    buildInfoSpy.mockImplementationOnce(() => Promise.reject('Error when fetching build info'));

    tree = mount(
      <GkeMetadataProvider>
        <MemoryRouter>
          <EnhancedSideNav page={RoutePage.PIPELINES} {...routerProps} />
        </MemoryRouter>
      </GkeMetadataProvider>,
    );
    const base = tree.html();
    await TestUtils.flushPromises();
    expect(
      diffHTML({
        base,
        baseAnnotation: 'base',
        update: tree.html(),
        updateAnnotation: 'after GKE metadata loaded',
      }),
    ).toMatchInlineSnapshot(`
      Snapshot Diff:
      - base
      + after GKE metadata loaded

      @@ --- --- @@
                  <path fill="none" d="M0 0h24v24H0z"></path></svg></span
              ><span class="MuiTouchRipple-root-53"></span>
            </button>
          </div>
          <div class="infoVisible">
      +     <div
      +       class="envMetadata"
      +       title="Cluster name: some-cluster-name, Project ID: some-project-id"
      +     >
      +       <span>Cluster name: </span
      +       ><a
      +         href="https://console.cloud.google.com/kubernetes/list?project=some-project-id&amp;filter=name:some-cluster-name"
      +         class="link unstyled"
      +         rel="noopener"
      +         target="_blank"
      +         >some-cluster-name</a
      +       >
      +     </div>
            <div class="envMetadata" title="Report an Issue">
              <a
                href="https://github.com/kubeflow/pipelines/issues/new?template=BUG_REPORT.md"
                class="link unstyled"
                rel="noopener"
    `);
  });

  it('displays the frontend tag name if the api server hash is not returned', async () => {
    const buildInfo = {
      apiServerReady: true,
      // No apiServerCommitHash or apiServerTagName
      buildDate: 'Tue Oct 23 14:23:53 UTC 2018',
      frontendCommitHash: '302e93ce99099173f387c7e0635476fe1b69ea98',
      frontendTagName: '1.0.0',
    };
    buildInfoSpy.mockImplementationOnce(() => buildInfo);

    tree = shallow(<SideNav page={RoutePage.PIPELINES} {...defaultProps} />);
    await TestUtils.flushPromises();

    expect(tree.state('displayBuildInfo')).toEqual(
      expect.objectContaining({
        commitHash: buildInfo.frontendCommitHash.substring(0, 7),
        tagName: buildInfo.frontendTagName,
      }),
    );
  });

  it('uses the frontend commit hash for the link URL if the api server hash is not returned', async () => {
    const buildInfo = {
      apiServerReady: true,
      // No apiServerCommitHash
      buildDate: 'Tue Oct 23 14:23:53 UTC 2018',
      frontendCommitHash: '302e93ce99099173f387c7e0635476fe1b69ea98',
    };
    buildInfoSpy.mockImplementationOnce(() => buildInfo);

    tree = shallow(<SideNav page={RoutePage.PIPELINES} {...defaultProps} />);
    await TestUtils.flushPromises();

    expect(tree.state('displayBuildInfo')).toEqual(
      expect.objectContaining({
        commitUrl:
          'https://www.github.com/kubeflow/pipelines/commit/' + buildInfo.frontendCommitHash,
      }),
    );
  });

  it("displays 'unknown' if the frontend and api server tag names/commit hashes are not returned", async () => {
    const buildInfo = {
      apiServerReady: true,
      // No apiServerCommitHash
      buildDate: 'Tue Oct 23 14:23:53 UTC 2018',
      // No frontendCommitHash
    };
    buildInfoSpy.mockImplementationOnce(() => buildInfo);

    tree = shallow(<SideNav page={RoutePage.PIPELINES} {...defaultProps} />);
    await TestUtils.flushPromises();

    expect(tree.state('displayBuildInfo')).toEqual(
      expect.objectContaining({
        commitHash: 'unknown',
        tagName: 'unknown',
      }),
    );
  });

  it('links to the github repo root if the frontend and api server commit hashes are not returned', async () => {
    const buildInfo = {
      apiServerReady: true,
      // No apiServerCommitHash
      buildDate: 'Tue Oct 23 14:23:53 UTC 2018',
      // No frontendCommitHash
    };
    buildInfoSpy.mockImplementationOnce(() => buildInfo);

    tree = shallow(<SideNav page={RoutePage.PIPELINES} {...defaultProps} />);
    await TestUtils.flushPromises();

    expect(tree.state('displayBuildInfo')).toEqual(
      expect.objectContaining({
        commitUrl: 'https://www.github.com/kubeflow/pipelines',
      }),
    );
  });

  it("displays 'unknown' if the date is not returned", async () => {
    const buildInfo = {
      apiServerCommitHash: '0a7b9e38f2b9bcdef4bbf3234d971e1635b50cd5',
      apiServerReady: true,
      // No buildDate
      frontendCommitHash: '302e93ce99099173f387c7e0635476fe1b69ea98',
    };
    buildInfoSpy.mockImplementationOnce(() => buildInfo);

    tree = shallow(<SideNav page={RoutePage.PIPELINES} {...defaultProps} />);
    await TestUtils.flushPromises();

    expect(tree.state('displayBuildInfo')).toEqual(
      expect.objectContaining({
        date: 'unknown',
      }),
    );
  });

  it('logs an error if the call getBuildInfo fails', async () => {
    TestUtils.makeErrorResponseOnce(buildInfoSpy, 'Uh oh!');

    tree = shallow(<SideNav page={RoutePage.PIPELINES} {...defaultProps} />);
    await TestUtils.flushPromises();

    expect(tree.state('displayBuildInfo')).toBeUndefined();
    expect(consoleErrorSpy.mock.calls[0][0]).toBe('Failed to retrieve build info');
  });
});