Skip to content

Commit fd05580

Browse files
committed
fix(vite): set ssr.noExternal even if not present in project package.json
1 parent f4b1508 commit fd05580

File tree

4 files changed

+235
-1
lines changed

4 files changed

+235
-1
lines changed

src/__tests__/utils.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export const IS_JSDOM = window.navigator.userAgent.includes('jsdom')
44

55
export const IS_HAPPYDOM = !IS_JSDOM // right now it's happy or js
66

7+
export const IS_JEST = Boolean(process.env.JEST_WORKER_ID)
8+
79
export const IS_SVELTE_5 = SVELTE_VERSION >= '5'
810

911
export const MODE_LEGACY = 'legacy'

src/__tests__/vite-plugin.test.js

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import { beforeEach, describe, expect, test, vi } from 'vitest'
2+
3+
import { svelteTesting } from '../vite.js'
4+
import { IS_JEST } from './utils.js'
5+
6+
describe.skipIf(IS_JEST)('vite plugin', () => {
7+
beforeEach(() => {
8+
vi.stubEnv('VITEST', '1')
9+
})
10+
11+
test('does not modify config if disabled', () => {
12+
const subject = svelteTesting({
13+
resolveBrowser: false,
14+
autoCleanup: false,
15+
noExternal: false,
16+
})
17+
18+
const config = {}
19+
subject.config(config)
20+
21+
expect(config).toEqual({})
22+
})
23+
24+
test('does not modify config if not Vitest', () => {
25+
vi.stubEnv('VITEST', '')
26+
27+
const subject = svelteTesting()
28+
const config = {}
29+
30+
subject.config(config)
31+
32+
expect(config).toEqual({})
33+
})
34+
35+
test.each([
36+
{
37+
config: { resolve: { conditions: ['node'] } },
38+
expectedConditions: ['browser', 'node'],
39+
},
40+
{
41+
config: { resolve: { conditions: ['svelte', 'node'] } },
42+
expectedConditions: ['svelte', 'browser', 'node'],
43+
},
44+
])(
45+
'adds browser condition if necessary',
46+
({ config, expectedConditions }) => {
47+
const subject = svelteTesting()
48+
const viteConfig = structuredClone(config)
49+
50+
subject.config(viteConfig)
51+
52+
expect(viteConfig).toMatchObject({
53+
resolve: {
54+
conditions: expectedConditions,
55+
},
56+
})
57+
}
58+
)
59+
60+
test.each([
61+
{
62+
config: {},
63+
expectedConditions: [],
64+
},
65+
{
66+
config: { resolve: { conditions: [] } },
67+
expectedConditions: [],
68+
},
69+
{
70+
config: { resolve: { conditions: ['svelte'] } },
71+
expectedConditions: ['svelte'],
72+
},
73+
])(
74+
'skips browser condition if possible',
75+
({ config, expectedConditions }) => {
76+
const subject = svelteTesting()
77+
const viteConfig = structuredClone(config)
78+
79+
subject.config(viteConfig)
80+
81+
expect(viteConfig).toMatchObject({
82+
resolve: {
83+
conditions: expectedConditions,
84+
},
85+
})
86+
}
87+
)
88+
89+
test.each([
90+
{
91+
config: {},
92+
expectedSetupFiles: [expect.stringMatching(/src\/vitest.js$/u)],
93+
},
94+
{
95+
config: { test: { setupFiles: [] } },
96+
expectedSetupFiles: [expect.stringMatching(/src\/vitest.js$/u)],
97+
},
98+
{
99+
config: { test: { setupFiles: 'other-file.js' } },
100+
expectedSetupFiles: [
101+
'other-file.js',
102+
expect.stringMatching(/src\/vitest.js$/u),
103+
],
104+
},
105+
])('adds cleanup', ({ config, expectedSetupFiles }) => {
106+
const subject = svelteTesting()
107+
const viteConfig = structuredClone(config)
108+
109+
subject.config(viteConfig)
110+
111+
expect(viteConfig).toMatchObject({
112+
test: {
113+
setupFiles: expectedSetupFiles,
114+
},
115+
})
116+
})
117+
118+
test.each([
119+
{
120+
config: { ssr: { noExternal: [] } },
121+
expectedNoExternal: ['@testing-library/svelte'],
122+
},
123+
{
124+
config: {},
125+
expectedNoExternal: ['@testing-library/svelte'],
126+
},
127+
{
128+
config: { ssr: { noExternal: 'other-file.js' } },
129+
expectedNoExternal: ['other-file.js', '@testing-library/svelte'],
130+
},
131+
{
132+
config: { ssr: { noExternal: /other/u } },
133+
expectedNoExternal: [/other/u, '@testing-library/svelte'],
134+
},
135+
])('adds noExternal rule', ({ config, expectedNoExternal }) => {
136+
const subject = svelteTesting()
137+
const viteConfig = structuredClone(config)
138+
139+
subject.config(viteConfig)
140+
141+
expect(viteConfig).toMatchObject({
142+
ssr: {
143+
noExternal: expectedNoExternal,
144+
},
145+
})
146+
})
147+
148+
test.each([
149+
{
150+
config: { ssr: { noExternal: true } },
151+
expectedNoExternal: true,
152+
},
153+
{
154+
config: { ssr: { noExternal: '@testing-library/svelte' } },
155+
expectedNoExternal: '@testing-library/svelte',
156+
},
157+
{
158+
config: { ssr: { noExternal: /svelte/u } },
159+
expectedNoExternal: /svelte/u,
160+
},
161+
])('skips noExternal if able', ({ config, expectedNoExternal }) => {
162+
const subject = svelteTesting()
163+
const viteConfig = structuredClone(config)
164+
165+
subject.config(viteConfig)
166+
167+
expect(viteConfig).toMatchObject({
168+
ssr: {
169+
noExternal: expectedNoExternal,
170+
},
171+
})
172+
})
173+
174+
test('bails on noExternal if input is unexpected', () => {
175+
const subject = svelteTesting()
176+
const viteConfig = structuredClone({ ssr: { noExternal: false } })
177+
178+
subject.config(viteConfig)
179+
180+
expect(viteConfig).toMatchObject({
181+
ssr: {
182+
noExternal: false,
183+
},
184+
})
185+
})
186+
})

src/vite.js

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ import { fileURLToPath } from 'node:url'
77
* Ensures Svelte is imported correctly in tests
88
* and that the DOM is cleaned up after each test.
99
*
10-
* @param {{resolveBrowser?: boolean, autoCleanup?: boolean}} options
10+
* @param {{resolveBrowser?: boolean, autoCleanup?: boolean, noExternal?: boolean}} options
1111
* @returns {import('vite').Plugin}
1212
*/
1313
export const svelteTesting = ({
1414
resolveBrowser = true,
1515
autoCleanup = true,
16+
noExternal = true,
1617
} = {}) => ({
1718
name: 'vite-plugin-svelte-testing-library',
1819
config: (config) => {
@@ -27,6 +28,10 @@ export const svelteTesting = ({
2728
if (autoCleanup) {
2829
addAutoCleanup(config)
2930
}
31+
32+
if (noExternal) {
33+
addNoExternal(config)
34+
}
3035
},
3136
})
3237

@@ -73,3 +78,43 @@ const addAutoCleanup = (config) => {
7378
test.setupFiles = setupFiles
7479
config.test = test
7580
}
81+
82+
/**
83+
* Add `@testing-library/svelte` to Vite's noExternal rules, if not present.
84+
*
85+
* This ensures `@testing-library/svelte` is processed by `@sveltejs/vite-plugin-svelte`
86+
* in certain monorepo setups.
87+
*/
88+
const addNoExternal = (config) => {
89+
const ssr = config.ssr ?? {}
90+
let noExternal = ssr.noExternal ?? []
91+
92+
if (noExternal === true) {
93+
return
94+
}
95+
96+
if (typeof noExternal === 'string' || noExternal instanceof RegExp) {
97+
noExternal = [noExternal]
98+
}
99+
100+
if (!Array.isArray(noExternal)) {
101+
return
102+
}
103+
104+
for (const rule of noExternal) {
105+
if (typeof rule === 'string' && rule === '@testing-library/svelte') {
106+
return
107+
}
108+
109+
if (
110+
noExternal instanceof RegExp &&
111+
noExternal.test('@testing-library/svelte')
112+
) {
113+
return
114+
}
115+
}
116+
117+
noExternal.push('@testing-library/svelte')
118+
ssr.noExternal = noExternal
119+
config.ssr = ssr
120+
}

vite.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export default defineConfig({
1111
setupFiles: ['./src/__tests__/_vitest-setup.js'],
1212
mockReset: true,
1313
unstubGlobals: true,
14+
unstubEnvs: true,
1415
coverage: {
1516
provider: 'v8',
1617
include: ['src/**/*'],

0 commit comments

Comments
 (0)