react-router#Routes TypeScript Examples

The following examples show how to use react-router#Routes. 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: Router.tsx    From backstage with Apache License 2.0 6 votes vote down vote up
Router = () => {
  const { entity } = useEntity();

  if (!isGithubActionsAvailable(entity)) {
    return (
      <MissingAnnotationEmptyState annotation={GITHUB_ACTIONS_ANNOTATION} />
    );
  }
  return (
    <Routes>
      <Route path="/" element={<WorkflowRunsTable entity={entity} />} />
      <Route
        path={`${buildRouteRef.path}`}
        element={<WorkflowRunDetails entity={entity} />}
      />
      )
    </Routes>
  );
}
Example #2
Source File: App.tsx    From Riakuto-StartingReact-ja3.1 with Apache License 2.0 6 votes vote down vote up
App: VFC = () => (
  <>
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/:orgCode/members" element={<Members />} />
      <Route path="*" element={<Navigate to="/" />} />
    </Routes>
  </>
)
Example #3
Source File: App.tsx    From Riakuto-StartingReact-ja3.1 with Apache License 2.0 6 votes vote down vote up
App: VFC = () => {
  const { hash, pathname } = useLocation();

  useEffect(() => {
    if (!hash) window.scrollTo(0, 0);
  }, [hash, pathname]);

  return (
    <div className="container">
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="characters" element={<Characters />}>
          <Route path="" element={<AllCharacters />} />
          <Route path=":schoolCode" element={<SchoolCharacters />} />
        </Route>
        <Route path="*" element={<Navigate to="/" replace />} />
      </Routes>
    </div>
  );
}
Example #4
Source File: SettingsPage.tsx    From lightning-terminal with MIT License 6 votes vote down vote up
SettingsPage: React.FC = () => {
  const { Wrapper } = Styled;

  return (
    <Wrapper>
      <Routes>
        <Route path="/" element={<GeneralSettings />} />
        <Route path="unit" element={<UnitSettings />} />
        <Route path="balance" element={<BalanceSettings />} />
        <Route path="explorers" element={<ExplorerSettings />} />
      </Routes>
    </Wrapper>
  );
}
Example #5
Source File: App.tsx    From axios-source-analysis with MIT License 6 votes vote down vote up
function App() {
  debugger
  return (
    <Routes>
      {/* 注意,这里不是LayoutRoute,因为LayoutRoute只允许element和children,而这里有path */}
      <Route path='/' element={<Layout />}>
        {
          routeConfigs.map(({ path, Element }) => <Route key={path} path={`${path}${path === '*' ? '': '/*'}`} element={<Element />} />)
        }
      </Route>
    </Routes>
  )
}
Example #6
Source File: Router.tsx    From backstage with Apache License 2.0 6 votes vote down vote up
Router = ({ entity }: { entity: Entity }) => {
  return (
    <Routes>
      <Route
        path="/"
        element={
          <SentryIssuesWidget
            entity={entity}
            statsFor="24h"
            tableOptions={{
              padding: 'dense',
              paging: true,
              search: false,
              pageSize: 5,
            }}
          />
        }
      />
      )
    </Routes>
  );
}
Example #7
Source File: Router.tsx    From backstage with Apache License 2.0 6 votes vote down vote up
Router = (props: RouterProps) => {
  const { groups, components = {}, defaultPreviewTemplate } = props;

  const { TemplateCardComponent, TaskPageComponent } = components;

  const outlet = useOutlet();
  const TaskPageElement = TaskPageComponent ?? TaskPage;

  const customFieldExtensions = useElementFilter(outlet, elements =>
    elements
      .selectByComponentData({
        key: FIELD_EXTENSION_WRAPPER_KEY,
      })
      .findComponentData<FieldExtensionOptions>({
        key: FIELD_EXTENSION_KEY,
      }),
  );

  const fieldExtensions = [
    ...customFieldExtensions,
    ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(
      ({ name }) =>
        !customFieldExtensions.some(
          customFieldExtension => customFieldExtension.name === name,
        ),
    ),
  ];

  return (
    <Routes>
      <Route
        element={
          <ScaffolderPage
            groups={groups}
            TemplateCardComponent={TemplateCardComponent}
            contextMenu={props.contextMenu}
          />
        }
      />
      <Route
        path={selectedTemplateRouteRef.path}
        element={
          <SecretsContextProvider>
            <TemplatePage customFieldExtensions={fieldExtensions} />
          </SecretsContextProvider>
        }
      />
      <Route
        path={scaffolderListTaskRouteRef.path}
        element={<ListTasksPage />}
      />
      <Route path={scaffolderTaskRouteRef.path} element={<TaskPageElement />} />
      <Route path={actionsRouteRef.path} element={<ActionsPage />} />
      <Route
        path={editRouteRef.path}
        element={
          <SecretsContextProvider>
            <TemplateEditorPage
              defaultPreviewTemplate={defaultPreviewTemplate}
              customFieldExtensions={fieldExtensions}
            />
          </SecretsContextProvider>
        }
      />

      <Route path="preview" element={<Navigate to="../edit" />} />
    </Routes>
  );
}
Example #8
Source File: Router.tsx    From backstage with Apache License 2.0 6 votes vote down vote up
Router = (_props: Props) => {
  const { entity } = useEntity();

  if (!isPluginApplicableToEntity(entity)) {
    <MissingAnnotationEmptyState annotation={ROLLBAR_ANNOTATION} />;
  }

  return (
    <Routes>
      <Route path="/" element={<EntityPageRollbar />} />
    </Routes>
  );
}
Example #9
Source File: Router.tsx    From backstage with Apache License 2.0 6 votes vote down vote up
Router = (_props: Props) => {
  const { entity } = useEntity();

  if (!isPluginApplicableToEntity(entity)) {
    return (
      <MissingAnnotationEmptyState
        annotation={KAFKA_CONSUMER_GROUP_ANNOTATION}
      />
    );
  }

  return (
    <Routes>
      <Route path="/" element={<KafkaTopicsForConsumer />} />
    </Routes>
  );
}
Example #10
Source File: Router.tsx    From backstage with Apache License 2.0 6 votes vote down vote up
Router = (_props: Props) => {
  const { entity } = useEntity();

  if (!isJenkinsAvailable(entity)) {
    return <MissingAnnotationEmptyState annotation={JENKINS_ANNOTATION} />;
  }

  return (
    <Routes>
      <Route path="/" element={<CITable />} />
      <Route path={`/${buildRouteRef.path}`} element={<DetailedViewPage />} />
    </Routes>
  );
}
Example #11
Source File: Router.tsx    From backstage with Apache License 2.0 6 votes vote down vote up
Router = () => {
  const { entity } = useEntity();

  if (!isCloudbuildAvailable(entity)) {
    // TODO(shmidt-i): move warning to a separate standardized component
    return <MissingAnnotationEmptyState annotation={CLOUDBUILD_ANNOTATION} />;
  }
  return (
    <Routes>
      <Route path="/" element={<WorkflowRunsTable entity={entity} />} />
      <Route
        path={`${buildRouteRef.path}`}
        element={<WorkflowRunDetails entity={entity} />}
      />
    </Routes>
  );
}
Example #12
Source File: Router.tsx    From backstage with Apache License 2.0 6 votes vote down vote up
Router = () => {
  const { entity } = useEntity();

  if (!isCircleCIAvailable(entity)) {
    return <MissingAnnotationEmptyState annotation={CIRCLECI_ANNOTATION} />;
  }

  return (
    <Routes>
      <Route path="/" element={<BuildsPage />} />
      <Route
        path={`${circleCIBuildRouteRef.path}`}
        element={<BuildWithStepsPage />}
      />
    </Routes>
  );
}
Example #13
Source File: Button.test.tsx    From backstage with Apache License 2.0 6 votes vote down vote up
describe('<Button />', () => {
  it('navigates using react-router', async () => {
    const testString = 'This is test string';
    const buttonLabel = 'Navigate!';
    const { getByText } = render(
      wrapInTestApp(
        <Routes>
          <Route path="/test" element={<p>{testString}</p>} />
          <Button to="/test">{buttonLabel}</Button>
        </Routes>,
      ),
    );

    expect(() => getByText(testString)).toThrow();
    await act(async () => {
      fireEvent.click(getByText(buttonLabel));
    });
    expect(getByText(testString)).toBeInTheDocument();
  });
});
Example #14
Source File: PlaygroundLayout.tsx    From atlas with GNU General Public License v3.0 5 votes vote down vote up
PlaygroundLayout = () => {
  const [isMemberDropdownActive, setIsMemberDropdownActive] = useState(false)
  const { activeMembership, activeAccountId, activeMemberId, extensionConnected, signIn } = useUser()
  const isLoggedIn = activeAccountId && !!activeMemberId && !!extensionConnected
  const { url: memberAvatarUrl, isLoadingAsset: memberAvatarLoading } = useMemberAvatar(activeMembership)
  return (
    <ActiveUserProvider>
      <TopbarBase
        fullLogoNode={
          <LogoWrapper>
            <SvgJoystreamLogoShort />
            <Text variant="h500" margin={{ left: 2 }}>
              Playground
            </Text>
          </LogoWrapper>
        }
        logoLinkUrl={absoluteRoutes.playground.index()}
      >
        <ButtonContainer>
          <Button variant="secondary" to={absoluteRoutes.viewer.index()}>
            Go to viewer
          </Button>
          <Button variant="secondary" to={absoluteRoutes.studio.index()}>
            Go to studio
          </Button>
          {isLoggedIn ? (
            <Avatar
              size="small"
              assetUrl={memberAvatarUrl}
              loading={memberAvatarLoading}
              onClick={() => setIsMemberDropdownActive(true)}
            />
          ) : (
            <Button onClick={signIn}>Sign in</Button>
          )}
        </ButtonContainer>
      </TopbarBase>
      <MemberDropdown isActive={isMemberDropdownActive} closeDropdown={() => setIsMemberDropdownActive(false)} />
      <ConfirmationModalProvider>
        <Container>
          <NavContainer>
            {playgroundRoutes.map((route) => (
              <Link key={route.path} to={`/playground/${route.path}`}>
                {route.name}
              </Link>
            ))}
          </NavContainer>
          <ContentContainer>
            <Routes>
              {playgroundRoutes.map((route) => (
                <Route key={route.path} path={route.path} element={route.element} />
              ))}
            </Routes>
          </ContentContainer>
        </Container>
        <ConnectionStatusManager />
      </ConfirmationModalProvider>
    </ActiveUserProvider>
  )
}
Example #15
Source File: TabbedLayout.test.tsx    From backstage with Apache License 2.0 5 votes vote down vote up
describe('TabbedLayout', () => {
  it('renders simplest case', async () => {
    const { getByText } = await renderInTestApp(
      <TabbedLayout>
        <TabbedLayout.Route path="/" title="tabbed-test-title">
          <div>tabbed-test-content</div>
        </TabbedLayout.Route>
      </TabbedLayout>,
    );

    expect(getByText('tabbed-test-title')).toBeInTheDocument();
    expect(getByText('tabbed-test-content')).toBeInTheDocument();
  });

  it('throws if any other component is a child of TabbedLayout', async () => {
    const { error } = await withLogCollector(async () => {
      await expect(
        renderInTestApp(
          <TabbedLayout>
            <TabbedLayout.Route path="/" title="tabbed-test-title">
              <div>tabbed-test-content</div>
            </TabbedLayout.Route>
            <div>This will cause app to throw</div>
          </TabbedLayout>,
        ),
      ).rejects.toThrow(/Child of TabbedLayout must be an TabbedLayout.Route/);
    });

    expect(error).toEqual([
      expect.stringMatching(
        /Child of TabbedLayout must be an TabbedLayout.Route/,
      ),
      expect.stringMatching(
        /The above error occurred in the <TabbedLayout> component/,
      ),
    ]);
  });

  it('navigates when user clicks different tab', async () => {
    const { getByText, queryByText, queryAllByRole } = await renderInTestApp(
      <Routes>
        <Route
          path="/*"
          element={
            <TabbedLayout>
              <TabbedLayout.Route path="/" title="tabbed-test-title">
                <div>tabbed-test-content</div>
              </TabbedLayout.Route>
              <TabbedLayout.Route
                path="/some-other-path"
                title="tabbed-test-title-2"
              >
                <div>tabbed-test-content-2</div>
              </TabbedLayout.Route>
            </TabbedLayout>
          }
        />
      </Routes>,
    );

    const secondTab = queryAllByRole('tab')[1];
    act(() => {
      fireEvent.click(secondTab);
    });

    expect(getByText('tabbed-test-title')).toBeInTheDocument();
    expect(queryByText('tabbed-test-content')).not.toBeInTheDocument();

    expect(getByText('tabbed-test-title-2')).toBeInTheDocument();
    expect(queryByText('tabbed-test-content-2')).toBeInTheDocument();
  });
});
Example #16
Source File: Router.tsx    From backstage with Apache License 2.0 5 votes vote down vote up
Router = (props: PropsWithChildren<NextRouterProps>) => {
  const { components: { TemplateCardComponent } = {} } = props;

  const outlet = useOutlet() || props.children;

  const customFieldExtensions = useElementFilter(outlet, elements =>
    elements
      .selectByComponentData({
        key: FIELD_EXTENSION_WRAPPER_KEY,
      })
      .findComponentData<FieldExtensionOptions>({
        key: FIELD_EXTENSION_KEY,
      }),
  );

  const fieldExtensions = [
    ...customFieldExtensions,
    ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(
      ({ name }) =>
        !customFieldExtensions.some(
          customFieldExtension => customFieldExtension.name === name,
        ),
    ),
  ];

  return (
    <Routes>
      <Route
        element={
          <TemplateListPage
            TemplateCardComponent={TemplateCardComponent}
            groups={props.groups}
          />
        }
      />

      <Route
        path={selectedTemplateRouteRef.path}
        element={
          <SecretsContextProvider>
            <TemplateWizardPage customFieldExtensions={fieldExtensions} />
          </SecretsContextProvider>
        }
      />
    </Routes>
  );
}
Example #17
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 #18
Source File: app.tsx    From flood with GNU General Public License v3.0 5 votes vote down vote up
FloodApp: FC = observer(() => {
  useEffect(() => {
    UIStore.registerDependency([
      {
        id: 'notifications',
        message: {id: 'dependency.loading.notifications'},
      },
    ]);

    UIStore.registerDependency([
      {
        id: 'torrent-taxonomy',
        message: {id: 'dependency.loading.torrent.taxonomy'},
      },
    ]);

    UIStore.registerDependency([
      {
        id: 'transfer-data',
        message: {id: 'dependency.loading.transfer.rate.details'},
      },
      {
        id: 'transfer-history',
        message: {id: 'dependency.loading.transfer.history'},
      },
    ]);

    UIStore.registerDependency([
      {
        id: 'torrent-list',
        message: {id: 'dependency.loading.torrent.list'},
      },
    ]);
  }, []);

  const isSystemPreferDark = useMedia('(prefers-color-scheme: dark)');
  useEffect(() => {
    ConfigStore.setSystemPreferDark(isSystemPreferDark);
  }, [isSystemPreferDark]);

  // max-width here must sync with CSS
  const isSmallScreen = useMedia('(max-width: 720px)');
  useEffect(() => {
    ConfigStore.setSmallScreen(isSmallScreen);
  }, [isSmallScreen]);

  return (
    <Suspense fallback={<LoadingOverlay />}>
      <AsyncIntlProvider>
        <BrowserRouter basename={stringUtil.withoutTrailingSlash(ConfigStore.baseURI)}>
          <AppWrapper className={ConfigStore.isPreferDark ? 'dark' : undefined}>
            <Routes>
              <Route path="/login" element={<Login />} />
              <Route path="/overview" element={<Overview />} />
              <Route path="/register" element={<Register />} />
            </Routes>
          </AppWrapper>
        </BrowserRouter>
      </AsyncIntlProvider>
    </Suspense>
  );
})
Example #19
Source File: EntityLayout.test.tsx    From backstage with Apache License 2.0 4 votes vote down vote up
describe('EntityLayout', () => {
  it('renders simplest case', async () => {
    const rendered = await renderInTestApp(
      <ApiProvider apis={mockApis}>
        <EntityProvider entity={mockEntity}>
          <EntityLayout>
            <EntityLayout.Route path="/" title="tabbed-test-title">
              <div>tabbed-test-content</div>
            </EntityLayout.Route>
          </EntityLayout>
        </EntityProvider>
      </ApiProvider>,
      {
        mountedRoutes: {
          '/catalog/:namespace/:kind/:name': entityRouteRef,
        },
      },
    );

    expect(rendered.getByText('my-entity')).toBeInTheDocument();
    expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
    expect(rendered.getByText('tabbed-test-content')).toBeInTheDocument();
  });

  it('renders the entity title if defined', async () => {
    const mockEntityWithTitle = {
      kind: 'MyKind',
      metadata: {
        name: 'my-entity',
        title: 'My Entity',
      },
    } as Entity;

    const rendered = await renderInTestApp(
      <ApiProvider apis={mockApis}>
        <EntityProvider entity={mockEntityWithTitle}>
          <EntityLayout>
            <EntityLayout.Route path="/" title="tabbed-test-title">
              <div>tabbed-test-content</div>
            </EntityLayout.Route>
          </EntityLayout>
        </EntityProvider>
      </ApiProvider>,
      {
        mountedRoutes: {
          '/catalog/:namespace/:kind/:name': entityRouteRef,
        },
      },
    );

    expect(rendered.getByText('My Entity')).toBeInTheDocument();
    expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
    expect(rendered.getByText('tabbed-test-content')).toBeInTheDocument();
  });

  it('renders default error message when entity is not found', async () => {
    const rendered = await renderInTestApp(
      <ApiProvider apis={mockApis}>
        <AsyncEntityProvider loading={false}>
          <EntityLayout>
            <EntityLayout.Route path="/" title="tabbed-test-title">
              <div>tabbed-test-content</div>
            </EntityLayout.Route>
          </EntityLayout>
        </AsyncEntityProvider>
      </ApiProvider>,
      {
        mountedRoutes: {
          '/catalog/:namespace/:kind/:name': entityRouteRef,
        },
      },
    );

    expect(rendered.getByText('Warning: Entity not found')).toBeInTheDocument();
    expect(rendered.queryByText('my-entity')).not.toBeInTheDocument();
    expect(rendered.queryByText('tabbed-test-title')).not.toBeInTheDocument();
    expect(rendered.queryByText('tabbed-test-content')).not.toBeInTheDocument();
  });

  it('renders custom message when entity is not found', async () => {
    const rendered = await renderInTestApp(
      <ApiProvider apis={mockApis}>
        <AsyncEntityProvider loading={false}>
          <EntityLayout
            NotFoundComponent={<div>Oppps.. Your entity was not found</div>}
          >
            <EntityLayout.Route path="/" title="tabbed-test-title">
              <div>tabbed-test-content</div>
            </EntityLayout.Route>
          </EntityLayout>
        </AsyncEntityProvider>
      </ApiProvider>,
      {
        mountedRoutes: {
          '/catalog/:namespace/:kind/:name': entityRouteRef,
        },
      },
    );

    expect(
      rendered.getByText('Oppps.. Your entity was not found'),
    ).toBeInTheDocument();
    expect(rendered.queryByText('my-entity')).not.toBeInTheDocument();
    expect(rendered.queryByText('tabbed-test-title')).not.toBeInTheDocument();
    expect(rendered.queryByText('tabbed-test-content')).not.toBeInTheDocument();
  });

  it('navigates when user clicks different tab', async () => {
    const rendered = await renderInTestApp(
      <Routes>
        <Route
          path="/*"
          element={
            <ApiProvider apis={mockApis}>
              <EntityProvider entity={mockEntity}>
                <EntityLayout>
                  <EntityLayout.Route path="/" title="tabbed-test-title">
                    <div>tabbed-test-content</div>
                  </EntityLayout.Route>
                  <EntityLayout.Route
                    path="/some-other-path"
                    title="tabbed-test-title-2"
                  >
                    <div>tabbed-test-content-2</div>
                  </EntityLayout.Route>
                </EntityLayout>
              </EntityProvider>
            </ApiProvider>
          }
        />
      </Routes>,
      {
        mountedRoutes: {
          '/catalog/:namespace/:kind/:name': entityRouteRef,
        },
      },
    );

    const secondTab = rendered.queryAllByRole('tab')[1];
    act(() => {
      fireEvent.click(secondTab);
    });

    expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
    expect(rendered.queryByText('tabbed-test-content')).not.toBeInTheDocument();

    expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument();
    expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument();
  });

  it('should conditionally render tabs', async () => {
    const shouldRenderTab = (e: Entity) => e.metadata.name === 'my-entity';
    const shouldNotRenderTab = (e: Entity) => e.metadata.name === 'some-entity';

    const rendered = await renderInTestApp(
      <ApiProvider apis={mockApis}>
        <EntityProvider entity={mockEntity}>
          <EntityLayout>
            <EntityLayout.Route path="/" title="tabbed-test-title">
              <div>tabbed-test-content</div>
            </EntityLayout.Route>
            <EntityLayout.Route
              path="/some-other-path"
              title="tabbed-test-title-2"
              if={shouldNotRenderTab}
            >
              <div>tabbed-test-content-2</div>
            </EntityLayout.Route>
            <EntityLayout.Route
              path="/some-other-other-path"
              title="tabbed-test-title-3"
              if={shouldRenderTab}
            >
              <div>tabbed-test-content-3</div>
            </EntityLayout.Route>
          </EntityLayout>
        </EntityProvider>
      </ApiProvider>,
      {
        mountedRoutes: {
          '/catalog/:namespace/:kind/:name': entityRouteRef,
        },
      },
    );

    expect(rendered.queryByText('tabbed-test-title')).toBeInTheDocument();
    expect(rendered.queryByText('tabbed-test-title-2')).not.toBeInTheDocument();
    expect(rendered.queryByText('tabbed-test-title-3')).toBeInTheDocument();
  });
});
Example #20
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 #21
Source File: appWrappers.test.tsx    From backstage with Apache License 2.0 4 votes vote down vote up
describe('wrapInTestApp', () => {
  it('should provide routing and warn about missing act()', async () => {
    const { error } = await withLogCollector(['error'], async () => {
      const rendered = render(
        wrapInTestApp(
          <Routes>
            <Route path="/route1" element={<p>Route 1</p>} />
            <Route path="/route2" element={<p>Route 2</p>} />
          </Routes>,
          { routeEntries: ['/route2'] },
        ),
      );

      expect(rendered.getByText('Route 2')).toBeInTheDocument();
      // Wait for async actions to trigger the act() warnings that we assert below
      await Promise.resolve();
    });

    expect(
      error.some(e =>
        e.includes(
          'Warning: An update to %s inside a test was not wrapped in act(...)',
        ),
      ),
    ).toBeTruthy();
  });

  it('should render a component in a test app without warning about missing act()', async () => {
    const { error } = await withLogCollector(['error'], async () => {
      const Foo = () => {
        return <p>foo</p>;
      };

      const rendered = await renderInTestApp(Foo);
      expect(rendered.getByText('foo')).toBeInTheDocument();
    });

    expect(error).toEqual([]);
  });

  it('should render a node in a test app', async () => {
    const Foo = () => {
      return <p>foo</p>;
    };

    const rendered = await renderInTestApp(<Foo />);
    expect(rendered.getByText('foo')).toBeInTheDocument();
  });

  it('should provide mock API implementations', async () => {
    const A = () => {
      const errorApi = useApi(errorApiRef);
      errorApi.post(new Error('NOPE'));
      return null;
    };

    const { error } = await withLogCollector(['error'], async () => {
      await expect(renderInTestApp(A)).rejects.toThrow('NOPE');
    });

    expect(error).toEqual([
      expect.stringMatching(
        /^Error: Uncaught \[Error: MockErrorApi received unexpected error, Error: NOPE\]/,
      ),
      expect.stringMatching(/^The above error occurred in the <A> component:/),
    ]);
  });

  it('should allow custom API implementations', async () => {
    const mockErrorApi = new MockErrorApi({ collect: true });

    const A = () => {
      const errorApi = useApi(errorApiRef);
      useEffect(() => {
        errorApi.post(new Error('NOPE'));
      }, [errorApi]);
      return <p>foo</p>;
    };

    const rendered = await renderInTestApp(
      <TestApiProvider apis={[[errorApiRef, mockErrorApi]]}>
        <A />
      </TestApiProvider>,
    );

    expect(rendered.getByText('foo')).toBeInTheDocument();
    expect(mockErrorApi.getErrors()).toEqual([{ error: new Error('NOPE') }]);
  });

  it('should allow route refs to be mounted on specific paths', async () => {
    const aRouteRef = createRouteRef({ id: 'A' });
    const bRouteRef = createRouteRef({ id: 'B', params: ['name'] });
    const subRouteRef = createSubRouteRef({
      id: 'S',
      parent: bRouteRef,
      path: '/:page',
    });
    const externalRouteRef = createExternalRouteRef({
      id: 'E',
      params: ['name'],
    });

    const MyComponent = () => {
      const a = useRouteRef(aRouteRef);
      const b = useRouteRef(bRouteRef);
      const s = useRouteRef(subRouteRef);
      const e = useRouteRef(externalRouteRef);
      return (
        <div>
          <div>Link A: {a()}</div>
          <div>Link B: {b({ name: 'x' })}</div>
          <div>Link S: {s({ name: 'y', page: 'p' })}</div>
          <div>Link E: {e({ name: 'z' })}</div>
        </div>
      );
    };

    const rendered = await renderInTestApp(<MyComponent />, {
      mountedRoutes: {
        '/my-a-path': aRouteRef,
        '/my-b-path/:name': bRouteRef,
        '/my-e-path/:name': externalRouteRef,
      },
    });
    expect(rendered.getByText('Link A: /my-a-path')).toBeInTheDocument();
    expect(rendered.getByText('Link B: /my-b-path/x')).toBeInTheDocument();
    expect(rendered.getByText('Link S: /my-b-path/y/p')).toBeInTheDocument();
    expect(rendered.getByText('Link E: /my-e-path/z')).toBeInTheDocument();
  });

  it('should not make route mounting elements visible during tests', async () => {
    const routeRef = createRouteRef({ id: 'foo' });

    const rendered = await renderInTestApp(<span>foo</span>, {
      mountedRoutes: { '/foo': routeRef },
    });

    const [root] = rendered.baseElement.children;
    expect(root).toBeInTheDocument();
    expect(root.children.length).toBe(1);
    expect(root.children[0].textContent).toBe('foo');
  });

  it('should support rerenders', async () => {
    const MyComponent = () => {
      const app = useApp();
      const { Progress } = app.getComponents();
      return <Progress />;
    };

    const rendered = await renderInTestApp(<MyComponent />);
    expect(rendered.getByTestId('progress')).toBeInTheDocument();

    rendered.rerender(<MyComponent />);
    expect(rendered.getByTestId('progress')).toBeInTheDocument();
  });
});
Example #22
Source File: RoutedTabs.test.tsx    From backstage with Apache License 2.0 4 votes vote down vote up
describe('RoutedTabs', () => {
  it('renders simplest case', async () => {
    const rendered = await renderInTestApp(
      <RoutedTabs routes={[testRoute1]} />,
    );

    expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
    expect(rendered.getByText('tabbed-test-content')).toBeInTheDocument();
  });

  it('navigates when user clicks different tab', async () => {
    const rendered = await renderInTestApp(
      <Routes>
        <Route
          path="/*"
          element={<RoutedTabs routes={[testRoute1, testRoute2, testRoute3]} />}
        />
      </Routes>,
    );

    const secondTab = rendered.queryAllByRole('tab')[1];
    act(() => {
      fireEvent.click(secondTab);
    });

    expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
    expect(rendered.queryByText('tabbed-test-content')).not.toBeInTheDocument();

    expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument();
    expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument();

    const thirdTab = rendered.queryAllByRole('tab')[2];
    act(() => {
      fireEvent.click(thirdTab);
    });
    expect(rendered.getByText('tabbed-test-title-3')).toBeInTheDocument();
    expect(rendered.queryByText('tabbed-test-content-3')).toBeInTheDocument();
  });

  describe('correctly delegates nested links', () => {
    const renderRoute = (route: string) =>
      renderInTestApp(
        <Routes>
          <Route
            path="/*"
            element={
              <RoutedTabs
                routes={[
                  testRoute1,
                  {
                    ...testRoute2,
                    children: (
                      <div>
                        tabbed-test-content-2
                        <Routes>
                          <Route
                            path="/nested"
                            element={<div>tabbed-test-nested-content-2</div>}
                          />
                        </Routes>
                      </div>
                    ),
                  },
                ]}
              />
            }
          />
        </Routes>,
        { routeEntries: [route] },
      );

    it('works for nested content', async () => {
      const rendered = await renderRoute('/some-other-path/nested');

      expect(
        rendered.queryByText('tabbed-test-content'),
      ).not.toBeInTheDocument();
      expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument();
      expect(
        rendered.queryByText('tabbed-test-nested-content-2'),
      ).toBeInTheDocument();
    });

    it('works for non-nested content', async () => {
      const rendered = await renderRoute('/some-other-path/');

      expect(
        rendered.queryByText('tabbed-test-content'),
      ).not.toBeInTheDocument();
      expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument();
      expect(
        rendered.queryByText('tabbed-test-nested-content-2'),
      ).not.toBeInTheDocument();
    });
  });

  it('shows only one tab contents at a time', async () => {
    const rendered = await renderInTestApp(
      <RoutedTabs routes={[testRoute1, testRoute2]} />,
      { routeEntries: ['/some-other-path'] },
    );

    expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
    expect(rendered.queryByText('tabbed-test-content')).not.toBeInTheDocument();

    expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument();
    expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument();
  });

  it('redirects to the top level when no route is matching the url', async () => {
    const rendered = await renderInTestApp(
      <RoutedTabs routes={[testRoute1, testRoute2]} />,
      { routeEntries: ['/non-existing-path'] },
    );

    expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
    expect(rendered.getByText('tabbed-test-content')).toBeInTheDocument();
    expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument();

    expect(
      rendered.queryByText('tabbed-test-content-2'),
    ).not.toBeInTheDocument();
  });
});
Example #23
Source File: Link.test.tsx    From backstage with Apache License 2.0 4 votes vote down vote up
describe('<Link />', () => {
  it('navigates using react-router', async () => {
    const testString = 'This is test string';
    const linkText = 'Navigate!';
    const { getByText } = render(
      wrapInTestApp(
        <Routes>
          <Link to="/test">{linkText}</Link>
          <Route path="/test" element={<p>{testString}</p>} />
        </Routes>,
      ),
    );
    expect(() => getByText(testString)).toThrow();
    fireEvent.click(getByText(linkText));
    await waitFor(() => {
      expect(getByText(testString)).toBeInTheDocument();
    });
  });

  it('captures click using analytics api', async () => {
    const linkText = 'Navigate!';
    const analyticsApi = new MockAnalyticsApi();
    const customOnClick = jest.fn();

    const { getByText } = render(
      wrapInTestApp(
        <TestApiProvider apis={[[analyticsApiRef, analyticsApi]]}>
          <Link to="/test" onClick={customOnClick}>
            {linkText}
          </Link>
        </TestApiProvider>,
      ),
    );

    fireEvent.click(getByText(linkText));

    // Analytics event should have been fired.
    await waitFor(() => {
      expect(analyticsApi.getEvents()[0]).toMatchObject({
        action: 'click',
        subject: linkText,
        attributes: {
          to: '/test',
        },
      });

      // Custom onClick handler should have still been fired too.
      expect(customOnClick).toHaveBeenCalled();
    });
  });

  it('does not capture click when noTrack is set', async () => {
    const linkText = 'Navigate!';
    const analyticsApi = new MockAnalyticsApi();
    const customOnClick = jest.fn();

    const { getByText } = render(
      wrapInTestApp(
        <TestApiProvider apis={[[analyticsApiRef, analyticsApi]]}>
          <Link to="/test" onClick={customOnClick} noTrack>
            {linkText}
          </Link>
        </TestApiProvider>,
      ),
    );

    fireEvent.click(getByText(linkText));

    // Analytics event should have been fired.
    await waitFor(() => {
      // Custom onClick handler should have been fired.
      expect(customOnClick).toHaveBeenCalled();

      // But there should be no analytics event.
      expect(analyticsApi.getEvents()).toHaveLength(0);
    });
  });

  describe('isExternalUri', () => {
    it.each([
      [true, 'http://'],
      [true, 'https://'],
      [true, 'https://some-host'],
      [true, 'https://some-host/path#fragment'],
      [true, 'https://some-host/path?param1=value'],
      [true, 'slack://'],
      [true, 'mailto:[email protected]'],
      [true, 'ms-help://'],
      [true, 'ms.help://'],
      [true, 'ms+help://'],
      [false, '//'],
      [false, '123://'],
      [false, 'abc&xzy://'],
      [false, 'http'],
      [false, 'path/to'],
      [false, 'path/to/something#fragment'],
      [false, 'path/to/something?param1=value'],
      [false, '/path/to/something'],
      [false, '/path/to/something#fragment'],
    ])('should be %p when %p', (expected, uri) => {
      expect(isExternalUri(uri)).toBe(expected);
    });
  });
});
Example #24
Source File: StudioLayout.tsx    From atlas with GNU General Public License v3.0 4 votes vote down vote up
StudioLayout = () => {
  const location = useLocation()
  const displayedLocation = useVideoWorkspaceRouting()
  const internetConnectionStatus = useConnectionStatusStore((state) => state.internetConnectionStatus)
  const nodeConnectionStatus = useConnectionStatusStore((state) => state.nodeConnectionStatus)
  const {
    activeAccountId,
    activeMemberId,
    activeChannelId,
    memberships,
    membershipsLoading,
    activeMembershipLoading,
    extensionConnected,
    isLoading,
  } = useUser()

  const [openUnsupportedBrowserDialog, closeUnsupportedBrowserDialog] = useConfirmationModal()
  const [enterLocation] = useState(location.pathname)
  const hasMembership = !!memberships?.length

  const accountSet = !membershipsLoading && !!activeAccountId
  const memberSet = accountSet && !!activeMemberId && hasMembership
  const channelSet = memberSet && !!activeChannelId && hasMembership

  useEffect(() => {
    if (!isAllowedBrowser()) {
      openUnsupportedBrowserDialog({
        iconType: 'warning',
        title: 'Unsupported browser detected',
        description:
          'It seems the browser you are using is not fully supported by Joystream Studio. Some of the features may not be accessible. For the best experience, please use a recent version of Chrome, Firefox or Edge.',
        primaryButton: {
          text: 'I understand',
          onClick: () => {
            closeUnsupportedBrowserDialog()
          },
        },
        onExitClick: () => {
          closeUnsupportedBrowserDialog()
        },
      })
    }
  }, [closeUnsupportedBrowserDialog, openUnsupportedBrowserDialog])
  return (
    <>
      <TopbarStudio hideChannelInfo={!memberSet} />
      <NoConnectionIndicator
        hasSidebar={channelSet}
        nodeConnectionStatus={nodeConnectionStatus}
        isConnectedToInternet={internetConnectionStatus === 'connected'}
      />
      {activeMembershipLoading ||
      membershipsLoading ||
      isLoading ||
      extensionConnected === 'pending' ||
      extensionConnected === null ? (
        <StudioLoading />
      ) : (
        <>
          <CSSTransition
            in={channelSet}
            timeout={parseInt(transitions.timings.regular)}
            classNames={SLIDE_ANIMATION}
            mountOnEnter
            unmountOnExit
          >
            <StyledSidenavStudio />
          </CSSTransition>
          <MainContainer hasSidebar={channelSet}>
            <Routes location={displayedLocation}>
              <Route
                path={relativeRoutes.studio.index()}
                element={<StudioEntrypoint enterLocation={enterLocation} />}
              />
              <Route
                path={relativeRoutes.studio.signIn()}
                element={
                  <PrivateRoute element={<StudioWelcomeView />} isAuth={!channelSet} redirectTo={ENTRY_POINT_ROUTE} />
                }
              />
              <Route
                path={relativeRoutes.studio.newChannel()}
                element={
                  <PrivateRoute
                    element={<CreateEditChannelView newChannel />}
                    isAuth={memberSet}
                    redirectTo={ENTRY_POINT_ROUTE}
                  />
                }
              />
              <Route
                path={relativeRoutes.studio.editChannel()}
                element={
                  <PrivateRoute
                    element={<CreateEditChannelView />}
                    isAuth={channelSet}
                    redirectTo={ENTRY_POINT_ROUTE}
                  />
                }
              />
              <Route
                path={relativeRoutes.studio.uploads()}
                element={
                  <PrivateRoute element={<MyUploadsView />} isAuth={channelSet} redirectTo={ENTRY_POINT_ROUTE} />
                }
              />
              <Route
                path={relativeRoutes.studio.videos()}
                element={<PrivateRoute element={<MyVideosView />} isAuth={channelSet} redirectTo={ENTRY_POINT_ROUTE} />}
              />
              <Route
                path={relativeRoutes.studio.notifications()}
                element={
                  <PrivateRoute element={<NotificationsView />} isAuth={channelSet} redirectTo={ENTRY_POINT_ROUTE} />
                }
              />
            </Routes>
          </MainContainer>
          {channelSet && <VideoWorkspace />}
        </>
      )}
    </>
  )
}