diff --git a/apm/package.json b/apm/package.json
index 5391c9972..336544d3e 100644
--- a/apm/package.json
+++ b/apm/package.json
@@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
- "atom-package-manager": "1.18.8"
+ "atom-package-manager": "1.18.10"
}
}
diff --git a/keymaps/darwin.cson b/keymaps/darwin.cson
index fa942d97c..7161a8478 100644
--- a/keymaps/darwin.cson
+++ b/keymaps/darwin.cson
@@ -132,6 +132,7 @@
'ctrl-shift-w': 'editor:select-word'
'cmd-ctrl-left': 'editor:move-selection-left'
'cmd-ctrl-right': 'editor:move-selection-right'
+ 'cmd-shift-V': 'editor:paste-without-reformatting'
# Emacs
'alt-f': 'editor:move-to-end-of-word'
diff --git a/keymaps/linux.cson b/keymaps/linux.cson
index d6ded1f90..9d3e4dbb1 100644
--- a/keymaps/linux.cson
+++ b/keymaps/linux.cson
@@ -105,6 +105,7 @@
'alt-shift-right': 'editor:select-to-next-subword-boundary'
'alt-backspace': 'editor:delete-to-beginning-of-subword'
'alt-delete': 'editor:delete-to-end-of-subword'
+ 'ctrl-shift-V': 'editor:paste-without-reformatting'
# Sublime Parity
'ctrl-a': 'core:select-all'
diff --git a/keymaps/win32.cson b/keymaps/win32.cson
index 14f5a4283..8a8e92249 100644
--- a/keymaps/win32.cson
+++ b/keymaps/win32.cson
@@ -110,6 +110,7 @@
'alt-shift-right': 'editor:select-to-next-subword-boundary'
'alt-backspace': 'editor:delete-to-beginning-of-subword'
'alt-delete': 'editor:delete-to-end-of-subword'
+ 'ctrl-shift-V': 'editor:paste-without-reformatting'
# Sublime Parity
'ctrl-a': 'core:select-all'
diff --git a/menus/darwin.cson b/menus/darwin.cson
index 055cd2405..2dffda1ef 100644
--- a/menus/darwin.cson
+++ b/menus/darwin.cson
@@ -65,6 +65,7 @@
{ label: 'Copy', command: 'core:copy' }
{ label: 'Copy Path', command: 'editor:copy-path' }
{ label: 'Paste', command: 'core:paste' }
+ { label: 'Paste Without Reformatting', command: 'editor:paste-without-reformatting' }
{ label: 'Select All', command: 'core:select-all' }
{ type: 'separator' }
{ label: 'Toggle Comments', command: 'editor:toggle-line-comments' }
diff --git a/menus/linux.cson b/menus/linux.cson
index 2a1ca47f8..b44900398 100644
--- a/menus/linux.cson
+++ b/menus/linux.cson
@@ -38,6 +38,7 @@
{ label: 'C&opy', command: 'core:copy' }
{ label: 'Copy Pat&h', command: 'editor:copy-path' }
{ label: '&Paste', command: 'core:paste' }
+ { label: 'Paste Without Reformatting', command: 'editor:paste-without-reformatting' }
{ label: 'Select &All', command: 'core:select-all' }
{ type: 'separator' }
{ label: '&Toggle Comments', command: 'editor:toggle-line-comments' }
diff --git a/menus/win32.cson b/menus/win32.cson
index 553b6017e..a921bae74 100644
--- a/menus/win32.cson
+++ b/menus/win32.cson
@@ -46,6 +46,7 @@
{ label: '&Copy', command: 'core:copy' }
{ label: 'Copy Pat&h', command: 'editor:copy-path' }
{ label: '&Paste', command: 'core:paste' }
+ { label: 'Paste Without Reformatting', command: 'editor:paste-without-reformatting' }
{ label: 'Select &All', command: 'core:select-all' }
{ type: 'separator' }
{ label: '&Toggle Comments', command: 'editor:toggle-line-comments' }
diff --git a/package.json b/package.json
index 777e96513..6ca73f8ce 100644
--- a/package.json
+++ b/package.json
@@ -14,9 +14,10 @@
"license": "MIT",
"electronVersion": "1.6.15",
"dependencies": {
+ "@atom/nsfw": "^1.0.18",
"@atom/source-map-support": "^0.3.4",
"async": "0.2.6",
- "atom-keymap": "8.2.7",
+ "atom-keymap": "8.2.8",
"atom-select-list": "^0.1.0",
"atom-ui": "0.4.1",
"babel-core": "5.8.38",
@@ -24,14 +25,14 @@
"chai": "3.5.0",
"chart.js": "^2.3.0",
"clear-cut": "^2.0.2",
- "coffee-script": "1.11.1",
+ "coffee-script": "1.12.7",
"color": "^0.7.3",
- "dedent": "^0.6.0",
+ "dedent": "^0.7.0",
"devtron": "1.3.0",
"etch": "^0.12.6",
"event-kit": "^2.4.0",
"find-parent-dir": "^0.3.0",
- "first-mate": "7.0.9",
+ "first-mate": "7.1.0",
"focus-trap": "^2.3.0",
"fs-admin": "^0.1.6",
"fs-plus": "^3.0.1",
@@ -53,7 +54,6 @@
"mocha-multi-reporters": "^1.1.4",
"mock-spawn": "^0.2.6",
"normalize-package-data": "^2.0.0",
- "nsfw": "^1.0.15",
"nslog": "^3",
"oniguruma": "6.2.1",
"pathwatcher": "8.0.1",
@@ -65,12 +65,12 @@
"scandal": "^3.1.0",
"scoped-property-store": "^0.17.0",
"scrollbar-style": "^3.2",
- "season": "^6.0.1",
+ "season": "^6.0.2",
"semver": "^4.3.3",
"service-hub": "^0.7.4",
"sinon": "1.17.4",
"temp": "^0.8.3",
- "text-buffer": "13.5.7",
+ "text-buffer": "13.7.1",
"typescript-simple": "1.0.0",
"underscore-plus": "^1.6.6",
"winreg": "^1.2.1",
@@ -91,10 +91,10 @@
"solarized-light-syntax": "1.1.2",
"about": "1.7.8",
"archive-view": "0.63.4",
- "autocomplete-atom-api": "0.10.3",
- "autocomplete-css": "0.17.3",
- "autocomplete-html": "0.8.2",
- "autocomplete-plus": "2.36.8",
+ "autocomplete-atom-api": "0.10.5",
+ "autocomplete-css": "0.17.4",
+ "autocomplete-html": "0.8.3",
+ "autocomplete-plus": "2.37.2",
"autocomplete-snippets": "1.11.2",
"autoflow": "0.29.0",
"autosave": "0.24.6",
@@ -112,63 +112,63 @@
"github": "0.7.0",
"git-diff": "1.3.6",
"go-to-line": "0.32.1",
- "grammar-selector": "0.49.6",
+ "grammar-selector": "0.49.8",
"image-view": "0.62.4",
"incompatible-packages": "0.27.3",
"keybinding-resolver": "0.38.0",
"line-ending-selector": "0.7.4",
"link": "0.31.3",
- "markdown-preview": "0.159.15",
+ "markdown-preview": "0.159.17",
"metrics": "1.2.6",
"notifications": "0.69.2",
"open-on-github": "1.2.1",
"package-generator": "1.1.1",
- "settings-view": "0.252.0",
- "snippets": "1.1.5",
+ "settings-view": "0.252.2",
+ "snippets": "1.1.7",
"spell-check": "0.72.3",
- "status-bar": "1.8.13",
- "styleguide": "0.49.7",
+ "status-bar": "1.8.14",
+ "styleguide": "0.49.8",
"symbols-view": "0.118.1",
"tabs": "0.108.0",
"timecop": "0.36.0",
- "tree-view": "0.220.0",
+ "tree-view": "0.221.0",
"update-package-dependencies": "0.12.0",
"welcome": "0.36.5",
"whitespace": "0.37.4",
"wrap-guide": "0.40.2",
"language-c": "0.58.1",
"language-clojure": "0.22.4",
- "language-coffee-script": "0.49.1",
+ "language-coffee-script": "0.49.2",
"language-csharp": "0.14.3",
- "language-css": "0.42.6",
- "language-gfm": "0.90.1",
+ "language-css": "0.42.7",
+ "language-gfm": "0.90.2",
"language-git": "0.19.1",
- "language-go": "0.44.2",
- "language-html": "0.48.1",
- "language-hyperlink": "0.16.2",
- "language-java": "0.27.4",
- "language-javascript": "0.127.5",
+ "language-go": "0.44.3",
+ "language-html": "0.48.2",
+ "language-hyperlink": "0.16.3",
+ "language-java": "0.27.5",
+ "language-javascript": "0.127.6",
"language-json": "0.19.1",
- "language-less": "0.33.0",
+ "language-less": "0.34.0",
"language-make": "0.22.3",
- "language-mustache": "0.14.3",
+ "language-mustache": "0.14.4",
"language-objective-c": "0.15.1",
"language-perl": "0.38.1",
- "language-php": "0.42.1",
+ "language-php": "0.42.2",
"language-property-list": "0.9.1",
- "language-python": "0.45.4",
- "language-ruby": "0.71.3",
+ "language-python": "0.45.5",
+ "language-ruby": "0.71.4",
"language-ruby-on-rails": "0.25.2",
- "language-sass": "0.61.1",
- "language-shellscript": "0.25.3",
+ "language-sass": "0.61.2",
+ "language-shellscript": "0.25.4",
"language-source": "0.9.0",
"language-sql": "0.25.8",
"language-text": "0.7.3",
- "language-todo": "0.29.2",
+ "language-todo": "0.29.3",
"language-toml": "0.18.1",
"language-typescript": "0.2.2",
"language-xml": "0.35.2",
- "language-yaml": "0.31.0"
+ "language-yaml": "0.31.1"
},
"private": true,
"scripts": {
diff --git a/script/lib/generate-startup-snapshot.js b/script/lib/generate-startup-snapshot.js
index 2905bca1b..333acdc0a 100644
--- a/script/lib/generate-startup-snapshot.js
+++ b/script/lib/generate-startup-snapshot.js
@@ -27,47 +27,37 @@ module.exports = function (packagedAppPath) {
coreModules.has(modulePath) ||
(relativePath.startsWith(path.join('..', 'src')) && relativePath.endsWith('-element.js')) ||
relativePath.startsWith(path.join('..', 'node_modules', 'dugite')) ||
+ relativePath.endsWith(path.join('node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js')) ||
+ relativePath.endsWith(path.join('node_modules', 'fs-extra', 'lib', 'index.js')) ||
+ relativePath.endsWith(path.join('node_modules', 'graceful-fs', 'graceful-fs.js')) ||
+ relativePath.endsWith(path.join('node_modules', 'htmlparser2', 'lib', 'index.js')) ||
+ relativePath.endsWith(path.join('node_modules', 'minimatch', 'minimatch.js')) ||
relativePath === path.join('..', 'exports', 'atom.js') ||
relativePath === path.join('..', 'src', 'electron-shims.js') ||
relativePath === path.join('..', 'src', 'safe-clipboard.js') ||
relativePath === path.join('..', 'node_modules', 'atom-keymap', 'lib', 'command-event.js') ||
relativePath === path.join('..', 'node_modules', 'babel-core', 'index.js') ||
relativePath === path.join('..', 'node_modules', 'cached-run-in-this-context', 'lib', 'main.js') ||
- relativePath === path.join('..', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') ||
- relativePath === path.join('..', 'node_modules', 'cson-parser', 'node_modules', 'coffee-script', 'lib', 'coffee-script', 'register.js') ||
relativePath === path.join('..', 'node_modules', 'decompress-zip', 'lib', 'decompress-zip.js') ||
relativePath === path.join('..', 'node_modules', 'debug', 'node.js') ||
- relativePath === path.join('..', 'node_modules', 'fs-extra', 'lib', 'index.js') ||
- relativePath === path.join('..', 'node_modules', 'github', 'node_modules', 'fs-extra', 'lib', 'index.js') ||
relativePath === path.join('..', 'node_modules', 'git-utils', 'src', 'git.js') ||
relativePath === path.join('..', 'node_modules', 'glob', 'glob.js') ||
- relativePath === path.join('..', 'node_modules', 'graceful-fs', 'graceful-fs.js') ||
- relativePath === path.join('..', 'node_modules', 'htmlparser2', 'lib', 'index.js') ||
- relativePath === path.join('..', 'node_modules', 'markdown-preview', 'node_modules', 'htmlparser2', 'lib', 'index.js') ||
- relativePath === path.join('..', 'node_modules', 'roaster', 'node_modules', 'htmlparser2', 'lib', 'index.js') ||
- relativePath === path.join('..', 'node_modules', 'task-lists', 'node_modules', 'htmlparser2', 'lib', 'index.js') ||
relativePath === path.join('..', 'node_modules', 'iconv-lite', 'lib', 'index.js') ||
relativePath === path.join('..', 'node_modules', 'less', 'index.js') ||
relativePath === path.join('..', 'node_modules', 'less', 'lib', 'less', 'fs.js') ||
relativePath === path.join('..', 'node_modules', 'less', 'lib', 'less-node', 'index.js') ||
- relativePath === path.join('..', 'node_modules', 'less', 'node_modules', 'graceful-fs', 'graceful-fs.js') ||
- relativePath === path.join('..', 'node_modules', 'minimatch', 'minimatch.js') ||
relativePath === path.join('..', 'node_modules', 'node-fetch', 'lib', 'fetch-error.js') ||
- relativePath === path.join('..', 'node_modules', 'nsfw', 'node_modules', 'fs-extra', 'lib', 'index.js') ||
relativePath === path.join('..', 'node_modules', 'superstring', 'index.js') ||
relativePath === path.join('..', 'node_modules', 'oniguruma', 'src', 'oniguruma.js') ||
relativePath === path.join('..', 'node_modules', 'request', 'index.js') ||
relativePath === path.join('..', 'node_modules', 'resolve', 'index.js') ||
relativePath === path.join('..', 'node_modules', 'resolve', 'lib', 'core.js') ||
- relativePath === path.join('..', 'node_modules', 'scandal', 'node_modules', 'minimatch', 'minimatch.js') ||
relativePath === path.join('..', 'node_modules', 'settings-view', 'node_modules', 'glob', 'glob.js') ||
- relativePath === path.join('..', 'node_modules', 'settings-view', 'node_modules', 'minimatch', 'minimatch.js') ||
relativePath === path.join('..', 'node_modules', 'spellchecker', 'lib', 'spellchecker.js') ||
relativePath === path.join('..', 'node_modules', 'spelling-manager', 'node_modules', 'natural', 'lib', 'natural', 'index.js') ||
relativePath === path.join('..', 'node_modules', 'tar', 'tar.js') ||
relativePath === path.join('..', 'node_modules', 'temp', 'lib', 'temp.js') ||
- relativePath === path.join('..', 'node_modules', 'tmp', 'lib', 'tmp.js') ||
- relativePath === path.join('..', 'node_modules', 'tree-view', 'node_modules', 'minimatch', 'minimatch.js')
+ relativePath === path.join('..', 'node_modules', 'tmp', 'lib', 'tmp.js')
)
}
}).then((snapshotScript) => {
diff --git a/spec/fixtures/packages/package-with-rb-filetype/grammars/rb.cson b/spec/fixtures/packages/package-with-rb-filetype/grammars/rb.cson
index 8b4d85412..37aac3d4d 100644
--- a/spec/fixtures/packages/package-with-rb-filetype/grammars/rb.cson
+++ b/spec/fixtures/packages/package-with-rb-filetype/grammars/rb.cson
@@ -1,5 +1,6 @@
'name': 'Test Ruby'
'scopeName': 'test.rb'
+'firstLineMatch': '^\\#!.*(?:\\s|\\/)(?:testruby)(?:$|\\s)'
'fileTypes': [
'rb'
]
diff --git a/spec/grammars-spec.coffee b/spec/grammars-spec.coffee
index 7b70797ba..db716528d 100644
--- a/spec/grammars-spec.coffee
+++ b/spec/grammars-spec.coffee
@@ -120,6 +120,8 @@ describe "the `grammars` global", ->
atom.grammars.grammarForScopeName('source.ruby').bundledPackage = true
atom.grammars.grammarForScopeName('test.rb').bundledPackage = false
+ expect(atom.grammars.selectGrammar('test.rb', '#!/usr/bin/env ruby').scopeName).toBe 'source.ruby'
+ expect(atom.grammars.selectGrammar('test.rb', '#!/usr/bin/env testruby').scopeName).toBe 'test.rb'
expect(atom.grammars.selectGrammar('test.rb').scopeName).toBe 'test.rb'
describe "when there is no file path", ->
diff --git a/spec/selection-spec.coffee b/spec/selection-spec.coffee
index cb070310a..b0e65be30 100644
--- a/spec/selection-spec.coffee
+++ b/spec/selection-spec.coffee
@@ -103,6 +103,11 @@ describe "Selection", ->
selection.insertText("\r\n", autoIndent: true)
expect(buffer.lineForRow(2)).toBe " "
+ it "does not adjust the indent of trailing lines if preserveTrailingLineIndentation is true", ->
+ selection.setBufferRange [[5, 0], [5, 0]]
+ selection.insertText(' foo\n bar\n', preserveTrailingLineIndentation: true, indentBasis: 1)
+ expect(buffer.lineForRow(6)).toBe(' bar')
+
describe ".fold()", ->
it "folds the buffer range spanned by the selection", ->
selection.setBufferRange([[0, 3], [1, 6]])
diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee
index c20bfc827..7621f9cae 100644
--- a/spec/spec-helper.coffee
+++ b/spec/spec-helper.coffee
@@ -58,7 +58,7 @@ if specPackagePath = FindParentDir.sync(testPaths[0], 'package.json')
if specDirectory = FindParentDir.sync(testPaths[0], 'fixtures')
specProjectPath = path.join(specDirectory, 'fixtures')
else
- specProjectPath = path.join(__dirname, 'fixtures')
+ specProjectPath = require('os').tmpdir()
beforeEach ->
atom.project.setPaths([specProjectPath])
diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js
index 41d770212..5f0a28883 100644
--- a/spec/text-editor-component-spec.js
+++ b/spec/text-editor-component-spec.js
@@ -1896,6 +1896,8 @@ describe('TextEditorComponent', () => {
const decoration = editor.decorateMarker(marker, {type: 'overlay', item: overlayElement, class: 'a'})
await component.getNextUpdatePromise()
+ const overlayComponent = component.overlayComponents.values().next().value
+
const overlayWrapper = overlayElement.parentElement
expect(overlayWrapper.classList.contains('a')).toBe(true)
expect(overlayWrapper.getBoundingClientRect().top).toBe(clientTopForLine(component, 5))
@@ -1926,12 +1928,12 @@ describe('TextEditorComponent', () => {
await setScrollTop(component, 20)
expect(overlayWrapper.getBoundingClientRect().top).toBe(clientTopForLine(component, 5))
overlayElement.style.height = 60 + 'px'
- await component.getNextUpdatePromise()
+ await overlayComponent.getNextUpdatePromise()
expect(overlayWrapper.getBoundingClientRect().bottom).toBe(clientTopForLine(component, 4))
// Does not flip the overlay vertically if it would overflow the top of the window
overlayElement.style.height = 80 + 'px'
- await component.getNextUpdatePromise()
+ await overlayComponent.getNextUpdatePromise()
expect(overlayWrapper.getBoundingClientRect().top).toBe(clientTopForLine(component, 5))
// Can update overlay wrapper class
diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee
index 53011fdcc..b8d4bdcf9 100644
--- a/spec/text-editor-spec.coffee
+++ b/spec/text-editor-spec.coffee
@@ -1871,7 +1871,7 @@ describe "TextEditor", ->
expect(selection1.getBufferRange()).toEqual [[2, 2], [3, 3]]
describe "when the 'preserveFolds' option is false (the default)", ->
- it "removes folds that contain the selections", ->
+ it "removes folds that contain one or both of the selection's end points", ->
editor.setSelectedBufferRange([[0, 0], [0, 0]])
editor.foldBufferRowRange(1, 4)
editor.foldBufferRowRange(2, 3)
@@ -1884,6 +1884,9 @@ describe "TextEditor", ->
expect(editor.isFoldedAtScreenRow(6)).toBeFalsy()
expect(editor.isFoldedAtScreenRow(10)).toBeTruthy()
+ editor.setSelectedBufferRange([[10, 0], [12, 0]])
+ expect(editor.isFoldedAtScreenRow(10)).toBeTruthy()
+
describe "when the 'preserveFolds' option is true", ->
it "does not remove folds that contain the selections", ->
editor.setSelectedBufferRange([[0, 0], [0, 0]])
@@ -4222,6 +4225,19 @@ describe "TextEditor", ->
expect(editor.lineTextForBufferRow(3)).toBe(" if (items.length <= 1) return items;")
expect(editor.getCursorBufferPosition()).toEqual([3, 13])
+ it "respects options that preserve the formatting of the pasted text", ->
+ editor.update({autoIndentOnPaste: true})
+ atom.clipboard.write("a(x);\n b(x);\r\nc(x);\n", indentBasis: 0)
+ editor.setCursorBufferPosition([5, 0])
+ editor.insertText(' ')
+ editor.pasteText({autoIndent: false, preserveTrailingLineIndentation: true, normalizeLineEndings: false})
+
+ expect(editor.lineTextForBufferRow(5)).toBe " a(x);"
+ expect(editor.lineTextForBufferRow(6)).toBe " b(x);"
+ expect(editor.buffer.lineEndingForRow(6)).toBe "\r\n"
+ expect(editor.lineTextForBufferRow(7)).toBe "c(x);"
+ expect(editor.lineTextForBufferRow(8)).toBe " current = items.shift();"
+
describe ".indentSelectedRows()", ->
describe "when nothing is selected", ->
describe "when softTabs is enabled", ->
@@ -4363,108 +4379,6 @@ describe "TextEditor", ->
expect(editor.lineTextForBufferRow(4)).toBe " }"
expect(editor.lineTextForBufferRow(5)).toBe " i=1"
- describe ".toggleLineCommentsInSelection()", ->
- it "toggles comments on the selected lines", ->
- editor.setSelectedBufferRange([[4, 5], [7, 5]])
- editor.toggleLineCommentsInSelection()
-
- expect(buffer.lineForRow(4)).toBe " // while(items.length > 0) {"
- expect(buffer.lineForRow(5)).toBe " // current = items.shift();"
- expect(buffer.lineForRow(6)).toBe " // current < pivot ? left.push(current) : right.push(current);"
- expect(buffer.lineForRow(7)).toBe " // }"
- expect(editor.getSelectedBufferRange()).toEqual [[4, 8], [7, 8]]
-
- editor.toggleLineCommentsInSelection()
- expect(buffer.lineForRow(4)).toBe " while(items.length > 0) {"
- expect(buffer.lineForRow(5)).toBe " current = items.shift();"
- expect(buffer.lineForRow(6)).toBe " current < pivot ? left.push(current) : right.push(current);"
- expect(buffer.lineForRow(7)).toBe " }"
-
- it "does not comment the last line of a non-empty selection if it ends at column 0", ->
- editor.setSelectedBufferRange([[4, 5], [7, 0]])
- editor.toggleLineCommentsInSelection()
- expect(buffer.lineForRow(4)).toBe " // while(items.length > 0) {"
- expect(buffer.lineForRow(5)).toBe " // current = items.shift();"
- expect(buffer.lineForRow(6)).toBe " // current < pivot ? left.push(current) : right.push(current);"
- expect(buffer.lineForRow(7)).toBe " }"
-
- it "uncomments lines if all lines match the comment regex", ->
- editor.setSelectedBufferRange([[0, 0], [0, 1]])
- editor.toggleLineCommentsInSelection()
- expect(buffer.lineForRow(0)).toBe "// var quicksort = function () {"
-
- editor.setSelectedBufferRange([[0, 0], [2, Infinity]])
- editor.toggleLineCommentsInSelection()
- expect(buffer.lineForRow(0)).toBe "// // var quicksort = function () {"
- expect(buffer.lineForRow(1)).toBe "// var sort = function(items) {"
- expect(buffer.lineForRow(2)).toBe "// if (items.length <= 1) return items;"
-
- editor.setSelectedBufferRange([[0, 0], [2, Infinity]])
- editor.toggleLineCommentsInSelection()
- expect(buffer.lineForRow(0)).toBe "// var quicksort = function () {"
- expect(buffer.lineForRow(1)).toBe " var sort = function(items) {"
- expect(buffer.lineForRow(2)).toBe " if (items.length <= 1) return items;"
-
- editor.setSelectedBufferRange([[0, 0], [0, Infinity]])
- editor.toggleLineCommentsInSelection()
- expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
-
- it "uncomments commented lines separated by an empty line", ->
- editor.setSelectedBufferRange([[0, 0], [1, Infinity]])
- editor.toggleLineCommentsInSelection()
- expect(buffer.lineForRow(0)).toBe "// var quicksort = function () {"
- expect(buffer.lineForRow(1)).toBe "// var sort = function(items) {"
-
- buffer.insert([0, Infinity], '\n')
-
- editor.setSelectedBufferRange([[0, 0], [2, Infinity]])
- editor.toggleLineCommentsInSelection()
- expect(buffer.lineForRow(0)).toBe "var quicksort = function () {"
- expect(buffer.lineForRow(1)).toBe ""
- expect(buffer.lineForRow(2)).toBe " var sort = function(items) {"
-
- it "preserves selection emptiness", ->
- editor.setCursorBufferPosition([4, 0])
- editor.toggleLineCommentsInSelection()
- expect(editor.getLastSelection().isEmpty()).toBeTruthy()
-
- it "does not explode if the current language mode has no comment regex", ->
- editor = new TextEditor(buffer: new TextBuffer(text: 'hello'))
- editor.setSelectedBufferRange([[0, 0], [0, 5]])
- editor.toggleLineCommentsInSelection()
- expect(editor.lineTextForBufferRow(0)).toBe "hello"
-
- it "does nothing for empty lines and null grammar", ->
- runs ->
- editor.setGrammar(atom.grammars.grammarForScopeName('text.plain.null-grammar'))
- editor.setCursorBufferPosition([10, 0])
- editor.toggleLineCommentsInSelection()
- expect(editor.buffer.lineForRow(10)).toBe ""
-
- it "uncomments when the line lacks the trailing whitespace in the comment regex", ->
- editor.setCursorBufferPosition([10, 0])
- editor.toggleLineCommentsInSelection()
-
- expect(buffer.lineForRow(10)).toBe "// "
- expect(editor.getSelectedBufferRange()).toEqual [[10, 3], [10, 3]]
- editor.backspace()
- expect(buffer.lineForRow(10)).toBe "//"
-
- editor.toggleLineCommentsInSelection()
- expect(buffer.lineForRow(10)).toBe ""
- expect(editor.getSelectedBufferRange()).toEqual [[10, 0], [10, 0]]
-
- it "uncomments when the line has leading whitespace", ->
- editor.setCursorBufferPosition([10, 0])
- editor.toggleLineCommentsInSelection()
-
- expect(buffer.lineForRow(10)).toBe "// "
- editor.moveToBeginningOfLine()
- editor.insertText(" ")
- editor.setSelectedBufferRange([[10, 0], [10, 0]])
- editor.toggleLineCommentsInSelection()
- expect(buffer.lineForRow(10)).toBe " "
-
describe ".undo() and .redo()", ->
it "undoes/redoes the last change", ->
editor.insertText("foo")
diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js
index c81df8089..d10efa695 100644
--- a/spec/text-editor-spec.js
+++ b/spec/text-editor-spec.js
@@ -2,6 +2,8 @@ const fs = require('fs')
const temp = require('temp').track()
const {Point, Range} = require('text-buffer')
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers')
+const TextBuffer = require('text-buffer')
+const TextEditor = require('../src/text-editor')
describe('TextEditor', () => {
let editor
@@ -58,6 +60,276 @@ describe('TextEditor', () => {
})
})
+ describe('.toggleLineCommentsInSelection()', () => {
+ beforeEach(async () => {
+ await atom.packages.activatePackage('language-javascript')
+ editor = await atom.workspace.open('sample.js')
+ })
+
+ it('toggles comments on the selected lines', () => {
+ editor.setSelectedBufferRange([[4, 5], [7, 5]])
+ editor.toggleLineCommentsInSelection()
+
+ expect(editor.lineTextForBufferRow(4)).toBe(' // while(items.length > 0) {')
+ expect(editor.lineTextForBufferRow(5)).toBe(' // current = items.shift();')
+ expect(editor.lineTextForBufferRow(6)).toBe(' // current < pivot ? left.push(current) : right.push(current);')
+ expect(editor.lineTextForBufferRow(7)).toBe(' // }')
+ expect(editor.getSelectedBufferRange()).toEqual([[4, 8], [7, 8]])
+
+ editor.toggleLineCommentsInSelection()
+ expect(editor.lineTextForBufferRow(4)).toBe(' while(items.length > 0) {')
+ expect(editor.lineTextForBufferRow(5)).toBe(' current = items.shift();')
+ expect(editor.lineTextForBufferRow(6)).toBe(' current < pivot ? left.push(current) : right.push(current);')
+ expect(editor.lineTextForBufferRow(7)).toBe(' }')
+ })
+
+ it('does not comment the last line of a non-empty selection if it ends at column 0', () => {
+ editor.setSelectedBufferRange([[4, 5], [7, 0]])
+ editor.toggleLineCommentsInSelection()
+ expect(editor.lineTextForBufferRow(4)).toBe(' // while(items.length > 0) {')
+ expect(editor.lineTextForBufferRow(5)).toBe(' // current = items.shift();')
+ expect(editor.lineTextForBufferRow(6)).toBe(' // current < pivot ? left.push(current) : right.push(current);')
+ expect(editor.lineTextForBufferRow(7)).toBe(' }')
+ })
+
+ it('uncomments lines if all lines match the comment regex', () => {
+ editor.setSelectedBufferRange([[0, 0], [0, 1]])
+ editor.toggleLineCommentsInSelection()
+ expect(editor.lineTextForBufferRow(0)).toBe('// var quicksort = function () {')
+
+ editor.setSelectedBufferRange([[0, 0], [2, Infinity]])
+ editor.toggleLineCommentsInSelection()
+ expect(editor.lineTextForBufferRow(0)).toBe('// // var quicksort = function () {')
+ expect(editor.lineTextForBufferRow(1)).toBe('// var sort = function(items) {')
+ expect(editor.lineTextForBufferRow(2)).toBe('// if (items.length <= 1) return items;')
+
+ editor.setSelectedBufferRange([[0, 0], [2, Infinity]])
+ editor.toggleLineCommentsInSelection()
+ expect(editor.lineTextForBufferRow(0)).toBe('// var quicksort = function () {')
+ expect(editor.lineTextForBufferRow(1)).toBe(' var sort = function(items) {')
+ expect(editor.lineTextForBufferRow(2)).toBe(' if (items.length <= 1) return items;')
+
+ editor.setSelectedBufferRange([[0, 0], [0, Infinity]])
+ editor.toggleLineCommentsInSelection()
+ expect(editor.lineTextForBufferRow(0)).toBe('var quicksort = function () {')
+ })
+
+ it('uncomments commented lines separated by an empty line', () => {
+ editor.setSelectedBufferRange([[0, 0], [1, Infinity]])
+ editor.toggleLineCommentsInSelection()
+ expect(editor.lineTextForBufferRow(0)).toBe('// var quicksort = function () {')
+ expect(editor.lineTextForBufferRow(1)).toBe('// var sort = function(items) {')
+
+ editor.getBuffer().insert([0, Infinity], '\n')
+
+ editor.setSelectedBufferRange([[0, 0], [2, Infinity]])
+ editor.toggleLineCommentsInSelection()
+ expect(editor.lineTextForBufferRow(0)).toBe('var quicksort = function () {')
+ expect(editor.lineTextForBufferRow(1)).toBe('')
+ expect(editor.lineTextForBufferRow(2)).toBe(' var sort = function(items) {')
+ })
+
+ it('preserves selection emptiness', () => {
+ editor.setCursorBufferPosition([4, 0])
+ editor.toggleLineCommentsInSelection()
+ expect(editor.getLastSelection().isEmpty()).toBeTruthy()
+ })
+
+ it('does not explode if the current language mode has no comment regex', () => {
+ const editor = new TextEditor({buffer: new TextBuffer({text: 'hello'})})
+ editor.setSelectedBufferRange([[0, 0], [0, 5]])
+ editor.toggleLineCommentsInSelection()
+ expect(editor.lineTextForBufferRow(0)).toBe('hello')
+ })
+
+ it('does nothing for empty lines and null grammar', () => {
+ editor.setGrammar(atom.grammars.grammarForScopeName('text.plain.null-grammar'))
+ editor.setCursorBufferPosition([10, 0])
+ editor.toggleLineCommentsInSelection()
+ expect(editor.lineTextForBufferRow(10)).toBe('')
+ })
+
+ it('uncomments when the line lacks the trailing whitespace in the comment regex', () => {
+ editor.setCursorBufferPosition([10, 0])
+ editor.toggleLineCommentsInSelection()
+
+ expect(editor.lineTextForBufferRow(10)).toBe('// ')
+ expect(editor.getSelectedBufferRange()).toEqual([[10, 3], [10, 3]])
+ editor.backspace()
+ expect(editor.lineTextForBufferRow(10)).toBe('//')
+
+ editor.toggleLineCommentsInSelection()
+ expect(editor.lineTextForBufferRow(10)).toBe('')
+ expect(editor.getSelectedBufferRange()).toEqual([[10, 0], [10, 0]])
+ })
+
+ it('uncomments when the line has leading whitespace', () => {
+ editor.setCursorBufferPosition([10, 0])
+ editor.toggleLineCommentsInSelection()
+
+ expect(editor.lineTextForBufferRow(10)).toBe('// ')
+ editor.moveToBeginningOfLine()
+ editor.insertText(' ')
+ editor.setSelectedBufferRange([[10, 0], [10, 0]])
+ editor.toggleLineCommentsInSelection()
+ expect(editor.lineTextForBufferRow(10)).toBe(' ')
+ })
+ })
+
+ describe('.toggleLineCommentsForBufferRows', () => {
+ describe('xml', () => {
+ beforeEach(async () => {
+ await atom.packages.activatePackage('language-xml')
+ editor = await atom.workspace.open('test.xml')
+ editor.setText('')
+ })
+
+ it('removes the leading whitespace from the comment end pattern match when uncommenting lines', () => {
+ editor.toggleLineCommentsForBufferRows(0, 0)
+ expect(editor.lineTextForBufferRow(0)).toBe('test')
+ })
+ })
+
+ describe('less', () => {
+ beforeEach(async () => {
+ await atom.packages.activatePackage('language-less')
+ await atom.packages.activatePackage('language-css')
+ editor = await atom.workspace.open('sample.less')
+ })
+
+ it('only uses the `commentEnd` pattern if it comes from the same grammar as the `commentStart` when commenting lines', () => {
+ editor.toggleLineCommentsForBufferRows(0, 0)
+ expect(editor.lineTextForBufferRow(0)).toBe('// @color: #4D926F;')
+ })
+ })
+
+ describe('css', () => {
+ beforeEach(async () => {
+ await atom.packages.activatePackage('language-css')
+ editor = await atom.workspace.open('css.css')
+ })
+
+ it('comments/uncomments lines in the given range', () => {
+ editor.toggleLineCommentsForBufferRows(0, 1)
+ expect(editor.lineTextForBufferRow(0)).toBe('/* body {')
+ expect(editor.lineTextForBufferRow(1)).toBe(' font-size: 1234px; */')
+ expect(editor.lineTextForBufferRow(2)).toBe(' width: 110%;')
+ expect(editor.lineTextForBufferRow(3)).toBe(' font-weight: bold !important;')
+
+ editor.toggleLineCommentsForBufferRows(2, 2)
+ expect(editor.lineTextForBufferRow(0)).toBe('/* body {')
+ expect(editor.lineTextForBufferRow(1)).toBe(' font-size: 1234px; */')
+ expect(editor.lineTextForBufferRow(2)).toBe(' /* width: 110%; */')
+ expect(editor.lineTextForBufferRow(3)).toBe(' font-weight: bold !important;')
+
+ editor.toggleLineCommentsForBufferRows(0, 1)
+ expect(editor.lineTextForBufferRow(0)).toBe('body {')
+ expect(editor.lineTextForBufferRow(1)).toBe(' font-size: 1234px;')
+ expect(editor.lineTextForBufferRow(2)).toBe(' /* width: 110%; */')
+ expect(editor.lineTextForBufferRow(3)).toBe(' font-weight: bold !important;')
+ })
+
+ it('uncomments lines with leading whitespace', () => {
+ editor.setTextInBufferRange([[2, 0], [2, Infinity]], ' /* width: 110%; */')
+ editor.toggleLineCommentsForBufferRows(2, 2)
+ expect(editor.lineTextForBufferRow(2)).toBe(' width: 110%;')
+ })
+
+ it('uncomments lines with trailing whitespace', () => {
+ editor.setTextInBufferRange([[2, 0], [2, Infinity]], '/* width: 110%; */ ')
+ editor.toggleLineCommentsForBufferRows(2, 2)
+ expect(editor.lineTextForBufferRow(2)).toBe('width: 110%; ')
+ })
+
+ it('uncomments lines with leading and trailing whitespace', () => {
+ editor.setTextInBufferRange([[2, 0], [2, Infinity]], ' /* width: 110%; */ ')
+ editor.toggleLineCommentsForBufferRows(2, 2)
+ expect(editor.lineTextForBufferRow(2)).toBe(' width: 110%; ')
+ })
+ })
+
+ describe('coffeescript', () => {
+ beforeEach(async () => {
+ await atom.packages.activatePackage('language-coffee-script')
+ editor = await atom.workspace.open('coffee.coffee')
+ })
+
+ it('comments/uncomments lines in the given range', () => {
+ editor.toggleLineCommentsForBufferRows(4, 6)
+ expect(editor.lineTextForBufferRow(4)).toBe(' # pivot = items.shift()')
+ expect(editor.lineTextForBufferRow(5)).toBe(' # left = []')
+ expect(editor.lineTextForBufferRow(6)).toBe(' # right = []')
+
+ editor.toggleLineCommentsForBufferRows(4, 5)
+ expect(editor.lineTextForBufferRow(4)).toBe(' pivot = items.shift()')
+ expect(editor.lineTextForBufferRow(5)).toBe(' left = []')
+ expect(editor.lineTextForBufferRow(6)).toBe(' # right = []')
+ })
+
+ it('comments/uncomments empty lines', () => {
+ editor.toggleLineCommentsForBufferRows(4, 7)
+ expect(editor.lineTextForBufferRow(4)).toBe(' # pivot = items.shift()')
+ expect(editor.lineTextForBufferRow(5)).toBe(' # left = []')
+ expect(editor.lineTextForBufferRow(6)).toBe(' # right = []')
+ expect(editor.lineTextForBufferRow(7)).toBe(' # ')
+
+ editor.toggleLineCommentsForBufferRows(4, 5)
+ expect(editor.lineTextForBufferRow(4)).toBe(' pivot = items.shift()')
+ expect(editor.lineTextForBufferRow(5)).toBe(' left = []')
+ expect(editor.lineTextForBufferRow(6)).toBe(' # right = []')
+ expect(editor.lineTextForBufferRow(7)).toBe(' # ')
+ })
+ })
+
+ describe('javascript', () => {
+ beforeEach(async () => {
+ await atom.packages.activatePackage('language-javascript')
+ editor = await atom.workspace.open('sample.js')
+ })
+
+ it('comments/uncomments lines in the given range', () => {
+ editor.toggleLineCommentsForBufferRows(4, 7)
+ expect(editor.lineTextForBufferRow(4)).toBe(' // while(items.length > 0) {')
+ expect(editor.lineTextForBufferRow(5)).toBe(' // current = items.shift();')
+ expect(editor.lineTextForBufferRow(6)).toBe(' // current < pivot ? left.push(current) : right.push(current);')
+ expect(editor.lineTextForBufferRow(7)).toBe(' // }')
+
+ editor.toggleLineCommentsForBufferRows(4, 5)
+ expect(editor.lineTextForBufferRow(4)).toBe(' while(items.length > 0) {')
+ expect(editor.lineTextForBufferRow(5)).toBe(' current = items.shift();')
+ expect(editor.lineTextForBufferRow(6)).toBe(' // current < pivot ? left.push(current) : right.push(current);')
+ expect(editor.lineTextForBufferRow(7)).toBe(' // }')
+
+ editor.setText('\tvar i;')
+ editor.toggleLineCommentsForBufferRows(0, 0)
+ expect(editor.lineTextForBufferRow(0)).toBe('\t// var i;')
+
+ editor.setText('var i;')
+ editor.toggleLineCommentsForBufferRows(0, 0)
+ expect(editor.lineTextForBufferRow(0)).toBe('// var i;')
+
+ editor.setText(' var i;')
+ editor.toggleLineCommentsForBufferRows(0, 0)
+ expect(editor.lineTextForBufferRow(0)).toBe(' // var i;')
+
+ editor.setText(' ')
+ editor.toggleLineCommentsForBufferRows(0, 0)
+ expect(editor.lineTextForBufferRow(0)).toBe(' // ')
+
+ editor.setText(' a\n \n b')
+ editor.toggleLineCommentsForBufferRows(0, 2)
+ expect(editor.lineTextForBufferRow(0)).toBe(' // a')
+ expect(editor.lineTextForBufferRow(1)).toBe(' // ')
+ expect(editor.lineTextForBufferRow(2)).toBe(' // b')
+
+ editor.setText(' \n // var i;')
+ editor.toggleLineCommentsForBufferRows(0, 1)
+ expect(editor.lineTextForBufferRow(0)).toBe(' ')
+ expect(editor.lineTextForBufferRow(1)).toBe(' var i;')
+ })
+ })
+ })
+
describe('folding', () => {
beforeEach(async () => {
await atom.packages.activatePackage('language-javascript')
@@ -173,6 +445,26 @@ describe('TextEditor', () => {
})
})
+ describe('.foldCurrentRow()', () => {
+ it('creates a fold at the location of the last cursor', async () => {
+ editor = await atom.workspace.open()
+ editor.setText('\nif (x) {\n y()\n}')
+ editor.setCursorBufferPosition([1, 0])
+ expect(editor.getScreenLineCount()).toBe(4)
+ editor.foldCurrentRow()
+ expect(editor.getScreenLineCount()).toBe(3)
+ })
+
+ it('does nothing when the current row cannot be folded', async () => {
+ editor = await atom.workspace.open()
+ editor.setText('var x;\nx++\nx++')
+ editor.setCursorBufferPosition([0, 0])
+ expect(editor.getScreenLineCount()).toBe(3)
+ editor.foldCurrentRow()
+ expect(editor.getScreenLineCount()).toBe(3)
+ })
+ })
+
describe('.foldAllAtIndentLevel(indentLevel)', () => {
it('folds blocks of text at the given indentation level', async () => {
editor = await atom.workspace.open('sample.js', {autoIndent: false})
diff --git a/spec/tokenized-buffer-spec.js b/spec/tokenized-buffer-spec.js
index ba43f9ff3..b1574673a 100644
--- a/spec/tokenized-buffer-spec.js
+++ b/spec/tokenized-buffer-spec.js
@@ -643,186 +643,6 @@ describe('TokenizedBuffer', () => {
})
})
- describe('.toggleLineCommentsForBufferRows', () => {
- describe('xml', () => {
- beforeEach(async () => {
- await atom.packages.activatePackage('language-xml')
- buffer = new TextBuffer('')
- tokenizedBuffer = new TokenizedBuffer({
- buffer,
- grammar: atom.grammars.grammarForScopeName('text.xml'),
- scopedSettingsDelegate: new ScopedSettingsDelegate(atom.config)
- })
- })
-
- it('removes the leading whitespace from the comment end pattern match when uncommenting lines', () => {
- tokenizedBuffer.toggleLineCommentsForBufferRows(0, 0)
- expect(buffer.lineForRow(0)).toBe('test')
- })
- })
-
- describe('less', () => {
- beforeEach(async () => {
- await atom.packages.activatePackage('language-less')
- await atom.packages.activatePackage('language-css')
- buffer = await TextBuffer.load(require.resolve('./fixtures/sample.less'))
- tokenizedBuffer = new TokenizedBuffer({
- buffer,
- grammar: atom.grammars.grammarForScopeName('source.css.less'),
- scopedSettingsDelegate: new ScopedSettingsDelegate(atom.config)
- })
- })
-
- it('only uses the `commentEnd` pattern if it comes from the same grammar as the `commentStart` when commenting lines', () => {
- tokenizedBuffer.toggleLineCommentsForBufferRows(0, 0)
- expect(buffer.lineForRow(0)).toBe('// @color: #4D926F;')
- })
- })
-
- describe('css', () => {
- beforeEach(async () => {
- await atom.packages.activatePackage('language-css')
- buffer = await TextBuffer.load(require.resolve('./fixtures/css.css'))
- tokenizedBuffer = new TokenizedBuffer({
- buffer,
- grammar: atom.grammars.grammarForScopeName('source.css'),
- scopedSettingsDelegate: new ScopedSettingsDelegate(atom.config)
- })
- })
-
- it('comments/uncomments lines in the given range', () => {
- tokenizedBuffer.toggleLineCommentsForBufferRows(0, 1)
- expect(buffer.lineForRow(0)).toBe('/*body {')
- expect(buffer.lineForRow(1)).toBe(' font-size: 1234px;*/')
- expect(buffer.lineForRow(2)).toBe(' width: 110%;')
- expect(buffer.lineForRow(3)).toBe(' font-weight: bold !important;')
-
- tokenizedBuffer.toggleLineCommentsForBufferRows(2, 2)
- expect(buffer.lineForRow(0)).toBe('/*body {')
- expect(buffer.lineForRow(1)).toBe(' font-size: 1234px;*/')
- expect(buffer.lineForRow(2)).toBe(' /*width: 110%;*/')
- expect(buffer.lineForRow(3)).toBe(' font-weight: bold !important;')
-
- tokenizedBuffer.toggleLineCommentsForBufferRows(0, 1)
- expect(buffer.lineForRow(0)).toBe('body {')
- expect(buffer.lineForRow(1)).toBe(' font-size: 1234px;')
- expect(buffer.lineForRow(2)).toBe(' /*width: 110%;*/')
- expect(buffer.lineForRow(3)).toBe(' font-weight: bold !important;')
- })
-
- it('uncomments lines with leading whitespace', () => {
- buffer.setTextInRange([[2, 0], [2, Infinity]], ' /*width: 110%;*/')
- tokenizedBuffer.toggleLineCommentsForBufferRows(2, 2)
- expect(buffer.lineForRow(2)).toBe(' width: 110%;')
- })
-
- it('uncomments lines with trailing whitespace', () => {
- buffer.setTextInRange([[2, 0], [2, Infinity]], '/*width: 110%;*/ ')
- tokenizedBuffer.toggleLineCommentsForBufferRows(2, 2)
- expect(buffer.lineForRow(2)).toBe('width: 110%; ')
- })
-
- it('uncomments lines with leading and trailing whitespace', () => {
- buffer.setTextInRange([[2, 0], [2, Infinity]], ' /*width: 110%;*/ ')
- tokenizedBuffer.toggleLineCommentsForBufferRows(2, 2)
- expect(buffer.lineForRow(2)).toBe(' width: 110%; ')
- })
- })
-
- describe('coffeescript', () => {
- beforeEach(async () => {
- await atom.packages.activatePackage('language-coffee-script')
- buffer = await TextBuffer.load(require.resolve('./fixtures/coffee.coffee'))
- tokenizedBuffer = new TokenizedBuffer({
- buffer,
- tabLength: 2,
- grammar: atom.grammars.grammarForScopeName('source.coffee'),
- scopedSettingsDelegate: new ScopedSettingsDelegate(atom.config)
- })
- })
-
- it('comments/uncomments lines in the given range', () => {
- tokenizedBuffer.toggleLineCommentsForBufferRows(4, 6)
- expect(buffer.lineForRow(4)).toBe(' # pivot = items.shift()')
- expect(buffer.lineForRow(5)).toBe(' # left = []')
- expect(buffer.lineForRow(6)).toBe(' # right = []')
-
- tokenizedBuffer.toggleLineCommentsForBufferRows(4, 5)
- expect(buffer.lineForRow(4)).toBe(' pivot = items.shift()')
- expect(buffer.lineForRow(5)).toBe(' left = []')
- expect(buffer.lineForRow(6)).toBe(' # right = []')
- })
-
- it('comments/uncomments empty lines', () => {
- tokenizedBuffer.toggleLineCommentsForBufferRows(4, 7)
- expect(buffer.lineForRow(4)).toBe(' # pivot = items.shift()')
- expect(buffer.lineForRow(5)).toBe(' # left = []')
- expect(buffer.lineForRow(6)).toBe(' # right = []')
- expect(buffer.lineForRow(7)).toBe(' # ')
-
- tokenizedBuffer.toggleLineCommentsForBufferRows(4, 5)
- expect(buffer.lineForRow(4)).toBe(' pivot = items.shift()')
- expect(buffer.lineForRow(5)).toBe(' left = []')
- expect(buffer.lineForRow(6)).toBe(' # right = []')
- expect(buffer.lineForRow(7)).toBe(' # ')
- })
- })
-
- describe('javascript', () => {
- beforeEach(async () => {
- await atom.packages.activatePackage('language-javascript')
- buffer = await TextBuffer.load(require.resolve('./fixtures/sample.js'))
- tokenizedBuffer = new TokenizedBuffer({
- buffer,
- tabLength: 2,
- grammar: atom.grammars.grammarForScopeName('source.js'),
- scopedSettingsDelegate: new ScopedSettingsDelegate(atom.config)
- })
- })
-
- it('comments/uncomments lines in the given range', () => {
- tokenizedBuffer.toggleLineCommentsForBufferRows(4, 7)
- expect(buffer.lineForRow(4)).toBe(' // while(items.length > 0) {')
- expect(buffer.lineForRow(5)).toBe(' // current = items.shift();')
- expect(buffer.lineForRow(6)).toBe(' // current < pivot ? left.push(current) : right.push(current);')
- expect(buffer.lineForRow(7)).toBe(' // }')
-
- tokenizedBuffer.toggleLineCommentsForBufferRows(4, 5)
- expect(buffer.lineForRow(4)).toBe(' while(items.length > 0) {')
- expect(buffer.lineForRow(5)).toBe(' current = items.shift();')
- expect(buffer.lineForRow(6)).toBe(' // current < pivot ? left.push(current) : right.push(current);')
- expect(buffer.lineForRow(7)).toBe(' // }')
-
- buffer.setText('\tvar i;')
- tokenizedBuffer.toggleLineCommentsForBufferRows(0, 0)
- expect(buffer.lineForRow(0)).toBe('\t// var i;')
-
- buffer.setText('var i;')
- tokenizedBuffer.toggleLineCommentsForBufferRows(0, 0)
- expect(buffer.lineForRow(0)).toBe('// var i;')
-
- buffer.setText(' var i;')
- tokenizedBuffer.toggleLineCommentsForBufferRows(0, 0)
- expect(buffer.lineForRow(0)).toBe(' // var i;')
-
- buffer.setText(' ')
- tokenizedBuffer.toggleLineCommentsForBufferRows(0, 0)
- expect(buffer.lineForRow(0)).toBe(' // ')
-
- buffer.setText(' a\n \n b')
- tokenizedBuffer.toggleLineCommentsForBufferRows(0, 2)
- expect(buffer.lineForRow(0)).toBe(' // a')
- expect(buffer.lineForRow(1)).toBe(' // ')
- expect(buffer.lineForRow(2)).toBe(' // b')
-
- buffer.setText(' \n // var i;')
- tokenizedBuffer.toggleLineCommentsForBufferRows(0, 1)
- expect(buffer.lineForRow(0)).toBe(' ')
- expect(buffer.lineForRow(1)).toBe(' var i;')
- })
- })
- })
-
describe('.isFoldableAtRow(row)', () => {
beforeEach(() => {
buffer = atom.project.bufferForPathSync('sample.js')
diff --git a/spec/tooltip-manager-spec.coffee b/spec/tooltip-manager-spec.coffee
deleted file mode 100644
index 95182853e..000000000
--- a/spec/tooltip-manager-spec.coffee
+++ /dev/null
@@ -1,213 +0,0 @@
-{CompositeDisposable} = require 'atom'
-TooltipManager = require '../src/tooltip-manager'
-Tooltip = require '../src/tooltip'
-_ = require 'underscore-plus'
-
-describe "TooltipManager", ->
- [manager, element] = []
-
- ctrlX = _.humanizeKeystroke("ctrl-x")
- ctrlY = _.humanizeKeystroke("ctrl-y")
-
- beforeEach ->
- manager = new TooltipManager(keymapManager: atom.keymaps, viewRegistry: atom.views)
- element = createElement 'foo'
-
- createElement = (className) ->
- el = document.createElement('div')
- el.classList.add(className)
- jasmine.attachToDOM(el)
- el
-
- mouseEnter = (element) ->
- element.dispatchEvent(new CustomEvent('mouseenter', bubbles: false))
- element.dispatchEvent(new CustomEvent('mouseover', bubbles: true))
-
- mouseLeave = (element) ->
- element.dispatchEvent(new CustomEvent('mouseleave', bubbles: false))
- element.dispatchEvent(new CustomEvent('mouseout', bubbles: true))
-
- hover = (element, fn) ->
- mouseEnter(element)
- advanceClock(manager.hoverDefaults.delay.show)
- fn()
- mouseLeave(element)
- advanceClock(manager.hoverDefaults.delay.hide)
-
- describe "::add(target, options)", ->
- describe "when the trigger is 'hover' (the default)", ->
- it "creates a tooltip when hovering over the target element", ->
- manager.add element, title: "Title"
- hover element, ->
- expect(document.body.querySelector(".tooltip")).toHaveText("Title")
-
- it "displays tooltips immediately when hovering over new elements once a tooltip has been displayed once", ->
- disposables = new CompositeDisposable
- element1 = createElement('foo')
- disposables.add(manager.add element1, title: 'Title')
- element2 = createElement('bar')
- disposables.add(manager.add element2, title: 'Title')
- element3 = createElement('baz')
- disposables.add(manager.add element3, title: 'Title')
-
- hover element1, ->
- expect(document.body.querySelector(".tooltip")).toBeNull()
-
- mouseEnter(element2)
- expect(document.body.querySelector(".tooltip")).not.toBeNull()
- mouseLeave(element2)
- advanceClock(manager.hoverDefaults.delay.hide)
- expect(document.body.querySelector(".tooltip")).toBeNull()
-
- advanceClock(Tooltip.FOLLOW_THROUGH_DURATION)
- mouseEnter(element3)
- expect(document.body.querySelector(".tooltip")).toBeNull()
- advanceClock(manager.hoverDefaults.delay.show)
- expect(document.body.querySelector(".tooltip")).not.toBeNull()
-
- disposables.dispose()
-
- describe "when the trigger is 'manual'", ->
- it "creates a tooltip immediately and only hides it on dispose", ->
- disposable = manager.add element, title: "Title", trigger: "manual"
- expect(document.body.querySelector(".tooltip")).toHaveText("Title")
- disposable.dispose()
- expect(document.body.querySelector(".tooltip")).toBeNull()
-
- describe "when the trigger is 'click'", ->
- it "shows and hides the tooltip when the target element is clicked", ->
- disposable = manager.add element, title: "Title", trigger: "click"
- expect(document.body.querySelector(".tooltip")).toBeNull()
- element.click()
- expect(document.body.querySelector(".tooltip")).not.toBeNull()
- element.click()
- expect(document.body.querySelector(".tooltip")).toBeNull()
-
- # Hide the tooltip when clicking anywhere but inside the tooltip element
- element.click()
- expect(document.body.querySelector(".tooltip")).not.toBeNull()
- document.body.querySelector(".tooltip").click()
- expect(document.body.querySelector(".tooltip")).not.toBeNull()
- document.body.querySelector(".tooltip").firstChild.click()
- expect(document.body.querySelector(".tooltip")).not.toBeNull()
- document.body.click()
- expect(document.body.querySelector(".tooltip")).toBeNull()
-
- # Tooltip can show again after hiding due to clicking outside of the tooltip
- element.click()
- expect(document.body.querySelector(".tooltip")).not.toBeNull()
- element.click()
- expect(document.body.querySelector(".tooltip")).toBeNull()
-
- it "allows a custom item to be specified for the content of the tooltip", ->
- tooltipElement = document.createElement('div')
- manager.add element, item: {element: tooltipElement}
- hover element, ->
- expect(tooltipElement.closest(".tooltip")).not.toBeNull()
-
- it "allows a custom class to be specified for the tooltip", ->
- tooltipElement = document.createElement('div')
- manager.add element, title: 'Title', class: 'custom-tooltip-class'
- hover element, ->
- expect(document.body.querySelector(".tooltip").classList.contains('custom-tooltip-class')).toBe(true)
-
- 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 keyBindingCommand is specified", ->
- describe "when a title is specified", ->
- it "appends the key binding corresponding to the command to the title", ->
- atom.keymaps.add 'test',
- '.foo': 'ctrl-x ctrl-y': 'test-command'
- '.bar': 'ctrl-x ctrl-z': 'test-command'
-
- manager.add element, title: "Title", keyBindingCommand: 'test-command'
-
- hover element, ->
- tooltipElement = document.body.querySelector(".tooltip")
- expect(tooltipElement).toHaveText "Title #{ctrlX} #{ctrlY}"
-
- describe "when no title is specified", ->
- it "shows the key binding corresponding to the command alone", ->
- atom.keymaps.add 'test', '.foo': 'ctrl-x ctrl-y': 'test-command'
-
- manager.add element, keyBindingCommand: 'test-command'
-
- hover element, ->
- tooltipElement = document.body.querySelector(".tooltip")
- expect(tooltipElement).toHaveText "#{ctrlX} #{ctrlY}"
-
- describe "when a keyBindingTarget is specified", ->
- it "looks up the key binding relative to the target", ->
- atom.keymaps.add 'test',
- '.bar': 'ctrl-x ctrl-z': 'test-command'
- '.foo': 'ctrl-x ctrl-y': 'test-command'
-
- manager.add element, keyBindingCommand: 'test-command', keyBindingTarget: element
-
- hover element, ->
- tooltipElement = document.body.querySelector(".tooltip")
- expect(tooltipElement).toHaveText "#{ctrlX} #{ctrlY}"
-
- it "does not display the keybinding if there is nothing mapped to the specified keyBindingCommand", ->
- manager.add element, title: 'A Title', keyBindingCommand: 'test-command', keyBindingTarget: element
-
- hover element, ->
- tooltipElement = document.body.querySelector(".tooltip")
- expect(tooltipElement.textContent).toBe "A Title"
-
- describe "when .dispose() is called on the returned disposable", ->
- it "no longer displays the tooltip on hover", ->
- disposable = manager.add element, title: "Title"
-
- hover element, ->
- expect(document.body.querySelector(".tooltip")).toHaveText("Title")
-
- disposable.dispose()
-
- hover element, ->
- expect(document.body.querySelector(".tooltip")).toBeNull()
-
- describe "when the window is resized", ->
- it "hides the tooltips", ->
- disposable = manager.add element, title: "Title"
- hover element, ->
- expect(document.body.querySelector(".tooltip")).not.toBeNull()
- window.dispatchEvent(new CustomEvent('resize'))
- expect(document.body.querySelector(".tooltip")).toBeNull()
- disposable.dispose()
-
- describe "findTooltips", ->
- it "adds and remove tooltips correctly", ->
- expect(manager.findTooltips(element).length).toBe(0)
- disposable1 = manager.add element, title: "elem1"
- expect(manager.findTooltips(element).length).toBe(1)
- disposable2 = manager.add element, title: "elem2"
- expect(manager.findTooltips(element).length).toBe(2)
- disposable1.dispose()
- expect(manager.findTooltips(element).length).toBe(1)
- disposable2.dispose()
- expect(manager.findTooltips(element).length).toBe(0)
-
- it "lets us hide tooltips programmatically", ->
- disposable = manager.add element, title: "Title"
- hover element, ->
- expect(document.body.querySelector(".tooltip")).not.toBeNull()
- manager.findTooltips(element)[0].hide()
- expect(document.body.querySelector(".tooltip")).toBeNull()
- disposable.dispose()
diff --git a/spec/tooltip-manager-spec.js b/spec/tooltip-manager-spec.js
new file mode 100644
index 000000000..65587839f
--- /dev/null
+++ b/spec/tooltip-manager-spec.js
@@ -0,0 +1,253 @@
+const {CompositeDisposable} = require('atom')
+const TooltipManager = require('../src/tooltip-manager')
+const Tooltip = require('../src/tooltip')
+const _ = require('underscore-plus')
+
+describe('TooltipManager', () => {
+ let manager, element
+
+ const ctrlX = _.humanizeKeystroke('ctrl-x')
+ const ctrlY = _.humanizeKeystroke('ctrl-y')
+
+ const hover = function (element, fn) {
+ mouseEnter(element)
+ advanceClock(manager.hoverDefaults.delay.show)
+ fn()
+ mouseLeave(element)
+ advanceClock(manager.hoverDefaults.delay.hide)
+ }
+
+ beforeEach(function () {
+ manager = new TooltipManager({keymapManager: atom.keymaps, viewRegistry: atom.views})
+ element = createElement('foo')
+ })
+
+ describe('::add(target, options)', () => {
+ describe("when the trigger is 'hover' (the default)", () => {
+ it('creates a tooltip when hovering over the target element', () => {
+ manager.add(element, {title: 'Title'})
+ hover(element, () => expect(document.body.querySelector('.tooltip')).toHaveText('Title'))
+ })
+
+ it('displays tooltips immediately when hovering over new elements once a tooltip has been displayed once', () => {
+ const disposables = new CompositeDisposable()
+ const element1 = createElement('foo')
+ disposables.add(manager.add(element1, {title: 'Title'}))
+ const element2 = createElement('bar')
+ disposables.add(manager.add(element2, {title: 'Title'}))
+ const element3 = createElement('baz')
+ disposables.add(manager.add(element3, {title: 'Title'}))
+
+ hover(element1, () => {})
+ expect(document.body.querySelector('.tooltip')).toBeNull()
+
+ mouseEnter(element2)
+ expect(document.body.querySelector('.tooltip')).not.toBeNull()
+ mouseLeave(element2)
+ advanceClock(manager.hoverDefaults.delay.hide)
+ expect(document.body.querySelector('.tooltip')).toBeNull()
+
+ advanceClock(Tooltip.FOLLOW_THROUGH_DURATION)
+ mouseEnter(element3)
+ expect(document.body.querySelector('.tooltip')).toBeNull()
+ advanceClock(manager.hoverDefaults.delay.show)
+ expect(document.body.querySelector('.tooltip')).not.toBeNull()
+
+ disposables.dispose()
+ })
+ })
+
+ describe("when the trigger is 'manual'", () =>
+ it('creates a tooltip immediately and only hides it on dispose', () => {
+ const disposable = manager.add(element, {title: 'Title', trigger: 'manual'})
+ expect(document.body.querySelector('.tooltip')).toHaveText('Title')
+ disposable.dispose()
+ expect(document.body.querySelector('.tooltip')).toBeNull()
+ })
+ )
+
+ describe("when the trigger is 'click'", () =>
+ it('shows and hides the tooltip when the target element is clicked', () => {
+ manager.add(element, {title: 'Title', trigger: 'click'})
+ expect(document.body.querySelector('.tooltip')).toBeNull()
+ element.click()
+ expect(document.body.querySelector('.tooltip')).not.toBeNull()
+ element.click()
+ expect(document.body.querySelector('.tooltip')).toBeNull()
+
+ // Hide the tooltip when clicking anywhere but inside the tooltip element
+ element.click()
+ expect(document.body.querySelector('.tooltip')).not.toBeNull()
+ document.body.querySelector('.tooltip').click()
+ expect(document.body.querySelector('.tooltip')).not.toBeNull()
+ document.body.querySelector('.tooltip').firstChild.click()
+ expect(document.body.querySelector('.tooltip')).not.toBeNull()
+ document.body.click()
+ expect(document.body.querySelector('.tooltip')).toBeNull()
+
+ // Tooltip can show again after hiding due to clicking outside of the tooltip
+ element.click()
+ expect(document.body.querySelector('.tooltip')).not.toBeNull()
+ element.click()
+ expect(document.body.querySelector('.tooltip')).toBeNull()
+ })
+ )
+
+ it('allows a custom item to be specified for the content of the tooltip', () => {
+ const tooltipElement = document.createElement('div')
+ manager.add(element, {item: {element: tooltipElement}})
+ hover(element, () => expect(tooltipElement.closest('.tooltip')).not.toBeNull())
+ })
+
+ it('allows a custom class to be specified for the tooltip', () => {
+ manager.add(element, {title: 'Title', class: 'custom-tooltip-class'})
+ hover(element, () => expect(document.body.querySelector('.tooltip').classList.contains('custom-tooltip-class')).toBe(true))
+ })
+
+ it('allows jQuery elements to be passed as the target', () => {
+ const element2 = document.createElement('div')
+ jasmine.attachToDOM(element2)
+
+ const fakeJqueryWrapper = [element, element2]
+ fakeJqueryWrapper.jquery = 'any-version'
+ const 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 keyBindingCommand is specified', () => {
+ describe('when a title is specified', () =>
+ it('appends the key binding corresponding to the command to the title', () => {
+ atom.keymaps.add('test', {
+ '.foo': { 'ctrl-x ctrl-y': 'test-command'
+ },
+ '.bar': { 'ctrl-x ctrl-z': 'test-command'
+ }
+ }
+ )
+
+ manager.add(element, {title: 'Title', keyBindingCommand: 'test-command'})
+
+ hover(element, function () {
+ const tooltipElement = document.body.querySelector('.tooltip')
+ expect(tooltipElement).toHaveText(`Title ${ctrlX} ${ctrlY}`)
+ })
+ })
+ )
+
+ describe('when no title is specified', () =>
+ it('shows the key binding corresponding to the command alone', () => {
+ atom.keymaps.add('test', {'.foo': {'ctrl-x ctrl-y': 'test-command'}})
+
+ manager.add(element, {keyBindingCommand: 'test-command'})
+
+ hover(element, function () {
+ const tooltipElement = document.body.querySelector('.tooltip')
+ expect(tooltipElement).toHaveText(`${ctrlX} ${ctrlY}`)
+ })
+ })
+ )
+
+ describe('when a keyBindingTarget is specified', () => {
+ it('looks up the key binding relative to the target', () => {
+ atom.keymaps.add('test', {
+ '.bar': { 'ctrl-x ctrl-z': 'test-command'
+ },
+ '.foo': { 'ctrl-x ctrl-y': 'test-command'
+ }
+ }
+ )
+
+ manager.add(element, {keyBindingCommand: 'test-command', keyBindingTarget: element})
+
+ hover(element, function () {
+ const tooltipElement = document.body.querySelector('.tooltip')
+ expect(tooltipElement).toHaveText(`${ctrlX} ${ctrlY}`)
+ })
+ })
+
+ it('does not display the keybinding if there is nothing mapped to the specified keyBindingCommand', () => {
+ manager.add(element, {title: 'A Title', keyBindingCommand: 'test-command', keyBindingTarget: element})
+
+ hover(element, function () {
+ const tooltipElement = document.body.querySelector('.tooltip')
+ expect(tooltipElement.textContent).toBe('A Title')
+ })
+ })
+ })
+ })
+
+ describe('when .dispose() is called on the returned disposable', () =>
+ it('no longer displays the tooltip on hover', () => {
+ const disposable = manager.add(element, {title: 'Title'})
+
+ hover(element, () => expect(document.body.querySelector('.tooltip')).toHaveText('Title'))
+
+ disposable.dispose()
+
+ hover(element, () => expect(document.body.querySelector('.tooltip')).toBeNull())
+ })
+ )
+
+ describe('when the window is resized', () =>
+ it('hides the tooltips', () => {
+ const disposable = manager.add(element, {title: 'Title'})
+ hover(element, function () {
+ expect(document.body.querySelector('.tooltip')).not.toBeNull()
+ window.dispatchEvent(new CustomEvent('resize'))
+ expect(document.body.querySelector('.tooltip')).toBeNull()
+ disposable.dispose()
+ })
+ })
+ )
+
+ describe('findTooltips', () => {
+ it('adds and remove tooltips correctly', () => {
+ expect(manager.findTooltips(element).length).toBe(0)
+ const disposable1 = manager.add(element, {title: 'elem1'})
+ expect(manager.findTooltips(element).length).toBe(1)
+ const disposable2 = manager.add(element, {title: 'elem2'})
+ expect(manager.findTooltips(element).length).toBe(2)
+ disposable1.dispose()
+ expect(manager.findTooltips(element).length).toBe(1)
+ disposable2.dispose()
+ expect(manager.findTooltips(element).length).toBe(0)
+ })
+
+ it('lets us hide tooltips programmatically', () => {
+ const disposable = manager.add(element, {title: 'Title'})
+ hover(element, function () {
+ expect(document.body.querySelector('.tooltip')).not.toBeNull()
+ manager.findTooltips(element)[0].hide()
+ expect(document.body.querySelector('.tooltip')).toBeNull()
+ disposable.dispose()
+ })
+ })
+ })
+ })
+})
+
+function createElement (className) {
+ const el = document.createElement('div')
+ el.classList.add(className)
+ jasmine.attachToDOM(el)
+ return el
+}
+
+function mouseEnter (element) {
+ element.dispatchEvent(new CustomEvent('mouseenter', {bubbles: false}))
+ element.dispatchEvent(new CustomEvent('mouseover', {bubbles: true}))
+}
+
+function mouseLeave (element) {
+ element.dispatchEvent(new CustomEvent('mouseleave', {bubbles: false}))
+ element.dispatchEvent(new CustomEvent('mouseout', {bubbles: true}))
+}
diff --git a/spec/view-registry-spec.coffee b/spec/view-registry-spec.coffee
deleted file mode 100644
index 4bae1d811..000000000
--- a/spec/view-registry-spec.coffee
+++ /dev/null
@@ -1,163 +0,0 @@
-ViewRegistry = require '../src/view-registry'
-
-describe "ViewRegistry", ->
- registry = null
-
- beforeEach ->
- registry = new ViewRegistry
-
- afterEach ->
- registry.clearDocumentRequests()
-
- describe "::getView(object)", ->
- describe "when passed a DOM node", ->
- it "returns the given DOM node", ->
- node = document.createElement('div')
- expect(registry.getView(node)).toBe node
-
- describe "when passed an object with an element property", ->
- it "returns the element property if it's an instance of HTMLElement", ->
- class TestComponent
- constructor: -> @element = document.createElement('div')
-
- component = new TestComponent
- expect(registry.getView(component)).toBe component.element
-
- describe "when passed an object with a getElement function", ->
- it "returns the return value of getElement if it's an instance of HTMLElement", ->
- class TestComponent
- getElement: ->
- @myElement ?= document.createElement('div')
-
- component = new TestComponent
- expect(registry.getView(component)).toBe component.myElement
-
- describe "when passed a model object", ->
- describe "when a view provider is registered matching the object's constructor", ->
- it "constructs a view element and assigns the model on it", ->
- class TestModel
-
- class TestModelSubclass extends TestModel
-
- class TestView
- initialize: (@model) -> this
-
- model = new TestModel
-
- registry.addViewProvider TestModel, (model) ->
- new TestView().initialize(model)
-
- view = registry.getView(model)
- expect(view instanceof TestView).toBe true
- expect(view.model).toBe model
-
- subclassModel = new TestModelSubclass
- view2 = registry.getView(subclassModel)
- expect(view2 instanceof TestView).toBe true
- expect(view2.model).toBe subclassModel
-
- describe "when a view provider is registered generically, and works with the object", ->
- it "constructs a view element and assigns the model on it", ->
- model = {a: 'b'}
-
- registry.addViewProvider (model) ->
- if model.a is 'b'
- element = document.createElement('div')
- element.className = 'test-element'
- element
-
- view = registry.getView({a: 'b'})
- expect(view.className).toBe 'test-element'
-
- expect(-> registry.getView({a: 'c'})).toThrow()
-
- describe "when no view provider is registered for the object's constructor", ->
- 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", ->
- class TestModel
- class TestView
- initialize: (@model) -> this
-
- disposable = registry.addViewProvider TestModel, (model) ->
- new TestView().initialize(model)
-
- expect(registry.getView(new TestModel) instanceof TestView).toBe true
- disposable.dispose()
- expect(-> registry.getView(new TestModel)).toThrow()
-
- describe "::updateDocument(fn) and ::readDocument(fn)", ->
- frameRequests = null
-
- beforeEach ->
- frameRequests = []
- spyOn(window, 'requestAnimationFrame').andCallFake (fn) -> frameRequests.push(fn)
-
- it "performs all pending writes before all pending reads on the next animation frame", ->
- events = []
-
- registry.updateDocument -> events.push('write 1')
- registry.readDocument -> events.push('read 1')
- registry.readDocument -> events.push('read 2')
- registry.updateDocument -> events.push('write 2')
-
- expect(events).toEqual []
-
- expect(frameRequests.length).toBe 1
- frameRequests[0]()
- expect(events).toEqual ['write 1', 'write 2', 'read 1', 'read 2']
-
- frameRequests = []
- events = []
- disposable = registry.updateDocument -> events.push('write 3')
- registry.updateDocument -> events.push('write 4')
- registry.readDocument -> events.push('read 3')
-
- disposable.dispose()
-
- expect(frameRequests.length).toBe 1
- frameRequests[0]()
- expect(events).toEqual ['write 4', 'read 3']
-
- it "performs writes requested from read callbacks in the same animation frame", ->
- spyOn(window, 'setInterval').andCallFake(fakeSetInterval)
- spyOn(window, 'clearInterval').andCallFake(fakeClearInterval)
- events = []
-
- registry.updateDocument -> events.push('write 1')
- registry.readDocument ->
- registry.updateDocument -> events.push('write from read 1')
- events.push('read 1')
- registry.readDocument ->
- registry.updateDocument -> events.push('write from read 2')
- events.push('read 2')
- registry.updateDocument -> events.push('write 2')
-
- expect(frameRequests.length).toBe 1
- frameRequests[0]()
- expect(frameRequests.length).toBe 1
-
- expect(events).toEqual [
- 'write 1'
- 'write 2'
- 'read 1'
- 'read 2'
- 'write from read 1'
- 'write from read 2'
- ]
-
- describe "::getNextUpdatePromise()", ->
- it "returns a promise that resolves at the end of the next update cycle", ->
- updateCalled = false
- readCalled = false
-
- waitsFor 'getNextUpdatePromise to resolve', (done) ->
- registry.getNextUpdatePromise().then ->
- expect(updateCalled).toBe true
- expect(readCalled).toBe true
- done()
-
- registry.updateDocument -> updateCalled = true
- registry.readDocument -> readCalled = true
diff --git a/spec/view-registry-spec.js b/spec/view-registry-spec.js
new file mode 100644
index 000000000..db8b077f1
--- /dev/null
+++ b/spec/view-registry-spec.js
@@ -0,0 +1,216 @@
+/*
+ * decaffeinate suggestions:
+ * DS102: Remove unnecessary code created because of implicit returns
+ * DS207: Consider shorter variations of null checks
+ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
+ */
+const ViewRegistry = require('../src/view-registry')
+
+describe('ViewRegistry', () => {
+ let registry = null
+
+ beforeEach(() => {
+ registry = new ViewRegistry()
+ })
+
+ afterEach(() => {
+ registry.clearDocumentRequests()
+ })
+
+ describe('::getView(object)', () => {
+ describe('when passed a DOM node', () =>
+ it('returns the given DOM node', () => {
+ const node = document.createElement('div')
+ expect(registry.getView(node)).toBe(node)
+ })
+ )
+
+ describe('when passed an object with an element property', () =>
+ it("returns the element property if it's an instance of HTMLElement", () => {
+ class TestComponent {
+ constructor () {
+ this.element = document.createElement('div')
+ }
+ }
+
+ const component = new TestComponent()
+ expect(registry.getView(component)).toBe(component.element)
+ })
+ )
+
+ describe('when passed an object with a getElement function', () =>
+ it("returns the return value of getElement if it's an instance of HTMLElement", () => {
+ class TestComponent {
+ getElement () {
+ if (this.myElement == null) {
+ this.myElement = document.createElement('div')
+ }
+ return this.myElement
+ }
+ }
+
+ const component = new TestComponent()
+ expect(registry.getView(component)).toBe(component.myElement)
+ })
+ )
+
+ describe('when passed a model object', () => {
+ describe("when a view provider is registered matching the object's constructor", () =>
+ it('constructs a view element and assigns the model on it', () => {
+ class TestModel {}
+
+ class TestModelSubclass extends TestModel {}
+
+ class TestView {
+ initialize (model) {
+ this.model = model
+ return this
+ }
+ }
+
+ const model = new TestModel()
+
+ registry.addViewProvider(TestModel, (model) =>
+ new TestView().initialize(model)
+ )
+
+ const view = registry.getView(model)
+ expect(view instanceof TestView).toBe(true)
+ expect(view.model).toBe(model)
+
+ const subclassModel = new TestModelSubclass()
+ const view2 = registry.getView(subclassModel)
+ expect(view2 instanceof TestView).toBe(true)
+ expect(view2.model).toBe(subclassModel)
+ })
+ )
+
+ describe('when a view provider is registered generically, and works with the object', () =>
+ it('constructs a view element and assigns the model on it', () => {
+ registry.addViewProvider((model) => {
+ if (model.a === 'b') {
+ const element = document.createElement('div')
+ element.className = 'test-element'
+ return element
+ }
+ })
+
+ const view = registry.getView({a: 'b'})
+ expect(view.className).toBe('test-element')
+
+ expect(() => registry.getView({a: 'c'})).toThrow()
+ })
+ )
+
+ describe("when no view provider is registered for the object's constructor", () =>
+ it('throws an exception', () => {
+ expect(() => registry.getView({})).toThrow()
+ })
+ )
+ })
+ })
+
+ describe('::addViewProvider(providerSpec)', () =>
+ it('returns a disposable that can be used to remove the provider', () => {
+ class TestModel {}
+ class TestView {
+ initialize (model) {
+ this.model = model
+ return this
+ }
+ }
+
+ const disposable = registry.addViewProvider(TestModel, (model) =>
+ new TestView().initialize(model)
+ )
+
+ expect(registry.getView(new TestModel()) instanceof TestView).toBe(true)
+ disposable.dispose()
+ expect(() => registry.getView(new TestModel())).toThrow()
+ })
+ )
+
+ describe('::updateDocument(fn) and ::readDocument(fn)', () => {
+ let frameRequests = null
+
+ beforeEach(() => {
+ frameRequests = []
+ spyOn(window, 'requestAnimationFrame').andCallFake(fn => frameRequests.push(fn))
+ })
+
+ it('performs all pending writes before all pending reads on the next animation frame', () => {
+ let events = []
+
+ registry.updateDocument(() => events.push('write 1'))
+ registry.readDocument(() => events.push('read 1'))
+ registry.readDocument(() => events.push('read 2'))
+ registry.updateDocument(() => events.push('write 2'))
+
+ expect(events).toEqual([])
+
+ expect(frameRequests.length).toBe(1)
+ frameRequests[0]()
+ expect(events).toEqual(['write 1', 'write 2', 'read 1', 'read 2'])
+
+ frameRequests = []
+ events = []
+ const disposable = registry.updateDocument(() => events.push('write 3'))
+ registry.updateDocument(() => events.push('write 4'))
+ registry.readDocument(() => events.push('read 3'))
+
+ disposable.dispose()
+
+ expect(frameRequests.length).toBe(1)
+ frameRequests[0]()
+ expect(events).toEqual(['write 4', 'read 3'])
+ })
+
+ it('performs writes requested from read callbacks in the same animation frame', () => {
+ spyOn(window, 'setInterval').andCallFake(fakeSetInterval)
+ spyOn(window, 'clearInterval').andCallFake(fakeClearInterval)
+ const events = []
+
+ registry.updateDocument(() => events.push('write 1'))
+ registry.readDocument(() => {
+ registry.updateDocument(() => events.push('write from read 1'))
+ events.push('read 1')
+ })
+ registry.readDocument(() => {
+ registry.updateDocument(() => events.push('write from read 2'))
+ events.push('read 2')
+ })
+ registry.updateDocument(() => events.push('write 2'))
+
+ expect(frameRequests.length).toBe(1)
+ frameRequests[0]()
+ expect(frameRequests.length).toBe(1)
+
+ expect(events).toEqual([
+ 'write 1',
+ 'write 2',
+ 'read 1',
+ 'read 2',
+ 'write from read 1',
+ 'write from read 2'
+ ])
+ })
+ })
+
+ describe('::getNextUpdatePromise()', () =>
+ it('returns a promise that resolves at the end of the next update cycle', () => {
+ let updateCalled = false
+ let readCalled = false
+
+ waitsFor('getNextUpdatePromise to resolve', (done) => {
+ registry.getNextUpdatePromise().then(() => {
+ expect(updateCalled).toBe(true)
+ expect(readCalled).toBe(true)
+ done()
+ })
+
+ registry.updateDocument(() => { updateCalled = true })
+ registry.readDocument(() => { readCalled = true })
+ })
+ })
+ )
+})
diff --git a/spec/window-event-handler-spec.coffee b/spec/window-event-handler-spec.coffee
deleted file mode 100644
index 9c9f4a098..000000000
--- a/spec/window-event-handler-spec.coffee
+++ /dev/null
@@ -1,209 +0,0 @@
-KeymapManager = require 'atom-keymap'
-TextEditor = require '../src/text-editor'
-WindowEventHandler = require '../src/window-event-handler'
-{ipcRenderer} = require 'electron'
-
-describe "WindowEventHandler", ->
- [windowEventHandler] = []
-
- beforeEach ->
- atom.uninstallWindowEventHandler()
- spyOn(atom, 'hide')
- initialPath = atom.project.getPaths()[0]
- spyOn(atom, 'getLoadSettings').andCallFake ->
- loadSettings = atom.getLoadSettings.originalValue.call(atom)
- loadSettings.initialPath = initialPath
- loadSettings
- atom.project.destroy()
- windowEventHandler = new WindowEventHandler({atomEnvironment: atom, applicationDelegate: atom.applicationDelegate})
- windowEventHandler.initialize(window, document)
-
- afterEach ->
- windowEventHandler.unsubscribe()
- atom.installWindowEventHandler()
-
- describe "when the window is loaded", ->
- it "doesn't have .is-blurred on the body tag", ->
- return if process.platform is 'win32' #Win32TestFailures - can not steal focus
- expect(document.body.className).not.toMatch("is-blurred")
-
- describe "when the window is blurred", ->
- beforeEach ->
- window.dispatchEvent(new CustomEvent('blur'))
-
- afterEach ->
- document.body.classList.remove('is-blurred')
-
- it "adds the .is-blurred class on the body", ->
- expect(document.body.className).toMatch("is-blurred")
-
- describe "when the window is focused again", ->
- it "removes the .is-blurred class from the body", ->
- 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.dispatchEvent(new CustomEvent('window:close'))
- expect(atom.close).toHaveBeenCalled()
-
- describe "when a link is clicked", ->
- it "opens the http/https links in an external application", ->
- {shell} = require 'electron'
- spyOn(shell, 'openExternal')
-
- 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()
-
- 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()
-
- link.href = ''
- windowEventHandler.handleLinkClick(fakeEvent)
- expect(shell.openExternal).not.toHaveBeenCalled()
- shell.openExternal.reset()
-
- 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", ->
- 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", ->
- wrapperDiv = document.createElement('div')
- wrapperDiv.innerHTML = """
-
-
-
-
- """
- elements = wrapperDiv.firstChild
- jasmine.attachToDOM(elements)
-
- elements.dispatchEvent(new CustomEvent("core:focus-next", bubbles: true))
- expect(document.activeElement.tabIndex).toBe 1
-
- 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/lowest tabindex, skipping disabled elements", ->
- wrapperDiv = document.createElement('div')
- wrapperDiv.innerHTML = """
-