@@ -14,7 +14,7 @@ const winattr = require('winattr')
1414process . on ( 'unhandledRejection' , console . error )
1515
1616async function wait ( ms ) {
17- return new Promise ( r => setTimeout ( r , ms ) ) ;
17+ return new Promise ( r => setTimeout ( r , ms ) )
1818}
1919
2020describe ( 'TextBuffer IO' , ( ) => {
@@ -34,7 +34,7 @@ describe('TextBuffer IO', () => {
3434 // `await` briefly to allow the file watcher to clean up. This is a
3535 // `pathwatcher` requirement that we can fix by updating its API — but
3636 // that's a can of worms we don't want to open yet.
37- await wait ( 10 ) ;
37+ await wait ( 10 )
3838 } )
3939
4040 describe ( '.load' , ( ) => {
@@ -455,11 +455,15 @@ describe('TextBuffer IO', () => {
455455 // Modify the file after the save has been asynchronously initiated
456456 buffer . onDidSave ( ( ) => buffer . append ( '!' ) )
457457
458- const subscription = buffer . file . onDidChange ( ( ) => setTimeout ( ( ) => {
459- subscription . dispose ( )
460- expect ( events . length ) . toBe ( 0 )
461- done ( )
462- } , buffer . fileChangeDelay ) )
458+ let subscription
459+ let handler = ( ) => {
460+ setTimeout ( ( ) => {
461+ subscription ?. dispose ( )
462+ expect ( events . length ) . toBe ( 0 )
463+ done ( )
464+ } , buffer . fileChangeDelay )
465+ }
466+ subscription = buffer . file . onDidChange ( handler )
463467 } )
464468 } )
465469
@@ -581,8 +585,9 @@ describe('TextBuffer IO', () => {
581585 } )
582586
583587 describe ( '.isModified' , ( ) => {
588+ let filePath
584589 beforeEach ( async done => {
585- const filePath = temp . openSync ( 'atom' ) . path
590+ filePath = temp . openSync ( 'atom' ) . path
586591 fs . writeFileSync ( filePath , '' )
587592 buffer = await TextBuffer . load ( filePath )
588593 done ( )
@@ -607,10 +612,23 @@ describe('TextBuffer IO', () => {
607612 buffer . undo ( )
608613 buffer . undo ( )
609614 expect ( buffer . isModified ( ) ) . toBe ( false )
615+ expect ( buffer . isDeleted ( ) ) . toBe ( false )
610616 await stopChangingPromise ( )
611617 expect ( modifiedStatusChanges ) . toEqual ( [ true , false ] )
612618 done ( )
613619 } )
620+
621+ describe ( 'and the file is deleted' , ( ) => {
622+ it ( 'reports the modified status as true' , async ( ) => {
623+ buffer . setText ( `lorem ipsum` )
624+ await buffer . save ( )
625+ buffer . setText ( `lorem ipsum dolor` )
626+ fs . unlinkSync ( filePath )
627+ await wait ( 500 )
628+ expect ( buffer . isModified ( ) ) . toBe ( true )
629+ expect ( buffer . isDeleted ( ) ) . toBe ( true )
630+ } )
631+ } )
614632 } )
615633
616634 describe ( 'when the buffer is saved' , ( ) => {
@@ -627,6 +645,103 @@ describe('TextBuffer IO', () => {
627645 expect ( modifiedStatusChanges ) . toEqual ( [ false ] )
628646 done ( )
629647 } )
648+
649+ describe ( 'and the file is deleted' , ( ) => {
650+ it ( 'reports the modified status as false' , async ( ) => {
651+ buffer . setText ( `lorem ipsum` )
652+ await buffer . save ( )
653+ fs . unlinkSync ( filePath )
654+ await wait ( 500 )
655+ expect ( buffer . isModified ( ) ) . toBe ( false )
656+ expect ( buffer . isDeleted ( ) ) . toBe ( true )
657+ } )
658+
659+ it ( 'initially reports the modified status as false, but flips it back to true if the user makes further changes' , async ( ) => {
660+ buffer . setText ( `lorem ipsum` )
661+ await buffer . save ( )
662+ fs . unlinkSync ( filePath )
663+ await wait ( 500 )
664+ expect ( buffer . isModified ( ) ) . toBe ( false )
665+ expect ( buffer . isDeleted ( ) ) . toBe ( true )
666+
667+ buffer . insert ( [ 0 , 0 ] , '! ' )
668+ expect ( buffer . isModified ( ) ) . toBe ( true )
669+ expect ( buffer . isDeleted ( ) ) . toBe ( true )
670+
671+ // `isModified` should return `true` even if we revert the buffer's
672+ // contents to what they were at the time of deletion.
673+ buffer . setText ( `lorem ipsum` )
674+ expect ( buffer . isModified ( ) ) . toBe ( true )
675+ expect ( buffer . isDeleted ( ) ) . toBe ( true )
676+ } )
677+
678+ describe ( 'and re-saved' , ( ) => {
679+ it ( 'results in isModified and isDeleted no longer returning true' , async ( ) => {
680+ buffer . setText ( `lorem ipsum` )
681+ await buffer . save ( )
682+ fs . unlinkSync ( filePath )
683+ await wait ( 500 )
684+ buffer . insert ( [ 0 , 0 ] , '! ' )
685+ expect ( buffer . isModified ( ) ) . toBe ( true )
686+ expect ( buffer . isDeleted ( ) ) . toBe ( true )
687+
688+ await buffer . saveAs ( filePath )
689+
690+ expect ( buffer . isModified ( ) ) . toBe ( false )
691+ expect ( buffer . isDeleted ( ) ) . toBe ( false )
692+ } )
693+ } )
694+ } )
695+
696+ } )
697+
698+ describe ( 'when the buffer’s file is deleted' , ( ) => {
699+ it ( 'does not report `isModified` as `true` unless the buffer was modified at time of deletion' , async ( ) => {
700+ expect ( buffer . isModified ( ) ) . toBe ( false )
701+ fs . unlinkSync ( filePath )
702+ await wait ( 500 )
703+ expect ( buffer . isDeleted ( ) ) . toBe ( true )
704+ expect ( buffer . isModified ( ) ) . toBe ( false )
705+
706+ await wait ( 500 )
707+ await buffer . save ( )
708+ expect ( buffer . isDeleted ( ) ) . toBe ( false )
709+ expect ( buffer . isModified ( ) ) . toBe ( false )
710+
711+ buffer . insert ( [ 0 , 0 ] , 'hi' )
712+ expect ( buffer . isModified ( ) ) . toBe ( true )
713+ fs . unlinkSync ( filePath )
714+ await wait ( 500 )
715+ expect ( buffer . isDeleted ( ) ) . toBe ( true )
716+ expect ( buffer . isModified ( ) ) . toBe ( true )
717+ } )
718+ } )
719+
720+ describe ( 'when the buffer is re-saved after deletion' , ( ) => {
721+ it ( 'stops reporting the file as deleted or modified' , async done => {
722+ buffer . insert ( [ 0 , 0 ] , 'hi' )
723+ expect ( buffer . isModified ( ) ) . toBe ( true )
724+
725+ fs . unlinkSync ( filePath )
726+ await wait ( 500 )
727+ expect ( buffer . isDeleted ( ) ) . toBe ( true )
728+ expect ( buffer . isModified ( ) ) . toBe ( true )
729+
730+ await wait ( 500 )
731+
732+ await buffer . save ( )
733+ expect ( buffer . isDeleted ( ) ) . toBe ( false )
734+ expect ( buffer . isModified ( ) ) . toBe ( false )
735+
736+ buffer . insert ( [ 0 , 0 ] , 'hi' )
737+ await wait ( 500 )
738+ fs . unlinkSync ( filePath )
739+ await wait ( 500 )
740+ expect ( buffer . isDeleted ( ) ) . toBe ( true )
741+ expect ( buffer . isModified ( ) ) . toBe ( true )
742+ done ( )
743+ } )
744+
630745 } )
631746
632747 describe ( 'when the buffer is reloaded' , ( ) => {
@@ -843,22 +958,27 @@ describe('TextBuffer IO', () => {
843958 } )
844959
845960 it ( 'emits a conflict event if the buffer is modified and backed by a custom file' , async done => {
961+ fs . writeFileSync ( buffer . getPath ( ) , 'abcde' )
846962 const file = new ReverseCaseFile ( filePath )
847963 buffer . setFile ( file )
848964
965+ // `ReverseCaseFile` uses `fs.watch` to set up file-watching. This
966+ // built-in method is fast, but not instantaneous.
967+ await wait ( process . env . CI ? 500 : 200 )
968+
849969 buffer . append ( 'f' )
850970 expect ( buffer . getText ( ) ) . toBe ( 'abcdef' )
851971 expect ( buffer . isModified ( ) ) . toBe ( true )
852972
853- fs . writeFileSync ( buffer . getPath ( ) , ' abc' )
854-
855973 const subscription = buffer . onDidConflict ( ( ) => {
856974 subscription . dispose ( )
857975 expect ( buffer . getText ( ) ) . toBe ( 'abcdef' )
858976 expect ( buffer . isModified ( ) ) . toBe ( true )
859977 expect ( buffer . isInConflict ( ) ) . toBe ( true )
860978 done ( )
861979 } )
980+
981+ fs . writeFileSync ( buffer . getPath ( ) , ' abc' )
862982 } )
863983
864984 it ( 'updates the buffer and its markers and notifies change observers if the buffer is unmodified' , async done => {
@@ -1087,7 +1207,18 @@ describe('TextBuffer IO', () => {
10871207 fs . removeSync ( filePath )
10881208 buffer . file . onDidDelete ( ( ) => {
10891209 expect ( buffer . getPath ( ) ) . toBe ( filePath )
1090- expect ( buffer . isModified ( ) ) . toBeTruthy ( )
1210+ // `buffer.isModified` used to report `true` automatically whenever
1211+ // a buffer does not have a backing file. Now it depends on whether
1212+ // the file ever existed – and, if so, whether the buffer was in a
1213+ // modified state when the file was deleted.
1214+ //
1215+ // The narrow exception we're carving out is one where the file
1216+ // contents were in sync with the buffer contents at the moment of
1217+ // file deletion. If so, its `isModified=false` status will persist
1218+ // until even one edit is made, at which point it will flip back to
1219+ // `isModified=true` until the buffer is destroyed or once again
1220+ // saved to disk.
1221+ expect ( buffer . isModified ( ) ) . toBeFalsy ( )
10911222 done ( )
10921223 } )
10931224 } )
@@ -1106,11 +1237,12 @@ describe('TextBuffer IO', () => {
11061237 expect ( fs . existsSync ( buffer . getPath ( ) ) ) . toBeTruthy ( )
11071238 expect ( buffer . isInConflict ( ) ) . toBeFalsy ( )
11081239
1109- fs . writeFileSync ( filePath , 'moo' )
11101240 buffer . onDidChange ( ( ) => {
11111241 expect ( buffer . getText ( ) ) . toBe ( 'moo' )
11121242 done ( )
11131243 } )
1244+ await wait ( process . env . CI ? 200 : 20 )
1245+ fs . writeFileSync ( filePath , 'moo' )
11141246 } )
11151247 } )
11161248} )
0 commit comments