Skip to content

Commit eedf745

Browse files
committed
test(project): Cover create and delete watcher events in BuildServer
Add an integration test that explicitly fires 'create' and 'delete' watcher events through the @parcel/watcher mock to cover the fileAddedOrRemoved=true branch in BuildServer _projectResourceChanged. Previously this branch was only hit as a side-effect of the fixture copy running after the watcher subscribed; with the mock, no such side-effect exists and the path needs explicit coverage.
1 parent 991b558 commit eedf745

1 file changed

Lines changed: 125 additions & 1 deletion

File tree

packages/project/test/lib/build/BuildServer.integration.js

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,125 @@ test.serial("Serve application.a, request application resource", async (t) => {
194194
t.true(servedFileContent.includes(`test("line added");`), "Resource contains changed file content");
195195
});
196196

197+
test.serial("Serve application.a, create and delete a source file", async (t) => {
198+
const fixtureTester = t.context.fixtureTester = await FixtureTester.create(t, "application.a");
199+
200+
await fixtureTester.serveProject();
201+
202+
// Create a new source file in application.a *before* the first resource request
203+
const createdFilePath = `${fixtureTester.fixturePath}/webapp/created.js`;
204+
await fs.writeFile(createdFilePath, `test("created file");\n`);
205+
await fixtureTester.fireWatcherEvent("create", createdFilePath);
206+
207+
// #1 first request — initial build picks up the just-created file
208+
const createdRes = await fixtureTester.requestResource({
209+
resource: "/created.js",
210+
assertions: {
211+
projects: {
212+
"application.a": {}
213+
}
214+
}
215+
});
216+
const createdContent = await createdRes.getString();
217+
t.true(createdContent.includes(`test("created file");`),
218+
"Created resource contains the expected content");
219+
220+
// #2 request again with cache — no rebuild expected
221+
await fixtureTester.requestResource({
222+
resource: "/created.js",
223+
assertions: {
224+
projects: {}
225+
}
226+
});
227+
228+
// Create a *second* new file after the first build has populated the persistent cache
229+
const anotherFilePath = `${fixtureTester.fixturePath}/webapp/another.js`;
230+
await fs.writeFile(anotherFilePath, `test("another file");\n`);
231+
await fixtureTester.fireWatcherEvent("create", anotherFilePath);
232+
233+
// #3 request the second created resource — rebuild reuses cached task results
234+
const anotherRes = await fixtureTester.requestResource({
235+
resource: "/another.js",
236+
assertions: {
237+
projects: {
238+
"application.a": {
239+
skippedTasks: [
240+
"escapeNonAsciiCharacters",
241+
"replaceCopyright",
242+
"enhanceManifest",
243+
"generateFlexChangesBundle",
244+
]
245+
}
246+
}
247+
}
248+
});
249+
const anotherContent = await anotherRes.getString();
250+
t.true(anotherContent.includes(`test("another file");`),
251+
"Second created resource contains the expected content");
252+
253+
// Delete the second file again
254+
await fs.rm(anotherFilePath);
255+
await fixtureTester.fireWatcherEvent("delete", anotherFilePath);
256+
257+
// #4 the originally created file is still served and the cache from builds #1 and #2 is reused
258+
await fixtureTester.requestResource({
259+
resource: "/created.js",
260+
assertions: {
261+
projects: {}
262+
}
263+
});
264+
265+
// #5 the second file is no longer served, but requesting it triggers a build of the dependencies
266+
// because the file is not known anymore and might come from a different project.
267+
// Note: This is special for applications, which are served at root level. For libraries, the server
268+
// can determine whether a resources is inside a project namespace and only trigger a build for the affected
269+
// project. The logic could be improved, especially like in this case where the requested resource is outside
270+
// of /resources or /test-resources.
271+
await fixtureTester.requestResource({
272+
resource: "/another.js",
273+
notFound: true,
274+
assertions: {
275+
projects: {
276+
"library.d": {},
277+
"library.a": {},
278+
"library.b": {},
279+
"library.c": {},
280+
}
281+
}
282+
});
283+
284+
// Delete the first source file again
285+
await fs.rm(createdFilePath);
286+
await fixtureTester.fireWatcherEvent("delete", createdFilePath);
287+
288+
// #6 request the deleted resource — must no longer be served
289+
// Partial rebuild is needed as there is no complete cache of the project without the file
290+
await fixtureTester.requestResource({
291+
resource: "/created.js",
292+
notFound: true,
293+
assertions: {
294+
projects: {
295+
"application.a": {
296+
skippedTasks: [
297+
"escapeNonAsciiCharacters",
298+
"replaceCopyright",
299+
"enhanceManifest",
300+
"generateFlexChangesBundle",
301+
]
302+
}
303+
}
304+
}
305+
});
306+
307+
// Sanity check: the original /test.js is still served from the rebuilt project
308+
await fixtureTester.requestResource({
309+
resource: "/test.js",
310+
assertions: {
311+
projects: {}
312+
}
313+
});
314+
});
315+
197316
test.serial("Serve application.a, request library resource", async (t) => {
198317
const fixtureTester = t.context.fixtureTester = await FixtureTester.create(t, "application.a");
199318

@@ -963,9 +1082,14 @@ class FixtureTester {
9631082
this._reader = this.buildServer.getReader();
9641083
}
9651084

966-
async requestResource({resource, assertions}) {
1085+
async requestResource({resource, notFound = false, assertions}) {
9671086
this._sinon.resetHistory();
9681087
const res = await this._reader.byPath(resource);
1088+
if (notFound) {
1089+
this._t.is(res, null, `Resource '${resource}' must not be served`);
1090+
} else {
1091+
this._t.truthy(res, `Resource '${resource}' must be served`);
1092+
}
9691093
// Apply assertions if provided
9701094
if (assertions) {
9711095
this._assertBuild(assertions);

0 commit comments

Comments
 (0)