diff --git a/.github/workflows/editor-tests.yml b/.github/workflows/editor-tests.yml index c40264941f..760d1b0e2a 100644 --- a/.github/workflows/editor-tests.yml +++ b/.github/workflows/editor-tests.yml @@ -42,8 +42,8 @@ jobs: - name: Run Tests if: runner.os != 'Linux' - run: node script/run-tests.js spec + run: yarn test:editor - name: Run Tests with xvfb-run (Linux) if: runner.os == 'Linux' - run: xvfb-run --auto-servernum node script/run-tests.js spec + run: xvfb-run --auto-servernum yarn test:editor diff --git a/.github/workflows/package-tests-linux.yml b/.github/workflows/package-tests-linux.yml index db872336c4..35060cdf66 100644 --- a/.github/workflows/package-tests-linux.yml +++ b/.github/workflows/package-tests-linux.yml @@ -201,4 +201,3 @@ jobs: - name: Run ${{ matrix.package }} Tests run: Xvfb :1 & cd node_modules/${{ matrix.package }} && if test -d spec; then DISPLAY=:1 pulsar --test spec; fi - # run: node -e "require('./script/run-package-tests')(/${{ matrix.package }}/)" diff --git a/package.json b/package.json index 17339a8718..f97d6ff61e 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "engines": { "node": ">=14" }, + "atomTestRunner": "runners/jasmine2-test-runner", "license": "MIT", "electronVersion": "12.2.3", "resolutions": { @@ -86,8 +87,8 @@ "image-view": "file:packages/image-view", "incompatible-packages": "file:packages/incompatible-packages", "jasmine-json": "~0.0", - "jasmine-reporters": "1.1.0", "jasmine-tagged": "^1.1.4", + "jasmine": "2.5.3", "key-path-helpers": "^0.4.0", "keybinding-resolver": "file:./packages/keybinding-resolver", "language-c": "file:packages/language-c", @@ -293,7 +294,9 @@ "start": "electron --no-sandbox --enable-logging . -f", "dist": "node script/electron-builder.js", "js-docs": "jsdoc2md --files src --configure docs/.jsdoc.json > ./docs/Pulsar-API-Documentation.md", - "private-js-docs": "jsdoc2md --private --files src --configure docs/.jsdoc.json > ./docs/Source-Code-Documentation.md" + "private-js-docs": "jsdoc2md --private --files src --configure docs/.jsdoc.json > ./docs/Source-Code-Documentation.md", + "test:editor": "yarn test:only spec", + "test:only": "yarn start --test" }, "devDependencies": { "@electron/notarize": "^1.2.3", diff --git a/packages/archive-view/package.json b/packages/archive-view/package.json index 64389cb8b5..20f728d7a8 100644 --- a/packages/archive-view/package.json +++ b/packages/archive-view/package.json @@ -14,6 +14,7 @@ "engines": { "atom": "*" }, + "atomTestRunner": "runners/jasmine2-test-runner", "deserializers": { "ArchiveEditor": "deserialize", "ArchiveEditorView": "deserialize" diff --git a/packages/archive-view/spec/archive-editor-spec.js b/packages/archive-view/spec/archive-editor-spec.js index 7b0bea958c..1ab995be05 100644 --- a/packages/archive-view/spec/archive-editor-spec.js +++ b/packages/archive-view/spec/archive-editor-spec.js @@ -1,5 +1,3 @@ -const {it, fit, ffit, fffit, beforeEach, afterEach, conditionPromise} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars - const path = require('path') const ArchiveEditor = require('../lib/archive-editor') const ArchiveEditorView = require('../lib/archive-editor-view') diff --git a/packages/archive-view/spec/archive-editor-view-spec.js b/packages/archive-view/spec/archive-editor-view-spec.js index 4ded3e5a8e..38d2848703 100644 --- a/packages/archive-view/spec/archive-editor-view-spec.js +++ b/packages/archive-view/spec/archive-editor-view-spec.js @@ -1,6 +1,6 @@ const {Disposable, File} = require('atom') const getIconServices = require('../lib/get-icon-services') -const {it, fit, ffit, fffit, beforeEach, afterEach, conditionPromise} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars +const {conditionPromise} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars async function condition (handler) { if (jasmine.isSpy(window.setTimeout)) { @@ -13,21 +13,21 @@ describe('ArchiveEditorView', () => { let archiveEditorView, onDidChangeCallback, onDidRenameCallback, onDidDeleteCallback beforeEach(async () => { - spyOn(File.prototype, 'onDidChange').andCallFake(function (callback) { + spyOn(File.prototype, 'onDidChange').and.callFake(function (callback) { if (/\.tar$/.test(this.getPath())) { onDidChangeCallback = callback } return new Disposable() }) - spyOn(File.prototype, 'onDidRename').andCallFake(function (callback) { + spyOn(File.prototype, 'onDidRename').and.callFake(function (callback) { if (/\.tar$/.test(this.getPath())) { onDidRenameCallback = callback } return new Disposable() }) - spyOn(File.prototype, 'onDidDelete').andCallFake(function (callback) { + spyOn(File.prototype, 'onDidDelete').and.callFake(function (callback) { if (/\.tar$/.test(this.getPath())) { onDidDeleteCallback = callback } @@ -150,9 +150,9 @@ describe('ArchiveEditorView', () => { describe('when the file is renamed', () => { it('refreshes the view and updates the title', async () => { - spyOn(File.prototype, 'getPath').andReturn('nested-renamed.tar') + spyOn(File.prototype, 'getPath').and.returnValue('nested-renamed.tar') await condition(() => archiveEditorView.element.querySelectorAll('.entry').length > 0) - spyOn(archiveEditorView, 'refresh').andCallThrough() + spyOn(archiveEditorView, 'refresh').and.callThrough() spyOn(archiveEditorView, 'getTitle') onDidRenameCallback() expect(archiveEditorView.refresh).toHaveBeenCalled() diff --git a/packages/archive-view/spec/async-spec-helpers.js b/packages/archive-view/spec/async-spec-helpers.js index 73002c049a..019902ce82 100644 --- a/packages/archive-view/spec/async-spec-helpers.js +++ b/packages/archive-view/spec/async-spec-helpers.js @@ -1,39 +1,5 @@ /** @babel */ -export function beforeEach (fn) { - global.beforeEach(function () { - const result = fn() - if (result instanceof Promise) { - waitsForPromise(() => result) - } - }) -} - -export function afterEach (fn) { - global.afterEach(function () { - const result = fn() - if (result instanceof Promise) { - waitsForPromise(() => result) - } - }) -} - -['it', 'fit', 'ffit', 'fffit'].forEach(function (name) { - module.exports[name] = function (description, fn) { - if (fn === undefined) { - global[name](description) - return - } - - global[name](description, function () { - const result = fn() - if (result instanceof Promise) { - waitsForPromise(() => result) - } - }) - } -}) - export async function conditionPromise (condition, description = 'anonymous condition') { const startTime = Date.now() @@ -50,54 +16,8 @@ export async function conditionPromise (condition, description = 'anonymous cond } } -export function timeoutPromise (timeout) { +function timeoutPromise (timeout) { return new Promise(function (resolve) { global.setTimeout(resolve, timeout) }) } - -function waitsForPromise (fn) { - const promise = fn() - global.waitsFor('spec promise to resolve', function (done) { - promise.then(done, function (error) { - jasmine.getEnv().currentSpec.fail(error) - done() - }) - }) -} - -export function emitterEventPromise (emitter, event, timeout = 15000) { - return new Promise((resolve, reject) => { - const timeoutHandle = setTimeout(() => { - reject(new Error(`Timed out waiting for '${event}' event`)) - }, timeout) - emitter.once(event, () => { - clearTimeout(timeoutHandle) - resolve() - }) - }) -} - -export function promisify (original) { - return function (...args) { - return new Promise((resolve, reject) => { - args.push((err, ...results) => { - if (err) { - reject(err) - } else { - resolve(...results) - } - }) - - return original(...args) - }) - } -} - -export function promisifySome (obj, fnNames) { - const result = {} - for (const fnName of fnNames) { - result[fnName] = promisify(obj[fnName]) - } - return result -} diff --git a/script/electron-builder.js b/script/electron-builder.js index 9cd5ff86b2..e14174f8f1 100644 --- a/script/electron-builder.js +++ b/script/electron-builder.js @@ -61,11 +61,8 @@ let options = { // Core Repo Test Inclusions "spec/jasmine-test-runner.js", - "spec/spec-helper.js", - "spec/jasmine-junit-reporter.js", - "spec/spec-helper-functions.js", - "spec/atom-reporter.js", - "spec/jasmine-list-reporter.js", + "spec/helpers/**/*", + "spec/runners/**/*", // --- Exclusions --- // Core Repo Exclusions diff --git a/script/run-package-tests.js b/script/run-package-tests.js deleted file mode 100644 index 558332ccbc..0000000000 --- a/script/run-package-tests.js +++ /dev/null @@ -1,20 +0,0 @@ -const fs = require('fs') -const path = require('path') -const packJson = require('../package.json') -const runAllSpecs = require('./run-tests') - -module.exports = function(filter) { - let packagePath = [] - - for(let pack in packJson.packageDependencies) { - if(pack.match(filter)) { - let basePath = path.join('node_modules', pack) - let testPath = path.join(basePath, 'test') - let specPath = path.join(basePath, 'spec') - if(fs.existsSync(testPath)) packagePath.push(testPath) - if(fs.existsSync(specPath)) packagePath.push(specPath) - } - } - - runAllSpecs(packagePath) -} diff --git a/script/run-tests.js b/script/run-tests.js deleted file mode 100644 index f6fff0c3c8..0000000000 --- a/script/run-tests.js +++ /dev/null @@ -1,76 +0,0 @@ -const cp = require('child_process') - -function runAllSpecs(files) { - runSpecs(files, []) -} - -function runSpecs(files, retries) { - let env = process.env - env.ATOM_JASMINE_REPORTER='list' - if(retries.length > 0) { - // Escape possible tests that can generate a regexp that will not match... - const escaped = retries.map(str => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - env.SPEC_FILTER = escaped.join("|") - } - const res = cp.spawn('yarn', ['start', '--test', ...files], { - cwd: process.cwd(), - env: env - }) - - let out; - res.stdout.on('data', data => { - process.stdout.write(data.toString()); - }); - - res.stderr.on('data', data => { - const strData = data.toString(); - process.stderr.write(strData); - - if(strData.match(/ALL TESTS THAT FAILED:/)) { - out = ''; - } else if(out !== undefined) { - out += strData; - } - }); - - res.on('close', code => { - if(code !== 0 && retries.length === 0) { - const failed = filterSpecs(out) - - console.log(`********************* -Tests failed. Retrying failed tests... -********************* - -`) - runSpecs(files, failed) - } else { - process.exit(code) - } - }); -} - -function filterSpecs(output) { - if(!output) return '' - let descriptions = [] - let start = true - for(let out of output.split("\n")) { - if(start) { - if(out !== '') { - start = false - descriptions.push(out) - } - } else if(out !== '') { - descriptions.push(out) - } else { - return descriptions - } - } -} - -if(process.argv[0] === __filename) { - runAllSpecs(process.argv.splice(1)) -} else if(process.argv[1] === __filename) { - runAllSpecs(process.argv.splice(2)) -} else { - module.exports = runAllSpecs -} diff --git a/spec/atom-environment-spec.js b/spec/atom-environment-spec.js index 2a44b24042..9d7b1eaad6 100644 --- a/spec/atom-environment-spec.js +++ b/spec/atom-environment-spec.js @@ -1,16 +1,10 @@ -const { conditionPromise } = require('./async-spec-helpers'); +const { conditionPromise } = require('./helpers/async-spec-helpers'); const fs = require('fs'); const path = require('path'); const temp = require('temp').track(); const AtomEnvironment = require('../src/atom-environment'); describe('AtomEnvironment', () => { - afterEach(() => { - try { - temp.cleanupSync(); - } catch (error) {} - }); - describe('window sizing methods', () => { describe('::getPosition and ::setPosition', () => { let originalPosition = null; @@ -27,7 +21,10 @@ describe('AtomEnvironment', () => { describe('::getSize and ::setSize', () => { let originalSize = null; beforeEach(() => (originalSize = atom.getSize())); - afterEach(() => atom.setSize(originalSize.width, originalSize.height)); + + afterEach(async () => { + await atom.setSize(originalSize.width, originalSize.height); + }); it('sets the size of the window, and can retrieve the size just set', async () => { const newWidth = originalSize.width - 12; @@ -41,7 +38,7 @@ describe('AtomEnvironment', () => { describe('.isReleasedVersion()', () => { it('returns false if the version is a SHA and true otherwise', () => { let version = '0.1.0'; - spyOn(atom, 'getVersion').andCallFake(() => version); + spyOn(atom, 'getVersion').and.callFake(() => version); expect(atom.isReleasedVersion()).toBe(true); version = '36b5518'; expect(atom.isReleasedVersion()).toBe(false); @@ -51,7 +48,7 @@ describe('AtomEnvironment', () => { describe('.versionSatisfies()', () => { it('returns appropriately for provided range', () => { let testPulsarVersion = '0.1.0'; - spyOn(atom, 'getVersion').andCallFake(() => testPulsarVersion); + spyOn(atom, 'getVersion').and.callFake(() => testPulsarVersion); expect(atom.versionSatisfies('>0.2.0')).toBe(false); expect(atom.versionSatisfies('>=0.x.x <=2.x.x')).toBe(true); expect(atom.versionSatisfies('^0.1.x')).toBe(true); @@ -70,7 +67,7 @@ describe('AtomEnvironment', () => { let devToolsPromise = null; beforeEach(() => { devToolsPromise = Promise.resolve(); - spyOn(atom, 'openDevTools').andReturn(devToolsPromise); + spyOn(atom, 'openDevTools').and.returnValue(devToolsPromise); spyOn(atom, 'executeJavaScriptInDevTools'); }); @@ -103,7 +100,7 @@ describe('AtomEnvironment', () => { window.onerror(e.toString(), 'abc', 2, 3, e); } - delete willThrowSpy.mostRecentCall.args[0].preventDefault; + delete willThrowSpy.calls.mostRecent().args[0].preventDefault; expect(willThrowSpy).toHaveBeenCalledWith({ message: error.toString(), url: 'abc', @@ -114,7 +111,7 @@ describe('AtomEnvironment', () => { }); it('will not show the devtools when preventDefault() is called', () => { - willThrowSpy.andCallFake(errorObject => errorObject.preventDefault()); + willThrowSpy.and.callFake(errorObject => errorObject.preventDefault()); atom.onWillThrowError(willThrowSpy); try { @@ -158,7 +155,7 @@ describe('AtomEnvironment', () => { beforeEach(() => { errors = []; - spyOn(atom, 'isReleasedVersion').andReturn(true); + spyOn(atom, 'isReleasedVersion').and.returnValue(true); atom.onDidFailAssertion(error => errors.push(error)); }); @@ -188,8 +185,8 @@ describe('AtomEnvironment', () => { describe('when Atom has been built from source', () => { it('throws an error', () => { - atom.isReleasedVersion.andReturn(false); - expect(() => atom.assert(false, 'testing')).toThrow( + atom.isReleasedVersion.and.returnValue(false); + expect(() => atom.assert(false, 'testing')).toThrowError( 'Assertion failed: testing' ); }); @@ -220,8 +217,8 @@ describe('AtomEnvironment', () => { windowState: null }); - spyOn(atom, 'getLoadSettings').andCallFake(() => loadSettings); - spyOn(atom, 'serialize').andReturn({ stuff: 'cool' }); + spyOn(atom, 'getLoadSettings').and.callFake(() => loadSettings); + spyOn(atom, 'serialize').and.returnValue({ stuff: 'cool' }); atom.project.setPaths([dir1, dir2]); @@ -260,7 +257,7 @@ describe('AtomEnvironment', () => { expect(atomEnv.saveState).toHaveBeenCalledWith({ isUnloading: false }); expect(atomEnv.saveState).not.toHaveBeenCalledWith({ isUnloading: true }); - atomEnv.saveState.reset(); + atomEnv.saveState.calls.reset(); const mousedown = new MouseEvent('mousedown'); atomEnv.document.dispatchEvent(mousedown); advanceClock(atomEnv.saveStateDebounceInterval); @@ -297,23 +294,23 @@ describe('AtomEnvironment', () => { advanceClock(atomEnv.saveStateDebounceInterval); idleCallbacks.shift()(); - expect(atomEnv.saveState.calls.length).toBe(1); + expect(atomEnv.saveState.calls.count()).toBe(1); mousedown = new MouseEvent('mousedown'); atomEnv.document.dispatchEvent(mousedown); advanceClock(atomEnv.saveStateDebounceInterval); idleCallbacks.shift()(); - expect(atomEnv.saveState.calls.length).toBe(1); + expect(atomEnv.saveState.calls.count()).toBe(1); atomEnv.destroy(); }); it('serializes the project state with all the options supplied in saveState', async () => { - spyOn(atom.project, 'serialize').andReturn({ foo: 42 }); + spyOn(atom.project, 'serialize').and.returnValue({ foo: 42 }); await atom.saveState({ anyOption: 'any option' }); - expect(atom.project.serialize.calls.length).toBe(1); - expect(atom.project.serialize.mostRecentCall.args[0]).toEqual({ + expect(atom.project.serialize.calls.count()).toBe(1); + expect(atom.project.serialize.calls.mostRecent().args[0]).toEqual({ anyOption: 'any option' }); }); @@ -348,7 +345,7 @@ describe('AtomEnvironment', () => { describe('deserialization failures', () => { it('propagates unrecognized project state restoration failures', async () => { let err; - spyOn(atom.project, 'deserialize').andCallFake(() => { + spyOn(atom.project, 'deserialize').and.callFake(() => { err = new Error('deserialization failure'); return Promise.reject(err); }); @@ -365,7 +362,7 @@ describe('AtomEnvironment', () => { }); it('disregards missing project folder errors', async () => { - spyOn(atom.project, 'deserialize').andCallFake(() => { + spyOn(atom.project, 'deserialize').and.callFake(() => { const err = new Error('deserialization failure'); err.missingProjectPaths = ['nah']; return Promise.reject(err); @@ -381,7 +378,7 @@ describe('AtomEnvironment', () => { describe('openInitialEmptyEditorIfNecessary', () => { describe('when there are no paths set', () => { beforeEach(() => - spyOn(atom, 'getLoadSettings').andReturn({ hasOpenFiles: false }) + spyOn(atom, 'getLoadSettings').and.returnValue({ hasOpenFiles: false }) ); it('opens an empty buffer', () => { @@ -399,7 +396,7 @@ describe('AtomEnvironment', () => { expect(atom.workspace.open).not.toHaveBeenCalled(); }); - it('does not open an empty buffer when core.openEmptyEditorOnStart is false', async () => { + it('does not open an empty buffer when core.openEmptyEditorOnStart is false', () => { atom.config.set('core.openEmptyEditorOnStart', false); spyOn(atom.workspace, 'open'); atom.openInitialEmptyEditorIfNecessary(); @@ -409,7 +406,7 @@ describe('AtomEnvironment', () => { describe('when the project has a path', () => { beforeEach(() => { - spyOn(atom, 'getLoadSettings').andReturn({ hasOpenFiles: true }); + spyOn(atom, 'getLoadSettings').and.returnValue({ hasOpenFiles: true }); spyOn(atom.workspace, 'open'); }); @@ -423,21 +420,21 @@ describe('AtomEnvironment', () => { describe('adding a project folder', () => { it('does nothing if the user dismisses the file picker', () => { const projectRoots = atom.project.getPaths(); - spyOn(atom, 'pickFolder').andCallFake(callback => callback(null)); + spyOn(atom, 'pickFolder').and.callFake(callback => callback(null)); atom.addProjectFolder(); expect(atom.project.getPaths()).toEqual(projectRoots); }); describe('when there is no saved state for the added folders', () => { beforeEach(() => { - spyOn(atom, 'loadState').andReturn(Promise.resolve(null)); + spyOn(atom, 'loadState').and.returnValue(Promise.resolve(null)); spyOn(atom, 'attemptRestoreProjectStateForPaths'); }); it('adds the selected folder to the project', async () => { atom.project.setPaths([]); const tempDirectory = temp.mkdirSync('a-new-directory'); - spyOn(atom, 'pickFolder').andCallFake(callback => + spyOn(atom, 'pickFolder').and.callFake(callback => callback([tempDirectory]) ); await atom.addProjectFolder(); @@ -450,12 +447,10 @@ describe('AtomEnvironment', () => { const state = Symbol('savedState'); beforeEach(() => { - spyOn(atom, 'getStateKey').andCallFake(dirs => dirs.join(':')); - spyOn(atom, 'loadState').andCallFake(async key => - key === __dirname ? state : null - ); + spyOn(atom, 'getStateKey').and.callFake(dirs => dirs.join(':')); + spyOn(atom, 'loadState').and.callFake((key) => key === __dirname ? state : null); spyOn(atom, 'attemptRestoreProjectStateForPaths'); - spyOn(atom, 'pickFolder').andCallFake(callback => + spyOn(atom, 'pickFolder').and.callFake(callback => callback([__dirname]) ); atom.project.setPaths([]); @@ -560,7 +555,7 @@ describe('AtomEnvironment', () => { it('prompts the user to restore the state', () => { const dock = atom.workspace.getLeftDock(); dock.getActivePane().addItem(editor); - spyOn(atom, 'confirm').andReturn(1); + spyOn(atom, 'confirm').and.returnValue(1); spyOn(atom.project, 'addPath'); spyOn(atom.workspace, 'open'); const state = Symbol('state'); @@ -575,7 +570,7 @@ describe('AtomEnvironment', () => { 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, 'confirm').and.callFake((options, callback) => callback(1)); spyOn(atom.project, 'addPath'); spyOn(atom.workspace, 'open'); const state = Symbol('state'); @@ -586,16 +581,16 @@ describe('AtomEnvironment', () => { [__filename] ); expect(atom.confirm).toHaveBeenCalled(); - await conditionPromise(() => atom.project.addPath.callCount === 1); + await conditionPromise(() => atom.project.addPath.calls.count() === 1); expect(atom.project.addPath).toHaveBeenCalledWith(__dirname); - expect(atom.workspace.open.callCount).toBe(1); + expect(atom.workspace.open.calls.count()).toBe(1); expect(atom.workspace.open).toHaveBeenCalledWith(__filename); }); 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, 'confirm').and.callFake((options, callback) => callback(0)); spyOn(atom, 'open'); const state = Symbol('state'); @@ -605,7 +600,7 @@ describe('AtomEnvironment', () => { [__filename] ); expect(atom.confirm).toHaveBeenCalled(); - await conditionPromise(() => atom.open.callCount === 1); + await conditionPromise(() => atom.open.calls.count() === 1); expect(atom.open).toHaveBeenCalledWith({ pathsToOpen: [__dirname, __filename], newWindow: true, @@ -651,11 +646,11 @@ describe('AtomEnvironment', () => { applicationDelegate: atom.applicationDelegate }); atomEnvironment.initialize({ window, document: fakeDocument }); - spyOn(atomEnvironment.packages, 'loadPackages').andReturn( + spyOn(atomEnvironment.packages, 'loadPackages').and.returnValue( Promise.resolve() ); - spyOn(atomEnvironment.packages, 'activate').andReturn(Promise.resolve()); - spyOn(atomEnvironment, 'displayWindow').andReturn(Promise.resolve()); + spyOn(atomEnvironment.packages, 'activate').and.returnValue(Promise.resolve()); + spyOn(atomEnvironment, 'displayWindow').and.returnValue(Promise.resolve()); await atomEnvironment.startEditorWindow(); atomEnvironment.unloadEditorWindow(); atomEnvironment.destroy(); @@ -708,7 +703,7 @@ describe('AtomEnvironment', () => { describe('when there is no saved state', () => { beforeEach(() => { - spyOn(atom, 'loadState').andReturn(Promise.resolve(null)); + spyOn(atom, 'loadState').and.returnValue(Promise.resolve(null)); }); describe('when the opened path exists', () => { @@ -791,8 +786,6 @@ describe('AtomEnvironment', () => { } } ); - - waitsFor(() => atom.project.directoryProviders.length > 0); }); afterEach(() => { @@ -812,8 +805,8 @@ describe('AtomEnvironment', () => { const state = Symbol('savedState'); beforeEach(() => { - spyOn(atom, 'getStateKey').andCallFake(dirs => dirs.join(':')); - spyOn(atom, 'loadState').andCallFake(function(key) { + spyOn(atom, 'getStateKey').and.callFake(dirs => dirs.join(':')); + spyOn(atom, 'loadState').and.callFake(function(key) { if (key === __dirname) { return Promise.resolve(state); } else { @@ -839,7 +832,7 @@ describe('AtomEnvironment', () => { const existingDir = path.join(__dirname, 'fixtures'); const missingDir = path.join(__dirname, 'no'); - atom.loadState.andCallFake(function(key) { + atom.loadState.and.callFake(function(key) { if (key === `${existingDir}:${missingDir}`) { return Promise.resolve(state); } else { @@ -908,7 +901,7 @@ describe('AtomEnvironment', () => { let version; beforeEach(() => { - spyOn(atom, 'getVersion').andCallFake(() => version); + spyOn(atom, 'getVersion').and.callFake(() => version); }); it('returns the correct channel based on the version number', () => { diff --git a/spec/atom-paths-spec.js b/spec/atom-paths-spec.js index 1b8ef3303f..059ccafc77 100644 --- a/spec/atom-paths-spec.js +++ b/spec/atom-paths-spec.js @@ -41,14 +41,16 @@ describe('AtomPaths', () => { expect(process.env.ATOM_HOME).toEqual(portableAtomHomePath); }); - it('uses ATOM_HOME if no write access to portable .atom folder', () => { - if (process.platform === 'win32') return; + it('uses ATOM_HOME if no write access to portable .atom folder', (done) => { + jasmine.filterByPlatform({except: ['win32']}, done); const readOnlyPath = temp.mkdirSync('atom-path-spec-no-write-access'); process.env.ATOM_HOME = readOnlyPath; fs.chmodSync(portableAtomHomePath, 444); atomPaths.setAtomHome(app.getPath('home')); expect(process.env.ATOM_HOME).toEqual(readOnlyPath); + + done(); }); }); @@ -119,14 +121,16 @@ describe('AtomPaths', () => { expect(app.getPath('userData')).toEqual(electronUserDataPath); }); - it('leaves userData unchanged if no write access to electronUserData folder', () => { - if (process.platform === 'win32') return; + it('leaves userData unchanged if no write access to electronUserData folder', (done) => { + jasmine.filterByPlatform({except: ['win32']}, done); fs.mkdirSync(electronUserDataPath); fs.chmodSync(electronUserDataPath, 444); atomPaths.setUserData(app); fs.chmodSync(electronUserDataPath, 666); expect(app.getPath('userData')).toEqual(defaultElectronUserDataPath); + + done(); }); }); diff --git a/spec/atom-protocol-handler-spec.js b/spec/atom-protocol-handler-spec.js index b15a6d8e20..eabb0eb8e4 100644 --- a/spec/atom-protocol-handler-spec.js +++ b/spec/atom-protocol-handler-spec.js @@ -1,10 +1,10 @@ -describe('"atom" protocol URL', () => - it('sends the file relative in the package as response', function() { - let called = false; +describe('"atom" protocol URL', () => { + it('sends the file relative in the package as response', (done) => { const request = new XMLHttpRequest(); - request.addEventListener('load', () => (called = true)); + request.addEventListener('load', () => { + done(); + }); request.open('GET', 'atom://async/package.json', true); request.send(); - - waitsFor('request to be done', () => called === true); - })); + }) +}); diff --git a/spec/buffered-node-process-spec.js b/spec/buffered-node-process-spec.js index c7813a125d..e38b03b216 100644 --- a/spec/buffered-node-process-spec.js +++ b/spec/buffered-node-process-spec.js @@ -3,8 +3,7 @@ const path = require('path'); const BufferedNodeProcess = require('../src/buffered-node-process'); describe('BufferedNodeProcess', function() { - it('executes the script in a new process', function() { - const exit = jasmine.createSpy('exitCallback'); + it('executes the script in a new process', async function() { let output = ''; const stdout = lines => (output += lines); let error = ''; @@ -12,18 +11,16 @@ describe('BufferedNodeProcess', function() { const args = ['hi']; const command = path.join(__dirname, 'fixtures', 'script.js'); - new BufferedNodeProcess({ command, args, stdout, stderr, exit }); - - waitsFor(() => exit.callCount === 1); + await new Promise((resolve) => { + new BufferedNodeProcess({ command, args, stdout, stderr, exit: resolve }); + }) - runs(function() { - expect(output).toBe('hi'); - expect(error).toBe(''); - expect(args).toEqual(['hi']); - }); + expect(output).toBe('hi'); + expect(error).toBe(''); + expect(args).toEqual(['hi']); }); - it('suppresses deprecations in the new process', function() { + it('suppresses deprecations in the new process', async function() { const exit = jasmine.createSpy('exitCallback'); let output = ''; const stdout = lines => (output += lines); @@ -35,13 +32,11 @@ describe('BufferedNodeProcess', function() { 'script-with-deprecations.js' ); - new BufferedNodeProcess({ command, stdout, stderr, exit }); - - waitsFor(() => exit.callCount === 1); + await new Promise((resolve) => { + new BufferedNodeProcess({ command, stdout, stderr, exit: resolve }); + }) - runs(function() { - expect(output).toBe('hi'); - expect(error).toBe(''); - }); + expect(output).toBe('hi'); + expect(error).toBe(''); }); }); diff --git a/spec/buffered-process-spec.js b/spec/buffered-process-spec.js index ab159a0d15..41d81cfaed 100644 --- a/spec/buffered-process-spec.js +++ b/spec/buffered-process-spec.js @@ -6,42 +6,41 @@ const BufferedProcess = require('../src/buffered-process'); describe('BufferedProcess', function() { describe('when a bad command is specified', function() { - let [oldOnError] = []; + let windowOnErrorSpy; + beforeEach(function() { - oldOnError = window.onerror; - window.onerror = jasmine.createSpy(); + windowOnErrorSpy = spyOn(window, 'onerror'); }); - afterEach(() => (window.onerror = oldOnError)); - describe('when there is an error handler specified', function() { describe('when an error event is emitted by the process', () => - it('calls the error handler and does not throw an exception', function() { + it('calls the error handler and does not throw an exception', async function() { const bufferedProcess = new BufferedProcess({ command: 'bad-command-nope1', args: ['nothing'], options: { shell: false } }); - const errorSpy = jasmine - .createSpy() - .andCallFake(error => error.handle()); - bufferedProcess.onWillThrowError(errorSpy); + const errorSpy = jasmine.createSpy() - waitsFor(() => errorSpy.callCount > 0); + await new Promise((resolve) => { + errorSpy.and.callFake((error) => { + error.handle(); + resolve(); + }); + bufferedProcess.onWillThrowError(errorSpy);; + }) - runs(function() { - expect(window.onerror).not.toHaveBeenCalled(); - expect(errorSpy).toHaveBeenCalled(); - expect(errorSpy.mostRecentCall.args[0].error.message).toContain( - 'spawn bad-command-nope1 ENOENT' - ); - }); + expect(window.onerror).not.toHaveBeenCalled(); + expect(errorSpy).toHaveBeenCalled(); + expect(errorSpy.calls.mostRecent().args[0].error.message).toContain( + 'spawn bad-command-nope1 ENOENT' + ); })); describe('when an error is thrown spawning the process', () => - it('calls the error handler and does not throw an exception', function() { - spyOn(ChildProcess, 'spawn').andCallFake(function() { + it('calls the error handler and does not throw an exception', async function() { + spyOn(ChildProcess, 'spawn').and.callFake(function() { const error = new Error('Something is really wrong'); error.code = 'EAGAIN'; throw error; @@ -53,42 +52,43 @@ describe('BufferedProcess', function() { options: {} }); - const errorSpy = jasmine - .createSpy() - .andCallFake(error => error.handle()); - bufferedProcess.onWillThrowError(errorSpy); + const errorSpy = jasmine.createSpy(); - waitsFor(() => errorSpy.callCount > 0); + await new Promise((resolve) => { + errorSpy.and.callFake(error => { + error.handle(); + resolve(); + }); + bufferedProcess.onWillThrowError(errorSpy); + }) - runs(function() { - expect(window.onerror).not.toHaveBeenCalled(); - expect(errorSpy).toHaveBeenCalled(); - expect(errorSpy.mostRecentCall.args[0].error.message).toContain( - 'Something is really wrong' - ); - }); + expect(window.onerror).not.toHaveBeenCalled(); + expect(errorSpy).toHaveBeenCalled(); + expect(errorSpy.calls.mostRecent().args[0].error.message).toContain( + 'Something is really wrong' + ); })); }); describe('when there is not an error handler specified', () => - it('does throw an exception', function() { - new BufferedProcess({ - command: 'bad-command-nope2', - args: ['nothing'], - options: { shell: false } - }); - - waitsFor(() => window.onerror.callCount > 0); + it('does throw an exception', async function() { + await new Promise((resolve) => { + window.onerror.and.callFake(resolve); - runs(function() { - expect(window.onerror).toHaveBeenCalled(); - expect(window.onerror.mostRecentCall.args[0]).toContain( - 'Failed to spawn command `bad-command-nope2`' - ); - expect(window.onerror.mostRecentCall.args[4].name).toBe( - 'BufferedProcessError' - ); + new BufferedProcess({ + command: 'bad-command-nope2', + args: ['nothing'], + options: {shell: false} + }); }); + + expect(window.onerror).toHaveBeenCalled(); + expect(window.onerror.calls.mostRecent().args[0]).toContain( + 'Failed to spawn command `bad-command-nope2`' + ); + expect(window.onerror.calls.mostRecent().args[4].name).toBe( + 'BufferedProcessError' + ); })); }); @@ -97,7 +97,7 @@ describe('BufferedProcess', function() { * TODO: FAILING TEST - This test fails with the following output: * timeout: timed out after 120000 msec waiting for condition */ - xit('doesnt start unless start method is called', function() { + xit('doesnt start unless start method is called', async function() { let stdout = ''; let stderr = ''; const exitCallback = jasmine.createSpy('exit callback'); @@ -116,70 +116,73 @@ describe('BufferedProcess', function() { }); expect(apmProcess.started).not.toBe(true); - apmProcess.start(); - expect(apmProcess.started).toBe(true); - waitsFor(() => exitCallback.callCount === 1); - runs(function() { - expect(stderr).toContain('apm - Atom Package Manager'); - expect(stdout).toEqual(''); - }); + await new Promise((resolve) => { + exitCallback.and.callFake(() => { + expect(apmProcess.started).toBe(true); + resolve(); + }) + + apmProcess.start(); + }) + + expect(stderr).toContain('apm - Atom Package Manager'); + expect(stdout).toEqual(''); })); /** * TODO: FAILING TEST - This test fails with the following output: - * timeout: timed out after 120000 msec waiting for condition + * timeout: timed out after 120000 msec waiting for condition */ - xit('calls the specified stdout, stderr, and exit callbacks', function() { + xit('calls the specified stdout, stderr, and exit callbacks', async function() { let stdout = ''; let stderr = ''; - const exitCallback = jasmine.createSpy('exit callback'); - new BufferedProcess({ - command: atom.packages.getApmPath(), - args: ['-h'], - options: {}, - stdout(lines) { - stdout += lines; - }, - stderr(lines) { - stderr += lines; - }, - exit: exitCallback - }); - waitsFor(() => exitCallback.callCount === 1); + await new Promise((resolve) => { + new BufferedProcess({ + command: atom.packages.getApmPath(), + args: ['-h'], + options: {}, + stdout(lines) { + stdout += lines; + }, + stderr(lines) { + stderr += lines; + }, + exit: resolve + }); + }) - runs(function() { - expect(stderr).toContain('apm - Atom Package Manager'); - expect(stdout).toEqual(''); - }); + expect(stderr).toContain('apm - Atom Package Manager'); + expect(stdout).toEqual(''); }); - it('calls the specified stdout callback with whole lines', function() { + it('calls the specified stdout callback with whole lines', async function() { const exitCallback = jasmine.createSpy('exit callback'); const loremPath = require.resolve('./fixtures/lorem.txt'); const content = fs.readFileSync(loremPath).toString(); let stdout = ''; let allLinesEndWithNewline = true; - new BufferedProcess({ - command: process.platform === 'win32' ? 'type' : 'cat', - args: [loremPath], - options: {}, - stdout(lines) { - const endsWithNewline = lines.charAt(lines.length - 1) === '\n'; - if (!endsWithNewline) { - allLinesEndWithNewline = false; - } - stdout += lines; - }, - exit: exitCallback - }); - waitsFor(() => exitCallback.callCount === 1); + await new Promise((resolve) => { + exitCallback.and.callFake(resolve) - runs(function() { - expect(allLinesEndWithNewline).toBe(true); - expect(stdout).toBe(content); + new BufferedProcess({ + command: process.platform === 'win32' ? 'type' : 'cat', + args: [loremPath], + options: {}, + stdout(lines) { + const endsWithNewline = lines.charAt(lines.length - 1) === '\n'; + if (!endsWithNewline) { + allLinesEndWithNewline = false; + } + stdout += lines; + }, + exit: exitCallback + }); }); + + expect(allLinesEndWithNewline).toBe(true); + expect(stdout).toBe(content); }); describe('on Windows', function() { @@ -202,20 +205,20 @@ describe('BufferedProcess', function() { command: 'explorer.exe', args: ['/root,C:\\foo'] }); - expect(ChildProcess.spawn.argsForCall[0][1][3]).toBe( + expect(ChildProcess.spawn.calls.argsFor(0)[1][3]).toBe( '"explorer.exe /root,C:\\foo"' ); })); it('spawns the command using a cmd.exe wrapper when options.shell is undefined', function() { new BufferedProcess({ command: 'dir' }); - expect(path.basename(ChildProcess.spawn.argsForCall[0][0])).toBe( + expect(path.basename(ChildProcess.spawn.calls.argsFor(0)[0])).toBe( 'cmd.exe' ); - expect(ChildProcess.spawn.argsForCall[0][1][0]).toBe('/s'); - expect(ChildProcess.spawn.argsForCall[0][1][1]).toBe('/d'); - expect(ChildProcess.spawn.argsForCall[0][1][2]).toBe('/c'); - expect(ChildProcess.spawn.argsForCall[0][1][3]).toBe('"dir"'); + expect(ChildProcess.spawn.calls.argsFor(0)[1][0]).toBe('/s'); + expect(ChildProcess.spawn.calls.argsFor(0)[1][1]).toBe('/d'); + expect(ChildProcess.spawn.calls.argsFor(0)[1][2]).toBe('/c'); + expect(ChildProcess.spawn.calls.argsFor(0)[1][3]).toBe('"dir"'); }); }); }); diff --git a/spec/compile-cache-spec.js b/spec/compile-cache-spec.js index c550ce11cf..6f7ad9c802 100644 --- a/spec/compile-cache-spec.js +++ b/spec/compile-cache-spec.js @@ -14,7 +14,7 @@ const CSON = require('season'); const CompileCache = require('../src/compile-cache'); describe('CompileCache', () => { - let [atomHome, fixtures] = Array.from([]); + let atomHome, fixtures; beforeEach(() => { fixtures = atom.project.getPaths()[0]; @@ -24,77 +24,87 @@ describe('CompileCache', () => { CompileCache.resetCacheStats(); spyOn(babelCompiler, 'compile'); - spyOn(CoffeeScript, 'compile').andReturn('the-coffee-code'); - return spyOn(TypeScriptSimple.prototype, 'compile').andReturn('the-typescript-code'); + spyOn(CoffeeScript, 'compile').and.returnValue('the-coffee-code'); + spyOn(TypeScriptSimple.prototype, 'compile').and.returnValue('the-typescript-code'); }); afterEach(() => { CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME); CSON.setCacheDir(CompileCache.getCacheDirectory()); try { - return temp.cleanupSync(); + temp.cleanupSync(); } catch (error) {} }); describe('addPathToCache(filePath, atomHome)', () => { - describe('when the given file is plain javascript', () => it('does not compile or cache the file', function() { - CompileCache.addPathToCache(path.join(fixtures, 'sample.js'), atomHome); - return expect(CompileCache.getCacheStats()['.js']).toEqual({hits: 0, misses: 0}); - })); + describe('when the given file is plain javascript', () => { + it('does not compile or cache the file', function () { + CompileCache.addPathToCache(path.join(fixtures, 'sample.js'), atomHome); + expect(CompileCache.getCacheStats()['.js']).toEqual({hits: 0, misses: 0}); + }) + }); /** * TODO: FAILING TEST - This test fails with the following output: * TypeError: The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received undefined */ - xdescribe('when the given file uses babel', () => it('compiles the file with babel and caches it', function() { - CompileCache.addPathToCache(path.join(fixtures, 'babel', 'babel-comment.js'), atomHome); - expect(CompileCache.getCacheStats()['.js']).toEqual({hits: 0, misses: 1}); - expect(babelCompiler.compile.callCount).toBe(1); - - CompileCache.addPathToCache(path.join(fixtures, 'babel', 'babel-comment.js'), atomHome); - expect(CompileCache.getCacheStats()['.js']).toEqual({hits: 1, misses: 1}); - return expect(babelCompiler.compile.callCount).toBe(1); - })); - - describe('when the given file is coffee-script', () => it('compiles the file with coffee-script and caches it', function() { - CompileCache.addPathToCache(path.join(fixtures, 'coffee.coffee'), atomHome); - expect(CompileCache.getCacheStats()['.coffee']).toEqual({hits: 0, misses: 1}); - expect(CoffeeScript.compile.callCount).toBe(1); - - CompileCache.addPathToCache(path.join(fixtures, 'coffee.coffee'), atomHome); - expect(CompileCache.getCacheStats()['.coffee']).toEqual({hits: 1, misses: 1}); - return expect(CoffeeScript.compile.callCount).toBe(1); - })); - - describe('when the given file is typescript', () => it('compiles the file with typescript and caches it', function() { - CompileCache.addPathToCache(path.join(fixtures, 'typescript', 'valid.ts'), atomHome); - expect(CompileCache.getCacheStats()['.ts']).toEqual({hits: 0, misses: 1}); - expect(TypeScriptSimple.prototype.compile.callCount).toBe(1); - - CompileCache.addPathToCache(path.join(fixtures, 'typescript', 'valid.ts'), atomHome); - expect(CompileCache.getCacheStats()['.ts']).toEqual({hits: 1, misses: 1}); - return expect(TypeScriptSimple.prototype.compile.callCount).toBe(1); - })); - - return describe('when the given file is CSON', () => it('compiles the file to JSON and caches it', function() { - spyOn(CSON, 'setCacheDir').andCallThrough(); - spyOn(CSON, 'readFileSync').andCallThrough(); - - CompileCache.addPathToCache(path.join(fixtures, 'cson.cson'), atomHome); - expect(CSON.readFileSync).toHaveBeenCalledWith(path.join(fixtures, 'cson.cson')); - expect(CSON.setCacheDir).toHaveBeenCalledWith(path.join(atomHome, '/compile-cache')); - - CSON.readFileSync.reset(); - CSON.setCacheDir.reset(); - CompileCache.addPathToCache(path.join(fixtures, 'cson.cson'), atomHome); - expect(CSON.readFileSync).toHaveBeenCalledWith(path.join(fixtures, 'cson.cson')); - return expect(CSON.setCacheDir).not.toHaveBeenCalled(); - })); + xdescribe('when the given file uses babel', () => { + it('compiles the file with babel and caches it', function () { + CompileCache.addPathToCache(path.join(fixtures, 'babel', 'babel-comment.js'), atomHome); + expect(CompileCache.getCacheStats()['.js']).toEqual({hits: 0, misses: 1}); + expect(babelCompiler.compile.calls.count()).toBe(1); + + CompileCache.addPathToCache(path.join(fixtures, 'babel', 'babel-comment.js'), atomHome); + expect(CompileCache.getCacheStats()['.js']).toEqual({hits: 1, misses: 1}); + expect(babelCompiler.compile.calls.count()).toBe(1); + }) + }); + + describe('when the given file is coffee-script', () => { + it('compiles the file with coffee-script and caches it', function () { + CompileCache.addPathToCache(path.join(fixtures, 'coffee.coffee'), atomHome); + expect(CompileCache.getCacheStats()['.coffee']).toEqual({hits: 0, misses: 1}); + expect(CoffeeScript.compile.calls.count()).toBe(1); + + CompileCache.addPathToCache(path.join(fixtures, 'coffee.coffee'), atomHome); + expect(CompileCache.getCacheStats()['.coffee']).toEqual({hits: 1, misses: 1}); + expect(CoffeeScript.compile.calls.count()).toBe(1); + }) + }); + + describe('when the given file is typescript', () => { + it('compiles the file with typescript and caches it', function () { + CompileCache.addPathToCache(path.join(fixtures, 'typescript', 'valid.ts'), atomHome); + expect(CompileCache.getCacheStats()['.ts']).toEqual({hits: 0, misses: 1}); + expect(TypeScriptSimple.prototype.compile.calls.count()).toBe(1); + + CompileCache.addPathToCache(path.join(fixtures, 'typescript', 'valid.ts'), atomHome); + expect(CompileCache.getCacheStats()['.ts']).toEqual({hits: 1, misses: 1}); + expect(TypeScriptSimple.prototype.compile.calls.count()).toBe(1); + }) + }); + + describe('when the given file is CSON', () => { + it('compiles the file to JSON and caches it', function () { + spyOn(CSON, 'setCacheDir').and.callThrough(); + spyOn(CSON, 'readFileSync').and.callThrough(); + + CompileCache.addPathToCache(path.join(fixtures, 'cson.cson'), atomHome); + expect(CSON.readFileSync).toHaveBeenCalledWith(path.join(fixtures, 'cson.cson')); + expect(CSON.setCacheDir).toHaveBeenCalledWith(path.join(atomHome, '/compile-cache')); + + CSON.readFileSync.calls.reset(); + CSON.setCacheDir.calls.reset(); + CompileCache.addPathToCache(path.join(fixtures, 'cson.cson'), atomHome); + expect(CSON.readFileSync).toHaveBeenCalledWith(path.join(fixtures, 'cson.cson')); + expect(CSON.setCacheDir).not.toHaveBeenCalled(); + }) + }); }); - return describe('overriding Error.prepareStackTrace', function() { - it('removes the override on the next tick, and always assigns the raw stack', function() { - if (process.platform === 'win32') { return; } // Flakey Error.stack contents on Win32 + describe('overriding Error.prepareStackTrace', function() { + it('removes the override on the next tick, and always assigns the raw stack', async function(done) { + jasmine.filterByPlatform({except: ['win32']}, done); // Flakey Error.stack contents on Win32 Error.prepareStackTrace = () => 'a-stack-trace'; @@ -102,12 +112,16 @@ describe('CompileCache', () => { expect(error.stack).toBe('a-stack-trace'); expect(Array.isArray(error.getRawStack())).toBe(true); - waits(1); - return runs(function() { - error = new Error("Oops again"); - expect(error.stack).toContain('compile-cache-spec.js'); - return expect(Array.isArray(error.getRawStack())).toBe(true); + await new Promise((resolve) => { + jasmine.unspy(window, 'setTimeout'); + setTimeout(resolve, 1) }); + + error = new Error("Oops again"); + expect(error.stack).toContain('compile-cache-spec.js'); + expect(Array.isArray(error.getRawStack())).toBe(true); + + done(); }); it('does not infinitely loop when the original prepareStackTrace value is reassigned', function() { @@ -118,10 +132,10 @@ describe('CompileCache', () => { const error = new Error('Oops'); expect(error.stack).toContain('compile-cache-spec.js'); - return expect(Array.isArray(error.getRawStack())).toBe(true); + expect(Array.isArray(error.getRawStack())).toBe(true); }); - return it('does not infinitely loop when the assigned prepareStackTrace calls the original prepareStackTrace', function() { + it('does not infinitely loop when the assigned prepareStackTrace calls the original prepareStackTrace', function() { const originalPrepareStackTrace = Error.prepareStackTrace; Error.prepareStackTrace = function(error, stack) { @@ -132,7 +146,7 @@ describe('CompileCache', () => { const error = new Error('Oops'); expect(error.stack).toContain('compile-cache-spec.js'); expect(error.foo).toBe('bar'); - return expect(Array.isArray(error.getRawStack())).toBe(true); + expect(Array.isArray(error.getRawStack())).toBe(true); }); }); }); diff --git a/spec/config-spec.js b/spec/config-spec.js index f292542f93..9a06fd4cec 100644 --- a/spec/config-spec.js +++ b/spec/config-spec.js @@ -756,16 +756,16 @@ describe('Config', () => { newValue: 'value 2', oldValue: 'value 1' }); - observeHandler.reset(); - observeHandler.andCallFake(() => { + observeHandler.calls.reset(); + observeHandler.and.callFake(() => { throw new Error('oops'); }); - expect(() => atom.config.set('foo.bar.baz', 'value 1')).toThrow('oops'); + expect(() => atom.config.set('foo.bar.baz', 'value 1')).toThrowError('oops'); expect(observeHandler).toHaveBeenCalledWith({ newValue: 'value 1', oldValue: 'value 2' }); - observeHandler.reset(); + observeHandler.calls.reset(); // Regression: exception in earlier handler shouldn't put observer // into a bad state. @@ -785,33 +785,33 @@ describe('Config', () => { expect(observeHandler).not.toHaveBeenCalled()); it('fires the callback every time any value changes', () => { - observeHandler.reset(); // clear the initial call + observeHandler.calls.reset(); // clear the initial call atom.config.set('foo.bar.baz', 'value 2'); expect(observeHandler).toHaveBeenCalled(); - expect(observeHandler.mostRecentCall.args[0].newValue.foo.bar.baz).toBe( + expect(observeHandler.calls.mostRecent().args[0].newValue.foo.bar.baz).toBe( 'value 2' ); - expect(observeHandler.mostRecentCall.args[0].oldValue.foo.bar.baz).toBe( + expect(observeHandler.calls.mostRecent().args[0].oldValue.foo.bar.baz).toBe( 'value 1' ); - observeHandler.reset(); + observeHandler.calls.reset(); atom.config.set('foo.bar.baz', 'value 1'); expect(observeHandler).toHaveBeenCalled(); - expect(observeHandler.mostRecentCall.args[0].newValue.foo.bar.baz).toBe( + expect(observeHandler.calls.mostRecent().args[0].newValue.foo.bar.baz).toBe( 'value 1' ); - expect(observeHandler.mostRecentCall.args[0].oldValue.foo.bar.baz).toBe( + expect(observeHandler.calls.mostRecent().args[0].oldValue.foo.bar.baz).toBe( 'value 2' ); - observeHandler.reset(); + observeHandler.calls.reset(); atom.config.set('foo.bar.int', 1); expect(observeHandler).toHaveBeenCalled(); - expect(observeHandler.mostRecentCall.args[0].newValue.foo.bar.int).toBe( + expect(observeHandler.calls.mostRecent().args[0].newValue.foo.bar.int).toBe( 1 ); - expect(observeHandler.mostRecentCall.args[0].oldValue.foo.bar.int).toBe( + expect(observeHandler.calls.mostRecent().args[0].oldValue.foo.bar.int).toBe( undefined ); }); @@ -831,42 +831,42 @@ describe('Config', () => { oldValue: undefined, newValue: 12 }); - changeSpy.reset(); + changeSpy.calls.reset(); atom.config.set('foo.bar.baz', 22, { scopeSelector: '.source .string.quoted.double', source: 'a' }); expect(changeSpy).toHaveBeenCalledWith({ oldValue: 12, newValue: 22 }); - changeSpy.reset(); + changeSpy.calls.reset(); atom.config.set('foo.bar.baz', 42, { scopeSelector: '.source.coffee .string.quoted.double.coffee', source: 'b' }); expect(changeSpy).toHaveBeenCalledWith({ oldValue: 22, newValue: 42 }); - changeSpy.reset(); + changeSpy.calls.reset(); atom.config.unset(null, { scopeSelector: '.source.coffee .string.quoted.double.coffee', source: 'b' }); expect(changeSpy).toHaveBeenCalledWith({ oldValue: 42, newValue: 22 }); - changeSpy.reset(); + changeSpy.calls.reset(); atom.config.unset(null, { scopeSelector: '.source .string.quoted.double', source: 'a' }); expect(changeSpy).toHaveBeenCalledWith({ oldValue: 22, newValue: 12 }); - changeSpy.reset(); + changeSpy.calls.reset(); atom.config.set('foo.bar.baz', undefined); expect(changeSpy).toHaveBeenCalledWith({ oldValue: 12, newValue: undefined }); - changeSpy.reset(); + changeSpy.calls.reset(); })); }); @@ -883,38 +883,38 @@ describe('Config', () => { expect(observeHandler).toHaveBeenCalledWith('value 1')); it('fires the callback every time the observed value changes', () => { - observeHandler.reset(); // clear the initial call + observeHandler.calls.reset(); // clear the initial call atom.config.set('foo.bar.baz', 'value 2'); expect(observeHandler).toHaveBeenCalledWith('value 2'); - observeHandler.reset(); + observeHandler.calls.reset(); atom.config.set('foo.bar.baz', 'value 1'); expect(observeHandler).toHaveBeenCalledWith('value 1'); advanceClock(100); // complete pending save that was requested in ::set - observeHandler.reset(); + observeHandler.calls.reset(); atom.config.resetUserSettings({ foo: {} }); expect(observeHandler).toHaveBeenCalledWith(undefined); }); it('fires the callback when the observed value is deleted', () => { - observeHandler.reset(); // clear the initial call + observeHandler.calls.reset(); // clear the initial call atom.config.set('foo.bar.baz', undefined); expect(observeHandler).toHaveBeenCalledWith(undefined); }); it('fires the callback when the full key path goes into and out of existence', () => { - observeHandler.reset(); // clear the initial call + observeHandler.calls.reset(); // clear the initial call atom.config.set('foo.bar', undefined); expect(observeHandler).toHaveBeenCalledWith(undefined); - observeHandler.reset(); + observeHandler.calls.reset(); atom.config.set('foo.bar.baz', "i'm back"); expect(observeHandler).toHaveBeenCalledWith("i'm back"); }); it('does not fire the callback once the subscription is disposed', () => { - observeHandler.reset(); // clear the initial call + observeHandler.calls.reset(); // clear the initial call observeSubscription.dispose(); atom.config.set('foo.bar.baz', 'value 2'); expect(observeHandler).not.toHaveBeenCalled(); @@ -927,7 +927,7 @@ describe('Config', () => { bazCatHandler ); - bazCatHandler.reset(); + bazCatHandler.calls.reset(); atom.config.set('foo.bar.baz', 'value 10'); expect(bazCatHandler).not.toHaveBeenCalled(); }); @@ -965,43 +965,43 @@ describe('Config', () => { changeSpy ); expect(changeSpy).toHaveBeenCalledWith('value 1'); - changeSpy.reset(); + changeSpy.calls.reset(); atom.config.set('foo.bar.baz', 12); expect(changeSpy).toHaveBeenCalledWith(12); - changeSpy.reset(); + changeSpy.calls.reset(); atom.config.set('foo.bar.baz', 22, { scopeSelector: '.source .string.quoted.double', source: 'a' }); expect(changeSpy).toHaveBeenCalledWith(22); - changeSpy.reset(); + changeSpy.calls.reset(); atom.config.set('foo.bar.baz', 42, { scopeSelector: '.source.coffee .string.quoted.double.coffee', source: 'b' }); expect(changeSpy).toHaveBeenCalledWith(42); - changeSpy.reset(); + changeSpy.calls.reset(); atom.config.unset(null, { scopeSelector: '.source.coffee .string.quoted.double.coffee', source: 'b' }); expect(changeSpy).toHaveBeenCalledWith(22); - changeSpy.reset(); + changeSpy.calls.reset(); atom.config.unset(null, { scopeSelector: '.source .string.quoted.double', source: 'a' }); expect(changeSpy).toHaveBeenCalledWith(12); - changeSpy.reset(); + changeSpy.calls.reset(); atom.config.set('foo.bar.baz', undefined); expect(changeSpy).toHaveBeenCalledWith(undefined); - changeSpy.reset(); + changeSpy.calls.reset(); }); }); }); @@ -1021,8 +1021,8 @@ describe('Config', () => { atom.config.set('foo.bar.baz', 3); }); - expect(changeSpy.callCount).toBe(1); - expect(changeSpy.argsForCall[0][0]).toEqual({ + expect(changeSpy.calls.count()).toBe(1); + expect(changeSpy.calls.argsFor(0)[0]).toEqual({ newValue: 3, oldValue: undefined }); @@ -1042,7 +1042,7 @@ describe('Config', () => { atom.config.onDidChange('foo.bar.baz', changeSpy); }); - it('allows only one change event for the duration of the given promise if it gets resolved', () => { + it('allows only one change event for the duration of the given promise if it gets resolved', async () => { let promiseResult = null; const transactionPromise = atom.config.transactAsync(() => { atom.config.set('foo.bar.baz', 1); @@ -1051,23 +1051,17 @@ describe('Config', () => { return Promise.resolve('a result'); }); - waitsForPromise(() => - transactionPromise.then(result => { - promiseResult = result; - }) - ); + promiseResult = await transactionPromise; - runs(() => { - expect(promiseResult).toBe('a result'); - expect(changeSpy.callCount).toBe(1); - expect(changeSpy.argsForCall[0][0]).toEqual({ - newValue: 3, - oldValue: undefined - }); + expect(promiseResult).toBe('a result'); + expect(changeSpy.calls.count()).toBe(1); + expect(changeSpy.calls.argsFor(0)[0]).toEqual({ + newValue: 3, + oldValue: undefined }); }); - it('allows only one change event for the duration of the given promise if it gets rejected', () => { + it('allows only one change event for the duration of the given promise if it gets rejected', async () => { let promiseError = null; const transactionPromise = atom.config.transactAsync(() => { atom.config.set('foo.bar.baz', 1); @@ -1076,23 +1070,19 @@ describe('Config', () => { return Promise.reject(new Error('an error')); }); - waitsForPromise(() => - transactionPromise.catch(error => { - promiseError = error; - }) - ); + await transactionPromise.catch(error => { + promiseError = error; + }); - runs(() => { - expect(promiseError.message).toBe('an error'); - expect(changeSpy.callCount).toBe(1); - expect(changeSpy.argsForCall[0][0]).toEqual({ - newValue: 3, - oldValue: undefined - }); + expect(promiseError.message).toBe('an error'); + expect(changeSpy.calls.count()).toBe(1); + expect(changeSpy.calls.argsFor(0)[0]).toEqual({ + newValue: 3, + oldValue: undefined }); }); - it('allows only one change event even when the given callback throws', () => { + it('allows only one change event even when the given callback throws', async () => { const error = new Error('Oops!'); let promiseError = null; const transactionPromise = atom.config.transactAsync(() => { @@ -1102,19 +1092,15 @@ describe('Config', () => { throw error; }); - waitsForPromise(() => - transactionPromise.catch(e => { - promiseError = e; - }) - ); + await transactionPromise.catch(e => { + promiseError = e; + }); - runs(() => { - expect(promiseError).toBe(error); - expect(changeSpy.callCount).toBe(1); - expect(changeSpy.argsForCall[0][0]).toEqual({ - newValue: 3, - oldValue: undefined - }); + expect(promiseError).toBe(error); + expect(changeSpy.calls.count()).toBe(1); + expect(changeSpy.calls.argsFor(0)[0]).toEqual({ + newValue: 3, + oldValue: undefined }); }); }); @@ -1189,7 +1175,7 @@ describe('Config', () => { atom.config.save(); const writtenConfig = savedSettings[0]; - expect(writtenConfig).toEqualJson({ + expect(writtenConfig).toEqual({ '*': atom.config.settings, '.ruby.source': { foo: { @@ -1296,7 +1282,7 @@ describe('Config', () => { expect(atom.config.get('foo.bar')).toBe('baz'); expect(atom.config.get('foo.int')).toBe(12); expect(console.warn).toHaveBeenCalled(); - expect(console.warn.mostRecentCall.args[0]).toContain('foo.int'); + expect(console.warn.calls.mostRecent().args[0]).toContain('foo.int'); }); }); @@ -1387,7 +1373,7 @@ describe('Config', () => { atom.config.set('foo.bar.baz', ['a']); const observeHandler = jasmine.createSpy('observeHandler'); atom.config.observe('foo.bar.baz', observeHandler); - observeHandler.reset(); + observeHandler.calls.reset(); expect(atom.config.pushAtKeyPath('foo.bar.baz', 'b')).toBe(2); expect(atom.config.get('foo.bar.baz')).toEqual(['a', 'b']); @@ -1402,7 +1388,7 @@ describe('Config', () => { atom.config.set('foo.bar.baz', ['b']); const observeHandler = jasmine.createSpy('observeHandler'); atom.config.observe('foo.bar.baz', observeHandler); - observeHandler.reset(); + observeHandler.calls.reset(); expect(atom.config.unshiftAtKeyPath('foo.bar.baz', 'a')).toBe(2); expect(atom.config.get('foo.bar.baz')).toEqual(['a', 'b']); @@ -1417,7 +1403,7 @@ describe('Config', () => { atom.config.set('foo.bar.baz', ['a', 'b', 'c']); const observeHandler = jasmine.createSpy('observeHandler'); atom.config.observe('foo.bar.baz', observeHandler); - observeHandler.reset(); + observeHandler.calls.reset(); expect(atom.config.removeAtKeyPath('foo.bar.baz', 'b')).toEqual([ 'a', @@ -1446,9 +1432,9 @@ describe('Config', () => { it('emits an updated event', () => { const updatedCallback = jasmine.createSpy('updated'); atom.config.onDidChange('foo.bar.baz.a', updatedCallback); - expect(updatedCallback.callCount).toBe(0); + expect(updatedCallback.calls.count()).toBe(0); atom.config.setDefaults('foo.bar.baz', { a: 2 }); - expect(updatedCallback.callCount).toBe(1); + expect(updatedCallback.calls.count()).toBe(1); }); }); diff --git a/spec/decoration-manager-spec.js b/spec/decoration-manager-spec.js index 93ba15ddb8..fef8f67113 100644 --- a/spec/decoration-manager-spec.js +++ b/spec/decoration-manager-spec.js @@ -2,16 +2,16 @@ const DecorationManager = require('../src/decoration-manager'); const TextEditor = require('../src/text-editor'); describe('DecorationManager', function() { - let [decorationManager, buffer, editor, markerLayer1, markerLayer2] = []; + let decorationManager, buffer, editor, markerLayer1, markerLayer2; - beforeEach(function() { + beforeEach(async function() { buffer = atom.project.bufferForPathSync('sample.js'); editor = new TextEditor({ buffer }); markerLayer1 = editor.addMarkerLayer(); markerLayer2 = editor.addMarkerLayer(); decorationManager = new DecorationManager(editor); - waitsForPromise(() => atom.packages.activatePackage('language-javascript')); + await atom.packages.activatePackage('language-javascript'); }); afterEach(() => buffer.destroy()); @@ -68,7 +68,7 @@ describe('DecorationManager', function() { type: 'overlay', item: document.createElement('div') }) - ).toThrow('Cannot decorate a destroyed marker'); + ).toThrowError('Cannot decorate a destroyed marker'); expect(decorationManager.getOverlayDecorations()).toEqual([]); }); @@ -77,7 +77,7 @@ describe('DecorationManager', function() { layer.destroy(); expect(() => decorationManager.decorateMarkerLayer(layer, { type: 'highlight' }) - ).toThrow('Cannot decorate a destroyed marker layer'); + ).toThrowError('Cannot decorate a destroyed marker layer'); }); describe('when a decoration is updated via Decoration::update()', () => @@ -94,7 +94,7 @@ describe('DecorationManager', function() { const { oldProperties, newProperties - } = updatedSpy.mostRecentCall.args[0]; + } = updatedSpy.calls.mostRecent().args[0]; expect(oldProperties).toEqual(decorationProperties); expect(newProperties.type).toBe('line-number'); expect(newProperties.gutterName).toBe('line-number'); diff --git a/spec/default-directory-provider-spec.js b/spec/default-directory-provider-spec.js index 8ebd1b6402..0691938a4b 100644 --- a/spec/default-directory-provider-spec.js +++ b/spec/default-directory-provider-spec.js @@ -33,7 +33,9 @@ describe('DefaultDirectoryProvider', function() { expect(directory.getPath()).toEqual(tmp); }); - it('normalizes disk drive letter in path on #win32', function() { + it('normalizes disk drive letter in path on win32', function(done) { + jasmine.filterByPlatform({only: ['win32']}, done); + const provider = new DefaultDirectoryProvider(); const nonNormalizedPath = tmp[0].toLowerCase() + tmp.slice(1); expect(tmp).not.toMatch(/^[a-z]:/); @@ -41,6 +43,8 @@ describe('DefaultDirectoryProvider', function() { const directory = provider.directoryForURISync(nonNormalizedPath); expect(directory.getPath()).toEqual(tmp); + + done() }); it('creates a Directory for its parent dir when passed a file', function() { @@ -61,13 +65,10 @@ describe('DefaultDirectoryProvider', function() { }); describe('.directoryForURI(uri)', () => - it('returns a Promise that resolves to a Directory with a path that matches the uri', function() { + it('returns a Promise that resolves to a Directory with a path that matches the uri', async function() { const provider = new DefaultDirectoryProvider(); - waitsForPromise(() => - provider - .directoryForURI(tmp) - .then(directory => expect(directory.getPath()).toEqual(tmp)) - ); + let directory = await provider.directoryForURI(tmp); + expect(directory.getPath()).toEqual(tmp); })); }); diff --git a/spec/default-directory-searcher-spec.js b/spec/default-directory-searcher-spec.js index 9526682460..715a34e474 100644 --- a/spec/default-directory-searcher-spec.js +++ b/spec/default-directory-searcher-spec.js @@ -23,7 +23,7 @@ describe('DefaultDirectorySearcher', function() { didSearchPaths() {} }; - spyOn(Task.prototype, 'terminate').andCallThrough(); + spyOn(Task.prototype, 'terminate').and.callThrough(); await searcher.search( [ diff --git a/spec/dock-spec.js b/spec/dock-spec.js index 6afc780b21..8a8b6c4870 100644 --- a/spec/dock-spec.js +++ b/spec/dock-spec.js @@ -37,7 +37,7 @@ describe('Dock', () => { dock.activate(); expect(document.activeElement).toBe(dock.getActivePane().getElement()); - expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(true); + expect(didChangeVisibleSpy.calls.mostRecent().args[0]).toBe(true); dock.hide(); expect(document.activeElement).toBe( @@ -46,11 +46,11 @@ describe('Dock', () => { .getActivePane() .getElement() ); - expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(false); + expect(didChangeVisibleSpy.calls.mostRecent().args[0]).toBe(false); dock.activate(); expect(document.activeElement).toBe(dock.getActivePane().getElement()); - expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(true); + expect(didChangeVisibleSpy.calls.mostRecent().args[0]).toBe(true); dock.toggle(); expect(document.activeElement).toBe( @@ -59,7 +59,7 @@ describe('Dock', () => { .getActivePane() .getElement() ); - expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(false); + expect(didChangeVisibleSpy.calls.mostRecent().args[0]).toBe(false); // Don't change focus if the dock was not focused in the first place const modalElement = document.createElement('div'); @@ -70,11 +70,11 @@ describe('Dock', () => { dock.show(); expect(document.activeElement).toBe(modalElement); - expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(true); + expect(didChangeVisibleSpy.calls.mostRecent().args[0]).toBe(true); dock.hide(); expect(document.activeElement).toBe(modalElement); - expect(didChangeVisibleSpy.mostRecentCall.args[0]).toBe(false); + expect(didChangeVisibleSpy.calls.mostRecent().args[0]).toBe(false); }); }); @@ -422,7 +422,7 @@ describe('Dock', () => { spyOn(Grim, 'deprecate'); atom.workspace.getLeftDock().getActiveTextEditor(); - expect(Grim.deprecate.callCount).toBe(1); + expect(Grim.deprecate.calls.count()).toBe(1); }); }); }); diff --git a/spec/git-repository-provider-spec.js b/spec/git-repository-provider-spec.js index 80e6717f97..413dfe67e1 100644 --- a/spec/git-repository-provider-spec.js +++ b/spec/git-repository-provider-spec.js @@ -31,7 +31,7 @@ describe('GitRepositoryProvider', () => { path.join(__dirname, 'fixtures', 'git', 'master.git') ); const result = await provider.repositoryForDirectory(directory); - expect(result).toBeInstanceOf(GitRepository); + expect(result).toEqual(jasmine.any(GitRepository)); expect(provider.pathToRepository[result.getPath()]).toBeTruthy(); expect(result.getType()).toBe('git'); @@ -49,7 +49,7 @@ describe('GitRepositoryProvider', () => { ) ); - expect(firstRepo).toBeInstanceOf(GitRepository); + expect(firstRepo).toEqual(jasmine.any(GitRepository)); expect(firstRepo).toBe(secondRepo); }); }); @@ -91,7 +91,7 @@ describe('GitRepositoryProvider', () => { const directory = new Directory(workDirPath); const result = await provider.repositoryForDirectory(directory); - expect(result).toBeInstanceOf(GitRepository); + expect(result).toEqual(jasmine.any(GitRepository)); expect(provider.pathToRepository[result.getPath()]).toBeTruthy(); expect(result.getType()).toBe('git'); }); @@ -110,7 +110,7 @@ describe('GitRepositoryProvider', () => { ) ); const result = await provider.repositoryForDirectory(directory); - expect(result).toBeInstanceOf(GitRepository); + expect(result).toEqual(jasmine.any(GitRepository)); expect(provider.pathToRepository[result.getPath()]).toBeTruthy(); expect(result.getType()).toBe('git'); }); @@ -128,7 +128,7 @@ describe('GitRepositoryProvider', () => { return true; } }; - spyOn(directory, 'getSubdirectory').andReturn(subdirectory); + spyOn(directory, 'getSubdirectory').and.returnValue(subdirectory); }); it('returns a Promise that resolves to null', async () => { @@ -146,7 +146,7 @@ describe('GitRepositoryProvider', () => { path.join(__dirname, 'fixtures', 'git', 'master.git') ); const result = provider.repositoryForDirectorySync(directory); - expect(result).toBeInstanceOf(GitRepository); + expect(result).toEqual(jasmine.any(GitRepository)); expect(provider.pathToRepository[result.getPath()]).toBeTruthy(); expect(result.getType()).toBe('git'); @@ -164,7 +164,7 @@ describe('GitRepositoryProvider', () => { ) ); - expect(firstRepo).toBeInstanceOf(GitRepository); + expect(firstRepo).toEqual(jasmine.any(GitRepository)); expect(firstRepo).toBe(secondRepo); }); }); @@ -206,7 +206,7 @@ describe('GitRepositoryProvider', () => { const directory = new Directory(workDirPath); const result = provider.repositoryForDirectorySync(directory); - expect(result).toBeInstanceOf(GitRepository); + expect(result).toEqual(jasmine.any(GitRepository)); expect(provider.pathToRepository[result.getPath()]).toBeTruthy(); expect(result.getType()).toBe('git'); }); @@ -225,7 +225,7 @@ describe('GitRepositoryProvider', () => { ) ); const result = provider.repositoryForDirectorySync(directory); - expect(result).toBeInstanceOf(GitRepository); + expect(result).toEqual(jasmine.any(GitRepository)); expect(provider.pathToRepository[result.getPath()]).toBeTruthy(); expect(result.getType()).toBe('git'); }); @@ -243,7 +243,7 @@ describe('GitRepositoryProvider', () => { return true; } }; - spyOn(directory, 'getSubdirectory').andReturn(subdirectory); + spyOn(directory, 'getSubdirectory').and.returnValue(subdirectory); }); it('returns null', () => { diff --git a/spec/git-repository-spec.js b/spec/git-repository-spec.js index 1a3401c9fb..3adba8945e 100644 --- a/spec/git-repository-spec.js +++ b/spec/git-repository-spec.js @@ -148,14 +148,14 @@ describe('GitRepository', () => { const statusHandler = jasmine.createSpy('statusHandler'); repo.onDidChangeStatus(statusHandler); repo.checkoutHead(filePath); - expect(statusHandler.callCount).toBe(1); - expect(statusHandler.argsForCall[0][0]).toEqual({ + expect(statusHandler.calls.count()).toBe(1); + expect(statusHandler.calls.argsFor(0)[0]).toEqual({ path: filePath, pathStatus: 0 }); repo.checkoutHead(filePath); - expect(statusHandler.callCount).toBe(1); + expect(statusHandler.calls.count()).toBe(1); }); }); @@ -177,27 +177,30 @@ describe('GitRepository', () => { editor = await atom.workspace.open(filePath); }); - it('displays a confirmation dialog by default', () => { - // Permissions issues with this test on Windows - if (process.platform === 'win32') return; + it('displays a confirmation dialog by default', (done) => { + jasmine.filterByPlatform({except: ['win32']}, done); // Permissions issues with this test on Windows - atom.confirm.andCallFake(({ buttons }) => buttons.OK()); + atom.confirm.and.callFake(({ buttons }) => buttons.OK()); atom.config.set('editor.confirmCheckoutHeadRevision', true); repo.checkoutHeadForEditor(editor); expect(fs.readFileSync(filePath, 'utf8')).toBe(''); + + done(); }); - it('does not display a dialog when confirmation is disabled', () => { - // Flakey EPERM opening a.txt on Win32 - if (process.platform === 'win32') return; + it('does not display a dialog when confirmation is disabled', (done) => { + jasmine.filterByPlatform({except: ['win32']}, done); // Flakey EPERM opening a.txt on Win32 + atom.config.set('editor.confirmCheckoutHeadRevision', false); repo.checkoutHeadForEditor(editor); expect(fs.readFileSync(filePath, 'utf8')).toBe(''); expect(atom.confirm).not.toHaveBeenCalled(); + + done(); }); }); @@ -225,15 +228,15 @@ describe('GitRepository', () => { repo.onDidChangeStatus(statusHandler); fs.writeFileSync(filePath, ''); let status = repo.getPathStatus(filePath); - expect(statusHandler.callCount).toBe(1); - expect(statusHandler.argsForCall[0][0]).toEqual({ + expect(statusHandler.calls.count()).toBe(1); + expect(statusHandler.calls.argsFor(0)[0]).toEqual({ path: filePath, pathStatus: status }); fs.writeFileSync(filePath, 'abc'); status = repo.getPathStatus(filePath); - expect(statusHandler.callCount).toBe(1); + expect(statusHandler.calls.count()).toBe(1); }); }); @@ -282,7 +285,7 @@ describe('GitRepository', () => { fs.writeFileSync(modifiedPath, 'making this path modified'); await repo.refreshStatus(); - expect(statusHandler.callCount).toBe(1); + expect(statusHandler.calls.count()).toBe(1); expect(repo.getCachedPathStatus(cleanPath)).toBeUndefined(); expect(repo.isStatusNew(repo.getCachedPathStatus(newPath))).toBeTruthy(); expect( @@ -349,7 +352,7 @@ describe('GitRepository', () => { atom.project.getRepositories()[0].onDidChangeStatus(statusHandler); await editor.save(); - expect(statusHandler.callCount).toBe(1); + expect(statusHandler.calls.count()).toBe(1); expect(statusHandler).toHaveBeenCalledWith({ path: editor.getPath(), pathStatus: 256 @@ -363,14 +366,14 @@ describe('GitRepository', () => { atom.project.getRepositories()[0].onDidChangeStatus(statusHandler); await editor.getBuffer().reload(); - expect(statusHandler.callCount).toBe(1); + expect(statusHandler.calls.count()).toBe(1); expect(statusHandler).toHaveBeenCalledWith({ path: editor.getPath(), pathStatus: 256 }); await editor.getBuffer().reload(); - expect(statusHandler.callCount).toBe(1); + expect(statusHandler.calls.count()).toBe(1); }); it("emits a status-changed event when a buffer's path changes", () => { @@ -379,13 +382,13 @@ describe('GitRepository', () => { const statusHandler = jasmine.createSpy('statusHandler'); atom.project.getRepositories()[0].onDidChangeStatus(statusHandler); editor.getBuffer().emitter.emit('did-change-path'); - expect(statusHandler.callCount).toBe(1); + expect(statusHandler.calls.count()).toBe(1); expect(statusHandler).toHaveBeenCalledWith({ path: editor.getPath(), pathStatus: 256 }); editor.getBuffer().emitter.emit('did-change-path'); - expect(statusHandler.callCount).toBe(1); + expect(statusHandler.calls.count()).toBe(1); }); it('stops listening to the buffer when the repository is destroyed (regression)', () => { @@ -424,7 +427,7 @@ describe('GitRepository', () => { project2.getRepositories()[0].onDidChangeStatus(statusHandler); await buffer.save(); - expect(statusHandler.callCount).toBe(1); + expect(statusHandler.calls.count()).toBe(1); expect(statusHandler).toHaveBeenCalledWith({ path: buffer.getPath(), pathStatus: 256 diff --git a/spec/grammar-registry-spec.js b/spec/grammar-registry-spec.js index cae311ec8e..396227e990 100644 --- a/spec/grammar-registry-spec.js +++ b/spec/grammar-registry-spec.js @@ -520,7 +520,7 @@ describe('GrammarRegistry', () => { const filePath = require.resolve('./fixtures/shebang'); const filePathContents = fs.readFileSync(filePath, 'utf8'); - spyOn(fs, 'read').andCallThrough(); + spyOn(fs, 'read').and.callThrough(); expect(atom.grammars.selectGrammar(filePath, filePathContents).name).toBe( 'Ruby' ); diff --git a/spec/async-spec-helpers.js b/spec/helpers/async-spec-helpers.js similarity index 100% rename from spec/async-spec-helpers.js rename to spec/helpers/async-spec-helpers.js diff --git a/spec/helpers/attach-to-dom.js b/spec/helpers/attach-to-dom.js new file mode 100644 index 0000000000..cb8575b033 --- /dev/null +++ b/spec/helpers/attach-to-dom.js @@ -0,0 +1,4 @@ +jasmine.attachToDOM = function(element) { + const jasmineContent = document.querySelector('#jasmine-content'); + if (!jasmineContent.contains(element)) { jasmineContent.appendChild(element); } +}; diff --git a/spec/helpers/build-atom-environment.js b/spec/helpers/build-atom-environment.js new file mode 100644 index 0000000000..43f08863a0 --- /dev/null +++ b/spec/helpers/build-atom-environment.js @@ -0,0 +1,28 @@ +const fs = require('fs-plus'); +const temp = require('temp'); +const path = require('path'); + +const userHome = process.env.ATOM_HOME || path.join(fs.getHomeDirectory(), '.atom'); +const atomHome = temp.mkdirSync({prefix: 'atom-test-home-'}); +if (process.env.APM_TEST_PACKAGES) { + const testPackages = process.env.APM_TEST_PACKAGES.split(/\s+/); + fs.makeTreeSync(path.join(atomHome, 'packages')); + for (let packName of Array.from(testPackages)) { + const userPack = path.join(userHome, 'packages', packName); + const loadablePack = path.join(atomHome, 'packages', packName); + + try { + fs.symlinkSync(userPack, loadablePack, 'dir'); + } catch (error) { + fs.copySync(userPack, loadablePack); + } + } +} + +const ApplicationDelegate = require('../../src/application-delegate'); +const applicationDelegate = new ApplicationDelegate(); +applicationDelegate.setRepresentedFilename = function () {}; +applicationDelegate.setWindowDocumentEdited = function () {}; + +exports.atomHome = atomHome +exports.applicationDelegate = applicationDelegate; diff --git a/spec/helpers/default-timeout.js b/spec/helpers/default-timeout.js new file mode 100644 index 0000000000..e2a5901e0b --- /dev/null +++ b/spec/helpers/default-timeout.js @@ -0,0 +1,5 @@ +if (process.env.CI) { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 120000; +} else { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; +} diff --git a/spec/helpers/deprecation-snapshots.js b/spec/helpers/deprecation-snapshots.js new file mode 100644 index 0000000000..d8329f2de5 --- /dev/null +++ b/spec/helpers/deprecation-snapshots.js @@ -0,0 +1,15 @@ +const _ = require("underscore-plus"); +const Grim = require("grim"); + +let grimDeprecationsSnapshot = null; +let stylesDeprecationsSnapshot = null; + +jasmine.snapshotDeprecations = function() { + grimDeprecationsSnapshot = _.clone(Grim.deprecations); + return stylesDeprecationsSnapshot = _.clone(atom.styles.deprecationsBySourcePath); +}; + +jasmine.restoreDeprecationsSnapshot = function() { + Grim.deprecations = grimDeprecationsSnapshot; + return atom.styles.deprecationsBySourcePath = stylesDeprecationsSnapshot; +}; diff --git a/spec/helpers/document-title.js b/spec/helpers/document-title.js new file mode 100644 index 0000000000..63098a13db --- /dev/null +++ b/spec/helpers/document-title.js @@ -0,0 +1,12 @@ +// Allow document.title to be assigned in specs without screwing up spec window title +let documentTitle = null; + +Object.defineProperty(document, 'title', { + get() { + return documentTitle; + }, + set(title) { + return documentTitle = title; + } + } +); diff --git a/spec/helpers/fixture-packages.js b/spec/helpers/fixture-packages.js new file mode 100644 index 0000000000..242d3586e1 --- /dev/null +++ b/spec/helpers/fixture-packages.js @@ -0,0 +1,4 @@ +const path = require('path') + +const fixturePackagesPath = path.resolve(__dirname, '../fixtures/packages'); +atom.packages.packageDirPaths.unshift(fixturePackagesPath); diff --git a/spec/jasmine-list-reporter.js b/spec/helpers/jasmine-list-reporter.js similarity index 100% rename from spec/jasmine-list-reporter.js rename to spec/helpers/jasmine-list-reporter.js diff --git a/spec/helpers/jasmine-singleton.js b/spec/helpers/jasmine-singleton.js new file mode 100644 index 0000000000..f871baa00c --- /dev/null +++ b/spec/helpers/jasmine-singleton.js @@ -0,0 +1,16 @@ +let jasmine; + +jasmineVendor = require('../../vendor/jasmine'); +for (let key in jasmineVendor) { window[key] = jasmineVendor[key]; } + +jasmine = jasmineVendor.jasmine; + +require('jasmine-json'); + +if ( !jasmine.TerminalReporter ) { + const { jasmineNode} = require('jasmine-node/lib/jasmine-node/reporter'); + + jasmine.TerminalReporter = jasmineNode.TerminalReporter; +} + +module.exports = jasmine; diff --git a/spec/atom-reporter.js b/spec/helpers/jasmine1-atom-reporter.js similarity index 99% rename from spec/atom-reporter.js rename to spec/helpers/jasmine1-atom-reporter.js index d67d2a305a..553229b4ad 100644 --- a/spec/atom-reporter.js +++ b/spec/helpers/jasmine1-atom-reporter.js @@ -12,8 +12,8 @@ const path = require('path'); const process = require('process'); const _ = require('underscore-plus'); const grim = require('grim'); -const listen = require('../src/delegated-listener'); -const ipcHelpers = require('../src/ipc-helpers'); +const listen = require('../../src/delegated-listener'); +const ipcHelpers = require('../../src/ipc-helpers'); const formatStackTrace = function(spec, message, stackTrace) { if (message == null) { message = ''; } diff --git a/spec/spec-helper.js b/spec/helpers/jasmine1-spec-helper.js similarity index 96% rename from spec/spec-helper.js rename to spec/helpers/jasmine1-spec-helper.js index 6917c55301..16ef5fc6a7 100644 --- a/spec/spec-helper.js +++ b/spec/helpers/jasmine1-spec-helper.js @@ -8,9 +8,9 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md */ let specDirectory, specPackageName, specPackagePath, specProjectPath; -require('jasmine-json'); -require('../src/window'); -require('../vendor/jasmine-jquery'); +require('./jasmine-singleton'); +require('../../src/window'); +require('../../vendor/jasmine-jquery'); const path = require('path'); const _ = require('underscore-plus'); const fs = require('fs-plus'); @@ -19,18 +19,18 @@ const pathwatcher = require('pathwatcher'); const FindParentDir = require('find-parent-dir'); const {CompositeDisposable} = require('event-kit'); -const TextEditor = require('../src/text-editor'); -const TextEditorElement = require('../src/text-editor-element'); -const TextMateLanguageMode = require('../src/text-mate-language-mode'); -const TreeSitterLanguageMode = require('../src/tree-sitter-language-mode'); +const TextEditor = require('../../src/text-editor'); +const TextEditorElement = require('../../src/text-editor-element'); +const TextMateLanguageMode = require('../../src/text-mate-language-mode'); +const TreeSitterLanguageMode = require('../../src/tree-sitter-language-mode'); const {clipboard} = require('electron'); -const {mockDebounce} = require("./spec-helper-functions.js"); +const {mockDebounce} = require("./mock-debounce.js"); const jasmineStyle = document.createElement('style'); jasmineStyle.textContent = atom.themes.loadStylesheet(atom.themes.resolveStylesheet('../static/jasmine')); document.head.appendChild(jasmineStyle); -const fixturePackagesPath = path.resolve(__dirname, './fixtures/packages'); +const fixturePackagesPath = path.resolve(__dirname, '../fixtures/packages'); atom.packages.packageDirPaths.unshift(fixturePackagesPath); document.querySelector('html').style.overflow = 'auto'; diff --git a/spec/helpers/jasmine2-atom-reporter.js b/spec/helpers/jasmine2-atom-reporter.js new file mode 100644 index 0000000000..7b2e84853a --- /dev/null +++ b/spec/helpers/jasmine2-atom-reporter.js @@ -0,0 +1,371 @@ +const path = require('path'); +const process = require('process'); +const listen = require('../../src/delegated-listener'); +const ipcHelpers = require('../../src/ipc-helpers'); + +function formatStackTrace(spec, message = '', stackTrace) { + if (!stackTrace) { return stackTrace; } + + // at ... (.../jasmine.js:1:2) + const jasminePattern = /^\s*at\s+.*\(?.*[/\\]jasmine(-[^/\\]*)?\.js:\d+:\d+\)?\s*$/; + // at jasmine.Something... (.../jasmine.js:1:2) + const firstJasmineLinePattern = /^\s*at\s+jasmine\.[A-Z][^\s]*\s+\(?.*[/\\]jasmine(-[^/\\]*)?\.js:\d+:\d+\)?\s*$/; + let lines = []; + for (let line of stackTrace.split('\n')) { + if (firstJasmineLinePattern.test(line)) { break; } + if (!jasminePattern.test(line)) { lines.push(line); } + } + + // Remove first line of stack when it is the same as the error message + const errorMatch = lines[0]?.match(/^Error: (.*)/); + if (message.trim() === errorMatch?.[1]?.trim()) { lines.shift(); } + + lines = lines.map(function (line) { + // Only format actual stacktrace lines + if (/^\s*at\s/.test(line)) { + // Needs to occur before path relativization + if ((process.platform === 'win32') && /file:\/\/\//.test(line)) { + // file:///C:/some/file -> C:\some\file + line = line + .replace('file:///', '') + .replace(new RegExp(`${path.posix.sep}`, 'g'), path.win32.sep); + } + + line = line.trim() + // at jasmine.Spec. (path:1:2) -> at path:1:2 + .replace(/^at jasmine\.Spec\. \(([^)]+)\)/, 'at $1') + // at jasmine.Spec.it (path:1:2) -> at path:1:2 + .replace(/^at jasmine\.Spec\.f*it \(([^)]+)\)/, 'at $1') + // at it (path:1:2) -> at path:1:2 + .replace(/^at f*it \(([^)]+)\)/, 'at $1') + // at spec/file-test.js -> at file-test.js + .replace(spec.specDirectory + path.sep, ''); + } + + return line; + }); + + return lines.join('\n').trim(); +} + +// Spec objects in the reporter lifecycle don't have all the metadata we need. +// We'll store the full objects in this map, then look them up as needed by ID. +const REGISTRY = new Map(); + +class AtomReporter { + constructor() { + this.startedAt = null; + this.runningSpecCount = 0; + this.completeSpecCount = 0; + this.passedCount = 0; + this.failedCount = 0; + this.skippedCount = 0; + this.totalSpecCount = 0; + this.deprecationCount = 0; + this.timeoutId = 0; + this.element = document.createElement('div'); + this.element.classList.add('spec-reporter-container'); + this.element.innerHTML = `\ +
+
+ +
+
+
+
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    \ +`; + + for (let element of Array.from(this.element.querySelectorAll('[outlet]'))) { + this[element.getAttribute('outlet')] = element; + } + } + + jasmineStarted(_suiteInfo) { + let topSuite = jasmine.getEnv().topSuite(); + topSuite._isTopSuite = true; + this.specs = this.getSpecs(topSuite); + + this.handleEvents(); + this.startedAt = Date.now(); + this.totalSpecCount = Object.keys(this.specs).length; + + // Create summary dots for each test. + for (let spec of Object.values(this.specs)) { + const symbol = document.createElement('li'); + symbol.setAttribute('id', `spec-summary-${spec.id}`); + symbol.setAttribute('title', this.specTitle(spec)); + symbol.className = "spec-summary pending"; + this.userSummary.appendChild(symbol); + } + + document.body.appendChild(this.element); + } + + getSpecs(suite, specs = {}) { + for (const child of suite.children) { + if (child.children) { + specs = this.getSpecs(child, specs); + } else { + REGISTRY.set(child.id, child); + child.suite = suite; + child.suites = this.specSuites(child, suite); + child.title = this.specTitle(child); + specs[child.id] = child; + } + } + return specs; + } + + specSuites(spec, parentSuite) { + const suites = []; + spec.suite ??= parentSuite; + + let { suite } = spec; + while (suite.parentSuite) { + suites.unshift({ + id: suite.id, + description: suite.result.description + }); + suite = suite.parentSuite; + } + return suites; + } + + suiteStarted(_result) {} + + specStarted(_spec) { + this.runningSpecCount++; + } + + jasmineDone() { + this.updateSpecCounts(); + if (this.failedCount === 0) { + this.status.classList.add('alert-success'); + this.status.classList.remove('alert-info'); + } + + if (this.failedCount === 1) { + this.message.textContent = `${this.failedCount} failure`; + } else { + this.message.textContent = `${this.failedCount} failures`; + } + } + + handleEvents() { + listen(document, 'click', '.spec-toggle', function (event) { + const specFailures = event.currentTarget.parentElement.querySelector('.spec-failures'); + + if (specFailures.style.display === 'none') { + specFailures.style.display = ''; + event.currentTarget.classList.remove('folded'); + } else { + specFailures.style.display = 'none'; + event.currentTarget.classList.add('folded'); + } + + event.preventDefault(); + }); + + listen(document, 'click', '.deprecation-list', function (event) { + const deprecationList = event.currentTarget.parentElement.querySelector('.deprecation-list'); + + if (deprecationList.style.display === 'none') { + deprecationList.style.display = ''; + event.currentTarget.classList.remove('folded'); + } else { + deprecationList.style.display = 'none'; + event.currentTarget.classList.add('folded'); + } + + event.preventDefault(); + }); + + listen(document, 'click', '.stack-trace', event => event.currentTarget.classList.toggle('expanded')); + + this.reloadButton.addEventListener('click', () => ipcHelpers.call('window-method', 'reload')); + } + + updateSpecCounts() { + let specCount; + if (this.skippedCount) { + specCount = `${this.completeSpecCount - this.skippedCount}/${this.totalSpecCount - this.skippedCount} (${this.skippedCount} skipped)`; + } else { + specCount = `${this.completeSpecCount}/${this.totalSpecCount}`; + } + this.specCount.textContent = specCount; + } + + updateStatusView(spec) { + if (this.failedCount > 0) { + this.status.classList.add('alert-danger'); + this.status.classList.remove('alert-info'); + } + let fullSpec = REGISTRY.get(spec.id); + + this.updateSpecCounts(); + + let rootSuite = fullSpec.suite; + while (rootSuite.parentSuite) { + if (rootSuite.parentSuite._isTopSuite) break; + rootSuite = rootSuite.parentSuite; + } + this.message.textContent = rootSuite.description; + + let time = `${Math.round((spec.endedAt - this.startedAt) / 10)}`; + if (time.length < 3) { time = `0${time}`; } + this.time.textContent = `${time.slice(0, -2)}.${time.slice(-2)}s`; + } + + specTitle(spec) { + const parentDescs = []; + let s = spec.suite; + while (s && !s._isTopSuite) { + parentDescs.unshift(s.description); + s = s.parentSuite; + } + + let suiteString = ""; + let indent = ""; + for (let desc of parentDescs) { + suiteString += indent + desc + "\n"; + indent += " "; + } + + return `${suiteString} ${indent} it ${spec.description}`; + } + + suiteDone(_suite) {} + + specDone(spec) { + const specSummaryElement = document.getElementById(`spec-summary-${spec.id}`); + if (!specSummaryElement) { + console.warn(`Does not exist:`, spec.id); + return; + } + specSummaryElement.classList.remove('pending'); + switch (spec.status) { + case 'disabled': + specSummaryElement.classList.add('skipped'); + this.skippedCount++; + break; + case 'failed': { + specSummaryElement.classList.add('failed'); + const specView = new SpecResultView(spec); + specView.attach(); + this.failedCount++; + break; + } + case 'passed': + specSummaryElement.classList.add('passed'); + this.passedCount++; + break; + default: + // no-op + } + + this.completeSpecCount++; + spec.endedAt = Date.now(); + if (spec.status !== 'disabled') { + this.updateStatusView(spec); + } + } +} + +module.exports = AtomReporter; + +class SuiteResultView { + constructor(suite) { + this.suite = suite; + this.element = document.createElement('div'); + this.element.className = 'suite'; + this.element.setAttribute('id', `suite-view-${this.suite.id}`); + this.description = document.createElement('div'); + this.description.className = 'description'; + this.description.textContent = this.suite.description; + this.element.appendChild(this.description); + } + + attach() { + (this.parentSuiteView() || document.querySelector('.results')).appendChild(this.element); + } + + parentSuiteView() { + let suiteViewElement; + if (!this.suite.parentSuite || this.suite.parentSuite._isTopSuite) { return; } + + if (!(suiteViewElement = document.querySelector(`#suite-view-${this.suite.parentSuite.id}`))) { + const suiteView = new SuiteResultView(this.suite.parentSuite); + suiteView.attach(); + suiteViewElement = suiteView.element; + } + + return suiteViewElement; + } +} + +class SpecResultView { + constructor(spec) { + this.spec = spec; + this.element = document.createElement('div'); + this.element.className = 'spec'; + this.element.innerHTML = `\ +
    +
    +
    \ +`; + this.description = this.element.querySelector('[outlet="description"]'); + this.specFailures = this.element.querySelector('[outlet="specFailures"]'); + + this.element.classList.add(`spec-view-${this.spec.id}`); + + let { + description + } = this.spec; + if (description.indexOf('it ') !== 0) { description = `it ${description}`; } + this.description.textContent = description; + + for (let result of this.spec.failedExpectations) { + let stackTrace = formatStackTrace(this.spec, result.message, result.stack); + const resultElement = document.createElement('div'); + resultElement.className = 'result-message fail'; + resultElement.textContent = result.message; + this.specFailures.appendChild(resultElement); + + if (stackTrace) { + const traceElement = document.createElement('pre'); + traceElement.className = 'stack-trace padded'; + traceElement.textContent = stackTrace; + this.specFailures.appendChild(traceElement); + } + } + } + + attach() { + this.parentSuiteView().appendChild(this.element); + } + + parentSuiteView() { + let suiteViewElement; + let fullSpec = REGISTRY.get(this.spec.id) + if (!(suiteViewElement = document.querySelector(`#suite-view-${fullSpec.suite.id}`))) { + const suiteView = new SuiteResultView(fullSpec.suite); + suiteView.attach(); + suiteViewElement = suiteView.element; + } + + return suiteViewElement; + } +} diff --git a/spec/helpers/jasmine2-custom-matchers.js b/spec/helpers/jasmine2-custom-matchers.js new file mode 100644 index 0000000000..68f1b93453 --- /dev/null +++ b/spec/helpers/jasmine2-custom-matchers.js @@ -0,0 +1,167 @@ +const _ = require("underscore-plus"); +const fs = require("fs-plus"); +const path = require("path"); + +exports.register = (jasmineEnv) => { + jasmineEnv.beforeEach(function () { + jasmineEnv.addCustomEqualityTester(function (a, b) { + // Match jasmine.any's equality matching logic + if ((a != null ? a.jasmineMatches : undefined) != null) { + return a.jasmineMatches(b); + } + if ((b != null ? b.jasmineMatches : undefined) != null) { + return b.jasmineMatches(a); + } + + // Use underscore's definition of equality for toEqual assertions + return _.isEqual(a, b); + }); + + jasmineEnv.addMatchers({ + toHaveLength: function (util, customEqualityTesters) { + return { + compare: function (actual, expected) { + if (actual == null) { + return { + pass: false, + message: `Expected object ${actual} has no length method`, + }; + } else { + return { + pass: actual.length === expected, + message: `Expected object with length ${actual.length} to have length ${expected}`, + }; + } + }, + } + }, + + toExistOnDisk: function (util, customEqualityTesters) { + return { + compare: function (actual) { + return { + pass: fs.existsSync(actual), + message: `Expected path '${actual}' to exist.`, + }; + }, + } + }, + + toHaveFocus: function (util, customEqualityTesters) { + return { + compare: function (actual) { + if (!document.hasFocus()) { + console.error("Specs will fail because the Dev Tools have focus. To fix this close the Dev Tools or click the spec runner."); + } + + let element = actual; + if (element.jquery) { + element = element.get(0); + } + + return { + pass: (element === document.activeElement) || element.contains(document.activeElement), + message: `Expected element '${actual}' or its descendants to have focus.`, + }; + }, + } + }, + + toShow: function (util, customEqualityTesters) { + return { + compare: function (actual) { + let element = actual; + if (element.jquery) { + element = element.get(0); + } + const computedStyle = getComputedStyle(element); + + return { + pass: (computedStyle.display !== 'none') && (computedStyle.visibility === 'visible') && !element.hidden, + message: `Expected element '${element}' or its descendants to show.`, + }; + }, + } + }, + + toEqualPath: function (util, customEqualityTesters) { + return { + compare: function (actual, expected) { + const actualPath = path.normalize(actual); + const expectedPath = path.normalize(expected); + + return { + pass: actualPath === expectedPath, + message: `Expected path '${actualPath}' to be equal to '${expectedPath}'.`, + }; + }, + } + }, + + toBeNear: function (util, customEqualityTesters) { + return { + compare: function (actual, expected) { + let acceptedError = 1; + + return { + pass: ((expected - acceptedError) <= actual) && (actual <= (expected + acceptedError)), + message: `Expected '${actual}' to be near to '${expected}'.`, + }; + }, + } + }, + + toHaveNearPixels: function (util, customEqualityTesters) { + return { + compare: function (actual, expected) { + let acceptedError = 1; + + const expectedNumber = parseFloat(expected); + const actualNumber = parseFloat(actual); + + return { + pass: (expected.indexOf('px') >= 1) && (actual.indexOf('px') >= 1) && ((expectedNumber - acceptedError) <= actualNumber) && (actualNumber <= (expectedNumber + acceptedError)), + message: `Expected '${actual}' to have near pixels to '${expected}'.`, + } + } + } + }, + + toHaveClass: function (util, customEqualityTesters) { + return { + compare: function (actual, expected) { + return { + pass: actual instanceof HTMLElement && actual.classList.contains(expected), + message: `Expected '${actual}' to have '${expected}' class` + } + } + } + }, + + toHaveText: function (util, customEqualityTesters) { + return { + compare: function (actual, expected) { + return { + pass: actual instanceof HTMLElement && actual.textContent == expected, + message: `Expected '${actual}' to have text: '${expected}'` + } + } + } + }, + + toExist: function (util, customEqualityTesters) { + return { + compare: function (actual) { + if (actual instanceof HTMLElement) { + return {pass: true} + } else if (actual) { + return {pass: actual.size() > 0} + } else { + return {pass: false} + } + } + } + } + }); + }); +} diff --git a/spec/helpers/jasmine2-singleton.js b/spec/helpers/jasmine2-singleton.js new file mode 100644 index 0000000000..12b1f0576f --- /dev/null +++ b/spec/helpers/jasmine2-singleton.js @@ -0,0 +1,6 @@ +let Jasmine = require('jasmine'); +let jasmine = new Jasmine(); + +window['jasmine'] = jasmine.jasmine + +module.exports = jasmine; diff --git a/spec/helpers/jasmine2-spies.js b/spec/helpers/jasmine2-spies.js new file mode 100644 index 0000000000..f2b5abf17a --- /dev/null +++ b/spec/helpers/jasmine2-spies.js @@ -0,0 +1,99 @@ +const FindParentDir = require("find-parent-dir"); +const path = require("path"); +const _ = require("underscore-plus"); +const TextEditorElement = require("../../src/text-editor-element"); +const pathwatcher = require("pathwatcher"); +const TextEditor = require("../../src/text-editor"); +const TextMateLanguageMode = require("../../src/text-mate-language-mode"); +const TreeSitterLanguageMode = require("../../src/tree-sitter-language-mode"); +const {CompositeDisposable} = require("event-kit"); +const {clipboard} = require("electron"); + +const {testPaths} = atom.getLoadSettings(); +let specPackagePath = FindParentDir.sync(testPaths[0], 'package.json') + +let specPackageName; +if (specPackagePath) { + const packageMetadata = require(path.join(specPackagePath, 'package.json')); + specPackageName = packageMetadata.name; +} + +let specDirectory = FindParentDir.sync(testPaths[0], 'fixtures'); +let specProjectPath; +if (specDirectory) { + specProjectPath = path.join(specDirectory, 'fixtures'); +} else { + specProjectPath = require('os').tmpdir(); +} + +exports.register = (jasmineEnv) => { + jasmineEnv.beforeEach(function () { + // Do not clobber recent project history + spyOn(Object.getPrototypeOf(atom.history), 'saveState').and.returnValue(Promise.resolve()); + + atom.project.setPaths([specProjectPath]); + + atom.packages._originalResolvePackagePath = atom.packages.resolvePackagePath; + const spy = spyOn(atom.packages, 'resolvePackagePath') + spy.and.callFake(function (packageName) { + if (specPackageName && (packageName === specPackageName)) { + return atom.packages._originalResolvePackagePath(specPackagePath); + } else { + return atom.packages._originalResolvePackagePath(packageName); + } + }); + + // prevent specs from modifying Atom's menus + spyOn(atom.menu, 'sendToBrowserProcess'); + + // reset config before each spec + atom.config.set("core.destroyEmptyPanes", false); + atom.config.set("editor.fontFamily", "Courier"); + atom.config.set("editor.fontSize", 16); + atom.config.set("editor.autoIndent", false); + atom.config.set("core.disabledPackages", ["package-that-throws-an-exception", + "package-with-broken-package-json", "package-with-broken-keymap"]); + + // advanceClock(1000); + // window.setTimeout.calls.reset(); + + // make editor display updates synchronous + TextEditorElement.prototype.setUpdatedSynchronously(true); + + spyOn(pathwatcher.File.prototype, "detectResurrectionAfterDelay").and.callFake(function () { + return this.detectResurrection(); + }); + spyOn(TextEditor.prototype, "shouldPromptToSave").and.returnValue(false); + + // make tokenization synchronous + TextMateLanguageMode.prototype.chunkSize = Infinity; + TreeSitterLanguageMode.prototype.syncTimeoutMicros = Infinity; + spyOn(TextMateLanguageMode.prototype, "tokenizeInBackground").and.callFake(function () { + return this.tokenizeNextChunk(); + }); + + // Without this spy, TextEditor.onDidTokenize callbacks would not be called + // after the buffer's language mode changed, because by the time the editor + // called its new language mode's onDidTokenize method, the language mode + // would already be fully tokenized. + spyOn(TextEditor.prototype, "onDidTokenize").and.callFake(function (callback) { + return new CompositeDisposable( + this.emitter.on("did-tokenize", callback), + this.onDidChangeGrammar(() => { + const languageMode = this.buffer.getLanguageMode(); + if (languageMode.tokenizeInBackground != null ? languageMode.tokenizeInBackground.originalValue : undefined) { + return callback(); + } + }) + ); + }); + + let clipboardContent = 'initial clipboard content'; + spyOn(clipboard, 'writeText').and.callFake(text => clipboardContent = text); + spyOn(clipboard, 'readText').and.callFake(() => clipboardContent); + }); +} + +jasmine.unspy = function(object, methodName) { + object[methodName].and.callThrough(); +}; diff --git a/spec/helpers/jasmine2-time.js b/spec/helpers/jasmine2-time.js new file mode 100644 index 0000000000..e61cf1c933 --- /dev/null +++ b/spec/helpers/jasmine2-time.js @@ -0,0 +1,91 @@ +const _ = require("underscore-plus"); +const { mockDebounce } = require("../helpers/mock-debounce"); + +jasmine.useRealClock = function() { + jasmine.unspy(window, 'setTimeout'); + jasmine.unspy(window, 'clearTimeout'); + jasmine.unspy(window, 'setInterval'); + jasmine.unspy(window, 'clearInterval'); + jasmine.unspy(_._, 'now'); + jasmine.unspy(Date, 'now'); +}; + +let now; +let timeoutCount; +let intervalCount; +let timeouts; +let intervalTimeouts; + +const resetTimeouts = function() { + now = 0; + timeoutCount = 0; + intervalCount = 0; + timeouts = []; + intervalTimeouts = {}; +}; + +const fakeSetTimeout = function(callback, ms) { + if (ms == null) { ms = 0; } + const id = ++timeoutCount; + timeouts.push([id, now + ms, callback]); + return id; +}; + +const fakeClearTimeout = (idToClear) => { + timeouts = timeouts.filter(function (...args) { + const [id] = Array.from(args[0]); + return id !== idToClear; + }); +} + +const fakeSetInterval = function(callback, ms) { + const id = ++intervalCount; + var action = function() { + callback(); + return intervalTimeouts[id] = fakeSetTimeout(action, ms); + }; + intervalTimeouts[id] = fakeSetTimeout(action, ms); + return id; +}; + +fakeClearInterval = function(idToClear) { + fakeClearTimeout(intervalTimeouts[idToClear]); +}; + +window.advanceClock = function(delta) { + if (delta == null) { delta = 1; } + now += delta; + const callbacks = []; + + timeouts = timeouts.filter(function(...args) { + let id, strikeTime; + let callback; + [id, strikeTime, callback] = Array.from(args[0]); + if (strikeTime <= now) { + callbacks.push(callback); + return false; + } else { + return true; + } + }); + + return (() => { + const result = []; + for (let callback of Array.from(callbacks)) { result.push(callback()); + } + return result; + })(); +}; + +exports.register = (jasmineEnv) => { + jasmineEnv.beforeEach(() => { + resetTimeouts(); + spyOn(_._, "now").and.callFake(() => now); + spyOn(Date, 'now').and.callFake(() => now); + spyOn(window, "setTimeout").and.callFake(fakeSetTimeout); + spyOn(window, "clearTimeout").and.callFake(fakeClearTimeout); + spyOn(window, 'setInterval').and.callFake(fakeSetInterval); + spyOn(window, 'clearInterval').and.callFake(fakeClearInterval); + spyOn(_, "debounce").and.callFake(mockDebounce); + }) +} diff --git a/spec/helpers/jasmine2-warnings.js b/spec/helpers/jasmine2-warnings.js new file mode 100644 index 0000000000..24e100896b --- /dev/null +++ b/spec/helpers/jasmine2-warnings.js @@ -0,0 +1,21 @@ +const { + ensureNoDeprecatedFunctionCalls, + ensureNoDeprecatedStylesheets, + warnIfLeakingPathSubscriptions +} = require('./warnings') + +exports.register = (jasmineEnv) => { + jasmineEnv.afterEach(async (done) => { + ensureNoDeprecatedFunctionCalls(); + ensureNoDeprecatedStylesheets(); + + await atom.reset(); + + if (!window.debugContent) { + document.getElementById('jasmine-content').innerHTML = ''; + } + warnIfLeakingPathSubscriptions(); + + done(); + }); +} diff --git a/spec/helpers/load-jasmine-stylesheet.js b/spec/helpers/load-jasmine-stylesheet.js new file mode 100644 index 0000000000..0977c4c372 --- /dev/null +++ b/spec/helpers/load-jasmine-stylesheet.js @@ -0,0 +1,6 @@ +const jasmineStyle = document.createElement('style'); +jasmineStyle.textContent = atom.themes.loadStylesheet(atom.themes.resolveStylesheet('../static/jasmine')); +document.head.appendChild(jasmineStyle); + +document.querySelector('html').style.overflow = 'auto'; +document.body.style.overflow = 'auto'; diff --git a/spec/spec-helper-functions.js b/spec/helpers/mock-debounce.js similarity index 100% rename from spec/spec-helper-functions.js rename to spec/helpers/mock-debounce.js diff --git a/spec/helpers/mock-local-storage.js b/spec/helpers/mock-local-storage.js new file mode 100644 index 0000000000..30a8f03c1f --- /dev/null +++ b/spec/helpers/mock-local-storage.js @@ -0,0 +1,6 @@ +exports.mockLocalStorage = function() { + const items = {}; + spyOn(global.localStorage, 'setItem').and.callFake(function(key, item) { items[key] = item.toString(); return undefined; }); + spyOn(global.localStorage, 'getItem').and.callFake(key => items[key] != null ? items[key] : null); + return spyOn(global.localStorage, 'removeItem').and.callFake(function(key) { delete items[key]; return undefined; }); +}; diff --git a/spec/helpers/normalize-comments.js b/spec/helpers/normalize-comments.js new file mode 100644 index 0000000000..919c35d45c --- /dev/null +++ b/spec/helpers/normalize-comments.js @@ -0,0 +1,141 @@ +// This will normalize the comments for the special format of grammar tests +// that TextMate and Tree-Sitter do +// +// Basically, receiving a text editor and the regex that probably defines +// what a comment is, it'll return an object with `expect` - that is what was +// expected to pass the test, like a scope description for example, and two +// Point-compatible fields - `editorPosition`, that is basically in what +// position of the editor `expect` should be satisfied, and `testPosition`, that +// is where in file the test actually happened. This makes it easier for us +// to construct an error showing where EXACTLY was the assertion that failed +function normalizeTreeSitterTextData(editor, commentRegex) { + let allMatches = [], lastNonComment = 0 + const checkAssert = new RegExp('^\\s*' + commentRegex.source + '\\s*[\\<\\-|\\^]') + editor.getBuffer().getLines().forEach((row, i) => { + const m = row.match(commentRegex) + if(m) { + // const scope = editor.scopeDescriptorForBufferPosition([i, m.index]) + // FIXME: use editor.scopeDescriptorForBufferPosition when it works + const scope = editor.tokensForScreenRow(i) + const scopes = scope.flatMap(e => e.scopes) + if(scopes.find(s => s.match(/comment/)) && row.match(checkAssert)) { + allMatches.push({row: lastNonComment, text: row, col: m.index, testRow: i}) + return + } + } + lastNonComment = i + }) + return allMatches.map(({text, row, col, testRow}) => { + const exactPos = text.match(/\^\s+(.*)/) + if(exactPos) { + const expected = exactPos[1] + return { + expected, + editorPosition: {row, column: exactPos.index}, + testPosition: {row: testRow, column: col} + } + } else { + const pos = text.match(/\<-\s+(.*)/) + if(!pos) throw new Error(`Can't match ${text}`) + return { + expected: pos[1], + editorPosition: {row, column: col}, + testPosition: {row: testRow, column: col} + } + } + }) +} +exports.normalizeTreeSitterTextData = normalizeTreeSitterTextData; +window.normalizeTreeSitterTextData = normalizeTreeSitterTextData + +async function openDocument(fullPath) { + const editor = await atom.workspace.open(fullPath); + await editor.languageMode.ready; + return editor; +} + +async function runGrammarTests(fullPath, commentRegex) { + const editor = await openDocument(fullPath); + + const normalized = normalizeTreeSitterTextData(editor, commentRegex) + expect(normalized.length).toSatisfy((n, reason) => { + reason("Tokenizer didn't run correctly - could not find any comment") + return n > 0 + }) + normalized.forEach(({expected, editorPosition, testPosition}) => { + expect(editor.scopeDescriptorForBufferPosition(editorPosition).scopes).toSatisfy((scopes, reason) => { + const dontFindScope = expected.startsWith("!"); + expected = expected.replace(/^!/, "") + if(dontFindScope) { + reason(`Expected to NOT find scope "${expected}" but found it\n` + + ` at ${fullPath}:${testPosition.row+1}:${testPosition.column+1}` + ); + } else { + reason(`Expected to find scope "${expected}" but found "${scopes}"\n` + + ` at ${fullPath}:${testPosition.row+1}:${testPosition.column+1}` + ); + } + const normalized = expected.replace(/([\.\-])/g, '\\$1'); + const scopeRegex = new RegExp('^' + normalized + '(\\..+)?$'); + let result = scopes.find(e => e.match(scopeRegex)) !== undefined; + if(dontFindScope) result = !result; + return result + }) + }) +} +exports.runGrammarTests = runGrammarTests; +window.runGrammarTests = runGrammarTests; + +async function runFoldsTests(fullPath, commentRegex) { + const editor = await openDocument(fullPath); + let grouped = {} + const normalized = normalizeTreeSitterTextData(editor, commentRegex).forEach(test => { + const [kind, id] = test.expected.split('.') + if(!kind || !id) { + throw new Error(`Folds must be in the format fold_end.some-id\n` + + ` at ${test.testPosition.row+1}:${test.testPosition.column+1}`) + } + grouped[id] ||= {} + grouped[id][kind] = test + }) + for(const k in grouped) { + const v = grouped[k] + const keys = Object.keys(v) + if(keys.indexOf('fold_begin') === -1) + throw new Error(`Fold ${k} must contain fold_begin`) + if(keys.indexOf('fold_end') === -1) + throw new Error(`Fold ${k} must contain fold_end`) + if(keys.indexOf('fold_new_position') === -1) + throw new Error(`Fold ${k} must contain fold_new_position`) + } + + for(const k in grouped) { + const fold = grouped[k] + const begin = fold['fold_begin'] + const end = fold['fold_end'] + const newPos = fold['fold_new_position'] + + expect(editor.isFoldableAtBufferRow(begin.editorPosition.row)) + .toSatisfy((foldable, reason) => { + reason(`Editor is not foldable at row ${begin.editorPosition.row+1}\n` + + ` at ${fullPath}:${begin.testPosition.row+1}:${begin.testPosition.column+1}`) + return foldable + }) + editor.foldBufferRow(begin.editorPosition.row) + + expect(editor.screenPositionForBufferPosition(end.editorPosition)) + .toSatisfy((screenPosition, reason) => { + const {row,column} = newPos.editorPosition + reason(`At row ${begin.editorPosition.row+1}, editor should fold ` + + `up to the ${end.editorPosition.row+1}:${end.editorPosition.column+1}\n` + + ` into the new position ${row+1}:${column+1}\n`+ + ` but folded to position ${screenPosition.row+1}:${screenPosition.column+1}\n`+ + ` at ${fullPath}:${newPos.testPosition.row+1}:${newPos.testPosition.column+1}\n` + + ` at ${fullPath}:${end.testPosition.row+1}:${end.testPosition.column+1}`) + return row === screenPosition.row && column === screenPosition.column + }) + editor.unfoldAll() + } +} +exports.runFoldsTests = runFoldsTests; +window.runFoldsTests = runFoldsTests; diff --git a/spec/helpers/platform-filter.js b/spec/helpers/platform-filter.js new file mode 100644 index 0000000000..6a9fc8a1d0 --- /dev/null +++ b/spec/helpers/platform-filter.js @@ -0,0 +1,11 @@ +jasmine.filterByPlatform = ({only, except}, done) => { + if (only && !only.includes(process.platform)) { + done(); + pending(); + } + + if (except && except.includes(process.platform)) { + done(); + pending(); + } +} diff --git a/spec/spec-helper-platform.js b/spec/helpers/platform.js similarity index 92% rename from spec/spec-helper-platform.js rename to spec/helpers/platform.js index b552ec30ee..f992d40611 100644 --- a/spec/spec-helper-platform.js +++ b/spec/helpers/platform.js @@ -14,7 +14,7 @@ module.exports = { // Returns nothing. generateEvilFiles() { let filenames; - const evilFilesPath = path.join(__dirname, 'fixtures', 'evil-files'); + const evilFilesPath = path.join(__dirname, '..', 'fixtures', 'evil-files'); if (fs.existsSync(evilFilesPath)) { fs.removeSync(evilFilesPath); } diff --git a/spec/helpers/set-prototype-extensions.js b/spec/helpers/set-prototype-extensions.js new file mode 100644 index 0000000000..01955d5b25 --- /dev/null +++ b/spec/helpers/set-prototype-extensions.js @@ -0,0 +1,17 @@ +Set.prototype.jasmineToString = function() { + return `Set {${[...this.values()].join(', ')}}`; +}; + +Set.prototype.isEqual = function(other) { + if (other instanceof Set) { + let next; + if (this.size !== other.size) { return false; } + const values = this.values(); + while (!(next = values.next()).done) { + if (!other.has(next.value)) { return false; } + } + return true; + } else { + return false; + } +}; diff --git a/spec/helpers/warnings.js b/spec/helpers/warnings.js new file mode 100644 index 0000000000..0ce07ff26d --- /dev/null +++ b/spec/helpers/warnings.js @@ -0,0 +1,56 @@ +const pathwatcher = require("pathwatcher"); +const _ = require("underscore-plus"); +const Grim = require("grim"); + +exports.warnIfLeakingPathSubscriptions = function() { + const watchedPaths = pathwatcher.getWatchedPaths(); + if (watchedPaths.length > 0) { + console.error("WARNING: Leaking subscriptions for paths: " + watchedPaths.join(", ")); + } + return pathwatcher.closeAllWatchers(); +}; + +exports.ensureNoDeprecatedFunctionCalls = function() { + const deprecations = _.clone(Grim.getDeprecations()); + Grim.clearDeprecations(); + if (deprecations.length > 0) { + const originalPrepareStackTrace = Error.prepareStackTrace; + Error.prepareStackTrace = function(error, stack) { + const output = []; + for (let deprecation of Array.from(deprecations)) { + output.push(`${deprecation.originName} is deprecated. ${deprecation.message}`); + output.push(_.multiplyString("-", output[output.length - 1].length)); + for (stack of Array.from(deprecation.getStacks())) { + for (let {functionName, location} of Array.from(stack)) { + output.push(`${functionName} -- ${location}`); + } + } + output.push(""); + } + return output.join("\n"); + }; + + const error = new Error(`Deprecated function(s) ${deprecations.map(({originName}) => originName).join(', ')}) were called.`); + error.stack; + Error.prepareStackTrace = originalPrepareStackTrace; + throw error; + } +}; + +exports.ensureNoDeprecatedStylesheets = function() { + const deprecations = _.clone(atom.styles.getDeprecations()); + atom.styles.clearDeprecations(); + return (() => { + const result = []; + for (let sourcePath in deprecations) { + const deprecation = deprecations[sourcePath]; + const title = + sourcePath !== 'undefined' ? + `Deprecated stylesheet at '${sourcePath}':` + : + "Deprecated stylesheet:"; + throw new Error(`${title}\n${deprecation.message}`); + } + return result; + })(); +}; diff --git a/spec/history-manager-spec.js b/spec/history-manager-spec.js index b3e425da3e..0687c3405b 100644 --- a/spec/history-manager-spec.js +++ b/spec/history-manager-spec.js @@ -8,7 +8,7 @@ describe('HistoryManager', () => { beforeEach(async () => { commandDisposable = jasmine.createSpyObj('Disposable', ['dispose']); commandRegistry = jasmine.createSpyObj('CommandRegistry', ['add']); - commandRegistry.add.andReturn(commandDisposable); + commandRegistry.add.and.returnValue(commandDisposable); stateStore = new StateStore('history-manager-test', 1); await stateStore.save('history-manager', { @@ -23,7 +23,7 @@ describe('HistoryManager', () => { projectDisposable = jasmine.createSpyObj('Disposable', ['dispose']); project = jasmine.createSpyObj('Project', ['onDidChangePaths']); - project.onDidChangePaths.andCallFake(f => { + project.onDidChangePaths.and.callFake(f => { project.didChangePathsListener = f; return projectDisposable; }); @@ -43,7 +43,7 @@ describe('HistoryManager', () => { describe('constructor', () => { it("registers the 'clear-project-history' command function", () => { expect(commandRegistry.add).toHaveBeenCalled(); - const cmdCall = commandRegistry.add.calls[0]; + const cmdCall = commandRegistry.add.calls.first(); expect(cmdCall.args.length).toBe(3); expect(cmdCall.args[0]).toBe('atom-workspace'); expect(typeof cmdCall.args[1]['application:clear-project-history']).toBe( @@ -202,7 +202,7 @@ describe('HistoryManager', () => { // so that no data is actually stored to it. jasmine.unspy(historyManager, 'saveState'); - spyOn(historyManager.stateStore, 'save').andCallFake((name, history) => { + spyOn(historyManager.stateStore, 'save').and.callFake((name, history) => { savedHistory = history; return Promise.resolve(); }); @@ -216,7 +216,7 @@ describe('HistoryManager', () => { project, commands: commandRegistry }); - spyOn(historyManager2.stateStore, 'load').andCallFake(name => + spyOn(historyManager2.stateStore, 'load').and.callFake(name => Promise.resolve(savedHistory) ); await historyManager2.loadState(); diff --git a/spec/jasmine-junit-reporter.js b/spec/jasmine-junit-reporter.js deleted file mode 100644 index 26c3231653..0000000000 --- a/spec/jasmine-junit-reporter.js +++ /dev/null @@ -1,21 +0,0 @@ -require('jasmine-reporters'); - -class JasmineJUnitReporter extends jasmine.JUnitXmlReporter { - fullDescription(spec) { - let fullDescription = spec.description; - let currentSuite = spec.suite; - while (currentSuite) { - fullDescription = currentSuite.description + ' ' + fullDescription; - currentSuite = currentSuite.parentSuite; - } - - return fullDescription; - } - - reportSpecResults(spec) { - spec.description = this.fullDescription(spec); - return super.reportSpecResults(spec); - } -} - -module.exports = { JasmineJUnitReporter }; diff --git a/spec/jasmine-test-runner.js b/spec/jasmine-test-runner.js index 5c91fb7c81..a77d95f117 100644 --- a/spec/jasmine-test-runner.js +++ b/spec/jasmine-test-runner.js @@ -1,222 +1 @@ -/* - * decaffeinate suggestions: - * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns - * DS202: Simplify dynamic range loops - * DS205: Consider reworking code to avoid use of IIFEs - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md - */ -const Grim = require('grim'); -const fs = require('fs-plus'); -const temp = require('temp'); -const path = require('path'); -const {ipcRenderer} = require('electron'); - -temp.track(); - -module.exports = function({logFile, headless, testPaths, buildAtomEnvironment}) { - const object = require('../vendor/jasmine'); - for (let key in object) { const value = object[key]; window[key] = value; } - - require('jasmine-tagged'); - - // Rewrite global jasmine functions to have support for async tests. - // This way packages can create async specs without having to import these from the - // async-spec-helpers file. - global.it = asyncifyJasmineFn(global.it, 1); - global.fit = asyncifyJasmineFn(global.fit, 1); - global.ffit = asyncifyJasmineFn(global.ffit, 1); - global.fffit = asyncifyJasmineFn(global.fffit, 1); - global.beforeEach = asyncifyJasmineFn(global.beforeEach, 0); - global.afterEach = asyncifyJasmineFn(global.afterEach, 0); - - // Allow document.title to be assigned in specs without screwing up spec window title - let documentTitle = null; - Object.defineProperty(document, 'title', { - get() { return documentTitle; }, - set(title) { return documentTitle = title; } - } - ); - - const userHome = process.env.ATOM_HOME || path.join(fs.getHomeDirectory(), '.atom'); - const atomHome = temp.mkdirSync({prefix: 'atom-test-home-'}); - if (process.env.APM_TEST_PACKAGES) { - const testPackages = process.env.APM_TEST_PACKAGES.split(/\s+/); - fs.makeTreeSync(path.join(atomHome, 'packages')); - for (let packName of Array.from(testPackages)) { - const userPack = path.join(userHome, 'packages', packName); - const loadablePack = path.join(atomHome, 'packages', packName); - - try { - fs.symlinkSync(userPack, loadablePack, 'dir'); - } catch (error) { - fs.copySync(userPack, loadablePack); - } - } - } - - const ApplicationDelegate = require('../src/application-delegate'); - const applicationDelegate = new ApplicationDelegate(); - applicationDelegate.setRepresentedFilename = function() {}; - applicationDelegate.setWindowDocumentEdited = function() {}; - window.atom = buildAtomEnvironment({ - applicationDelegate, window, document, - configDirPath: atomHome, - enablePersistence: false - }); - - require('./spec-helper'); - if (process.env.JANKY_SHA1 || process.env.CI) { disableFocusMethods(); } - for (let testPath of Array.from(testPaths)) { requireSpecs(testPath); } - - setSpecType('user'); - - let resolveWithExitCode = null; - const promise = new Promise((resolve, reject) => resolveWithExitCode = resolve); - const jasmineEnv = jasmine.getEnv(); - jasmineEnv.addReporter(buildReporter({logFile, headless, resolveWithExitCode})); - - if(process.env.SPEC_FILTER) { - const {getFullDescription} = require('./jasmine-list-reporter'); - const regex = new RegExp(process.env.SPEC_FILTER) - jasmineEnv.specFilter = (spec) => getFullDescription(spec, false).match(regex) - } - - if (process.env.TEST_JUNIT_XML_PATH) { - const {JasmineJUnitReporter} = require('./jasmine-junit-reporter'); - process.stdout.write(`Outputting JUnit XML to <${process.env.TEST_JUNIT_XML_PATH}>\n`); - const outputDir = path.dirname(process.env.TEST_JUNIT_XML_PATH); - const fileBase = path.basename(process.env.TEST_JUNIT_XML_PATH, '.xml'); - - jasmineEnv.addReporter(new JasmineJUnitReporter(outputDir, true, false, fileBase, true)); - } - - jasmineEnv.setIncludedTags([process.platform]); - - const jasmineContent = document.createElement('div'); - jasmineContent.setAttribute('id', 'jasmine-content'); - - document.body.appendChild(jasmineContent); - - jasmineEnv.execute(); - return promise; -}; - -var asyncifyJasmineFn = (fn, callbackPosition) => (function(...args) { - if (typeof args[callbackPosition] === 'function') { - const callback = args[callbackPosition]; - - args[callbackPosition] = function(...args) { - const result = callback.apply(this, args); - if (result instanceof Promise) { - return waitsForPromise(() => result); - } - }; - } - - return fn.apply(this, args); -}); - -var waitsForPromise = function(fn) { - const promise = fn(); - - return global.waitsFor('spec promise to resolve', done => promise.then(done, function(error) { - jasmine.getEnv().currentSpec.fail(error); - return done(); - })); -}; - -var disableFocusMethods = () => ['fdescribe', 'ffdescribe', 'fffdescribe', 'fit', 'ffit', 'fffit'].forEach(function(methodName) { - const focusMethod = window[methodName]; - return window[methodName] = function(description) { - const error = new Error('Focused spec is running on CI'); - return focusMethod(description, function() { throw error; }); - }; -}); - -var requireSpecs = function(testPath, specType) { - if (fs.isDirectorySync(testPath)) { - return (() => { - const result = []; - for (let testFilePath of Array.from(fs.listTreeSync(testPath))) { - if (/-spec\.(coffee|js)$/.test(testFilePath)) { - require(testFilePath); - // Set spec directory on spec for setting up the project in spec-helper - result.push(setSpecDirectory(testPath)); - } - } - return result; - })(); - } else { - require(testPath); - return setSpecDirectory(path.dirname(testPath)); - } -}; - -const setSpecField = function(name, value) { - const specs = jasmine.getEnv().currentRunner().specs(); - if (specs.length === 0) { return; } - return (() => { - const result = []; - for (let start = specs.length-1, index = start, asc = start <= 0; asc ? index <= 0 : index >= 0; asc ? index++ : index--) { - if (specs[index][name] != null) { break; } - result.push(specs[index][name] = value); - } - return result; - })(); -}; - -var setSpecType = specType => setSpecField('specType', specType); - -var setSpecDirectory = specDirectory => setSpecField('specDirectory', specDirectory); - -var buildReporter = function({logFile, headless, resolveWithExitCode}) { - if (headless) { - return buildTerminalReporter(logFile, resolveWithExitCode); - } else { - let reporter; - const AtomReporter = require('./atom-reporter.js'); - return reporter = new AtomReporter(); - } -}; - -var buildTerminalReporter = function(logFile, resolveWithExitCode) { - let logStream; - if (logFile != null) { logStream = fs.openSync(logFile, 'w'); } - const log = function(str) { - if (logStream != null) { - return fs.writeSync(logStream, str); - } else { - return ipcRenderer.send('write-to-stderr', str); - } - }; - - const options = { - print(str) { - return log(str); - }, - onComplete(runner) { - if (logStream != null) { fs.closeSync(logStream); } - if (Grim.getDeprecationsLength() > 0) { - Grim.logDeprecations(); - resolveWithExitCode(1); - return; - } - - if (runner.results().failedCount > 0) { - return resolveWithExitCode(1); - } else { - return resolveWithExitCode(0); - } - } - }; - - if (process.env.ATOM_JASMINE_REPORTER === 'list') { - const {JasmineListReporter} = require('./jasmine-list-reporter'); - return new JasmineListReporter(options); - } else { - const {TerminalReporter} = require('jasmine-tagged'); - return new TerminalReporter(options); - } -}; +module.exports = require('./runners/jasmine1-test-runner'); diff --git a/spec/main-process/atom-application.test.js b/spec/main-process/atom-application.test.js index 1aba2382b1..b67a0b9038 100644 --- a/spec/main-process/atom-application.test.js +++ b/spec/main-process/atom-application.test.js @@ -12,7 +12,7 @@ const parseCommandLine = require('../../src/main-process/parse-command-line'); const { emitterEventPromise, conditionPromise -} = require('../async-spec-helpers'); +} = require('../helpers/async-spec-helpers'); // These tests use a utility class called LaunchScenario, defined below, to manipulate AtomApplication instances that // (1) are stubbed to only simulate AtomWindow creation and (2) allow you to use a shorthand notation to assert the diff --git a/spec/main-process/atom-window.test.js b/spec/main-process/atom-window.test.js index 7b6ecbfc60..9dd71403c0 100644 --- a/spec/main-process/atom-window.test.js +++ b/spec/main-process/atom-window.test.js @@ -9,7 +9,7 @@ const sandbox = require('sinon').createSandbox(); const dedent = require('dedent'); const AtomWindow = require('../../src/main-process/atom-window'); -const { emitterEventPromise } = require('../async-spec-helpers'); +const { emitterEventPromise } = require('../helpers/async-spec-helpers'); describe('AtomWindow', function() { let sinon, app, service; diff --git a/spec/main-process/mocha-test-runner.js b/spec/main-process/mocha-test-runner.js index 2c3a28ded1..03eec1edf6 100644 --- a/spec/main-process/mocha-test-runner.js +++ b/spec/main-process/mocha-test-runner.js @@ -9,15 +9,6 @@ module.exports = function(testPaths) { reporterEnabled: 'list' }; - if (process.env.TEST_JUNIT_XML_PATH) { - reporterOptions = { - reporterEnabled: 'list, mocha-junit-reporter', - mochaJunitReporterReporterOptions: { - mochaFile: process.env.TEST_JUNIT_XML_PATH - } - }; - } - const mocha = new Mocha({ reporter: 'mocha-multi-reporters', reporterOptions diff --git a/spec/menu-manager-spec.js b/spec/menu-manager-spec.js index 3b78e42b60..c6c3af0477 100644 --- a/spec/menu-manager-spec.js +++ b/spec/menu-manager-spec.js @@ -116,7 +116,7 @@ describe('MenuManager', function() { atom.keymaps.add('test', { 'atom-workspace': { 'ctrl-b': 'b' } }); menu.update(); advanceClock(1); - expect(menu.sendToBrowserProcess.argsForCall[0][1]['b']).toEqual([ + expect(menu.sendToBrowserProcess.calls.argsFor(0)[1]['b']).toEqual([ 'ctrl-b' ]); }); @@ -128,7 +128,7 @@ describe('MenuManager', function() { atom.keymaps.add('test', { 'atom-workspace': { 'ctrl-b': 'b' } }); atom.keymaps.add('test', { 'atom-text-editor': { 'ctrl-b': 'unset!' } }); advanceClock(1); - expect(menu.sendToBrowserProcess.argsForCall[0][1]['b']).toBeUndefined(); + expect(menu.sendToBrowserProcess.calls.argsFor(0)[1]['b']).toBeUndefined(); }); it('omits key bindings that could conflict with AltGraph characters on macOS', function() { @@ -153,9 +153,9 @@ describe('MenuManager', function() { }); advanceClock(1); - expect(menu.sendToBrowserProcess.argsForCall[0][1]['b']).toBeUndefined(); - expect(menu.sendToBrowserProcess.argsForCall[0][1]['c']).toBeUndefined(); - expect(menu.sendToBrowserProcess.argsForCall[0][1]['d']).toEqual([ + expect(menu.sendToBrowserProcess.calls.argsFor(0)[1]['b']).toBeUndefined(); + expect(menu.sendToBrowserProcess.calls.argsFor(0)[1]['c']).toBeUndefined(); + expect(menu.sendToBrowserProcess.calls.argsFor(0)[1]['d']).toEqual([ 'alt-cmd-d' ]); }); @@ -182,9 +182,9 @@ describe('MenuManager', function() { }); advanceClock(1); - expect(menu.sendToBrowserProcess.argsForCall[0][1]['b']).toBeUndefined(); - expect(menu.sendToBrowserProcess.argsForCall[0][1]['c']).toBeUndefined(); - expect(menu.sendToBrowserProcess.argsForCall[0][1]['d']).toEqual([ + expect(menu.sendToBrowserProcess.calls.argsFor(0)[1]['b']).toBeUndefined(); + expect(menu.sendToBrowserProcess.calls.argsFor(0)[1]['c']).toBeUndefined(); + expect(menu.sendToBrowserProcess.calls.argsFor(0)[1]['d']).toEqual([ 'ctrl-alt-cmd-d' ]); }); diff --git a/spec/module-cache-spec.js b/spec/module-cache-spec.js index 96f5c0b196..0ddf36f204 100644 --- a/spec/module-cache-spec.js +++ b/spec/module-cache-spec.js @@ -5,7 +5,7 @@ const temp = require('temp').track(); const ModuleCache = require('../src/module-cache'); describe('ModuleCache', function() { - beforeEach(() => spyOn(Module, '_findPath').andCallThrough()); + beforeEach(() => spyOn(Module, '_findPath').and.callThrough()); afterEach(function() { try { @@ -23,7 +23,7 @@ describe('ModuleCache', function() { expect(fs.isFileSync(require.resolve(builtinName))).toBeTruthy(); } - expect(Module._findPath.callCount).toBe(0); + expect(Module._findPath.calls.count()).toBe(0); }); it('resolves relative core paths without hitting the filesystem', function() { @@ -35,7 +35,7 @@ describe('ModuleCache', function() { } }); expect(require('./fixtures/module-cache/file.json').foo).toBe('bar'); - expect(Module._findPath.callCount).toBe(0); + expect(Module._findPath.calls.count()).toBe(0); }); it('resolves module paths when a compatible version is provided by core', function() { @@ -78,9 +78,9 @@ exports.load = function() { require('underscore-plus'); };\ ); const packageMain = require(indexPath); - Module._findPath.reset(); + Module._findPath.calls.reset(); packageMain.load(); - expect(Module._findPath.callCount).toBe(0); + expect(Module._findPath.calls.count()).toBe(0); }); it('does not resolve module paths when no compatible version is provided by core', function() { @@ -122,10 +122,10 @@ exports.load = function() { require('underscore-plus'); };\ ` ); - spyOn(process, 'cwd').andReturn('/'); // Required when running this test from CLI + spyOn(process, 'cwd').and.returnValue('/'); // Required when running this test from CLI const packageMain = require(indexPath); - Module._findPath.reset(); + Module._findPath.calls.reset(); expect(() => packageMain.load()).toThrow(); - expect(Module._findPath.callCount).toBe(1); + expect(Module._findPath.calls.count()).toBe(1); }); }); diff --git a/spec/native-compile-cache-spec.js b/spec/native-compile-cache-spec.js index b1f2b1e145..d6d81e4789 100644 --- a/spec/native-compile-cache-spec.js +++ b/spec/native-compile-cache-spec.js @@ -17,9 +17,9 @@ describe("NativeCompileCache", function() { cachedFiles = []; fakeCacheStore = jasmine.createSpyObj("cache store", ["set", "get", "has", "delete"]); - fakeCacheStore.has.andCallFake(cacheKey => fakeCacheStore.get(cacheKey) != null); + fakeCacheStore.has.and.callFake(cacheKey => fakeCacheStore.get(cacheKey) != null); - fakeCacheStore.get.andCallFake(function(cacheKey) { + fakeCacheStore.get.and.callFake(function(cacheKey) { for (let i = cachedFiles.length - 1; i >= 0; i--) { const entry = cachedFiles[i]; if (entry.cacheKey !== cacheKey) { continue; } @@ -27,7 +27,7 @@ describe("NativeCompileCache", function() { } }); - fakeCacheStore.set.andCallFake((cacheKey, cacheBuffer) => cachedFiles.push({cacheKey, cacheBuffer})); + fakeCacheStore.set.and.callFake((cacheKey, cacheBuffer) => cachedFiles.push({cacheKey, cacheBuffer})); nativeCompileCache.setCacheStore(fakeCacheStore); nativeCompileCache.setV8Version("a-v8-version"); @@ -39,11 +39,11 @@ describe("NativeCompileCache", function() { const fn2 = require('./fixtures/native-cache/file-2'); expect(cachedFiles.length).toBe(2); - expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array); + expect(cachedFiles[0].cacheBuffer).toEqual(jasmine.any(Uint8Array)); expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0); expect(fn1()).toBe(1); - expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array); + expect(cachedFiles[1].cacheBuffer).toEqual(jasmine.any(Uint8Array)); expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0); expect(fn2()).toBe(2); @@ -58,7 +58,7 @@ describe("NativeCompileCache", function() { let fn4 = require('./fixtures/native-cache/file-4'); expect(cachedFiles.length).toBe(1); - expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array); + expect(cachedFiles[0].cacheBuffer).toEqual(jasmine.any(Uint8Array)); expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0); expect(fn4()).toBe("file-4"); @@ -67,7 +67,7 @@ describe("NativeCompileCache", function() { fn4 = require('./fixtures/native-cache/file-4'); expect(cachedFiles.length).toBe(2); - expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array); + expect(cachedFiles[1].cacheBuffer).toEqual(jasmine.any(Uint8Array)); return expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0); })); @@ -83,7 +83,7 @@ module.exports = function () { return "file-5" }\ let fn5 = require('./fixtures/native-cache/file-5'); expect(cachedFiles.length).toBe(1); - expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array); + expect(cachedFiles[0].cacheBuffer).toEqual(jasmine.any(Uint8Array)); expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0); expect(fn5()).toBe("file-5"); @@ -92,14 +92,14 @@ module.exports = function () { return "file-5" }\ fn5 = require('./fixtures/native-cache/file-5'); expect(cachedFiles.length).toBe(2); - expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array); + expect(cachedFiles[1].cacheBuffer).toEqual(jasmine.any(Uint8Array)); return expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0); }); }); return it("deletes previously cached code when the cache is an invalid file", function() { - fakeCacheStore.has.andReturn(true); - fakeCacheStore.get.andCallFake(() => Buffer.from("an invalid cache")); + fakeCacheStore.has.and.returnValue(true); + fakeCacheStore.get.and.callFake(() => Buffer.from("an invalid cache")); const fn3 = require('./fixtures/native-cache/file-3'); diff --git a/spec/native-watcher-registry-spec.js b/spec/native-watcher-registry-spec.js index fa0e22fb4c..a0475beffe 100644 --- a/spec/native-watcher-registry-spec.js +++ b/spec/native-watcher-registry-spec.js @@ -100,7 +100,7 @@ describe('NativeWatcherRegistry', function() { }); it('reuses an existing NativeWatcher on the same directory', async function() { - this.RETRY_FLAKY_TEST_AND_SLOW_DOWN_THE_BUILD(); + // this.RETRY_FLAKY_TEST_AND_SLOW_DOWN_THE_BUILD(); const EXISTING = new MockNative('existing'); const existingPath = absolute('existing', 'path'); diff --git a/spec/notification-manager-spec.js b/spec/notification-manager-spec.js index 9d8f28d1ce..d3291d5455 100644 --- a/spec/notification-manager-spec.js +++ b/spec/notification-manager-spec.js @@ -24,7 +24,7 @@ describe('NotificationManager', () => { manager.add('error', 'Some error!', { icon: 'someIcon' }); expect(addSpy).toHaveBeenCalled(); - const notification = addSpy.mostRecentCall.args[0]; + const notification = addSpy.calls.mostRecent().args[0]; expect(notification.getType()).toBe('error'); expect(notification.getMessage()).toBe('Some error!'); expect(notification.getIcon()).toBe('someIcon'); @@ -33,35 +33,35 @@ describe('NotificationManager', () => { it('emits a fatal error when ::addFatalError has been called', () => { manager.addFatalError('Some error!', { icon: 'someIcon' }); expect(addSpy).toHaveBeenCalled(); - const notification = addSpy.mostRecentCall.args[0]; + const notification = addSpy.calls.mostRecent().args[0]; expect(notification.getType()).toBe('fatal'); }); it('emits an error when ::addError has been called', () => { manager.addError('Some error!', { icon: 'someIcon' }); expect(addSpy).toHaveBeenCalled(); - const notification = addSpy.mostRecentCall.args[0]; + const notification = addSpy.calls.mostRecent().args[0]; expect(notification.getType()).toBe('error'); }); it('emits a warning notification when ::addWarning has been called', () => { manager.addWarning('Something!', { icon: 'someIcon' }); expect(addSpy).toHaveBeenCalled(); - const notification = addSpy.mostRecentCall.args[0]; + const notification = addSpy.calls.mostRecent().args[0]; expect(notification.getType()).toBe('warning'); }); it('emits an info notification when ::addInfo has been called', () => { manager.addInfo('Something!', { icon: 'someIcon' }); expect(addSpy).toHaveBeenCalled(); - const notification = addSpy.mostRecentCall.args[0]; + const notification = addSpy.calls.mostRecent().args[0]; expect(notification.getType()).toBe('info'); }); it('emits a success notification when ::addSuccess has been called', () => { manager.addSuccess('Something!', { icon: 'someIcon' }); expect(addSpy).toHaveBeenCalled(); - const notification = addSpy.mostRecentCall.args[0]; + const notification = addSpy.calls.mostRecent().args[0]; expect(notification.getType()).toBe('success'); }); }); diff --git a/spec/package-manager-spec.js b/spec/package-manager-spec.js index 8b7da3ad32..60369fd388 100644 --- a/spec/package-manager-spec.js +++ b/spec/package-manager-spec.js @@ -6,7 +6,7 @@ const temp = require('temp').track(); const fs = require('fs-plus'); const { Disposable } = require('atom'); const { buildKeydownEvent } = require('../src/keymap-extensions'); -const { mockLocalStorage } = require('./spec-helper'); +const { mockLocalStorage } = require('./helpers/mock-local-storage'); const ModuleCache = require('../src/module-cache'); describe('PackageManager', () => { @@ -103,14 +103,14 @@ describe('PackageManager', () => { }); it('returns the package if it has an invalid keymap', () => { - spyOn(atom, 'inSpecMode').andReturn(false); + spyOn(atom, 'inSpecMode').and.returnValue(false); const pack = atom.packages.loadPackage('package-with-broken-keymap'); expect(pack instanceof Package).toBe(true); expect(pack.metadata.name).toBe('package-with-broken-keymap'); }); it('returns the package if it has an invalid stylesheet', () => { - spyOn(atom, 'inSpecMode').andReturn(false); + spyOn(atom, 'inSpecMode').and.returnValue(false); const pack = atom.packages.loadPackage('package-with-invalid-styles'); expect(pack instanceof Package).toBe(true); expect(pack.metadata.name).toBe('package-with-invalid-styles'); @@ -119,27 +119,27 @@ describe('PackageManager', () => { const addErrorHandler = jasmine.createSpy(); atom.notifications.onDidAddNotification(addErrorHandler); expect(() => pack.reloadStylesheets()).not.toThrow(); - expect(addErrorHandler.callCount).toBe(2); - expect(addErrorHandler.argsForCall[1][0].message).toContain( + expect(addErrorHandler.calls.count()).toBe(2); + expect(addErrorHandler.calls.argsFor(1)[0].message).toContain( 'Failed to reload the package-with-invalid-styles package stylesheets' ); - expect(addErrorHandler.argsForCall[1][0].options.packageName).toEqual( + expect(addErrorHandler.calls.argsFor(1)[0].options.packageName).toEqual( 'package-with-invalid-styles' ); }); it('returns null if the package has an invalid package.json', () => { - spyOn(atom, 'inSpecMode').andReturn(false); + spyOn(atom, 'inSpecMode').and.returnValue(false); const addErrorHandler = jasmine.createSpy(); atom.notifications.onDidAddNotification(addErrorHandler); expect( atom.packages.loadPackage('package-with-broken-package-json') ).toBeNull(); - expect(addErrorHandler.callCount).toBe(1); - expect(addErrorHandler.argsForCall[0][0].message).toContain( + expect(addErrorHandler.calls.count()).toBe(1); + expect(addErrorHandler.calls.argsFor(0)[0].message).toContain( 'Failed to load the package-with-broken-package-json package' ); - expect(addErrorHandler.argsForCall[0][0].options.packageName).toEqual( + expect(addErrorHandler.calls.argsFor(0)[0].options.packageName).toEqual( 'package-with-broken-package-json' ); }); @@ -176,8 +176,8 @@ describe('PackageManager', () => { expect( atom.packages.loadPackage('this-package-cannot-be-found') ).toBeNull(); - expect(console.warn.callCount).toBe(1); - expect(console.warn.argsForCall[0][0]).toContain('Could not resolve'); + expect(console.warn.calls.count()).toBe(1); + expect(console.warn.calls.argsFor(0)[0]).toContain('Could not resolve'); }); it('invokes ::onDidLoadPackage listeners with the loaded package', () => { @@ -258,18 +258,18 @@ describe('PackageManager', () => { it("registers the view providers when any of the package's deserializers are used", () => { atom.packages.loadPackage('package-with-view-providers'); - spyOn(atom.views, 'addViewProvider').andCallThrough(); + spyOn(atom.views, 'addViewProvider').and.callThrough(); atom.deserializers.deserialize({ deserializer: 'DeserializerFromPackageWithViewProviders', a: 'b' }); - expect(atom.views.addViewProvider.callCount).toBe(2); + expect(atom.views.addViewProvider.calls.count()).toBe(2); atom.deserializers.deserialize({ deserializer: 'DeserializerFromPackageWithViewProviders', a: 'b' }); - expect(atom.views.addViewProvider.callCount).toBe(2); + expect(atom.views.addViewProvider.calls.count()).toBe(2); const element1 = atom.views.getView(model1); expect(element1 instanceof HTMLDivElement).toBe(true); @@ -597,18 +597,18 @@ describe('PackageManager', () => { describe('::activatePackage(id)', () => { describe('when called multiple times', () => { it('it only calls activate on the package once', async () => { - spyOn(Package.prototype, 'activateNow').andCallThrough(); + spyOn(Package.prototype, 'activateNow').and.callThrough(); await atom.packages.activatePackage('package-with-index'); await atom.packages.activatePackage('package-with-index'); await atom.packages.activatePackage('package-with-index'); - expect(Package.prototype.activateNow.callCount).toBe(1); + expect(Package.prototype.activateNow.calls.count()).toBe(1); }); }); describe('when the package has a main module', () => { beforeEach(() => { - spyOn(Package.prototype, 'requireMainModule').andCallThrough(); + spyOn(Package.prototype, 'requireMainModule').and.callThrough(); }); describe('when the metadata specifies a main module path˜', () => { @@ -665,7 +665,7 @@ describe('PackageManager', () => { jasmine.attachToDOM(atom.workspace.getElement()); mainModule = require('./fixtures/packages/package-with-activation-commands/index'); mainModule.activationCommandCallCount = 0; - spyOn(mainModule, 'activate').andCallThrough(); + spyOn(mainModule, 'activate').and.callThrough(); workspaceCommandListener = jasmine.createSpy( 'workspaceCommandListener' @@ -689,7 +689,7 @@ describe('PackageManager', () => { }); it('defers requiring/activating the main module until an activation event bubbles to the root view', async () => { - expect(Package.prototype.requireMainModule.callCount).toBe(0); + expect(Package.prototype.requireMainModule.calls.count()).toBe(0); atom.workspace .getElement() @@ -698,7 +698,7 @@ describe('PackageManager', () => { ); await promise; - expect(Package.prototype.requireMainModule.callCount).toBe(1); + expect(Package.prototype.requireMainModule.calls.count()).toBe(1); }); it('triggers the activation event on all handlers registered during activation', async () => { @@ -717,31 +717,31 @@ describe('PackageManager', () => { ); atom.commands.dispatch(editorElement, 'activation-command'); - expect(mainModule.activate.callCount).toBe(1); + expect(mainModule.activate.calls.count()).toBe(1); expect(mainModule.activationCommandCallCount).toBe(1); - expect(editorCommandListener.callCount).toBe(1); - expect(workspaceCommandListener.callCount).toBe(1); + expect(editorCommandListener.calls.count()).toBe(1); + expect(workspaceCommandListener.calls.count()).toBe(1); atom.commands.dispatch(editorElement, 'activation-command'); expect(mainModule.activationCommandCallCount).toBe(2); - expect(editorCommandListener.callCount).toBe(2); - expect(workspaceCommandListener.callCount).toBe(2); - expect(mainModule.activate.callCount).toBe(1); + expect(editorCommandListener.calls.count()).toBe(2); + expect(workspaceCommandListener.calls.count()).toBe(2); + expect(mainModule.activate.calls.count()).toBe(1); }); it('activates the package immediately when the events are empty', async () => { mainModule = require('./fixtures/packages/package-with-empty-activation-commands/index'); - spyOn(mainModule, 'activate').andCallThrough(); + spyOn(mainModule, 'activate').and.callThrough(); atom.packages.activatePackage( 'package-with-empty-activation-commands' ); - expect(mainModule.activate.callCount).toBe(1); + expect(mainModule.activate.calls.count()).toBe(1); }); it('adds a notification when the activation commands are invalid', () => { - spyOn(atom, 'inSpecMode').andReturn(false); + spyOn(atom, 'inSpecMode').and.returnValue(false); const addErrorHandler = jasmine.createSpy(); atom.notifications.onDidAddNotification(addErrorHandler); expect(() => @@ -749,27 +749,27 @@ describe('PackageManager', () => { 'package-with-invalid-activation-commands' ) ).not.toThrow(); - expect(addErrorHandler.callCount).toBe(1); - expect(addErrorHandler.argsForCall[0][0].message).toContain( + expect(addErrorHandler.calls.count()).toBe(1); + expect(addErrorHandler.calls.argsFor(0)[0].message).toContain( 'Failed to activate the package-with-invalid-activation-commands package' ); - expect(addErrorHandler.argsForCall[0][0].options.packageName).toEqual( + expect(addErrorHandler.calls.argsFor(0)[0].options.packageName).toEqual( 'package-with-invalid-activation-commands' ); }); it('adds a notification when the context menu is invalid', () => { - spyOn(atom, 'inSpecMode').andReturn(false); + spyOn(atom, 'inSpecMode').and.returnValue(false); const addErrorHandler = jasmine.createSpy(); atom.notifications.onDidAddNotification(addErrorHandler); expect(() => atom.packages.activatePackage('package-with-invalid-context-menu') ).not.toThrow(); - expect(addErrorHandler.callCount).toBe(1); - expect(addErrorHandler.argsForCall[0][0].message).toContain( + expect(addErrorHandler.calls.count()).toBe(1); + expect(addErrorHandler.calls.argsFor(0)[0].message).toContain( 'Failed to activate the package-with-invalid-context-menu package' ); - expect(addErrorHandler.argsForCall[0][0].options.packageName).toEqual( + expect(addErrorHandler.calls.argsFor(0)[0].options.packageName).toEqual( 'package-with-invalid-context-menu' ); }); @@ -826,10 +826,10 @@ describe('PackageManager', () => { beforeEach(() => { jasmine.attachToDOM(atom.workspace.getElement()); - spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true); + spyOn(atom.packages, 'hasActivatedInitialPackages').and.returnValue(true); mainModule = require('./fixtures/packages/package-with-activation-commands-and-deserializers/index'); mainModule.activationCommandCallCount = 0; - spyOn(mainModule, 'activate').andCallThrough(); + spyOn(mainModule, 'activate').and.callThrough(); workspaceCommandListener = jasmine.createSpy( 'workspaceCommandListener' ); @@ -852,7 +852,7 @@ describe('PackageManager', () => { }); it('activates the package when a deserializer is called', async () => { - expect(Package.prototype.requireMainModule.callCount).toBe(0); + expect(Package.prototype.requireMainModule.calls.count()).toBe(0); const state1 = { deserializer: 'Deserializer1', a: 'b' }; expect(atom.deserializers.deserialize(state1, atom)).toEqual({ @@ -861,11 +861,11 @@ describe('PackageManager', () => { }); await promise; - expect(Package.prototype.requireMainModule.callCount).toBe(1); + expect(Package.prototype.requireMainModule.calls.count()).toBe(1); }); it('defers requiring/activating the main module until an activation event bubbles to the root view', async () => { - expect(Package.prototype.requireMainModule.callCount).toBe(0); + expect(Package.prototype.requireMainModule.calls.count()).toBe(0); atom.workspace .getElement() @@ -874,9 +874,9 @@ describe('PackageManager', () => { ); await promise; - expect(mainModule.activate.callCount).toBe(1); + expect(mainModule.activate.calls.count()).toBe(1); expect(mainModule.activationCommandCallCount).toBe(1); - expect(Package.prototype.requireMainModule.callCount).toBe(1); + expect(Package.prototype.requireMainModule.calls.count()).toBe(1); }); }); @@ -885,35 +885,35 @@ describe('PackageManager', () => { beforeEach(() => { mainModule = require('./fixtures/packages/package-with-activation-hooks/index'); - spyOn(mainModule, 'activate').andCallThrough(); + spyOn(mainModule, 'activate').and.callThrough(); }); it('defers requiring/activating the main module until an triggering of an activation hook occurs', async () => { promise = atom.packages.activatePackage( 'package-with-activation-hooks' ); - expect(Package.prototype.requireMainModule.callCount).toBe(0); + expect(Package.prototype.requireMainModule.calls.count()).toBe(0); atom.packages.triggerActivationHook( 'language-fictitious:grammar-used' ); atom.packages.triggerDeferredActivationHooks(); await promise; - expect(Package.prototype.requireMainModule.callCount).toBe(1); + expect(Package.prototype.requireMainModule.calls.count()).toBe(1); }); it('does not double register activation hooks when deactivating and reactivating', async () => { promise = atom.packages.activatePackage( 'package-with-activation-hooks' ); - expect(mainModule.activate.callCount).toBe(0); + expect(mainModule.activate.calls.count()).toBe(0); atom.packages.triggerActivationHook( 'language-fictitious:grammar-used' ); atom.packages.triggerDeferredActivationHooks(); await promise; - expect(mainModule.activate.callCount).toBe(1); + expect(mainModule.activate.calls.count()).toBe(1); await atom.packages.deactivatePackage( 'package-with-activation-hooks' @@ -928,20 +928,20 @@ describe('PackageManager', () => { atom.packages.triggerDeferredActivationHooks(); await promise; - expect(mainModule.activate.callCount).toBe(2); + expect(mainModule.activate.calls.count()).toBe(2); }); it('activates the package immediately when activationHooks is empty', async () => { mainModule = require('./fixtures/packages/package-with-empty-activation-hooks/index'); - spyOn(mainModule, 'activate').andCallThrough(); + spyOn(mainModule, 'activate').and.callThrough(); - expect(Package.prototype.requireMainModule.callCount).toBe(0); + expect(Package.prototype.requireMainModule.calls.count()).toBe(0); await atom.packages.activatePackage( 'package-with-empty-activation-hooks' ); - expect(mainModule.activate.callCount).toBe(1); - expect(Package.prototype.requireMainModule.callCount).toBe(1); + expect(mainModule.activate.calls.count()).toBe(1); + expect(Package.prototype.requireMainModule.calls.count()).toBe(1); }); it('activates the package immediately if the activation hook had already been triggered', async () => { @@ -949,11 +949,11 @@ describe('PackageManager', () => { 'language-fictitious:grammar-used' ); atom.packages.triggerDeferredActivationHooks(); - expect(Package.prototype.requireMainModule.callCount).toBe(0); + expect(Package.prototype.requireMainModule.calls.count()).toBe(0); await atom.packages.activatePackage('package-with-activation-hooks'); - expect(mainModule.activate.callCount).toBe(1); - expect(Package.prototype.requireMainModule.callCount).toBe(1); + expect(mainModule.activate.calls.count()).toBe(1); + expect(Package.prototype.requireMainModule.calls.count()).toBe(1); }); }); @@ -962,28 +962,28 @@ describe('PackageManager', () => { beforeEach(() => { mainModule = require('./fixtures/packages/package-with-workspace-openers/index'); - spyOn(mainModule, 'activate').andCallThrough(); + spyOn(mainModule, 'activate').and.callThrough(); }); it('defers requiring/activating the main module until a registered opener is called', async () => { promise = atom.packages.activatePackage( 'package-with-workspace-openers' ); - expect(Package.prototype.requireMainModule.callCount).toBe(0); + expect(Package.prototype.requireMainModule.calls.count()).toBe(0); atom.workspace.open('atom://fictitious'); await promise; - expect(Package.prototype.requireMainModule.callCount).toBe(1); + expect(Package.prototype.requireMainModule.calls.count()).toBe(1); expect(mainModule.openerCount).toBe(1); }); it('activates the package immediately when the events are empty', async () => { mainModule = require('./fixtures/packages/package-with-empty-workspace-openers/index'); - spyOn(mainModule, 'activate').andCallThrough(); + spyOn(mainModule, 'activate').and.callThrough(); atom.packages.activatePackage('package-with-empty-workspace-openers'); - expect(mainModule.activate.callCount).toBe(1); + expect(mainModule.activate.calls.count()).toBe(1); }); }); }); @@ -991,7 +991,7 @@ describe('PackageManager', () => { describe('when the package has no main module', () => { it('does not throw an exception', () => { spyOn(console, 'error'); - spyOn(console, 'warn').andCallThrough(); + spyOn(console, 'warn').and.callThrough(); expect(() => atom.packages.activatePackage('package-without-module') ).not.toThrow(); @@ -1017,7 +1017,7 @@ describe('PackageManager', () => { atom.packages.serializePackage('package-with-serialization'); await atom.packages.deactivatePackage('package-with-serialization'); - spyOn(pack.mainModule, 'activate').andCallThrough(); + spyOn(pack.mainModule, 'activate').and.callThrough(); await atom.packages.activatePackage('package-with-serialization'); expect(pack.mainModule.activate).toHaveBeenCalledWith({ someNumber: 77 }); }); @@ -1034,18 +1034,18 @@ describe('PackageManager', () => { describe("when the package's main module throws an error on load", () => { it('adds a notification instead of throwing an exception', () => { - spyOn(atom, 'inSpecMode').andReturn(false); + spyOn(atom, 'inSpecMode').and.returnValue(false); atom.config.set('core.disabledPackages', []); const addErrorHandler = jasmine.createSpy(); atom.notifications.onDidAddNotification(addErrorHandler); expect(() => atom.packages.activatePackage('package-that-throws-an-exception') ).not.toThrow(); - expect(addErrorHandler.callCount).toBe(1); - expect(addErrorHandler.argsForCall[0][0].message).toContain( + expect(addErrorHandler.calls.count()).toBe(1); + expect(addErrorHandler.calls.argsFor(0)[0].message).toContain( 'Failed to load the package-that-throws-an-exception package' ); - expect(addErrorHandler.argsForCall[0][0].options.packageName).toEqual( + expect(addErrorHandler.calls.argsFor(0)[0].options.packageName).toEqual( 'package-that-throws-an-exception' ); }); @@ -1054,7 +1054,7 @@ describe('PackageManager', () => { atom.config.set('core.disabledPackages', []); expect(() => atom.packages.activatePackage('package-that-throws-an-exception') - ).toThrow('This package throws an exception'); + ).toThrowError('This package throws an exception'); }); }); @@ -1067,7 +1067,7 @@ describe('PackageManager', () => { await atom.packages.activatePackage('this-doesnt-exist'); expect('Error to be thrown').toBe(''); } catch (error) { - expect(console.warn.callCount).toBe(1); + expect(console.warn.calls.count()).toBe(1); expect(error.message).toContain( "Failed to load package 'this-doesnt-exist'" ); @@ -1234,7 +1234,7 @@ describe('PackageManager', () => { beforeEach(() => { userKeymapPath = path.join(temp.mkdirSync(), 'user-keymaps.cson'); - spyOn(atom.keymaps, 'getUserKeymapPath').andReturn(userKeymapPath); + spyOn(atom.keymaps, 'getUserKeymapPath').and.returnValue(userKeymapPath); element = createTestElement('test-1'); jasmine.attachToDOM(element); @@ -1461,7 +1461,7 @@ describe('PackageManager', () => { const uri = 'atom://package-with-uri-handler/some/url?with=args'; const mod = require('./fixtures/packages/package-with-uri-handler'); spyOn(mod, 'handleURI'); - spyOn(atom.packages, 'hasLoadedInitialPackages').andReturn(true); + spyOn(atom.packages, 'hasLoadedInitialPackages').and.returnValue(true); const activationPromise = atom.packages.activatePackage( 'package-with-uri-handler' ); @@ -1478,17 +1478,17 @@ describe('PackageManager', () => { let firstServiceV3Disposed = false; let firstServiceV4Disposed = false; let secondServiceDisposed = false; - spyOn(consumerModule, 'consumeFirstServiceV3').andReturn( + spyOn(consumerModule, 'consumeFirstServiceV3').and.returnValue( new Disposable(() => { firstServiceV3Disposed = true; }) ); - spyOn(consumerModule, 'consumeFirstServiceV4').andReturn( + spyOn(consumerModule, 'consumeFirstServiceV4').and.returnValue( new Disposable(() => { firstServiceV4Disposed = true; }) ); - spyOn(consumerModule, 'consumeSecondService').andReturn( + spyOn(consumerModule, 'consumeSecondService').and.returnValue( new Disposable(() => { secondServiceDisposed = true; }) @@ -1496,7 +1496,7 @@ describe('PackageManager', () => { await atom.packages.activatePackage('package-with-consumed-services'); await atom.packages.activatePackage('package-with-provided-services'); - expect(consumerModule.consumeFirstServiceV3.callCount).toBe(1); + expect(consumerModule.consumeFirstServiceV3.calls.count()).toBe(1); expect(consumerModule.consumeFirstServiceV3).toHaveBeenCalledWith( 'first-service-v3' ); @@ -1507,9 +1507,9 @@ describe('PackageManager', () => { 'second-service' ); - consumerModule.consumeFirstServiceV3.reset(); - consumerModule.consumeFirstServiceV4.reset(); - consumerModule.consumeSecondService.reset(); + consumerModule.consumeFirstServiceV3.calls.reset(); + consumerModule.consumeFirstServiceV4.calls.reset(); + consumerModule.consumeSecondService.calls.reset(); await atom.packages.deactivatePackage('package-with-provided-services'); expect(firstServiceV3Disposed).toBe(true); @@ -1543,20 +1543,20 @@ describe('PackageManager', () => { 'package-with-missing-provided-services' ) ).toBe(true); - expect(addErrorHandler.callCount).toBe(0); + expect(addErrorHandler.calls.count()).toBe(0); }); }); }); describe('::serialize', () => { it('does not serialize packages that threw an error during activation', async () => { - spyOn(atom, 'inSpecMode').andReturn(false); + spyOn(atom, 'inSpecMode').and.returnValue(false); spyOn(console, 'warn'); const badPack = await atom.packages.activatePackage( 'package-that-throws-on-activate' ); - spyOn(badPack.mainModule, 'serialize').andCallThrough(); + spyOn(badPack.mainModule, 'serialize').and.callThrough(); atom.packages.serialize(); expect(badPack.mainModule.serialize).not.toHaveBeenCalled(); @@ -1599,7 +1599,7 @@ describe('PackageManager', () => { afterEach(() => atom.packages.unloadPackages()); it("calls `deactivate` on the package's main module if activate was successful", async () => { - spyOn(atom, 'inSpecMode').andReturn(false); + spyOn(atom, 'inSpecMode').and.returnValue(false); const pack = await atom.packages.activatePackage( 'package-with-deactivate' @@ -1607,7 +1607,7 @@ describe('PackageManager', () => { expect( atom.packages.isPackageActive('package-with-deactivate') ).toBeTruthy(); - spyOn(pack.mainModule, 'deactivate').andCallThrough(); + spyOn(pack.mainModule, 'deactivate').and.callThrough(); await atom.packages.deactivatePackage('package-with-deactivate'); expect(pack.mainModule.deactivate).toHaveBeenCalled(); @@ -1620,7 +1620,7 @@ describe('PackageManager', () => { expect( atom.packages.isPackageActive('package-that-throws-on-activate') ).toBeTruthy(); - spyOn(badPack.mainModule, 'deactivate').andCallThrough(); + spyOn(badPack.mainModule, 'deactivate').and.callThrough(); await atom.packages.deactivatePackage('package-that-throws-on-activate'); expect(badPack.mainModule.deactivate).not.toHaveBeenCalled(); @@ -1713,7 +1713,7 @@ describe('PackageManager', () => { describe('::activate()', () => { beforeEach(() => { - spyOn(atom, 'inSpecMode').andReturn(false); + spyOn(atom, 'inSpecMode').and.returnValue(false); jasmine.snapshotDeprecations(); spyOn(console, 'warn'); atom.packages.loadPackages(); @@ -1729,7 +1729,7 @@ describe('PackageManager', () => { }); it('sets hasActivatedInitialPackages', async () => { - spyOn(atom.styles, 'getUserStyleSheetPath').andReturn(null); + spyOn(atom.styles, 'getUserStyleSheetPath').and.returnValue(null); spyOn(atom.packages, 'activatePackages'); expect(atom.packages.hasActivatedInitialPackages()).toBe(false); @@ -1746,12 +1746,12 @@ describe('PackageManager', () => { expect(packageActivator).toHaveBeenCalled(); expect(themeActivator).toHaveBeenCalled(); - const packages = packageActivator.mostRecentCall.args[0]; + const packages = packageActivator.calls.mostRecent().args[0]; for (let pack of packages) { expect(['atom', 'textmate']).toContain(pack.getType()); } - const themes = themeActivator.mostRecentCall.args[0]; + const themes = themeActivator.calls.mostRecent().args[0]; themes.map(theme => expect(['theme']).toContain(theme.getType())); }); @@ -1761,7 +1761,7 @@ describe('PackageManager', () => { const package3 = atom.packages.loadPackage( 'package-with-activation-commands' ); - spyOn(atom.packages, 'getLoadedPackages').andReturn([ + spyOn(atom.packages, 'getLoadedPackages').and.returnValue([ package1, package2, package3 @@ -1820,7 +1820,7 @@ describe('PackageManager', () => { it('returns null if the package cannot be loaded', () => { spyOn(console, 'warn'); expect(atom.packages.enablePackage('this-doesnt-exist')).toBeNull(); - expect(console.warn.callCount).toBe(1); + expect(console.warn.calls.count()).toBe(1); }); it('does not disable an already disabled package', () => { diff --git a/spec/package-spec.js b/spec/package-spec.js index a235c4e919..3953e33bee 100644 --- a/spec/package-spec.js +++ b/spec/package-spec.js @@ -1,7 +1,7 @@ const path = require('path'); const Package = require('../src/package'); const ThemePackage = require('../src/theme-package'); -const { mockLocalStorage } = require('./spec-helper'); +const { mockLocalStorage } = require('./helpers/mock-local-storage'); describe('Package', function() { const build = (constructor, packagePath) => @@ -85,8 +85,8 @@ describe('Package', function() { buildPackage(packagePath).activateNow(); expect(atom.notifications.addFatalError).not.toHaveBeenCalled(); - expect(console.warn.callCount).toBe(1); - expect(console.warn.mostRecentCall.args[0]).toContain( + expect(console.warn.calls.count()).toBe(1); + expect(console.warn.calls.mostRecent().args[0]).toContain( 'it requires one or more incompatible native modules (native-module)' ); }); @@ -100,13 +100,13 @@ describe('Package', function() { afterEach(() => (atom.packages.devMode = true)); - it('returns a promise resolving to the results of `apm rebuild`', function() { + it('returns a promise resolving to the results of `apm rebuild`', async function() { const packagePath = __guard__(atom.project.getDirectories()[0], x => x.resolve('packages/package-with-index') ); const pack = buildPackage(packagePath); const rebuildCallbacks = []; - spyOn(pack, 'runRebuildProcess').andCallFake(callback => + spyOn(pack, 'runRebuildProcess').and.callFake(callback => rebuildCallbacks.push(callback) ); @@ -117,16 +117,11 @@ describe('Package', function() { stderr: 'stderr output' }); - waitsFor(done => - promise.then(function(result) { - expect(result).toEqual({ - code: 0, - stdout: 'stdout output', - stderr: 'stderr output' - }); - done(); - }) - ); + expect(await promise).toEqual({ + code: 0, + stdout: 'stdout output', + stderr: 'stderr output' + }); }); it('persists build failures in local storage', function() { @@ -139,7 +134,7 @@ describe('Package', function() { expect(pack.getBuildFailureOutput()).toBeNull(); const rebuildCallbacks = []; - spyOn(pack, 'runRebuildProcess').andCallFake(callback => + spyOn(pack, 'runRebuildProcess').and.callFake(callback => rebuildCallbacks.push(callback) ); @@ -164,20 +159,18 @@ describe('Package', function() { }); describe('theme', function() { - let [editorElement, theme] = []; + let editorElement, theme; beforeEach(function() { editorElement = document.createElement('atom-text-editor'); jasmine.attachToDOM(editorElement); }); - afterEach(() => - waitsForPromise(function() { - if (theme != null) { - return Promise.resolve(theme.deactivate()); - } - }) - ); + afterEach(async () => { + if (theme != null) { + await theme.deactivate(); + } + }); describe('when the theme contains a single style file', function() { it('loads and applies css', function() { @@ -262,11 +255,11 @@ describe('Package', function() { theme.activate(); }); - it('deactivated event fires on .deactivate()', function() { - let spy; - theme.onDidDeactivate((spy = jasmine.createSpy())); - waitsForPromise(() => Promise.resolve(theme.deactivate())); - runs(() => expect(spy).toHaveBeenCalled()); + it('deactivated event fires on .deactivate()', async function() { + let spy = jasmine.createSpy(); + theme.onDidDeactivate(spy); + await theme.deactivate(); + expect(spy).toHaveBeenCalled(); }); }); }); @@ -297,7 +290,7 @@ describe('Package', function() { expect(mainModule.initialize).not.toHaveBeenCalled(); pack.activate(); expect(mainModule.initialize).toHaveBeenCalled(); - expect(mainModule.initialize.callCount).toBe(1); + expect(mainModule.initialize.calls.count()).toBe(1); }); it('gets called when a deserializer is used', function() { diff --git a/spec/package-transpilation-registry-spec.js b/spec/package-transpilation-registry-spec.js index 54d5d90d95..d80a363fd0 100644 --- a/spec/package-transpilation-registry-spec.js +++ b/spec/package-transpilation-registry-spec.js @@ -106,7 +106,7 @@ describe('PackageTranspilationRegistry', () => { coffeeSpec._transpilerSource = 'coffee-transpiler-source'; omgTranspiler._transpilerSource = 'omg-transpiler-source'; - spyOn(registry, 'getTranspiler').andCallFake(spec => { + spyOn(registry, 'getTranspiler').and.callFake(spec => { if (spec.transpiler === './transpiler-js') return jsTranspiler; if (spec.transpiler === './transpiler-coffee') return coffeeTranspiler; if (spec.transpiler === './transpiler-omg') return omgTranspiler; @@ -122,7 +122,7 @@ describe('PackageTranspilationRegistry', () => { }); it('always returns true from shouldCompile for a file in that dir that match a glob', () => { - spyOn(originalCompiler, 'shouldCompile').andReturn(false); + spyOn(originalCompiler, 'shouldCompile').and.returnValue(false); expect(wrappedCompiler.shouldCompile('source', hitPath)).toBe(true); expect(wrappedCompiler.shouldCompile('source', hitPathCoffee)).toBe(true); expect(wrappedCompiler.shouldCompile('source', hitNonStandardExt)).toBe( @@ -141,8 +141,8 @@ describe('PackageTranspilationRegistry', () => { }); it('calls getCacheKeyData on the transpiler to get additional cache key data', () => { - spyOn(registry, 'getTranspilerPath').andReturn('./transpiler-js'); - spyOn(jsTranspiler, 'getCacheKeyData').andCallThrough(); + spyOn(registry, 'getTranspilerPath').and.returnValue('./transpiler-js'); + spyOn(jsTranspiler, 'getCacheKeyData').and.callThrough(); wrappedCompiler.getCachePath('source', missPath, jsSpec); expect(jsTranspiler.getCacheKeyData).not.toHaveBeenCalledWith( @@ -161,9 +161,9 @@ describe('PackageTranspilationRegistry', () => { }); it('compiles files matching a glob with the associated transpiler, and the old one otherwise', () => { - spyOn(jsTranspiler, 'transpile').andCallThrough(); - spyOn(coffeeTranspiler, 'transpile').andCallThrough(); - spyOn(omgTranspiler, 'transpile').andCallThrough(); + spyOn(jsTranspiler, 'transpile').and.callThrough(); + spyOn(coffeeTranspiler, 'transpile').and.callThrough(); + spyOn(omgTranspiler, 'transpile').and.callThrough(); expect(wrappedCompiler.compile('source', hitPath)).toEqual( 'source-transpiler-js' @@ -218,7 +218,7 @@ describe('PackageTranspilationRegistry', () => { }); it('returns appropriate values from shouldCompile', () => { - spyOn(originalCompiler, 'shouldCompile').andReturn(false); + spyOn(originalCompiler, 'shouldCompile').and.returnValue(false); expect( wrappedCompiler.shouldCompile( 'source', diff --git a/spec/pane-container-element-spec.js b/spec/pane-container-element-spec.js index b4e5f78b7d..d6909ff28a 100644 --- a/spec/pane-container-element-spec.js +++ b/spec/pane-container-element-spec.js @@ -152,7 +152,7 @@ describe('PaneContainerElement', function() { const getPaneElement = i => containerElement.querySelectorAll('atom-pane')[i]; - it('adds and removes panes in the direction that the pane is being dragged', function() { + it('adds and removes panes in the direction that the pane is being dragged', async function() { const leftPane = container.getActivePane(); expectPaneScale([leftPane, 1]); @@ -175,14 +175,14 @@ describe('PaneContainerElement', function() { ); expectPaneScale([leftPane, 0.5], [middlePane, 0.75], [rightPane, 1.75]); - waitsForPromise(() => middlePane.close()); - runs(() => expectPaneScale([leftPane, 0.44], [rightPane, 1.55])); + await middlePane.close(); + expectPaneScale([leftPane, 0.44], [rightPane, 1.55]); - waitsForPromise(() => leftPane.close()); - runs(() => expectPaneScale([rightPane, 1])); + await leftPane.close(); + expectPaneScale([rightPane, 1]); }); - it('splits or closes panes in orthogonal direction that the pane is being dragged', function() { + it('splits or closes panes in orthogonal direction that the pane is being dragged', async function() { const leftPane = container.getActivePane(); expectPaneScale([leftPane, 1]); @@ -204,32 +204,44 @@ describe('PaneContainerElement', function() { ); // dynamically close pane, the pane's flexscale will recover to origin value - waitsForPromise(() => lowerPane.close()); - runs(() => expectPaneScale([leftPane, 0.5], [rightPane, 1.5])); + await lowerPane.close(); + + expectPaneScale([leftPane, 0.5], [rightPane, 1.5]); }); - it('unsubscribes from mouse events when the pane is detached', function() { - container.getActivePane().splitRight(); - const element = getResizeElement(0); - spyOn(document, 'addEventListener').andCallThrough(); - spyOn(document, 'removeEventListener').andCallThrough(); - spyOn(element, 'resizeStopped').andCallThrough(); + describe('when the pane is detached', () => { + let element; - element.dispatchEvent( - new MouseEvent('mousedown', { - view: window, - bubbles: true, - button: 0 - }) - ); + beforeEach((done) => { + container.getActivePane().splitRight(); + element = getResizeElement(0); + + document._originalAddEventListener = document.addEventListener; + spyOn(document, 'addEventListener').and.callFake((...args) => { + document._originalAddEventListener(...args); - waitsFor(() => document.addEventListener.callCount === 2); + if (document.addEventListener.calls.count() == 2) { + done(); + } + }); + + spyOn(document, 'removeEventListener').and.callThrough(); + spyOn(element, 'resizeStopped').and.callThrough(); + + element.dispatchEvent( + new MouseEvent('mousedown', { + view: window, + bubbles: true, + button: 0 + }) + ); + }); - runs(function() { - expect(element.resizeStopped.callCount).toBe(0); + it('unsubscribes from mouse events', function() { + expect(element.resizeStopped.calls.count()).toBe(0); container.destroy(); - expect(element.resizeStopped.callCount).toBe(1); - expect(document.removeEventListener.callCount).toBe(2); + expect(element.resizeStopped.calls.count()).toBe(1); + expect(document.removeEventListener.calls.count()).toBe(2); }); }); diff --git a/spec/pane-container-spec.js b/spec/pane-container-spec.js index d96f759b15..25e959bf19 100644 --- a/spec/pane-container-spec.js +++ b/spec/pane-container-spec.js @@ -4,7 +4,7 @@ describe('PaneContainer', () => { let confirm, params; beforeEach(() => { - confirm = spyOn(atom.applicationDelegate, 'confirm').andCallFake( + confirm = spyOn(atom.applicationDelegate, 'confirm').and.callFake( (options, callback) => callback(0) ); params = { @@ -286,14 +286,14 @@ describe('PaneContainer', () => { }); it('returns true if the user saves all modified files when prompted', async () => { - confirm.andCallFake((options, callback) => callback(0)); + confirm.and.callFake((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.andCallFake((options, callback) => callback(1)); + confirm.and.callFake((options, callback) => callback(1)); const saved = await container.confirmClose(); expect(confirm).toHaveBeenCalled(); expect(saved).toBeFalsy(); diff --git a/spec/pane-element-spec.js b/spec/pane-element-spec.js index 71855c2479..8a3765d9f2 100644 --- a/spec/pane-element-spec.js +++ b/spec/pane-element-spec.js @@ -301,8 +301,8 @@ describe('PaneElement', function() { { path: '/fake2' } ]); paneElement.dispatchEvent(event); - expect(atom.applicationDelegate.open.callCount).toBe(1); - expect(atom.applicationDelegate.open.argsForCall[0][0]).toEqual({ + expect(atom.applicationDelegate.open.calls.count()).toBe(1); + expect(atom.applicationDelegate.open.calls.argsFor(0)[0]).toEqual({ pathsToOpen: ['/fake1', '/fake2'], here: true }); diff --git a/spec/pane-spec.js b/spec/pane-spec.js index 7482f9662d..8cc6076a4b 100644 --- a/spec/pane-spec.js +++ b/spec/pane-spec.js @@ -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 { conditionPromise, timeoutPromise } = require('./async-spec-helpers'); +const { conditionPromise, timeoutPromise } = require('./helpers/async-spec-helpers'); describe('Pane', () => { let confirm, showSaveDialog, deserializerDisposable; @@ -240,16 +240,14 @@ describe('Pane', () => { pane.addItem(itemB); - waitsFor(() => eventOrder.length === 2); - - runs(() => expect(eventOrder).toEqual(['add', 'remove'])); + expect(eventOrder).toEqual(['add', 'remove']); }); it('subscribes to be notified when item terminates its pending state', () => { const fakeDisposable = { dispose: () => {} }; const spy = jasmine .createSpy('onDidTerminatePendingState') - .andReturn(fakeDisposable); + .and.returnValue(fakeDisposable); const pane = new Pane(paneParams({ items: [] })); const item = { @@ -263,7 +261,7 @@ describe('Pane', () => { it('subscribes to be notified when item is destroyed', () => { const fakeDisposable = { dispose: () => {} }; - const spy = jasmine.createSpy('onDidDestroy').andReturn(fakeDisposable); + const spy = jasmine.createSpy('onDidDestroy').and.returnValue(fakeDisposable); const pane = new Pane(paneParams({ items: [] })); const item = { @@ -677,7 +675,7 @@ describe('Pane', () => { describe('when the item has a uri', () => { it('saves the item before destroying it', async () => { itemURI = 'test'; - confirm.andCallFake((options, callback) => callback(0)); + confirm.and.callFake((options, callback) => callback(0)); const success = await pane.destroyItem(item1); expect(item1.save).toHaveBeenCalled(); @@ -693,15 +691,15 @@ describe('Pane', () => { itemURI = null; - showSaveDialog.andCallFake((options, callback) => + showSaveDialog.and.callFake((options, callback) => callback('/selected/path') ); - confirm.andCallFake((options, callback) => callback(0)); + confirm.and.callFake((options, callback) => callback(0)); const success = await pane.destroyItem(item1); - expect(showSaveDialog.mostRecentCall.args[0]).toEqual({}); + expect(showSaveDialog.calls.mostRecent().args[0]).toEqual({}); - await conditionPromise(() => item1.saveAs.callCount === 1); + await conditionPromise(() => item1.saveAs.calls.count() === 1); expect(item1.saveAs).toHaveBeenCalledWith('/selected/path'); expect(pane.getItems().includes(item1)).toBe(false); expect(item1.isDestroyed()).toBe(true); @@ -712,7 +710,7 @@ describe('Pane', () => { describe("if the [Don't Save] option is selected", () => { it('removes and destroys the item without saving it', async () => { - confirm.andCallFake((options, callback) => callback(2)); + confirm.and.callFake((options, callback) => callback(2)); const success = await pane.destroyItem(item1); expect(item1.save).not.toHaveBeenCalled(); @@ -724,7 +722,7 @@ describe('Pane', () => { describe('if the [Cancel] option is selected', () => { it('does not save, remove, or destroy the item', async () => { - confirm.andCallFake((options, callback) => callback(1)); + confirm.and.callFake((options, callback) => callback(1)); const success = await pane.destroyItem(item1); expect(item1.save).not.toHaveBeenCalled(); @@ -772,7 +770,7 @@ describe('Pane', () => { describe('when passed a permanent dock item', () => { it("doesn't destroy the item", async () => { - spyOn(item1, 'isPermanentDockItem').andReturn(true); + spyOn(item1, 'isPermanentDockItem').and.returnValue(true); const success = await pane.destroyItem(item1); expect(pane.getItems().includes(item1)).toBe(true); expect(item1.isDestroyed()).toBe(false); @@ -780,7 +778,7 @@ describe('Pane', () => { }); it('destroy the item if force=true', async () => { - spyOn(item1, 'isPermanentDockItem').andReturn(true); + spyOn(item1, 'isPermanentDockItem').and.returnValue(true); const success = await pane.destroyItem(item1, true); expect(pane.getItems().includes(item1)).toBe(false); expect(item1.isDestroyed()).toBe(true); @@ -864,7 +862,7 @@ describe('Pane', () => { beforeEach(() => { pane = new Pane(paneParams({ items: [new Item('A')] })); - showSaveDialog.andCallFake((options, callback) => + showSaveDialog.and.callFake((options, callback) => callback('/selected/path') ); }); @@ -895,7 +893,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.mostRecentCall.args[0]).toEqual({}); + expect(showSaveDialog.calls.mostRecent().args[0]).toEqual({}); expect(pane.getActiveItem().saveAs).toHaveBeenCalledWith( '/selected/path' ); @@ -912,14 +910,14 @@ describe('Pane', () => { it('does nothing if the user cancels choosing a path', async () => { pane.getActiveItem().saveAs = jasmine.createSpy('saveAs'); - showSaveDialog.andCallFake((options, callback) => callback(undefined)); + showSaveDialog.and.callFake((options, callback) => callback(undefined)); await pane.saveActiveItem(); expect(pane.getActiveItem().saveAs).not.toHaveBeenCalled(); }); }); describe("when the item's saveAs rejects with a well-known IO error", () => { - it('creates a notification', () => { + it('creates a notification', (done) => { pane.getActiveItem().saveAs = () => { const error = new Error("EACCES, permission denied '/foo'"); error.path = '/foo'; @@ -927,23 +925,19 @@ describe('Pane', () => { return Promise.reject(error); }; - waitsFor(done => { - const subscription = atom.notifications.onDidAddNotification(function( - notification - ) { - expect(notification.getType()).toBe('warning'); - expect(notification.getMessage()).toContain('Permission denied'); - expect(notification.getMessage()).toContain('/foo'); - subscription.dispose(); - done(); - }); - pane.saveActiveItem(); + const subscription = atom.notifications.onDidAddNotification(function(notification) { + expect(notification.getType()).toBe('warning'); + expect(notification.getMessage()).toContain('Permission denied'); + expect(notification.getMessage()).toContain('/foo'); + subscription.dispose(); + done(); }); + pane.saveActiveItem(); }); }); describe("when the item's saveAs throws a well-known IO error", () => { - it('creates a notification', () => { + it('creates a notification', (done) => { pane.getActiveItem().saveAs = () => { const error = new Error("EACCES, permission denied '/foo'"); error.path = '/foo'; @@ -951,18 +945,14 @@ describe('Pane', () => { throw error; }; - waitsFor(done => { - const subscription = atom.notifications.onDidAddNotification(function( - notification - ) { - expect(notification.getType()).toBe('warning'); - expect(notification.getMessage()).toContain('Permission denied'); - expect(notification.getMessage()).toContain('/foo'); - subscription.dispose(); - done(); - }); - pane.saveActiveItem(); + const subscription = atom.notifications.onDidAddNotification(function(notification) { + expect(notification.getType()).toBe('warning'); + expect(notification.getMessage()).toContain('Permission denied'); + expect(notification.getMessage()).toContain('/foo'); + subscription.dispose(); + done(); }); + pane.saveActiveItem(); }); }); }); @@ -972,7 +962,7 @@ describe('Pane', () => { beforeEach(() => { pane = new Pane(paneParams({ items: [new Item('A')] })); - showSaveDialog.andCallFake((options, callback) => + showSaveDialog.and.callFake((options, callback) => callback('/selected/path') ); }); @@ -984,12 +974,12 @@ describe('Pane', () => { pane.getActiveItem().path = __filename; pane.getActiveItem().saveAs = jasmine.createSpy('saveAs'); pane.saveActiveItemAs(); - expect(showSaveDialog.mostRecentCall.args[0]).toEqual({ + expect(showSaveDialog.calls.mostRecent().args[0]).toEqual({ defaultPath: __filename }); await conditionPromise( - () => pane.getActiveItem().saveAs.callCount === 1 + () => pane.getActiveItem().saveAs.calls.count() === 1 ); expect(pane.getActiveItem().saveAs).toHaveBeenCalledWith( '/selected/path' @@ -1006,7 +996,7 @@ describe('Pane', () => { }); describe("when the item's saveAs method throws a well-known IO error", () => { - it('creates a notification', () => { + it('creates a notification', (done) => { pane.getActiveItem().saveAs = () => { const error = new Error("EACCES, permission denied '/foo'"); error.path = '/foo'; @@ -1014,18 +1004,14 @@ describe('Pane', () => { return Promise.reject(error); }; - waitsFor(done => { - const subscription = atom.notifications.onDidAddNotification(function( - notification - ) { - expect(notification.getType()).toBe('warning'); - expect(notification.getMessage()).toContain('Permission denied'); - expect(notification.getMessage()).toContain('/foo'); - subscription.dispose(); - done(); - }); - pane.saveActiveItemAs(); + const subscription = atom.notifications.onDidAddNotification(function(notification) { + expect(notification.getType()).toBe('warning'); + expect(notification.getMessage()).toContain('Permission denied'); + expect(notification.getMessage()).toContain('/foo'); + subscription.dispose(); + done(); }); + pane.saveActiveItemAs(); }); }); }); @@ -1383,7 +1369,7 @@ describe('Pane', () => { item1.getURI = () => '/test/path'; item1.save = jasmine.createSpy('save'); - confirm.andCallFake((options, callback) => callback(0)); + confirm.and.callFake((options, callback) => callback(0)); await pane.close(); expect(confirm).toHaveBeenCalled(); expect(item1.save).toHaveBeenCalled(); @@ -1400,7 +1386,7 @@ describe('Pane', () => { item1.getURI = () => '/test/path'; item1.save = jasmine.createSpy('save'); - confirm.andCallFake((options, callback) => callback(1)); + confirm.and.callFake((options, callback) => callback(1)); await pane.close(); expect(confirm).toHaveBeenCalled(); @@ -1417,12 +1403,12 @@ describe('Pane', () => { item1.shouldPromptToSave = () => true; item1.saveAs = jasmine.createSpy('saveAs'); - confirm.andCallFake((options, callback) => callback(0)); - showSaveDialog.andCallFake((options, callback) => callback(undefined)); + confirm.and.callFake((options, callback) => callback(0)); + showSaveDialog.and.callFake((options, callback) => callback(undefined)); await pane.close(); expect(atom.applicationDelegate.confirm).toHaveBeenCalled(); - expect(confirm.callCount).toBe(1); + expect(confirm.calls.count()).toBe(1); expect(item1.saveAs).not.toHaveBeenCalled(); expect(pane.isDestroyed()).toBe(false); }); @@ -1441,7 +1427,7 @@ describe('Pane', () => { item1.shouldPromptToSave = () => true; item1.getURI = () => '/test/path'; - item1.save = jasmine.createSpy('save').andCallFake(() => { + item1.save = jasmine.createSpy('save').and.callFake(() => { const error = new Error("EACCES, permission denied '/test/path'"); error.path = '/test/path'; error.code = 'EACCES'; @@ -1451,7 +1437,7 @@ describe('Pane', () => { it('does not destroy the pane if save fails and user clicks cancel', async () => { let confirmations = 0; - confirm.andCallFake((options, callback) => { + confirm.and.callFake((options, callback) => { confirmations++; if (confirmations === 1) { callback(0); // click save @@ -1468,21 +1454,21 @@ describe('Pane', () => { }); it('does destroy the pane if the user saves the file under a new name', async () => { - item1.saveAs = jasmine.createSpy('saveAs').andReturn(true); + item1.saveAs = jasmine.createSpy('saveAs').and.returnValue(true); let confirmations = 0; - confirm.andCallFake((options, callback) => { + confirm.and.callFake((options, callback) => { confirmations++; callback(0); }); // save and then save as - showSaveDialog.andCallFake((options, callback) => callback('new/path')); + showSaveDialog.and.callFake((options, callback) => callback('new/path')); await pane.close(); expect(atom.applicationDelegate.confirm).toHaveBeenCalled(); expect(confirmations).toBe(2); expect( - atom.applicationDelegate.showSaveDialog.mostRecentCall.args[0] + atom.applicationDelegate.showSaveDialog.calls.mostRecent().args[0] ).toEqual({}); expect(item1.save).toHaveBeenCalled(); expect(item1.saveAs).toHaveBeenCalled(); @@ -1490,7 +1476,7 @@ describe('Pane', () => { }); it('asks again if the saveAs also fails', async () => { - item1.saveAs = jasmine.createSpy('saveAs').andCallFake(() => { + item1.saveAs = jasmine.createSpy('saveAs').and.callFake(() => { const error = new Error("EACCES, permission denied '/test/path'"); error.path = '/test/path'; error.code = 'EACCES'; @@ -1498,7 +1484,7 @@ describe('Pane', () => { }); let confirmations = 0; - confirm.andCallFake((options, callback) => { + confirm.and.callFake((options, callback) => { confirmations++; if (confirmations < 3) { callback(0); // save, save as, save as @@ -1507,13 +1493,13 @@ describe('Pane', () => { } }); - showSaveDialog.andCallFake((options, callback) => callback('new/path')); + showSaveDialog.and.callFake((options, callback) => callback('new/path')); await pane.close(); expect(atom.applicationDelegate.confirm).toHaveBeenCalled(); expect(confirmations).toBe(3); expect( - atom.applicationDelegate.showSaveDialog.mostRecentCall.args[0] + atom.applicationDelegate.showSaveDialog.calls.mostRecent().args[0] ).toEqual({}); expect(item1.save).toHaveBeenCalled(); expect(item1.saveAs).toHaveBeenCalled(); @@ -1633,13 +1619,13 @@ describe('Pane', () => { }); it('only calls clearPendingItem if there is a pending item to clear', () => { - spyOn(pane, 'clearPendingItem').andCallThrough(); + spyOn(pane, 'clearPendingItem').and.callThrough(); editor1.terminatePendingState(); editor1.terminatePendingState(); expect(pane.getPendingItem()).toBeNull(); - expect(pane.clearPendingItem.callCount).toBe(1); + expect(pane.clearPendingItem.calls.count()).toBe(1); }); }); diff --git a/spec/panel-container-element-spec.js b/spec/panel-container-element-spec.js index 5d5429242a..d4975d0ca7 100644 --- a/spec/panel-container-element-spec.js +++ b/spec/panel-container-element-spec.js @@ -2,6 +2,7 @@ const Panel = require('../src/panel'); const PanelContainer = require('../src/panel-container'); +const { conditionPromise } = require('./helpers/async-spec-helpers'); describe('PanelContainerElement', () => { let jasmineContent, element, container; @@ -254,7 +255,7 @@ describe('PanelContainerElement', () => { panel.destroy() }); - it('returns focus to the original activeElement', () => { + it('returns focus to the original activeElement', async () => { const panel = createPanel(); const previousActiveElement = document.activeElement; const panelEl = panel.getElement(); @@ -263,10 +264,10 @@ describe('PanelContainerElement', () => { panel.show(); panel.hide(); - waitsFor(() => document.activeElement === previousActiveElement); - runs(() => { - expect(document.activeElement).toBe(previousActiveElement); - }); + jasmine.useRealClock(); + await conditionPromise(() => document.activeElement === previousActiveElement); + + expect(document.activeElement).toBe(previousActiveElement); }); }); }); diff --git a/spec/panel-spec.js b/spec/panel-spec.js index d778e295a5..f0b3db6c80 100644 --- a/spec/panel-spec.js +++ b/spec/panel-spec.js @@ -60,7 +60,7 @@ describe('Panel', () => { panel.hide(); expect(panel.isVisible()).toBe(false); expect(spy).toHaveBeenCalledWith(false); - spy.reset(); + spy.calls.reset(); panel.show(); expect(panel.isVisible()).toBe(true); diff --git a/spec/project-spec.js b/spec/project-spec.js index a812418c73..3b8777b43b 100644 --- a/spec/project-spec.js +++ b/spec/project-spec.js @@ -12,9 +12,6 @@ describe('Project', () => { const directory = atom.project.getDirectories()[0]; const paths = directory ? [directory.resolve('dir')] : [null]; atom.project.setPaths(paths); - - // Wait for project's service consumers to be asynchronously added - waits(1); }); describe('serialization', () => { @@ -34,7 +31,7 @@ describe('Project', () => { } }); - it("does not deserialize paths to directories that don't exist", () => { + it("does not deserialize paths to directories that don't exist", async () => { deserializedProject = new Project({ notificationManager: atom.notifications, packageManager: atom.packages, @@ -45,21 +42,15 @@ describe('Project', () => { state.paths.push('/directory/that/does/not/exist'); let err = null; - waitsForPromise(() => - deserializedProject.deserialize(state, atom.deserializers).catch(e => { - err = e; - }) - ); + await deserializedProject.deserialize(state, atom.deserializers).catch(e => err = e); - runs(() => { - expect(deserializedProject.getPaths()).toEqual(atom.project.getPaths()); - expect(err.missingProjectPaths).toEqual([ - '/directory/that/does/not/exist' - ]); - }); + expect(deserializedProject.getPaths()).toEqual(atom.project.getPaths()); + expect(err.missingProjectPaths).toEqual([ + '/directory/that/does/not/exist' + ]); }); - it('does not deserialize paths that are now files', () => { + it('does not deserialize paths that are now files', async () => { const childPath = path.join(temp.mkdirSync('atom-spec-project'), 'child'); fs.mkdirSync(childPath); @@ -76,262 +67,192 @@ describe('Project', () => { fs.writeFileSync(childPath, 'surprise!\n'); let err = null; - waitsForPromise(() => - deserializedProject.deserialize(state, atom.deserializers).catch(e => { - err = e; - }) - ); + await deserializedProject.deserialize(state, atom.deserializers).catch(e => err = e); - runs(() => { - expect(deserializedProject.getPaths()).toEqual([]); - expect(err.missingProjectPaths).toEqual([childPath]); - }); + expect(deserializedProject.getPaths()).toEqual([]); + expect(err.missingProjectPaths).toEqual([childPath]); }); - it('does not include unretained buffers in the serialized state', () => { - waitsForPromise(() => atom.project.bufferForPath('a')); + it('does not include unretained buffers in the serialized state', async () => { + await atom.project.bufferForPath('a'); - runs(() => { - expect(atom.project.getBuffers().length).toBe(1); + expect(atom.project.getBuffers().length).toBe(1); - deserializedProject = new Project({ - notificationManager: atom.notifications, - packageManager: atom.packages, - confirm: atom.confirm, - grammarRegistry: atom.grammars - }); + deserializedProject = new Project({ + notificationManager: atom.notifications, + packageManager: atom.packages, + confirm: atom.confirm, + grammarRegistry: atom.grammars }); - waitsForPromise(() => - deserializedProject.deserialize( - atom.project.serialize({ isUnloading: false }) - ) - ); + await deserializedProject.deserialize(atom.project.serialize({ isUnloading: false })); - runs(() => expect(deserializedProject.getBuffers().length).toBe(0)); + expect(deserializedProject.getBuffers().length).toBe(0); }); - it('listens for destroyed events on deserialized buffers and removes them when they are destroyed', () => { - waitsForPromise(() => atom.workspace.open('a')); + it('listens for destroyed events on deserialized buffers and removes them when they are destroyed', async () => { + await atom.workspace.open('a'); - runs(() => { - expect(atom.project.getBuffers().length).toBe(1); - deserializedProject = new Project({ - notificationManager: atom.notifications, - packageManager: atom.packages, - confirm: atom.confirm, - grammarRegistry: atom.grammars - }); + expect(atom.project.getBuffers().length).toBe(1); + deserializedProject = new Project({ + notificationManager: atom.notifications, + packageManager: atom.packages, + confirm: atom.confirm, + grammarRegistry: atom.grammars }); - waitsForPromise(() => - deserializedProject.deserialize( - atom.project.serialize({ isUnloading: false }) - ) - ); + await deserializedProject.deserialize(atom.project.serialize({ isUnloading: false })); - runs(() => { - expect(deserializedProject.getBuffers().length).toBe(1); - deserializedProject.getBuffers()[0].destroy(); - expect(deserializedProject.getBuffers().length).toBe(0); - }); + expect(deserializedProject.getBuffers().length).toBe(1); + deserializedProject.getBuffers()[0].destroy(); + expect(deserializedProject.getBuffers().length).toBe(0); }); - it('does not deserialize buffers when their path is now a directory', () => { + it('does not deserialize buffers when their path is now a directory', async () => { const pathToOpen = path.join( temp.mkdirSync('atom-spec-project'), 'file.txt' ); - waitsForPromise(() => atom.workspace.open(pathToOpen)); - - runs(() => { - expect(atom.project.getBuffers().length).toBe(1); - fs.mkdirSync(pathToOpen); - deserializedProject = new Project({ - notificationManager: atom.notifications, - packageManager: atom.packages, - confirm: atom.confirm, - grammarRegistry: atom.grammars - }); + await atom.workspace.open(pathToOpen); + + expect(atom.project.getBuffers().length).toBe(1); + fs.mkdirSync(pathToOpen); + deserializedProject = new Project({ + notificationManager: atom.notifications, + packageManager: atom.packages, + confirm: atom.confirm, + grammarRegistry: atom.grammars }); - waitsForPromise(() => - deserializedProject.deserialize( - atom.project.serialize({ isUnloading: false }) - ) - ); + await deserializedProject.deserialize(atom.project.serialize({ isUnloading: false })); - runs(() => expect(deserializedProject.getBuffers().length).toBe(0)); + expect(deserializedProject.getBuffers().length).toBe(0); }); - it('does not deserialize buffers when their path is inaccessible', () => { - if (process.platform === 'win32') { - return; - } // chmod not supported on win32 + it('does not deserialize buffers when their path is inaccessible', async (done) => { + jasmine.filterByPlatform({except: ['win32']}, done); // chmod not supported on win32 + const pathToOpen = path.join( temp.mkdirSync('atom-spec-project'), 'file.txt' ); fs.writeFileSync(pathToOpen, ''); - waitsForPromise(() => atom.workspace.open(pathToOpen)); - - runs(() => { - expect(atom.project.getBuffers().length).toBe(1); - fs.chmodSync(pathToOpen, '000'); - deserializedProject = new Project({ - notificationManager: atom.notifications, - packageManager: atom.packages, - confirm: atom.confirm, - grammarRegistry: atom.grammars - }); + await atom.workspace.open(pathToOpen); + + expect(atom.project.getBuffers().length).toBe(1); + fs.chmodSync(pathToOpen, '000'); + deserializedProject = new Project({ + notificationManager: atom.notifications, + packageManager: atom.packages, + confirm: atom.confirm, + grammarRegistry: atom.grammars }); - waitsForPromise(() => - deserializedProject.deserialize( - atom.project.serialize({ isUnloading: false }) - ) - ); + await deserializedProject.deserialize(atom.project.serialize({ isUnloading: false })); + + expect(deserializedProject.getBuffers().length).toBe(0); - runs(() => expect(deserializedProject.getBuffers().length).toBe(0)); + done(); }); - it('does not deserialize buffers with their path is no longer present', () => { + it('does not deserialize buffers with their path is no longer present', async () => { const pathToOpen = path.join( temp.mkdirSync('atom-spec-project'), 'file.txt' ); fs.writeFileSync(pathToOpen, ''); - waitsForPromise(() => atom.workspace.open(pathToOpen)); - - runs(() => { - expect(atom.project.getBuffers().length).toBe(1); - fs.unlinkSync(pathToOpen); - deserializedProject = new Project({ - notificationManager: atom.notifications, - packageManager: atom.packages, - confirm: atom.confirm, - grammarRegistry: atom.grammars - }); + await atom.workspace.open(pathToOpen); + + expect(atom.project.getBuffers().length).toBe(1); + fs.unlinkSync(pathToOpen); + deserializedProject = new Project({ + notificationManager: atom.notifications, + packageManager: atom.packages, + confirm: atom.confirm, + grammarRegistry: atom.grammars }); - waitsForPromise(() => - deserializedProject.deserialize( - atom.project.serialize({ isUnloading: false }) - ) - ); + await deserializedProject.deserialize(atom.project.serialize({ isUnloading: false })); - runs(() => expect(deserializedProject.getBuffers().length).toBe(0)); + expect(deserializedProject.getBuffers().length).toBe(0); }); - it('deserializes buffers that have never been saved before', () => { + it('deserializes buffers that have never been saved before', async () => { const pathToOpen = path.join( temp.mkdirSync('atom-spec-project'), 'file.txt' ); - waitsForPromise(() => atom.workspace.open(pathToOpen)); + await atom.workspace.open(pathToOpen); - runs(() => { - atom.workspace.getActiveTextEditor().setText('unsaved\n'); - expect(atom.project.getBuffers().length).toBe(1); + atom.workspace.getActiveTextEditor().setText('unsaved\n'); + expect(atom.project.getBuffers().length).toBe(1); - deserializedProject = new Project({ - notificationManager: atom.notifications, - packageManager: atom.packages, - confirm: atom.confirm, - grammarRegistry: atom.grammars - }); + deserializedProject = new Project({ + notificationManager: atom.notifications, + packageManager: atom.packages, + confirm: atom.confirm, + grammarRegistry: atom.grammars }); - waitsForPromise(() => - deserializedProject.deserialize( - atom.project.serialize({ isUnloading: false }) - ) - ); + await deserializedProject.deserialize(atom.project.serialize({ isUnloading: false })); - runs(() => { - expect(deserializedProject.getBuffers().length).toBe(1); - expect(deserializedProject.getBuffers()[0].getPath()).toBe(pathToOpen); - expect(deserializedProject.getBuffers()[0].getText()).toBe('unsaved\n'); - }); + expect(deserializedProject.getBuffers().length).toBe(1); + expect(deserializedProject.getBuffers()[0].getPath()).toBe(pathToOpen); + expect(deserializedProject.getBuffers()[0].getText()).toBe('unsaved\n'); }); - it('serializes marker layers and history only if Atom is quitting', () => { - waitsForPromise(() => atom.workspace.open('a')); - - let bufferA = null; - let layerA = null; - let markerA = null; - - runs(() => { - bufferA = atom.project.getBuffers()[0]; - layerA = bufferA.addMarkerLayer({ persistent: true }); - markerA = layerA.markPosition([0, 3]); - bufferA.append('!'); - notQuittingProject = new Project({ - notificationManager: atom.notifications, - packageManager: atom.packages, - confirm: atom.confirm, - grammarRegistry: atom.grammars - }); - }); + it('serializes marker layers and history only if Atom is quitting', async () => { + await atom.workspace.open('a'); - waitsForPromise(() => - notQuittingProject.deserialize( - atom.project.serialize({ isUnloading: false }) - ) - ); + let bufferA = atom.project.getBuffers()[0]; + let layerA = bufferA.addMarkerLayer({ persistent: true }); + let markerA = layerA.markPosition([0, 3]); - runs(() => { - expect( - notQuittingProject.getBuffers()[0].getMarkerLayer(layerA.id), - x => x.getMarker(markerA.id) - ).toBeUndefined(); - expect(notQuittingProject.getBuffers()[0].undo()).toBe(false); - quittingProject = new Project({ - notificationManager: atom.notifications, - packageManager: atom.packages, - confirm: atom.confirm, - grammarRegistry: atom.grammars - }); + bufferA.append('!'); + notQuittingProject = new Project({ + notificationManager: atom.notifications, + packageManager: atom.packages, + confirm: atom.confirm, + grammarRegistry: atom.grammars }); - waitsForPromise(() => - quittingProject.deserialize( - atom.project.serialize({ isUnloading: true }) - ) - ); + await notQuittingProject.deserialize(atom.project.serialize({ isUnloading: false })); - runs(() => { - expect(quittingProject.getBuffers()[0].getMarkerLayer(layerA.id), x => - x.getMarker(markerA.id) - ).not.toBeUndefined(); - expect(quittingProject.getBuffers()[0].undo()).toBe(true); + expect( + notQuittingProject.getBuffers()[0].getMarkerLayer(layerA.id), + x => x.getMarker(markerA.id) + ).toBeUndefined(); + expect(notQuittingProject.getBuffers()[0].undo()).toBe(false); + quittingProject = new Project({ + notificationManager: atom.notifications, + packageManager: atom.packages, + confirm: atom.confirm, + grammarRegistry: atom.grammars }); + + await quittingProject.deserialize(atom.project.serialize({ isUnloading: true })); + + expect(quittingProject.getBuffers()[0].getMarkerLayer(layerA.id), x => + x.getMarker(markerA.id) + ).not.toBeUndefined(); + expect(quittingProject.getBuffers()[0].undo()).toBe(true); }); }); describe('when an editor is saved and the project has no path', () => { - it("sets the project's path to the saved file's parent directory", () => { + it("sets the project's path to the saved file's parent directory", async () => { const tempFile = temp.openSync().path; atom.project.setPaths([]); expect(atom.project.getPaths()[0]).toBeUndefined(); - let editor = null; - - waitsForPromise(() => - atom.workspace.open().then(o => { - editor = o; - }) - ); + let editor = await atom.workspace.open(); - waitsForPromise(() => editor.saveAs(tempFile)); + await editor.saveAs(tempFile); - runs(() => - expect(atom.project.getPaths()[0]).toBe(path.dirname(tempFile)) - ); + expect(atom.project.getPaths()[0]).toBe(path.dirname(tempFile)) }); }); @@ -382,51 +303,39 @@ describe('Project', () => { describe('before and after saving a buffer', () => { let buffer; - beforeEach(() => - waitsForPromise(() => - atom.project - .bufferForPath(path.join(__dirname, 'fixtures', 'sample.js')) - .then(o => { - buffer = o; - buffer.retain(); - }) - ) - ); + beforeEach(async () => { + buffer = await atom.project.bufferForPath(path.join(__dirname, 'fixtures', 'sample.js')) + buffer.retain(); + }); afterEach(() => buffer.release()); - it('emits save events on the main process', () => { + it('emits save events on the main process', async () => { spyOn(atom.project.applicationDelegate, 'emitDidSavePath'); spyOn(atom.project.applicationDelegate, 'emitWillSavePath'); - waitsForPromise(() => buffer.save()); - - runs(() => { - expect( - atom.project.applicationDelegate.emitDidSavePath.calls.length - ).toBe(1); - expect( - atom.project.applicationDelegate.emitDidSavePath - ).toHaveBeenCalledWith(buffer.getPath()); - expect( - atom.project.applicationDelegate.emitWillSavePath.calls.length - ).toBe(1); - expect( - atom.project.applicationDelegate.emitWillSavePath - ).toHaveBeenCalledWith(buffer.getPath()); - }); + await buffer.save(); + + expect( + atom.project.applicationDelegate.emitDidSavePath.calls.count() + ).toBe(1); + expect( + atom.project.applicationDelegate.emitDidSavePath + ).toHaveBeenCalledWith(buffer.getPath()); + expect( + atom.project.applicationDelegate.emitWillSavePath.calls.count() + ).toBe(1); + expect( + atom.project.applicationDelegate.emitWillSavePath + ).toHaveBeenCalledWith(buffer.getPath()); }); }); describe('when a watch error is thrown from the TextBuffer', () => { let editor = null; - beforeEach(() => - waitsForPromise(() => - atom.workspace.open(require.resolve('./fixtures/dir/a')).then(o => { - editor = o; - }) - ) - ); + beforeEach(async () => { + editor = await atom.workspace.open(require.resolve('./fixtures/dir/a')); + }); it('creates a warning notification', () => { let noteSpy; @@ -441,7 +350,7 @@ describe('Project', () => { expect(noteSpy).toHaveBeenCalled(); - const notification = noteSpy.mostRecentCall.args[0]; + const notification = noteSpy.calls.mostRecent().args[0]; expect(notification.getType()).toBe('warning'); expect(notification.getDetail()).toBe('SomeError'); expect(notification.getMessage()).toContain('`resurrect`'); @@ -480,8 +389,7 @@ describe('Project', () => { '0.1.0', fakeRepositoryProvider ); - waitsFor(() => atom.project.repositoryProviders.length > 1); - runs(() => atom.project.getRepositories()[0] === fakeRepository); + atom.project.getRepositories()[0] === fakeRepository; }); it('does not create any new repositories if every directory has a repository', () => { @@ -494,8 +402,7 @@ describe('Project', () => { '0.1.0', fakeRepositoryProvider ); - waitsFor(() => atom.project.repositoryProviders.length > 1); - runs(() => expect(atom.project.getRepositories()).toBe(repositories)); + expect(atom.project.getRepositories()).toBe(repositories); }); it('stops using it to create repositories when the service is removed', () => { @@ -506,12 +413,10 @@ describe('Project', () => { '0.1.0', fakeRepositoryProvider ); - waitsFor(() => atom.project.repositoryProviders.length > 1); - runs(() => { - disposable.dispose(); - atom.project.addPath(temp.mkdirSync('atom-project')); - expect(atom.project.getRepositories()).toEqual([null]); - }); + + disposable.dispose(); + atom.project.addPath(temp.mkdirSync('atom-project')); + expect(atom.project.getRepositories()).toEqual([null]); }); }); @@ -570,8 +475,6 @@ describe('Project', () => { } ); onDidChangeFilesCallback = null; - - waitsFor(() => atom.project.directoryProviders.length > 0); }); it("uses the provider's custom directories for any paths that it handles", () => { @@ -606,26 +509,24 @@ describe('Project', () => { expect(atom.project.getDirectories().length).toBe(0); }); - it('uses the custom onDidChangeFiles as the watcher if available', () => { + it('uses the custom onDidChangeFiles as the watcher if available', async () => { // Ensure that all preexisting watchers are stopped - waitsForPromise(() => stopAllWatchers()); + await stopAllWatchers(); const remotePath = 'ssh://another-directory:8080/does-exist'; - runs(() => atom.project.setPaths([remotePath])); - waitsForPromise(() => atom.project.getWatcherPromise(remotePath)); + atom.project.setPaths([remotePath]); + await atom.project.getWatcherPromise(remotePath); - runs(() => { - expect(onDidChangeFilesCallback).not.toBeNull(); + expect(onDidChangeFilesCallback).not.toBeNull(); - const changeSpy = jasmine.createSpy('atom.project.onDidChangeFiles'); - const disposable = atom.project.onDidChangeFiles(changeSpy); + const changeSpy = jasmine.createSpy('atom.project.onDidChangeFiles'); + const disposable = atom.project.onDidChangeFiles(changeSpy); - const events = [{ action: 'created', path: remotePath + '/test.txt' }]; - onDidChangeFilesCallback(events); + const events = [{ action: 'created', path: remotePath + '/test.txt' }]; + onDidChangeFilesCallback(events); - expect(changeSpy).toHaveBeenCalledWith(events); - disposable.dispose(); - }); + expect(changeSpy).toHaveBeenCalledWith(events); + disposable.dispose(); }); }); @@ -639,77 +540,46 @@ describe('Project', () => { }); describe("when given an absolute path that isn't currently open", () => { - it("returns a new edit session for the given path and emits 'buffer-created'", () => { - let editor = null; - waitsForPromise(() => - atom.workspace.open(absolutePath).then(o => { - editor = o; - }) - ); + it("returns a new edit session for the given path and emits 'buffer-created'", async () => { + let editor = await atom.workspace.open(absolutePath); - runs(() => { - expect(editor.buffer.getPath()).toBe(absolutePath); - expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer); - }); + expect(editor.buffer.getPath()).toBe(absolutePath); + expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer); }); }); describe("when given a relative path that isn't currently opened", () => { - it("returns a new edit session for the given path (relative to the project root) and emits 'buffer-created'", () => { - let editor = null; - waitsForPromise(() => - atom.workspace.open(absolutePath).then(o => { - editor = o; - }) - ); + it("returns a new edit session for the given path (relative to the project root) and emits 'buffer-created'", async () => { + let editor = await atom.workspace.open(absolutePath); - runs(() => { - expect(editor.buffer.getPath()).toBe(absolutePath); - expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer); - }); + expect(editor.buffer.getPath()).toBe(absolutePath); + expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer); }); }); describe('when passed the path to a buffer that is currently opened', () => { - it('returns a new edit session containing currently opened buffer', () => { - let editor = null; + it('returns a new edit session containing currently opened buffer', async () => { + let editor = await atom.workspace.open(absolutePath); + let buffer; - waitsForPromise(() => - atom.workspace.open(absolutePath).then(o => { - editor = o; - }) - ); + newBufferHandler.calls.reset(); - runs(() => newBufferHandler.reset()); + buffer = (await atom.workspace.open(absolutePath)).buffer; + expect(buffer).toBe(editor.buffer); - waitsForPromise(() => - atom.workspace - .open(absolutePath) - .then(({ buffer }) => expect(buffer).toBe(editor.buffer)) - ); - waitsForPromise(() => - atom.workspace.open('a').then(({ buffer }) => { - expect(buffer).toBe(editor.buffer); - expect(newBufferHandler).not.toHaveBeenCalled(); - }) - ); + buffer = (await atom.workspace.open('a')).buffer; + expect(buffer).toBe(editor.buffer); + expect(newBufferHandler).not.toHaveBeenCalled(); }); }); describe('when not passed a path', () => { - it("returns a new edit session and emits 'buffer-created'", () => { - let editor = null; - waitsForPromise(() => - atom.workspace.open().then(o => { - editor = o; - }) - ); + it("returns a new edit session and emits 'buffer-created'", async () => { + let editor = await atom.workspace.open(); - runs(() => { - expect(editor.buffer.getPath()).toBeUndefined(); - expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer); - }); + expect(editor.buffer.getPath()).toBeUndefined(); + expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer); }); }); }); @@ -717,117 +587,81 @@ describe('Project', () => { describe('.bufferForPath(path)', () => { let buffer = null; - beforeEach(() => - waitsForPromise(() => - atom.project.bufferForPath('a').then(o => { - buffer = o; - buffer.retain(); - }) - ) - ); + beforeEach(async () => { + buffer = await atom.project.bufferForPath('a'); + buffer.retain(); + }); afterEach(() => buffer.release()); describe('when opening a previously opened path', () => { - it('does not create a new buffer', () => { - waitsForPromise(() => - atom.project - .bufferForPath('a') - .then(anotherBuffer => expect(anotherBuffer).toBe(buffer)) - ); + it('does not create a new buffer', async () => { + expect(await atom.project.bufferForPath('a')).toBe(buffer) + expect(await atom.project.bufferForPath('b')).not.toBe(buffer) - waitsForPromise(() => - atom.project - .bufferForPath('b') - .then(anotherBuffer => expect(anotherBuffer).not.toBe(buffer)) - ); + const [buffer1, buffer2] = await Promise.all([ + atom.project.bufferForPath('c'), + atom.project.bufferForPath('c') + ]); - waitsForPromise(() => - Promise.all([ - atom.project.bufferForPath('c'), - atom.project.bufferForPath('c') - ]).then(([buffer1, buffer2]) => { - expect(buffer1).toBe(buffer2); - }) - ); + expect(buffer1).toBe(buffer2); }); - it('retries loading the buffer if it previously failed', () => { - waitsForPromise({ shouldReject: true }, () => { - spyOn(TextBuffer, 'load').andCallFake(() => - Promise.reject(new Error('Could not open file')) - ); - return atom.project.bufferForPath('b'); - }); + it('retries loading the buffer if it previously failed', async () => { + const error = new Error('Could not open file'); + spyOn(TextBuffer, 'load').and.callFake(() => + Promise.reject(error) + ); + await atom.project.bufferForPath('b').catch(e => expect(e).toBe(error)) - waitsForPromise({ shouldReject: false }, () => { - TextBuffer.load.andCallThrough(); - return atom.project.bufferForPath('b'); - }); + TextBuffer.load.and.callThrough(); + await atom.project.bufferForPath('b') }); - it('creates a new buffer if the previous buffer was destroyed', () => { + it('creates a new buffer if the previous buffer was destroyed', async () => { buffer.release(); - - waitsForPromise(() => - atom.project - .bufferForPath('b') - .then(anotherBuffer => expect(anotherBuffer).not.toBe(buffer)) - ); + expect(await atom.project.bufferForPath('b')).not.toBe(buffer); }); }); }); describe('.repositoryForDirectory(directory)', () => { - it('resolves to null when the directory does not have a repository', () => { - waitsForPromise(() => { - const directory = new Directory('/tmp'); - return atom.project.repositoryForDirectory(directory).then(result => { - expect(result).toBeNull(); - expect(atom.project.repositoryProviders.length).toBeGreaterThan(0); - expect(atom.project.repositoryPromisesByPath.size).toBe(0); - }); - }); + it('resolves to null when the directory does not have a repository', async () => { + const directory = new Directory('/tmp'); + const result = await atom.project.repositoryForDirectory(directory); + + expect(result).toBeNull(); + expect(atom.project.repositoryProviders.length).toBeGreaterThan(0); + expect(atom.project.repositoryPromisesByPath.size).toBe(0); }); - it('resolves to a GitRepository and is cached when the given directory is a Git repo', () => { - waitsForPromise(() => { - const directory = new Directory(path.join(__dirname, '..')); - const promise = atom.project.repositoryForDirectory(directory); - return promise.then(result => { - expect(result).toBeInstanceOf(GitRepository); - const dirPath = directory.getRealPathSync(); - expect(result.getPath()).toBe(path.join(dirPath, '.git')); + it('resolves to a GitRepository and is cached when the given directory is a Git repo', async () => { + const directory = new Directory(path.join(__dirname, '..')); + + const promise = atom.project.repositoryForDirectory(directory); + const result = await promise; - // Verify that the result is cached. - expect(atom.project.repositoryForDirectory(directory)).toBe(promise); - }); - }); + expect(result).toEqual(jasmine.any(GitRepository)); + const dirPath = directory.getRealPathSync(); + expect(result.getPath()).toBe(path.join(dirPath, '.git')); + + // Verify that the result is cached. + expect(atom.project.repositoryForDirectory(directory)).toBe(promise); }); - it('creates a new repository if a previous one with the same directory had been destroyed', () => { + it('creates a new repository if a previous one with the same directory had been destroyed', async () => { let repository = null; const directory = new Directory(path.join(__dirname, '..')); - waitsForPromise(() => - atom.project.repositoryForDirectory(directory).then(repo => { - repository = repo; - }) - ); + repository = await atom.project.repositoryForDirectory(directory); - runs(() => { - expect(repository.isDestroyed()).toBe(false); - repository.destroy(); - expect(repository.isDestroyed()).toBe(true); - }); + expect(repository.isDestroyed()).toBe(false); + repository.destroy(); + expect(repository.isDestroyed()).toBe(true); - waitsForPromise(() => - atom.project.repositoryForDirectory(directory).then(repo => { - repository = repo; - }) - ); + repository = await atom.project.repositoryForDirectory(directory); - runs(() => expect(repository.isDestroyed()).toBe(false)); + expect(repository.isDestroyed()).toBe(false); }); }); @@ -876,8 +710,8 @@ describe('Project', () => { const paths = [temp.mkdirSync('dir1'), temp.mkdirSync('dir2')]; atom.project.setPaths(paths); - expect(onDidChangePathsSpy.callCount).toBe(1); - expect(onDidChangePathsSpy.mostRecentCall.args[0]).toEqual(paths); + expect(onDidChangePathsSpy.calls.count()).toBe(1); + expect(onDidChangePathsSpy.calls.mostRecent().args[0]).toEqual(paths); }); it('optionally throws an error with any paths that did not exist', () => { @@ -932,8 +766,8 @@ describe('Project', () => { const newPath = temp.mkdirSync('dir'); atom.project.addPath(newPath); - expect(onDidChangePathsSpy.callCount).toBe(1); - expect(onDidChangePathsSpy.mostRecentCall.args[0]).toEqual([ + expect(onDidChangePathsSpy.calls.count()).toBe(1); + expect(onDidChangePathsSpy.calls.mostRecent().args[0]).toEqual([ oldPath, newPath ]); @@ -1107,68 +941,39 @@ describe('Project', () => { }); describe('.onDidAddBuffer()', () => { - it('invokes the callback with added text buffers', () => { + it('invokes the callback with added text buffers', async () => { const buffers = []; const added = []; - waitsForPromise(() => - atom.project - .buildBuffer(require.resolve('./fixtures/dir/a')) - .then(o => buffers.push(o)) - ); + buffers.push(await atom.project.buildBuffer(require.resolve('./fixtures/dir/a'))) - runs(() => { - expect(buffers.length).toBe(1); - atom.project.onDidAddBuffer(buffer => added.push(buffer)); - }); + expect(buffers.length).toBe(1); + atom.project.onDidAddBuffer(buffer => added.push(buffer)); - waitsForPromise(() => - atom.project - .buildBuffer(require.resolve('./fixtures/dir/b')) - .then(o => buffers.push(o)) - ); + buffers.push(await atom.project.buildBuffer(require.resolve('./fixtures/dir/b'))) - runs(() => { - expect(buffers.length).toBe(2); - expect(added).toEqual([buffers[1]]); - }); + expect(buffers.length).toBe(2); + expect(added).toEqual([buffers[1]]); }); }); describe('.observeBuffers()', () => { - it('invokes the observer with current and future text buffers', () => { + it('invokes the observer with current and future text buffers', async () => { const buffers = []; const observed = []; - waitsForPromise(() => - atom.project - .buildBuffer(require.resolve('./fixtures/dir/a')) - .then(o => buffers.push(o)) - ); - - waitsForPromise(() => - atom.project - .buildBuffer(require.resolve('./fixtures/dir/b')) - .then(o => buffers.push(o)) - ); + buffers.push(await atom.project.buildBuffer(require.resolve('./fixtures/dir/a'))) + buffers.push(await atom.project.buildBuffer(require.resolve('./fixtures/dir/b'))) - runs(() => { - expect(buffers.length).toBe(2); - atom.project.observeBuffers(buffer => observed.push(buffer)); - expect(observed).toEqual(buffers); - }); + expect(buffers.length).toBe(2); + atom.project.observeBuffers(buffer => observed.push(buffer)); + expect(observed).toEqual(buffers); - waitsForPromise(() => - atom.project - .buildBuffer(require.resolve('./fixtures/dir/b')) - .then(o => buffers.push(o)) - ); + buffers.push(await atom.project.buildBuffer(require.resolve('./fixtures/dir/b'))) - runs(() => { - expect(observed.length).toBe(3); - expect(buffers.length).toBe(3); - expect(observed).toEqual(buffers); - }); + expect(observed.length).toBe(3); + expect(buffers.length).toBe(3); + expect(observed).toEqual(buffers); }); }); @@ -1363,8 +1168,12 @@ describe('Project', () => { }); describe('.resolvePath(uri)', () => { - it('normalizes disk drive letter in passed path on #win32', () => { + it('normalizes disk drive letter in passed path on win32', (done) => { + jasmine.filterByPlatform({only: ['win32']}, done); + expect(atom.project.resolvePath('d:\\file.txt')).toEqual('D:\\file.txt'); + + done(); }); }); }); diff --git a/spec/reopen-project-menu-manager-spec.js b/spec/reopen-project-menu-manager-spec.js index c10d9db87b..e3f2038078 100644 --- a/spec/reopen-project-menu-manager-spec.js +++ b/spec/reopen-project-menu-manager-spec.js @@ -18,17 +18,17 @@ describe('ReopenProjectMenuManager', () => { beforeEach(() => { menuManager = jasmine.createSpyObj('MenuManager', ['add']); - menuManager.add.andReturn(new Disposable()); + menuManager.add.and.returnValue(new Disposable()); commandRegistry = jasmine.createSpyObj('CommandRegistry', ['add']); commandDisposable = jasmine.createSpyObj('Disposable', ['dispose']); - commandRegistry.add.andReturn(commandDisposable); + commandRegistry.add.and.returnValue(commandDisposable); config = jasmine.createSpyObj('Config', ['onDidChange', 'get']); - config.get.andReturn(10); + config.get.and.returnValue(10); configDisposable = jasmine.createSpyObj('Disposable', ['dispose']); config.didChangeListener = {}; - config.onDidChange.andCallFake((key, fn) => { + config.onDidChange.and.callFake((key, fn) => { config.didChangeListener[key] = fn; return configDisposable; }); @@ -37,9 +37,9 @@ describe('ReopenProjectMenuManager', () => { 'getProjects', 'onDidChangeProjects' ]); - historyManager.getProjects.andReturn([]); + historyManager.getProjects.and.returnValue([]); historyDisposable = jasmine.createSpyObj('Disposable', ['dispose']); - historyManager.onDidChangeProjects.andCallFake(fn => { + historyManager.onDidChangeProjects.and.callFake(fn => { historyManager.changeProjectsListener = fn; return historyDisposable; }); @@ -57,7 +57,7 @@ describe('ReopenProjectMenuManager', () => { describe('constructor', () => { it("registers the 'reopen-project' command function", () => { expect(commandRegistry.add).toHaveBeenCalled(); - const cmdCall = commandRegistry.add.calls[0]; + const cmdCall = commandRegistry.add.calls.all()[0]; expect(cmdCall.args.length).toBe(2); expect(cmdCall.args[0]).toBe('atom-workspace'); expect(typeof cmdCall.args[1]['application:reopen-project']).toBe( @@ -76,7 +76,7 @@ describe('ReopenProjectMenuManager', () => { it('disposes of the menu disposable once used', () => { const menuDisposable = jasmine.createSpyObj('Disposable', ['dispose']); - menuManager.add.andReturn(menuDisposable); + menuManager.add.and.returnValue(menuDisposable); reopenProjects.update(); expect(menuDisposable.dispose).not.toHaveBeenCalled(); reopenProjects.dispose(); @@ -86,23 +86,23 @@ describe('ReopenProjectMenuManager', () => { describe('the command', () => { it('calls open with the paths of the project specified by the detail index', () => { - historyManager.getProjects.andReturn([ + historyManager.getProjects.and.returnValue([ { paths: ['/a'] }, { paths: ['/b', 'c:\\'] } ]); reopenProjects.update(); const reopenProjectCommand = - commandRegistry.add.calls[0].args[1]['application:reopen-project']; + commandRegistry.add.calls.all()[0].args[1]['application:reopen-project']; reopenProjectCommand({ detail: { index: 1 } }); expect(openFunction).toHaveBeenCalled(); - expect(openFunction.calls[0].args[0]).toEqual(['/b', 'c:\\']); + expect(openFunction.calls.all()[0].args[0]).toEqual(['/b', 'c:\\']); }); it('does not call open when no command detail is supplied', () => { const reopenProjectCommand = - commandRegistry.add.calls[0].args[1]['application:reopen-project']; + commandRegistry.add.calls.all()[0].args[1]['application:reopen-project']; reopenProjectCommand({}); expect(openFunction).not.toHaveBeenCalled(); @@ -110,7 +110,7 @@ describe('ReopenProjectMenuManager', () => { it('does not call open when no command detail index is supplied', () => { const reopenProjectCommand = - commandRegistry.add.calls[0].args[1]['application:reopen-project']; + commandRegistry.add.calls.all()[0].args[1]['application:reopen-project']; reopenProjectCommand({ detail: { anything: 'here' } }); expect(openFunction).not.toHaveBeenCalled(); @@ -119,14 +119,14 @@ describe('ReopenProjectMenuManager', () => { describe('update', () => { it('adds menu items to MenuManager based on projects from HistoryManager', () => { - historyManager.getProjects.andReturn([ + historyManager.getProjects.and.returnValue([ { paths: ['/a'] }, { paths: ['/b', 'c:\\'] } ]); reopenProjects.update(); expect(historyManager.getProjects).toHaveBeenCalled(); expect(menuManager.add).toHaveBeenCalled(); - const menuArg = menuManager.add.calls[0].args[0]; + const menuArg = menuManager.add.calls.all()[0].args[0]; expect(menuArg.length).toBe(1); expect(menuArg[0].label).toBe('File'); expect(menuArg[0].submenu.length).toBe(1); @@ -146,12 +146,12 @@ describe('ReopenProjectMenuManager', () => { }); it("adds only the number of menu items specified in the 'core.reopenProjectMenuCount' config", () => { - historyManager.getProjects.andReturn( + historyManager.getProjects.and.returnValue( numberRange(1, 100).map(i => ({ paths: ['/test/' + i] })) ); reopenProjects.update(); expect(menuManager.add).toHaveBeenCalled(); - const menu = menuManager.add.calls[0].args[0][0]; + const menu = menuManager.add.calls.all()[0].args[0][0]; expect(menu.label).toBe('File'); expect(menu.submenu.length).toBe(1); expect(menu.submenu[0].label).toBe('Reopen Project'); @@ -160,7 +160,7 @@ describe('ReopenProjectMenuManager', () => { it('disposes the previously menu built', () => { const menuDisposable = jasmine.createSpyObj('Disposable', ['dispose']); - menuManager.add.andReturn(menuDisposable); + menuManager.add.and.returnValue(menuDisposable); reopenProjects.update(); expect(menuDisposable.dispose).not.toHaveBeenCalled(); reopenProjects.update(); @@ -168,17 +168,17 @@ describe('ReopenProjectMenuManager', () => { }); it("is called when the Config changes for 'core.reopenProjectMenuCount'", () => { - historyManager.getProjects.andReturn( + historyManager.getProjects.and.returnValue( numberRange(1, 100).map(i => ({ paths: ['/test/' + i] })) ); reopenProjects.update(); - config.get.andReturn(25); + config.get.and.returnValue(25); config.didChangeListener['core.reopenProjectMenuCount']({ oldValue: 10, newValue: 25 }); - const finalArgs = menuManager.add.calls[1].args[0]; + const finalArgs = menuManager.add.calls.all()[1].args[0]; const projectsMenu = finalArgs[0].submenu[0].submenu; expect(projectsMenu.length).toBe(25); @@ -186,14 +186,14 @@ describe('ReopenProjectMenuManager', () => { it("is called when the HistoryManager's projects change", () => { reopenProjects.update(); - historyManager.getProjects.andReturn([ + historyManager.getProjects.and.returnValue([ { paths: ['/a'] }, { paths: ['/b', 'c:\\'] } ]); historyManager.changeProjectsListener(); - expect(menuManager.add.calls.length).toBe(2); + expect(menuManager.add.calls.count()).toBe(2); - const finalArgs = menuManager.add.calls[1].args[0]; + const finalArgs = menuManager.add.calls.all()[1].args[0]; const projectsMenu = finalArgs[0].submenu[0]; const first = projectsMenu.submenu[0]; @@ -277,20 +277,24 @@ describe('ReopenProjectMenuManager', () => { expect(name).toBe('three'); }); - it('returns the standard base name for a relative Windows path', () => { - if (process.platform === 'win32') { - const name = ReopenProjectMenuManager.betterBaseName('.\\one\\two'); - expect(name).toBe('two'); - } + it('returns the standard base name for a relative Windows path', (done) => { + jasmine.filterByPlatform({only: ['win32']}, done); + + const name = ReopenProjectMenuManager.betterBaseName('.\\one\\two'); + expect(name).toBe('two'); + + done(); }); - it('returns the standard base name for an absolute Windows path', () => { - if (process.platform === 'win32') { - const name = ReopenProjectMenuManager.betterBaseName( - 'c:\\missions\\apollo\\11' - ); - expect(name).toBe('11'); - } + it('returns the standard base name for an absolute Windows path', (done) => { + jasmine.filterByPlatform({only: ['win32']}, done); + + const name = ReopenProjectMenuManager.betterBaseName( + 'c:\\missions\\apollo\\11' + ); + expect(name).toBe('11'); + + done(); }); it('returns the drive root for a Windows drive name', () => { diff --git a/spec/runners/jasmine1-test-runner.js b/spec/runners/jasmine1-test-runner.js new file mode 100644 index 0000000000..2771d890b8 --- /dev/null +++ b/spec/runners/jasmine1-test-runner.js @@ -0,0 +1,215 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS202: Simplify dynamic range loops + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +const Grim = require('grim'); +const fs = require('fs-plus'); +const temp = require('temp'); +const path = require('path'); +const {ipcRenderer} = require('electron'); + +temp.track(); + +module.exports = function({logFile, headless, testPaths, buildAtomEnvironment}) { + require('../helpers/jasmine-singleton'); + + const normalizeComments = require('../helpers/normalize-comments'); + for (let key in normalizeComments) { window[key] = normalizeComments[key]; } + + // Rewrite global jasmine functions to have support for async tests. + // This way packages can create async specs without having to import these from the + // async-spec-helpers file. + global.it = asyncifyJasmineFn(global.it, 1); + global.fit = asyncifyJasmineFn(global.fit, 1); + global.ffit = asyncifyJasmineFn(global.ffit, 1); + global.fffit = asyncifyJasmineFn(global.fffit, 1); + global.beforeEach = asyncifyJasmineFn(global.beforeEach, 0); + global.afterEach = asyncifyJasmineFn(global.afterEach, 0); + + // Allow document.title to be assigned in specs without screwing up spec window title + let documentTitle = null; + Object.defineProperty(document, 'title', { + get() { return documentTitle; }, + set(title) { return documentTitle = title; } + } + ); + + const userHome = process.env.ATOM_HOME || path.join(fs.getHomeDirectory(), '.atom'); + const atomHome = temp.mkdirSync({prefix: 'atom-test-home-'}); + if (process.env.APM_TEST_PACKAGES) { + const testPackages = process.env.APM_TEST_PACKAGES.split(/\s+/); + fs.makeTreeSync(path.join(atomHome, 'packages')); + for (let packName of Array.from(testPackages)) { + const userPack = path.join(userHome, 'packages', packName); + const loadablePack = path.join(atomHome, 'packages', packName); + + try { + fs.symlinkSync(userPack, loadablePack, 'dir'); + } catch (error) { + fs.copySync(userPack, loadablePack); + } + } + } + + const ApplicationDelegate = require('../../src/application-delegate'); + const applicationDelegate = new ApplicationDelegate(); + applicationDelegate.setRepresentedFilename = function() {}; + applicationDelegate.setWindowDocumentEdited = function() {}; + window.atom = buildAtomEnvironment({ + applicationDelegate, window, document, + configDirPath: atomHome, + enablePersistence: false + }); + + require('../helpers/jasmine1-spec-helper'); + if (process.env.JANKY_SHA1 || process.env.CI) { disableFocusMethods(); } + for (let testPath of Array.from(testPaths)) { requireSpecs(testPath); } + + setSpecType('user'); + + let resolveWithExitCode = null; + const promise = new Promise((resolve, reject) => resolveWithExitCode = resolve); + const jasmineEnv = jasmine.getEnv(); + jasmineEnv.addReporter(buildReporter({logFile, headless, resolveWithExitCode})); + + if(process.env.SPEC_FILTER) { + const {getFullDescription} = require('../helpers/jasmine-list-reporter'); + const regex = new RegExp(process.env.SPEC_FILTER) + jasmineEnv.specFilter = (spec) => getFullDescription(spec, false).match(regex) + } + + if (jasmineEnv.setIncludedTags) { + jasmineEnv.setIncludedTags([process.platform]); + } + + const jasmineContent = document.createElement('div'); + jasmineContent.setAttribute('id', 'jasmine-content'); + + document.body.appendChild(jasmineContent); + + jasmineEnv.execute(); + return promise; +}; + +var asyncifyJasmineFn = (fn, callbackPosition) => (function(...args) { + if (typeof args[callbackPosition] === 'function') { + const callback = args[callbackPosition]; + + args[callbackPosition] = function(...args) { + const result = callback.apply(this, args); + if (result instanceof Promise) { + return waitsForPromise(() => result); + } + }; + } + + return fn.apply(this, args); +}); + +var waitsForPromise = function(fn) { + const promise = fn(); + + return global.waitsFor('spec promise to resolve', done => promise.then(done, function(error) { + jasmine.getEnv().currentSpec.fail(error); + return done(); + })); +}; + +var disableFocusMethods = () => ['fdescribe', 'ffdescribe', 'fffdescribe', 'fit', 'ffit', 'fffit'].forEach(function(methodName) { + const focusMethod = window[methodName]; + return window[methodName] = function(description) { + const error = new Error('Focused spec is running on CI'); + return focusMethod(description, function() { throw error; }); + }; +}); + +var requireSpecs = function(testPath, specType) { + if (fs.isDirectorySync(testPath)) { + return (() => { + const result = []; + for (let testFilePath of Array.from(fs.listTreeSync(testPath))) { + if (/-spec\.(coffee|js)$/.test(testFilePath)) { + require(testFilePath); + // Set spec directory on spec for setting up the project in spec-helper + result.push(setSpecDirectory(testPath)); + } + } + return result; + })(); + } else { + require(testPath); + return setSpecDirectory(path.dirname(testPath)); + } +}; + +const setSpecField = function(name, value) { + const specs = jasmine.getEnv().currentRunner().specs(); + if (specs.length === 0) { return; } + return (() => { + const result = []; + for (let start = specs.length-1, index = start, asc = start <= 0; asc ? index <= 0 : index >= 0; asc ? index++ : index--) { + if (specs[index][name] != null) { break; } + result.push(specs[index][name] = value); + } + return result; + })(); +}; + +var setSpecType = specType => setSpecField('specType', specType); + +var setSpecDirectory = specDirectory => setSpecField('specDirectory', specDirectory); + +var buildReporter = function({logFile, headless, resolveWithExitCode}) { + if (headless) { + return buildTerminalReporter(logFile, resolveWithExitCode); + } else { + let reporter; + const AtomReporter = require('../helpers/jasmine1-atom-reporter.js'); + return reporter = new AtomReporter(); + } +}; + +var buildTerminalReporter = function(logFile, resolveWithExitCode) { + let logStream; + if (logFile != null) { logStream = fs.openSync(logFile, 'w'); } + const log = function(str) { + if (logStream != null) { + return fs.writeSync(logStream, str); + } else { + return ipcRenderer.send('write-to-stderr', str); + } + }; + + const options = { + print(str) { + return log(str); + }, + onComplete(runner) { + if (logStream != null) { fs.closeSync(logStream); } + if (Grim.getDeprecationsLength() > 0) { + Grim.logDeprecations(); + resolveWithExitCode(1); + return; + } + + if (runner.results().failedCount > 0) { + return resolveWithExitCode(1); + } else { + return resolveWithExitCode(0); + } + } + }; + + if (process.env.ATOM_JASMINE_REPORTER === 'list') { + const {JasmineListReporter} = require('../helpers/jasmine-list-reporter'); + return new JasmineListReporter(options); + } else { + const {TerminalReporter} = require('jasmine-tagged'); + return new TerminalReporter(options); + } +}; diff --git a/spec/runners/jasmine2-test-runner.js b/spec/runners/jasmine2-test-runner.js new file mode 100644 index 0000000000..7df2526f09 --- /dev/null +++ b/spec/runners/jasmine2-test-runner.js @@ -0,0 +1,240 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS202: Simplify dynamic range loops + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md + */ +const Grim = require('grim'); +const fs = require('fs-plus'); +const temp = require('temp'); +const path = require('path'); +const {ipcRenderer} = require('electron'); + +temp.track(); + +module.exports = function({logFile, headless, testPaths, buildAtomEnvironment}) { + // Load Jasmine 2.x + require('../helpers/jasmine2-singleton'); + defineJasmineHelpersOnWindow(jasmine.getEnv()) + + // Build Atom Environment + const { atomHome, applicationDelegate } = require('../helpers/build-atom-environment'); + window.atom = buildAtomEnvironment({ + applicationDelegate, window, document, + configDirPath: atomHome, + enablePersistence: false + }); + + // Load general helpers + require('../../src/window'); + require('../helpers/normalize-comments'); + require('../helpers/document-title'); + require('../helpers/load-jasmine-stylesheet'); + require('../helpers/fixture-packages'); + require('../helpers/set-prototype-extensions'); + require('../helpers/default-timeout'); + require('../helpers/attach-to-dom'); + require('../helpers/deprecation-snapshots'); + require('../helpers/platform-filter'); + + const jasmineContent = document.createElement('div'); + jasmineContent.setAttribute('id', 'jasmine-content'); + document.body.appendChild(jasmineContent); + + return loadSpecsAndRunThem(logFile, headless, testPaths) + .then((result) => { + // Retrying failures only really makes sense in headless mode, + // otherwise the whole HTML view is replaced before the user can inspect the details of the failures + if (!headless) return result; + // All specs passed, don't need to rerun any of them - pass the results to handle possible Grim deprecations + if (result.failedSpecs.length === 0) return result; + + console.log('\n', '\n', `Retrying ${result.failedSpecs.length} spec(s)`, '\n', '\n'); + + // Gather the full names of the failed specs - this is the closest to be a unique identifier for all specs + const fullNamesOfFailedSpecs = result.failedSpecs.map((spec) => { + return spec.fullName; + }) + + // Force-delete the current env - this way Jasmine will reset and we'll be able to re-run failed specs only. The next time the code calls getEnv(), it'll generate a new environment + jasmine.currentEnv_ = null; + + // As all the jasmine helpers (it, describe, etc..) were registered to the previous environment, we need to re-set them on window + defineJasmineHelpersOnWindow(jasmine.getEnv()); + + // Set up a specFilter to disable all passing spec and re-run only the flaky ones + jasmine.getEnv().specFilter = (spec) => { + return fullNamesOfFailedSpecs.includes(spec.result.fullName); + }; + + // Run the specs again - due to the spec filter, only the failed specs will run this time + return loadSpecsAndRunThem(logFile, headless, testPaths); + }).then((result) => { + // Some of the specs failed, we should return with a non-zero exit code + if (result.failedSpecs.length !== 0) return 1; + + // Some of the tests had deprecation warnings, we should log them and return with a non-zero exit code + if (result.hasDeprecations) { + Grim.logDeprecations(); + return 1; + } + + // Everything went good, time to return with a zero exit code + return 0; + }) +}; + +const defineJasmineHelpersOnWindow = (jasmineEnv) => { + for (let key in jasmineEnv) { + window[key] = jasmineEnv[key]; + } + + ['it', 'fit', 'xit'].forEach((key) => { + window[key] = (name, originalFn) => { + jasmineEnv[key](name, async (done) => { + if(originalFn.length === 0) { + await originalFn() + done(); + } else { + originalFn(done); + } + }); + } + }); + + ['beforeEach', 'afterEach'].forEach((key) => { + window[key] = (originalFn) => { + jasmineEnv[key](async (done) => { + if(originalFn.length === 0) { + await originalFn() + done(); + } else { + originalFn(done); + } + }) + } + }); +} + +const loadSpecsAndRunThem = (logFile, headless, testPaths) => { + return new Promise((resolve) => { + const jasmineEnv = jasmine.getEnv(); + + // Load before and after hooks, custom matchers + require('../helpers/jasmine2-custom-matchers').register(jasmineEnv); + require('../helpers/jasmine2-spies').register(jasmineEnv); + require('../helpers/jasmine2-time').register(jasmineEnv); + require('../helpers/jasmine2-warnings').register(jasmineEnv); + + // Load specs and set spec type + for (let testPath of Array.from(testPaths)) { requireSpecs(testPath); } + setSpecType('user'); + + // Add the reporter and register the promise resolve as a callback + jasmineEnv.addReporter(buildReporter({logFile, headless})); + jasmineEnv.addReporter(buildRetryReporter(resolve)); + + // And finally execute the tests + jasmineEnv.execute(); + }) +} + +// This is a helper function to remove a file from the require cache. +// We are using this to force a re-evaluation of the test files when we need to re-run some flaky tests +const unrequire = (requiredPath) => { + for (const path in require.cache) { + if (path === requiredPath) { + delete require.cache[path]; + } + } +} + +const requireSpecs = (testPath) => { + if (fs.isDirectorySync(testPath)) { + for (let testFilePath of fs.listTreeSync(testPath)) { + if (/-spec\.js$/.test(testFilePath)) { + unrequire(testFilePath); + require(testFilePath); + // Set spec directory on spec for setting up the project in spec-helper + setSpecDirectory(testPath); + } + } + } else { + unrequire(testPath); + require(testPath); + setSpecDirectory(path.dirname(testPath)); + } +}; + +const setSpecField = (name, value) => { + const specs = (new jasmine.JsApiReporter({})).specs(); + if (specs.length === 0) { return; } + + for (let index = specs.length - 1; index >= 0; index--) { + if (specs[index][name] != null) { break; } + specs[index][name] = value; + } +}; + +const setSpecType = specType => setSpecField('specType', specType); + +const setSpecDirectory = specDirectory => setSpecField('specDirectory', specDirectory); + +const buildReporter = ({logFile, headless}) => { + if (headless) { + return buildConsoleReporter(logFile); + } else { + const AtomReporter = require('../helpers/jasmine2-atom-reporter.js'); + return new AtomReporter(); + } +}; + +const buildRetryReporter = (onCompleteCallback) => { + const failedSpecs = []; + + return { + jasmineStarted: () => {}, + suiteStarted: () => {}, + specStarted: () => {}, + suiteDone: () => {}, + + specDone: (spec) => { + if (spec.status === 'failed') { + failedSpecs.push(spec); + } + }, + + jasmineDone: () => { + onCompleteCallback({failedSpecs, hasDeprecations: Grim.getDeprecationsLength() > 0}); + } + }; +} + +const buildConsoleReporter = (logFile) => { + let logStream; + if (logFile != null) { logStream = fs.openSync(logFile, 'w'); } + const log = function(str) { + if (logStream != null) { + fs.writeSync(logStream, str); + } else { + ipcRenderer.send('write-to-stderr', str); + } + }; + + const options = { + print(str) { + log(str); + }, + onComplete() { + if (logStream != null) { fs.closeSync(logStream); } + }, + printDeprecation: (msg) => { + console.log(msg) + } + }; + + return new jasmine.ConsoleReporter(options); +}; diff --git a/spec/scope-resolver-spec.js b/spec/scope-resolver-spec.js index 0aee61c607..278cd60afa 100644 --- a/spec/scope-resolver-spec.js +++ b/spec/scope-resolver-spec.js @@ -347,7 +347,7 @@ describe('ScopeResolver', () => { `); // Prevent an exception from being thrown before we can even check the // scopeResolver. - spyOn(languageMode, 'isRowCommented').andReturn(false); + spyOn(languageMode, 'isRowCommented').and.returnValue(false); await languageMode.ready; let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode); diff --git a/spec/selection-spec.js b/spec/selection-spec.js index 19094c48a0..0fe8d74596 100644 --- a/spec/selection-spec.js +++ b/spec/selection-spec.js @@ -95,7 +95,7 @@ describe('Selection', () => { buffer.insert([2, 5], 'abc'); expect(changeScreenRangeHandler).toHaveBeenCalled(); expect( - changeScreenRangeHandler.mostRecentCall.args[0] + changeScreenRangeHandler.calls.mostRecent().args[0] ).not.toBeUndefined(); }); }); @@ -111,7 +111,7 @@ describe('Selection', () => { buffer.insert([2, 5], 'abc'); expect(changeScreenRangeHandler).toHaveBeenCalled(); expect( - changeScreenRangeHandler.mostRecentCall.args[0] + changeScreenRangeHandler.calls.mostRecent().args[0] ).not.toBeUndefined(); }); }); diff --git a/spec/state-store-spec.js b/spec/state-store-spec.js index 6bb3f11170..693670d6a8 100644 --- a/spec/state-store-spec.js +++ b/spec/state-store-spec.js @@ -46,21 +46,28 @@ describe('StateStore', () => { }); describe('when there is an error reading from the database', () => { - it('rejects the promise returned by load', () => { + it('rejects the promise returned by load', async () => { const store = new StateStore(databaseName, version); const fakeErrorEvent = { target: { errorCode: 'Something bad happened' } }; - spyOn(IDBObjectStore.prototype, 'get').andCallFake(key => { - let request = {}; - process.nextTick(() => request.onerror(fakeErrorEvent)); - return request; - }); + const fakeIDBObjectStore = {}; + let loadStorePromise; + + await new Promise((resolve) => { + spyOn(IDBObjectStore.prototype, 'get').and.callFake(key => { + resolve(); + return fakeIDBObjectStore; + }); + + loadStorePromise = store.load('nonexistentKey'); + }) + + fakeIDBObjectStore.onerror(fakeErrorEvent); - return store - .load('nonexistentKey') + loadStorePromise .then(() => { throw new Error('Promise should have been rejected'); }) diff --git a/spec/task-spec.js b/spec/task-spec.js index 5b25dac739..8395aca468 100644 --- a/spec/task-spec.js +++ b/spec/task-spec.js @@ -3,65 +3,66 @@ const Grim = require('grim'); describe('Task', function() { describe('@once(taskPath, args..., callback)', () => - it('terminates the process after it completes', function() { + it('terminates the process after it completes', async function() { let handlerResult = null; - const task = Task.once( - require.resolve('./fixtures/task-spec-handler'), - result => (handlerResult = result) - ); - - let processErrored = false; - const { childProcess } = task; - spyOn(childProcess, 'kill').andCallThrough(); - task.childProcess.on('error', () => (processErrored = true)); - - waitsFor(() => handlerResult != null); - - runs(function() { - expect(handlerResult).toBe('hello'); - expect(childProcess.kill).toHaveBeenCalled(); - expect(processErrored).toBe(false); - }); + let task; + let processErroredCallbak = jasmine.createSpy(); + let childProcess; + + await new Promise((resolve) => { + task = Task.once( + require.resolve('./fixtures/task-spec-handler'), + (result) => { + handlerResult = result; + resolve(); + } + ); + + childProcess = task.childProcess; + spyOn(childProcess, 'kill').and.callThrough(); + task.childProcess.on('error', processErroredCallbak); + }) + + expect(handlerResult).toBe('hello'); + expect(childProcess.kill).toHaveBeenCalled(); + expect(processErroredCallbak).not.toHaveBeenCalled(); })); - it('calls listeners registered with ::on when events are emitted in the task', function() { + it('calls listeners registered with ::on when events are emitted in the task', async function() { const task = new Task(require.resolve('./fixtures/task-spec-handler')); const eventSpy = jasmine.createSpy('eventSpy'); task.on('some-event', eventSpy); - waitsFor(done => task.start(done)); + await new Promise((resolve) => task.start(resolve)) - runs(() => expect(eventSpy).toHaveBeenCalledWith(1, 2, 3)); + expect(eventSpy).toHaveBeenCalledWith(1, 2, 3); }); - it('unregisters listeners when the Disposable returned by ::on is disposed', function() { + it('unregisters listeners when the Disposable returned by ::on is disposed', async function() { const task = new Task(require.resolve('./fixtures/task-spec-handler')); const eventSpy = jasmine.createSpy('eventSpy'); const disposable = task.on('some-event', eventSpy); disposable.dispose(); - waitsFor(done => task.start(done)); + await new Promise((resolve) => task.start(resolve)) - runs(() => expect(eventSpy).not.toHaveBeenCalled()); + expect(eventSpy).not.toHaveBeenCalled(); }); - it('reports deprecations in tasks', function() { + it('reports deprecations in tasks', async function() { jasmine.snapshotDeprecations(); const handlerPath = require.resolve( './fixtures/task-handler-with-deprecations' ); const task = new Task(handlerPath); + await new Promise((resolve) => task.start(resolve)) - waitsFor(done => task.start(done)); - - runs(function() { - const deprecations = Grim.getDeprecations(); - expect(deprecations.length).toBe(1); - expect(deprecations[0].getStacks()[0][1].fileName).toBe(handlerPath); - jasmine.restoreDeprecationsSnapshot(); - }); + const deprecations = Grim.getDeprecations(); + expect(deprecations.length).toBe(1); + expect(deprecations[0].getStacks()[0][1].fileName).toBe(handlerPath); + jasmine.restoreDeprecationsSnapshot(); }); it('adds data listeners to standard out and error to report output', function() { @@ -79,7 +80,7 @@ describe('Task', function() { }); it('does not throw an error for forked processes missing stdout/stderr', function() { - spyOn(require('child_process'), 'fork').andCallFake(function() { + spyOn(require('child_process'), 'fork').and.callFake(function() { const Events = require('events'); const fakeProcess = new Events(); fakeProcess.send = function() {}; @@ -105,21 +106,20 @@ describe('Task', function() { expect(completedEventSpy).not.toHaveBeenCalled(); }); - it("does not dispatch 'task:cancelled' when invoked on an inactive task", function() { - let handlerResult = null; - const task = Task.once( - require.resolve('./fixtures/task-spec-handler'), - result => (handlerResult = result) - ); - - waitsFor(() => handlerResult != null); - - runs(function() { - const cancelledEventSpy = jasmine.createSpy('eventSpy'); - task.on('task:cancelled', cancelledEventSpy); - expect(task.cancel()).toBe(false); - expect(cancelledEventSpy).not.toHaveBeenCalled(); - }); + it("does not dispatch 'task:cancelled' when invoked on an inactive task", async function() { + let task = null; + + await new Promise(resolve => { + task = Task.once( + require.resolve('./fixtures/task-spec-handler'), + resolve + ); + }) + + const cancelledEventSpy = jasmine.createSpy('eventSpy'); + task.on('task:cancelled', cancelledEventSpy); + expect(task.cancel()).toBe(false); + expect(cancelledEventSpy).not.toHaveBeenCalled(); }); }); }); diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 487d055cec..7d5353f0f9 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1,4 +1,4 @@ -const { conditionPromise } = require('./async-spec-helpers'); +const { conditionPromise } = require('./helpers/async-spec-helpers'); const Random = require('random-seed'); const { getRandomBufferRange, buildRandomLines } = require('./helpers/random'); @@ -24,17 +24,19 @@ class DummyElement extends HTMLElement { } } -window.customElements.define( - 'text-editor-component-test-element', - DummyElement -); - -document.createElement('text-editor-component-test-element'); - const editors = []; let verticalScrollbarWidth, horizontalScrollbarHeight; describe('TextEditorComponent', () => { + beforeEach(() => { + if(!window.customElements.get('text-editor-component-test-element')) { + window.customElements.define( + 'text-editor-component-test-element', + DummyElement + ); + } + }) + beforeEach(() => { jasmine.useRealClock(); @@ -1138,12 +1140,12 @@ describe('TextEditorComponent', () => { let originalTimeout; beforeEach(() => { - originalTimeout = jasmine.getEnv().defaultTimeoutInterval; - jasmine.getEnv().defaultTimeoutInterval = 60 * 1000; + originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 1000; }); afterEach(() => { - jasmine.getEnv().defaultTimeoutInterval = originalTimeout; + jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; }); it('renders the visible rows correctly after randomly mutating the editor', async () => { @@ -1177,8 +1179,8 @@ describe('TextEditorComponent', () => { if (k < 10) { editor.setSoftWrapped(!editor.isSoftWrapped()); } else if (k < 15) { - if (random(2)) setEditorWidthInCharacters(component, random(20)); - if (random(2)) setEditorHeightInLines(component, random(10)); + if (random(2)) await setEditorWidthInCharacters(component, random(20)); + if (random(2)) await setEditorHeightInLines(component, random(10)); } else if (k < 40) { editor.setSelectedBufferRange(range); editor.backspace(); @@ -2569,10 +2571,10 @@ describe('TextEditorComponent', () => { fakeWindow.style.backgroundColor = 'blue'; fakeWindow.appendChild(component.element); jasmine.attachToDOM(fakeWindow); - spyOn(component, 'getWindowInnerWidth').andCallFake( + spyOn(component, 'getWindowInnerWidth').and.callFake( () => fakeWindow.getBoundingClientRect().width ); - spyOn(component, 'getWindowInnerHeight').andCallFake( + spyOn(component, 'getWindowInnerHeight').and.callFake( () => fakeWindow.getBoundingClientRect().height ); return fakeWindow; @@ -4589,7 +4591,7 @@ describe('TextEditorComponent', () => { const { didDrag, didStopDragging - } = component.handleMouseDragUntilMouseUp.argsForCall[0][0]; + } = component.handleMouseDragUntilMouseUp.calls.argsFor(0)[0]; didDrag(clientPositionForCharacter(component, 8, 8)); expect(editor.getSelectedScreenRange()).toEqual([[1, 4], [8, 8]]); didDrag(clientPositionForCharacter(component, 4, 8)); @@ -4614,7 +4616,7 @@ describe('TextEditorComponent', () => { const { didDrag, didStopDragging - } = component.handleMouseDragUntilMouseUp.argsForCall[1][0]; + } = component.handleMouseDragUntilMouseUp.calls.argsFor(1)[0]; didDrag(clientPositionForCharacter(component, 2, 8)); expect(editor.getSelectedScreenRanges()).toEqual([ [[1, 4], [4, 8]], @@ -4662,7 +4664,7 @@ describe('TextEditorComponent', () => { const { didDrag - } = component.handleMouseDragUntilMouseUp.argsForCall[1][0]; + } = component.handleMouseDragUntilMouseUp.calls.argsFor(1)[0]; didDrag(clientPositionForCharacter(component, 0, 8)); expect(editor.getSelectedScreenRange()).toEqual([[0, 4], [1, 5]]); didDrag(clientPositionForCharacter(component, 2, 10)); @@ -4690,7 +4692,7 @@ describe('TextEditorComponent', () => { const { didDrag - } = component.handleMouseDragUntilMouseUp.argsForCall[2][0]; + } = component.handleMouseDragUntilMouseUp.calls.argsFor(2)[0]; didDrag(clientPositionForCharacter(component, 1, 8)); expect(editor.getSelectedScreenRange()).toEqual([[1, 0], [3, 0]]); didDrag(clientPositionForCharacter(component, 4, 10)); @@ -4752,7 +4754,7 @@ describe('TextEditorComponent', () => { }); const { didDrag - } = component.handleMouseDragUntilMouseUp.argsForCall[0][0]; + } = component.handleMouseDragUntilMouseUp.calls.argsFor(0)[0]; didDrag({ clientX: 199, clientY: 199 }); assertScrolledDownAndRight(); @@ -4800,7 +4802,7 @@ describe('TextEditorComponent', () => { }); it('pastes the previously selected text when clicking the middle mouse button on Linux', async () => { - spyOn(electron.ipcRenderer, 'send').andCallFake(function( + spyOn(electron.ipcRenderer, 'send').and.callFake(function( eventName, selectedText ) { @@ -4854,7 +4856,7 @@ describe('TextEditorComponent', () => { }); it('does not paste into a read only editor when clicking the middle mouse button on Linux', async () => { - spyOn(electron.ipcRenderer, 'send').andCallFake(function( + spyOn(electron.ipcRenderer, 'send').and.callFake(function( eventName, selectedText ) { @@ -4984,7 +4986,7 @@ describe('TextEditorComponent', () => { const { didDrag, didStopDragging - } = component.handleMouseDragUntilMouseUp.argsForCall[0][0]; + } = component.handleMouseDragUntilMouseUp.calls.argsFor(0)[0]; didDrag({ clientY: clientTopForLine(component, 1) }); @@ -5022,7 +5024,7 @@ describe('TextEditorComponent', () => { const { didDrag, didStopDragging - } = component.handleMouseDragUntilMouseUp.argsForCall[0][0]; + } = component.handleMouseDragUntilMouseUp.calls.argsFor(0)[0]; didDrag({ clientY: clientTopForLine(component, 1) @@ -5122,7 +5124,7 @@ describe('TextEditorComponent', () => { }); const { didDrag - } = component.handleMouseDragUntilMouseUp.argsForCall[0][0]; + } = component.handleMouseDragUntilMouseUp.calls.argsFor(0)[0]; didDrag({ clientX: 199, clientY: 199 }); assertScrolledDown(); didDrag({ clientX: 199, clientY: 199 }); @@ -5622,7 +5624,9 @@ describe('TextEditorComponent', () => { expect(component.getScrollTopRow()).toBe(4); }); - it('gracefully handles the editor being hidden after a styling change', async () => { + it('gracefully handles the editor being hidden after a styling change', async (done) => { + jasmine.filterByPlatform({only: ['linux']}, done); + const { component, element } = buildComponent({ autoHeight: false }); @@ -5631,6 +5635,8 @@ describe('TextEditorComponent', () => { TextEditor.didUpdateStyles(); element.style.display = 'none'; await component.getNextUpdatePromise(); + + done(); }); it('does not throw an exception when the editor is soft-wrapped and changing the font size changes also the longest screen line', async () => { @@ -5716,7 +5722,7 @@ describe('TextEditorComponent', () => { updatedSynchronously: true }); editor.setSoftWrapped(true); - spyOn(window, 'onerror').andCallThrough(); + spyOn(window, 'onerror').and.callThrough(); jasmine.attachToDOM(element); // should not throw an exception expect(window.onerror).not.toHaveBeenCalled(); }); @@ -5937,24 +5943,24 @@ describe('TextEditorComponent', () => { }); spyOn(Grim, 'deprecate'); expect(editor.getHeight()).toBe(component.getScrollContainerHeight()); - expect(Grim.deprecate.callCount).toBe(1); + expect(Grim.deprecate.calls.count()).toBe(1); editor.setHeight(100); await component.getNextUpdatePromise(); expect(component.getScrollContainerHeight()).toBe(100); - expect(Grim.deprecate.callCount).toBe(2); + expect(Grim.deprecate.calls.count()).toBe(2); }); it('delegates setWidth and getWidth to the component', async () => { const { component, editor } = buildComponent(); spyOn(Grim, 'deprecate'); expect(editor.getWidth()).toBe(component.getScrollContainerWidth()); - expect(Grim.deprecate.callCount).toBe(1); + expect(Grim.deprecate.calls.count()).toBe(1); editor.setWidth(100); await component.getNextUpdatePromise(); expect(component.getScrollContainerWidth()).toBe(100); - expect(Grim.deprecate.callCount).toBe(2); + expect(Grim.deprecate.calls.count()).toBe(2); }); it('delegates getFirstVisibleScreenRow, getLastVisibleScreenRow, and getVisibleRowRange to the component', async () => { diff --git a/spec/text-editor-element-spec.js b/spec/text-editor-element-spec.js index fbe7af1f7c..16d3b997c7 100644 --- a/spec/text-editor-element-spec.js +++ b/spec/text-editor-element-spec.js @@ -298,7 +298,7 @@ describe('TextEditorElement', () => { expect(attachedCallback).toHaveBeenCalled(); expect(detachedCallback).not.toHaveBeenCalled(); - attachedCallback.reset(); + attachedCallback.calls.reset(); element.remove(); expect(attachedCallback).not.toHaveBeenCalled(); @@ -307,7 +307,7 @@ describe('TextEditorElement', () => { describe('::setUpdatedSynchronously', () => { it('controls whether the text editor is updated synchronously', () => { - spyOn(window, 'requestAnimationFrame').andCallFake(fn => fn()); + spyOn(window, 'requestAnimationFrame').and.callFake(fn => fn()); const element = buildTextEditorElement(); @@ -318,7 +318,7 @@ describe('TextEditorElement', () => { expect(element.textContent).toContain('hello'); - window.requestAnimationFrame.reset(); + window.requestAnimationFrame.calls.reset(); element.setUpdatedSynchronously(true); element.getModel().setText('goodbye'); expect(window.requestAnimationFrame).not.toHaveBeenCalled(); diff --git a/spec/text-editor-registry-spec.js b/spec/text-editor-registry-spec.js index 71b921e9d1..5d958d9ef9 100644 --- a/spec/text-editor-registry-spec.js +++ b/spec/text-editor-registry-spec.js @@ -63,16 +63,16 @@ describe('TextEditorRegistry', function() { const [editor1, editor2, editor3] = [{}, {}, {}]; registry.add(editor1); const subscription = registry.observe(spy); - expect(spy.calls.length).toBe(1); + expect(spy.calls.count()).toBe(1); registry.add(editor2); - expect(spy.calls.length).toBe(2); - expect(spy.argsForCall[0][0]).toBe(editor1); - expect(spy.argsForCall[1][0]).toBe(editor2); + expect(spy.calls.count()).toBe(2); + expect(spy.calls.argsFor(0)[0]).toBe(editor1); + expect(spy.calls.argsFor(1)[0]).toBe(editor2); subscription.dispose(); registry.add(editor3); - expect(spy.calls.length).toBe(2); + expect(spy.calls.count()).toBe(2); }); }); @@ -94,7 +94,7 @@ describe('TextEditorRegistry', function() { expect(editor.getTabLength()).toBe(8); expect(editor.getGrammar()).toEqual(NullGrammar); - expect(languageMode.onDidChangeHighlighting.calls.length).toBe(1); + expect(languageMode.onDidChangeHighlighting.calls.count()).toBe(1); }); }); diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index 832c482a53..262642772e 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -49,7 +49,7 @@ describe('TextEditor', () => { // The id generator should skip the id used up by the deserialized one: const fresh = new TextEditor(); - expect(fresh.id).toNotEqual(deserialized.id); + expect(fresh.id).not.toEqual(deserialized.id); }); describe('when the editor is deserialized', () => { @@ -211,7 +211,7 @@ describe('TextEditor', () => { }); expect(returnedPromise).toBe(element.component.getNextUpdatePromise()); - expect(changeSpy.callCount).toBe(1); + expect(changeSpy.calls.count()).toBe(1); expect(editor.getTabLength()).toBe(6); expect(editor.getSoftTabs()).toBe(false); expect(editor.isSoftWrapped()).toBe(true); @@ -366,8 +366,8 @@ describe('TextEditor', () => { expect(editorCallback).toHaveBeenCalled(); expect(cursorCallback).toHaveBeenCalled(); - const eventObject = editorCallback.mostRecentCall.args[0]; - expect(cursorCallback.mostRecentCall.args[0]).toEqual(eventObject); + const eventObject = editorCallback.calls.mostRecent().args[0]; + expect(cursorCallback.calls.mostRecent().args[0]).toEqual(eventObject); expect(eventObject.oldBufferPosition).toEqual([0, 0]); expect(eventObject.oldScreenPosition).toEqual([0, 0]); @@ -1619,7 +1619,7 @@ describe('TextEditor', () => { editor.selectToBufferPosition([6, 2]); expect(rangeChangedHandler).toHaveBeenCalled(); - const eventObject = rangeChangedHandler.mostRecentCall.args[0]; + const eventObject = rangeChangedHandler.calls.mostRecent().args[0]; expect(eventObject.oldBufferRange).toEqual([[3, 0], [4, 5]]); expect(eventObject.oldScreenRange).toEqual([[3, 0], [4, 5]]); @@ -2250,7 +2250,7 @@ describe('TextEditor', () => { spyOn( editor.getBuffer().getLanguageMode(), 'getNonWordCharacters' - ).andCallFake(function (position) { + ).and.callFake(function (position) { const result = '/()"\':,.;<>~!@#$%^&*|+=[]{}`?'; const scopes = this.scopeDescriptorForPosition( position @@ -4002,7 +4002,7 @@ describe('TextEditor', () => { it('notifies the observers when inserting text', () => { const willInsertSpy = jasmine .createSpy() - .andCallFake(() => + .and.callFake(() => expect(buffer.lineForRow(1)).toBe( ' var sort = function(items) {' ) @@ -4010,7 +4010,7 @@ describe('TextEditor', () => { const didInsertSpy = jasmine .createSpy() - .andCallFake(() => + .and.callFake(() => expect(buffer.lineForRow(1)).toBe( 'xxxvar sort = function(items) {' ) @@ -4025,18 +4025,18 @@ describe('TextEditor', () => { expect(willInsertSpy).toHaveBeenCalled(); expect(didInsertSpy).toHaveBeenCalled(); - let options = willInsertSpy.mostRecentCall.args[0]; + let options = willInsertSpy.calls.mostRecent().args[0]; expect(options.text).toBe('xxx'); expect(options.cancel).toBeDefined(); - options = didInsertSpy.mostRecentCall.args[0]; + options = didInsertSpy.calls.mostRecent().args[0]; expect(options.text).toBe('xxx'); }); it('cancels text insertion when an ::onWillInsertText observer calls cancel on an event', () => { const willInsertSpy = jasmine .createSpy() - .andCallFake(({ cancel }) => cancel()); + .and.callFake(({ cancel }) => cancel()); const didInsertSpy = jasmine.createSpy(); @@ -8345,7 +8345,7 @@ describe('TextEditor', () => { beforeEach(async () => { editor = await atom.workspace.open('sample.js'); jasmine.unspy(editor, 'shouldPromptToSave'); - spyOn(atom.stateStore, 'isConnected').andReturn(true); + spyOn(atom.stateStore, 'isConnected').and.returnValue(true); }); it('returns true when buffer has unsaved changes', () => { diff --git a/spec/text-mate-language-mode-spec.js b/spec/text-mate-language-mode-spec.js index ad23d91143..3b46f76f54 100644 --- a/spec/text-mate-language-mode-spec.js +++ b/spec/text-mate-language-mode-spec.js @@ -456,7 +456,7 @@ describe('TextMateLanguageMode', () => { const tokenizedHandler = jasmine.createSpy('tokenized handler'); editor.languageMode.onDidTokenize(tokenizedHandler); fullyTokenize(editor.getBuffer().getLanguageMode()); - expect(tokenizedHandler.callCount).toBe(1); + expect(tokenizedHandler.calls.count()).toBe(1); }); it("doesn't re-emit the `tokenized` event when it is re-tokenized", async () => { @@ -471,7 +471,7 @@ describe('TextMateLanguageMode', () => { }); }); - describe('when the grammar is updated because a grammar it includes is activated', async () => { + describe('when the grammar is updated because a grammar it includes is activated', () => { it('re-emits the `tokenized` event', async () => { let tokenizationCount = 0; @@ -520,7 +520,7 @@ describe('TextMateLanguageMode', () => { describe('when the buffer is configured with the null grammar', () => { it('does not actually tokenize using the grammar', () => { - spyOn(NullGrammar, 'tokenizeLine').andCallThrough(); + spyOn(NullGrammar, 'tokenizeLine').and.callThrough(); buffer = atom.project.bufferForPathSync( 'sample.will-use-the-null-grammar' ); @@ -532,14 +532,14 @@ describe('TextMateLanguageMode', () => { expect(languageMode.tokenizedLines[0]).toBeUndefined(); expect(languageMode.tokenizedLines[1]).toBeUndefined(); expect(languageMode.tokenizedLines[2]).toBeUndefined(); - expect(tokenizeCallback.callCount).toBe(0); + expect(tokenizeCallback.calls.count()).toBe(0); expect(NullGrammar.tokenizeLine).not.toHaveBeenCalled(); fullyTokenize(languageMode); expect(languageMode.tokenizedLines[0]).toBeUndefined(); expect(languageMode.tokenizedLines[1]).toBeUndefined(); expect(languageMode.tokenizedLines[2]).toBeUndefined(); - expect(tokenizeCallback.callCount).toBe(0); + expect(tokenizeCallback.calls.count()).toBe(0); expect(NullGrammar.tokenizeLine).not.toHaveBeenCalled(); }); }); diff --git a/spec/theme-manager-spec.js b/spec/theme-manager-spec.js index 97828472a6..8a135f8861 100644 --- a/spec/theme-manager-spec.js +++ b/spec/theme-manager-spec.js @@ -4,17 +4,16 @@ const temp = require('temp').track(); describe('atom.themes', function() { beforeEach(function() { - spyOn(atom, 'inSpecMode').andReturn(false); + spyOn(atom, 'inSpecMode').and.returnValue(false); spyOn(console, 'warn'); }); - afterEach(function() { - waitsForPromise(() => atom.themes.deactivateThemes()); - runs(function() { - try { - temp.cleanupSync(); - } catch (error) {} - }); + afterEach(async function() { + await atom.themes.deactivateThemes(); + + try { + temp.cleanupSync(); + } catch (error) {} }); describe('theme getters and setters', function() { @@ -32,15 +31,13 @@ describe('atom.themes', function() { })); describe('getActiveThemes', () => - it('gets all the active themes', function() { - waitsForPromise(() => atom.themes.activateThemes()); - - runs(function() { - const names = atom.config.get('core.themes'); - expect(names.length).toBeGreaterThan(0); - const themes = atom.themes.getActiveThemes(); - expect(themes).toHaveLength(names.length); - }); + it('gets all the active themes', async function() { + await atom.themes.activateThemes(); + + const names = atom.config.get('core.themes'); + expect(names.length).toBeGreaterThan(0); + const themes = atom.themes.getActiveThemes(); + expect(themes).toHaveLength(names.length); })); }); @@ -87,133 +84,113 @@ describe('atom.themes', function() { }); describe('when the core.themes config value changes', function() { - it('add/removes stylesheets to reflect the new config value', function() { - let didChangeActiveThemesHandler; - atom.themes.onDidChangeActiveThemes( - (didChangeActiveThemesHandler = jasmine.createSpy()) - ); - spyOn(atom.styles, 'getUserStyleSheetPath').andCallFake(() => null); + it('add/removes stylesheets to reflect the new config value', async function() { + let didChangeActiveThemesHandler = jasmine.createSpy(); + atom.themes.onDidChangeActiveThemes(didChangeActiveThemesHandler); + spyOn(atom.styles, 'getUserStyleSheetPath').and.callFake(() => null); - waitsForPromise(() => atom.themes.activateThemes()); + await atom.themes.activateThemes(); - runs(function() { - didChangeActiveThemesHandler.reset(); + await new Promise((resolve) => { + didChangeActiveThemesHandler.and.callFake(resolve) atom.config.set('core.themes', []); - }); + }) - waitsFor('a', () => didChangeActiveThemesHandler.callCount === 1); + expect(document.querySelectorAll('style.theme')).toHaveLength(0); - runs(function() { - didChangeActiveThemesHandler.reset(); - expect(document.querySelectorAll('style.theme')).toHaveLength(0); + await new Promise((resolve) => { + didChangeActiveThemesHandler.and.callFake(resolve); atom.config.set('core.themes', ['atom-dark-ui']); - }); + }) - waitsFor('b', () => didChangeActiveThemesHandler.callCount === 1); + expect(document.querySelectorAll('style[priority="1"]')).toHaveLength(2); + expect( + document + .querySelector('style[priority="1"]') + .getAttribute('source-path') + ).toMatch(/atom-dark-ui/); - runs(function() { - didChangeActiveThemesHandler.reset(); - expect(document.querySelectorAll('style[priority="1"]')).toHaveLength( - 2 - ); - expect( - document - .querySelector('style[priority="1"]') - .getAttribute('source-path') - ).toMatch(/atom-dark-ui/); + await new Promise((resolve) => { + didChangeActiveThemesHandler.and.callFake(resolve); atom.config.set('core.themes', ['atom-light-ui', 'atom-dark-ui']); }); - waitsFor('c', () => didChangeActiveThemesHandler.callCount === 1); + expect(document.querySelectorAll('style[priority="1"]')).toHaveLength(2); + expect( + document + .querySelectorAll('style[priority="1"]')[0] + .getAttribute('source-path') + ).toMatch(/atom-dark-ui/); + expect( + document + .querySelectorAll('style[priority="1"]')[1] + .getAttribute('source-path') + ).toMatch(/atom-light-ui/); - runs(function() { - didChangeActiveThemesHandler.reset(); - expect(document.querySelectorAll('style[priority="1"]')).toHaveLength( - 2 - ); - expect( - document - .querySelectorAll('style[priority="1"]')[0] - .getAttribute('source-path') - ).toMatch(/atom-dark-ui/); - expect( - document - .querySelectorAll('style[priority="1"]')[1] - .getAttribute('source-path') - ).toMatch(/atom-light-ui/); + await new Promise((resolve) => { + didChangeActiveThemesHandler.and.callFake(resolve); atom.config.set('core.themes', []); - }); + }) - waitsFor(() => didChangeActiveThemesHandler.callCount === 1); + expect(document.querySelectorAll('style[priority="1"]')).toHaveLength(2); - runs(function() { - didChangeActiveThemesHandler.reset(); - expect(document.querySelectorAll('style[priority="1"]')).toHaveLength( - 2 - ); + + await new Promise((resolve) => { + didChangeActiveThemesHandler.and.callFake(resolve); // atom-dark-ui has a directory path, the syntax one doesn't atom.config.set('core.themes', [ 'theme-with-index-less', 'atom-dark-ui' ]); - }); - - waitsFor(() => didChangeActiveThemesHandler.callCount === 1); + }) - runs(function() { - expect(document.querySelectorAll('style[priority="1"]')).toHaveLength( - 2 - ); - const importPaths = atom.themes.getImportPaths(); - expect(importPaths.length).toBe(1); - expect(importPaths[0]).toContain('atom-dark-ui'); - }); + expect(document.querySelectorAll('style[priority="1"]')).toHaveLength(2); + const importPaths = atom.themes.getImportPaths(); + expect(importPaths.length).toBe(1); + expect(importPaths[0]).toContain('atom-dark-ui'); }); - it('adds theme-* classes to the workspace for each active theme', function() { + it('adds theme-* classes to the workspace for each active theme', async function() { atom.config.set('core.themes', ['atom-dark-ui', 'atom-dark-syntax']); - let didChangeActiveThemesHandler; + let didChangeActiveThemesHandler = jasmine.createSpy(); + atom.themes.onDidChangeActiveThemes(didChangeActiveThemesHandler); + + await atom.themes.activateThemes(); + + const workspaceElement = atom.workspace.getElement(); + expect(workspaceElement).toHaveClass('theme-atom-dark-ui'); + atom.themes.onDidChangeActiveThemes( (didChangeActiveThemesHandler = jasmine.createSpy()) ); - waitsForPromise(() => atom.themes.activateThemes()); - - const workspaceElement = atom.workspace.getElement(); - runs(function() { - expect(workspaceElement).toHaveClass('theme-atom-dark-ui'); - atom.themes.onDidChangeActiveThemes( - (didChangeActiveThemesHandler = jasmine.createSpy()) - ); + await new Promise((resolve) => { + didChangeActiveThemesHandler.and.callFake(resolve); atom.config.set('core.themes', [ 'theme-with-ui-variables', 'theme-with-syntax-variables' ]); }); - waitsFor(() => didChangeActiveThemesHandler.callCount > 0); - - runs(function() { - // `theme-` twice as it prefixes the name with `theme-` - expect(workspaceElement).toHaveClass('theme-theme-with-ui-variables'); - expect(workspaceElement).toHaveClass( - 'theme-theme-with-syntax-variables' - ); - expect(workspaceElement).not.toHaveClass('theme-atom-dark-ui'); - expect(workspaceElement).not.toHaveClass('theme-atom-dark-syntax'); - }); + // `theme-` twice as it prefixes the name with `theme-` + expect(workspaceElement).toHaveClass('theme-theme-with-ui-variables'); + expect(workspaceElement).toHaveClass( + 'theme-theme-with-syntax-variables' + ); + expect(workspaceElement).not.toHaveClass('theme-atom-dark-ui'); + expect(workspaceElement).not.toHaveClass('theme-atom-dark-syntax'); }); }); describe('when a theme fails to load', () => it('logs a warning', function() { - console.warn.reset(); + console.warn.calls.reset(); atom.packages .activatePackage('a-theme-that-will-not-be-found') .then(function() {}, function() {}); - expect(console.warn.callCount).toBe(1); - expect(console.warn.argsForCall[0][0]).toContain( + expect(console.warn.calls.count()).toBe(1); + expect(console.warn.calls.argsFor(0)[0]).toContain( "Could not resolve 'a-theme-that-will-not-be-found'" ); })); @@ -251,7 +228,7 @@ describe('atom.themes', function() { expect(element.textContent).toBe(fs.readFileSync(cssPath, 'utf8')); // doesn't append twice - styleElementAddedHandler.reset(); + styleElementAddedHandler.calls.reset(); atom.themes.requireStylesheet(cssPath); expect(document.querySelectorAll('head style').length).toBe( lengthBefore + 1 @@ -347,27 +324,53 @@ h2 { }); describe('base style sheet loading', function() { - beforeEach(function() { + beforeEach(async function() { const workspaceElement = atom.workspace.getElement(); jasmine.attachToDOM(atom.workspace.getElement()); workspaceElement.appendChild(document.createElement('atom-text-editor')); - waitsForPromise(() => atom.themes.activateThemes()); + await atom.themes.activateThemes(); }); - it("loads the correct values from the theme's ui-variables file", function() { - let didChangeActiveThemesHandler; - atom.themes.onDidChangeActiveThemes( - (didChangeActiveThemesHandler = jasmine.createSpy()) - ); - atom.config.set('core.themes', [ - 'theme-with-ui-variables', - 'theme-with-syntax-variables' - ]); + it("loads the correct values from the theme's ui-variables file", async function() { + await new Promise((resolve) => { + atom.themes.onDidChangeActiveThemes(resolve); + atom.config.set('core.themes', [ + 'theme-with-ui-variables', + 'theme-with-syntax-variables' + ]); + }) + + // an override loaded in the base css + expect( + getComputedStyle(atom.workspace.getElement())['background-color'] + ).toBe('rgb(0, 0, 255)'); + + // from within the theme itself + expect( + getComputedStyle(document.querySelector('atom-text-editor')) + .paddingTop + ).toBe('150px'); + expect( + getComputedStyle(document.querySelector('atom-text-editor')) + .paddingRight + ).toBe('150px'); + expect( + getComputedStyle(document.querySelector('atom-text-editor')) + .paddingBottom + ).toBe('150px'); + }); - waitsFor(() => didChangeActiveThemesHandler.callCount > 0); + describe('when there is a theme with incomplete variables', () => + it('loads the correct values from the fallback ui-variables', async function() { + await new Promise((resolve) => { + atom.themes.onDidChangeActiveThemes(resolve); + atom.config.set('core.themes', [ + 'theme-with-incomplete-ui-variables', + 'theme-with-syntax-variables' + ]); + }) - runs(function() { // an override loaded in the base css expect( getComputedStyle(atom.workspace.getElement())['background-color'] @@ -376,44 +379,8 @@ h2 { // from within the theme itself expect( getComputedStyle(document.querySelector('atom-text-editor')) - .paddingTop - ).toBe('150px'); - expect( - getComputedStyle(document.querySelector('atom-text-editor')) - .paddingRight - ).toBe('150px'); - expect( - getComputedStyle(document.querySelector('atom-text-editor')) - .paddingBottom - ).toBe('150px'); - }); - }); - - describe('when there is a theme with incomplete variables', () => - it('loads the correct values from the fallback ui-variables', function() { - let didChangeActiveThemesHandler; - atom.themes.onDidChangeActiveThemes( - (didChangeActiveThemesHandler = jasmine.createSpy()) - ); - atom.config.set('core.themes', [ - 'theme-with-incomplete-ui-variables', - 'theme-with-syntax-variables' - ]); - - waitsFor(() => didChangeActiveThemesHandler.callCount > 0); - - runs(function() { - // an override loaded in the base css - expect( - getComputedStyle(atom.workspace.getElement())['background-color'] - ).toBe('rgb(0, 0, 255)'); - - // from within the theme itself - expect( - getComputedStyle(document.querySelector('atom-text-editor')) - .backgroundColor - ).toBe('rgb(0, 152, 255)'); - }); + .backgroundColor + ).toBe('rgb(0, 152, 255)'); })); }); @@ -425,7 +392,7 @@ h2 { userStylesheetPath, 'body {border-style: dotted !important;}' ); - spyOn(atom.styles, 'getUserStyleSheetPath').andReturn(userStylesheetPath); + spyOn(atom.styles, 'getUserStyleSheetPath').and.returnValue(userStylesheetPath); }); describe('when the user stylesheet changes', function() { @@ -433,57 +400,55 @@ h2 { afterEach(() => jasmine.restoreDeprecationsSnapshot()); - it('reloads it', function() { - let styleElementAddedHandler, styleElementRemovedHandler; + it('reloads it', async function() { + let styleElementAddedHandler = jasmine.createSpy('styleElementRemovedHandler'); + let styleElementRemovedHandler = jasmine.createSpy('styleElementAddedHandler'); - waitsForPromise(() => atom.themes.activateThemes()); + atom.themes._originalLoadUserStylesheet = atom.themes.loadUserStylesheet; + const loadUserStylesheetSpy = spyOn(atom.themes, 'loadUserStylesheet').and.callThrough(); - runs(function() { - atom.styles.onDidRemoveStyleElement( - (styleElementRemovedHandler = jasmine.createSpy( - 'styleElementRemovedHandler' - )) - ); - atom.styles.onDidAddStyleElement( - (styleElementAddedHandler = jasmine.createSpy( - 'styleElementAddedHandler' - )) - ); + await atom.themes.activateThemes(); - spyOn(atom.themes, 'loadUserStylesheet').andCallThrough(); + atom.styles.onDidRemoveStyleElement(styleElementRemovedHandler); + atom.styles.onDidAddStyleElement(styleElementAddedHandler); - expect(getComputedStyle(document.body).borderStyle).toBe('dotted'); + expect(getComputedStyle(document.body).borderStyle).toBe('dotted'); + + await new Promise((resolve) => { + loadUserStylesheetSpy.and.callFake((...args) => { + atom.themes._originalLoadUserStylesheet(...args); + resolve(); + }); fs.writeFileSync(userStylesheetPath, 'body {border-style: dashed}'); - }); + }) - waitsFor(() => atom.themes.loadUserStylesheet.callCount === 1); + expect(getComputedStyle(document.body).borderStyle).toBe('dashed'); - runs(function() { - expect(getComputedStyle(document.body).borderStyle).toBe('dashed'); + expect(styleElementRemovedHandler).toHaveBeenCalled(); + expect( + styleElementRemovedHandler.calls.argsFor(0)[0].textContent + ).toContain('dotted'); - expect(styleElementRemovedHandler).toHaveBeenCalled(); - expect( - styleElementRemovedHandler.argsForCall[0][0].textContent - ).toContain('dotted'); + expect(styleElementAddedHandler).toHaveBeenCalled(); + expect( + styleElementAddedHandler.calls.argsFor(0)[0].textContent + ).toContain('dashed'); - expect(styleElementAddedHandler).toHaveBeenCalled(); - expect( - styleElementAddedHandler.argsForCall[0][0].textContent - ).toContain('dashed'); + styleElementRemovedHandler.calls.reset(); - styleElementRemovedHandler.reset(); + await new Promise((resolve) => { + loadUserStylesheetSpy.and.callFake((...args) => { + atom.themes._originalLoadUserStylesheet(...args); + resolve(); + }); fs.removeSync(userStylesheetPath); - }); + }) - waitsFor(() => atom.themes.loadUserStylesheet.callCount === 2); - - runs(function() { - expect(styleElementRemovedHandler).toHaveBeenCalled(); - expect( - styleElementRemovedHandler.argsForCall[0][0].textContent - ).toContain('dashed'); - expect(getComputedStyle(document.body).borderStyle).toBe('none'); - }); + expect(styleElementRemovedHandler).toHaveBeenCalled(); + expect( + styleElementRemovedHandler.calls.argsFor(0)[0].textContent + ).toContain('dashed'); + expect(getComputedStyle(document.body).borderStyle).toBe('none'); }); }); @@ -491,7 +456,7 @@ h2 { let addErrorHandler = null; beforeEach(function() { atom.themes.loadUserStylesheet(); - spyOn(atom.themes.lessCache, 'cssForFile').andCallFake(function() { + spyOn(atom.themes.lessCache, 'cssForFile').and.callFake(function() { throw new Error('EACCES permission denied "styles.less"'); }); atom.notifications.onDidAddNotification( @@ -502,7 +467,7 @@ h2 { it('creates an error notification and does not add the stylesheet', function() { atom.themes.loadUserStylesheet(); expect(addErrorHandler).toHaveBeenCalled(); - const note = addErrorHandler.mostRecentCall.args[0]; + const note = addErrorHandler.calls.mostRecent().args[0]; expect(note.getType()).toBe('error'); expect(note.getMessage()).toContain('Error loading'); expect( @@ -517,12 +482,12 @@ h2 { let addErrorHandler = null; beforeEach(function() { const { File } = require('pathwatcher'); - spyOn(File.prototype, 'on').andCallFake(function(event) { + spyOn(File.prototype, 'on').and.callFake(function(event) { if (event.indexOf('contents-changed') > -1) { throw new Error('Unable to watch path'); } }); - spyOn(atom.themes, 'loadStylesheet').andReturn(''); + spyOn(atom.themes, 'loadStylesheet').and.returnValue(''); atom.notifications.onDidAddNotification( (addErrorHandler = jasmine.createSpy()) ); @@ -531,7 +496,7 @@ h2 { it('creates an error notification', function() { atom.themes.loadUserStylesheet(); expect(addErrorHandler).toHaveBeenCalled(); - const note = addErrorHandler.mostRecentCall.args[0]; + const note = addErrorHandler.calls.mostRecent().args[0]; expect(note.getType()).toBe('error'); expect(note.getMessage()).toContain('Unable to watch path'); }); @@ -545,27 +510,27 @@ h2 { .activatePackage('theme-with-invalid-styles') .then(function() {}, function() {}) ).not.toThrow(); - expect(addErrorHandler.callCount).toBe(2); - expect(addErrorHandler.argsForCall[1][0].message).toContain( + expect(addErrorHandler.calls.count()).toBe(2); + expect(addErrorHandler.calls.argsFor(1)[0].message).toContain( 'Failed to activate the theme-with-invalid-styles theme' ); }); }); describe('when a non-existent theme is present in the config', function() { - beforeEach(function() { - console.warn.reset(); + beforeEach(async function() { + console.warn.calls.reset(); atom.config.set('core.themes', [ 'non-existent-dark-ui', 'non-existent-dark-syntax' ]); - waitsForPromise(() => atom.themes.activateThemes()); + await atom.themes.activateThemes(); }); it('uses the default one-dark UI and syntax themes and logs a warning', function() { const activeThemeNames = atom.themes.getActiveThemeNames(); - expect(console.warn.callCount).toBe(2); + expect(console.warn.calls.count()).toBe(2); expect(activeThemeNames.length).toBe(2); expect(activeThemeNames).toContain('one-dark-ui'); expect(activeThemeNames).toContain('one-dark-syntax'); @@ -574,10 +539,10 @@ h2 { describe('when in safe mode', function() { describe('when the enabled UI and syntax themes are bundled with Atom', function() { - beforeEach(function() { + beforeEach(async function() { atom.config.set('core.themes', ['atom-light-ui', 'atom-dark-syntax']); - waitsForPromise(() => atom.themes.activateThemes()); + await atom.themes.activateThemes(); }); it('uses the enabled themes', function() { @@ -589,13 +554,13 @@ h2 { }); describe('when the enabled UI and syntax themes are not bundled with Atom', function() { - beforeEach(function() { + beforeEach(async function() { atom.config.set('core.themes', [ 'installed-dark-ui', 'installed-dark-syntax' ]); - waitsForPromise(() => atom.themes.activateThemes()); + await atom.themes.activateThemes(); }); it('uses the default dark UI and syntax themes', function() { @@ -607,13 +572,13 @@ h2 { }); describe('when the enabled UI theme is not bundled with Atom', function() { - beforeEach(function() { + beforeEach(async function() { atom.config.set('core.themes', [ 'installed-dark-ui', 'atom-light-syntax' ]); - waitsForPromise(() => atom.themes.activateThemes()); + await atom.themes.activateThemes(); }); it('uses the default one-dark UI theme', function() { @@ -625,13 +590,13 @@ h2 { }); describe('when the enabled syntax theme is not bundled with Atom', function() { - beforeEach(function() { + beforeEach(async function() { atom.config.set('core.themes', [ 'atom-light-ui', 'installed-dark-syntax' ]); - waitsForPromise(() => atom.themes.activateThemes()); + await atom.themes.activateThemes(); }); it('uses the default one-dark syntax theme', function() { diff --git a/spec/tree-sitter-language-mode-spec.js b/spec/tree-sitter-language-mode-spec.js index 3fc7451d78..26567c3566 100644 --- a/spec/tree-sitter-language-mode-spec.js +++ b/spec/tree-sitter-language-mode-spec.js @@ -46,7 +46,7 @@ describe('TreeSitterLanguageMode', () => { }); describe('highlighting', () => { - it('applies the most specific scope mapping to each node in the syntax tree', async () => { + 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: { @@ -75,7 +75,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('provides the grammar with the text of leaf nodes only', async () => { + it('provides the grammar with the text of leaf nodes only', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', scopes: { @@ -127,7 +127,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('can start or end multiple scopes at the same position', async () => { + it('can start or end multiple scopes at the same position', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', scopes: { @@ -158,7 +158,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('can resume highlighting on a line that starts with whitespace', async () => { + it('can resume highlighting on a line that starts with whitespace', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', scopes: { @@ -185,7 +185,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('correctly skips over tokens with zero size', async () => { + it('correctly skips over tokens with zero size', () => { const grammar = new TreeSitterGrammar(atom.grammars, cGrammarPath, { parser: 'tree-sitter-c', scopes: { @@ -232,7 +232,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it("updates lines' highlighting when they are affected by distant changes", async () => { + it("updates lines' highlighting when they are affected by distant changes", () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', scopes: { @@ -263,7 +263,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('allows comma-separated selectors as scope mapping keys', async () => { + it('allows comma-separated selectors as scope mapping keys', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', scopes: { @@ -292,7 +292,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('handles edits after tokens that end between CR and LF characters (regression)', async () => { + it('handles edits after tokens that end between CR and LF characters (regression)', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', scopes: { @@ -332,7 +332,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('handles multi-line nodes with children on different lines (regression)', async () => { + it('handles multi-line nodes with children on different lines (regression)', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', scopes: { @@ -365,7 +365,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('handles folds inside of highlighted tokens', async () => { + it('handles folds inside of highlighted tokens', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', scopes: { @@ -398,7 +398,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('applies regex match rules when specified', async () => { + it('applies regex match rules when specified', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', scopes: { @@ -430,7 +430,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('handles nodes that start before their first child and end after their last child', async () => { + it('handles nodes that start before their first child and end after their last child', () => { const grammar = new TreeSitterGrammar(atom.grammars, rubyGrammarPath, { parser: 'tree-sitter-ruby', scopes: { @@ -662,7 +662,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('highlights the content after injections', async () => { + it('highlights the content after injections', () => { atom.grammars.addGrammar(jsGrammar); atom.grammars.addGrammar(htmlGrammar); buffer.setText('\n
    \n
    '); @@ -758,7 +758,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('handles injections that intersect', async () => { + it('handles injections that intersect', () => { const ejsGrammar = new TreeSitterGrammar( atom.grammars, ejsGrammarPath, @@ -894,7 +894,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('terminates comment token at the end of an injection, so that the next injection is NOT a continuation of the comment', async () => { + it('terminates comment token at the end of an injection, so that the next injection is NOT a continuation of the comment', () => { const ejsGrammar = new TreeSitterGrammar( atom.grammars, ejsGrammarPath, @@ -958,7 +958,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('only covers scope boundaries in parent layers if a nested layer has a boundary at the same position', async () => { + it('only covers scope boundaries in parent layers if a nested layer has a boundary at the same position', () => { const jsdocGrammar = new TreeSitterGrammar( atom.grammars, jsdocGrammarPath, @@ -1023,7 +1023,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('respects the `includeChildren` property of injection points', async () => { + it('respects the `includeChildren` property of injection points', () => { const rustGrammar = new TreeSitterGrammar( atom.grammars, rustGrammarPath, @@ -1142,12 +1142,12 @@ describe('TreeSitterLanguageMode', () => { let originalTimeout; beforeEach(() => { - originalTimeout = jasmine.getEnv().defaultTimeoutInterval; - jasmine.getEnv().defaultTimeoutInterval = 60 * 1000; + originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 1000; }); afterEach(() => { - jasmine.getEnv().defaultTimeoutInterval = originalTimeout; + jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; }); it('matches the highlighting of a freshly-opened editor', async () => { @@ -1223,21 +1223,13 @@ describe('TreeSitterLanguageMode', () => { const tokens1 = editor.tokensForScreenRow(j); const tokens2 = editor2.tokensForScreenRow(j); expect(tokens1).toEqual(tokens2, `Seed: ${seed}, screen line: ${j}`); - if (jasmine.getEnv().currentSpec.results().failedCount > 0) { - console.log(tokens1); - console.log(tokens2); - debugger; // eslint-disable-line no-debugger - break; - } } - - if (jasmine.getEnv().currentSpec.results().failedCount > 0) break; } }); }); describe('folding', () => { - it('can fold nodes that start and end with specified tokens', async () => { + it('can fold nodes that start and end with specified tokens', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', folds: [ @@ -1292,7 +1284,7 @@ describe('TreeSitterLanguageMode', () => { `); }); - it('folds entire buffer rows when necessary to keep words on separate lines', async () => { + it('folds entire buffer rows when necessary to keep words on separate lines', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', folds: [ @@ -1340,7 +1332,7 @@ describe('TreeSitterLanguageMode', () => { `); }); - it('can fold nodes of specified types', async () => { + it('can fold nodes of specified types', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', folds: [ @@ -1401,7 +1393,7 @@ describe('TreeSitterLanguageMode', () => { `); }); - it('can fold entire nodes when no start or end parameters are specified', async () => { + 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: [ @@ -1444,7 +1436,7 @@ describe('TreeSitterLanguageMode', () => { `); }); - it('tries each folding strategy for a given node in the order specified', async () => { + 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: [ @@ -1556,7 +1548,7 @@ describe('TreeSitterLanguageMode', () => { `); }); - it('does not fold when the start and end parameters match the same child', async () => { + it('does not fold when the start and end parameters match the same child', () => { const grammar = new TreeSitterGrammar(atom.grammars, htmlGrammarPath, { parser: 'tree-sitter-html', folds: [ @@ -1589,7 +1581,7 @@ describe('TreeSitterLanguageMode', () => { `); }); - it('can target named vs anonymous nodes as fold boundaries', async () => { + it('can target named vs anonymous nodes as fold boundaries', () => { const grammar = new TreeSitterGrammar(atom.grammars, rubyGrammarPath, { parser: 'tree-sitter-ruby', folds: [ @@ -1696,7 +1688,7 @@ describe('TreeSitterLanguageMode', () => { }); describe('when folding a node that ends with a line break', () => { - it('ends the fold at the end of the previous line', async () => { + it('ends the fold at the end of the previous line', () => { const grammar = new TreeSitterGrammar( atom.grammars, pythonGrammarPath, @@ -1734,7 +1726,7 @@ describe('TreeSitterLanguageMode', () => { }); }); - it('folds code in injected languages', async () => { + it('folds code in injected languages', () => { const htmlGrammar = new TreeSitterGrammar( atom.grammars, htmlGrammarPath, @@ -1822,7 +1814,7 @@ describe('TreeSitterLanguageMode', () => { }); describe('.scopeDescriptorForPosition', () => { - it('returns a scope descriptor representing the given position in the syntax tree', async () => { + it('returns a scope descriptor representing the given position in the syntax tree', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { scopeName: 'source.js', parser: 'tree-sitter-javascript', @@ -1863,7 +1855,7 @@ describe('TreeSitterLanguageMode', () => { ).toEqual(['source.js', 'comment.block']); }); - it('includes nodes in injected syntax trees', async () => { + it('includes nodes in injected syntax trees', () => { const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { scopeName: 'source.js', parser: 'tree-sitter-javascript', @@ -1964,7 +1956,7 @@ describe('TreeSitterLanguageMode', () => { }); describe('.syntaxTreeScopeDescriptorForPosition', () => { - it('returns a scope descriptor representing the given position in the syntax tree', async () => { + it('returns a scope descriptor representing the given position in the syntax tree', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { scopeName: 'source.js', parser: 'tree-sitter-javascript' @@ -1998,7 +1990,7 @@ describe('TreeSitterLanguageMode', () => { ).toEqual(['source.js', 'program', 'comment']); }); - it('includes nodes in injected syntax trees', async () => { + it('includes nodes in injected syntax trees', () => { const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { scopeName: 'source.js', parser: 'tree-sitter-javascript', @@ -2065,7 +2057,7 @@ describe('TreeSitterLanguageMode', () => { describe('.bufferRangeForScopeAtPosition(selector?, position)', () => { describe('when selector = null', () => { - it('returns the range of the smallest node at position', async () => { + it('returns the range of the smallest node at position', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { scopeName: 'javascript', parser: 'tree-sitter-javascript' @@ -2084,7 +2076,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('includes nodes in injected syntax trees', async () => { + it('includes nodes in injected syntax trees', () => { const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { scopeName: 'javascript', parser: 'tree-sitter-javascript', @@ -2135,7 +2127,7 @@ describe('TreeSitterLanguageMode', () => { }); describe('with a selector', () => { - it('returns the range of the smallest matching node at position', async () => { + it('returns the range of the smallest matching node at position', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { scopeName: 'javascript', parser: 'tree-sitter-javascript', @@ -2156,7 +2148,7 @@ describe('TreeSitterLanguageMode', () => { ).toEqual([[0, 2], [0, 24]]); }); - it('includes nodes in injected syntax trees', async () => { + it('includes nodes in injected syntax trees', () => { const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { scopeName: 'javascript', parser: 'tree-sitter-javascript', @@ -2218,7 +2210,7 @@ describe('TreeSitterLanguageMode', () => { ).toEqual(buffer.findSync('\\${person\\.name}')); }); - it('accepts node-matching functions as selectors', async () => { + it('accepts node-matching functions as selectors', () => { const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { scopeName: 'javascript', parser: 'tree-sitter-javascript', @@ -2276,7 +2268,7 @@ describe('TreeSitterLanguageMode', () => { }); describe('.getSyntaxNodeAtPosition(position, where?)', () => { - it('returns the range of the smallest matching node at position', async () => { + it('returns the range of the smallest matching node at position', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { scopeName: 'javascript', parser: 'tree-sitter-javascript' @@ -2391,7 +2383,7 @@ describe('TreeSitterLanguageMode', () => { }); describe('TextEditor.selectLargerSyntaxNode and .selectSmallerSyntaxNode', () => { - it('expands and contracts the selection based on the syntax tree', async () => { + it('expands and contracts the selection based on the syntax tree', () => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', scopes: { program: 'source' } @@ -2432,7 +2424,7 @@ describe('TreeSitterLanguageMode', () => { expect(editor.getSelectedBufferRange()).toEqual([[1, 3], [1, 3]]); }); - it('handles injected languages', async () => { + it('handles injected languages', () => { const jsGrammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { scopeName: 'javascript', parser: 'tree-sitter-javascript', diff --git a/spec/update-process-env-spec.js b/spec/update-process-env-spec.js index f99c961fa4..e18f29811f 100644 --- a/spec/update-process-env-spec.js +++ b/spec/update-process-env-spec.js @@ -250,10 +250,9 @@ describe('updateProcessEnv(launchEnv)', function() { describe('when the launch environment does not come from a shell', function() { describe('on macOS', function() { - it("updates process.env to match the environment in the user's login shell", async function() { - if (process.platform === 'win32') return; // TestsThatFailOnWin32 + it("updates process.env to match the environment in the user's login shell", async function(done) { + jasmine.filterByPlatform({only: ['darwin']}, done); // TestsThatFailOnWin32 - process.platform = 'darwin'; process.env.SHELL = '/my/custom/bash'; spawn.setDefault( spawn.simple( @@ -277,14 +276,15 @@ describe('updateProcessEnv(launchEnv)', function() { // Doesn't error await updateProcessEnv(null); + + done(); }); }); describe('on linux', function() { - it("updates process.env to match the environment in the user's login shell", async function() { - if (process.platform === 'win32') return; // TestsThatFailOnWin32 + it("updates process.env to match the environment in the user's login shell", async function(done) { + jasmine.filterByPlatform({only: ['linux']}, done); // TestsThatFailOnWin32 - process.platform = 'linux'; process.env.SHELL = '/my/custom/bash'; spawn.setDefault( spawn.simple( @@ -308,24 +308,25 @@ describe('updateProcessEnv(launchEnv)', function() { // Doesn't error await updateProcessEnv(null); + + done(); }); }); describe('on windows', function() { it('does not update process.env', async function() { process.platform = 'win32'; - spyOn(childProcess, 'spawn'); process.env = { FOO: 'bar' }; await updateProcessEnv(process.env); - expect(childProcess.spawn).not.toHaveBeenCalled(); + expect(spawn.calls.length).toBe(0); expect(process.env).toEqual({ FOO: 'bar' }); }); }); describe('shouldGetEnvFromShell()', function() { - it('indicates when the environment should be fetched from the shell', function() { - if (process.platform === 'win32') return; // TestsThatFailOnWin32 + it('indicates when the environment should be fetched from the shell', function(done) { + jasmine.filterByPlatform({except: ['win32']}, done); // TestsThatFailOnWin32 process.platform = 'darwin'; expect(shouldGetEnvFromShell({ SHELL: '/bin/sh' })).toBe(true); @@ -361,6 +362,8 @@ describe('updateProcessEnv(launchEnv)', function() { expect(shouldGetEnvFromShell({ SHELL: '/usr/local/bin/fish' })).toBe( true ); + + done(); }); it('returns false when the environment indicates that Atom was launched from a shell', function() { diff --git a/spec/uri-handler-registry-spec.js b/spec/uri-handler-registry-spec.js index 390cfd903c..19a8fc3282 100644 --- a/spec/uri-handler-registry-spec.js +++ b/spec/uri-handler-registry-spec.js @@ -55,7 +55,7 @@ describe('URIHandlerRegistry', () => { await registry.handleURI(u); } - expect(changeSpy.callCount).toBe(5); + expect(changeSpy.calls.count()).toBe(5); expect(registry.getRecentlyHandledURIs()).toEqual( uris .map((u, idx) => { @@ -70,7 +70,7 @@ describe('URIHandlerRegistry', () => { ); await registry.handleURI('atom://another/url'); - expect(changeSpy.callCount).toBe(6); + expect(changeSpy.calls.count()).toBe(6); const history = registry.getRecentlyHandledURIs(); expect(history.length).toBe(5); expect(history[0].uri).toBe('atom://another/url'); diff --git a/spec/view-registry-spec.js b/spec/view-registry-spec.js index 192d1b347e..c5e9b7bf5e 100644 --- a/spec/view-registry-spec.js +++ b/spec/view-registry-spec.js @@ -128,7 +128,7 @@ describe('ViewRegistry', () => { beforeEach(() => { frameRequests = []; - spyOn(window, 'requestAnimationFrame').andCallFake(fn => + spyOn(window, 'requestAnimationFrame').and.callFake(fn => frameRequests.push(fn) ); }); @@ -161,8 +161,6 @@ describe('ViewRegistry', () => { }); it('performs writes requested from read callbacks in the same animation frame', () => { - spyOn(window, 'setInterval').andCallFake(fakeSetInterval); - spyOn(window, 'clearInterval').andCallFake(fakeClearInterval); const events = []; registry.updateDocument(() => events.push('write 1')); @@ -192,23 +190,16 @@ describe('ViewRegistry', () => { }); describe('::getNextUpdatePromise()', () => - it('returns a promise that resolves at the end of the next update cycle', () => { - let updateCalled = false; - let readCalled = false; - - waitsFor('getNextUpdatePromise to resolve', done => { - registry.getNextUpdatePromise().then(() => { - expect(updateCalled).toBe(true); - expect(readCalled).toBe(true); - done(); - }); - - registry.updateDocument(() => { - updateCalled = true; - }); - registry.readDocument(() => { - readCalled = true; - }); - }); + it('returns a promise that resolves at the end of the next update cycle', async () => { + let updateDocumentSpy = jasmine.createSpy('update document'); + let readDocumentSpy = jasmine.createSpy('read document'); + + registry.updateDocument(updateDocumentSpy); + registry.readDocument(readDocumentSpy); + + await registry.getNextUpdatePromise() + + expect(updateDocumentSpy).toHaveBeenCalled(); + expect(readDocumentSpy).toHaveBeenCalled(); })); }); diff --git a/spec/wasm-tree-sitter-language-mode-spec.js b/spec/wasm-tree-sitter-language-mode-spec.js index 0d4836f8b7..77846fa9fd 100644 --- a/spec/wasm-tree-sitter-language-mode-spec.js +++ b/spec/wasm-tree-sitter-language-mode-spec.js @@ -1037,7 +1037,7 @@ describe('WASMTreeSitterLanguageMode', () => { atom.grammars.addGrammar(htmlGrammar); htmlGrammar.highlightsQuery = false; // Pretend this grammar doesn't have a highlights query. - spyOn(htmlGrammar, 'getQuery').andReturn(Promise.resolve(null)); + spyOn(htmlGrammar, 'getQuery').and.returnValue(Promise.resolve(null)); const languageMode = new WASMTreeSitterLanguageMode({ grammar: jsGrammar, buffer, @@ -1543,12 +1543,12 @@ describe('WASMTreeSitterLanguageMode', () => { let originalTimeout; beforeEach(() => { - originalTimeout = jasmine.getEnv().defaultTimeoutInterval; - jasmine.getEnv().defaultTimeoutInterval = 60 * 1000; + originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 1000; }); afterEach(() => { - jasmine.getEnv().defaultTimeoutInterval = originalTimeout; + jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; }); it('matches the highlighting of a freshly-opened editor', async () => { @@ -1633,15 +1633,7 @@ describe('WASMTreeSitterLanguageMode', () => { const tokens1 = editor.tokensForScreenRow(j); const tokens2 = editor2.tokensForScreenRow(j); expect(tokens1).toEqual(tokens2, `Seed: ${seed}, screen line: ${j}`); - if (jasmine.getEnv().currentSpec.results().failedCount > 0) { - console.log(tokens1); - console.log(tokens2); - debugger; // eslint-disable-line no-debugger - break; - } } - - if (jasmine.getEnv().currentSpec.results().failedCount > 0) break; } }); }); @@ -3475,8 +3467,8 @@ describe('WASMTreeSitterLanguageMode', () => { htmlConfig ); - spyOn(jsGrammar, 'getCommentDelimiters').andReturn({ line: undefined, block: undefined }); - spyOn(htmlGrammar, 'getCommentDelimiters').andReturn({ line: undefined, block: undefined }); + spyOn(jsGrammar, 'getCommentDelimiters').and.returnValue({ line: undefined, block: undefined }); + spyOn(htmlGrammar, 'getCommentDelimiters').and.returnValue({ line: undefined, block: undefined }); atom.config.set( 'editor.commentDelimiters', @@ -3938,7 +3930,7 @@ describe('WASMTreeSitterLanguageMode', () => { spyOn( languageMode, 'suggestedIndentForLineAtBufferRow' - ).andReturn(9); + ).and.returnValue(9); buffer.setLanguageMode(languageMode); await languageMode.ready; @@ -4168,7 +4160,7 @@ describe('WASMTreeSitterLanguageMode', () => { await languageMode.ready; await wait(0); - spyOn(languageMode, 'suggestedIndentForBufferRows').andCallThrough(); + spyOn(languageMode, 'suggestedIndentForBufferRows').and.callThrough(); editor.setCursorBufferPosition([0, 15]); editor.transact(() => { @@ -4455,7 +4447,7 @@ describe('WASMTreeSitterLanguageMode', () => { // Force this test to use async indent in all cases. languageMode.transactionReparseBudgetMs = 0; languageMode.currentTransactionReparseBudgetMs = 0; - spyOn(languageMode, 'suggestedIndentForBufferRows').andCallThrough(); + spyOn(languageMode, 'suggestedIndentForBufferRows').and.callThrough(); buffer.setLanguageMode(languageMode); await languageMode.ready; @@ -4495,7 +4487,6 @@ describe('WASMTreeSitterLanguageMode', () => { function test () { return } `) - }) }); diff --git a/spec/window-event-handler-spec.js b/spec/window-event-handler-spec.js index 51a3ca47bb..ba454d2023 100644 --- a/spec/window-event-handler-spec.js +++ b/spec/window-event-handler-spec.js @@ -1,6 +1,6 @@ const KeymapManager = require('atom-keymap'); const WindowEventHandler = require('../src/window-event-handler'); -const { conditionPromise } = require('./async-spec-helpers'); +const { conditionPromise } = require('./helpers/async-spec-helpers'); describe('WindowEventHandler', () => { let windowEventHandler; @@ -9,7 +9,7 @@ describe('WindowEventHandler', () => { atom.uninstallWindowEventHandler(); spyOn(atom, 'hide'); const initialPath = atom.project.getPaths()[0]; - spyOn(atom, 'getLoadSettings').andCallFake(() => { + spyOn(atom, 'getLoadSettings').and.callFake(() => { const loadSettings = atom.getLoadSettings.originalValue.call(atom); loadSettings.initialPath = initialPath; return loadSettings; @@ -28,11 +28,11 @@ describe('WindowEventHandler', () => { }); describe('when the window is loaded', () => - it("doesn't have .is-blurred on the body tag", () => { - if (process.platform === 'win32') { - return; - } // Win32TestFailures - can not steal focus + it("doesn't have .is-blurred on the body tag", (done) => { + jasmine.filterByPlatform({except: ['win32']}, done); // Win32TestFailures - can not steal focus expect(document.body.className).not.toMatch('is-blurred'); + + done(); })); describe('when the window is blurred', () => { @@ -51,13 +51,13 @@ describe('WindowEventHandler', () => { }); describe('resize event', () => - it('calls storeWindowDimensions', async () => { + it('calls storeWindowDimensions', async (done) => { jasmine.useRealClock(); - spyOn(atom, 'storeWindowDimensions'); + spyOn(atom, 'storeWindowDimensions').and.callFake(() => { + done(); + }); window.dispatchEvent(new CustomEvent('resize')); - - await conditionPromise(() => atom.storeWindowDimensions.callCount > 0); })); describe('window:close event', () => @@ -85,19 +85,19 @@ describe('WindowEventHandler', () => { windowEventHandler.handleLinkClick(fakeEvent); expect(shell.openExternal).toHaveBeenCalled(); - expect(shell.openExternal.argsForCall[0][0]).toBe('http://github.com'); - shell.openExternal.reset(); + expect(shell.openExternal.calls.argsFor(0)[0]).toBe('http://github.com'); + shell.openExternal.calls.reset(); link.href = 'https://github.com'; windowEventHandler.handleLinkClick(fakeEvent); expect(shell.openExternal).toHaveBeenCalled(); - expect(shell.openExternal.argsForCall[0][0]).toBe('https://github.com'); - shell.openExternal.reset(); + expect(shell.openExternal.calls.argsFor(0)[0]).toBe('https://github.com'); + shell.openExternal.calls.reset(); link.href = ''; windowEventHandler.handleLinkClick(fakeEvent); expect(shell.openExternal).not.toHaveBeenCalled(); - shell.openExternal.reset(); + shell.openExternal.calls.reset(); link.href = '#scroll-me'; windowEventHandler.handleLinkClick(fakeEvent); @@ -122,7 +122,7 @@ describe('WindowEventHandler', () => { windowEventHandler.handleLinkClick(fakeEvent); expect(uriHandler.handleURI).toHaveBeenCalled(); - expect(uriHandler.handleURI.argsForCall[0][0]).toBe('atom://github.com'); + expect(uriHandler.handleURI.calls.argsFor(0)[0]).toBe('atom://github.com'); }); }); @@ -263,7 +263,7 @@ describe('WindowEventHandler', () => { 'copy', 'paste' ]); - spyOn(atom.applicationDelegate, 'getCurrentWindow').andReturn({ + spyOn(atom.applicationDelegate, 'getCurrentWindow').and.returnValue({ webContents: webContentsSpy, on: () => {} }); @@ -279,8 +279,8 @@ describe('WindowEventHandler', () => { expect(webContentsSpy.copy).toHaveBeenCalled(); expect(webContentsSpy.paste).toHaveBeenCalled(); - webContentsSpy.copy.reset(); - webContentsSpy.paste.reset(); + webContentsSpy.copy.calls.reset(); + webContentsSpy.paste.calls.reset(); const normalInput = document.createElement('input'); jasmine.attachToDOM(normalInput); diff --git a/spec/workspace-element-spec.js b/spec/workspace-element-spec.js index ea23ed73b4..d99745f15d 100644 --- a/spec/workspace-element-spec.js +++ b/spec/workspace-element-spec.js @@ -864,7 +864,7 @@ describe('WorkspaceElement', () => { it('has a class based on the style of the scrollbar', () => { let observeCallback; const scrollbarStyle = require('scrollbar-style'); - spyOn(scrollbarStyle, 'observePreferredScrollbarStyle').andCallFake( + spyOn(scrollbarStyle, 'observePreferredScrollbarStyle').and.callFake( cb => { observeCallback = cb; return new Disposable(() => {}); @@ -898,11 +898,15 @@ describe('WorkspaceElement', () => { atom.config.get('editor.fontSize') + 'px' ); - atom.config.set( - 'editor.fontSize', - atom.config.get('editor.fontSize') + 5 - ); - await editorElement.component.getNextUpdatePromise(); + await new Promise((resolve) => { + editorElement.component.getNextUpdatePromise().then(() => resolve()); + + atom.config.set( + 'editor.fontSize', + atom.config.get('editor.fontSize') + 5 + ); + }) + expect(getComputedStyle(editorElement).fontSize).toBe( atom.config.get('editor.fontSize') + 'px' ); @@ -914,17 +918,24 @@ describe('WorkspaceElement', () => { let fontFamily = atom.config.get('editor.fontFamily'); expect(getComputedStyle(editorElement).fontFamily).toBe(fontFamily); - atom.config.set('editor.fontFamily', 'sans-serif'); + await new Promise((resolve) => { + editorElement.component.getNextUpdatePromise().then(() => resolve()); + atom.config.set('editor.fontFamily', 'sans-serif'); + }) + fontFamily = atom.config.get('editor.fontFamily'); - await editorElement.component.getNextUpdatePromise(); expect(getComputedStyle(editorElement).fontFamily).toBe(fontFamily); expect(editor.getDefaultCharWidth()).not.toBe(initialCharWidth); }); it("updates the line-height based on the 'editor.lineHeight' config value", async () => { const initialLineHeight = editor.getLineHeightInPixels(); - atom.config.set('editor.lineHeight', '30px'); - await editorElement.component.getNextUpdatePromise(); + + await new Promise((resolve) => { + editorElement.component.getNextUpdatePromise().then(() => resolve()); + atom.config.set('editor.lineHeight', '30px'); + }); + expect(getComputedStyle(editorElement).lineHeight).toBe( atom.config.get('editor.lineHeight') ); @@ -1098,7 +1109,7 @@ describe('WorkspaceElement', () => { path.join(projectPaths[0], 'spec'), {} ); - ipcRenderer.send.reset(); + ipcRenderer.send.calls.reset(); // Active item doesn't implement ::getPath(). Use first project directory. const item = document.createElement('div'); @@ -1109,7 +1120,7 @@ describe('WorkspaceElement', () => { path.join(projectPaths[0], 'spec'), {} ); - ipcRenderer.send.reset(); + ipcRenderer.send.calls.reset(); // Active item has no path. Use first project directory. item.getPath = () => null; @@ -1119,7 +1130,7 @@ describe('WorkspaceElement', () => { path.join(projectPaths[0], 'spec'), {} ); - ipcRenderer.send.reset(); + ipcRenderer.send.calls.reset(); // Active item has path. Use project path for item path. item.getPath = () => path.join(projectPaths[1], 'a-file.txt'); @@ -1129,7 +1140,7 @@ describe('WorkspaceElement', () => { path.join(projectPaths[1], 'spec'), {} ); - ipcRenderer.send.reset(); + ipcRenderer.send.calls.reset(); }); it('passes additional options to the spec window', () => { diff --git a/spec/workspace-spec.js b/spec/workspace-spec.js index c8e0c451ec..1850aa7f92 100644 --- a/spec/workspace-spec.js +++ b/spec/workspace-spec.js @@ -5,18 +5,24 @@ const TextBuffer = require('text-buffer'); const TextEditor = require('../src/text-editor'); const Workspace = require('../src/workspace'); const Project = require('../src/project'); -const platform = require('./spec-helper-platform'); +const platform = require('./helpers/platform'); const _ = require('underscore-plus'); const fstream = require('fstream'); const fs = require('fs-plus'); const AtomEnvironment = require('../src/atom-environment'); -const { conditionPromise } = require('./async-spec-helpers'); +const { conditionPromise } = require('./helpers/async-spec-helpers'); describe('Workspace', () => { let workspace; let setDocumentEdited; - beforeEach(() => { + let fsGetSizeSyncSpy; + let fsOpenSyncSpy; + + beforeEach(async () => { + fsGetSizeSyncSpy ||= spyOn(fs, 'getSizeSync').and.callThrough(); + fsOpenSyncSpy ||= spyOn(fs, 'openSync').and.callThrough(); + workspace = atom.workspace; workspace.resetFontSize(); spyOn(atom.applicationDelegate, 'confirm'); @@ -25,12 +31,14 @@ describe('Workspace', () => { 'setWindowDocumentEdited' ); atom.project.setPaths([atom.project.getDirectories()[0].resolve('dir')]); - waits(1); - waitsForPromise(() => atom.workspace.itemLocationStore.clear()); + await atom.workspace.itemLocationStore.clear(); }); afterEach(() => { + fsGetSizeSyncSpy.and.callThrough(); + fsOpenSyncSpy.and.callThrough(); + try { temp.cleanupSync(); } catch (e) { @@ -38,130 +46,106 @@ describe('Workspace', () => { } }); - function simulateReload() { - waitsForPromise(() => { - const workspaceState = workspace.serialize(); - const projectState = atom.project.serialize({ isUnloading: true }); - workspace.destroy(); - atom.project.destroy(); - atom.project = new Project({ - notificationManager: atom.notifications, - packageManager: atom.packages, - confirm: atom.confirm.bind(atom), - applicationDelegate: atom.applicationDelegate, - grammarRegistry: atom.grammars - }); - return atom.project.deserialize(projectState).then(() => { - workspace = atom.workspace = new Workspace({ - config: atom.config, - project: atom.project, - packageManager: atom.packages, - grammarRegistry: atom.grammars, - styleManager: atom.styles, - deserializerManager: atom.deserializers, - notificationManager: atom.notifications, - applicationDelegate: atom.applicationDelegate, - viewRegistry: atom.views, - assert: atom.assert.bind(atom), - textEditorRegistry: atom.textEditors - }); - workspace.deserialize(workspaceState, atom.deserializers); - }); - }); + async function simulateReload() { + const workspaceState = workspace.serialize(); + const projectState = atom.project.serialize({ isUnloading: true }); + workspace.destroy(); + atom.project.destroy(); + atom.project = new Project({ + notificationManager: atom.notifications, + packageManager: atom.packages, + confirm: atom.confirm.bind(atom), + applicationDelegate: atom.applicationDelegate, + grammarRegistry: atom.grammars + }); + + await atom.project.deserialize(projectState) + + workspace = atom.workspace = new Workspace({ + config: atom.config, + project: atom.project, + packageManager: atom.packages, + grammarRegistry: atom.grammars, + styleManager: atom.styles, + deserializerManager: atom.deserializers, + notificationManager: atom.notifications, + applicationDelegate: atom.applicationDelegate, + viewRegistry: atom.views, + assert: atom.assert.bind(atom), + textEditorRegistry: atom.textEditors + }); + workspace.deserialize(workspaceState, atom.deserializers); } describe('serialization', () => { describe('when the workspace contains text editors', () => { - it('constructs the view with the same panes', () => { + it('constructs the view with the same panes', async () => { const pane1 = atom.workspace.getActivePane(); const pane2 = pane1.splitRight({ copyActiveItem: true }); const pane3 = pane2.splitRight({ copyActiveItem: true }); let pane4 = null; + let editor; - waitsForPromise(() => - atom.workspace - .open(null) - .then(editor => editor.setText('An untitled editor.')) - ); + (await atom.workspace.open(null)).setText('An untitled editor.') - waitsForPromise(() => - atom.workspace - .open('b') - .then(editor => pane2.activateItem(editor.copy())) - ); + pane2.activateItem((await atom.workspace.open('b')).copy()); - waitsForPromise(() => - atom.workspace - .open('../sample.js') - .then(editor => pane3.activateItem(editor)) - ); + pane3.activateItem(await atom.workspace.open('../sample.js')); - runs(() => { - pane3.activeItem.setCursorScreenPosition([2, 4]); - pane4 = pane2.splitDown(); - }); + pane3.activeItem.setCursorScreenPosition([2, 4]); + pane4 = pane2.splitDown(); - waitsForPromise(() => - atom.workspace - .open('../sample.txt') - .then(editor => pane4.activateItem(editor)) - ); + pane4.activateItem(await atom.workspace.open('../sample.txt')); - runs(() => { - pane4.getActiveItem().setCursorScreenPosition([0, 2]); - pane2.activate(); - }); + pane4.getActiveItem().setCursorScreenPosition([0, 2]); + pane2.activate(); - simulateReload(); - - runs(() => { - expect(atom.workspace.getTextEditors().length).toBe(5); - const [ - editor1, - editor2, - untitledEditor, - editor3, - editor4 - ] = atom.workspace.getTextEditors(); - const firstDirectory = atom.project.getDirectories()[0]; - expect(firstDirectory).toBeDefined(); - expect(editor1.getPath()).toBe(firstDirectory.resolve('b')); - expect(editor2.getPath()).toBe( - firstDirectory.resolve('../sample.txt') - ); - expect(editor2.getCursorScreenPosition()).toEqual([0, 2]); - expect(editor3.getPath()).toBe(firstDirectory.resolve('b')); - expect(editor4.getPath()).toBe( - firstDirectory.resolve('../sample.js') - ); - expect(editor4.getCursorScreenPosition()).toEqual([2, 4]); - expect(untitledEditor.getPath()).toBeUndefined(); - expect(untitledEditor.getText()).toBe('An untitled editor.'); + await simulateReload(); - expect(atom.workspace.getActiveTextEditor().getPath()).toBe( - editor3.getPath() - ); - const pathEscaped = fs.tildify( - escapeStringRegex(atom.project.getPaths()[0]) - ); - expect(document.title).toMatch( - new RegExp( - `^${path.basename(editor3.getLongTitle())} \\u2014 ${pathEscaped}` - ) - ); - }); + expect(atom.workspace.getTextEditors().length).toBe(5); + const [ + editor1, + editor2, + untitledEditor, + editor3, + editor4 + ] = atom.workspace.getTextEditors(); + const firstDirectory = atom.project.getDirectories()[0]; + expect(firstDirectory).toBeDefined(); + expect(editor1.getPath()).toBe(firstDirectory.resolve('b')); + expect(editor2.getPath()).toBe( + firstDirectory.resolve('../sample.txt') + ); + expect(editor2.getCursorScreenPosition()).toEqual([0, 2]); + expect(editor3.getPath()).toBe(firstDirectory.resolve('b')); + expect(editor4.getPath()).toBe( + firstDirectory.resolve('../sample.js') + ); + expect(editor4.getCursorScreenPosition()).toEqual([2, 4]); + expect(untitledEditor.getPath()).toBeUndefined(); + expect(untitledEditor.getText()).toBe('An untitled editor.'); + + expect(atom.workspace.getActiveTextEditor().getPath()).toBe( + editor3.getPath() + ); + const pathEscaped = fs.tildify( + escapeStringRegex(atom.project.getPaths()[0]) + ); + expect(document.title).toMatch( + new RegExp( + `^${path.basename(editor3.getLongTitle())} \\u2014 ${pathEscaped}` + ) + ); }); }); describe('where there are no open panes or editors', () => { - it('constructs the view with no open editors', () => { + it('constructs the view with no open editors', async () => { atom.workspace.getActivePane().destroy(); expect(atom.workspace.getTextEditors().length).toBe(0); - simulateReload(); + await simulateReload(); - runs(() => { - expect(atom.workspace.getTextEditors().length).toBe(0); - }); + expect(atom.workspace.getTextEditors().length).toBe(0); }); }); }); @@ -172,109 +156,87 @@ describe('Workspace', () => { beforeEach(() => { openEvents = []; workspace.onDidOpen(event => openEvents.push(event)); - spyOn(workspace.getActivePane(), 'activate').andCallThrough(); + spyOn(workspace.getActivePane(), 'activate').and.callThrough(); }); describe("when the 'searchAllPanes' option is false (default)", () => { describe('when called without a uri or item', () => { - it('adds and activates an empty editor on the active pane', () => { + it('adds and activates an empty editor on the active pane', async () => { let editor1; let editor2; - waitsForPromise(() => - workspace.open().then(editor => { - editor1 = editor; - }) - ); + editor1 = await workspace.open() - runs(() => { - expect(editor1.getPath()).toBeUndefined(); - expect(workspace.getActivePane().items).toEqual([editor1]); - expect(workspace.getActivePaneItem()).toBe(editor1); - expect(workspace.getActivePane().activate).toHaveBeenCalled(); - expect(openEvents).toEqual([ - { - uri: undefined, - pane: workspace.getActivePane(), - item: editor1, - index: 0 - } - ]); - openEvents = []; - }); + expect(editor1.getPath()).toBeUndefined(); + expect(workspace.getActivePane().items).toEqual([editor1]); + expect(workspace.getActivePaneItem()).toBe(editor1); + expect(workspace.getActivePane().activate).toHaveBeenCalled(); + expect(openEvents).toEqual([ + { + uri: undefined, + pane: workspace.getActivePane(), + item: editor1, + index: 0 + } + ]); - waitsForPromise(() => - workspace.open().then(editor => { - editor2 = editor; - }) - ); + openEvents = []; + editor2 = await workspace.open(); - runs(() => { - expect(editor2.getPath()).toBeUndefined(); - expect(workspace.getActivePane().items).toEqual([editor1, editor2]); - expect(workspace.getActivePaneItem()).toBe(editor2); - expect(workspace.getActivePane().activate).toHaveBeenCalled(); - expect(openEvents).toEqual([ - { - uri: undefined, - pane: workspace.getActivePane(), - item: editor2, - index: 1 - } - ]); - }); + expect(editor2.getPath()).toBeUndefined(); + expect(workspace.getActivePane().items).toEqual([editor1, editor2]); + expect(workspace.getActivePaneItem()).toBe(editor2); + expect(workspace.getActivePane().activate).toHaveBeenCalled(); + expect(openEvents).toEqual([ + { + uri: undefined, + pane: workspace.getActivePane(), + item: editor2, + index: 1 + } + ]); }); }); describe('when called with a uri', () => { describe('when the active pane already has an editor for the given uri', () => { - it('activates the existing editor on the active pane', () => { + it('activates the existing editor on the active pane', async () => { let editor = null; let editor1 = null; let editor2 = null; - waitsForPromise(() => - workspace.open('a').then(o => { - editor1 = o; - return workspace.open('b').then(o => { - editor2 = o; - return workspace.open('a').then(o => { - editor = o; - }); - }); - }) - ); + editor1 = await workspace.open('a'); + editor2 = await workspace.open('b'); + editor = await workspace.open('a'); - runs(() => { - expect(editor).toBe(editor1); - expect(workspace.getActivePaneItem()).toBe(editor); - expect(workspace.getActivePane().activate).toHaveBeenCalled(); - const firstDirectory = atom.project.getDirectories()[0]; - expect(firstDirectory).toBeDefined(); - expect(openEvents).toEqual([ - { - uri: firstDirectory.resolve('a'), - item: editor1, - pane: atom.workspace.getActivePane(), - index: 0 - }, - { - uri: firstDirectory.resolve('b'), - item: editor2, - pane: atom.workspace.getActivePane(), - index: 1 - }, - { - uri: firstDirectory.resolve('a'), - item: editor1, - pane: atom.workspace.getActivePane(), - index: 0 - } - ]); - }); + expect(editor).toBe(editor1); + expect(workspace.getActivePaneItem()).toBe(editor); + expect(workspace.getActivePane().activate).toHaveBeenCalled(); + const firstDirectory = atom.project.getDirectories()[0]; + expect(firstDirectory).toBeDefined(); + expect(openEvents).toEqual([ + { + uri: firstDirectory.resolve('a'), + item: editor1, + pane: atom.workspace.getActivePane(), + index: 0 + }, + { + uri: firstDirectory.resolve('b'), + item: editor2, + pane: atom.workspace.getActivePane(), + index: 1 + }, + { + uri: firstDirectory.resolve('a'), + item: editor1, + pane: atom.workspace.getActivePane(), + index: 0 + } + ]); }); - it('finds items in docks', () => { + it('finds items in docks', async () => { const dock = atom.workspace.getRightDock(); const ITEM_URI = 'atom://test'; const item = { @@ -284,30 +246,24 @@ describe('Workspace', () => { }; dock.getActivePane().addItem(item); expect(dock.getPaneItems()).toHaveLength(1); - waitsForPromise(() => - atom.workspace.open(ITEM_URI, { searchAllPanes: true }) - ); - runs(() => { - expect(atom.workspace.getPaneItems()).toHaveLength(1); - expect(dock.getPaneItems()).toHaveLength(1); - expect(dock.getPaneItems()[0]).toBe(item); - }); + + await atom.workspace.open(ITEM_URI, { searchAllPanes: true }) + + expect(atom.workspace.getPaneItems()).toHaveLength(1); + expect(dock.getPaneItems()).toHaveLength(1); + expect(dock.getPaneItems()[0]).toBe(item); }); }); describe("when the 'activateItem' option is false", () => { - it('adds the item to the workspace', () => { + it('adds the item to the workspace', async () => { let editor; - waitsForPromise(() => workspace.open('a')); - waitsForPromise(() => - workspace.open('b', { activateItem: false }).then(o => { - editor = o; - }) - ); - runs(() => { - expect(workspace.getPaneItems()).toContain(editor); - expect(workspace.getActivePaneItem()).not.toBe(editor); - }); + + await workspace.open('a'); + editor = await workspace.open('b', { activateItem: false }); + + expect(workspace.getPaneItems()).toContain(editor); + expect(workspace.getActivePaneItem()).not.toBe(editor); }); }); @@ -321,66 +277,55 @@ describe('Workspace', () => { atom.workspace.enablePersistence = false; }); - it('adds and activates a new editor for the given path on the active pane', () => { - let editor = null; - waitsForPromise(() => - workspace.open('a').then(o => { - editor = o; - }) - ); + it('adds and activates a new editor for the given path on the active pane', async () => { + let editor = await workspace.open('a'); - runs(() => { - const firstDirectory = atom.project.getDirectories()[0]; - expect(firstDirectory).toBeDefined(); - expect(editor.getURI()).toBe(firstDirectory.resolve('a')); - expect(workspace.getActivePaneItem()).toBe(editor); - expect(workspace.getActivePane().items).toEqual([editor]); - expect(workspace.getActivePane().activate).toHaveBeenCalled(); - }); + const firstDirectory = atom.project.getDirectories()[0]; + expect(firstDirectory).toBeDefined(); + expect(editor.getURI()).toBe(firstDirectory.resolve('a')); + expect(workspace.getActivePaneItem()).toBe(editor); + expect(workspace.getActivePane().items).toEqual([editor]); + expect(workspace.getActivePane().activate).toHaveBeenCalled(); }); - it('discovers existing editors that are still opening', () => { + it('discovers existing editors that are still opening', async () => { let editor0 = null; let editor1 = null; - waitsForPromise(() => - Promise.all([ - workspace.open('spartacus.txt').then(o0 => { - editor0 = o0; - }), - workspace.open('spartacus.txt').then(o1 => { - editor1 = o1; - }) - ]) - ); + await Promise.all([ + workspace.open('spartacus.txt').then(o0 => { + editor0 = o0; + }), + workspace.open('spartacus.txt').then(o1 => { + editor1 = o1; + }) + ]); - runs(() => { - expect(editor0).toEqual(editor1); - expect(workspace.getActivePane().items).toEqual([editor0]); - }); + expect(editor0).toEqual(editor1); + expect(workspace.getActivePane().items).toEqual([editor0]); }); - it("uses the location specified by the model's `getDefaultLocation()` method", () => { + it("uses the location specified by the model's `getDefaultLocation()` method", async () => { const item = { - getDefaultLocation: jasmine.createSpy().andReturn('right'), + getDefaultLocation: jasmine.createSpy().and.returnValue('right'), getElement: () => document.createElement('div') }; - const opener = jasmine.createSpy().andReturn(item); + const opener = jasmine.createSpy().and.returnValue(item); const dock = atom.workspace.getRightDock(); - spyOn(atom.workspace.itemLocationStore, 'load').andReturn( + spyOn(atom.workspace.itemLocationStore, 'load').and.returnValue( Promise.resolve() ); - spyOn(atom.workspace, 'getOpeners').andReturn([opener]); + spyOn(atom.workspace, 'getOpeners').and.returnValue([opener]); expect(dock.getPaneItems()).toHaveLength(0); - waitsForPromise(() => atom.workspace.open('a')); - runs(() => { - expect(dock.getPaneItems()).toHaveLength(1); - expect(opener).toHaveBeenCalled(); - expect(item.getDefaultLocation).toHaveBeenCalled(); - }); + + await atom.workspace.open('a'); + + expect(dock.getPaneItems()).toHaveLength(1); + expect(opener).toHaveBeenCalled(); + expect(item.getDefaultLocation).toHaveBeenCalled(); }); - it('prefers the last location the user used for that item', () => { + it('prefers the last location the user used for that item', async () => { const ITEM_URI = 'atom://test'; const item = { getURI: () => ITEM_URI, @@ -389,18 +334,18 @@ describe('Workspace', () => { }; const opener = uri => (uri === ITEM_URI ? item : null); const dock = atom.workspace.getRightDock(); - spyOn(atom.workspace.itemLocationStore, 'load').andCallFake(uri => + spyOn(atom.workspace.itemLocationStore, 'load').and.callFake(uri => uri === 'atom://test' ? Promise.resolve('right') : Promise.resolve() ); - spyOn(atom.workspace, 'getOpeners').andReturn([opener]); + spyOn(atom.workspace, 'getOpeners').and.returnValue([opener]); expect(dock.getPaneItems()).toHaveLength(0); - waitsForPromise(() => atom.workspace.open(ITEM_URI)); - runs(() => { - expect(dock.getPaneItems()).toHaveLength(1); - expect(dock.getPaneItems()[0]).toBe(item); - }); + + await atom.workspace.open(ITEM_URI); + + expect(dock.getPaneItems()).toHaveLength(1); + expect(dock.getPaneItems()[0]).toBe(item); }); }); }); @@ -433,37 +378,27 @@ describe('Workspace', () => { describe("when the 'searchAllPanes' option is true", () => { describe('when an editor for the given uri is already open on an inactive pane', () => { - it('activates the existing editor on the inactive pane, then activates that pane', () => { + it('activates the existing editor on the inactive pane, then activates that pane', async () => { let editor1 = null; let editor2 = null; const pane1 = workspace.getActivePane(); const pane2 = workspace.getActivePane().splitRight(); - waitsForPromise(() => { - pane1.activate(); - return workspace.open('a').then(o => { - editor1 = o; - }); - }); + pane1.activate(); + editor1 = await workspace.open('a'); - waitsForPromise(() => { - pane2.activate(); - return workspace.open('b').then(o => { - editor2 = o; - }); - }); + pane2.activate(); + editor2 = await workspace.open('b'); - runs(() => expect(workspace.getActivePaneItem()).toBe(editor2)); + expect(workspace.getActivePaneItem()).toBe(editor2); - waitsForPromise(() => workspace.open('a', { searchAllPanes: true })); + await workspace.open('a', { searchAllPanes: true }); - runs(() => { - expect(workspace.getActivePane()).toBe(pane1); - expect(workspace.getActivePaneItem()).toBe(editor1); - }); + expect(workspace.getActivePane()).toBe(pane1); + expect(workspace.getActivePaneItem()).toBe(editor1); }); - it('discovers existing editors that are still opening in an inactive pane', () => { + it('discovers existing editors that are still opening in an inactive pane', async () => { let editor0 = null; let editor1 = null; const pane0 = workspace.getActivePane(); @@ -482,46 +417,37 @@ describe('Workspace', () => { editor1 = o1; }); - waitsForPromise(() => Promise.all([promise0, promise1])); + await Promise.all([promise0, promise1]); - runs(() => { - expect(editor0).toBeDefined(); - expect(editor1).toBeDefined(); + expect(editor0).toBeDefined(); + expect(editor1).toBeDefined(); - expect(editor0).toEqual(editor1); - expect(workspace.getActivePane().items).toEqual([editor0]); - }); + expect(editor0).toEqual(editor1); + expect(workspace.getActivePane().items).toEqual([editor0]); }); - it('activates the pane in the dock with the matching item', () => { + it('activates the pane in the dock with the matching item', async () => { const dock = atom.workspace.getRightDock(); const ITEM_URI = 'atom://test'; const item = { getURI: () => ITEM_URI, - getDefaultLocation: jasmine.createSpy().andReturn('left'), + getDefaultLocation: jasmine.createSpy().and.returnValue('left'), getElement: () => document.createElement('div') }; dock.getActivePane().addItem(item); spyOn(dock.paneForItem(item), 'activate'); - waitsForPromise(() => - atom.workspace.open(ITEM_URI, { searchAllPanes: true }) - ); - runs(() => - expect(dock.paneForItem(item).activate).toHaveBeenCalled() - ); + + await atom.workspace.open(ITEM_URI, { searchAllPanes: true }); + + expect(dock.paneForItem(item).activate).toHaveBeenCalled() }); }); describe('when no editor for the given uri is open in any pane', () => { - it('opens an editor for the given uri in the active pane', () => { - let editor = null; - waitsForPromise(() => - workspace.open('a', { searchAllPanes: true }).then(o => { - editor = o; - }) - ); + it('opens an editor for the given uri in the active pane', async () => { + let editor = await workspace.open('a', { searchAllPanes: true }); - runs(() => expect(workspace.getActivePaneItem()).toBe(editor)); + expect(workspace.getActivePaneItem()).toBe(editor); }); }); }); @@ -585,42 +511,29 @@ describe('Workspace', () => { describe("when the 'split' option is set", () => { describe("when the 'split' option is 'left'", () => { - it('opens the editor in the leftmost pane of the current pane axis', () => { + it('opens the editor in the leftmost pane of the current pane axis', async () => { const pane1 = workspace.getActivePane(); const pane2 = pane1.splitRight(); expect(workspace.getActivePane()).toBe(pane2); - let editor = null; - waitsForPromise(() => - workspace.open('a', { split: 'left' }).then(o => { - editor = o; - }) - ); + let editor = await workspace.open('a', { split: 'left' }); - runs(() => { - expect(workspace.getActivePane()).toBe(pane1); - expect(pane1.items).toEqual([editor]); - expect(pane2.items).toEqual([]); - }); + expect(workspace.getActivePane()).toBe(pane1); + expect(pane1.items).toEqual([editor]); + expect(pane2.items).toEqual([]); // Focus right pane and reopen the file on the left - waitsForPromise(() => { - pane2.focus(); - return workspace.open('a', { split: 'left' }).then(o => { - editor = o; - }); - }); + pane2.focus(); + editor = await workspace.open('a', { split: 'left' }); - runs(() => { - expect(workspace.getActivePane()).toBe(pane1); - expect(pane1.items).toEqual([editor]); - expect(pane2.items).toEqual([]); - }); + expect(workspace.getActivePane()).toBe(pane1); + expect(pane1.items).toEqual([editor]); + expect(pane2.items).toEqual([]); }); }); describe('when a pane axis is the leftmost sibling of the current pane', () => { - it('opens the new item in the current pane', () => { + it('opens the new item in the current pane', async () => { let editor = null; const pane1 = workspace.getActivePane(); const pane2 = pane1.splitLeft(); @@ -628,54 +541,37 @@ describe('Workspace', () => { pane1.activate(); expect(workspace.getActivePane()).toBe(pane1); - waitsForPromise(() => - workspace.open('a', { split: 'left' }).then(o => { - editor = o; - }) - ); + editor = await workspace.open('a', { split: 'left' }); - runs(() => { - expect(workspace.getActivePane()).toBe(pane1); - expect(pane1.items).toEqual([editor]); - }); + expect(workspace.getActivePane()).toBe(pane1); + expect(pane1.items).toEqual([editor]); }); }); describe("when the 'split' option is 'right'", () => { - it('opens the editor in the rightmost pane of the current pane axis', () => { + it('opens the editor in the rightmost pane of the current pane axis', async () => { let editor = null; const pane1 = workspace.getActivePane(); let pane2 = null; - waitsForPromise(() => - workspace.open('a', { split: 'right' }).then(o => { - editor = o; - }) - ); - runs(() => { - pane2 = workspace.getPanes().filter(p => p !== pane1)[0]; - expect(workspace.getActivePane()).toBe(pane2); - expect(pane1.items).toEqual([]); - expect(pane2.items).toEqual([editor]); - }); + editor = await workspace.open('a', { split: 'right' }); + + pane2 = workspace.getPanes().filter(p => p !== pane1)[0]; + expect(workspace.getActivePane()).toBe(pane2); + expect(pane1.items).toEqual([]); + expect(pane2.items).toEqual([editor]); // Focus right pane and reopen the file on the right - waitsForPromise(() => { - pane1.focus(); - return workspace.open('a', { split: 'right' }).then(o => { - editor = o; - }); - }); + pane1.focus(); + editor = await workspace.open('a', { split: 'right' }); - runs(() => { - expect(workspace.getActivePane()).toBe(pane2); - expect(pane1.items).toEqual([]); - expect(pane2.items).toEqual([editor]); - }); + expect(workspace.getActivePane()).toBe(pane2); + expect(pane1.items).toEqual([]); + expect(pane2.items).toEqual([editor]); }); describe('when a pane axis is the rightmost sibling of the current pane', () => { - it('opens the new item in a new pane split to the right of the current pane', () => { + it('opens the new item in a new pane split to the right of the current pane', async () => { let editor = null; const pane1 = workspace.getActivePane(); const pane2 = pane1.splitRight(); @@ -684,64 +580,45 @@ describe('Workspace', () => { expect(workspace.getActivePane()).toBe(pane1); let pane4 = null; - waitsForPromise(() => - workspace.open('a', { split: 'right' }).then(o => { - editor = o; - }) - ); + editor = await workspace.open('a', { split: 'right' }); - runs(() => { - pane4 = workspace.getPanes().filter(p => p !== pane1)[0]; - expect(workspace.getActivePane()).toBe(pane4); - expect(pane4.items).toEqual([editor]); - expect(workspace.getCenter().paneContainer.root.children[0]).toBe( - pane1 - ); - expect(workspace.getCenter().paneContainer.root.children[1]).toBe( - pane4 - ); - }); + pane4 = workspace.getPanes().filter(p => p !== pane1)[0]; + expect(workspace.getActivePane()).toBe(pane4); + expect(pane4.items).toEqual([editor]); + expect(workspace.getCenter().paneContainer.root.children[0]).toBe( + pane1 + ); + expect(workspace.getCenter().paneContainer.root.children[1]).toBe( + pane4 + ); }); }); }); describe("when the 'split' option is 'up'", () => { - it('opens the editor in the topmost pane of the current pane axis', () => { + it('opens the editor in the topmost pane of the current pane axis', async () => { const pane1 = workspace.getActivePane(); const pane2 = pane1.splitDown(); expect(workspace.getActivePane()).toBe(pane2); - let editor = null; - waitsForPromise(() => - workspace.open('a', { split: 'up' }).then(o => { - editor = o; - }) - ); + let editor = await workspace.open('a', { split: 'up' }); - runs(() => { - expect(workspace.getActivePane()).toBe(pane1); - expect(pane1.items).toEqual([editor]); - expect(pane2.items).toEqual([]); - }); + expect(workspace.getActivePane()).toBe(pane1); + expect(pane1.items).toEqual([editor]); + expect(pane2.items).toEqual([]); // Focus bottom pane and reopen the file on the top - waitsForPromise(() => { - pane2.focus(); - return workspace.open('a', { split: 'up' }).then(o => { - editor = o; - }); - }); + pane2.focus(); + editor = await workspace.open('a', { split: 'up' }); - runs(() => { - expect(workspace.getActivePane()).toBe(pane1); - expect(pane1.items).toEqual([editor]); - expect(pane2.items).toEqual([]); - }); + expect(workspace.getActivePane()).toBe(pane1); + expect(pane1.items).toEqual([editor]); + expect(pane2.items).toEqual([]); }); }); describe('when a pane axis is the topmost sibling of the current pane', () => { - it('opens the new item in the current pane', () => { + it('opens the new item in the current pane', async () => { let editor = null; const pane1 = workspace.getActivePane(); const pane2 = pane1.splitUp(); @@ -749,54 +626,37 @@ describe('Workspace', () => { pane1.activate(); expect(workspace.getActivePane()).toBe(pane1); - waitsForPromise(() => - workspace.open('a', { split: 'up' }).then(o => { - editor = o; - }) - ); + editor = await workspace.open('a', { split: 'up' }); - runs(() => { - expect(workspace.getActivePane()).toBe(pane1); - expect(pane1.items).toEqual([editor]); - }); + expect(workspace.getActivePane()).toBe(pane1); + expect(pane1.items).toEqual([editor]); }); }); describe("when the 'split' option is 'down'", () => { - it('opens the editor in the bottommost pane of the current pane axis', () => { + it('opens the editor in the bottommost pane of the current pane axis', async () => { let editor = null; const pane1 = workspace.getActivePane(); let pane2 = null; - waitsForPromise(() => - workspace.open('a', { split: 'down' }).then(o => { - editor = o; - }) - ); - runs(() => { - pane2 = workspace.getPanes().filter(p => p !== pane1)[0]; - expect(workspace.getActivePane()).toBe(pane2); - expect(pane1.items).toEqual([]); - expect(pane2.items).toEqual([editor]); - }); + editor = await workspace.open('a', { split: 'down' }); + + pane2 = workspace.getPanes().filter(p => p !== pane1)[0]; + expect(workspace.getActivePane()).toBe(pane2); + expect(pane1.items).toEqual([]); + expect(pane2.items).toEqual([editor]); // Focus bottom pane and reopen the file on the right - waitsForPromise(() => { - pane1.focus(); - return workspace.open('a', { split: 'down' }).then(o => { - editor = o; - }); - }); + pane1.focus(); + editor = await workspace.open('a', { split: 'down' }); - runs(() => { - expect(workspace.getActivePane()).toBe(pane2); - expect(pane1.items).toEqual([]); - expect(pane2.items).toEqual([editor]); - }); + expect(workspace.getActivePane()).toBe(pane2); + expect(pane1.items).toEqual([]); + expect(pane2.items).toEqual([editor]); }); describe('when a pane axis is the bottommost sibling of the current pane', () => { - it('opens the new item in a new pane split to the bottom of the current pane', () => { + it('opens the new item in a new pane split to the bottom of the current pane', async () => { let editor = null; const pane1 = workspace.getActivePane(); const pane2 = pane1.splitDown(); @@ -804,92 +664,62 @@ describe('Workspace', () => { expect(workspace.getActivePane()).toBe(pane1); let pane4 = null; - waitsForPromise(() => - workspace.open('a', { split: 'down' }).then(o => { - editor = o; - }) - ); + editor = await workspace.open('a', { split: 'down' }); - runs(() => { - pane4 = workspace.getPanes().filter(p => p !== pane1)[0]; - expect(workspace.getActivePane()).toBe(pane4); - expect(pane4.items).toEqual([editor]); - expect(workspace.getCenter().paneContainer.root.children[0]).toBe( - pane1 - ); - expect(workspace.getCenter().paneContainer.root.children[1]).toBe( - pane2 - ); - }); + pane4 = workspace.getPanes().filter(p => p !== pane1)[0]; + expect(workspace.getActivePane()).toBe(pane4); + expect(pane4.items).toEqual([editor]); + expect(workspace.getCenter().paneContainer.root.children[0]).toBe( + pane1 + ); + expect(workspace.getCenter().paneContainer.root.children[1]).toBe( + pane2 + ); }); }); }); }); describe('when an initialLine and initialColumn are specified', () => { - it('moves the cursor to the indicated location', () => { - waitsForPromise(() => - workspace.open('a', { initialLine: 1, initialColumn: 5 }) - ); + it('moves the cursor to the indicated location', async () => { + await workspace.open('a', { initialLine: 1, initialColumn: 5 }); - runs(() => - expect( - workspace.getActiveTextEditor().getCursorBufferPosition() - ).toEqual([1, 5]) - ); + expect( + workspace.getActiveTextEditor().getCursorBufferPosition() + ).toEqual([1, 5]) - waitsForPromise(() => - workspace.open('a', { initialLine: 2, initialColumn: 4 }) - ); + await workspace.open('a', { initialLine: 2, initialColumn: 4 }); - runs(() => - expect( - workspace.getActiveTextEditor().getCursorBufferPosition() - ).toEqual([2, 4]) - ); + expect( + workspace.getActiveTextEditor().getCursorBufferPosition() + ).toEqual([2, 4]) - waitsForPromise(() => - workspace.open('a', { initialLine: 0, initialColumn: 0 }) - ); + await workspace.open('a', { initialLine: 0, initialColumn: 0 }); - runs(() => - expect( - workspace.getActiveTextEditor().getCursorBufferPosition() - ).toEqual([0, 0]) - ); + expect( + workspace.getActiveTextEditor().getCursorBufferPosition() + ).toEqual([0, 0]) - waitsForPromise(() => - workspace.open('a', { initialLine: NaN, initialColumn: 4 }) - ); + await workspace.open('a', { initialLine: NaN, initialColumn: 4 }); - runs(() => - expect( - workspace.getActiveTextEditor().getCursorBufferPosition() - ).toEqual([0, 4]) - ); + expect( + workspace.getActiveTextEditor().getCursorBufferPosition() + ).toEqual([0, 4]) - waitsForPromise(() => - workspace.open('a', { initialLine: 2, initialColumn: NaN }) - ); + await workspace.open('a', { initialLine: 2, initialColumn: NaN }); - runs(() => - expect( - workspace.getActiveTextEditor().getCursorBufferPosition() - ).toEqual([2, 0]) - ); + expect( + workspace.getActiveTextEditor().getCursorBufferPosition() + ).toEqual([2, 0]) - waitsForPromise(() => - workspace.open('a', { - initialLine: Infinity, - initialColumn: Infinity - }) - ); + await workspace.open('a', { + initialLine: Infinity, + initialColumn: Infinity + }); - runs(() => - expect( - workspace.getActiveTextEditor().getCursorBufferPosition() - ).toEqual([2, 11]) - ); + expect( + workspace.getActiveTextEditor().getCursorBufferPosition() + ).toEqual([2, 11]) }); it('unfolds the fold containing the line', async () => { @@ -911,10 +741,10 @@ describe('Workspace', () => { describe('when the file size is over the limit defined in `core.warnOnLargeFileLimit`', () => { const shouldPromptForFileOfSize = async (size, shouldPrompt) => { - spyOn(fs, 'getSizeSync').andReturn(size * 1048577); + fsGetSizeSyncSpy.and.returnValue(size * 1048577); let selectedButtonIndex = 1; // cancel - atom.applicationDelegate.confirm.andCallFake((options, callback) => + atom.applicationDelegate.confirm.and.callFake((options, callback) => callback(selectedButtonIndex) ); @@ -923,10 +753,10 @@ describe('Workspace', () => { expect(editor).toBeUndefined(); expect(atom.applicationDelegate.confirm).toHaveBeenCalled(); - atom.applicationDelegate.confirm.reset(); + atom.applicationDelegate.confirm.calls.reset(); selectedButtonIndex = 0; // open the file - editor = await workspace.open('sample.js'); + await workspace.open('sample.js'); expect(atom.applicationDelegate.confirm).toHaveBeenCalled(); } else { @@ -951,7 +781,7 @@ describe('Workspace', () => { }); describe('when passed a path that matches a custom opener', () => { - it('returns the resource returned by the custom opener', () => { + it('returns the resource returned by the custom opener', async () => { const fooOpener = (pathToOpen, options) => { if (pathToOpen != null ? pathToOpen.match(/\.foo/) : undefined) { return { foo: pathToOpen, options }; @@ -965,70 +795,50 @@ describe('Workspace', () => { workspace.addOpener(fooOpener); workspace.addOpener(barOpener); - waitsForPromise(() => { - const pathToOpen = atom.project.getDirectories()[0].resolve('a.foo'); - return workspace.open(pathToOpen, { hey: 'there' }).then(item => - expect(item).toEqual({ - foo: pathToOpen, - options: { hey: 'there' } - }) - ); - }); + const pathToOpen = atom.project.getDirectories()[0].resolve('a.foo'); + expect(await workspace.open(pathToOpen, { hey: 'there' })).toEqual({ + foo: pathToOpen, + options: { hey: 'there' } + }) - waitsForPromise(() => - workspace - .open('bar://baz') - .then(item => expect(item).toEqual({ bar: 'bar://baz' })) - ); + expect(await workspace.open('bar://baz')).toEqual({ bar: 'bar://baz' }) }); }); - it("adds the file to the application's recent documents list", () => { - if (process.platform !== 'darwin') { - return; - } // Feature only supported on macOS + it("adds the file to the application's recent documents list", async (done) => { + jasmine.filterByPlatform({only: ['darwin']}, done); // Feature only supported on macOS + spyOn(atom.applicationDelegate, 'addRecentDocument'); - waitsForPromise(() => workspace.open()); + await workspace.open(); - runs(() => - expect( - atom.applicationDelegate.addRecentDocument - ).not.toHaveBeenCalled() - ); + expect( + atom.applicationDelegate.addRecentDocument + ).not.toHaveBeenCalled() - waitsForPromise(() => workspace.open('something://a/url')); + await workspace.open('something://a/url'); - runs(() => - expect( - atom.applicationDelegate.addRecentDocument - ).not.toHaveBeenCalled() - ); + expect( + atom.applicationDelegate.addRecentDocument + ).not.toHaveBeenCalled() - waitsForPromise(() => workspace.open(__filename)); + await workspace.open(__filename); - runs(() => - expect(atom.applicationDelegate.addRecentDocument).toHaveBeenCalledWith( - __filename - ) - ); + expect(atom.applicationDelegate.addRecentDocument).toHaveBeenCalledWith( + __filename + ) + + done(); }); - it('notifies ::onDidAddTextEditor observers', () => { + it('notifies ::onDidAddTextEditor observers', async () => { const absolutePath = require.resolve('./fixtures/dir/a'); const newEditorHandler = jasmine.createSpy('newEditorHandler'); workspace.onDidAddTextEditor(newEditorHandler); - let editor = null; - waitsForPromise(() => - workspace.open(absolutePath).then(e => { - editor = e; - }) - ); + let editor = await workspace.open(absolutePath); - runs(() => - expect(newEditorHandler.argsForCall[0][0].textEditor).toBe(editor) - ); + expect(newEditorHandler.calls.argsFor(0)[0].textEditor).toBe(editor); }); describe('when there is an error opening the file', () => { @@ -1040,187 +850,148 @@ describe('Workspace', () => { ); describe('when a file does not exist', () => { - it('creates an empty buffer for the specified path', () => { - waitsForPromise(() => workspace.open('not-a-file.md')); + it('creates an empty buffer for the specified path', async () => { + await workspace.open('not-a-file.md'); - runs(() => { - const editor = workspace.getActiveTextEditor(); - expect(notificationSpy).not.toHaveBeenCalled(); - expect(editor.getPath()).toContain('not-a-file.md'); - }); + const editor = workspace.getActiveTextEditor(); + expect(notificationSpy).not.toHaveBeenCalled(); + expect(editor.getPath()).toContain('not-a-file.md'); }); }); describe('when the user does not have access to the file', () => { - beforeEach(() => - spyOn(fs, 'openSync').andCallFake(path => { + beforeEach(() => { + fsOpenSyncSpy.and.callFake(path => { const error = new Error(`EACCES, permission denied '${path}'`); error.path = path; error.code = 'EACCES'; throw error; - }) - ); + }); + }); - it('creates a notification', () => { - waitsForPromise(() => workspace.open('file1')); + it('creates a notification', async () => { + await workspace.open('file1'); - runs(() => { - expect(notificationSpy).toHaveBeenCalled(); - const notification = notificationSpy.mostRecentCall.args[0]; - expect(notification.getType()).toBe('warning'); - expect(notification.getMessage()).toContain('Permission denied'); - expect(notification.getMessage()).toContain('file1'); - }); + expect(notificationSpy).toHaveBeenCalled(); + const notification = notificationSpy.calls.mostRecent().args[0]; + expect(notification.getType()).toBe('warning'); + expect(notification.getMessage()).toContain('Permission denied'); + expect(notification.getMessage()).toContain('file1'); }); }); describe('when the the operation is not permitted', () => { - beforeEach(() => - spyOn(fs, 'openSync').andCallFake(path => { + beforeEach(() => { + fsOpenSyncSpy.and.callFake(path => { const error = new Error(`EPERM, operation not permitted '${path}'`); error.path = path; error.code = 'EPERM'; throw error; - }) - ); + }); + }); - it('creates a notification', () => { - waitsForPromise(() => workspace.open('file1')); + it('creates a notification', async () => { + await workspace.open('file1'); - runs(() => { - expect(notificationSpy).toHaveBeenCalled(); - const notification = notificationSpy.mostRecentCall.args[0]; - expect(notification.getType()).toBe('warning'); - expect(notification.getMessage()).toContain('Unable to open'); - expect(notification.getMessage()).toContain('file1'); - }); + expect(notificationSpy).toHaveBeenCalled(); + const notification = notificationSpy.calls.mostRecent().args[0]; + expect(notification.getType()).toBe('warning'); + expect(notification.getMessage()).toContain('Unable to open'); + expect(notification.getMessage()).toContain('file1'); }); }); describe('when the the file is already open in windows', () => { - beforeEach(() => - spyOn(fs, 'openSync').andCallFake(path => { + beforeEach(() => { + fsOpenSyncSpy.and.callFake(path => { const error = new Error(`EBUSY, resource busy or locked '${path}'`); error.path = path; error.code = 'EBUSY'; throw error; }) - ); + }); - it('creates a notification', () => { - waitsForPromise(() => workspace.open('file1')); + it('creates a notification', async () => { + await workspace.open('file1'); - runs(() => { - expect(notificationSpy).toHaveBeenCalled(); - const notification = notificationSpy.mostRecentCall.args[0]; - expect(notification.getType()).toBe('warning'); - expect(notification.getMessage()).toContain('Unable to open'); - expect(notification.getMessage()).toContain('file1'); - }); + expect(notificationSpy).toHaveBeenCalled(); + const notification = notificationSpy.calls.mostRecent().args[0]; + expect(notification.getType()).toBe('warning'); + expect(notification.getMessage()).toContain('Unable to open'); + expect(notification.getMessage()).toContain('file1'); }); }); describe('when there is an unhandled error', () => { - beforeEach(() => - spyOn(fs, 'openSync').andCallFake(path => { + beforeEach(() => { + fsOpenSyncSpy.and.callFake(path => { throw new Error('I dont even know what is happening right now!!'); - }) - ); + }); + }); - it('rejects the promise', () => { - waitsFor(done => { - workspace.open('file1').catch(error => { - expect(error.message).toBe( - 'I dont even know what is happening right now!!' - ); - done(); - }); + it('rejects the promise', (done) => { + workspace.open('file1').catch(error => { + expect(error.message).toBe( + 'I dont even know what is happening right now!!' + ); + done(); }); }); }); }); describe('when the file is already open in pending state', () => { - it('should terminate the pending state', () => { + it('should terminate the pending state', async () => { let editor = null; let pane = null; - waitsForPromise(() => - atom.workspace.open('sample.js', { pending: true }).then(o => { - editor = o; - pane = atom.workspace.getActivePane(); - }) - ); + editor = await atom.workspace.open('sample.js', { pending: true }); + pane = atom.workspace.getActivePane(); - runs(() => expect(pane.getPendingItem()).toEqual(editor)); + expect(pane.getPendingItem()).toEqual(editor); - waitsForPromise(() => atom.workspace.open('sample.js')); + await atom.workspace.open('sample.js'); - runs(() => expect(pane.getPendingItem()).toBeNull()); + expect(pane.getPendingItem()).toBeNull(); }); }); describe('when opening will switch from a pending tab to a permanent tab', () => { - it('keeps the pending tab open', () => { + it('keeps the pending tab open', async () => { let editor1 = null; let editor2 = null; - waitsForPromise(() => - atom.workspace.open('sample.txt').then(o => { - editor1 = o; - }) - ); - - waitsForPromise(() => - atom.workspace.open('sample2.txt', { pending: true }).then(o => { - editor2 = o; - }) - ); + editor1 = await atom.workspace.open('sample.txt'); + editor2 = await atom.workspace.open('sample2.txt', { pending: true }); - runs(() => { - const pane = atom.workspace.getActivePane(); - pane.activateItem(editor1); - expect(pane.getItems().length).toBe(2); - expect(pane.getItems()).toEqual([editor1, editor2]); - }); + const pane = atom.workspace.getActivePane(); + pane.activateItem(editor1); + expect(pane.getItems().length).toBe(2); + expect(pane.getItems()).toEqual([editor1, editor2]); }); }); describe('when replacing a pending item which is the last item in a second pane', () => { - it('does not destroy the pane even if core.destroyEmptyPanes is on', () => { + it('does not destroy the pane even if core.destroyEmptyPanes is on', async () => { atom.config.set('core.destroyEmptyPanes', true); let editor1 = null; let editor2 = null; const leftPane = atom.workspace.getActivePane(); let rightPane = null; - waitsForPromise(() => - atom.workspace - .open('sample.js', { pending: true, split: 'right' }) - .then(o => { - editor1 = o; - rightPane = atom.workspace.getActivePane(); - spyOn(rightPane, 'destroy').andCallThrough(); - }) - ); + editor1 = await atom.workspace.open('sample.js', { pending: true, split: 'right' }); + rightPane = atom.workspace.getActivePane(); + spyOn(rightPane, 'destroy').and.callThrough(); - runs(() => { - expect(leftPane).not.toBe(rightPane); - expect(atom.workspace.getActivePane()).toBe(rightPane); - expect(atom.workspace.getActivePane().getItems().length).toBe(1); - expect(rightPane.getPendingItem()).toBe(editor1); - }); + expect(leftPane).not.toBe(rightPane); + expect(atom.workspace.getActivePane()).toBe(rightPane); + expect(atom.workspace.getActivePane().getItems().length).toBe(1); + expect(rightPane.getPendingItem()).toBe(editor1); - waitsForPromise(() => - atom.workspace.open('sample.txt', { pending: true }).then(o => { - editor2 = o; - }) - ); + editor2 = await atom.workspace.open('sample.txt', { pending: true }); - runs(() => { - expect(rightPane.getPendingItem()).toBe(editor2); - expect(rightPane.destroy.callCount).toBe(0); - }); + expect(rightPane.getPendingItem()).toBe(editor2); + expect(rightPane.destroy.calls.count()).toBe(0); }); }); @@ -1608,7 +1379,7 @@ describe('Workspace', () => { autoIndent: false }); expect(javascriptGrammarUsed).toHaveBeenCalled(); - expect(observeTextEditorsSpy.callCount).toBe(1); + expect(observeTextEditorsSpy.calls.count()).toBe(1); expect(coffeeScriptGrammarUsed).not.toHaveBeenCalled(); atom.grammars.assignLanguageMode(editor, 'source.coffee'); @@ -1644,7 +1415,7 @@ describe('Workspace', () => { autoIndent: false }); expect(javascriptGrammarUsed).toHaveBeenCalled(); - expect(observeTextEditorsSpy.callCount).toBe(1); + expect(observeTextEditorsSpy.calls.count()).toBe(1); expect(coffeeScriptGrammarUsed).not.toHaveBeenCalled(); atom.grammars.assignLanguageMode(editor, 'source.coffee'); @@ -1671,74 +1442,60 @@ describe('Workspace', () => { }); describe('::reopenItem()', () => { - it("opens the uri associated with the last closed pane that isn't currently open", () => { + it("opens the uri associated with the last closed pane that isn't currently open", async () => { const pane = workspace.getActivePane(); - waitsForPromise(() => - workspace - .open('a') - .then(() => - workspace - .open('b') - .then(() => workspace.open('file1').then(() => workspace.open())) - ) - ); - runs(() => { - // does not reopen items with no uri - expect(workspace.getActivePaneItem().getURI()).toBeUndefined(); - pane.destroyActiveItem(); - }); + await workspace.open('a'); + await workspace.open('b'); + await workspace.open('file1'); + await workspace.open(); + + // does not reopen items with no uri + expect(workspace.getActivePaneItem().getURI()).toBeUndefined(); + pane.destroyActiveItem(); - waitsForPromise(() => workspace.reopenItem()); + await workspace.reopenItem(); const firstDirectory = atom.project.getDirectories()[0]; expect(firstDirectory).toBeDefined(); - runs(() => { - expect(workspace.getActivePaneItem().getURI()).not.toBeUndefined(); + expect(workspace.getActivePaneItem().getURI()).not.toBeUndefined(); - // destroy all items - expect(workspace.getActivePaneItem().getURI()).toBe( - firstDirectory.resolve('file1') - ); - pane.destroyActiveItem(); - expect(workspace.getActivePaneItem().getURI()).toBe( - firstDirectory.resolve('b') - ); - pane.destroyActiveItem(); - expect(workspace.getActivePaneItem().getURI()).toBe( - firstDirectory.resolve('a') - ); - pane.destroyActiveItem(); + // destroy all items + expect(workspace.getActivePaneItem().getURI()).toBe( + firstDirectory.resolve('file1') + ); + pane.destroyActiveItem(); + expect(workspace.getActivePaneItem().getURI()).toBe( + firstDirectory.resolve('b') + ); + pane.destroyActiveItem(); + expect(workspace.getActivePaneItem().getURI()).toBe( + firstDirectory.resolve('a') + ); + pane.destroyActiveItem(); - // reopens items with uris - expect(workspace.getActivePaneItem()).toBeUndefined(); - }); + // reopens items with uris + expect(workspace.getActivePaneItem()).toBeUndefined(); - waitsForPromise(() => workspace.reopenItem()); + await workspace.reopenItem(); - runs(() => - expect(workspace.getActivePaneItem().getURI()).toBe( - firstDirectory.resolve('a') - ) - ); + expect(workspace.getActivePaneItem().getURI()).toBe( + firstDirectory.resolve('a') + ) // does not reopen items that are already open - waitsForPromise(() => workspace.open('b')); + await workspace.open('b'); - runs(() => - expect(workspace.getActivePaneItem().getURI()).toBe( - firstDirectory.resolve('b') - ) - ); + expect(workspace.getActivePaneItem().getURI()).toBe( + firstDirectory.resolve('b') + ) - waitsForPromise(() => workspace.reopenItem()); + await workspace.reopenItem(); - runs(() => - expect(workspace.getActivePaneItem().getURI()).toBe( - firstDirectory.resolve('file1') - ) - ); + expect(workspace.getActivePaneItem().getURI()).toBe( + firstDirectory.resolve('file1') + ) }); }); @@ -1801,11 +1558,9 @@ describe('Workspace', () => { }); describe('::openLicense()', () => { - it('opens the license as plain-text in a buffer', () => { - waitsForPromise(() => workspace.openLicense()); - runs(() => - expect(workspace.getActivePaneItem().getText()).toMatch(/Copyright/) - ); + it('opens the license as plain-text in a buffer', async () => { + await workspace.openLicense(); + expect(workspace.getActivePaneItem().getText()).toMatch(/Copyright/) }); }); @@ -1855,16 +1610,16 @@ describe('Workspace', () => { }); describe('::observeTextEditors()', () => { - it('invokes the observer with current and future text editors', () => { + it('invokes the observer with current and future text editors', async () => { const observed = []; - waitsForPromise(() => workspace.open()); - waitsForPromise(() => workspace.open()); - waitsForPromise(() => workspace.openLicense()); + await workspace.open(); + await workspace.open(); + await workspace.openLicense(); - runs(() => workspace.observeTextEditors(editor => observed.push(editor))); + workspace.observeTextEditors(editor => observed.push(editor)); - waitsForPromise(() => workspace.open()); + await workspace.open(); expect(observed).toEqual(workspace.getTextEditors()); }); @@ -1948,13 +1703,11 @@ describe('Workspace', () => { it('invokes the observer when closing the one and only text editor after deserialization', async () => { pane.activateItem(new TextEditor()); - simulateReload(); + await simulateReload(); - runs(() => { - workspace.onDidChangeActiveTextEditor(editor => observed.push(editor)); - workspace.closeActivePaneItemOrEmptyPaneOrWindow(); - expect(observed).toEqual([undefined]); - }); + workspace.onDidChangeActiveTextEditor(editor => observed.push(editor)); + workspace.closeActivePaneItemOrEmptyPaneOrWindow(); + expect(observed).toEqual([undefined]); }); }); @@ -2058,11 +1811,10 @@ describe('Workspace', () => { }); describe("when the active pane item's path is not inside a project path", () => { - beforeEach(() => - waitsForPromise(() => - atom.workspace.open('b').then(() => atom.project.setPaths([])) - ) - ); + beforeEach(async () => { + await atom.workspace.open('b'); + atom.project.setPaths([]); + }); it("sets the title to the pane item's title plus the item's path", () => { const item = atom.workspace.getActivePaneItem(); @@ -2128,7 +1880,9 @@ describe('Workspace', () => { }); describe('when the active pane item is inside a project path', () => { - beforeEach(() => waitsForPromise(() => atom.workspace.open('b'))); + beforeEach(async () => { + await atom.workspace.open('b'); + }); describe('when there is an active pane item', () => { it("sets the title to the pane item's title plus the project path", () => { @@ -2190,9 +1944,11 @@ describe('Workspace', () => { }); describe('when the workspace is deserialized', () => { - beforeEach(() => waitsForPromise(() => atom.workspace.open('a'))); + beforeEach(async () => { + await atom.workspace.open('a'); + }); - it("updates the title to contain the project's path", () => { + it("updates the title to contain the project's path", async () => { document.title = null; const atom2 = new AtomEnvironment({ @@ -2206,25 +1962,21 @@ describe('Workspace', () => { }) }); - waitsForPromise(() => - atom2.project.deserialize(atom.project.serialize()) - ); + await atom2.project.deserialize(atom.project.serialize()) - runs(() => { - atom2.workspace.deserialize( - atom.workspace.serialize(), - atom2.deserializers - ); - const item = atom2.workspace.getActivePaneItem(); - const pathEscaped = fs.tildify( - escapeStringRegex(atom.project.getPaths()[0]) - ); - expect(document.title).toMatch( - new RegExp(`^${item.getLongTitle()} \\u2014 ${pathEscaped}`) - ); + atom2.workspace.deserialize( + atom.workspace.serialize(), + atom2.deserializers + ); + const item = atom2.workspace.getActivePaneItem(); + const pathEscaped = fs.tildify( + escapeStringRegex(atom.project.getPaths()[0]) + ); + expect(document.title).toMatch( + new RegExp(`^${item.getLongTitle()} \\u2014 ${pathEscaped}`) + ); - atom2.destroy(); - }); + atom2.destroy(); }); }); }); @@ -2233,12 +1985,11 @@ describe('Workspace', () => { let item1; let item2; - beforeEach(() => { - waitsForPromise(() => atom.workspace.open('a')); - waitsForPromise(() => atom.workspace.open('b')); - runs(() => { - [item1, item2] = atom.workspace.getPaneItems(); - }); + beforeEach(async () => { + await atom.workspace.open('a'); + await atom.workspace.open('b'); + + [item1, item2] = atom.workspace.getPaneItems(); }); it('calls setDocumentEdited when the active item changes', () => { @@ -2585,7 +2336,7 @@ describe('Workspace', () => { }); }); - describe('newlines on regexps', async () => { + describe('newlines on regexps', () => { it('returns multiline results from regexps', async () => { const results = []; @@ -2725,7 +2476,7 @@ describe('Workspace', () => { }); }); }); - describe('pcre2 enabled', async () => { + describe('pcre2 enabled', () => { it('supports lookbehind searches', async () => { const results = []; @@ -3175,8 +2926,6 @@ describe('Workspace', () => { } } ); - - waitsFor(() => atom.workspace.directorySearchers.length > 0); }); it('can override the DefaultDirectorySearcher on a per-directory basis', async () => { @@ -3215,8 +2964,8 @@ describe('Workspace', () => { // onPathsSearched should be called once by each DirectorySearcher. The order is not // guaranteed, so we can only verify the total number of paths searched is correct // after the second call. - expect(onPathsSearched.callCount).toBe(2); - expect(onPathsSearched.mostRecentCall.args[0]).toBe( + expect(onPathsSearched.calls.count()).toBe(2); + expect(onPathsSearched.calls.mostRecent().args[0]).toBe( numPathsToPretendToSearchInCustomDirectorySearcher + numPathsSearchedInDir2 ); @@ -3226,24 +2975,16 @@ describe('Workspace', () => { const thenable = scan(/aaaa/, {}, () => {}); let resultOfPromiseSearch = null; - waitsFor('fakeSearch to be defined', () => fakeSearch != null); + expect(fakeSearch.cancelled).toBe(undefined); + thenable.cancel(); + expect(fakeSearch.cancelled).toBe(true); - runs(() => { - expect(fakeSearch.cancelled).toBe(undefined); - thenable.cancel(); - expect(fakeSearch.cancelled).toBe(true); - }); - - waitsForPromise(() => - thenable.then(promiseResult => { - resultOfPromiseSearch = promiseResult; - }) - ); + resultOfPromiseSearch = await thenable; - runs(() => expect(resultOfPromiseSearch).toBe('cancelled')); + expect(resultOfPromiseSearch).toBe('cancelled'); }); - it('will have the side-effect of failing the overall search if it fails', () => { + it('will have the side-effect of failing the overall search if it fails', async () => { // This provider's search should be cancelled when the first provider fails let cancelableSearch; let fakeSearch2 = null; @@ -3261,24 +3002,19 @@ describe('Workspace', () => { } ); - let didReject = false; - const promise = (cancelableSearch = scan(/aaaa/, () => {})); - waitsFor('fakeSearch to be defined', () => fakeSearch != null); + let cancelableSearchCatchSpy = jasmine.createSpy('cancelableSearch catch spy'); + cancelableSearch = scan(/aaaa/, () => {}); - runs(() => fakeSearch.hoistedReject()); + fakeSearch.hoistedReject(); - waitsForPromise(() => - cancelableSearch.catch(() => { - didReject = true; - }) - ); + await cancelableSearch.catch(cancelableSearchCatchSpy); - waitsFor(done => promise.then(null, done)); + await new Promise((resolve) => { + cancelableSearch.then(null, resolve); + }) - runs(() => { - expect(didReject).toBe(true); - expect(fakeSearch2.cancelled).toBe(true); - }); + expect(cancelableSearchCatchSpy).toHaveBeenCalled(); + expect(fakeSearch2.cancelled).toBe(true); }); }); }); @@ -3395,67 +3131,55 @@ describe('Workspace', () => { }); describe("when a file doesn't exist", () => { - it('calls back with an error', () => { + it('calls back with an error', async () => { const errors = []; const missingPath = path.resolve('/not-a-file.js'); expect(fs.existsSync(missingPath)).toBeFalsy(); - waitsForPromise(() => - atom.workspace.replace( - /items/gi, - 'items', - [missingPath], - (result, error) => errors.push(error) - ) + await atom.workspace.replace( + /items/gi, + 'items', + [missingPath], + (result, error) => errors.push(error) ); - runs(() => { - expect(errors).toHaveLength(1); - expect(errors[0].path).toBe(missingPath); - }); + expect(errors).toHaveLength(1); + expect(errors[0].path).toBe(missingPath); }); }); describe('when called with unopened files', () => { - it('replaces properly', () => { + it('replaces properly', async () => { const filePath = path.join(projectDir, 'sample.js'); fs.copyFileSync(path.join(fixturesDir, 'sample.js'), filePath); const results = []; - waitsForPromise(() => - atom.workspace.replace(/items/gi, 'items', [filePath], result => - results.push(result) - ) - ); - - runs(() => { - expect(results).toHaveLength(1); - expect(results[0].filePath).toBe(filePath); - expect(results[0].replacements).toBe(6); + await atom.workspace.replace(/items/gi, 'items', [filePath], (result) => { + results.push(result) }); + + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].replacements).toBe(6); }); - it('does not discard the multiline flag', () => { + it('does not discard the multiline flag', async () => { const filePath = path.join(projectDir, 'sample.js'); fs.copyFileSync(path.join(fixturesDir, 'sample.js'), filePath); const results = []; - waitsForPromise(() => - atom.workspace.replace(/;$/gim, 'items', [filePath], result => - results.push(result) - ) - ); - - runs(() => { - expect(results).toHaveLength(1); - expect(results[0].filePath).toBe(filePath); - expect(results[0].replacements).toBe(8); + await atom.workspace.replace(/;$/gim, 'items', [filePath], result => { + results.push(result) }); + + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].replacements).toBe(8); }); }); describe('when a buffer is already open', () => { - it('replaces properly and saves when not modified', () => { + it('replaces properly and saves when not modified', async () => { const filePath = path.join(projectDir, 'sample.js'); fs.copyFileSync( path.join(fixturesDir, 'sample.js'), @@ -3465,30 +3189,22 @@ describe('Workspace', () => { let editor = null; const results = []; - waitsForPromise(() => - atom.workspace.open('sample.js').then(o => { - editor = o; - }) - ); + editor = await atom.workspace.open('sample.js'); - runs(() => expect(editor.isModified()).toBeFalsy()); + expect(editor.isModified()).toBeFalsy(); - waitsForPromise(() => - atom.workspace.replace(/items/gi, 'items', [filePath], result => - results.push(result) - ) - ); + await atom.workspace.replace(/items/gi, 'items', [filePath], result => { + results.push(result) + }); - runs(() => { - expect(results).toHaveLength(1); - expect(results[0].filePath).toBe(filePath); - expect(results[0].replacements).toBe(6); + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].replacements).toBe(6); - expect(editor.isModified()).toBeFalsy(); - }); + expect(editor.isModified()).toBeFalsy(); }); - it('does not replace when the path is not specified', () => { + it('does not replace when the path is not specified', async () => { const filePath = path.join(projectDir, 'sample.js'); const commentFilePath = path.join( projectDir, @@ -3501,54 +3217,40 @@ describe('Workspace', () => { ); const results = []; - waitsForPromise(() => atom.workspace.open('sample-with-comments.js')); + await atom.workspace.open('sample-with-comments.js'); - waitsForPromise(() => - atom.workspace.replace( - /items/gi, - 'items', - [commentFilePath], - result => results.push(result) - ) + await atom.workspace.replace( + /items/gi, + 'items', + [commentFilePath], + result => results.push(result) ); - runs(() => { - expect(results).toHaveLength(1); - expect(results[0].filePath).toBe(commentFilePath); - }); + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(commentFilePath); }); - it('does NOT save when modified', () => { + it('does NOT save when modified', async () => { const filePath = path.join(projectDir, 'sample.js'); fs.copyFileSync(path.join(fixturesDir, 'sample.js'), filePath); let editor = null; const results = []; - waitsForPromise(() => - atom.workspace.open('sample.js').then(o => { - editor = o; - }) - ); + editor = await atom.workspace.open('sample.js'); - runs(() => { - editor.buffer.setTextInRange([[0, 0], [0, 0]], 'omg'); - expect(editor.isModified()).toBeTruthy(); - }); + editor.buffer.setTextInRange([[0, 0], [0, 0]], 'omg'); + expect(editor.isModified()).toBeTruthy(); - waitsForPromise(() => - atom.workspace.replace(/items/gi, 'okthen', [filePath], result => - results.push(result) - ) - ); + await atom.workspace.replace(/items/gi, 'okthen', [filePath], result => { + results.push(result) + }); - runs(() => { - expect(results).toHaveLength(1); - expect(results[0].filePath).toBe(filePath); - expect(results[0].replacements).toBe(6); + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].replacements).toBe(6); - expect(editor.isModified()).toBeTruthy(); - }); + expect(editor.isModified()).toBeTruthy(); }); }); }); @@ -3556,56 +3258,48 @@ describe('Workspace', () => { describe('::saveActivePaneItem()', () => { let editor, notificationSpy; - beforeEach(() => { - waitsForPromise(() => - atom.workspace.open('sample.js').then(o => { - editor = o; - }) - ); + beforeEach(async () => { + editor = await atom.workspace.open('sample.js'); notificationSpy = jasmine.createSpy('did-add-notification'); atom.notifications.onDidAddNotification(notificationSpy); }); describe('when there is an error', () => { - it('emits a warning notification when the file cannot be saved', () => { - spyOn(editor, 'save').andCallFake(() => { + it('emits a warning notification when the file cannot be saved', async () => { + spyOn(editor, 'save').and.callFake(() => { throw new Error("'/some/file' is a directory"); }); - waitsForPromise(() => - atom.workspace.saveActivePaneItem().then(() => { - expect(notificationSpy).toHaveBeenCalled(); - expect(notificationSpy.mostRecentCall.args[0].getType()).toBe( - 'warning' - ); - expect( - notificationSpy.mostRecentCall.args[0].getMessage() - ).toContain('Unable to save'); - }) + await atom.workspace.saveActivePaneItem(); + + expect(notificationSpy).toHaveBeenCalled(); + expect(notificationSpy.calls.mostRecent().args[0].getType()).toBe( + 'warning' ); + expect( + notificationSpy.calls.mostRecent().args[0].getMessage() + ).toContain('Unable to save'); }); - it('emits a warning notification when the directory cannot be written to', () => { - spyOn(editor, 'save').andCallFake(() => { + it('emits a warning notification when the directory cannot be written to', async () => { + spyOn(editor, 'save').and.callFake(() => { throw new Error("ENOTDIR, not a directory '/Some/dir/and-a-file.js'"); }); - waitsForPromise(() => - atom.workspace.saveActivePaneItem().then(() => { - expect(notificationSpy).toHaveBeenCalled(); - expect(notificationSpy.mostRecentCall.args[0].getType()).toBe( - 'warning' - ); - expect( - notificationSpy.mostRecentCall.args[0].getMessage() - ).toContain('Unable to save'); - }) + await atom.workspace.saveActivePaneItem(); + + expect(notificationSpy).toHaveBeenCalled(); + expect(notificationSpy.calls.mostRecent().args[0].getType()).toBe( + 'warning' ); + expect( + notificationSpy.calls.mostRecent().args[0].getMessage() + ).toContain('Unable to save'); }); - it('emits a warning notification when the user does not have permission', () => { - spyOn(editor, 'save').andCallFake(() => { + it('emits a warning notification when the user does not have permission', async () => { + spyOn(editor, 'save').and.callFake(() => { const error = new Error( "EACCES, permission denied '/Some/dir/and-a-file.js'" ); @@ -3614,21 +3308,19 @@ describe('Workspace', () => { throw error; }); - waitsForPromise(() => - atom.workspace.saveActivePaneItem().then(() => { - expect(notificationSpy).toHaveBeenCalled(); - expect(notificationSpy.mostRecentCall.args[0].getType()).toBe( - 'warning' - ); - expect( - notificationSpy.mostRecentCall.args[0].getMessage() - ).toContain('Unable to save'); - }) + await atom.workspace.saveActivePaneItem() + + expect(notificationSpy).toHaveBeenCalled(); + expect(notificationSpy.calls.mostRecent().args[0].getType()).toBe( + 'warning' ); + expect( + notificationSpy.calls.mostRecent().args[0].getMessage() + ).toContain('Unable to save'); }); - it('emits a warning notification when the operation is not permitted', () => { - spyOn(editor, 'save').andCallFake(() => { + it('emits a warning notification when the operation is not permitted', async () => { + spyOn(editor, 'save').and.callFake(() => { const error = new Error( "EPERM, operation not permitted '/Some/dir/and-a-file.js'" ); @@ -3637,21 +3329,19 @@ describe('Workspace', () => { throw error; }); - waitsForPromise(() => - atom.workspace.saveActivePaneItem().then(() => { - expect(notificationSpy).toHaveBeenCalled(); - expect(notificationSpy.mostRecentCall.args[0].getType()).toBe( - 'warning' - ); - expect( - notificationSpy.mostRecentCall.args[0].getMessage() - ).toContain('Unable to save'); - }) + await atom.workspace.saveActivePaneItem() + + expect(notificationSpy).toHaveBeenCalled(); + expect(notificationSpy.calls.mostRecent().args[0].getType()).toBe( + 'warning' ); + expect( + notificationSpy.calls.mostRecent().args[0].getMessage() + ).toContain('Unable to save'); }); - it('emits a warning notification when the file is already open by another app', () => { - spyOn(editor, 'save').andCallFake(() => { + it('emits a warning notification when the file is already open by another app', async () => { + spyOn(editor, 'save').and.callFake(() => { const error = new Error( "EBUSY, resource busy or locked '/Some/dir/and-a-file.js'" ); @@ -3660,21 +3350,19 @@ describe('Workspace', () => { throw error; }); - waitsForPromise(() => - atom.workspace.saveActivePaneItem().then(() => { - expect(notificationSpy).toHaveBeenCalled(); - expect(notificationSpy.mostRecentCall.args[0].getType()).toBe( - 'warning' - ); - expect( - notificationSpy.mostRecentCall.args[0].getMessage() - ).toContain('Unable to save'); - }) + await atom.workspace.saveActivePaneItem() + + expect(notificationSpy).toHaveBeenCalled(); + expect(notificationSpy.calls.mostRecent().args[0].getType()).toBe( + 'warning' ); + expect( + notificationSpy.calls.mostRecent().args[0].getMessage() + ).toContain('Unable to save'); }); - it('emits a warning notification when the file system is read-only', () => { - spyOn(editor, 'save').andCallFake(() => { + it('emits a warning notification when the file system is read-only', async () => { + spyOn(editor, 'save').and.callFake(() => { const error = new Error( "EROFS, read-only file system '/Some/dir/and-a-file.js'" ); @@ -3683,35 +3371,34 @@ describe('Workspace', () => { throw error; }); - waitsForPromise(() => - atom.workspace.saveActivePaneItem().then(() => { - expect(notificationSpy).toHaveBeenCalled(); - expect(notificationSpy.mostRecentCall.args[0].getType()).toBe( - 'warning' - ); - expect( - notificationSpy.mostRecentCall.args[0].getMessage() - ).toContain('Unable to save'); - }) + await atom.workspace.saveActivePaneItem() + + expect(notificationSpy).toHaveBeenCalled(); + expect(notificationSpy.calls.mostRecent().args[0].getType()).toBe( + 'warning' ); + expect( + notificationSpy.calls.mostRecent().args[0].getMessage() + ).toContain('Unable to save'); }); - it('emits a warning notification when the file cannot be saved', () => { - spyOn(editor, 'save').andCallFake(() => { + it('emits a warning notification when the file cannot be saved', async () => { + spyOn(editor, 'save').and.callFake(() => { throw new Error('no one knows'); }); - waitsForPromise({ shouldReject: true }, () => - atom.workspace.saveActivePaneItem() - ); + const catchSpy = jasmine.createSpy(); + await atom.workspace.saveActivePaneItem().catch(catchSpy) + + expect(catchSpy).toHaveBeenCalled(); }); }); }); describe('::closeActivePaneItemOrEmptyPaneOrWindow', () => { - beforeEach(() => { + beforeEach(async () => { spyOn(atom, 'close'); - waitsForPromise(() => atom.workspace.open()); + await atom.workspace.open(); }); it('closes the active center pane item, or the active center pane if it is empty, or the current window if there is only the empty root pane in the center', async () => { @@ -3888,17 +3575,14 @@ describe('Workspace', () => { }); describe('when the core.allowPendingPaneItems option is falsy', () => { - it('does not open item with `pending: true` option as pending', () => { + it('does not open item with `pending: true` option as pending', async () => { let pane = null; atom.config.set('core.allowPendingPaneItems', false); - waitsForPromise(() => - atom.workspace.open('sample.js', { pending: true }).then(() => { - pane = atom.workspace.getActivePane(); - }) - ); + await atom.workspace.open('sample.js', {pending: true}) + pane = atom.workspace.getActivePane(); - runs(() => expect(pane.getPendingItem()).toBeFalsy()); + expect(pane.getPendingItem()).toBeFalsy(); }); }); @@ -3968,7 +3652,7 @@ describe('Workspace', () => { }); describe("when there's no repository for the editor's file", () => { - it("doesn't do anything", async () => { + it("doesn't do anything", () => { editor = new TextEditor(); editor.setText('stuff'); atom.workspace.checkoutHeadRevision(editor); diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index 93eae815b1..16daacc03d 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -1750,37 +1750,60 @@ module.exports = class AtomApplication extends EventEmitter { } resolveTestRunnerPath(testPath) { - let packageRoot; - if (FindParentDir == null) { - FindParentDir = require('find-parent-dir'); + FindParentDir ||= require('find-parent-dir'); + + let packageRoot = FindParentDir.sync(testPath, 'package.json'); + + if (!packageRoot) { + process.stderr.write('Error: Could not find root directory'); + process.exit(1); } - if ((packageRoot = FindParentDir.sync(testPath, 'package.json'))) { - const packageMetadata = require(path.join(packageRoot, 'package.json')); - if (packageMetadata.atomTestRunner) { - let testRunnerPath; - if (Resolve == null) { - Resolve = require('resolve'); - } - if ( - (testRunnerPath = Resolve.sync(packageMetadata.atomTestRunner, { - basedir: packageRoot, - extensions: Object.keys(require.extensions) - })) - ) { - return testRunnerPath; - } else { - process.stderr.write( - `Error: Could not resolve test runner path '${ - packageMetadata.atomTestRunner - }'` - ); - process.exit(1); - } + const packageMetadata = require(path.join(packageRoot, 'package.json')); + let atomTestRunner = packageMetadata.atomTestRunner; + + if (!atomTestRunner) { + process.stdout.write('atomTestRunner was not defined, using the deprecated runners/jasmine1-test-runner.'); + atomTestRunner = 'runners/jasmine1-test-runner'; + } + + let testRunnerPath; + Resolve ||= require('resolve'); + + // First try to run with local runners (e.g: `./test/runner.js`) or packages (e.g.: `atom-mocha-test-runner`) + try { + testRunnerPath = Resolve.sync(atomTestRunner, { + basedir: packageRoot, + extensions: Object.keys(require.extensions) + }); + + if (testRunnerPath) { + return testRunnerPath; + } + } catch { + // Nothing to do, try the next strategy + } + + // Then try to use one of the runners defined in Pulsar + try { + testRunnerPath = Resolve.sync(`./spec/${atomTestRunner}`, { + basedir: this.devResourcePath, + extensions: Object.keys(require.extensions) + }); + + if (testRunnerPath) { + return testRunnerPath; } + } catch { + // Nothing to do, try the next strategy } - return this.resolveLegacyTestRunnerPath(); + process.stderr.write( + `Error: Could not resolve test runner path '${ + packageMetadata.atomTestRunner + }'` + ); + process.exit(1); } resolveLegacyTestRunnerPath() { diff --git a/src/main-process/atom-window.js b/src/main-process/atom-window.js index f5e242da51..bdf6ad83ab 100644 --- a/src/main-process/atom-window.js +++ b/src/main-process/atom-window.js @@ -60,7 +60,6 @@ module.exports = class AtomWindow extends EventEmitter { disableBlinkFeatures: 'Auxclick', nodeIntegration: true, contextIsolation: false, - enableRemoteModule: true, webviewTag: true, // TodoElectronIssue: remote module is deprecated https://www.electronjs.org/docs/breaking-changes#default-changed-enableremotemodule-defaults-to-false diff --git a/vendor/jasmine.js b/vendor/jasmine.js index d69b33d2d8..43c68d77f5 100644 --- a/vendor/jasmine.js +++ b/vendor/jasmine.js @@ -2705,142 +2705,3 @@ jasmine.Matchers.prototype.toSatisfy = function(fn) { } return fn(this.actual, msgFun) }; - -// This will normalize the comments for the special format of grammar tests -// that TextMate and Tree-Sitter do -// -// Basically, receiving a text editor and the regex that probably defines -// what a comment is, it'll return an object with `expect` - that is what was -// expected to pass the test, like a scope description for example, and two -// Point-compatible fields - `editorPosition`, that is basically in what -// position of the editor `expect` should be satisfied, and `testPosition`, that -// is where in file the test actually happened. This makes it easier for us -// to construct an error showing where EXACTLY was the assertion that failed -function normalizeTreeSitterTextData(editor, commentRegex) { - let allMatches = [], lastNonComment = 0 - const checkAssert = new RegExp('^\\s*' + commentRegex.source + '\\s*[\\<\\-|\\^]') - editor.getBuffer().getLines().forEach((row, i) => { - const m = row.match(commentRegex) - if(m) { - // const scope = editor.scopeDescriptorForBufferPosition([i, m.index]) - // FIXME: use editor.scopeDescriptorForBufferPosition when it works - const scope = editor.tokensForScreenRow(i) - const scopes = scope.flatMap(e => e.scopes) - if(scopes.find(s => s.match(/comment/)) && row.match(checkAssert)) { - allMatches.push({row: lastNonComment, text: row, col: m.index, testRow: i}) - return - } - } - lastNonComment = i - }) - return allMatches.map(({text, row, col, testRow}) => { - const exactPos = text.match(/\^\s+(.*)/) - if(exactPos) { - const expected = exactPos[1] - return { - expected, - editorPosition: {row, column: exactPos.index}, - testPosition: {row: testRow, column: col} - } - } else { - const pos = text.match(/\<-\s+(.*)/) - if(!pos) throw new Error(`Can't match ${text}`) - return { - expected: pos[1], - editorPosition: {row, column: col}, - testPosition: {row: testRow, column: col} - } - } - }) -} -if (isCommonJS) exports.normalizeTreeSitterTextData = normalizeTreeSitterTextData; - -async function openDocument(fullPath) { - const editor = await atom.workspace.open(fullPath); - await editor.languageMode.ready; - return editor; -} - -async function runGrammarTests(fullPath, commentRegex) { - const editor = await openDocument(fullPath); - - const normalized = normalizeTreeSitterTextData(editor, commentRegex) - expect(normalized.length).toSatisfy((n, reason) => { - reason("Tokenizer didn't run correctly - could not find any comment") - return n > 0 - }) - normalized.forEach(({expected, editorPosition, testPosition}) => { - expect(editor.scopeDescriptorForBufferPosition(editorPosition).scopes).toSatisfy((scopes, reason) => { - const dontFindScope = expected.startsWith("!"); - expected = expected.replace(/^!/, "") - if(dontFindScope) { - reason(`Expected to NOT find scope "${expected}" but found it\n` + - ` at ${fullPath}:${testPosition.row+1}:${testPosition.column+1}` - ); - } else { - reason(`Expected to find scope "${expected}" but found "${scopes}"\n` + - ` at ${fullPath}:${testPosition.row+1}:${testPosition.column+1}` - ); - } - const normalized = expected.replace(/([\.\-])/g, '\\$1'); - const scopeRegex = new RegExp('^' + normalized + '(\\..+)?$'); - let result = scopes.find(e => e.match(scopeRegex)) !== undefined; - if(dontFindScope) result = !result; - return result - }) - }) -} -if (isCommonJS) exports.runGrammarTests = runGrammarTests; - -async function runFoldsTests(fullPath, commentRegex) { - const editor = await openDocument(fullPath); - let grouped = {} - const normalized = normalizeTreeSitterTextData(editor, commentRegex).forEach(test => { - const [kind, id] = test.expected.split('.') - if(!kind || !id) { - throw new Error(`Folds must be in the format fold_end.some-id\n` + - ` at ${test.testPosition.row+1}:${test.testPosition.column+1}`) - } - grouped[id] ||= {} - grouped[id][kind] = test - }) - for(const k in grouped) { - const v = grouped[k] - const keys = Object.keys(v) - if(keys.indexOf('fold_begin') === -1) - throw new Error(`Fold ${k} must contain fold_begin`) - if(keys.indexOf('fold_end') === -1) - throw new Error(`Fold ${k} must contain fold_end`) - if(keys.indexOf('fold_new_position') === -1) - throw new Error(`Fold ${k} must contain fold_new_position`) - } - - for(const k in grouped) { - const fold = grouped[k] - const begin = fold['fold_begin'] - const end = fold['fold_end'] - const newPos = fold['fold_new_position'] - - expect(editor.isFoldableAtBufferRow(begin.editorPosition.row)) - .toSatisfy((foldable, reason) => { - reason(`Editor is not foldable at row ${begin.editorPosition.row+1}\n` + - ` at ${fullPath}:${begin.testPosition.row+1}:${begin.testPosition.column+1}`) - return foldable - }) - editor.foldBufferRow(begin.editorPosition.row) - - expect(editor.screenPositionForBufferPosition(end.editorPosition)) - .toSatisfy((screenPosition, reason) => { - const {row,column} = newPos.editorPosition - reason(`At row ${begin.editorPosition.row+1}, editor should fold ` + - `up to the ${end.editorPosition.row+1}:${end.editorPosition.column+1}\n` + - ` into the new position ${row+1}:${column+1}\n`+ - ` but folded to position ${screenPosition.row+1}:${screenPosition.column+1}\n`+ - ` at ${fullPath}:${newPos.testPosition.row+1}:${newPos.testPosition.column+1}\n` + - ` at ${fullPath}:${end.testPosition.row+1}:${end.testPosition.column+1}`) - return row === screenPosition.row && column === screenPosition.column - }) - editor.unfoldAll() - } -} -if (isCommonJS) exports.runFoldsTests = runFoldsTests; diff --git a/yarn.lock b/yarn.lock index 4505c010e9..d74a5ae956 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4380,6 +4380,11 @@ event-stream@~3.1.0: stack-trace "0.0.9" underscore-plus "^1.7.0" +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + expand-template@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" @@ -4986,7 +4991,7 @@ glob@7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: +glob@^7.0.0, glob@^7.0.6, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -5804,6 +5809,11 @@ jake@^10.8.5: filelist "^1.0.1" minimatch "^3.0.4" +jasmine-core@~2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.5.2.tgz#6f61bd79061e27f43e6f9355e44b3c6cab6ff297" + integrity sha512-kpf1e8MlD9rnurToeGLjfmsSSAupkgZM+ky9BmXFKbFnr/MgNnLokfGkrZg+dqtbsQF7hv/1s67vFVLS3iOVxA== + jasmine-focused@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/jasmine-focused/-/jasmine-focused-1.0.7.tgz#b83c757c800e68e1d6efc1a3a1a13ff39ff6dcd2" @@ -5831,13 +5841,6 @@ jasmine-json@~0.0: underscore ">= 1.3.1" walkdir ">= 0.0.1" -jasmine-reporters@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/jasmine-reporters/-/jasmine-reporters-1.1.0.tgz#f3350885890c9edaad12a087c62f2cc19dcf66c0" - integrity sha512-y0sNPC0/emtTk9eDCXp57JqOAEfBkOCSF/p+d1Zd4dv2tLdfmvAm2PtEUpI/j1Y5qYsxnoWO1M3VZ4YoZIPoTg== - dependencies: - mkdirp "~0.3.5" - jasmine-reporters@>=0.2.0: version "2.5.2" resolved "https://registry.yarnpkg.com/jasmine-reporters/-/jasmine-reporters-2.5.2.tgz#b5dfa1d9c40b8020c5225e0e1e2b9953d66a4d69" @@ -5853,6 +5856,15 @@ jasmine-tagged@^1.1.4: dependencies: jasmine-focused "^1.0.7" +jasmine@2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.5.3.tgz#5441f254e1fc2269deb1dfd93e0e57d565ff4d22" + integrity sha512-gJeubNIl+BgKUQ/YgTX3yH4iNGa1HicbZl81v5VSZSInjj8LhkzuwTd3CjiDUxQL09f02sR3BlmQQcfrT6qTSw== + dependencies: + exit "^0.1.2" + glob "^7.0.6" + jasmine-core "~2.5.2" + js-sdsl@^4.1.4: version "4.1.5" resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.1.5.tgz#1ff1645e6b4d1b028cd3f862db88c9d887f26e2a"