Skip to content

Commit bb5fee2

Browse files
authored
test: set runtime config using IPC signal (#3572)
1 parent b4b7329 commit bb5fee2

12 files changed

+153
-81
lines changed

knip.jsonc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"src/runtime/{plugins,components,composables}/**",
99
"src/runtime/server/**",
1010
"src/runtime/kit/**",
11-
"scripts/*"
11+
"scripts/*",
12+
"specs/utils/nitro-plugin.ts"
1213
],
1314
"ignoreUnresolved": [
1415
// virtuals

specs/basic-usage-tests.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,12 @@ export function basicUsageTests() {
157157

158158
expect(await getText(page, '#runtime-config')).toEqual('Hello from runtime config!')
159159

160-
const restore = await startServerWithRuntimeConfig({
160+
await startServerWithRuntimeConfig({
161161
public: { runtimeValue: 'The environment variable has changed!' }
162162
})
163163

164164
await gotoPath(page, '/')
165165
expect(await getText(page, '#runtime-config')).toEqual('The environment variable has changed!')
166-
167-
await restore()
168166
})
169167

170168
test('layer provides locale `nl` and translation for key `hello`', async () => {
@@ -389,7 +387,7 @@ export function basicUsageTests() {
389387
test('render seo tags with baseUrl', async () => {
390388
const configDomain = 'https://runtime-config-domain.com'
391389

392-
const restore = await startServerWithRuntimeConfig({
390+
await startServerWithRuntimeConfig({
393391
public: {
394392
i18n: {
395393
baseUrl: configDomain
@@ -407,8 +405,6 @@ export function basicUsageTests() {
407405
expect(dom.querySelector('#i18n-alt-fr')?.getAttribute('href')).toEqual(
408406
'https://runtime-config-domain.com/fr?canonical='
409407
)
410-
411-
await restore()
412408
})
413409

414410
test('render seo tags with `experimental.alternateLinkCanonicalQueries`', async () => {
@@ -534,15 +530,13 @@ export function basicUsageTests() {
534530
})
535531

536532
test('(#2000) Should be able to load large vue-i18n messages', async () => {
537-
const restore = await startServerWithRuntimeConfig({
533+
await startServerWithRuntimeConfig({
538534
public: { longTextTest: true }
539535
})
540536

541537
const { page } = await renderPage('/nl/long-text')
542538

543539
expect(await getText(page, '#long-text')).toEqual('hallo,'.repeat(8 * 500))
544-
545-
await restore()
546540
})
547541

548542
test('(#2094) vue-i18n messages are loaded from config exported as variable', async () => {

specs/basic_usage.spec.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,17 @@ describe('basic usage', async () => {
3232
describe('language switching', async () => {
3333
beforeAll(async () => {
3434
setTestContext(ctx)
35-
await startServerWithRuntimeConfig({
36-
public: {
37-
i18n: {
38-
skipSettingLocaleOnNavigate: true,
39-
detectBrowserLanguage: false
35+
await startServerWithRuntimeConfig(
36+
{
37+
public: {
38+
i18n: {
39+
skipSettingLocaleOnNavigate: true,
40+
detectBrowserLanguage: false
41+
}
4042
}
41-
}
42-
})
43+
},
44+
true
45+
)
4346
})
4447

4548
languageSwitchingTests()

specs/basic_usage_compat_4.spec.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,17 @@ describe('basic usage - compatibilityVersion: 4', async () => {
3232
describe('language switching', async () => {
3333
beforeAll(async () => {
3434
setTestContext(ctx)
35-
await startServerWithRuntimeConfig({
36-
public: {
37-
i18n: {
38-
skipSettingLocaleOnNavigate: true,
39-
detectBrowserLanguage: false
35+
await startServerWithRuntimeConfig(
36+
{
37+
public: {
38+
i18n: {
39+
skipSettingLocaleOnNavigate: true,
40+
detectBrowserLanguage: false
41+
}
4042
}
41-
}
42-
})
43+
},
44+
true
45+
)
4346
})
4447

4548
languageSwitchingTests()

specs/browser_language_detection/prefix_except_default.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ await setup({
2121

2222
describe('`detectBrowserLanguage` using strategy `prefix_except_default`', async () => {
2323
test('(#2262) redirect using browser cookie with `alwaysRedirect: true`', async () => {
24-
const restore = await startServerWithRuntimeConfig({
24+
await startServerWithRuntimeConfig({
2525
public: {
2626
i18n: {
2727
detectBrowserLanguage: {
@@ -52,8 +52,6 @@ describe('`detectBrowserLanguage` using strategy `prefix_except_default`', async
5252
await page.locator('#nuxt-locale-link-en').click()
5353
expect(await getText(page, '#lang-switcher-current-locale code')).toEqual('en')
5454
expect(await ctx.cookies()).toMatchObject([{ name: 'i18n_redirected', value: 'en' }])
55-
56-
await restore()
5755
})
5856

5957
describe('(#2255) detect browser language and redirect on root', async () => {

specs/helper.ts

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// @ts-ignore
22
import createJITI from 'jiti'
33
import { JSDOM } from 'jsdom'
4-
import { getBrowser, startServer, url, useTestContext } from './utils'
4+
import { getBrowser, TestContext, url, useTestContext } from './utils'
55
import { snakeCase } from 'scule'
66
import { resolveAlias } from '@nuxt/kit'
7+
import { onTestFinished } from 'vitest'
78

89
import { errors, type BrowserContextOptions, type Page } from 'playwright-core'
910

@@ -149,42 +150,42 @@ export async function waitForURL(page: Page, path: string) {
149150
}
150151
}
151152

152-
function flattenObject(obj: Record<string, unknown> = {}) {
153-
const flattened: Record<string, unknown> = {}
154-
155-
for (const key of Object.keys(obj)) {
156-
const entry = obj[key]
157-
158-
if (typeof entry !== 'object' || entry == null) {
159-
flattened[key] = obj[key]
160-
continue
153+
async function updateProcessRuntimeConfig(ctx: TestContext, config: unknown) {
154+
const updated = new Promise<unknown>(resolve => {
155+
const handler = (msg: { type: string; value: unknown }) => {
156+
if (msg.type === 'confirm:runtime-config') {
157+
ctx.serverProcess!.process?.off('message', handler)
158+
resolve(msg.value)
159+
}
161160
}
161+
ctx.serverProcess!.process?.on('message', handler)
162+
})
162163

163-
const flatObject = flattenObject(entry as Record<string, unknown>)
164-
for (const x of Object.keys(flatObject)) {
165-
flattened[key + '_' + x] = flatObject[x]
166-
}
167-
}
164+
ctx.serverProcess!.process?.send({ type: 'update:runtime-config', value: config }, undefined, {
165+
keepOpen: true
166+
})
168167

169-
return flattened
168+
return await updated
170169
}
171170

172-
function convertObjectToConfig(obj: Record<string, unknown>) {
173-
const makeEnvKey = (str: string) => `NUXT_${snakeCase(str).toUpperCase()}`
171+
export async function startServerWithRuntimeConfig(env: Record<string, unknown>, skipRestore = false) {
172+
const ctx = useTestContext()
173+
174+
const stored = await updateProcessRuntimeConfig(ctx, env)
174175

175-
const env: Record<string, unknown> = {}
176-
const flattened = flattenObject(obj)
177-
for (const key in flattened) {
178-
env[makeEnvKey(key)] = flattened[key]
176+
let restored = false
177+
const restoreFn = async () => {
178+
if (restored) return
179+
180+
restored = true
181+
await await updateProcessRuntimeConfig(ctx, stored)
179182
}
180183

181-
return env
182-
}
184+
if (!skipRestore) {
185+
onTestFinished(restoreFn)
186+
}
183187

184-
export async function startServerWithRuntimeConfig(env: Record<string, unknown>) {
185-
const converted = convertObjectToConfig(env)
186-
await startServer(converted)
187-
return async () => startServer()
188+
return restoreFn
188189
}
189190

190191
export async function localeLoaderHelpers() {

specs/routing_strategies/no_prefix.spec.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@ await setup({
3030

3131
describe('strategy: no_prefix', async () => {
3232
beforeAll(async () => {
33-
await startServerWithRuntimeConfig({
34-
public: {
35-
i18n: { detectBrowserLanguage: false }
36-
}
37-
})
33+
await startServerWithRuntimeConfig(
34+
{
35+
public: {
36+
i18n: { detectBrowserLanguage: false }
37+
}
38+
},
39+
true
40+
)
3841
})
3942

4043
test('cannot access with locale prefix: /ja', async () => {

specs/routing_strategies/prefix.spec.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@ await setup({
2222
describe('strategy: prefix', async () => {
2323
beforeEach(async () => {
2424
// use original fixture `detectBrowserLanguage` value as default for tests, overwrite here needed
25-
await startServerWithRuntimeConfig({
26-
public: {
27-
i18n: { detectBrowserLanguage: false }
28-
}
29-
})
25+
await startServerWithRuntimeConfig(
26+
{
27+
public: {
28+
i18n: { detectBrowserLanguage: false }
29+
}
30+
},
31+
true
32+
)
3033
})
3134

3235
test.each([
@@ -120,7 +123,7 @@ describe('strategy: prefix', async () => {
120123
})
121124

122125
test('(#1889) navigation to page with `defineI18nRoute(false)`', async () => {
123-
const restore = await startServerWithRuntimeConfig({
126+
await startServerWithRuntimeConfig({
124127
public: {
125128
i18n: {
126129
detectBrowserLanguage: {
@@ -153,8 +156,6 @@ describe('strategy: prefix', async () => {
153156
// does not redirect to prefixed route for routes with disabled localization
154157
await page.goto(url('/ignore-routes/disable'))
155158
await waitForURL(page, '/ignore-routes/disable')
156-
157-
await restore()
158159
})
159160

160161
test('should not transform `defineI18nRoute()` inside template', async () => {
@@ -165,7 +166,7 @@ describe('strategy: prefix', async () => {
165166
})
166167

167168
test("(#2132) should redirect on root url with `redirectOn: 'no prefix'`", async () => {
168-
const restore = await startServerWithRuntimeConfig({
169+
await startServerWithRuntimeConfig({
169170
public: {
170171
i18n: {
171172
detectBrowserLanguage: {
@@ -183,8 +184,6 @@ describe('strategy: prefix', async () => {
183184

184185
await gotoPath(page, '/en')
185186
expect(await getText(page, '#home-header')).toEqual('Homepage')
186-
187-
await restore()
188187
})
189188

190189
test('(#2020) pass query parameter', async () => {

specs/routing_strategies/root_redirect.spec.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ await setup({
1919

2020
describe('rootRedirect', async () => {
2121
test('can redirect to rootRedirect option path', async () => {
22-
const restore = await startServerWithRuntimeConfig({
22+
await startServerWithRuntimeConfig({
2323
public: {
2424
i18n: {
2525
rootRedirect: 'fr'
@@ -29,12 +29,10 @@ describe('rootRedirect', async () => {
2929

3030
const res = await fetch('/')
3131
expect(res.url).toBe(url('/fr'))
32-
33-
await restore()
3432
})
3533

3634
test('(#2758) `statusCode` in `rootRedirect` should work with strategy "prefix"', async () => {
37-
const restore = await startServerWithRuntimeConfig({
35+
await startServerWithRuntimeConfig({
3836
public: {
3937
i18n: {
4038
rootRedirect: { statusCode: 418, path: 'test-route' }
@@ -45,7 +43,5 @@ describe('rootRedirect', async () => {
4543
const res = await fetch(url('/'))
4644
expect(res.status).toEqual(418)
4745
expect(res.headers.get('location')).toEqual('/en/test-route')
48-
49-
await restore()
5046
})
5147
})

specs/utils/nitro-plugin.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { defineNitroPlugin, useRuntimeConfig } from 'nitropack/runtime'
2+
import { snakeCase } from 'scule'
3+
4+
function flattenObject(obj: Record<string, unknown> = {}) {
5+
const flattened: Record<string, unknown> = {}
6+
7+
for (const key of Object.keys(obj)) {
8+
const entry = obj[key]
9+
10+
if (typeof entry !== 'object' || entry == null) {
11+
flattened[key] = obj[key]
12+
continue
13+
}
14+
15+
const flatObject = flattenObject(entry as Record<string, unknown>)
16+
for (const x of Object.keys(flatObject)) {
17+
flattened[key + '_' + x] = flatObject[x]
18+
}
19+
}
20+
21+
return flattened
22+
}
23+
24+
export function convertObjectToConfig(obj: Record<string, unknown>) {
25+
const makeEnvKey = (str: string) => `NUXT_${snakeCase(str).toUpperCase()}`
26+
27+
const env: Record<string, unknown> = {}
28+
const flattened = flattenObject(obj)
29+
for (const key in flattened) {
30+
env[makeEnvKey(key)] = flattened[key]
31+
}
32+
33+
return env
34+
}
35+
36+
export default defineNitroPlugin(async nitroApp => {
37+
const config = useRuntimeConfig()
38+
const tempKeys = new Set<string>()
39+
40+
const handler = (msg: { type: string; value: Record<string, string> }) => {
41+
if (msg.type !== 'update:runtime-config') return
42+
43+
// cleanup temporary keys
44+
for (const k of tempKeys) {
45+
delete process.env[k]
46+
}
47+
48+
// flatten object and use env variable keys
49+
const envConfig = convertObjectToConfig(msg.value)
50+
for (const [k, val] of Object.entries(envConfig)) {
51+
// collect keys which are newly introduced to cleanup later
52+
if (k in process.env === false) {
53+
tempKeys.add(k)
54+
}
55+
56+
// @ts-expect-error untyped
57+
process.env[k] = val
58+
}
59+
60+
process.send!({ type: 'confirm:runtime-config', value: config }, undefined, {
61+
keepOpen: true
62+
})
63+
}
64+
65+
process.on('message', handler)
66+
67+
nitroApp.hooks.hook('close', () => process.off('message', handler))
68+
})

0 commit comments

Comments
 (0)