pulsar/spec/app/buffer-spec.coffee
Nathan Sobo 4a84c5f8f9 Add 'stopped-changing' event to buffer. Fires 300ms after last change.
This will be used by the status bar and other expensive code that needs to respond to the buffer changing, but that we don't want running on every single keystroke when the user is typing quickly.
2012-11-14 15:33:44 -07:00

750 lines
29 KiB
CoffeeScript

Project = require 'project'
Buffer = require 'buffer'
fs = require 'fs'
describe 'Buffer', ->
[filePath, fileContents, buffer] = []
beforeEach ->
filePath = require.resolve('fixtures/sample.js')
fileContents = fs.read(filePath)
buffer = new Buffer(filePath)
afterEach ->
buffer?.release()
describe 'constructor', ->
beforeEach ->
buffer.release()
describe "when given a path", ->
describe "when a file exists for the path", ->
it "loads the contents of that file", ->
filePath = require.resolve 'fixtures/sample.txt'
buffer = new Buffer(filePath)
expect(buffer.getText()).toBe fs.read(filePath)
it "is not modified and has no undo history", ->
buffer = new Buffer(filePath)
expect(buffer.isModified()).toBeFalsy()
expect(buffer.undoManager.undoHistory.length).toBe 0
describe "when no file exists for the path", ->
it "throws an exception", ->
buffer = null
filePath = "does-not-exist.txt"
expect(fs.exists(filePath)).toBeFalsy()
expect(-> new Buffer(filePath)).toThrow()
describe "when no path is given", ->
it "creates an empty buffer", ->
buffer = new Buffer
expect(buffer.getText()).toBe ""
describe "path-change event", ->
[path, newPath, bufferToChange, eventHandler] = []
beforeEach ->
path = fs.join(require.resolve("fixtures/"), "atom-manipulate-me")
newPath = "#{path}-i-moved"
fs.write(path, "")
bufferToChange = new Buffer(path)
eventHandler = jasmine.createSpy('eventHandler')
bufferToChange.on 'path-change', eventHandler
afterEach ->
bufferToChange.destroy()
fs.remove(path) if fs.exists(path)
fs.remove(newPath) if fs.exists(newPath)
it "triggers a `path-change` event when path is changed", ->
bufferToChange.saveAs(newPath)
expect(eventHandler).toHaveBeenCalledWith(bufferToChange)
it "triggers a `path-change` event when the file is moved", ->
fs.remove(newPath) if fs.exists(newPath)
fs.move(path, newPath)
waitsFor "buffer path change", ->
eventHandler.callCount > 0
runs ->
expect(eventHandler).toHaveBeenCalledWith(bufferToChange)
it "triggers a `path-change` event when the file is removed", ->
fs.remove(path)
waitsFor "buffer path change", ->
eventHandler.callCount > 0
describe "when the buffer's on-disk contents change (via another process writing to its file)", ->
path = null
beforeEach ->
path = "/tmp/tmp.txt"
fs.write(path, "first")
buffer.release()
buffer = new Buffer(path).retain()
afterEach ->
fs.remove(path)
it "does not trigger a contents-change event when Atom modifies the file", ->
buffer.insert([0,0], "HELLO!")
changeHandler = jasmine.createSpy("buffer changed")
buffer.on "change", changeHandler
buffer.save()
waits 30
runs ->
expect(changeHandler).not.toHaveBeenCalled()
describe "when the buffer's memory contents are the same as the *previous* disk contents", ->
it "changes the memory contents of the buffer to match the new disk contents and triggers a 'change' event", ->
changeHandler = jasmine.createSpy('changeHandler')
buffer.on 'change', changeHandler
fs.write(path, "second")
expect(changeHandler.callCount).toBe 0
waitsFor "file to trigger change event", ->
changeHandler.callCount > 0
runs ->
[event] = changeHandler.argsForCall[0]
expect(event.oldRange).toEqual [[0, 0], [0, 5]]
expect(event.newRange).toEqual [[0, 0], [0, 6]]
expect(event.oldText).toBe "first"
expect(event.newText).toBe "second"
expect(buffer.isModified()).toBeFalsy()
describe "when the buffer's memory contents differ from the *previous* disk contents", ->
it "leaves the buffer in a modified state (does not update its memory contents)", ->
fileChangeHandler = jasmine.createSpy('fileChange')
buffer.file.on 'contents-change', fileChangeHandler
buffer.insert([0, 0], "a change")
fs.write(path, "second")
expect(fileChangeHandler.callCount).toBe 0
waitsFor "file to trigger contents-change event", ->
fileChangeHandler.callCount > 0
runs ->
expect(buffer.isModified()).toBeTruthy()
describe "when the buffer's file is deleted (via another process)", ->
it "no longer has a path", ->
path = "/tmp/atom-file-to-delete.txt"
fs.write(path, '')
bufferToDelete = new Buffer(path)
expect(bufferToDelete.getPath()).toBe path
fs.remove(path)
waitsFor "file to be removed", ->
not bufferToDelete.getPath()
describe ".isModified()", ->
it "returns true when user changes buffer", ->
expect(buffer.isModified()).toBeFalsy()
buffer.insert([0,0], "hi")
expect(buffer.isModified()).toBe true
it "returns false after modified buffer is saved", ->
filePath = "/tmp/atom-tmp-file"
fs.write(filePath, '')
buffer.release()
buffer = new Buffer(filePath)
expect(buffer.isModified()).toBe false
buffer.insert([0,0], "hi")
expect(buffer.isModified()).toBe true
buffer.save()
expect(buffer.isModified()).toBe false
it "returns false for an empty buffer with no path", ->
buffer.release()
buffer = new Buffer()
expect(buffer.isModified()).toBeFalsy()
it "returns true for a non-empty buffer with no path", ->
buffer.release()
buffer = new Buffer()
buffer.setText('a')
expect(buffer.isModified()).toBeTruthy()
buffer.setText('\n')
expect(buffer.isModified()).toBeTruthy()
describe ".getLines()", ->
it "returns an array of lines in the text contents", ->
expect(buffer.getLines().length).toBe fileContents.split("\n").length
expect(buffer.getLines().join('\n')).toBe fileContents
describe ".change(range, string)", ->
changeHandler = null
beforeEach ->
changeHandler = jasmine.createSpy('changeHandler')
buffer.on 'change', changeHandler
describe "when used to insert (called with an empty range and a non-empty string)", ->
describe "when the given string has no newlines", ->
it "inserts the string at the location of the given range", ->
range = [[3, 4], [3, 4]]
buffer.change range, "foo"
expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items;"
expect(buffer.lineForRow(3)).toBe " foovar pivot = items.shift(), current, left = [], right = [];"
expect(buffer.lineForRow(4)).toBe " while(items.length > 0) {"
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
expect(event.oldRange).toEqual range
expect(event.newRange).toEqual [[3, 4], [3, 7]]
expect(event.oldText).toBe ""
expect(event.newText).toBe "foo"
describe "when the given string has newlines", ->
it "inserts the lines at the location of the given range", ->
range = [[3, 4], [3, 4]]
buffer.change range, "foo\n\nbar\nbaz"
expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items;"
expect(buffer.lineForRow(3)).toBe " foo"
expect(buffer.lineForRow(4)).toBe ""
expect(buffer.lineForRow(5)).toBe "bar"
expect(buffer.lineForRow(6)).toBe "bazvar pivot = items.shift(), current, left = [], right = [];"
expect(buffer.lineForRow(7)).toBe " while(items.length > 0) {"
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
expect(event.oldRange).toEqual range
expect(event.newRange).toEqual [[3, 4], [6, 3]]
expect(event.oldText).toBe ""
expect(event.newText).toBe "foo\n\nbar\nbaz"
describe "when used to remove (called with a non-empty range and an empty string)", ->
describe "when the range is contained within a single line", ->
it "removes the characters within the range", ->
range = [[3, 4], [3, 7]]
buffer.change range, ""
expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items;"
expect(buffer.lineForRow(3)).toBe " pivot = items.shift(), current, left = [], right = [];"
expect(buffer.lineForRow(4)).toBe " while(items.length > 0) {"
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
expect(event.oldRange).toEqual range
expect(event.newRange).toEqual [[3, 4], [3, 4]]
expect(event.oldText).toBe "var"
expect(event.newText).toBe ""
describe "when the range spans 2 lines", ->
it "removes the characters within the range and joins the lines", ->
range = [[3, 16], [4, 4]]
buffer.change range, ""
expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items;"
expect(buffer.lineForRow(3)).toBe " var pivot = while(items.length > 0) {"
expect(buffer.lineForRow(4)).toBe " current = items.shift();"
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
expect(event.oldRange).toEqual range
expect(event.newRange).toEqual [[3, 16], [3, 16]]
expect(event.oldText).toBe "items.shift(), current, left = [], right = [];\n "
expect(event.newText).toBe ""
describe "when the range spans more than 2 lines", ->
it "removes the characters within the range, joining the first and last line and removing the lines in-between", ->
buffer.change [[3, 16], [11, 9]], ""
expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items;"
expect(buffer.lineForRow(3)).toBe " var pivot = sort(Array.apply(this, arguments));"
expect(buffer.lineForRow(4)).toBe "};"
describe "when used to replace text with other text (called with non-empty range and non-empty string)", ->
it "replaces the old text with the new text", ->
range = [[3, 16], [11, 9]]
oldText = buffer.getTextInRange(range)
buffer.change range, "foo\nbar"
expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items;"
expect(buffer.lineForRow(3)).toBe " var pivot = foo"
expect(buffer.lineForRow(4)).toBe "barsort(Array.apply(this, arguments));"
expect(buffer.lineForRow(5)).toBe "};"
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
expect(event.oldRange).toEqual range
expect(event.newRange).toEqual [[3, 16], [4, 3]]
expect(event.oldText).toBe oldText
expect(event.newText).toBe "foo\nbar"
it "allows a 'change' event handler to safely undo the change", ->
buffer.on 'change', -> buffer.undo()
buffer.change([0, 0], "hello")
expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
describe ".setText(text)", ->
it "changes the entire contents of the buffer and emits a change event", ->
lastRow = buffer.getLastRow()
expectedPreRange = [[0,0], [lastRow, buffer.lineForRow(lastRow).length]]
changeHandler = jasmine.createSpy('changeHandler')
buffer.on 'change', changeHandler
newText = "I know you are.\nBut what am I?"
buffer.setText(newText)
expect(buffer.getText()).toBe newText
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
expect(event.newText).toBe newText
expect(event.oldRange).toEqual expectedPreRange
expect(event.newRange).toEqual [[0, 0], [1, 14]]
describe ".save()", ->
beforeEach ->
buffer.release()
describe "when the buffer has a path", ->
filePath = null
beforeEach ->
filePath = '/tmp/temp.txt'
fs.write(filePath, "")
buffer = new Buffer filePath
afterEach ->
fs.remove filePath if fs.exists(filePath)
it "saves the contents of the buffer to the path", ->
buffer.setText 'Buffer contents!'
buffer.save()
expect(fs.read(filePath)).toEqual 'Buffer contents!'
it "fires beforeSave and afterSave events around the call to fs.write", ->
events = []
beforeSave1 = -> events.push('beforeSave1')
beforeSave2 = -> events.push('beforeSave2')
afterSave1 = -> events.push('afterSave1')
afterSave2 = -> events.push('afterSave2')
buffer.on 'before-save', beforeSave1
buffer.on 'before-save', beforeSave2
spyOn(fs, 'write').andCallFake -> events.push 'fs.write'
buffer.on 'after-save', afterSave1
buffer.on 'after-save', afterSave2
buffer.save()
expect(events).toEqual ['beforeSave1', 'beforeSave2', 'fs.write', 'afterSave1', 'afterSave2']
describe "when the buffer has no path", ->
it "throws an exception", ->
buffer = new Buffer
expect(-> buffer.save()).toThrow()
describe "reload()", ->
it "reloads current text from disk and clears any conflicts", ->
buffer.setText("abc")
buffer.conflict = true
buffer.reload()
expect(buffer.isModified()).toBeFalsy()
expect(buffer.isInConflict()).toBeFalsy()
expect(buffer.getText()).toBe(fileContents)
describe ".saveAs(path)", ->
[filePath, saveAsBuffer] = []
afterEach ->
saveAsBuffer.release()
it "saves the contents of the buffer to the path", ->
filePath = '/tmp/temp.txt'
fs.remove filePath if fs.exists(filePath)
saveAsBuffer = new Buffer().retain()
eventHandler = jasmine.createSpy('eventHandler')
saveAsBuffer.on 'path-change', eventHandler
saveAsBuffer.setText 'Buffer contents!'
saveAsBuffer.saveAs(filePath)
expect(fs.read(filePath)).toEqual 'Buffer contents!'
expect(eventHandler).toHaveBeenCalledWith(saveAsBuffer)
it "stops listening to events on previous path and begins listening to events on new path", ->
originalPath = "/tmp/original.txt"
newPath = "/tmp/new.txt"
fs.write(originalPath, "")
saveAsBuffer = new Buffer(originalPath).retain()
changeHandler = jasmine.createSpy('changeHandler')
saveAsBuffer.on 'change', changeHandler
saveAsBuffer.saveAs(newPath)
expect(changeHandler).not.toHaveBeenCalled()
fs.write(originalPath, "should not trigger buffer event")
waits 20
runs ->
expect(changeHandler).not.toHaveBeenCalled()
fs.write(newPath, "should trigger buffer event")
waitsFor ->
changeHandler.callCount > 0
describe ".getTextInRange(range)", ->
describe "when range is empty", ->
it "returns an empty string", ->
range = [[1,1], [1,1]]
expect(buffer.getTextInRange(range)).toBe ""
describe "when range spans one line", ->
it "returns characters in range", ->
range = [[2,8], [2,13]]
expect(buffer.getTextInRange(range)).toBe "items"
lineLength = buffer.lineForRow(2).length
range = [[2,0], [2,lineLength]]
expect(buffer.getTextInRange(range)).toBe " if (items.length <= 1) return items;"
describe "when range spans multiple lines", ->
it "returns characters in range (including newlines)", ->
lineLength = buffer.lineForRow(2).length
range = [[2,0], [3,0]]
expect(buffer.getTextInRange(range)).toBe " if (items.length <= 1) return items;\n"
lineLength = buffer.lineForRow(2).length
range = [[2,10], [4,10]]
expect(buffer.getTextInRange(range)).toBe "ems.length <= 1) return items;\n var pivot = items.shift(), current, left = [], right = [];\n while("
describe ".scanInRange(range, regex, fn)", ->
describe "when given a regex with a ignore case flag", ->
it "does a case-insensitive search", ->
matches = []
buffer.scanInRange /cuRRent/i, [[0,0], [12,0]], (match, range) ->
matches.push(match)
expect(matches.length).toBe 1
describe "when given a regex with no global flag", ->
it "calls the iterator with the first match for the given regex in the given range", ->
matches = []
ranges = []
buffer.scanInRange /cu(rr)ent/, [[4,0], [6,44]], (match, range) ->
matches.push(match)
ranges.push(range)
expect(matches.length).toBe 1
expect(ranges.length).toBe 1
expect(matches[0][0]).toBe 'current'
expect(matches[0][1]).toBe 'rr'
expect(ranges[0]).toEqual [[5,6], [5,13]]
describe "when given a regex with a global flag", ->
it "calls the iterator with each match for the given regex in the given range", ->
matches = []
ranges = []
buffer.scanInRange /cu(rr)ent/g, [[4,0], [6,59]], (match, range) ->
matches.push(match)
ranges.push(range)
expect(matches.length).toBe 3
expect(ranges.length).toBe 3
expect(matches[0][0]).toBe 'current'
expect(matches[0][1]).toBe 'rr'
expect(ranges[0]).toEqual [[5,6], [5,13]]
expect(matches[1][0]).toBe 'current'
expect(matches[1][1]).toBe 'rr'
expect(ranges[1]).toEqual [[6,6], [6,13]]
expect(matches[2][0]).toBe 'current'
expect(matches[2][1]).toBe 'rr'
expect(ranges[2]).toEqual [[6,34], [6,41]]
describe "when the last regex match exceeds the end of the range", ->
describe "when the portion of the match within the range also matches the regex", ->
it "calls the iterator with the truncated match", ->
matches = []
ranges = []
buffer.scanInRange /cu(r*)/g, [[4,0], [6,9]], (match, range) ->
matches.push(match)
ranges.push(range)
expect(matches.length).toBe 2
expect(ranges.length).toBe 2
expect(matches[0][0]).toBe 'curr'
expect(matches[0][1]).toBe 'rr'
expect(ranges[0]).toEqual [[5,6], [5,10]]
expect(matches[1][0]).toBe 'cur'
expect(matches[1][1]).toBe 'r'
expect(ranges[1]).toEqual [[6,6], [6,9]]
describe "when the portion of the match within the range does not matches the regex", ->
it "calls the iterator with the truncated match", ->
matches = []
ranges = []
buffer.scanInRange /cu(r*)e/g, [[4,0], [6,9]], (match, range) ->
matches.push(match)
ranges.push(range)
expect(matches.length).toBe 1
expect(ranges.length).toBe 1
expect(matches[0][0]).toBe 'curre'
expect(matches[0][1]).toBe 'rr'
expect(ranges[0]).toEqual [[5,6], [5,11]]
describe "when the iterator calls the 'replace' control function with a replacement string", ->
it "replaces each occurrence of the regex match with the string", ->
ranges = []
buffer.scanInRange /cu(rr)ent/g, [[4,0], [6,59]], (match, range, { replace }) ->
ranges.push(range)
replace("foo")
expect(ranges[0]).toEqual [[5,6], [5,13]]
expect(ranges[1]).toEqual [[6,6], [6,13]]
expect(ranges[2]).toEqual [[6,30], [6,37]]
expect(buffer.lineForRow(5)).toBe ' foo = items.shift();'
expect(buffer.lineForRow(6)).toBe ' foo < pivot ? left.push(foo) : right.push(current);'
it "allows the match to be replaced with the empty string", ->
buffer.scanInRange /current/g, [[4,0], [6,59]], (match, range, { replace }) ->
replace("")
expect(buffer.lineForRow(5)).toBe ' = items.shift();'
expect(buffer.lineForRow(6)).toBe ' < pivot ? left.push() : right.push(current);'
describe "when the iterator calls the 'stop' control function", ->
it "stops the traversal", ->
ranges = []
buffer.scanInRange /cu(rr)ent/g, [[4,0], [6,59]], (match, range, { stop }) ->
ranges.push(range)
stop() if ranges.length == 2
expect(ranges.length).toBe 2
describe ".backwardsScanInRange(range, regex, fn)", ->
describe "when given a regex with no global flag", ->
it "calls the iterator with the last match for the given regex in the given range", ->
matches = []
ranges = []
buffer.backwardsScanInRange /cu(rr)ent/, [[4,0], [6,44]], (match, range) ->
matches.push(match)
ranges.push(range)
expect(matches.length).toBe 1
expect(ranges.length).toBe 1
expect(matches[0][0]).toBe 'current'
expect(matches[0][1]).toBe 'rr'
expect(ranges[0]).toEqual [[6,34], [6,41]]
describe "when given a regex with a global flag", ->
it "calls the iterator with each match for the given regex in the given range, starting with the last match", ->
matches = []
ranges = []
buffer.backwardsScanInRange /cu(rr)ent/g, [[4,0], [6,59]], (match, range) ->
matches.push(match)
ranges.push(range)
expect(matches.length).toBe 3
expect(ranges.length).toBe 3
expect(matches[0][0]).toBe 'current'
expect(matches[0][1]).toBe 'rr'
expect(ranges[0]).toEqual [[6,34], [6,41]]
expect(matches[1][0]).toBe 'current'
expect(matches[1][1]).toBe 'rr'
expect(ranges[1]).toEqual [[6,6], [6,13]]
expect(matches[2][0]).toBe 'current'
expect(matches[2][1]).toBe 'rr'
expect(ranges[2]).toEqual [[5,6], [5,13]]
describe "when the iterator calls the 'replace' control function with a replacement string", ->
it "replaces each occurrence of the regex match with the string", ->
ranges = []
buffer.backwardsScanInRange /cu(rr)ent/g, [[4,0], [6,59]], (match, range, { replace }) ->
ranges.push(range)
replace("foo") unless range.start.isEqual([6,6])
expect(ranges[0]).toEqual [[6,34], [6,41]]
expect(ranges[1]).toEqual [[6,6], [6,13]]
expect(ranges[2]).toEqual [[5,6], [5,13]]
expect(buffer.lineForRow(5)).toBe ' foo = items.shift();'
expect(buffer.lineForRow(6)).toBe ' current < pivot ? left.push(foo) : right.push(current);'
describe "when the iterator calls the 'stop' control function", ->
it "stops the traversal", ->
ranges = []
buffer.backwardsScanInRange /cu(rr)ent/g, [[4,0], [6,59]], (match, range, { stop }) ->
ranges.push(range)
stop() if ranges.length == 2
expect(ranges.length).toBe 2
expect(ranges[0]).toEqual [[6,34], [6,41]]
expect(ranges[1]).toEqual [[6,6], [6,13]]
describe ".characterIndexForPosition(position)", ->
it "returns the total number of charachters that precede the given position", ->
expect(buffer.characterIndexForPosition([0, 0])).toBe 0
expect(buffer.characterIndexForPosition([0, 1])).toBe 1
expect(buffer.characterIndexForPosition([0, 29])).toBe 29
expect(buffer.characterIndexForPosition([1, 0])).toBe 30
expect(buffer.characterIndexForPosition([2, 0])).toBe 61
expect(buffer.characterIndexForPosition([12, 2])).toBe 408
describe ".positionForCharacterIndex(position)", ->
it "returns the position based on charachter index", ->
expect(buffer.positionForCharacterIndex(0)).toEqual [0, 0]
expect(buffer.positionForCharacterIndex(1)).toEqual [0, 1]
expect(buffer.positionForCharacterIndex(29)).toEqual [0, 29]
expect(buffer.positionForCharacterIndex(30)).toEqual [1, 0]
expect(buffer.positionForCharacterIndex(61)).toEqual [2, 0]
expect(buffer.positionForCharacterIndex(408)).toEqual [12, 2]
describe "anchors", ->
[anchor, destroyHandler] = []
beforeEach ->
destroyHandler = jasmine.createSpy("destroyHandler")
anchor = buffer.addAnchorAtPosition([4, 25])
anchor.on 'destroy', destroyHandler
describe "when anchor.ignoreChangesStartingOnAnchor is true", ->
beforeEach ->
anchor.ignoreChangesStartingOnAnchor = true
describe "when the change ends before the anchor position", ->
it "moves the anchor", ->
buffer.change([[4, 23], [4, 24]], "...")
expect(anchor.getBufferPosition()).toEqual [4, 27]
expect(destroyHandler).not.toHaveBeenCalled()
describe "when the change ends on the anchor position", ->
it "moves the anchor", ->
buffer.change([[4, 24], [4, 25]], "...")
expect(anchor.getBufferPosition()).toEqual [4, 27]
expect(destroyHandler).not.toHaveBeenCalled()
describe "when the change begins on the anchor position", ->
it "doesn't move the anchor", ->
buffer.change([[4, 25], [4, 26]], ".....")
expect(anchor.getBufferPosition()).toEqual [4, 25]
expect(destroyHandler).not.toHaveBeenCalled()
describe "when the change begins after the anchor position", ->
it "doesn't move the anchor", ->
buffer.change([[4, 26], [4, 27]], ".....")
expect(anchor.getBufferPosition()).toEqual [4, 25]
expect(destroyHandler).not.toHaveBeenCalled()
describe "when the buffer changes and the oldRange is equalTo than the newRange (text is replaced)", ->
describe "when the anchor is contained by the oldRange", ->
it "destroys the anchor", ->
buffer.change([[4, 20], [4, 26]], ".......")
expect(destroyHandler).toHaveBeenCalled()
describe "when the anchor is not contained by the oldRange", ->
it "does not move the anchor", ->
buffer.change([[4, 20], [4, 21]], ".")
expect(anchor.getBufferPosition()).toEqual [4, 25]
expect(destroyHandler).not.toHaveBeenCalled()
describe "when the buffer changes and the oldRange is smaller than the newRange (text is inserted)", ->
describe "when the buffer changes and the oldRange starts and ends before the anchor ", ->
it "updates the anchor position", ->
buffer.change([[4, 24], [4, 24]], "..")
expect(anchor.getBufferPosition()).toEqual [4, 27]
expect(destroyHandler).not.toHaveBeenCalled()
describe "when the buffer changes and the oldRange contains before the anchor ", ->
it "destroys the anchor", ->
buffer.change([[4, 24], [4, 26]], ".....")
expect(destroyHandler).toHaveBeenCalled()
describe "when the buffer changes and the oldRange stars after the anchor", ->
it "does not move the anchor", ->
buffer.change([[4, 26], [4, 26]], "....")
expect(anchor.getBufferPosition()).toEqual [4, 25]
expect(destroyHandler).not.toHaveBeenCalled()
describe "when the buffer changes and the oldRange is larger than the newRange (text is deleted)", ->
describe "when the buffer changes and the oldRange starts and ends before the anchor ", ->
it "updates the anchor position", ->
buffer.change([[4, 20], [4, 21]], "")
expect(anchor.getBufferPosition()).toEqual [4, 24]
expect(destroyHandler).not.toHaveBeenCalled()
describe "when the buffer changes and the oldRange contains before the anchor ", ->
it "destroys the anchor", ->
buffer.change([[4, 24], [4, 26]], ".")
expect(destroyHandler).toHaveBeenCalled()
describe "when the oldRange stars after the anchor", ->
it "does not move the anchor", ->
buffer.change([[4, 26], [4, 27]], "")
expect(anchor.getBufferPosition()).toEqual [4, 25]
expect(destroyHandler).not.toHaveBeenCalled()
describe "when a buffer change surrounds an anchor", ->
it "destroys the anchor", ->
buffer.delete([[3, 0], [5, 0]])
expect(destroyHandler).toHaveBeenCalled()
expect(buffer.getAnchors().indexOf(anchor)).toBe -1
describe ".usesSoftTabs()", ->
it "returns true if the first indented line begins with tabs", ->
buffer.setText("function() {\n foo();\n}")
expect(buffer.usesSoftTabs()).toBeTruthy()
buffer.setText("function() {\n\tfoo();\n}")
expect(buffer.usesSoftTabs()).toBeFalsy()
buffer.setText("")
expect(buffer.usesSoftTabs()).toBeUndefined()
describe ".isEmpty()", ->
it "returns true for an empty buffer", ->
buffer.setText('')
expect(buffer.isEmpty()).toBeTruthy()
it "returns false for a non-empty buffer", ->
buffer.setText('a')
expect(buffer.isEmpty()).toBeFalsy()
buffer.setText('a\nb\nc')
expect(buffer.isEmpty()).toBeFalsy()
buffer.setText('\n')
expect(buffer.isEmpty()).toBeFalsy()
describe "stopped-changing event", ->
it "fires 'stoppedChangingDelay' ms after the last buffer change", ->
delay = buffer.stoppedChangingDelay
stoppedChangingHandler = jasmine.createSpy("stoppedChangingHandler")
buffer.on 'stopped-changing', stoppedChangingHandler
buffer.insert([0, 0], 'a')
expect(stoppedChangingHandler).not.toHaveBeenCalled()
advanceClock(delay / 2)
buffer.insert([0, 0], 'b')
expect(stoppedChangingHandler).not.toHaveBeenCalled()
advanceClock(delay / 2)
expect(stoppedChangingHandler).not.toHaveBeenCalled()
advanceClock(delay / 2)
expect(stoppedChangingHandler).toHaveBeenCalled()