pulsar/spec/pane-spec.js

1701 lines
56 KiB
JavaScript
Raw Normal View History

2019-02-22 10:55:17 +03:00
const { extend } = require('underscore-plus')
const { Emitter } = require('event-kit')
2017-06-01 21:42:18 +03:00
const Grim = require('grim')
const Pane = require('../src/pane')
const PaneContainer = require('../src/pane-container')
const { conditionPromise, timeoutPromise } = require('./async-spec-helpers')
2017-06-01 21:42:18 +03:00
describe('Pane', () => {
let confirm, showSaveDialog, deserializerDisposable
class Item {
2019-02-22 10:55:17 +03:00
static deserialize ({ name, uri }) {
2017-06-01 21:42:18 +03:00
return new Item(name, uri)
}
constructor (name, uri) {
this.name = name
this.uri = uri
this.emitter = new Emitter()
this.destroyed = false
}
2019-02-22 10:55:17 +03:00
getURI () {
return this.uri
}
getPath () {
return this.path
}
isEqual (other) {
return this.name === (other && other.name)
}
isPermanentDockItem () {
return false
}
isDestroyed () {
return this.destroyed
}
2017-06-01 21:42:18 +03:00
serialize () {
2019-02-22 10:55:17 +03:00
return { deserializer: 'Item', name: this.name, uri: this.uri }
2017-06-01 21:42:18 +03:00
}
copy () {
return new Item(this.name, this.uri)
}
destroy () {
this.destroyed = true
return this.emitter.emit('did-destroy')
}
onDidDestroy (fn) {
return this.emitter.on('did-destroy', fn)
}
onDidTerminatePendingState (callback) {
return this.emitter.on('terminate-pending-state', callback)
}
terminatePendingState () {
return this.emitter.emit('terminate-pending-state')
}
}
beforeEach(() => {
confirm = spyOn(atom.applicationDelegate, 'confirm')
showSaveDialog = spyOn(atom.applicationDelegate, 'showSaveDialog')
deserializerDisposable = atom.deserializers.add(Item)
})
afterEach(() => {
deserializerDisposable.dispose()
})
function paneParams (params) {
2019-02-22 10:55:17 +03:00
return extend(
{
applicationDelegate: atom.applicationDelegate,
config: atom.config,
deserializerManager: atom.deserializers,
notificationManager: atom.notifications
},
params
)
2017-06-01 21:42:18 +03:00
}
describe('construction', () => {
it('sets the active item to the first item', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B')] })
)
2017-06-01 21:42:18 +03:00
expect(pane.getActiveItem()).toBe(pane.itemAtIndex(0))
})
it('compacts the items array', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [undefined, new Item('A'), null, new Item('B')] })
)
2017-06-01 21:42:18 +03:00
expect(pane.getItems().length).toBe(2)
expect(pane.getActiveItem()).toBe(pane.itemAtIndex(0))
})
})
describe('::activate()', () => {
let container, pane1, pane2
beforeEach(() => {
container = new PaneContainer({
location: 'center',
config: atom.config,
applicationDelegate: atom.applicationDelegate
})
container.getActivePane().splitRight()
;[pane1, pane2] = container.getPanes()
})
it('changes the active pane on the container', () => {
expect(container.getActivePane()).toBe(pane2)
pane1.activate()
expect(container.getActivePane()).toBe(pane1)
pane2.activate()
expect(container.getActivePane()).toBe(pane2)
})
it('invokes ::onDidChangeActivePane observers on the container', () => {
const observed = []
container.onDidChangeActivePane(activePane => observed.push(activePane))
pane1.activate()
pane1.activate()
pane2.activate()
pane1.activate()
expect(observed).toEqual([pane1, pane2, pane1])
})
it('invokes ::onDidChangeActive observers on the relevant panes', () => {
const observed = []
pane1.onDidChangeActive(active => observed.push(active))
pane1.activate()
pane2.activate()
expect(observed).toEqual([true, false])
})
it('invokes ::onDidActivate() observers', () => {
let eventCount = 0
pane1.onDidActivate(() => eventCount++)
pane1.activate()
pane1.activate()
pane2.activate()
expect(eventCount).toBe(2)
})
})
describe('::addItem(item, index)', () => {
it('adds the item at the given index', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B')] })
)
2017-06-01 21:42:18 +03:00
const [item1, item2] = pane.getItems()
const item3 = new Item('C')
2019-02-22 10:55:17 +03:00
pane.addItem(item3, { index: 1 })
2017-06-01 21:42:18 +03:00
expect(pane.getItems()).toEqual([item1, item3, item2])
})
it('adds the item after the active item if no index is provided', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
)
2017-06-01 21:42:18 +03:00
const [item1, item2, item3] = pane.getItems()
pane.activateItem(item2)
const item4 = new Item('D')
pane.addItem(item4)
expect(pane.getItems()).toEqual([item1, item2, item4, item3])
})
it('sets the active item after adding the first item', () => {
const pane = new Pane(paneParams())
const item = new Item('A')
pane.addItem(item)
expect(pane.getActiveItem()).toBe(item)
})
it('invokes ::onDidAddItem() observers', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B')] })
)
2017-06-01 21:42:18 +03:00
const events = []
pane.onDidAddItem(event => events.push(event))
const item = new Item('C')
2019-02-22 10:55:17 +03:00
pane.addItem(item, { index: 1 })
expect(events).toEqual([{ item, index: 1, moved: false }])
2017-06-01 21:42:18 +03:00
})
it('throws an exception if the item is already present on a pane', () => {
const item = new Item('A')
2019-02-22 10:55:17 +03:00
const container = new PaneContainer({
config: atom.config,
applicationDelegate: atom.applicationDelegate
})
2017-06-01 21:42:18 +03:00
const pane1 = container.getActivePane()
pane1.addItem(item)
const pane2 = pane1.splitRight()
expect(() => pane2.addItem(item)).toThrow()
})
it("throws an exception if the item isn't an object", () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(paneParams({ items: [] }))
2017-06-01 21:42:18 +03:00
expect(() => pane.addItem(null)).toThrow()
expect(() => pane.addItem('foo')).toThrow()
expect(() => pane.addItem(1)).toThrow()
})
it('destroys any existing pending item', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(paneParams({ items: [] }))
2017-06-01 21:42:18 +03:00
const itemA = new Item('A')
const itemB = new Item('B')
const itemC = new Item('C')
2019-02-22 10:55:17 +03:00
pane.addItem(itemA, { pending: false })
pane.addItem(itemB, { pending: true })
pane.addItem(itemC, { pending: false })
2017-06-01 21:42:18 +03:00
expect(itemB.isDestroyed()).toBe(true)
})
it('adds the new item before destroying any existing pending item', () => {
const eventOrder = []
2019-02-22 10:55:17 +03:00
const pane = new Pane(paneParams({ items: [] }))
2017-06-01 21:42:18 +03:00
const itemA = new Item('A')
const itemB = new Item('B')
2019-02-22 10:55:17 +03:00
pane.addItem(itemA, { pending: true })
2017-06-01 21:42:18 +03:00
2019-02-22 10:55:17 +03:00
pane.onDidAddItem(function ({ item }) {
2017-06-01 21:42:18 +03:00
if (item === itemB) eventOrder.push('add')
})
2019-02-22 10:55:17 +03:00
pane.onDidRemoveItem(function ({ item }) {
2017-06-01 21:42:18 +03:00
if (item === itemA) eventOrder.push('remove')
})
pane.addItem(itemB)
waitsFor(() => eventOrder.length === 2)
runs(() => expect(eventOrder).toEqual(['add', 'remove']))
})
it('subscribes to be notified when item terminates its pending state', () => {
const fakeDisposable = { dispose: () => {} }
2019-02-22 10:55:17 +03:00
const spy = jasmine
.createSpy('onDidTerminatePendingState')
.andReturn(fakeDisposable)
2019-02-22 10:55:17 +03:00
const pane = new Pane(paneParams({ items: [] }))
const item = {
getTitle: () => '',
onDidTerminatePendingState: spy
}
pane.addItem(item)
expect(spy).toHaveBeenCalled()
})
it('subscribes to be notified when item is destroyed', () => {
const fakeDisposable = { dispose: () => {} }
2019-02-22 10:55:17 +03:00
const spy = jasmine.createSpy('onDidDestroy').andReturn(fakeDisposable)
2019-02-22 10:55:17 +03:00
const pane = new Pane(paneParams({ items: [] }))
const item = {
getTitle: () => '',
onDidDestroy: spy
}
pane.addItem(item)
expect(spy).toHaveBeenCalled()
})
2017-06-01 21:42:18 +03:00
describe('when using the old API of ::addItem(item, index)', () => {
beforeEach(() => spyOn(Grim, 'deprecate'))
it('supports the older public API', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(paneParams({ items: [] }))
2017-06-01 21:42:18 +03:00
const itemA = new Item('A')
const itemB = new Item('B')
const itemC = new Item('C')
pane.addItem(itemA, 0)
pane.addItem(itemB, 0)
pane.addItem(itemC, 0)
expect(pane.getItems()).toEqual([itemC, itemB, itemA])
})
it('shows a deprecation warning', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(paneParams({ items: [] }))
2017-06-01 21:42:18 +03:00
pane.addItem(new Item(), 2)
2019-02-22 10:55:17 +03:00
expect(Grim.deprecate).toHaveBeenCalledWith(
'Pane::addItem(item, 2) is deprecated in favor of Pane::addItem(item, {index: 2})'
)
2017-06-01 21:42:18 +03:00
})
})
})
describe('::activateItem(item)', () => {
let pane = null
beforeEach(() => {
2019-02-22 10:55:17 +03:00
pane = new Pane(paneParams({ items: [new Item('A'), new Item('B')] }))
2017-06-01 21:42:18 +03:00
})
it('changes the active item to the current item', () => {
expect(pane.getActiveItem()).toBe(pane.itemAtIndex(0))
pane.activateItem(pane.itemAtIndex(1))
expect(pane.getActiveItem()).toBe(pane.itemAtIndex(1))
})
it("adds the given item if it isn't present in ::items", () => {
const item = new Item('C')
pane.activateItem(item)
expect(pane.getItems().includes(item)).toBe(true)
expect(pane.getActiveItem()).toBe(item)
})
it('invokes ::onDidChangeActiveItem() observers', () => {
const observed = []
pane.onDidChangeActiveItem(item => observed.push(item))
pane.activateItem(pane.itemAtIndex(1))
expect(observed).toEqual([pane.itemAtIndex(1)])
})
describe('when the item being activated is pending', () => {
let itemC = null
let itemD = null
beforeEach(() => {
itemC = new Item('C')
itemD = new Item('D')
})
it('replaces the active item if it is pending', () => {
2019-02-22 10:55:17 +03:00
pane.activateItem(itemC, { pending: true })
2017-06-01 21:42:18 +03:00
expect(pane.getItems().map(item => item.name)).toEqual(['A', 'C', 'B'])
2019-02-22 10:55:17 +03:00
pane.activateItem(itemD, { pending: true })
2017-06-01 21:42:18 +03:00
expect(pane.getItems().map(item => item.name)).toEqual(['A', 'D', 'B'])
})
it('adds the item after the active item if it is not pending', () => {
2019-02-22 10:55:17 +03:00
pane.activateItem(itemC, { pending: true })
2017-06-01 21:42:18 +03:00
pane.activateItemAtIndex(2)
2019-02-22 10:55:17 +03:00
pane.activateItem(itemD, { pending: true })
2017-06-01 21:42:18 +03:00
expect(pane.getItems().map(item => item.name)).toEqual(['A', 'B', 'D'])
})
})
})
describe('::setPendingItem', () => {
let pane = null
beforeEach(() => {
pane = atom.workspace.getActivePane()
})
it('changes the pending item', () => {
expect(pane.getPendingItem()).toBeNull()
pane.setPendingItem('fake item')
expect(pane.getPendingItem()).toEqual('fake item')
})
})
describe('::onItemDidTerminatePendingState callback', () => {
let pane = null
let callbackCalled = false
beforeEach(() => {
pane = atom.workspace.getActivePane()
callbackCalled = false
})
it('is called when the pending item changes', () => {
pane.setPendingItem('fake item one')
pane.onItemDidTerminatePendingState(function (item) {
callbackCalled = true
expect(item).toEqual('fake item one')
})
pane.setPendingItem('fake item two')
expect(callbackCalled).toBeTruthy()
})
it('has access to the new pending item via ::getPendingItem', () => {
pane.setPendingItem('fake item one')
pane.onItemDidTerminatePendingState(function (item) {
callbackCalled = true
expect(pane.getPendingItem()).toEqual('fake item two')
})
pane.setPendingItem('fake item two')
expect(callbackCalled).toBeTruthy()
})
2017-06-02 00:42:45 +03:00
it("isn't called when a pending item is replaced with a new one", async () => {
2017-06-01 21:42:18 +03:00
pane = null
const pendingSpy = jasmine.createSpy('onItemDidTerminatePendingState')
const destroySpy = jasmine.createSpy('onWillDestroyItem')
2019-02-22 10:55:17 +03:00
await atom.workspace.open('sample.txt', { pending: true }).then(() => {
2017-06-02 00:42:45 +03:00
pane = atom.workspace.getActivePane()
2017-06-01 21:42:18 +03:00
})
2017-06-02 00:42:45 +03:00
pane.onItemDidTerminatePendingState(pendingSpy)
pane.onWillDestroyItem(destroySpy)
2017-06-01 21:42:18 +03:00
2019-02-22 10:55:17 +03:00
await atom.workspace.open('sample.js', { pending: true })
2017-06-02 00:42:45 +03:00
expect(destroySpy).toHaveBeenCalled()
expect(pendingSpy).not.toHaveBeenCalled()
2017-06-01 21:42:18 +03:00
})
})
describe('::activateNextRecentlyUsedItem() and ::activatePreviousRecentlyUsedItem()', () => {
it('sets the active item to the next/previous item in the itemStack, looping around at either end', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({
items: [
new Item('A'),
new Item('B'),
new Item('C'),
new Item('D'),
new Item('E')
]
})
)
2017-06-01 21:42:18 +03:00
const [item1, item2, item3, item4, item5] = pane.getItems()
pane.itemStack = [item3, item1, item2, item5, item4]
pane.activateItem(item4)
expect(pane.getActiveItem()).toBe(item4)
pane.activateNextRecentlyUsedItem()
expect(pane.getActiveItem()).toBe(item5)
pane.activateNextRecentlyUsedItem()
expect(pane.getActiveItem()).toBe(item2)
pane.activatePreviousRecentlyUsedItem()
expect(pane.getActiveItem()).toBe(item5)
pane.activatePreviousRecentlyUsedItem()
expect(pane.getActiveItem()).toBe(item4)
pane.activatePreviousRecentlyUsedItem()
expect(pane.getActiveItem()).toBe(item3)
pane.activatePreviousRecentlyUsedItem()
expect(pane.getActiveItem()).toBe(item1)
pane.activateNextRecentlyUsedItem()
expect(pane.getActiveItem()).toBe(item3)
pane.activateNextRecentlyUsedItem()
expect(pane.getActiveItem()).toBe(item4)
pane.activateNextRecentlyUsedItem()
pane.moveActiveItemToTopOfStack()
expect(pane.getActiveItem()).toBe(item5)
expect(pane.itemStack[4]).toBe(item5)
})
})
describe('::activateNextItem() and ::activatePreviousItem()', () => {
it('sets the active item to the next/previous item, looping around at either end', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
)
2017-06-01 21:42:18 +03:00
const [item1, item2, item3] = pane.getItems()
expect(pane.getActiveItem()).toBe(item1)
pane.activatePreviousItem()
expect(pane.getActiveItem()).toBe(item3)
pane.activatePreviousItem()
expect(pane.getActiveItem()).toBe(item2)
pane.activateNextItem()
expect(pane.getActiveItem()).toBe(item3)
pane.activateNextItem()
expect(pane.getActiveItem()).toBe(item1)
})
})
describe('::activateLastItem()', () => {
it('sets the active item to the last item', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
)
const [item1, , item3] = pane.getItems()
2017-06-01 21:42:18 +03:00
expect(pane.getActiveItem()).toBe(item1)
pane.activateLastItem()
expect(pane.getActiveItem()).toBe(item3)
})
})
describe('::moveItemRight() and ::moveItemLeft()', () => {
it('moves the active item to the right and left, without looping around at either end', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
)
2017-06-01 21:42:18 +03:00
const [item1, item2, item3] = pane.getItems()
pane.activateItemAtIndex(0)
expect(pane.getActiveItem()).toBe(item1)
pane.moveItemLeft()
expect(pane.getItems()).toEqual([item1, item2, item3])
pane.moveItemRight()
expect(pane.getItems()).toEqual([item2, item1, item3])
pane.moveItemLeft()
expect(pane.getItems()).toEqual([item1, item2, item3])
pane.activateItemAtIndex(2)
expect(pane.getActiveItem()).toBe(item3)
pane.moveItemRight()
expect(pane.getItems()).toEqual([item1, item2, item3])
})
})
describe('::activateItemAtIndex(index)', () => {
it('activates the item at the given index', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
)
2017-06-01 21:42:18 +03:00
const [item1, item2, item3] = pane.getItems()
pane.activateItemAtIndex(2)
expect(pane.getActiveItem()).toBe(item3)
pane.activateItemAtIndex(1)
expect(pane.getActiveItem()).toBe(item2)
pane.activateItemAtIndex(0)
expect(pane.getActiveItem()).toBe(item1)
// Doesn't fail with out-of-bounds indices
pane.activateItemAtIndex(100)
expect(pane.getActiveItem()).toBe(item1)
pane.activateItemAtIndex(-1)
expect(pane.getActiveItem()).toBe(item1)
})
})
describe('::destroyItem(item)', () => {
let pane, item1, item2, item3
beforeEach(() => {
2019-02-22 10:55:17 +03:00
pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
)
2017-06-01 21:42:18 +03:00
;[item1, item2, item3] = pane.getItems()
})
it('removes the item from the items list and destroys it', () => {
expect(pane.getActiveItem()).toBe(item1)
pane.destroyItem(item2)
expect(pane.getItems().includes(item2)).toBe(false)
expect(item2.isDestroyed()).toBe(true)
expect(pane.getActiveItem()).toBe(item1)
pane.destroyItem(item1)
expect(pane.getItems().includes(item1)).toBe(false)
expect(item1.isDestroyed()).toBe(true)
})
it('removes the item from the itemStack', () => {
pane.itemStack = [item2, item3, item1]
pane.activateItem(item1)
expect(pane.getActiveItem()).toBe(item1)
pane.destroyItem(item3)
expect(pane.itemStack).toEqual([item2, item1])
expect(pane.getActiveItem()).toBe(item1)
pane.destroyItem(item1)
expect(pane.itemStack).toEqual([item2])
expect(pane.getActiveItem()).toBe(item2)
pane.destroyItem(item2)
expect(pane.itemStack).toEqual([])
expect(pane.getActiveItem()).toBeUndefined()
})
it('invokes ::onWillDestroyItem() and PaneContainer::onWillDestroyPaneItem observers before destroying the item', async () => {
jasmine.useRealClock()
2019-02-22 10:55:17 +03:00
pane.container = new PaneContainer({ config: atom.config, confirm })
2017-06-01 21:42:18 +03:00
const events = []
2019-02-22 10:55:17 +03:00
pane.onWillDestroyItem(async event => {
2017-06-01 21:42:18 +03:00
expect(item2.isDestroyed()).toBe(false)
await timeoutPromise(50)
expect(item2.isDestroyed()).toBe(false)
events.push(['will-destroy-item', event])
})
2019-02-22 10:55:17 +03:00
pane.container.onWillDestroyPaneItem(async event => {
expect(item2.isDestroyed()).toBe(false)
await timeoutPromise(50)
expect(item2.isDestroyed()).toBe(false)
events.push(['will-destroy-pane-item', event])
2017-06-01 21:42:18 +03:00
})
await pane.destroyItem(item2)
2017-06-01 21:42:18 +03:00
expect(item2.isDestroyed()).toBe(true)
expect(events).toEqual([
2019-02-22 10:55:17 +03:00
['will-destroy-item', { item: item2, index: 1 }],
['will-destroy-pane-item', { item: item2, index: 1, pane }]
])
2017-06-01 21:42:18 +03:00
})
it('invokes ::onWillRemoveItem() observers', () => {
const events = []
pane.onWillRemoveItem(event => events.push(event))
pane.destroyItem(item2)
2019-02-22 10:55:17 +03:00
expect(events).toEqual([
{ item: item2, index: 1, moved: false, destroyed: true }
])
2017-06-01 21:42:18 +03:00
})
it('invokes ::onDidRemoveItem() observers', () => {
const events = []
pane.onDidRemoveItem(event => events.push(event))
pane.destroyItem(item2)
2019-02-22 10:55:17 +03:00
expect(events).toEqual([
{ item: item2, index: 1, moved: false, destroyed: true }
])
2017-06-01 21:42:18 +03:00
})
describe('when the destroyed item is the active item and is the first item', () => {
it('activates the next item', () => {
expect(pane.getActiveItem()).toBe(item1)
pane.destroyItem(item1)
expect(pane.getActiveItem()).toBe(item2)
})
})
describe('when the destroyed item is the active item and is not the first item', () => {
beforeEach(() => pane.activateItem(item2))
it('activates the previous item', () => {
expect(pane.getActiveItem()).toBe(item2)
pane.destroyItem(item2)
expect(pane.getActiveItem()).toBe(item1)
})
})
describe('if the item is modified', () => {
let itemURI = null
beforeEach(() => {
item1.shouldPromptToSave = () => true
item1.save = jasmine.createSpy('save')
item1.saveAs = jasmine.createSpy('saveAs')
item1.getURI = () => itemURI
})
describe('if the [Save] option is selected', () => {
describe('when the item has a uri', () => {
2017-06-02 00:42:45 +03:00
it('saves the item before destroying it', async () => {
2017-06-01 21:42:18 +03:00
itemURI = 'test'
2017-11-19 03:12:23 +03:00
confirm.andCallFake((options, callback) => callback(0))
2017-06-01 21:42:18 +03:00
const success = await pane.destroyItem(item1)
2017-06-02 00:42:45 +03:00
expect(item1.save).toHaveBeenCalled()
expect(pane.getItems().includes(item1)).toBe(false)
expect(item1.isDestroyed()).toBe(true)
expect(success).toBe(true)
2017-06-01 21:42:18 +03:00
})
})
describe('when the item has no uri', () => {
2017-06-02 00:42:45 +03:00
it('presents a save-as dialog, then saves the item with the given uri before removing and destroying it', async () => {
jasmine.useRealClock()
2017-06-01 21:42:18 +03:00
itemURI = null
2019-02-22 10:55:17 +03:00
showSaveDialog.andCallFake((options, callback) =>
callback('/selected/path')
)
2017-11-19 03:12:23 +03:00
confirm.andCallFake((options, callback) => callback(0))
2017-06-01 21:42:18 +03:00
const success = await pane.destroyItem(item1)
expect(showSaveDialog.mostRecentCall.args[0]).toEqual({})
await conditionPromise(() => item1.saveAs.callCount === 1)
2017-06-02 00:42:45 +03:00
expect(item1.saveAs).toHaveBeenCalledWith('/selected/path')
expect(pane.getItems().includes(item1)).toBe(false)
expect(item1.isDestroyed()).toBe(true)
expect(success).toBe(true)
2017-06-01 21:42:18 +03:00
})
})
})
describe("if the [Don't Save] option is selected", () => {
2017-06-02 00:42:45 +03:00
it('removes and destroys the item without saving it', async () => {
2017-11-19 03:12:23 +03:00
confirm.andCallFake((options, callback) => callback(2))
2017-06-01 21:42:18 +03:00
const success = await pane.destroyItem(item1)
2017-06-02 00:42:45 +03:00
expect(item1.save).not.toHaveBeenCalled()
expect(pane.getItems().includes(item1)).toBe(false)
expect(item1.isDestroyed()).toBe(true)
2019-02-22 10:55:17 +03:00
expect(success).toBe(true)
2017-06-01 21:42:18 +03:00
})
})
describe('if the [Cancel] option is selected', () => {
2017-06-02 00:42:45 +03:00
it('does not save, remove, or destroy the item', async () => {
2017-11-19 03:12:23 +03:00
confirm.andCallFake((options, callback) => callback(1))
2017-06-01 21:42:18 +03:00
const success = await pane.destroyItem(item1)
2017-06-01 21:42:18 +03:00
expect(item1.save).not.toHaveBeenCalled()
expect(pane.getItems().includes(item1)).toBe(true)
expect(item1.isDestroyed()).toBe(false)
expect(success).toBe(false)
2017-06-01 21:42:18 +03:00
})
})
describe('when force=true', () => {
2017-06-02 00:42:45 +03:00
it('destroys the item immediately', async () => {
const success = await pane.destroyItem(item1, true)
2017-06-01 21:42:18 +03:00
expect(item1.save).not.toHaveBeenCalled()
expect(pane.getItems().includes(item1)).toBe(false)
expect(item1.isDestroyed()).toBe(true)
expect(success).toBe(true)
2017-06-01 21:42:18 +03:00
})
})
})
describe('when the last item is destroyed', () => {
describe("when the 'core.destroyEmptyPanes' config option is false (the default)", () => {
it('does not destroy the pane, but leaves it in place with empty items', () => {
expect(atom.config.get('core.destroyEmptyPanes')).toBe(false)
2019-02-22 10:55:17 +03:00
for (let item of pane.getItems()) {
pane.destroyItem(item)
}
2017-06-01 21:42:18 +03:00
expect(pane.isDestroyed()).toBe(false)
expect(pane.getActiveItem()).toBeUndefined()
expect(() => pane.saveActiveItem()).not.toThrow()
expect(() => pane.saveActiveItemAs()).not.toThrow()
})
})
describe("when the 'core.destroyEmptyPanes' config option is true", () => {
it('destroys the pane', () => {
atom.config.set('core.destroyEmptyPanes', true)
for (let item of pane.getItems()) {
pane.destroyItem(item)
}
expect(pane.isDestroyed()).toBe(true)
})
})
})
describe('when passed a permanent dock item', () => {
it("doesn't destroy the item", async () => {
2017-06-01 21:42:18 +03:00
spyOn(item1, 'isPermanentDockItem').andReturn(true)
const success = await pane.destroyItem(item1)
2017-06-01 21:42:18 +03:00
expect(pane.getItems().includes(item1)).toBe(true)
expect(item1.isDestroyed()).toBe(false)
2019-02-22 10:55:17 +03:00
expect(success).toBe(false)
2017-06-01 21:42:18 +03:00
})
it('destroy the item if force=true', async () => {
2017-06-01 21:42:18 +03:00
spyOn(item1, 'isPermanentDockItem').andReturn(true)
const success = await pane.destroyItem(item1, true)
2017-06-01 21:42:18 +03:00
expect(pane.getItems().includes(item1)).toBe(false)
expect(item1.isDestroyed()).toBe(true)
expect(success).toBe(true)
2017-06-01 21:42:18 +03:00
})
})
})
describe('::destroyActiveItem()', () => {
it('destroys the active item', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B')] })
)
2017-06-01 21:42:18 +03:00
const activeItem = pane.getActiveItem()
pane.destroyActiveItem()
expect(activeItem.isDestroyed()).toBe(true)
expect(pane.getItems().includes(activeItem)).toBe(false)
})
it('does not throw an exception if there are no more items', () => {
const pane = new Pane(paneParams())
pane.destroyActiveItem()
})
})
describe('::destroyItems()', () => {
2017-06-02 00:42:45 +03:00
it('destroys all items', async () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
)
2017-06-01 21:42:18 +03:00
const [item1, item2, item3] = pane.getItems()
2017-06-02 00:42:45 +03:00
await pane.destroyItems()
expect(item1.isDestroyed()).toBe(true)
expect(item2.isDestroyed()).toBe(true)
expect(item3.isDestroyed()).toBe(true)
expect(pane.getItems()).toEqual([])
2017-06-01 21:42:18 +03:00
})
})
describe('::observeItems()', () => {
it('invokes the observer with all current and future items', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(paneParams({ items: [new Item(), new Item()] }))
2017-06-01 21:42:18 +03:00
const [item1, item2] = pane.getItems()
const observed = []
pane.observeItems(item => observed.push(item))
const item3 = new Item()
pane.addItem(item3)
expect(observed).toEqual([item1, item2, item3])
})
})
describe('when an item emits a destroyed event', () => {
it('removes it from the list of items', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
)
const [item1, , item3] = pane.getItems()
2017-06-01 21:42:18 +03:00
pane.itemAtIndex(1).destroy()
expect(pane.getItems()).toEqual([item1, item3])
})
})
describe('::destroyInactiveItems()', () => {
it('destroys all items but the active item', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] })
)
2017-06-01 21:42:18 +03:00
const [, item2] = pane.getItems()
pane.activateItem(item2)
pane.destroyInactiveItems()
expect(pane.getItems()).toEqual([item2])
})
})
describe('::saveActiveItem()', () => {
let pane
beforeEach(() => {
2019-02-22 10:55:17 +03:00
pane = new Pane(paneParams({ items: [new Item('A')] }))
showSaveDialog.andCallFake((options, callback) =>
callback('/selected/path')
)
2017-06-01 21:42:18 +03:00
})
describe('when the active item has a uri', () => {
beforeEach(() => {
pane.getActiveItem().uri = 'test'
})
describe('when the active item has a save method', () => {
it('saves the current item', () => {
pane.getActiveItem().save = jasmine.createSpy('save')
pane.saveActiveItem()
expect(pane.getActiveItem().save).toHaveBeenCalled()
})
})
describe('when the current item has no save method', () => {
it('does nothing', () => {
expect(pane.getActiveItem().save).toBeUndefined()
pane.saveActiveItem()
})
})
})
describe('when the current item has no uri', () => {
describe('when the current item has a saveAs method', () => {
it('opens a save dialog and saves the current item as the selected path', async () => {
2017-06-01 21:42:18 +03:00
pane.getActiveItem().saveAs = jasmine.createSpy('saveAs')
await pane.saveActiveItem()
expect(showSaveDialog.mostRecentCall.args[0]).toEqual({})
2019-02-22 10:55:17 +03:00
expect(pane.getActiveItem().saveAs).toHaveBeenCalledWith(
'/selected/path'
)
2017-06-01 21:42:18 +03:00
})
})
describe('when the current item has no saveAs method', () => {
it('does nothing', async () => {
2017-06-01 21:42:18 +03:00
expect(pane.getActiveItem().saveAs).toBeUndefined()
await pane.saveActiveItem()
2017-06-01 21:42:18 +03:00
expect(showSaveDialog).not.toHaveBeenCalled()
})
})
it('does nothing if the user cancels choosing a path', async () => {
pane.getActiveItem().saveAs = jasmine.createSpy('saveAs')
showSaveDialog.andCallFake((options, callback) => callback(undefined))
await pane.saveActiveItem()
expect(pane.getActiveItem().saveAs).not.toHaveBeenCalled()
})
2017-06-01 21:42:18 +03:00
})
describe("when the item's saveAs rejects with a well-known IO error", () => {
it('creates a notification', () => {
pane.getActiveItem().saveAs = () => {
const error = new Error("EACCES, permission denied '/foo'")
error.path = '/foo'
error.code = 'EACCES'
return Promise.reject(error)
}
2019-02-22 10:55:17 +03:00
waitsFor(done => {
const subscription = atom.notifications.onDidAddNotification(
function (notification) {
expect(notification.getType()).toBe('warning')
expect(notification.getMessage()).toContain('Permission denied')
expect(notification.getMessage()).toContain('/foo')
subscription.dispose()
done()
}
)
2017-06-01 21:42:18 +03:00
pane.saveActiveItem()
})
})
})
describe("when the item's saveAs throws a well-known IO error", () => {
it('creates a notification', () => {
pane.getActiveItem().saveAs = () => {
const error = new Error("EACCES, permission denied '/foo'")
error.path = '/foo'
error.code = 'EACCES'
throw error
}
2019-02-22 10:55:17 +03:00
waitsFor(done => {
const subscription = atom.notifications.onDidAddNotification(
function (notification) {
expect(notification.getType()).toBe('warning')
expect(notification.getMessage()).toContain('Permission denied')
expect(notification.getMessage()).toContain('/foo')
subscription.dispose()
done()
}
)
pane.saveActiveItem()
})
})
})
2017-06-01 21:42:18 +03:00
})
describe('::saveActiveItemAs()', () => {
let pane = null
beforeEach(() => {
2019-02-22 10:55:17 +03:00
pane = new Pane(paneParams({ items: [new Item('A')] }))
showSaveDialog.andCallFake((options, callback) =>
callback('/selected/path')
)
2017-06-01 21:42:18 +03:00
})
describe('when the current item has a saveAs method', () => {
it('opens the save dialog and calls saveAs on the item with the selected path', async () => {
jasmine.useRealClock()
2017-06-01 21:42:18 +03:00
pane.getActiveItem().path = __filename
pane.getActiveItem().saveAs = jasmine.createSpy('saveAs')
pane.saveActiveItemAs()
2019-02-22 10:55:17 +03:00
expect(showSaveDialog.mostRecentCall.args[0]).toEqual({
defaultPath: __filename
})
2019-02-22 10:55:17 +03:00
await conditionPromise(
() => pane.getActiveItem().saveAs.callCount === 1
)
expect(pane.getActiveItem().saveAs).toHaveBeenCalledWith(
'/selected/path'
)
2017-06-01 21:42:18 +03:00
})
})
describe('when the current item does not have a saveAs method', () => {
it('does nothing', () => {
expect(pane.getActiveItem().saveAs).toBeUndefined()
pane.saveActiveItemAs()
expect(showSaveDialog).not.toHaveBeenCalled()
})
})
describe("when the item's saveAs method throws a well-known IO error", () => {
it('creates a notification', () => {
pane.getActiveItem().saveAs = () => {
const error = new Error("EACCES, permission denied '/foo'")
error.path = '/foo'
error.code = 'EACCES'
return Promise.reject(error)
}
2019-02-22 10:55:17 +03:00
waitsFor(done => {
const subscription = atom.notifications.onDidAddNotification(
function (notification) {
expect(notification.getType()).toBe('warning')
expect(notification.getMessage()).toContain('Permission denied')
expect(notification.getMessage()).toContain('/foo')
subscription.dispose()
done()
}
)
2017-06-01 21:42:18 +03:00
pane.saveActiveItemAs()
})
})
})
})
describe('::itemForURI(uri)', () => {
it('returns the item for which a call to .getURI() returns the given uri', () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({
items: [new Item('A'), new Item('B'), new Item('C'), new Item('D')]
})
)
2017-06-01 21:42:18 +03:00
const [item1, item2] = pane.getItems()
item1.uri = 'a'
item2.uri = 'b'
expect(pane.itemForURI('a')).toBe(item1)
expect(pane.itemForURI('b')).toBe(item2)
expect(pane.itemForURI('bogus')).toBeUndefined()
})
})
describe('::moveItem(item, index)', () => {
let pane, item1, item2, item3, item4
beforeEach(() => {
2019-02-22 10:55:17 +03:00
pane = new Pane(
paneParams({
items: [new Item('A'), new Item('B'), new Item('C'), new Item('D')]
})
)
2017-06-01 21:42:18 +03:00
;[item1, item2, item3, item4] = pane.getItems()
})
it('moves the item to the given index and invokes ::onDidMoveItem observers', () => {
pane.moveItem(item1, 2)
expect(pane.getItems()).toEqual([item2, item3, item1, item4])
pane.moveItem(item2, 3)
expect(pane.getItems()).toEqual([item3, item1, item4, item2])
pane.moveItem(item2, 1)
expect(pane.getItems()).toEqual([item3, item2, item1, item4])
})
it('invokes ::onDidMoveItem() observers', () => {
const events = []
pane.onDidMoveItem(event => events.push(event))
pane.moveItem(item1, 2)
pane.moveItem(item2, 3)
expect(events).toEqual([
2019-02-22 10:55:17 +03:00
{ item: item1, oldIndex: 0, newIndex: 2 },
{ item: item2, oldIndex: 0, newIndex: 3 }
2017-06-01 21:42:18 +03:00
])
})
})
describe('::moveItemToPane(item, pane, index)', () => {
let container, pane1, pane2
let item1, item2, item3, item4, item5
beforeEach(() => {
2019-02-22 10:55:17 +03:00
container = new PaneContainer({ config: atom.config, confirm })
2017-06-01 21:42:18 +03:00
pane1 = container.getActivePane()
pane1.addItems([new Item('A'), new Item('B'), new Item('C')])
2019-02-22 10:55:17 +03:00
pane2 = pane1.splitRight({ items: [new Item('D'), new Item('E')] })
;[item1, item2, item3] = pane1.getItems()
;[item4, item5] = pane2.getItems()
2017-06-01 21:42:18 +03:00
})
it('moves the item to the given pane at the given index', () => {
pane1.moveItemToPane(item2, pane2, 1)
expect(pane1.getItems()).toEqual([item1, item3])
expect(pane2.getItems()).toEqual([item4, item2, item5])
})
it('invokes ::onWillRemoveItem() observers', () => {
const events = []
pane1.onWillRemoveItem(event => events.push(event))
pane1.moveItemToPane(item2, pane2, 1)
2019-02-22 10:55:17 +03:00
expect(events).toEqual([
{ item: item2, index: 1, moved: true, destroyed: false }
])
2017-06-01 21:42:18 +03:00
})
it('invokes ::onDidRemoveItem() observers', () => {
const events = []
pane1.onDidRemoveItem(event => events.push(event))
pane1.moveItemToPane(item2, pane2, 1)
2019-02-22 10:55:17 +03:00
expect(events).toEqual([
{ item: item2, index: 1, moved: true, destroyed: false }
])
2017-06-01 21:42:18 +03:00
})
it('does not invoke ::onDidAddPaneItem observers on the container', () => {
const addedItems = []
container.onDidAddPaneItem(item => addedItems.push(item))
pane1.moveItemToPane(item2, pane2, 1)
expect(addedItems).toEqual([])
})
describe('when the moved item the last item in the source pane', () => {
beforeEach(() => item5.destroy())
describe("when the 'core.destroyEmptyPanes' config option is false (the default)", () => {
it('does not destroy the pane or the item', () => {
pane2.moveItemToPane(item4, pane1, 0)
expect(pane2.isDestroyed()).toBe(false)
expect(item4.isDestroyed()).toBe(false)
})
})
describe("when the 'core.destroyEmptyPanes' config option is true", () => {
it('destroys the pane, but not the item', () => {
atom.config.set('core.destroyEmptyPanes', true)
pane2.moveItemToPane(item4, pane1, 0)
expect(pane2.isDestroyed()).toBe(true)
expect(item4.isDestroyed()).toBe(false)
})
})
})
describe('when the item being moved is pending', () => {
it('is made permanent in the new pane', () => {
const item6 = new Item('F')
2019-02-22 10:55:17 +03:00
pane1.addItem(item6, { pending: true })
2017-06-01 21:42:18 +03:00
expect(pane1.getPendingItem()).toEqual(item6)
pane1.moveItemToPane(item6, pane2, 0)
expect(pane2.getPendingItem()).not.toEqual(item6)
})
})
describe('when the target pane has a pending item', () => {
it('does not destroy the pending item', () => {
const item6 = new Item('F')
2019-02-22 10:55:17 +03:00
pane1.addItem(item6, { pending: true })
2017-06-01 21:42:18 +03:00
expect(pane1.getPendingItem()).toEqual(item6)
pane2.moveItemToPane(item5, pane1, 0)
expect(pane1.getPendingItem()).toEqual(item6)
})
})
})
describe('split methods', () => {
let pane1, item1, container
beforeEach(() => {
2019-02-22 10:55:17 +03:00
container = new PaneContainer({
config: atom.config,
confirm,
deserializerManager: atom.deserializers
})
2017-06-01 21:42:18 +03:00
pane1 = container.getActivePane()
item1 = new Item('A')
pane1.addItem(item1)
})
describe('::splitLeft(params)', () => {
describe('when the parent is the container root', () => {
it('replaces itself with a row and inserts a new pane to the left of itself', () => {
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitLeft({ items: [new Item('B')] })
const pane3 = pane1.splitLeft({ items: [new Item('C')] })
2017-06-01 21:42:18 +03:00
expect(container.root.orientation).toBe('horizontal')
expect(container.root.children).toEqual([pane2, pane3, pane1])
})
})
describe('when `moveActiveItem: true` is passed in the params', () => {
it('moves the active item', () => {
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitLeft({ moveActiveItem: true })
2017-06-01 21:42:18 +03:00
expect(pane2.getActiveItem()).toBe(item1)
})
})
describe('when `copyActiveItem: true` is passed in the params', () => {
it('duplicates the active item', () => {
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitLeft({ copyActiveItem: true })
2017-06-01 21:42:18 +03:00
expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem())
})
it("does nothing if the active item doesn't implement .copy()", () => {
item1.copy = null
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitLeft({ copyActiveItem: true })
2017-06-01 21:42:18 +03:00
expect(pane2.getActiveItem()).toBeUndefined()
})
})
describe('when the parent is a column', () => {
it('replaces itself with a row and inserts a new pane to the left of itself', () => {
pane1.splitDown()
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitLeft({ items: [new Item('B')] })
const pane3 = pane1.splitLeft({ items: [new Item('C')] })
2017-06-01 21:42:18 +03:00
const row = container.root.children[0]
expect(row.orientation).toBe('horizontal')
expect(row.children).toEqual([pane2, pane3, pane1])
})
})
})
describe('::splitRight(params)', () => {
describe('when the parent is the container root', () => {
it('replaces itself with a row and inserts a new pane to the right of itself', () => {
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitRight({ items: [new Item('B')] })
const pane3 = pane1.splitRight({ items: [new Item('C')] })
2017-06-01 21:42:18 +03:00
expect(container.root.orientation).toBe('horizontal')
expect(container.root.children).toEqual([pane1, pane3, pane2])
})
})
describe('when `moveActiveItem: true` is passed in the params', () => {
it('moves the active item', () => {
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitRight({ moveActiveItem: true })
2017-06-01 21:42:18 +03:00
expect(pane2.getActiveItem()).toBe(item1)
})
})
describe('when `copyActiveItem: true` is passed in the params', () => {
it('duplicates the active item', () => {
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitRight({ copyActiveItem: true })
2017-06-01 21:42:18 +03:00
expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem())
})
})
describe('when the parent is a column', () => {
it('replaces itself with a row and inserts a new pane to the right of itself', () => {
pane1.splitDown()
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitRight({ items: [new Item('B')] })
const pane3 = pane1.splitRight({ items: [new Item('C')] })
2017-06-01 21:42:18 +03:00
const row = container.root.children[0]
expect(row.orientation).toBe('horizontal')
expect(row.children).toEqual([pane1, pane3, pane2])
})
})
})
describe('::splitUp(params)', () => {
describe('when the parent is the container root', () => {
it('replaces itself with a column and inserts a new pane above itself', () => {
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitUp({ items: [new Item('B')] })
const pane3 = pane1.splitUp({ items: [new Item('C')] })
2017-06-01 21:42:18 +03:00
expect(container.root.orientation).toBe('vertical')
expect(container.root.children).toEqual([pane2, pane3, pane1])
})
})
describe('when `moveActiveItem: true` is passed in the params', () => {
it('moves the active item', () => {
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitUp({ moveActiveItem: true })
2017-06-01 21:42:18 +03:00
expect(pane2.getActiveItem()).toBe(item1)
})
})
describe('when `copyActiveItem: true` is passed in the params', () => {
it('duplicates the active item', () => {
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitUp({ copyActiveItem: true })
2017-06-01 21:42:18 +03:00
expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem())
})
})
describe('when the parent is a row', () => {
it('replaces itself with a column and inserts a new pane above itself', () => {
pane1.splitRight()
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitUp({ items: [new Item('B')] })
const pane3 = pane1.splitUp({ items: [new Item('C')] })
2017-06-01 21:42:18 +03:00
const column = container.root.children[0]
expect(column.orientation).toBe('vertical')
expect(column.children).toEqual([pane2, pane3, pane1])
})
})
})
describe('::splitDown(params)', () => {
describe('when the parent is the container root', () => {
it('replaces itself with a column and inserts a new pane below itself', () => {
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitDown({ items: [new Item('B')] })
const pane3 = pane1.splitDown({ items: [new Item('C')] })
2017-06-01 21:42:18 +03:00
expect(container.root.orientation).toBe('vertical')
expect(container.root.children).toEqual([pane1, pane3, pane2])
})
})
describe('when `moveActiveItem: true` is passed in the params', () => {
it('moves the active item', () => {
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitDown({ moveActiveItem: true })
2017-06-01 21:42:18 +03:00
expect(pane2.getActiveItem()).toBe(item1)
})
})
describe('when `copyActiveItem: true` is passed in the params', () => {
it('duplicates the active item', () => {
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitDown({ copyActiveItem: true })
2017-06-01 21:42:18 +03:00
expect(pane2.getActiveItem()).toEqual(pane1.getActiveItem())
})
})
describe('when the parent is a row', () => {
it('replaces itself with a column and inserts a new pane below itself', () => {
pane1.splitRight()
2019-02-22 10:55:17 +03:00
const pane2 = pane1.splitDown({ items: [new Item('B')] })
const pane3 = pane1.splitDown({ items: [new Item('C')] })
2017-06-01 21:42:18 +03:00
const column = container.root.children[0]
expect(column.orientation).toBe('vertical')
expect(column.children).toEqual([pane1, pane3, pane2])
})
})
})
describe('when the pane is empty', () => {
describe('when `moveActiveItem: true` is passed in the params', () => {
it('gracefully ignores the moveActiveItem parameter', () => {
pane1.destroyItem(item1)
expect(pane1.getActiveItem()).toBe(undefined)
2019-02-22 10:55:17 +03:00
const pane2 = pane1.split('horizontal', 'before', {
moveActiveItem: true
})
expect(container.root.children).toEqual([pane2, pane1])
expect(pane2.getActiveItem()).toBe(undefined)
})
})
describe('when `copyActiveItem: true` is passed in the params', () => {
it('gracefully ignores the copyActiveItem parameter', () => {
pane1.destroyItem(item1)
expect(pane1.getActiveItem()).toBe(undefined)
2019-02-22 10:55:17 +03:00
const pane2 = pane1.split('horizontal', 'before', {
copyActiveItem: true
})
expect(container.root.children).toEqual([pane2, pane1])
expect(pane2.getActiveItem()).toBe(undefined)
})
})
})
2017-06-01 21:42:18 +03:00
it('activates the new pane', () => {
expect(pane1.isActive()).toBe(true)
const pane2 = pane1.splitRight()
expect(pane1.isActive()).toBe(false)
expect(pane2.isActive()).toBe(true)
})
})
describe('::close()', () => {
2017-06-02 00:42:45 +03:00
it('prompts to save unsaved items before destroying the pane', async () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B')] })
)
2017-06-01 21:42:18 +03:00
const [item1] = pane.getItems()
item1.shouldPromptToSave = () => true
item1.getURI = () => '/test/path'
item1.save = jasmine.createSpy('save')
2017-11-19 03:12:23 +03:00
confirm.andCallFake((options, callback) => callback(0))
2017-06-02 00:42:45 +03:00
await pane.close()
expect(confirm).toHaveBeenCalled()
expect(item1.save).toHaveBeenCalled()
expect(pane.isDestroyed()).toBe(true)
2017-06-01 21:42:18 +03:00
})
2017-06-02 01:48:08 +03:00
it('does not destroy the pane if the user clicks cancel', async () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B')] })
)
2017-06-01 21:42:18 +03:00
const [item1] = pane.getItems()
item1.shouldPromptToSave = () => true
item1.getURI = () => '/test/path'
item1.save = jasmine.createSpy('save')
2017-11-19 03:12:23 +03:00
confirm.andCallFake((options, callback) => callback(1))
2017-06-02 01:48:08 +03:00
2017-06-02 00:42:45 +03:00
await pane.close()
expect(confirm).toHaveBeenCalled()
expect(item1.save).not.toHaveBeenCalled()
expect(pane.isDestroyed()).toBe(false)
2017-06-01 21:42:18 +03:00
})
2017-06-02 01:48:08 +03:00
it('does not destroy the pane if the user starts to save but then does not choose a path', async () => {
2019-02-22 10:55:17 +03:00
const pane = new Pane(
paneParams({ items: [new Item('A'), new Item('B')] })
)
2017-06-02 01:48:08 +03:00
const [item1] = pane.getItems()
item1.shouldPromptToSave = () => true
item1.saveAs = jasmine.createSpy('saveAs')
2017-11-19 03:12:23 +03:00
confirm.andCallFake((options, callback) => callback(0))
showSaveDialog.andCallFake((options, callback) => callback(undefined))
2017-06-02 01:48:08 +03:00
await pane.close()
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
expect(confirm.callCount).toBe(1)
expect(item1.saveAs).not.toHaveBeenCalled()
expect(pane.isDestroyed()).toBe(false)
})
2017-06-01 21:42:18 +03:00
describe('when item fails to save', () => {
let pane, item1
beforeEach(() => {
2019-02-22 10:55:17 +03:00
pane = new Pane({
items: [new Item('A'), new Item('B')],
applicationDelegate: atom.applicationDelegate,
config: atom.config
})
;[item1] = pane.getItems()
2017-06-01 21:42:18 +03:00
item1.shouldPromptToSave = () => true
item1.getURI = () => '/test/path'
item1.save = jasmine.createSpy('save').andCallFake(() => {
const error = new Error("EACCES, permission denied '/test/path'")
error.path = '/test/path'
error.code = 'EACCES'
throw error
})
})
2017-06-02 00:42:45 +03:00
it('does not destroy the pane if save fails and user clicks cancel', async () => {
2017-06-01 21:42:18 +03:00
let confirmations = 0
2017-11-19 03:12:23 +03:00
confirm.andCallFake((options, callback) => {
2017-06-01 21:42:18 +03:00
confirmations++
if (confirmations === 1) {
2017-11-19 03:12:23 +03:00
callback(0) // click save
2017-06-01 21:42:18 +03:00
} else {
2017-11-19 03:12:23 +03:00
callback(1)
2017-06-01 21:42:18 +03:00
}
}) // click cancel
2017-06-02 00:42:45 +03:00
await pane.close()
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
expect(confirmations).toBe(2)
expect(item1.save).toHaveBeenCalled()
expect(pane.isDestroyed()).toBe(false)
2017-06-01 21:42:18 +03:00
})
2017-06-02 00:42:45 +03:00
it('does destroy the pane if the user saves the file under a new name', async () => {
2017-06-01 21:42:18 +03:00
item1.saveAs = jasmine.createSpy('saveAs').andReturn(true)
let confirmations = 0
2017-11-19 03:12:23 +03:00
confirm.andCallFake((options, callback) => {
2017-06-01 21:42:18 +03:00
confirmations++
2017-11-19 03:12:23 +03:00
callback(0)
2017-06-01 21:42:18 +03:00
}) // save and then save as
showSaveDialog.andCallFake((options, callback) => callback('new/path'))
2017-06-01 21:42:18 +03:00
2017-06-02 00:42:45 +03:00
await pane.close()
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
expect(confirmations).toBe(2)
2019-02-22 10:55:17 +03:00
expect(
atom.applicationDelegate.showSaveDialog.mostRecentCall.args[0]
).toEqual({})
2017-06-02 00:42:45 +03:00
expect(item1.save).toHaveBeenCalled()
expect(item1.saveAs).toHaveBeenCalled()
expect(pane.isDestroyed()).toBe(true)
2017-06-01 21:42:18 +03:00
})
2017-06-02 00:42:45 +03:00
it('asks again if the saveAs also fails', async () => {
2017-06-01 21:42:18 +03:00
item1.saveAs = jasmine.createSpy('saveAs').andCallFake(() => {
const error = new Error("EACCES, permission denied '/test/path'")
error.path = '/test/path'
error.code = 'EACCES'
throw error
})
let confirmations = 0
2017-11-19 03:12:23 +03:00
confirm.andCallFake((options, callback) => {
2017-06-01 21:42:18 +03:00
confirmations++
if (confirmations < 3) {
2017-11-19 03:12:23 +03:00
callback(0) // save, save as, save as
} else {
callback(2) // don't save
2017-06-01 21:42:18 +03:00
}
2017-11-19 03:12:23 +03:00
})
2017-06-01 21:42:18 +03:00
showSaveDialog.andCallFake((options, callback) => callback('new/path'))
2017-06-01 21:42:18 +03:00
2017-06-02 00:42:45 +03:00
await pane.close()
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
expect(confirmations).toBe(3)
2019-02-22 10:55:17 +03:00
expect(
atom.applicationDelegate.showSaveDialog.mostRecentCall.args[0]
).toEqual({})
2017-06-02 00:42:45 +03:00
expect(item1.save).toHaveBeenCalled()
expect(item1.saveAs).toHaveBeenCalled()
expect(pane.isDestroyed()).toBe(true)
2017-06-01 21:42:18 +03:00
})
})
})
describe('::destroy()', () => {
let container, pane1, pane2
beforeEach(() => {
2019-02-22 10:55:17 +03:00
container = new PaneContainer({ config: atom.config, confirm })
2017-06-01 21:42:18 +03:00
pane1 = container.root
pane1.addItems([new Item('A'), new Item('B')])
pane2 = pane1.splitRight()
})
it('invokes ::onWillDestroy observers before destroying items', () => {
let itemsDestroyed = null
pane1.onWillDestroy(() => {
2019-02-22 10:55:17 +03:00
itemsDestroyed = pane1.getItems().map(item => item.isDestroyed())
2017-06-01 21:42:18 +03:00
})
pane1.destroy()
expect(itemsDestroyed).toEqual([false, false])
})
it("destroys the pane's destroyable items", () => {
const [item1, item2] = pane1.getItems()
pane1.destroy()
expect(item1.isDestroyed()).toBe(true)
expect(item2.isDestroyed()).toBe(true)
})
describe('if the pane is active', () => {
it('makes the next pane active', () => {
expect(pane2.isActive()).toBe(true)
pane2.destroy()
expect(pane1.isActive()).toBe(true)
})
})
describe("if the pane's parent has more than two children", () => {
it('removes the pane from its parent', () => {
const pane3 = pane2.splitRight()
expect(container.root.children).toEqual([pane1, pane2, pane3])
pane2.destroy()
expect(container.root.children).toEqual([pane1, pane3])
})
})
describe("if the pane's parent has two children", () => {
it('replaces the parent with its last remaining child', () => {
const pane3 = pane2.splitDown()
expect(container.root.children[0]).toBe(pane1)
expect(container.root.children[1].children).toEqual([pane2, pane3])
pane3.destroy()
expect(container.root.children).toEqual([pane1, pane2])
pane2.destroy()
expect(container.root).toBe(pane1)
})
})
})
describe('pending state', () => {
2017-06-02 00:42:45 +03:00
let editor1, pane, eventCount
2017-06-01 21:42:18 +03:00
2017-06-02 00:42:45 +03:00
beforeEach(async () => {
2019-02-22 10:55:17 +03:00
editor1 = await atom.workspace.open('sample.txt', { pending: true })
2017-06-02 00:42:45 +03:00
pane = atom.workspace.getActivePane()
eventCount = 0
editor1.onDidTerminatePendingState(() => eventCount++)
2017-06-01 21:42:18 +03:00
})
2017-06-02 00:42:45 +03:00
it('does not open file in pending state by default', async () => {
await atom.workspace.open('sample.js')
expect(pane.getPendingItem()).toBeNull()
2017-06-01 21:42:18 +03:00
})
2017-06-02 00:42:45 +03:00
it("opens file in pending state if 'pending' option is true", () => {
expect(pane.getPendingItem()).toEqual(editor1)
})
2017-06-01 21:42:18 +03:00
it('terminates pending state if ::terminatePendingState is invoked', () => {
editor1.terminatePendingState()
expect(pane.getPendingItem()).toBeNull()
expect(eventCount).toBe(1)
})
it('terminates pending state when buffer is changed', () => {
2019-02-22 10:55:17 +03:00
editor1.insertText("I'll be back!")
2017-06-01 21:42:18 +03:00
advanceClock(editor1.getBuffer().stoppedChangingDelay)
expect(pane.getPendingItem()).toBeNull()
expect(eventCount).toBe(1)
})
2017-06-02 00:42:45 +03:00
it('only calls terminate handler once when text is modified twice', async () => {
2017-06-01 21:42:18 +03:00
const originalText = editor1.getText()
editor1.insertText('Some text')
advanceClock(editor1.getBuffer().stoppedChangingDelay)
2017-06-02 00:42:45 +03:00
await editor1.save()
2017-06-01 21:42:18 +03:00
2017-06-02 00:42:45 +03:00
editor1.insertText('More text')
advanceClock(editor1.getBuffer().stoppedChangingDelay)
2017-06-01 21:42:18 +03:00
2017-06-02 00:42:45 +03:00
expect(pane.getPendingItem()).toBeNull()
expect(eventCount).toBe(1)
2017-06-01 21:42:18 +03:00
// Reset fixture back to original state
2017-06-02 00:42:45 +03:00
editor1.setText(originalText)
await editor1.save()
2017-06-01 21:42:18 +03:00
})
it('only calls clearPendingItem if there is a pending item to clear', () => {
spyOn(pane, 'clearPendingItem').andCallThrough()
editor1.terminatePendingState()
editor1.terminatePendingState()
expect(pane.getPendingItem()).toBeNull()
expect(pane.clearPendingItem.callCount).toBe(1)
})
})
describe('serialization', () => {
let pane = null
beforeEach(() => {
2019-02-22 10:55:17 +03:00
pane = new Pane(
paneParams({
items: [new Item('A', 'a'), new Item('B', 'b'), new Item('C', 'c')],
flexScale: 2
})
)
2017-06-01 21:42:18 +03:00
})
it('can serialize and deserialize the pane and all its items', () => {
const newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.getItems()).toEqual(pane.getItems())
})
it('restores the active item on deserialization', () => {
pane.activateItemAtIndex(1)
const newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.getActiveItem()).toEqual(newPane.itemAtIndex(1))
})
it("restores the active item when it doesn't implement getURI()", () => {
pane.items[1].getURI = null
pane.activateItemAtIndex(1)
const newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.getActiveItem()).toEqual(newPane.itemAtIndex(1))
})
it("restores the correct item when it doesn't implement getURI() and some items weren't deserialized", () => {
const unserializable = {}
2019-02-22 10:55:17 +03:00
pane.addItem(unserializable, { index: 0 })
2017-06-01 21:42:18 +03:00
pane.items[2].getURI = null
pane.activateItemAtIndex(2)
const newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.getActiveItem()).toEqual(newPane.itemAtIndex(1))
})
it('does not include items that cannot be deserialized', () => {
spyOn(console, 'warn')
const unserializable = {}
pane.activateItem(unserializable)
const newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.getActiveItem()).toEqual(pane.itemAtIndex(0))
expect(newPane.getItems().length).toBe(pane.getItems().length - 1)
})
it("includes the pane's focus state in the serialized state", () => {
pane.focus()
const newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.focused).toBe(true)
})
it('can serialize and deserialize the order of the items in the itemStack', () => {
const [item1, item2, item3] = pane.getItems()
pane.itemStack = [item3, item1, item2]
const newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.itemStack).toEqual(pane.itemStack)
expect(newPane.itemStack[2]).toEqual(item2)
})
it('builds the itemStack if the itemStack is not serialized', () => {
const newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.getItems()).toEqual(newPane.itemStack)
})
it('rebuilds the itemStack if items.length does not match itemStack.length', () => {
const [, item2, item3] = pane.getItems()
pane.itemStack = [item2, item3]
const newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.getItems()).toEqual(newPane.itemStack)
})
it('does not serialize the reference to the items in the itemStack for pane items that will not be serialized', () => {
const [item1, item2, item3] = pane.getItems()
pane.itemStack = [item2, item1, item3]
const unserializable = {}
pane.activateItem(unserializable)
const newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.itemStack).toEqual([item2, item1, item3])
})
})
})