From a91201aff87f582e00624c5f60e19459f5b4cda4 Mon Sep 17 00:00:00 2001 From: Tim Kye Date: Fri, 14 Mar 2025 14:54:26 -0700 Subject: [PATCH] feat: support underscore in path parts --- CHANGELOG.md | 4 ++++ docs/api/useRoutes.md | 2 +- src/router.tsx | 6 ++++-- test/router.spec.tsx | 8 ++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 570c8ab..c3c3a70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [5.0.0] - UNRELEASED +### Changed +- Added support for underscore `_` in path part matchers + ## [4.1.2] - 2022-11-02 ### Fixed - `useHash` undefined error from missing prop on event diff --git a/docs/api/useRoutes.md b/docs/api/useRoutes.md index c90a7b0..5ad30e5 100644 --- a/docs/api/useRoutes.md +++ b/docs/api/useRoutes.md @@ -24,7 +24,7 @@ function useRoutes( ## Basic -The first parameter is an object of path keys whose values are functions that return a **ReactElement** (or null when no match is found). The paths should start with a forward-slash `/` and then contain literal matches (`/base`), path variables (`/:userId`), and a `*` for catch-all wildcards. Path variables will be provided to the matching route-function. +The first parameter is an object of path keys whose values are functions that return a **ReactElement** (or null when no match is found). The paths should start with a forward-slash `/` and then contain literal matches (`/base`), path variables (`/:userId`, in the format `: + [a-zA-Z_]+`), and a `*` for catch-all wildcards. Path variables will be provided to the matching route-function. ```jsx import { useRoutes, Link } from 'raviger' diff --git a/src/router.tsx b/src/router.tsx index 7e295fd..ac603ed 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -197,16 +197,18 @@ function getMatchParams( return [routeMatch, props] } +const pathPartRegex = /:[a-zA-Z_]+/g + function createRouteMatcher(path: string): RouteMatcher { return { path, regex: new RegExp( `${path.substr(0, 1) === '*' ? '' : '^'}${escapeRegExp(path) - .replace(/:[a-zA-Z]+/g, '([^/]+)') + .replace(pathPartRegex, '([^/]+)') .replace(/\*/g, '')}${path.substr(-1) === '*' ? '' : '$'}`, 'i', ), - props: (path.match(/:[a-zA-Z]+/g) ?? []).map((paramName) => paramName.substr(1)), + props: (path.match(pathPartRegex) ?? []).map((paramName) => paramName.substr(1)), } } diff --git a/test/router.spec.tsx b/test/router.spec.tsx index 7a1bf66..9170db0 100644 --- a/test/router.spec.tsx +++ b/test/router.spec.tsx @@ -42,6 +42,7 @@ describe('useRoutes', () => { '/': () => , '/about': ({ extra }: { extra?: string }) => , '/users/:userId': ({ userId }) => , + '/people/:person_id': ({ person_id }) => , '/weird (route)': () => , '/weird (route+2)': () => , } @@ -110,6 +111,13 @@ describe('useRoutes', () => { expect(getByTestId('label')).toHaveTextContent('User 1') }) + test('matches route with underscored parameters', async () => { + const { getByTestId } = render() + + act(() => navigate('/people/1')) + expect(getByTestId('label')).toHaveTextContent('Person 1') + }) + test('matches case insensitive rout', async () => { const { getByTestId } = render()