From 1ff71407ee39bbf84fe98e903cb999f9098395d5 Mon Sep 17 00:00:00 2001 From: Bobbie Goede Date: Thu, 21 Dec 2023 18:18:03 +0100 Subject: [PATCH] feat: change runtime config using ipc signal --- examples/module/test/basic.test.ts | 40 ++++++++++++++----- examples/module/test/fixtures/basic/app.vue | 6 ++- .../fixtures/basic/plugins/ipc-listener.ts | 30 ++++++++++++++ .../test/fixtures/basic/server/api/config.ts | 4 ++ src/core/runtime-config.ts | 28 ++++++++++--- 5 files changed, 91 insertions(+), 17 deletions(-) create mode 100644 examples/module/test/fixtures/basic/plugins/ipc-listener.ts create mode 100644 examples/module/test/fixtures/basic/server/api/config.ts diff --git a/examples/module/test/basic.test.ts b/examples/module/test/basic.test.ts index 2e66023545..a1ffd5ba60 100644 --- a/examples/module/test/basic.test.ts +++ b/examples/module/test/basic.test.ts @@ -1,26 +1,48 @@ import { fileURLToPath } from 'node:url' import { describe, expect, it } from 'vitest' -import { $fetch, setRuntimeConfig, setup } from '@nuxt/test-utils/e2e' +import { $fetch, getBrowser, setRuntimeConfig, setup, url } from '@nuxt/test-utils/e2e' describe('ssr', async () => { await setup({ rootDir: fileURLToPath(new URL('./fixtures/basic', import.meta.url)), + browser: true, }) it('renders the index page', async () => { // Get response to a server-rendered page with `$fetch`. const html = await $fetch('/') - expect(html).toContain('
basic original value
') + expect(html).toContain('original value') }) - it('changes runtime config and restarts', async () => { - const restoreConfig = await setRuntimeConfig({ public: { myValue: 'overwritten by test!' } }) + it('changes runtime config client-side', async () => { + const browser = await getBrowser() + const page = await browser.newPage() + await page.goto(url('/')) - const html = await $fetch('/') - expect(html).toContain('
basic overwritten by test!
') + const el = page.locator('#runtime') + expect(await el.innerText()).to.equal('original value') + + await page.evaluate(() => { + window.__NUXT_TEST_RUNTIME_CONFIG_SETTER__({ public: { myValue: 'overwritten by test!' } }) + }) + + expect(await el.innerText()).to.equal('overwritten by test!') + }) + + it('changes runtime config in server route', async () => { + const originalConfig = await $fetch('/api/config') + expect(originalConfig.public.myValue).to.equal('original value') - await restoreConfig() - const htmlRestored = await $fetch('/') - expect(htmlRestored).toContain('
basic original value
') + await setRuntimeConfig({ public: { myValue: 'overwritten by test!' } }) + + const newConfig = await $fetch('/api/config') + expect(newConfig.public.myValue).to.equal('overwritten by test!') + }) + + it('changes runtime config', async () => { + await setRuntimeConfig({ public: { myValue: 'overwritten by test!' } }) + + const html = await $fetch('/') + expect(html).toContain('overwritten by test!') }) }) diff --git a/examples/module/test/fixtures/basic/app.vue b/examples/module/test/fixtures/basic/app.vue index 0e4c395fe9..22f0dbf4af 100644 --- a/examples/module/test/fixtures/basic/app.vue +++ b/examples/module/test/fixtures/basic/app.vue @@ -1,7 +1,9 @@ diff --git a/examples/module/test/fixtures/basic/plugins/ipc-listener.ts b/examples/module/test/fixtures/basic/plugins/ipc-listener.ts new file mode 100644 index 0000000000..b1bcd800b3 --- /dev/null +++ b/examples/module/test/fixtures/basic/plugins/ipc-listener.ts @@ -0,0 +1,30 @@ +import defu from 'defu' +import { defineNuxtPlugin } from 'nuxt/app' + +declare global { + interface Window { + __NUXT_TEST_RUNTIME_CONFIG_SETTER__: (env: { public: Record }) => void + } +} + +export default defineNuxtPlugin(() => { + const config = useRuntimeConfig() + + if (process.client) { + window.__NUXT_TEST_RUNTIME_CONFIG_SETTER__ ??= (env: { public: Record }) => { + config.public = defu(env.public, config.public) + } + } + + if (process.server) { + process.on('message', (msg: { type: string; value: Record }) => { + if (msg.type === 'update:runtime-config') { + for (const [key, value] of Object.entries(msg.value)) { + process.env[key] = value + } + + process!.send!({ type: 'confirm:runtime-config' }) + } + }) + } +}) diff --git a/examples/module/test/fixtures/basic/server/api/config.ts b/examples/module/test/fixtures/basic/server/api/config.ts new file mode 100644 index 0000000000..f07457e242 --- /dev/null +++ b/examples/module/test/fixtures/basic/server/api/config.ts @@ -0,0 +1,4 @@ +export default defineEventHandler(async (event) => { + const config = useRuntimeConfig(event) + return config +}) diff --git a/src/core/runtime-config.ts b/src/core/runtime-config.ts index ba4a9ccdd7..a95a90c079 100644 --- a/src/core/runtime-config.ts +++ b/src/core/runtime-config.ts @@ -1,5 +1,5 @@ import { snakeCase } from 'scule' -import { startServer } from './server' +import { useTestContext } from './context' export function flattenObject(obj: Record = {}) { const flattened: Record = {} @@ -26,20 +26,36 @@ export function flattenObject(obj: Record = {}) { export function convertObjectToConfig(obj: Record, envPrefix: string) { const makeEnvKey = (str: string) => `${envPrefix}${snakeCase(str).toUpperCase()}` - + const env: Record = {} const flattened = flattenObject(obj) for (const key in flattened) { env[makeEnvKey(key)] = flattened[key] } - + return env } export async function setRuntimeConfig(config: Record, envPrefix = 'NUXT_') { const env = convertObjectToConfig(config, envPrefix) - await startServer({ env }) + const ctx = useTestContext() + + let updatedConfig = false + ctx.serverProcess?.once('message', (msg: { type: string }) => { + if (msg.type === 'confirm:runtime-config') { + updatedConfig = true + } + }) - // restore - return async () => startServer() + ctx.serverProcess?.send({ type: 'update:runtime-config', value: env }) + + // Wait for confirmation to ensure + for (let i = 0; i < 10; i++) { + if (updatedConfig) break + await new Promise((resolve) => setTimeout(resolve, 1000)) + } + + if (!updatedConfig) { + throw new Error('Missing confirmation of runtime config update!') + } }