Merge branch 'master' into ns-use-display-layers

# Conflicts:
#	package.json
#	src/display-buffer.coffee
#	src/text-editor.coffee
#	src/tokenized-buffer.coffee
This commit is contained in:
Antonio Scandurra
2016-03-10 13:53:14 +01:00
114 changed files with 2475 additions and 1047 deletions

View File

@@ -19,7 +19,9 @@ exports.afterEach = (fn) ->
waitsForPromise = (fn) ->
promise = fn()
waitsFor 'spec promise to resolve', 30000, (done) ->
# This timeout is 3 minutes. We need to bump it back down once we fix backgrounding
# of the renderer process on CI. See https://github.com/atom/electron/issues/4317
waitsFor 'spec promise to resolve', 3 * 60 * 1000, (done) ->
promise.then(
done,
(error) ->

View File

@@ -152,6 +152,8 @@ describe "AtomEnvironment", ->
atom.enablePersistence = false
it "selects the state based on the current project paths", ->
jasmine.useRealClock()
[dir1, dir2] = [temp.mkdirSync("dir1-"), temp.mkdirSync("dir2-")]
loadSettings = _.extend atom.getLoadSettings(),
@@ -159,20 +161,55 @@ describe "AtomEnvironment", ->
windowState: null
spyOn(atom, 'getLoadSettings').andCallFake -> loadSettings
spyOn(atom.getStorageFolder(), 'getPath').andReturn(temp.mkdirSync("storage-dir-"))
spyOn(atom, 'serialize').andReturn({stuff: 'cool'})
atom.state.stuff = "cool"
atom.project.setPaths([dir1, dir2])
atom.saveStateSync()
# State persistence will fail if other Atom instances are running
waitsForPromise ->
atom.stateStore.connect().then (isConnected) ->
expect(isConnected).toBe true
atom.state = {}
atom.loadStateSync()
expect(atom.state.stuff).toBeUndefined()
waitsForPromise ->
atom.saveState().then ->
atom.loadState().then (state) ->
expect(state).toBeNull()
loadSettings.initialPaths = [dir2, dir1]
atom.state = {}
atom.loadStateSync()
expect(atom.state.stuff).toBe("cool")
waitsForPromise ->
loadSettings.initialPaths = [dir2, dir1]
atom.loadState().then (state) ->
expect(state).toEqual({stuff: 'cool'})
it "saves state on keydown, mousedown, and when the editor window unloads", ->
spyOn(atom, 'saveState')
keydown = new KeyboardEvent('keydown')
atom.document.dispatchEvent(keydown)
advanceClock atom.saveStateDebounceInterval
expect(atom.saveState).toHaveBeenCalledWith({isUnloading: false})
expect(atom.saveState).not.toHaveBeenCalledWith({isUnloading: true})
atom.saveState.reset()
mousedown = new MouseEvent('mousedown')
atom.document.dispatchEvent(mousedown)
advanceClock atom.saveStateDebounceInterval
expect(atom.saveState).toHaveBeenCalledWith({isUnloading: false})
expect(atom.saveState).not.toHaveBeenCalledWith({isUnloading: true})
atom.saveState.reset()
atom.unloadEditorWindow()
mousedown = new MouseEvent('mousedown')
atom.document.dispatchEvent(mousedown)
advanceClock atom.saveStateDebounceInterval
expect(atom.saveState).toHaveBeenCalledWith({isUnloading: true})
expect(atom.saveState).not.toHaveBeenCalledWith({isUnloading: false})
it "serializes the project state with all the options supplied in saveState", ->
spyOn(atom.project, 'serialize').andReturn({foo: 42})
waitsForPromise -> atom.saveState({anyOption: 'any option'})
runs ->
expect(atom.project.serialize.calls.length).toBe(1)
expect(atom.project.serialize.mostRecentCall.args[0]).toEqual({anyOption: 'any option'})
describe "openInitialEmptyEditorIfNecessary", ->
describe "when there are no paths set", ->
@@ -230,23 +267,6 @@ describe "AtomEnvironment", ->
atomEnvironment.destroy()
it "saves the serialized state of the window so it can be deserialized after reload", ->
atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, window, document})
spyOn(atomEnvironment, 'saveStateSync')
workspaceState = atomEnvironment.workspace.serialize()
grammarsState = {grammarOverridesByPath: atomEnvironment.grammars.grammarOverridesByPath}
projectState = atomEnvironment.project.serialize()
atomEnvironment.unloadEditorWindow()
expect(atomEnvironment.state.workspace).toEqual workspaceState
expect(atomEnvironment.state.grammars).toEqual grammarsState
expect(atomEnvironment.state.project).toEqual projectState
expect(atomEnvironment.saveStateSync).toHaveBeenCalled()
atomEnvironment.destroy()
describe "::destroy()", ->
it "does not throw exceptions when unsubscribing from ipc events (regression)", ->
configDirPath = temp.mkdirSync()
@@ -258,6 +278,7 @@ describe "AtomEnvironment", ->
}
atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, window, document: fakeDocument})
spyOn(atomEnvironment.packages, 'getAvailablePackagePaths').andReturn []
spyOn(atomEnvironment, 'displayWindow').andReturn Promise.resolve()
atomEnvironment.startEditorWindow()
atomEnvironment.unloadEditorWindow()
atomEnvironment.destroy()
@@ -273,6 +294,14 @@ describe "AtomEnvironment", ->
atom.openLocations([{pathToOpen}])
expect(atom.project.getPaths()[0]).toBe __dirname
describe "then a second path is opened with forceAddToWindow", ->
it "adds the second path to the project's paths", ->
firstPathToOpen = __dirname
secondPathToOpen = path.resolve(__dirname, './fixtures')
atom.openLocations([{pathToOpen: firstPathToOpen}])
atom.openLocations([{pathToOpen: secondPathToOpen, forceAddToWindow: true}])
expect(atom.project.getPaths()).toEqual([firstPathToOpen, secondPathToOpen])
describe "when the opened path does not exist but its parent directory does", ->
it "adds the parent directory to the project paths", ->
pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt')
@@ -318,3 +347,18 @@ describe "AtomEnvironment", ->
runs ->
{releaseVersion} = updateAvailableHandler.mostRecentCall.args[0]
expect(releaseVersion).toBe 'version'
describe "::getReleaseChannel()", ->
[version] = []
beforeEach ->
spyOn(atom, 'getVersion').andCallFake -> version
it "returns the correct channel based on the version number", ->
version = '1.5.6'
expect(atom.getReleaseChannel()).toBe 'stable'
version = '1.5.0-beta10'
expect(atom.getReleaseChannel()).toBe 'beta'
version = '1.7.0-dev-5340c91'
expect(atom.getReleaseChannel()).toBe 'dev'

View File

@@ -172,7 +172,7 @@ class AtomReporter
listen document, 'click', '.stack-trace', (event) ->
event.currentTarget.classList.toggle('expanded')
@reloadButton.addEventListener('click', -> require('ipc').send('call-window-method', 'restart'))
@reloadButton.addEventListener('click', -> require('electron').ipcRenderer.send('call-window-method', 'restart'))
updateSpecCounts: ->
if @skippedCount

View File

@@ -0,0 +1,115 @@
'use babel'
import AutoUpdateManager from '../src/auto-update-manager'
import {remote} from 'electron'
const electronAutoUpdater = remote.require('electron').autoUpdater
describe('AutoUpdateManager (renderer)', () => {
let autoUpdateManager
beforeEach(() => {
autoUpdateManager = new AutoUpdateManager({
applicationDelegate: atom.applicationDelegate
})
})
afterEach(() => {
autoUpdateManager.destroy()
})
describe('::onDidBeginCheckingForUpdate', () => {
it('subscribes to "did-begin-checking-for-update" event', () => {
const spy = jasmine.createSpy('spy')
autoUpdateManager.onDidBeginCheckingForUpdate(spy)
electronAutoUpdater.emit('checking-for-update')
waitsFor(() => {
return spy.callCount === 1
})
})
})
describe('::onDidBeginDownloadingUpdate', () => {
it('subscribes to "did-begin-downloading-update" event', () => {
const spy = jasmine.createSpy('spy')
autoUpdateManager.onDidBeginDownloadingUpdate(spy)
electronAutoUpdater.emit('update-available')
waitsFor(() => {
return spy.callCount === 1
})
})
})
describe('::onDidCompleteDownloadingUpdate', () => {
it('subscribes to "did-complete-downloading-update" event', () => {
const spy = jasmine.createSpy('spy')
autoUpdateManager.onDidCompleteDownloadingUpdate(spy)
electronAutoUpdater.emit('update-downloaded', null, null, '1.2.3')
waitsFor(() => {
return spy.callCount === 1
})
runs(() => {
expect(spy.mostRecentCall.args[0].releaseVersion).toBe('1.2.3')
})
})
})
describe('::onUpdateNotAvailable', () => {
it('subscribes to "update-not-available" event', () => {
const spy = jasmine.createSpy('spy')
autoUpdateManager.onUpdateNotAvailable(spy)
electronAutoUpdater.emit('update-not-available')
waitsFor(() => {
return spy.callCount === 1
})
})
})
describe('::platformSupportsUpdates', () => {
let state, releaseChannel
it('returns true on OS X and Windows when in stable', () => {
spyOn(autoUpdateManager, 'getState').andCallFake(() => state)
spyOn(atom, 'getReleaseChannel').andCallFake(() => releaseChannel)
state = 'idle'
releaseChannel = 'stable'
expect(autoUpdateManager.platformSupportsUpdates()).toBe(true)
state = 'idle'
releaseChannel = 'dev'
expect(autoUpdateManager.platformSupportsUpdates()).toBe(false)
state = 'unsupported'
releaseChannel = 'stable'
expect(autoUpdateManager.platformSupportsUpdates()).toBe(false)
state = 'unsupported'
releaseChannel = 'dev'
expect(autoUpdateManager.platformSupportsUpdates()).toBe(false)
})
})
describe('::destroy', () => {
it('unsubscribes from all events', () => {
const spy = jasmine.createSpy('spy')
const doneIndicator = jasmine.createSpy('spy')
atom.applicationDelegate.onUpdateNotAvailable(doneIndicator)
autoUpdateManager.onDidBeginCheckingForUpdate(spy)
autoUpdateManager.onDidBeginDownloadingUpdate(spy)
autoUpdateManager.onDidCompleteDownloadingUpdate(spy)
autoUpdateManager.onUpdateNotAvailable(spy)
autoUpdateManager.destroy()
electronAutoUpdater.emit('checking-for-update')
electronAutoUpdater.emit('update-available')
electronAutoUpdater.emit('update-downloaded', null, null, '1.2.3')
electronAutoUpdater.emit('update-not-available')
waitsFor(() => {
return doneIndicator.callCount === 1
})
runs(() => {
expect(spy.callCount).toBe(0)
})
})
})
})

View File

@@ -15,7 +15,6 @@ describe "Babel transpiler support", ->
CompileCache.setCacheDirectory(temp.mkdirSync('compile-cache'))
for cacheKey in Object.keys(require.cache)
if cacheKey.startsWith(path.join(__dirname, 'fixtures', 'babel'))
console.log('deleting', cacheKey)
delete require.cache[cacheKey]
afterEach ->

View File

@@ -1621,6 +1621,16 @@ describe "Config", ->
expect(color.toHexString()).toBe '#ff0000'
expect(color.toRGBAString()).toBe 'rgba(255, 0, 0, 1)'
color.red = 11
color.green = 11
color.blue = 124
color.alpha = 1
atom.config.set('foo.bar.aColor', color)
color = atom.config.get('foo.bar.aColor')
expect(color.toHexString()).toBe '#0b0b7c'
expect(color.toRGBAString()).toBe 'rgba(11, 11, 124, 1)'
it 'coerces various types to a color object', ->
atom.config.set('foo.bar.aColor', 'red')
expect(atom.config.get('foo.bar.aColor')).toEqual {red: 255, green: 0, blue: 0, alpha: 1}

View File

@@ -1,6 +0,0 @@
module.exports = function (state) {
return {
wasDeserializedBy: 'Deserializer1',
state: state
}
}

View File

@@ -1,6 +0,0 @@
module.exports = function (state) {
return {
wasDeserializedBy: 'Deserializer2',
state: state
}
}

View File

@@ -1,3 +1,17 @@
module.exports = {
activate: function() {}
activate () {},
deserializeMethod1 (state) {
return {
wasDeserializedBy: 'deserializeMethod1',
state: state
}
},
deserializeMethod2 (state) {
return {
wasDeserializedBy: 'deserializeMethod2',
state: state
}
}
}

View File

@@ -3,7 +3,7 @@
"version": "1.0.0",
"main": "./index",
"deserializers": {
"Deserializer1": "./deserializer-1.js",
"Deserializer2": "./deserializer-2.js"
"Deserializer1": "deserializeMethod1",
"Deserializer2": "deserializeMethod2"
}
}

View File

@@ -0,0 +1,8 @@
{
"name": "package-with-a-git-prefixed-git-repo-url",
"repository": {
"type": "git",
"url": "git+https://github.com/example/repo.git"
},
"_id": "this is here to simulate the URL being already normalized by npm. we still need to stript git+ from the beginning and .git from the end."
}

View File

@@ -1,3 +0,0 @@
module.exports = function (state) {
return {state: state}
}

View File

@@ -1,3 +1,25 @@
'use strict'
module.exports = {
activate: function() {}
activate () {},
theDeserializerMethod (state) {
return {state: state}
},
viewProviderMethod1 (model) {
if (model.worksWithViewProvider1) {
let element = document.createElement('div')
element.dataset['createdBy'] = 'view-provider-1'
return element
}
},
viewProviderMethod2 (model) {
if (model.worksWithViewProvider2) {
let element = document.createElement('div')
element.dataset['createdBy'] = 'view-provider-2'
return element
}
}
}

View File

@@ -3,10 +3,10 @@
"main": "./index",
"version": "1.0.0",
"deserializers": {
"DeserializerFromPackageWithViewProviders": "./deserializer"
"DeserializerFromPackageWithViewProviders": "theDeserializerMethod"
},
"viewProviders": [
"./view-provider-1",
"./view-provider-2"
"viewProviderMethod1",
"viewProviderMethod2"
]
}

View File

@@ -1,9 +0,0 @@
'use strict'
module.exports = function (model) {
if (model.worksWithViewProvider1) {
let element = document.createElement('div')
element.dataset['createdBy'] = 'view-provider-1'
return element
}
}

View File

@@ -1,9 +0,0 @@
'use strict'
module.exports = function (model) {
if (model.worksWithViewProvider2) {
let element = document.createElement('div')
element.dataset['createdBy'] = 'view-provider-2'
return element
}
}

View File

@@ -9,12 +9,23 @@ var quicksort = function () {
// Wowza
if (items.length <= 1) return items;
var pivot = items.shift(), current, left = [], right = [];
/*
This is a multiline comment block with
an empty line inside of it.
Awesome.
*/
while(items.length > 0) {
current = items.shift();
current < pivot ? left.push(current) : right.push(current);
}
// This is a collection of
// single line comments
// ...with an empty line
// among it, geez!
return sort(left).concat(pivot).concat(sort(right));
};
// this is a single-line comment
return sort(Array.apply(this, arguments));
};
};

View File

@@ -338,10 +338,10 @@ describe('GitRepositoryAsync', () => {
})
describe('.refreshStatus()', () => {
let newPath, modifiedPath, cleanPath
let newPath, modifiedPath, cleanPath, workingDirectory
beforeEach(() => {
const workingDirectory = copyRepository()
workingDirectory = copyRepository()
repo = GitRepositoryAsync.open(workingDirectory)
modifiedPath = path.join(workingDirectory, 'file.txt')
newPath = path.join(workingDirectory, 'untracked.txt')
@@ -362,7 +362,7 @@ describe('GitRepositoryAsync', () => {
describe('in a repository with submodules', () => {
beforeEach(() => {
const workingDirectory = copySubmoduleRepository()
workingDirectory = copySubmoduleRepository()
repo = GitRepositoryAsync.open(workingDirectory)
modifiedPath = path.join(workingDirectory, 'jstips', 'README.md')
newPath = path.join(workingDirectory, 'You-Dont-Need-jQuery', 'untracked.txt')
@@ -380,6 +380,86 @@ describe('GitRepositoryAsync', () => {
expect(repo.isStatusModified(await repo.getCachedPathStatus(modifiedPath))).toBe(true)
})
})
it('caches the proper statuses when a subdir is open', async () => {
const subDir = path.join(workingDirectory, 'dir')
fs.mkdirSync(subDir)
const filePath = path.join(subDir, 'b.txt')
fs.writeFileSync(filePath, '')
atom.project.setPaths([subDir])
await atom.workspace.open('b.txt')
const repo = atom.project.getRepositories()[0].async
await repo.refreshStatus()
const status = await repo.getCachedPathStatus(filePath)
expect(repo.isStatusModified(status)).toBe(false)
expect(repo.isStatusNew(status)).toBe(false)
})
it('caches the proper statuses when multiple project are open', async () => {
const otherWorkingDirectory = copyRepository()
atom.project.setPaths([workingDirectory, otherWorkingDirectory])
await atom.workspace.open('b.txt')
const repo = atom.project.getRepositories()[0].async
await repo.refreshStatus()
const subDir = path.join(workingDirectory, 'dir')
fs.mkdirSync(subDir)
const filePath = path.join(subDir, 'b.txt')
fs.writeFileSync(filePath, 'some content!')
const status = await repo.getCachedPathStatus(filePath)
expect(repo.isStatusModified(status)).toBe(true)
expect(repo.isStatusNew(status)).toBe(false)
})
it('emits did-change-statuses if the status changes', async () => {
const someNewPath = path.join(workingDirectory, 'MyNewJSFramework.md')
fs.writeFileSync(someNewPath, '')
const statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatuses(statusHandler)
await repo.refreshStatus()
waitsFor('the onDidChangeStatuses handler to be called', () => statusHandler.callCount > 0)
})
it('emits did-change-statuses if the branch changes', async () => {
const statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatuses(statusHandler)
repo._refreshBranch = jasmine.createSpy('_refreshBranch').andCallFake(() => {
return Promise.resolve(true)
})
await repo.refreshStatus()
waitsFor('the onDidChangeStatuses handler to be called', () => statusHandler.callCount > 0)
})
it('emits did-change-statuses if the ahead/behind changes', async () => {
const statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatuses(statusHandler)
repo._refreshAheadBehindCount = jasmine.createSpy('_refreshAheadBehindCount').andCallFake(() => {
return Promise.resolve(true)
})
await repo.refreshStatus()
waitsFor('the onDidChangeStatuses handler to be called', () => statusHandler.callCount > 0)
})
})
describe('.isProjectAtRoot()', () => {
@@ -499,7 +579,7 @@ describe('GitRepositoryAsync', () => {
await atom.workspace.open('file.txt')
project2 = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
project2.deserialize(atom.project.serialize(), atom.deserializers)
project2.deserialize(atom.project.serialize({isUnloading: true}))
const repo = project2.getRepositories()[0].async
waitsForPromise(() => repo.refreshStatus())
@@ -548,6 +628,14 @@ describe('GitRepositoryAsync', () => {
const relativizedPath = repo.relativize(`${workdir}/a/b.txt`, workdir)
expect(relativizedPath).toBe('a/b.txt')
})
it('preserves file case', () => {
repo.isCaseInsensitive = true
const workdir = '/tmp/foo/bar/baz/'
const relativizedPath = repo.relativize(`${workdir}a/README.txt`, workdir)
expect(relativizedPath).toBe('a/README.txt')
})
})
describe('.getShortHead(path)', () => {
@@ -626,7 +714,7 @@ describe('GitRepositoryAsync', () => {
repo = GitRepositoryAsync.open(workingDirectory)
})
it('returns 0, 0 for a branch with no upstream', async () => {
it('returns 1, 0 for a branch which is ahead by 1', async () => {
await repo.refreshStatus()
const {ahead, behind} = await repo.getCachedUpstreamAheadBehindCount('You-Dont-Need-jQuery')

View File

@@ -205,7 +205,7 @@ describe "GitRepository", ->
expect(repo.isStatusModified(repo.getDirectoryStatus(directoryPath))).toBe true
describe ".refreshStatus()", ->
[newPath, modifiedPath, cleanPath, originalModifiedPathText] = []
[newPath, modifiedPath, cleanPath, originalModifiedPathText, workingDirectory] = []
beforeEach ->
workingDirectory = copyRepository()
@@ -231,6 +231,64 @@ describe "GitRepository", ->
expect(repo.isStatusNew(repo.getCachedPathStatus(newPath))).toBeTruthy()
expect(repo.isStatusModified(repo.getCachedPathStatus(modifiedPath))).toBeTruthy()
it 'caches the proper statuses when a subdir is open', ->
subDir = path.join(workingDirectory, 'dir')
fs.mkdirSync(subDir)
filePath = path.join(subDir, 'b.txt')
fs.writeFileSync(filePath, '')
atom.project.setPaths([subDir])
waitsForPromise ->
atom.workspace.open('b.txt')
statusHandler = null
runs ->
repo = atom.project.getRepositories()[0]
statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatuses statusHandler
repo.refreshStatus()
waitsFor ->
statusHandler.callCount > 0
runs ->
status = repo.getCachedPathStatus(filePath)
expect(repo.isStatusModified(status)).toBe false
expect(repo.isStatusNew(status)).toBe false
it 'caches the proper statuses when multiple project are open', ->
otherWorkingDirectory = copyRepository()
atom.project.setPaths([workingDirectory, otherWorkingDirectory])
waitsForPromise ->
atom.workspace.open('b.txt')
statusHandler = null
runs ->
repo = atom.project.getRepositories()[0]
statusHandler = jasmine.createSpy('statusHandler')
repo.onDidChangeStatuses statusHandler
repo.refreshStatus()
waitsFor ->
statusHandler.callCount > 0
runs ->
subDir = path.join(workingDirectory, 'dir')
fs.mkdirSync(subDir)
filePath = path.join(subDir, 'b.txt')
fs.writeFileSync(filePath, '')
status = repo.getCachedPathStatus(filePath)
expect(repo.isStatusModified(status)).toBe true
expect(repo.isStatusNew(status)).toBe false
describe "buffer events", ->
[editor] = []
@@ -289,7 +347,7 @@ describe "GitRepository", ->
runs ->
project2 = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
project2.deserialize(atom.project.serialize(), atom.deserializers)
project2.deserialize(atom.project.serialize({isUnloading: false}))
buffer = project2.getBuffers()[0]
waitsFor ->

View File

@@ -15,6 +15,8 @@ ChromedriverPort = 9515
ChromedriverURLBase = "/wd/hub"
ChromedriverStatusURL = "http://localhost:#{ChromedriverPort}#{ChromedriverURLBase}/status"
userDataDir = temp.mkdirSync('atom-user-data-dir')
chromeDriverUp = (done) ->
checkStatus = ->
http
@@ -48,7 +50,7 @@ buildAtomClient = (args, env) ->
"atom-env=#{map(env, (value, key) -> "#{key}=#{value}").join(" ")}"
"dev"
"safe"
"user-data-dir=#{temp.mkdirSync('atom-user-data-dir')}"
"user-data-dir=#{userDataDir}"
"socket-path=#{SocketPath}"
])
@@ -124,7 +126,7 @@ buildAtomClient = (args, env) ->
.addCommand "simulateQuit", (done) ->
@execute -> atom.unloadEditorWindow()
.execute -> require("remote").require("app").emit("before-quit")
.execute -> require("electron").remote.app.emit("before-quit")
.call(done)
module.exports = (args, env, fn) ->

View File

@@ -28,13 +28,12 @@ describe "Starting Atom", ->
it "opens the parent directory and creates an empty text editor", ->
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
client
.waitForPaneItemCount(1, 1000)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath])
.waitForExist("atom-text-editor", 5000)
.then (exists) -> expect(exists).toBe true
.waitForPaneItemCount(1, 1000)
.click("atom-text-editor")
.keys("Hello!")
.execute -> atom.workspace.getActiveTextEditor().getText()
@@ -124,6 +123,34 @@ describe "Starting Atom", ->
.waitForPaneItemCount(0, 1000)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([otherTempDirPath])
describe "when using the -a, --add option", ->
it "reuses that window and add the folder to project paths", ->
fourthTempDir = temp.mkdirSync("a-fourth-dir")
fourthTempFilePath = path.join(fourthTempDir, "a-file")
fs.writeFileSync(fourthTempFilePath, "4 - This file was already here.")
fifthTempDir = temp.mkdirSync("a-fifth-dir")
fifthTempFilePath = path.join(fifthTempDir, "a-file")
fs.writeFileSync(fifthTempFilePath, "5 - This file was already here.")
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
client
.waitForPaneItemCount(1, 5000)
# Opening another file reuses the same window and add parent dir to
# project paths.
.startAnotherAtom(['-a', fourthTempFilePath], ATOM_HOME: atomHome)
.waitForPaneItemCount(2, 5000)
.waitForWindowCount(1, 1000)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath, fourthTempDir])
.execute -> atom.workspace.getActiveTextEditor().getText()
.then ({value: text}) -> expect(text).toBe "4 - This file was already here."
# Opening another directory resuses the same window and add the folder to project paths.
.startAnotherAtom(['--add', fifthTempDir], ATOM_HOME: atomHome)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath, fourthTempDir, fifthTempDir])
it "opens the new window offset from the other window", ->
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
@@ -153,6 +180,8 @@ describe "Starting Atom", ->
.waitForPaneItemCount(0, 3000)
.execute -> atom.workspace.open()
.waitForPaneItemCount(1, 3000)
.keys("Hello!")
.waitUntil((-> Promise.resolve(false)), 1100)
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
client

View File

@@ -1,7 +1,7 @@
_ = require 'underscore-plus'
fs = require 'fs-plus'
path = require 'path'
ipc = require 'ipc'
{ipcRenderer} = require 'electron'
module.exports = ({logFile, headless, testPaths, buildAtomEnvironment}) ->
window[key] = value for key, value of require '../vendor/jasmine'
@@ -88,7 +88,7 @@ buildTerminalReporter = (logFile, resolveWithExitCode) ->
if logStream?
fs.writeSync(logStream, str)
else
ipc.send 'write-to-stderr', str
ipcRenderer.send 'write-to-stderr', str
{TerminalReporter} = require 'jasmine-tagged'
new TerminalReporter

View File

@@ -430,7 +430,7 @@ describe "LanguageMode", ->
languageMode.foldAll()
fold1 = editor.tokenizedLineForScreenRow(0).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 30]
fold1.destroy()
fold2 = editor.tokenizedLineForScreenRow(1).fold
@@ -441,6 +441,14 @@ describe "LanguageMode", ->
fold4 = editor.tokenizedLineForScreenRow(3).fold
expect([fold4.getStartRow(), fold4.getEndRow()]).toEqual [6, 8]
fold5 = editor.tokenizedLineForScreenRow(6).fold
expect([fold5.getStartRow(), fold5.getEndRow()]).toEqual [11, 16]
fold5.destroy()
fold6 = editor.tokenizedLineForScreenRow(13).fold
expect([fold6.getStartRow(), fold6.getEndRow()]).toEqual [21, 22]
fold6.destroy()
describe ".foldAllAtIndentLevel()", ->
it "folds every foldable range at a given indentLevel", ->
languageMode.foldAllAtIndentLevel(2)
@@ -450,14 +458,26 @@ describe "LanguageMode", ->
fold1.destroy()
fold2 = editor.tokenizedLineForScreenRow(11).fold
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [11, 14]
expect([fold2.getStartRow(), fold2.getEndRow()]).toEqual [11, 16]
fold2.destroy()
fold3 = editor.tokenizedLineForScreenRow(17).fold
expect([fold3.getStartRow(), fold3.getEndRow()]).toEqual [17, 20]
fold3.destroy()
fold4 = editor.tokenizedLineForScreenRow(21).fold
expect([fold4.getStartRow(), fold4.getEndRow()]).toEqual [21, 22]
fold4.destroy()
fold5 = editor.tokenizedLineForScreenRow(24).fold
expect([fold5.getStartRow(), fold5.getEndRow()]).toEqual [24, 25]
fold5.destroy()
it "does not fold anything but the indentLevel", ->
languageMode.foldAllAtIndentLevel(0)
fold1 = editor.tokenizedLineForScreenRow(0).fold
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 19]
expect([fold1.getStartRow(), fold1.getEndRow()]).toEqual [0, 30]
fold1.destroy()
fold2 = editor.tokenizedLineForScreenRow(5).fold
@@ -467,7 +487,13 @@ describe "LanguageMode", ->
it "returns true if the line starts a multi-line comment", ->
expect(languageMode.isFoldableAtBufferRow(1)).toBe true
expect(languageMode.isFoldableAtBufferRow(6)).toBe true
expect(languageMode.isFoldableAtBufferRow(17)).toBe false
expect(languageMode.isFoldableAtBufferRow(8)).toBe false
expect(languageMode.isFoldableAtBufferRow(11)).toBe true
expect(languageMode.isFoldableAtBufferRow(15)).toBe false
expect(languageMode.isFoldableAtBufferRow(17)).toBe true
expect(languageMode.isFoldableAtBufferRow(21)).toBe true
expect(languageMode.isFoldableAtBufferRow(24)).toBe true
expect(languageMode.isFoldableAtBufferRow(28)).toBe false
it "does not return true for a line in the middle of a comment that's followed by an indented line", ->
expect(languageMode.isFoldableAtBufferRow(7)).toBe false

View File

@@ -55,12 +55,17 @@ describe "PackageManager", ->
it "normalizes short repository urls in package.json", ->
{metadata} = atom.packages.loadPackage("package-with-short-url-package-json")
expect(metadata.repository.type).toBe "git"
expect(metadata.repository.url).toBe "https://github.com/example/repo.git"
expect(metadata.repository.url).toBe "https://github.com/example/repo"
{metadata} = atom.packages.loadPackage("package-with-invalid-url-package-json")
expect(metadata.repository.type).toBe "git"
expect(metadata.repository.url).toBe "foo"
it "trims git+ from the beginning and .git from the end of repository URLs, even if npm already normalized them ", ->
{metadata} = atom.packages.loadPackage("package-with-prefixed-and-suffixed-repo-url")
expect(metadata.repository.type).toBe "git"
expect(metadata.repository.url).toBe "https://github.com/example/repo"
it "returns null if the package is not found in any package directory", ->
spyOn(console, 'warn')
expect(atom.packages.loadPackage("this-package-cannot-be-found")).toBeNull()
@@ -88,18 +93,16 @@ describe "PackageManager", ->
state1 = {deserializer: 'Deserializer1', a: 'b'}
expect(atom.deserializers.deserialize(state1)).toEqual {
wasDeserializedBy: 'Deserializer1'
wasDeserializedBy: 'deserializeMethod1'
state: state1
}
state2 = {deserializer: 'Deserializer2', c: 'd'}
expect(atom.deserializers.deserialize(state2)).toEqual {
wasDeserializedBy: 'Deserializer2'
wasDeserializedBy: 'deserializeMethod2'
state: state2
}
expect(pack.mainModule).toBeNull()
describe "when there are view providers specified in the package's package.json", ->
model1 = {worksWithViewProvider1: true}
model2 = {worksWithViewProvider2: true}
@@ -448,16 +451,15 @@ describe "PackageManager", ->
pack = null
waitsForPromise ->
atom.packages.activatePackage("package-with-serialization").then (p) -> pack = p
runs ->
expect(pack.mainModule.someNumber).not.toBe 77
pack.mainModule.someNumber = 77
atom.packages.deactivatePackage("package-with-serialization")
spyOn(pack.mainModule, 'activate').andCallThrough()
waitsForPromise ->
atom.packages.activatePackage("package-with-serialization")
runs ->
expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77})
waitsForPromise ->
atom.packages.activatePackage("package-with-serialization")
runs ->
expect(pack.mainModule.activate).toHaveBeenCalledWith({someNumber: 77})
it "invokes ::onDidActivatePackage listeners with the activated package", ->
activatedPackage = null
@@ -821,6 +823,34 @@ describe "PackageManager", ->
expect(atom.packages.isPackageActive("package-with-missing-provided-services")).toBe true
expect(addErrorHandler.callCount).toBe 0
describe "::serialize", ->
it "does not serialize packages that threw an error during activation", ->
spyOn(console, 'warn')
badPack = null
waitsForPromise ->
atom.packages.activatePackage("package-that-throws-on-activate").then (p) -> badPack = p
runs ->
spyOn(badPack.mainModule, 'serialize').andCallThrough()
atom.packages.serialize()
expect(badPack.mainModule.serialize).not.toHaveBeenCalled()
it "absorbs exceptions that are thrown by the package module's serialize method", ->
spyOn(console, 'error')
waitsForPromise ->
atom.packages.activatePackage('package-with-serialize-error')
waitsForPromise ->
atom.packages.activatePackage('package-with-serialization')
runs ->
atom.packages.serialize()
expect(atom.packages.packageStates['package-with-serialize-error']).toBeUndefined()
expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1
expect(console.error).toHaveBeenCalled()
describe "::deactivatePackage(id)", ->
afterEach ->
atom.packages.unloadPackages()
@@ -852,33 +882,6 @@ describe "PackageManager", ->
expect(badPack.mainModule.deactivate).not.toHaveBeenCalled()
expect(atom.packages.isPackageActive("package-that-throws-on-activate")).toBeFalsy()
it "does not serialize packages that have not been activated called on their main module", ->
spyOn(console, 'warn')
badPack = null
waitsForPromise ->
atom.packages.activatePackage("package-that-throws-on-activate").then (p) -> badPack = p
runs ->
spyOn(badPack.mainModule, 'serialize').andCallThrough()
atom.packages.deactivatePackage("package-that-throws-on-activate")
expect(badPack.mainModule.serialize).not.toHaveBeenCalled()
it "absorbs exceptions that are thrown by the package module's serialize method", ->
spyOn(console, 'error')
waitsForPromise ->
atom.packages.activatePackage('package-with-serialize-error')
waitsForPromise ->
atom.packages.activatePackage('package-with-serialization')
runs ->
atom.packages.deactivatePackages()
expect(atom.packages.packageStates['package-with-serialize-error']).toBeUndefined()
expect(atom.packages.packageStates['package-with-serialization']).toEqual someNumber: 1
expect(console.error).toHaveBeenCalled()
it "absorbs exceptions that are thrown by the package module's deactivate method", ->
spyOn(console, 'error')

View File

@@ -1,5 +1,6 @@
{extend} = require 'underscore-plus'
{Emitter} = require 'event-kit'
Grim = require 'grim'
Pane = require '../src/pane'
PaneAxis = require '../src/pane-axis'
PaneContainer = require '../src/pane-container'
@@ -18,8 +19,8 @@ describe "Pane", ->
onDidDestroy: (fn) -> @emitter.on('did-destroy', fn)
destroy: -> @destroyed = true; @emitter.emit('did-destroy')
isDestroyed: -> @destroyed
isPending: -> @pending
pending: false
onDidTerminatePendingState: (callback) -> @emitter.on 'terminate-pending-state', callback
terminatePendingState: -> @emitter.emit 'terminate-pending-state'
beforeEach ->
confirm = spyOn(atom.applicationDelegate, 'confirm')
@@ -92,7 +93,7 @@ describe "Pane", ->
pane = new Pane(paneParams(items: [new Item("A"), new Item("B")]))
[item1, item2] = pane.getItems()
item3 = new Item("C")
pane.addItem(item3, 1)
pane.addItem(item3, index: 1)
expect(pane.getItems()).toEqual [item1, item3, item2]
it "adds the item after the active item if no index is provided", ->
@@ -115,7 +116,7 @@ describe "Pane", ->
pane.onDidAddItem (event) -> events.push(event)
item = new Item("C")
pane.addItem(item, 1)
pane.addItem(item, index: 1)
expect(events).toEqual [{item, index: 1, moved: false}]
it "throws an exception if the item is already present on a pane", ->
@@ -132,15 +133,56 @@ describe "Pane", ->
expect(-> pane.addItem('foo')).toThrow()
expect(-> pane.addItem(1)).toThrow()
it "destroys any existing pending item if the new item is pending", ->
it "destroys any existing pending item", ->
pane = new Pane(paneParams(items: []))
itemA = new Item("A")
itemB = new Item("B")
itemA.pending = true
itemB.pending = true
pane.addItem(itemA)
itemC = new Item("C")
pane.addItem(itemA, pending: false)
pane.addItem(itemB, pending: true)
pane.addItem(itemC, pending: false)
expect(itemB.isDestroyed()).toBe true
it "adds the new item before destroying any existing pending item", ->
eventOrder = []
pane = new Pane(paneParams(items: []))
itemA = new Item("A")
itemB = new Item("B")
pane.addItem(itemA, pending: true)
pane.onDidAddItem ({item}) ->
eventOrder.push("add") if item is itemB
pane.onDidRemoveItem ({item}) ->
eventOrder.push("remove") if item is itemA
pane.addItem(itemB)
expect(itemA.isDestroyed()).toBe true
waitsFor ->
eventOrder.length is 2
runs ->
expect(eventOrder).toEqual ["add", "remove"]
describe "when using the old API of ::addItem(item, index)", ->
beforeEach ->
spyOn Grim, "deprecate"
it "supports the older public API", ->
pane = new Pane(paneParams(items: []))
itemA = new Item("A")
itemB = new Item("B")
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", ->
pane = new Pane(paneParams(items: []))
pane.addItem(new Item(), 2)
expect(Grim.deprecate).toHaveBeenCalledWith "Pane::addItem(item, 2) is deprecated in favor of Pane::addItem(item, {index: 2})"
describe "::activateItem(item)", ->
pane = null
@@ -172,21 +214,83 @@ describe "Pane", ->
beforeEach ->
itemC = new Item("C")
itemD = new Item("D")
itemC.pending = true
itemD.pending = true
it "replaces the active item if it is pending", ->
pane.activateItem(itemC)
pane.activateItem(itemC, pending: true)
expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'C', 'B']
pane.activateItem(itemD)
pane.activateItem(itemD, pending: true)
expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'D', 'B']
it "adds the item after the active item if it is not pending", ->
pane.activateItem(itemC)
pane.activateItem(itemC, pending: true)
pane.activateItemAtIndex(2)
pane.activateItem(itemD)
pane.activateItem(itemD, pending: true)
expect(pane.getItems().map (item) -> item.name).toEqual ['A', 'B', 'D']
describe "::setPendingItem", ->
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", ->
pane = null
callbackCalled = false
beforeEach ->
pane = atom.workspace.getActivePane()
callbackCalled = false
it "is called when the pending item changes", ->
pane.setPendingItem("fake item one")
pane.onItemDidTerminatePendingState (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 (item) ->
callbackCalled = true
expect(pane.getPendingItem()).toEqual "fake item two"
pane.setPendingItem("fake item two")
expect(callbackCalled).toBeTruthy()
describe "::activateNextRecentlyUsedItem() and ::activatePreviousRecentlyUsedItem()", ->
it "sets the active item to the next/previous item in the itemStack, looping around at either end", ->
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C"), new Item("D"), new Item("E")]))
[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", ->
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
@@ -253,7 +357,7 @@ describe "Pane", ->
pane = new Pane(paneParams(items: [new Item("A"), new Item("B"), new Item("C")]))
[item1, item2, item3] = pane.getItems()
it "removes the item from the items list and destroyes it", ->
it "removes the item from the items list and destroys it", ->
expect(pane.getActiveItem()).toBe item1
pane.destroyItem(item2)
expect(item2 in pane.getItems()).toBe false
@@ -264,6 +368,23 @@ describe "Pane", ->
expect(item1 in pane.getItems()).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() observers before destroying the item", ->
events = []
pane.onWillDestroyItem (event) ->
@@ -605,6 +726,23 @@ describe "Pane", ->
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", ->
item6 = new Item("F")
pane1.addItem(item6, pending: true)
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", ->
item6 = new Item("F")
pane1.addItem(item6, pending: true)
expect(pane1.getPendingItem()).toEqual item6
pane2.moveItemToPane(item5, pane1, 0)
expect(pane1.getPendingItem()).toEqual item6
describe "split methods", ->
[pane1, item1, container] = []
@@ -806,6 +944,67 @@ describe "Pane", ->
pane2.destroy()
expect(container.root).toBe pane1
describe "pending state", ->
editor1 = null
pane = null
eventCount = null
beforeEach ->
waitsForPromise ->
atom.workspace.open('sample.txt', pending: true).then (o) ->
editor1 = o
pane = atom.workspace.getActivePane()
runs ->
eventCount = 0
editor1.onDidTerminatePendingState -> eventCount++
it "does not open file in pending state by default", ->
waitsForPromise ->
atom.workspace.open('sample.js').then (o) ->
editor1 = o
pane = atom.workspace.getActivePane()
runs ->
expect(pane.getPendingItem()).toBeNull()
it "opens file in pending state if 'pending' option is true", ->
expect(pane.getPendingItem()).toEqual editor1
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", ->
editor1.insertText('I\'ll be back!')
advanceClock(editor1.getBuffer().stoppedChangingDelay)
expect(pane.getPendingItem()).toBeNull()
expect(eventCount).toBe 1
it "only calls terminate handler once when text is modified twice", ->
editor1.insertText('Some text')
advanceClock(editor1.getBuffer().stoppedChangingDelay)
editor1.save()
editor1.insertText('More text')
advanceClock(editor1.getBuffer().stoppedChangingDelay)
expect(pane.getPendingItem()).toBeNull()
expect(eventCount).toBe 1
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", ->
pane = null
@@ -837,3 +1036,30 @@ describe "Pane", ->
pane.focus()
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.focused).toBe true
it "can serialize and deserialize the order of the items in the itemStack", ->
[item1, item2, item3] = pane.getItems()
pane.itemStack = [item3, item1, item2]
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", ->
[item1, item2, item3] = pane.getItems()
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.getItems()).toEqual newPane.itemStack
it "rebuilds the itemStack if items.length does not match itemStack.length", ->
[item1, item2, item3] = pane.getItems()
pane.itemStack = [item2, item3]
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", ->
[item1, item2, item3] = pane.getItems()
pane.itemStack = [item2, item1, item3]
unserializable = {}
pane.activateItem(unserializable)
newPane = Pane.deserialize(pane.serialize(), atom)
expect(newPane.itemStack).toEqual [item2, item1, item3]

View File

@@ -21,6 +21,14 @@ describe "Project", ->
afterEach ->
deserializedProject?.destroy()
it "does not deserialize paths to non directories", ->
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
state = atom.project.serialize()
state.paths.push('/directory/that/does/not/exist')
state.paths.push(path.join(__dirname, 'fixtures', 'sample.js'))
deserializedProject.deserialize(state, atom.deserializers)
expect(deserializedProject.getPaths()).toEqual(atom.project.getPaths())
it "does not include unretained buffers in the serialized state", ->
waitsForPromise ->
atom.project.bufferForPath('a')
@@ -29,7 +37,7 @@ describe "Project", ->
expect(atom.project.getBuffers().length).toBe 1
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
expect(deserializedProject.getBuffers().length).toBe 0
it "listens for destroyed events on deserialized buffers and removes them when they are destroyed", ->
@@ -39,7 +47,7 @@ describe "Project", ->
runs ->
expect(atom.project.getBuffers().length).toBe 1
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
expect(deserializedProject.getBuffers().length).toBe 1
deserializedProject.getBuffers()[0].destroy()
@@ -56,7 +64,7 @@ describe "Project", ->
expect(atom.project.getBuffers().length).toBe 1
fs.mkdirSync(pathToOpen)
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
expect(deserializedProject.getBuffers().length).toBe 0
it "does not deserialize buffers when their path is inaccessible", ->
@@ -70,9 +78,26 @@ describe "Project", ->
expect(atom.project.getBuffers().length).toBe 1
fs.chmodSync(pathToOpen, '000')
deserializedProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
deserializedProject.deserialize(atom.project.serialize(), atom.deserializers)
deserializedProject.deserialize(atom.project.serialize({isUnloading: false}))
expect(deserializedProject.getBuffers().length).toBe 0
it "serializes marker layers only if Atom is quitting", ->
waitsForPromise ->
atom.workspace.open('a')
runs ->
bufferA = atom.project.getBuffers()[0]
layerA = bufferA.addMarkerLayer(maintainHistory: true)
markerA = layerA.markPosition([0, 3])
notQuittingProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
notQuittingProject.deserialize(atom.project.serialize({isUnloading: false}))
expect(notQuittingProject.getBuffers()[0].getMarkerLayer(layerA.id)?.getMarker(markerA.id)).toBeUndefined()
quittingProject = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm})
quittingProject.deserialize(atom.project.serialize({isUnloading: true}))
expect(quittingProject.getBuffers()[0].getMarkerLayer(layerA.id)?.getMarker(markerA.id)).not.toBeUndefined()
describe "when an editor is saved and the project has no path", ->
it "sets the project's path to the saved file's parent directory", ->
tempFile = temp.openSync().path

View File

@@ -83,3 +83,11 @@ describe "Selection", ->
selection.setBufferRange([[2, 0], [2, 10]])
selection.destroy()
expect(selection.marker.isDestroyed()).toBeTruthy()
describe ".insertText(text, options)", ->
it "allows pasting white space only lines when autoIndent is enabled", ->
selection.setBufferRange [[0, 0], [0, 0]]
selection.insertText(" \n \n\n", autoIndent: true)
expect(buffer.lineForRow(0)).toBe " "
expect(buffer.lineForRow(1)).toBe " "
expect(buffer.lineForRow(2)).toBe ""

61
spec/state-store-spec.js Normal file
View File

@@ -0,0 +1,61 @@
/** @babel */
import {it, fit, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
const StateStore = require('../src/state-store.js')
describe("StateStore", () => {
let databaseName = `test-database-${Date.now()}`
let version = 1
it("can save and load states", () => {
const store = new StateStore(databaseName, version)
return store.save('key', {foo:'bar'})
.then(() => store.load('key'))
.then((state) => {
expect(state).toEqual({foo:'bar'})
})
})
it("resolves with null when a non-existent key is loaded", () => {
const store = new StateStore(databaseName, version)
return store.load('no-such-key').then((value) => {
expect(value).toBeNull()
})
})
it("can clear the state object store", () => {
const store = new StateStore(databaseName, version)
return store.save('key', {foo:'bar'})
.then(() => store.count())
.then((count) =>
expect(count).toBe(1)
)
.then(() => store.clear())
.then(() => store.count())
.then((count) => {
expect(count).toBe(0)
})
})
describe("when there is an error reading from the database", () => {
it("rejects the promise returned by load", () => {
const store = new StateStore(databaseName, version)
const fakeErrorEvent = {target: {errorCode: "Something bad happened"}}
spyOn(IDBObjectStore.prototype, 'get').andCallFake((key) => {
let request = {}
process.nextTick(() => request.onerror(fakeErrorEvent))
return request
})
return store.load('nonexistentKey')
.then(() => {
throw new Error("Promise should have been rejected")
})
.catch((event) => {
expect(event).toBe(fakeErrorEvent)
})
})
})
})

View File

@@ -1,6 +1,6 @@
/** @babel */
import {it, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
import {it, fit, ffit, fffit, beforeEach, afterEach} from './async-spec-helpers'
import TextEditorElement from '../src/text-editor-element'
import _, {extend, flatten, last, toArray} from 'underscore-plus'
@@ -2151,10 +2151,11 @@ describe('TextEditorComponent', function () {
item.style.height = itemHeight + 'px'
wrapperNode.style.width = windowWidth + 'px'
wrapperNode.style.height = windowHeight + 'px'
atom.setWindowDimensions({
await atom.setWindowDimensions({
width: windowWidth,
height: windowHeight
})
component.measureDimensions()
component.measureWindowSize()
await nextViewUpdatePromise()
@@ -4834,7 +4835,7 @@ describe('TextEditorComponent', function () {
it('pastes the previously selected text at the clicked location', async function () {
let clipboardWrittenTo = false
spyOn(require('ipc'), 'send').andCallFake(function (eventName, selectedText) {
spyOn(require('electron').ipcRenderer, 'send').andCallFake(function (eventName, selectedText) {
if (eventName === 'write-text-to-selection-clipboard') {
require('../src/safe-clipboard').writeText(selectedText, 'selection')
clipboardWrittenTo = true

View File

@@ -91,11 +91,13 @@ describe "TextEditorPresenter", ->
expectNoStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(false, presenter, fn)
waitsForStateToUpdate = (presenter, fn) ->
waitsFor "presenter state to update", 1000, (done) ->
fn?()
line = new Error().stack.split('\n')[2].split(':')[1]
waitsFor "presenter state to update at line #{line}", 1000, (done) ->
disposable = presenter.onDidUpdateState ->
disposable.dispose()
process.nextTick(done)
fn?()
tiledContentContract = (stateFn) ->
it "contains states for tiles that are visible on screen", ->
@@ -633,16 +635,28 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> presenter.setExplicitHeight(500)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe 500
it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", ->
presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10)
expectStateUpdate presenter, -> presenter.setScrollTop(300)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
describe "scrollPastEnd", ->
it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", ->
presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10)
expectStateUpdate presenter, -> presenter.setScrollTop(300)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3)
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3)
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
it "doesn't add the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true but the presenter is created with scrollPastEnd as false", ->
presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10, scrollPastEnd: false)
expectStateUpdate presenter, -> presenter.setScrollTop(300)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false)
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe presenter.contentHeight
describe ".scrollTop", ->
it "tracks the value of ::scrollTop", ->
@@ -1336,9 +1350,11 @@ describe "TextEditorPresenter", ->
presenter = buildPresenter()
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
blockDecoration3 = addBlockDecorationBeforeScreenRow(7)
blockDecoration4 = addBlockDecorationAfterScreenRow(7)
blockDecoration4 = null
waitsForStateToUpdate presenter, ->
blockDecoration4 = addBlockDecorationAfterScreenRow(7)
waitsForStateToUpdate presenter
runs ->
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([blockDecoration1])
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
@@ -1472,9 +1488,9 @@ describe "TextEditorPresenter", ->
decoration1 = editor.decorateMarker(marker1, type: 'line', class: 'a')
presenter = buildPresenter()
marker2 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
decoration2 = editor.decorateMarker(marker2, type: 'line', class: 'b')
decoration2 = null
waitsForStateToUpdate presenter
waitsForStateToUpdate presenter, -> decoration2 = editor.decorateMarker(marker2, type: 'line', class: 'b')
runs ->
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
@@ -2150,31 +2166,40 @@ describe "TextEditorPresenter", ->
}
# becoming empty
waitsForStateToUpdate presenter, -> editor.getSelections()[1].clear(autoscroll: false)
runs ->
editor.getSelections()[1].clear(autoscroll: false)
waitsForStateToUpdate presenter
runs ->
expectUndefinedStateForSelection(presenter, 1)
# becoming non-empty
waitsForStateToUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
runs ->
editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
waitsForStateToUpdate presenter
runs ->
expectValues stateForSelectionInTile(presenter, 1, 2), {
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
}
# moving out of view
waitsForStateToUpdate presenter, -> editor.getSelections()[1].setBufferRange([[3, 4], [3, 6]], autoscroll: false)
runs ->
editor.getSelections()[1].setBufferRange([[3, 4], [3, 6]], autoscroll: false)
waitsForStateToUpdate presenter
runs ->
expectUndefinedStateForSelection(presenter, 1)
# adding
waitsForStateToUpdate presenter, -> editor.addSelectionForBufferRange([[1, 4], [1, 6]], autoscroll: false)
runs -> editor.addSelectionForBufferRange([[1, 4], [1, 6]], autoscroll: false)
waitsForStateToUpdate presenter
runs ->
expectValues stateForSelectionInTile(presenter, 2, 0), {
regions: [{top: 10, left: 4 * 10, width: 2 * 10, height: 10}]
}
# moving added selection
waitsForStateToUpdate presenter, -> editor.getSelections()[2].setBufferRange([[1, 4], [1, 8]], autoscroll: false)
runs ->
editor.getSelections()[2].setBufferRange([[1, 4], [1, 8]], autoscroll: false)
waitsForStateToUpdate presenter
destroyedSelection = null
runs ->
@@ -2208,8 +2233,9 @@ describe "TextEditorPresenter", ->
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2)
marker = editor.markBufferPosition([2, 2])
highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a')
highlight = null
waitsForStateToUpdate presenter, ->
highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a')
marker.setBufferRange([[2, 2], [5, 2]])
highlight.flash('b', 500)
runs ->
@@ -2969,9 +2995,8 @@ describe "TextEditorPresenter", ->
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 35)
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 40)
presenter.setBlockDecorationDimensions(blockDecoration5, 0, 50)
presenter.setBlockDecorationDimensions(blockDecoration6, 0, 60)
waitsForStateToUpdate presenter
waitsForStateToUpdate presenter, -> presenter.setBlockDecorationDimensions(blockDecoration6, 0, 60)
runs ->
expect(lineNumberStateForScreenRow(presenter, 0).blockDecorationsHeight).toBe(10)
expect(lineNumberStateForScreenRow(presenter, 1).blockDecorationsHeight).toBe(0)
@@ -3460,9 +3485,9 @@ describe "TextEditorPresenter", ->
gutterName: 'test-gutter-2'
class: 'test-class'
marker4 = editor.markBufferRange([[0, 0], [1, 0]])
decoration4 = editor.decorateMarker(marker4, decorationParams)
decoration4 = null
waitsForStateToUpdate presenter
waitsForStateToUpdate presenter, -> decoration4 = editor.decorateMarker(marker4, decorationParams)
runs ->
expectStateUpdate presenter, -> editor.addGutter({name: 'test-gutter-2'})

View File

@@ -0,0 +1,38 @@
TextEditorRegistry = require '../src/text-editor-registry'
describe "TextEditorRegistry", ->
[registry, editor] = []
beforeEach ->
registry = new TextEditorRegistry
describe "when a TextEditor is added", ->
it "gets added to the list of registered editors", ->
editor = {}
registry.add(editor)
expect(registry.editors.size).toBe 1
expect(registry.editors.has(editor)).toBe(true)
it "returns a Disposable that can unregister the editor", ->
editor = {}
disposable = registry.add(editor)
expect(registry.editors.size).toBe 1
disposable.dispose()
expect(registry.editors.size).toBe 0
describe "when the registry is observed", ->
it "calls the callback for current and future editors until unsubscribed", ->
[editor1, editor2, editor3] = [{}, {}, {}]
registry.add(editor1)
subscription = registry.observe spy = jasmine.createSpy()
expect(spy.calls.length).toBe 1
registry.add(editor2)
expect(spy.calls.length).toBe 2
expect(spy.argsForCall[0][0]).toBe editor1
expect(spy.argsForCall[1][0]).toBe editor2
subscription.dispose()
registry.add(editor3)
expect(spy.calls.length).toBe 2

View File

@@ -55,16 +55,6 @@ describe "TextEditor", ->
expect(editor.tokenizedLineForScreenRow(0).invisibles.eol).toBe '?'
it "restores pending tabs in pending state", ->
expect(editor.isPending()).toBe false
editor2 = TextEditor.deserialize(editor.serialize(), atom)
expect(editor2.isPending()).toBe false
pendingEditor = atom.workspace.buildTextEditor(pending: true)
expect(pendingEditor.isPending()).toBe true
editor3 = TextEditor.deserialize(pendingEditor.serialize(), atom)
expect(editor3.isPending()).toBe true
describe "when the editor is constructed with the largeFileMode option set to true", ->
it "loads the editor but doesn't tokenize", ->
editor = null
@@ -139,6 +129,15 @@ describe "TextEditor", ->
expect(editor2.getSoftTabs()).toBe true
expect(editor2.getEncoding()).toBe 'macroman'
atom.config.set('editor.tabLength', -1)
expect(editor2.getTabLength()).toBe 1
atom.config.set('editor.tabLength', 2)
expect(editor2.getTabLength()).toBe 2
atom.config.set('editor.tabLength', 17)
expect(editor2.getTabLength()).toBe 17
atom.config.set('editor.tabLength', 128)
expect(editor2.getTabLength()).toBe 128
it "uses scoped `core.fileEncoding` values", ->
editor1 = null
editor2 = null
@@ -2133,20 +2132,31 @@ describe "TextEditor", ->
editor.splitSelectionsIntoLines()
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 3]]]
describe ".consolidateSelections()", ->
it "destroys all selections but the least recent, returning true if any selections were destroyed", ->
editor.setSelectedBufferRange([[3, 16], [3, 21]])
selection1 = editor.getLastSelection()
describe "::consolidateSelections()", ->
makeMultipleSelections = ->
selection.setBufferRange [[3, 16], [3, 21]]
selection2 = editor.addSelectionForBufferRange([[3, 25], [3, 34]])
selection3 = editor.addSelectionForBufferRange([[8, 4], [8, 10]])
selection4 = editor.addSelectionForBufferRange([[1, 6], [1, 10]])
expect(editor.getSelections()).toEqual [selection, selection2, selection3, selection4]
[selection, selection2, selection3, selection4]
it "destroys all selections but the oldest selection and autoscrolls to it, returning true if any selections were destroyed", ->
[selection1] = makeMultipleSelections()
autoscrollEvents = []
editor.onDidRequestAutoscroll (event) -> autoscrollEvents.push(event)
expect(editor.getSelections()).toEqual [selection1, selection2, selection3]
expect(editor.consolidateSelections()).toBeTruthy()
expect(editor.getSelections()).toEqual [selection1]
expect(selection1.isEmpty()).toBeFalsy()
expect(editor.consolidateSelections()).toBeFalsy()
expect(editor.getSelections()).toEqual [selection1]
expect(autoscrollEvents).toEqual([
{screenRange: selection1.getScreenRange(), options: {center: true, reversed: false}}
])
describe "when the cursor is moved while there is a selection", ->
makeSelection = -> selection.setBufferRange [[1, 2], [1, 5]]
@@ -5819,52 +5829,29 @@ describe "TextEditor", ->
rangeIsReversed: false
}
describe "pending state", ->
editor1 = null
eventCount = null
describe "when the editor is constructed with the showInvisibles option set to false", ->
beforeEach ->
atom.workspace.destroyActivePane()
waitsForPromise ->
atom.workspace.open('sample.txt', pending: true).then (o) -> editor1 = o
atom.workspace.open('sample.js', showInvisibles: false).then (o) -> editor = o
runs ->
eventCount = 0
editor1.onDidTerminatePendingState -> eventCount++
it "ignores invisibles even if editor.showInvisibles is true", ->
atom.config.set('editor.showInvisibles', true)
invisibles = editor.tokenizedLineForScreenRow(0).invisibles
expect(invisibles).toBe(null)
it "does not open file in pending state by default", ->
expect(editor.isPending()).toBe false
describe "when the editor is constructed with the grammar option set", ->
beforeEach ->
atom.workspace.destroyActivePane()
waitsForPromise ->
atom.packages.activatePackage('language-coffee-script')
it "opens file in pending state if 'pending' option is true", ->
expect(editor1.isPending()).toBe true
waitsForPromise ->
atom.workspace.open('sample.js', grammar: atom.grammars.grammarForScopeName('source.coffee')).then (o) -> editor = o
it "terminates pending state if ::terminatePendingState is invoked", ->
editor1.terminatePendingState()
it "sets the grammar", ->
expect(editor.getGrammar().name).toBe 'CoffeeScript'
expect(editor1.isPending()).toBe false
expect(eventCount).toBe 1
it "terminates pending state when buffer is changed", ->
editor1.insertText('I\'ll be back!')
advanceClock(editor1.getBuffer().stoppedChangingDelay)
expect(editor1.isPending()).toBe false
expect(eventCount).toBe 1
it "only calls terminate handler once when text is modified twice", ->
editor1.insertText('Some text')
advanceClock(editor1.getBuffer().stoppedChangingDelay)
editor1.save()
editor1.insertText('More text')
advanceClock(editor1.getBuffer().stoppedChangingDelay)
expect(editor1.isPending()).toBe false
expect(eventCount).toBe 1
it "only calls terminate handler once when terminatePendingState is called twice", ->
editor1.terminatePendingState()
editor1.terminatePendingState()
expect(editor1.isPending()).toBe false
expect(eventCount).toBe 1
describe "::getElement", ->
it "returns an element", ->
expect(editor.getElement() instanceof HTMLElement).toBe(true)

View File

@@ -202,8 +202,7 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.firstInvalidRow()).toBe 3
advanceClock()
# we discover that row 2 starts a foldable region when line 3 gets tokenized
expect(changeHandler).toHaveBeenCalledWith(start: 2, end: 7, delta: 0)
expect(changeHandler).toHaveBeenCalledWith(start: 3, end: 7, delta: 0)
expect(tokenizedBuffer.firstInvalidRow()).toBe 8
describe "when there is a buffer change surrounding an invalid row", ->
@@ -253,7 +252,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 1, end: 2, delta: 0)
expect(event).toEqual(start: 2, end: 2, delta: 0)
changeHandler.reset()
advanceClock()
@@ -263,8 +262,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
# we discover that row 2 starts a foldable region when line 3 gets tokenized
expect(event).toEqual(start: 2, end: 5, delta: 0)
expect(event).toEqual(start: 3, end: 5, delta: 0)
it "resumes highlighting with the state of the previous line", ->
buffer.insert([0, 0], '/*')
@@ -292,7 +290,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 0, end: 3, delta: -2) # starts at 0 because foldable on row 0 becomes false
expect(event).toEqual(start: 1, end: 3, delta: -2)
describe "when the change invalidates the tokenization of subsequent lines", ->
it "schedules the invalidated lines to be tokenized in the background", ->
@@ -305,7 +303,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 1, end: 3, delta: -1)
expect(event).toEqual(start: 2, end: 3, delta: -1)
changeHandler.reset()
advanceClock()
@@ -314,8 +312,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
# we discover that row 2 starts a foldable region when line 3 gets tokenized
expect(event).toEqual(start: 2, end: 4, delta: 0)
expect(event).toEqual(start: 3, end: 4, delta: 0)
describe "when lines are both updated and inserted", ->
it "updates tokens to reflect the change", ->
@@ -339,7 +336,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 0, end: 2, delta: 2) # starts at 0 because .foldable becomes false on row 0
expect(event).toEqual(start: 1, end: 2, delta: 2)
describe "when the change invalidates the tokenization of subsequent lines", ->
it "schedules the invalidated lines to be tokenized in the background", ->
@@ -350,7 +347,7 @@ describe "TokenizedBuffer", ->
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
delete event.bufferChange
expect(event).toEqual(start: 1, end: 2, delta: 2)
expect(event).toEqual(start: 2, end: 2, delta: 2)
expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[0].scopes).toEqual ['source.js', 'comment.block.js', 'punctuation.definition.comment.js']
expect(tokenizedBuffer.tokenizedLineForRow(3).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
expect(tokenizedBuffer.tokenizedLineForRow(4).tokens[0].scopes).toEqual ['source.js', 'comment.block.js']
@@ -894,7 +891,7 @@ describe "TokenizedBuffer", ->
buffer.setTextInRange([[7, 0], [8, 65]], ' ok')
delete changeHandler.argsForCall[0][0].bufferChange
expect(changeHandler).toHaveBeenCalledWith(start: 4, end: 10, delta: -1) # starts at row 4 because it became foldable
expect(changeHandler).toHaveBeenCalledWith(start: 5, end: 10, delta: -1)
expect(tokenizedBuffer.tokenizedLineForRow(5).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(6).indentLevel).toBe 2
@@ -903,7 +900,7 @@ describe "TokenizedBuffer", ->
expect(tokenizedBuffer.tokenizedLineForRow(9).indentLevel).toBe 2
expect(tokenizedBuffer.tokenizedLineForRow(10).indentLevel).toBe 2 # }
describe ".foldable on tokenized lines", ->
describe "::isFoldableAtRow(row)", ->
changes = null
beforeEach ->
@@ -915,74 +912,66 @@ describe "TokenizedBuffer", ->
buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert
})
fullyTokenize(tokenizedBuffer)
tokenizedBuffer.onDidChange (change) ->
delete change.bufferChange
changes.push(change)
it "sets .foldable to true on the first line of multi-line comments", ->
expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true # because of indent
expect(tokenizedBuffer.tokenizedLineForRow(13).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(14).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(15).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(16).foldable).toBe false
it "includes the first line of multi-line comments", ->
expect(tokenizedBuffer.isFoldableAtRow(0)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(2)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(3)).toBe true # because of indent
expect(tokenizedBuffer.isFoldableAtRow(13)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(14)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(15)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(16)).toBe false
buffer.insert([0, Infinity], '\n')
expect(changes).toEqual [{start: 0, end: 1, delta: 1}]
expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe false
expect(tokenizedBuffer.isFoldableAtRow(0)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(2)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(3)).toBe false
changes = []
buffer.undo()
expect(changes).toEqual [{start: 0, end: 2, delta: -1}]
expect(tokenizedBuffer.tokenizedLineForRow(0).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true # because of indent
it "sets .foldable to true on non-comment lines that precede an increase in indentation", ->
expect(tokenizedBuffer.isFoldableAtRow(0)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(2)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(3)).toBe true # because of indent
it "includes non-comment lines that precede an increase in indentation", ->
buffer.insert([2, 0], ' ') # commented lines preceding an indent aren't foldable
expect(tokenizedBuffer.tokenizedLineForRow(1).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(2).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(3).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(4).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(5).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
changes = []
expect(tokenizedBuffer.isFoldableAtRow(1)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(2)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(3)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(4)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(5)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(6)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
buffer.insert([7, 0], ' ')
expect(changes).toEqual [{start: 6, end: 7, delta: 0}]
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
changes = []
expect(tokenizedBuffer.isFoldableAtRow(6)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
buffer.undo()
expect(changes).toEqual [{start: 6, end: 7, delta: 0}]
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
changes = []
expect(tokenizedBuffer.isFoldableAtRow(6)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
buffer.insert([7, 0], " \n x\n")
expect(changes).toEqual [{start: 6, end: 7, delta: 2}]
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
changes = []
expect(tokenizedBuffer.isFoldableAtRow(6)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
buffer.insert([9, 0], " ")
expect(changes).toEqual [{start: 9, end: 9, delta: 0}]
expect(tokenizedBuffer.tokenizedLineForRow(6).foldable).toBe true
expect(tokenizedBuffer.tokenizedLineForRow(7).foldable).toBe false
expect(tokenizedBuffer.tokenizedLineForRow(8).foldable).toBe false
expect(tokenizedBuffer.isFoldableAtRow(6)).toBe true
expect(tokenizedBuffer.isFoldableAtRow(7)).toBe false
expect(tokenizedBuffer.isFoldableAtRow(8)).toBe false
describe "when the buffer is configured with the null grammar", ->
it "uses the placeholder tokens and does not actually tokenize using the grammar", ->

View File

@@ -28,6 +28,12 @@ describe "TooltipManager", ->
hover element, ->
expect(document.body.querySelector(".tooltip")).toHaveText("Title")
it "creates a tooltip immediately if the trigger type is manual", ->
disposable = manager.add element, title: "Title", trigger: "manual"
expect(document.body.querySelector(".tooltip")).toHaveText("Title")
disposable.dispose()
expect(document.body.querySelector(".tooltip")).toBeNull()
it "allows jQuery elements to be passed as the target", ->
element2 = document.createElement('div')
jasmine.attachToDOM(element2)

View File

@@ -23,6 +23,15 @@ describe "ViewRegistry", ->
component = new TestComponent
expect(registry.getView(component)).toBe component.element
describe "when passed an object with a getElement function", ->
it "returns the return value of getElement if it's an instance of HTMLElement", ->
class TestComponent
getElement: ->
@myElement ?= document.createElement('div')
component = new TestComponent
expect(registry.getView(component)).toBe component.myElement
describe "when passed a model object", ->
describe "when a view provider is registered matching the object's constructor", ->
it "constructs a view element and assigns the model on it", ->

View File

@@ -4,7 +4,7 @@ fs = require 'fs-plus'
temp = require 'temp'
TextEditor = require '../src/text-editor'
WindowEventHandler = require '../src/window-event-handler'
ipc = require 'ipc'
{ipcRenderer} = require 'electron'
describe "WindowEventHandler", ->
[projectPath, windowEventHandler] = []
@@ -53,7 +53,7 @@ describe "WindowEventHandler", ->
describe "beforeunload event", ->
beforeEach ->
jasmine.unspy(TextEditor.prototype, "shouldPromptToSave")
spyOn(ipc, 'send')
spyOn(ipcRenderer, 'send')
describe "when pane items are modified", ->
editor = null
@@ -65,13 +65,13 @@ describe "WindowEventHandler", ->
spyOn(atom.workspace, 'confirmClose').andReturn(true)
window.dispatchEvent(new CustomEvent('beforeunload'))
expect(atom.workspace.confirmClose).toHaveBeenCalled()
expect(ipc.send).not.toHaveBeenCalledWith('did-cancel-window-unload')
expect(ipcRenderer.send).not.toHaveBeenCalledWith('did-cancel-window-unload')
it "cancels the unload if the user selects cancel", ->
spyOn(atom.workspace, 'confirmClose').andReturn(false)
window.dispatchEvent(new CustomEvent('beforeunload'))
expect(atom.workspace.confirmClose).toHaveBeenCalled()
expect(ipc.send).toHaveBeenCalledWith('did-cancel-window-unload')
expect(ipcRenderer.send).toHaveBeenCalledWith('did-cancel-window-unload')
describe "when a link is clicked", ->
it "opens the http/https links in an external application", ->

View File

@@ -1,4 +1,4 @@
ipc = require 'ipc'
{ipcRenderer} = require 'electron'
path = require 'path'
temp = require('temp').track()
@@ -127,35 +127,35 @@ describe "WorkspaceElement", ->
describe "the 'window:run-package-specs' command", ->
it "runs the package specs for the active item's project path, or the first project path", ->
workspaceElement = atom.views.getView(atom.workspace)
spyOn(ipc, 'send')
spyOn(ipcRenderer, 'send')
# No project paths. Don't try to run specs.
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
expect(ipc.send).not.toHaveBeenCalledWith("run-package-specs")
expect(ipcRenderer.send).not.toHaveBeenCalledWith("run-package-specs")
projectPaths = [temp.mkdirSync("dir1-"), temp.mkdirSync("dir2-")]
atom.project.setPaths(projectPaths)
# No active item. Use first project directory.
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
ipc.send.reset()
expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
ipcRenderer.send.reset()
# Active item doesn't implement ::getPath(). Use first project directory.
item = document.createElement("div")
atom.workspace.getActivePane().activateItem(item)
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
ipc.send.reset()
expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
ipcRenderer.send.reset()
# Active item has no path. Use first project directory.
item.getPath = -> null
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
ipc.send.reset()
expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[0], "spec"))
ipcRenderer.send.reset()
# Active item has path. Use project path for item path.
item.getPath = -> path.join(projectPaths[1], "a-file.txt")
atom.commands.dispatch(workspaceElement, "window:run-package-specs")
expect(ipc.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[1], "spec"))
ipc.send.reset()
expect(ipcRenderer.send).toHaveBeenCalledWith("run-package-specs", path.join(projectPaths[1], "spec"))
ipcRenderer.send.reset()

View File

@@ -22,11 +22,11 @@ describe "Workspace", ->
describe "serialization", ->
simulateReload = ->
workspaceState = atom.workspace.serialize()
projectState = atom.project.serialize()
projectState = atom.project.serialize({isUnloading: true})
atom.workspace.destroy()
atom.project.destroy()
atom.project = new Project({notificationManager: atom.notifications, packageManager: atom.packages, confirm: atom.confirm.bind(atom)})
atom.project.deserialize(projectState, atom.deserializers)
atom.project.deserialize(projectState)
atom.workspace = new Workspace({
config: atom.config, project: atom.project, packageManager: atom.packages,
grammarRegistry: atom.grammars, deserializerManager: atom.deserializers,
@@ -585,6 +585,72 @@ describe "Workspace", ->
open = -> workspace.open('file1', workspace.getActivePane())
expect(open).toThrow()
describe "when the file is already open in pending state", ->
it "should terminate the pending state", ->
editor = null
pane = null
waitsForPromise ->
atom.workspace.open('sample.js', pending: true).then (o) ->
editor = o
pane = atom.workspace.getActivePane()
runs ->
expect(pane.getPendingItem()).toEqual editor
waitsForPromise ->
atom.workspace.open('sample.js')
runs ->
expect(pane.getPendingItem()).toBeNull()
describe "when opening will switch from a pending tab to a permanent tab", ->
it "keeps the pending tab open", ->
editor1 = null
editor2 = null
waitsForPromise ->
atom.workspace.open('sample.txt').then (o) ->
editor1 = o
waitsForPromise ->
atom.workspace.open('sample2.txt', pending: true).then (o) ->
editor2 = o
runs ->
pane = atom.workspace.getActivePane()
pane.activateItem(editor1)
expect(pane.getItems().length).toBe 2
expect(pane.getItems()).toEqual [editor1, editor2]
describe "when replacing a pending item which is the last item in a second pane", ->
it "does not destory the pane even if core.destroyEmptyPanes is on", ->
atom.config.set('core.destroyEmptyPanes', true)
editor1 = null
editor2 = null
leftPane = atom.workspace.getActivePane()
rightPane = null
waitsForPromise ->
atom.workspace.open('sample.js', pending: true, split: 'right').then (o) ->
editor1 = o
rightPane = atom.workspace.getActivePane()
spyOn rightPane, "destroyed"
runs ->
expect(leftPane).not.toBe rightPane
expect(atom.workspace.getActivePane()).toBe rightPane
expect(atom.workspace.getActivePane().getItems().length).toBe 1
expect(rightPane.getPendingItem()).toBe editor1
waitsForPromise ->
atom.workspace.open('sample.txt', pending: true).then (o) ->
editor2 = o
runs ->
expect(rightPane.getPendingItem()).toBe editor2
expect(rightPane.destroyed.callCount).toBe 0
describe "::reopenItem()", ->
it "opens the uri associated with the last closed pane that isn't currently open", ->
pane = workspace.getActivePane()
@@ -1532,3 +1598,15 @@ describe "Workspace", ->
atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow()
expect(atom.close).toHaveBeenCalled()
describe "when the core.allowPendingPaneItems option is falsey", ->
it "does not open item with `pending: true` option as pending", ->
pane = null
atom.config.set('core.allowPendingPaneItems', false)
waitsForPromise ->
atom.workspace.open('sample.js', pending: true).then ->
pane = atom.workspace.getActivePane()
runs ->
expect(pane.getPendingItem()).toBeFalsy()