Merge branch 'master' into pr/8232

This commit is contained in:
abe33
2015-10-11 14:23:36 +02:00
206 changed files with 6052 additions and 17727 deletions

View File

@@ -1,15 +1,9 @@
{$} = require '../src/space-pen-extensions'
describe '"atom" protocol URL', ->
it 'sends the file relative in the package as response', ->
called = false
callback = -> called = true
$.ajax
url: 'atom://async/package.json'
success: callback
# In old versions of jQuery, ajax calls to custom protocol would always
# be treated as error eventhough the browser thinks it's a success
# request.
error: callback
request = new XMLHttpRequest()
request.addEventListener('load', -> called = true)
request.open('GET', 'atom://async/package.json', true)
request.send()
waitsFor 'request to be done', -> called is true

View File

@@ -1,9 +1,10 @@
path = require 'path'
_ = require 'underscore-plus'
{View, $, $$} = require '../src/space-pen-extensions'
grim = require 'grim'
marked = require 'marked'
listen = require '../src/delegated-listener'
formatStackTrace = (spec, message='', stackTrace) ->
return stackTrace unless stackTrace
@@ -30,30 +31,43 @@ formatStackTrace = (spec, message='', stackTrace) ->
lines.join('\n').trim()
module.exports =
class AtomReporter extends View
@content: ->
@div class: 'spec-reporter', =>
@div class: 'padded pull-right', =>
@button outlet: 'reloadButton', class: 'btn btn-small reload-button', 'Reload Specs'
@div outlet: 'coreArea', class: 'symbol-area', =>
@div outlet: 'coreHeader', class: 'symbol-header'
@ul outlet: 'coreSummary', class: 'symbol-summary list-unstyled'
@div outlet: 'bundledArea', class: 'symbol-area', =>
@div outlet: 'bundledHeader', class: 'symbol-header'
@ul outlet: 'bundledSummary', class: 'symbol-summary list-unstyled'
@div outlet: 'userArea', class: 'symbol-area', =>
@div outlet: 'userHeader', class: 'symbol-header'
@ul outlet: 'userSummary', class: 'symbol-summary list-unstyled'
@div outlet: "status", class: 'status alert alert-info', =>
@div outlet: "time", class: 'time'
@div outlet: "specCount", class: 'spec-count'
@div outlet: "message", class: 'message'
@div outlet: "results", class: 'results'
class AtomReporter
@div outlet: "deprecations", class: 'status alert alert-warning', style: 'display: none', =>
@span outlet: 'deprecationStatus', '0 deprecations'
@div class: 'deprecation-toggle'
@div outlet: 'deprecationList', class: 'deprecation-list'
constructor: ->
@element = document.createElement('div')
@element.innerHTML = """
<div class="spec-reporter">
<div class="padded pull-right">
<button outlet="reloadButton" class="btn btn-small reload-button">Reload Specs</button>
</div>
<div outlet="coreArea" class="symbol-area">
<div outlet="coreHeader" class="symbol-header"></div>
<ul outlet="coreSummary"class="symbol-summary list-unstyled"></ul>
</div>
<div outlet="bundledArea" class="symbol-area">
<div outlet="bundledHeader" class="symbol-header"></div>
<ul outlet="bundledSummary"class="symbol-summary list-unstyled"></ul>
</div>
<div outlet="userArea" class="symbol-area">
<div outlet="userHeader" class="symbol-header"></div>
<ul outlet="userSummary"class="symbol-summary list-unstyled"></ul>
</div>
<div outlet="status" class="status alert alert-info">
<div outlet="time" class="time"></div>
<div outlet="specCount" class="spec-count"></div>
<div outlet="message" class="message"></div>
</div>
<div outlet="results" class="results"></div>
<div outlet="deprecations" class="status alert alert-warning" style="display: none">
<span outlet="deprecationStatus">0 deprecations</span>
<div class="deprecation-toggle"></div>
</div>
<div outlet="deprecationList" class="deprecation-list"></div>
</div>
"""
for element in @element.querySelectorAll('[outlet]')
this[element.getAttribute('outlet')] = element
startedAt: null
runningSpecCount: 0
@@ -71,20 +85,18 @@ class AtomReporter extends View
specs = runner.specs()
@totalSpecCount = specs.length
@addSpecs(specs)
$(document.body).append this
@on 'click', '.stack-trace', ->
$(this).toggleClass('expanded')
@reloadButton.on 'click', -> require('ipc').send('call-window-method', 'restart')
document.body.appendChild(@element)
reportRunnerResults: (runner) ->
@updateSpecCounts()
@status.addClass('alert-success').removeClass('alert-info') if @failedCount is 0
if @failedCount is 0
@status.classList.add('alert-success')
@status.classList.remove('alert-info')
if @failedCount is 1
@message.text "#{@failedCount} failure"
@message.textContent = "#{@failedCount} failure"
else
@message.text "#{@failedCount} failures"
@message.textConent = "#{@failedCount} failures"
reportSuiteResults: (suite) ->
@@ -100,170 +112,214 @@ class AtomReporter extends View
addDeprecations: (spec) ->
deprecations = grim.getDeprecations()
@deprecationCount += deprecations.length
@deprecations.show() if @deprecationCount > 0
@deprecations.style.display = '' if @deprecationCount > 0
if @deprecationCount is 1
@deprecationStatus.text("1 deprecation")
@deprecationStatus.textContent = "1 deprecation"
else
@deprecationStatus.text("#{@deprecationCount} deprecations")
@deprecationStatus.textContent = "#{@deprecationCount} deprecations"
for deprecation in deprecations
@deprecationList.append $$ ->
@div class: 'padded', =>
@div class: 'result-message fail deprecation-message', =>
@raw marked(deprecation.message)
@deprecationList.appendChild(@buildDeprecationElement(spec, deprecation))
for stack in deprecation.getStacks()
fullStack = stack.map ({functionName, location}) ->
if functionName is '<unknown>'
" at #{location}"
else
" at #{functionName} (#{location})"
@pre class: 'stack-trace padded', formatStackTrace(spec, deprecation.message, fullStack.join('\n'))
grim.clearDeprecations()
handleEvents: ->
$(document).on "click", ".spec-toggle", ({currentTarget}) ->
element = $(currentTarget)
specFailures = element.parent().find('.spec-failures')
specFailures.toggle()
element.toggleClass('folded')
false
buildDeprecationElement: (spec, deprecation) ->
div = document.createElement('div')
div.className = 'padded'
div.innerHTML = """
<div class="result-message fail deprecation-message">
#{marked(deprecation.message)}
</div>
"""
$(document).on "click", ".deprecation-toggle", ({currentTarget}) ->
element = $(currentTarget)
deprecationList = $(document).find('.deprecation-list')
deprecationList.toggle()
element.toggleClass('folded')
false
for stack in deprecation.getStacks()
fullStack = stack.map ({functionName, location}) ->
if functionName is '<unknown>'
" at #{location}"
else
" at #{functionName} (#{location})"
pre = document.createElement('pre')
pre.className = 'stack-trace padded'
pre.textContent = formatStackTrace(spec, deprecation.message, fullStack.join('\n'))
div.appendChild(pre)
div
handleEvents: ->
listen document, 'click', '.spec-toggle', (event) ->
specFailures = event.currentTarget.parentElement.querySelector('.spec-failures')
if specFailures.style.display is 'none'
specFailures.style.display = ''
event.currentTarget.classList.remove('folded')
else
specFailures.style.display = 'none'
event.currentTarget.classList.add('folded')
event.preventDefault()
listen document, 'click', '.deprecation-list', (event) ->
deprecationList = event.currentTarget.parentElement.querySelector('.deprecation-list')
if deprecationList.style.display is 'none'
deprecationList.style.display = ''
event.currentTarget.classList.remove('folded')
else
deprecationList.style.display = 'none'
event.currentTarget.classList.add('folded')
event.preventDefault()
listen document, 'click', '.stack-trace', (event) ->
event.currentTarget.classList.toggle('expanded')
@reloadButton.addEventListener('click', -> require('ipc').send('call-window-method', 'restart'))
updateSpecCounts: ->
if @skippedCount
specCount = "#{@completeSpecCount - @skippedCount}/#{@totalSpecCount - @skippedCount} (#{@skippedCount} skipped)"
else
specCount = "#{@completeSpecCount}/#{@totalSpecCount}"
@specCount[0].textContent = specCount
@specCount.textContent = specCount
updateStatusView: (spec) ->
if @failedCount > 0
@status.addClass('alert-danger').removeClass('alert-info')
@status.classList.add('alert-danger')
@status.classList.remove('alert-info')
@updateSpecCounts()
rootSuite = spec.suite
rootSuite = rootSuite.parentSuite while rootSuite.parentSuite
@message.text rootSuite.description
@message.textContent = rootSuite.description
time = "#{Math.round((spec.endedAt - @startedAt) / 10)}"
time = "0#{time}" if time.length < 3
@time[0].textContent = "#{time[0...-2]}.#{time[-2..]}s"
@time.textContent = "#{time[0...-2]}.#{time[-2..]}s"
addSpecs: (specs) ->
coreSpecs = 0
bundledPackageSpecs = 0
userPackageSpecs = 0
for spec in specs
symbol = $$ -> @li id: "spec-summary-#{spec.id}", class: "spec-summary pending"
symbol = document.createElement('li')
symbol.setAttribute('id', "spec-summary-#{spec.id}")
symbol.className = "spec-summary pending"
switch spec.specType
when 'core'
coreSpecs++
@coreSummary.append symbol
@coreSummary.appendChild symbol
when 'bundled'
bundledPackageSpecs++
@bundledSummary.append symbol
@bundledSummary.appendChild symbol
when 'user'
userPackageSpecs++
@userSummary.append symbol
@userSummary.appendChild symbol
if coreSpecs > 0
@coreHeader.text("Core Specs (#{coreSpecs})")
@coreHeader.textContent = "Core Specs (#{coreSpecs})"
else
@coreArea.hide()
@coreArea.style.display = 'none'
if bundledPackageSpecs > 0
@bundledHeader.text("Bundled Package Specs (#{bundledPackageSpecs})")
@bundledHeader.textContent = "Bundled Package Specs (#{bundledPackageSpecs})"
else
@bundledArea.hide()
@bundledArea.style.display = 'none'
if userPackageSpecs > 0
if coreSpecs is 0 and bundledPackageSpecs is 0
# Package specs being run, show a more descriptive label
{specDirectory} = specs[0]
packageFolderName = path.basename(path.dirname(specDirectory))
packageName = _.undasherize(_.uncamelcase(packageFolderName))
@userHeader.text("#{packageName} Specs")
@userHeader.textContent = "#{packageName} Specs"
else
@userHeader.text("User Package Specs (#{userPackageSpecs})")
@userHeader.textContent = "User Package Specs (#{userPackageSpecs})"
else
@userArea.hide()
@userArea.style.display = 'none'
specStarted: (spec) ->
@runningSpecCount++
specComplete: (spec) ->
specSummaryElement = $("#spec-summary-#{spec.id}")
specSummaryElement.removeClass('pending')
specSummaryElement.setTooltip(title: spec.getFullName(), container: '.spec-reporter')
specSummaryElement = document.getElementById("spec-summary-#{spec.id}")
specSummaryElement.classList.remove('pending')
results = spec.results()
if results.skipped
specSummaryElement.addClass("skipped")
specSummaryElement.classList.add("skipped")
@skippedCount++
else if results.passed()
specSummaryElement.addClass("passed")
specSummaryElement.classList.add("passed")
@passedCount++
else
specSummaryElement.addClass("failed")
specSummaryElement.classList.add("failed")
specView = new SpecResultView(spec)
specView.attach()
@failedCount++
@addDeprecations(spec)
class SuiteResultView extends View
@content: ->
@div class: 'suite', =>
@div outlet: 'description', class: 'description'
initialize: (@suite) ->
@attr('id', "suite-view-#{@suite.id}")
@description.text(@suite.description)
class SuiteResultView
constructor: (@suite) ->
@element = document.createElement('div')
@element.className = 'suite'
@element.setAttribute('id', "suite-view-#{@suite.id}")
@description = document.createElement('div')
@description.className = 'description'
@description.textContent = @suite.description
@element.appendChild(@description)
attach: ->
(@parentSuiteView() or $('.results')).append this
(@parentSuiteView() or document.querySelector('.results')).appendChild(@element)
parentSuiteView: ->
return unless @suite.parentSuite
if not suiteView = $("#suite-view-#{@suite.parentSuite.id}").view()
unless suiteViewElement = document.querySelector("#suite-view-#{@suite.parentSuite.id}")
suiteView = new SuiteResultView(@suite.parentSuite)
suiteView.attach()
suiteViewElement = suiteView.element
suiteView
suiteViewElement
class SpecResultView extends View
@content: ->
@div class: 'spec', =>
@div class: 'spec-toggle'
@div outlet: 'description', class: 'description'
@div outlet: 'specFailures', class: 'spec-failures'
class SpecResultView
constructor: (@spec) ->
@element = document.createElement('div')
@element.className = 'spec'
@element.innerHTML = """
<div class='spec-toggle'></div>
<div outlet='description' class='description'></div>
<div outlet='specFailures' class='spec-failures'></div>
"""
@description = @element.querySelector('[outlet="description"]')
@specFailures = @element.querySelector('[outlet="specFailures"]')
initialize: (@spec) ->
@addClass("spec-view-#{@spec.id}")
@element.classList.add("spec-view-#{@spec.id}")
description = @spec.description
description = "it #{description}" if description.indexOf('it ') isnt 0
@description.text(description)
@description.textContent = description
for result in @spec.results().getItems() when not result.passed()
stackTrace = formatStackTrace(@spec, result.message, result.trace.stack)
@specFailures.append $$ ->
@div result.message, class: 'result-message fail'
@pre stackTrace, class: 'stack-trace padded' if stackTrace
resultElement = document.createElement('div')
resultElement.className = 'result-message fail'
resultElement.textContent = result.message
@specFailures.appendChild(resultElement)
if stackTrace
traceElement = document.createElement('pre')
traceElement.className = 'stack-trace padded'
traceElement.textContent = stackTrace
@specFailures.appendChild(traceElement)
attach: ->
@parentSuiteView().append this
@parentSuiteView().appendChild(@element)
parentSuiteView: ->
if not suiteView = $("#suite-view-#{@spec.suite.id}").view()
unless suiteViewElement = document.querySelector("#suite-view-#{@spec.suite.id}")
suiteView = new SuiteResultView(@spec.suite)
suiteView.attach()
suiteViewElement = suiteView.element
suiteView
suiteViewElement

View File

@@ -1,4 +1,3 @@
{$, $$} = require '../src/space-pen-extensions'
Exec = require('child_process').exec
path = require 'path'
Package = require '../src/package'
@@ -223,3 +222,31 @@ describe "the `atom` global", ->
spyOn(atom, "pickFolder").andCallFake (callback) -> callback(null)
atom.addProjectFolder()
expect(atom.project.getPaths()).toEqual(initialPaths)
describe "::unloadEditorWindow()", ->
it "saves the serialized state of the window so it can be deserialized after reload", ->
workspaceState = atom.workspace.serialize()
syntaxState = atom.grammars.serialize()
projectState = atom.project.serialize()
atom.unloadEditorWindow()
expect(atom.state.workspace).toEqual workspaceState
expect(atom.state.grammars).toEqual syntaxState
expect(atom.state.project).toEqual projectState
expect(atom.saveSync).toHaveBeenCalled()
describe "::removeEditorWindow()", ->
it "unsubscribes from all buffers", ->
waitsForPromise ->
atom.workspace.open("sample.js")
runs ->
buffer = atom.workspace.getActivePaneItem().buffer
pane = atom.workspace.getActivePane()
pane.splitRight(copyActiveItem: true)
expect(atom.workspace.getTextEditors().length).toBe 2
atom.removeEditorWindow()
expect(buffer.getSubscriptionCount()).toBe 0

View File

@@ -1,34 +1,81 @@
path = require 'path'
fs = require 'fs-plus'
temp = require 'temp'
installer = require '../src/command-installer'
CommandInstaller = require '../src/command-installer'
describe "install(commandPath, callback)", ->
commandFilePath = temp.openSync("atom-command").path
commandName = path.basename(commandFilePath)
installationPath = temp.mkdirSync("atom-bin")
installationFilePath = path.join(installationPath, commandName)
describe "CommandInstaller on #darwin", ->
[installer, resourcesPath, installationPath, atomBinPath, apmBinPath] = []
beforeEach ->
fs.chmodSync(commandFilePath, '755')
spyOn(installer, 'getInstallDirectory').andReturn installationPath
installationPath = temp.mkdirSync("atom-bin")
describe "on #darwin", ->
it "symlinks the command and makes it executable", ->
expect(fs.isFileSync(commandFilePath)).toBeTruthy()
expect(fs.isFileSync(installationFilePath)).toBeFalsy()
resourcesPath = temp.mkdirSync('atom-app')
atomBinPath = path.join(resourcesPath, 'app', 'atom.sh')
apmBinPath = path.join(resourcesPath, 'app', 'apm', 'node_modules', '.bin', 'apm')
fs.writeFileSync(atomBinPath, "")
fs.writeFileSync(apmBinPath, "")
fs.chmodSync(atomBinPath, '755')
fs.chmodSync(apmBinPath, '755')
installDone = false
installError = null
installer.createSymlink commandFilePath, false, (error) ->
installDone = true
installError = error
spyOn(CommandInstaller::, 'getResourcesDirectory').andReturn(resourcesPath)
spyOn(CommandInstaller::, 'getInstallDirectory').andReturn(installationPath)
waitsFor ->
installDone
describe "when using a stable version of atom", ->
beforeEach ->
installer = new CommandInstaller("2.0.2")
it "symlinks the atom command as 'atom'", ->
installedAtomPath = path.join(installationPath, 'atom')
expect(fs.isFileSync(installedAtomPath)).toBeFalsy()
waitsFor (done) ->
installer.installAtomCommand(false, done)
runs ->
expect(installError).toBeNull()
expect(fs.isFileSync(installationFilePath)).toBeTruthy()
expect(fs.realpathSync(installationFilePath)).toBe fs.realpathSync(commandFilePath)
expect(fs.isExecutableSync(installationFilePath)).toBeTruthy()
expect(fs.realpathSync(installedAtomPath)).toBe fs.realpathSync(atomBinPath)
expect(fs.isExecutableSync(installedAtomPath)).toBe true
expect(fs.isFileSync(path.join(installationPath, 'atom-beta'))).toBe false
it "symlinks the apm command as 'apm'", ->
installedApmPath = path.join(installationPath, 'apm')
expect(fs.isFileSync(installedApmPath)).toBeFalsy()
waitsFor (done) ->
installer.installApmCommand(false, done)
runs ->
expect(fs.realpathSync(installedApmPath)).toBe fs.realpathSync(apmBinPath)
expect(fs.isExecutableSync(installedApmPath)).toBeTruthy()
expect(fs.isFileSync(path.join(installationPath, 'apm-beta'))).toBe false
describe "when using a beta version of atom", ->
beforeEach ->
installer = new CommandInstaller("2.2.0-beta.0")
it "symlinks the atom command as 'atom-beta'", ->
installedAtomPath = path.join(installationPath, 'atom-beta')
expect(fs.isFileSync(installedAtomPath)).toBeFalsy()
waitsFor (done) ->
installer.installAtomCommand(false, done)
runs ->
expect(fs.realpathSync(installedAtomPath)).toBe fs.realpathSync(atomBinPath)
expect(fs.isExecutableSync(installedAtomPath)).toBe true
expect(fs.isFileSync(path.join(installationPath, 'atom'))).toBe false
it "symlinks the apm command as 'apm-beta'", ->
installedApmPath = path.join(installationPath, 'apm-beta')
expect(fs.isFileSync(installedApmPath)).toBeFalsy()
waitsFor (done) ->
installer.installApmCommand(false, done)
runs ->
expect(fs.realpathSync(installedApmPath)).toBe fs.realpathSync(apmBinPath)
expect(fs.isExecutableSync(installedApmPath)).toBeTruthy()
expect(fs.isFileSync(path.join(installationPath, 'apm'))).toBe false

View File

@@ -115,6 +115,15 @@ describe "CommandRegistry", ->
grandchild.dispatchEvent(dispatchedEvent)
expect(dispatchedEvent.abortKeyBinding).toHaveBeenCalled()
it "copies non-standard properties from the original event to the synthetic event", ->
syntheticEvent = null
registry.add '.child', 'command', (event) -> syntheticEvent = event
dispatchedEvent = new CustomEvent('command', bubbles: true)
dispatchedEvent.nonStandardProperty = 'testing'
grandchild.dispatchEvent(dispatchedEvent)
expect(syntheticEvent.nonStandardProperty).toBe 'testing'
it "allows listeners to be removed via a disposable returned by ::add", ->
calls = []

View File

@@ -2,7 +2,6 @@ path = require 'path'
temp = require 'temp'
CSON = require 'season'
fs = require 'fs-plus'
Grim = require 'grim'
describe "Config", ->
dotAtomPath = null
@@ -364,16 +363,6 @@ describe "Config", ->
expect(atom.config.save).not.toHaveBeenCalled()
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 55
it "deprecates passing a scope selector as the first argument", ->
atom.config.setDefaults("foo", bar: baz: 10)
atom.config.set('foo.bar.baz', 55, scopeSelector: '.source.coffee')
spyOn(Grim, 'deprecate')
atom.config.unset('.source.coffee', 'foo.bar.baz')
expect(Grim.deprecate).toHaveBeenCalled()
expect(atom.config.get('foo.bar.baz', scope: ['.source.coffee'])).toBe 10
describe ".onDidChange(keyPath, {scope})", ->
[observeHandler, observeSubscription] = []
@@ -458,15 +447,6 @@ describe "Config", ->
expect(changeSpy).toHaveBeenCalledWith({oldValue: 12, newValue: undefined})
changeSpy.reset()
it 'deprecates using a scope descriptor as an optional first argument', ->
keyPath = "foo.bar.baz"
spyOn(Grim, 'deprecate')
atom.config.onDidChange [".source.coffee", ".string.quoted.double.coffee"], keyPath, changeSpy = jasmine.createSpy()
expect(Grim.deprecate).toHaveBeenCalled()
atom.config.set("foo.bar.baz", 12)
expect(changeSpy).toHaveBeenCalledWith({oldValue: undefined, newValue: 12})
describe ".observe(keyPath, {scope})", ->
[observeHandler, observeSubscription] = []
@@ -535,16 +515,6 @@ describe "Config", ->
expect(observeHandler).toHaveBeenCalledWith("value 2")
expect(otherHandler).not.toHaveBeenCalledWith("value 2")
it "deprecates using a scope descriptor as the first argument", ->
spyOn(Grim, 'deprecate')
atom.config.observe([".some.scope"], "foo.bar.baz", observeHandler)
atom.config.observe([".another.scope"], "foo.bar.baz", otherHandler)
expect(Grim.deprecate).toHaveBeenCalled()
atom.config.set('foo.bar.baz', "value 2", scopeSelector: ".some")
expect(observeHandler).toHaveBeenCalledWith("value 2")
expect(otherHandler).not.toHaveBeenCalledWith("value 2")
it 'calls the callback when properties with more specific selectors are removed', ->
changeSpy = jasmine.createSpy()
atom.config.observe("foo.bar.baz", scope: [".source.coffee", ".string.quoted.double.coffee"], changeSpy)
@@ -1058,10 +1028,6 @@ describe "Config", ->
atom.config.setDefaults("foo.bar.baz", a: 2)
expect(updatedCallback.callCount).toBe 1
it "sets a default when the setting's key contains an escaped dot", ->
atom.config.setDefaults("foo", 'a\\.b': 1, b: 2)
expect(atom.config.get("foo")).toEqual 'a\\.b': 1, b: 2
describe ".setSchema(keyPath, schema)", ->
it 'creates a properly nested schema', ->
schema =
@@ -1629,135 +1595,3 @@ describe "Config", ->
expect(atom.config.set('foo.bar.arr', ['two', 'three'])).toBe true
expect(atom.config.get('foo.bar.arr')).toEqual ['two', 'three']
describe "Deprecated Methods", ->
describe ".getDefault(keyPath)", ->
it "returns a clone of the default value", ->
atom.config.setDefaults("foo", same: 1, changes: 1)
spyOn(Grim, 'deprecate')
expect(atom.config.getDefault('foo.same')).toBe 1
expect(atom.config.getDefault('foo.changes')).toBe 1
expect(Grim.deprecate.callCount).toBe 2
atom.config.set('foo.same', 2)
atom.config.set('foo.changes', 3)
expect(atom.config.getDefault('foo.same')).toBe 1
expect(atom.config.getDefault('foo.changes')).toBe 1
expect(Grim.deprecate.callCount).toBe 4
initialDefaultValue = [1, 2, 3]
atom.config.setDefaults("foo", bar: initialDefaultValue)
expect(atom.config.getDefault('foo.bar')).toEqual initialDefaultValue
expect(atom.config.getDefault('foo.bar')).not.toBe initialDefaultValue
expect(Grim.deprecate.callCount).toBe 6
describe "when scoped settings are used", ->
it "returns the global default when no scoped default set", ->
atom.config.setDefaults("foo", bar: baz: 10)
spyOn(Grim, 'deprecate')
expect(atom.config.getDefault('.source.coffee', 'foo.bar.baz')).toBe 10
expect(Grim.deprecate).toHaveBeenCalled()
it "returns the scoped settings not including the user's config file", ->
atom.config.setDefaults("foo", bar: baz: 10)
atom.config.set("foo.bar.baz", 42, scopeSelector: ".source.coffee", source: "some-source")
spyOn(Grim, 'deprecate')
expect(atom.config.getDefault('.source.coffee', 'foo.bar.baz')).toBe 42
expect(Grim.deprecate.callCount).toBe 1
atom.config.set('foo.bar.baz', 55, scopeSelector: '.source.coffee')
expect(atom.config.getDefault('.source.coffee', 'foo.bar.baz')).toBe 42
expect(Grim.deprecate.callCount).toBe 2
describe ".isDefault(keyPath)", ->
it "returns true when the value of the key path is its default value", ->
atom.config.setDefaults("foo", same: 1, changes: 1)
spyOn(Grim, 'deprecate')
expect(atom.config.isDefault('foo.same')).toBe true
expect(atom.config.isDefault('foo.changes')).toBe true
expect(Grim.deprecate.callCount).toBe 2
atom.config.set('foo.same', 2)
atom.config.set('foo.changes', 3)
expect(atom.config.isDefault('foo.same')).toBe false
expect(atom.config.isDefault('foo.changes')).toBe false
expect(Grim.deprecate.callCount).toBe 4
describe "when scoped settings are used", ->
it "returns false when a scoped setting was set by the user", ->
spyOn(Grim, 'deprecate')
expect(atom.config.isDefault('.source.coffee', 'foo.bar.baz')).toBe true
expect(Grim.deprecate.callCount).toBe 1
atom.config.set("foo.bar.baz", 42, scopeSelector: ".source.coffee", source: "something-else")
expect(atom.config.isDefault('.source.coffee', 'foo.bar.baz')).toBe true
expect(Grim.deprecate.callCount).toBe 2
atom.config.set('foo.bar.baz', 55, scopeSelector: '.source.coffee')
expect(atom.config.isDefault('.source.coffee', 'foo.bar.baz')).toBe false
expect(Grim.deprecate.callCount).toBe 3
describe ".toggle(keyPath)", ->
beforeEach ->
jasmine.snapshotDeprecations()
afterEach ->
jasmine.restoreDeprecationsSnapshot()
it "negates the boolean value of the current key path value", ->
atom.config.set('foo.a', 1)
atom.config.toggle('foo.a')
expect(atom.config.get('foo.a')).toBe false
atom.config.set('foo.a', '')
atom.config.toggle('foo.a')
expect(atom.config.get('foo.a')).toBe true
atom.config.set('foo.a', null)
atom.config.toggle('foo.a')
expect(atom.config.get('foo.a')).toBe true
atom.config.set('foo.a', true)
atom.config.toggle('foo.a')
expect(atom.config.get('foo.a')).toBe false
describe ".getSettings()", ->
it "returns all settings including defaults", ->
atom.config.setDefaults("foo", bar: baz: 10)
atom.config.set("foo.ok", 12)
jasmine.snapshotDeprecations()
expect(atom.config.getSettings().foo).toEqual
ok: 12
bar:
baz: 10
jasmine.restoreDeprecationsSnapshot()
describe ".getPositiveInt(keyPath, defaultValue)", ->
beforeEach ->
jasmine.snapshotDeprecations()
afterEach ->
jasmine.restoreDeprecationsSnapshot()
it "returns the proper coerced value", ->
atom.config.set('editor.preferredLineLength', 0)
expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 1
it "returns the proper coerced value", ->
atom.config.set('editor.preferredLineLength', -1234)
expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 1
it "returns the default value when a string is passed in", ->
atom.config.set('editor.preferredLineLength', 'abcd')
expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 80
it "returns the default value when null is passed in", ->
atom.config.set('editor.preferredLineLength', null)
expect(atom.config.getPositiveInt('editor.preferredLineLength', 80)).toBe 80

View File

@@ -1,5 +1,3 @@
{$$} = require '../src/space-pen-extensions'
ContextMenuManager = require '../src/context-menu-manager'
describe "ContextMenuManager", ->
@@ -158,32 +156,3 @@ describe "ContextMenuManager", ->
catch error
addError = error
expect(addError.message).toContain('<>')
describe "when the menus are specified in a legacy format", ->
beforeEach ->
jasmine.snapshotDeprecations()
afterEach ->
jasmine.restoreDeprecationsSnapshot()
it "allows items to be specified in the legacy format for now", ->
contextMenu.add '.parent':
'A': 'a'
'Separator 1': '-'
'B':
'C': 'c'
'Separator 2': '-'
'D': 'd'
expect(contextMenu.templateForElement(parent)).toEqual [
{label: 'A', command: 'a'}
{type: 'separator'}
{
label: 'B'
submenu: [
{label: 'C', command: 'c'}
{type: 'separator'}
{label: 'D', command: 'd'}
]
}
]

View File

@@ -47,14 +47,6 @@ describe "DisplayBuffer", ->
buffer.insert([0, 0], oneHundredLines)
expect(displayBuffer.getLineCount()).toBe 100 + originalLineCount
it "reassigns the scrollTop if it exceeds the max possible value after lines are removed", ->
displayBuffer.setHeight(50)
displayBuffer.setLineHeightInPixels(10)
displayBuffer.setScrollTop(80)
buffer.delete([[8, 0], [10, 0]])
expect(displayBuffer.getScrollTop()).toBe 60
it "updates the display buffer prior to invoking change handlers registered on the buffer", ->
buffer.onDidChange -> expect(displayBuffer2.tokenizedLineForScreenRow(0).text).toBe "testing"
displayBuffer2 = new DisplayBuffer({buffer, tabLength})
@@ -240,7 +232,8 @@ describe "DisplayBuffer", ->
describe "when a newline is inserted, deleted, and re-inserted at the end of a wrapped line (regression)", ->
it "correctly renders the original wrapped line", ->
buffer = atom.project.buildBufferSync(null, '')
displayBuffer = new DisplayBuffer({buffer, tabLength, editorWidthInChars: 30, softWrapped: true})
displayBuffer = new DisplayBuffer({buffer, tabLength, editorWidthInChars: 30})
displayBuffer.setSoftWrapped(true)
buffer.insert([0, 0], "the quick brown fox jumps over the lazy dog.")
buffer.insert([0, Infinity], '\n')
@@ -281,6 +274,9 @@ describe "DisplayBuffer", ->
describe ".setEditorWidthInChars(length)", ->
it "changes the length at which lines are wrapped and emits a change event for all screen lines", ->
tokensText = (tokens) ->
_.pluck(tokens, 'value').join('')
displayBuffer.setEditorWidthInChars(40)
expect(tokensText displayBuffer.tokenizedLineForScreenRow(4).tokens).toBe ' left = [], right = [];'
expect(tokensText displayBuffer.tokenizedLineForScreenRow(5).tokens).toBe ' while(items.length > 0) {'
@@ -293,18 +289,6 @@ describe "DisplayBuffer", ->
displayBuffer.setEditorWidthInChars(-1)
expect(displayBuffer.editorWidthInChars).not.toBe -1
it "sets ::scrollLeft to 0 and keeps it there when soft wrapping is enabled", ->
displayBuffer.setDefaultCharWidth(10)
displayBuffer.setWidth(85)
displayBuffer.setSoftWrapped(false)
displayBuffer.setScrollLeft(Infinity)
expect(displayBuffer.getScrollLeft()).toBeGreaterThan 0
displayBuffer.setSoftWrapped(true)
expect(displayBuffer.getScrollLeft()).toBe 0
displayBuffer.setScrollLeft(10)
expect(displayBuffer.getScrollLeft()).toBe 0
describe "primitive folding", ->
beforeEach ->
displayBuffer.destroy()
@@ -734,21 +718,6 @@ describe "DisplayBuffer", ->
expect(displayBuffer.clipScreenPosition([0, 1], clip: 'forward')).toEqual [0, tabLength]
expect(displayBuffer.clipScreenPosition([0, tabLength], clip: 'forward')).toEqual [0, tabLength]
describe "::screenPositionForPixelPosition(pixelPosition)", ->
it "clips pixel positions above buffer start", ->
displayBuffer.setLineHeightInPixels(20)
expect(displayBuffer.screenPositionForPixelPosition(top: -Infinity, left: -Infinity)).toEqual [0, 0]
expect(displayBuffer.screenPositionForPixelPosition(top: -Infinity, left: Infinity)).toEqual [0, 0]
expect(displayBuffer.screenPositionForPixelPosition(top: -1, left: Infinity)).toEqual [0, 0]
expect(displayBuffer.screenPositionForPixelPosition(top: 0, left: Infinity)).toEqual [0, 29]
it "clips pixel positions below buffer end", ->
displayBuffer.setLineHeightInPixels(20)
expect(displayBuffer.screenPositionForPixelPosition(top: Infinity, left: -Infinity)).toEqual [12, 2]
expect(displayBuffer.screenPositionForPixelPosition(top: Infinity, left: Infinity)).toEqual [12, 2]
expect(displayBuffer.screenPositionForPixelPosition(top: displayBuffer.getHeight() + 1, left: 0)).toEqual [12, 2]
expect(displayBuffer.screenPositionForPixelPosition(top: displayBuffer.getHeight() - 1, left: 0)).toEqual [12, 0]
describe "::screenPositionForBufferPosition(bufferPosition, options)", ->
it "clips the specified buffer position", ->
expect(displayBuffer.screenPositionForBufferPosition([0, 2])).toEqual [0, 2]
@@ -1182,20 +1151,6 @@ describe "DisplayBuffer", ->
expect(marker1.getProperties()).toEqual a: 1, b: 2
expect(marker2.getProperties()).toEqual a: 1, b: 3
describe "Marker::getPixelRange()", ->
it "returns the start and end positions of the marker based on the line height and character widths assigned to the DisplayBuffer", ->
marker = displayBuffer.markScreenRange([[5, 10], [6, 4]])
displayBuffer.setLineHeightInPixels(20)
displayBuffer.setDefaultCharWidth(10)
for char in ['r', 'e', 't', 'u', 'r', 'n']
displayBuffer.setScopedCharWidth(["source.js", "keyword.control.js"], char, 11)
{start, end} = marker.getPixelRange()
expect(start.top).toBe 5 * 20
expect(start.left).toBe (4 * 10) + (6 * 11)
describe 'when there are multiple DisplayBuffers for a buffer', ->
describe 'when a marker is created', ->
it 'the second display buffer will not emit a marker-created event when the marker has been deleted in the first marker-created event', ->
@@ -1250,157 +1205,18 @@ describe "DisplayBuffer", ->
expect(displayBuffer.getDecorations(class: 'two').length).toEqual 0
expect(displayBuffer.getDecorations(class: 'one').length).toEqual 1
describe "::setScrollTop", ->
beforeEach ->
displayBuffer.setLineHeightInPixels(10)
it "disallows negative values", ->
displayBuffer.setHeight(displayBuffer.getScrollHeight() + 100)
expect(displayBuffer.setScrollTop(-10)).toBe 0
expect(displayBuffer.getScrollTop()).toBe 0
it "disallows values that would make ::getScrollBottom() exceed ::getScrollHeight()", ->
displayBuffer.setHeight(50)
maxScrollTop = displayBuffer.getScrollHeight() - displayBuffer.getHeight()
expect(displayBuffer.setScrollTop(maxScrollTop)).toBe maxScrollTop
expect(displayBuffer.getScrollTop()).toBe maxScrollTop
expect(displayBuffer.setScrollTop(maxScrollTop + 50)).toBe maxScrollTop
expect(displayBuffer.getScrollTop()).toBe maxScrollTop
describe "editor.scrollPastEnd", ->
describe "when editor.scrollPastEnd is false", ->
beforeEach ->
atom.config.set("editor.scrollPastEnd", false)
displayBuffer.setLineHeightInPixels(10)
it "does not add the height of the view to the scroll height", ->
lineHeight = displayBuffer.getLineHeightInPixels()
originalScrollHeight = displayBuffer.getScrollHeight()
displayBuffer.setHeight(50)
expect(displayBuffer.getScrollHeight()).toBe originalScrollHeight
describe "when editor.scrollPastEnd is true", ->
beforeEach ->
atom.config.set("editor.scrollPastEnd", true)
displayBuffer.setLineHeightInPixels(10)
it "adds the height of the view to the scroll height", ->
lineHeight = displayBuffer.getLineHeightInPixels()
originalScrollHeight = displayBuffer.getScrollHeight()
displayBuffer.setHeight(50)
expect(displayBuffer.getScrollHeight()).toEqual(originalScrollHeight + displayBuffer.height - (lineHeight * 3))
describe "::setScrollLeft", ->
beforeEach ->
displayBuffer.setLineHeightInPixels(10)
displayBuffer.setDefaultCharWidth(10)
it "disallows negative values", ->
displayBuffer.setWidth(displayBuffer.getScrollWidth() + 100)
expect(displayBuffer.setScrollLeft(-10)).toBe 0
expect(displayBuffer.getScrollLeft()).toBe 0
it "disallows values that would make ::getScrollRight() exceed ::getScrollWidth()", ->
displayBuffer.setWidth(50)
maxScrollLeft = displayBuffer.getScrollWidth() - displayBuffer.getWidth()
expect(displayBuffer.setScrollLeft(maxScrollLeft)).toBe maxScrollLeft
expect(displayBuffer.getScrollLeft()).toBe maxScrollLeft
expect(displayBuffer.setScrollLeft(maxScrollLeft + 50)).toBe maxScrollLeft
expect(displayBuffer.getScrollLeft()).toBe maxScrollLeft
describe "::scrollToScreenPosition(position, [options])", ->
beforeEach ->
displayBuffer.setLineHeightInPixels(10)
displayBuffer.setDefaultCharWidth(10)
displayBuffer.setHorizontalScrollbarHeight(0)
displayBuffer.setHeight(50)
displayBuffer.setWidth(150)
it "sets the scroll top and scroll left so the given screen position is in view", ->
displayBuffer.scrollToScreenPosition([8, 20])
expect(displayBuffer.getScrollBottom()).toBe (9 + displayBuffer.getVerticalScrollMargin()) * 10
expect(displayBuffer.getScrollRight()).toBe (20 + displayBuffer.getHorizontalScrollMargin()) * 10
it "triggers ::onDidRequestAutoscroll with the logical coordinates along with the options", ->
scrollSpy = jasmine.createSpy("::onDidRequestAutoscroll")
displayBuffer.onDidRequestAutoscroll(scrollSpy)
displayBuffer.scrollToScreenPosition([8, 20])
expect(displayBuffer.getScrollBottom()).toBe (9 + displayBuffer.getVerticalScrollMargin()) * 10
expect(displayBuffer.getScrollRight()).toBe (20 + displayBuffer.getHorizontalScrollMargin()) * 10
displayBuffer.scrollToScreenPosition([8, 20], center: true)
displayBuffer.scrollToScreenPosition([8, 20], center: false, reversed: true)
describe "when the 'center' option is true", ->
it "vertically scrolls to center the given position vertically", ->
displayBuffer.scrollToScreenPosition([8, 20], center: true)
expect(displayBuffer.getScrollTop()).toBe (8 * 10) + 5 - (50 / 2)
expect(displayBuffer.getScrollRight()).toBe (20 + displayBuffer.getHorizontalScrollMargin()) * 10
it "does not scroll vertically if the position is already in view", ->
displayBuffer.scrollToScreenPosition([4, 20], center: true)
expect(displayBuffer.getScrollTop()).toBe 0
describe "scroll width", ->
cursorWidth = 1
beforeEach ->
displayBuffer.setDefaultCharWidth(10)
it "recomputes the scroll width when the default character width changes", ->
expect(displayBuffer.getScrollWidth()).toBe 10 * 65 + cursorWidth
displayBuffer.setDefaultCharWidth(12)
expect(displayBuffer.getScrollWidth()).toBe 12 * 65 + cursorWidth
it "recomputes the scroll width when the max line length changes", ->
buffer.insert([6, 12], ' ')
expect(displayBuffer.getScrollWidth()).toBe 10 * 66 + cursorWidth
buffer.delete([[6, 10], [6, 12]], ' ')
expect(displayBuffer.getScrollWidth()).toBe 10 * 64 + cursorWidth
it "recomputes the scroll width when the scoped character widths change", ->
operatorWidth = 20
displayBuffer.setScopedCharWidth(['source.js', 'keyword.operator.js'], '<', operatorWidth)
expect(displayBuffer.getScrollWidth()).toBe 10 * 64 + operatorWidth + cursorWidth
it "recomputes the scroll width when the scoped character widths change in a batch", ->
operatorWidth = 20
displayBuffer.onDidChangeCharacterWidths changedSpy = jasmine.createSpy()
displayBuffer.batchCharacterMeasurement ->
displayBuffer.setScopedCharWidth(['source.js', 'keyword.operator.js'], '<', operatorWidth)
displayBuffer.setScopedCharWidth(['source.js', 'keyword.operator.js'], '?', operatorWidth)
expect(displayBuffer.getScrollWidth()).toBe 10 * 63 + operatorWidth * 2 + cursorWidth
expect(changedSpy.callCount).toBe 1
describe "::getVisibleRowRange()", ->
beforeEach ->
displayBuffer.setLineHeightInPixels(10)
displayBuffer.setHeight(100)
it "returns the first and the last visible rows", ->
displayBuffer.setScrollTop(0)
expect(displayBuffer.getVisibleRowRange()).toEqual [0, 9]
it "includes partially visible rows in the range", ->
displayBuffer.setScrollTop(5)
expect(displayBuffer.getVisibleRowRange()).toEqual [0, 10]
it "returns an empty range when lineHeight is 0", ->
displayBuffer.setLineHeightInPixels(0)
expect(displayBuffer.getVisibleRowRange()).toEqual [0, 0]
it "ends at last buffer row even if there's more space available", ->
displayBuffer.setHeight(150)
displayBuffer.setScrollTop(60)
expect(displayBuffer.getVisibleRowRange()).toEqual [0, 13]
expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {})
expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: true})
expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: false, reversed: true})
describe "::decorateMarker", ->
describe "when decorating gutters", ->

View File

@@ -0,0 +1,60 @@
DOMElementPool = require '../src/dom-element-pool'
{contains} = require 'underscore-plus'
describe "DOMElementPool", ->
domElementPool = null
beforeEach ->
domElementPool = new DOMElementPool
it "builds DOM nodes, recycling them when they are freed", ->
[div, span1, span2, span3, span4, span5, textNode] = elements = [
domElementPool.buildElement("div")
domElementPool.buildElement("span")
domElementPool.buildElement("span")
domElementPool.buildElement("span")
domElementPool.buildElement("span")
domElementPool.buildElement("span")
domElementPool.buildText("Hello world!")
]
div.appendChild(span1)
span1.appendChild(span2)
div.appendChild(span3)
span3.appendChild(span4)
span4.appendChild(textNode)
domElementPool.freeElementAndDescendants(div)
domElementPool.freeElementAndDescendants(span5)
expect(contains(elements, domElementPool.buildElement("div"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(true)
expect(contains(elements, domElementPool.buildText("another text"))).toBe(true)
expect(contains(elements, domElementPool.buildElement("div"))).toBe(false)
expect(contains(elements, domElementPool.buildElement("span"))).toBe(false)
expect(contains(elements, domElementPool.buildText("unexisting"))).toBe(false)
it "forgets free nodes after being cleared", ->
span = domElementPool.buildElement("span")
div = domElementPool.buildElement("div")
domElementPool.freeElementAndDescendants(span)
domElementPool.freeElementAndDescendants(div)
domElementPool.clear()
expect(domElementPool.buildElement("span")).not.toBe(span)
expect(domElementPool.buildElement("div")).not.toBe(div)
it "throws an error when trying to free the same node twice", ->
div = domElementPool.buildElement("div")
domElementPool.freeElementAndDescendants(div)
expect(-> domElementPool.freeElementAndDescendants(div)).toThrow()
it "throws an error when trying to free an invalid element", ->
expect(-> domElementPool.freeElementAndDescendants(null)).toThrow()
expect(-> domElementPool.freeElementAndDescendants(undefined)).toThrow()

View File

@@ -0,0 +1,73 @@
{Point} = require 'text-buffer'
module.exports =
class FakeLinesYardstick
constructor: (@model, @presenter) ->
@characterWidthsByScope = {}
prepareScreenRowsForMeasurement: ->
@presenter.getPreMeasurementState()
getScopedCharacterWidth: (scopeNames, char) ->
@getScopedCharacterWidths(scopeNames)[char]
getScopedCharacterWidths: (scopeNames) ->
scope = @characterWidthsByScope
for scopeName in scopeNames
scope[scopeName] ?= {}
scope = scope[scopeName]
scope.characterWidths ?= {}
scope.characterWidths
setScopedCharacterWidth: (scopeNames, character, width) ->
@getScopedCharacterWidths(scopeNames)[character] = width
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
screenPosition = Point.fromObject(screenPosition)
screenPosition = @model.clipScreenPosition(screenPosition) if clip
targetRow = screenPosition.row
targetColumn = screenPosition.column
baseCharacterWidth = @model.getDefaultCharWidth()
top = targetRow * @model.getLineHeightInPixels()
left = 0
column = 0
iterator = @model.tokenizedLineForScreenRow(targetRow).getTokenIterator()
while iterator.next()
characterWidths = @getScopedCharacterWidths(iterator.getScopes())
valueIndex = 0
text = iterator.getText()
while valueIndex < text.length
if iterator.isPairedCharacter()
char = text
charLength = 2
valueIndex += 2
else
char = text[valueIndex]
charLength = 1
valueIndex++
break if column is targetColumn
left += characterWidths[char] ? baseCharacterWidth unless char is '\0'
column += charLength
{top, left}
pixelRectForScreenRange: (screenRange) ->
lineHeight = @model.getLineHeightInPixels()
if screenRange.end.row > screenRange.start.row
top = @pixelPositionForScreenPosition(screenRange.start).top
left = 0
height = (screenRange.end.row - screenRange.start.row + 1) * lineHeight
width = @presenter.getScrollWidth()
else
{top, left} = @pixelPositionForScreenPosition(screenRange.start, false)
height = lineHeight
width = @pixelPositionForScreenPosition(screenRange.end, false).left - left
{top, left, width, height}

View File

@@ -0,0 +1,12 @@
{
"name": "package-with-cached-incompatible-native-module",
"version": "1.0.0",
"main": "./main.js",
"_atomModuleCache": {
"extensions": {
".node": [
"node_modules/native-module/build/Release/native.node"
]
}
}
}

View File

@@ -0,0 +1,4 @@
{
"name": "compatible-native-module",
"main": "./main.js"
}

View File

@@ -0,0 +1 @@
throw new Error("this simulates a native module's failure to load")

View File

@@ -0,0 +1,4 @@
{
"name": "native-module",
"main": "./main.js"
}

View File

@@ -0,0 +1,12 @@
{
"name": "package-with-ignored-incompatible-native-module",
"version": "1.0.0",
"main": "./main.js",
"_atomModuleCache": {
"extensions": {
".node": [
"node_modules/compatible-native-module/build/Release/native.node"
]
}
}
}

View File

@@ -1,3 +1,4 @@
{Git} = require 'atom'
{deprecate} = require 'grim'
deprecate('Fake task deprecation')
module.exports = ->

View File

@@ -11,7 +11,7 @@ describe "GitRepositoryProvider", ->
it "returns a Promise that resolves to a GitRepository", ->
waitsForPromise ->
provider = new GitRepositoryProvider atom.project
directory = new Directory path.join(__dirname, 'fixtures/git/master.git')
directory = new Directory path.join(__dirname, 'fixtures', 'git', 'master.git')
provider.repositoryForDirectory(directory).then (result) ->
expect(result).toBeInstanceOf GitRepository
expect(provider.pathToRepository[result.getPath()]).toBeTruthy()
@@ -24,11 +24,11 @@ describe "GitRepositoryProvider", ->
secondRepo = null
waitsForPromise ->
directory = new Directory path.join(__dirname, 'fixtures/git/master.git')
directory = new Directory path.join(__dirname, 'fixtures', 'git', 'master.git')
provider.repositoryForDirectory(directory).then (result) -> firstRepo = result
waitsForPromise ->
directory = new Directory path.join(__dirname, 'fixtures/git/master.git/objects')
directory = new Directory path.join(__dirname, 'fixtures', 'git', 'master.git', 'objects')
provider.repositoryForDirectory(directory).then (result) -> secondRepo = result
runs ->
@@ -56,6 +56,21 @@ describe "GitRepositoryProvider", ->
provider.repositoryForDirectory(directory).then (result) ->
expect(result).toBe null
describe "when specified a Directory with a valid gitfile-linked repository", ->
it "returns a Promise that resolves to a GitRepository", ->
waitsForPromise ->
provider = new GitRepositoryProvider atom.project
gitDirPath = path.join(__dirname, 'fixtures', 'git', 'master.git')
workDirPath = temp.mkdirSync('git-workdir')
fs.writeFileSync(path.join(workDirPath, '.git'), 'gitdir: ' + gitDirPath+'\n')
directory = new Directory workDirPath
provider.repositoryForDirectory(directory).then (result) ->
expect(result).toBeInstanceOf GitRepository
expect(provider.pathToRepository[result.getPath()]).toBeTruthy()
expect(result.statusTask).toBeTruthy()
expect(result.getType()).toBe 'git'
describe "when specified a Directory without existsSync()", ->
directory = null
provider = null

View File

@@ -3,6 +3,7 @@ GitRepository = require '../src/git-repository'
fs = require 'fs-plus'
path = require 'path'
Task = require '../src/task'
Project = require '../src/project'
copyRepository = ->
workingDirPath = temp.mkdirSync('atom-working-dir')
@@ -276,7 +277,7 @@ describe "GitRepository", ->
atom.workspace.open('file.txt')
runs ->
project2 = atom.project.testSerialization()
project2 = Project.deserialize(atom.project.serialize())
buffer = project2.getBuffers()[0]
waitsFor ->

View File

@@ -1,5 +1,6 @@
Gutter = require '../src/gutter'
GutterContainerComponent = require '../src/gutter-container-component'
DOMElementPool = require '../src/dom-element-pool'
describe "GutterContainerComponent", ->
[gutterContainerComponent] = []
@@ -22,9 +23,10 @@ describe "GutterContainerComponent", ->
mockTestState
beforeEach ->
domElementPool = new DOMElementPool
mockEditor = {}
mockMouseDown = ->
gutterContainerComponent = new GutterContainerComponent({editor: mockEditor, onMouseDown: mockMouseDown})
gutterContainerComponent = new GutterContainerComponent({editor: mockEditor, onMouseDown: mockMouseDown, domElementPool})
it "creates a DOM node with no child gutter nodes when it is initialized", ->
expect(gutterContainerComponent.getDomNode() instanceof HTMLElement).toBe true

View File

@@ -43,6 +43,5 @@ for arg in "$@"; do
done
echo "Launching Atom" >&2
echo ${atom_path} ${atom_args[@]} ${atom_switches[@]} >&2
exec ${atom_path} ${atom_args[@]} ${atom_switches[@]}
echo "${atom_path}" ${atom_args[@]} ${atom_switches[@]} >&2
exec "${atom_path}" ${atom_args[@]} ${atom_switches[@]}

View File

@@ -9,7 +9,7 @@ webdriverio = require "../../../build/node_modules/webdriverio"
AtomPath = remote.process.argv[0]
AtomLauncherPath = path.join(__dirname, "..", "helpers", "atom-launcher.sh")
ChromedriverPath = path.resolve(__dirname, '..', '..', '..', 'atom-shell', 'chromedriver', 'chromedriver')
ChromedriverPath = path.resolve(__dirname, '..', '..', '..', 'electron', 'chromedriver', 'chromedriver')
SocketPath = path.join(temp.mkdirSync("socket-dir"), "atom-#{process.env.USER}.sock")
ChromedriverPort = 9515
ChromedriverURLBase = "/wd/hub"

View File

@@ -6,20 +6,21 @@ return unless process.env.ATOM_INTEGRATION_TESTS_ENABLED
# run them on Travis.
return if process.env.TRAVIS
fs = require "fs"
fs = require "fs-plus"
path = require "path"
temp = require("temp").track()
runAtom = require "./helpers/start-atom"
CSON = require "season"
describe "Starting Atom", ->
[tempDirPath, otherTempDirPath, atomHome] = []
atomHome = temp.mkdirSync('atom-home')
[tempDirPath, otherTempDirPath] = []
beforeEach ->
jasmine.useRealClock()
atomHome = temp.mkdirSync('atom-home')
fs.writeFileSync(path.join(atomHome, 'config.cson'), fs.readFileSync(path.join(__dirname, 'fixtures', 'atom-home', 'config.cson')))
fs.removeSync(path.join(atomHome, 'storage'))
tempDirPath = temp.mkdirSync("empty-dir")
otherTempDirPath = temp.mkdirSync("another-temp-dir")

View File

@@ -1,8 +1,6 @@
fs = require 'fs'
module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) ->
{$, $$} = require '../src/space-pen-extensions'
window[key] = value for key, value of require '../vendor/jasmine'
{TerminalReporter} = require 'jasmine-tagged'
@@ -25,7 +23,7 @@ module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) ->
log(str)
onComplete: (runner) ->
fs.closeSync(logStream) if logStream?
if process.env.JANKY_SHA1
if process.env.JANKY_SHA1 or process.env.CI
grim = require 'grim'
if grim.getDeprecationsLength() > 0
@@ -47,7 +45,9 @@ module.exports.runSpecSuite = (specSuite, logFile, logErrors=true) ->
jasmineEnv.addReporter(timeReporter)
jasmineEnv.setIncludedTags([process.platform])
$('body').append $$ -> @div id: 'jasmine-content'
jasmineContent = document.createElement('div')
jasmineContent.setAttribute('id', 'jasmine-content')
document.body.appendChild(jasmineContent)
jasmineEnv.execute()

View File

@@ -0,0 +1,184 @@
LinesYardstick = require "../src/lines-yardstick"
{toArray} = require 'underscore-plus'
describe "LinesYardstick", ->
[editor, mockPresenter, mockLineNodesProvider, createdLineNodes, linesYardstick] = []
beforeEach ->
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
waitsForPromise ->
atom.project.open('sample.js').then (o) -> editor = o
runs ->
createdLineNodes = []
availableScreenRows = {}
screenRowsToMeasure = []
buildLineNode = (screenRow) ->
tokenizedLine = editor.tokenizedLineForScreenRow(screenRow)
iterator = tokenizedLine.getTokenIterator()
lineNode = document.createElement("div")
lineNode.style.whiteSpace = "pre"
while iterator.next()
span = document.createElement("span")
span.className = iterator.getScopes().join(' ').replace(/\.+/g, ' ')
span.textContent = iterator.getText()
lineNode.appendChild(span)
jasmine.attachToDOM(lineNode)
createdLineNodes.push(lineNode)
lineNode
mockPresenter =
setScreenRowsToMeasure: (screenRows) -> screenRowsToMeasure = screenRows
clearScreenRowsToMeasure: -> setScreenRowsToMeasure = []
getPreMeasurementState: ->
state = {}
for screenRow in screenRowsToMeasure
tokenizedLine = editor.tokenizedLineForScreenRow(screenRow)
state[tokenizedLine.id] = screenRow
state
mockLineNodesProvider =
updateSync: (state) -> availableScreenRows = state
lineNodeForLineIdAndScreenRow: (lineId, screenRow) ->
return if availableScreenRows[lineId] isnt screenRow
buildLineNode(screenRow)
textNodesForLineIdAndScreenRow: (lineId, screenRow) ->
lineNode = @lineNodeForLineIdAndScreenRow(lineId, screenRow)
textNodes = []
for span in lineNode.children
for textNode in span.childNodes
textNodes.push(textNode)
textNodes
editor.setLineHeightInPixels(14)
linesYardstick = new LinesYardstick(editor, mockPresenter, mockLineNodesProvider)
afterEach ->
lineNode.remove() for lineNode in createdLineNodes
atom.themes.removeStylesheet('test')
describe "::pixelPositionForScreenPosition(screenPosition)", ->
it "converts screen positions to pixel positions", ->
atom.styles.addStyleSheet """
* {
font-size: 12px;
font-family: monospace;
}
.function {
font-size: 16px
}
"""
expect(linesYardstick.pixelPositionForScreenPosition([0, 0])).toEqual({left: 0, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition([0, 1])).toEqual({left: 7, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition([0, 5])).toEqual({left: 37.8046875, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition([1, 6])).toEqual({left: 43.20703125, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition([1, 9])).toEqual({left: 72.20703125, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition([2, Infinity])).toEqual({left: 288.046875, top: 28})
it "reuses already computed pixel positions unless it is invalidated", ->
atom.styles.addStyleSheet """
* {
font-size: 16px;
font-family: monospace;
}
"""
expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 19.203125, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 57.609375, top: 28})
expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 95.609375, top: 70})
atom.styles.addStyleSheet """
* {
font-size: 20px;
}
"""
expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 19.203125, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 57.609375, top: 28})
expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 95.609375, top: 70})
linesYardstick.invalidateCache()
expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 24.00390625, top: 14})
expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 72.01171875, top: 28})
expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 120.01171875, top: 70})
it "correctly handles RTL characters", ->
atom.styles.addStyleSheet """
* {
font-size: 14px;
font-family: monospace;
}
"""
editor.setText("السلام عليكم")
expect(linesYardstick.pixelPositionForScreenPosition([0, 0]).left).toBe 0
expect(linesYardstick.pixelPositionForScreenPosition([0, 1]).left).toBe 8
expect(linesYardstick.pixelPositionForScreenPosition([0, 2]).left).toBe 16
expect(linesYardstick.pixelPositionForScreenPosition([0, 5]).left).toBe 33
expect(linesYardstick.pixelPositionForScreenPosition([0, 7]).left).toBe 50
expect(linesYardstick.pixelPositionForScreenPosition([0, 9]).left).toBe 67
expect(linesYardstick.pixelPositionForScreenPosition([0, 11]).left).toBe 84
it "doesn't measure invisible lines if it is explicitly told so", ->
atom.styles.addStyleSheet """
* {
font-size: 12px;
font-family: monospace;
}
"""
expect(linesYardstick.pixelPositionForScreenPosition([0, 0], true, true)).toEqual({left: 0, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition([0, 1], true, true)).toEqual({left: 0, top: 0})
expect(linesYardstick.pixelPositionForScreenPosition([0, 5], true, true)).toEqual({left: 0, top: 0})
describe "::screenPositionForPixelPosition(pixelPosition)", ->
it "converts pixel positions to screen positions", ->
atom.styles.addStyleSheet """
* {
font-size: 12px;
font-family: monospace;
}
.function {
font-size: 16px
}
"""
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 12.5})).toEqual([0, 2])
expect(linesYardstick.screenPositionForPixelPosition({top: 14, left: 18.8})).toEqual([1, 3])
expect(linesYardstick.screenPositionForPixelPosition({top: 28, left: 100})).toEqual([2, 14])
expect(linesYardstick.screenPositionForPixelPosition({top: 32, left: 24.3})).toEqual([2, 3])
expect(linesYardstick.screenPositionForPixelPosition({top: 46, left: 66.5})).toEqual([3, 9])
expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 99.9})).toEqual([5, 14])
expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 224.4365234375})).toEqual([5, 29])
expect(linesYardstick.screenPositionForPixelPosition({top: 80, left: 225})).toEqual([5, 30])
it "clips pixel positions above buffer start", ->
expect(linesYardstick.screenPositionForPixelPosition(top: -Infinity, left: -Infinity)).toEqual [0, 0]
expect(linesYardstick.screenPositionForPixelPosition(top: -Infinity, left: Infinity)).toEqual [0, 0]
expect(linesYardstick.screenPositionForPixelPosition(top: -1, left: Infinity)).toEqual [0, 0]
expect(linesYardstick.screenPositionForPixelPosition(top: 0, left: Infinity)).toEqual [0, 29]
it "clips pixel positions below buffer end", ->
expect(linesYardstick.screenPositionForPixelPosition(top: Infinity, left: -Infinity)).toEqual [12, 2]
expect(linesYardstick.screenPositionForPixelPosition(top: Infinity, left: Infinity)).toEqual [12, 2]
expect(linesYardstick.screenPositionForPixelPosition(top: (editor.getLastScreenRow() + 1) * 14, left: 0)).toEqual [12, 2]
expect(linesYardstick.screenPositionForPixelPosition(top: editor.getLastScreenRow() * 14, left: 0)).toEqual [12, 0]
it "doesn't measure invisible lines if it is explicitly told so", ->
atom.styles.addStyleSheet """
* {
font-size: 12px;
font-family: monospace;
}
"""
expect(linesYardstick.screenPositionForPixelPosition({top: 0, left: 13}, true)).toEqual([0, 0])
expect(linesYardstick.screenPositionForPixelPosition({top: 14, left: 20}, true)).toEqual([1, 0])
expect(linesYardstick.screenPositionForPixelPosition({top: 28, left: 100}, true)).toEqual([2, 0])

View File

@@ -52,7 +52,7 @@ describe "MenuManager", ->
it "sends the current menu template and associated key bindings to the browser process", ->
spyOn(menu, 'sendToBrowserProcess')
menu.add [{label: "A", submenu: [{label: "B", command: "b"}]}]
atom.keymap.add 'test', 'atom-workspace': 'ctrl-b': 'b'
atom.keymaps.add 'test', 'atom-workspace': 'ctrl-b': 'b'
menu.update()
waits 1
@@ -64,8 +64,8 @@ describe "MenuManager", ->
# more dynamic interaction between the currently focused element and the menu
spyOn(menu, 'sendToBrowserProcess')
menu.add [{label: "A", submenu: [{label: "B", command: "b"}]}]
atom.keymap.add 'test', 'atom-workspace': 'ctrl-b': 'b'
atom.keymap.add 'test', 'atom-text-editor': 'ctrl-b': 'unset!'
atom.keymaps.add 'test', 'atom-workspace': 'ctrl-b': 'b'
atom.keymaps.add 'test', 'atom-text-editor': 'ctrl-b': 'unset!'
waits 1

View File

@@ -1,11 +1,15 @@
path = require 'path'
{$, $$} = require '../src/space-pen-extensions'
Package = require '../src/package'
{Disposable} = require 'atom'
describe "PackageManager", ->
workspaceElement = null
createTestElement = (className) ->
element = document.createElement('div')
element.className = className
element
beforeEach ->
workspaceElement = atom.views.getView(atom.workspace)
@@ -58,17 +62,7 @@ describe "PackageManager", ->
expect(console.warn.argsForCall[0][0]).toContain("Could not resolve")
describe "when the package is deprecated", ->
grim = require 'grim'
includeDeprecatedAPIs = null
beforeEach ->
{includeDeprecatedAPIs} = grim
afterEach ->
grim.includeDeprecatedAPIs = includeDeprecatedAPIs
it "returns null", ->
grim.includeDeprecatedAPIs = false
expect(atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'packages', 'wordcount'))).toBeNull()
expect(atom.packages.isDeprecatedPackage('wordcount', '2.1.9')).toBe true
expect(atom.packages.isDeprecatedPackage('wordcount', '2.2.0')).toBe true
@@ -170,24 +164,6 @@ describe "PackageManager", ->
expect(atom.config.set('package-with-config-schema.numbers.one', '10')).toBe true
expect(atom.config.get('package-with-config-schema.numbers.one')).toBe 10
describe "when a package has configDefaults", ->
beforeEach ->
jasmine.snapshotDeprecations()
afterEach ->
jasmine.restoreDeprecationsSnapshot()
it "still assigns configDefaults from the module though deprecated", ->
expect(atom.config.get('package-with-config-defaults.numbers.one')).toBeUndefined()
waitsForPromise ->
atom.packages.activatePackage('package-with-config-defaults')
runs ->
expect(atom.config.get('package-with-config-defaults.numbers.one')).toBe 1
expect(atom.config.get('package-with-config-defaults.numbers.two')).toBe 2
describe "when the package metadata includes `activationCommands`", ->
[mainModule, promise, workspaceCommandListener, registration] = []
@@ -209,33 +185,31 @@ describe "PackageManager", ->
mainModule = null
it "defers requiring/activating the main module until an activation event bubbles to the root view", ->
expect(promise.isFulfilled()).not.toBeTruthy()
expect(Package.prototype.requireMainModule.callCount).toBe 0
workspaceElement.dispatchEvent(new CustomEvent('activation-command', bubbles: true))
waitsForPromise ->
promise
runs ->
expect(Package.prototype.requireMainModule.callCount).toBe 1
it "triggers the activation event on all handlers registered during activation", ->
waitsForPromise ->
atom.workspace.open()
runs ->
editorView = atom.views.getView(atom.workspace.getActiveTextEditor()).__spacePenView
legacyCommandListener = jasmine.createSpy("legacyCommandListener")
editorView.command 'activation-command', legacyCommandListener
editorElement = atom.views.getView(atom.workspace.getActiveTextEditor())
editorCommandListener = jasmine.createSpy("editorCommandListener")
atom.commands.add 'atom-text-editor', 'activation-command', editorCommandListener
atom.commands.dispatch(editorView[0], 'activation-command')
atom.commands.dispatch(editorElement, 'activation-command')
expect(mainModule.activate.callCount).toBe 1
expect(mainModule.legacyActivationCommandCallCount).toBe 1
expect(mainModule.activationCommandCallCount).toBe 1
expect(legacyCommandListener.callCount).toBe 1
expect(editorCommandListener.callCount).toBe 1
expect(workspaceCommandListener.callCount).toBe 1
atom.commands.dispatch(editorView[0], 'activation-command')
expect(mainModule.legacyActivationCommandCallCount).toBe 2
atom.commands.dispatch(editorElement, 'activation-command')
expect(mainModule.activationCommandCallCount).toBe 2
expect(legacyCommandListener.callCount).toBe 2
expect(editorCommandListener.callCount).toBe 2
expect(workspaceCommandListener.callCount).toBe 2
expect(mainModule.activate.callCount).toBe 1
@@ -301,8 +275,8 @@ describe "PackageManager", ->
promise = atom.packages.activatePackage('package-with-activation-hooks')
it "defers requiring/activating the main module until an triggering of an activation hook occurs", ->
expect(promise.isFulfilled()).not.toBeTruthy()
expect(Package.prototype.requireMainModule.callCount).toBe 0
atom.packages.triggerActivationHook('language-fictitious:grammar-used')
waitsForPromise ->
@@ -399,36 +373,36 @@ describe "PackageManager", ->
describe "keymap loading", ->
describe "when the metadata does not contain a 'keymaps' manifest", ->
it "loads all the .cson/.json files in the keymaps directory", ->
element1 = $$ -> @div class: 'test-1'
element2 = $$ -> @div class: 'test-2'
element3 = $$ -> @div class: 'test-3'
element1 = createTestElement('test-1')
element2 = createTestElement('test-2')
element3 = createTestElement('test-3')
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1[0])).toHaveLength 0
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element2[0])).toHaveLength 0
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element3[0])).toHaveLength 0
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1)).toHaveLength 0
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element2)).toHaveLength 0
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element3)).toHaveLength 0
waitsForPromise ->
atom.packages.activatePackage("package-with-keymaps")
runs ->
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1[0])[0].command).toBe "test-1"
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element2[0])[0].command).toBe "test-2"
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element3[0])).toHaveLength 0
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1)[0].command).toBe "test-1"
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element2)[0].command).toBe "test-2"
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element3)).toHaveLength 0
describe "when the metadata contains a 'keymaps' manifest", ->
it "loads only the keymaps specified by the manifest, in the specified order", ->
element1 = $$ -> @div class: 'test-1'
element3 = $$ -> @div class: 'test-3'
element1 = createTestElement('test-1')
element3 = createTestElement('test-3')
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1[0])).toHaveLength 0
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1)).toHaveLength 0
waitsForPromise ->
atom.packages.activatePackage("package-with-keymaps-manifest")
runs ->
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1[0])[0].command).toBe 'keymap-1'
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-n', target: element1[0])[0].command).toBe 'keymap-2'
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-y', target: element3[0])).toHaveLength 0
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1)[0].command).toBe 'keymap-1'
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-n', target: element1)[0].command).toBe 'keymap-2'
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-y', target: element3)).toHaveLength 0
describe "when the keymap file is empty", ->
it "does not throw an error on activation", ->
@@ -440,9 +414,9 @@ describe "PackageManager", ->
describe "when the package's keymaps have been disabled", ->
it "does not add the keymaps", ->
element1 = $$ -> @div class: 'test-1'
element1 = createTestElement('test-1')
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1[0])).toHaveLength 0
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1)).toHaveLength 0
atom.config.set("core.packagesWithKeymapsDisabled", ["package-with-keymaps-manifest"])
@@ -450,11 +424,11 @@ describe "PackageManager", ->
atom.packages.activatePackage("package-with-keymaps-manifest")
runs ->
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1[0])).toHaveLength 0
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1)).toHaveLength 0
describe "when the package's keymaps are disabled and re-enabled after it is activated", ->
it "removes and re-adds the keymaps", ->
element1 = $$ -> @div class: 'test-1'
element1 = createTestElement('test-1')
atom.packages.observePackagesWithKeymapsDisabled()
waitsForPromise ->
@@ -462,10 +436,10 @@ describe "PackageManager", ->
runs ->
atom.config.set("core.packagesWithKeymapsDisabled", ['package-with-keymaps-manifest'])
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1[0])).toHaveLength 0
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1)).toHaveLength 0
atom.config.set("core.packagesWithKeymapsDisabled", [])
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1[0])[0].command).toBe 'keymap-1'
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: element1)[0].command).toBe 'keymap-1'
describe "menu loading", ->
beforeEach ->
@@ -474,7 +448,7 @@ describe "PackageManager", ->
describe "when the metadata does not contain a 'menus' manifest", ->
it "loads all the .cson/.json files in the menus directory", ->
element = ($$ -> @div class: 'test-1')[0]
element = createTestElement('test-1')
expect(atom.contextMenu.templateForElement(element)).toEqual []
@@ -491,7 +465,7 @@ describe "PackageManager", ->
describe "when the metadata contains a 'menus' manifest", ->
it "loads only the menus specified by the manifest, in the specified order", ->
element = ($$ -> @div class: 'test-1')[0]
element = createTestElement('test-1')
expect(atom.contextMenu.templateForElement(element)).toEqual []
@@ -535,7 +509,8 @@ describe "PackageManager", ->
expect(atom.themes.stylesheetElementForId(one)).not.toBeNull()
expect(atom.themes.stylesheetElementForId(two)).not.toBeNull()
expect(atom.themes.stylesheetElementForId(three)).toBeNull()
expect($('#jasmine-content').css('font-size')).toBe '1px'
expect(getComputedStyle(document.querySelector('#jasmine-content')).fontSize).toBe '1px'
describe "when the metadata does not contain a 'styleSheets' manifest", ->
it "loads all style sheets from the styles directory", ->
@@ -562,7 +537,7 @@ describe "PackageManager", ->
expect(atom.themes.stylesheetElementForId(two)).not.toBeNull()
expect(atom.themes.stylesheetElementForId(three)).not.toBeNull()
expect(atom.themes.stylesheetElementForId(four)).not.toBeNull()
expect($('#jasmine-content').css('font-size')).toBe '3px'
expect(getComputedStyle(document.querySelector('#jasmine-content')).fontSize).toBe '3px'
it "assigns the stylesheet's context based on the filename", ->
waitsForPromise ->
@@ -747,8 +722,8 @@ describe "PackageManager", ->
runs ->
atom.packages.deactivatePackage('package-with-keymaps')
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: ($$ -> @div class: 'test-1')[0])).toHaveLength 0
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: ($$ -> @div class: 'test-2')[0])).toHaveLength 0
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: createTestElement('test-1'))).toHaveLength 0
expect(atom.keymaps.findKeyBindings(keystrokes: 'ctrl-z', target: createTestElement('test-2'))).toHaveLength 0
it "removes the package's stylesheets", ->
waitsForPromise ->
@@ -890,7 +865,7 @@ describe "PackageManager", ->
# enabling of theme
pack = atom.packages.enablePackage(packageName)
waitsFor ->
waitsFor 'theme to enable', 500, ->
pack in atom.packages.getActivePackages()
runs ->
@@ -903,7 +878,7 @@ describe "PackageManager", ->
pack = atom.packages.disablePackage(packageName)
waitsFor ->
waitsFor 'did-change-active-themes event to fire', 500, ->
didChangeActiveThemesHandler.callCount is 1
runs ->
@@ -911,66 +886,3 @@ describe "PackageManager", ->
expect(atom.config.get('core.themes')).not.toContain packageName
expect(atom.config.get('core.themes')).not.toContain packageName
expect(atom.config.get('core.disabledPackages')).not.toContain packageName
describe "deleting non-bundled autocomplete packages", ->
[autocompleteCSSPath, autocompletePlusPath] = []
fs = require 'fs-plus'
path = require 'path'
beforeEach ->
fixturePath = path.resolve(__dirname, './fixtures/packages')
autocompleteCSSPath = path.join(fixturePath, 'autocomplete-css')
autocompletePlusPath = path.join(fixturePath, 'autocomplete-plus')
try
fs.mkdirSync(autocompleteCSSPath)
fs.writeFileSync(path.join(autocompleteCSSPath, 'package.json'), '{}')
fs.symlinkSync(path.join(fixturePath, 'package-with-main'), autocompletePlusPath, 'dir')
expect(fs.isDirectorySync(autocompleteCSSPath)).toBe true
expect(fs.isSymbolicLinkSync(autocompletePlusPath)).toBe true
jasmine.unspy(atom.packages, 'uninstallAutocompletePlus')
afterEach ->
try
fs.unlink autocompletePlusPath, ->
it "removes the packages", ->
atom.packages.loadPackages()
waitsFor ->
not fs.isDirectorySync(autocompleteCSSPath)
runs ->
expect(fs.isDirectorySync(autocompleteCSSPath)).toBe false
expect(fs.isSymbolicLinkSync(autocompletePlusPath)).toBe true
describe "when the deprecated sublime-tabs package is installed", ->
grim = require 'grim'
includeDeprecatedAPIs = null
beforeEach ->
{includeDeprecatedAPIs} = grim
grim.includeDeprecatedAPIs = false
afterEach ->
grim.includeDeprecatedAPIs = includeDeprecatedAPIs
it "enables the tree-view and tabs package", ->
atom.config.pushAtKeyPath('core.disabledPackages', 'tree-view')
atom.config.pushAtKeyPath('core.disabledPackages', 'tabs')
spyOn(atom.packages, 'getAvailablePackagePaths').andReturn [
path.join(__dirname, 'fixtures', 'packages', 'sublime-tabs')
path.resolve(__dirname, '..', 'node_modules', 'tree-view')
path.resolve(__dirname, '..', 'node_modules', 'tabs')
]
atom.packages.loadPackages()
waitsFor ->
not atom.packages.isPackageDisabled('tree-view') and not atom.packages.isPackageDisabled('tabs')
runs ->
expect(atom.packages.isPackageLoaded('tree-view')).toBe true
expect(atom.packages.isPackageLoaded('tabs')).toBe true

View File

@@ -1,4 +1,3 @@
{$} = require '../src/space-pen-extensions'
path = require 'path'
Package = require '../src/package'
ThemePackage = require '../src/theme-package'
@@ -7,6 +6,10 @@ describe "Package", ->
describe "when the package contains incompatible native modules", ->
beforeEach ->
spyOn(atom, 'inDevMode').andReturn(false)
items = {}
spyOn(global.localStorage, 'setItem').andCallFake (key, item) -> items[key] = item; undefined
spyOn(global.localStorage, 'getItem').andCallFake (key) -> items[key] ? null
spyOn(global.localStorage, 'removeItem').andCallFake (key) -> delete items[key]; undefined
it "does not activate it", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-incompatible-native-module')
@@ -15,16 +18,18 @@ describe "Package", ->
expect(pack.incompatibleModules[0].name).toBe 'native-module'
expect(pack.incompatibleModules[0].path).toBe path.join(packagePath, 'node_modules', 'native-module')
it "utilizes _atomModuleCache if present to determine the package's native dependencies", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-ignored-incompatible-native-module')
pack = new Package(packagePath)
expect(pack.getNativeModuleDependencyPaths().length).toBe(1) # doesn't see the incompatible module
expect(pack.isCompatible()).toBe true
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-cached-incompatible-native-module')
pack = new Package(packagePath)
expect(pack.isCompatible()).toBe false
it "caches the incompatible native modules in local storage", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-incompatible-native-module')
cacheKey = null
cacheItem = null
spyOn(global.localStorage, 'setItem').andCallFake (key, item) ->
cacheKey = key
cacheItem = item
spyOn(global.localStorage, 'getItem').andCallFake (key) ->
return cacheItem if cacheKey is key
expect(new Package(packagePath).isCompatible()).toBe false
expect(global.localStorage.getItem.callCount).toBe 1
@@ -34,55 +39,121 @@ describe "Package", ->
expect(global.localStorage.getItem.callCount).toBe 2
expect(global.localStorage.setItem.callCount).toBe 1
describe "::rebuild()", ->
beforeEach ->
spyOn(atom, 'inDevMode').andReturn(false)
items = {}
spyOn(global.localStorage, 'setItem').andCallFake (key, item) -> items[key] = item; undefined
spyOn(global.localStorage, 'getItem').andCallFake (key) -> items[key] ? null
spyOn(global.localStorage, 'removeItem').andCallFake (key) -> delete items[key]; undefined
it "returns a promise resolving to the results of `apm rebuild`", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-index')
pack = new Package(packagePath)
rebuildCallbacks = []
spyOn(pack, 'runRebuildProcess').andCallFake ((callback) -> rebuildCallbacks.push(callback))
promise = pack.rebuild()
rebuildCallbacks[0]({code: 0, stdout: 'stdout output', stderr: 'stderr output'})
waitsFor (done) ->
promise.then (result) ->
expect(result).toEqual {code: 0, stdout: 'stdout output', stderr: 'stderr output'}
done()
it "persists build failures in local storage", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-index')
pack = new Package(packagePath)
expect(pack.isCompatible()).toBe true
expect(pack.getBuildFailureOutput()).toBeNull()
rebuildCallbacks = []
spyOn(pack, 'runRebuildProcess').andCallFake ((callback) -> rebuildCallbacks.push(callback))
pack.rebuild()
rebuildCallbacks[0]({code: 13, stderr: 'It is broken'})
expect(pack.getBuildFailureOutput()).toBe 'It is broken'
expect(pack.getIncompatibleNativeModules()).toEqual []
expect(pack.isCompatible()).toBe false
# A different package instance has the same failure output (simulates reload)
pack2 = new Package(packagePath)
expect(pack2.getBuildFailureOutput()).toBe 'It is broken'
expect(pack2.isCompatible()).toBe false
# Clears the build failure after a successful build
pack.rebuild()
rebuildCallbacks[1]({code: 0, stdout: 'It worked'})
expect(pack.getBuildFailureOutput()).toBeNull()
expect(pack2.getBuildFailureOutput()).toBeNull()
it "sets cached incompatible modules to an empty array when the rebuild completes (there may be a build error, but rebuilding *deletes* native modules)", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-incompatible-native-module')
pack = new Package(packagePath)
expect(pack.getIncompatibleNativeModules().length).toBeGreaterThan(0)
rebuildCallbacks = []
spyOn(pack, 'runRebuildProcess').andCallFake ((callback) -> rebuildCallbacks.push(callback))
pack.rebuild()
expect(pack.getIncompatibleNativeModules().length).toBeGreaterThan(0)
rebuildCallbacks[0]({code: 0, stdout: 'It worked'})
expect(pack.getIncompatibleNativeModules().length).toBe(0)
describe "theme", ->
theme = null
[editorElement, theme] = []
beforeEach ->
$("#jasmine-content").append $("<atom-text-editor></atom-text-editor>")
editorElement = document.createElement('atom-text-editor')
jasmine.attachToDOM(editorElement)
afterEach ->
theme.deactivate() if theme?
describe "when the theme contains a single style file", ->
it "loads and applies css", ->
expect($("atom-text-editor").css("padding-bottom")).not.toBe "1234px"
expect(getComputedStyle(editorElement).paddingBottom).not.toBe "1234px"
themePath = atom.project.getDirectories()[0]?.resolve('packages/theme-with-index-css')
theme = new ThemePackage(themePath)
theme.activate()
expect($("atom-text-editor").css("padding-top")).toBe "1234px"
expect(getComputedStyle(editorElement).paddingTop).toBe "1234px"
it "parses, loads and applies less", ->
expect($("atom-text-editor").css("padding-bottom")).not.toBe "1234px"
expect(getComputedStyle(editorElement).paddingBottom).not.toBe "1234px"
themePath = atom.project.getDirectories()[0]?.resolve('packages/theme-with-index-less')
theme = new ThemePackage(themePath)
theme.activate()
expect($("atom-text-editor").css("padding-top")).toBe "4321px"
expect(getComputedStyle(editorElement).paddingTop).toBe "4321px"
describe "when the theme contains a package.json file", ->
it "loads and applies stylesheets from package.json in the correct order", ->
expect($("atom-text-editor").css("padding-top")).not.toBe("101px")
expect($("atom-text-editor").css("padding-right")).not.toBe("102px")
expect($("atom-text-editor").css("padding-bottom")).not.toBe("103px")
expect(getComputedStyle(editorElement).paddingTop).not.toBe("101px")
expect(getComputedStyle(editorElement).paddingRight).not.toBe("102px")
expect(getComputedStyle(editorElement).paddingBottom).not.toBe("103px")
themePath = atom.project.getDirectories()[0]?.resolve('packages/theme-with-package-file')
theme = new ThemePackage(themePath)
theme.activate()
expect($("atom-text-editor").css("padding-top")).toBe("101px")
expect($("atom-text-editor").css("padding-right")).toBe("102px")
expect($("atom-text-editor").css("padding-bottom")).toBe("103px")
expect(getComputedStyle(editorElement).paddingTop).toBe("101px")
expect(getComputedStyle(editorElement).paddingRight).toBe("102px")
expect(getComputedStyle(editorElement).paddingBottom).toBe("103px")
describe "when the theme does not contain a package.json file and is a directory", ->
it "loads all stylesheet files in the directory", ->
expect($("atom-text-editor").css("padding-top")).not.toBe "10px"
expect($("atom-text-editor").css("padding-right")).not.toBe "20px"
expect($("atom-text-editor").css("padding-bottom")).not.toBe "30px"
expect(getComputedStyle(editorElement).paddingTop).not.toBe "10px"
expect(getComputedStyle(editorElement).paddingRight).not.toBe "20px"
expect(getComputedStyle(editorElement).paddingBottom).not.toBe "30px"
themePath = atom.project.getDirectories()[0]?.resolve('packages/theme-without-package-file')
theme = new ThemePackage(themePath)
theme.activate()
expect($("atom-text-editor").css("padding-top")).toBe "10px"
expect($("atom-text-editor").css("padding-right")).toBe "20px"
expect($("atom-text-editor").css("padding-bottom")).toBe "30px"
expect(getComputedStyle(editorElement).paddingTop).toBe "10px"
expect(getComputedStyle(editorElement).paddingRight).toBe "20px"
expect(getComputedStyle(editorElement).paddingBottom).toBe "30px"
describe "reloading a theme", ->
beforeEach ->

View File

@@ -4,16 +4,13 @@ PaneAxis = require '../src/pane-axis'
describe "PaneContainerElement", ->
describe "when panes are added or removed", ->
[paneAxisElement, paneAxis] = []
it "inserts or removes resize elements", ->
childTagNames = ->
child.nodeName.toLowerCase() for child in paneAxisElement.children
beforeEach ->
paneAxis = new PaneAxis
paneAxisElement = new PaneAxisElement().initialize(paneAxis)
childTagNames = ->
child.nodeName.toLowerCase() for child in paneAxisElement.children
it "inserts or removes resize elements", ->
expect(childTagNames()).toEqual []
paneAxis.addChild(new PaneAxis)
@@ -44,6 +41,45 @@ describe "PaneContainerElement", ->
'atom-pane-axis'
]
it "transfers focus to the next pane if a focused pane is removed", ->
container = new PaneContainer
containerElement = atom.views.getView(container)
leftPane = container.getActivePane()
leftPaneElement = atom.views.getView(leftPane)
rightPane = leftPane.splitRight()
rightPaneElement = atom.views.getView(rightPane)
jasmine.attachToDOM(containerElement)
rightPaneElement.focus()
expect(document.activeElement).toBe rightPaneElement
rightPane.destroy()
expect(document.activeElement).toBe leftPaneElement
describe "when a pane is split", ->
it "builds appropriately-oriented atom-pane-axis elements", ->
container = new PaneContainer
containerElement = atom.views.getView(container)
pane1 = container.getRoot()
pane2 = pane1.splitRight()
pane3 = pane2.splitDown()
horizontalPanes = containerElement.querySelectorAll('atom-pane-container > atom-pane-axis.horizontal > atom-pane')
expect(horizontalPanes.length).toBe 1
expect(horizontalPanes[0]).toBe atom.views.getView(pane1)
verticalPanes = containerElement.querySelectorAll('atom-pane-container > atom-pane-axis.horizontal > atom-pane-axis.vertical > atom-pane')
expect(verticalPanes.length).toBe 2
expect(verticalPanes[0]).toBe atom.views.getView(pane2)
expect(verticalPanes[1]).toBe atom.views.getView(pane3)
pane1.destroy()
verticalPanes = containerElement.querySelectorAll('atom-pane-container > atom-pane-axis.vertical > atom-pane')
expect(verticalPanes.length).toBe 2
expect(verticalPanes[0]).toBe atom.views.getView(pane2)
expect(verticalPanes[1]).toBe atom.views.getView(pane3)
describe "when the resize element is dragged ", ->
[container, containerElement] = []
@@ -194,3 +230,96 @@ describe "PaneContainerElement", ->
atom.commands.dispatch(atom.views.getView(rightPane), 'pane:decrease-size')
expect(leftPane.getFlexScale()).toBe 1/1.1
expect(rightPane.getFlexScale()).toBe 1/1.1
describe "changing focus directionally between panes", ->
[containerElement, pane1, pane2, pane3, pane4, pane5, pane6, pane7, pane8, pane9] = []
beforeEach ->
# Set up a grid of 9 panes, in the following arrangement, where the
# numbers correspond to the variable names below.
#
# -------
# |1|2|3|
# -------
# |4|5|6|
# -------
# |7|8|9|
# -------
buildElement = (id) ->
element = document.createElement('div')
element.textContent = id
element.tabIndex = -1
element
container = new PaneContainer
pane1 = container.getRoot()
pane1.activateItem(buildElement('1'))
pane4 = pane1.splitDown(items: [buildElement('4')])
pane7 = pane4.splitDown(items: [buildElement('7')])
pane2 = pane1.splitRight(items: [buildElement('2')])
pane3 = pane2.splitRight(items: [buildElement('3')])
pane5 = pane4.splitRight(items: [buildElement('5')])
pane6 = pane5.splitRight(items: [buildElement('6')])
pane8 = pane7.splitRight(items: [buildElement('8')])
pane9 = pane8.splitRight(items: [buildElement('9')])
containerElement = atom.views.getView(container)
containerElement.style.height = '400px'
containerElement.style.width = '400px'
jasmine.attachToDOM(containerElement)
describe "::focusPaneViewAbove()", ->
describe "when there are multiple rows above the focused pane", ->
it "focuses up to the adjacent row", ->
pane8.activate()
containerElement.focusPaneViewAbove()
expect(document.activeElement).toBe pane5.getActiveItem()
describe "when there are no rows above the focused pane", ->
it "keeps the current pane focused", ->
pane2.activate()
containerElement.focusPaneViewAbove()
expect(document.activeElement).toBe pane2.getActiveItem()
describe "::focusPaneViewBelow()", ->
describe "when there are multiple rows below the focused pane", ->
it "focuses down to the adjacent row", ->
pane2.activate()
containerElement.focusPaneViewBelow()
expect(document.activeElement).toBe pane5.getActiveItem()
describe "when there are no rows below the focused pane", ->
it "keeps the current pane focused", ->
pane8.activate()
containerElement.focusPaneViewBelow()
expect(document.activeElement).toBe pane8.getActiveItem()
describe "::focusPaneViewOnLeft()", ->
describe "when there are multiple columns to the left of the focused pane", ->
it "focuses left to the adjacent column", ->
pane6.activate()
containerElement.focusPaneViewOnLeft()
expect(document.activeElement).toBe pane5.getActiveItem()
describe "when there are no columns to the left of the focused pane", ->
it "keeps the current pane focused", ->
pane4.activate()
containerElement.focusPaneViewOnLeft()
expect(document.activeElement).toBe pane4.getActiveItem()
describe "::focusPaneViewOnRight()", ->
describe "when there are multiple columns to the right of the focused pane", ->
it "focuses right to the adjacent column", ->
pane4.activate()
containerElement.focusPaneViewOnRight()
expect(document.activeElement).toBe pane5.getActiveItem()
describe "when there are no columns to the right of the focused pane", ->
it "keeps the current pane focused", ->
pane6.activate()
containerElement.focusPaneViewOnRight()
expect(document.activeElement).toBe pane6.getActiveItem()

View File

@@ -21,7 +21,7 @@ describe "PaneContainer", ->
it "preserves the focused pane across serialization", ->
expect(pane3A.focused).toBe true
containerB = containerA.testSerialization()
containerB = PaneContainer.deserialize(containerA.serialize())
[pane1B, pane2B, pane3B] = containerB.getPanes()
expect(pane3B.focused).toBe true
@@ -29,7 +29,7 @@ describe "PaneContainer", ->
pane3A.activate()
expect(containerA.getActivePane()).toBe pane3A
containerB = containerA.testSerialization()
containerB = PaneContainer.deserialize(containerA.serialize())
[pane1B, pane2B, pane3B] = containerB.getPanes()
expect(containerB.getActivePane()).toBe pane3B
@@ -40,6 +40,32 @@ describe "PaneContainer", ->
containerB = atom.deserializers.deserialize(state)
expect(containerB.getActivePane()).toBe containerB.getPanes()[0]
describe "if there are empty panes after deserialization", ->
beforeEach ->
pane3A.getItems()[0].serialize = -> deserializer: 'Bogus'
describe "if the 'core.destroyEmptyPanes' config option is false (the default)", ->
it "leaves the empty panes intact", ->
state = containerA.serialize()
containerB = atom.deserializers.deserialize(state)
[leftPane, column] = containerB.getRoot().getChildren()
[topPane, bottomPane] = column.getChildren()
expect(leftPane.getItems().length).toBe 1
expect(topPane.getItems().length).toBe 1
expect(bottomPane.getItems().length).toBe 0
describe "if the 'core.destroyEmptyPanes' config option is true", ->
it "removes empty panes on deserialization", ->
atom.config.set('core.destroyEmptyPanes', true)
state = containerA.serialize()
containerB = atom.deserializers.deserialize(state)
[leftPane, rightPane] = containerB.getRoot().getChildren()
expect(leftPane.getItems().length).toBe 1
expect(rightPane.getItems().length).toBe 1
it "does not allow the root pane to be destroyed", ->
container = new PaneContainer
container.getRoot().destroy()
@@ -223,3 +249,19 @@ describe "PaneContainer", ->
['will', {item: item2, pane: pane2, index: 0}]
['did', {item: item2, pane: pane2, index: 0}]
]
describe "::saveAll()", ->
it "saves all open pane items", ->
container = new PaneContainer
pane1 = container.getRoot()
pane2 = pane1.splitRight()
pane1.addItem(item1 = {getURI: (-> ''), save: -> @saved = true})
pane1.addItem(item2 = {getURI: (-> ''), save: -> @saved = true})
pane2.addItem(item3 = {getURI: (-> ''), save: -> @saved = true})
container.saveAll()
expect(item1.saved).toBe true
expect(item2.saved).toBe true
expect(item3.saved).toBe true

View File

@@ -1,334 +0,0 @@
path = require 'path'
temp = require 'temp'
PaneContainer = require '../src/pane-container'
PaneContainerView = require '../src/pane-container-view'
PaneView = require '../src/pane-view'
{Disposable} = require 'event-kit'
{$, View, $$} = require '../src/space-pen-extensions'
describe "PaneContainerView", ->
[TestView, container, pane1, pane2, pane3, deserializerDisposable] = []
beforeEach ->
class TestView extends View
deserializerDisposable = atom.deserializers.add(this)
@deserialize: ({name}) -> new TestView(name)
@content: -> @div tabindex: -1
initialize: (@name) -> @text(@name)
serialize: -> {deserializer: 'TestView', @name}
getURI: -> path.join(temp.dir, @name)
save: -> @saved = true
isEqual: (other) -> @name is other?.name
onDidChangeTitle: -> new Disposable(->)
onDidChangeModified: -> new Disposable(->)
container = atom.views.getView(atom.workspace.paneContainer).__spacePenView
pane1 = container.getRoot()
pane1.activateItem(new TestView('1'))
pane2 = pane1.splitRight(new TestView('2'))
pane3 = pane2.splitDown(new TestView('3'))
afterEach ->
deserializerDisposable.dispose()
describe ".getActivePaneView()", ->
it "returns the most-recently focused pane", ->
focusStealer = $$ -> @div tabindex: -1, "focus stealer"
focusStealer.attachToDom()
container.attachToDom()
pane2.focus()
expect(container.getFocusedPane()).toBe pane2
expect(container.getActivePaneView()).toBe pane2
focusStealer.focus()
expect(container.getFocusedPane()).toBeUndefined()
expect(container.getActivePaneView()).toBe pane2
pane3.focus()
expect(container.getFocusedPane()).toBe pane3
expect(container.getActivePaneView()).toBe pane3
describe ".eachPaneView(callback)", ->
it "runs the callback with all current and future panes until the subscription is cancelled", ->
panes = []
subscription = container.eachPaneView (pane) -> panes.push(pane)
expect(panes).toEqual [pane1, pane2, pane3]
panes = []
pane4 = pane3.splitRight(pane3.copyActiveItem())
expect(panes).toEqual [pane4]
panes = []
subscription.off()
pane4.splitDown()
expect(panes).toEqual []
describe ".saveAll()", ->
it "saves all open pane items", ->
pane1.activateItem(new TestView('4'))
container.saveAll()
for pane in container.getPaneViews()
for item in pane.getItems()
expect(item.saved).toBeTruthy()
describe "serialization", ->
it "can be serialized and deserialized, and correctly adjusts dimensions of deserialized panes after attach", ->
newContainer = atom.views.getView(container.model.testSerialization()).__spacePenView
expect(newContainer.find('atom-pane-axis.horizontal > :contains(1)')).toExist()
expect(newContainer.find('atom-pane-axis.horizontal > atom-pane-axis.vertical > :contains(2)')).toExist()
expect(newContainer.find('atom-pane-axis.horizontal > atom-pane-axis.vertical > :contains(3)')).toExist()
newContainer.height(200).width(300).attachToDom()
expect(newContainer.find('atom-pane-axis.horizontal > :contains(1)').width()).toBe 150
expect(newContainer.find('atom-pane-axis.horizontal > atom-pane-axis.vertical > :contains(2)').height()).toBe 100
describe "if there are empty panes after deserialization", ->
beforeEach ->
# only deserialize pane 1's view successfully
TestView.deserialize = ({name}) -> new TestView(name) if name is '1'
describe "if the 'core.destroyEmptyPanes' config option is false (the default)", ->
it "leaves the empty panes intact", ->
newContainer = atom.views.getView(container.model.testSerialization()).__spacePenView
expect(newContainer.find('atom-pane-axis.horizontal > :contains(1)')).toExist()
expect(newContainer.find('atom-pane-axis.horizontal > atom-pane-axis.vertical > atom-pane').length).toBe 2
describe "if the 'core.destroyEmptyPanes' config option is true", ->
it "removes empty panes on deserialization", ->
atom.config.set('core.destroyEmptyPanes', true)
newContainer = atom.views.getView(container.model.testSerialization()).__spacePenView
expect(newContainer.find('atom-pane-axis.horizontal, atom-pane-axis.vertical')).not.toExist()
expect(newContainer.find('> :contains(1)')).toExist()
describe "pane-container:active-pane-item-changed", ->
[pane1, item1a, item1b, item2a, item2b, item3a, container, activeItemChangedHandler] = []
beforeEach ->
item1a = new TestView('1a')
item1b = new TestView('1b')
item2a = new TestView('2a')
item2b = new TestView('2b')
item3a = new TestView('3a')
container = atom.views.getView(new PaneContainer).__spacePenView
pane1 = container.getRoot()
pane1.activateItem(item1a)
container.attachToDom()
activeItemChangedHandler = jasmine.createSpy("activeItemChangedHandler")
container.on 'pane-container:active-pane-item-changed', activeItemChangedHandler
describe "when there is one pane", ->
it "is triggered when a new pane item is added", ->
pane1.activateItem(item1b)
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1b
it "is not triggered when the active pane item is shown again", ->
pane1.activateItem(item1a)
expect(activeItemChangedHandler).not.toHaveBeenCalled()
it "is triggered when switching to an existing pane item", ->
pane1.activateItem(item1b)
activeItemChangedHandler.reset()
pane1.activateItem(item1a)
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
it "is triggered when the active pane item is destroyed", ->
pane1.activateItem(item1b)
activeItemChangedHandler.reset()
pane1.destroyItem(item1b)
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
it "is not triggered when an inactive pane item is destroyed", ->
pane1.activateItem(item1b)
activeItemChangedHandler.reset()
pane1.destroyItem(item1a)
expect(activeItemChangedHandler).not.toHaveBeenCalled()
it "is triggered when all pane items are destroyed", ->
pane1.destroyItem(item1a)
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toBe undefined
describe "when there are two panes", ->
[pane2] = []
beforeEach ->
pane2 = pane1.splitLeft(item2a)
activeItemChangedHandler.reset()
it "is triggered when a new pane item is added to the active pane", ->
pane2.activateItem(item2b)
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item2b
it "is not triggered when a new pane item is added to an inactive pane", ->
pane1.activateItem(item1b)
expect(activeItemChangedHandler).not.toHaveBeenCalled()
it "is triggered when the active pane's active item is destroyed", ->
pane2.activateItem(item2b)
activeItemChangedHandler.reset()
pane2.destroyItem(item2b)
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item2a
it "is not triggered when an inactive pane's active item is destroyed", ->
pane1.activateItem(item1b)
activeItemChangedHandler.reset()
pane1.destroyItem(item1b)
expect(activeItemChangedHandler).not.toHaveBeenCalled()
it "is triggered when the active pane is destroyed", ->
pane2.remove()
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
it "is not triggered when an inactive pane is destroyed", ->
pane1.remove()
expect(activeItemChangedHandler).not.toHaveBeenCalled()
it "is triggered when the active pane is changed", ->
pane1.activate()
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a
describe "when there are multiple panes", ->
beforeEach ->
pane2 = pane1.splitRight(item2a)
activeItemChangedHandler.reset()
it "is triggered when a new pane is added", ->
pane2.splitDown(item3a)
expect(activeItemChangedHandler.callCount).toBe 1
expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item3a
it "is not triggered when an inactive pane is destroyed", ->
pane3 = pane2.splitDown(item3a)
activeItemChangedHandler.reset()
pane1.remove()
pane2.remove()
expect(activeItemChangedHandler).not.toHaveBeenCalled()
describe ".focusNextPaneView()", ->
it "focuses the pane following the focused pane or the first pane if no pane has focus", ->
container.attachToDom()
container.focusNextPaneView()
expect(pane1.activeItem).toMatchSelector ':focus'
container.focusNextPaneView()
expect(pane2.activeItem).toMatchSelector ':focus'
container.focusNextPaneView()
expect(pane3.activeItem).toMatchSelector ':focus'
container.focusNextPaneView()
expect(pane1.activeItem).toMatchSelector ':focus'
describe ".focusPreviousPaneView()", ->
it "focuses the pane preceding the focused pane or the last pane if no pane has focus", ->
container.attachToDom()
container.getPaneViews()[0].focus() # activate first pane
container.focusPreviousPaneView()
expect(pane3.activeItem).toMatchSelector ':focus'
container.focusPreviousPaneView()
expect(pane2.activeItem).toMatchSelector ':focus'
container.focusPreviousPaneView()
expect(pane1.activeItem).toMatchSelector ':focus'
container.focusPreviousPaneView()
expect(pane3.activeItem).toMatchSelector ':focus'
describe "changing focus directionally between panes", ->
[pane1, pane2, pane3, pane4, pane5, pane6, pane7, pane8, pane9] = []
beforeEach ->
# Set up a grid of 9 panes, in the following arrangement, where the
# numbers correspond to the variable names below.
#
# -------
# |1|2|3|
# -------
# |4|5|6|
# -------
# |7|8|9|
# -------
container = atom.views.getView(new PaneContainer).__spacePenView
pane1 = container.getRoot()
pane1.activateItem(new TestView('1'))
pane4 = pane1.splitDown(new TestView('4'))
pane7 = pane4.splitDown(new TestView('7'))
pane2 = pane1.splitRight(new TestView('2'))
pane3 = pane2.splitRight(new TestView('3'))
pane5 = pane4.splitRight(new TestView('5'))
pane6 = pane5.splitRight(new TestView('6'))
pane8 = pane7.splitRight(new TestView('8'))
pane9 = pane8.splitRight(new TestView('9'))
container.height(400)
container.width(400)
container.attachToDom()
describe ".focusPaneViewAbove()", ->
describe "when there are multiple rows above the focused pane", ->
it "focuses up to the adjacent row", ->
pane8.focus()
container.focusPaneViewAbove()
expect(pane5.activeItem).toMatchSelector ':focus'
describe "when there are no rows above the focused pane", ->
it "keeps the current pane focused", ->
pane2.focus()
container.focusPaneViewAbove()
expect(pane2.activeItem).toMatchSelector ':focus'
describe ".focusPaneViewBelow()", ->
describe "when there are multiple rows below the focused pane", ->
it "focuses down to the adjacent row", ->
pane2.focus()
container.focusPaneViewBelow()
expect(pane5.activeItem).toMatchSelector ':focus'
describe "when there are no rows below the focused pane", ->
it "keeps the current pane focused", ->
pane8.focus()
container.focusPaneViewBelow()
expect(pane8.activeItem).toMatchSelector ':focus'
describe ".focusPaneViewOnLeft()", ->
describe "when there are multiple columns to the left of the focused pane", ->
it "focuses left to the adjacent column", ->
pane6.focus()
container.focusPaneViewOnLeft()
expect(pane5.activeItem).toMatchSelector ':focus'
describe "when there are no columns to the left of the focused pane", ->
it "keeps the current pane focused", ->
pane4.focus()
container.focusPaneViewOnLeft()
expect(pane4.activeItem).toMatchSelector ':focus'
describe ".focusPaneViewOnRight()", ->
describe "when there are multiple columns to the right of the focused pane", ->
it "focuses right to the adjacent column", ->
pane4.focus()
container.focusPaneViewOnRight()
expect(pane5.activeItem).toMatchSelector ':focus'
describe "when there are no columns to the right of the focused pane", ->
it "keeps the current pane focused", ->
pane6.focus()
container.focusPaneViewOnRight()
expect(pane6.activeItem).toMatchSelector ':focus'

View File

@@ -0,0 +1,197 @@
PaneContainer = require '../src/pane-container'
describe "PaneElement", ->
[paneElement, container, pane] = []
beforeEach ->
container = new PaneContainer
pane = container.getRoot()
paneElement = atom.views.getView(pane)
describe "when the pane's active status changes", ->
it "adds or removes the .active class as appropriate", ->
pane2 = pane.splitRight()
expect(pane2.isActive()).toBe true
expect(paneElement.className).not.toMatch /active/
pane.activate()
expect(paneElement.className).toMatch /active/
pane2.activate()
expect(paneElement.className).not.toMatch /active/
describe "when the active item changes", ->
it "hides all item elements except the active one", ->
item1 = document.createElement('div')
item2 = document.createElement('div')
item3 = document.createElement('div')
pane.addItem(item1)
pane.addItem(item2)
pane.addItem(item3)
expect(pane.getActiveItem()).toBe item1
expect(item1.parentElement).toBeDefined()
expect(item1.style.display).toBe ''
expect(item2.parentElement).toBeNull()
expect(item3.parentElement).toBeNull()
pane.activateItem(item2)
expect(item2.parentElement).toBeDefined()
expect(item1.style.display).toBe 'none'
expect(item2.style.display).toBe ''
expect(item3.parentElement).toBeNull()
pane.activateItem(item3)
expect(item3.parentElement).toBeDefined()
expect(item1.style.display).toBe 'none'
expect(item2.style.display).toBe 'none'
expect(item3.style.display).toBe ''
it "transfers focus to the new item if the previous item was focused", ->
item1 = document.createElement('div')
item1.tabIndex = -1
item2 = document.createElement('div')
item2.tabIndex = -1
pane.addItem(item1)
pane.addItem(item2)
jasmine.attachToDOM(paneElement)
paneElement.focus()
expect(document.activeElement).toBe item1
pane.activateItem(item2)
expect(document.activeElement).toBe item2
describe "if the active item is a model object", ->
it "retrieves the associated view from atom.views and appends it to the itemViews div", ->
class TestModel
atom.views.addViewProvider TestModel, (model) ->
view = document.createElement('div')
view.model = model
view
item1 = new TestModel
item2 = new TestModel
pane.addItem(item1)
pane.addItem(item2)
expect(paneElement.itemViews.children[0].model).toBe item1
expect(paneElement.itemViews.children[0].style.display).toBe ''
pane.activateItem(item2)
expect(paneElement.itemViews.children[1].model).toBe item2
expect(paneElement.itemViews.children[0].style.display).toBe 'none'
expect(paneElement.itemViews.children[1].style.display).toBe ''
describe "when the new active implements .getPath()", ->
it "adds the file path and file name as a data attribute on the pane", ->
item1 = document.createElement('div')
item1.getPath = -> '/foo/bar.txt'
item2 = document.createElement('div')
pane.addItem(item1)
pane.addItem(item2)
expect(paneElement.dataset.activeItemPath).toBe '/foo/bar.txt'
expect(paneElement.dataset.activeItemName).toBe 'bar.txt'
pane.activateItem(item2)
expect(paneElement.dataset.activeItemPath).toBeUndefined()
expect(paneElement.dataset.activeItemName).toBeUndefined()
pane.activateItem(item1)
expect(paneElement.dataset.activeItemPath).toBe '/foo/bar.txt'
expect(paneElement.dataset.activeItemName).toBe 'bar.txt'
pane.destroyItems()
expect(paneElement.dataset.activeItemPath).toBeUndefined()
expect(paneElement.dataset.activeItemName).toBeUndefined()
describe "when an item is removed from the pane", ->
describe "when the destroyed item is an element", ->
it "removes the item from the itemViews div", ->
item1 = document.createElement('div')
item2 = document.createElement('div')
pane.addItem(item1)
pane.addItem(item2)
paneElement = atom.views.getView(pane)
expect(item1.parentElement).toBe paneElement.itemViews
pane.destroyItem(item1)
expect(item1.parentElement).toBeNull()
expect(item2.parentElement).toBe paneElement.itemViews
pane.destroyItem(item2)
expect(item2.parentElement).toBeNull()
describe "when the destroyed item is a model", ->
it "removes the model's associated view", ->
class TestModel
atom.views.addViewProvider TestModel, (model) ->
view = document.createElement('div')
model.element = view
view.model = model
view
item1 = new TestModel
item2 = new TestModel
pane.addItem(item1)
pane.addItem(item2)
expect(item1.element.parentElement).toBe paneElement.itemViews
pane.destroyItem(item1)
expect(item1.element.parentElement).toBeNull()
expect(item2.element.parentElement).toBe paneElement.itemViews
pane.destroyItem(item2)
expect(item2.element.parentElement).toBeNull()
describe "when the pane element is focused", ->
it "transfers focus to the active view", ->
item = document.createElement('div')
item.tabIndex = -1
pane.activateItem(item)
jasmine.attachToDOM(paneElement)
expect(document.activeElement).toBe document.body
paneElement.focus()
expect(document.activeElement).toBe item
it "makes the pane active", ->
pane.splitRight()
expect(pane.isActive()).toBe false
jasmine.attachToDOM(paneElement)
paneElement.focus()
expect(pane.isActive()).toBe true
describe "when the pane element is attached", ->
it "focuses the pane element if isFocused() returns true on its model", ->
pane.focus()
jasmine.attachToDOM(paneElement)
expect(document.activeElement).toBe paneElement
describe "drag and drop", ->
buildDragEvent = (type, files) ->
dataTransfer =
files: files
data: {}
setData: (key, value) -> @data[key] = value
getData: (key) -> @data[key]
event = new CustomEvent("drop")
event.dataTransfer = dataTransfer
event
describe "when a file is dragged to the pane", ->
it "opens it", ->
spyOn(atom, "open")
event = buildDragEvent("drop", [{path: "/fake1"}, {path: "/fake2"}])
paneElement.dispatchEvent(event)
expect(atom.open.callCount).toBe 1
expect(atom.open.argsForCall[0][0]).toEqual pathsToOpen: ['/fake1', '/fake2']
describe "when a non-file is dragged to the pane", ->
it "does nothing", ->
spyOn(atom, "open")
event = buildDragEvent("drop", [])
paneElement.dispatchEvent(event)
expect(atom.open).not.toHaveBeenCalled()

View File

@@ -1,4 +1,4 @@
{Model} = require 'theorist'
{Emitter} = require 'event-kit'
Pane = require '../src/pane'
PaneAxis = require '../src/pane-axis'
PaneContainer = require '../src/pane-container'
@@ -6,13 +6,17 @@ PaneContainer = require '../src/pane-container'
describe "Pane", ->
deserializerDisposable = null
class Item extends Model
class Item
@deserialize: ({name, uri}) -> new this(name, uri)
constructor: (@name, @uri) ->
constructor: (@name, @uri) -> @emitter = new Emitter
destroyed: false
getURI: -> @uri
getPath: -> @path
serialize: -> {deserializer: 'Item', @name, @uri}
isEqual: (other) -> @name is other?.name
onDidDestroy: (fn) -> @emitter.on('did-destroy', fn)
destroy: -> @destroyed = true; @emitter.emit('did-destroy')
isDestroyed: -> @destroyed
beforeEach ->
deserializerDisposable = atom.deserializers.add(Item)
@@ -99,7 +103,7 @@ describe "Pane", ->
item = new Item("C")
pane.addItem(item, 1)
expect(events).toEqual [{item, index: 1}]
expect(events).toEqual [{item, index: 1, moved: false}]
it "throws an exception if the item is already present on a pane", ->
item = new Item("A")
@@ -219,13 +223,13 @@ describe "Pane", ->
events = []
pane.onWillRemoveItem (event) -> events.push(event)
pane.destroyItem(item2)
expect(events).toEqual [{item: item2, index: 1, destroyed: true}]
expect(events).toEqual [{item: item2, index: 1, moved: false, destroyed: true}]
it "invokes ::onDidRemoveItem() observers", ->
events = []
pane.onDidRemoveItem (event) -> events.push(event)
pane.destroyItem(item2)
expect(events).toEqual [{item: item2, index: 1, destroyed: true}]
expect(events).toEqual [{item: item2, index: 1, moved: false, destroyed: true}]
describe "when the destroyed item is the active item and is the first item", ->
it "activates the next item", ->
@@ -513,14 +517,20 @@ describe "Pane", ->
pane1.onWillRemoveItem (event) -> events.push(event)
pane1.moveItemToPane(item2, pane2, 1)
expect(events).toEqual [{item: item2, index: 1, destroyed: false}]
expect(events).toEqual [{item: item2, index: 1, moved: true, destroyed: false}]
it "invokes ::onDidRemoveItem() observers", ->
events = []
pane1.onDidRemoveItem (event) -> events.push(event)
pane1.moveItemToPane(item2, pane2, 1)
expect(events).toEqual [{item: item2, index: 1, destroyed: false}]
expect(events).toEqual [{item: item2, index: 1, moved: true, destroyed: false}]
it "does not invoke ::onDidAddPaneItem observers on the container", ->
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 ->
@@ -728,12 +738,12 @@ describe "Pane", ->
pane = new Pane(params)
it "can serialize and deserialize the pane and all its items", ->
newPane = pane.testSerialization()
newPane = Pane.deserialize(pane.serialize())
expect(newPane.getItems()).toEqual pane.getItems()
it "restores the active item on deserialization", ->
pane.activateItemAtIndex(1)
newPane = pane.testSerialization()
newPane = Pane.deserialize(pane.serialize())
expect(newPane.getActiveItem()).toEqual newPane.itemAtIndex(1)
it "does not include items that cannot be deserialized", ->
@@ -741,11 +751,11 @@ describe "Pane", ->
unserializable = {}
pane.activateItem(unserializable)
newPane = pane.testSerialization()
newPane = Pane.deserialize(pane.serialize())
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()
newPane = pane.testSerialization()
newPane = Pane.deserialize(pane.serialize())
expect(newPane.focused).toBe true

View File

@@ -1,389 +0,0 @@
PaneContainer = require '../src/pane-container'
PaneView = require '../src/pane-view'
fs = require 'fs-plus'
{Emitter, Disposable} = require 'event-kit'
{$, View} = require '../src/space-pen-extensions'
path = require 'path'
temp = require 'temp'
describe "PaneView", ->
[container, containerModel, view1, view2, editor1, editor2, pane, paneModel, deserializerDisposable] = []
class TestView extends View
@deserialize: ({id, text}) -> new TestView({id, text})
@content: ({id, text}) -> @div class: 'test-view', id: id, tabindex: -1, text
initialize: ({@id, @text}) ->
@emitter = new Emitter
serialize: -> {deserializer: 'TestView', @id, @text}
getURI: -> @id
isEqual: (other) -> other? and @id is other.id and @text is other.text
changeTitle: ->
@emitter.emit 'did-change-title', 'title'
onDidChangeTitle: (callback) ->
@emitter.on 'did-change-title', callback
onDidChangeModified: -> new Disposable(->)
beforeEach ->
jasmine.snapshotDeprecations()
deserializerDisposable = atom.deserializers.add(TestView)
container = atom.views.getView(new PaneContainer).__spacePenView
containerModel = container.model
view1 = new TestView(id: 'view-1', text: 'View 1')
view2 = new TestView(id: 'view-2', text: 'View 2')
waitsForPromise ->
atom.workspace.open('sample.js').then (o) -> editor1 = o
waitsForPromise ->
atom.workspace.open('sample.txt').then (o) -> editor2 = o
runs ->
pane = container.getRoot()
paneModel = pane.getModel()
paneModel.addItems([view1, editor1, view2, editor2])
afterEach ->
deserializerDisposable.dispose()
jasmine.restoreDeprecationsSnapshot()
describe "when the active pane item changes", ->
it "hides all item views except the active one", ->
expect(pane.getActiveItem()).toBe view1
expect(view1.css('display')).not.toBe 'none'
pane.activateItem(view2)
expect(view1.css('display')).toBe 'none'
expect(view2.css('display')).not.toBe 'none'
it "triggers 'pane:active-item-changed'", ->
itemChangedHandler = jasmine.createSpy("itemChangedHandler")
container.on 'pane:active-item-changed', itemChangedHandler
expect(pane.getActiveItem()).toBe view1
paneModel.activateItem(view2)
paneModel.activateItem(view2)
expect(itemChangedHandler.callCount).toBe 1
expect(itemChangedHandler.argsForCall[0][1]).toBe view2
itemChangedHandler.reset()
paneModel.activateItem(editor1)
expect(itemChangedHandler).toHaveBeenCalled()
expect(itemChangedHandler.argsForCall[0][1]).toBe editor1
itemChangedHandler.reset()
it "transfers focus to the new active view if the previous view was focused", ->
container.attachToDom()
pane.focus()
expect(pane.activeView).not.toBe view2
expect(pane.activeView).toMatchSelector ':focus'
paneModel.activateItem(view2)
expect(view2).toMatchSelector ':focus'
describe "when the new activeItem is a model", ->
it "shows the item's view or creates and shows a new view for the item if none exists", ->
initialViewCount = pane.itemViews.find('.test-view').length
model1 =
id: 'test-model-1'
text: 'Test Model 1'
serialize: -> {@id, @text}
getViewClass: -> TestView
model2 =
id: 'test-model-2'
text: 'Test Model 2'
serialize: -> {@id, @text}
getViewClass: -> TestView
paneModel.activateItem(model1)
paneModel.activateItem(model2)
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2
paneModel.activatePreviousItem()
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2
paneModel.destroyItem(model2)
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 1
paneModel.destroyItem(model1)
expect(pane.itemViews.find('.test-view').length).toBe initialViewCount
describe "when the new activeItem is a view", ->
it "appends it to the itemViews div if it hasn't already been appended and shows it", ->
expect(pane.itemViews.find('#view-2')).not.toExist()
paneModel.activateItem(view2)
expect(pane.itemViews.find('#view-2')).toExist()
paneModel.activateItem(view1)
paneModel.activateItem(view2)
expect(pane.itemViews.find('#view-2').length).toBe 1
describe "when the new activeItem implements ::getPath", ->
beforeEach ->
paneModel.activateItem(editor1)
it "adds the file path as a data attribute to the pane", ->
expect(pane).toHaveAttr('data-active-item-path')
it "adds the file name as a data attribute to the pane", ->
expect(pane).toHaveAttr('data-active-item-name')
describe "when the activeItem is destroyed", ->
it "removes the data attributes", ->
pane.destroyItems()
expect(pane).not.toHaveAttr('data-active-item-path')
expect(pane).not.toHaveAttr('data-active-item-name')
describe "when the new activeItem does not implement ::getPath", ->
beforeEach ->
paneModel.activateItem(editor1)
paneModel.activateItem(document.createElement('div'))
it "does not add the file path as a data attribute to the pane", ->
expect(pane).not.toHaveAttr('data-active-item-path')
it "does not add the file name as data attribute to the pane", ->
expect(pane).not.toHaveAttr('data-active-item-name')
describe "when an item is destroyed", ->
it "triggers the 'pane:item-removed' event with the item and its former index", ->
itemRemovedHandler = jasmine.createSpy("itemRemovedHandler")
pane.on 'pane:item-removed', itemRemovedHandler
paneModel.destroyItem(editor1)
expect(itemRemovedHandler).toHaveBeenCalled()
expect(itemRemovedHandler.argsForCall[0][1..2]).toEqual [editor1, 1]
describe "when the destroyed item is a view", ->
it "removes the item from the 'item-views' div", ->
expect(view1.parent()).toMatchSelector pane.itemViews
paneModel.destroyItem(view1)
expect(view1.parent()).not.toMatchSelector pane.itemViews
describe "when the destroyed item is a model", ->
it "removes the associated view", ->
paneModel.activateItem(editor1)
expect(pane.itemViews.find('atom-text-editor').length).toBe 1
pane.destroyItem(editor1)
expect(pane.itemViews.find('atom-text-editor').length).toBe 0
describe "when an item is moved within the same pane", ->
it "emits a 'pane:item-moved' event with the item and the new index", ->
pane.on 'pane:item-moved', itemMovedHandler = jasmine.createSpy("itemMovedHandler")
paneModel.moveItem(view1, 2)
expect(itemMovedHandler).toHaveBeenCalled()
expect(itemMovedHandler.argsForCall[0][1..2]).toEqual [view1, 2]
describe "when an item is moved to another pane", ->
it "detaches the item's view rather than removing it", ->
container.attachToDom()
expect(view1.is(':visible')).toBe true
paneModel2 = paneModel.splitRight()
view1.data('preservative', 1234)
paneModel.moveItemToPane(view1, paneModel2, 1)
expect(view1.data('preservative')).toBe 1234
paneModel2.activateItemAtIndex(1)
expect(view1.data('preservative')).toBe 1234
expect(view1.is(':visible')).toBe true
describe "when the title of the active item changes", ->
describe 'when there is no onDidChangeTitle method (deprecated)', ->
beforeEach ->
jasmine.snapshotDeprecations()
view1.onDidChangeTitle = null
view2.onDidChangeTitle = null
pane.activateItem(view2)
pane.activateItem(view1)
afterEach ->
jasmine.restoreDeprecationsSnapshot()
it "emits pane:active-item-title-changed", ->
activeItemTitleChangedHandler = jasmine.createSpy("activeItemTitleChangedHandler")
pane.on 'pane:active-item-title-changed', activeItemTitleChangedHandler
expect(pane.getActiveItem()).toBe view1
view2.trigger 'title-changed'
expect(activeItemTitleChangedHandler).not.toHaveBeenCalled()
view1.trigger 'title-changed'
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
activeItemTitleChangedHandler.reset()
pane.activateItem(view2)
view2.trigger 'title-changed'
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
describe 'when there is a onDidChangeTitle method', ->
it "emits pane:active-item-title-changed", ->
activeItemTitleChangedHandler = jasmine.createSpy("activeItemTitleChangedHandler")
pane.on 'pane:active-item-title-changed', activeItemTitleChangedHandler
expect(pane.getActiveItem()).toBe view1
view2.changeTitle()
expect(activeItemTitleChangedHandler).not.toHaveBeenCalled()
view1.changeTitle()
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
activeItemTitleChangedHandler.reset()
pane.activateItem(view2)
view2.changeTitle()
expect(activeItemTitleChangedHandler).toHaveBeenCalled()
describe "when an unmodifed buffer's path is deleted", ->
it "removes the pane item", ->
editor = null
jasmine.unspy(window, 'setTimeout')
filePath = path.join(temp.mkdirSync(), 'file.txt')
fs.writeFileSync(filePath, '')
waitsForPromise ->
atom.workspace.open(filePath).then (o) -> editor = o
runs ->
pane.activateItem(editor)
expect(pane.items).toHaveLength(5)
fs.removeSync(filePath)
waitsFor ->
pane.items.length is 4
describe "when a pane is destroyed", ->
[pane2, pane2Model] = []
beforeEach ->
pane2Model = paneModel.splitRight() # Can't destroy the last pane, so we add another
pane2 = atom.views.getView(pane2Model).__spacePenView
it "triggers a 'pane:removed' event with the pane", ->
removedHandler = jasmine.createSpy("removedHandler")
container.on 'pane:removed', removedHandler
paneModel.destroy()
expect(removedHandler).toHaveBeenCalled()
expect(removedHandler.argsForCall[0][1]).toBe pane
describe "if the destroyed pane has focus", ->
[paneToLeft, paneToRight] = []
it "focuses the next pane", ->
container.attachToDom()
pane2.activate()
expect(pane.hasFocus()).toBe false
expect(pane2.hasFocus()).toBe true
pane2Model.destroy()
expect(pane.hasFocus()).toBe true
describe "::getNextPane()", ->
it "returns the next pane if one exists, wrapping around from the last pane to the first", ->
pane.activateItem(editor1)
expect(pane.getNextPane()).toBeUndefined
pane2 = pane.splitRight(pane.copyActiveItem())
expect(pane.getNextPane()).toBe pane2
expect(pane2.getNextPane()).toBe pane
describe "when the pane's active status changes", ->
[pane2, pane2Model] = []
beforeEach ->
pane2Model = paneModel.splitRight(items: [pane.copyActiveItem()])
pane2 = atom.views.getView(pane2Model).__spacePenView
expect(pane2Model.isActive()).toBe true
it "adds or removes the .active class as appropriate", ->
expect(pane).not.toHaveClass('active')
paneModel.activate()
expect(pane).toHaveClass('active')
pane2Model.activate()
expect(pane).not.toHaveClass('active')
it "triggers 'pane:became-active' or 'pane:became-inactive' according to the current status", ->
pane.on 'pane:became-active', becameActiveHandler = jasmine.createSpy("becameActiveHandler")
pane.on 'pane:became-inactive', becameInactiveHandler = jasmine.createSpy("becameInactiveHandler")
paneModel.activate()
expect(becameActiveHandler.callCount).toBe 1
expect(becameInactiveHandler.callCount).toBe 0
pane2Model.activate()
expect(becameActiveHandler.callCount).toBe 1
expect(becameInactiveHandler.callCount).toBe 1
describe "when the pane is focused", ->
beforeEach ->
container.attachToDom()
it "transfers focus to the active view", ->
focusHandler = jasmine.createSpy("focusHandler")
pane.getActiveItem().on 'focus', focusHandler
pane.focus()
expect(focusHandler).toHaveBeenCalled()
it "makes the pane active", ->
paneModel.splitRight(items: [pane.copyActiveItem()])
expect(paneModel.isActive()).toBe false
pane.focus()
expect(paneModel.isActive()).toBe true
describe "when a pane is split", ->
it "builds the appropriateatom-pane-axis.horizontal and pane-column views", ->
pane1 = pane
pane1Model = pane.getModel()
pane.activateItem(editor1)
pane2Model = pane1Model.splitRight(items: [pane1Model.copyActiveItem()])
pane3Model = pane2Model.splitDown(items: [pane2Model.copyActiveItem()])
pane2 = pane2Model._view
pane2 = atom.views.getView(pane2Model).__spacePenView
pane3 = atom.views.getView(pane3Model).__spacePenView
expect(container.find('> atom-pane-axis.horizontal > atom-pane').toArray()).toEqual [pane1[0]]
expect(container.find('> atom-pane-axis.horizontal > atom-pane-axis.vertical > atom-pane').toArray()).toEqual [pane2[0], pane3[0]]
pane1Model.destroy()
expect(container.find('> atom-pane-axis.vertical > atom-pane').toArray()).toEqual [pane2[0], pane3[0]]
describe "serialization", ->
it "focuses the pane after attach only if had focus when serialized", ->
container.attachToDom()
pane.focus()
container2 = atom.views.getView(container.model.testSerialization()).__spacePenView
pane2 = container2.getRoot()
container2.attachToDom()
expect(pane2).toMatchSelector(':has(:focus)')
$(document.activeElement).blur()
container3 = atom.views.getView(container.model.testSerialization()).__spacePenView
pane3 = container3.getRoot()
container3.attachToDom()
expect(pane3).not.toMatchSelector(':has(:focus)')
describe "drag and drop", ->
buildDragEvent = (type, files) ->
dataTransfer =
files: files
data: {}
setData: (key, value) -> @data[key] = value
getData: (key) -> @data[key]
event = new CustomEvent("drop")
event.dataTransfer = dataTransfer
event
describe "when a file is dragged to window", ->
it "opens it", ->
spyOn(atom, "open")
event = buildDragEvent("drop", [ {path: "/fake1"}, {path: "/fake2"} ])
pane[0].dispatchEvent(event)
expect(atom.open.callCount).toBe 1
expect(atom.open.argsForCall[0][0]).toEqual pathsToOpen: ['/fake1', '/fake2']
describe "when a non-file is dragged to window", ->
it "does nothing", ->
spyOn(atom, "open")
event = buildDragEvent("drop", [])
pane[0].dispatchEvent(event)
expect(atom.open).not.toHaveBeenCalled()

View File

@@ -66,7 +66,7 @@ describe "Project", ->
runs ->
expect(atom.project.getBuffers().length).toBe 1
deserializedProject = atom.project.testSerialization()
deserializedProject = Project.deserialize(atom.project.serialize())
expect(deserializedProject.getBuffers().length).toBe 0
it "listens for destroyed events on deserialized buffers and removes them when they are destroyed", ->
@@ -75,7 +75,7 @@ describe "Project", ->
runs ->
expect(atom.project.getBuffers().length).toBe 1
deserializedProject = atom.project.testSerialization()
deserializedProject = Project.deserialize(atom.project.serialize())
expect(deserializedProject.getBuffers().length).toBe 1
deserializedProject.getBuffers()[0].destroy()
@@ -91,7 +91,7 @@ describe "Project", ->
runs ->
expect(atom.project.getBuffers().length).toBe 1
fs.mkdirSync(pathToOpen)
deserializedProject = atom.project.testSerialization()
deserializedProject = Project.deserialize(atom.project.serialize())
expect(deserializedProject.getBuffers().length).toBe 0
it "does not deserialize buffers when their path is inaccessible", ->
@@ -104,7 +104,7 @@ describe "Project", ->
runs ->
expect(atom.project.getBuffers().length).toBe 1
fs.chmodSync(pathToOpen, '000')
deserializedProject = atom.project.testSerialization()
deserializedProject = Project.deserialize(atom.project.serialize())
expect(deserializedProject.getBuffers().length).toBe 0
describe "when an editor is saved and the project has no path", ->
@@ -201,18 +201,6 @@ describe "Project", ->
expect(editor.buffer.getPath()).toBeUndefined()
expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer)
it "returns number of read bytes as progress indicator", ->
filePath = atom.project.getDirectories()[0]?.resolve 'a'
totalBytes = 0
promise = atom.project.open(filePath)
promise.progress (bytesRead) -> totalBytes = bytesRead
waitsForPromise ->
promise
runs ->
expect(totalBytes).toBe fs.statSync(filePath).size
describe ".bufferForPath(path)", ->
[buffer] = []
beforeEach ->
@@ -520,36 +508,3 @@ describe "Project", ->
randomPath = path.join("some", "random", "path")
expect(atom.project.contains(randomPath)).toBe false
describe ".eachBuffer(callback)", ->
beforeEach ->
jasmine.snapshotDeprecations()
atom.project.bufferForPathSync('a')
afterEach ->
jasmine.restoreDeprecationsSnapshot()
it "invokes the callback for existing buffer", ->
count = 0
count = 0
callbackBuffer = null
callback = (buffer) ->
callbackBuffer = buffer
count++
atom.project.eachBuffer(callback)
expect(count).toBe 1
expect(callbackBuffer).toBe atom.project.getBuffers()[0]
it "invokes the callback for new buffers", ->
count = 0
callbackBuffer = null
callback = (buffer) ->
callbackBuffer = buffer
count++
atom.project.eachBuffer(callback)
count = 0
callbackBuffer = null
atom.project.bufferForPathSync(require.resolve('./fixtures/sample.txt'))
expect(count).toBe 1
expect(callbackBuffer).toBe atom.project.getBuffers()[1]

View File

@@ -1,212 +0,0 @@
SelectListView = require '../src/select-list-view'
{$, $$} = require '../src/space-pen-extensions'
describe "SelectListView", ->
[selectList, items, list, filterEditorView] = []
beforeEach ->
items = [
["A", "Alpha"], ["B", "Bravo"], ["C", "Charlie"],
["D", "Delta"], ["E", "Echo"], ["F", "Foxtrot"]
]
selectList = new SelectListView
selectList.setMaxItems(4)
selectList.getFilterKey = -> 1
selectList.viewForItem = (item) ->
$$ -> @li item[1], class: item[0]
selectList.confirmed = jasmine.createSpy('confirmed hook')
selectList.cancelled = jasmine.createSpy('cancelled hook')
selectList.setItems(items)
{list, filterEditorView} = selectList
describe "when an array is assigned", ->
it "populates the list with up to maxItems items, based on the liForElement function", ->
expect(list.find('li').length).toBe selectList.maxItems
expect(list.find('li:eq(0)')).toHaveText 'Alpha'
expect(list.find('li:eq(0)')).toHaveClass 'A'
describe "viewForItem(item)", ->
it "allows raw DOM elements to be returned", ->
selectList.viewForItem = (item) ->
li = document.createElement('li')
li.classList.add(item[0])
li.innerText = item[1]
li
selectList.setItems(items)
expect(list.find('li').length).toBe selectList.maxItems
expect(list.find('li:eq(0)')).toHaveText 'Alpha'
expect(list.find('li:eq(0)')).toHaveClass 'A'
expect(selectList.getSelectedItem()).toBe items[0]
it "allows raw HTML to be returned", ->
selectList.viewForItem = (item) ->
"<li>#{item}</li>"
selectList.setItems(['Bermuda', 'Bahama'])
expect(list.find('li:eq(0)')).toHaveText 'Bermuda'
expect(selectList.getSelectedItem()).toBe 'Bermuda'
describe "when the text of the mini editor changes", ->
beforeEach ->
selectList.attachToDom()
it "filters the elements in the list based on the scoreElement function and selects the first item", ->
filterEditorView.getEditor().insertText('la')
window.advanceClock(selectList.inputThrottle)
expect(list.find('li').length).toBe 2
expect(list.find('li:contains(Alpha)')).toExist()
expect(list.find('li:contains(Delta)')).toExist()
expect(list.find('li:first')).toHaveClass 'selected'
expect(selectList.error).not.toBeVisible()
it "displays an error if there are no matches, removes error when there are matches", ->
filterEditorView.getEditor().insertText('nothing will match this')
window.advanceClock(selectList.inputThrottle)
expect(list.find('li').length).toBe 0
expect(selectList.error).not.toBeHidden()
filterEditorView.getEditor().setText('la')
window.advanceClock(selectList.inputThrottle)
expect(list.find('li').length).toBe 2
expect(selectList.error).not.toBeVisible()
it "displays no elements until the array has been set on the list", ->
selectList.items = null
selectList.list.empty()
filterEditorView.getEditor().insertText('la')
window.advanceClock(selectList.inputThrottle)
expect(list.find('li').length).toBe 0
expect(selectList.error).toBeHidden()
selectList.setItems(items)
expect(list.find('li').length).toBe 2
describe "when core:move-up / core:move-down are triggered on the filterEditorView", ->
it "selects the previous / next item in the list, or wraps around to the other side", ->
expect(list.find('li:first')).toHaveClass 'selected'
filterEditorView.trigger 'core:move-up'
expect(list.find('li:first')).not.toHaveClass 'selected'
expect(list.find('li:last')).toHaveClass 'selected'
filterEditorView.trigger 'core:move-down'
expect(list.find('li:first')).toHaveClass 'selected'
expect(list.find('li:last')).not.toHaveClass 'selected'
filterEditorView.trigger 'core:move-down'
expect(list.find('li:eq(0)')).not.toHaveClass 'selected'
expect(list.find('li:eq(1)')).toHaveClass 'selected'
filterEditorView.trigger 'core:move-down'
expect(list.find('li:eq(1)')).not.toHaveClass 'selected'
expect(list.find('li:eq(2)')).toHaveClass 'selected'
filterEditorView.trigger 'core:move-up'
expect(list.find('li:eq(2)')).not.toHaveClass 'selected'
expect(list.find('li:eq(1)')).toHaveClass 'selected'
it "scrolls to keep the selected item in view", ->
selectList.attachToDom()
itemHeight = list.find('li').outerHeight()
list.height(itemHeight * 2)
filterEditorView.trigger 'core:move-down'
filterEditorView.trigger 'core:move-down'
expect(list.scrollBottom()).toBe itemHeight * 3
filterEditorView.trigger 'core:move-down'
expect(list.scrollBottom()).toBe itemHeight * 4
filterEditorView.trigger 'core:move-up'
filterEditorView.trigger 'core:move-up'
expect(list.scrollTop()).toBe itemHeight
describe "the core:confirm event", ->
describe "when there is an item selected (because the list in not empty)", ->
it "triggers the selected hook with the selected array element", ->
filterEditorView.trigger 'core:move-down'
filterEditorView.trigger 'core:move-down'
filterEditorView.trigger 'core:confirm'
expect(selectList.confirmed).toHaveBeenCalledWith(items[2])
describe "when there is no item selected (because the list is empty)", ->
beforeEach ->
selectList.attachToDom()
it "does not trigger the confirmed hook", ->
filterEditorView.getEditor().insertText("i will never match anything")
window.advanceClock(selectList.inputThrottle)
expect(list.find('li')).not.toExist()
filterEditorView.trigger 'core:confirm'
expect(selectList.confirmed).not.toHaveBeenCalled()
it "does trigger the cancelled hook", ->
filterEditorView.getEditor().insertText("i will never match anything")
window.advanceClock(selectList.inputThrottle)
expect(list.find('li')).not.toExist()
filterEditorView.trigger 'core:confirm'
expect(selectList.cancelled).toHaveBeenCalled()
describe "when a list item is clicked", ->
it "selects the item on mousedown and confirms it on mouseup", ->
item = list.find('li:eq(1)')
item.mousedown()
expect(item).toHaveClass 'selected'
item.mouseup()
expect(selectList.confirmed).toHaveBeenCalledWith(items[1])
describe "the core:cancel event", ->
it "triggers the cancelled hook and detaches and empties the select list", ->
spyOn(selectList, 'detach')
filterEditorView.trigger 'core:cancel'
expect(selectList.cancelled).toHaveBeenCalled()
expect(selectList.detach).toHaveBeenCalled()
expect(selectList.list).toBeEmpty()
describe "when the mini editor loses focus", ->
it "triggers the cancelled hook and detaches the select list", ->
spyOn(selectList, 'detach')
filterEditorView.trigger 'blur'
expect(selectList.cancelled).toHaveBeenCalled()
expect(selectList.detach).toHaveBeenCalled()
describe "the core:move-to-top event", ->
it "scrolls to the top, selects the first element, and does not bubble the event", ->
selectList.attachToDom()
moveToTopHandler = jasmine.createSpy("moveToTopHandler")
selectList.parent().on 'core:move-to-top', moveToTopHandler
selectList.trigger 'core:move-down'
expect(list.find('li:eq(1)')).toHaveClass 'selected'
selectList.trigger 'core:move-to-top'
expect(list.find('li:first')).toHaveClass 'selected'
expect(moveToTopHandler).not.toHaveBeenCalled()
describe "the core:move-to-bottom event", ->
it "scrolls to the bottom, selects the last element, and does not bubble the event", ->
selectList.attachToDom()
moveToBottomHandler = jasmine.createSpy("moveToBottomHandler")
selectList.parent().on 'core:move-to-bottom', moveToBottomHandler
expect(list.find('li:first')).toHaveClass 'selected'
selectList.trigger 'core:move-to-bottom'
expect(list.find('li:last')).toHaveClass 'selected'
expect(moveToBottomHandler).not.toHaveBeenCalled()

View File

@@ -56,6 +56,19 @@ describe "Selection", ->
selection.selectToScreenPosition([0, 25])
expect(selection.isReversed()).toBeFalsy()
describe ".selectLine(row)", ->
describe "when passed a row", ->
it "selects the specified row", ->
selection.setBufferRange([[2, 4], [3, 4]])
selection.selectLine(5)
expect(selection.getBufferRange()).toEqual [[5, 0], [6, 0]]
describe "when not passed a row", ->
it "selects all rows spanned by the selection", ->
selection.setBufferRange([[2, 4], [3, 4]])
selection.selectLine()
expect(selection.getBufferRange()).toEqual [[2, 0], [4, 0]]
describe "when only the selection's tail is moved (regression)", ->
it "notifies ::onDidChangeRange observers", ->
selection.setBufferRange([[2, 0], [2, 10]], reversed: true)

View File

@@ -1,41 +0,0 @@
{View, $, $$} = require '../src/space-pen-extensions'
describe "SpacePen extensions", ->
class TestView extends View
@content: -> @div()
[view, parent] = []
beforeEach ->
view = new TestView
parent = $$ -> @div()
parent.append(view)
describe "View.subscribe(eventEmitter, eventName, callback)", ->
[emitter, eventHandler] = []
beforeEach ->
eventHandler = jasmine.createSpy 'eventHandler'
emitter = $$ -> @div()
view.subscribe emitter, 'foo', eventHandler
it "subscribes to the given event emitter and unsubscribes when unsubscribe is called", ->
emitter.trigger "foo"
expect(eventHandler).toHaveBeenCalled()
describe "tooltips", ->
describe "when the window is resized", ->
it "hides the tooltips", ->
class TooltipView extends View
@content: ->
@div()
view = new TooltipView()
view.attachToDom()
view.setTooltip('this is a tip')
view.tooltip('show')
expect($(document.body).find('.tooltip')).toBeVisible()
$(window).trigger('resize')
expect($(document.body).find('.tooltip')).not.toExist()

View File

@@ -10,16 +10,12 @@ fs = require 'fs-plus'
Grim = require 'grim'
KeymapManager = require '../src/keymap-extensions'
# FIXME: Remove jquery from this
{$} = require '../src/space-pen-extensions'
Config = require '../src/config'
{Point} = require 'text-buffer'
Project = require '../src/project'
Workspace = require '../src/workspace'
ServiceHub = require 'service-hub'
TextEditor = require '../src/text-editor'
TextEditorView = require '../src/text-editor-view'
TextEditorElement = require '../src/text-editor-element'
TokenizedBuffer = require '../src/tokenized-buffer'
TextEditorComponent = require '../src/text-editor-component'
@@ -41,7 +37,9 @@ window.addEventListener 'core:close', -> window.close()
window.addEventListener 'beforeunload', ->
atom.storeWindowDimensions()
atom.saveSync()
$('html,body').css('overflow', 'auto')
document.querySelector('html').style.overflow = 'auto'
document.body.style.overflow = 'auto'
# Allow document.title to be assigned in specs without screwing up spec window title
documentTitle = null
@@ -91,7 +89,6 @@ if specDirectory
isCoreSpec = specDirectory is fs.realpathSync(__dirname)
beforeEach ->
$.fx.off = true
documentTitle = null
projectPath = specProjectPath ? path.join(@specDirectory, 'fixtures')
atom.packages.serviceHub = new ServiceHub
@@ -102,7 +99,7 @@ beforeEach ->
atom.styles.restoreSnapshot(styleElementsToRestore)
atom.views.clearDocumentRequests()
atom.workspaceViewParentSelector = '#jasmine-content'
atom.workspaceParentSelectorctor = '#jasmine-content'
window.resetTimeouts()
spyOn(_._, "now").andCallFake -> window.now
@@ -159,8 +156,6 @@ beforeEach ->
spyOn(clipboard, 'writeText').andCallFake (text) -> clipboardContent = text
spyOn(clipboard, 'readText').andCallFake -> clipboardContent
spyOn(atom.packages, 'uninstallAutocompletePlus')
addCustomMatchers(this)
afterEach ->
@@ -171,7 +166,6 @@ afterEach ->
atom.workspace?.destroy()
atom.workspace = null
atom.__workspaceView = null
delete atom.state.workspace
atom.project?.destroy()
@@ -181,7 +175,7 @@ afterEach ->
delete atom.state.packageStates
$('#jasmine-content').empty() unless window.debugContent
document.getElementById('jasmine-content').innerHTML = '' unless window.debugContent
jasmine.unspy(atom, 'saveSync')
ensureNoPathSubscriptions()
@@ -279,43 +273,6 @@ addCustomMatchers = (spec) ->
@message = -> return "Expected element '#{element}' or its descendants#{notText} to show."
element.style.display in ['block', 'inline-block', 'static', 'fixed']
window.keyIdentifierForKey = (key) ->
if key.length > 1 # named key
key
else
charCode = key.toUpperCase().charCodeAt(0)
"U+00" + charCode.toString(16)
window.keydownEvent = (key, properties={}) ->
originalEventProperties = {}
originalEventProperties.ctrl = properties.ctrlKey
originalEventProperties.alt = properties.altKey
originalEventProperties.shift = properties.shiftKey
originalEventProperties.cmd = properties.metaKey
originalEventProperties.target = properties.target?[0] ? properties.target
originalEventProperties.which = properties.which
originalEvent = KeymapManager.buildKeydownEvent(key, originalEventProperties)
properties = $.extend({originalEvent}, properties)
$.Event("keydown", properties)
window.mouseEvent = (type, properties) ->
if properties.point
{point, editorView} = properties
{top, left} = @pagePixelPositionForPoint(editorView, point)
properties.pageX = left + 1
properties.pageY = top + 1
properties.originalEvent ?= {detail: 1}
$.Event type, properties
window.clickEvent = (properties={}) ->
window.mouseEvent("click", properties)
window.mousedownEvent = (properties={}) ->
window.mouseEvent('mousedown', properties)
window.mousemoveEvent = (properties={}) ->
window.mouseEvent('mousemove', properties)
window.waitsForPromise = (args...) ->
if args.length > 1
{shouldReject, timeout} = args[0]
@@ -374,45 +331,3 @@ window.advanceClock = (delta=1) ->
true
callback() for callback in callbacks
window.pagePixelPositionForPoint = (editorView, point) ->
point = Point.fromObject point
top = editorView.renderedLines.offset().top + point.row * editorView.lineHeight
left = editorView.renderedLines.offset().left + point.column * editorView.charWidth - editorView.renderedLines.scrollLeft()
{top, left}
window.tokensText = (tokens) ->
_.pluck(tokens, 'value').join('')
window.setEditorWidthInChars = (editorView, widthInChars, charWidth=editorView.charWidth) ->
editorView.width(charWidth * widthInChars + editorView.gutter.outerWidth())
$(window).trigger 'resize' # update width of editor view's on-screen lines
window.setEditorHeightInLines = (editorView, heightInLines, lineHeight=editorView.lineHeight) ->
editorView.height(editorView.getEditor().getLineHeightInPixels() * heightInLines)
editorView.component?.measureDimensions()
$.fn.resultOfTrigger = (type) ->
event = $.Event(type)
this.trigger(event)
event.result
$.fn.enableKeymap = ->
@on 'keydown', (e) ->
originalEvent = e.originalEvent ? e
Object.defineProperty(originalEvent, 'target', get: -> e.target) unless originalEvent.target?
atom.keymaps.handleKeyboardEvent(originalEvent)
not e.originalEvent.defaultPrevented
$.fn.attachToDom = ->
@appendTo($('#jasmine-content')) unless @isOnDom()
$.fn.simulateDomAttachment = ->
$('<html>').append(this)
$.fn.textInput = (data) ->
this.each ->
event = document.createEvent('TextEvent')
event.initTextEvent('textInput', true, true, window, data)
event = $.event.fix(event)
$(this).trigger(event)

File diff suppressed because it is too large Load Diff

View File

@@ -243,3 +243,61 @@ describe "TextEditorElement", ->
expect(element.hasAttribute('mini')).toBe true
element.getModel().setMini(false)
expect(element.hasAttribute('mini')).toBe false
describe "events", ->
element = null
beforeEach ->
element = new TextEditorElement
element.getModel().setText("lorem\nipsum\ndolor\nsit\namet")
element.setUpdatedSynchronously(true)
element.setHeight(20)
element.setWidth(20)
describe "::onDidChangeScrollTop(callback)", ->
it "triggers even when subscribing before attaching the element", ->
positions = []
subscription1 = element.onDidChangeScrollTop (p) -> positions.push(p)
jasmine.attachToDOM(element)
subscription2 = element.onDidChangeScrollTop (p) -> positions.push(p)
positions.length = 0
element.setScrollTop(10)
expect(positions).toEqual([10, 10])
element.remove()
jasmine.attachToDOM(element)
positions.length = 0
element.setScrollTop(20)
expect(positions).toEqual([20, 20])
subscription1.dispose()
positions.length = 0
element.setScrollTop(30)
expect(positions).toEqual([30])
describe "::onDidChangeScrollLeft(callback)", ->
it "triggers even when subscribing before attaching the element", ->
positions = []
subscription1 = element.onDidChangeScrollLeft (p) -> positions.push(p)
jasmine.attachToDOM(element)
subscription2 = element.onDidChangeScrollLeft (p) -> positions.push(p)
positions.length = 0
element.setScrollLeft(10)
expect(positions).toEqual([10, 10])
element.remove()
jasmine.attachToDOM(element)
positions.length = 0
element.setScrollLeft(20)
expect(positions).toEqual([20, 20])
subscription1.dispose()
positions.length = 0
element.setScrollLeft(30)
expect(positions).toEqual([30])

View File

@@ -4,6 +4,7 @@ TextBuffer = require 'text-buffer'
{Point, Range} = TextBuffer
TextEditor = require '../src/text-editor'
TextEditorPresenter = require '../src/text-editor-presenter'
FakeLinesYardstick = require './fake-lines-yardstick'
describe "TextEditorPresenter", ->
# These `describe` and `it` blocks mirror the structure of the ::state object.
@@ -40,7 +41,9 @@ describe "TextEditorPresenter", ->
scrollTop: 0
scrollLeft: 0
new TextEditorPresenter(params)
presenter = new TextEditorPresenter(params)
presenter.setLinesYardstick(new FakeLinesYardstick(editor, presenter))
presenter
expectValues = (actual, expected) ->
for key, value of expected
@@ -99,6 +102,57 @@ describe "TextEditorPresenter", ->
expect(stateFn(presenter).tiles[12]).toBeUndefined()
it "includes state for tiles containing screen rows to measure", ->
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2)
presenter.setScreenRowsToMeasure([10, 12])
expect(stateFn(presenter).tiles[0]).toBeDefined()
expect(stateFn(presenter).tiles[2]).toBeDefined()
expect(stateFn(presenter).tiles[4]).toBeDefined()
expect(stateFn(presenter).tiles[6]).toBeDefined()
expect(stateFn(presenter).tiles[8]).toBeUndefined()
expect(stateFn(presenter).tiles[10]).toBeDefined()
expect(stateFn(presenter).tiles[12]).toBeDefined()
# clearing additional rows won't trigger a state update
expectNoStateUpdate presenter, -> presenter.clearScreenRowsToMeasure()
expect(stateFn(presenter).tiles[0]).toBeDefined()
expect(stateFn(presenter).tiles[2]).toBeDefined()
expect(stateFn(presenter).tiles[4]).toBeDefined()
expect(stateFn(presenter).tiles[6]).toBeDefined()
expect(stateFn(presenter).tiles[8]).toBeUndefined()
expect(stateFn(presenter).tiles[10]).toBeDefined()
expect(stateFn(presenter).tiles[12]).toBeDefined()
# when another change triggers a state update we remove useless lines
expectStateUpdate presenter, -> presenter.setScrollTop(1)
expect(stateFn(presenter).tiles[0]).toBeDefined()
expect(stateFn(presenter).tiles[2]).toBeDefined()
expect(stateFn(presenter).tiles[4]).toBeDefined()
expect(stateFn(presenter).tiles[6]).toBeDefined()
expect(stateFn(presenter).tiles[8]).toBeDefined()
expect(stateFn(presenter).tiles[10]).toBeUndefined()
expect(stateFn(presenter).tiles[12]).toBeUndefined()
it "excludes invalid tiles for screen rows to measure", ->
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2)
presenter.setScreenRowsToMeasure([20, 30]) # unexisting rows
expect(stateFn(presenter).tiles[0]).toBeDefined()
expect(stateFn(presenter).tiles[2]).toBeDefined()
expect(stateFn(presenter).tiles[4]).toBeDefined()
expect(stateFn(presenter).tiles[6]).toBeDefined()
expect(stateFn(presenter).tiles[8]).toBeUndefined()
expect(stateFn(presenter).tiles[10]).toBeUndefined()
expect(stateFn(presenter).tiles[12]).toBeUndefined()
presenter.setScreenRowsToMeasure([12])
buffer.deleteRows(12, 13)
expect(stateFn(presenter).tiles[12]).toBeUndefined()
it "includes state for all tiles if no external ::explicitHeight is assigned", ->
presenter = buildPresenter(explicitHeight: null, tileSize: 2)
expect(stateFn(presenter).tiles[0]).toBeDefined()
@@ -162,12 +216,13 @@ describe "TextEditorPresenter", ->
expect(stateFn(presenter).tiles[6]).toBeDefined()
expect(stateFn(presenter).tiles[8]).toBeUndefined()
expectStateUpdate presenter, -> presenter.setLineHeight(2)
expectStateUpdate presenter, -> presenter.setLineHeight(4)
expect(stateFn(presenter).tiles[0]).toBeDefined()
expect(stateFn(presenter).tiles[2]).toBeDefined()
expect(stateFn(presenter).tiles[4]).toBeDefined()
expect(stateFn(presenter).tiles[6]).toBeUndefined()
expect(stateFn(presenter).tiles[4]).toBeUndefined()
expect(stateFn(presenter).tiles[6]).toBeDefined()
expect(stateFn(presenter).tiles[8]).toBeUndefined()
it "does not remove out-of-view tiles corresponding to ::mouseWheelScreenRow until ::stoppedScrollingDelay elapses", ->
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2, stoppedScrollingDelay: 200)
@@ -289,15 +344,7 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> presenter.setContentFrameWidth(10 * maxLineLength + 20)
expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 20
it "updates when the ::baseCharacterWidth changes", ->
maxLineLength = editor.getMaxScreenLineLength()
presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10)
expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15)
expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 15 * maxLineLength + 1
it "updates when the scoped character widths change", ->
it "updates when character widths change", ->
waitsForPromise -> atom.packages.activatePackage('language-javascript')
runs ->
@@ -305,7 +352,9 @@ describe "TextEditorPresenter", ->
presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10)
expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1
expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20)
expectStateUpdate presenter, ->
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20)
presenter.characterWidthsChanged()
expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide
it "updates when ::softWrapped changes on the editor", ->
@@ -357,6 +406,18 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> presenter.setScrollLeft(-300)
expect(presenter.getState().horizontalScrollbar.scrollLeft).toBe 0
it "is always 0 when soft wrapping is enabled", ->
presenter = buildPresenter(scrollLeft: 0, verticalScrollbarWidth: 0, contentFrameWidth: 85, baseCharacterWidth: 10)
editor.setSoftWrapped(false)
presenter.setScrollLeft(Infinity)
expect(presenter.getState().content.scrollLeft).toBeGreaterThan 0
editor.setSoftWrapped(true)
expect(presenter.getState().content.scrollLeft).toBe 0
presenter.setScrollLeft(10)
expect(presenter.getState().content.scrollLeft).toBe 0
describe ".verticalScrollbar", ->
describe ".visible", ->
it "is true if the scrollHeight exceeds the computed client height", ->
@@ -504,11 +565,11 @@ describe "TextEditorPresenter", ->
expectValues presenter.getState().hiddenInput, {top: 0, left: 0}
expectStateUpdate presenter, -> editor.setCursorBufferPosition([11, 43])
expectValues presenter.getState().hiddenInput, {top: 11 * 10 - editor.getScrollTop(), left: 43 * 10 - editor.getScrollLeft()}
expectValues presenter.getState().hiddenInput, {top: 11 * 10 - presenter.getScrollTop(), left: 43 * 10 - presenter.getScrollLeft()}
newCursor = null
expectStateUpdate presenter, -> newCursor = editor.addCursorAtBufferPosition([6, 10])
expectValues presenter.getState().hiddenInput, {top: (6 * 10) - editor.getScrollTop(), left: (10 * 10) - editor.getScrollLeft()}
expectValues presenter.getState().hiddenInput, {top: (6 * 10) - presenter.getScrollTop(), left: (10 * 10) - presenter.getScrollLeft()}
expectStateUpdate presenter, -> newCursor.destroy()
expectValues presenter.getState().hiddenInput, {top: 50 - 10, left: 300 - 10}
@@ -536,7 +597,9 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15)
expect(presenter.getState().hiddenInput.width).toBe 15
expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20)
expectStateUpdate presenter, ->
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20)
presenter.characterWidthsChanged()
expect(presenter.getState().hiddenInput.width).toBe 20
it "is 2px at the end of lines", ->
@@ -554,6 +617,7 @@ describe "TextEditorPresenter", ->
advanceClock(100)
expect(presenter.getState().content.scrollingVertically).toBe true
presenter.setScrollTop(10)
presenter.getState() # commits scroll position
advanceClock(100)
expect(presenter.getState().content.scrollingVertically).toBe true
expectStateUpdate presenter, -> advanceClock(100)
@@ -623,15 +687,7 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, -> presenter.setContentFrameWidth(10 * maxLineLength + 20)
expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 20
it "updates when the ::baseCharacterWidth changes", ->
maxLineLength = editor.getMaxScreenLineLength()
presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10)
expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 1
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15)
expect(presenter.getState().content.scrollWidth).toBe 15 * maxLineLength + 1
it "updates when the scoped character widths change", ->
it "updates when character widths change", ->
waitsForPromise -> atom.packages.activatePackage('language-javascript')
runs ->
@@ -639,7 +695,9 @@ describe "TextEditorPresenter", ->
presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10)
expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 1
expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20)
expectStateUpdate presenter, ->
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20)
presenter.characterWidthsChanged()
expect(presenter.getState().content.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide
it "updates when ::softWrapped changes on the editor", ->
@@ -666,12 +724,55 @@ describe "TextEditorPresenter", ->
expect(presenter.getState().content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1
describe ".scrollTop", ->
it "changes based on the scroll operation that was performed last", ->
presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 20)
expect(presenter.getState().content.scrollTop).toBe(0)
presenter.setScrollTop(20)
editor.setCursorBufferPosition([5, 0])
expect(presenter.getState().content.scrollTop).toBe(50)
editor.setCursorBufferPosition([8, 0])
presenter.setScrollTop(10)
expect(presenter.getState().content.scrollTop).toBe(10)
it "corresponds to the passed logical coordinates when building the presenter", ->
presenter = buildPresenter(scrollRow: 4, lineHeight: 10, explicitHeight: 20)
expect(presenter.getState().content.scrollTop).toBe(40)
it "tracks the value of ::scrollTop", ->
presenter = buildPresenter(scrollTop: 10, lineHeight: 10, explicitHeight: 20)
expect(presenter.getState().content.scrollTop).toBe 10
expectStateUpdate presenter, -> presenter.setScrollTop(50)
expect(presenter.getState().content.scrollTop).toBe 50
it "keeps the model up to date with the corresponding logical coordinates", ->
presenter = buildPresenter(scrollTop: 0, explicitHeight: 20, horizontalScrollbarHeight: 10, lineHeight: 10)
expectStateUpdate presenter, -> presenter.setScrollTop(50)
presenter.getState() # commits scroll position
expect(editor.getScrollRow()).toBe(5)
expectStateUpdate presenter, -> presenter.setScrollTop(57)
presenter.getState() # commits scroll position
expect(editor.getScrollRow()).toBe(6)
it "reassigns the scrollTop if it exceeds the max possible value after lines are removed", ->
presenter = buildPresenter(scrollTop: 80, lineHeight: 10, explicitHeight: 50, horizontalScrollbarHeight: 0)
expect(presenter.getState().content.scrollTop).toBe(80)
buffer.deleteRows(10, 9, 8)
expect(presenter.getState().content.scrollTop).toBe(60)
it "is always rounded to the nearest integer", ->
presenter = buildPresenter(scrollTop: 10, lineHeight: 10, explicitHeight: 20)
expect(presenter.getState().content.scrollTop).toBe 10
expectStateUpdate presenter, -> presenter.setScrollTop(11.4)
expect(presenter.getState().content.scrollTop).toBe 11
expectStateUpdate presenter, -> presenter.setScrollTop(12.6)
expect(presenter.getState().content.scrollTop).toBe 13
it "scrolls down automatically when the model is changed", ->
presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 20)
@@ -683,23 +784,40 @@ describe "TextEditorPresenter", ->
expect(presenter.getState().content.scrollTop).toBe(10)
it "never exceeds the computed scroll height minus the computed client height", ->
didChangeScrollTopSpy = jasmine.createSpy()
presenter = buildPresenter(scrollTop: 10, lineHeight: 10, explicitHeight: 50, horizontalScrollbarHeight: 10)
presenter.onDidChangeScrollTop(didChangeScrollTopSpy)
expectStateUpdate presenter, -> presenter.setScrollTop(100)
expect(presenter.getState().content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight
expect(presenter.getRealScrollTop()).toBe presenter.scrollHeight - presenter.clientHeight
expect(didChangeScrollTopSpy).toHaveBeenCalledWith presenter.scrollHeight - presenter.clientHeight
didChangeScrollTopSpy.reset()
expectStateUpdate presenter, -> presenter.setExplicitHeight(60)
expect(presenter.getState().content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight
expect(presenter.getRealScrollTop()).toBe presenter.scrollHeight - presenter.clientHeight
expect(didChangeScrollTopSpy).toHaveBeenCalledWith presenter.scrollHeight - presenter.clientHeight
didChangeScrollTopSpy.reset()
expectStateUpdate presenter, -> presenter.setHorizontalScrollbarHeight(5)
expect(presenter.getState().content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight
expect(presenter.getRealScrollTop()).toBe presenter.scrollHeight - presenter.clientHeight
expect(didChangeScrollTopSpy).toHaveBeenCalledWith presenter.scrollHeight - presenter.clientHeight
didChangeScrollTopSpy.reset()
expectStateUpdate presenter, -> editor.getBuffer().delete([[8, 0], [12, 0]])
expect(presenter.getState().content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight
expect(presenter.getRealScrollTop()).toBe presenter.scrollHeight - presenter.clientHeight
expect(didChangeScrollTopSpy).toHaveBeenCalledWith presenter.scrollHeight - presenter.clientHeight
# Scroll top only gets smaller when needed as dimensions change, never bigger
scrollTopBefore = presenter.getState().verticalScrollbar.scrollTop
didChangeScrollTopSpy.reset()
expectStateUpdate presenter, -> editor.getBuffer().insert([9, Infinity], '\n\n\n')
expect(presenter.getState().content.scrollTop).toBe scrollTopBefore
expect(presenter.getRealScrollTop()).toBe scrollTopBefore
expect(didChangeScrollTopSpy).not.toHaveBeenCalled()
it "never goes negative", ->
presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10)
@@ -719,30 +837,84 @@ describe "TextEditorPresenter", ->
expect(presenter.getState().content.scrollTop).toBe presenter.contentHeight - presenter.clientHeight
describe ".scrollLeft", ->
it "changes based on the scroll operation that was performed last", ->
presenter = buildPresenter(scrollLeft: 0, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 10)
expect(presenter.getState().content.scrollLeft).toBe(0)
presenter.setScrollLeft(20)
editor.setCursorBufferPosition([0, 9])
expect(presenter.getState().content.scrollLeft).toBe(90)
editor.setCursorBufferPosition([0, 18])
presenter.setScrollLeft(50)
expect(presenter.getState().content.scrollLeft).toBe(50)
it "corresponds to the passed logical coordinates when building the presenter", ->
presenter = buildPresenter(scrollColumn: 3, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500)
expect(presenter.getState().content.scrollLeft).toBe(30)
it "tracks the value of ::scrollLeft", ->
presenter = buildPresenter(scrollLeft: 10, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500)
expect(presenter.getState().content.scrollLeft).toBe 10
expectStateUpdate presenter, -> presenter.setScrollLeft(50)
expect(presenter.getState().content.scrollLeft).toBe 50
it "never exceeds the computed scrollWidth minus the computed clientWidth", ->
it "keeps the model up to date with the corresponding logical coordinates", ->
presenter = buildPresenter(scrollLeft: 0, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500)
expectStateUpdate presenter, -> presenter.setScrollLeft(50)
presenter.getState() # commits scroll position
expect(editor.getScrollColumn()).toBe(5)
expectStateUpdate presenter, -> presenter.setScrollLeft(57)
presenter.getState() # commits scroll position
expect(editor.getScrollColumn()).toBe(6)
it "is always rounded to the nearest integer", ->
presenter = buildPresenter(scrollLeft: 10, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500)
expect(presenter.getState().content.scrollLeft).toBe 10
expectStateUpdate presenter, -> presenter.setScrollLeft(11.4)
expect(presenter.getState().content.scrollLeft).toBe 11
expectStateUpdate presenter, -> presenter.setScrollLeft(12.6)
expect(presenter.getState().content.scrollLeft).toBe 13
it "never exceeds the computed scrollWidth minus the computed clientWidth", ->
didChangeScrollLeftSpy = jasmine.createSpy()
presenter = buildPresenter(scrollLeft: 10, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500)
presenter.onDidChangeScrollLeft(didChangeScrollLeftSpy)
expectStateUpdate presenter, -> presenter.setScrollLeft(300)
expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth
expect(presenter.getRealScrollLeft()).toBe presenter.scrollWidth - presenter.clientWidth
expect(didChangeScrollLeftSpy).toHaveBeenCalledWith presenter.scrollWidth - presenter.clientWidth
didChangeScrollLeftSpy.reset()
expectStateUpdate presenter, -> presenter.setContentFrameWidth(600)
expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth
expect(presenter.getRealScrollLeft()).toBe presenter.scrollWidth - presenter.clientWidth
expect(didChangeScrollLeftSpy).toHaveBeenCalledWith presenter.scrollWidth - presenter.clientWidth
didChangeScrollLeftSpy.reset()
expectStateUpdate presenter, -> presenter.setVerticalScrollbarWidth(5)
expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth
expect(presenter.getRealScrollLeft()).toBe presenter.scrollWidth - presenter.clientWidth
expect(didChangeScrollLeftSpy).toHaveBeenCalledWith presenter.scrollWidth - presenter.clientWidth
didChangeScrollLeftSpy.reset()
expectStateUpdate presenter, -> editor.getBuffer().delete([[6, 0], [6, Infinity]])
expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth
expect(presenter.getRealScrollLeft()).toBe presenter.scrollWidth - presenter.clientWidth
expect(didChangeScrollLeftSpy).toHaveBeenCalledWith presenter.scrollWidth - presenter.clientWidth
# Scroll top only gets smaller when needed as dimensions change, never bigger
scrollLeftBefore = presenter.getState().content.scrollLeft
didChangeScrollLeftSpy.reset()
expectStateUpdate presenter, -> editor.getBuffer().insert([6, 0], new Array(100).join('x'))
expect(presenter.getState().content.scrollLeft).toBe scrollLeftBefore
expect(presenter.getRealScrollLeft()).toBe scrollLeftBefore
expect(didChangeScrollLeftSpy).not.toHaveBeenCalled()
it "never goes negative", ->
presenter = buildPresenter(scrollLeft: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500)
@@ -851,7 +1023,6 @@ describe "TextEditorPresenter", ->
firstNonWhitespaceIndex: line3.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line3.firstTrailingWhitespaceIndex
invisibles: line3.invisibles
top: 0
}
line4 = editor.tokenizedLineForScreenRow(4)
@@ -863,7 +1034,6 @@ describe "TextEditorPresenter", ->
firstNonWhitespaceIndex: line4.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line4.firstTrailingWhitespaceIndex
invisibles: line4.invisibles
top: 1
}
line5 = editor.tokenizedLineForScreenRow(5)
@@ -875,7 +1045,6 @@ describe "TextEditorPresenter", ->
firstNonWhitespaceIndex: line5.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line5.firstTrailingWhitespaceIndex
invisibles: line5.invisibles
top: 2
}
line6 = editor.tokenizedLineForScreenRow(6)
@@ -887,7 +1056,6 @@ describe "TextEditorPresenter", ->
firstNonWhitespaceIndex: line6.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line6.firstTrailingWhitespaceIndex
invisibles: line6.invisibles
top: 0
}
line7 = editor.tokenizedLineForScreenRow(7)
@@ -899,7 +1067,6 @@ describe "TextEditorPresenter", ->
firstNonWhitespaceIndex: line7.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line7.firstTrailingWhitespaceIndex
invisibles: line7.invisibles
top: 1
}
line8 = editor.tokenizedLineForScreenRow(8)
@@ -911,7 +1078,6 @@ describe "TextEditorPresenter", ->
firstNonWhitespaceIndex: line8.firstNonWhitespaceIndex
firstTrailingWhitespaceIndex: line8.firstTrailingWhitespaceIndex
invisibles: line8.invisibles
top: 2
}
expect(lineStateForScreenRow(presenter, 9)).toBeUndefined()
@@ -1179,13 +1345,6 @@ describe "TextEditorPresenter", ->
expect(stateForCursor(presenter, 3)).toEqual {top: 5, left: 12 * 10, width: 10, height: 5}
expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 5 - 20, left: 4 * 10, width: 10, height: 5}
it "updates when ::baseCharacterWidth changes", ->
editor.setCursorBufferPosition([2, 4])
presenter = buildPresenter(explicitHeight: 20, scrollTop: 20)
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(20)
expect(stateForCursor(presenter, 0)).toEqual {top: 0, left: 4 * 20, width: 20, height: 10}
it "updates when scoped character widths change", ->
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
@@ -1194,10 +1353,14 @@ describe "TextEditorPresenter", ->
editor.setCursorBufferPosition([1, 4])
presenter = buildPresenter(explicitHeight: 20)
expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'v', 20)
expectStateUpdate presenter, ->
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'v', 20)
presenter.characterWidthsChanged()
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 10, height: 10}
expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20)
expectStateUpdate presenter, ->
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20)
presenter.characterWidthsChanged()
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 20, height: 10}
it "updates when cursors are added, moved, hidden, shown, or destroyed", ->
@@ -1513,21 +1676,6 @@ describe "TextEditorPresenter", ->
]
}
it "updates when ::baseCharacterWidth changes", ->
editor.setSelectedBufferRanges([
[[2, 2], [2, 4]],
])
presenter = buildPresenter(explicitHeight: 20, scrollTop: 0, tileSize: 2)
expectValues stateForSelectionInTile(presenter, 0, 2), {
regions: [{top: 0, left: 2 * 10, width: 2 * 10, height: 10}]
}
expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(20)
expectValues stateForSelectionInTile(presenter, 0, 2), {
regions: [{top: 0, left: 2 * 20, width: 2 * 20, height: 10}]
}
it "updates when scoped character widths change", ->
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
@@ -1542,7 +1690,9 @@ describe "TextEditorPresenter", ->
expectValues stateForSelectionInTile(presenter, 0, 2), {
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
}
expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'keyword.control.js'], 'i', 20)
expectStateUpdate presenter, ->
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'keyword.control.js'], 'i', 20)
presenter.characterWidthsChanged()
expectValues stateForSelectionInTile(presenter, 0, 2), {
regions: [{top: 0, left: 4 * 10, width: 20 + 10, height: 10}]
}
@@ -1695,7 +1845,7 @@ describe "TextEditorPresenter", ->
pixelPosition: {top: 3 * 10 - presenter.state.content.scrollTop, left: 13 * 10}
}
it "updates when ::baseCharacterWidth changes", ->
it "updates when character widths changes", ->
scrollTop = 20
marker = editor.markBufferPosition([2, 13], invalidate: 'touch')
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
@@ -1821,7 +1971,8 @@ describe "TextEditorPresenter", ->
expectStateUpdate presenter, ->
editor.insertNewline()
editor.setScrollTop(scrollTop) # I'm fighting the editor
presenter.setScrollTop(scrollTop) # I'm fighting the editor
expectValues stateForOverlay(presenter, decoration), {
item: item
pixelPosition: {top: 6 * 10 - scrollTop - itemHeight, left: gutterWidth}
@@ -1923,6 +2074,24 @@ describe "TextEditorPresenter", ->
}
describe ".height", ->
it "updates model's rows per page when it changes", ->
presenter = buildPresenter(explicitHeight: 50, lineHeightInPixels: 10, horizontalScrollbarHeight: 10)
presenter.getState() # trigger state update
expect(editor.getRowsPerPage()).toBe(4)
presenter.setExplicitHeight(100)
presenter.getState() # trigger state update
expect(editor.getRowsPerPage()).toBe(9)
presenter.setHorizontalScrollbarHeight(0)
presenter.getState() # trigger state update
expect(editor.getRowsPerPage()).toBe(10)
presenter.setLineHeight(5)
presenter.getState() # trigger state update
expect(editor.getRowsPerPage()).toBe(20)
it "tracks the computed content height if ::autoHeight is true so the editor auto-expands vertically", ->
presenter = buildPresenter(explicitHeight: null, autoHeight: true)
expect(presenter.getState().height).toBe editor.getScreenLineCount() * 10
@@ -2035,15 +2204,10 @@ describe "TextEditorPresenter", ->
lineNumberStateForScreenRow = (presenter, screenRow) ->
editor = presenter.model
tileRow = presenter.tileForRow(screenRow)
bufferRow = editor.bufferRowForScreenRow(screenRow)
wrapCount = screenRow - editor.screenRowForBufferRow(bufferRow)
if wrapCount > 0
key = bufferRow + '-' + wrapCount
else
key = bufferRow
line = editor.tokenizedLineForScreenRow(screenRow)
gutterState = getLineNumberGutterState(presenter)
gutterState.content.tiles[tileRow]?.lineNumbers[key]
gutterState.content.tiles[tileRow]?.lineNumbers[line?.id]
tiledContentContract (presenter) -> getLineNumberGutterState(presenter).content
@@ -2055,12 +2219,12 @@ describe "TextEditorPresenter", ->
presenter = buildPresenter(explicitHeight: 25, scrollTop: 30, lineHeight: 10, tileSize: 2)
expect(lineNumberStateForScreenRow(presenter, 1)).toBeUndefined()
expectValues lineNumberStateForScreenRow(presenter, 2), {screenRow: 2, bufferRow: 2, softWrapped: false, top: 0 * 10}
expectValues lineNumberStateForScreenRow(presenter, 3), {screenRow: 3, bufferRow: 3, softWrapped: false, top: 1 * 10}
expectValues lineNumberStateForScreenRow(presenter, 4), {screenRow: 4, bufferRow: 3, softWrapped: true, top: 0 * 10}
expectValues lineNumberStateForScreenRow(presenter, 5), {screenRow: 5, bufferRow: 4, softWrapped: false, top: 1 * 10}
expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 7, softWrapped: false, top: 0 * 10}
expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 8, softWrapped: false, top: 1 * 10}
expectValues lineNumberStateForScreenRow(presenter, 2), {screenRow: 2, bufferRow: 2, softWrapped: false}
expectValues lineNumberStateForScreenRow(presenter, 3), {screenRow: 3, bufferRow: 3, softWrapped: false}
expectValues lineNumberStateForScreenRow(presenter, 4), {screenRow: 4, bufferRow: 3, softWrapped: true}
expectValues lineNumberStateForScreenRow(presenter, 5), {screenRow: 5, bufferRow: 4, softWrapped: false}
expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 7, softWrapped: false}
expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 8, softWrapped: false}
expect(lineNumberStateForScreenRow(presenter, 8)).toBeUndefined()
it "updates when the editor's content changes", ->
@@ -2103,6 +2267,12 @@ describe "TextEditorPresenter", ->
expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 3, softWrapped: true}
expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 4, softWrapped: false}
presenter.setContentFrameWidth(500)
expectValues lineNumberStateForScreenRow(presenter, 5), {screenRow: 5, bufferRow: 4, softWrapped: false}
expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 5, softWrapped: false}
expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 6, softWrapped: false}
describe ".decorationClasses", ->
it "adds decoration classes to the relevant line number state objects, both initially and when decorations change", ->
marker1 = editor.markBufferRange([[4, 0], [6, 2]], invalidate: 'touch', maintainHistory: true)

View File

@@ -31,7 +31,7 @@ describe "TextEditor", ->
runs ->
fs.mkdirSync(pathToOpen)
expect(editor1.testSerialization()).toBeUndefined()
expect(TextEditor.deserialize(editor1.serialize())).toBeUndefined()
it "restores selections and folds based on markers in the buffer", ->
editor.setSelectedBufferRange([[1, 2], [3, 4]])
@@ -39,7 +39,7 @@ describe "TextEditor", ->
editor.foldBufferRow(4)
expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
editor2 = editor.testSerialization()
editor2 = TextEditor.deserialize(editor.serialize())
expect(editor2.id).toBe editor.id
expect(editor2.getBuffer().getPath()).toBe editor.getBuffer().getPath()
@@ -52,7 +52,7 @@ describe "TextEditor", ->
atom.config.set('editor.showInvisibles', true)
previousInvisibles = editor.tokenizedLineForScreenRow(0).invisibles
editor2 = editor.testSerialization()
editor2 = TextEditor.deserialize(editor.serialize())
expect(previousInvisibles).toBeDefined()
expect(editor2.displayBuffer.tokenizedLineForScreenRow(0).invisibles).toEqual previousInvisibles
@@ -909,118 +909,6 @@ describe "TextEditor", ->
cursor2 = editor.addCursorAtBufferPosition([1, 4])
expect(cursor2.marker).toBe cursor1.marker
describe "autoscroll", ->
beforeEach ->
editor.setVerticalScrollMargin(2)
editor.setHorizontalScrollMargin(2)
editor.setLineHeightInPixels(10)
editor.setDefaultCharWidth(10)
editor.setHorizontalScrollbarHeight(0)
editor.setHeight(5.5 * 10)
editor.setWidth(5.5 * 10)
it "scrolls down when the last cursor gets closer than ::verticalScrollMargin to the bottom of the editor", ->
expect(editor.getScrollTop()).toBe 0
expect(editor.getScrollBottom()).toBe 5.5 * 10
editor.setCursorScreenPosition([2, 0])
expect(editor.getScrollBottom()).toBe 5.5 * 10
editor.moveDown()
expect(editor.getScrollBottom()).toBe 6 * 10
editor.moveDown()
expect(editor.getScrollBottom()).toBe 7 * 10
it "scrolls up when the last cursor gets closer than ::verticalScrollMargin to the top of the editor", ->
editor.setCursorScreenPosition([11, 0])
editor.setScrollBottom(editor.getScrollHeight())
editor.moveUp()
expect(editor.getScrollBottom()).toBe editor.getScrollHeight()
editor.moveUp()
expect(editor.getScrollTop()).toBe 7 * 10
editor.moveUp()
expect(editor.getScrollTop()).toBe 6 * 10
it "scrolls right when the last cursor gets closer than ::horizontalScrollMargin to the right of the editor", ->
expect(editor.getScrollLeft()).toBe 0
expect(editor.getScrollRight()).toBe 5.5 * 10
editor.setCursorScreenPosition([0, 2])
expect(editor.getScrollRight()).toBe 5.5 * 10
editor.moveRight()
expect(editor.getScrollRight()).toBe 6 * 10
editor.moveRight()
expect(editor.getScrollRight()).toBe 7 * 10
it "scrolls left when the last cursor gets closer than ::horizontalScrollMargin to the left of the editor", ->
editor.setScrollRight(editor.getScrollWidth())
expect(editor.getScrollRight()).toBe editor.getScrollWidth()
editor.setCursorScreenPosition([6, 62], autoscroll: false)
editor.moveLeft()
expect(editor.getScrollLeft()).toBe 59 * 10
editor.moveLeft()
expect(editor.getScrollLeft()).toBe 58 * 10
it "scrolls down when inserting lines makes the document longer than the editor's height", ->
editor.setCursorScreenPosition([13, Infinity])
editor.insertNewline()
expect(editor.getScrollBottom()).toBe 14 * 10
editor.insertNewline()
expect(editor.getScrollBottom()).toBe 15 * 10
it "autoscrolls to the cursor when it moves due to undo", ->
editor.insertText('abc')
editor.setScrollTop(Infinity)
editor.undo()
expect(editor.getScrollTop()).toBe 0
it "doesn't scroll when the cursor moves into the visible area", ->
editor.setCursorBufferPosition([0, 0])
editor.setScrollTop(40)
expect(editor.getVisibleRowRange()).toEqual([4, 9])
editor.setCursorBufferPosition([6, 0])
expect(editor.getScrollTop()).toBe 40
it "honors the autoscroll option on cursor and selection manipulation methods", ->
expect(editor.getScrollTop()).toBe 0
editor.addCursorAtScreenPosition([11, 11], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.addCursorAtBufferPosition([11, 11], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.setCursorScreenPosition([11, 11], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.setCursorBufferPosition([11, 11], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.addSelectionForBufferRange([[11, 11], [11, 11]], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.addSelectionForScreenRange([[11, 11], [11, 12]], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.setSelectedBufferRange([[11, 0], [11, 1]], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.setSelectedScreenRange([[11, 0], [11, 6]], autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.clearSelections(autoscroll: false)
expect(editor.getScrollTop()).toBe 0
editor.addSelectionForScreenRange([[0, 0], [0, 4]])
editor.getCursors()[0].setScreenPosition([11, 11], autoscroll: true)
expect(editor.getScrollTop()).toBeGreaterThan 0
editor.getCursors()[0].setBufferPosition([0, 0], autoscroll: true)
expect(editor.getScrollTop()).toBe 0
editor.getSelections()[0].setScreenRange([[11, 0], [11, 4]], autoscroll: true)
expect(editor.getScrollTop()).toBeGreaterThan 0
editor.getSelections()[0].setBufferRange([[0, 0], [0, 4]], autoscroll: true)
expect(editor.getScrollTop()).toBe 0
describe '.logCursorScope()', ->
beforeEach ->
spyOn(atom.notifications, 'addInfo')
@@ -1284,7 +1172,7 @@ describe "TextEditor", ->
expect(selection2.isReversed()).toBeFalsy()
describe ".selectLinesContainingCursors()", ->
it "selects the entire line (including newlines) at given row", ->
it "selects to the entire line (including newlines) at given row", ->
editor.setCursorScreenPosition([1, 2])
editor.selectLinesContainingCursors()
expect(editor.getSelectedBufferRange()).toEqual [[1, 0], [2, 0]]
@@ -1299,19 +1187,12 @@ describe "TextEditor", ->
editor.selectLinesContainingCursors()
expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [2, 0]]
it "autoscrolls to the selection", ->
editor.setLineHeightInPixels(10)
editor.setDefaultCharWidth(10)
editor.setHeight(50)
editor.setWidth(50)
editor.setHorizontalScrollbarHeight(0)
editor.setCursorScreenPosition([5, 6])
editor.scrollToTop()
expect(editor.getScrollTop()).toBe 0
editor.selectLinesContainingCursors()
expect(editor.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10
describe "when the selection spans multiple row", ->
it "selects from the beginning of the first line to the last line", ->
selection = editor.getLastSelection()
selection.setBufferRange [[1, 10], [3, 20]]
editor.selectLinesContainingCursors()
expect(editor.getSelectedBufferRange()).toEqual [[1, 0], [4, 0]]
describe ".selectToBeginningOfWord()", ->
it "selects text from cusor position to beginning of word", ->
@@ -1565,30 +1446,6 @@ describe "TextEditor", ->
expect(selection1).toBe selection
expect(selection1.getScreenRange()).toEqual [[2, 2], [3, 4]]
describe ".setSelectedBufferRange(range)", ->
it "autoscrolls the selection if it is last unless the 'autoscroll' option is false", ->
editor.setVerticalScrollMargin(2)
editor.setHorizontalScrollMargin(2)
editor.setLineHeightInPixels(10)
editor.setDefaultCharWidth(10)
editor.setHeight(70)
editor.setWidth(100)
editor.setHorizontalScrollbarHeight(0)
expect(editor.getScrollTop()).toBe 0
editor.setSelectedBufferRange([[5, 6], [6, 8]])
expect(editor.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10
expect(editor.getScrollRight()).toBe (8 + editor.getHorizontalScrollMargin()) * 10
editor.setSelectedBufferRange([[0, 0], [0, 0]])
expect(editor.getScrollTop()).toBe 0
expect(editor.getScrollLeft()).toBe 0
editor.setSelectedBufferRange([[6, 6], [6, 8]])
expect(editor.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10
expect(editor.getScrollRight()).toBe (8 + editor.getHorizontalScrollMargin()) * 10
describe ".selectMarker(marker)", ->
describe "if the marker is valid", ->
it "selects the marker's range and returns the selected range", ->
@@ -1608,17 +1465,6 @@ describe "TextEditor", ->
editor.addSelectionForBufferRange([[3, 4], [5, 6]])
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 0]], [[3, 4], [5, 6]]]
it "autoscrolls to the added selection if needed", ->
editor.setVerticalScrollMargin(2)
editor.setHorizontalScrollMargin(2)
editor.setLineHeightInPixels(10)
editor.setDefaultCharWidth(10)
editor.setHeight(80)
editor.setWidth(100)
editor.addSelectionForBufferRange([[8, 10], [8, 15]])
expect(editor.getScrollBottom()).toBe (9 * 10) + (2 * 10)
expect(editor.getScrollRight()).toBe (15 * 10) + (2 * 10)
describe ".addSelectionBelow()", ->
describe "when the selection is non-empty", ->
it "selects the same region of the line below current selections if possible", ->
@@ -1883,7 +1729,7 @@ describe "TextEditor", ->
expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 3]]]
describe ".consolidateSelections()", ->
it "destroys all selections but the most recent, returning true if any selections were destroyed", ->
it "destroys all selections but the least recent, returning true if any selections were destroyed", ->
editor.setSelectedBufferRange([[3, 16], [3, 21]])
selection1 = editor.getLastSelection()
selection2 = editor.addSelectionForBufferRange([[3, 25], [3, 34]])
@@ -1891,10 +1737,10 @@ describe "TextEditor", ->
expect(editor.getSelections()).toEqual [selection1, selection2, selection3]
expect(editor.consolidateSelections()).toBeTruthy()
expect(editor.getSelections()).toEqual [selection3]
expect(selection3.isEmpty()).toBeFalsy()
expect(editor.getSelections()).toEqual [selection1]
expect(selection1.isEmpty()).toBeFalsy()
expect(editor.consolidateSelections()).toBeFalsy()
expect(editor.getSelections()).toEqual [selection3]
expect(editor.getSelections()).toEqual [selection1]
describe "when the cursor is moved while there is a selection", ->
makeSelection = -> selection.setBufferRange [[1, 2], [1, 5]]
@@ -2267,16 +2113,6 @@ describe "TextEditor", ->
expect(cursor1.getBufferPosition()).toEqual [1, 5]
expect(cursor2.getBufferPosition()).toEqual [2, 7]
it "autoscrolls to the last cursor", ->
editor.setCursorScreenPosition([1, 2])
editor.addCursorAtScreenPosition([10, 4])
editor.setLineHeightInPixels(10)
editor.setHeight(50)
expect(editor.getScrollTop()).toBe 0
editor.insertText('a')
expect(editor.getScrollTop()).toBe 80
describe "when there are multiple non-empty selections", ->
describe "when the selections are on the same line", ->
it "replaces each selection range with the inserted characters", ->
@@ -3190,6 +3026,32 @@ describe "TextEditor", ->
expect(buffer.lineForRow(3)).toBe ' var pivot = item'
expect(atom.clipboard.read()).toBe ' <= 1) ret\ns.shift(), current, left = [], right = [];'
describe ".cutToEndOfBufferLine()", ->
beforeEach ->
editor.setSoftWrapped(true)
editor.setEditorWidthInChars(10)
describe "when nothing is selected", ->
it "cuts up to the end of the buffer line", ->
editor.setCursorBufferPosition([2, 20])
editor.addCursorAtBufferPosition([3, 20])
editor.cutToEndOfBufferLine()
expect(buffer.lineForRow(2)).toBe ' if (items.length'
expect(buffer.lineForRow(3)).toBe ' var pivot = item'
expect(atom.clipboard.read()).toBe ' <= 1) return items;\ns.shift(), current, left = [], right = [];'
describe "when text is selected", ->
it "only cuts the selected text, not to the end of the buffer line", ->
editor.setSelectedBufferRanges([[[2, 20], [2, 30]], [[3, 20], [3, 20]]])
editor.cutToEndOfBufferLine()
expect(buffer.lineForRow(2)).toBe ' if (items.lengthurn items;'
expect(buffer.lineForRow(3)).toBe ' var pivot = item'
expect(atom.clipboard.read()).toBe ' <= 1) ret\ns.shift(), current, left = [], right = [];'
describe ".copySelectedText()", ->
it "copies selected text onto the clipboard", ->
editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]], [[2, 8], [2, 13]]])
@@ -4120,19 +3982,6 @@ describe "TextEditor", ->
runs ->
expect(editor.softTabs).toBe false
it "uses hard tabs in Makefile files", ->
# FIXME remove once this is handled by a scoped setting in the
# language-make package
waitsForPromise ->
atom.packages.activatePackage('language-make')
waitsForPromise ->
atom.project.open('Makefile').then (o) -> editor = o
runs ->
expect(editor.softTabs).toBe false
describe "when editor.tabType is 'hard'", ->
beforeEach ->
atom.config.set('editor.tabType', 'hard')
@@ -4812,30 +4661,10 @@ describe "TextEditor", ->
editor.normalizeTabsInBufferRange([[0, 0], [Infinity, Infinity]])
expect(editor.getText()).toBe ' '
describe ".scrollToCursorPosition()", ->
it "scrolls the last cursor into view, centering around the cursor if possible and the 'center' option isn't false", ->
editor.setCursorScreenPosition([8, 8])
editor.setLineHeightInPixels(10)
editor.setDefaultCharWidth(10)
editor.setHeight(60)
editor.setWidth(130)
editor.setHorizontalScrollbarHeight(0)
expect(editor.getScrollTop()).toBe 0
expect(editor.getScrollLeft()).toBe 0
editor.scrollToCursorPosition()
expect(editor.getScrollTop()).toBe (8.5 * 10) - 30
expect(editor.getScrollBottom()).toBe (8.5 * 10) + 30
expect(editor.getScrollRight()).toBe (9 + editor.getHorizontalScrollMargin()) * 10
editor.setScrollTop(0)
editor.scrollToCursorPosition(center: false)
expect(editor.getScrollBottom()).toBe (9 + editor.getVerticalScrollMargin()) * 10
describe ".pageUp/Down()", ->
it "moves the cursor down one page length", ->
editor.setLineHeightInPixels(10)
editor.setHeight(50)
editor.setRowsPerPage(5)
expect(editor.getCursorBufferPosition().row).toBe 0
editor.pageDown()
@@ -4852,9 +4681,8 @@ describe "TextEditor", ->
describe ".selectPageUp/Down()", ->
it "selects one screen height of text up or down", ->
editor.setLineHeightInPixels(10)
editor.setHeight(50)
expect(editor.getScrollHeight()).toBe 130
editor.setRowsPerPage(5)
expect(editor.getCursorBufferPosition().row).toBe 0
editor.selectPageDown()
@@ -5244,18 +5072,6 @@ describe "TextEditor", ->
beforeEach ->
marker = editor.markBufferRange([[1, 0], [1, 0]])
it "casts 'gutter' type to 'line-number' unless a gutter name is specified.", ->
jasmine.snapshotDeprecations()
lineNumberDecoration = editor.decorateMarker(marker, {type: 'gutter'})
customGutterDecoration = editor.decorateMarker(marker, {type: 'gutter', gutterName: 'custom'})
expect(lineNumberDecoration.getProperties().type).toBe 'line-number'
expect(lineNumberDecoration.getProperties().gutterName).toBe 'line-number'
expect(customGutterDecoration.getProperties().type).toBe 'gutter'
expect(customGutterDecoration.getProperties().gutterName).toBe 'custom'
jasmine.restoreDeprecationsSnapshot()
it 'reflects an added decoration when one of its custom gutters is decorated.', ->
gutter = editor.addGutter {'name': 'custom-gutter'}
decoration = gutter.decorateMarker marker, {class: 'custom-class'}

View File

@@ -1,22 +1,18 @@
path = require 'path'
{$, $$} = require '../src/space-pen-extensions'
fs = require 'fs-plus'
temp = require 'temp'
ThemeManager = require '../src/theme-manager'
Package = require '../src/package'
describe "ThemeManager", ->
themeManager = null
describe "atom.themes", ->
resourcePath = atom.getLoadSettings().resourcePath
configDirPath = atom.getConfigDirPath()
beforeEach ->
themeManager = new ThemeManager({packageManager: atom.packages, resourcePath, configDirPath})
spyOn(console, 'warn')
afterEach ->
themeManager.deactivateThemes()
atom.themes.deactivateThemes()
describe "theme getters and setters", ->
beforeEach ->
@@ -26,19 +22,20 @@ describe "ThemeManager", ->
afterEach ->
jasmine.restoreDeprecationsSnapshot()
it 'getLoadedThemes get all the loaded themes', ->
themes = themeManager.getLoadedThemes()
expect(themes.length).toBeGreaterThan(2)
describe 'getLoadedThemes', ->
it 'gets all the loaded themes', ->
themes = atom.themes.getLoadedThemes()
expect(themes.length).toBeGreaterThan(2)
it 'getActiveThemes get all the active themes', ->
waitsForPromise ->
themeManager.activateThemes()
describe "getActiveThemes", ->
it 'gets all the active themes', ->
waitsForPromise -> atom.themes.activateThemes()
runs ->
names = atom.config.get('core.themes')
expect(names.length).toBeGreaterThan(0)
themes = themeManager.getActiveThemes()
expect(themes).toHaveLength(names.length)
runs ->
names = atom.config.get('core.themes')
expect(names.length).toBeGreaterThan(0)
themes = atom.themes.getActiveThemes()
expect(themes).toHaveLength(names.length)
describe "when the core.themes config value contains invalid entry", ->
it "ignores theme", ->
@@ -54,13 +51,13 @@ describe "ThemeManager", ->
'atom-dark-ui'
]
expect(themeManager.getEnabledThemeNames()).toEqual ['atom-dark-ui', 'atom-light-ui']
expect(atom.themes.getEnabledThemeNames()).toEqual ['atom-dark-ui', 'atom-light-ui']
describe "::getImportPaths()", ->
it "returns the theme directories before the themes are loaded", ->
atom.config.set('core.themes', ['theme-with-index-less', 'atom-dark-ui', 'atom-light-ui'])
paths = themeManager.getImportPaths()
paths = atom.themes.getImportPaths()
# syntax theme is not a dir at this time, so only two.
expect(paths.length).toBe 2
@@ -69,45 +66,45 @@ describe "ThemeManager", ->
it "ignores themes that cannot be resolved to a directory", ->
atom.config.set('core.themes', ['definitely-not-a-theme'])
expect(-> themeManager.getImportPaths()).not.toThrow()
expect(-> atom.themes.getImportPaths()).not.toThrow()
describe "when the core.themes config value changes", ->
it "add/removes stylesheets to reflect the new config value", ->
themeManager.onDidChangeActiveThemes didChangeActiveThemesHandler = jasmine.createSpy()
atom.themes.onDidChangeActiveThemes didChangeActiveThemesHandler = jasmine.createSpy()
spyOn(atom.styles, 'getUserStyleSheetPath').andCallFake -> null
waitsForPromise ->
themeManager.activateThemes()
atom.themes.activateThemes()
runs ->
didChangeActiveThemesHandler.reset()
atom.config.set('core.themes', [])
waitsFor ->
waitsFor 'a', ->
didChangeActiveThemesHandler.callCount is 1
runs ->
didChangeActiveThemesHandler.reset()
expect($('style.theme')).toHaveLength 0
expect(document.querySelectorAll('style.theme')).toHaveLength 0
atom.config.set('core.themes', ['atom-dark-ui'])
waitsFor ->
waitsFor 'b', ->
didChangeActiveThemesHandler.callCount is 1
runs ->
didChangeActiveThemesHandler.reset()
expect($('style[priority=1]')).toHaveLength 2
expect($('style[priority=1]:eq(0)').attr('source-path')).toMatch /atom-dark-ui/
expect(document.querySelectorAll('style[priority="1"]')).toHaveLength 2
expect(document.querySelector('style[priority="1"]').getAttribute('source-path')).toMatch /atom-dark-ui/
atom.config.set('core.themes', ['atom-light-ui', 'atom-dark-ui'])
waitsFor ->
waitsFor 'c', ->
didChangeActiveThemesHandler.callCount is 1
runs ->
didChangeActiveThemesHandler.reset()
expect($('style[priority=1]')).toHaveLength 2
expect($('style[priority=1]:eq(0)').attr('source-path')).toMatch /atom-dark-ui/
expect($('style[priority=1]:eq(1)').attr('source-path')).toMatch /atom-light-ui/
expect(document.querySelectorAll('style[priority="1"]')).toHaveLength 2
expect(document.querySelectorAll('style[priority="1"]')[0].getAttribute('source-path')).toMatch /atom-dark-ui/
expect(document.querySelectorAll('style[priority="1"]')[1].getAttribute('source-path')).toMatch /atom-light-ui/
atom.config.set('core.themes', [])
waitsFor ->
@@ -115,7 +112,7 @@ describe "ThemeManager", ->
runs ->
didChangeActiveThemesHandler.reset()
expect($('style[priority=1]')).toHaveLength 2
expect(document.querySelectorAll('style[priority="1"]')).toHaveLength 2
# atom-dark-ui has an directory path, the syntax one doesn't
atom.config.set('core.themes', ['theme-with-index-less', 'atom-dark-ui'])
@@ -123,23 +120,23 @@ describe "ThemeManager", ->
didChangeActiveThemesHandler.callCount is 1
runs ->
expect($('style[priority=1]')).toHaveLength 2
importPaths = themeManager.getImportPaths()
expect(document.querySelectorAll('style[priority="1"]')).toHaveLength 2
importPaths = atom.themes.getImportPaths()
expect(importPaths.length).toBe 1
expect(importPaths[0]).toContain 'atom-dark-ui'
it 'adds theme-* classes to the workspace for each active theme', ->
atom.config.set('core.themes', ['atom-dark-ui', 'atom-dark-syntax'])
workspaceElement = atom.views.getView(atom.workspace)
themeManager.onDidChangeActiveThemes didChangeActiveThemesHandler = jasmine.createSpy()
atom.themes.onDidChangeActiveThemes didChangeActiveThemesHandler = jasmine.createSpy()
waitsForPromise ->
themeManager.activateThemes()
atom.themes.activateThemes()
runs ->
expect(workspaceElement).toHaveClass 'theme-atom-dark-ui'
themeManager.onDidChangeActiveThemes didChangeActiveThemesHandler = jasmine.createSpy()
atom.themes.onDidChangeActiveThemes didChangeActiveThemesHandler = jasmine.createSpy()
atom.config.set('core.themes', ['theme-with-ui-variables', 'theme-with-syntax-variables'])
waitsFor ->
@@ -154,8 +151,8 @@ describe "ThemeManager", ->
describe "when a theme fails to load", ->
it "logs a warning", ->
spyOn(console, 'warn')
atom.packages.activatePackage('a-theme-that-will-not-be-found')
console.warn.reset()
atom.packages.activatePackage('a-theme-that-will-not-be-found').then((->), (->))
expect(console.warn.callCount).toBe 1
expect(console.warn.argsForCall[0][0]).toContain "Could not resolve 'a-theme-that-will-not-be-found'"
@@ -168,41 +165,37 @@ describe "ThemeManager", ->
it "synchronously loads css at the given path and installs a style tag for it in the head", ->
atom.styles.onDidAddStyleElement styleElementAddedHandler = jasmine.createSpy("styleElementAddedHandler")
themeManager.onDidChangeStylesheets stylesheetsChangedHandler = jasmine.createSpy("stylesheetsChangedHandler")
themeManager.onDidAddStylesheet stylesheetAddedHandler = jasmine.createSpy("stylesheetAddedHandler")
cssPath = atom.project.getDirectories()[0]?.resolve('css.css')
lengthBefore = $('head style').length
lengthBefore = document.querySelectorAll('head style').length
themeManager.requireStylesheet(cssPath)
expect($('head style').length).toBe lengthBefore + 1
atom.themes.requireStylesheet(cssPath)
expect(document.querySelectorAll('head style').length).toBe lengthBefore + 1
expect(styleElementAddedHandler).toHaveBeenCalled()
expect(stylesheetAddedHandler).toHaveBeenCalled()
expect(stylesheetsChangedHandler).toHaveBeenCalled()
element = $('head style[source-path*="css.css"]')
expect(element.attr('source-path')).toBe themeManager.stringToId(cssPath)
expect(element.text()).toBe fs.readFileSync(cssPath, 'utf8')
expect(element[0].sheet).toBe stylesheetAddedHandler.argsForCall[0][0]
element = document.querySelector('head style[source-path*="css.css"]')
expect(element.getAttribute('source-path')).toBe atom.themes.stringToId(cssPath)
expect(element.textContent).toBe fs.readFileSync(cssPath, 'utf8')
# doesn't append twice
styleElementAddedHandler.reset()
themeManager.requireStylesheet(cssPath)
expect($('head style').length).toBe lengthBefore + 1
atom.themes.requireStylesheet(cssPath)
expect(document.querySelectorAll('head style').length).toBe lengthBefore + 1
expect(styleElementAddedHandler).not.toHaveBeenCalled()
$('head style[id*="css.css"]').remove()
for styleElement in document.querySelectorAll('head style[id*="css.css"]')
styleElement.remove()
it "synchronously loads and parses less files at the given path and installs a style tag for it in the head", ->
lessPath = atom.project.getDirectories()[0]?.resolve('sample.less')
lengthBefore = $('head style').length
themeManager.requireStylesheet(lessPath)
expect($('head style').length).toBe lengthBefore + 1
lengthBefore = document.querySelectorAll('head style').length
atom.themes.requireStylesheet(lessPath)
expect(document.querySelectorAll('head style').length).toBe lengthBefore + 1
element = $('head style[source-path*="sample.less"]')
expect(element.attr('source-path')).toBe themeManager.stringToId(lessPath)
expect(element.text()).toBe """
element = document.querySelector('head style[source-path*="sample.less"]')
expect(element.getAttribute('source-path')).toBe atom.themes.stringToId(lessPath)
expect(element.textContent).toBe """
#header {
color: #4d926f;
}
@@ -213,41 +206,35 @@ describe "ThemeManager", ->
"""
# doesn't append twice
themeManager.requireStylesheet(lessPath)
expect($('head style').length).toBe lengthBefore + 1
$('head style[id*="sample.less"]').remove()
atom.themes.requireStylesheet(lessPath)
expect(document.querySelectorAll('head style').length).toBe lengthBefore + 1
for styleElement in document.querySelectorAll('head style[id*="sample.less"]')
styleElement.remove()
it "supports requiring css and less stylesheets without an explicit extension", ->
themeManager.requireStylesheet path.join(__dirname, 'fixtures', 'css')
expect($('head style[source-path*="css.css"]').attr('source-path')).toBe themeManager.stringToId(atom.project.getDirectories()[0]?.resolve('css.css'))
themeManager.requireStylesheet path.join(__dirname, 'fixtures', 'sample')
expect($('head style[source-path*="sample.less"]').attr('source-path')).toBe themeManager.stringToId(atom.project.getDirectories()[0]?.resolve('sample.less'))
atom.themes.requireStylesheet path.join(__dirname, 'fixtures', 'css')
expect(document.querySelector('head style[source-path*="css.css"]').getAttribute('source-path')).toBe atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('css.css'))
atom.themes.requireStylesheet path.join(__dirname, 'fixtures', 'sample')
expect(document.querySelector('head style[source-path*="sample.less"]').getAttribute('source-path')).toBe atom.themes.stringToId(atom.project.getDirectories()[0]?.resolve('sample.less'))
$('head style[id*="css.css"]').remove()
$('head style[id*="sample.less"]').remove()
document.querySelector('head style[source-path*="css.css"]').remove()
document.querySelector('head style[source-path*="sample.less"]').remove()
it "returns a disposable allowing styles applied by the given path to be removed", ->
cssPath = require.resolve('./fixtures/css.css')
expect($(document.body).css('font-weight')).not.toBe("bold")
disposable = themeManager.requireStylesheet(cssPath)
expect($(document.body).css('font-weight')).toBe("bold")
expect(getComputedStyle(document.body).fontWeight).not.toBe("bold")
disposable = atom.themes.requireStylesheet(cssPath)
expect(getComputedStyle(document.body).fontWeight).toBe("bold")
atom.styles.onDidRemoveStyleElement styleElementRemovedHandler = jasmine.createSpy("styleElementRemovedHandler")
themeManager.onDidRemoveStylesheet stylesheetRemovedHandler = jasmine.createSpy("stylesheetRemovedHandler")
themeManager.onDidChangeStylesheets stylesheetsChangedHandler = jasmine.createSpy("stylesheetsChangedHandler")
disposable.dispose()
expect($(document.body).css('font-weight')).not.toBe("bold")
expect(getComputedStyle(document.body).fontWeight).not.toBe("bold")
expect(styleElementRemovedHandler).toHaveBeenCalled()
expect(stylesheetRemovedHandler).toHaveBeenCalled()
stylesheet = stylesheetRemovedHandler.argsForCall[0][0]
expect(stylesheet instanceof CSSStyleSheet).toBe true
expect(stylesheet.cssRules[0].selectorText).toBe 'body'
expect(stylesheetsChangedHandler).toHaveBeenCalled()
describe "base style sheet loading", ->
workspaceElement = null
@@ -257,10 +244,10 @@ describe "ThemeManager", ->
workspaceElement.appendChild document.createElement('atom-text-editor')
waitsForPromise ->
themeManager.activateThemes()
atom.themes.activateThemes()
it "loads the correct values from the theme's ui-variables file", ->
themeManager.onDidChangeActiveThemes didChangeActiveThemesHandler = jasmine.createSpy()
atom.themes.onDidChangeActiveThemes didChangeActiveThemesHandler = jasmine.createSpy()
atom.config.set('core.themes', ['theme-with-ui-variables', 'theme-with-syntax-variables'])
waitsFor ->
@@ -271,13 +258,13 @@ describe "ThemeManager", ->
expect(getComputedStyle(workspaceElement)["background-color"]).toBe "rgb(0, 0, 255)"
# from within the theme itself
expect($("atom-text-editor").css("padding-top")).toBe "150px"
expect($("atom-text-editor").css("padding-right")).toBe "150px"
expect($("atom-text-editor").css("padding-bottom")).toBe "150px"
expect(getComputedStyle(document.querySelector("atom-text-editor")).paddingTop).toBe "150px"
expect(getComputedStyle(document.querySelector("atom-text-editor")).paddingRight).toBe "150px"
expect(getComputedStyle(document.querySelector("atom-text-editor")).paddingBottom).toBe "150px"
describe "when there is a theme with incomplete variables", ->
it "loads the correct values from the fallback ui-variables", ->
themeManager.onDidChangeActiveThemes didChangeActiveThemesHandler = jasmine.createSpy()
atom.themes.onDidChangeActiveThemes didChangeActiveThemesHandler = jasmine.createSpy()
atom.config.set('core.themes', ['theme-with-incomplete-ui-variables', 'theme-with-syntax-variables'])
waitsFor ->
@@ -288,7 +275,7 @@ describe "ThemeManager", ->
expect(getComputedStyle(workspaceElement)["background-color"]).toBe "rgb(0, 0, 255)"
# from within the theme itself
expect($("atom-text-editor").css("background-color")).toBe "rgb(0, 152, 255)"
expect(getComputedStyle(document.querySelector("atom-text-editor")).backgroundColor).toBe "rgb(0, 152, 255)"
describe "user stylesheet", ->
userStylesheetPath = null
@@ -306,67 +293,52 @@ describe "ThemeManager", ->
it "reloads it", ->
[styleElementAddedHandler, styleElementRemovedHandler] = []
[stylesheetRemovedHandler, stylesheetAddedHandler, stylesheetsChangedHandler] = []
waitsForPromise ->
themeManager.activateThemes()
atom.themes.activateThemes()
runs ->
atom.styles.onDidRemoveStyleElement styleElementRemovedHandler = jasmine.createSpy("styleElementRemovedHandler")
atom.styles.onDidAddStyleElement styleElementAddedHandler = jasmine.createSpy("styleElementAddedHandler")
themeManager.onDidChangeStylesheets stylesheetsChangedHandler = jasmine.createSpy("stylesheetsChangedHandler")
themeManager.onDidRemoveStylesheet stylesheetRemovedHandler = jasmine.createSpy("stylesheetRemovedHandler")
themeManager.onDidAddStylesheet stylesheetAddedHandler = jasmine.createSpy("stylesheetAddedHandler")
spyOn(themeManager, 'loadUserStylesheet').andCallThrough()
spyOn(atom.themes, 'loadUserStylesheet').andCallThrough()
expect($(document.body).css('border-style')).toBe 'dotted'
expect(getComputedStyle(document.body).borderStyle).toBe 'dotted'
fs.writeFileSync(userStylesheetPath, 'body {border-style: dashed}')
waitsFor ->
themeManager.loadUserStylesheet.callCount is 1
atom.themes.loadUserStylesheet.callCount is 1
runs ->
expect($(document.body).css('border-style')).toBe 'dashed'
expect(getComputedStyle(document.body).borderStyle).toBe 'dashed'
expect(styleElementRemovedHandler).toHaveBeenCalled()
expect(styleElementRemovedHandler.argsForCall[0][0].textContent).toContain 'dotted'
expect(stylesheetRemovedHandler).toHaveBeenCalled()
expect(stylesheetRemovedHandler.argsForCall[0][0].cssRules[0].style.border).toBe 'dotted'
expect(styleElementAddedHandler).toHaveBeenCalled()
expect(styleElementAddedHandler.argsForCall[0][0].textContent).toContain 'dashed'
expect(stylesheetAddedHandler).toHaveBeenCalled()
expect(stylesheetAddedHandler.argsForCall[0][0].cssRules[0].style.border).toBe 'dashed'
expect(stylesheetsChangedHandler).toHaveBeenCalled()
styleElementRemovedHandler.reset()
stylesheetRemovedHandler.reset()
stylesheetsChangedHandler.reset()
fs.removeSync(userStylesheetPath)
waitsFor ->
themeManager.loadUserStylesheet.callCount is 2
atom.themes.loadUserStylesheet.callCount is 2
runs ->
expect(styleElementRemovedHandler).toHaveBeenCalled()
expect(styleElementRemovedHandler.argsForCall[0][0].textContent).toContain 'dashed'
expect(stylesheetRemovedHandler).toHaveBeenCalled()
expect(stylesheetRemovedHandler.argsForCall[0][0].cssRules[0].style.border).toBe 'dashed'
expect($(document.body).css('border-style')).toBe 'none'
expect(stylesheetsChangedHandler).toHaveBeenCalled()
expect(getComputedStyle(document.body).borderStyle).toBe 'none'
describe "when there is an error reading the stylesheet", ->
addErrorHandler = null
beforeEach ->
themeManager.loadUserStylesheet()
spyOn(themeManager.lessCache, 'cssForFile').andCallFake ->
atom.themes.loadUserStylesheet()
spyOn(atom.themes.lessCache, 'cssForFile').andCallFake ->
throw new Error('EACCES permission denied "styles.less"')
atom.notifications.onDidAddNotification addErrorHandler = jasmine.createSpy()
it "creates an error notification and does not add the stylesheet", ->
themeManager.loadUserStylesheet()
atom.themes.loadUserStylesheet()
expect(addErrorHandler).toHaveBeenCalled()
note = addErrorHandler.mostRecentCall.args[0]
expect(note.getType()).toBe 'error'
@@ -380,11 +352,11 @@ describe "ThemeManager", ->
spyOn(File::, 'on').andCallFake (event) ->
if event.indexOf('contents-changed') > -1
throw new Error('Unable to watch path')
spyOn(themeManager, 'loadStylesheet').andReturn ''
spyOn(atom.themes, 'loadStylesheet').andReturn ''
atom.notifications.onDidAddNotification addErrorHandler = jasmine.createSpy()
it "creates an error notification", ->
themeManager.loadUserStylesheet()
atom.themes.loadUserStylesheet()
expect(addErrorHandler).toHaveBeenCalled()
note = addErrorHandler.mostRecentCall.args[0]
expect(note.getType()).toBe 'error'
@@ -393,38 +365,35 @@ describe "ThemeManager", ->
it "adds a notification when a theme's stylesheet is invalid", ->
addErrorHandler = jasmine.createSpy()
atom.notifications.onDidAddNotification(addErrorHandler)
expect(-> atom.packages.activatePackage('theme-with-invalid-styles')).not.toThrow()
expect(-> atom.packages.activatePackage('theme-with-invalid-styles').then((->), (->))).not.toThrow()
expect(addErrorHandler.callCount).toBe 2
expect(addErrorHandler.argsForCall[1][0].message).toContain("Failed to activate the theme-with-invalid-styles theme")
describe "when a non-existent theme is present in the config", ->
beforeEach ->
spyOn(console, 'warn')
console.warn.reset()
atom.config.set('core.themes', ['non-existent-dark-ui', 'non-existent-dark-syntax'])
waitsForPromise ->
themeManager.activateThemes()
atom.themes.activateThemes()
it 'uses the default dark UI and syntax themes and logs a warning', ->
activeThemeNames = themeManager.getActiveThemeNames()
activeThemeNames = atom.themes.getActiveThemeNames()
expect(console.warn.callCount).toBe 2
expect(activeThemeNames.length).toBe(2)
expect(activeThemeNames).toContain('atom-dark-ui')
expect(activeThemeNames).toContain('atom-dark-syntax')
describe "when in safe mode", ->
beforeEach ->
themeManager = new ThemeManager({packageManager: atom.packages, resourcePath, configDirPath, safeMode: true})
describe 'when the enabled UI and syntax themes are bundled with Atom', ->
beforeEach ->
atom.config.set('core.themes', ['atom-light-ui', 'atom-dark-syntax'])
waitsForPromise ->
themeManager.activateThemes()
atom.themes.activateThemes()
it 'uses the enabled themes', ->
activeThemeNames = themeManager.getActiveThemeNames()
activeThemeNames = atom.themes.getActiveThemeNames()
expect(activeThemeNames.length).toBe(2)
expect(activeThemeNames).toContain('atom-light-ui')
expect(activeThemeNames).toContain('atom-dark-syntax')
@@ -434,10 +403,10 @@ describe "ThemeManager", ->
atom.config.set('core.themes', ['installed-dark-ui', 'installed-dark-syntax'])
waitsForPromise ->
themeManager.activateThemes()
atom.themes.activateThemes()
it 'uses the default dark UI and syntax themes', ->
activeThemeNames = themeManager.getActiveThemeNames()
activeThemeNames = atom.themes.getActiveThemeNames()
expect(activeThemeNames.length).toBe(2)
expect(activeThemeNames).toContain('atom-dark-ui')
expect(activeThemeNames).toContain('atom-dark-syntax')
@@ -447,10 +416,10 @@ describe "ThemeManager", ->
atom.config.set('core.themes', ['installed-dark-ui', 'atom-light-syntax'])
waitsForPromise ->
themeManager.activateThemes()
atom.themes.activateThemes()
it 'uses the default dark UI theme', ->
activeThemeNames = themeManager.getActiveThemeNames()
activeThemeNames = atom.themes.getActiveThemeNames()
expect(activeThemeNames.length).toBe(2)
expect(activeThemeNames).toContain('atom-dark-ui')
expect(activeThemeNames).toContain('atom-light-syntax')
@@ -460,10 +429,10 @@ describe "ThemeManager", ->
atom.config.set('core.themes', ['atom-light-ui', 'installed-dark-syntax'])
waitsForPromise ->
themeManager.activateThemes()
atom.themes.activateThemes()
it 'uses the default dark syntax theme', ->
activeThemeNames = themeManager.getActiveThemeNames()
activeThemeNames = atom.themes.getActiveThemeNames()
expect(activeThemeNames.length).toBe(2)
expect(activeThemeNames).toContain('atom-light-ui')
expect(activeThemeNames).toContain('atom-dark-syntax')

View File

@@ -0,0 +1,35 @@
TextBuffer = require 'text-buffer'
TokenizedBuffer = require '../src/tokenized-buffer'
describe "TokenIterator", ->
it "correctly terminates scopes at the beginning of the line (regression)", ->
grammar = atom.grammars.createGrammar('test', {
'scopeName': 'text.broken'
'name': 'Broken grammar'
'patterns': [
{
'begin': 'start'
'end': '(?=end)'
'name': 'blue.broken'
}
{
'match': '.'
'name': 'yellow.broken'
}
]
})
buffer = new TextBuffer(text: """
start x
end x
x
""")
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer.setGrammar(grammar)
tokenIterator = tokenizedBuffer.tokenizedLineForRow(1).getTokenIterator()
tokenIterator.next()
expect(tokenIterator.getBufferStart()).toBe 0
expect(tokenIterator.getScopeEnds()).toEqual []
expect(tokenIterator.getScopeStarts()).toEqual ['text.broken', 'yellow.broken']

View File

@@ -1,5 +1,4 @@
TooltipManager = require '../src/tooltip-manager'
{$} = require '../src/space-pen-extensions'
_ = require "underscore-plus"
describe "TooltipManager", ->
@@ -15,18 +14,51 @@ describe "TooltipManager", ->
jasmine.attachToDOM(element)
hover = (element, fn) ->
$(element).trigger 'mouseenter'
element.dispatchEvent(new CustomEvent('mouseenter', bubbles: false))
element.dispatchEvent(new CustomEvent('mouseover', bubbles: true))
advanceClock(manager.defaults.delay.show)
fn()
$(element).trigger 'mouseleave'
element.dispatchEvent(new CustomEvent('mouseleave', bubbles: false))
element.dispatchEvent(new CustomEvent('mouseout', bubbles: true))
advanceClock(manager.defaults.delay.hide)
describe "::add(target, options)", ->
describe "when the target is an element", ->
it "creates a tooltip based on the given options when hovering over the target element", ->
manager.add element, title: "Title"
hover element, ->
expect(document.body.querySelector(".tooltip")).toHaveText("Title")
it "creates a tooltip based on the given options when hovering over the target element", ->
manager.add element, title: "Title"
hover element, ->
expect(document.body.querySelector(".tooltip")).toHaveText("Title")
it "allows jQuery elements to be passed as the target", ->
element2 = document.createElement('div')
jasmine.attachToDOM(element2)
fakeJqueryWrapper = [element, element2]
fakeJqueryWrapper.jquery = 'any-version'
disposable = manager.add fakeJqueryWrapper, title: "Title"
hover element, -> expect(document.body.querySelector(".tooltip")).toHaveText("Title")
expect(document.body.querySelector(".tooltip")).toBeNull()
hover element2, -> expect(document.body.querySelector(".tooltip")).toHaveText("Title")
expect(document.body.querySelector(".tooltip")).toBeNull()
disposable.dispose()
hover element, -> expect(document.body.querySelector(".tooltip")).toBeNull()
hover element2, -> expect(document.body.querySelector(".tooltip")).toBeNull()
describe "when a selector is specified", ->
it "creates a tooltip when hovering over a descendant of the target that matches the selector", ->
child = document.createElement('div')
child.classList.add('bar')
grandchild = document.createElement('div')
element.appendChild(child)
child.appendChild(grandchild)
manager.add element, selector: '.bar', title: 'Bar'
hover grandchild, ->
expect(document.body.querySelector('.tooltip')).toHaveText('Bar')
expect(document.body.querySelector('.tooltip')).toBeNull()
describe "when a keyBindingCommand is specified", ->
describe "when a title is specified", ->
@@ -81,3 +113,11 @@ describe "TooltipManager", ->
hover element, ->
expect(document.body.querySelector(".tooltip")).toBeNull()
describe "when the window is resized", ->
it "hides the tooltips", ->
manager.add element, title: "Title"
hover element, ->
expect(document.body.querySelector(".tooltip")).toBeDefined()
window.dispatchEvent(new CustomEvent('resize'))
expect(document.body.querySelector(".tooltip")).toBeNull()

View File

@@ -1,5 +1,4 @@
ViewRegistry = require '../src/view-registry'
{View} = require '../src/space-pen-extensions'
describe "ViewRegistry", ->
registry = null
@@ -16,16 +15,6 @@ describe "ViewRegistry", ->
node = document.createElement('div')
expect(registry.getView(node)).toBe node
describe "when passed a SpacePen view", ->
it "returns the root node of the view with a .spacePenView property pointing at the SpacePen view", ->
class TestView extends View
@content: -> @div "Hello"
view = new TestView
node = registry.getView(view)
expect(node.textContent).toBe "Hello"
expect(node.spacePenView).toBe view
describe "when passed an object with an element property", ->
it "returns the element property if it's an instance of HTMLElement", ->
class TestComponent
@@ -59,30 +48,8 @@ describe "ViewRegistry", ->
expect(view2.model).toBe subclassModel
describe "when no view provider is registered for the object's constructor", ->
describe "when the object has a .getViewClass() method", ->
it "builds an instance of the view class with the model, then returns its root node with a __spacePenView property pointing at the view", ->
class TestView extends View
@content: (model) -> @div model.name
initialize: (@model) ->
class TestModel
constructor: (@name) ->
getViewClass: -> TestView
model = new TestModel("hello")
node = registry.getView(model)
expect(node.textContent).toBe "hello"
view = node.spacePenView
expect(view instanceof TestView).toBe true
expect(view.model).toBe model
# returns the same DOM node for repeated calls
expect(registry.getView(model)).toBe node
describe "when the object has no .getViewClass() method", ->
it "throws an exception", ->
expect(-> registry.getView(new Object)).toThrow()
it "throws an exception", ->
expect(-> registry.getView(new Object)).toThrow()
describe "::addViewProvider(providerSpec)", ->
it "returns a disposable that can be used to remove the provider", ->

View File

@@ -1,4 +1,4 @@
{$, $$} = require '../src/space-pen-extensions'
KeymapManager = require 'atom-keymap'
path = require 'path'
fs = require 'fs-plus'
temp = require 'temp'
@@ -16,45 +16,41 @@ describe "Window", ->
loadSettings.initialPath = initialPath
loadSettings
atom.project.destroy()
windowEventHandler = new WindowEventHandler()
atom.deserializeEditorWindow()
atom.windowEventHandler.unsubscribe()
windowEventHandler = new WindowEventHandler
projectPath = atom.project.getPaths()[0]
afterEach ->
windowEventHandler.unsubscribe()
$(window).off 'beforeunload'
describe "when the window is loaded", ->
it "doesn't have .is-blurred on the body tag", ->
expect($("body")).not.toHaveClass("is-blurred")
expect(document.body.className).not.toMatch("is-blurred")
describe "when the window is blurred", ->
beforeEach ->
$(window).triggerHandler 'blur'
window.dispatchEvent(new CustomEvent('blur'))
afterEach ->
$('body').removeClass('is-blurred')
document.body.classList.remove('is-blurred')
it "adds the .is-blurred class on the body", ->
expect($("body")).toHaveClass("is-blurred")
expect(document.body.className).toMatch("is-blurred")
describe "when the window is focused again", ->
it "removes the .is-blurred class from the body", ->
$(window).triggerHandler 'focus'
expect($("body")).not.toHaveClass("is-blurred")
window.dispatchEvent(new CustomEvent('focus'))
expect(document.body.className).not.toMatch("is-blurred")
describe "window:close event", ->
it "closes the window", ->
spyOn(atom, 'close')
$(window).trigger 'window:close'
window.dispatchEvent(new CustomEvent('window:close'))
expect(atom.close).toHaveBeenCalled()
describe "beforeunload event", ->
[beforeUnloadEvent] = []
beforeEach ->
jasmine.unspy(TextEditor.prototype, "shouldPromptToSave")
beforeUnloadEvent = $.Event(new Event('beforeunload'))
describe "when pane items are modified", ->
it "prompts user to save and calls atom.workspace.confirmClose", ->
@@ -67,7 +63,7 @@ describe "Window", ->
runs ->
editor.insertText("I look different, I feel different.")
$(window).trigger(beforeUnloadEvent)
window.dispatchEvent(new CustomEvent('beforeunload'))
expect(atom.workspace.confirmClose).toHaveBeenCalled()
expect(atom.confirm).toHaveBeenCalled()
@@ -80,7 +76,7 @@ describe "Window", ->
runs ->
editor.insertText("I look different, I feel different.")
$(window).trigger(beforeUnloadEvent)
window.dispatchEvent(new CustomEvent('beforeunload'))
expect(atom.confirm).toHaveBeenCalled()
it "prompts user to save and handler returns false if dialog is canceled", ->
@@ -91,11 +87,12 @@ describe "Window", ->
runs ->
editor.insertText("I look different, I feel different.")
$(window).trigger(beforeUnloadEvent)
window.dispatchEvent(new CustomEvent('beforeunload'))
expect(atom.confirm).toHaveBeenCalled()
describe "when the same path is modified in multiple panes", ->
it "prompts to save the item", ->
return
editor = null
filePath = path.join(temp.mkdirSync('atom-file'), 'file.txt')
fs.writeFileSync(filePath, 'hello')
@@ -108,146 +105,125 @@ describe "Window", ->
runs ->
atom.workspace.getActivePane().splitRight(copyActiveItem: true)
editor.setText('world')
$(window).trigger(beforeUnloadEvent)
window.dispatchEvent(new CustomEvent('beforeunload'))
expect(atom.workspace.confirmClose).toHaveBeenCalled()
expect(atom.confirm.callCount).toBe 1
expect(fs.readFileSync(filePath, 'utf8')).toBe 'world'
describe ".unloadEditorWindow()", ->
it "saves the serialized state of the window so it can be deserialized after reload", ->
workspaceState = atom.workspace.serialize()
syntaxState = atom.grammars.serialize()
projectState = atom.project.serialize()
atom.unloadEditorWindow()
expect(atom.state.workspace).toEqual workspaceState
expect(atom.state.grammars).toEqual syntaxState
expect(atom.state.project).toEqual projectState
expect(atom.saveSync).toHaveBeenCalled()
describe ".removeEditorWindow()", ->
it "unsubscribes from all buffers", ->
waitsForPromise ->
atom.workspace.open("sample.js")
runs ->
buffer = atom.workspace.getActivePaneItem().buffer
pane = atom.workspace.getActivePane()
pane.splitRight(copyActiveItem: true)
expect(atom.workspace.getTextEditors().length).toBe 2
atom.removeEditorWindow()
expect(buffer.getSubscriptionCount()).toBe 0
describe "when a link is clicked", ->
it "opens the http/https links in an external application", ->
shell = require 'shell'
spyOn(shell, 'openExternal')
$("<a href='http://github.com'>the website</a>").appendTo(document.body).click().remove()
link = document.createElement('a')
linkChild = document.createElement('span')
link.appendChild(linkChild)
link.href = 'http://github.com'
jasmine.attachToDOM(link)
fakeEvent = {target: linkChild, currentTarget: link, preventDefault: (->)}
windowEventHandler.handleLinkClick(fakeEvent)
expect(shell.openExternal).toHaveBeenCalled()
expect(shell.openExternal.argsForCall[0][0]).toBe "http://github.com"
shell.openExternal.reset()
$("<a href='https://github.com'>the website</a>").appendTo(document.body).click().remove()
link.href = 'https://github.com'
windowEventHandler.handleLinkClick(fakeEvent)
expect(shell.openExternal).toHaveBeenCalled()
expect(shell.openExternal.argsForCall[0][0]).toBe "https://github.com"
shell.openExternal.reset()
$("<a href=''>the website</a>").appendTo(document.body).click().remove()
link.href = ''
windowEventHandler.handleLinkClick(fakeEvent)
expect(shell.openExternal).not.toHaveBeenCalled()
shell.openExternal.reset()
$("<a href='#scroll-me'>link</a>").appendTo(document.body).click().remove()
link.href = '#scroll-me'
windowEventHandler.handleLinkClick(fakeEvent)
expect(shell.openExternal).not.toHaveBeenCalled()
describe "when a form is submitted", ->
it "prevents the default so that the window's URL isn't changed", ->
submitSpy = jasmine.createSpy('submit')
$(document).on('submit', 'form', submitSpy)
$("<form>foo</form>").appendTo(document.body).submit().remove()
expect(submitSpy.callCount).toBe 1
expect(submitSpy.argsForCall[0][0].isDefaultPrevented()).toBe true
form = document.createElement('form')
jasmine.attachToDOM(form)
defaultPrevented = false
event = new CustomEvent('submit', bubbles: true)
event.preventDefault = -> defaultPrevented = true
form.dispatchEvent(event)
expect(defaultPrevented).toBe(true)
describe "core:focus-next and core:focus-previous", ->
describe "when there is no currently focused element", ->
it "focuses the element with the lowest/highest tabindex", ->
elements = $$ ->
@div =>
@button tabindex: 2
@input tabindex: 1
wrapperDiv = document.createElement('div')
wrapperDiv.innerHTML = """
<div>
<button tabindex="2"></button>
<input tabindex="1">
</div>
"""
elements = wrapperDiv.firstChild
jasmine.attachToDOM(elements)
elements.attachToDom()
elements.dispatchEvent(new CustomEvent("core:focus-next", bubbles: true))
expect(document.activeElement.tabIndex).toBe 1
elements.trigger "core:focus-next"
expect(elements.find("[tabindex=1]:focus")).toExist()
$(":focus").blur()
elements.trigger "core:focus-previous"
expect(elements.find("[tabindex=2]:focus")).toExist()
document.body.focus()
elements.dispatchEvent(new CustomEvent("core:focus-previous", bubbles: true))
expect(document.activeElement.tabIndex).toBe 2
describe "when a tabindex is set on the currently focused element", ->
it "focuses the element with the next highest tabindex", ->
elements = $$ ->
@div =>
@input tabindex: 1
@button tabindex: 2
@button tabindex: 5
@input tabindex: -1
@input tabindex: 3
@button tabindex: 7
it "focuses the element with the next highest/lowest tabindex, skipping disabled elements", ->
wrapperDiv = document.createElement('div')
wrapperDiv.innerHTML = """
<div>
<input tabindex="1">
<button tabindex="2"></button>
<button tabindex="5"></button>
<input tabindex="-1">
<input tabindex="3">
<button tabindex="7"></button>
<input tabindex="9" disabled>
</div>
"""
elements = wrapperDiv.firstChild
jasmine.attachToDOM(elements)
elements.attachToDom()
elements.find("[tabindex=1]").focus()
elements.querySelector('[tabindex="1"]').focus()
elements.trigger "core:focus-next"
expect(elements.find("[tabindex=2]:focus")).toExist()
elements.dispatchEvent(new CustomEvent("core:focus-next", bubbles: true))
expect(document.activeElement.tabIndex).toBe 2
elements.trigger "core:focus-next"
expect(elements.find("[tabindex=3]:focus")).toExist()
elements.dispatchEvent(new CustomEvent("core:focus-next", bubbles: true))
expect(document.activeElement.tabIndex).toBe 3
elements.focus().trigger "core:focus-next"
expect(elements.find("[tabindex=5]:focus")).toExist()
elements.dispatchEvent(new CustomEvent("core:focus-next", bubbles: true))
expect(document.activeElement.tabIndex).toBe 5
elements.focus().trigger "core:focus-next"
expect(elements.find("[tabindex=7]:focus")).toExist()
elements.dispatchEvent(new CustomEvent("core:focus-next", bubbles: true))
expect(document.activeElement.tabIndex).toBe 7
elements.focus().trigger "core:focus-next"
expect(elements.find("[tabindex=1]:focus")).toExist()
elements.dispatchEvent(new CustomEvent("core:focus-next", bubbles: true))
expect(document.activeElement.tabIndex).toBe 1
elements.trigger "core:focus-previous"
expect(elements.find("[tabindex=7]:focus")).toExist()
elements.dispatchEvent(new CustomEvent("core:focus-previous", bubbles: true))
expect(document.activeElement.tabIndex).toBe 7
elements.trigger "core:focus-previous"
expect(elements.find("[tabindex=5]:focus")).toExist()
elements.dispatchEvent(new CustomEvent("core:focus-previous", bubbles: true))
expect(document.activeElement.tabIndex).toBe 5
elements.focus().trigger "core:focus-previous"
expect(elements.find("[tabindex=3]:focus")).toExist()
elements.dispatchEvent(new CustomEvent("core:focus-previous", bubbles: true))
expect(document.activeElement.tabIndex).toBe 3
elements.focus().trigger "core:focus-previous"
expect(elements.find("[tabindex=2]:focus")).toExist()
elements.dispatchEvent(new CustomEvent("core:focus-previous", bubbles: true))
expect(document.activeElement.tabIndex).toBe 2
elements.focus().trigger "core:focus-previous"
expect(elements.find("[tabindex=1]:focus")).toExist()
elements.dispatchEvent(new CustomEvent("core:focus-previous", bubbles: true))
expect(document.activeElement.tabIndex).toBe 1
it "skips disabled elements", ->
elements = $$ ->
@div =>
@input tabindex: 1
@button tabindex: 2, disabled: 'disabled'
@input tabindex: 3
elements.attachToDom()
elements.find("[tabindex=1]").focus()
elements.trigger "core:focus-next"
expect(elements.find("[tabindex=3]:focus")).toExist()
elements.trigger "core:focus-previous"
expect(elements.find("[tabindex=1]:focus")).toExist()
elements.dispatchEvent(new CustomEvent("core:focus-previous", bubbles: true))
expect(document.activeElement.tabIndex).toBe 7
describe "the window:open-locations event", ->
beforeEach ->
@@ -304,3 +280,16 @@ describe "Window", ->
runs ->
expect(atom.project.getPaths()[0]).toBe pathToOpen
describe "when keydown events occur on the document", ->
it "dispatches the event via the KeymapManager and CommandRegistry", ->
dispatchedCommands = []
atom.commands.onWillDispatch (command) -> dispatchedCommands.push(command)
atom.commands.add '*', 'foo-command': ->
atom.keymaps.add 'source-name', '*': {'x': 'foo-command'}
event = KeymapManager.buildKeydownEvent('x', target: document.createElement('div'))
document.dispatchEvent(event)
expect(dispatchedCommands.length).toBe 1
expect(dispatchedCommands[0].type).toBe 'foo-command'

View File

@@ -3,13 +3,90 @@ path = require 'path'
temp = require('temp').track()
describe "WorkspaceElement", ->
workspaceElement = null
describe "when the workspace element is focused", ->
it "transfers focus to the active pane", ->
workspaceElement = atom.views.getView(atom.workspace)
jasmine.attachToDOM(workspaceElement)
activePaneElement = atom.views.getView(atom.workspace.getActivePane())
document.body.focus()
expect(document.activeElement).not.toBe(activePaneElement)
workspaceElement.focus()
expect(document.activeElement).toBe(activePaneElement)
beforeEach ->
workspaceElement = atom.views.getView(atom.workspace)
describe "the scrollbar visibility class", ->
it "has a class based on the style of the scrollbar", ->
observeCallback = null
scrollbarStyle = require 'scrollbar-style'
spyOn(scrollbarStyle, 'observePreferredScrollbarStyle').andCallFake (cb) -> observeCallback = cb
workspaceElement = atom.views.getView(atom.workspace)
observeCallback('legacy')
expect(workspaceElement.className).toMatch 'scrollbars-visible-always'
observeCallback('overlay')
expect(workspaceElement).toHaveClass 'scrollbars-visible-when-scrolling'
describe "editor font styling", ->
[editor, editorElement] = []
beforeEach ->
waitsForPromise -> atom.workspace.open('sample.js')
runs ->
workspaceElement = atom.views.getView(atom.workspace)
jasmine.attachToDOM(workspaceElement)
editor = atom.workspace.getActiveTextEditor()
editorElement = atom.views.getView(editor)
it "updates the font-size based on the 'editor.fontSize' config value", ->
initialCharWidth = editor.getDefaultCharWidth()
expect(getComputedStyle(editorElement).fontSize).toBe atom.config.get('editor.fontSize') + 'px'
atom.config.set('editor.fontSize', atom.config.get('editor.fontSize') + 5)
expect(getComputedStyle(editorElement).fontSize).toBe atom.config.get('editor.fontSize') + 'px'
expect(editor.getDefaultCharWidth()).toBeGreaterThan initialCharWidth
it "updates the font-family based on the 'editor.fontFamily' config value", ->
initialCharWidth = editor.getDefaultCharWidth()
expect(getComputedStyle(editorElement).fontFamily).toBe atom.config.get('editor.fontFamily')
atom.config.set('editor.fontFamily', 'sans-serif')
expect(getComputedStyle(editorElement).fontFamily).toBe atom.config.get('editor.fontFamily')
expect(editor.getDefaultCharWidth()).not.toBe initialCharWidth
it "updates the line-height based on the 'editor.lineHeight' config value", ->
initialLineHeight = editor.getLineHeightInPixels()
atom.config.set('editor.lineHeight', '30px')
expect(getComputedStyle(editorElement).lineHeight).toBe atom.config.get('editor.lineHeight')
expect(editor.getLineHeightInPixels()).not.toBe initialLineHeight
describe 'panel containers', ->
it 'inserts panel container elements in the correct places in the DOM', ->
workspaceElement = atom.views.getView(atom.workspace)
leftContainer = workspaceElement.querySelector('atom-panel-container.left')
rightContainer = workspaceElement.querySelector('atom-panel-container.right')
expect(leftContainer.nextSibling).toBe workspaceElement.verticalAxis
expect(rightContainer.previousSibling).toBe workspaceElement.verticalAxis
topContainer = workspaceElement.querySelector('atom-panel-container.top')
bottomContainer = workspaceElement.querySelector('atom-panel-container.bottom')
expect(topContainer.nextSibling).toBe workspaceElement.paneContainer
expect(bottomContainer.previousSibling).toBe workspaceElement.paneContainer
modalContainer = workspaceElement.querySelector('atom-panel-container.modal')
expect(modalContainer.parentNode).toBe workspaceElement
describe "the 'window:toggle-invisibles' command", ->
it "shows/hides invisibles in all open and future editors", ->
workspaceElement = atom.views.getView(atom.workspace)
expect(atom.config.get('editor.showInvisibles')).toBe false
atom.commands.dispatch(workspaceElement, 'window:toggle-invisibles')
expect(atom.config.get('editor.showInvisibles')).toBe true
atom.commands.dispatch(workspaceElement, 'window:toggle-invisibles')
expect(atom.config.get('editor.showInvisibles')).toBe false
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')
# No project paths. Don't try to run specs.

View File

@@ -2,12 +2,10 @@ path = require 'path'
temp = require 'temp'
Workspace = require '../src/workspace'
Pane = require '../src/pane'
{View} = require '../src/space-pen-extensions'
platform = require './spec-helper-platform'
_ = require 'underscore-plus'
fstream = require 'fstream'
fs = require 'fs-plus'
Grim = require 'grim'
describe "Workspace", ->
workspace = null
@@ -17,6 +15,64 @@ describe "Workspace", ->
atom.workspace = workspace = new Workspace
waits(1)
describe "serialization", ->
simulateReload = ->
workspaceState = atom.workspace.serialize()
projectState = atom.project.serialize()
atom.workspace.destroy()
atom.project.destroy()
atom.project = atom.deserializers.deserialize(projectState)
atom.workspace = Workspace.deserialize(workspaceState)
describe "when the workspace contains text editors", ->
it "constructs the view with the same panes", ->
pane1 = atom.workspace.getActivePane()
pane2 = pane1.splitRight(copyActiveItem: true)
pane3 = pane2.splitRight(copyActiveItem: true)
pane4 = null
waitsForPromise ->
atom.workspace.open('b').then (editor) ->
pane2.activateItem(editor.copy())
waitsForPromise ->
atom.workspace.open('../sample.js').then (editor) ->
pane3.activateItem(editor)
runs ->
pane3.activeItem.setCursorScreenPosition([2, 4])
pane4 = pane2.splitDown()
waitsForPromise ->
atom.workspace.open('../sample.txt').then (editor) ->
pane4.activateItem(editor)
runs ->
pane4.getActiveItem().setCursorScreenPosition([0, 2])
pane2.activate()
simulateReload()
expect(atom.workspace.getTextEditors().length).toBe 4
[editor1, editor2, editor3, editor4] = atom.workspace.getTextEditors()
expect(editor1.getPath()).toBe atom.project.getDirectories()[0]?.resolve('b')
expect(editor2.getPath()).toBe atom.project.getDirectories()[0]?.resolve('../sample.txt')
expect(editor2.getCursorScreenPosition()).toEqual [0, 2]
expect(editor3.getPath()).toBe atom.project.getDirectories()[0]?.resolve('b')
expect(editor4.getPath()).toBe atom.project.getDirectories()[0]?.resolve('../sample.js')
expect(editor4.getCursorScreenPosition()).toEqual [2, 4]
expect(atom.workspace.getActiveTextEditor().getPath()).toBe editor3.getPath()
expect(document.title).toBe "#{path.basename(editor3.getPath())} - #{atom.project.getPaths()[0]} - Atom"
describe "where there are no open panes or editors", ->
it "constructs the view with no open editors", ->
atom.workspace.getActivePane().destroy()
expect(atom.workspace.getTextEditors().length).toBe 0
simulateReload()
expect(atom.workspace.getTextEditors().length).toBe 0
describe "::open(uri, options)", ->
openEvents = null
@@ -326,21 +382,6 @@ describe "Workspace", ->
runs ->
expect(newEditorHandler.argsForCall[0][0].textEditor).toBe editor
it "records a deprecation warning on the appropriate package if the item has a ::getUri method instead of ::getURI", ->
jasmine.snapshotDeprecations()
waitsForPromise -> atom.packages.activatePackage('package-with-deprecated-pane-item-method')
waitsForPromise ->
atom.workspace.open("test")
runs ->
deprecations = Grim.getDeprecations()
expect(deprecations.length).toBe 1
expect(deprecations[0].message).toBe "Pane item with class `TestItem` should implement `::getURI` instead of `::getUri`."
expect(deprecations[0].getStacks()[0].metadata.packageName).toBe "package-with-deprecated-pane-item-method"
jasmine.restoreDeprecationsSnapshot()
describe "when there is an error opening the file", ->
notificationSpy = null
beforeEach ->
@@ -625,7 +666,7 @@ describe "Workspace", ->
it "updates the title to contain the project's path", ->
document.title = null
workspace2 = atom.workspace.testSerialization()
workspace2 = Workspace.deserialize(atom.workspace.serialize())
item = atom.workspace.getActivePaneItem()
expect(document.title).toBe "#{item.getTitle()} - #{atom.project.getPaths()[0]} - Atom"
workspace2.destroy()
@@ -1065,11 +1106,16 @@ describe "Workspace", ->
it "can be cancelled when the object returned by scan() has its cancel() method invoked", ->
thenable = atom.workspace.scan /aaaa/, ->
expect(fakeSearch.cancelled).toBe(undefined)
thenable.cancel()
expect(fakeSearch.cancelled).toBe(true)
resultOfPromiseSearch = null
waitsFor 'fakeSearch to be defined', -> fakeSearch?
runs ->
expect(fakeSearch.cancelled).toBe(undefined)
thenable.cancel()
expect(fakeSearch.cancelled).toBe(true)
waitsForPromise ->
thenable.then (promiseResult) -> resultOfPromiseSearch = promiseResult
@@ -1077,15 +1123,28 @@ describe "Workspace", ->
expect(resultOfPromiseSearch).toBe('cancelled')
it "will have the side-effect of failing the overall search if it fails", ->
cancelableSearch = atom.workspace.scan /aaaa/, ->
fakeSearch.hoistedReject()
# This provider's search should be cancelled when the first provider fails
fakeSearch2 = null
atom.packages.serviceHub.provide('atom.directory-searcher', '0.1.0', {
canSearchDirectory: (directory) -> directory.getPath() is dir2
search: (directory, regex, options) -> fakeSearch2 = new FakeSearch(options)
})
didReject = false
promise = cancelableSearch = atom.workspace.scan /aaaa/, ->
waitsFor 'fakeSearch to be defined', -> fakeSearch?
runs ->
fakeSearch.hoistedReject()
waitsForPromise ->
cancelableSearch.catch -> didReject = true
waitsFor (done) -> promise.then(null, done)
runs ->
expect(didReject).toBe(true)
expect(fakeSearch2.cancelled).toBe true # Cancels other ongoing searches
describe "::replace(regex, replacementText, paths, iterator)", ->
[filePath, commentFilePath, sampleContent, sampleCommentContent] = []
@@ -1267,3 +1326,32 @@ describe "Workspace", ->
save = -> atom.workspace.saveActivePaneItem()
expect(save).toThrow()
describe "::destroyActivePaneItemOrEmptyPane", ->
beforeEach ->
waitsForPromise -> atom.workspace.open()
it "closes the active pane item until all that remains is a single empty pane", ->
atom.config.set('core.destroyEmptyPanes', false)
pane1 = atom.workspace.getActivePane()
pane2 = pane1.splitRight(copyActiveItem: true)
expect(atom.workspace.getPanes().length).toBe 2
expect(pane2.getItems().length).toBe 1
atom.workspace.destroyActivePaneItemOrEmptyPane()
expect(atom.workspace.getPanes().length).toBe 2
expect(pane2.getItems().length).toBe 0
atom.workspace.destroyActivePaneItemOrEmptyPane()
expect(atom.workspace.getPanes().length).toBe 1
expect(pane1.getItems().length).toBe 1
atom.workspace.destroyActivePaneItemOrEmptyPane()
expect(atom.workspace.getPanes().length).toBe 1
expect(pane1.getItems().length).toBe 0
atom.workspace.destroyActivePaneItemOrEmptyPane()
expect(atom.workspace.getPanes().length).toBe 1

View File

@@ -1,301 +0,0 @@
{$, $$, View} = require '../src/space-pen-extensions'
Q = require 'q'
path = require 'path'
temp = require 'temp'
TextEditorView = require '../src/text-editor-view'
PaneView = require '../src/pane-view'
Workspace = require '../src/workspace'
describe "WorkspaceView", ->
pathToOpen = null
beforeEach ->
jasmine.snapshotDeprecations()
atom.project.setPaths([atom.project.getDirectories()[0]?.resolve('dir')])
pathToOpen = atom.project.getDirectories()[0]?.resolve('a')
atom.workspace = new Workspace
atom.workspaceView = atom.views.getView(atom.workspace).__spacePenView
atom.workspaceView.enableKeymap()
atom.workspaceView.focus()
waitsForPromise ->
atom.workspace.open(pathToOpen)
afterEach ->
jasmine.restoreDeprecationsSnapshot()
describe "@deserialize()", ->
viewState = null
simulateReload = ->
workspaceState = atom.workspace.serialize()
projectState = atom.project.serialize()
atom.workspaceView.remove()
atom.project = atom.deserializers.deserialize(projectState)
atom.workspace = Workspace.deserialize(workspaceState)
atom.workspaceView = atom.views.getView(atom.workspace).__spacePenView
atom.workspaceView.attachToDom()
describe "when the serialized WorkspaceView has an unsaved buffer", ->
it "constructs the view with the same panes", ->
atom.workspaceView.attachToDom()
waitsForPromise ->
atom.workspace.open()
runs ->
editorView1 = atom.workspaceView.getActiveView()
buffer = editorView1.getEditor().getBuffer()
editorView1.getPaneView().getModel().splitRight(copyActiveItem: true)
expect(atom.workspaceView.getActivePaneView()).toBe atom.workspaceView.getPaneViews()[1]
simulateReload()
expect(atom.workspaceView.getEditorViews().length).toBe 2
expect(atom.workspaceView.getActivePaneView()).toBe atom.workspaceView.getPaneViews()[1]
expect(document.title).toBe "untitled - #{atom.project.getPaths()[0]} - Atom"
describe "when there are open editors", ->
it "constructs the view with the same panes", ->
atom.workspaceView.attachToDom()
pane1 = atom.workspaceView.getActivePaneView()
pane2 = pane1.splitRight()
pane3 = pane2.splitRight()
pane4 = null
waitsForPromise ->
atom.workspace.open('b').then (editor) ->
pane2.activateItem(editor.copy())
waitsForPromise ->
atom.workspace.open('../sample.js').then (editor) ->
pane3.activateItem(editor)
runs ->
pane3.activeItem.setCursorScreenPosition([2, 4])
pane4 = pane2.splitDown()
waitsForPromise ->
atom.workspace.open('../sample.txt').then (editor) ->
pane4.activateItem(editor)
runs ->
pane4.activeItem.setCursorScreenPosition([0, 2])
pane2.focus()
simulateReload()
expect(atom.workspaceView.getEditorViews().length).toBe 4
editorView1 = atom.workspaceView.panes.find('atom-pane-axis.horizontal > atom-pane atom-text-editor:eq(0)').view()
editorView3 = atom.workspaceView.panes.find('atom-pane-axis.horizontal > atom-pane atom-text-editor:eq(1)').view()
editorView2 = atom.workspaceView.panes.find('atom-pane-axis.horizontal > atom-pane-axis.vertical > atom-pane atom-text-editor:eq(0)').view()
editorView4 = atom.workspaceView.panes.find('atom-pane-axis.horizontal > atom-pane-axis.vertical > atom-pane atom-text-editor:eq(1)').view()
expect(editorView1.getEditor().getPath()).toBe atom.project.getDirectories()[0]?.resolve('a')
expect(editorView2.getEditor().getPath()).toBe atom.project.getDirectories()[0]?.resolve('b')
expect(editorView3.getEditor().getPath()).toBe atom.project.getDirectories()[0]?.resolve('../sample.js')
expect(editorView3.getEditor().getCursorScreenPosition()).toEqual [2, 4]
expect(editorView4.getEditor().getPath()).toBe atom.project.getDirectories()[0]?.resolve('../sample.txt')
expect(editorView4.getEditor().getCursorScreenPosition()).toEqual [0, 2]
# ensure adjust pane dimensions is called
expect(editorView1.width()).toBeGreaterThan 0
expect(editorView2.width()).toBeGreaterThan 0
expect(editorView3.width()).toBeGreaterThan 0
expect(editorView4.width()).toBeGreaterThan 0
# ensure correct editorView is focused again
expect(editorView2).toHaveFocus()
expect(editorView1).not.toHaveFocus()
expect(editorView3).not.toHaveFocus()
expect(editorView4).not.toHaveFocus()
expect(document.title).toBe "#{path.basename(editorView2.getEditor().getPath())} - #{atom.project.getPaths()[0]} - Atom"
describe "where there are no open editors", ->
it "constructs the view with no open editors", ->
atom.workspaceView.getActivePaneView().remove()
expect(atom.workspaceView.getEditorViews().length).toBe 0
simulateReload()
expect(atom.workspaceView.getEditorViews().length).toBe 0
describe "focus", ->
beforeEach ->
atom.workspaceView.attachToDom()
it "hands off focus to the active pane", ->
activePane = atom.workspaceView.getActivePaneView()
$('body').focus()
expect(activePane).not.toHaveFocus()
atom.workspaceView.focus()
expect(activePane).toHaveFocus()
describe "keymap wiring", ->
describe "when a keydown event is triggered in the WorkspaceView", ->
it "triggers matching keybindings for that event", ->
commandHandler = jasmine.createSpy('commandHandler')
atom.workspaceView.on('foo-command', commandHandler)
atom.keymaps.add('name', '*': {'x': 'foo-command'})
event = keydownEvent 'x', target: atom.workspaceView[0]
atom.workspaceView.trigger(event)
expect(commandHandler).toHaveBeenCalled()
describe "window:toggle-invisibles event", ->
it "shows/hides invisibles in all open and future editors", ->
atom.workspaceView.height(200)
atom.workspaceView.attachToDom()
rightEditorView = atom.workspaceView.getActiveView()
rightEditorView.getEditor().setText("\t \n")
rightEditorView.getPaneView().getModel().splitLeft(copyActiveItem: true)
leftEditorView = atom.workspaceView.getActiveView()
expect(rightEditorView.find(".line:first").text()).toBe " "
expect(leftEditorView.find(".line:first").text()).toBe " "
{space, tab, eol} = atom.config.get('editor.invisibles')
withInvisiblesShowing = "#{tab} #{space}#{space}#{eol}"
atom.workspaceView.trigger "window:toggle-invisibles"
expect(rightEditorView.find(".line:first").text()).toBe withInvisiblesShowing
expect(leftEditorView.find(".line:first").text()).toBe withInvisiblesShowing
leftEditorView.getPaneView().getModel().splitDown(copyActiveItem: true)
lowerLeftEditorView = atom.workspaceView.getActiveView()
expect(lowerLeftEditorView.find(".line:first").text()).toBe withInvisiblesShowing
atom.workspaceView.trigger "window:toggle-invisibles"
expect(rightEditorView.find(".line:first").text()).toBe " "
expect(leftEditorView.find(".line:first").text()).toBe " "
rightEditorView.getPaneView().getModel().splitDown(copyActiveItem: true)
lowerRightEditorView = atom.workspaceView.getActiveView()
expect(lowerRightEditorView.find(".line:first").text()).toBe " "
describe ".eachEditorView(callback)", ->
beforeEach ->
atom.workspaceView.attachToDom()
it "invokes the callback for existing editor", ->
count = 0
callbackEditor = null
callback = (editor) ->
callbackEditor = editor
count++
atom.workspaceView.eachEditorView(callback)
expect(count).toBe 1
expect(callbackEditor).toBe atom.workspaceView.getActiveView()
it "invokes the callback for new editor", ->
count = 0
callbackEditor = null
callback = (editor) ->
callbackEditor = editor
count++
atom.workspaceView.eachEditorView(callback)
count = 0
callbackEditor = null
atom.workspaceView.getActiveView().getPaneView().getModel().splitRight(copyActiveItem: true)
expect(count).toBe 1
expect(callbackEditor).toBe atom.workspaceView.getActiveView()
it "does not invoke the callback for mini editors", ->
editorViewCreatedHandler = jasmine.createSpy('editorViewCreatedHandler')
atom.workspaceView.eachEditorView(editorViewCreatedHandler)
editorViewCreatedHandler.reset()
miniEditor = new TextEditorView(mini: true)
atom.workspaceView.append(miniEditor)
expect(editorViewCreatedHandler).not.toHaveBeenCalled()
it "returns a subscription that can be disabled", ->
count = 0
callback = (editor) -> count++
subscription = atom.workspaceView.eachEditorView(callback)
expect(count).toBe 1
atom.workspaceView.getActiveView().getPaneView().getModel().splitRight(copyActiveItem: true)
expect(count).toBe 2
subscription.off()
atom.workspaceView.getActiveView().getPaneView().getModel().splitRight(copyActiveItem: true)
expect(count).toBe 2
describe "core:close", ->
it "closes the active pane item until all that remains is a single empty pane", ->
atom.config.set('core.destroyEmptyPanes', true)
paneView1 = atom.workspaceView.getActivePaneView()
editorView = atom.workspaceView.getActiveView()
editorView.getPaneView().getModel().splitRight(copyActiveItem: true)
paneView2 = atom.workspaceView.getActivePaneView()
expect(paneView1).not.toBe paneView2
expect(atom.workspaceView.getPaneViews()).toHaveLength 2
atom.workspaceView.trigger('core:close')
expect(atom.workspaceView.getActivePaneView().getItems()).toHaveLength 1
expect(atom.workspaceView.getPaneViews()).toHaveLength 1
atom.workspaceView.trigger('core:close')
expect(atom.workspaceView.getActivePaneView().getItems()).toHaveLength 0
expect(atom.workspaceView.getPaneViews()).toHaveLength 1
describe "the scrollbar visibility class", ->
it "has a class based on the style of the scrollbar", ->
style = 'legacy'
scrollbarStyle = require 'scrollbar-style'
spyOn(scrollbarStyle, 'getPreferredScrollbarStyle').andCallFake -> style
atom.workspaceView.element.observeScrollbarStyle()
expect(atom.workspaceView).toHaveClass 'scrollbars-visible-always'
style = 'overlay'
atom.workspaceView.element.observeScrollbarStyle()
expect(atom.workspaceView).toHaveClass 'scrollbars-visible-when-scrolling'
describe "editor font styling", ->
[editorNode, editor] = []
beforeEach ->
atom.workspaceView.attachToDom()
editorNode = atom.workspaceView.find('atom-text-editor')[0]
editor = atom.workspaceView.find('atom-text-editor').view().getEditor()
it "updates the font-size based on the 'editor.fontSize' config value", ->
initialCharWidth = editor.getDefaultCharWidth()
expect(getComputedStyle(editorNode).fontSize).toBe atom.config.get('editor.fontSize') + 'px'
atom.config.set('editor.fontSize', atom.config.get('editor.fontSize') + 5)
expect(getComputedStyle(editorNode).fontSize).toBe atom.config.get('editor.fontSize') + 'px'
expect(editor.getDefaultCharWidth()).toBeGreaterThan initialCharWidth
it "updates the font-family based on the 'editor.fontFamily' config value", ->
initialCharWidth = editor.getDefaultCharWidth()
expect(getComputedStyle(editorNode).fontFamily).toBe atom.config.get('editor.fontFamily')
atom.config.set('editor.fontFamily', 'sans-serif')
expect(getComputedStyle(editorNode).fontFamily).toBe atom.config.get('editor.fontFamily')
expect(editor.getDefaultCharWidth()).not.toBe initialCharWidth
it "updates the line-height based on the 'editor.lineHeight' config value", ->
initialLineHeight = editor.getLineHeightInPixels()
atom.config.set('editor.lineHeight', '30px')
expect(getComputedStyle(editorNode).lineHeight).toBe atom.config.get('editor.lineHeight')
expect(editor.getLineHeightInPixels()).not.toBe initialLineHeight
describe 'panel containers', ->
workspaceElement = null
beforeEach ->
workspaceElement = atom.views.getView(atom.workspace)
it 'inserts panel container elements in the correct places in the DOM', ->
leftContainer = workspaceElement.querySelector('atom-panel-container.left')
rightContainer = workspaceElement.querySelector('atom-panel-container.right')
expect(leftContainer.nextSibling).toBe workspaceElement.verticalAxis
expect(rightContainer.previousSibling).toBe workspaceElement.verticalAxis
topContainer = workspaceElement.querySelector('atom-panel-container.top')
bottomContainer = workspaceElement.querySelector('atom-panel-container.bottom')
expect(topContainer.nextSibling).toBe workspaceElement.paneContainer
expect(bottomContainer.previousSibling).toBe workspaceElement.paneContainer
modalContainer = workspaceElement.querySelector('atom-panel-container.modal')
expect(modalContainer.parentNode).toBe workspaceElement