Skip to content

Commit 87ca96e

Browse files
Upgrade package (#238)
* Upgrade package - Updated package - Updated broken imports - Updated ReadMe to include optional * Added integration tests for path matching * Added change set
1 parent eab782d commit 87ca96e

File tree

6 files changed

+228
-28
lines changed

6 files changed

+228
-28
lines changed

.changeset/mighty-boats-wink.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-resource-router": minor
3+
---
4+
5+
Updated the path regex matching method added test to make sure complex routing is not broken

docs/router/configuration.md

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ export const routes = [
4848
];
4949
```
5050

51+
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?'`.
52+
5153
## History
5254

5355
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

package-lock.json

+7-25
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"dependencies": {
6767
"lodash.debounce": "^4.0.8",
6868
"lodash.noop": "^3.0.1",
69-
"path-to-regexp": "^1.7.0",
69+
"path-to-regexp": "^6.2.1",
7070
"react-sweet-state": "^2.6.4",
7171
"url-parse": "^1.5.10"
7272
},

src/__tests__/integration.test.tsx

+212-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@ import React, { StrictMode } from 'react';
55
import { defaultRegistry } from 'react-sweet-state';
66

77
import { isServerEnvironment } from '../common/utils/is-server-environment';
8-
import { Route, RouteComponent, Router, type Plugin } from '../index';
8+
import {
9+
Route,
10+
RouteComponent,
11+
Router,
12+
type Plugin,
13+
usePathParam,
14+
useQueryParam,
15+
} from '../index';
916

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

@@ -318,5 +325,209 @@ describe('<Router /> client-side integration tests', () => {
318325
expect(screen.getByText('route component')).toBeInTheDocument();
319326
});
320327
});
328+
329+
describe(`path matching integration tests: strict mode ${strictModeState}`, () => {
330+
it('matches dynamic route with optional parameter', () => {
331+
const MigrationComponent = () => {
332+
const [step] = usePathParam('step');
333+
const [migrationId] = usePathParam('migrationId');
334+
335+
return (
336+
<div>
337+
Step: {step}, Migration ID: {migrationId || 'N/A'}
338+
</div>
339+
);
340+
};
341+
342+
const route = {
343+
name: 'migration',
344+
path: '/settings/system/migration/:step/:migrationId?',
345+
component: MigrationComponent,
346+
};
347+
const { history } = mountRouter({ routes: [route], strictMode: true });
348+
349+
act(() => {
350+
history.push('/settings/system/migration/plan-configuration/123');
351+
});
352+
353+
expect(
354+
screen.getByText('Step: plan-configuration, Migration ID: 123')
355+
).toBeInTheDocument();
356+
});
357+
358+
it('matches route with regex constraint on path parameter', () => {
359+
const PlanComponent = () => {
360+
const [planId] = usePathParam('planId');
361+
362+
return <div>Plan ID: {planId}</div>;
363+
};
364+
365+
const route = {
366+
name: 'plans',
367+
path: '/plans/:planId(\\d+)',
368+
component: PlanComponent,
369+
};
370+
const { history } = mountRouter({ routes: [route], strictMode: true });
371+
372+
act(() => {
373+
history.push('/plans/456');
374+
});
375+
376+
expect(screen.getByText('Plan ID: 456')).toBeInTheDocument();
377+
});
378+
379+
it('matches route with multiple dynamic parameters', () => {
380+
const ProjectAppComponent = () => {
381+
const [projectType] = usePathParam('projectType');
382+
const [projectKey] = usePathParam('projectKey');
383+
const [appId] = usePathParam('appId');
384+
385+
return (
386+
<div>
387+
Project Type: {projectType}, Project Key: {projectKey}, App ID:{' '}
388+
{appId}
389+
</div>
390+
);
391+
};
392+
393+
const route = {
394+
name: 'project-app',
395+
path: '/app/:projectType(software|servicedesk)/projects/:projectKey/apps/:appId',
396+
component: ProjectAppComponent,
397+
};
398+
const { history } = mountRouter({ routes: [route], strictMode: true });
399+
400+
act(() => {
401+
history.push('/app/software/projects/PROJ123/apps/456');
402+
});
403+
404+
expect(
405+
screen.getByText(
406+
'Project Type: software, Project Key: PROJ123, App ID: 456'
407+
)
408+
).toBeInTheDocument();
409+
});
410+
411+
it('matches route with dynamic and query parameters', () => {
412+
const IssueComponent = () => {
413+
const [issueKey] = usePathParam('issueKey');
414+
const [queryParam] = useQueryParam('query');
415+
416+
return (
417+
<div>
418+
Issue Key: {issueKey}, Query: {queryParam || 'None'}
419+
</div>
420+
);
421+
};
422+
423+
const route = {
424+
name: 'browse',
425+
path: '/browse/:issueKey(\\w+-\\d+)',
426+
component: IssueComponent,
427+
};
428+
const { history } = mountRouter({ routes: [route], strictMode: true });
429+
430+
act(() => {
431+
history.push('/browse/ISSUE-123?query=details');
432+
});
433+
434+
expect(
435+
screen.getByText('Issue Key: ISSUE-123, Query: details')
436+
).toBeInTheDocument();
437+
});
438+
439+
it('matches route with complex regex constraint on path parameter and wildcard', () => {
440+
const IssueComponent = () => {
441+
const [issueKey] = usePathParam('issueKey');
442+
443+
return <div>Issue Key: {issueKey}</div>;
444+
};
445+
446+
const route = {
447+
name: 'browse',
448+
path: '/browse/:issueKey(\\w+-\\d+)(.*)?',
449+
component: IssueComponent,
450+
};
451+
const { history } = mountRouter({ routes: [route], strictMode: true });
452+
453+
act(() => {
454+
history.push('/browse/ISSUE-123/details');
455+
});
456+
457+
expect(screen.getByText('Issue Key: ISSUE-123')).toBeInTheDocument();
458+
});
459+
460+
it('matches route with multiple dynamic segments and regex constraints', () => {
461+
const SettingsComponent = () => {
462+
const [settingsType] = usePathParam('settingsType');
463+
const [appId] = usePathParam('appId');
464+
const [envId] = usePathParam('envId');
465+
const [route] = usePathParam('route');
466+
467+
return (
468+
<div>
469+
Settings Type: {settingsType}, App ID: {appId}, Environment ID:{' '}
470+
{envId}, Route: {route || 'None'}
471+
</div>
472+
);
473+
};
474+
475+
const route = {
476+
name: 'settings',
477+
path: '/settings/apps/:settingsType(configure|get-started)/:appId/:envId/:route?',
478+
component: SettingsComponent,
479+
};
480+
const { history } = mountRouter({ routes: [route], strictMode: true });
481+
482+
act(() => {
483+
history.push('/settings/apps/configure/app123/env456/setup');
484+
});
485+
486+
expect(
487+
screen.getByText(
488+
'Settings Type: configure, App ID: app123, Environment ID: env456, Route: setup'
489+
)
490+
).toBeInTheDocument();
491+
});
492+
493+
it('matches route with regex constraint and renders wildcard route for invalid paths', () => {
494+
const IssueComponent = () => {
495+
const [issueKey] = usePathParam('issueKey');
496+
497+
return <div>Issue Key: {issueKey}</div>;
498+
};
499+
500+
const NotFoundComponent = () => <div>Not Found</div>;
501+
502+
const routes = [
503+
{
504+
name: 'issue',
505+
path: '/browse/:issueKey(\\w+-\\d+)(.*)?',
506+
component: IssueComponent,
507+
},
508+
{
509+
name: 'wildcard',
510+
path: '/',
511+
component: NotFoundComponent,
512+
},
513+
];
514+
const { history } = mountRouter({ routes, strictMode: true });
515+
516+
act(() => {
517+
history.push('/browse/TEST-1');
518+
});
519+
expect(screen.getByText('Issue Key: TEST-1')).toBeInTheDocument();
520+
521+
act(() => {
522+
history.push('/browse/1');
523+
});
524+
expect(screen.getByText('Not Found')).toBeInTheDocument();
525+
526+
act(() => {
527+
history.push('/browse/TEST');
528+
});
529+
expect(screen.getByText('Not Found')).toBeInTheDocument();
530+
});
531+
});
321532
}
322533
});

src/common/utils/match-route/matchPath.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// TAKEN FROM https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/matchPath.js
22

3-
import pathToRegexp from 'path-to-regexp';
3+
import { pathToRegexp } from 'path-to-regexp';
44

55
const cache: { [key: string]: any } = {};
66
const cacheLimit = 10000;

0 commit comments

Comments
 (0)