diff --git a/contributors.yml b/contributors.yml index bc025058fe8..dcf809c2ed9 100644 --- a/contributors.yml +++ b/contributors.yml @@ -343,6 +343,7 @@ - philandstuff - phishy - plastic041 +- pokedotdev - plondon - princerajroy - prvnbist diff --git a/integration/multiple-pathless-routes-test.ts b/integration/multiple-pathless-routes-test.ts new file mode 100644 index 00000000000..daa760c688d --- /dev/null +++ b/integration/multiple-pathless-routes-test.ts @@ -0,0 +1,91 @@ +import { test, expect } from "@playwright/test"; + +import type { Fixture, AppFixture } from "./helpers/create-fixture"; +import { createAppFixture, createFixture, js } from "./helpers/create-fixture"; + +let fixture: Fixture; +let appFixture: AppFixture; + +const TEXTS = { + SLUG_LAYOUT: "Layout: slug", + FOO_LAYOUT: "Layout: foo", + FOO_CHILD: "deep foo", + SLUG_CHILD: "Slug index from foo layout", + BAR_LAYOUT: "Layout: bar", + BAR_CHILD: "deep bar", +} as const + +test.beforeAll(async () => { + fixture = await createFixture({ + files: { + "app/routes/slug.jsx": js` + import { Outlet } from "@remix-run/react"; + export default () => { + return ( +
+

${TEXTS.SLUG_LAYOUT}

+ +
+ ) + } + `, + "app/routes/slug/__foo.jsx": js` + import { Outlet } from "@remix-run/react"; + export default () => { + return ( +
+

${TEXTS.FOO_LAYOUT}

+ +
+ ) + } + `, + "app/routes/slug/__foo/foo.jsx": js` + export default () =>

${TEXTS.FOO_CHILD}

; + `, + "app/routes/slug/__foo/index.jsx": js` + export default () =>

${TEXTS.SLUG_CHILD}

; + `, + "app/routes/slug/__bar.jsx": js` + import { Outlet } from "@remix-run/react"; + export default () => { + return ( +
+

${TEXTS.BAR_LAYOUT}

+ +
+ ) + } + `, + "app/routes/slug/__bar/bar.jsx": js` + export default () =>

${TEXTS.BAR_CHILD}

; + `, + }, + }); + + appFixture = await createAppFixture(fixture); +}); + +test.afterAll(async () => appFixture.close()); + +test("slug index & foo layout", async () => { + let res = await fixture.requestDocument("/slug"); + let text = await res.text(); + expect(text).toMatch(TEXTS.SLUG_LAYOUT); + expect(text).toMatch(TEXTS.FOO_LAYOUT); + expect(text).toMatch(TEXTS.SLUG_CHILD); +}); + +test("foo layout", async () => { + let res = await fixture.requestDocument("/slug/foo"); + let text = await res.text(); + expect(text).toMatch(TEXTS.FOO_LAYOUT); + expect(text).toMatch(TEXTS.FOO_CHILD); +}); + +test("bar layout", async () => { + let res = await fixture.requestDocument("/slug/bar"); + let text = await res.text(); + expect(text).toMatch(TEXTS.BAR_LAYOUT); + expect(text).toMatch(TEXTS.BAR_CHILD); +}); diff --git a/packages/remix-dev/config/routesConvention.ts b/packages/remix-dev/config/routesConvention.ts index 4fb5fc3211e..89af8517104 100644 --- a/packages/remix-dev/config/routesConvention.ts +++ b/packages/remix-dev/config/routesConvention.ts @@ -62,13 +62,19 @@ export function defineConventionalRoutes( ); for (let routeId of childRouteIds) { - let routePath: string | undefined = createRoutePath( - routeId.slice((parentId || "routes").length + 1) - ); + let lastSegment = routeId.slice((parentId || "routes").length + 1); + let routePath: string | undefined = createRoutePath(lastSegment); - let isIndexRoute = routeId.endsWith("/index"); + let isIndexRoute = lastSegment === "index"; + let isLayoutRoute = lastSegment.startsWith("__"); let fullPath = createRoutePath(routeId.slice("routes".length + 1)); - let uniqueRouteId = (fullPath || "") + (isIndexRoute ? "?index" : ""); + let uniqueRouteId = fullPath || ""; + + if (isIndexRoute) { + uniqueRouteId += "?index"; + } else if (isLayoutRoute) { + uniqueRouteId += lastSegment; + } if (uniqueRouteId) { if (uniqueRoutes.has(uniqueRouteId)) {