mirror of
https://github.com/atom/atom.git
synced 2026-02-16 17:45:24 -05:00
Merge branch 'master' into wl-rm-safe-clipboard
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers')
|
||||
const {it, fit, ffit, beforeEach, afterEach, conditionPromise} = require('./async-spec-helpers')
|
||||
const _ = require('underscore-plus')
|
||||
const path = require('path')
|
||||
const temp = require('temp').track()
|
||||
@@ -518,27 +518,31 @@ describe('AtomEnvironment', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('prompts the user to restore the state in a new window, discarding it and adding folder to current window', () => {
|
||||
spyOn(atom, 'confirm').andReturn(1)
|
||||
it('prompts the user to restore the state in a new window, discarding it and adding folder to current window', async () => {
|
||||
jasmine.useRealClock()
|
||||
spyOn(atom, 'confirm').andCallFake((options, callback) => callback(1))
|
||||
spyOn(atom.project, 'addPath')
|
||||
spyOn(atom.workspace, 'open')
|
||||
const state = Symbol()
|
||||
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
expect(atom.project.addPath.callCount).toBe(1)
|
||||
await conditionPromise(() => atom.project.addPath.callCount === 1)
|
||||
|
||||
expect(atom.project.addPath).toHaveBeenCalledWith(__dirname)
|
||||
expect(atom.workspace.open.callCount).toBe(1)
|
||||
expect(atom.workspace.open).toHaveBeenCalledWith(__filename)
|
||||
})
|
||||
|
||||
it('prompts the user to restore the state in a new window, opening a new window', () => {
|
||||
spyOn(atom, 'confirm').andReturn(0)
|
||||
it('prompts the user to restore the state in a new window, opening a new window', async () => {
|
||||
jasmine.useRealClock()
|
||||
spyOn(atom, 'confirm').andCallFake((options, callback) => callback(0))
|
||||
spyOn(atom, 'open')
|
||||
const state = Symbol()
|
||||
|
||||
atom.attemptRestoreProjectStateForPaths(state, [__dirname], [__filename])
|
||||
expect(atom.confirm).toHaveBeenCalled()
|
||||
await conditionPromise(() => atom.open.callCount === 1)
|
||||
expect(atom.open).toHaveBeenCalledWith({
|
||||
pathsToOpen: [__dirname, __filename],
|
||||
newWindow: true,
|
||||
|
||||
@@ -35,9 +35,9 @@ describe('CommandInstaller on #darwin', () => {
|
||||
|
||||
installer.installShellCommandsInteractively()
|
||||
|
||||
expect(appDelegate.confirm).toHaveBeenCalledWith({
|
||||
expect(appDelegate.confirm.mostRecentCall.args[0]).toEqual({
|
||||
message: 'Failed to install shell commands',
|
||||
detailedMessage: 'an error'
|
||||
detail: 'an error'
|
||||
})
|
||||
|
||||
appDelegate.confirm.reset()
|
||||
@@ -46,9 +46,9 @@ describe('CommandInstaller on #darwin', () => {
|
||||
|
||||
installer.installShellCommandsInteractively()
|
||||
|
||||
expect(appDelegate.confirm).toHaveBeenCalledWith({
|
||||
expect(appDelegate.confirm.mostRecentCall.args[0]).toEqual({
|
||||
message: 'Failed to install shell commands',
|
||||
detailedMessage: 'another error'
|
||||
detail: 'another error'
|
||||
})
|
||||
})
|
||||
|
||||
@@ -61,9 +61,9 @@ describe('CommandInstaller on #darwin', () => {
|
||||
|
||||
installer.installShellCommandsInteractively()
|
||||
|
||||
expect(appDelegate.confirm).toHaveBeenCalledWith({
|
||||
expect(appDelegate.confirm.mostRecentCall.args[0]).toEqual({
|
||||
message: 'Commands installed.',
|
||||
detailedMessage: 'The shell commands `atom` and `apm` are installed.'
|
||||
detail: 'The shell commands `atom` and `apm` are installed.'
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const CommandRegistry = require('../src/command-registry');
|
||||
const _ = require('underscore-plus');
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers');
|
||||
|
||||
describe("CommandRegistry", () => {
|
||||
let registry, parent, child, grandchild;
|
||||
@@ -357,12 +358,41 @@ describe("CommandRegistry", () => {
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it("returns a boolean indicating whether any listeners matched the command", () => {
|
||||
it("returns a promise if any listeners matched the command", () => {
|
||||
registry.add('.grandchild', 'command', () => {});
|
||||
|
||||
expect(registry.dispatch(grandchild, 'command')).toBe(true);
|
||||
expect(registry.dispatch(grandchild, 'bogus')).toBe(false);
|
||||
expect(registry.dispatch(parent, 'command')).toBe(false);
|
||||
expect(registry.dispatch(grandchild, 'command').constructor.name).toBe("Promise");
|
||||
expect(registry.dispatch(grandchild, 'bogus')).toBe(null);
|
||||
expect(registry.dispatch(parent, 'command')).toBe(null);
|
||||
});
|
||||
|
||||
it("returns a promise that resolves when the listeners resolve", async () => {
|
||||
jasmine.useRealClock();
|
||||
registry.add('.grandchild', 'command', () => 1);
|
||||
registry.add('.grandchild', 'command', () => Promise.resolve(2));
|
||||
registry.add('.grandchild', 'command', () => new Promise((resolve) => {
|
||||
setTimeout(() => { resolve(3); }, 1);
|
||||
}));
|
||||
|
||||
const values = await registry.dispatch(grandchild, 'command');
|
||||
expect(values).toEqual([3, 2, 1]);
|
||||
});
|
||||
|
||||
it("returns a promise that rejects when a listener is rejected", async () => {
|
||||
jasmine.useRealClock();
|
||||
registry.add('.grandchild', 'command', () => 1);
|
||||
registry.add('.grandchild', 'command', () => Promise.resolve(2));
|
||||
registry.add('.grandchild', 'command', () => new Promise((resolve, reject) => {
|
||||
setTimeout(() => { reject(3); }, 1);
|
||||
}));
|
||||
|
||||
let value;
|
||||
try {
|
||||
value = await registry.dispatch(grandchild, 'command');
|
||||
} catch (err) {
|
||||
value = err;
|
||||
}
|
||||
expect(value).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -106,6 +106,15 @@ describe "Config", ->
|
||||
atom.config.set("foo.bar.baz", 1, scopeSelector: ".source.coffee", source: "some-package")
|
||||
expect(atom.config.get("foo.bar.baz", scope: [".source.coffee"])).toBe 100
|
||||
|
||||
describe "when the first component of the scope descriptor matches a legacy scope alias", ->
|
||||
it "falls back to properties defined for the legacy scope if no value is found for the original scope descriptor", ->
|
||||
atom.config.addLegacyScopeAlias('javascript', '.source.js')
|
||||
atom.config.set('foo', 100, scopeSelector: '.source.js')
|
||||
atom.config.set('foo', 200, scopeSelector: 'javascript for_statement')
|
||||
|
||||
expect(atom.config.get('foo', scope: ['javascript', 'for_statement', 'identifier'])).toBe(200)
|
||||
expect(atom.config.get('foo', scope: ['javascript', 'function', 'identifier'])).toBe(100)
|
||||
|
||||
describe ".getAll(keyPath, {scope, sources, excludeSources})", ->
|
||||
it "reads all of the values for a given key-path", ->
|
||||
expect(atom.config.set("foo", 41)).toBe true
|
||||
@@ -130,6 +139,20 @@ describe "Config", ->
|
||||
{scopeSelector: '*', value: 40}
|
||||
]
|
||||
|
||||
describe "when the first component of the scope descriptor matches a legacy scope alias", ->
|
||||
it "includes the values defined for the legacy scope", ->
|
||||
atom.config.addLegacyScopeAlias('javascript', '.source.js')
|
||||
|
||||
expect(atom.config.set('foo', 41)).toBe true
|
||||
expect(atom.config.set('foo', 42, scopeSelector: 'javascript')).toBe true
|
||||
expect(atom.config.set('foo', 43, scopeSelector: '.source.js')).toBe true
|
||||
|
||||
expect(atom.config.getAll('foo', scope: ['javascript'])).toEqual([
|
||||
{scopeSelector: 'javascript', value: 42},
|
||||
{scopeSelector: '.js.source', value: 43},
|
||||
{scopeSelector: '*', value: 41}
|
||||
])
|
||||
|
||||
describe ".set(keyPath, value, {source, scopeSelector})", ->
|
||||
it "allows a key path's value to be written", ->
|
||||
expect(atom.config.set("foo.bar.baz", 42)).toBe true
|
||||
|
||||
1
spec/fixtures/packages/package-with-tree-sitter-grammar/grammars/fake-parser.js
vendored
Normal file
1
spec/fixtures/packages/package-with-tree-sitter-grammar/grammars/fake-parser.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
exports.isFakeTreeSitterParser = true
|
||||
14
spec/fixtures/packages/package-with-tree-sitter-grammar/grammars/some-language.cson
vendored
Normal file
14
spec/fixtures/packages/package-with-tree-sitter-grammar/grammars/some-language.cson
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: 'Some Language'
|
||||
|
||||
id: 'some-language'
|
||||
|
||||
type: 'tree-sitter'
|
||||
|
||||
parser: './fake-parser'
|
||||
|
||||
fileTypes: [
|
||||
'somelang'
|
||||
]
|
||||
|
||||
scopes:
|
||||
'class > identifier': 'entity.name.type.class'
|
||||
@@ -1,10 +1,13 @@
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers')
|
||||
|
||||
const dedent = require('dedent')
|
||||
const path = require('path')
|
||||
const fs = require('fs-plus')
|
||||
const temp = require('temp').track()
|
||||
const TextBuffer = require('text-buffer')
|
||||
const GrammarRegistry = require('../src/grammar-registry')
|
||||
const TreeSitterGrammar = require('../src/tree-sitter-grammar')
|
||||
const FirstMate = require('first-mate')
|
||||
|
||||
describe('GrammarRegistry', () => {
|
||||
let grammarRegistry
|
||||
@@ -13,8 +16,8 @@ describe('GrammarRegistry', () => {
|
||||
grammarRegistry = new GrammarRegistry({config: atom.config})
|
||||
})
|
||||
|
||||
describe('.assignLanguageMode(buffer, languageName)', () => {
|
||||
it('assigns to the buffer a language mode with the given language name', async () => {
|
||||
describe('.assignLanguageMode(buffer, languageId)', () => {
|
||||
it('assigns to the buffer a language mode with the given language id', async () => {
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-css/grammars/css.cson'))
|
||||
|
||||
@@ -34,7 +37,7 @@ describe('GrammarRegistry', () => {
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css')
|
||||
})
|
||||
|
||||
describe('when no languageName is passed', () => {
|
||||
describe('when no languageId is passed', () => {
|
||||
it('makes the buffer use the null grammar', () => {
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-css/grammars/css.cson'))
|
||||
|
||||
@@ -48,6 +51,36 @@ describe('GrammarRegistry', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('.grammarForId(languageId)', () => {
|
||||
it('converts the language id to a text-mate language id when `core.useTreeSitterParsers` is false', () => {
|
||||
atom.config.set('core.useTreeSitterParsers', false)
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/tree-sitter-javascript.cson'))
|
||||
|
||||
const grammar = grammarRegistry.grammarForId('javascript')
|
||||
expect(grammar instanceof FirstMate.Grammar).toBe(true)
|
||||
expect(grammar.scopeName).toBe('source.js')
|
||||
|
||||
grammarRegistry.removeGrammar(grammar)
|
||||
expect(grammarRegistry.grammarForId('javascript')).toBe(undefined)
|
||||
})
|
||||
|
||||
it('converts the language id to a tree-sitter language id when `core.useTreeSitterParsers` is true', () => {
|
||||
atom.config.set('core.useTreeSitterParsers', true)
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/tree-sitter-javascript.cson'))
|
||||
|
||||
const grammar = grammarRegistry.grammarForId('source.js')
|
||||
expect(grammar instanceof TreeSitterGrammar).toBe(true)
|
||||
expect(grammar.id).toBe('javascript')
|
||||
|
||||
grammarRegistry.removeGrammar(grammar)
|
||||
expect(grammarRegistry.grammarForId('source.js') instanceof FirstMate.Grammar).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.autoAssignLanguageMode(buffer)', () => {
|
||||
it('assigns to the buffer a language mode based on the best available grammar', () => {
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
@@ -78,7 +111,9 @@ describe('GrammarRegistry', () => {
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('source.c')
|
||||
})
|
||||
|
||||
it('updates the buffer\'s grammar when a more appropriate grammar is added for its path', async () => {
|
||||
it('updates the buffer\'s grammar when a more appropriate text-mate grammar is added for its path', async () => {
|
||||
atom.config.set('core.useTreeSitterParsers', false)
|
||||
|
||||
const buffer = new TextBuffer()
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe(null)
|
||||
|
||||
@@ -87,6 +122,25 @@ describe('GrammarRegistry', () => {
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('source.js')
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/tree-sitter-javascript.cson'))
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('source.js')
|
||||
})
|
||||
|
||||
it('updates the buffer\'s grammar when a more appropriate tree-sitter grammar is added for its path', async () => {
|
||||
atom.config.set('core.useTreeSitterParsers', true)
|
||||
|
||||
const buffer = new TextBuffer()
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe(null)
|
||||
|
||||
buffer.setPath('test.js')
|
||||
grammarRegistry.maintainLanguageMode(buffer)
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/tree-sitter-javascript.cson'))
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('javascript')
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
expect(buffer.getLanguageMode().getLanguageId()).toBe('javascript')
|
||||
})
|
||||
|
||||
it('can be overridden by calling .assignLanguageMode', () => {
|
||||
@@ -226,6 +280,32 @@ describe('GrammarRegistry', () => {
|
||||
expect(atom.grammars.selectGrammar('/hu.git/config').name).toBe('Null Grammar')
|
||||
})
|
||||
|
||||
describe('when the grammar has a contentRegExp field', () => {
|
||||
it('favors grammars whose contentRegExp matches a prefix of the file\'s content', () => {
|
||||
atom.grammars.addGrammar({
|
||||
id: 'javascript-1',
|
||||
fileTypes: ['js']
|
||||
})
|
||||
atom.grammars.addGrammar({
|
||||
id: 'flow-javascript',
|
||||
contentRegExp: new RegExp('//.*@flow'),
|
||||
fileTypes: ['js']
|
||||
})
|
||||
atom.grammars.addGrammar({
|
||||
id: 'javascript-2',
|
||||
fileTypes: ['js']
|
||||
})
|
||||
|
||||
const selectedGrammar = atom.grammars.selectGrammar('test.js', dedent`
|
||||
// Copyright EvilCorp
|
||||
// @flow
|
||||
|
||||
module.exports = function () { return 1 + 1 }
|
||||
`)
|
||||
expect(selectedGrammar.id).toBe('flow-javascript')
|
||||
})
|
||||
})
|
||||
|
||||
it("uses the filePath's shebang line if the grammar cannot be determined by the extension or basename", async () => {
|
||||
await atom.packages.activatePackage('language-javascript')
|
||||
await atom.packages.activatePackage('language-ruby')
|
||||
@@ -335,14 +415,38 @@ describe('GrammarRegistry', () => {
|
||||
await atom.packages.activatePackage('language-javascript')
|
||||
expect(atom.grammars.selectGrammar('foo.rb', '#!/usr/bin/env node').scopeName).toBe('source.ruby')
|
||||
})
|
||||
|
||||
describe('tree-sitter vs text-mate', () => {
|
||||
it('favors a text-mate grammar over a tree-sitter grammar when `core.useTreeSitterParsers` is false', () => {
|
||||
atom.config.set('core.useTreeSitterParsers', false)
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/tree-sitter-javascript.cson'))
|
||||
|
||||
const grammar = grammarRegistry.selectGrammar('test.js')
|
||||
expect(grammar.scopeName).toBe('source.js')
|
||||
expect(grammar instanceof FirstMate.Grammar).toBe(true)
|
||||
})
|
||||
|
||||
it('favors a tree-sitter grammar over a text-mate grammar when `core.useTreeSitterParsers` is true', () => {
|
||||
atom.config.set('core.useTreeSitterParsers', true)
|
||||
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/javascript.cson'))
|
||||
grammarRegistry.loadGrammarSync(require.resolve('language-javascript/grammars/tree-sitter-javascript.cson'))
|
||||
|
||||
const grammar = grammarRegistry.selectGrammar('test.js')
|
||||
expect(grammar.id).toBe('javascript')
|
||||
expect(grammar instanceof TreeSitterGrammar).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.removeGrammar(grammar)', () => {
|
||||
it("removes the grammar, so it won't be returned by selectGrammar", async () => {
|
||||
await atom.packages.activatePackage('language-javascript')
|
||||
const grammar = atom.grammars.selectGrammar('foo.js')
|
||||
await atom.packages.activatePackage('language-css')
|
||||
const grammar = atom.grammars.selectGrammar('foo.css')
|
||||
atom.grammars.removeGrammar(grammar)
|
||||
expect(atom.grammars.selectGrammar('foo.js').name).not.toBe(grammar.name)
|
||||
expect(atom.grammars.selectGrammar('foo.css').name).not.toBe(grammar.name)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
/** @babel */
|
||||
|
||||
import season from 'season'
|
||||
import dedent from 'dedent'
|
||||
import electron from 'electron'
|
||||
import fs from 'fs-plus'
|
||||
import path from 'path'
|
||||
import sinon from 'sinon'
|
||||
import AtomApplication from '../../src/main-process/atom-application'
|
||||
import parseCommandLine from '../../src/main-process/parse-command-line'
|
||||
import {timeoutPromise, conditionPromise, emitterEventPromise} from '../async-spec-helpers'
|
||||
const temp = require('temp').track()
|
||||
const season = require('season')
|
||||
const dedent = require('dedent')
|
||||
const electron = require('electron')
|
||||
const fs = require('fs-plus')
|
||||
const path = require('path')
|
||||
const sinon = require('sinon')
|
||||
const AtomApplication = require('../../src/main-process/atom-application')
|
||||
const parseCommandLine = require('../../src/main-process/parse-command-line')
|
||||
const {timeoutPromise, conditionPromise, emitterEventPromise} = require('../async-spec-helpers')
|
||||
|
||||
const ATOM_RESOURCE_PATH = path.resolve(__dirname, '..', '..')
|
||||
|
||||
@@ -17,7 +16,7 @@ describe('AtomApplication', function () {
|
||||
|
||||
let originalAppQuit, originalShowMessageBox, originalAtomHome, atomApplicationsToDestroy
|
||||
|
||||
beforeEach(function () {
|
||||
beforeEach(() => {
|
||||
originalAppQuit = electron.app.quit
|
||||
originalShowMessageBox = electron.dialog.showMessageBox
|
||||
mockElectronAppQuit()
|
||||
@@ -34,7 +33,7 @@ describe('AtomApplication', function () {
|
||||
atomApplicationsToDestroy = []
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
afterEach(async () => {
|
||||
process.env.ATOM_HOME = originalAtomHome
|
||||
for (let atomApplication of atomApplicationsToDestroy) {
|
||||
await atomApplication.destroy()
|
||||
@@ -44,8 +43,8 @@ describe('AtomApplication', function () {
|
||||
electron.dialog.showMessageBox = originalShowMessageBox
|
||||
})
|
||||
|
||||
describe('launch', function () {
|
||||
it('can open to a specific line number of a file', async function () {
|
||||
describe('launch', () => {
|
||||
it('can open to a specific line number of a file', async () => {
|
||||
const filePath = path.join(makeTempDir(), 'new-file')
|
||||
fs.writeFileSync(filePath, '1\n2\n3\n4\n')
|
||||
const atomApplication = buildAtomApplication()
|
||||
@@ -53,8 +52,8 @@ describe('AtomApplication', function () {
|
||||
const window = atomApplication.launch(parseCommandLine([filePath + ':3']))
|
||||
await focusWindow(window)
|
||||
|
||||
const cursorRow = await evalInWebContents(window.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
atom.workspace.observeTextEditors(function (textEditor) {
|
||||
const cursorRow = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(textEditor => {
|
||||
sendBackToMainProcess(textEditor.getCursorBufferPosition().row)
|
||||
})
|
||||
})
|
||||
@@ -62,7 +61,7 @@ describe('AtomApplication', function () {
|
||||
assert.equal(cursorRow, 2)
|
||||
})
|
||||
|
||||
it('can open to a specific line and column of a file', async function () {
|
||||
it('can open to a specific line and column of a file', async () => {
|
||||
const filePath = path.join(makeTempDir(), 'new-file')
|
||||
fs.writeFileSync(filePath, '1\n2\n3\n4\n')
|
||||
const atomApplication = buildAtomApplication()
|
||||
@@ -70,8 +69,8 @@ describe('AtomApplication', function () {
|
||||
const window = atomApplication.launch(parseCommandLine([filePath + ':2:2']))
|
||||
await focusWindow(window)
|
||||
|
||||
const cursorPosition = await evalInWebContents(window.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
atom.workspace.observeTextEditors(function (textEditor) {
|
||||
const cursorPosition = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(textEditor => {
|
||||
sendBackToMainProcess(textEditor.getCursorBufferPosition())
|
||||
})
|
||||
})
|
||||
@@ -79,7 +78,7 @@ describe('AtomApplication', function () {
|
||||
assert.deepEqual(cursorPosition, {row: 1, column: 1})
|
||||
})
|
||||
|
||||
it('removes all trailing whitespace and colons from the specified path', async function () {
|
||||
it('removes all trailing whitespace and colons from the specified path', async () => {
|
||||
let filePath = path.join(makeTempDir(), 'new-file')
|
||||
fs.writeFileSync(filePath, '1\n2\n3\n4\n')
|
||||
const atomApplication = buildAtomApplication()
|
||||
@@ -87,8 +86,8 @@ describe('AtomApplication', function () {
|
||||
const window = atomApplication.launch(parseCommandLine([filePath + ':: ']))
|
||||
await focusWindow(window)
|
||||
|
||||
const openedPath = await evalInWebContents(window.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
atom.workspace.observeTextEditors(function (textEditor) {
|
||||
const openedPath = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(textEditor => {
|
||||
sendBackToMainProcess(textEditor.getPath())
|
||||
})
|
||||
})
|
||||
@@ -97,7 +96,7 @@ describe('AtomApplication', function () {
|
||||
})
|
||||
|
||||
if (process.platform === 'darwin' || process.platform === 'win32') {
|
||||
it('positions new windows at an offset distance from the previous window', async function () {
|
||||
it('positions new windows at an offset distance from the previous window', async () => {
|
||||
const atomApplication = buildAtomApplication()
|
||||
|
||||
const window1 = atomApplication.launch(parseCommandLine([makeTempDir()]))
|
||||
@@ -115,7 +114,7 @@ describe('AtomApplication', function () {
|
||||
})
|
||||
}
|
||||
|
||||
it('reuses existing windows when opening paths, but not directories', async function () {
|
||||
it('reuses existing windows when opening paths, but not directories', async () => {
|
||||
const dirAPath = makeTempDir("a")
|
||||
const dirBPath = makeTempDir("b")
|
||||
const dirCPath = makeTempDir("c")
|
||||
@@ -127,8 +126,8 @@ describe('AtomApplication', function () {
|
||||
await emitterEventPromise(window1, 'window:locations-opened')
|
||||
await focusWindow(window1)
|
||||
|
||||
let activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
atom.workspace.observeTextEditors(function (textEditor) {
|
||||
let activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(textEditor => {
|
||||
sendBackToMainProcess(textEditor.getPath())
|
||||
})
|
||||
})
|
||||
@@ -139,8 +138,8 @@ describe('AtomApplication', function () {
|
||||
const reusedWindow = atomApplication.launch(parseCommandLine([existingDirCFilePath]))
|
||||
assert.equal(reusedWindow, window1)
|
||||
assert.deepEqual(atomApplication.getAllWindows(), [window1])
|
||||
activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
const subscription = atom.workspace.onDidChangeActivePaneItem(function (textEditor) {
|
||||
activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
const subscription = atom.workspace.onDidChangeActivePaneItem(textEditor => {
|
||||
sendBackToMainProcess(textEditor.getPath())
|
||||
subscription.dispose()
|
||||
})
|
||||
@@ -156,7 +155,7 @@ describe('AtomApplication', function () {
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window2), [dirCPath])
|
||||
})
|
||||
|
||||
it('adds folders to existing windows when the --add option is used', async function () {
|
||||
it('adds folders to existing windows when the --add option is used', async () => {
|
||||
const dirAPath = makeTempDir("a")
|
||||
const dirBPath = makeTempDir("b")
|
||||
const dirCPath = makeTempDir("c")
|
||||
@@ -167,8 +166,8 @@ describe('AtomApplication', function () {
|
||||
const window1 = atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')]))
|
||||
await focusWindow(window1)
|
||||
|
||||
let activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
atom.workspace.observeTextEditors(function (textEditor) {
|
||||
let activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(textEditor => {
|
||||
sendBackToMainProcess(textEditor.getPath())
|
||||
})
|
||||
})
|
||||
@@ -179,8 +178,8 @@ describe('AtomApplication', function () {
|
||||
let reusedWindow = atomApplication.launch(parseCommandLine([existingDirCFilePath, '--add']))
|
||||
assert.equal(reusedWindow, window1)
|
||||
assert.deepEqual(atomApplication.getAllWindows(), [window1])
|
||||
activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
const subscription = atom.workspace.onDidChangeActivePaneItem(function (textEditor) {
|
||||
activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
const subscription = atom.workspace.onDidChangeActivePaneItem(textEditor => {
|
||||
sendBackToMainProcess(textEditor.getPath())
|
||||
subscription.dispose()
|
||||
})
|
||||
@@ -198,14 +197,14 @@ describe('AtomApplication', function () {
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirCPath, dirBPath])
|
||||
})
|
||||
|
||||
it('persists window state based on the project directories', async function () {
|
||||
it('persists window state based on the project directories', async () => {
|
||||
const tempDirPath = makeTempDir()
|
||||
const atomApplication = buildAtomApplication()
|
||||
const nonExistentFilePath = path.join(tempDirPath, 'new-file')
|
||||
|
||||
const window1 = atomApplication.launch(parseCommandLine([nonExistentFilePath]))
|
||||
await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
atom.workspace.observeTextEditors(function (textEditor) {
|
||||
await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(textEditor => {
|
||||
textEditor.insertText('Hello World!')
|
||||
sendBackToMainProcess(null)
|
||||
})
|
||||
@@ -217,7 +216,7 @@ describe('AtomApplication', function () {
|
||||
// Restore unsaved state when opening the directory itself
|
||||
const window2 = atomApplication.launch(parseCommandLine([tempDirPath]))
|
||||
await window2.loadedPromise
|
||||
const window2Text = await evalInWebContents(window2.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
const window2Text = await evalInWebContents(window2.browserWindow.webContents, sendBackToMainProcess => {
|
||||
const textEditor = atom.workspace.getActiveTextEditor()
|
||||
textEditor.moveToBottom()
|
||||
textEditor.insertText(' How are you?')
|
||||
@@ -231,13 +230,13 @@ describe('AtomApplication', function () {
|
||||
// Restore unsaved state when opening a path to a non-existent file in the directory
|
||||
const window3 = atomApplication.launch(parseCommandLine([path.join(tempDirPath, 'another-non-existent-file')]))
|
||||
await window3.loadedPromise
|
||||
const window3Texts = await evalInWebContents(window3.browserWindow.webContents, function (sendBackToMainProcess, nonExistentFilePath) {
|
||||
const window3Texts = await evalInWebContents(window3.browserWindow.webContents, (sendBackToMainProcess, nonExistentFilePath) => {
|
||||
sendBackToMainProcess(atom.workspace.getTextEditors().map(editor => editor.getText()))
|
||||
})
|
||||
assert.include(window3Texts, 'Hello World! How are you?')
|
||||
})
|
||||
|
||||
it('shows all directories in the tree view when multiple directory paths are passed to Atom', async function () {
|
||||
it('shows all directories in the tree view when multiple directory paths are passed to Atom', async () => {
|
||||
const dirAPath = makeTempDir("a")
|
||||
const dirBPath = makeTempDir("b")
|
||||
const dirBSubdirPath = path.join(dirBPath, 'c')
|
||||
@@ -250,7 +249,7 @@ describe('AtomApplication', function () {
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirBPath])
|
||||
})
|
||||
|
||||
it('reuses windows with no project paths to open directories', async function () {
|
||||
it('reuses windows with no project paths to open directories', async () => {
|
||||
const tempDirPath = makeTempDir()
|
||||
const atomApplication = buildAtomApplication()
|
||||
const window1 = atomApplication.launch(parseCommandLine([]))
|
||||
@@ -261,18 +260,18 @@ describe('AtomApplication', function () {
|
||||
await conditionPromise(async () => (await getTreeViewRootDirectories(reusedWindow)).length > 0)
|
||||
})
|
||||
|
||||
it('opens a new window with a single untitled buffer when launched with no path, even if windows already exist', async function () {
|
||||
it('opens a new window with a single untitled buffer when launched with no path, even if windows already exist', async () => {
|
||||
const atomApplication = buildAtomApplication()
|
||||
const window1 = atomApplication.launch(parseCommandLine([]))
|
||||
await focusWindow(window1)
|
||||
const window1EditorTitle = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
const window1EditorTitle = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle())
|
||||
})
|
||||
assert.equal(window1EditorTitle, 'untitled')
|
||||
|
||||
const window2 = atomApplication.openWithOptions(parseCommandLine([]))
|
||||
await focusWindow(window2)
|
||||
const window2EditorTitle = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
const window2EditorTitle = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle())
|
||||
})
|
||||
assert.equal(window2EditorTitle, 'untitled')
|
||||
@@ -280,7 +279,7 @@ describe('AtomApplication', function () {
|
||||
assert.deepEqual(atomApplication.getAllWindows(), [window2, window1])
|
||||
})
|
||||
|
||||
it('does not open an empty editor when opened with no path if the core.openEmptyEditorOnStart config setting is false', async function () {
|
||||
it('does not open an empty editor when opened with no path if the core.openEmptyEditorOnStart config setting is false', async () => {
|
||||
const configPath = path.join(process.env.ATOM_HOME, 'config.cson')
|
||||
const config = season.readFileSync(configPath)
|
||||
if (!config['*'].core) config['*'].core = {}
|
||||
@@ -294,19 +293,19 @@ describe('AtomApplication', function () {
|
||||
// wait a bit just to make sure we don't pass due to querying the render process before it loads
|
||||
await timeoutPromise(1000)
|
||||
|
||||
const itemCount = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
const itemCount = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
sendBackToMainProcess(atom.workspace.getActivePane().getItems().length)
|
||||
})
|
||||
assert.equal(itemCount, 0)
|
||||
})
|
||||
|
||||
it('opens an empty text editor and loads its parent directory in the tree-view when launched with a new file path', async function () {
|
||||
it('opens an empty text editor and loads its parent directory in the tree-view when launched with a new file path', async () => {
|
||||
const atomApplication = buildAtomApplication()
|
||||
const newFilePath = path.join(makeTempDir(), 'new-file')
|
||||
const window = atomApplication.launch(parseCommandLine([newFilePath]))
|
||||
await focusWindow(window)
|
||||
const {editorTitle, editorText} = await evalInWebContents(window.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
atom.workspace.observeTextEditors(function (editor) {
|
||||
const {editorTitle, editorText} = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.observeTextEditors(editor => {
|
||||
sendBackToMainProcess({editorTitle: editor.getTitle(), editorText: editor.getText()})
|
||||
})
|
||||
})
|
||||
@@ -315,7 +314,7 @@ describe('AtomApplication', function () {
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window), [path.dirname(newFilePath)])
|
||||
})
|
||||
|
||||
it('adds a remote directory to the project when launched with a remote directory', async function () {
|
||||
it('adds a remote directory to the project when launched with a remote directory', async () => {
|
||||
const packagePath = path.join(__dirname, '..', 'fixtures', 'packages', 'package-with-directory-provider')
|
||||
const packagesDirPath = path.join(process.env.ATOM_HOME, 'packages')
|
||||
fs.mkdirSync(packagesDirPath)
|
||||
@@ -338,13 +337,13 @@ describe('AtomApplication', function () {
|
||||
assert.deepEqual(directories, [{type: 'FakeRemoteDirectory', path: remotePath}])
|
||||
|
||||
function getProjectDirectories () {
|
||||
return evalInWebContents(window.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
return evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => {
|
||||
sendBackToMainProcess(atom.project.getDirectories().map(d => ({ type: d.constructor.name, path: d.getPath() })))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('reopens any previously opened windows when launched with no path', async function () {
|
||||
it('reopens any previously opened windows when launched with no path', async () => {
|
||||
if (process.platform === 'win32') return; // Test is too flakey on Windows
|
||||
|
||||
const tempDirPath1 = makeTempDir()
|
||||
@@ -372,7 +371,7 @@ describe('AtomApplication', function () {
|
||||
assert.deepEqual(await getTreeViewRootDirectories(app2Window2), [tempDirPath2])
|
||||
})
|
||||
|
||||
it('does not reopen any previously opened windows when launched with no path and `core.restorePreviousWindowsOnStart` is no', async function () {
|
||||
it('does not reopen any previously opened windows when launched with no path and `core.restorePreviousWindowsOnStart` is no', async () => {
|
||||
const atomApplication1 = buildAtomApplication()
|
||||
const app1Window1 = atomApplication1.launch(parseCommandLine([makeTempDir()]))
|
||||
await focusWindow(app1Window1)
|
||||
@@ -391,30 +390,136 @@ describe('AtomApplication', function () {
|
||||
assert.deepEqual(app2Window.representedDirectoryPaths, [])
|
||||
})
|
||||
|
||||
describe('when closing the last window', function () {
|
||||
describe('when the `--wait` flag is passed', () => {
|
||||
let killedPids, atomApplication, onDidKillProcess
|
||||
|
||||
beforeEach(() => {
|
||||
killedPids = []
|
||||
onDidKillProcess = null
|
||||
atomApplication = buildAtomApplication({
|
||||
killProcess (pid) {
|
||||
killedPids.push(pid)
|
||||
if (onDidKillProcess) onDidKillProcess()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('kills the specified pid after a newly-opened window is closed', async () => {
|
||||
const window1 = atomApplication.launch(parseCommandLine(['--wait', '--pid', '101']))
|
||||
await focusWindow(window1)
|
||||
|
||||
const [window2] = atomApplication.launch(parseCommandLine(['--new-window', '--wait', '--pid', '102']))
|
||||
await focusWindow(window2)
|
||||
assert.deepEqual(killedPids, [])
|
||||
|
||||
let processKillPromise = new Promise(resolve => { onDidKillProcess = resolve })
|
||||
window1.close()
|
||||
await processKillPromise
|
||||
assert.deepEqual(killedPids, [101])
|
||||
|
||||
processKillPromise = new Promise(resolve => { onDidKillProcess = resolve })
|
||||
window2.close()
|
||||
await processKillPromise
|
||||
assert.deepEqual(killedPids, [101, 102])
|
||||
})
|
||||
|
||||
it('kills the specified pid after a newly-opened file in an existing window is closed', async () => {
|
||||
const window = atomApplication.launch(parseCommandLine(['--wait', '--pid', '101']))
|
||||
await focusWindow(window)
|
||||
|
||||
const filePath1 = temp.openSync('test').path
|
||||
const filePath2 = temp.openSync('test').path
|
||||
fs.writeFileSync(filePath1, 'File 1')
|
||||
fs.writeFileSync(filePath2, 'File 2')
|
||||
|
||||
const reusedWindow = atomApplication.launch(parseCommandLine(['--wait', '--pid', '102', filePath1, filePath2]))
|
||||
assert.equal(reusedWindow, window)
|
||||
|
||||
const activeEditorPath = await evalInWebContents(window.browserWindow.webContents, send => {
|
||||
const subscription = atom.workspace.onDidChangeActivePaneItem(editor => {
|
||||
send(editor.getPath())
|
||||
subscription.dispose()
|
||||
})
|
||||
})
|
||||
|
||||
assert([filePath1, filePath2].includes(activeEditorPath))
|
||||
assert.deepEqual(killedPids, [])
|
||||
|
||||
await evalInWebContents(window.browserWindow.webContents, send => {
|
||||
atom.workspace.getActivePaneItem().destroy()
|
||||
send()
|
||||
})
|
||||
await timeoutPromise(100)
|
||||
assert.deepEqual(killedPids, [])
|
||||
|
||||
let processKillPromise = new Promise(resolve => { onDidKillProcess = resolve })
|
||||
await evalInWebContents(window.browserWindow.webContents, send => {
|
||||
atom.workspace.getActivePaneItem().destroy()
|
||||
send()
|
||||
})
|
||||
await processKillPromise
|
||||
assert.deepEqual(killedPids, [102])
|
||||
|
||||
processKillPromise = new Promise(resolve => { onDidKillProcess = resolve })
|
||||
window.close()
|
||||
await processKillPromise
|
||||
assert.deepEqual(killedPids, [102, 101])
|
||||
})
|
||||
|
||||
it('kills the specified pid after a newly-opened directory in an existing window is closed', async () => {
|
||||
const window = atomApplication.launch(parseCommandLine([]))
|
||||
await focusWindow(window)
|
||||
|
||||
const dirPath1 = makeTempDir()
|
||||
const reusedWindow = atomApplication.launch(parseCommandLine(['--wait', '--pid', '101', dirPath1]))
|
||||
assert.equal(reusedWindow, window)
|
||||
assert.deepEqual(await getTreeViewRootDirectories(window), [dirPath1])
|
||||
assert.deepEqual(killedPids, [])
|
||||
|
||||
const dirPath2 = makeTempDir()
|
||||
await evalInWebContents(window.browserWindow.webContents, (send, dirPath1, dirPath2) => {
|
||||
atom.project.setPaths([dirPath1, dirPath2])
|
||||
send()
|
||||
}, dirPath1, dirPath2)
|
||||
await timeoutPromise(100)
|
||||
assert.deepEqual(killedPids, [])
|
||||
|
||||
let processKillPromise = new Promise(resolve => { onDidKillProcess = resolve })
|
||||
await evalInWebContents(window.browserWindow.webContents, (send, dirPath2) => {
|
||||
atom.project.setPaths([dirPath2])
|
||||
send()
|
||||
}, dirPath2)
|
||||
await processKillPromise
|
||||
assert.deepEqual(killedPids, [101])
|
||||
})
|
||||
})
|
||||
|
||||
describe('when closing the last window', () => {
|
||||
if (process.platform === 'linux' || process.platform === 'win32') {
|
||||
it('quits the application', async function () {
|
||||
it('quits the application', async () => {
|
||||
const atomApplication = buildAtomApplication()
|
||||
const window = atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')]))
|
||||
await focusWindow(window)
|
||||
window.close()
|
||||
await window.closedPromise
|
||||
assert(electron.app.hasQuitted())
|
||||
await atomApplication.lastBeforeQuitPromise
|
||||
assert(electron.app.didQuit())
|
||||
})
|
||||
} else if (process.platform === 'darwin') {
|
||||
it('leaves the application open', async function () {
|
||||
it('leaves the application open', async () => {
|
||||
const atomApplication = buildAtomApplication()
|
||||
const window = atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')]))
|
||||
await focusWindow(window)
|
||||
window.close()
|
||||
await window.closedPromise
|
||||
assert(!electron.app.hasQuitted())
|
||||
await timeoutPromise(1000)
|
||||
assert(!electron.app.didQuit())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('when adding or removing project folders', function () {
|
||||
it('stores the window state immediately', async function () {
|
||||
describe('when adding or removing project folders', () => {
|
||||
it('stores the window state immediately', async () => {
|
||||
const dirA = makeTempDir()
|
||||
const dirB = makeTempDir()
|
||||
|
||||
@@ -441,8 +546,8 @@ describe('AtomApplication', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('when opening atom:// URLs', function () {
|
||||
it('loads the urlMain file in a new window', async function () {
|
||||
describe('when opening atom:// URLs', () => {
|
||||
it('loads the urlMain file in a new window', async () => {
|
||||
const packagePath = path.join(__dirname, '..', 'fixtures', 'packages', 'package-with-url-main')
|
||||
const packagesDirPath = path.join(process.env.ATOM_HOME, 'packages')
|
||||
fs.mkdirSync(packagesDirPath)
|
||||
@@ -454,7 +559,7 @@ describe('AtomApplication', function () {
|
||||
let windows = atomApplication.launch(launchOptions)
|
||||
await windows[0].loadedPromise
|
||||
|
||||
let reached = await evalInWebContents(windows[0].browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
let reached = await evalInWebContents(windows[0].browserWindow.webContents, sendBackToMainProcess => {
|
||||
sendBackToMainProcess(global.reachedUrlMain)
|
||||
})
|
||||
assert.equal(reached, true);
|
||||
@@ -488,7 +593,7 @@ describe('AtomApplication', function () {
|
||||
})
|
||||
})
|
||||
|
||||
it('waits until all the windows have saved their state before quitting', async function () {
|
||||
it('waits until all the windows have saved their state before quitting', async () => {
|
||||
const dirAPath = makeTempDir("a")
|
||||
const dirBPath = makeTempDir("b")
|
||||
const atomApplication = buildAtomApplication()
|
||||
@@ -497,9 +602,12 @@ describe('AtomApplication', function () {
|
||||
const window2 = atomApplication.launch(parseCommandLine([path.join(dirBPath, 'file-b')]))
|
||||
await focusWindow(window2)
|
||||
electron.app.quit()
|
||||
assert(!electron.app.hasQuitted())
|
||||
await new Promise(process.nextTick)
|
||||
assert(!electron.app.didQuit())
|
||||
|
||||
await Promise.all([window1.lastPrepareToUnloadPromise, window2.lastPrepareToUnloadPromise])
|
||||
assert(electron.app.hasQuitted())
|
||||
await new Promise(process.nextTick)
|
||||
assert(electron.app.didQuit())
|
||||
})
|
||||
|
||||
it('prevents quitting if user cancels when prompted to save an item', async () => {
|
||||
@@ -507,30 +615,30 @@ describe('AtomApplication', function () {
|
||||
const window1 = atomApplication.launch(parseCommandLine([]))
|
||||
const window2 = atomApplication.launch(parseCommandLine([]))
|
||||
await Promise.all([window1.loadedPromise, window2.loadedPromise])
|
||||
await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.getActiveTextEditor().insertText('unsaved text')
|
||||
sendBackToMainProcess()
|
||||
})
|
||||
|
||||
// Choosing "Cancel"
|
||||
mockElectronShowMessageBox({choice: 1})
|
||||
mockElectronShowMessageBox({response: 1})
|
||||
electron.app.quit()
|
||||
await atomApplication.lastBeforeQuitPromise
|
||||
assert(!electron.app.hasQuitted())
|
||||
assert(!electron.app.didQuit())
|
||||
assert.equal(electron.app.quit.callCount, 1) // Ensure choosing "Cancel" doesn't try to quit the electron app more than once (regression)
|
||||
|
||||
// Choosing "Don't save"
|
||||
mockElectronShowMessageBox({choice: 2})
|
||||
mockElectronShowMessageBox({response: 2})
|
||||
electron.app.quit()
|
||||
await atomApplication.lastBeforeQuitPromise
|
||||
assert(electron.app.hasQuitted())
|
||||
assert(electron.app.didQuit())
|
||||
})
|
||||
|
||||
function buildAtomApplication () {
|
||||
const atomApplication = new AtomApplication({
|
||||
function buildAtomApplication (params = {}) {
|
||||
const atomApplication = new AtomApplication(Object.assign({
|
||||
resourcePath: ATOM_RESOURCE_PATH,
|
||||
atomHomeDirPath: process.env.ATOM_HOME
|
||||
})
|
||||
atomHomeDirPath: process.env.ATOM_HOME,
|
||||
}, params))
|
||||
atomApplicationsToDestroy.push(atomApplication)
|
||||
return atomApplication
|
||||
}
|
||||
@@ -542,40 +650,34 @@ describe('AtomApplication', function () {
|
||||
}
|
||||
|
||||
function mockElectronAppQuit () {
|
||||
let quitted = false
|
||||
electron.app.quit = function () {
|
||||
if (electron.app.quit.callCount) {
|
||||
electron.app.quit.callCount++
|
||||
} else {
|
||||
electron.app.quit.callCount = 1
|
||||
}
|
||||
let didQuit = false
|
||||
|
||||
let shouldQuit = true
|
||||
electron.app.emit('before-quit', {preventDefault: () => { shouldQuit = false }})
|
||||
if (shouldQuit) {
|
||||
quitted = true
|
||||
}
|
||||
}
|
||||
electron.app.hasQuitted = function () {
|
||||
return quitted
|
||||
electron.app.quit = function () {
|
||||
this.quit.callCount++
|
||||
let defaultPrevented = false
|
||||
this.emit('before-quit', {preventDefault() { defaultPrevented = true }})
|
||||
if (!defaultPrevented) didQuit = true
|
||||
}
|
||||
|
||||
electron.app.quit.callCount = 0
|
||||
|
||||
electron.app.didQuit = () => didQuit
|
||||
}
|
||||
|
||||
function mockElectronShowMessageBox ({choice}) {
|
||||
electron.dialog.showMessageBox = function () {
|
||||
return choice
|
||||
function mockElectronShowMessageBox ({response}) {
|
||||
electron.dialog.showMessageBox = (window, options, callback) => {
|
||||
callback(response)
|
||||
}
|
||||
}
|
||||
|
||||
function makeTempDir (name) {
|
||||
const temp = require('temp').track()
|
||||
return fs.realpathSync(temp.mkdirSync(name))
|
||||
}
|
||||
|
||||
let channelIdCounter = 0
|
||||
function evalInWebContents (webContents, source, ...args) {
|
||||
const channelId = 'eval-result-' + channelIdCounter++
|
||||
return new Promise(function (resolve) {
|
||||
return new Promise(resolve => {
|
||||
electron.ipcMain.on(channelId, receiveResult)
|
||||
|
||||
function receiveResult (event, result) {
|
||||
@@ -587,13 +689,13 @@ describe('AtomApplication', function () {
|
||||
function sendBackToMainProcess (result) {
|
||||
require('electron').ipcRenderer.send('${channelId}', result)
|
||||
}
|
||||
(${source})(sendBackToMainProcess)
|
||||
(${source})(sendBackToMainProcess, ${args.map(JSON.stringify).join(', ')})
|
||||
`)
|
||||
})
|
||||
}
|
||||
|
||||
function getTreeViewRootDirectories (atomWindow) {
|
||||
return evalInWebContents(atomWindow.browserWindow.webContents, function (sendBackToMainProcess) {
|
||||
return evalInWebContents(atomWindow.browserWindow.webContents, sendBackToMainProcess => {
|
||||
atom.workspace.getLeftDock().observeActivePaneItem((treeView) => {
|
||||
if (treeView) {
|
||||
sendBackToMainProcess(
|
||||
@@ -607,8 +709,8 @@ describe('AtomApplication', function () {
|
||||
}
|
||||
|
||||
function clearElectronSession () {
|
||||
return new Promise(function (resolve) {
|
||||
electron.session.defaultSession.clearStorageData(function () {
|
||||
return new Promise(resolve => {
|
||||
electron.session.defaultSession.clearStorageData(() => {
|
||||
// Resolve promise on next tick, otherwise the process stalls. This
|
||||
// might be a bug in Electron, but it's probably fixed on the newer
|
||||
// versions.
|
||||
|
||||
@@ -1030,6 +1030,13 @@ describe('PackageManager', () => {
|
||||
expect(atom.grammars.selectGrammar('a.alot').name).toBe('Alot')
|
||||
expect(atom.grammars.selectGrammar('a.alittle').name).toBe('Alittle')
|
||||
})
|
||||
|
||||
it('loads any tree-sitter grammars defined in the package', async () => {
|
||||
await atom.packages.activatePackage('package-with-tree-sitter-grammar')
|
||||
const grammar = atom.grammars.selectGrammar('test.somelang')
|
||||
expect(grammar.name).toBe('Some Language')
|
||||
expect(grammar.languageModule.isFakeTreeSitterParser).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('scoped-property loading', () => {
|
||||
|
||||
@@ -5,7 +5,7 @@ describe('PaneContainer', () => {
|
||||
let confirm, params
|
||||
|
||||
beforeEach(() => {
|
||||
confirm = spyOn(atom.applicationDelegate, 'confirm').andReturn(0)
|
||||
confirm = spyOn(atom.applicationDelegate, 'confirm').andCallFake((options, callback) => callback(0))
|
||||
params = {
|
||||
location: 'center',
|
||||
config: atom.config,
|
||||
@@ -280,14 +280,14 @@ describe('PaneContainer', () => {
|
||||
})
|
||||
|
||||
it('returns true if the user saves all modified files when prompted', async () => {
|
||||
confirm.andReturn(0)
|
||||
confirm.andCallFake((options, callback) => callback(0))
|
||||
const saved = await container.confirmClose()
|
||||
expect(confirm).toHaveBeenCalled()
|
||||
expect(saved).toBeTruthy()
|
||||
})
|
||||
|
||||
it('returns false if the user cancels saving any modified file', async () => {
|
||||
confirm.andReturn(1)
|
||||
confirm.andCallFake((options, callback) => callback(1))
|
||||
const saved = await container.confirmClose()
|
||||
expect(confirm).toHaveBeenCalled()
|
||||
expect(saved).toBeFalsy()
|
||||
|
||||
@@ -3,7 +3,7 @@ const {Emitter} = require('event-kit')
|
||||
const Grim = require('grim')
|
||||
const Pane = require('../src/pane')
|
||||
const PaneContainer = require('../src/pane-container')
|
||||
const {it, fit, ffit, fffit, beforeEach, timeoutPromise} = require('./async-spec-helpers')
|
||||
const {it, fit, ffit, fffit, beforeEach, conditionPromise, timeoutPromise} = require('./async-spec-helpers')
|
||||
|
||||
describe('Pane', () => {
|
||||
let confirm, showSaveDialog, deserializerDisposable
|
||||
@@ -564,7 +564,7 @@ describe('Pane', () => {
|
||||
describe('when the item has a uri', () => {
|
||||
it('saves the item before destroying it', async () => {
|
||||
itemURI = 'test'
|
||||
confirm.andReturn(0)
|
||||
confirm.andCallFake((options, callback) => callback(0))
|
||||
|
||||
const success = await pane.destroyItem(item1)
|
||||
expect(item1.save).toHaveBeenCalled()
|
||||
@@ -576,13 +576,17 @@ describe('Pane', () => {
|
||||
|
||||
describe('when the item has no uri', () => {
|
||||
it('presents a save-as dialog, then saves the item with the given uri before removing and destroying it', async () => {
|
||||
jasmine.useRealClock()
|
||||
|
||||
itemURI = null
|
||||
|
||||
showSaveDialog.andReturn('/selected/path')
|
||||
confirm.andReturn(0)
|
||||
showSaveDialog.andCallFake((options, callback) => callback('/selected/path'))
|
||||
confirm.andCallFake((options, callback) => callback(0))
|
||||
|
||||
const success = await pane.destroyItem(item1)
|
||||
expect(showSaveDialog).toHaveBeenCalledWith({})
|
||||
expect(showSaveDialog.mostRecentCall.args[0]).toEqual({})
|
||||
|
||||
await conditionPromise(() => item1.saveAs.callCount === 1)
|
||||
expect(item1.saveAs).toHaveBeenCalledWith('/selected/path')
|
||||
expect(pane.getItems().includes(item1)).toBe(false)
|
||||
expect(item1.isDestroyed()).toBe(true)
|
||||
@@ -593,7 +597,7 @@ describe('Pane', () => {
|
||||
|
||||
describe("if the [Don't Save] option is selected", () => {
|
||||
it('removes and destroys the item without saving it', async () => {
|
||||
confirm.andReturn(2)
|
||||
confirm.andCallFake((options, callback) => callback(2))
|
||||
|
||||
const success = await pane.destroyItem(item1)
|
||||
expect(item1.save).not.toHaveBeenCalled()
|
||||
@@ -605,7 +609,7 @@ describe('Pane', () => {
|
||||
|
||||
describe('if the [Cancel] option is selected', () => {
|
||||
it('does not save, remove, or destroy the item', async () => {
|
||||
confirm.andReturn(1)
|
||||
confirm.andCallFake((options, callback) => callback(1))
|
||||
|
||||
const success = await pane.destroyItem(item1)
|
||||
expect(item1.save).not.toHaveBeenCalled()
|
||||
@@ -735,7 +739,7 @@ describe('Pane', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
pane = new Pane(paneParams({items: [new Item('A')]}))
|
||||
showSaveDialog.andReturn('/selected/path')
|
||||
showSaveDialog.andCallFake((options, callback) => callback('/selected/path'))
|
||||
})
|
||||
|
||||
describe('when the active item has a uri', () => {
|
||||
@@ -764,7 +768,7 @@ describe('Pane', () => {
|
||||
it('opens a save dialog and saves the current item as the selected path', async () => {
|
||||
pane.getActiveItem().saveAs = jasmine.createSpy('saveAs')
|
||||
await pane.saveActiveItem()
|
||||
expect(showSaveDialog).toHaveBeenCalledWith({})
|
||||
expect(showSaveDialog.mostRecentCall.args[0]).toEqual({})
|
||||
expect(pane.getActiveItem().saveAs).toHaveBeenCalledWith('/selected/path')
|
||||
})
|
||||
})
|
||||
@@ -779,7 +783,7 @@ describe('Pane', () => {
|
||||
|
||||
it('does nothing if the user cancels choosing a path', async () => {
|
||||
pane.getActiveItem().saveAs = jasmine.createSpy('saveAs')
|
||||
showSaveDialog.andReturn(undefined)
|
||||
showSaveDialog.andCallFake((options, callback) => callback(undefined))
|
||||
await pane.saveActiveItem()
|
||||
expect(pane.getActiveItem().saveAs).not.toHaveBeenCalled()
|
||||
})
|
||||
@@ -835,15 +839,19 @@ describe('Pane', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
pane = new Pane(paneParams({items: [new Item('A')]}))
|
||||
showSaveDialog.andReturn('/selected/path')
|
||||
showSaveDialog.andCallFake((options, callback) => callback('/selected/path'))
|
||||
})
|
||||
|
||||
describe('when the current item has a saveAs method', () => {
|
||||
it('opens the save dialog and calls saveAs on the item with the selected path', () => {
|
||||
it('opens the save dialog and calls saveAs on the item with the selected path', async () => {
|
||||
jasmine.useRealClock()
|
||||
|
||||
pane.getActiveItem().path = __filename
|
||||
pane.getActiveItem().saveAs = jasmine.createSpy('saveAs')
|
||||
pane.saveActiveItemAs()
|
||||
expect(showSaveDialog).toHaveBeenCalledWith({defaultPath: __filename})
|
||||
expect(showSaveDialog.mostRecentCall.args[0]).toEqual({defaultPath: __filename})
|
||||
|
||||
await conditionPromise(() => pane.getActiveItem().saveAs.callCount === 1)
|
||||
expect(pane.getActiveItem().saveAs).toHaveBeenCalledWith('/selected/path')
|
||||
})
|
||||
})
|
||||
@@ -1210,7 +1218,7 @@ describe('Pane', () => {
|
||||
item1.getURI = () => '/test/path'
|
||||
item1.save = jasmine.createSpy('save')
|
||||
|
||||
confirm.andReturn(0)
|
||||
confirm.andCallFake((options, callback) => callback(0))
|
||||
await pane.close()
|
||||
expect(confirm).toHaveBeenCalled()
|
||||
expect(item1.save).toHaveBeenCalled()
|
||||
@@ -1225,7 +1233,7 @@ describe('Pane', () => {
|
||||
item1.getURI = () => '/test/path'
|
||||
item1.save = jasmine.createSpy('save')
|
||||
|
||||
confirm.andReturn(1)
|
||||
confirm.andCallFake((options, callback) => callback(1))
|
||||
|
||||
await pane.close()
|
||||
expect(confirm).toHaveBeenCalled()
|
||||
@@ -1240,8 +1248,8 @@ describe('Pane', () => {
|
||||
item1.shouldPromptToSave = () => true
|
||||
item1.saveAs = jasmine.createSpy('saveAs')
|
||||
|
||||
confirm.andReturn(0)
|
||||
showSaveDialog.andReturn(undefined)
|
||||
confirm.andCallFake((options, callback) => callback(0))
|
||||
showSaveDialog.andCallFake((options, callback) => callback(undefined))
|
||||
|
||||
await pane.close()
|
||||
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
||||
@@ -1270,12 +1278,12 @@ describe('Pane', () => {
|
||||
|
||||
it('does not destroy the pane if save fails and user clicks cancel', async () => {
|
||||
let confirmations = 0
|
||||
confirm.andCallFake(() => {
|
||||
confirm.andCallFake((options, callback) => {
|
||||
confirmations++
|
||||
if (confirmations === 1) {
|
||||
return 0 // click save
|
||||
callback(0) // click save
|
||||
} else {
|
||||
return 1
|
||||
callback(1)
|
||||
}
|
||||
}) // click cancel
|
||||
|
||||
@@ -1290,17 +1298,17 @@ describe('Pane', () => {
|
||||
item1.saveAs = jasmine.createSpy('saveAs').andReturn(true)
|
||||
|
||||
let confirmations = 0
|
||||
confirm.andCallFake(() => {
|
||||
confirm.andCallFake((options, callback) => {
|
||||
confirmations++
|
||||
return 0
|
||||
callback(0)
|
||||
}) // save and then save as
|
||||
|
||||
showSaveDialog.andReturn('new/path')
|
||||
showSaveDialog.andCallFake((options, callback) => callback('new/path'))
|
||||
|
||||
await pane.close()
|
||||
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
||||
expect(confirmations).toBe(2)
|
||||
expect(atom.applicationDelegate.showSaveDialog).toHaveBeenCalledWith({})
|
||||
expect(atom.applicationDelegate.showSaveDialog.mostRecentCall.args[0]).toEqual({})
|
||||
expect(item1.save).toHaveBeenCalled()
|
||||
expect(item1.saveAs).toHaveBeenCalled()
|
||||
expect(pane.isDestroyed()).toBe(true)
|
||||
@@ -1315,20 +1323,21 @@ describe('Pane', () => {
|
||||
})
|
||||
|
||||
let confirmations = 0
|
||||
confirm.andCallFake(() => {
|
||||
confirm.andCallFake((options, callback) => {
|
||||
confirmations++
|
||||
if (confirmations < 3) {
|
||||
return 0 // save, save as, save as
|
||||
callback(0) // save, save as, save as
|
||||
} else {
|
||||
callback(2) // don't save
|
||||
}
|
||||
return 2
|
||||
}) // don't save
|
||||
})
|
||||
|
||||
showSaveDialog.andReturn('new/path')
|
||||
showSaveDialog.andCallFake((options, callback) => callback('new/path'))
|
||||
|
||||
await pane.close()
|
||||
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
||||
expect(confirmations).toBe(3)
|
||||
expect(atom.applicationDelegate.showSaveDialog).toHaveBeenCalledWith({})
|
||||
expect(atom.applicationDelegate.showSaveDialog.mostRecentCall.args[0]).toEqual({})
|
||||
expect(item1.save).toHaveBeenCalled()
|
||||
expect(item1.saveAs).toHaveBeenCalled()
|
||||
expect(pane.isDestroyed()).toBe(true)
|
||||
|
||||
@@ -111,7 +111,8 @@ beforeEach ->
|
||||
new CompositeDisposable(
|
||||
@emitter.on("did-tokenize", callback),
|
||||
@onDidChangeGrammar =>
|
||||
if @buffer.getLanguageMode().tokenizeInBackground.originalValue
|
||||
languageMode = @buffer.getLanguageMode()
|
||||
if languageMode.tokenizeInBackground?.originalValue
|
||||
callback()
|
||||
)
|
||||
|
||||
|
||||
77
spec/syntax-scope-map-spec.js
Normal file
77
spec/syntax-scope-map-spec.js
Normal file
@@ -0,0 +1,77 @@
|
||||
const SyntaxScopeMap = require('../src/syntax-scope-map')
|
||||
|
||||
describe('SyntaxScopeMap', () => {
|
||||
it('can match immediate child selectors', () => {
|
||||
const map = new SyntaxScopeMap({
|
||||
'a > b > c': 'x',
|
||||
'b > c': 'y',
|
||||
'c': 'z'
|
||||
})
|
||||
|
||||
expect(map.get(['a', 'b', 'c'], [0, 0, 0])).toBe('x')
|
||||
expect(map.get(['d', 'b', 'c'], [0, 0, 0])).toBe('y')
|
||||
expect(map.get(['d', 'e', 'c'], [0, 0, 0])).toBe('z')
|
||||
expect(map.get(['e', 'c'], [0, 0, 0])).toBe('z')
|
||||
expect(map.get(['c'], [0, 0, 0])).toBe('z')
|
||||
expect(map.get(['d'], [0, 0, 0])).toBe(undefined)
|
||||
})
|
||||
|
||||
it('can match :nth-child pseudo-selectors on leaves', () => {
|
||||
const map = new SyntaxScopeMap({
|
||||
'a > b': 'w',
|
||||
'a > b:nth-child(1)': 'x',
|
||||
'b': 'y',
|
||||
'b:nth-child(2)': 'z'
|
||||
})
|
||||
|
||||
expect(map.get(['a', 'b'], [0, 0])).toBe('w')
|
||||
expect(map.get(['a', 'b'], [0, 1])).toBe('x')
|
||||
expect(map.get(['a', 'b'], [0, 2])).toBe('w')
|
||||
expect(map.get(['b'], [0])).toBe('y')
|
||||
expect(map.get(['b'], [1])).toBe('y')
|
||||
expect(map.get(['b'], [2])).toBe('z')
|
||||
})
|
||||
|
||||
it('can match :nth-child pseudo-selectors on interior nodes', () => {
|
||||
const map = new SyntaxScopeMap({
|
||||
'b:nth-child(1) > c': 'w',
|
||||
'a > b > c': 'x',
|
||||
'a > b:nth-child(2) > c': 'y'
|
||||
})
|
||||
|
||||
expect(map.get(['b', 'c'], [0, 0])).toBe(undefined)
|
||||
expect(map.get(['b', 'c'], [1, 0])).toBe('w')
|
||||
expect(map.get(['a', 'b', 'c'], [1, 0, 0])).toBe('x')
|
||||
expect(map.get(['a', 'b', 'c'], [1, 2, 0])).toBe('y')
|
||||
})
|
||||
|
||||
it('allows anonymous tokens to be referred to by their string value', () => {
|
||||
const map = new SyntaxScopeMap({
|
||||
'"b"': 'w',
|
||||
'a > "b"': 'x',
|
||||
'a > "b":nth-child(1)': 'y'
|
||||
})
|
||||
|
||||
expect(map.get(['b'], [0], true)).toBe(undefined)
|
||||
expect(map.get(['b'], [0], false)).toBe('w')
|
||||
expect(map.get(['a', 'b'], [0, 0], false)).toBe('x')
|
||||
expect(map.get(['a', 'b'], [0, 1], false)).toBe('y')
|
||||
})
|
||||
|
||||
it('supports the wildcard selector', () => {
|
||||
const map = new SyntaxScopeMap({
|
||||
'*': 'w',
|
||||
'a > *': 'x',
|
||||
'a > *:nth-child(1)': 'y',
|
||||
'a > *:nth-child(1) > b': 'z'
|
||||
})
|
||||
|
||||
expect(map.get(['b'], [0])).toBe('w')
|
||||
expect(map.get(['c'], [0])).toBe('w')
|
||||
expect(map.get(['a', 'b'], [0, 0])).toBe('x')
|
||||
expect(map.get(['a', 'b'], [0, 1])).toBe('y')
|
||||
expect(map.get(['a', 'c'], [0, 1])).toBe('y')
|
||||
expect(map.get(['a', 'c', 'b'], [0, 1, 1])).toBe('z')
|
||||
expect(map.get(['a', 'c', 'b'], [0, 2, 1])).toBe('w')
|
||||
})
|
||||
})
|
||||
@@ -263,13 +263,13 @@ describe('TextEditorComponent', () => {
|
||||
it('keeps the number of tiles stable when the visible line count changes during vertical scrolling', async () => {
|
||||
const {component, element, editor} = buildComponent({rowsPerTile: 3, autoHeight: false})
|
||||
await setEditorHeightInLines(component, 5.5)
|
||||
expect(component.refs.lineTiles.children.length).toBe(3 + 1) // account for cursors container
|
||||
expect(component.refs.lineTiles.children.length).toBe(3 + 2) // account for cursors and highlights containers
|
||||
|
||||
await setScrollTop(component, 0.5 * component.getLineHeight())
|
||||
expect(component.refs.lineTiles.children.length).toBe(3 + 1) // account for cursors container
|
||||
expect(component.refs.lineTiles.children.length).toBe(3 + 2) // account for cursors and highlights containers
|
||||
|
||||
await setScrollTop(component, 1 * component.getLineHeight())
|
||||
expect(component.refs.lineTiles.children.length).toBe(3 + 1) // account for cursors container
|
||||
expect(component.refs.lineTiles.children.length).toBe(3 + 2) // account for cursors and highlights containers
|
||||
})
|
||||
|
||||
it('recycles tiles on resize', async () => {
|
||||
|
||||
@@ -89,6 +89,22 @@ describe('TextEditorElement', () => {
|
||||
expect(element.getModel().getText()).toBe('testing')
|
||||
})
|
||||
|
||||
describe('tabIndex', () => {
|
||||
it('uses a default value of -1', () => {
|
||||
jasmineContent.innerHTML = '<atom-text-editor />'
|
||||
const element = jasmineContent.firstChild
|
||||
expect(element.tabIndex).toBe(-1)
|
||||
expect(element.querySelector('input').tabIndex).toBe(-1)
|
||||
})
|
||||
|
||||
it('uses the custom value when given', () => {
|
||||
jasmineContent.innerHTML = '<atom-text-editor tabIndex="42" />'
|
||||
const element = jasmineContent.firstChild
|
||||
expect(element.tabIndex).toBe(-1)
|
||||
expect(element.querySelector('input').tabIndex).toBe(42)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the model is assigned', () =>
|
||||
it("adds the 'mini' attribute if .isMini() returns true on the model", async () => {
|
||||
const element = buildTextEditorElement()
|
||||
|
||||
@@ -2376,6 +2376,19 @@ describe('TextEditor', () => {
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('does not create a new selection if it would be fully contained within another selection', () => {
|
||||
editor.setText('abc\ndef\nghi\njkl\nmno')
|
||||
editor.setCursorBufferPosition([0, 1])
|
||||
|
||||
let addedSelectionCount = 0
|
||||
editor.onDidAddSelection(() => { addedSelectionCount++ })
|
||||
|
||||
editor.addSelectionBelow()
|
||||
editor.addSelectionBelow()
|
||||
editor.addSelectionBelow()
|
||||
expect(addedSelectionCount).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.addSelectionAbove()', () => {
|
||||
@@ -2498,6 +2511,19 @@ describe('TextEditor', () => {
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('does not create a new selection if it would be fully contained within another selection', () => {
|
||||
editor.setText('abc\ndef\nghi\njkl\nmno')
|
||||
editor.setCursorBufferPosition([4, 1])
|
||||
|
||||
let addedSelectionCount = 0
|
||||
editor.onDidAddSelection(() => { addedSelectionCount++ })
|
||||
|
||||
editor.addSelectionAbove()
|
||||
editor.addSelectionAbove()
|
||||
editor.addSelectionAbove()
|
||||
expect(addedSelectionCount).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.splitSelectionsIntoLines()', () => {
|
||||
@@ -5401,6 +5427,34 @@ describe('TextEditor', () => {
|
||||
expect(buffer.getLineCount()).toBe(count - 2)
|
||||
})
|
||||
|
||||
it("restores cursor position for multiple cursors", () => {
|
||||
const line = '0123456789'.repeat(8)
|
||||
editor.setText((line + '\n').repeat(5))
|
||||
editor.setCursorScreenPosition([0, 5])
|
||||
editor.addCursorAtScreenPosition([2, 8])
|
||||
editor.deleteLine()
|
||||
|
||||
const cursors = editor.getCursors()
|
||||
expect(cursors.length).toBe(2)
|
||||
expect(cursors[0].getScreenPosition()).toEqual([0, 5])
|
||||
expect(cursors[1].getScreenPosition()).toEqual([1, 8])
|
||||
})
|
||||
|
||||
it("restores cursor position for multiple selections", () => {
|
||||
const line = '0123456789'.repeat(8)
|
||||
editor.setText((line + '\n').repeat(5))
|
||||
editor.setSelectedBufferRanges([
|
||||
[[0, 5], [0, 8]],
|
||||
[[2, 4], [2, 15]]
|
||||
])
|
||||
editor.deleteLine()
|
||||
|
||||
const cursors = editor.getCursors()
|
||||
expect(cursors.length).toBe(2)
|
||||
expect(cursors[0].getScreenPosition()).toEqual([0, 5])
|
||||
expect(cursors[1].getScreenPosition()).toEqual([1, 4])
|
||||
})
|
||||
|
||||
it('deletes a line only once when multiple selections are on the same line', () => {
|
||||
const line1 = buffer.lineForRow(1)
|
||||
const count = buffer.getLineCount()
|
||||
|
||||
560
spec/tree-sitter-language-mode-spec.js
Normal file
560
spec/tree-sitter-language-mode-spec.js
Normal file
@@ -0,0 +1,560 @@
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers')
|
||||
|
||||
const dedent = require('dedent')
|
||||
const TextBuffer = require('text-buffer')
|
||||
const {Point} = TextBuffer
|
||||
const TextEditor = require('../src/text-editor')
|
||||
const TreeSitterGrammar = require('../src/tree-sitter-grammar')
|
||||
const TreeSitterLanguageMode = require('../src/tree-sitter-language-mode')
|
||||
|
||||
const cGrammarPath = require.resolve('language-c/grammars/tree-sitter-c.cson')
|
||||
const pythonGrammarPath = require.resolve('language-python/grammars/tree-sitter-python.cson')
|
||||
const jsGrammarPath = require.resolve('language-javascript/grammars/tree-sitter-javascript.cson')
|
||||
|
||||
describe('TreeSitterLanguageMode', () => {
|
||||
let editor, buffer
|
||||
|
||||
beforeEach(async () => {
|
||||
editor = await atom.workspace.open('')
|
||||
buffer = editor.getBuffer()
|
||||
})
|
||||
|
||||
describe('highlighting', () => {
|
||||
it('applies the most specific scope mapping to each node in the syntax tree', () => {
|
||||
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
||||
parser: 'tree-sitter-javascript',
|
||||
scopes: {
|
||||
'program': 'source',
|
||||
'call_expression > identifier': 'function',
|
||||
'property_identifier': 'property',
|
||||
'call_expression > member_expression > property_identifier': 'method'
|
||||
}
|
||||
})
|
||||
|
||||
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
||||
buffer.setText('aa.bbb = cc(d.eee());')
|
||||
expectTokensToEqual(editor, [[
|
||||
{text: 'aa.', scopes: ['source']},
|
||||
{text: 'bbb', scopes: ['source', 'property']},
|
||||
{text: ' = ', scopes: ['source']},
|
||||
{text: 'cc', scopes: ['source', 'function']},
|
||||
{text: '(d.', scopes: ['source']},
|
||||
{text: 'eee', scopes: ['source', 'method']},
|
||||
{text: '());', scopes: ['source']}
|
||||
]])
|
||||
})
|
||||
|
||||
it('can start or end multiple scopes at the same position', () => {
|
||||
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
||||
parser: 'tree-sitter-javascript',
|
||||
scopes: {
|
||||
'program': 'source',
|
||||
'call_expression': 'call',
|
||||
'member_expression': 'member',
|
||||
'identifier': 'variable',
|
||||
'"("': 'open-paren',
|
||||
'")"': 'close-paren',
|
||||
}
|
||||
})
|
||||
|
||||
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
||||
buffer.setText('a = bb.ccc();')
|
||||
expectTokensToEqual(editor, [[
|
||||
{text: 'a', scopes: ['source', 'variable']},
|
||||
{text: ' = ', scopes: ['source']},
|
||||
{text: 'bb', scopes: ['source', 'call', 'member', 'variable']},
|
||||
{text: '.ccc', scopes: ['source', 'call', 'member']},
|
||||
{text: '(', scopes: ['source', 'call', 'open-paren']},
|
||||
{text: ')', scopes: ['source', 'call', 'close-paren']},
|
||||
{text: ';', scopes: ['source']}
|
||||
]])
|
||||
})
|
||||
|
||||
it('can resume highlighting on a line that starts with whitespace', () => {
|
||||
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
||||
parser: 'tree-sitter-javascript',
|
||||
scopes: {
|
||||
'call_expression > member_expression > property_identifier': 'function',
|
||||
'property_identifier': 'member',
|
||||
'identifier': 'variable'
|
||||
}
|
||||
})
|
||||
|
||||
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
||||
buffer.setText('a\n .b();')
|
||||
expectTokensToEqual(editor, [
|
||||
[
|
||||
{text: 'a', scopes: ['variable']},
|
||||
],
|
||||
[
|
||||
{text: ' ', scopes: ['whitespace']},
|
||||
{text: '.', scopes: []},
|
||||
{text: 'b', scopes: ['function']},
|
||||
{text: '();', scopes: []}
|
||||
]
|
||||
])
|
||||
})
|
||||
|
||||
it('correctly skips over tokens with zero size', () => {
|
||||
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
||||
parser: 'tree-sitter-c',
|
||||
scopes: {
|
||||
'primitive_type': 'type',
|
||||
'identifier': 'variable',
|
||||
}
|
||||
})
|
||||
|
||||
const languageMode = new TreeSitterLanguageMode({buffer, grammar})
|
||||
buffer.setLanguageMode(languageMode)
|
||||
buffer.setText('int main() {\n int a\n int b;\n}');
|
||||
|
||||
editor.screenLineForScreenRow(0)
|
||||
expect(
|
||||
languageMode.document.rootNode.descendantForPosition(Point(1, 2), Point(1, 6)).toString()
|
||||
).toBe('(declaration (primitive_type) (identifier) (MISSING))')
|
||||
|
||||
expectTokensToEqual(editor, [
|
||||
[
|
||||
{text: 'int', scopes: ['type']},
|
||||
{text: ' ', scopes: []},
|
||||
{text: 'main', scopes: ['variable']},
|
||||
{text: '() {', scopes: []}
|
||||
],
|
||||
[
|
||||
{text: ' ', scopes: ['whitespace']},
|
||||
{text: 'int', scopes: ['type']},
|
||||
{text: ' ', scopes: []},
|
||||
{text: 'a', scopes: ['variable']}
|
||||
],
|
||||
[
|
||||
{text: ' ', scopes: ['whitespace']},
|
||||
{text: 'int', scopes: ['type']},
|
||||
{text: ' ', scopes: []},
|
||||
{text: 'b', scopes: ['variable']},
|
||||
{text: ';', scopes: []}
|
||||
],
|
||||
[
|
||||
{text: '}', scopes: []}
|
||||
]
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('folding', () => {
|
||||
beforeEach(() => {
|
||||
editor.displayLayer.reset({foldCharacter: '…'})
|
||||
})
|
||||
|
||||
it('can fold nodes that start and end with specified tokens', () => {
|
||||
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
||||
parser: 'tree-sitter-javascript',
|
||||
folds: [
|
||||
{
|
||||
start: {type: '{', index: 0},
|
||||
end: {type: '}', index: -1}
|
||||
},
|
||||
{
|
||||
start: {type: '(', index: 0},
|
||||
end: {type: ')', index: -1}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
||||
buffer.setText(dedent `
|
||||
module.exports =
|
||||
class A {
|
||||
getB (c,
|
||||
d,
|
||||
e) {
|
||||
return this.f(g)
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
editor.screenLineForScreenRow(0)
|
||||
|
||||
expect(editor.isFoldableAtBufferRow(0)).toBe(false)
|
||||
expect(editor.isFoldableAtBufferRow(1)).toBe(true)
|
||||
expect(editor.isFoldableAtBufferRow(2)).toBe(true)
|
||||
expect(editor.isFoldableAtBufferRow(3)).toBe(false)
|
||||
expect(editor.isFoldableAtBufferRow(4)).toBe(true)
|
||||
expect(editor.isFoldableAtBufferRow(5)).toBe(false)
|
||||
|
||||
editor.foldBufferRow(2)
|
||||
expect(getDisplayText(editor)).toBe(dedent `
|
||||
module.exports =
|
||||
class A {
|
||||
getB (…) {
|
||||
return this.f(g)
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
editor.foldBufferRow(4)
|
||||
expect(getDisplayText(editor)).toBe(dedent `
|
||||
module.exports =
|
||||
class A {
|
||||
getB (…) {…}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
it('can fold nodes of specified types', () => {
|
||||
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
||||
parser: 'tree-sitter-javascript',
|
||||
folds: [
|
||||
// Start the fold after the first child (the opening tag) and end it at the last child
|
||||
// (the closing tag).
|
||||
{
|
||||
type: 'jsx_element',
|
||||
start: {index: 0},
|
||||
end: {index: -1}
|
||||
},
|
||||
|
||||
// End the fold at the *second* to last child of the self-closing tag: the `/`.
|
||||
{
|
||||
type: 'jsx_self_closing_element',
|
||||
start: {index: 1},
|
||||
end: {index: -2}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
||||
buffer.setText(dedent `
|
||||
const element1 = <Element
|
||||
className='submit'
|
||||
id='something' />
|
||||
|
||||
const element2 = <Element>
|
||||
<span>hello</span>
|
||||
<span>world</span>
|
||||
</Element>
|
||||
`)
|
||||
|
||||
editor.screenLineForScreenRow(0)
|
||||
|
||||
expect(editor.isFoldableAtBufferRow(0)).toBe(true)
|
||||
expect(editor.isFoldableAtBufferRow(1)).toBe(false)
|
||||
expect(editor.isFoldableAtBufferRow(2)).toBe(false)
|
||||
expect(editor.isFoldableAtBufferRow(3)).toBe(false)
|
||||
expect(editor.isFoldableAtBufferRow(4)).toBe(true)
|
||||
expect(editor.isFoldableAtBufferRow(5)).toBe(false)
|
||||
|
||||
editor.foldBufferRow(0)
|
||||
expect(getDisplayText(editor)).toBe(dedent `
|
||||
const element1 = <Element…/>
|
||||
|
||||
const element2 = <Element>
|
||||
<span>hello</span>
|
||||
<span>world</span>
|
||||
</Element>
|
||||
`)
|
||||
|
||||
editor.foldBufferRow(4)
|
||||
expect(getDisplayText(editor)).toBe(dedent `
|
||||
const element1 = <Element…/>
|
||||
|
||||
const element2 = <Element>…
|
||||
</Element>
|
||||
`)
|
||||
})
|
||||
|
||||
it('can fold entire nodes when no start or end parameters are specified', () => {
|
||||
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
||||
parser: 'tree-sitter-javascript',
|
||||
folds: [
|
||||
// By default, for a node with no children, folds are started at the *end* of the first
|
||||
// line of a node, and ended at the *beginning* of the last line.
|
||||
{type: 'comment'}
|
||||
]
|
||||
})
|
||||
|
||||
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
||||
buffer.setText(dedent `
|
||||
/**
|
||||
* Important
|
||||
*/
|
||||
const x = 1 /*
|
||||
Also important
|
||||
*/
|
||||
`)
|
||||
|
||||
editor.screenLineForScreenRow(0)
|
||||
|
||||
expect(editor.isFoldableAtBufferRow(0)).toBe(true)
|
||||
expect(editor.isFoldableAtBufferRow(1)).toBe(false)
|
||||
expect(editor.isFoldableAtBufferRow(2)).toBe(false)
|
||||
expect(editor.isFoldableAtBufferRow(3)).toBe(true)
|
||||
expect(editor.isFoldableAtBufferRow(4)).toBe(false)
|
||||
|
||||
editor.foldBufferRow(0)
|
||||
expect(getDisplayText(editor)).toBe(dedent `
|
||||
/**… */
|
||||
const x = 1 /*
|
||||
Also important
|
||||
*/
|
||||
`)
|
||||
|
||||
editor.foldBufferRow(3)
|
||||
expect(getDisplayText(editor)).toBe(dedent `
|
||||
/**… */
|
||||
const x = 1 /*…*/
|
||||
`)
|
||||
})
|
||||
|
||||
it('tries each folding strategy for a given node in the order specified', () => {
|
||||
const grammar = new TreeSitterGrammar(atom.grammars, cGrammarPath, {
|
||||
parser: 'tree-sitter-c',
|
||||
folds: [
|
||||
// If the #ifdef has an `#else` clause, then end the fold there.
|
||||
{
|
||||
type: ['preproc_ifdef', 'preproc_elif'],
|
||||
start: {index: 1},
|
||||
end: {type: ['preproc_else', 'preproc_elif']}
|
||||
},
|
||||
|
||||
// Otherwise, end the fold at the last child - the `#endif`.
|
||||
{
|
||||
type: 'preproc_ifdef',
|
||||
start: {index: 1},
|
||||
end: {index: -1}
|
||||
},
|
||||
|
||||
// When folding an `#else` clause, the fold extends to the end of the clause.
|
||||
{
|
||||
type: 'preproc_else',
|
||||
start: {index: 0}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
||||
|
||||
buffer.setText(dedent `
|
||||
#ifndef FOO_H_
|
||||
#define FOO_H_
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <windows.h>
|
||||
const char *path_separator = "\\";
|
||||
|
||||
#elif defined MACOS
|
||||
|
||||
#include <carbon.h>
|
||||
const char *path_separator = "/";
|
||||
|
||||
#else
|
||||
|
||||
#include <dirent.h>
|
||||
const char *path_separator = "/";
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
`)
|
||||
|
||||
editor.screenLineForScreenRow(0)
|
||||
|
||||
editor.foldBufferRow(3)
|
||||
expect(getDisplayText(editor)).toBe(dedent `
|
||||
#ifndef FOO_H_
|
||||
#define FOO_H_
|
||||
|
||||
#ifdef _WIN32…
|
||||
#elif defined MACOS
|
||||
|
||||
#include <carbon.h>
|
||||
const char *path_separator = "/";
|
||||
|
||||
#else
|
||||
|
||||
#include <dirent.h>
|
||||
const char *path_separator = "/";
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
`)
|
||||
|
||||
editor.foldBufferRow(8)
|
||||
expect(getDisplayText(editor)).toBe(dedent `
|
||||
#ifndef FOO_H_
|
||||
#define FOO_H_
|
||||
|
||||
#ifdef _WIN32…
|
||||
#elif defined MACOS…
|
||||
#else
|
||||
|
||||
#include <dirent.h>
|
||||
const char *path_separator = "/";
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
`)
|
||||
|
||||
editor.foldBufferRow(0)
|
||||
expect(getDisplayText(editor)).toBe(dedent `
|
||||
#ifndef FOO_H_…
|
||||
#endif
|
||||
`)
|
||||
|
||||
editor.foldAllAtIndentLevel(1)
|
||||
expect(getDisplayText(editor)).toBe(dedent `
|
||||
#ifndef FOO_H_
|
||||
#define FOO_H_
|
||||
|
||||
#ifdef _WIN32…
|
||||
#elif defined MACOS…
|
||||
#else…
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
`)
|
||||
})
|
||||
|
||||
describe('when folding a node that ends with a line break', () => {
|
||||
it('ends the fold at the end of the previous line', () => {
|
||||
const grammar = new TreeSitterGrammar(atom.grammars, pythonGrammarPath, {
|
||||
parser: 'tree-sitter-python',
|
||||
folds: [
|
||||
{
|
||||
type: 'function_definition',
|
||||
start: {type: ':'}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
||||
|
||||
buffer.setText(dedent `
|
||||
def ab():
|
||||
print 'a'
|
||||
print 'b'
|
||||
|
||||
def cd():
|
||||
print 'c'
|
||||
print 'd'
|
||||
`)
|
||||
|
||||
editor.screenLineForScreenRow(0)
|
||||
|
||||
editor.foldBufferRow(0)
|
||||
expect(getDisplayText(editor)).toBe(dedent `
|
||||
def ab():…
|
||||
|
||||
def cd():
|
||||
print 'c'
|
||||
print 'd'
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.scopeDescriptorForPosition', () => {
|
||||
it('returns a scope descriptor representing the given position in the syntax tree', () => {
|
||||
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
||||
id: 'javascript',
|
||||
parser: 'tree-sitter-javascript'
|
||||
})
|
||||
|
||||
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
||||
|
||||
buffer.setText('foo({bar: baz});')
|
||||
|
||||
editor.screenLineForScreenRow(0)
|
||||
expect(editor.scopeDescriptorForBufferPosition({row: 0, column: 6}).getScopesArray()).toEqual([
|
||||
'javascript',
|
||||
'program',
|
||||
'expression_statement',
|
||||
'call_expression',
|
||||
'arguments',
|
||||
'object',
|
||||
'pair',
|
||||
'property_identifier'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('TextEditor.selectLargerSyntaxNode and .selectSmallerSyntaxNode', () => {
|
||||
it('expands and contract the selection based on the syntax tree', () => {
|
||||
const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, {
|
||||
parser: 'tree-sitter-javascript',
|
||||
scopes: {'program': 'source'}
|
||||
})
|
||||
|
||||
buffer.setLanguageMode(new TreeSitterLanguageMode({buffer, grammar}))
|
||||
buffer.setText(dedent `
|
||||
function a (b, c, d) {
|
||||
eee.f()
|
||||
g()
|
||||
}
|
||||
`)
|
||||
|
||||
editor.screenLineForScreenRow(0)
|
||||
|
||||
editor.setCursorBufferPosition([1, 3])
|
||||
editor.selectLargerSyntaxNode()
|
||||
expect(editor.getSelectedText()).toBe('eee')
|
||||
editor.selectLargerSyntaxNode()
|
||||
expect(editor.getSelectedText()).toBe('eee.f')
|
||||
editor.selectLargerSyntaxNode()
|
||||
expect(editor.getSelectedText()).toBe('eee.f()')
|
||||
editor.selectLargerSyntaxNode()
|
||||
expect(editor.getSelectedText()).toBe('{\n eee.f()\n g()\n}')
|
||||
editor.selectLargerSyntaxNode()
|
||||
expect(editor.getSelectedText()).toBe('function a (b, c, d) {\n eee.f()\n g()\n}')
|
||||
|
||||
editor.selectSmallerSyntaxNode()
|
||||
expect(editor.getSelectedText()).toBe('{\n eee.f()\n g()\n}')
|
||||
editor.selectSmallerSyntaxNode()
|
||||
expect(editor.getSelectedText()).toBe('eee.f()')
|
||||
editor.selectSmallerSyntaxNode()
|
||||
expect(editor.getSelectedText()).toBe('eee.f')
|
||||
editor.selectSmallerSyntaxNode()
|
||||
expect(editor.getSelectedText()).toBe('eee')
|
||||
editor.selectSmallerSyntaxNode()
|
||||
expect(editor.getSelectedBufferRange()).toEqual([[1, 3], [1, 3]])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function getDisplayText (editor) {
|
||||
return editor.displayLayer.getText()
|
||||
}
|
||||
|
||||
function expectTokensToEqual (editor, expectedTokenLines) {
|
||||
const lastRow = editor.getLastScreenRow()
|
||||
|
||||
// Assert that the correct tokens are returned regardless of which row
|
||||
// the highlighting iterator starts on.
|
||||
for (let startRow = 0; startRow <= lastRow; startRow++) {
|
||||
editor.displayLayer.clearSpatialIndex()
|
||||
editor.displayLayer.getScreenLines(startRow, Infinity)
|
||||
|
||||
const tokenLines = []
|
||||
for (let row = startRow; row <= lastRow; row++) {
|
||||
tokenLines[row] = editor.tokensForScreenRow(row).map(({text, scopes}) => ({
|
||||
text,
|
||||
scopes: scopes.map(scope => scope
|
||||
.split(' ')
|
||||
.map(className => className.slice('syntax--'.length))
|
||||
.join(' '))
|
||||
}))
|
||||
}
|
||||
|
||||
for (let row = startRow; row <= lastRow; row++) {
|
||||
const tokenLine = tokenLines[row]
|
||||
const expectedTokenLine = expectedTokenLines[row]
|
||||
|
||||
expect(tokenLine.length).toEqual(expectedTokenLine.length)
|
||||
for (let i = 0; i < tokenLine.length; i++) {
|
||||
expect(tokenLine[i]).toEqual(expectedTokenLine[i], `Token ${i}, startRow: ${startRow}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,15 @@ describe('WindowEventHandler', () => {
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
describe('resize event', () =>
|
||||
it('calls storeWindowDimensions', () => {
|
||||
spyOn(atom, 'storeWindowDimensions')
|
||||
window.dispatchEvent(new CustomEvent('resize'))
|
||||
expect(atom.storeWindowDimensions).toHaveBeenCalled()
|
||||
})
|
||||
)
|
||||
|
||||
describe('window:close event', () =>
|
||||
it('closes the window', () => {
|
||||
spyOn(atom, 'close')
|
||||
|
||||
@@ -10,7 +10,7 @@ const _ = require('underscore-plus')
|
||||
const fstream = require('fstream')
|
||||
const fs = require('fs-plus')
|
||||
const AtomEnvironment = require('../src/atom-environment')
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach} = require('./async-spec-helpers')
|
||||
const {it, fit, ffit, fffit, beforeEach, afterEach, conditionPromise} = require('./async-spec-helpers')
|
||||
|
||||
describe('Workspace', () => {
|
||||
let workspace
|
||||
@@ -659,47 +659,42 @@ describe('Workspace', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the file is over user-defined limit', () => {
|
||||
const shouldPromptForFileOfSize = (size, shouldPrompt) => {
|
||||
describe('when the file size is over the limit defined in `core.warnOnLargeFileLimit`', () => {
|
||||
const shouldPromptForFileOfSize = async (size, shouldPrompt) => {
|
||||
spyOn(fs, 'getSizeSync').andReturn(size * 1048577)
|
||||
atom.applicationDelegate.confirm.andCallFake(() => selectedButtonIndex)
|
||||
atom.applicationDelegate.confirm()
|
||||
var selectedButtonIndex = 1 // cancel
|
||||
|
||||
let editor = null
|
||||
waitsForPromise(() => workspace.open('sample.js').then(e => { editor = e }))
|
||||
let selectedButtonIndex = 1 // cancel
|
||||
atom.applicationDelegate.confirm.andCallFake((options, callback) => callback(selectedButtonIndex))
|
||||
|
||||
let editor = await workspace.open('sample.js')
|
||||
if (shouldPrompt) {
|
||||
runs(() => {
|
||||
expect(editor).toBeUndefined()
|
||||
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
||||
expect(editor).toBeUndefined()
|
||||
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
||||
|
||||
atom.applicationDelegate.confirm.reset()
|
||||
selectedButtonIndex = 0
|
||||
}) // open the file
|
||||
atom.applicationDelegate.confirm.reset()
|
||||
selectedButtonIndex = 0 // open the file
|
||||
|
||||
waitsForPromise(() => workspace.open('sample.js').then(e => { editor = e }))
|
||||
editor = await workspace.open('sample.js')
|
||||
|
||||
runs(() => {
|
||||
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
||||
})
|
||||
expect(atom.applicationDelegate.confirm).toHaveBeenCalled()
|
||||
} else {
|
||||
runs(() => expect(editor).not.toBeUndefined())
|
||||
expect(editor).not.toBeUndefined()
|
||||
}
|
||||
}
|
||||
|
||||
it('prompts the user to make sure they want to open a file this big', () => {
|
||||
it('prompts before opening the file', async () => {
|
||||
atom.config.set('core.warnOnLargeFileLimit', 20)
|
||||
shouldPromptForFileOfSize(20, true)
|
||||
await shouldPromptForFileOfSize(20, true)
|
||||
})
|
||||
|
||||
it("doesn't prompt on files below the limit", () => {
|
||||
it("doesn't prompt on files below the limit", async () => {
|
||||
atom.config.set('core.warnOnLargeFileLimit', 30)
|
||||
shouldPromptForFileOfSize(20, false)
|
||||
await shouldPromptForFileOfSize(20, false)
|
||||
})
|
||||
|
||||
it('prompts for smaller files with a lower limit', () => {
|
||||
it('prompts for smaller files with a lower limit', async () => {
|
||||
atom.config.set('core.warnOnLargeFileLimit', 5)
|
||||
shouldPromptForFileOfSize(10, true)
|
||||
await shouldPromptForFileOfSize(10, true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2809,29 +2804,30 @@ describe('Workspace', () => {
|
||||
|
||||
describe('.checkoutHeadRevision()', () => {
|
||||
let editor = null
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
jasmine.useRealClock()
|
||||
atom.config.set('editor.confirmCheckoutHeadRevision', false)
|
||||
|
||||
waitsForPromise(() => atom.workspace.open('sample-with-comments.js').then(o => { editor = o }))
|
||||
editor = await atom.workspace.open('sample-with-comments.js')
|
||||
})
|
||||
|
||||
it('reverts to the version of its file checked into the project repository', () => {
|
||||
it('reverts to the version of its file checked into the project repository', async () => {
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
editor.insertText('---\n')
|
||||
expect(editor.lineTextForBufferRow(0)).toBe('---')
|
||||
|
||||
waitsForPromise(() => atom.workspace.checkoutHeadRevision(editor))
|
||||
atom.workspace.checkoutHeadRevision(editor)
|
||||
|
||||
runs(() => expect(editor.lineTextForBufferRow(0)).toBe(''))
|
||||
await conditionPromise(() => editor.lineTextForBufferRow(0) === '')
|
||||
})
|
||||
|
||||
describe("when there's no repository for the editor's file", () => {
|
||||
it("doesn't do anything", () => {
|
||||
it("doesn't do anything", async () => {
|
||||
editor = new TextEditor()
|
||||
editor.setText('stuff')
|
||||
atom.workspace.checkoutHeadRevision(editor)
|
||||
|
||||
waitsForPromise(() => atom.workspace.checkoutHeadRevision(editor))
|
||||
atom.workspace.checkoutHeadRevision(editor)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user