Merge branch 'master' into wl-rm-safe-clipboard

This commit is contained in:
Wliu
2018-01-09 21:39:04 -05:00
57 changed files with 5020 additions and 2194 deletions

View File

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

View File

@@ -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.'
})
})

View File

@@ -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);
});
});

View File

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

View File

@@ -0,0 +1 @@
exports.isFakeTreeSitterParser = true

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

View File

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

View File

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

View File

@@ -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', () => {

View File

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

View File

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

View File

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

View 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')
})
})

View File

@@ -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 () => {

View File

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

View File

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

View 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}`)
}
}
}
}

View File

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

View File

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