diff --git a/static/app/components/modals/sudoModal.spec.tsx b/static/app/components/modals/sudoModal.spec.tsx
index f0efd7dba4e0ea..01d00a2f82bb5d 100644
--- a/static/app/components/modals/sudoModal.spec.tsx
+++ b/static/app/components/modals/sudoModal.spec.tsx
@@ -1,6 +1,5 @@
import {OrganizationFixture} from 'sentry-fixture/organization';
-import {initializeOrg} from 'sentry-test/initializeOrg';
import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
import ConfigStore from 'sentry/stores/configStore';
@@ -63,7 +62,6 @@ describe('Sudo Modal', () => {
});
it('can delete an org with sudo flow', async () => {
- const {routerProps} = initializeOrg({router: {params: {}}});
setHasPasswordAuth(true);
const successCb = jest.fn();
@@ -76,11 +74,7 @@ describe('Sudo Modal', () => {
error: errorCb,
});
- render(
-
- placeholder content
-
- );
+ render();
// Should have Modal + input
expect(await screen.findByRole('dialog')).toBeInTheDocument();
@@ -137,17 +131,12 @@ describe('Sudo Modal', () => {
});
it('shows button to redirect if user does not have password auth', async () => {
- const {routerProps} = initializeOrg({router: {params: {}}});
setHasPasswordAuth(false);
// Should return w/ `sudoRequired` and trigger the modal to open
new MockApiClient().request('/organizations/org-slug/', {method: 'DELETE'});
- render(
-
- placeholder content
-
- );
+ render();
// Should have Modal + input
expect(await screen.findByRole('dialog')).toBeInTheDocument();
diff --git a/static/app/routes.tsx b/static/app/routes.tsx
index 408a7655b6c042..a28de60b9a300f 100644
--- a/static/app/routes.tsx
+++ b/static/app/routes.tsx
@@ -3151,7 +3151,6 @@ function buildRoutes(): RouteObject[] {
{
path: '/',
component: errorHandler(App),
- deprecatedRouteProps: true,
children: [
rootRoutes,
authV2Routes,
diff --git a/static/app/views/app/index.spec.tsx b/static/app/views/app/index.spec.tsx
index 6222a67a5ebb6b..03cb17d26c546a 100644
--- a/static/app/views/app/index.spec.tsx
+++ b/static/app/views/app/index.spec.tsx
@@ -1,7 +1,6 @@
import {InstallWizardFixture} from 'sentry-fixture/installWizard';
import {OrganizationFixture} from 'sentry-fixture/organization';
-import {initializeOrg} from 'sentry-test/initializeOrg';
import {act, render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
import AlertStore from 'sentry/stores/alertStore';
@@ -20,9 +19,23 @@ function HookWrapper(props: any) {
);
}
+function PlaceholderContent() {
+ return
placeholder content
;
+}
+
+const defaultRouterConfig = {
+ location: {pathname: '/organizations/org-slug/'},
+ children: [
+ {
+ index: true,
+ element: ,
+ },
+ ],
+ route: '/organizations/:orgSlug/',
+};
+
describe('App', () => {
const configState = ConfigStore.getState();
- const {routerProps} = initializeOrg();
beforeEach(() => {
const organization = OrganizationFixture();
@@ -68,11 +81,7 @@ describe('App', () => {
});
it('renders', async () => {
- render(
-
- placeholder content
-
- );
+ render(, {initialRouterConfig: defaultRouterConfig});
await waitFor(() => OrganizationsStore.getAll().length === 1);
expect(screen.getByText('placeholder content')).toBeInTheDocument();
@@ -83,11 +92,7 @@ describe('App', () => {
const user = ConfigStore.get('user');
user.flags.newsletter_consent_prompt = true;
- render(
-
- placeholder content
-
- );
+ render(, {initialRouterConfig: defaultRouterConfig});
await waitFor(() => OrganizationsStore.getAll().length === 1);
@@ -113,11 +118,7 @@ describe('App', () => {
},
});
- render(
-
- placeholder content
-
- );
+ render(, {initialRouterConfig: defaultRouterConfig});
await waitFor(() => OrganizationsStore.getAll().length === 1);
@@ -135,11 +136,7 @@ describe('App', () => {
agreements: ['standard', 'partner_presence'],
});
HookStore.add('component:partnership-agreement', () => );
- render(
-
- placeholder content
-
- );
+ render(, {initialRouterConfig: defaultRouterConfig});
await waitFor(() => OrganizationsStore.getAll().length === 1);
expect(HookStore.get('component:partnership-agreement')).toHaveLength(1);
@@ -149,11 +146,7 @@ describe('App', () => {
it('does not render PartnerAgreement for non-partnered orgs', async () => {
ConfigStore.set('partnershipAgreementPrompt', null);
HookStore.add('component:partnership-agreement', () => );
- render(
-
- placeholder content
-
- );
+ render(, {initialRouterConfig: defaultRouterConfig});
await waitFor(() => OrganizationsStore.getAll().length === 1);
expect(screen.getByText('placeholder content')).toBeInTheDocument();
@@ -172,11 +165,7 @@ describe('App', () => {
},
});
- render(
-
- placeholder content
-
- );
+ render(, {initialRouterConfig: defaultRouterConfig});
await waitFor(() => OrganizationsStore.getAll().length === 1);
@@ -191,11 +180,7 @@ describe('App', () => {
ConfigStore.set('needsUpgrade', true);
ConfigStore.set('isSelfHosted', false);
- render(
-
- placeholder content
-
- );
+ render(, {initialRouterConfig: defaultRouterConfig});
await waitFor(() => OrganizationsStore.getAll().length === 1);
expect(screen.getByText('placeholder content')).toBeInTheDocument();
@@ -204,13 +189,23 @@ describe('App', () => {
it('redirects to sentryUrl on invalid org slug', async () => {
const {sentryUrl} = ConfigStore.get('links');
- render(
-
- placeholder content
-
- );
+ // Avoid mounting OrganizationContextProvider and related preloads which
+ // would issue org-specific requests for the invalid slug
+ ConfigStore.set('shouldPreloadData', false);
+ render(, {
+ initialRouterConfig: {
+ location: {pathname: '/organizations/albertos%2fapples/'},
+ route: '/organizations/:orgId/',
+ children: [
+ {
+ index: true,
+ element: ,
+ },
+ ],
+ },
+ });
- await waitFor(() => OrganizationsStore.getAll().length === 1);
+ await waitFor(() => expect(testableWindowLocation.replace).toHaveBeenCalled());
expect(screen.queryByText('placeholder content')).not.toBeInTheDocument();
expect(sentryUrl).toBe('https://sentry.io');
expect(testableWindowLocation.replace).toHaveBeenCalledWith('https://sentry.io');
@@ -234,11 +229,7 @@ describe('App', () => {
const restore = ConfigStore.get('isSelfHosted');
ConfigStore.set('isSelfHosted', true);
- render(
-
- placeholder content
-
- );
+ render(, {initialRouterConfig: defaultRouterConfig});
act(() => ConfigStore.set('isSelfHosted', restore));
await waitFor(() => OrganizationsStore.getAll().length === 1);
diff --git a/static/app/views/app/index.tsx b/static/app/views/app/index.tsx
index f4c7a1294a7501..1f6ed5da10292a 100644
--- a/static/app/views/app/index.tsx
+++ b/static/app/views/app/index.tsx
@@ -1,4 +1,5 @@
import {lazy, Suspense, useCallback, useEffect, useRef} from 'react';
+import {Outlet} from 'react-router-dom';
import styled from '@emotion/styled';
import {
@@ -22,7 +23,6 @@ import GuideStore from 'sentry/stores/guideStore';
import HookStore from 'sentry/stores/hookStore';
import OrganizationsStore from 'sentry/stores/organizationsStore';
import {useLegacyStore} from 'sentry/stores/useLegacyStore';
-import type {RouteComponentProps} from 'sentry/types/legacyReactRouter';
import {DemoToursProvider} from 'sentry/utils/demoMode/demoTours';
import isValidOrgSlug from 'sentry/utils/isValidOrgSlug';
import {onRenderCallback, Profiler} from 'sentry/utils/performanceForSentry';
@@ -33,6 +33,7 @@ import {useColorscheme} from 'sentry/utils/useColorscheme';
import {GlobalFeedbackForm} from 'sentry/utils/useFeedbackForm';
import {useHotkeys} from 'sentry/utils/useHotkeys';
import {useLocation} from 'sentry/utils/useLocation';
+import {useParams} from 'sentry/utils/useParams';
import {useUser} from 'sentry/utils/useUser';
import {AsyncSDKIntegrationContextProvider} from 'sentry/views/app/asyncSDKIntegrationProvider';
import LastKnownRouteContextProvider from 'sentry/views/lastKnownRouteContextProvider';
@@ -41,10 +42,6 @@ import RouteAnalyticsContextProvider from 'sentry/views/routeAnalyticsContextPro
import ExplorerPanel from 'sentry/views/seerExplorer/explorerPanel';
import {useExplorerPanel} from 'sentry/views/seerExplorer/useExplorerPanel';
-type Props = {
- children: React.ReactNode;
-} & RouteComponentProps<{orgId?: string}>;
-
const InstallWizard = lazy(() => import('sentry/views/admin/installWizard'));
const NewsletterConsent = lazy(() => import('sentry/views/newsletterConsent'));
const BeaconConsent = lazy(() => import('sentry/views/beaconConsent'));
@@ -52,7 +49,7 @@ const BeaconConsent = lazy(() => import('sentry/views/beaconConsent'));
/**
* App is the root level container for all uathenticated routes.
*/
-function App({children, params}: Props) {
+function App() {
useColorscheme();
const api = useApi();
@@ -125,7 +122,7 @@ function App({children, params}: Props) {
}, [api, config.isSelfHosted]);
const {sentryUrl} = ConfigStore.get('links');
- const {orgId} = params;
+ const {orgId} = useParams<{orgId?: string}>();
const isOrgSlugValid = orgId ? isValidOrgSlug(orgId) : true;
useEffect(() => {
@@ -243,7 +240,7 @@ function App({children, params}: Props) {
return null;
}
- return children;
+ return ;
}
const renderOrganizationContextProvider = useCallback(