2012-08-28 02:36:36 +04:00
|
|
|
$ = require 'jquery'
|
|
|
|
fs = require 'fs'
|
|
|
|
RootView = require 'root-view'
|
|
|
|
Buffer = require 'buffer'
|
|
|
|
Editor = require 'editor'
|
2012-09-26 03:38:48 +04:00
|
|
|
{View, $$} = require 'space-pen'
|
2012-08-28 02:36:36 +04:00
|
|
|
|
|
|
|
describe "RootView", ->
|
|
|
|
rootView = null
|
|
|
|
path = null
|
|
|
|
|
|
|
|
beforeEach ->
|
|
|
|
path = require.resolve 'fixtures/dir/a'
|
|
|
|
rootView = new RootView(path)
|
|
|
|
rootView.enableKeymap()
|
|
|
|
rootView.focus()
|
|
|
|
|
|
|
|
afterEach ->
|
2012-09-26 00:22:06 +04:00
|
|
|
rootView.deactivate()
|
2012-08-28 02:36:36 +04:00
|
|
|
|
|
|
|
describe "initialize(pathToOpen)", ->
|
|
|
|
describe "when called with a pathToOpen", ->
|
|
|
|
describe "when pathToOpen references a file", ->
|
|
|
|
it "creates a project for the file's parent directory, then sets the document.title and opens the file in an editor", ->
|
|
|
|
expect(rootView.project.getPath()).toBe fs.directory(path)
|
|
|
|
expect(rootView.getEditors().length).toBe 1
|
|
|
|
expect(rootView.getEditors()[0]).toHaveClass 'active'
|
|
|
|
expect(rootView.getActiveEditor().getPath()).toBe path
|
|
|
|
expect(rootView.getActiveEditor().editSessions.length).toBe 1
|
|
|
|
expect(document.title).toBe path
|
|
|
|
|
|
|
|
describe "when pathToOpen references a directory", ->
|
|
|
|
beforeEach ->
|
|
|
|
rootView.remove()
|
|
|
|
|
|
|
|
it "creates a project for the directory and sets the document.title, but does not open an editor", ->
|
|
|
|
path = require.resolve 'fixtures/dir'
|
|
|
|
rootView = new RootView(path)
|
|
|
|
rootView.focus()
|
|
|
|
|
|
|
|
expect(rootView.project.getPath()).toBe path
|
|
|
|
expect(rootView.getEditors().length).toBe 0
|
|
|
|
expect(document.title).toBe path
|
|
|
|
|
|
|
|
describe "when called with view state data returned from a previous call to RootView.prototype.serialize", ->
|
|
|
|
viewState = null
|
|
|
|
|
|
|
|
describe "when the serialized RootView has an unsaved buffer", ->
|
|
|
|
buffer = null
|
|
|
|
|
|
|
|
beforeEach ->
|
|
|
|
rootView.remove()
|
|
|
|
rootView = new RootView
|
|
|
|
rootView.open()
|
|
|
|
editor1 = rootView.getActiveEditor()
|
|
|
|
buffer = editor1.getBuffer()
|
|
|
|
editor1.splitRight()
|
|
|
|
viewState = rootView.serialize()
|
|
|
|
|
|
|
|
it "constructs the view with the same panes", ->
|
|
|
|
rootView = RootView.deserialize(viewState)
|
|
|
|
expect(rootView.project.getPath()?).toBeFalsy()
|
|
|
|
expect(rootView.getEditors().length).toBe 2
|
|
|
|
expect(rootView.getActiveEditor().getText()).toBe buffer.getText()
|
|
|
|
expect(document.title).toBe 'untitled'
|
|
|
|
|
|
|
|
describe "when the serialized RootView has a project", ->
|
|
|
|
beforeEach ->
|
|
|
|
path = require.resolve 'fixtures'
|
|
|
|
rootView.remove()
|
|
|
|
rootView = new RootView(path)
|
|
|
|
rootView.open('dir/a')
|
|
|
|
|
|
|
|
editor1 = rootView.getActiveEditor()
|
|
|
|
editor2 = editor1.splitRight()
|
|
|
|
editor3 = editor2.splitRight()
|
|
|
|
editor4 = editor2.splitDown()
|
|
|
|
editor2.edit(rootView.project.buildEditSessionForPath('dir/b'))
|
|
|
|
editor3.edit(rootView.project.buildEditSessionForPath('sample.js'))
|
|
|
|
editor3.setCursorScreenPosition([2, 3])
|
|
|
|
editor4.edit(rootView.project.buildEditSessionForPath('sample.txt'))
|
|
|
|
editor4.setCursorScreenPosition([0, 2])
|
|
|
|
rootView.attachToDom()
|
|
|
|
editor2.focus()
|
|
|
|
viewState = rootView.serialize()
|
|
|
|
rootView.remove()
|
|
|
|
|
|
|
|
it "constructs the view with the same project and panes", ->
|
|
|
|
rootView = RootView.deserialize(viewState)
|
|
|
|
rootView.attachToDom()
|
|
|
|
|
|
|
|
expect(rootView.getEditors().length).toBe 4
|
|
|
|
editor1 = rootView.panes.find('.row > .pane .editor:eq(0)').view()
|
|
|
|
editor3 = rootView.panes.find('.row > .pane .editor:eq(1)').view()
|
|
|
|
editor2 = rootView.panes.find('.row > .column > .pane .editor:eq(0)').view()
|
|
|
|
editor4 = rootView.panes.find('.row > .column > .pane .editor:eq(1)').view()
|
|
|
|
|
|
|
|
expect(editor1.getPath()).toBe require.resolve('fixtures/dir/a')
|
|
|
|
expect(editor2.getPath()).toBe require.resolve('fixtures/dir/b')
|
|
|
|
expect(editor3.getPath()).toBe require.resolve('fixtures/sample.js')
|
|
|
|
expect(editor3.getCursorScreenPosition()).toEqual [2, 3]
|
|
|
|
expect(editor4.getPath()).toBe require.resolve('fixtures/sample.txt')
|
|
|
|
expect(editor4.getCursorScreenPosition()).toEqual [0, 2]
|
|
|
|
|
|
|
|
# ensure adjust pane dimensions is called
|
|
|
|
expect(editor1.width()).toBeGreaterThan 0
|
|
|
|
expect(editor2.width()).toBeGreaterThan 0
|
|
|
|
expect(editor3.width()).toBeGreaterThan 0
|
|
|
|
expect(editor4.width()).toBeGreaterThan 0
|
|
|
|
|
|
|
|
# ensure correct editor is focused again
|
|
|
|
expect(editor2.isFocused).toBeTruthy()
|
|
|
|
expect(editor1.isFocused).toBeFalsy()
|
|
|
|
expect(editor3.isFocused).toBeFalsy()
|
|
|
|
expect(editor4.isFocused).toBeFalsy()
|
|
|
|
|
|
|
|
expect(document.title).toBe editor2.getPath()
|
|
|
|
|
|
|
|
describe "when called with no pathToOpen", ->
|
2012-09-26 00:22:06 +04:00
|
|
|
it "opens an empty buffer", ->
|
2012-08-28 02:36:36 +04:00
|
|
|
rootView.remove()
|
|
|
|
rootView = new RootView
|
2012-09-26 00:22:06 +04:00
|
|
|
expect(rootView.getEditors().length).toBe 1
|
|
|
|
expect(rootView.getEditors()[0].getText()).toEqual ""
|
2012-08-28 02:36:36 +04:00
|
|
|
expect(document.title).toBe 'untitled'
|
|
|
|
|
|
|
|
describe ".serialize()", ->
|
|
|
|
it "absorbs exceptions that are thrown by extension serialize methods", ->
|
|
|
|
spyOn(console, 'error')
|
|
|
|
|
|
|
|
rootView.activateExtension(
|
|
|
|
name: "bad-egg"
|
|
|
|
activate: ->
|
|
|
|
serialize: -> throw new Error("I'm broken")
|
|
|
|
)
|
|
|
|
|
|
|
|
rootView.activateExtension(
|
|
|
|
name: "good-egg"
|
|
|
|
activate: ->
|
|
|
|
serialize: -> "I still get called"
|
|
|
|
)
|
|
|
|
|
|
|
|
data = rootView.serialize()
|
|
|
|
expect(data.extensionStates['good-egg']).toBe "I still get called"
|
|
|
|
expect(data.extensionStates['bad-egg']).toBeUndefined()
|
|
|
|
expect(console.error).toHaveBeenCalled()
|
|
|
|
|
|
|
|
describe "focus", ->
|
2012-09-26 01:11:07 +04:00
|
|
|
describe "when there is an active editor", ->
|
|
|
|
it "hands off focus to the active editor", ->
|
|
|
|
rootView.remove()
|
|
|
|
rootView = new RootView(require.resolve 'fixtures')
|
|
|
|
rootView.attachToDom()
|
2012-08-28 02:36:36 +04:00
|
|
|
|
2012-09-26 01:11:07 +04:00
|
|
|
rootView.open() # create an editor
|
|
|
|
expect(rootView).not.toMatchSelector(':focus')
|
|
|
|
expect(rootView.getActiveEditor().isFocused).toBeTruthy()
|
2012-08-28 02:36:36 +04:00
|
|
|
|
2012-09-26 01:11:07 +04:00
|
|
|
rootView.focus()
|
|
|
|
expect(rootView).not.toMatchSelector(':focus')
|
|
|
|
expect(rootView.getActiveEditor().isFocused).toBeTruthy()
|
2012-08-28 02:36:36 +04:00
|
|
|
|
2012-09-26 01:11:07 +04:00
|
|
|
describe "when there is no active editor", ->
|
2012-09-26 03:38:48 +04:00
|
|
|
describe "when are visible focusable elements (with a -1 tabindex)", ->
|
|
|
|
it "passes focus to the first focusable element", ->
|
2012-09-26 01:11:07 +04:00
|
|
|
rootView.remove()
|
|
|
|
rootView = new RootView(require.resolve 'fixtures')
|
|
|
|
|
2012-09-26 03:38:48 +04:00
|
|
|
rootView.horizontal.append $$ ->
|
|
|
|
@div "One", id: 'one', tabindex: -1
|
|
|
|
@div "Two", id: 'two', tabindex: -1
|
|
|
|
|
|
|
|
rootView.attachToDom()
|
2012-09-26 01:11:07 +04:00
|
|
|
expect(rootView).not.toMatchSelector(':focus')
|
2012-09-26 03:38:48 +04:00
|
|
|
expect(rootView.find('#one')).toMatchSelector(':focus')
|
|
|
|
expect(rootView.find('#two')).not.toMatchSelector(':focus')
|
2012-09-26 00:22:06 +04:00
|
|
|
|
2012-09-26 01:11:07 +04:00
|
|
|
describe "when there are no visible focusable elements", ->
|
|
|
|
it "retains focus itself", ->
|
|
|
|
rootView.remove()
|
|
|
|
rootView = new RootView(require.resolve 'fixtures')
|
|
|
|
rootView.attachToDom()
|
|
|
|
expect(rootView).toMatchSelector(':focus')
|
2012-09-26 00:22:06 +04:00
|
|
|
|
2012-08-28 02:36:36 +04:00
|
|
|
describe "panes", ->
|
|
|
|
[pane1, newPaneContent] = []
|
|
|
|
|
|
|
|
beforeEach ->
|
|
|
|
rootView.attachToDom()
|
|
|
|
rootView.width(800)
|
|
|
|
rootView.height(600)
|
|
|
|
pane1 = rootView.find('.pane').view()
|
|
|
|
pane1.attr('id', 'pane-1')
|
|
|
|
newPaneContent = $("<div>New pane content</div>")
|
|
|
|
spyOn(newPaneContent, 'focus')
|
|
|
|
|
|
|
|
describe "vertical splits", ->
|
|
|
|
describe "when .splitRight(view) is called on a pane", ->
|
|
|
|
it "places a new pane to the right of the current pane in a .row div", ->
|
|
|
|
expect(rootView.panes.find('.row')).not.toExist()
|
|
|
|
|
|
|
|
pane2 = pane1.splitRight(newPaneContent)
|
|
|
|
expect(newPaneContent.focus).toHaveBeenCalled()
|
|
|
|
|
|
|
|
expect(rootView.panes.find('.row')).toExist()
|
|
|
|
expect(rootView.panes.find('.row .pane').length).toBe 2
|
|
|
|
[leftPane, rightPane] = rootView.panes.find('.row .pane').map -> $(this)
|
|
|
|
expect(rightPane[0]).toBe pane2[0]
|
|
|
|
expect(leftPane.attr('id')).toBe 'pane-1'
|
|
|
|
expect(rightPane.html()).toBe "<div>New pane content</div>"
|
|
|
|
|
|
|
|
expectedColumnWidth = Math.floor(rootView.panes.width() / 2)
|
|
|
|
expect(leftPane.outerWidth()).toBe expectedColumnWidth
|
|
|
|
expect(rightPane.position().left).toBe expectedColumnWidth
|
|
|
|
expect(rightPane.outerWidth()).toBe expectedColumnWidth
|
|
|
|
|
|
|
|
pane2.remove()
|
|
|
|
|
|
|
|
expect(rootView.panes.find('.row')).not.toExist()
|
|
|
|
expect(rootView.panes.find('.pane').length).toBe 1
|
|
|
|
expect(pane1.outerWidth()).toBe rootView.panes.width()
|
|
|
|
|
|
|
|
describe "when splitLeft(view) is called on a pane", ->
|
|
|
|
it "places a new pane to the left of the current pane in a .row div", ->
|
|
|
|
expect(rootView.find('.row')).not.toExist()
|
|
|
|
|
|
|
|
pane2 = pane1.splitLeft(newPaneContent)
|
|
|
|
expect(newPaneContent.focus).toHaveBeenCalled()
|
|
|
|
|
|
|
|
expect(rootView.find('.row')).toExist()
|
|
|
|
expect(rootView.find('.row .pane').length).toBe 2
|
|
|
|
[leftPane, rightPane] = rootView.find('.row .pane').map -> $(this)
|
|
|
|
expect(leftPane[0]).toBe pane2[0]
|
|
|
|
expect(rightPane.attr('id')).toBe 'pane-1'
|
|
|
|
expect(leftPane.html()).toBe "<div>New pane content</div>"
|
|
|
|
|
|
|
|
expectedColumnWidth = Math.floor(rootView.panes.width() / 2)
|
|
|
|
expect(leftPane.outerWidth()).toBe expectedColumnWidth
|
|
|
|
expect(rightPane.position().left).toBe expectedColumnWidth
|
|
|
|
expect(rightPane.outerWidth()).toBe expectedColumnWidth
|
|
|
|
|
|
|
|
pane2.remove()
|
|
|
|
|
|
|
|
expect(rootView.panes.find('.row')).not.toExist()
|
|
|
|
expect(rootView.panes.find('.pane').length).toBe 1
|
|
|
|
expect(pane1.outerWidth()).toBe rootView.panes.width()
|
|
|
|
expect(pane1.position().left).toBe 0
|
|
|
|
|
|
|
|
describe "horizontal splits", ->
|
|
|
|
describe "when splitUp(view) is called on a pane", ->
|
|
|
|
it "places a new pane above the current pane in a .column div", ->
|
|
|
|
expect(rootView.find('.column')).not.toExist()
|
|
|
|
|
|
|
|
pane2 = pane1.splitUp(newPaneContent)
|
|
|
|
expect(newPaneContent.focus).toHaveBeenCalled()
|
|
|
|
|
|
|
|
expect(rootView.find('.column')).toExist()
|
|
|
|
expect(rootView.find('.column .pane').length).toBe 2
|
|
|
|
[topPane, bottomPane] = rootView.find('.column .pane').map -> $(this)
|
|
|
|
expect(topPane[0]).toBe pane2[0]
|
|
|
|
expect(bottomPane.attr('id')).toBe 'pane-1'
|
|
|
|
expect(topPane.html()).toBe "<div>New pane content</div>"
|
|
|
|
|
|
|
|
expectedRowHeight = Math.floor(rootView.panes.height() / 2)
|
|
|
|
expect(topPane.outerHeight()).toBe expectedRowHeight
|
|
|
|
expect(bottomPane.position().top).toBe expectedRowHeight
|
|
|
|
expect(bottomPane.outerHeight()).toBe expectedRowHeight
|
|
|
|
|
|
|
|
pane2.remove()
|
|
|
|
|
|
|
|
expect(rootView.panes.find('.column')).not.toExist()
|
|
|
|
expect(rootView.panes.find('.pane').length).toBe 1
|
|
|
|
expect(pane1.outerHeight()).toBe rootView.panes.height()
|
|
|
|
expect(pane1.position().top).toBe 0
|
|
|
|
|
|
|
|
describe "when splitDown(view) is called on a pane", ->
|
|
|
|
it "places a new pane below the current pane in a .column div", ->
|
|
|
|
expect(rootView.find('.column')).not.toExist()
|
|
|
|
|
|
|
|
pane2 = pane1.splitDown(newPaneContent)
|
|
|
|
expect(newPaneContent.focus).toHaveBeenCalled()
|
|
|
|
|
|
|
|
expect(rootView.find('.column')).toExist()
|
|
|
|
expect(rootView.find('.column .pane').length).toBe 2
|
|
|
|
[topPane, bottomPane] = rootView.find('.column .pane').map -> $(this)
|
|
|
|
expect(bottomPane[0]).toBe pane2[0]
|
|
|
|
expect(topPane.attr('id')).toBe 'pane-1'
|
|
|
|
expect(bottomPane.html()).toBe "<div>New pane content</div>"
|
|
|
|
|
|
|
|
expectedRowHeight = Math.floor(rootView.panes.height() / 2)
|
|
|
|
expect(topPane.outerHeight()).toBe expectedRowHeight
|
|
|
|
expect(bottomPane.position().top).toBe expectedRowHeight
|
|
|
|
expect(bottomPane.outerHeight()).toBe expectedRowHeight
|
|
|
|
|
|
|
|
pane2.remove()
|
|
|
|
|
|
|
|
expect(rootView.panes.find('.column')).not.toExist()
|
|
|
|
expect(rootView.panes.find('.pane').length).toBe 1
|
|
|
|
expect(pane1.outerHeight()).toBe rootView.panes.height()
|
|
|
|
|
|
|
|
describe "layout of nested vertical and horizontal splits", ->
|
|
|
|
it "lays out rows and columns with a consistent width", ->
|
|
|
|
pane1.html("1")
|
|
|
|
|
|
|
|
pane1
|
|
|
|
.splitLeft("2")
|
|
|
|
.splitUp("3")
|
|
|
|
.splitLeft("4")
|
|
|
|
.splitDown("5")
|
|
|
|
|
|
|
|
row1 = rootView.panes.children(':eq(0)')
|
|
|
|
expect(row1.children().length).toBe 2
|
|
|
|
column1 = row1.children(':eq(0)').view()
|
|
|
|
pane1 = row1.children(':eq(1)').view()
|
2012-08-31 03:12:40 +04:00
|
|
|
expect(column1.outerWidth()).toBe Math.round(2/3 * rootView.panes.width())
|
2012-08-28 02:36:36 +04:00
|
|
|
expect(column1.outerHeight()).toBe rootView.height()
|
2012-08-31 03:12:40 +04:00
|
|
|
expect(pane1.outerWidth()).toBe Math.round(1/3 * rootView.panes.width())
|
2012-08-28 02:36:36 +04:00
|
|
|
expect(pane1.outerHeight()).toBe rootView.height()
|
2012-08-31 03:12:40 +04:00
|
|
|
expect(Math.round(pane1.position().left)).toBe column1.outerWidth()
|
2012-08-28 02:36:36 +04:00
|
|
|
|
|
|
|
expect(column1.children().length).toBe 2
|
|
|
|
row2 = column1.children(':eq(0)').view()
|
|
|
|
pane2 = column1.children(':eq(1)').view()
|
|
|
|
expect(row2.outerWidth()).toBe column1.outerWidth()
|
2012-08-31 03:12:40 +04:00
|
|
|
expect(row2.height()).toBe 2/3 * rootView.panes.height()
|
2012-08-28 02:36:36 +04:00
|
|
|
expect(pane2.outerWidth()).toBe column1.outerWidth()
|
2012-08-31 03:12:40 +04:00
|
|
|
expect(pane2.outerHeight()).toBe 1/3 * rootView.panes.height()
|
2012-08-28 02:36:36 +04:00
|
|
|
expect(pane2.position().top).toBe row2.height()
|
|
|
|
|
|
|
|
expect(row2.children().length).toBe 2
|
|
|
|
column3 = row2.children(':eq(0)').view()
|
|
|
|
pane3 = row2.children(':eq(1)').view()
|
2012-08-31 03:12:40 +04:00
|
|
|
expect(column3.outerWidth()).toBe Math.round(1/3 * rootView.panes.width())
|
2012-08-28 02:36:36 +04:00
|
|
|
expect(column3.outerHeight()).toBe row2.outerHeight()
|
2012-08-31 03:12:40 +04:00
|
|
|
# the built in rounding seems to be rounding x.5 down, but we need to go up. this sucks.
|
|
|
|
expect(Math.round(pane3.trueWidth())).toBe Math.round(1/3 * rootView.panes.width())
|
2012-08-28 02:36:36 +04:00
|
|
|
expect(pane3.height()).toBe row2.outerHeight()
|
2012-08-31 03:12:40 +04:00
|
|
|
expect(Math.round(pane3.position().left)).toBe column3.width()
|
2012-08-28 02:36:36 +04:00
|
|
|
|
|
|
|
expect(column3.children().length).toBe 2
|
|
|
|
pane4 = column3.children(':eq(0)').view()
|
|
|
|
pane5 = column3.children(':eq(1)').view()
|
|
|
|
expect(pane4.outerWidth()).toBe column3.width()
|
2012-08-31 03:12:40 +04:00
|
|
|
expect(pane4.outerHeight()).toBe 1/3 * rootView.panes.height()
|
2012-08-28 02:36:36 +04:00
|
|
|
expect(pane5.outerWidth()).toBe column3.width()
|
|
|
|
expect(pane5.position().top).toBe pane4.outerHeight()
|
2012-08-31 03:12:40 +04:00
|
|
|
expect(pane5.outerHeight()).toBe 1/3 * rootView.panes.height()
|
2012-08-28 02:36:36 +04:00
|
|
|
|
|
|
|
pane5.remove()
|
|
|
|
|
|
|
|
expect(column3.parent()).not.toExist()
|
|
|
|
expect(pane2.outerHeight()).toBe Math.floor(1/2 * rootView.panes.height())
|
|
|
|
expect(pane3.outerHeight()).toBe Math.floor(1/2 * rootView.panes.height())
|
|
|
|
expect(pane4.outerHeight()).toBe Math.floor(1/2 * rootView.panes.height())
|
|
|
|
|
|
|
|
pane4.remove()
|
|
|
|
expect(row2.parent()).not.toExist()
|
|
|
|
expect(pane1.outerWidth()).toBe Math.floor(1/2 * rootView.panes.width())
|
|
|
|
expect(pane2.outerWidth()).toBe Math.floor(1/2 * rootView.panes.width())
|
|
|
|
expect(pane3.outerWidth()).toBe Math.floor(1/2 * rootView.panes.width())
|
|
|
|
|
|
|
|
pane3.remove()
|
|
|
|
expect(column1.parent()).not.toExist()
|
|
|
|
expect(pane2.outerHeight()).toBe rootView.panes.height()
|
|
|
|
|
|
|
|
pane2.remove()
|
|
|
|
expect(row1.parent()).not.toExist()
|
|
|
|
expect(rootView.panes.children().length).toBe 1
|
|
|
|
expect(rootView.panes.children('.pane').length).toBe 1
|
|
|
|
expect(pane1.outerWidth()).toBe rootView.panes.width()
|
|
|
|
|
|
|
|
describe ".focusNextPane()", ->
|
|
|
|
it "focuses the wrapped view of the pane after the currently focused pane", ->
|
|
|
|
class DummyView extends View
|
|
|
|
@content: (number) -> @div(number, tabindex: -1)
|
|
|
|
|
|
|
|
view1 = pane1.wrappedView
|
|
|
|
view2 = new DummyView(2)
|
|
|
|
view3 = new DummyView(3)
|
|
|
|
pane2 = pane1.splitDown(view2)
|
|
|
|
pane3 = pane2.splitRight(view3)
|
|
|
|
rootView.attachToDom()
|
|
|
|
view1.focus()
|
|
|
|
|
|
|
|
spyOn(view1, 'focus').andCallThrough()
|
|
|
|
spyOn(view2, 'focus').andCallThrough()
|
|
|
|
spyOn(view3, 'focus').andCallThrough()
|
|
|
|
|
|
|
|
rootView.focusNextPane()
|
|
|
|
expect(view2.focus).toHaveBeenCalled()
|
|
|
|
rootView.focusNextPane()
|
|
|
|
expect(view3.focus).toHaveBeenCalled()
|
|
|
|
rootView.focusNextPane()
|
|
|
|
expect(view1.focus).toHaveBeenCalled()
|
|
|
|
|
|
|
|
describe "extensions", ->
|
|
|
|
extension = null
|
|
|
|
|
|
|
|
beforeEach ->
|
|
|
|
extension =
|
|
|
|
name: 'extension'
|
|
|
|
deactivate: ->
|
|
|
|
activate: jasmine.createSpy("activate")
|
|
|
|
serialize: -> "it worked"
|
|
|
|
|
|
|
|
describe ".activateExtension(extension)", ->
|
|
|
|
it "calls activate on the extension", ->
|
|
|
|
rootView.activateExtension(extension)
|
2012-09-29 21:57:14 +04:00
|
|
|
expect(extension.activate).toHaveBeenCalledWith(rootView, undefined, undefined)
|
2012-08-28 02:36:36 +04:00
|
|
|
|
|
|
|
it "calls activate on the extension with its previous state", ->
|
|
|
|
rootView.activateExtension(extension)
|
|
|
|
extension.activate.reset()
|
|
|
|
|
|
|
|
newRootView = RootView.deserialize(rootView.serialize())
|
|
|
|
newRootView.activateExtension(extension)
|
2012-09-29 21:57:14 +04:00
|
|
|
expect(extension.activate).toHaveBeenCalledWith(newRootView, "it worked", undefined)
|
2012-08-28 02:36:36 +04:00
|
|
|
newRootView.remove()
|
|
|
|
|
2012-09-29 21:57:14 +04:00
|
|
|
it "calls activate on the extension with the config data", ->
|
|
|
|
config = {}
|
|
|
|
rootView.activateExtension(extension, config)
|
|
|
|
expect(extension.activate).toHaveBeenCalledWith(rootView, undefined, config)
|
|
|
|
|
2012-08-28 02:36:36 +04:00
|
|
|
it "throws an exception if the extension has no 'name' property", ->
|
|
|
|
expect(-> rootView.activateExtension({ activate: -> })).toThrow()
|
|
|
|
|
|
|
|
describe ".deactivateExtension(extension)", ->
|
|
|
|
it "deactivates and removes the extension from the extension list", ->
|
|
|
|
rootView.activateExtension(extension)
|
|
|
|
expect(rootView.extensions[extension.name]).toBeTruthy()
|
|
|
|
spyOn(extension, "deactivate").andCallThrough()
|
|
|
|
rootView.deactivateExtension(extension)
|
|
|
|
expect(extension.deactivate).toHaveBeenCalled()
|
|
|
|
expect(rootView.extensions[extension.name]).toBeFalsy()
|
|
|
|
|
|
|
|
it "is called when the rootView is deactivated to deactivate all extensions", ->
|
|
|
|
rootView.activateExtension(extension)
|
|
|
|
spyOn(rootView, "deactivateExtension").andCallThrough()
|
|
|
|
spyOn(extension, "deactivate").andCallThrough()
|
|
|
|
rootView.deactivate()
|
|
|
|
expect(rootView.deactivateExtension).toHaveBeenCalled()
|
|
|
|
expect(extension.deactivate).toHaveBeenCalled()
|
|
|
|
|
|
|
|
describe "keymap wiring", ->
|
|
|
|
commandHandler = null
|
|
|
|
beforeEach ->
|
|
|
|
commandHandler = jasmine.createSpy('commandHandler')
|
|
|
|
rootView.on('foo-command', commandHandler)
|
|
|
|
|
|
|
|
window.keymap.bindKeys('*', 'x': 'foo-command')
|
|
|
|
|
|
|
|
describe "when a keydown event is triggered on the RootView (not originating from Ace)", ->
|
|
|
|
it "triggers matching keybindings for that event", ->
|
|
|
|
event = keydownEvent 'x', target: rootView[0]
|
|
|
|
|
|
|
|
rootView.trigger(event)
|
|
|
|
expect(commandHandler).toHaveBeenCalled()
|
|
|
|
|
|
|
|
describe ".activeKeybindings()", ->
|
|
|
|
originalKeymap = null
|
|
|
|
keymap = null
|
|
|
|
editor = null
|
|
|
|
|
|
|
|
beforeEach ->
|
|
|
|
rootView.attachToDom()
|
|
|
|
editor = rootView.getActiveEditor()
|
|
|
|
keymap = new (require 'keymap')
|
|
|
|
originalKeymap = window.keymap
|
|
|
|
window.keymap = keymap
|
|
|
|
|
|
|
|
afterEach ->
|
|
|
|
window.keymap = originalKeymap
|
|
|
|
|
|
|
|
it "returns all keybindings available for focused element", ->
|
|
|
|
editor.on 'test-event-a', => # nothing
|
|
|
|
|
|
|
|
keymap.bindKeys ".editor",
|
|
|
|
"meta-a": "test-event-a"
|
|
|
|
"meta-b": "test-event-b"
|
|
|
|
|
|
|
|
keybindings = rootView.activeKeybindings()
|
|
|
|
expect(Object.keys(keybindings).length).toBe 2
|
|
|
|
expect(keybindings["meta-a"]).toEqual "test-event-a"
|
|
|
|
|
|
|
|
describe "when the focused editor changes", ->
|
|
|
|
it "changes the document.title and emits an active-editor-path-change event", ->
|
|
|
|
pathChangeHandler = jasmine.createSpy 'pathChangeHandler'
|
|
|
|
rootView.on 'active-editor-path-change', pathChangeHandler
|
|
|
|
|
|
|
|
editor1 = rootView.getActiveEditor()
|
|
|
|
expect(document.title).toBe path
|
|
|
|
|
|
|
|
editor2 = rootView.getActiveEditor().splitLeft()
|
|
|
|
|
|
|
|
path = rootView.project.resolve('b')
|
|
|
|
editor2.edit(rootView.project.buildEditSessionForPath(path))
|
|
|
|
expect(pathChangeHandler).toHaveBeenCalled()
|
|
|
|
expect(document.title).toBe rootView.project.resolve(path)
|
|
|
|
|
|
|
|
pathChangeHandler.reset()
|
|
|
|
editor1.getBuffer().saveAs("/tmp/should-not-be-title.txt")
|
|
|
|
expect(pathChangeHandler).not.toHaveBeenCalled()
|
|
|
|
expect(document.title).toBe rootView.project.resolve(path)
|
|
|
|
|
|
|
|
it "creates a project if there isn't one yet and the buffer was previously unsaved", ->
|
|
|
|
rootView.remove()
|
|
|
|
rootView = new RootView
|
|
|
|
rootView.open()
|
|
|
|
expect(rootView.project.getPath()?).toBeFalsy()
|
|
|
|
rootView.getActiveEditor().getBuffer().saveAs('/tmp/ignore-me')
|
|
|
|
expect(rootView.project.getPath()).toBe '/tmp'
|
|
|
|
|
|
|
|
describe "when editors are focused", ->
|
|
|
|
it "triggers 'active-editor-path-change' events if the path of the active editor actually changes", ->
|
|
|
|
pathChangeHandler = jasmine.createSpy 'pathChangeHandler'
|
|
|
|
rootView.on 'active-editor-path-change', pathChangeHandler
|
|
|
|
|
|
|
|
editor1 = rootView.getActiveEditor()
|
|
|
|
editor2 = rootView.getActiveEditor().splitLeft()
|
|
|
|
|
|
|
|
rootView.open(require.resolve('fixtures/sample.txt'))
|
|
|
|
expect(pathChangeHandler).toHaveBeenCalled()
|
|
|
|
pathChangeHandler.reset()
|
|
|
|
|
|
|
|
editor1.focus()
|
|
|
|
expect(pathChangeHandler).toHaveBeenCalled()
|
|
|
|
pathChangeHandler.reset()
|
|
|
|
|
|
|
|
rootView.focus()
|
|
|
|
expect(pathChangeHandler).not.toHaveBeenCalled()
|
|
|
|
|
|
|
|
editor2.edit(editor1.activeEditSession.copy())
|
|
|
|
editor2.focus()
|
|
|
|
expect(pathChangeHandler).not.toHaveBeenCalled()
|
|
|
|
|
|
|
|
describe "when the last editor is removed", ->
|
|
|
|
it "updates the title to the project path", ->
|
|
|
|
rootView.getEditors()[0].remove()
|
|
|
|
expect(document.title).toBe rootView.project.getPath()
|
|
|
|
|
|
|
|
describe "font size adjustment", ->
|
|
|
|
it "increases/decreases font size when increase/decrease-font-size events are triggered", ->
|
|
|
|
fontSizeBefore = rootView.getFontSize()
|
2012-10-19 22:51:36 +04:00
|
|
|
rootView.trigger 'window:increase-font-size'
|
2012-08-28 02:36:36 +04:00
|
|
|
expect(rootView.getFontSize()).toBe fontSizeBefore + 1
|
2012-10-19 22:51:36 +04:00
|
|
|
rootView.trigger 'window:increase-font-size'
|
2012-08-28 02:36:36 +04:00
|
|
|
expect(rootView.getFontSize()).toBe fontSizeBefore + 2
|
2012-10-19 22:51:36 +04:00
|
|
|
rootView.trigger 'window:decrease-font-size'
|
2012-08-28 02:36:36 +04:00
|
|
|
expect(rootView.getFontSize()).toBe fontSizeBefore + 1
|
2012-10-19 22:51:36 +04:00
|
|
|
rootView.trigger 'window:decrease-font-size'
|
2012-08-28 02:36:36 +04:00
|
|
|
expect(rootView.getFontSize()).toBe fontSizeBefore
|
|
|
|
|
|
|
|
it "does not allow the font size to be less than 1", ->
|
|
|
|
rootView.setFontSize(1)
|
|
|
|
expect(rootView.getFontSize()).toBe 1
|
|
|
|
|
|
|
|
rootView.setFontSize(0)
|
|
|
|
expect(rootView.getFontSize()).toBe 1
|
|
|
|
|
2012-09-20 20:31:04 +04:00
|
|
|
it "is serialized and set when deserialized", ->
|
|
|
|
rootView.setFontSize(100)
|
|
|
|
rootView.remove()
|
|
|
|
newRootView = RootView.deserialize(rootView.serialize())
|
|
|
|
expect(newRootView.getFontSize()).toBe(100)
|
|
|
|
|
2012-08-28 02:36:36 +04:00
|
|
|
describe ".open(path, options)", ->
|
|
|
|
describe "when there is no active editor", ->
|
|
|
|
beforeEach ->
|
|
|
|
rootView.getActiveEditor().destroyActiveEditSession()
|
|
|
|
expect(rootView.getActiveEditor()).toBeUndefined()
|
|
|
|
|
|
|
|
describe "when called with no path", ->
|
|
|
|
it "opens / returns an edit session for an empty buffer in a new editor", ->
|
|
|
|
editSession = rootView.open()
|
|
|
|
expect(rootView.getActiveEditor()).toBeDefined()
|
|
|
|
expect(rootView.getActiveEditor().getPath()).toBeUndefined()
|
|
|
|
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
|
|
|
|
|
|
|
|
describe "when called with a path", ->
|
|
|
|
it "opens a buffer with the given path in a new editor", ->
|
|
|
|
editSession = rootView.open('b')
|
|
|
|
expect(rootView.getActiveEditor()).toBeDefined()
|
|
|
|
expect(rootView.getActiveEditor().getPath()).toBe require.resolve('fixtures/dir/b')
|
|
|
|
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
|
|
|
|
|
|
|
|
describe "when there is an active editor", ->
|
|
|
|
beforeEach ->
|
|
|
|
expect(rootView.getActiveEditor()).toBeDefined()
|
|
|
|
|
|
|
|
describe "when called with no path", ->
|
|
|
|
it "opens an empty buffer in the active editor", ->
|
|
|
|
editSession = rootView.open()
|
|
|
|
expect(rootView.getActiveEditor().getPath()).toBeUndefined()
|
|
|
|
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
|
|
|
|
|
|
|
|
describe "when called with a path", ->
|
|
|
|
[editor1, editor2] = []
|
|
|
|
beforeEach ->
|
|
|
|
rootView.attachToDom()
|
|
|
|
editor1 = rootView.getActiveEditor()
|
|
|
|
editor2 = editor1.splitRight()
|
|
|
|
rootView.open('b')
|
|
|
|
editor2.loadPreviousEditSession()
|
|
|
|
editor1.focus()
|
|
|
|
|
|
|
|
describe "when allowActiveEditorChange is false (the default)", ->
|
|
|
|
activeEditor = null
|
|
|
|
beforeEach ->
|
|
|
|
activeEditor = rootView.getActiveEditor()
|
|
|
|
|
|
|
|
describe "when the active editor has an edit session for the given path", ->
|
|
|
|
it "re-activates the existing edit session", ->
|
|
|
|
expect(activeEditor.getPath()).toBe require.resolve('fixtures/dir/a')
|
|
|
|
previousEditSession = activeEditor.activeEditSession
|
|
|
|
|
|
|
|
editSession = rootView.open('b')
|
|
|
|
expect(activeEditor.activeEditSession).not.toBe previousEditSession
|
|
|
|
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
|
|
|
|
|
|
|
|
editSession = rootView.open('a')
|
|
|
|
expect(activeEditor.activeEditSession).toBe previousEditSession
|
|
|
|
expect(editSession).toBe previousEditSession
|
|
|
|
|
|
|
|
describe "when the active editor does not have an edit session for the given path", ->
|
|
|
|
it "creates a new edit session for the given path in the active editor", ->
|
|
|
|
editSession = rootView.open('b')
|
|
|
|
expect(activeEditor.editSessions.length).toBe 2
|
|
|
|
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
|
|
|
|
|
|
|
|
describe "when the 'allowActiveEditorChange' option is true", ->
|
|
|
|
describe "when the active editor has an edit session for the given path", ->
|
|
|
|
it "re-activates the existing edit session regardless of whether any other editor also has an edit session for the path", ->
|
|
|
|
activeEditor = rootView.getActiveEditor()
|
|
|
|
expect(activeEditor.getPath()).toBe require.resolve('fixtures/dir/a')
|
|
|
|
previousEditSession = activeEditor.activeEditSession
|
|
|
|
|
|
|
|
editSession = rootView.open('b')
|
|
|
|
expect(activeEditor.activeEditSession).not.toBe previousEditSession
|
|
|
|
expect(editSession).toBe activeEditor.activeEditSession
|
|
|
|
|
|
|
|
editSession = rootView.open('a', allowActiveEditorChange: true)
|
|
|
|
expect(activeEditor.activeEditSession).toBe previousEditSession
|
|
|
|
expect(editSession).toBe activeEditor.activeEditSession
|
|
|
|
|
|
|
|
describe "when the active editor does *not* have an edit session for the given path", ->
|
|
|
|
describe "when another editor has an edit session for the path", ->
|
|
|
|
it "focuses the other editor and activates its edit session for the path", ->
|
|
|
|
expect(rootView.getActiveEditor()).toBe editor1
|
|
|
|
editSession = rootView.open('b', allowActiveEditorChange: true)
|
|
|
|
expect(rootView.getActiveEditor()).toBe editor2
|
|
|
|
expect(editor2.getPath()).toBe require.resolve('fixtures/dir/b')
|
|
|
|
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
|
|
|
|
|
|
|
|
describe "when no other editor has an edit session for the path either", ->
|
|
|
|
it "creates a new edit session for the path on the current active editor", ->
|
|
|
|
path = require.resolve('fixtures/sample.js')
|
|
|
|
editSession = rootView.open(path, allowActiveEditorChange: true)
|
|
|
|
expect(rootView.getActiveEditor()).toBe editor1
|
|
|
|
expect(editor1.getPath()).toBe path
|
|
|
|
expect(editSession).toBe rootView.getActiveEditor().activeEditSession
|
2012-09-20 03:53:24 +04:00
|
|
|
|
2012-09-20 04:07:51 +04:00
|
|
|
describe ".saveAll()", ->
|
2012-09-20 03:53:24 +04:00
|
|
|
it "saves all open editors", ->
|
|
|
|
rootView.remove()
|
|
|
|
file1 = '/tmp/atom-temp1.txt'
|
|
|
|
file2 = '/tmp/atom-temp2.txt'
|
|
|
|
fs.write(file1, "file1")
|
|
|
|
fs.write(file2, "file2")
|
|
|
|
rootView = new RootView(file1)
|
|
|
|
|
|
|
|
editor1 = rootView.getActiveEditor()
|
|
|
|
buffer1 = editor1.activeEditSession.buffer
|
|
|
|
expect(buffer1.getText()).toBe("file1")
|
|
|
|
expect(buffer1.isModified()).toBe(false)
|
|
|
|
buffer1.setText('edited1')
|
|
|
|
expect(buffer1.isModified()).toBe(true)
|
|
|
|
|
|
|
|
editor2 = editor1.splitRight()
|
|
|
|
editor2.edit(rootView.project.buildEditSessionForPath('atom-temp2.txt'))
|
|
|
|
buffer2 = editor2.activeEditSession.buffer
|
|
|
|
expect(buffer2.getText()).toBe("file2")
|
|
|
|
expect(buffer2.isModified()).toBe(false)
|
|
|
|
buffer2.setText('edited2')
|
|
|
|
expect(buffer2.isModified()).toBe(true)
|
|
|
|
|
|
|
|
rootView.saveAll()
|
|
|
|
|
|
|
|
expect(buffer1.isModified()).toBe(false)
|
|
|
|
expect(fs.read(buffer1.getPath())).toBe("edited1")
|
|
|
|
expect(buffer2.isModified()).toBe(false)
|
|
|
|
expect(fs.read(buffer2.getPath())).toBe("edited2")
|
2012-10-19 01:44:21 +04:00
|
|
|
|
|
|
|
describe "window:toggle-invisibles event", ->
|
|
|
|
it "shows/hides invisibles in all open and future editors", ->
|
|
|
|
rootView.height(200)
|
|
|
|
rootView.attachToDom()
|
|
|
|
rightEditor = rootView.getActiveEditor()
|
|
|
|
rightEditor.setText(" \t ")
|
|
|
|
leftEditor = rightEditor.splitLeft()
|
|
|
|
expect(rightEditor.find(".line:first").text()).toBe " "
|
|
|
|
expect(leftEditor.find(".line:first").text()).toBe " "
|
|
|
|
|
2012-10-19 22:51:36 +04:00
|
|
|
rootView.trigger "window:toggle-invisibles"
|
2012-10-19 01:44:21 +04:00
|
|
|
expect(rightEditor.find(".line:first").text()).toBe "•▸ •¬"
|
|
|
|
expect(leftEditor.find(".line:first").text()).toBe "•▸ •¬"
|
|
|
|
|
|
|
|
lowerLeftEditor = leftEditor.splitDown()
|
|
|
|
expect(lowerLeftEditor.find(".line:first").text()).toBe "•▸ •¬"
|
|
|
|
|
2012-10-19 22:51:36 +04:00
|
|
|
rootView.trigger "window:toggle-invisibles"
|
2012-10-19 01:44:21 +04:00
|
|
|
expect(rightEditor.find(".line:first").text()).toBe " "
|
|
|
|
expect(leftEditor.find(".line:first").text()).toBe " "
|
|
|
|
|
|
|
|
lowerRightEditor = rightEditor.splitDown()
|
|
|
|
expect(lowerRightEditor.find(".line:first").text()).toBe " "
|
2012-10-29 05:29:30 +04:00
|
|
|
|
2012-10-29 23:22:29 +04:00
|
|
|
describe 'setInvisibles', ->
|
2012-10-29 05:29:30 +04:00
|
|
|
it 'updates the display of all edit sessions with the new map', ->
|
|
|
|
rootView.height(200)
|
|
|
|
rootView.attachToDom()
|
|
|
|
rightEditor = rootView.getActiveEditor()
|
|
|
|
rightEditor.setText(" \t ")
|
|
|
|
leftEditor = rightEditor.splitLeft()
|
|
|
|
|
2012-10-29 23:22:29 +04:00
|
|
|
rootView.setInvisibles
|
2012-10-29 20:48:44 +04:00
|
|
|
eol: ";"
|
|
|
|
space: "_"
|
2012-10-29 05:29:30 +04:00
|
|
|
tab: "tab"
|
2012-10-29 20:48:44 +04:00
|
|
|
|
2012-10-29 05:29:30 +04:00
|
|
|
rootView.trigger "window:toggle-invisibles"
|
|
|
|
expect(rightEditor.find(".line:first").text()).toBe "_tab _;"
|
|
|
|
expect(leftEditor.find(".line:first").text()).toBe "_tab _;"
|
|
|
|
|
2012-10-29 23:22:29 +04:00
|
|
|
describe 'getInvisibles', ->
|
2012-10-29 05:29:30 +04:00
|
|
|
it 'contains an eol key with a value', ->
|
2012-10-29 23:22:29 +04:00
|
|
|
expect(rootView.getInvisibles().eol).toBe "¬"
|
2012-10-29 05:29:30 +04:00
|
|
|
|
|
|
|
it 'contains a space key with a value', ->
|
2012-10-29 23:22:29 +04:00
|
|
|
expect(rootView.getInvisibles().space).toBe "•"
|
2012-10-29 05:29:30 +04:00
|
|
|
|
|
|
|
it 'contains a tab key with a value', ->
|
2012-10-29 23:22:29 +04:00
|
|
|
expect(rootView.getInvisibles().tab).toBe "▸"
|