mirror of
https://github.com/atom/atom.git
synced 2026-02-12 07:35:14 -05:00
Merge remote-tracking branch 'upstream/master' into move-lines-up-and-down-with-multiple-selections
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
{$$} = require '../src/space-pen-extensions'
|
||||
|
||||
ContextMenuManager = require '../src/context-menu-manager'
|
||||
|
||||
describe "ContextMenuManager", ->
|
||||
|
||||
@@ -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) {'
|
||||
|
||||
55
spec/dom-element-pool-spec.coffee
Normal file
55
spec/dom-element-pool-spec.coffee
Normal 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()
|
||||
0
spec/fixtures/packages/package-with-cached-incompatible-native-module/main.js
vendored
Normal file
0
spec/fixtures/packages/package-with-cached-incompatible-native-module/main.js
vendored
Normal file
12
spec/fixtures/packages/package-with-cached-incompatible-native-module/package.json
vendored
Normal file
12
spec/fixtures/packages/package-with-cached-incompatible-native-module/package.json
vendored
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
0
spec/fixtures/packages/package-with-ignored-incompatible-native-module/main.js
vendored
Normal file
0
spec/fixtures/packages/package-with-ignored-incompatible-native-module/main.js
vendored
Normal file
0
spec/fixtures/packages/package-with-ignored-incompatible-native-module/node_modules/compatible-native-module/main.js
generated
vendored
Normal file
0
spec/fixtures/packages/package-with-ignored-incompatible-native-module/node_modules/compatible-native-module/main.js
generated
vendored
Normal file
4
spec/fixtures/packages/package-with-ignored-incompatible-native-module/node_modules/compatible-native-module/package.json
generated
vendored
Normal file
4
spec/fixtures/packages/package-with-ignored-incompatible-native-module/node_modules/compatible-native-module/package.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "compatible-native-module",
|
||||
"main": "./main.js"
|
||||
}
|
||||
0
spec/fixtures/packages/package-with-ignored-incompatible-native-module/node_modules/native-module/build/Release/native.node
generated
vendored
Normal file
0
spec/fixtures/packages/package-with-ignored-incompatible-native-module/node_modules/native-module/build/Release/native.node
generated
vendored
Normal file
1
spec/fixtures/packages/package-with-ignored-incompatible-native-module/node_modules/native-module/main.js
generated
vendored
Normal file
1
spec/fixtures/packages/package-with-ignored-incompatible-native-module/node_modules/native-module/main.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
throw new Error("this simulates a native module's failure to load")
|
||||
4
spec/fixtures/packages/package-with-ignored-incompatible-native-module/node_modules/native-module/package.json
generated
vendored
Normal file
4
spec/fixtures/packages/package-with-ignored-incompatible-native-module/node_modules/native-module/package.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "native-module",
|
||||
"main": "./main.js"
|
||||
}
|
||||
12
spec/fixtures/packages/package-with-ignored-incompatible-native-module/package.json
vendored
Normal file
12
spec/fixtures/packages/package-with-ignored-incompatible-native-module/package.json
vendored
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
197
spec/pane-element-spec.coffee
Normal file
197
spec/pane-element-spec.coffee
Normal 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()
|
||||
@@ -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()
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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]]])
|
||||
|
||||
@@ -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", ->
|
||||
|
||||
35
spec/token-iterator-spec.coffee
Normal file
35
spec/token-iterator-spec.coffee
Normal 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']
|
||||
@@ -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()
|
||||
|
||||
@@ -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", ->
|
||||
|
||||
@@ -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'
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user