Skip to content

Commit

Permalink
test: Add multitenancy test for Next.js (#917)
Browse files Browse the repository at this point in the history
  • Loading branch information
franky47 authored Feb 17, 2025
1 parent 48cae6b commit b71a222
Show file tree
Hide file tree
Showing 63 changed files with 1,262 additions and 71 deletions.
68 changes: 68 additions & 0 deletions packages/e2e/next/cypress/e2e/multitenant.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { createTest, type TestConfig } from 'e2e-shared/create-test'
import { getOptionsUrl } from 'e2e-shared/lib/options'

function testMultiTenant(
options: TestConfig & {
expectedPathname: string
}
) {
const factory = createTest('Multitenant', ({ path }) => {
for (const shallow of [true, false]) {
for (const history of ['replace', 'push'] as const) {
it(`Updates with ({ shallow: ${shallow}, history: ${history} })`, () => {
cy.visit(getOptionsUrl(path, { shallow, history }))
cy.contains('#hydration-marker', 'hydrated').should('be.hidden')
cy.get('#client-state').should('be.empty')
cy.get('#server-state').should('be.empty')
cy.get('#client-tenant').should('have.text', 'david')
cy.get('#server-tenant').should('have.text', 'david')
cy.get('#router-pathname').should(
'have.text',
options.expectedPathname
)
cy.get('button').click()
cy.get('#client-state').should('have.text', 'pass')
cy.get('#client-tenant').should('have.text', 'david')
cy.get('#server-tenant').should('have.text', 'david')
cy.get('#router-pathname').should(
'have.text',
options.expectedPathname
)
if (shallow === false) {
cy.get('#server-state').should('have.text', 'pass')
} else {
cy.get('#server-state').should('be.empty')
}
if (history !== 'push') {
return
}
cy.go('back')
cy.get('#client-tenant').should('have.text', 'david')
cy.get('#server-tenant').should('have.text', 'david')
cy.get('#client-state').should('be.empty')
cy.get('#server-state').should('be.empty')
cy.get('#router-pathname').should(
'have.text',
options.expectedPathname
)
})
}
}
})

return factory(options)
}

testMultiTenant({
path: '/app/multitenant',
nextJsRouter: 'app',
description: 'Dynamic route',
expectedPathname: '/app/multitenant'
})

testMultiTenant({
path: '/pages/multitenant',
nextJsRouter: 'pages',
description: 'Dynamic route',
expectedPathname: '/pages/multitenant/[tenant]'
})
77 changes: 77 additions & 0 deletions packages/e2e/next/cypress/e2e/shared/dynamic-segments.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { testDynamicSegments } from 'e2e-shared/specs/dynamic-segments.cy'

testDynamicSegments({
path: '/app/dynamic-segments/dynamic/segment',
expectedSegments: ['segment'],
nextJsRouter: 'app'
})

testDynamicSegments({
path: '/pages/dynamic-segments/dynamic/segment',
expectedSegments: ['segment'],
nextJsRouter: 'pages'
})

// Catch-all --

testDynamicSegments({
path: '/app/dynamic-segments/catch-all/foo',
expectedSegments: ['foo'],
nextJsRouter: 'app'
})

testDynamicSegments({
path: '/pages/dynamic-segments/catch-all/foo',
expectedSegments: ['foo'],
nextJsRouter: 'pages'
})

testDynamicSegments({
path: '/app/dynamic-segments/catch-all/a/b/c',
expectedSegments: ['a', 'b', 'c'],
nextJsRouter: 'app'
})

testDynamicSegments({
path: '/pages/dynamic-segments/catch-all/a/b/c',
expectedSegments: ['a', 'b', 'c'],
nextJsRouter: 'pages'
})

// Optional catch-all --

testDynamicSegments({
path: '/app/dynamic-segments/optional-catch-all', // no segments
expectedSegments: [],
nextJsRouter: 'app'
})

testDynamicSegments({
path: '/pages/dynamic-segments/optional-catch-all', // no segments
expectedSegments: [],
nextJsRouter: 'pages'
})

testDynamicSegments({
path: '/app/dynamic-segments/optional-catch-all/foo',
expectedSegments: ['foo'],
nextJsRouter: 'app'
})

testDynamicSegments({
path: '/pages/dynamic-segments/optional-catch-all/foo',
expectedSegments: ['foo'],
nextJsRouter: 'pages'
})

testDynamicSegments({
path: '/app/dynamic-segments/optional-catch-all/a/b/c',
expectedSegments: ['a', 'b', 'c'],
nextJsRouter: 'app'
})

testDynamicSegments({
path: '/pages/dynamic-segments/optional-catch-all/a/b/c',
expectedSegments: ['a', 'b', 'c'],
nextJsRouter: 'pages'
})
11 changes: 11 additions & 0 deletions packages/e2e/next/cypress/e2e/shared/pretty-urls.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { testPrettyUrls } from 'e2e-shared/specs/pretty-urls.cy'

testPrettyUrls({
path: '/app/pretty-urls',
nextJsRouter: 'app'
})

testPrettyUrls({
path: '/pages/pretty-urls',
nextJsRouter: 'pages'
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client'

import { DisplaySegments } from 'e2e-shared/specs/dynamic-segments'
import { useParams } from 'next/navigation'
import { ReactNode } from 'react'

export function ClientSegment({ children }: { children?: ReactNode }) {
const params = useParams()
const segments = params?.segments as string[]
return (
<>
{children}
<DisplaySegments environment="client" segments={segments} />
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Display } from 'e2e-shared/components/display'
import { DisplaySegments, UrlControls } from 'e2e-shared/specs/dynamic-segments'
import { createLoader, parseAsString, type SearchParams } from 'nuqs/server'
import { Suspense } from 'react'
import { ClientSegment } from './client'

type PageProps = {
params: Promise<{ segments: string[] }>
searchParams: Promise<SearchParams>
}

const loadTest = createLoader({
test: parseAsString
})

export default async function DynamicPage(props: PageProps) {
const searchParams = await props.searchParams
const { segments } = await props.params
const { test: serverState } = loadTest(searchParams)
return (
<>
<Suspense>
<UrlControls>
<Display environment="server" state={serverState} />
</UrlControls>
</Suspense>
<Suspense>
<ClientSegment>
<DisplaySegments environment="server" segments={segments} />
</ClientSegment>
</Suspense>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client'

import { DisplaySegments } from 'e2e-shared/specs/dynamic-segments'
import { useParams } from 'next/navigation'
import { ReactNode } from 'react'

export function ClientSegment({ children }: { children?: ReactNode }) {
const params = useParams()
const segment = params?.segment as string
return (
<>
{children}
<DisplaySegments environment="client" segments={[segment]} />
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Display } from 'e2e-shared/components/display'
import { DisplaySegments, UrlControls } from 'e2e-shared/specs/dynamic-segments'
import { createLoader, parseAsString, type SearchParams } from 'nuqs/server'
import { Suspense } from 'react'
import { ClientSegment } from './client'

type PageProps = {
params: Promise<{ segment: string }>
searchParams: Promise<SearchParams>
}

const loadTest = createLoader({
test: parseAsString
})

export default async function DynamicPage(props: PageProps) {
const searchParams = await props.searchParams
const { segment } = await props.params
const { test: serverState } = loadTest(searchParams)
return (
<>
<Suspense>
<UrlControls>
<Display environment="server" state={serverState} />
</UrlControls>
</Suspense>
<Suspense>
<ClientSegment>
<DisplaySegments environment="server" segments={[segment]} />
</ClientSegment>
</Suspense>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client'

import { DisplaySegments } from 'e2e-shared/specs/dynamic-segments'
import { useParams } from 'next/navigation'
import { ReactNode } from 'react'

export function ClientSegment({ children }: { children?: ReactNode }) {
const params = useParams()
const segments = params?.segments as string[]
return (
<>
{children}
<DisplaySegments environment="client" segments={segments} />
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Display } from 'e2e-shared/components/display'
import { DisplaySegments, UrlControls } from 'e2e-shared/specs/dynamic-segments'
import { createLoader, parseAsString, type SearchParams } from 'nuqs/server'
import { Suspense } from 'react'
import { ClientSegment } from './client'

type PageProps = {
params: Promise<{ segments: string[] }>
searchParams: Promise<SearchParams>
}

const loadTest = createLoader({
test: parseAsString
})

export default async function DynamicPage(props: PageProps) {
const searchParams = await props.searchParams
const { segments } = await props.params
const { test: serverState } = loadTest(searchParams)
return (
<>
<Suspense>
<UrlControls>
<Display environment="server" state={serverState} />
</UrlControls>
</Suspense>
<Suspense>
<ClientSegment>
<DisplaySegments environment="server" segments={segments} />
</ClientSegment>
</Suspense>
</>
)
}
10 changes: 10 additions & 0 deletions packages/e2e/next/src/app/app/(shared)/pretty-urls/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { PrettyUrls } from 'e2e-shared/specs/pretty-urls'
import { Suspense } from 'react'

export default function Page() {
return (
<Suspense>
<PrettyUrls />
</Suspense>
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Display } from 'e2e-shared/components/display'
import { ShallowUseQueryState } from 'e2e-shared/specs/shallow'
import { ShallowDisplay } from 'e2e-shared/specs/shallow-display'
import {
createSearchParamsCache,
parseAsString,
Expand Down Expand Up @@ -29,7 +29,7 @@ export default async function Page({ searchParams }: PageProps) {
<Suspense>
<ShallowUseQueryState />
</Suspense>
<ShallowDisplay environment="server" state={cache.get('state')} />
<Display environment="server" state={cache.get('state')} />
</>
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Display } from 'e2e-shared/components/display'
import { ShallowUseQueryStates } from 'e2e-shared/specs/shallow'
import { ShallowDisplay } from 'e2e-shared/specs/shallow-display'
import {
createSearchParamsCache,
parseAsString,
Expand Down Expand Up @@ -29,7 +29,7 @@ export default async function Page({ searchParams }: PageProps) {
<Suspense>
<ShallowUseQueryStates />
</Suspense>
<ShallowDisplay environment="server" state={cache.get('state')} />
<Display environment="server" state={cache.get('state')} />
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use client'

import { useParams, usePathname } from 'next/navigation'

export function TenantClient() {
const params = useParams()
const pathname = usePathname()
return (
<>
<p id="client-tenant">{params?.tenant}</p>
<p id="router-pathname">{pathname}</p>
</>
)
}
Loading

0 comments on commit b71a222

Please sign in to comment.