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