Skip to content

Commit a2e8363

Browse files
committed
wip: add test mode for build --watch and e2e setup geared towards it. not complete and then needs fix
1 parent 3a6344f commit a2e8363

28 files changed

+656
-70
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
"private": true,
44
"type": "module",
55
"scripts": {
6-
"test": "run-s -c test:unit \"test:build {@}\" \"test:serve {@}\" --",
6+
"test": "run-s -c test:unit \"test:build {@}\" \"test:serve {@}\" \"test:build:watch {@}\" --",
77
"test:unit": "vitest run",
88
"test:serve": "vitest run -c vitest.config.e2e.ts",
99
"test:build": "cross-env TEST_BUILD=1 vitest run -c vitest.config.e2e.ts",
10+
"test:build:watch": "cross-env TEST_BUILD_WATCH=1 vitest run -c vitest.config.e2e.ts",
1011
"check": "run-p -c check:*",
1112
"check:audit": "pnpm audit --prod",
1213
"check:publint": "pnpm --filter \"./packages/*\" --parallel check:publint",

packages/e2e-tests/_test_dependencies/vite-plugins/index.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
import path from 'node:path';
22
import fs from 'node:fs';
3+
import MagicString from 'magic-string';
4+
5+
function replaceWithSourceMap(code, value, replacement) {
6+
const s = new MagicString(code);
7+
s.replaceAll(value, replacement);
8+
return {
9+
code: s.toString(),
10+
map: s.generateMap({ hires: 'boundary' })
11+
};
12+
}
313
/**
414
* Ensure transform flow is not interrupted
515
* @returns {import('vite').Plugin[]}
@@ -11,19 +21,19 @@ export function transformValidation() {
1121
enforce: 'pre',
1222
transform(code, id) {
1323
if (id.endsWith('.svelte')) {
14-
return code.replaceAll('__JS_TRANSFORM_1__', '__JS_TRANSFORM_2__');
24+
return replaceWithSourceMap(code, '__JS_TRANSFORM_1__', '__JS_TRANSFORM_2__');
1525
} else if (id.endsWith('.css')) {
16-
return code.replaceAll('__CSS_TRANSFORM_1__', '__CSS_TRANSFORM_2__');
26+
return replaceWithSourceMap(code, '__CSS_TRANSFORM_1__', '__CSS_TRANSFORM_2__');
1727
}
1828
}
1929
},
2030
{
2131
name: 'transform-validation:2',
2232
transform(code, id) {
2333
if (id.endsWith('.svelte')) {
24-
return code.replaceAll('__JS_TRANSFORM_2__', '__JS_TRANSFORM_3__');
34+
return replaceWithSourceMap(code, '__JS_TRANSFORM_2__', '__JS_TRANSFORM_3__');
2535
} else if (id.endsWith('.css')) {
26-
return code.replaceAll('__CSS_TRANSFORM_2__', 'red');
36+
return replaceWithSourceMap(code, '__CSS_TRANSFORM_2__', 'red');
2737
}
2838
}
2939
},
@@ -32,7 +42,7 @@ export function transformValidation() {
3242
enforce: 'post',
3343
transform(code, id) {
3444
if (id.endsWith('.svelte')) {
35-
return code.replaceAll('__JS_TRANSFORM_3__', 'Hello world');
45+
replaceWithSourceMap(code, '__JS_TRANSFORM_3__', 'Hello world');
3646
}
3747
// can't handle css here as in build, it would be `export default {}`
3848
}

packages/e2e-tests/_test_dependencies/vite-plugins/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@
66
"main": "./index.js",
77
"files": [
88
"index.js"
9-
]
9+
],
10+
"dependencies": {
11+
"magic-string": "^0.30.17"
12+
}
1013
}
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import {
2+
isBuildWatch,
3+
getEl,
4+
getText,
5+
editFileAndWaitForBuildWatchComplete,
6+
hmrCount,
7+
untilMatches,
8+
sleep,
9+
getColor,
10+
editFile,
11+
addFile,
12+
removeFile,
13+
editViteConfig,
14+
browserLogs,
15+
waitForBuildWatchAndPageReload
16+
} from '~utils';
17+
18+
test('should render App', async () => {
19+
expect(await getText('#app-header')).toBe('Test-App');
20+
});
21+
22+
test('should render static import', async () => {
23+
expect(await getText('#static-import .label')).toBe('static-import');
24+
});
25+
26+
test('should render dependency import', async () => {
27+
expect(await getText('#dependency-import .label')).toBe('dependency-import');
28+
});
29+
30+
test('should render dynamic import', async () => {
31+
expect(await getEl('#dynamic-import')).toBe(null);
32+
const dynamicImportButton = await getEl('#button-import-dynamic');
33+
expect(dynamicImportButton).toBeDefined();
34+
await dynamicImportButton.click();
35+
await untilMatches(
36+
() => getText('#dynamic-import .label'),
37+
'dynamic-import',
38+
'dynamic import loaded after click'
39+
);
40+
});
41+
42+
test('should not have failed requests', async () => {
43+
browserLogs.forEach((msg) => {
44+
expect(msg).not.toMatch('404');
45+
});
46+
});
47+
48+
test('should respect transforms', async () => {
49+
expect(await getText('#js-transform')).toBe('Hello world');
50+
expect(await getColor('#css-transform')).toBe('red');
51+
});
52+
53+
if (isBuildWatch) {
54+
describe('build --watch', () => {
55+
const updateHmrTest = editFileAndWaitForBuildWatchComplete.bind(
56+
null,
57+
'src/components/HmrTest.svelte'
58+
);
59+
const updateModuleContext = editFileAndWaitForBuildWatchComplete.bind(
60+
null,
61+
'src/components/partial-hmr/ModuleContext.svelte'
62+
);
63+
const updateApp = editFileAndWaitForBuildWatchComplete.bind(null, 'src/App.svelte');
64+
const updateStore = editFileAndWaitForBuildWatchComplete.bind(null, 'src/stores/hmr-stores.js');
65+
66+
test('should have expected initial state', async () => {
67+
// initial state, both counters 0, both labels red
68+
expect(await getText('#hmr-test-1 .counter')).toBe('0');
69+
expect(await getText('#hmr-test-2 .counter')).toBe('0');
70+
expect(await getText('#hmr-test-1 .label')).toBe('hmr-test');
71+
expect(await getText('#hmr-test-2 .label')).toBe('hmr-test');
72+
expect(await getColor('#hmr-test-1 .label')).toBe('red');
73+
expect(await getColor('#hmr-test-2 .label')).toBe('red');
74+
});
75+
76+
test('should have working increment button', async () => {
77+
// increment counter of one instance to have local state to verify after build updates
78+
await (await getEl('#hmr-test-1 .increment')).click();
79+
await sleep(50);
80+
81+
// counter1 = 1, counter2 = 0
82+
expect(await getText('#hmr-test-1 .counter')).toBe('1');
83+
expect(await getText('#hmr-test-2 .counter')).toBe('0');
84+
});
85+
86+
test('should apply css changes in HmrTest.svelte', async () => {
87+
// update style, change label color from red to green
88+
await updateHmrTest((content) => content.replace('color: red', 'color: green'));
89+
90+
// color should have changed
91+
expect(await getColor('#hmr-test-1 .label')).toBe('green');
92+
expect(await getColor('#hmr-test-2 .label')).toBe('green');
93+
});
94+
95+
test('should apply js change in HmrTest.svelte ', async () => {
96+
// update script, change label value
97+
await updateHmrTest((content) =>
98+
content.replace("const label = 'hmr-test'", "const label = 'hmr-test-updated'")
99+
);
100+
expect(await getText('#hmr-test-1 .label')).toBe('hmr-test-updated');
101+
expect(await getText('#hmr-test-2 .label')).toBe('hmr-test-updated');
102+
});
103+
104+
test('should reset state of external store used by HmrTest.svelte when editing App.svelte', async () => {
105+
// update App, add a new instance of HmrTest
106+
await updateApp((content) =>
107+
content.replace(
108+
'<!-- HMR-TEMPLATE-INJECT -->',
109+
'<HmrTest id="hmr-test-3"/>\n<!-- HMR-TEMPLATE-INJECT -->'
110+
)
111+
);
112+
// counter state is reset
113+
expect(await getText('#hmr-test-1 .counter')).toBe('0');
114+
expect(await getText('#hmr-test-2 .counter')).toBe('0');
115+
// a third instance has been added
116+
expect(await getText('#hmr-test-3 .counter')).toBe('0');
117+
});
118+
119+
test('should reset state of store when editing hmr-stores.js', async () => {
120+
// change state
121+
await (await getEl('#hmr-test-2 .increment')).click();
122+
await sleep(50);
123+
expect(await getText('#hmr-test-2 .counter')).toBe('1');
124+
await updateStore((content) => `${content}\n/*trigger change*/\n`);
125+
// counter state is reset
126+
expect(await getText('#hmr-test-2 .counter')).toBe('0');
127+
});
128+
129+
test('should work when editing script context="module"', async () => {
130+
expect(await getText('#hmr-with-context')).toContain('x=0 y=1 slot=1');
131+
expect(await getText('#hmr-without-context')).toContain('x=0 y=1 slot=');
132+
expect(hmrCount('UsingNamed.svelte'), 'updates for UsingNamed.svelte').toBe(0);
133+
expect(hmrCount('UsingDefault.svelte'), 'updates for UsingDefault.svelte').toBe(0);
134+
await updateModuleContext((content) => content.replace('y = 1', 'y = 2'));
135+
expect(await getText('#hmr-with-context')).toContain('x=0 y=2 slot=2');
136+
expect(await getText('#hmr-without-context')).toContain('x=0 y=2 slot=');
137+
expect(hmrCount('UsingNamed.svelte'), 'updates for UsingNamed.svelte').toBe(1);
138+
expect(hmrCount('UsingDefault.svelte'), 'updates for UsingDefault.svelte').toBe(0);
139+
});
140+
141+
test('should work with emitCss: false in vite config', async () => {
142+
await editViteConfig((c) => c.replace('svelte()', 'svelte({emitCss:false})'));
143+
expect(await getText('#hmr-test-1 .counter')).toBe('0');
144+
expect(await getColor('#hmr-test-1 .label')).toBe('green');
145+
await (await getEl('#hmr-test-1 .increment')).click();
146+
await sleep(50);
147+
expect(await getText('#hmr-test-1 .counter')).toBe('1');
148+
await updateHmrTest((content) => content.replace('color: green', 'color: red'));
149+
expect(await getColor('#hmr-test-1 .label')).toBe('red');
150+
// counter value is reset
151+
expect(await getText('#hmr-test-1 .counter')).toBe('0');
152+
});
153+
154+
test('should work with emitCss: false in svelte config', async () => {
155+
addFile('svelte.config.js', 'export default {vitePlugin:{emitCss:false}}');
156+
await waitForBuildWatchAndPageReload();
157+
expect(await getColor('#hmr-test-1 .label')).toBe('red');
158+
removeFile('svelte.config.js');
159+
await waitForBuildWatchAndPageReload();
160+
});
161+
162+
test('should detect changes in svelte config and rebuild', async () => {
163+
const injectPreprocessor = ({ content, filename }) => {
164+
if (filename && filename.includes('App.svelte')) {
165+
return {
166+
code: content.replace(
167+
'<!-- HMR-TEMPLATE-INJECT -->',
168+
'<div id="preprocess-inject">Injected</div>\n<!-- HMR-TEMPLATE-INJECT -->'
169+
)
170+
};
171+
}
172+
};
173+
await addFile(
174+
'svelte.config.js',
175+
`export default {
176+
preprocess:[{markup:${injectPreprocessor.toString()}}]};`
177+
);
178+
await waitForBuildWatchAndPageReload();
179+
expect(await getText('#preprocess-inject')).toBe('Injected');
180+
expect(await getText('#hmr-test-1 .counter')).toBe('0');
181+
expect(await getColor('#hmr-test-1 .label')).toBe('red');
182+
await (await getEl('#hmr-test-1 .increment')).click();
183+
await sleep(50);
184+
expect(await getText('#hmr-test-1 .counter')).toBe('1');
185+
await updateHmrTest((content) => content.replace('color: red', 'color: green'));
186+
expect(await getColor('#hmr-test-1 .label')).toBe('green');
187+
expect(await getText('#hmr-test-1 .counter')).toBe('0');
188+
await editFile('svelte.config.js', (content) =>
189+
content
190+
.replace('preprocess-inject', 'preprocess-inject-2')
191+
.replace('Injected', 'Injected 2')
192+
);
193+
await waitForBuildWatchAndPageReload();
194+
expect(await getText('#preprocess-inject-2')).toBe('Injected 2');
195+
expect(await getEl('#preprocess-inject')).toBe(null);
196+
expect(await getColor('#hmr-test-1 .label')).toBe('green');
197+
expect(await getText('#hmr-test-1 .counter')).toBe('0');
198+
await (await getEl('#hmr-test-1 .increment')).click();
199+
await sleep(50);
200+
expect(await getText('#hmr-test-1 .counter')).toBe('1');
201+
await updateHmrTest((content) => content.replace('color: green', 'color: red'));
202+
expect(await getColor('#hmr-test-1 .label')).toBe('red');
203+
expect(await getText('#hmr-test-1 .counter')).toBe('0');
204+
await removeFile('svelte.config.js');
205+
await waitForBuildWatchAndPageReload();
206+
expect(await getEl('#preprocess-inject-2')).toBe(null);
207+
expect(await getEl('#preprocess-inject')).toBe(null);
208+
expect(await getColor('#hmr-test-1 .label')).toBe('red');
209+
expect(await getText('#hmr-test-1 .counter')).toBe('0');
210+
});
211+
});
212+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" href="/favicon.png" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Svelte App</title>
8+
</head>
9+
<body>
10+
<script type="module" src="/src/index.js"></script>
11+
</body>
12+
</html>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "e2e-tests-build-watch",
3+
"private": true,
4+
"version": "0.0.0",
5+
"scripts": {
6+
"dev": "vite dev",
7+
"build": "vite build",
8+
"preview": "vite preview"
9+
},
10+
"dependencies": {
11+
"e2e-test-dep-svelte-simple": "file:../_test_dependencies/svelte-simple"
12+
},
13+
"devDependencies": {
14+
"@sveltejs/vite-plugin-svelte": "workspace:^",
15+
"e2e-test-dep-vite-plugins": "file:../_test_dependencies/vite-plugins",
16+
"node-fetch": "^3.3.2",
17+
"svelte": "^5.36.13",
18+
"vite": "^7.0.5"
19+
},
20+
"type": "module"
21+
}
3.05 KB
Loading
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<script>
2+
import StaticImport from './components/StaticImport.svelte';
3+
import Dependency from 'e2e-test-dep-svelte-simple';
4+
import HmrTest from './components/HmrTest.svelte';
5+
import PartialHmr from './components/partial-hmr/PartialHmr.svelte';
6+
const jsTransform = '__JS_TRANSFORM_1__';
7+
let dynamicImportComponent;
8+
function importDynamic() {
9+
import('./components/DynamicImport.svelte').then((m) => (dynamicImportComponent = m.default));
10+
}
11+
</script>
12+
13+
<h1 id="app-header">Test-App 2</h1>
14+
<!-- to be transformed into "Hello world!" text -->
15+
<p id="js-transform">{jsTransform}</p>
16+
<!-- to be transformed into "hello-world" class -->
17+
<p id="css-transform">Hello world</p>
18+
<StaticImport />
19+
<Dependency />
20+
{#if !dynamicImportComponent}
21+
<button id="button-import-dynamic" on:click={importDynamic}>import dynamic component</button>
22+
{:else}
23+
<svelte:component this={dynamicImportComponent} />
24+
{/if}
25+
<HmrTest id="hmr-test-1" />
26+
<HmrTest id="hmr-test-2" />
27+
28+
<!-- HMR-TEMPLATE-INJECT -->
29+
30+
<PartialHmr />
31+
32+
<style>
33+
h1 {
34+
color: #111111;
35+
}
36+
37+
:global(#css-transform) {
38+
color: __CSS_TRANSFORM_1__;
39+
}
40+
</style>
3.05 KB
Loading
3.05 KB
Loading

0 commit comments

Comments
 (0)