@@ -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+
207326test . 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