Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade package #238

Merged
merged 3 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/mighty-boats-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-resource-router": minor
---

Updated the path regex matching method added test to make sure complex routing is not broken
2 changes: 2 additions & 0 deletions docs/router/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export const routes = [
];
```

You can also mark a path param as optional by using the `?` suffix. For example, if you want to make the `userId` param optional, you would do so like this `'/user/:userId?'`.

## History

You must provide a `history` instance to the router. Again, this will feel familiar to users of `react-router`. Here is how to do this
Expand Down
32 changes: 7 additions & 25 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"dependencies": {
"lodash.debounce": "^4.0.8",
"lodash.noop": "^3.0.1",
"path-to-regexp": "^1.7.0",
"path-to-regexp": "^6.2.1",
"react-sweet-state": "^2.6.4",
"url-parse": "^1.5.10"
},
Expand Down
213 changes: 212 additions & 1 deletion src/__tests__/integration.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ import React, { StrictMode } from 'react';
import { defaultRegistry } from 'react-sweet-state';

import { isServerEnvironment } from '../common/utils/is-server-environment';
import { Route, RouteComponent, Router, type Plugin } from '../index';
import {
Route,
RouteComponent,
Router,
type Plugin,
usePathParam,
useQueryParam,
} from '../index';

jest.mock('../common/utils/is-server-environment');

Expand Down Expand Up @@ -318,5 +325,209 @@ describe('<Router /> client-side integration tests', () => {
expect(screen.getByText('route component')).toBeInTheDocument();
});
});

describe(`path matching integration tests: strict mode ${strictModeState}`, () => {
it('matches dynamic route with optional parameter', () => {
const MigrationComponent = () => {
const [step] = usePathParam('step');
const [migrationId] = usePathParam('migrationId');

return (
<div>
Step: {step}, Migration ID: {migrationId || 'N/A'}
</div>
);
};

const route = {
name: 'migration',
path: '/settings/system/migration/:step/:migrationId?',
component: MigrationComponent,
};
const { history } = mountRouter({ routes: [route], strictMode: true });

act(() => {
history.push('/settings/system/migration/plan-configuration/123');
});

expect(
screen.getByText('Step: plan-configuration, Migration ID: 123')
).toBeInTheDocument();
});

it('matches route with regex constraint on path parameter', () => {
const PlanComponent = () => {
const [planId] = usePathParam('planId');

return <div>Plan ID: {planId}</div>;
};

const route = {
name: 'plans',
path: '/plans/:planId(\\d+)',
component: PlanComponent,
};
const { history } = mountRouter({ routes: [route], strictMode: true });

act(() => {
history.push('/plans/456');
});

expect(screen.getByText('Plan ID: 456')).toBeInTheDocument();
});

it('matches route with multiple dynamic parameters', () => {
const ProjectAppComponent = () => {
const [projectType] = usePathParam('projectType');
const [projectKey] = usePathParam('projectKey');
const [appId] = usePathParam('appId');

return (
<div>
Project Type: {projectType}, Project Key: {projectKey}, App ID:{' '}
{appId}
</div>
);
};

const route = {
name: 'project-app',
path: '/app/:projectType(software|servicedesk)/projects/:projectKey/apps/:appId',
component: ProjectAppComponent,
};
const { history } = mountRouter({ routes: [route], strictMode: true });

act(() => {
history.push('/app/software/projects/PROJ123/apps/456');
});

expect(
screen.getByText(
'Project Type: software, Project Key: PROJ123, App ID: 456'
)
).toBeInTheDocument();
});

it('matches route with dynamic and query parameters', () => {
const IssueComponent = () => {
const [issueKey] = usePathParam('issueKey');
const [queryParam] = useQueryParam('query');

return (
<div>
Issue Key: {issueKey}, Query: {queryParam || 'None'}
</div>
);
};

const route = {
name: 'browse',
path: '/browse/:issueKey(\\w+-\\d+)',
component: IssueComponent,
};
const { history } = mountRouter({ routes: [route], strictMode: true });

act(() => {
history.push('/browse/ISSUE-123?query=details');
});

expect(
screen.getByText('Issue Key: ISSUE-123, Query: details')
).toBeInTheDocument();
});

it('matches route with complex regex constraint on path parameter and wildcard', () => {
const IssueComponent = () => {
const [issueKey] = usePathParam('issueKey');

return <div>Issue Key: {issueKey}</div>;
};

const route = {
name: 'browse',
path: '/browse/:issueKey(\\w+-\\d+)(.*)?',
component: IssueComponent,
};
const { history } = mountRouter({ routes: [route], strictMode: true });

act(() => {
history.push('/browse/ISSUE-123/details');
});

expect(screen.getByText('Issue Key: ISSUE-123')).toBeInTheDocument();
});

it('matches route with multiple dynamic segments and regex constraints', () => {
const SettingsComponent = () => {
const [settingsType] = usePathParam('settingsType');
const [appId] = usePathParam('appId');
const [envId] = usePathParam('envId');
const [route] = usePathParam('route');

return (
<div>
Settings Type: {settingsType}, App ID: {appId}, Environment ID:{' '}
{envId}, Route: {route || 'None'}
</div>
);
};

const route = {
name: 'settings',
path: '/settings/apps/:settingsType(configure|get-started)/:appId/:envId/:route?',
component: SettingsComponent,
};
const { history } = mountRouter({ routes: [route], strictMode: true });

act(() => {
history.push('/settings/apps/configure/app123/env456/setup');
});

expect(
screen.getByText(
'Settings Type: configure, App ID: app123, Environment ID: env456, Route: setup'
)
).toBeInTheDocument();
});

it('matches route with regex constraint and renders wildcard route for invalid paths', () => {
const IssueComponent = () => {
const [issueKey] = usePathParam('issueKey');

return <div>Issue Key: {issueKey}</div>;
};

const NotFoundComponent = () => <div>Not Found</div>;

const routes = [
{
name: 'issue',
path: '/browse/:issueKey(\\w+-\\d+)(.*)?',
component: IssueComponent,
},
{
name: 'wildcard',
path: '/',
component: NotFoundComponent,
},
];
const { history } = mountRouter({ routes, strictMode: true });

act(() => {
history.push('/browse/TEST-1');
});
expect(screen.getByText('Issue Key: TEST-1')).toBeInTheDocument();

act(() => {
history.push('/browse/1');
});
expect(screen.getByText('Not Found')).toBeInTheDocument();

act(() => {
history.push('/browse/TEST');
});
expect(screen.getByText('Not Found')).toBeInTheDocument();
});
});
}
});
2 changes: 1 addition & 1 deletion src/common/utils/match-route/matchPath.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// TAKEN FROM https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/matchPath.js

import pathToRegexp from 'path-to-regexp';
import { pathToRegexp } from 'path-to-regexp';

const cache: { [key: string]: any } = {};
const cacheLimit = 10000;
Expand Down
Loading