From 002af4eb3a9b64e1013a13f23446e21dcca16849 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 14 Dec 2023 13:23:55 +0100 Subject: [PATCH] Add test for importing client components from server actions (#59615) ## What? Adds two tests for server actions returning client components: - (supported) Importing a server action from a server component. That server action imports a client component and returns it. - (not supported yet) Importing a server action from a client component. The server action imports a client component and returns it. The second case is not supported yet as it would effectively mean a compilation loop: `server` -> `client` -> `server` -> `client` and if that last client component includes another server action it goes even further: `server` -> `client` -> `server` -> `client` -> `server` (and if that server action includes anothe client component it goes further and further) Whereas currently it's only `server` -> `client` -> `server`, so it's limited to that. In the future we should be able to support this server->client->server->client loop in Turbopack specifically because Turbopack has a single module graph. Importing the client component in a server action that is defined in the server compiler (i.e. when created inline or when imported from a server component) it does work correctly already. Closes NEXT-1873 --- test/e2e/app-dir/actions/app-action.test.ts | 33 +++++++++++++++++++ .../action-return-client-component/actions.js | 8 +++++ .../client-component.js | 5 +++ .../action-return-client-component/form.js | 16 +++++++++ .../action-return-client-component/page.js | 12 +++++++ .../action-return-client-component/actions.js | 8 +++++ .../client-component.js | 5 +++ .../action-return-client-component/form.js | 16 +++++++++ .../action-return-client-component/page.js | 11 +++++++ 9 files changed, 114 insertions(+) create mode 100644 test/e2e/app-dir/actions/app/client/action-return-client-component/actions.js create mode 100644 test/e2e/app-dir/actions/app/client/action-return-client-component/client-component.js create mode 100644 test/e2e/app-dir/actions/app/client/action-return-client-component/form.js create mode 100644 test/e2e/app-dir/actions/app/client/action-return-client-component/page.js create mode 100644 test/e2e/app-dir/actions/app/server/action-return-client-component/actions.js create mode 100644 test/e2e/app-dir/actions/app/server/action-return-client-component/client-component.js create mode 100644 test/e2e/app-dir/actions/app/server/action-return-client-component/form.js create mode 100644 test/e2e/app-dir/actions/app/server/action-return-client-component/page.js diff --git a/test/e2e/app-dir/actions/app-action.test.ts b/test/e2e/app-dir/actions/app-action.test.ts index 40b1de3ca1..b4698bf084 100644 --- a/test/e2e/app-dir/actions/app-action.test.ts +++ b/test/e2e/app-dir/actions/app-action.test.ts @@ -1081,5 +1081,38 @@ createNextDescribe( } ) }) + + describe('server actions render client components', () => { + describe('server component imported action', () => { + it('should support importing client components from actions', async () => { + const browser = await next.browser( + '/server/action-return-client-component' + ) + expect( + await browser + .elementByCss('#trigger-component-load') + .click() + .waitForElementByCss('#client-component') + .text() + ).toBe('Hello World') + }) + }) + + // Server Component -> Client Component -> Server Action (imported from client component) -> Import Client Component is not not supported yet. + describe.skip('client component imported action', () => { + it('should support importing client components from actions', async () => { + const browser = await next.browser( + '/client/action-return-client-component' + ) + expect( + await browser + .elementByCss('#trigger-component-load') + .click() + .waitForElementByCss('#client-component') + .text() + ).toBe('Hello World') + }) + }) + }) } ) diff --git a/test/e2e/app-dir/actions/app/client/action-return-client-component/actions.js b/test/e2e/app-dir/actions/app/client/action-return-client-component/actions.js new file mode 100644 index 0000000000..20f32bf225 --- /dev/null +++ b/test/e2e/app-dir/actions/app/client/action-return-client-component/actions.js @@ -0,0 +1,8 @@ +'use server' +import { Hello } from './client-component' + +export async function getComponent() { + return { + component: , + } +} diff --git a/test/e2e/app-dir/actions/app/client/action-return-client-component/client-component.js b/test/e2e/app-dir/actions/app/client/action-return-client-component/client-component.js new file mode 100644 index 0000000000..1eb8f26333 --- /dev/null +++ b/test/e2e/app-dir/actions/app/client/action-return-client-component/client-component.js @@ -0,0 +1,5 @@ +'use client' + +export function Hello() { + return

Hello World

+} diff --git a/test/e2e/app-dir/actions/app/client/action-return-client-component/form.js b/test/e2e/app-dir/actions/app/client/action-return-client-component/form.js new file mode 100644 index 0000000000..a37de24565 --- /dev/null +++ b/test/e2e/app-dir/actions/app/client/action-return-client-component/form.js @@ -0,0 +1,16 @@ +'use client' +import { useFormState } from 'react-dom' + +export function Form({ action }) { + const [state, formAction] = useFormState(action, null) + return ( + <> +
+ +
+ {state?.component} + + ) +} diff --git a/test/e2e/app-dir/actions/app/client/action-return-client-component/page.js b/test/e2e/app-dir/actions/app/client/action-return-client-component/page.js new file mode 100644 index 0000000000..ae5cfe7630 --- /dev/null +++ b/test/e2e/app-dir/actions/app/client/action-return-client-component/page.js @@ -0,0 +1,12 @@ +'use client' +import { getComponent } from './actions' +import { Form } from './form' + +export default function Page() { + return ( + <> +

Server Component loading client component through action

+
+ + ) +} diff --git a/test/e2e/app-dir/actions/app/server/action-return-client-component/actions.js b/test/e2e/app-dir/actions/app/server/action-return-client-component/actions.js new file mode 100644 index 0000000000..20f32bf225 --- /dev/null +++ b/test/e2e/app-dir/actions/app/server/action-return-client-component/actions.js @@ -0,0 +1,8 @@ +'use server' +import { Hello } from './client-component' + +export async function getComponent() { + return { + component: , + } +} diff --git a/test/e2e/app-dir/actions/app/server/action-return-client-component/client-component.js b/test/e2e/app-dir/actions/app/server/action-return-client-component/client-component.js new file mode 100644 index 0000000000..1eb8f26333 --- /dev/null +++ b/test/e2e/app-dir/actions/app/server/action-return-client-component/client-component.js @@ -0,0 +1,5 @@ +'use client' + +export function Hello() { + return

Hello World

+} diff --git a/test/e2e/app-dir/actions/app/server/action-return-client-component/form.js b/test/e2e/app-dir/actions/app/server/action-return-client-component/form.js new file mode 100644 index 0000000000..a37de24565 --- /dev/null +++ b/test/e2e/app-dir/actions/app/server/action-return-client-component/form.js @@ -0,0 +1,16 @@ +'use client' +import { useFormState } from 'react-dom' + +export function Form({ action }) { + const [state, formAction] = useFormState(action, null) + return ( + <> + + + + {state?.component} + + ) +} diff --git a/test/e2e/app-dir/actions/app/server/action-return-client-component/page.js b/test/e2e/app-dir/actions/app/server/action-return-client-component/page.js new file mode 100644 index 0000000000..bbbd568968 --- /dev/null +++ b/test/e2e/app-dir/actions/app/server/action-return-client-component/page.js @@ -0,0 +1,11 @@ +import { getComponent } from './actions' +import { Form } from './form' + +export default function Page() { + return ( + <> +

Server Component loading client component through action

+
+ + ) +}