Skip to content

Commit

Permalink
Upgrade package (#238)
Browse files Browse the repository at this point in the history
* Upgrade package

- Updated package
- Updated broken imports
- Updated ReadMe to include optional

* Added integration tests for path matching

* Added change set
  • Loading branch information
AlexanderKaran authored Mar 25, 2024
1 parent eab782d commit 87ca96e
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 28 deletions.
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

0 comments on commit 87ca96e

Please sign in to comment.