Skip to content

Conversation

@nlynzaad
Copy link
Contributor

@nlynzaad nlynzaad commented Sep 18, 2025

When parsing path params to interpolate the path non-nested path params /path/$param_ was being parsed in the same manner as regular path params /path/$param.

This PR adds consideration for the trailing underscore when parsing the path for non-nested path params as well as unit tests on path interpolation and e2e tests for useParams to ensure this is parsed correctly and updates correctly when params change.

This resolves #5164

Summary by CodeRabbit

  • New Features

    • Support trailing-underscore non-nested path params (e.g., /users/$id_ → /users/123).
    • UI shows an additional navigation link for the non-nested route (/params-ps/non-nested/foo2/bar2) and improved link display text.
  • Tests

    • Expanded React and Solid e2e tests to cover the new link, navigation, and param parsing for foo2/bar2.
    • Added unit tests validating interpolation/matching for the $param_ syntax.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 18, 2025

Walkthrough

Adds support for non-nested trailing-underscore params ($param_) in router-core parsing, adds tests for interpolation/matching/parsing, and extends React and Solid e2e routes/tests with an extra link and assertions for /params-ps/non-nested/foo2/bar2. No public API signatures changed.

Changes

Cohort / File(s) Summary of Changes
Core parsing & tests
packages/router-core/src/path.ts, packages/router-core/tests/path.test.ts
Normalize trailing underscores on path segments when matching tokens (treat $param_ as $param), update token matching to use the normalized part, and add tests for interpolatePath, matchPathname, and parsePathname covering /users/$id_ and /foo/$bar_.
React e2e route & test
e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx, e2e/react-router/basic-file-based/tests/params.spec.ts
Change displayed link text to concrete paths, add a second Link with params={{ foo: 'foo2', bar: 'bar2' }} (data-testid="l-to-non-nested-foo2-bar2"), and extend tests to assert hrefs, wait for URL navigation, and validate parsed params for both routes.
Solid e2e route & test
e2e/solid-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx, e2e/solid-router/basic-file-based/tests/params.spec.ts
Mirror React changes: update Link label, add second Link with params={{ foo: 'foo2', bar: 'bar2' }} and data-testid="l-to-non-nested-foo2-bar2", and extend tests to use URL-based waits and assert parsed params for both routes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App as App / Route defs
  participant Router as Router Core
  participant Parser as path.parse
  participant Interp as path.interpolate

  rect rgba(230,245,255,0.45)
  note over App,Router: Register route containing `$param_`
  App->>Router: register("/users/$id_")
  Router->>Parser: parse("/users/$id_") 
  Parser-->>Router: segment { type: PARAM, name: "id" } 
  end

  rect rgba(240,255,230,0.45)
  note over App,Interp: Navigation with params
  App->>Interp: interpolate("/users/$id_", { id: "123" })
  Interp-->>App: "/users/123"
  App->>Router: navigate("/users/123")
  Router-->>App: matched route, params { id: "123" }
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

package: react-router, package: solid-router

Suggested reviewers

  • schiller-manuel

Poem

I twitch my whiskers, hop with cheer,
A trailing underscore no longer stuck here.
$id_ hops free to match and run,
Links click smooth, params display fun.
🥕🐇

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "fix(router-core): parse non-nested path params correctly" is concise, single-sentence, and accurately summarizes the primary change: a router-core fix for parsing non-nested (trailing-underscore) path params along with associated tests. It matches the modifications described in packages/router-core/src/path.ts and the added unit and e2e tests. The conventional-commit style and scope are appropriate for a quick history scan.
Linked Issues Check ✅ Passed The changes directly address linked issue #5164: path.ts now normalizes non-nested parts by stripping a trailing underscore and uses that normalized part for token matching and decoding, which resolves recognition/updating of underscored params. Supporting unit tests in packages/router-core/tests/path.test.ts and the added React/Solid e2e tests exercise interpolation, matching, parsing, and navigation flows and assert that params update to expected values (e.g., foo: "foo2"). Based on the provided summaries, the code and test changes meet the coding requirements specified in issue #5164.
Out of Scope Changes Check ✅ Passed All modifications appear scoped to the stated objective: the production change is limited to packages/router-core/src/path.ts with matching unit tests, and the rest are test-fixture and e2e updates (added links and assertions) to validate the fix; UI text changes are confined to test fixtures. I do not see unrelated refactors, dependency changes, or public API alterations in the provided summaries.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch #5164

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3c98d3 and ebb3ffb.

📒 Files selected for processing (1)
  • packages/router-core/src/path.ts (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/router-core/src/path.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Comment @coderabbitai help to get the list of available commands and usage tips.

@nlynzaad nlynzaad changed the title parse non-nested path params correctly fix(router-core): parse non-nested path params correctly Sep 18, 2025
@nx-cloud
Copy link

nx-cloud bot commented Sep 18, 2025

View your CI Pipeline Execution ↗ for commit 112143d

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 4m 56s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1m 13s View ↗

☁️ Nx Cloud last updated this comment at 2025-09-18 18:27:27 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Sep 18, 2025

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@5165

@tanstack/directive-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/directive-functions-plugin@5165

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@5165

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@5165

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@5165

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@5165

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-ssr-query@5165

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@5165

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@5165

@tanstack/react-start-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-plugin@5165

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@5165

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@5165

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@5165

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@5165

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@5165

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@5165

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@5165

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-ssr-query-core@5165

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@5165

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@5165

@tanstack/server-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/server-functions-plugin@5165

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@5165

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@5165

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@5165

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@5165

@tanstack/solid-start-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-plugin@5165

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@5165

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@5165

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@5165

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@5165

@tanstack/start-server-functions-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-functions-client@5165

@tanstack/start-server-functions-fetcher

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-functions-fetcher@5165

@tanstack/start-server-functions-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-functions-server@5165

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@5165

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@5165

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@5165

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@5165

commit: 112143d

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx (1)

21-28: Two links render identical visible text; use distinct labels for a11y and clarity.

Both Links show "/params-ps/non-nested/$foo/$bar" which makes screen-reader and debugging output ambiguous. Align with Solid example and reflect actual param values.

Apply this diff:

           params={{ foo: 'foo2', bar: 'bar2' }}
           >
-            /params-ps/non-nested/$foo/$bar
+            /params-ps/non-nested/foo2/bar2
           </Link>

(Optional) Consider also updating the first link’s label (Lines 19-20) to "/params-ps/non-nested/foo/bar" for parity with the Solid route.

packages/router-core/tests/path.test.ts (1)

435-440: Nice coverage for interpolatePath with non‑nested syntax; add parse/match assertions too.

Recommend adding parse and match tests to prevent regressions in the tokenization/matching path.

Example additions:

it('parsePathname should parse $id_ as a param named $id', () => {
  expect(parsePathname('/$id_')).toEqual([
    { type: SEGMENT_TYPE_PATHNAME, value: '/' },
    { type: SEGMENT_TYPE_PARAM, value: '$id' },
  ])
})

it('matchPathname should match non-nested param', () => {
  expect(matchPathname('/', '/users/123', { to: '/users/$id_' }))
    .toStrictEqual({ id: '123' })
})
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb4c6a1 and f768adf.

📒 Files selected for processing (6)
  • e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx (1 hunks)
  • e2e/react-router/basic-file-based/tests/params.spec.ts (2 hunks)
  • e2e/solid-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx (1 hunks)
  • e2e/solid-router/basic-file-based/tests/params.spec.ts (1 hunks)
  • packages/router-core/src/path.ts (2 hunks)
  • packages/router-core/tests/path.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx (3)
e2e/react-router/basic-file-based/src/routes/params-ps/index.tsx (1)
  • Route (3-5)
e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/$bar.tsx (1)
  • Route (3-5)
e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/route.tsx (1)
  • Route (3-3)
e2e/solid-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx (3)
e2e/solid-router/basic-file-based/src/routes/params-ps/index.tsx (1)
  • Route (3-5)
e2e/solid-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/$bar.tsx (1)
  • Route (3-5)
e2e/solid-router/basic-file-based/src/routes/params-ps/non-nested/$foo_/route.tsx (1)
  • Route (3-3)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (2)
e2e/solid-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx (1)

19-27: LGTM: distinct, descriptive link labels.

The labels match the concrete hrefs (foo/bar and foo2/bar2), improving a11y and test readability.

packages/router-core/src/path.ts (1)

323-332: Parsing branch looks correct; consider char‑set parity comment.

Logic is fine and returns $param for $param_. Once the regexes above are tightened, this branch inherits the stricter charset and remains exclusive from the regular param case.

Please add a quick unit test to assert parsePathname('/$id_') yields a single SEGMENT_TYPE_PARAM with value: '$id' to lock behavior.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
e2e/react-router/basic-file-based/tests/params.spec.ts (3)

74-76: Minor: fold wait+assert into a single, auto‑waiting URL assertion

Simplifies and reduces boilerplate.

-    await page.waitForURL('/params-ps/non-nested/foo/bar')
-    const pagePathname = new URL(page.url()).pathname
-    expect(pagePathname).toBe('/params-ps/non-nested/foo/bar')
+    await expect(page).toHaveURL('/params-ps/non-nested/foo/bar')

94-97: Same here: compress navigation wait + assertion

Use Playwright’s auto‑waiting matcher.

-    await page.waitForURL('/params-ps/non-nested/foo2/bar2')
-    const pagePathname2 = new URL(page.url()).pathname
-    expect(pagePathname2).toBe('/params-ps/non-nested/foo2/bar2')
+    await expect(page).toHaveURL('/params-ps/non-nested/foo2/bar2')

99-107: Reduce flakiness: assert via locator matchers (auto‑wait) instead of manual innerText/JSON.parse

Locators’ toHaveText retries until content settles; manual reads don’t. This avoids timing races after navigation.

-    const foo2ParamsValue = page.getByTestId('foo-params-value')
-    const foo2ParamsText = await foo2ParamsValue.innerText()
-    const foo2ParamsObj = JSON.parse(foo2ParamsText)
-    expect(foo2ParamsObj).toEqual({ foo: 'foo2' })
+    await expect(page.getByTestId('foo-params-value')).toHaveText(
+      JSON.stringify({ foo: 'foo2' }),
+    )

-    const params2Value = page.getByTestId('foo-bar-params-value')
-    const params2Text = await params2Value.innerText()
-    const params2Obj = JSON.parse(params2Text)
-    expect(params2Obj).toEqual({ foo: 'foo2', bar: 'bar2' })
+    await expect(page.getByTestId('foo-bar-params-value')).toHaveText(
+      JSON.stringify({ foo: 'foo2', bar: 'bar2' }),
+    )
packages/router-core/tests/path.test.ts (3)

435-440: Add missing-param case for non-nested interpolation

To lock parity with regular named params, add a case where $id_ is missing and yields undefined.

       {
         name: 'should interpolate the non-nested path',
         path: '/users/$id_',
         params: { id: '123' },
         result: '/users/123',
       },
+      {
+        name: 'should interpolate the non-nested path with missing params',
+        path: '/users/$id_',
+        params: {},
+        result: '/users/undefined',
+      },

663-670: Add negative match to prove “non-nested” behavior

Ensure /users/$id_ does not match extra trailing segments.

       {
         name: 'should match and return the non-nested named path params',
         input: '/users/123',
         matchingOptions: {
           to: '/users/$id_',
         },
         expectedMatchedParams: { id: '123' },
       },
+      {
+        name: 'should not match when extra segment follows non-nested param',
+        input: '/users/123/extra',
+        matchingOptions: {
+          to: '/users/$id_',
+        },
+        expectedMatchedParams: undefined,
+      },

910-918: Cover root-level non-nested parsing

Add a root variant to confirm $bar_ canonicalizes to $bar at /.

       {
         name: 'should handle non-nested named params',
         to: '/foo/$bar_',
         expected: [
           { type: SEGMENT_TYPE_PATHNAME, value: '/' },
           { type: SEGMENT_TYPE_PATHNAME, value: 'foo' },
           { type: SEGMENT_TYPE_PARAM, value: '$bar' },
         ],
       },
+      {
+        name: 'should handle non-nested named params at the root',
+        to: '/$bar_',
+        expected: [
+          { type: SEGMENT_TYPE_PATHNAME, value: '/' },
+          { type: SEGMENT_TYPE_PARAM, value: '$bar' },
+        ],
+      },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f768adf and a3c98d3.

📒 Files selected for processing (5)
  • e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx (1 hunks)
  • e2e/react-router/basic-file-based/tests/params.spec.ts (2 hunks)
  • e2e/solid-router/basic-file-based/tests/params.spec.ts (2 hunks)
  • packages/router-core/src/path.ts (2 hunks)
  • packages/router-core/tests/path.test.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/router-core/src/path.ts
  • e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx
  • e2e/solid-router/basic-file-based/tests/params.spec.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/router-core/tests/path.test.ts (1)
packages/router-core/src/path.ts (2)
  • SEGMENT_TYPE_PATHNAME (6-6)
  • SEGMENT_TYPE_PARAM (7-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (1)
e2e/react-router/basic-file-based/tests/params.spec.ts (1)

90-93: LGTM: correct href assertion for the second link

Good coverage to ensure interpolation is right.

@Sheraff Sheraff self-requested a review September 18, 2025 17:17
@nlynzaad nlynzaad merged commit ab12c06 into main Sep 18, 2025
6 checks passed
@nlynzaad nlynzaad deleted the #5164 branch September 18, 2025 18:28
nlynzaad added a commit that referenced this pull request Sep 18, 2025
This PR continues on from #5165 and primarily adds additional tests to
path resolution and matchByPath for non-nested paths in additiona to e2e
test for both react and solid router to verify non-nesting
functionality.

One additional issue was picked up for handling of normal path segments.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- New Features
- Added non-nested demo routes and navigation examples (including
dynamic and edit paths) for React and Solid, with headings, links, and
parameter displays.
- Bug Fixes
- Improved path parsing/decoding for non-nested segments to prevent
unintended nesting and ensure correct parameter values.
- Tests
- Added broad unit tests for non-nested path parsing/matching/resolution
(case-sensitive/insensitive, wildcards, optional/fuzzy).
- Added e2e tests validating non-nested navigation and params; reduced
e2e flakiness by awaiting network idle.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
nlynzaad added a commit that referenced this pull request Sep 18, 2025
nlynzaad added a commit that referenced this pull request Sep 18, 2025
This PR merges the latest updates to main from #5169 and #5165 into
Alpha and brings with it an update to how non-nested paths are parsed.

It also adds additional unit testing for paths and matchByPath as well
as additional e2e tests for react and solid

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Route params with trailing underscore ($param_) do not update when navigating

1 participant