Repository: honojs/honox (ts)

Issue:
bug: nested grouping ((group)) results in incorrect url
### What version of HonoX are you using?

0.1.42

### What steps can reproduce the bug?

Create the following routes:
```
...
routes
  (api)/
    api/
  (static)/
    (legal)/
      privacy-policy/
        index.tsx
```
Put any content in index.tsx.

### What is the expected behavior?

/privacy-policy is accessible via /privacy-policy in browser

### What do you see instead?

/privacy-policy is inaccessible, but //privacy-policy is (notice double slash)

### Additional information

It'd be really convenient if expected behavior is implemted. What do you think?

Pull request description:
fix: correct nested grouping url
Fixes #300

While I was creating this PR, #301 was created with the same purpose. My PR has more thorough testing, but I referred the implementation of #301. Therefore, I add @sahithyandev as a co-author to this PR. Thanks!

Patch:
diff --git a/mocks/app-route-groups/routes/(static)/(legal)/privacy-policy/index.tsx b/mocks/app-route-groups/routes/(static)/(legal)/privacy-policy/index.tsx
new file mode 100644
index 0000000..af33a49
--- /dev/null
+++ b/mocks/app-route-groups/routes/(static)/(legal)/privacy-policy/index.tsx
@@ -0,0 +1,3 @@
+export default function PrivacyPolicy() {
+  return <h1>Privacy Policy</h1>
+}
diff --git a/src/server/utils/file.ts b/src/server/utils/file.ts
index cfb1253..e06cf5b 100644
--- a/src/server/utils/file.ts
+++ b/src/server/utils/file.ts
@@ -7,7 +7,7 @@ export const filePathToPath = (filePath: string) => {
     .replace(/\[\.{3}.+\]/, '*')
     .replace(/\((.+?)\)/g, '')
     .replace(/\[(.+?)\]/g, ':$1')
-    .replace(/\/\//g, '/')
+    .replace(/\/\/+/g, '/')
   return /^\//.test(filePath) ? filePath : '/' + filePath
 }
 


Test patch:
diff --git a/src/server/utils/file.test.ts b/src/server/utils/file.test.ts
index 65d0677..5123ed9 100644
--- a/src/server/utils/file.test.ts
+++ b/src/server/utils/file.test.ts
@@ -33,6 +33,9 @@ describe('filePathToPath', () => {
     expect(filePathToPath('/articles/(grouped1)/[slug]/(grouped1)/edit')).toBe(
       '/articles/:slug/edit'
     )
+
+    expect(filePathToPath('/(static)/(legal)/privacy')).toBe('/privacy')
+    expect(filePathToPath('/(static)/(legal)/privacy')).not.toBe('//privacy')
   })
 })
 
diff --git a/test-integration/apps.test.ts b/test-integration/apps.test.ts
index c318dd8..24d2b67 100644
--- a/test-integration/apps.test.ts
+++ b/test-integration/apps.test.ts
@@ -982,8 +982,12 @@ describe('Route Groups', () => {
         path: '/blog/hello-world',
         method: 'GET',
       },
+      {
+        path: '/privacy-policy',
+        method: 'GET',
+      },
     ]
-    expect(app.routes).toHaveLength(13)
+    expect(app.routes).toHaveLength(16)
     expect(app.routes).toEqual(
       expect.arrayContaining(
         routes.map(({ path, method }) => {
@@ -1014,4 +1018,17 @@ describe('Route Groups', () => {
       '<!DOCTYPE html><html><head><title></title></head><body><div><h1>Blog</h1><p>Hello World</p></div></body></html>'
     )
   })
+
+  it('Should render /privacy-policy with (content) route group', async () => {
+    const res = await app.request('/privacy-policy')
+    expect(res.status).toBe(200)
+    expect(await res.text()).toBe(
+      '<!DOCTYPE html><html><head><title></title></head><body><h1>Privacy Policy</h1></body></html>'
+    )
+  })
+
+  it('Should render //privacy-policy as a not found', async () => {
+    const res = await app.request('//privacy-policy')
+    expect(res.status).toBe(404)
+  })
 })