Skip to content

Commit e7645d5

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 8fc9492 commit e7645d5

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
@@ -204,6 +204,125 @@ test.serial("Serve application.a, request application resource", async (t) => {
204204
t.true(servedFileContent.includes(`test("line added");`), "Resource contains changed file content");
205205
});
206206

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

@@ -973,9 +1092,14 @@ class FixtureTester {
9731092
this._reader = this.buildServer.getReader();
9741093
}
9751094

976-
async requestResource({resource, assertions}) {
1095+
async requestResource({resource, notFound = false, assertions}) {
9771096
this._sinon.resetHistory();
9781097
const res = await this._reader.byPath(resource);
1098+
if (notFound) {
1099+
this._t.is(res, null, `Resource '${resource}' must not be served`);
1100+
} else {
1101+
this._t.truthy(res, `Resource '${resource}' must be served`);
1102+
}
9791103
// Apply assertions if provided
9801104
if (assertions) {
9811105
this._assertBuild(assertions);

0 commit comments

Comments
 (0)