Merge remote-tracking branch 'upstream/master' into move-lines-up-and-down-with-multiple-selections

This commit is contained in:
Luke Pommersheim
2015-09-23 08:38:37 +02:00
163 changed files with 3401 additions and 14348 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

@@ -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

@@ -1,5 +1,3 @@
{$$} = require '../src/space-pen-extensions'
ContextMenuManager = require '../src/context-menu-manager'
describe "ContextMenuManager", ->

View File

@@ -281,6 +281,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) {'

View File

@@ -0,0 +1,55 @@
DOMElementPool = require '../src/dom-element-pool'
describe "DOMElementPool", ->
domElementPool = null
beforeEach ->
domElementPool = new DOMElementPool
it "builds DOM nodes, recycling them when they are freed", ->
[div, span1, span2, span3, span4, span5] = elements = [
domElementPool.build("div")
domElementPool.build("span")
domElementPool.build("span")
domElementPool.build("span")
domElementPool.build("span")
domElementPool.build("span")
]
div.appendChild(span1)
span1.appendChild(span2)
div.appendChild(span3)
span3.appendChild(span4)
domElementPool.freeElementAndDescendants(div)
domElementPool.freeElementAndDescendants(span5)
expect(elements).toContain(domElementPool.build("div"))
expect(elements).toContain(domElementPool.build("span"))
expect(elements).toContain(domElementPool.build("span"))
expect(elements).toContain(domElementPool.build("span"))
expect(elements).toContain(domElementPool.build("span"))
expect(elements).toContain(domElementPool.build("span"))
expect(elements).not.toContain(domElementPool.build("div"))
expect(elements).not.toContain(domElementPool.build("span"))
it "forgets free nodes after being cleared", ->
span = domElementPool.build("span")
div = domElementPool.build("div")
domElementPool.freeElementAndDescendants(span)
domElementPool.freeElementAndDescendants(div)
domElementPool.clear()
expect(domElementPool.build("span")).not.toBe(span)
expect(domElementPool.build("div")).not.toBe(div)
it "throws an error when trying to free the same node twice", ->
div = domElementPool.build("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,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,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

@@ -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

@@ -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'
@@ -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

@@ -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)
@@ -209,33 +213,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 +303,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 +401,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 +442,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 +452,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 +464,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 +476,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 +493,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 +537,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 +565,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 +750,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 +893,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 +906,7 @@ describe "PackageManager", ->
pack = atom.packages.disablePackage(packageName)
waitsFor ->
waitsFor 'did-change-active-themes event to fire', 500, ->
didChangeActiveThemesHandler.callCount is 1
runs ->

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

@@ -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,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

@@ -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 ->

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
@@ -171,7 +168,6 @@ afterEach ->
atom.workspace?.destroy()
atom.workspace = null
atom.__workspaceView = null
delete atom.state.workspace
atom.project?.destroy()
@@ -181,7 +177,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 +275,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 +333,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)

View File

@@ -1,12 +1,11 @@
_ = require 'underscore-plus'
{extend, flatten, toArray, last} = _
TextEditorView = require '../src/text-editor-view'
TextEditorComponent = require '../src/text-editor-component'
TextEditorElement = require '../src/text-editor-element'
nbsp = String.fromCharCode(160)
describe "TextEditorComponent", ->
[contentNode, editor, wrapperView, wrapperNode, component, componentNode, verticalScrollbarNode, horizontalScrollbarNode] = []
[contentNode, editor, wrapperNode, component, componentNode, verticalScrollbarNode, horizontalScrollbarNode] = []
[lineHeightInPixels, charWidth, nextAnimationFrame, noAnimationFrame, tileSize, tileHeightInPixels] = []
beforeEach ->
@@ -34,12 +33,13 @@ describe "TextEditorComponent", ->
contentNode = document.querySelector('#jasmine-content')
contentNode.style.width = '1000px'
wrapperView = new TextEditorView(editor, {tileSize})
wrapperView.attachToDom()
wrapperNode = wrapperView.element
wrapperNode = new TextEditorElement()
wrapperNode.tileSize = tileSize
wrapperNode.initialize(editor)
wrapperNode.setUpdatedSynchronously(false)
jasmine.attachToDOM(wrapperNode)
{component} = wrapperView
{component} = wrapperNode
component.setFontFamily('monospace')
component.setLineHeight(1.3)
component.setFontSize(20)
@@ -108,7 +108,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(tilesNodes[0].style.zIndex).toBe("2")
expect(tilesNodes[1].style.zIndex).toBe("1")
@@ -118,7 +118,7 @@ describe "TextEditorComponent", ->
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(tilesNodes[0].style.zIndex).toBe("3")
expect(tilesNodes[1].style.zIndex).toBe("2")
@@ -130,7 +130,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(tilesNodes.length).toBe(3)
@@ -158,7 +158,7 @@ describe "TextEditorComponent", ->
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(component.lineNodeForScreenRow(2)).toBeUndefined()
expect(tilesNodes.length).toBe(3)
@@ -187,7 +187,7 @@ describe "TextEditorComponent", ->
editor.getBuffer().deleteRows(0, 1)
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
@@ -202,7 +202,7 @@ describe "TextEditorComponent", ->
editor.getBuffer().insert([0, 0], '\n\n')
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
@@ -294,7 +294,7 @@ describe "TextEditorComponent", ->
editorFullWidth = editor.getScrollWidth() + editor.getVerticalScrollbarWidth()
for lineNode in lineNodes
expect(lineNode.style.width).toBe editorFullWidth + 'px'
expect(lineNode.getBoundingClientRect().width).toBe(editorFullWidth)
componentNode.style.width = gutterWidth + editor.getScrollWidth() + 100 + 'px'
component.measureDimensions()
@@ -302,7 +302,7 @@ describe "TextEditorComponent", ->
scrollViewWidth = scrollViewNode.offsetWidth
for lineNode in lineNodes
expect(lineNode.style.width).toBe scrollViewWidth + 'px'
expect(lineNode.getBoundingClientRect().width).toBe(scrollViewWidth)
it "renders an nbsp on empty lines when no line-ending character is defined", ->
atom.config.set("editor.showInvisibles", false)
@@ -313,7 +313,7 @@ describe "TextEditorComponent", ->
backgroundColor = getComputedStyle(wrapperNode).backgroundColor
expect(linesNode.style.backgroundColor).toBe backgroundColor
for tileNode in linesNode.querySelectorAll(".tile")
for tileNode in component.tileNodesForLines()
expect(tileNode.style.backgroundColor).toBe(backgroundColor)
wrapperNode.style.backgroundColor = 'rgb(255, 0, 0)'
@@ -322,7 +322,7 @@ describe "TextEditorComponent", ->
advanceClock(atom.views.documentPollingInterval)
nextAnimationFrame()
expect(linesNode.style.backgroundColor).toBe 'rgb(255, 0, 0)'
for tileNode in linesNode.querySelectorAll(".tile")
for tileNode in component.tileNodesForLines()
expect(tileNode.style.backgroundColor).toBe("rgb(255, 0, 0)")
@@ -370,6 +370,24 @@ describe "TextEditorComponent", ->
expect(leafNodes[0].classList.contains('trailing-whitespace')).toBe true
expect(leafNodes[0].classList.contains('leading-whitespace')).toBe false
it "keeps rebuilding lines when continuous reflow is on", ->
wrapperNode.setContinuousReflow(true)
oldLineNodes = componentNode.querySelectorAll(".line")
advanceClock(10)
expect(nextAnimationFrame).toBe(noAnimationFrame)
advanceClock(component.presenter.minimumReflowInterval - 10)
nextAnimationFrame()
newLineNodes = componentNode.querySelectorAll(".line")
expect(oldLineNodes).not.toEqual(newLineNodes)
wrapperNode.setContinuousReflow(false)
advanceClock(component.presenter.minimumReflowInterval)
expect(nextAnimationFrame).toBe(noAnimationFrame)
describe "when showInvisibles is enabled", ->
invisibles = null
@@ -606,7 +624,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLineNumbers()
expect(tilesNodes[0].style.zIndex).toBe("2")
expect(tilesNodes[1].style.zIndex).toBe("1")
@@ -616,33 +634,13 @@ describe "TextEditorComponent", ->
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLineNumbers()
expect(tilesNodes[0].style.zIndex).toBe("3")
expect(tilesNodes[1].style.zIndex).toBe("2")
expect(tilesNodes[2].style.zIndex).toBe("1")
expect(tilesNodes[3].style.zIndex).toBe("0")
it "renders higher line numbers in front of lower ones", ->
wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px'
component.measureDimensions()
nextAnimationFrame()
# Tile 0
expect(component.lineNumberNodeForScreenRow(0).style.zIndex).toBe("2")
expect(component.lineNumberNodeForScreenRow(1).style.zIndex).toBe("1")
expect(component.lineNumberNodeForScreenRow(2).style.zIndex).toBe("0")
# Tile 1
expect(component.lineNumberNodeForScreenRow(3).style.zIndex).toBe("2")
expect(component.lineNumberNodeForScreenRow(4).style.zIndex).toBe("1")
expect(component.lineNumberNodeForScreenRow(5).style.zIndex).toBe("0")
# Tile 2
expect(component.lineNumberNodeForScreenRow(6).style.zIndex).toBe("2")
expect(component.lineNumberNodeForScreenRow(7).style.zIndex).toBe("1")
expect(component.lineNumberNodeForScreenRow(8).style.zIndex).toBe("0")
it "gives the line numbers container the same height as the wrapper node", ->
linesNode = componentNode.querySelector(".line-numbers")
@@ -663,7 +661,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLineNumbers()
expect(tilesNodes.length).toBe(3)
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
@@ -689,7 +687,7 @@ describe "TextEditorComponent", ->
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLineNumbers()
expect(component.lineNumberNodeForScreenRow(2)).toBeUndefined()
expect(tilesNodes.length).toBe(3)
@@ -791,7 +789,7 @@ describe "TextEditorComponent", ->
lineNumbersNode = gutterNode.querySelector('.line-numbers')
{backgroundColor} = getComputedStyle(wrapperNode)
expect(lineNumbersNode.style.backgroundColor).toBe backgroundColor
for tileNode in lineNumbersNode.querySelectorAll(".tile")
for tileNode in component.tileNodesForLineNumbers()
expect(tileNode.style.backgroundColor).toBe(backgroundColor)
# favor gutter color if it's assigned
@@ -800,7 +798,7 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
expect(lineNumbersNode.style.backgroundColor).toBe 'rgb(255, 0, 0)'
for tileNode in lineNumbersNode.querySelectorAll(".tile")
for tileNode in component.tileNodesForLineNumbers()
expect(tileNode.style.backgroundColor).toBe("rgb(255, 0, 0)")
it "hides or shows the gutter based on the '::isLineNumberGutterVisible' property on the model and the global 'editor.showLineNumbers' config setting", ->
@@ -827,6 +825,24 @@ describe "TextEditorComponent", ->
expect(componentNode.querySelector('.gutter').style.display).toBe ''
expect(component.lineNumberNodeForScreenRow(3)?).toBe true
it "keeps rebuilding line numbers when continuous reflow is on", ->
wrapperNode.setContinuousReflow(true)
oldLineNodes = componentNode.querySelectorAll(".line-number")
advanceClock(10)
expect(nextAnimationFrame).toBe(noAnimationFrame)
advanceClock(component.presenter.minimumReflowInterval - 10)
nextAnimationFrame()
newLineNodes = componentNode.querySelectorAll(".line-number")
expect(oldLineNodes).not.toEqual(newLineNodes)
wrapperNode.setContinuousReflow(false)
advanceClock(component.presenter.minimumReflowInterval)
expect(nextAnimationFrame).toBe(noAnimationFrame)
describe "fold decorations", ->
describe "rendering fold decorations", ->
it "adds the foldable class to line numbers when the line is foldable", ->
@@ -1006,10 +1022,11 @@ describe "TextEditorComponent", ->
cursor = componentNode.querySelector('.cursor')
cursorRect = cursor.getBoundingClientRect()
cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.source.js').firstChild
cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.source.js').childNodes[2]
range = document.createRange()
range.setStart(cursorLocationTextNode, 3)
range.setEnd(cursorLocationTextNode, 4)
range.setStart(cursorLocationTextNode, 0)
range.setEnd(cursorLocationTextNode, 1)
rangeRect = range.getBoundingClientRect()
expect(cursorRect.left).toBe rangeRect.left
@@ -1133,7 +1150,7 @@ describe "TextEditorComponent", ->
it "renders 2 regions for 2-line selections", ->
editor.setSelectedScreenRange([[1, 6], [2, 10]])
nextAnimationFrame()
tileNode = componentNode.querySelector(".lines").querySelectorAll(".tile")[0]
tileNode = component.tileNodesForLines()[0]
regions = tileNode.querySelectorAll('.selection .region')
expect(regions.length).toBe 2
@@ -1154,7 +1171,7 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
# Tile 0
tileNode = componentNode.querySelector(".lines").querySelectorAll(".tile")[0]
tileNode = component.tileNodesForLines()[0]
regions = tileNode.querySelectorAll('.selection .region')
expect(regions.length).toBe(3)
@@ -1177,7 +1194,7 @@ describe "TextEditorComponent", ->
expect(region3Rect.right).toBe tileNode.getBoundingClientRect().right
# Tile 3
tileNode = componentNode.querySelector(".lines").querySelectorAll(".tile")[1]
tileNode = component.tileNodesForLines()[1]
regions = tileNode.querySelectorAll('.selection .region')
expect(regions.length).toBe(3)
@@ -1850,7 +1867,7 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [10, 0]]
it "autoscrolls when the cursor exceeds the boundaries of the editor", ->
it "autoscrolls when the cursor approaches the boundaries of the editor", ->
wrapperNode.style.height = '100px'
wrapperNode.style.width = '100px'
component.measureDimensions()
@@ -1860,26 +1877,26 @@ describe "TextEditorComponent", ->
expect(editor.getScrollLeft()).toBe(0)
linesNode.dispatchEvent(buildMouseEvent('mousedown', {clientX: 0, clientY: 0}, which: 1))
linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 150, clientY: 50}, which: 1))
linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 100, clientY: 50}, which: 1))
nextAnimationFrame()
expect(editor.getScrollTop()).toBe(0)
expect(editor.getScrollLeft()).toBeGreaterThan(0)
linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 150, clientY: 150}, which: 1))
linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 100, clientY: 100}, which: 1))
nextAnimationFrame()
expect(editor.getScrollTop()).toBeGreaterThan(0)
previousScrollTop = editor.getScrollTop()
previousScrollLeft = editor.getScrollLeft()
linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: -20, clientY: 50}, which: 1))
linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 10, clientY: 50}, which: 1))
nextAnimationFrame()
expect(editor.getScrollTop()).toBe(previousScrollTop)
expect(editor.getScrollLeft()).toBeLessThan(previousScrollLeft)
linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: -20, clientY: -20}, which: 1))
linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 10, clientY: 10}, which: 1))
nextAnimationFrame()
expect(editor.getScrollTop()).toBeLessThan(previousScrollTop)
@@ -1898,16 +1915,13 @@ describe "TextEditorComponent", ->
expect(nextAnimationFrame).toBe noAnimationFrame
expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [6, 8]]
it "stops selecting if a textInput event occurs during the drag", ->
it "stops selecting before the buffer is modified during the drag", ->
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([2, 4]), which: 1))
linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([6, 8]), which: 1))
nextAnimationFrame()
expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [6, 8]]
inputEvent = new Event('textInput')
inputEvent.data = 'x'
Object.defineProperty(inputEvent, 'target', get: -> componentNode.querySelector('.hidden-input'))
componentNode.dispatchEvent(inputEvent)
editor.insertText('x')
nextAnimationFrame()
expect(editor.getSelectedScreenRange()).toEqual [[2, 5], [2, 5]]
@@ -1916,6 +1930,20 @@ describe "TextEditorComponent", ->
expect(nextAnimationFrame).toBe noAnimationFrame
expect(editor.getSelectedScreenRange()).toEqual [[2, 5], [2, 5]]
linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([2, 4]), which: 1))
linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([5, 4]), which: 1))
nextAnimationFrame()
expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [5, 4]]
editor.delete()
nextAnimationFrame()
expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [2, 4]]
linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([8, 0]), which: 1))
expect(nextAnimationFrame).toBe noAnimationFrame
expect(editor.getSelectedScreenRange()).toEqual [[2, 4], [2, 4]]
describe "when the command key is held down", ->
it "adds a new selection and selects to the nearest screen position, then merges intersecting selections when the mouse button is released", ->
editor.setSelectedScreenRange([[4, 4], [4, 9]])
@@ -1995,7 +2023,7 @@ describe "TextEditorComponent", ->
linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([8, 4]), which: 1))
nextAnimationFrame()
expect(editor.getSelectedScreenRange()).toEqual [[5, 0], [9, 0]]
expect(editor.getSelectedScreenRange()).toEqual [[5, 0], [8, 0]]
expect(editor.getScrollTop()).toBe maximalScrollTop # does not autoscroll upward (regression)
linesNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenPosition([9, 3]), which: 1))
@@ -2086,7 +2114,7 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
expect(editor.getLastSelection().isReversed()).toBe false
it "autoscrolls when the cursor exceeds the top or bottom of the editor", ->
it "autoscrolls when the cursor approaches the top or bottom of the editor", ->
wrapperNode.style.height = 6 * lineHeightInPixels + 'px'
component.measureDimensions()
nextAnimationFrame()
@@ -2100,11 +2128,12 @@ describe "TextEditorComponent", ->
expect(editor.getScrollTop()).toBeGreaterThan 0
maxScrollTop = editor.getScrollTop()
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(5)))
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(10)))
nextAnimationFrame()
expect(editor.getScrollTop()).toBe maxScrollTop
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(2)))
gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(7)))
nextAnimationFrame()
expect(editor.getScrollTop()).toBeLessThan maxScrollTop
@@ -2355,11 +2384,11 @@ describe "TextEditorComponent", ->
inputNode.focus()
nextAnimationFrame()
expect(componentNode.classList.contains('is-focused')).toBe true
expect(wrapperView.hasClass('is-focused')).toBe true
expect(wrapperNode.classList.contains('is-focused')).toBe true
inputNode.blur()
nextAnimationFrame()
expect(componentNode.classList.contains('is-focused')).toBe false
expect(wrapperView.hasClass('is-focused')).toBe false
expect(wrapperNode.classList.contains('is-focused')).toBe false
describe "selection handling", ->
cursor = null
@@ -2396,7 +2425,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
top = 0
for tileNode in tilesNodes
@@ -2867,17 +2896,18 @@ describe "TextEditorComponent", ->
describe "hiding and showing the editor", ->
describe "when the editor is hidden when it is mounted", ->
it "defers measurement and rendering until the editor becomes visible", ->
wrapperView.remove()
wrapperNode.remove()
hiddenParent = document.createElement('div')
hiddenParent.style.display = 'none'
contentNode.appendChild(hiddenParent)
wrapperView = new TextEditorView(editor, {tileSize})
wrapperNode = wrapperView.element
wrapperView.appendTo(hiddenParent)
wrapperNode = new TextEditorElement()
wrapperNode.tileSize = tileSize
wrapperNode.initialize(editor)
hiddenParent.appendChild(wrapperNode)
{component} = wrapperView
{component} = wrapperNode
componentNode = component.getDomNode()
expect(componentNode.querySelectorAll('.line').length).toBe 0
@@ -2888,18 +2918,25 @@ describe "TextEditorComponent", ->
describe "when the lineHeight changes while the editor is hidden", ->
it "does not attempt to measure the lineHeightInPixels until the editor becomes visible again", ->
wrapperView.hide()
initialLineHeightInPixels = null
wrapperNode.style.display = 'none'
component.checkForVisibilityChange()
initialLineHeightInPixels = editor.getLineHeightInPixels()
component.setLineHeight(2)
expect(editor.getLineHeightInPixels()).toBe initialLineHeightInPixels
wrapperView.show()
wrapperNode.style.display = ''
component.checkForVisibilityChange()
expect(editor.getLineHeightInPixels()).not.toBe initialLineHeightInPixels
describe "when the fontSize changes while the editor is hidden", ->
it "does not attempt to measure the lineHeightInPixels or defaultCharWidth until the editor becomes visible again", ->
wrapperView.hide()
wrapperNode.style.display = 'none'
component.checkForVisibilityChange()
initialLineHeightInPixels = editor.getLineHeightInPixels()
initialCharWidth = editor.getDefaultCharWidth()
@@ -2907,17 +2944,22 @@ describe "TextEditorComponent", ->
expect(editor.getLineHeightInPixels()).toBe initialLineHeightInPixels
expect(editor.getDefaultCharWidth()).toBe initialCharWidth
wrapperView.show()
wrapperNode.style.display = ''
component.checkForVisibilityChange()
expect(editor.getLineHeightInPixels()).not.toBe initialLineHeightInPixels
expect(editor.getDefaultCharWidth()).not.toBe initialCharWidth
it "does not re-measure character widths until the editor is shown again", ->
wrapperView.hide()
wrapperNode.style.display = 'none'
component.checkForVisibilityChange()
component.setFontSize(22)
editor.getBuffer().insert([0, 0], 'a') # regression test against atom/atom#3318
wrapperView.show()
wrapperNode.style.display = ''
component.checkForVisibilityChange()
editor.setCursorBufferPosition([0, Infinity])
nextAnimationFrame()
@@ -2927,22 +2969,29 @@ describe "TextEditorComponent", ->
describe "when the fontFamily changes while the editor is hidden", ->
it "does not attempt to measure the defaultCharWidth until the editor becomes visible again", ->
wrapperView.hide()
wrapperNode.style.display = 'none'
component.checkForVisibilityChange()
initialLineHeightInPixels = editor.getLineHeightInPixels()
initialCharWidth = editor.getDefaultCharWidth()
component.setFontFamily('serif')
expect(editor.getDefaultCharWidth()).toBe initialCharWidth
wrapperView.show()
wrapperNode.style.display = ''
component.checkForVisibilityChange()
expect(editor.getDefaultCharWidth()).not.toBe initialCharWidth
it "does not re-measure character widths until the editor is shown again", ->
wrapperView.hide()
wrapperNode.style.display = 'none'
component.checkForVisibilityChange()
component.setFontFamily('serif')
wrapperView.show()
wrapperNode.style.display = ''
component.checkForVisibilityChange()
editor.setCursorBufferPosition([0, Infinity])
nextAnimationFrame()
@@ -2957,14 +3006,18 @@ describe "TextEditorComponent", ->
it "does not re-measure character widths until the editor is shown again", ->
atom.config.set('editor.fontFamily', 'sans-serif')
wrapperView.hide()
wrapperNode.style.display = 'none'
component.checkForVisibilityChange()
atom.themes.applyStylesheet 'test', """
.function.js {
font-weight: bold;
}
"""
wrapperView.show()
wrapperNode.style.display = ''
component.checkForVisibilityChange()
editor.setCursorBufferPosition([0, Infinity])
nextAnimationFrame()
@@ -2975,11 +3028,17 @@ describe "TextEditorComponent", ->
describe "when lines are changed while the editor is hidden", ->
it "does not measure new characters until the editor is shown again", ->
editor.setText('')
wrapperView.hide()
wrapperNode.style.display = 'none'
component.checkForVisibilityChange()
editor.setText('var z = 1')
editor.setCursorBufferPosition([0, Infinity])
nextAnimationFrame()
wrapperView.show()
wrapperNode.style.display = 'none'
component.checkForVisibilityChange()
expect(componentNode.querySelector('.cursor').style['-webkit-transform']).toBe "translate(#{9 * charWidth}px, 0px)"
describe "soft wrapping", ->
@@ -3109,26 +3168,6 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
expect(componentNode.querySelector('.placeholder-text')).toBeNull()
describe "legacy editor compatibility", ->
it "triggers the screen-lines-changed event before the editor:display-updated event", ->
editor.setSoftWrapped(true)
callingOrder = []
editor.onDidChange -> callingOrder.push 'screen-lines-changed'
wrapperView.on 'editor:display-updated', -> callingOrder.push 'editor:display-updated'
editor.insertText("HELLO! HELLO!\n HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! ")
nextAnimationFrame()
expect(callingOrder).toEqual ['screen-lines-changed', 'editor:display-updated', 'editor:display-updated']
it "works with the ::setEditorHeightInLines and ::setEditorWidthInChars helpers", ->
setEditorHeightInLines(wrapperView, 7)
nextAnimationFrame()
expect(componentNode.offsetHeight).toBe lineHeightInPixels * 7
setEditorWidthInChars(wrapperView, 10)
expect(componentNode.querySelector('.scroll-view').offsetWidth).toBe charWidth * 10
describe "grammar data attributes", ->
it "adds and updates the grammar data attribute based on the current grammar", ->
expect(wrapperNode.dataset.grammar).toBe 'source js'
@@ -3143,10 +3182,10 @@ describe "TextEditorComponent", ->
describe "detaching and reattaching the editor (regression)", ->
it "does not throw an exception", ->
wrapperView.detach()
wrapperView.attachToDom()
wrapperNode.remove()
jasmine.attachToDOM(wrapperNode)
wrapperView.trigger('core:move-right')
atom.commands.dispatch(wrapperNode, 'core:move-right')
expect(editor.getCursorBufferPosition()).toEqual [0, 1]

View File

@@ -2035,15 +2035,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

View File

@@ -1284,7 +1284,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,6 +1299,13 @@ describe "TextEditor", ->
editor.selectLinesContainingCursors()
expect(editor.getSelectedBufferRange()).toEqual [[0, 0], [2, 0]]
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]]
it "autoscrolls to the selection", ->
editor.setLineHeightInPixels(10)
editor.setDefaultCharWidth(10)
@@ -2892,6 +2899,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]]])

View File

@@ -1,6 +1,4 @@
path = require 'path'
{$, $$} = require '../src/space-pen-extensions'
fs = require 'fs-plus'
temp = require 'temp'
@@ -26,19 +24,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 = themeManager.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 -> themeManager.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 = themeManager.getActiveThemes()
expect(themes).toHaveLength(names.length)
describe "when the core.themes config value contains invalid entry", ->
it "ignores theme", ->
@@ -88,7 +87,7 @@ describe "ThemeManager", ->
runs ->
didChangeActiveThemesHandler.reset()
expect($('style.theme')).toHaveLength 0
expect(document.querySelectorAll('style.theme')).toHaveLength 0
atom.config.set('core.themes', ['atom-dark-ui'])
waitsFor ->
@@ -96,8 +95,8 @@ describe "ThemeManager", ->
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 ->
@@ -105,9 +104,9 @@ describe "ThemeManager", ->
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 +114,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,7 +122,7 @@ describe "ThemeManager", ->
didChangeActiveThemesHandler.callCount is 1
runs ->
expect($('style[priority=1]')).toHaveLength 2
expect(document.querySelectorAll('style[priority="1"]')).toHaveLength 2
importPaths = themeManager.getImportPaths()
expect(importPaths.length).toBe 1
expect(importPaths[0]).toContain 'atom-dark-ui'
@@ -172,37 +171,38 @@ describe "ThemeManager", ->
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
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 themeManager.stringToId(cssPath)
expect(element.textContent).toBe fs.readFileSync(cssPath, 'utf8')
expect(element.sheet).toBe stylesheetAddedHandler.argsForCall[0][0]
# doesn't append twice
styleElementAddedHandler.reset()
themeManager.requireStylesheet(cssPath)
expect($('head style').length).toBe lengthBefore + 1
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
lengthBefore = document.querySelectorAll('head style').length
themeManager.requireStylesheet(lessPath)
expect($('head style').length).toBe lengthBefore + 1
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 themeManager.stringToId(lessPath)
expect(element.textContent).toBe """
#header {
color: #4d926f;
}
@@ -214,24 +214,25 @@ describe "ThemeManager", ->
# doesn't append twice
themeManager.requireStylesheet(lessPath)
expect($('head style').length).toBe lengthBefore + 1
$('head style[id*="sample.less"]').remove()
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'))
expect(document.querySelector('head style[source-path*="css.css"]').getAttribute('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'))
expect(document.querySelector('head style[source-path*="sample.less"]').getAttribute('source-path')).toBe themeManager.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")
expect(getComputedStyle(document.body).fontWeight).not.toBe("bold")
disposable = themeManager.requireStylesheet(cssPath)
expect($(document.body).css('font-weight')).toBe("bold")
expect(getComputedStyle(document.body).fontWeight).toBe("bold")
atom.styles.onDidRemoveStyleElement styleElementRemovedHandler = jasmine.createSpy("styleElementRemovedHandler")
themeManager.onDidRemoveStylesheet stylesheetRemovedHandler = jasmine.createSpy("stylesheetRemovedHandler")
@@ -239,7 +240,7 @@ describe "ThemeManager", ->
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()
@@ -271,9 +272,9 @@ 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", ->
@@ -288,7 +289,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
@@ -320,14 +321,14 @@ describe "ThemeManager", ->
themeManager.onDidAddStylesheet stylesheetAddedHandler = jasmine.createSpy("stylesheetAddedHandler")
spyOn(themeManager, '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
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'
@@ -354,7 +355,7 @@ describe "ThemeManager", ->
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(getComputedStyle(document.body).borderStyle).toBe 'none'
expect(stylesheetsChangedHandler).toHaveBeenCalled()
describe "when there is an error reading the stylesheet", ->

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,7 +2,6 @@ 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'
@@ -17,6 +16,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
@@ -1065,11 +1122,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 +1139,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 +1342,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