Skip to content

Commit 97dbd0a

Browse files
Merge pull request #13 from pulsar-edit/enhance-deleted-state
Enhance deleted state
2 parents 78b7f03 + 4b10375 commit 97dbd0a

3 files changed

Lines changed: 234 additions & 34 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"yargs": "^6.5.0"
3333
},
3434
"dependencies": {
35-
"@pulsar-edit/pathwatcher": "^9.0.2",
35+
"@pulsar-edit/pathwatcher": "^9.0.3",
3636
"@pulsar-edit/superstring": "^3.0.4",
3737
"delegato": "^1.0.0",
3838
"diff": "^2.2.1",

spec/text-buffer-io-spec.js

Lines changed: 144 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const winattr = require('winattr')
1414
process.on('unhandledRejection', console.error)
1515

1616
async function wait (ms) {
17-
return new Promise(r => setTimeout(r, ms));
17+
return new Promise(r => setTimeout(r, ms))
1818
}
1919

2020
describe('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

Comments
 (0)