diff --git a/spec/atom-environment-spec.js b/spec/atom-environment-spec.js index b6b076dd6b..2f765be93e 100644 --- a/spec/atom-environment-spec.js +++ b/spec/atom-environment-spec.js @@ -27,13 +27,20 @@ describe('AtomEnvironment', () => { describe('::getSize and ::setSize', () => { let originalSize = null; beforeEach(() => (originalSize = atom.getSize())); - afterEach(() => atom.setSize(originalSize.width, originalSize.height)); - it('sets the size of the window, and can retrieve the size just set', async () => { + afterEach(async (done) => { + await atom.setSize(originalSize.width, originalSize.height); + + done(); + }); + + it('sets the size of the window, and can retrieve the size just set', async (done) => { const newWidth = originalSize.width - 12; const newHeight = originalSize.height - 23; await atom.setSize(newWidth, newHeight); expect(atom.getSize()).toEqual({ width: newWidth, height: newHeight }); + + done(); }); }); }); @@ -41,7 +48,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 +58,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,11 +77,11 @@ describe('AtomEnvironment', () => { let devToolsPromise = null; beforeEach(() => { devToolsPromise = Promise.resolve(); - spyOn(atom, 'openDevTools').andReturn(devToolsPromise); + spyOn(atom, 'openDevTools').and.returnValue(devToolsPromise); spyOn(atom, 'executeJavaScriptInDevTools'); }); - it('will open the dev tools when an error is triggered', async () => { + it('will open the dev tools when an error is triggered', async (done) => { try { a + 1; // eslint-disable-line no-undef, no-unused-expressions } catch (e) { @@ -84,6 +91,8 @@ describe('AtomEnvironment', () => { await devToolsPromise; expect(atom.openDevTools).toHaveBeenCalled(); expect(atom.executeJavaScriptInDevTools).toHaveBeenCalled(); + + done(); }); describe('::onWillThrowError', () => { @@ -103,7 +112,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 +123,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 +167,7 @@ describe('AtomEnvironment', () => { beforeEach(() => { errors = []; - spyOn(atom, 'isReleasedVersion').andReturn(true); + spyOn(atom, 'isReleasedVersion').and.returnValue(true); atom.onDidFailAssertion(error => errors.push(error)); }); @@ -188,8 +197,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' ); }); @@ -210,7 +219,7 @@ describe('AtomEnvironment', () => { afterEach(() => (atom.enablePersistence = false)); - it('selects the state based on the current project paths', async () => { + it('selects the state based on the current project paths', async (done) => { jasmine.useRealClock(); const [dir1, dir2] = [temp.mkdirSync('dir1-'), temp.mkdirSync('dir2-')]; @@ -220,8 +229,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]); @@ -233,6 +242,8 @@ describe('AtomEnvironment', () => { loadSettings.initialProjectRoots = [dir2, dir1]; expect(await atom.loadState()).toEqual({ stuff: 'cool' }); + + done(); }); it('saves state when the CPU is idle after a keydown or mousedown event', () => { @@ -260,7 +271,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); @@ -271,7 +282,7 @@ describe('AtomEnvironment', () => { atomEnv.destroy(); }); - it('ignores mousedown/keydown events happening after calling prepareToUnloadEditorWindow', async () => { + it('ignores mousedown/keydown events happening after calling prepareToUnloadEditorWindow', async (done) => { const atomEnv = new AtomEnvironment({ applicationDelegate: global.atom.applicationDelegate }); @@ -297,28 +308,32 @@ 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(); + + done(); }); - it('serializes the project state with all the options supplied in saveState', async () => { - spyOn(atom.project, 'serialize').andReturn({ foo: 42 }); + it('serializes the project state with all the options supplied in saveState', async (done) => { + 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' }); + + done(); }); - it('serializes the text editor registry', async () => { + it('serializes the text editor registry', async (done) => { await atom.packages.activatePackage('language-text'); const editor = await atom.workspace.open('sample.js'); expect(atom.grammars.assignLanguageMode(editor, 'text.plain')).toBe(true); @@ -343,12 +358,14 @@ describe('AtomEnvironment', () => { .getLanguageId() ).toBe('text.plain'); atom2.destroy(); + + done(); }); describe('deserialization failures', () => { - it('propagates unrecognized project state restoration failures', async () => { + it('propagates unrecognized project state restoration failures', async (done) => { let err; - spyOn(atom.project, 'deserialize').andCallFake(() => { + spyOn(atom.project, 'deserialize').and.callFake(() => { err = new Error('deserialization failure'); return Promise.reject(err); }); @@ -362,10 +379,12 @@ describe('AtomEnvironment', () => { stack: err.stack } ); + + done(); }); - it('disregards missing project folder errors', async () => { - spyOn(atom.project, 'deserialize').andCallFake(() => { + it('disregards missing project folder errors', async (done) => { + spyOn(atom.project, 'deserialize').and.callFake(() => { const err = new Error('deserialization failure'); err.missingProjectPaths = ['nah']; return Promise.reject(err); @@ -374,6 +393,8 @@ describe('AtomEnvironment', () => { await atom.deserialize({ project: 'should work' }); expect(atom.notifications.addError).not.toHaveBeenCalled(); + + done(); }); }); }); @@ -381,7 +402,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', () => { @@ -392,14 +413,16 @@ describe('AtomEnvironment', () => { }); }); - it('does not open an empty buffer when a buffer is already open', async () => { + it('does not open an empty buffer when a buffer is already open', async (done) => { await atom.workspace.open(); spyOn(atom.workspace, 'open'); atom.openInitialEmptyEditorIfNecessary(); expect(atom.workspace.open).not.toHaveBeenCalled(); + + done(); }); - 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 +432,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,26 +446,28 @@ 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 () => { + it('adds the selected folder to the project', async (done) => { 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(); expect(atom.project.getPaths()).toEqual([tempDirectory]); expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled(); + + done(); }); }); @@ -450,25 +475,25 @@ 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([]); }); describe('when there are no project folders', () => { - it('attempts to restore the project state', async () => { + it('attempts to restore the project state', async (done) => { await atom.addProjectFolder(); expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith( state, [__dirname] ); expect(atom.project.getPaths()).toEqual([]); + + done(); }); }); @@ -477,12 +502,14 @@ describe('AtomEnvironment', () => { beforeEach(() => atom.project.setPaths([openedPath])); - it('does not attempt to restore the project state, instead adding the project paths', async () => { + it('does not attempt to restore the project state, instead adding the project paths', async (done) => { await atom.addProjectFolder(); expect( atom.attemptRestoreProjectStateForPaths ).not.toHaveBeenCalled(); expect(atom.project.getPaths()).toEqual([openedPath, __dirname]); + + done(); }); }); }); @@ -490,12 +517,14 @@ describe('AtomEnvironment', () => { describe('attemptRestoreProjectStateForPaths(state, projectPaths, filesToOpen)', () => { describe('when the window is clean (empty or has only unnamed, unmodified buffers)', () => { - beforeEach(async () => { + beforeEach(async (done) => { // Unnamed, unmodified buffer doesn't count toward "clean"-ness await atom.workspace.open(); + + done(); }); - it('automatically restores the saved state into the current environment', async () => { + it('automatically restores the saved state into the current environment', async (done) => { const projectPath = temp.mkdirSync(); const filePath1 = path.join(projectPath, 'file-1'); const filePath2 = path.join(projectPath, 'file-2'); @@ -525,6 +554,8 @@ describe('AtomEnvironment', () => { const restoredURIs = env2.workspace.getPaneItems().map(p => p.getURI()); expect(restoredURIs).toEqual([filePath1, filePath2, filePath3]); env2.destroy(); + + done(); }); describe('when a dock has a non-text editor', () => { @@ -551,16 +582,18 @@ describe('AtomEnvironment', () => { describe('when the window is dirty', () => { let editor; - beforeEach(async () => { + beforeEach(async (done) => { editor = await atom.workspace.open(); editor.setText('new editor'); + + done(); }); describe('when a dock has a modified editor', () => { 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'); @@ -573,9 +606,9 @@ describe('AtomEnvironment', () => { }); }); - it('prompts the user to restore the state in a new window, discarding it and adding folder to current window', async () => { + it('prompts the user to restore the state in a new window, discarding it and adding folder to current window', async (done) => { 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 +619,18 @@ 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); + + done(); }); - it('prompts the user to restore the state in a new window, opening a new window', async () => { + it('prompts the user to restore the state in a new window, opening a new window', async (done) => { 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,13 +640,15 @@ 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, devMode: atom.inDevMode(), safeMode: atom.inSafeMode() }); + + done(); }); }); }); @@ -640,7 +677,7 @@ describe('AtomEnvironment', () => { }); describe('::destroy()', () => { - it('does not throw exceptions when unsubscribing from ipc events (regression)', async () => { + it('does not throw exceptions when unsubscribing from ipc events (regression)', async (done) => { const fakeDocument = { addEventListener() {}, removeEventListener() {}, @@ -651,14 +688,16 @@ 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(); + + done(); }); }); @@ -686,18 +725,22 @@ describe('AtomEnvironment', () => { afterEach(() => atomEnvironment.destroy()); - it('is triggered once the shell environment is loaded', async () => { + it('is triggered once the shell environment is loaded', async (done) => { atomEnvironment.whenShellEnvironmentLoaded(spy); atomEnvironment.updateProcessEnvAndTriggerHooks(); await envLoaded(); expect(spy).toHaveBeenCalled(); + + done(); }); - it('triggers the callback immediately if the shell environment is already loaded', async () => { + it('triggers the callback immediately if the shell environment is already loaded', async (done) => { atomEnvironment.updateProcessEnvAndTriggerHooks(); await envLoaded(); atomEnvironment.whenShellEnvironmentLoaded(spy); expect(spy).toHaveBeenCalled(); + + done(); }); }); @@ -708,19 +751,21 @@ 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', () => { - it('opens a file', async () => { + it('opens a file', async (done) => { const pathToOpen = __filename; await atom.openLocations([ { pathToOpen, exists: true, isFile: true } ]); expect(atom.project.getPaths()).toEqual([]); + + done(); }); - it('opens a directory as a project folder', async () => { + it('opens a directory as a project folder', async (done) => { const pathToOpen = __dirname; await atom.openLocations([ { pathToOpen, exists: true, isDirectory: true } @@ -729,11 +774,13 @@ describe('AtomEnvironment', () => { [] ); expect(atom.project.getPaths()).toEqual([pathToOpen]); + + done(); }); }); describe('when the opened path does not exist', () => { - it('opens it as a new file', async () => { + it('opens it as a new file', async (done) => { const pathToOpen = path.join( __dirname, 'this-path-does-not-exist.txt' @@ -743,9 +790,11 @@ describe('AtomEnvironment', () => { [pathToOpen] ); expect(atom.project.getPaths()).toEqual([]); + + done(); }); - it('may be required to be an existing directory', async () => { + it('may be required to be an existing directory', async (done) => { spyOn(atom.notifications, 'addWarning'); const nonExistent = path.join(__dirname, 'no'); @@ -767,6 +816,8 @@ describe('AtomEnvironment', () => { description: `The directories \`${nonExistent}\` and \`${existingFile}\` do not exist.` } ); + + done(); }); }); @@ -791,19 +842,19 @@ describe('AtomEnvironment', () => { } } ); - - waitsFor(() => atom.project.directoryProviders.length > 0); }); afterEach(() => { serviceDisposable.dispose(); }); - it("adds it to the project's paths as is", async () => { + it("adds it to the project's paths as is", async (done) => { const pathToOpen = 'remote://server:7644/some/dir/path'; spyOn(atom.project, 'addPath'); await atom.openLocations([{ pathToOpen }]); expect(atom.project.addPath).toHaveBeenCalledWith(pathToOpen); + + done(); }); }); }); @@ -812,8 +863,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 { @@ -824,7 +875,7 @@ describe('AtomEnvironment', () => { }); describe('when there are no project folders', () => { - it('attempts to restore the project state', async () => { + it('attempts to restore the project state', async (done) => { const pathToOpen = __dirname; await atom.openLocations([{ pathToOpen, isDirectory: true }]); expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith( @@ -833,13 +884,15 @@ describe('AtomEnvironment', () => { [] ); expect(atom.project.getPaths()).toEqual([]); + + done(); }); - it('includes missing mandatory project folders in computation of initial state key', async () => { + it('includes missing mandatory project folders in computation of initial state key', async (done) => { 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 { @@ -858,9 +911,11 @@ describe('AtomEnvironment', () => { [] ); expect(atom.project.getPaths(), [existingDir]); + + done(); }); - it('opens the specified files', async () => { + it('opens the specified files', async (done) => { await atom.openLocations([ { pathToOpen: __dirname, isDirectory: true }, { pathToOpen: __filename } @@ -871,13 +926,15 @@ describe('AtomEnvironment', () => { [__filename] ); expect(atom.project.getPaths()).toEqual([]); + + done(); }); }); describe('when there are already project folders', () => { beforeEach(() => atom.project.setPaths([__dirname])); - it('does not attempt to restore the project state, instead adding the project paths', async () => { + it('does not attempt to restore the project state, instead adding the project paths', async (done) => { const pathToOpen = path.join(__dirname, 'fixtures'); await atom.openLocations([ { pathToOpen, exists: true, isDirectory: true } @@ -886,9 +943,11 @@ describe('AtomEnvironment', () => { atom.attemptRestoreProjectStateForPaths ).not.toHaveBeenCalled(); expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen]); + + done(); }); - it('opens the specified files', async () => { + it('opens the specified files', async (done) => { const pathToOpen = path.join(__dirname, 'fixtures'); const fileToOpen = path.join(pathToOpen, 'michelle-is-awesome.txt'); await atom.openLocations([ @@ -899,6 +958,8 @@ describe('AtomEnvironment', () => { atom.attemptRestoreProjectStateForPaths ).not.toHaveBeenCalledWith(state, [pathToOpen], [fileToOpen]); expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen]); + + done(); }); }); }); @@ -908,7 +969,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..a3cfc2db0a 100644 --- a/spec/atom-paths-spec.js +++ b/spec/atom-paths-spec.js @@ -42,7 +42,7 @@ describe('AtomPaths', () => { }); it('uses ATOM_HOME if no write access to portable .atom folder', () => { - if (process.platform === 'win32') return; + jasmine.filterByPlatform({except: ['win32']}); const readOnlyPath = temp.mkdirSync('atom-path-spec-no-write-access'); process.env.ATOM_HOME = readOnlyPath; @@ -120,7 +120,7 @@ describe('AtomPaths', () => { }); it('leaves userData unchanged if no write access to electronUserData folder', () => { - if (process.platform === 'win32') return; + jasmine.filterByPlatform({except: ['win32']}); fs.mkdirSync(electronUserDataPath); fs.chmodSync(electronUserDataPath, 444); 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..af71fa20f6 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(done) { let output = ''; const stdout = lines => (output += lines); let error = ''; @@ -12,18 +11,18 @@ describe('BufferedNodeProcess', function() { const args = ['hi']; const command = path.join(__dirname, 'fixtures', 'script.js'); - new BufferedNodeProcess({ command, args, stdout, stderr, exit }); + await new Promise((resolve) => { + new BufferedNodeProcess({ command, args, stdout, stderr, exit: resolve }); + }) - waitsFor(() => exit.callCount === 1); + expect(output).toBe('hi'); + expect(error).toBe(''); + expect(args).toEqual(['hi']); - runs(function() { - expect(output).toBe('hi'); - expect(error).toBe(''); - expect(args).toEqual(['hi']); - }); + done(); }); - it('suppresses deprecations in the new process', function() { + it('suppresses deprecations in the new process', async function(done) { const exit = jasmine.createSpy('exitCallback'); let output = ''; const stdout = lines => (output += lines); @@ -35,13 +34,13 @@ describe('BufferedNodeProcess', function() { 'script-with-deprecations.js' ); - new BufferedNodeProcess({ command, stdout, stderr, exit }); + await new Promise((resolve) => { + new BufferedNodeProcess({ command, stdout, stderr, exit: resolve }); + }) - waitsFor(() => exit.callCount === 1); + expect(output).toBe('hi'); + expect(error).toBe(''); - runs(function() { - expect(output).toBe('hi'); - expect(error).toBe(''); - }); + done(); }); }); diff --git a/spec/buffered-process-spec.js b/spec/buffered-process-spec.js index ab159a0d15..212c543610 100644 --- a/spec/buffered-process-spec.js +++ b/spec/buffered-process-spec.js @@ -6,42 +6,43 @@ 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(done) { 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' + ); + + done(); })); 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(done) { + spyOn(ChildProcess, 'spawn').and.callFake(function() { const error = new Error('Something is really wrong'); error.code = 'EAGAIN'; throw error; @@ -53,42 +54,47 @@ 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' + ); + + done(); })); }); 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 } + it('does throw an exception', async function(done) { + await new Promise((resolve) => { + window.onerror.and.callFake(resolve); + + new BufferedProcess({ + command: 'bad-command-nope2', + args: ['nothing'], + options: {shell: false} + }); }); - waitsFor(() => window.onerror.callCount > 0); + 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' + ); - 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' - ); - }); + done(); })); }); @@ -97,7 +103,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(done) { let stdout = ''; let stderr = ''; const exitCallback = jasmine.createSpy('exit callback'); @@ -116,70 +122,77 @@ 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(''); + + done(); })); /** * 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(done) { 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(done) { 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); + + done(); }); describe('on Windows', function() { @@ -202,20 +215,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/command-registry-spec.js b/spec/command-registry-spec.js index 1f170ef980..53be73fe76 100644 --- a/spec/command-registry-spec.js +++ b/spec/command-registry-spec.js @@ -387,7 +387,7 @@ describe('CommandRegistry', () => { expect(registry.dispatch(parent, 'command')).toBe(null); }); - it('returns a promise that resolves when the listeners resolve', async () => { + it('returns a promise that resolves when the listeners resolve', async (done) => { jasmine.useRealClock(); registry.add('.grandchild', 'command', () => 1); registry.add('.grandchild', 'command', () => Promise.resolve(2)); @@ -404,9 +404,10 @@ describe('CommandRegistry', () => { const values = await registry.dispatch(grandchild, 'command'); expect(values).toEqual([3, 2, 1]); + done(); }); - it('returns a promise that rejects when a listener is rejected', async () => { + it('returns a promise that rejects when a listener is rejected', async (done) => { jasmine.useRealClock(); registry.add('.grandchild', 'command', () => 1); registry.add('.grandchild', 'command', () => Promise.resolve(2)); @@ -428,6 +429,7 @@ describe('CommandRegistry', () => { value = err; } expect(value).toBe(3); + done(); }); }); 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-file-spec.js b/spec/config-file-spec.js index bc229ed384..ed2643cf56 100644 --- a/spec/config-file-spec.js +++ b/spec/config-file-spec.js @@ -18,24 +18,28 @@ describe('ConfigFile', () => { }); describe('when the file does not exist', () => { - it('returns an empty object from .get()', async () => { + it('returns an empty object from .get()', async (done) => { configFile = new ConfigFile(filePath); subscription = await configFile.watch(); expect(configFile.get()).toEqual({}); + + done(); }); }); describe('when the file is empty', () => { - it('returns an empty object from .get()', async () => { + it('returns an empty object from .get()', async (done) => { writeFileSync(filePath, ''); configFile = new ConfigFile(filePath); subscription = await configFile.watch(); expect(configFile.get()).toEqual({}); + + done(); }); }); describe('when the file is updated with valid CSON', () => { - it('notifies onDidChange observers with the data', async () => { + it('notifies onDidChange observers with the data', async (done) => { configFile = new ConfigFile(filePath); subscription = await configFile.watch(); @@ -61,11 +65,13 @@ describe('ConfigFile', () => { '*': { foo: 'bar' }, javascript: { foo: 'baz' } }); + + done(); }); }); describe('when the file is updated with invalid CSON', () => { - it('notifies onDidError observers', async () => { + it('notifies onDidError observers', async (done) => { configFile = new ConfigFile(filePath); subscription = await configFile.watch(); @@ -99,6 +105,8 @@ describe('ConfigFile', () => { '*': { foo: 'bar' }, javascript: { foo: 'baz' } }); + + done(); }); }); diff --git a/spec/config-spec.js b/spec/config-spec.js index f292542f93..1b7079a4cb 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 (done) => { let promiseResult = null; const transactionPromise = atom.config.transactAsync(() => { atom.config.set('foo.bar.baz', 1); @@ -1051,23 +1051,19 @@ 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 }); + + done(); }); - 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 (done) => { let promiseError = null; const transactionPromise = atom.config.transactAsync(() => { atom.config.set('foo.bar.baz', 1); @@ -1076,23 +1072,21 @@ 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 }); + + done(); }); - 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 (done) => { const error = new Error('Oops!'); let promiseError = null; const transactionPromise = atom.config.transactAsync(() => { @@ -1102,20 +1096,18 @@ 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 }); + + done(); }); }); @@ -1189,7 +1181,7 @@ describe('Config', () => { atom.config.save(); const writtenConfig = savedSettings[0]; - expect(writtenConfig).toEqualJson({ + expect(writtenConfig).toEqual({ '*': atom.config.settings, '.ruby.source': { foo: { @@ -1296,7 +1288,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 +1379,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 +1394,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 +1409,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 +1438,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..0249e91b2b 100644 --- a/spec/decoration-manager-spec.js +++ b/spec/decoration-manager-spec.js @@ -2,16 +2,18 @@ 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(done) { 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'); + + done(); }); afterEach(() => buffer.destroy()); @@ -68,7 +70,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 +79,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 +96,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..ffb2568ab5 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() { + jasmine.filterByPlatform({only: ['win32']}); + const provider = new DefaultDirectoryProvider(); const nonNormalizedPath = tmp[0].toLowerCase() + tmp.slice(1); expect(tmp).not.toMatch(/^[a-z]:/); @@ -61,13 +63,12 @@ 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(done) { 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); + + done(); })); }); diff --git a/spec/default-directory-searcher-spec.js b/spec/default-directory-searcher-spec.js index 9526682460..e93e0d6a0b 100644 --- a/spec/default-directory-searcher-spec.js +++ b/spec/default-directory-searcher-spec.js @@ -11,7 +11,7 @@ describe('DefaultDirectorySearcher', function() { searcher = new DefaultDirectorySearcher(); }); - it('terminates the task after running a search', async function() { + it('terminates the task after running a search', async function(done) { const options = { ignoreCase: false, includeHidden: false, @@ -23,7 +23,7 @@ describe('DefaultDirectorySearcher', function() { didSearchPaths() {} }; - spyOn(Task.prototype, 'terminate').andCallThrough(); + spyOn(Task.prototype, 'terminate').and.callThrough(); await searcher.search( [ @@ -38,5 +38,7 @@ describe('DefaultDirectorySearcher', function() { ); expect(Task.prototype.terminate).toHaveBeenCalled(); + + done(); }); }); diff --git a/spec/dock-spec.js b/spec/dock-spec.js index 6afc780b21..460ff30e04 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,16 +70,16 @@ 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); }); }); describe('when a pane in a dock is activated', () => { - it('opens the dock', async () => { + it('opens the dock', async (done) => { const item = { element: document.createElement('div'), getDefaultLocation() { @@ -95,6 +95,8 @@ describe('Dock', () => { .getPanes()[0] .activate(); expect(atom.workspace.getLeftDock().isVisible()).toBe(true); + + done(); }); }); @@ -164,7 +166,7 @@ describe('Dock', () => { describe('when the dock resize handle is double-clicked', () => { describe('when the dock is open', () => { - it("resizes a vertically-oriented dock to the current item's preferred width", async () => { + it("resizes a vertically-oriented dock to the current item's preferred width", async (done) => { jasmine.attachToDOM(atom.workspace.getElement()); const item = { @@ -193,9 +195,11 @@ describe('Dock', () => { await getNextUpdatePromise(); expect(dockElement.offsetWidth).toBe(item.getPreferredWidth()); + + done(); }); - it("resizes a horizontally-oriented dock to the current item's preferred width", async () => { + it("resizes a horizontally-oriented dock to the current item's preferred width", async (done) => { jasmine.attachToDOM(atom.workspace.getElement()); const item = { @@ -224,11 +228,13 @@ describe('Dock', () => { await getNextUpdatePromise(); expect(dockElement.offsetHeight).toBe(item.getPreferredHeight()); + + done(); }); }); describe('when the dock is closed', () => { - it('does nothing', async () => { + it('does nothing', async (done) => { jasmine.attachToDOM(atom.workspace.getElement()); const item = { @@ -258,13 +264,15 @@ describe('Dock', () => { expect(dockElement.querySelector('.atom-dock-mask').offsetHeight).toBe( 0 ); + + done(); }); }); }); describe('when you add an item to an empty dock', () => { describe('when the item has a preferred size', () => { - it('is takes the preferred size of the item', async () => { + it('is takes the preferred size of the item', async (done) => { jasmine.attachToDOM(atom.workspace.getElement()); const createItem = preferredWidth => ({ @@ -299,11 +307,13 @@ describe('Dock', () => { const item3 = createItem(333); await atom.workspace.open(item3); expect(dockElement.offsetWidth).toBe(222); + + done(); }); }); describe('when the item has no preferred size', () => { - it('is still has an explicit size', async () => { + it('is still has an explicit size', async (done) => { jasmine.attachToDOM(atom.workspace.getElement()); const item = { @@ -318,12 +328,14 @@ describe('Dock', () => { expect(dock.state.size).toBe(null); await atom.workspace.open(item); expect(dock.state.size).not.toBe(null); + + done(); }); }); }); describe('a deserialized dock', () => { - it('restores the serialized size', async () => { + it('restores the serialized size', async (done) => { jasmine.attachToDOM(atom.workspace.getElement()); const item = { @@ -352,9 +364,11 @@ describe('Dock', () => { dock.destroyActivePane(); dock.deserialize(serialized, atom.deserializers); expect(dockElement.offsetWidth).toBe(150); + + done(); }); - it("isn't visible if it has no items", async () => { + it("isn't visible if it has no items", async (done) => { jasmine.attachToDOM(atom.workspace.getElement()); const item = { @@ -374,11 +388,13 @@ describe('Dock', () => { dock.deserialize(serialized, atom.deserializers); expect(dock.getPaneItems()).toHaveLength(0); expect(dock.isVisible()).toBe(false); + + done(); }); }); describe('drag handling', () => { - it('expands docks to match the preferred size of the dragged item', async () => { + it('expands docks to match the preferred size of the dragged item', async (done) => { jasmine.attachToDOM(atom.workspace.getElement()); const element = document.createElement('div'); @@ -401,6 +417,8 @@ describe('Dock', () => { expect(atom.workspace.getLeftDock().refs.wrapperElement.offsetWidth).toBe( 144 ); + + done(); }); it('does nothing when text nodes are dragged', () => { @@ -422,7 +440,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..52d100587b 100644 --- a/spec/git-repository-provider-spec.js +++ b/spec/git-repository-provider-spec.js @@ -26,20 +26,22 @@ describe('GitRepositoryProvider', () => { describe('.repositoryForDirectory(directory)', () => { describe('when specified a Directory with a Git repository', () => { - it('resolves with a GitRepository', async () => { + it('resolves with a GitRepository', async (done) => { const directory = new Directory( 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'); // Refresh should be started await new Promise(resolve => result.onDidChangeStatuses(resolve)); + + done(); }); - it('resolves with the same GitRepository for different Directory objects in the same repo', async () => { + it('resolves with the same GitRepository for different Directory objects in the same repo', async (done) => { const firstRepo = await provider.repositoryForDirectory( new Directory(path.join(__dirname, 'fixtures', 'git', 'master.git')) ); @@ -49,21 +51,25 @@ describe('GitRepositoryProvider', () => { ) ); - expect(firstRepo).toBeInstanceOf(GitRepository); + expect(firstRepo).toEqual(jasmine.any(GitRepository)); expect(firstRepo).toBe(secondRepo); + + done(); }); }); describe('when specified a Directory without a Git repository', () => { - it('resolves with null', async () => { + it('resolves with null', async (done) => { const directory = new Directory(temp.mkdirSync('dir')); const repo = await provider.repositoryForDirectory(directory); expect(repo).toBe(null); + + done(); }); }); describe('when specified a Directory with an invalid Git repository', () => { - it('resolves with null', async () => { + it('resolves with null', async (done) => { const dirPath = temp.mkdirSync('dir'); fs.writeFileSync(path.join(dirPath, '.git', 'objects'), ''); fs.writeFileSync(path.join(dirPath, '.git', 'HEAD'), ''); @@ -72,11 +78,13 @@ describe('GitRepositoryProvider', () => { const directory = new Directory(dirPath); const repo = await provider.repositoryForDirectory(directory); expect(repo).toBe(null); + + done(); }); }); describe('when specified a Directory with a valid gitfile-linked repository', () => { - it('returns a Promise that resolves to a GitRepository', async () => { + it('returns a Promise that resolves to a GitRepository', async (done) => { const gitDirPath = path.join( __dirname, 'fixtures', @@ -91,14 +99,16 @@ 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'); + + done(); }); }); describe('when specified a Directory with a commondir file for a worktree', () => { - it('returns a Promise that resolves to a GitRepository', async () => { + it('returns a Promise that resolves to a GitRepository', async (done) => { const directory = new Directory( path.join( __dirname, @@ -110,9 +120,11 @@ 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'); + + done(); }); }); @@ -128,30 +140,34 @@ describe('GitRepositoryProvider', () => { return true; } }; - spyOn(directory, 'getSubdirectory').andReturn(subdirectory); + spyOn(directory, 'getSubdirectory').and.returnValue(subdirectory); }); - it('returns a Promise that resolves to null', async () => { + it('returns a Promise that resolves to null', async (done) => { const repo = await provider.repositoryForDirectory(directory); expect(repo).toBe(null); expect(directory.getSubdirectory).toHaveBeenCalledWith('.git'); + + done(); }); }); }); describe('.repositoryForDirectorySync(directory)', () => { describe('when specified a Directory with a Git repository', () => { - it('resolves with a GitRepository', async () => { + it('resolves with a GitRepository', async (done) => { const directory = new Directory( 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'); // Refresh should be started await new Promise(resolve => result.onDidChangeStatuses(resolve)); + + done(); }); it('resolves with the same GitRepository for different Directory objects in the same repo', () => { @@ -164,7 +180,7 @@ describe('GitRepositoryProvider', () => { ) ); - expect(firstRepo).toBeInstanceOf(GitRepository); + expect(firstRepo).toEqual(jasmine.any(GitRepository)); expect(firstRepo).toBe(secondRepo); }); }); @@ -206,7 +222,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 +241,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 +259,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..f04c8d5628 100644 --- a/spec/git-repository-spec.js +++ b/spec/git-repository-spec.js @@ -148,21 +148,21 @@ 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); }); }); describe('.checkoutHeadForEditor(editor)', () => { let filePath, editor; - beforeEach(async () => { + beforeEach(async (done) => { spyOn(atom, 'confirm'); const workingDirPath = copyRepository(); @@ -175,13 +175,14 @@ describe('GitRepository', () => { fs.writeFileSync(filePath, 'ch ch changes'); editor = await atom.workspace.open(filePath); + + done(); }); it('displays a confirmation dialog by default', () => { - // Permissions issues with this test on Windows - if (process.platform === 'win32') return; + jasmine.filterByPlatform({except: ['win32']}); // 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); @@ -190,8 +191,8 @@ describe('GitRepository', () => { }); it('does not display a dialog when confirmation is disabled', () => { - // Flakey EPERM opening a.txt on Win32 - if (process.platform === 'win32') return; + jasmine.filterByPlatform({except: ['win32']}); // Flakey EPERM opening a.txt on Win32 + atom.config.set('editor.confirmCheckoutHeadRevision', false); repo.checkoutHeadForEditor(editor); @@ -225,15 +226,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); }); }); @@ -276,21 +277,23 @@ describe('GitRepository', () => { newPath = fs.absolute(newPath); }); - it('returns status information for all new and modified files', async () => { + it('returns status information for all new and modified files', async (done) => { const statusHandler = jasmine.createSpy('statusHandler'); repo.onDidChangeStatuses(statusHandler); 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( repo.isStatusModified(repo.getCachedPathStatus(modifiedPath)) ).toBeTruthy(); + + done(); }); - it('caches the proper statuses when a subdir is open', async () => { + it('caches the proper statuses when a subdir is open', async (done) => { const subDir = path.join(workingDirectory, 'dir'); fs.mkdirSync(subDir); const filePath = path.join(subDir, 'b.txt'); @@ -303,9 +306,11 @@ describe('GitRepository', () => { const status = repo.getCachedPathStatus(filePath); expect(repo.isStatusModified(status)).toBe(false); expect(repo.isStatusNew(status)).toBe(false); + + done(); }); - it('works correctly when the project has multiple folders (regression)', async () => { + it('works correctly when the project has multiple folders (regression)', async (done) => { atom.project.addPath(workingDirectory); atom.project.addPath(path.join(__dirname, 'fixtures', 'dir')); @@ -315,9 +320,11 @@ describe('GitRepository', () => { expect( repo.isStatusModified(repo.getCachedPathStatus(modifiedPath)) ).toBeTruthy(); + + done(); }); - it('caches statuses that were looked up synchronously', async () => { + it('caches statuses that were looked up synchronously', async (done) => { const originalContent = 'undefined'; fs.writeFileSync(modifiedPath, 'making this path modified'); repo.getPathStatus('file.txt'); @@ -327,50 +334,58 @@ describe('GitRepository', () => { expect( repo.isStatusModified(repo.getCachedPathStatus(modifiedPath)) ).toBeFalsy(); + + done(); }); }); describe('buffer events', () => { let editor; - beforeEach(async () => { + beforeEach(async (done) => { atom.project.setPaths([copyRepository()]); const refreshPromise = new Promise(resolve => atom.project.getRepositories()[0].onDidChangeStatuses(resolve) ); editor = await atom.workspace.open('other.txt'); await refreshPromise; + + done(); }); - it('emits a status-changed event when a buffer is saved', async () => { + it('emits a status-changed event when a buffer is saved', async (done) => { editor.insertNewline(); const statusHandler = jasmine.createSpy('statusHandler'); 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 }); + + done(); }); - it('emits a status-changed event when a buffer is reloaded', async () => { + it('emits a status-changed event when a buffer is reloaded', async (done) => { fs.writeFileSync(editor.getPath(), 'changed'); const statusHandler = jasmine.createSpy('statusHandler'); 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); + + done(); }); it("emits a status-changed event when a buffer's path changes", () => { @@ -379,13 +394,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)', () => { @@ -401,7 +416,7 @@ describe('GitRepository', () => { if (project2) project2.destroy(); }); - it('subscribes to all the serialized buffers in the project', async () => { + it('subscribes to all the serialized buffers in the project', async (done) => { atom.project.setPaths([copyRepository()]); await atom.workspace.open('file.txt'); @@ -424,11 +439,13 @@ 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 }); + + done(); }); }); }); diff --git a/spec/grammar-registry-spec.js b/spec/grammar-registry-spec.js index cae311ec8e..e9fc4f5046 100644 --- a/spec/grammar-registry-spec.js +++ b/spec/grammar-registry-spec.js @@ -19,14 +19,16 @@ function setConfigForLanguageMode(mode, options = {}) { describe('GrammarRegistry', () => { let grammarRegistry; - beforeEach(async () => { + beforeEach(async (done) => { await SecondMate.ready grammarRegistry = new GrammarRegistry({ config: atom.config }); expect(subscriptionCount(grammarRegistry)).toBe(2); + + done(); }); describe('.assignLanguageMode(buffer, languageId)', () => { - it('assigns to the buffer a language mode with the given language id', async () => { + it('assigns to the buffer a language mode with the given language id', async (done) => { grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') ); @@ -55,6 +57,8 @@ describe('GrammarRegistry', () => { // Returns false if no language is found expect(grammarRegistry.assignLanguageMode(buffer, 'blub')).toBe(false); expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css'); + + done(); }); describe('when no languageId is passed', () => { @@ -177,7 +181,7 @@ describe('GrammarRegistry', () => { }); describe('.maintainLanguageMode(buffer)', () => { - it('assigns a grammar to the buffer based on its path', async () => { + it('assigns a grammar to the buffer based on its path', async (done) => { const buffer = new TextBuffer(); grammarRegistry.loadGrammarSync( @@ -193,9 +197,11 @@ describe('GrammarRegistry', () => { buffer.setPath('test.c'); expect(buffer.getLanguageMode().getLanguageId()).toBe('source.c'); + + done(); }); - it("updates the buffer's grammar when a more appropriate text-mate grammar is added for its path", async () => { + it("updates the buffer's grammar when a more appropriate text-mate grammar is added for its path", async (done) => { setConfigForLanguageMode('textmate'); const buffer = new TextBuffer(); @@ -215,9 +221,11 @@ describe('GrammarRegistry', () => { ) ); expect(buffer.getLanguageMode().grammar).toBe(textMateGrammar); + + done(); }); - it("updates the buffer's grammar when a more appropriate tree-sitter grammar is added for its path", async () => { + it("updates the buffer's grammar when a more appropriate tree-sitter grammar is added for its path", async (done) => { setConfigForLanguageMode('node-tree-sitter'); const buffer = new TextBuffer(); @@ -237,9 +245,11 @@ describe('GrammarRegistry', () => { require.resolve('language-javascript/grammars/javascript.cson') ); expect(buffer.getLanguageMode().grammar).toBe(treeSitterGrammar); + + done(); }); - it("updates the buffer's grammar when a more appropriate new-tree-sitter grammar is added for its path and the user has opted into new-tree-sitter", async () => { + it("updates the buffer's grammar when a more appropriate new-tree-sitter grammar is added for its path and the user has opted into new-tree-sitter", async (done) => { setConfigForLanguageMode('wasm-tree-sitter'); const buffer = new TextBuffer(); @@ -266,9 +276,11 @@ describe('GrammarRegistry', () => { require.resolve('language-javascript/grammars/javascript.cson') ); expect(buffer.getLanguageMode().grammar).toBe(modernTreeSitterGrammar); + + done(); }); - it("updates the buffer's grammar by ignoring a new-tree-sitter grammar if the user has NOT opted into new-tree-sitter", async () => { + it("updates the buffer's grammar by ignoring a new-tree-sitter grammar if the user has NOT opted into new-tree-sitter", async (done) => { setConfigForLanguageMode('node-tree-sitter'); const buffer = new TextBuffer(); @@ -298,6 +310,7 @@ describe('GrammarRegistry', () => { ); expect(buffer.getLanguageMode().grammar).toBe(treeSitterGrammar); + done(); }); @@ -321,7 +334,7 @@ describe('GrammarRegistry', () => { expect(buffer.getLanguageMode().getLanguageId()).toBe('source.css'); }); - it('returns a disposable that can be used to stop the registry from updating the buffer', async () => { + it('returns a disposable that can be used to stop the registry from updating the buffer', async (done) => { const buffer = new TextBuffer(); grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') @@ -353,9 +366,10 @@ describe('GrammarRegistry', () => { 'text.plain.null-grammar' ); expect(retainedBufferCount(grammarRegistry)).toBe(0); + done(); }); - it("doesn't do anything when called a second time with the same buffer", async () => { + it("doesn't do anything when called a second time with the same buffer", async (done) => { const buffer = new TextBuffer(); grammarRegistry.loadGrammarSync( require.resolve('language-javascript/grammars/javascript.cson') @@ -377,6 +391,7 @@ describe('GrammarRegistry', () => { expect(buffer.getLanguageMode().getLanguageId()).toBe( 'text.plain.null-grammar' ); + done(); }); it('does not retain the buffer after the buffer is destroyed', () => { @@ -425,14 +440,15 @@ describe('GrammarRegistry', () => { ); }); - it('selects the text.plain grammar over the null grammar', async () => { + it('selects the text.plain grammar over the null grammar', async (done) => { await atom.packages.activatePackage('language-text'); expect(atom.grammars.selectGrammar('test.txt').scopeName).toBe( 'text.plain' ); + done(); }); - it('selects a grammar based on the file path case insensitively', async () => { + it('selects a grammar based on the file path case insensitively', async (done) => { await atom.packages.activatePackage('language-coffee-script'); expect(atom.grammars.selectGrammar('/tmp/source.coffee').scopeName).toBe( 'source.coffee' @@ -440,6 +456,7 @@ describe('GrammarRegistry', () => { expect(atom.grammars.selectGrammar('/tmp/source.COFFEE').scopeName).toBe( 'source.coffee' ); + done(); }); describe('on Windows', () => { @@ -454,15 +471,16 @@ describe('GrammarRegistry', () => { Object.defineProperty(process, 'platform', { value: originalPlatform }); }); - it('normalizes back slashes to forward slashes when matching the fileTypes', async () => { + it('normalizes back slashes to forward slashes when matching the fileTypes', async (done) => { await atom.packages.activatePackage('language-git'); expect( atom.grammars.selectGrammar('something\\.git\\config').scopeName ).toBe('source.git-config'); + done(); }); }); - it("can use the filePath to load the correct grammar based on the grammar's filetype", async () => { + it("can use the filePath to load the correct grammar based on the grammar's filetype", async (done) => { await atom.packages.activatePackage('language-git'); await atom.packages.activatePackage('language-javascript'); await atom.packages.activatePackage('language-ruby'); @@ -476,25 +494,28 @@ describe('GrammarRegistry', () => { expect(atom.grammars.selectGrammar('/hu.git/config').name).toBe( 'Null Grammar' ); + done(); }); - it(`returns a legacy Tree-sitter grammar if the user opted into it via a scope-specific setting`, async () => { + it(`returns a legacy Tree-sitter grammar if the user opted into it via a scope-specific setting`, async (done) => { await atom.packages.activatePackage('language-javascript'); setConfigForLanguageMode('node-tree-sitter', { scopeSelector: '.source.js' }) let grammar = atom.grammars.selectGrammar('file.js'); expect(grammar.name).toBe('JavaScript'); expect(grammar.constructor.name).toBe('TreeSitterGrammar'); + done(); }) - it("uses the filePath's shebang line if the grammar cannot be determined by the extension or basename", async () => { + it("uses the filePath's shebang line if the grammar cannot be determined by the extension or basename", async (done) => { await atom.packages.activatePackage('language-javascript'); await atom.packages.activatePackage('language-ruby'); const filePath = require.resolve('./fixtures/shebang'); expect(atom.grammars.selectGrammar(filePath).name).toBe('Ruby'); + done(); }); - it('uses the number of newlines in the first line regex to determine the number of lines to test against', async () => { + it('uses the number of newlines in the first line regex to determine the number of lines to test against', async (done) => { await atom.packages.activatePackage('language-property-list'); await atom.packages.activatePackage('language-coffee-script'); @@ -513,18 +534,20 @@ describe('GrammarRegistry', () => { expect( atom.grammars.selectGrammar('grammar.tmLanguage', fileContent).name ).toBe('Property List (XML)'); + done(); }); - it("doesn't read the file when the file contents are specified", async () => { + it("doesn't read the file when the file contents are specified", async (done) => { await atom.packages.activatePackage('language-ruby'); 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' ); expect(fs.read).not.toHaveBeenCalled(); + done(); }); describe('when multiple grammars have matching fileTypes', () => { @@ -557,7 +580,7 @@ describe('GrammarRegistry', () => { }); }); - it('favors non-bundled packages when breaking scoring ties', async () => { + it('favors non-bundled packages when breaking scoring ties', async (done) => { await atom.packages.activatePackage('language-ruby'); await atom.packages.activatePackage( path.join(__dirname, 'fixtures', 'packages', 'package-with-rb-filetype') @@ -574,6 +597,7 @@ describe('GrammarRegistry', () => { .scopeName ).toBe('test.rb'); expect(atom.grammars.selectGrammar('test.rb').scopeName).toBe('test.rb'); + done(); }); describe('when there is no file path', () => { @@ -587,7 +611,7 @@ describe('GrammarRegistry', () => { }); describe('when the user has custom grammar file types', () => { - it('considers the custom file types as well as those defined in the grammar', async () => { + it('considers the custom file types as well as those defined in the grammar', async (done) => { await atom.packages.activatePackage('language-ruby'); atom.config.set('core.customFileTypes', { 'source.ruby': ['Cheffile'] @@ -596,9 +620,10 @@ describe('GrammarRegistry', () => { atom.grammars.selectGrammar('build/Cheffile', 'cookbook "postgres"') .scopeName ).toBe('source.ruby'); + done(); }); - it('favors user-defined file types over built-in ones of equal length', async () => { + it('favors user-defined file types over built-in ones of equal length', async (done) => { await atom.packages.activatePackage('language-ruby'); await atom.packages.activatePackage('language-coffee-script'); @@ -612,9 +637,10 @@ describe('GrammarRegistry', () => { expect(atom.grammars.selectGrammar('Cakefile', '').scopeName).toBe( 'source.ruby' ); + done(); }); - it('favors user-defined file types over grammars with matching first-line-regexps', async () => { + it('favors user-defined file types over grammars with matching first-line-regexps', async (done) => { await atom.packages.activatePackage('language-ruby'); await atom.packages.activatePackage('language-javascript'); @@ -625,15 +651,17 @@ describe('GrammarRegistry', () => { atom.grammars.selectGrammar('bootstrap', '#!/usr/bin/env node') .scopeName ).toBe('source.ruby'); + done(); }); }); - it('favors a grammar with a matching file type over one with m matching first line pattern', async () => { + it('favors a grammar with a matching file type over one with m matching first line pattern', async (done) => { await atom.packages.activatePackage('language-ruby'); await atom.packages.activatePackage('language-javascript'); expect( atom.grammars.selectGrammar('foo.rb', '#!/usr/bin/env node').scopeName ).toBe('source.ruby'); + done(); }); describe('tree-sitter vs text-mate', () => { @@ -879,7 +907,7 @@ describe('GrammarRegistry', () => { }); describe('.removeGrammar(grammar)', () => { - it("removes the grammar, so it won't be returned by selectGrammar", async () => { + it("removes the grammar, so it won't be returned by selectGrammar", async (done) => { await atom.packages.activatePackage('language-css'); const grammar = atom.grammars.selectGrammar('foo.css'); atom.grammars.removeGrammar(grammar); @@ -888,6 +916,7 @@ describe('GrammarRegistry', () => { grammar.name === newGrammar.name && grammar.constructor.name === newGrammar.constructor.name ).toBe(false); + done(); }); }); @@ -918,16 +947,17 @@ describe('GrammarRegistry', () => { updateCallbackDisposable?.dispose(); }); - it('adds an injection point to the grammar with the given id', async () => { + it('adds an injection point to the grammar with the given id', async (done) => { await atom.packages.activatePackage('language-javascript'); atom.grammars.addInjectionPoint('source.js', injectionPoint); const grammar = atom.grammars.grammarForId('source.js'); expect( grammar.injectionPointsByType['some_node_type'] ).toContain(injectionPoint); + done(); }); - it('fires the onDidUpdateGrammar callback', async () => { + it('fires the onDidUpdateGrammar callback', async (done) => { await atom.packages.activatePackage('language-javascript'); let callbackDisposable = atom.grammars.onDidUpdateGrammar((grammar) => { if (grammar.scopeName === 'source.js') { @@ -936,10 +966,11 @@ describe('GrammarRegistry', () => { }); atom.grammars.addInjectionPoint('source.js', injectionPoint); expect(updateCallbackFired).toBe(true); + done(); }); describe('when called before a grammar with the given id is loaded', () => { - it('adds the injection point once the grammar is loaded', async () => { + it('adds the injection point once the grammar is loaded', async (done) => { // Adding an injection point before a grammar loads should not trigger // onDidUpdateGrammar at any point. updateCallbackDisposable = atom.grammars.onDidUpdateGrammar((grammar) => { @@ -961,12 +992,13 @@ describe('GrammarRegistry', () => { ).toContain(injectionPoint); expect(updateCallbackFired).toBe(false); expect(addCallbackFired).toBe(true); + done(); }); }); }); describe('serialization', () => { - it("persists editors' grammar overrides", async () => { + it("persists editors' grammar overrides", async (done) => { const buffer1 = new TextBuffer(); const buffer2 = new TextBuffer(); @@ -1013,29 +1045,33 @@ describe('GrammarRegistry', () => { ); expect(buffer1Copy.getLanguageMode().getLanguageId()).toBe('source.c'); expect(buffer2Copy.getLanguageMode().getLanguageId()).toBe('source.js'); + done(); }); }); describe('when working with grammars', () => { - beforeEach(async () => { + beforeEach(async (done) => { await atom.packages.activatePackage('language-javascript'); + done(); }); - it('returns only Tree-sitter grammars by default', async () => { + it('returns only Tree-sitter grammars by default', async (done) => { const tmGrammars = atom.grammars.getGrammars(); const allGrammars = atom.grammars.getGrammars({ includeTreeSitter: true }); expect(allGrammars.length).toBeGreaterThan(tmGrammars.length); + done(); }); - it('executes the foreach callback on both Tree-sitter and TextMate grammars', async () => { + it('executes the foreach callback on both Tree-sitter and TextMate grammars', async (done) => { const numAllGrammars = atom.grammars.getGrammars({ includeTreeSitter: true }).length; let i = 0; atom.grammars.forEachGrammar(() => i++); expect(i).toBe(numAllGrammars); + done(); }); }); }); diff --git a/spec/helpers/jasmine1-spec-helper.js b/spec/helpers/jasmine1-spec-helper.js index 16ef5fc6a7..14b15cdec1 100644 --- a/spec/helpers/jasmine1-spec-helper.js +++ b/spec/helpers/jasmine1-spec-helper.js @@ -71,9 +71,9 @@ jasmine.getEnv().addEqualityTester(function(a, b) { }); if (process.env.CI) { - jasmine.getEnv().defaultTimeoutInterval = 120000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 120000; } else { - jasmine.getEnv().defaultTimeoutInterval = 5000; + jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; } const {testPaths} = atom.getLoadSettings(); diff --git a/spec/history-manager-spec.js b/spec/history-manager-spec.js index b3e425da3e..b813b7c2b9 100644 --- a/spec/history-manager-spec.js +++ b/spec/history-manager-spec.js @@ -5,10 +5,10 @@ describe('HistoryManager', () => { let historyManager, commandRegistry, project, stateStore; let commandDisposable, projectDisposable; - beforeEach(async () => { + beforeEach(async (done) => { 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; }); @@ -34,16 +34,18 @@ describe('HistoryManager', () => { commands: commandRegistry }); await historyManager.loadState(); + done(); }); - afterEach(async () => { + afterEach(async (done) => { await stateStore.clear(); + done(); }); 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( @@ -74,13 +76,14 @@ describe('HistoryManager', () => { }); describe('clearProjects', () => { - it('clears the list of projects', async () => { + it('clears the list of projects', async (done) => { expect(historyManager.getProjects().length).not.toBe(0); await historyManager.clearProjects(); expect(historyManager.getProjects().length).toBe(0); + done(); }); - it('saves the state', async () => { + it('saves the state', async (done) => { await historyManager.clearProjects(); const historyManager2 = new HistoryManager({ stateStore, @@ -89,14 +92,16 @@ describe('HistoryManager', () => { }); await historyManager2.loadState(); expect(historyManager.getProjects().length).toBe(0); + done(); }); - it('fires the onDidChangeProjects event', async () => { + it('fires the onDidChangeProjects event', async (done) => { const didChangeSpy = jasmine.createSpy(); historyManager.onDidChangeProjects(didChangeSpy); await historyManager.clearProjects(); expect(historyManager.getProjects().length).toBe(0); expect(didChangeSpy).toHaveBeenCalled(); + done(); }); }); @@ -120,63 +125,70 @@ describe('HistoryManager', () => { }); describe('loadState', () => { - it('defaults to an empty array if no state', async () => { + it('defaults to an empty array if no state', async (done) => { await stateStore.clear(); await historyManager.loadState(); expect(historyManager.getProjects()).toEqual([]); + done(); }); - it('defaults to an empty array if no projects', async () => { + it('defaults to an empty array if no projects', async (done) => { await stateStore.save('history-manager', {}); await historyManager.loadState(); expect(historyManager.getProjects()).toEqual([]); + done(); }); }); describe('addProject', () => { - it('adds a new project to the end', async () => { + it('adds a new project to the end', async (done) => { const date = new Date(2010, 10, 9, 8, 7, 6); await historyManager.addProject(['/a/b'], date); const projects = historyManager.getProjects(); expect(projects.length).toBe(3); expect(projects[2].paths).toEqual(['/a/b']); expect(projects[2].lastOpened).toBe(date); + done(); }); - it('adds a new project to the start', async () => { + it('adds a new project to the start', async (done) => { const date = new Date(); await historyManager.addProject(['/so/new'], date); const projects = historyManager.getProjects(); expect(projects.length).toBe(3); expect(projects[0].paths).toEqual(['/so/new']); expect(projects[0].lastOpened).toBe(date); + done(); }); - it('updates an existing project and moves it to the start', async () => { + it('updates an existing project and moves it to the start', async (done) => { const date = new Date(); await historyManager.addProject(['/test'], date); const projects = historyManager.getProjects(); expect(projects.length).toBe(2); expect(projects[0].paths).toEqual(['/test']); expect(projects[0].lastOpened).toBe(date); + done(); }); - it('fires the onDidChangeProjects event when adding a project', async () => { + it('fires the onDidChangeProjects event when adding a project', async (done) => { const didChangeSpy = jasmine.createSpy(); const beforeCount = historyManager.getProjects().length; historyManager.onDidChangeProjects(didChangeSpy); await historyManager.addProject(['/test-new'], new Date()); expect(didChangeSpy).toHaveBeenCalled(); expect(historyManager.getProjects().length).toBe(beforeCount + 1); + done(); }); - it('fires the onDidChangeProjects event when updating a project', async () => { + it('fires the onDidChangeProjects event when updating a project', async (done) => { const didChangeSpy = jasmine.createSpy(); const beforeCount = historyManager.getProjects().length; historyManager.onDidChangeProjects(didChangeSpy); await historyManager.addProject(['/test'], new Date()); expect(didChangeSpy).toHaveBeenCalled(); expect(historyManager.getProjects().length).toBe(beforeCount); + done(); }); }); @@ -202,13 +214,13 @@ 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(); }); }); - it('saves the state', async () => { + it('saves the state', async (done) => { await historyManager.addProject(['/save/state']); await historyManager.saveState(); const historyManager2 = new HistoryManager({ @@ -216,11 +228,12 @@ describe('HistoryManager', () => { project, commands: commandRegistry }); - spyOn(historyManager2.stateStore, 'load').andCallFake(name => + spyOn(historyManager2.stateStore, 'load').and.callFake(name => Promise.resolve(savedHistory) ); await historyManager2.loadState(); expect(historyManager2.getProjects()[0].paths).toEqual(['/save/state']); + done(); }); }); }); 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..58926c20a6 100644 --- a/spec/native-watcher-registry-spec.js +++ b/spec/native-watcher-registry-spec.js @@ -89,7 +89,7 @@ describe('NativeWatcherRegistry', function() { ); }); - it('attaches a Watcher to a newly created NativeWatcher for a new directory', async function() { + it('attaches a Watcher to a newly created NativeWatcher for a new directory', async function(done) { const watcher = new MockWatcher(absolute('some', 'path')); const NATIVE = new MockNative('created'); createNative = () => NATIVE; @@ -97,10 +97,12 @@ describe('NativeWatcherRegistry', function() { await registry.attach(watcher); expect(watcher.native).toBe(NATIVE); + + done(); }); - it('reuses an existing NativeWatcher on the same directory', async function() { - this.RETRY_FLAKY_TEST_AND_SLOW_DOWN_THE_BUILD(); + it('reuses an existing NativeWatcher on the same directory', async function(done) { + // this.RETRY_FLAKY_TEST_AND_SLOW_DOWN_THE_BUILD(); const EXISTING = new MockNative('existing'); const existingPath = absolute('existing', 'path'); @@ -119,9 +121,11 @@ describe('NativeWatcherRegistry', function() { await registry.attach(watcher); expect(watcher.native).toBe(EXISTING); + + done(); }); - it('attaches to an existing NativeWatcher on a parent directory', async function() { + it('attaches to an existing NativeWatcher on a parent directory', async function(done) { const EXISTING = new MockNative('existing'); const parentDir = absolute('existing', 'path'); const subDir = path.join(parentDir, 'sub', 'directory'); @@ -140,9 +144,10 @@ describe('NativeWatcherRegistry', function() { await registry.attach(watcher); expect(watcher.native).toBe(EXISTING); + done(); }); - it('adopts Watchers from NativeWatchers on child directories', async function() { + it('adopts Watchers from NativeWatchers on child directories', async function(done) { const parentDir = absolute('existing', 'path'); const childDir0 = path.join(parentDir, 'child', 'directory', 'zero'); const childDir1 = path.join(parentDir, 'child', 'directory', 'one'); @@ -197,10 +202,12 @@ describe('NativeWatcherRegistry', function() { expect(watcher2.native).toBe(OTHER); expect(OTHER.stopped).toBe(false); expect(OTHER.disposed).toBe(false); + + done(); }); describe('removing NativeWatchers', function() { - it('happens when they stop', async function() { + it('happens when they stop', async function(done) { const STOPPED = new MockNative('stopped'); const RUNNING = new MockNative('running'); @@ -252,9 +259,11 @@ describe('NativeWatcherRegistry', function() { children: () => false }); expect(stoppedNode).toBe(true); + + done(); }); - it('reassigns new child watchers when a parent watcher is stopped', async function() { + it('reassigns new child watchers when a parent watcher is stopped', async function(done) { const CHILD0 = new MockNative('child0'); const CHILD1 = new MockNative('child1'); const PARENT = new MockNative('parent'); @@ -319,9 +328,11 @@ describe('NativeWatcherRegistry', function() { children: () => false }) ).toBe(true); + + done(); }); - it('consolidates children when splitting a parent watcher', async function() { + it('consolidates children when splitting a parent watcher', async function(done) { const CHILD0 = new MockNative('child0'); const PARENT = new MockNative('parent'); @@ -384,6 +395,8 @@ describe('NativeWatcherRegistry', function() { children: () => false }) ).toBe(true); + + done(); }); }); }); 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 bd97eac9fc..8751a29434 100644 --- a/spec/package-manager-spec.js +++ b/spec/package-manager-spec.js @@ -81,9 +81,10 @@ describe('PackageManager', () => { describe('::loadPackages()', () => { beforeEach(() => spyOn(atom.packages, 'loadAvailablePackage')); - afterEach(async () => { + afterEach(async (done) => { await atom.packages.deactivatePackages(); atom.packages.unloadPackages(); + done(); }); it('sets hasLoadedInitialPackages', () => { @@ -103,14 +104,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 +120,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 +177,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', () => { @@ -228,9 +229,10 @@ describe('PackageManager', () => { const model1 = { worksWithViewProvider1: true }; const model2 = { worksWithViewProvider2: true }; - afterEach(async () => { + afterEach(async (done) => { await atom.packages.deactivatePackage('package-with-view-providers'); atom.packages.unloadPackage('package-with-view-providers'); + done(); }); it('does not load the view providers immediately', () => { @@ -241,7 +243,7 @@ describe('PackageManager', () => { expect(() => atom.views.getView(model2)).toThrow(); }); - it('registers the view providers when the package is activated', async () => { + it('registers the view providers when the package is activated', async (done) => { atom.packages.loadPackage('package-with-view-providers'); await atom.packages.activatePackage('package-with-view-providers'); @@ -253,23 +255,24 @@ describe('PackageManager', () => { const element2 = atom.views.getView(model2); expect(element2 instanceof HTMLDivElement).toBe(true); expect(element2.dataset.createdBy).toBe('view-provider-2'); + done(); }); 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); @@ -555,7 +558,7 @@ describe('PackageManager', () => { describe('::unloadPackage(name)', () => { describe('when the package is active', () => { - it('throws an error', async () => { + it('throws an error', async (done) => { const pack = await atom.packages.activatePackage('package-with-main'); expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy(); expect(atom.packages.isPackageActive(pack.name)).toBeTruthy(); @@ -563,6 +566,7 @@ describe('PackageManager', () => { expect(() => atom.packages.unloadPackage(pack.name)).toThrow(); expect(atom.packages.isPackageLoaded(pack.name)).toBeTruthy(); expect(atom.packages.isPackageActive(pack.name)).toBeTruthy(); + done(); }); }); @@ -596,34 +600,36 @@ 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(); + it('it only calls activate on the package once', async (done) => { + 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); + done(); }); }); 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˜', () => { - it('requires the module at the specified path', async () => { + it('requires the module at the specified path', async (done) => { const mainModule = require('./fixtures/packages/package-with-main/main-module'); spyOn(mainModule, 'activate'); const pack = await atom.packages.activatePackage('package-with-main'); expect(mainModule.activate).toHaveBeenCalled(); expect(pack.mainModule).toBe(mainModule); + done(); }); }); describe('when the metadata does not specify a main module', () => { - it('requires index.coffee', async () => { + it('requires index.coffee', async (done) => { const indexModule = require('./fixtures/packages/package-with-index/index'); spyOn(indexModule, 'activate'); @@ -632,10 +638,11 @@ describe('PackageManager', () => { ); expect(indexModule.activate).toHaveBeenCalled(); expect(pack.mainModule).toBe(indexModule); + done(); }); }); - it('assigns config schema, including defaults when package contains a schema', async () => { + it('assigns config schema, including defaults when package contains a schema', async (done) => { expect( atom.config.get('package-with-config-schema.numbers.one') ).toBeUndefined(); @@ -656,6 +663,7 @@ describe('PackageManager', () => { expect(atom.config.get('package-with-config-schema.numbers.one')).toBe( 10 ); + done(); }); describe('when the package metadata includes `activationCommands`', () => { @@ -665,7 +673,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' @@ -688,8 +696,8 @@ describe('PackageManager', () => { mainModule = null; }); - it('defers requiring/activating the main module until an activation event bubbles to the root view', async () => { - expect(Package.prototype.requireMainModule.callCount).toBe(0); + it('defers requiring/activating the main module until an activation event bubbles to the root view', async (done) => { + expect(Package.prototype.requireMainModule.calls.count()).toBe(0); atom.workspace .getElement() @@ -698,10 +706,11 @@ describe('PackageManager', () => { ); await promise; - expect(Package.prototype.requireMainModule.callCount).toBe(1); + expect(Package.prototype.requireMainModule.calls.count()).toBe(1); + done(); }); - it('triggers the activation event on all handlers registered during activation', async () => { + it('triggers the activation event on all handlers registered during activation', async (done) => { await atom.workspace.open(); const editorElement = atom.workspace @@ -717,31 +726,35 @@ 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); + + done(); }); - it('activates the package immediately when the events are empty', async () => { + it('activates the package immediately when the events are empty', async (done) => { 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); + + done(); }); 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,32 +762,32 @@ 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' ); }); - it('adds a notification when the grammar is invalid', async () => { + it('adds a notification when the grammar is invalid', async (done) => { let notificationEvent; await new Promise(resolve => { @@ -795,9 +808,11 @@ describe('PackageManager', () => { expect(notificationEvent.options.packageName).toEqual( 'package-with-invalid-grammar' ); + + done(); }); - it('adds a notification when the settings are invalid', async () => { + it('adds a notification when the settings are invalid', async (done) => { let notificationEvent; await new Promise(resolve => { @@ -818,6 +833,8 @@ describe('PackageManager', () => { expect(notificationEvent.options.packageName).toEqual( 'package-with-invalid-settings' ); + + done(); }); }); @@ -826,10 +843,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' ); @@ -851,8 +868,8 @@ describe('PackageManager', () => { mainModule = null; }); - it('activates the package when a deserializer is called', async () => { - expect(Package.prototype.requireMainModule.callCount).toBe(0); + it('activates the package when a deserializer is called', async (done) => { + expect(Package.prototype.requireMainModule.calls.count()).toBe(0); const state1 = { deserializer: 'Deserializer1', a: 'b' }; expect(atom.deserializers.deserialize(state1, atom)).toEqual({ @@ -861,11 +878,13 @@ describe('PackageManager', () => { }); await promise; - expect(Package.prototype.requireMainModule.callCount).toBe(1); + expect(Package.prototype.requireMainModule.calls.count()).toBe(1); + + done(); }); - it('defers requiring/activating the main module until an activation event bubbles to the root view', async () => { - expect(Package.prototype.requireMainModule.callCount).toBe(0); + it('defers requiring/activating the main module until an activation event bubbles to the root view', async (done) => { + expect(Package.prototype.requireMainModule.calls.count()).toBe(0); atom.workspace .getElement() @@ -874,9 +893,11 @@ 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); + + done(); }); }); @@ -885,35 +906,37 @@ 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 () => { + it('defers requiring/activating the main module until an triggering of an activation hook occurs', async (done) => { 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); + + done(); }); - it('does not double register activation hooks when deactivating and reactivating', async () => { + it('does not double register activation hooks when deactivating and reactivating', async (done) => { 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,32 +951,38 @@ describe('PackageManager', () => { atom.packages.triggerDeferredActivationHooks(); await promise; - expect(mainModule.activate.callCount).toBe(2); + expect(mainModule.activate.calls.count()).toBe(2); + + done(); }); - it('activates the package immediately when activationHooks is empty', async () => { + it('activates the package immediately when activationHooks is empty', async (done) => { 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); + + done(); }); - it('activates the package immediately if the activation hook had already been triggered', async () => { + it('activates the package immediately if the activation hook had already been triggered', async (done) => { atom.packages.triggerActivationHook( '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); + + done(); }); }); @@ -962,28 +991,32 @@ 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 () => { + it('defers requiring/activating the main module until a registered opener is called', async (done) => { 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); + + done(); }); - it('activates the package immediately when the events are empty', async () => { + it('activates the package immediately when the events are empty', async (done) => { 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); + + done(); }); }); }); @@ -991,7 +1024,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(); @@ -1001,14 +1034,16 @@ describe('PackageManager', () => { }); describe('when the package does not export an activate function', () => { - it('activates the package and does not throw an exception or log a warning', async () => { + it('activates the package and does not throw an exception or log a warning', async (done) => { spyOn(console, 'warn'); await atom.packages.activatePackage('package-with-no-activate'); expect(console.warn).not.toHaveBeenCalled(); + + done(); }); }); - it("passes the activate method the package's previously serialized state if it exists", async () => { + it("passes the activate method the package's previously serialized state if it exists", async (done) => { const pack = await atom.packages.activatePackage( 'package-with-serialization' ); @@ -1017,12 +1052,14 @@ 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 }); + + done(); }); - it('invokes ::onDidActivatePackage listeners with the activated package', async () => { + it('invokes ::onDidActivatePackage listeners with the activated package', async (done) => { let activatedPackage; atom.packages.onDidActivatePackage(pack => { activatedPackage = pack; @@ -1030,22 +1067,24 @@ describe('PackageManager', () => { await atom.packages.activatePackage('package-with-main'); expect(activatedPackage.name).toBe('package-with-main'); + + done(); }); 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,12 +1093,12 @@ 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'); }); }); describe('when the package is not found', () => { - it('rejects the promise', async () => { + it('rejects the promise', async (done) => { spyOn(console, 'warn'); atom.config.set('core.disabledPackages', []); @@ -1067,17 +1106,19 @@ 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'" ); } + + done(); }); }); describe('keymap loading', () => { describe("when the metadata does not contain a 'keymaps' manifest", () => { - it('loads all the .cson/.json files in the keymaps directory', async () => { + it('loads all the .cson/.json files in the keymaps directory', async (done) => { const element1 = createTestElement('test-1'); const element2 = createTestElement('test-2'); const element3 = createTestElement('test-3'); @@ -1119,11 +1160,13 @@ describe('PackageManager', () => { target: element3 }) ).toHaveLength(0); + + done(); }); }); describe("when the metadata contains a 'keymaps' manifest", () => { - it('loads only the keymaps specified by the manifest, in the specified order', async () => { + it('loads only the keymaps specified by the manifest, in the specified order', async (done) => { const element1 = createTestElement('test-1'); const element3 = createTestElement('test-3'); expect( @@ -1152,20 +1195,24 @@ describe('PackageManager', () => { target: element3 }) ).toHaveLength(0); + + done(); }); }); describe('when the keymap file is empty', () => { - it('does not throw an error on activation', async () => { + it('does not throw an error on activation', async (done) => { await atom.packages.activatePackage('package-with-empty-keymap'); expect( atom.packages.isPackageActive('package-with-empty-keymap') ).toBe(true); + + done(); }); }); describe("when the package's keymaps have been disabled", () => { - it('does not add the keymaps', async () => { + it('does not add the keymaps', async (done) => { const element1 = createTestElement('test-1'); expect( atom.keymaps.findKeyBindings({ @@ -1184,6 +1231,8 @@ describe('PackageManager', () => { target: element1 }) ).toHaveLength(0); + + done(); }); }); @@ -1203,7 +1252,7 @@ describe('PackageManager', () => { }); describe("when the package's keymaps are disabled and re-enabled after it is activated", () => { - it('removes and re-adds the keymaps', async () => { + it('removes and re-adds the keymaps', async (done) => { const element1 = createTestElement('test-1'); atom.packages.observePackagesWithKeymapsDisabled(); @@ -1226,6 +1275,8 @@ describe('PackageManager', () => { target: element1 })[0].command ).toBe('keymap-1'); + + done(); }); }); @@ -1234,7 +1285,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); @@ -1254,7 +1305,7 @@ describe('PackageManager', () => { temp.cleanupSync(); }); - it("doesn't override user-defined keymaps", async () => { + it("doesn't override user-defined keymaps", async (done) => { fs.writeFileSync( userKeymapPath, `".test-1": {"ctrl-z": "user-command"}` @@ -1275,6 +1326,8 @@ describe('PackageManager', () => { ); expect(events.length).toBe(2); expect(events[1].type).toBe('user-command'); + + done(); }); }); }); @@ -1286,7 +1339,7 @@ describe('PackageManager', () => { }); describe("when the metadata does not contain a 'menus' manifest", () => { - it('loads all the .cson/.json files in the menus directory', async () => { + it('loads all the .cson/.json files in the menus directory', async (done) => { const element = createTestElement('test-1'); expect(atom.contextMenu.templateForElement(element)).toEqual([]); @@ -1303,11 +1356,13 @@ describe('PackageManager', () => { expect(atom.contextMenu.templateForElement(element)[2].label).toBe( 'Menu item 3' ); + + done(); }); }); describe("when the metadata contains a 'menus' manifest", () => { - it('loads only the menus specified by the manifest, in the specified order', async () => { + it('loads only the menus specified by the manifest, in the specified order', async (done) => { const element = createTestElement('test-1'); expect(atom.contextMenu.templateForElement(element)).toEqual([]); @@ -1323,22 +1378,26 @@ describe('PackageManager', () => { expect( atom.contextMenu.templateForElement(element)[2] ).toBeUndefined(); + + done(); }); }); describe('when the menu file is empty', () => { - it('does not throw an error on activation', async () => { + it('does not throw an error on activation', async (done) => { await atom.packages.activatePackage('package-with-empty-menu'); expect(atom.packages.isPackageActive('package-with-empty-menu')).toBe( true ); + + done(); }); }); }); describe('stylesheet loading', () => { describe("when the metadata contains a 'styleSheets' manifest", () => { - it('loads style sheets from the styles directory as specified by the manifest', async () => { + it('loads style sheets from the styles directory as specified by the manifest', async (done) => { const one = require.resolve( './fixtures/packages/package-with-style-sheets-manifest/styles/1.css' ); @@ -1363,11 +1422,13 @@ describe('PackageManager', () => { getComputedStyle(document.querySelector('#jasmine-content')) .fontSize ).toBe('1px'); + + done(); }); }); describe("when the metadata does not contain a 'styleSheets' manifest", () => { - it('loads all style sheets from the styles directory', async () => { + it('loads all style sheets from the styles directory', async (done) => { const one = require.resolve( './fixtures/packages/package-with-styles/styles/1.css' ); @@ -1395,10 +1456,12 @@ describe('PackageManager', () => { getComputedStyle(document.querySelector('#jasmine-content')) .fontSize ).toBe('3px'); + + done(); }); }); - it("assigns the stylesheet's context based on the filename", async () => { + it("assigns the stylesheet's context based on the filename", async (done) => { await atom.packages.activatePackage('package-with-styles'); let count = 0; @@ -1425,70 +1488,80 @@ describe('PackageManager', () => { } expect(count).toBe(4); + + done(); }); }); describe('grammar loading', () => { - it("loads the package's grammars", async () => { + it("loads the package's grammars", async (done) => { await atom.packages.activatePackage('package-with-grammars'); expect(atom.grammars.selectGrammar('a.alot').name).toBe('Alot'); expect(atom.grammars.selectGrammar('a.alittle').name).toBe('Alittle'); + + done(); }); - it('loads any tree-sitter grammars defined in the package', async () => { + it('loads any tree-sitter grammars defined in the package', async (done) => { atom.config.set('core.useTreeSitterParsers', true); await atom.packages.activatePackage('package-with-tree-sitter-grammar'); const grammar = atom.grammars.selectGrammar('test.somelang'); expect(grammar.name).toBe('Some Language'); await grammar.getQuery('highlightsQuery'); expect(grammar.highlightsQuery.includes('(empty)')).toBe(true); + + done(); }); }); describe('scoped-property loading', () => { - it('loads the scoped properties', async () => { + it('loads the scoped properties', async (done) => { await atom.packages.activatePackage('package-with-settings'); expect( atom.config.get('editor.increaseIndentPattern', { scope: ['.source.omg'] }) ).toBe('^a'); + + done(); }); }); describe('URI handler registration', () => { - it("registers the package's specified URI handler", async () => { + it("registers the package's specified URI handler", async (done) => { 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' ); atom.dispatchURIMessage(uri); await activationPromise; expect(mod.handleURI).toHaveBeenCalledWith(url.parse(uri, true), uri); + + done(); }); }); describe('service registration', () => { - it("registers the package's provided and consumed services", async () => { + it("registers the package's provided and consumed services", async (done) => { const consumerModule = require('./fixtures/packages/package-with-consumed-services'); 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 +1569,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 +1580,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); @@ -1521,9 +1594,11 @@ describe('PackageManager', () => { expect(consumerModule.consumeFirstServiceV3).not.toHaveBeenCalled(); expect(consumerModule.consumeFirstServiceV4).not.toHaveBeenCalled(); expect(consumerModule.consumeSecondService).not.toHaveBeenCalled(); + + done(); }); - it('ignores provided and consumed services that do not exist', async () => { + it('ignores provided and consumed services that do not exist', async (done) => { const addErrorHandler = jasmine.createSpy(); atom.notifications.onDidAddNotification(addErrorHandler); @@ -1543,26 +1618,30 @@ describe('PackageManager', () => { 'package-with-missing-provided-services' ) ).toBe(true); - expect(addErrorHandler.callCount).toBe(0); + expect(addErrorHandler.calls.count()).toBe(0); + + done(); }); }); }); describe('::serialize', () => { - it('does not serialize packages that threw an error during activation', async () => { - spyOn(atom, 'inSpecMode').andReturn(false); + it('does not serialize packages that threw an error during activation', async (done) => { + 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(); + + done(); }); - it("absorbs exceptions that are thrown by the package module's serialize method", async () => { + it("absorbs exceptions that are thrown by the package module's serialize method", async (done) => { spyOn(console, 'error'); await atom.packages.activatePackage('package-with-serialize-error'); @@ -1575,11 +1654,13 @@ describe('PackageManager', () => { { someNumber: 1 } ); expect(console.error).toHaveBeenCalled(); + + done(); }); }); describe('::deactivatePackages()', () => { - it('deactivates all packages but does not serialize them', async () => { + it('deactivates all packages but does not serialize them', async (done) => { const pack1 = await atom.packages.activatePackage( 'package-with-deactivate' ); @@ -1592,14 +1673,16 @@ describe('PackageManager', () => { await atom.packages.deactivatePackages(); expect(pack1.mainModule.deactivate).toHaveBeenCalled(); expect(pack2.mainModule.serialize).not.toHaveBeenCalled(); + + done(); }); }); describe('::deactivatePackage(id)', () => { afterEach(() => atom.packages.unloadPackages()); - it("calls `deactivate` on the package's main module if activate was successful", async () => { - spyOn(atom, 'inSpecMode').andReturn(false); + it("calls `deactivate` on the package's main module if activate was successful", async (done) => { + spyOn(atom, 'inSpecMode').and.returnValue(false); const pack = await atom.packages.activatePackage( 'package-with-deactivate' @@ -1607,7 +1690,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,34 +1703,40 @@ 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(); expect( atom.packages.isPackageActive('package-that-throws-on-activate') ).toBeFalsy(); + + done(); }); - it("absorbs exceptions that are thrown by the package module's deactivate method", async () => { + it("absorbs exceptions that are thrown by the package module's deactivate method", async (done) => { spyOn(console, 'error'); await atom.packages.activatePackage('package-that-throws-on-deactivate'); await atom.packages.deactivatePackage( 'package-that-throws-on-deactivate' ); expect(console.error).toHaveBeenCalled(); + + done(); }); - it("removes the package's grammars", async () => { + it("removes the package's grammars", async (done) => { await atom.packages.activatePackage('package-with-grammars'); await atom.packages.deactivatePackage('package-with-grammars'); expect(atom.grammars.selectGrammar('a.alot').name).toBe('Null Grammar'); expect(atom.grammars.selectGrammar('a.alittle').name).toBe( 'Null Grammar' ); + + done(); }); - it("removes the package's keymaps", async () => { + it("removes the package's keymaps", async (done) => { await atom.packages.activatePackage('package-with-keymaps'); await atom.packages.deactivatePackage('package-with-keymaps'); expect( @@ -1662,9 +1751,11 @@ describe('PackageManager', () => { target: createTestElement('test-2') }) ).toHaveLength(0); + + done(); }); - it("removes the package's stylesheets", async () => { + it("removes the package's stylesheets", async (done) => { await atom.packages.activatePackage('package-with-styles'); await atom.packages.deactivatePackage('package-with-styles'); @@ -1680,9 +1771,11 @@ describe('PackageManager', () => { expect(atom.themes.stylesheetElementForId(one)).not.toExist(); expect(atom.themes.stylesheetElementForId(two)).not.toExist(); expect(atom.themes.stylesheetElementForId(three)).not.toExist(); + + done(); }); - it("removes the package's scoped-properties", async () => { + it("removes the package's scoped-properties", async (done) => { await atom.packages.activatePackage('package-with-settings'); expect( atom.config.get('editor.increaseIndentPattern', { @@ -1696,9 +1789,11 @@ describe('PackageManager', () => { scope: ['.source.omg'] }) ).toBeUndefined(); + + done(); }); - it('invokes ::onDidDeactivatePackage listeners with the deactivated package', async () => { + it('invokes ::onDidDeactivatePackage listeners with the deactivated package', async (done) => { await atom.packages.activatePackage('package-with-main'); let deactivatedPackage; @@ -1708,12 +1803,14 @@ describe('PackageManager', () => { await atom.packages.deactivatePackage('package-with-main'); expect(deactivatedPackage.name).toBe('package-with-main'); + + done(); }); }); describe('::activate()', () => { beforeEach(() => { - spyOn(atom, 'inSpecMode').andReturn(false); + spyOn(atom, 'inSpecMode').and.returnValue(false); jasmine.snapshotDeprecations(); spyOn(console, 'warn'); atom.packages.loadPackages(); @@ -1722,19 +1819,23 @@ describe('PackageManager', () => { expect(loadedPackages.length).toBeGreaterThan(0); }); - afterEach(async () => { + afterEach(async (done) => { await atom.packages.deactivatePackages(); atom.packages.unloadPackages(); jasmine.restoreDeprecationsSnapshot(); + + done(); }); - it('sets hasActivatedInitialPackages', async () => { - spyOn(atom.styles, 'getUserStyleSheetPath').andReturn(null); + it('sets hasActivatedInitialPackages', async (done) => { + spyOn(atom.styles, 'getUserStyleSheetPath').and.returnValue(null); spyOn(atom.packages, 'activatePackages'); expect(atom.packages.hasActivatedInitialPackages()).toBe(false); await atom.packages.activate(); expect(atom.packages.hasActivatedInitialPackages()).toBe(true); + + done(); }); it('activates all the packages, and none of the themes', () => { @@ -1746,22 +1847,22 @@ 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())); }); - it('calls callbacks registered with ::onDidActivateInitialPackages', async () => { + it('calls callbacks registered with ::onDidActivateInitialPackages', async (done) => { const package1 = atom.packages.loadPackage('package-with-main'); const package2 = atom.packages.loadPackage('package-with-index'); const package3 = atom.packages.loadPackage( 'package-with-activation-commands' ); - spyOn(atom.packages, 'getLoadedPackages').andReturn([ + spyOn(atom.packages, 'getLoadedPackages').and.returnValue([ package1, package2, package3 @@ -1777,12 +1878,14 @@ describe('PackageManager', () => { expect(atom.packages.getActivePackages().includes(package1)).toBe(true); expect(atom.packages.getActivePackages().includes(package2)).toBe(true); expect(atom.packages.getActivePackages().includes(package3)).toBe(false); + + done(); }); }); describe('::enablePackage(id) and ::disablePackage(id)', () => { describe('with packages', () => { - it('enables a disabled package', async () => { + it('enables a disabled package', async (done) => { const packageName = 'package-with-main'; atom.config.pushAtKeyPath('core.disabledPackages', packageName); atom.packages.observeDisabledPackages(); @@ -1798,9 +1901,11 @@ describe('PackageManager', () => { expect(atom.config.get('core.disabledPackages')).not.toContain( packageName ); + + done(); }); - it('disables an enabled package', async () => { + it('disables an enabled package', async (done) => { const packageName = 'package-with-main'; const pack = await atom.packages.activatePackage(packageName); @@ -1815,12 +1920,14 @@ describe('PackageManager', () => { expect(atom.packages.getActivePackages()).not.toContain(pack); expect(atom.config.get('core.disabledPackages')).toContain(packageName); + + done(); }); 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', () => { @@ -1841,7 +1948,7 @@ describe('PackageManager', () => { beforeEach(() => atom.themes.activateThemes()); afterEach(() => atom.themes.deactivateThemes()); - it('enables and disables a theme', async () => { + it('enables and disables a theme', async (done) => { const packageName = 'theme-with-package-file'; expect(atom.config.get('core.themes')).not.toContain(packageName); expect(atom.config.get('core.disabledPackages')).not.toContain( @@ -1870,6 +1977,8 @@ describe('PackageManager', () => { expect(atom.config.get('core.disabledPackages')).not.toContain( packageName ); + + done(); }); }); }); diff --git a/spec/package-spec.js b/spec/package-spec.js index 9d7b4a959d..0bc64a5cb6 100644 --- a/spec/package-spec.js +++ b/spec/package-spec.js @@ -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(done) { 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,13 @@ 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' + }); + + done(); }); it('persists build failures in local storage', function() { @@ -139,7 +136,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 +161,20 @@ 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 (done) => { + if (theme != null) { + await theme.deactivate(); + } + + done(); + }); describe('when the theme contains a single style file', function() { it('loads and applies css', function() { @@ -262,11 +259,13 @@ 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(done) { + let spy = jasmine.createSpy(); + theme.onDidDeactivate(spy); + await theme.deactivate(); + expect(spy).toHaveBeenCalled(); + + done(); }); }); }); @@ -297,7 +296,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..7dae8a0ae6 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(done) { const leftPane = container.getActivePane(); expectPaneScale([leftPane, 1]); @@ -175,14 +175,16 @@ 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]); + + done(); }); - 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(done) { const leftPane = container.getActivePane(); expectPaneScale([leftPane, 1]); @@ -204,32 +206,46 @@ 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]); + + done(); }); - 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..cc9e854523 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 = { @@ -285,18 +285,22 @@ describe('PaneContainer', () => { pane2.addItem(new TestItem()); }); - it('returns true if the user saves all modified files when prompted', async () => { - confirm.andCallFake((options, callback) => callback(0)); + it('returns true if the user saves all modified files when prompted', async (done) => { + confirm.and.callFake((options, callback) => callback(0)); const saved = await container.confirmClose(); expect(confirm).toHaveBeenCalled(); expect(saved).toBeTruthy(); + + done(); }); - it('returns false if the user cancels saving any modified file', async () => { - confirm.andCallFake((options, callback) => callback(1)); + it('returns false if the user cancels saving any modified file', async (done) => { + confirm.and.callFake((options, callback) => callback(1)); const saved = await container.confirmClose(); expect(confirm).toHaveBeenCalled(); expect(saved).toBeFalsy(); + + done(); }); }); @@ -392,7 +396,7 @@ describe('PaneContainer', () => { }); describe('::onWillDestroyPaneItem() and ::onDidDestroyPaneItem()', () => { - it('invokes the given callbacks when an item will be destroyed on any pane', async () => { + it('invokes the given callbacks when an item will be destroyed on any pane', async (done) => { const container = new PaneContainer(params); const pane1 = container.getRoot(); const item1 = {}; @@ -440,11 +444,13 @@ describe('PaneContainer', () => { expect(events[4][1].pane).toEqual(pane2); expect(events[4][1].index).toEqual(0); expect(typeof events[4][1].prevent).toEqual('function'); + + done(); }); }); describe('::saveAll()', () => - it('saves all modified pane items', async () => { + it('saves all modified pane items', async (done) => { const container = new PaneContainer(params); const pane1 = container.getRoot(); pane1.splitRight(); @@ -495,6 +501,8 @@ describe('PaneContainer', () => { expect(item1.saved).toBe(true); expect(item2.saved).toBe(false); expect(item3.saved).toBe(true); + + done(); })); describe('::moveActiveItemToPane(destPane) and ::copyActiveItemToPane(destPane)', () => { 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 81e6cb76d3..d8d58e4420 100644 --- a/spec/pane-spec.js +++ b/spec/pane-spec.js @@ -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 = { @@ -394,7 +392,7 @@ describe('Pane', () => { expect(callbackCalled).toBeTruthy(); }); - it("isn't called when a pending item is replaced with a new one", async () => { + it("isn't called when a pending item is replaced with a new one", async (done) => { pane = null; const pendingSpy = jasmine.createSpy('onItemDidTerminatePendingState'); const destroySpy = jasmine.createSpy('onWillDestroyItem'); @@ -409,6 +407,8 @@ describe('Pane', () => { expect(destroySpy).toHaveBeenCalled(); expect(pendingSpy).not.toHaveBeenCalled(); + + done(); }); }); @@ -594,7 +594,7 @@ describe('Pane', () => { expect(pane.getActiveItem()).toBe(item1); }); - it('invokes ::onWillDestroyItem() and PaneContainer::onWillDestroyPaneItem observers before destroying the item', async () => { + it('invokes ::onWillDestroyItem() and PaneContainer::onWillDestroyPaneItem observers before destroying the item', async (done) => { jasmine.useRealClock(); pane.container = new PaneContainer({ config: atom.config, confirm }); const events = []; @@ -625,6 +625,8 @@ describe('Pane', () => { expect(events[1][1].index).toEqual(1); expect(typeof events[1][1].prevent).toEqual('function'); expect(events[1][1].pane).toEqual(pane); + + done(); }); it('invokes ::onWillRemoveItem() observers', () => { @@ -675,72 +677,82 @@ describe('Pane', () => { describe('if the [Save] option is selected', () => { describe('when the item has a uri', () => { - it('saves the item before destroying it', async () => { + it('saves the item before destroying it', async (done) => { 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(); expect(pane.getItems().includes(item1)).toBe(false); expect(item1.isDestroyed()).toBe(true); expect(success).toBe(true); + + done(); }); }); describe('when the item has no uri', () => { - it('presents a save-as dialog, then saves the item with the given uri before removing and destroying it', async () => { + it('presents a save-as dialog, then saves the item with the given uri before removing and destroying it', async (done) => { jasmine.useRealClock(); 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); expect(success).toBe(true); + + done(); }); }); }); 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)); + it('removes and destroys the item without saving it', async (done) => { + confirm.and.callFake((options, callback) => callback(2)); const success = await pane.destroyItem(item1); expect(item1.save).not.toHaveBeenCalled(); expect(pane.getItems().includes(item1)).toBe(false); expect(item1.isDestroyed()).toBe(true); expect(success).toBe(true); + + done(); }); }); describe('if the [Cancel] option is selected', () => { - it('does not save, remove, or destroy the item', async () => { - confirm.andCallFake((options, callback) => callback(1)); + it('does not save, remove, or destroy the item', async (done) => { + confirm.and.callFake((options, callback) => callback(1)); const success = await pane.destroyItem(item1); expect(item1.save).not.toHaveBeenCalled(); expect(pane.getItems().includes(item1)).toBe(true); expect(item1.isDestroyed()).toBe(false); expect(success).toBe(false); + + done(); }); }); describe('when force=true', () => { - it('destroys the item immediately', async () => { + it('destroys the item immediately', async (done) => { const success = await pane.destroyItem(item1, true); expect(item1.save).not.toHaveBeenCalled(); expect(pane.getItems().includes(item1)).toBe(false); expect(item1.isDestroyed()).toBe(true); expect(success).toBe(true); + + done(); }); }); }); @@ -771,20 +783,24 @@ describe('Pane', () => { }); describe('when passed a permanent dock item', () => { - it("doesn't destroy the item", async () => { - spyOn(item1, 'isPermanentDockItem').andReturn(true); + it("doesn't destroy the item", async (done) => { + spyOn(item1, 'isPermanentDockItem').and.returnValue(true); const success = await pane.destroyItem(item1); expect(pane.getItems().includes(item1)).toBe(true); expect(item1.isDestroyed()).toBe(false); expect(success).toBe(false); + + done(); }); - it('destroy the item if force=true', async () => { - spyOn(item1, 'isPermanentDockItem').andReturn(true); + it('destroy the item if force=true', async (done) => { + 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); expect(success).toBe(true); + + done(); }); }); }); @@ -807,7 +823,7 @@ describe('Pane', () => { }); describe('::destroyItems()', () => { - it('destroys all items', async () => { + it('destroys all items', async (done) => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B'), new Item('C')] }) ); @@ -818,6 +834,8 @@ describe('Pane', () => { expect(item2.isDestroyed()).toBe(true); expect(item3.isDestroyed()).toBe(true); expect(pane.getItems()).toEqual([]); + + done(); }); }); @@ -864,7 +882,7 @@ describe('Pane', () => { beforeEach(() => { pane = new Pane(paneParams({ items: [new Item('A')] })); - showSaveDialog.andCallFake((options, callback) => + showSaveDialog.and.callFake((options, callback) => callback('/selected/path') ); }); @@ -892,34 +910,40 @@ describe('Pane', () => { describe('when the current item has no uri', () => { describe('when the current item has a saveAs method', () => { - it('opens a save dialog and saves the current item as the selected path', async () => { + it('opens a save dialog and saves the current item as the selected path', async (done) => { 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' ); + + done(); }); }); describe('when the current item has no saveAs method', () => { - it('does nothing', async () => { + it('does nothing', async (done) => { expect(pane.getActiveItem().saveAs).toBeUndefined(); await pane.saveActiveItem(); expect(showSaveDialog).not.toHaveBeenCalled(); + + done(); }); }); - it('does nothing if the user cancels choosing a path', async () => { + it('does nothing if the user cancels choosing a path', async (done) => { 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(); + + done(); }); }); 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 +951,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 +971,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,28 +988,30 @@ describe('Pane', () => { beforeEach(() => { pane = new Pane(paneParams({ items: [new Item('A')] })); - showSaveDialog.andCallFake((options, callback) => + showSaveDialog.and.callFake((options, callback) => callback('/selected/path') ); }); describe('when the current item has a saveAs method', () => { - it('opens the save dialog and calls saveAs on the item with the selected path', async () => { + it('opens the save dialog and calls saveAs on the item with the selected path', async (done) => { jasmine.useRealClock(); 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' ); + + done(); }); }); @@ -1006,7 +1024,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 +1032,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(); }); }); }); @@ -1373,7 +1387,7 @@ describe('Pane', () => { }); describe('::close()', () => { - it('prompts to save unsaved items before destroying the pane', async () => { + it('prompts to save unsaved items before destroying the pane', async (done) => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B')] }) ); @@ -1383,14 +1397,16 @@ 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(); expect(pane.isDestroyed()).toBe(true); + + done(); }); - it('does not destroy the pane if the user clicks cancel', async () => { + it('does not destroy the pane if the user clicks cancel', async (done) => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B')] }) ); @@ -1400,15 +1416,17 @@ 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(); expect(item1.save).not.toHaveBeenCalled(); expect(pane.isDestroyed()).toBe(false); + + done(); }); - it('does not destroy the pane if the user starts to save but then does not choose a path', async () => { + it('does not destroy the pane if the user starts to save but then does not choose a path', async (done) => { const pane = new Pane( paneParams({ items: [new Item('A'), new Item('B')] }) ); @@ -1417,14 +1435,16 @@ 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); + + done(); }); describe('when item fails to save', () => { @@ -1441,7 +1461,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'; @@ -1449,9 +1469,9 @@ describe('Pane', () => { }); }); - it('does not destroy the pane if save fails and user clicks cancel', async () => { + it('does not destroy the pane if save fails and user clicks cancel', async (done) => { let confirmations = 0; - confirm.andCallFake((options, callback) => { + confirm.and.callFake((options, callback) => { confirmations++; if (confirmations === 1) { callback(0); // click save @@ -1465,32 +1485,36 @@ describe('Pane', () => { expect(confirmations).toBe(2); expect(item1.save).toHaveBeenCalled(); expect(pane.isDestroyed()).toBe(false); + + done(); }); - it('does destroy the pane if the user saves the file under a new name', async () => { - item1.saveAs = jasmine.createSpy('saveAs').andReturn(true); + it('does destroy the pane if the user saves the file under a new name', async (done) => { + 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(); expect(pane.isDestroyed()).toBe(true); + + done(); }); - it('asks again if the saveAs also fails', async () => { - item1.saveAs = jasmine.createSpy('saveAs').andCallFake(() => { + it('asks again if the saveAs also fails', async (done) => { + 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 +1522,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,17 +1531,19 @@ 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(); expect(pane.isDestroyed()).toBe(true); + + done(); }); }); }); @@ -1583,16 +1609,20 @@ describe('Pane', () => { describe('pending state', () => { let editor1, pane, eventCount; - beforeEach(async () => { + beforeEach(async (done) => { editor1 = await atom.workspace.open('sample.txt', { pending: true }); pane = atom.workspace.getActivePane(); eventCount = 0; editor1.onDidTerminatePendingState(() => eventCount++); + + done(); }); - it('does not open file in pending state by default', async () => { + it('does not open file in pending state by default', async (done) => { await atom.workspace.open('sample.js'); expect(pane.getPendingItem()).toBeNull(); + + done(); }); it("opens file in pending state if 'pending' option is true", () => { @@ -1614,7 +1644,7 @@ describe('Pane', () => { expect(eventCount).toBe(1); }); - it('only calls terminate handler once when text is modified twice', async () => { + it('only calls terminate handler once when text is modified twice', async (done) => { const originalText = editor1.getText(); editor1.insertText('Some text'); advanceClock(editor1.getBuffer().stoppedChangingDelay); @@ -1630,16 +1660,18 @@ describe('Pane', () => { // Reset fixture back to original state editor1.setText(originalText); await editor1.save(); + + done(); }); 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..ce1757d9a3 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 (done) => { const panel = createPanel(); const previousActiveElement = document.activeElement; const panelEl = panel.getElement(); @@ -263,10 +264,12 @@ 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); + + done(); }); }); }); 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/path-watcher-spec.js b/spec/path-watcher-spec.js index a2a9a4e6d9..d8892e5043 100644 --- a/spec/path-watcher-spec.js +++ b/spec/path-watcher-spec.js @@ -24,9 +24,11 @@ describe('watchPath', function() { subs = new CompositeDisposable(); }); - afterEach(async function() { + afterEach(async function(done) { subs.dispose(); await stopAllWatchers(); + + done(); }); function waitForChanges(watcher, ...fileNames) { @@ -52,23 +54,27 @@ describe('watchPath', function() { } describe('watchPath()', function() { - it('resolves the returned promise when the watcher begins listening', async function() { + it('resolves the returned promise when the watcher begins listening', async function(done) { const rootDir = await tempMkdir('atom-fsmanager-test-'); const watcher = await watchPath(rootDir, {}, () => {}); expect(watcher.constructor.name).toBe('PathWatcher'); + + done(); }); - it('reuses an existing native watcher and resolves getStartPromise immediately if attached to a running watcher', async function() { + it('reuses an existing native watcher and resolves getStartPromise immediately if attached to a running watcher', async function(done) { const rootDir = await tempMkdir('atom-fsmanager-test-'); const watcher0 = await watchPath(rootDir, {}, () => {}); const watcher1 = await watchPath(rootDir, {}, () => {}); expect(watcher0.native).toBe(watcher1.native); + + done(); }); - it("reuses existing native watchers even while they're still starting", async function() { + it("reuses existing native watchers even while they're still starting", async function(done) { const rootDir = await tempMkdir('atom-fsmanager-test-'); const [watcher0, watcher1] = await Promise.all([ @@ -76,9 +82,11 @@ describe('watchPath', function() { watchPath(rootDir, {}, () => {}) ]); expect(watcher0.native).toBe(watcher1.native); + + done(); }); - it("doesn't attach new watchers to a native watcher that's stopping", async function() { + it("doesn't attach new watchers to a native watcher that's stopping", async function(done) { const rootDir = await tempMkdir('atom-fsmanager-test-'); const watcher0 = await watchPath(rootDir, {}, () => {}); @@ -88,9 +96,11 @@ describe('watchPath', function() { const watcher1 = await watchPath(rootDir, {}, () => {}); expect(watcher1.native).not.toBe(native0); + + done(); }); - it('reuses an existing native watcher on a parent directory and filters events', async function() { + it('reuses an existing native watcher on a parent directory and filters events', async function(done) { const rootDir = await tempMkdir('atom-fsmanager-test-').then(realpath); const rootFile = path.join(rootDir, 'rootfile.txt'); const subDir = path.join(rootDir, 'subdir'); @@ -115,9 +125,11 @@ describe('watchPath', function() { const nextRootEvent = waitForChanges(rootWatcher, rootFile); await writeFile(rootFile, 'rootfile\n', { encoding: 'utf8' }); await nextRootEvent; + + done(); }); - it('adopts existing child watchers and filters events appropriately to them', async function() { + it('adopts existing child watchers and filters events appropriately to them', async function(done) { const parentDir = await tempMkdir('atom-fsmanager-test-').then(realpath); // Create the directory tree @@ -168,6 +180,8 @@ describe('watchPath', function() { subWatcherChanges1, parentWatcherChanges ]); + + done(); }); }); }); diff --git a/spec/project-spec.js b/spec/project-spec.js index a812418c73..0c8b67a5a6 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 (done) => { deserializedProject = new Project({ notificationManager: atom.notifications, packageManager: atom.packages, @@ -45,21 +42,17 @@ 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' + ]); + + done(); }); - it('does not deserialize paths that are now files', () => { + it('does not deserialize paths that are now files', async (done) => { const childPath = path.join(temp.mkdirSync('atom-spec-project'), 'child'); fs.mkdirSync(childPath); @@ -76,262 +69,208 @@ 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]); + + done(); }); - 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 (done) => { + 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 })); + + expect(deserializedProject.getBuffers().length).toBe(0); - runs(() => expect(deserializedProject.getBuffers().length).toBe(0)); + done(); }); - 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 (done) => { + 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); + + done(); }); - 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 (done) => { 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 })); + + expect(deserializedProject.getBuffers().length).toBe(0); - runs(() => expect(deserializedProject.getBuffers().length).toBe(0)); + done(); }); - 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 })); - runs(() => expect(deserializedProject.getBuffers().length).toBe(0)); + 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 (done) => { 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); + + done(); }); - it('deserializes buffers that have never been saved before', () => { + it('deserializes buffers that have never been saved before', async (done) => { 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'); + + done(); }); - 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 (done) => { + 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); + + done(); }); }); 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 (done) => { const tempFile = temp.openSync().path; atom.project.setPaths([]); expect(atom.project.getPaths()[0]).toBeUndefined(); - let editor = null; + let editor = await atom.workspace.open(); - waitsForPromise(() => - atom.workspace.open().then(o => { - editor = o; - }) - ); + await editor.saveAs(tempFile); - waitsForPromise(() => editor.saveAs(tempFile)); + expect(atom.project.getPaths()[0]).toBe(path.dirname(tempFile)) - runs(() => - expect(atom.project.getPaths()[0]).toBe(path.dirname(tempFile)) - ); + done(); }); }); @@ -382,51 +321,45 @@ 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 (done) => { + buffer = await atom.project.bufferForPath(path.join(__dirname, 'fixtures', 'sample.js')) + buffer.retain(); + + done(); + }); afterEach(() => buffer.release()); - it('emits save events on the main process', () => { + it('emits save events on the main process', async (done) => { 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()); + + done(); }); }); 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 (done) => { + editor = await atom.workspace.open(require.resolve('./fixtures/dir/a')); + + done(); + }); it('creates a warning notification', () => { let noteSpy; @@ -441,7 +374,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 +413,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 +426,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 +437,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 +499,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 +533,26 @@ 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 (done) => { // 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(); + + done(); }); }); @@ -639,77 +566,54 @@ 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 (done) => { + 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); + + done(); }); }); 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 (done) => { + let editor = await atom.workspace.open(absolutePath); + + expect(editor.buffer.getPath()).toBe(absolutePath); + expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer); - runs(() => { - expect(editor.buffer.getPath()).toBe(absolutePath); - expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer); - }); + done(); }); }); 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 (done) => { + 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(); + + done(); }); }); 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 (done) => { + let editor = await atom.workspace.open(); + + expect(editor.buffer.getPath()).toBeUndefined(); + expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer); - runs(() => { - expect(editor.buffer.getPath()).toBeUndefined(); - expect(newBufferHandler).toHaveBeenCalledWith(editor.buffer); - }); + done(); }); }); }); @@ -717,117 +621,93 @@ describe('Project', () => { describe('.bufferForPath(path)', () => { let buffer = null; - beforeEach(() => - waitsForPromise(() => - atom.project.bufferForPath('a').then(o => { - buffer = o; - buffer.retain(); - }) - ) - ); + beforeEach(async (done) => { + buffer = await atom.project.bufferForPath('a'); + buffer.retain(); + + done(); + }); 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 (done) => { + 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); + + done(); }); - 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 (done) => { + 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').then(() => done()) }); - it('creates a new buffer if the previous buffer was destroyed', () => { + it('creates a new buffer if the previous buffer was destroyed', async (done) => { buffer.release(); + expect(await atom.project.bufferForPath('b')).not.toBe(buffer); - waitsForPromise(() => - atom.project - .bufferForPath('b') - .then(anotherBuffer => expect(anotherBuffer).not.toBe(buffer)) - ); + done(); }); }); }); 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 (done) => { + 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); + + done(); }); - 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 (done) => { + const directory = new Directory(path.join(__dirname, '..')); - // Verify that the result is cached. - expect(atom.project.repositoryForDirectory(directory)).toBe(promise); - }); - }); + const promise = atom.project.repositoryForDirectory(directory); + const result = await 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); + + done(); }); - 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 (done) => { 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); + + expect(repository.isDestroyed()).toBe(false); - runs(() => expect(repository.isDestroyed()).toBe(false)); + done(); }); }); @@ -876,8 +756,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 +812,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 +987,43 @@ describe('Project', () => { }); describe('.onDidAddBuffer()', () => { - it('invokes the callback with added text buffers', () => { + it('invokes the callback with added text buffers', async (done) => { 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]]); + + done(); }); }); describe('.observeBuffers()', () => { - it('invokes the observer with current and future text buffers', () => { + it('invokes the observer with current and future text buffers', async (done) => { const buffers = []; const observed = []; - 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'))) + buffers.push(await atom.project.buildBuffer(require.resolve('./fixtures/dir/b'))) - waitsForPromise(() => - atom.project - .buildBuffer(require.resolve('./fixtures/dir/b')) - .then(o => buffers.push(o)) - ); + expect(buffers.length).toBe(2); + atom.project.observeBuffers(buffer => observed.push(buffer)); + expect(observed).toEqual(buffers); - runs(() => { - expect(buffers.length).toBe(2); - atom.project.observeBuffers(buffer => observed.push(buffer)); - expect(observed).toEqual(buffers); - }); + buffers.push(await atom.project.buildBuffer(require.resolve('./fixtures/dir/b'))) - waitsForPromise(() => - atom.project - .buildBuffer(require.resolve('./fixtures/dir/b')) - .then(o => buffers.push(o)) - ); + expect(observed.length).toBe(3); + expect(buffers.length).toBe(3); + expect(observed).toEqual(buffers); - runs(() => { - expect(observed.length).toBe(3); - expect(buffers.length).toBe(3); - expect(observed).toEqual(buffers); - }); + done(); }); }); @@ -1363,7 +1218,9 @@ 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', () => { + jasmine.filterByPlatform({only: ['win32']}); + expect(atom.project.resolvePath('d:\\file.txt')).toEqual('D:\\file.txt'); }); }); diff --git a/spec/reopen-project-menu-manager-spec.js b/spec/reopen-project-menu-manager-spec.js index c10d9db87b..a38778121f 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]; @@ -278,19 +278,19 @@ describe('ReopenProjectMenuManager', () => { }); 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'); - } + jasmine.filterByPlatform({only: ['win32']}); + + const name = ReopenProjectMenuManager.betterBaseName('.\\one\\two'); + expect(name).toBe('two'); }); 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'); - } + jasmine.filterByPlatform({only: ['win32']}); + + const name = ReopenProjectMenuManager.betterBaseName( + 'c:\\missions\\apollo\\11' + ); + expect(name).toBe('11'); }); it('returns the drive root for a Windows drive name', () => { diff --git a/spec/scope-resolver-spec.js b/spec/scope-resolver-spec.js index 0aee61c607..a0032eaf5c 100644 --- a/spec/scope-resolver-spec.js +++ b/spec/scope-resolver-spec.js @@ -95,19 +95,21 @@ function rangeFromDescriptor(rawRange) { describe('ScopeResolver', () => { let editor, buffer, grammar; - beforeEach(async () => { + beforeEach(async (done) => { grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); editor = await atom.workspace.open(''); buffer = editor.getBuffer(); atom.grammars.addGrammar(grammar); atom.config.set('core.useTreeSitterParsers', true); + + done(); }); afterEach(() => { ScopeResolver.clearConfigCache(); }); - it('resolves all scopes in absence of any tests or adjustments', async () => { + it('resolves all scopes in absence of any tests or adjustments', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` (comment) @comment (string) @string @@ -130,9 +132,11 @@ describe('ScopeResolver', () => { expect(stringForNodeRange(range)) .toBe(stringForNodeRange(node)); } + + done(); }); - it('provides the grammar with the text of leaf nodes only', async () => { + it('provides the grammar with the text of leaf nodes only', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` (expression_statement) @not_leaf_node (call_expression) @also_not_leaf_node @@ -162,9 +166,11 @@ describe('ScopeResolver', () => { 'cc', 'dd', ]); + + done(); }); - it('interpolates magic tokens in scope names', async () => { + it('interpolates magic tokens in scope names', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` (lexical_declaration kind: _ @declaration._TYPE_) `); @@ -189,9 +195,11 @@ describe('ScopeResolver', () => { 'declaration.const', 'declaration.let' ]); + + done(); }); - it('does not apply any scopes when @_IGNORE_ is used', async () => { + it('does not apply any scopes when @_IGNORE_ is used', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` (lexical_declaration kind: _ @_IGNORE_ (#match? @_IGNORE_ "const")) @@ -219,9 +227,11 @@ describe('ScopeResolver', () => { expect(!!result).toBe(true); } } + + done(); }); - it('does not apply any scopes when multiple @_IGNORE_s are used', async () => { + it('does not apply any scopes when multiple @_IGNORE_s are used', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` (variable_declarator (identifier) @_IGNORE_.identifier @@ -249,11 +259,13 @@ describe('ScopeResolver', () => { expect(!!result).toBe(true); } } + + done(); }); describe('adjustments', () => { - it('adjusts ranges with (#set! adjust.startAt)', async () => { + it('adjusts ranges with (#set! adjust.startAt)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((try_statement) @try.plus.brace (#set! adjust.endAt @@ -274,9 +286,11 @@ describe('ScopeResolver', () => { expect(buffer.getTextInRange(rangeFromDescriptor(range))) .toBe('try {'); + + done(); }); - it('adjusts ranges with (#set! adjust.endAt)', async () => { + it('adjusts ranges with (#set! adjust.endAt)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((object) @object.interior (#set! adjust.startAt firstChild.endPosition) @@ -299,9 +313,11 @@ describe('ScopeResolver', () => { expect( buffer.getTextInRange(rangeFromDescriptor(range)) ).toBe(`from: 'x', to: 'y'`); + + done(); }); - it('adjusts ranges with (#set! adjust.offset(Start|End))', async () => { + it('adjusts ranges with (#set! adjust.offset(Start|End))', async (done) => { // Same result as the previous test, but with a different technique. await grammar.setQueryForTest('highlightsQuery', ` ((object) @object.interior @@ -324,9 +340,11 @@ describe('ScopeResolver', () => { expect( buffer.getTextInRange(rangeFromDescriptor(range)) ).toBe(`from: 'x', to: 'y'`); + + done(); }); - it('prevents adjustments outside the original capture', async () => { + it('prevents adjustments outside the original capture', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((comment) @too-early (#set! adjust.startAt previousSibling.startPosition)) @@ -347,7 +365,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); @@ -357,9 +375,11 @@ describe('ScopeResolver', () => { scopeResolver.store(capture); }).toThrow(); } + + done(); }); - it("adjusts a range around a regex match with `adjust.startAndEndAroundFirstMatchOf`", async () => { + it("adjusts a range around a regex match with `adjust.startAndEndAroundFirstMatchOf`", async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((comment) @todo (#set! adjust.startAndEndAroundFirstMatchOf "\\\\sTODO(?=:)")) @@ -389,12 +409,14 @@ describe('ScopeResolver', () => { expect( buffer.getTextInRange(rangeFromDescriptor(matched[0])) ).toBe(` TODO`); + + done(); }); }); describe('tests', () => { - it('rejects scopes for ranges that have already been claimed by another capture with (#set! capture.final)', async () => { + it('rejects scopes for ranges that have already been claimed by another capture with (#set! capture.final)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` (comment) @comment (string) @string0 @@ -428,9 +450,11 @@ describe('ScopeResolver', () => { expect(!!result).toBe(false); } } + + done(); }); - it('temporarily supports the deprecated (#set! test.final true)', async () => { + it('temporarily supports the deprecated (#set! test.final true)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` (comment) @comment (string) @string0 @@ -464,9 +488,11 @@ describe('ScopeResolver', () => { expect(!!result).toBe(false); } } + + done(); }); - it('rejects scopes for ranges that have already been claimed by another capture with (#set! capture.final)', async () => { + it('rejects scopes for ranges that have already been claimed by another capture with (#set! capture.final)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` (comment) @comment (string) @string0 @@ -500,9 +526,11 @@ describe('ScopeResolver', () => { expect(!!result).toBe(false); } } + + done(); }); - it('rejects scopes for ranges that have already been claimed if set with (#set! capture.shy true)', async () => { + it('rejects scopes for ranges that have already been claimed if set with (#set! capture.shy true)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` (comment) @comment (string "\\"") @string.double @@ -532,9 +560,11 @@ describe('ScopeResolver', () => { expect(!!result).toBe(expected); } } + + done(); }); - it('temporarily supports the deprecated (#set! test.shy true)', async () => { + it('temporarily supports the deprecated (#set! test.shy true)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` (comment) @comment (string "\\"") @string.double @@ -564,9 +594,11 @@ describe('ScopeResolver', () => { expect(!!result).toBe(expected); } } + + done(); }); - it('rejects scopes for ranges that fail test.first or test.last', async () => { + it('rejects scopes for ranges that fail test.first or test.last', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((string_fragment) @impossible.first (#is? test.first true)) @@ -603,9 +635,11 @@ describe('ScopeResolver', () => { expect(node.id).toBe(node.parent.firstChild.id); } } + + done(); }); - it('temporarily supports the deprecated (#set! test.onlyIfFirst) and (#set! test.onlyIfLast)', async () => { + it('temporarily supports the deprecated (#set! test.onlyIfFirst) and (#set! test.onlyIfLast)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((string_fragment) @impossible.first (#is? test.onlyIfFirst true)) @@ -642,9 +676,11 @@ describe('ScopeResolver', () => { expect(node.id).toBe(node.parent.firstChild.id); } } + + done(); }); - it('supports test.firstOfType and test.lastOfType', async () => { + it('supports test.firstOfType and test.lastOfType', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` (formal_parameters (identifier) @first-param (#is? test.firstOfType identifier)) @@ -677,9 +713,11 @@ describe('ScopeResolver', () => { expect(matched.map(pair => { return pair[0].name; })).toEqual(["first-param", "first-comma", "last-comma", "last-param"]); + + done(); }); - it('supports test.lastTextOnRow', async () => { + it('supports test.lastTextOnRow', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ("||" @hanging-logical-operator (#is? test.lastTextOnRow true)) @@ -708,9 +746,11 @@ describe('ScopeResolver', () => { expect(matched.map(capture => capture.name)).toEqual( ["hanging-logical-operator"]); + + done(); }); - it('supports test.firstTextOnRow', async () => { + it('supports test.firstTextOnRow', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ("||" @hanging-logical-operator (#is? test.firstTextOnRow true)) @@ -739,9 +779,11 @@ describe('ScopeResolver', () => { expect(matched.map(capture => capture.name)).toEqual( ["hanging-logical-operator"]); + + done(); }); - it('supports test.descendantOfType', async () => { + it('supports test.descendantOfType', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ("," @comma-inside-function (#is? test.descendantOfType function_declaration)) @@ -761,9 +803,11 @@ describe('ScopeResolver', () => { expect(matched.every(cap => { return cap.node.startPosition.row === 1; })).toBe(true); + + done(); }); - it('supports test.descendantOfType (multiple values)', async () => { + it('supports test.descendantOfType (multiple values)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ("," @comma-inside-function (#is? test.descendantOfType "function_declaration generator_function_declaration")) @@ -785,10 +829,12 @@ describe('ScopeResolver', () => { let expectedRow = index >= 2 ? 2 : 1; return cap.node.startPosition.row === expectedRow; })).toBe(true); + + done(); }); - it('supports test.ancestorOfType', async () => { + it('supports test.ancestorOfType', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((function_declaration) @function-with-semicolons (#is? test.ancestorOfType ";")) @@ -808,9 +854,11 @@ describe('ScopeResolver', () => { expect(matched.length).toBe(1); expect(matched[0].node.text.includes("function bar")).toBe(true); + + done(); }); - it('supports test.ancestorOfType (multiple values)', async () => { + it('supports test.ancestorOfType (multiple values)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((function_declaration) @function-with-semicolons-or-booleans (#is? test.ancestorOfType "; false")) @@ -834,9 +882,11 @@ describe('ScopeResolver', () => { expect(matched.length).toBe(2); expect(matched[0].node.text.includes("function ba")).toBe(true); expect(matched[1].node.text.includes("function ba")).toBe(true); + + done(); }); - it('supports test.descendantOfNodeWithData (without value)', async () => { + it('supports test.descendantOfNodeWithData (without value)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((function_declaration) @_IGNORE_ (#match? @_IGNORE_ "foo") @@ -861,9 +911,11 @@ describe('ScopeResolver', () => { return cap.node.startPosition.row === 0 && cap.node.text === ","; })).toBe(true); + + done(); }); - it('supports test.descendantOfNodeWithData (with right value)', async () => { + it('supports test.descendantOfNodeWithData (with right value)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((function_declaration) @_IGNORE_ (#match? @_IGNORE_ "foo" ) @@ -888,9 +940,11 @@ describe('ScopeResolver', () => { return cap.node.startPosition.row === 0 && cap.node.text === ","; })).toBe(true); + + done(); }); - it('supports test.descendantOfNodeWithData (with wrong value)', async () => { + it('supports test.descendantOfNodeWithData (with wrong value)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((function_declaration) @_IGNORE_ (#match? @_IGNORE_ "foo") @@ -912,9 +966,11 @@ describe('ScopeResolver', () => { // Wrong value, so test shouldn't pass. expect(matched.length).toBe(0); + + done(); }); - it('supports test.type', async () => { + it('supports test.type', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` (formal_parameters _ @function-comma (#is? test.type ",")) @@ -933,9 +989,11 @@ describe('ScopeResolver', () => { expect(matched.every(cap => { return cap.node.text === ","; })).toBe(true); + + done(); }); - it('supports test.type with multiple types', async () => { + it('supports test.type with multiple types', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` (formal_parameters _ @thing (#is? test.type ", identifier")) @@ -951,9 +1009,11 @@ describe('ScopeResolver', () => { let matched = await getAllMatches(grammar, languageMode); expect(matched.length).toBe(5); + + done(); }); - it('supports test.hasError', async () => { + it('supports test.hasError', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((statement_block) @messed-up-statement-block (#is? test.hasError true)) @@ -974,9 +1034,11 @@ describe('ScopeResolver', () => { expect(matched.every(cap => { return cap.name === 'messed-up-statement-block' && cap.node.hasError(); })).toBe(true); + + done(); }); - it('supports test.root', async () => { + it('supports test.root', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((_) @is-root (#is? test.root true)) @@ -998,9 +1060,11 @@ describe('ScopeResolver', () => { return cap.name === 'is-root' && cap.node.type === 'program' && !cap.node.parent; })).toBe(true); + + done(); }); - it('supports test.lastTextOnRow', async () => { + it('supports test.lastTextOnRow', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ("||" @orphaned-operator (#is? test.lastTextOnRow true)) @@ -1024,9 +1088,11 @@ describe('ScopeResolver', () => { expect(cap.node.type).toBe('||'); expect(cap.node.startPosition.row).toBe(2); } + + done(); }); - it('supports test.rangeWithData (without value)', async () => { + it('supports test.rangeWithData (without value)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((true) @_IGNORE_ (#set! isTrue true)) ([ (true) (false) ] @optimistic-boolean @@ -1050,9 +1116,11 @@ describe('ScopeResolver', () => { expect(cap.name).toBe('optimistic-boolean'); expect(cap.node.text).toBe('true'); } + + done(); }); - it('supports test.rangeWithData (with right value)', async () => { + it('supports test.rangeWithData (with right value)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((true) @_IGNORE_ (#set! isTrue "exactly")) ([ (true) (false) ] @optimistic-boolean @@ -1076,9 +1144,11 @@ describe('ScopeResolver', () => { expect(cap.name).toBe('optimistic-boolean'); expect(cap.node.text).toBe('true'); } + + done(); }); - it('supports test.rangeWithData (with wrong value)', async () => { + it('supports test.rangeWithData (with wrong value)', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((true) @_IGNORE_ (#set! isTrue "perhaps")) ([ (true) (false) ] @optimistic-boolean @@ -1099,9 +1169,11 @@ describe('ScopeResolver', () => { // Values don't match, so the test shouldn't pass. expect(matched.length).toBe(0); + + done(); }); - it('supports test.startsOnSameRowAs', async () => { + it('supports test.startsOnSameRowAs', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((false) @non-hanging-false (#is? test.startsOnSameRowAs parent.startPosition)) @@ -1126,9 +1198,11 @@ describe('ScopeResolver', () => { expect(cap.node.text).toBe('false'); expect(cap.node.startPosition.row).toBe(1); } + + done(); }); - it('supports test.endsOnSameRowAs', async () => { + it('supports test.endsOnSameRowAs', async (done) => { await grammar.setQueryForTest('highlightsQuery', ` ((true) @non-hanging-true (#is? test.endsOnSameRowAs parent.endPosition)) @@ -1153,9 +1227,11 @@ describe('ScopeResolver', () => { expect(cap.node.text).toBe('true'); expect(cap.node.startPosition.row).toBe(1); } + + done(); }); - it('supports test.config (with no arguments)', async () => { + it('supports test.config (with no arguments)', async (done) => { atom.config.set('core.careAboutBooleans', true); await grammar.setQueryForTest('highlightsQuery', ` @@ -1180,9 +1256,11 @@ describe('ScopeResolver', () => { matched = await getAllMatches(grammar, languageMode); expect(matched.length).toBe(0); + + done(); }); - it('supports test.config (with boolean arguments)', async () => { + it('supports test.config (with boolean arguments)', async (done) => { atom.config.set('core.careAboutBooleans', true); await grammar.setQueryForTest('highlightsQuery', ` @@ -1207,9 +1285,11 @@ describe('ScopeResolver', () => { matched = await getAllMatches(grammar, languageMode); expect(matched.length).toBe(0); + + done(); }); - it('supports test.config (with number arguments)', async () => { + it('supports test.config (with number arguments)', async (done) => { atom.config.set('core.careAboutBooleans', 0); await grammar.setQueryForTest('highlightsQuery', ` @@ -1234,9 +1314,11 @@ describe('ScopeResolver', () => { matched = await getAllMatches(grammar, languageMode); expect(matched.length).toBe(0); + + done(); }); - it('supports test.config (with string arguments)', async () => { + it('supports test.config (with string arguments)', async (done) => { atom.config.set('core.careAboutBooleans', "something"); await grammar.setQueryForTest('highlightsQuery', ` @@ -1282,9 +1364,11 @@ describe('ScopeResolver', () => { ); matched = await getAllMatchesWithScopeResolver(grammar, languageMode, scopeResolver); expect(matched.length).toBe(0); + + done(); }); - it('supports test.injection', async () => { + it('supports test.injection', async (done) => { jasmine.useRealClock(); await grammar.setQueryForTest('highlightsQuery', ` ((escape_sequence) @regex-escape @@ -1333,6 +1417,8 @@ describe('ScopeResolver', () => { for (let cap of matched) { expect(cap.node.startPosition.row).toBe(3); } + + done(); }); }); 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..10b15b0486 100644 --- a/spec/state-store-spec.js +++ b/spec/state-store-spec.js @@ -46,27 +46,36 @@ 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 (done) => { 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; + }); - return store - .load('nonexistentKey') + loadStorePromise = store.load('nonexistentKey'); + }) + + fakeIDBObjectStore.onerror(fakeErrorEvent); + + loadStorePromise .then(() => { throw new Error('Promise should have been rejected'); }) .catch(event => { expect(event).toBe(fakeErrorEvent); }); + + done(); }); }); }); diff --git a/spec/task-spec.js b/spec/task-spec.js index 5b25dac739..63fcad0c57 100644 --- a/spec/task-spec.js +++ b/spec/task-spec.js @@ -3,65 +3,74 @@ 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(done) { 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(); + + done(); })); - 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(done) { 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); + + done(); }); - 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(done) { 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)) + + expect(eventSpy).not.toHaveBeenCalled(); - runs(() => expect(eventSpy).not.toHaveBeenCalled()); + done(); }); - it('reports deprecations in tasks', function() { + it('reports deprecations in tasks', async function(done) { 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)); + const deprecations = Grim.getDeprecations(); + expect(deprecations.length).toBe(1); + expect(deprecations[0].getStacks()[0][1].fileName).toBe(handlerPath); + jasmine.restoreDeprecationsSnapshot(); - runs(function() { - const deprecations = Grim.getDeprecations(); - expect(deprecations.length).toBe(1); - expect(deprecations[0].getStacks()[0][1].fileName).toBe(handlerPath); - jasmine.restoreDeprecationsSnapshot(); - }); + done(); }); it('adds data listeners to standard out and error to report output', function() { @@ -79,7 +88,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 +114,22 @@ 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(done) { + 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(); + + done(); }); }); }); diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index a5b9d04730..cc230e4f06 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -64,7 +64,7 @@ describe('TextEditorComponent', () => { }); describe('rendering', () => { - it('renders lines and line numbers for the visible region', async () => { + it('renders lines and line numbers for the visible region', async (done) => { const { component, element, editor } = buildComponent({ rowsPerTile: 3, autoHeight: false @@ -131,9 +131,11 @@ describe('TextEditorComponent', () => { editor.lineTextForScreenRow(7), editor.lineTextForScreenRow(8) ]); + + done(); }); - it('bases the width of the lines div on the width of the longest initially-visible screen line', async () => { + it('bases the width of the lines div on the width of the longest initially-visible screen line', async (done) => { const { component, element, editor } = buildComponent({ rowsPerTile: 2, height: 20, @@ -186,9 +188,11 @@ describe('TextEditorComponent', () => { await component.getNextUpdatePromise(); } + + done(); }); - it('re-renders lines when their height changes', async () => { + it('re-renders lines when their height changes', async (done) => { const { component, element } = buildComponent({ rowsPerTile: 3, autoHeight: false @@ -227,9 +231,11 @@ describe('TextEditorComponent', () => { await component.getNextUpdatePromise(); expect(queryOnScreenLineNumberElements(element).length).toBe(9); expect(queryOnScreenLineElements(element).length).toBe(9); + + done(); }); - it('makes the content at least as tall as the scroll container client height', async () => { + it('makes the content at least as tall as the scroll container client height', async (done) => { const { component, editor } = buildComponent({ text: 'a'.repeat(100), width: 50, @@ -246,9 +252,11 @@ describe('TextEditorComponent', () => { component.getContentHeight(), 2 ); + + done(); }); - it('honors the scrollPastEnd option by adding empty space equivalent to the clientHeight to the end of the content area', async () => { + it('honors the scrollPastEnd option by adding empty space equivalent to the clientHeight to the end of the content area', async (done) => { const { component, editor } = buildComponent({ autoHeight: false, autoWidth: false @@ -276,9 +284,11 @@ describe('TextEditorComponent', () => { expect(component.getFirstVisibleRow()).toBe( editor.getScreenLineCount() + 1 ); + + done(); }); - it('does not fire onDidChangeScrollTop listeners when assigning the same maximal value and the content height has fractional pixels (regression)', async () => { + it('does not fire onDidChangeScrollTop listeners when assigning the same maximal value and the content height has fractional pixels (regression)', async (done) => { const { component, element, editor } = buildComponent({ autoHeight: false, autoWidth: false @@ -299,9 +309,11 @@ describe('TextEditorComponent', () => { throw new Error('Scroll top should not have changed'); }); component.setScrollTop(component.getScrollTop()); + + done(); }); - it('gives the line number tiles an explicit width and height so their layout can be strictly contained', async () => { + it('gives the line number tiles an explicit width and height so their layout can be strictly contained', async (done) => { const { component, editor } = buildComponent({ rowsPerTile: 3 }); const lineNumberGutterElement = @@ -336,9 +348,11 @@ describe('TextEditorComponent', () => { } } } + + done(); }); - it('keeps the number of tiles stable when the visible line count changes during vertical scrolling', async () => { + it('keeps the number of tiles stable when the visible line count changes during vertical scrolling', async (done) => { const { component } = buildComponent({ rowsPerTile: 3, autoHeight: false @@ -351,9 +365,11 @@ describe('TextEditorComponent', () => { await setScrollTop(component, 1 * component.getLineHeight()); expect(component.refs.lineTiles.children.length).toBe(3 + 2); // account for cursors and highlights containers + + done(); }); - it('recycles tiles on resize', async () => { + it('recycles tiles on resize', async (done) => { const { component } = buildComponent({ rowsPerTile: 2, autoHeight: false @@ -363,9 +379,11 @@ describe('TextEditorComponent', () => { const lineNode = lineNodeForScreenRow(component, 7); await setEditorHeightInLines(component, 4); expect(lineNodeForScreenRow(component, 7)).toBe(lineNode); + + done(); }); - it("updates lines numbers when a row's foldability changes (regression)", async () => { + it("updates lines numbers when a row's foldability changes (regression)", async (done) => { const { component, editor } = buildComponent({ text: 'abc\n' }); editor.setCursorBufferPosition([1, 0]); await component.getNextUpdatePromise(); @@ -384,9 +402,11 @@ describe('TextEditorComponent', () => { expect( lineNumberNodeForScreenRow(component, 0).querySelector('.foldable') ).toBeNull(); + + done(); }); - it('shows the foldable icon on the last screen row of a buffer row that can be folded', async () => { + it('shows the foldable icon on the last screen row of a buffer row that can be folded', async (done) => { const { component } = buildComponent({ text: 'abc\n de\nfghijklm\n no', softWrapped: true @@ -407,9 +427,11 @@ describe('TextEditorComponent', () => { expect( lineNumberNodeForScreenRow(component, 4).classList.contains('foldable') ).toBe(false); + + done(); }); - it('renders dummy vertical and horizontal scrollbars when content overflows', async () => { + it('renders dummy vertical and horizontal scrollbars when content overflows', async (done) => { const { component, editor } = buildComponent({ height: 100, width: 100 @@ -465,10 +487,12 @@ describe('TextEditorComponent', () => { expect(getHorizontalScrollbarHeight(component)).toBe(0); expect(verticalScrollbar.style.visibility).toBe('hidden'); expect(horizontalScrollbar.style.visibility).toBe('hidden'); + + done(); }); describe('when scrollbar styles change or the editor element is detached and then reattached', () => { - it('updates the bottom/right of dummy scrollbars and client height/width measurements', async () => { + it('updates the bottom/right of dummy scrollbars and client height/width measurements', async (done) => { const { component, element, editor } = buildComponent({ height: 100, width: 100 @@ -530,10 +554,12 @@ describe('TextEditorComponent', () => { TextEditor.didUpdateScrollbarStyles(); component.scheduleUpdate(); await component.getNextUpdatePromise(); + + done(); }); }); - it('renders cursors within the visible row range', async () => { + it('renders cursors within the visible row range', async (done) => { const { component, element, editor } = buildComponent({ height: 40, rowsPerTile: 2 @@ -576,9 +602,11 @@ describe('TextEditorComponent', () => { await component.getNextUpdatePromise(); cursorNodes = Array.from(element.querySelectorAll('.cursor')); expect(cursorNodes.length).toBe(0); + + done(); }); - it('hides cursors with non-empty selections when showCursorOnSelection is false', async () => { + it('hides cursors with non-empty selections when showCursorOnSelection is false', async (done) => { const { component, element, editor } = buildComponent(); editor.setSelectedScreenRanges([[[0, 0], [0, 3]], [[1, 0], [1, 0]]]); await component.getNextUpdatePromise(); @@ -603,6 +631,8 @@ describe('TextEditorComponent', () => { const cursorNodes = Array.from(element.querySelectorAll('.cursor')); expect(cursorNodes.length).toBe(0); } + + done(); }); /** @@ -610,7 +640,7 @@ describe('TextEditorComponent', () => { * Error: Timed out waiting on anonymous condition at * conditionPromise (/home/runner/work/pulsar/pulsar/spec/async-spec-helpers.js:20:13) */ - xit('blinks cursors when the editor is focused and the cursors are not moving', async () => { + xit('blinks cursors when the editor is focused and the cursors are not moving', async (done) => { assertDocumentFocused(); const { component, element, editor } = buildComponent(); component.props.cursorBlinkPeriod = 30; @@ -642,9 +672,11 @@ describe('TextEditorComponent', () => { expect(getComputedStyle(cursor1).opacity).toBe('1'); expect(getComputedStyle(cursor2).opacity).toBe('1'); + + done(); }); - it('gives cursors at the end of lines the width of an "x" character', async () => { + it('gives cursors at the end of lines the width of an "x" character', async (done) => { const { component, element, editor } = buildComponent(); editor.setText('abcde'); await setEditorWidthInCharacters(component, 5.5); @@ -664,9 +696,11 @@ describe('TextEditorComponent', () => { expect(element.querySelector('.cursor').offsetWidth).toBeLessThan( Math.round(component.getBaseCharacterWidth()) ); + + done(); }); - it('positions and sizes cursors correctly when they are located next to a fold marker', async () => { + it('positions and sizes cursors correctly when they are located next to a fold marker', async (done) => { const { component, element, editor } = buildComponent(); editor.foldBufferRange([[0, 3], [0, 6]]); @@ -677,9 +711,11 @@ describe('TextEditorComponent', () => { editor.setCursorScreenPosition([0, 4]); await component.getNextUpdatePromise(); verifyCursorPosition(component, element.querySelector('.cursor'), 0, 4); + + done(); }); - it('positions cursors and placeholder text correctly when the lines container has a margin and/or is padded', async () => { + it('positions cursors and placeholder text correctly when the lines container has a margin and/or is padded', async (done) => { const { component, element, editor } = buildComponent({ placeholderText: 'testing' }); @@ -714,9 +750,11 @@ describe('TextEditorComponent', () => { .getBoundingClientRect().left; const linesLeft = component.refs.lineTiles.getBoundingClientRect().left; expect(placeholderTextLeft).toBe(linesLeft); + + done(); }); - it('places the hidden input element at the location of the last cursor if it is visible', async () => { + it('places the hidden input element at the location of the last cursor if it is visible', async (done) => { const { component, editor } = buildComponent({ height: 60, width: 120, @@ -743,9 +781,11 @@ describe('TextEditorComponent', () => { expect(Math.round(hiddenInput.getBoundingClientRect().left)).toBeNear( clientLeftForCharacter(component, 7, 4) ); + + done(); }); - it('soft wraps lines based on the content width when soft wrap is enabled', async () => { + it('soft wraps lines based on the content width when soft wrap is enabled', async (done) => { let baseCharacterWidth, gutterContainerWidth; { const { component, editor } = buildComponent(); @@ -771,9 +811,11 @@ describe('TextEditorComponent', () => { const { scrollContainer } = component.refs; expect(scrollContainer.clientWidth).toBe(scrollContainer.scrollWidth); + + done(); }); - it('correctly forces the display layer to index visible rows when resizing (regression)', async () => { + it('correctly forces the display layer to index visible rows when resizing (regression)', async (done) => { const text = 'a'.repeat(30) + '\n' + 'b'.repeat(1000); const { component, element, editor } = buildComponent({ height: 300, @@ -787,18 +829,22 @@ describe('TextEditorComponent', () => { element.style.width = 200 + 'px'; await component.getNextUpdatePromise(); expect(queryOnScreenLineElements(element).length).toBe(24); + + done(); }); - it('decorates the line numbers of folded lines', async () => { + it('decorates the line numbers of folded lines', async (done) => { const { component, editor } = buildComponent(); editor.foldBufferRow(1); await component.getNextUpdatePromise(); expect( lineNumberNodeForScreenRow(component, 1).classList.contains('folded') ).toBe(true); + + done(); }); - it('makes lines at least as wide as the scrollContainer', async () => { + it('makes lines at least as wide as the scrollContainer', async (done) => { const { component, element, editor } = buildComponent(); const { scrollContainer } = component.refs; editor.setText('a'); @@ -807,9 +853,11 @@ describe('TextEditorComponent', () => { expect(element.querySelector('.line').offsetWidth).toBe( scrollContainer.offsetWidth - verticalScrollbarWidth ); + + done(); }); - it('resizes based on the content when the autoHeight and/or autoWidth options are true', async () => { + it('resizes based on the content when the autoHeight and/or autoWidth options are true', async (done) => { const { component, element, editor } = buildComponent({ autoHeight: true, autoWidth: true @@ -851,6 +899,8 @@ describe('TextEditorComponent', () => { 2 * editorPadding ); expect(element.offsetHeight).toBeGreaterThan(initialHeight); + + done(); }); it('does not render the line number gutter at all if the isLineNumberGutterVisible parameter is false', () => { @@ -860,7 +910,7 @@ describe('TextEditorComponent', () => { expect(element.querySelector('.line-number')).toBe(null); }); - it('does not render the line numbers but still renders the line number gutter if showLineNumbers is false', async () => { + it('does not render the line numbers but still renders the line number gutter if showLineNumbers is false', async (done) => { function checkScrollContainerLeft(component) { const { scrollContainer, gutterContainer } = component.refs; expect(scrollContainer.getBoundingClientRect().left).toBeNear( @@ -908,6 +958,8 @@ describe('TextEditorComponent', () => { ) ).toBe(true); checkScrollContainerLeft(component); + + done(); }); it('supports the placeholderText parameter', () => { @@ -916,7 +968,7 @@ describe('TextEditorComponent', () => { expect(element.textContent).toContain(placeholderText); }); - it('adds the data-grammar attribute and updates it when the grammar changes', async () => { + it('adds the data-grammar attribute and updates it when the grammar changes', async (done) => { await atom.packages.activatePackage('language-javascript'); const { editor, element, component } = buildComponent(); @@ -925,18 +977,22 @@ describe('TextEditorComponent', () => { atom.grammars.assignLanguageMode(editor.getBuffer(), 'source.js'); await component.getNextUpdatePromise(); expect(element.dataset.grammar).toBe('source js'); + + done(); }); - it('adds the data-encoding attribute and updates it when the encoding changes', async () => { + it('adds the data-encoding attribute and updates it when the encoding changes', async (done) => { const { editor, element, component } = buildComponent(); expect(element.dataset.encoding).toBe('utf8'); editor.setEncoding('ascii'); await component.getNextUpdatePromise(); expect(element.dataset.encoding).toBe('ascii'); + + done(); }); - it('adds the has-selection class when the editor has a non-empty selection', async () => { + it('adds the has-selection class when the editor has a non-empty selection', async (done) => { const { editor, element, component } = buildComponent(); expect(element.classList.contains('has-selection')).toBe(false); @@ -947,9 +1003,11 @@ describe('TextEditorComponent', () => { editor.setSelectedBufferRanges([[[0, 0], [0, 0]], [[1, 0], [1, 0]]]); await component.getNextUpdatePromise(); expect(element.classList.contains('has-selection')).toBe(false); + + done(); }); - it('assigns buffer-row and screen-row to each line number as data fields', async () => { + it('assigns buffer-row and screen-row to each line number as data fields', async (done) => { const { editor, element, component } = buildComponent(); editor.setSoftWrapped(true); await component.getNextUpdatePromise(); @@ -1063,9 +1121,11 @@ describe('TextEditorComponent', () => { '20' ]); } + + done(); }); - it('does not blow away class names added to the element by packages when changing the class name', async () => { + it('does not blow away class names added to the element by packages when changing the class name', async (done) => { assertDocumentFocused(); const { component, element } = buildComponent(); element.classList.add('a', 'b'); @@ -1076,9 +1136,11 @@ describe('TextEditorComponent', () => { document.body.focus(); await component.getNextUpdatePromise(); expect(element.className).toBe('editor a b'); + + done(); }); - it('does not blow away class names managed by the component when packages change the element class name', async () => { + it('does not blow away class names managed by the component when packages change the element class name', async (done) => { assertDocumentFocused(); const { component, element } = buildComponent({ mini: true }); element.classList.add('a', 'b'); @@ -1088,9 +1150,11 @@ describe('TextEditorComponent', () => { element.className = 'a c d'; await component.getNextUpdatePromise(); expect(element.className).toBe('a c d editor is-focused mini'); + + done(); }); - it('ignores resize events when the editor is hidden', async () => { + it('ignores resize events when the editor is hidden', async (done) => { const { component, element } = buildComponent({ autoHeight: false }); @@ -1132,21 +1196,23 @@ describe('TextEditorComponent', () => { expect(component.getLineNumberGutterWidth()).toBe( originalLineNumberGutterWidth ); + + done(); }); describe('randomized tests', () => { 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 () => { + it('renders the visible rows correctly after randomly mutating the editor', async (done) => { const initialSeed = Date.now(); for (var i = 0; i < 20; i++) { let seed = initialSeed + i; @@ -1177,8 +1243,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(); @@ -1257,6 +1323,8 @@ describe('TextEditorComponent', () => { element.remove(); editor.destroy(); } + + done(); }); }); }); @@ -1285,7 +1353,7 @@ describe('TextEditorComponent', () => { expect(element.querySelector('gutter-container')).toBeNull(); }); - it('does not render line decorations for the cursor line', async () => { + it('does not render line decorations for the cursor line', async (done) => { const { component, element, editor } = buildComponent({ mini: true }); expect( element.querySelector('.line').classList.contains('cursor-line') @@ -1302,9 +1370,11 @@ describe('TextEditorComponent', () => { expect( element.querySelector('.line').classList.contains('cursor-line') ).toBe(false); + + done(); }); - it('does not render scrollbars', async () => { + it('does not render scrollbars', async (done) => { const { component, editor } = buildComponent({ mini: true, autoHeight: false @@ -1318,6 +1388,8 @@ describe('TextEditorComponent', () => { expect(component.canScrollHorizontally()).toBe(false); expect(component.refs.horizontalScrollbar).toBeUndefined(); expect(component.refs.verticalScrollbar).toBeUndefined(); + + done(); }); }); @@ -1326,7 +1398,7 @@ describe('TextEditorComponent', () => { assertDocumentFocused(); }); - it('focuses the hidden input element and adds the is-focused class when focused', async () => { + it('focuses the hidden input element and adds the is-focused class when focused', async (done) => { const { component, element } = buildComponent(); const { hiddenInput } = component.refs.cursorsAndInput.refs; @@ -1344,9 +1416,11 @@ describe('TextEditorComponent', () => { expect(document.activeElement).not.toBe(hiddenInput); await component.getNextUpdatePromise(); expect(element.classList.contains('is-focused')).toBe(false); + + done(); }); - it('updates the component when the hidden input is focused directly', async () => { + it('updates the component when the hidden input is focused directly', async (done) => { const { component, element } = buildComponent(); const { hiddenInput } = component.refs.cursorsAndInput.refs; expect(element.classList.contains('is-focused')).toBe(false); @@ -1355,6 +1429,8 @@ describe('TextEditorComponent', () => { hiddenInput.focus(); await component.getNextUpdatePromise(); expect(element.classList.contains('is-focused')).toBe(true); + + done(); }); it('gracefully handles a focus event that occurs prior to the attachedCallback of the element', () => { @@ -1370,7 +1446,7 @@ describe('TextEditorComponent', () => { ); }); - it('gracefully handles a focus event that occurs prior to detecting the element has become visible', async () => { + it('gracefully handles a focus event that occurs prior to detecting the element has become visible', async (done) => { const { component, element } = buildComponent({ attach: false }); element.style.display = 'none'; jasmine.attachToDOM(element); @@ -1381,6 +1457,8 @@ describe('TextEditorComponent', () => { expect(document.activeElement).toBe( component.refs.cursorsAndInput.refs.hiddenInput ); + + done(); }); it('emits blur events only when focus shifts to something other than the editor itself or its hidden input', () => { @@ -1399,7 +1477,7 @@ describe('TextEditorComponent', () => { }); describe('autoscroll', () => { - it('automatically scrolls vertically when the requested range is within the vertical scroll margin of the top or bottom', async () => { + it('automatically scrolls vertically when the requested range is within the vertical scroll margin of the top or bottom', async (done) => { const { component, editor } = buildComponent({ height: 120 + horizontalScrollbarHeight }); @@ -1427,9 +1505,11 @@ describe('TextEditorComponent', () => { editor.scrollToScreenPosition([2, 0]); await component.getNextUpdatePromise(); expect(component.getScrollTop()).toBe(0); + + done(); }); - it('does not vertically autoscroll by more than half of the visible lines if the editor is shorter than twice the scroll margin', async () => { + it('does not vertically autoscroll by more than half of the visible lines if the editor is shorter than twice the scroll margin', async (done) => { const { component, element, editor } = buildComponent({ autoHeight: false }); @@ -1464,9 +1544,11 @@ describe('TextEditorComponent', () => { expect(component.getScrollBottom()).toBeNear( (6 + 1 + scrollMarginInLines) * component.measurements.lineHeight ); + + done(); }); - it('autoscrolls the given range to the center of the screen if the `center` option is true', async () => { + it('autoscrolls the given range to the center of the screen if the `center` option is true', async (done) => { const { component, editor } = buildComponent({ height: 50 }); expect(component.getLastVisibleRow()).toBe(2); @@ -1477,9 +1559,11 @@ describe('TextEditorComponent', () => { (component.getScrollTop() + component.getScrollBottom()) / 2; const expectedScrollCenter = ((4 + 7) / 2) * component.getLineHeight(); expect(actualScrollCenter).toBeCloseTo(expectedScrollCenter, 0); + + done(); }); - it('automatically scrolls horizontally when the requested range is within the horizontal scroll margin of the right edge of the gutter or right edge of the scroll container', async () => { + it('automatically scrolls horizontally when the requested range is within the horizontal scroll margin of the right edge of the gutter or right edge of the scroll container', async (done) => { const { component, element, editor } = buildComponent(); element.style.width = component.getGutterContainerWidth() + @@ -1508,9 +1592,11 @@ describe('TextEditorComponent', () => { component.measurements.baseCharacterWidth - component.getScrollContainerClientWidth(); expect(component.getScrollLeft()).toBeNear(expectedScrollLeft); + + done(); }); - it('does not horizontally autoscroll by more than half of the visible "base-width" characters if the editor is narrower than twice the scroll margin', async () => { + it('does not horizontally autoscroll by more than half of the visible "base-width" characters if the editor is narrower than twice the scroll margin', async (done) => { const { component, editor } = buildComponent({ autoHeight: false }); await setEditorWidthInCharacters( component, @@ -1530,9 +1616,11 @@ describe('TextEditorComponent', () => { component.getBaseCharacterWidth() ); expect(component.getScrollLeft()).toBeNear(expectedScrollLeft); + + done(); }); - it('correctly autoscrolls after inserting a line that exceeds the current content width', async () => { + it('correctly autoscrolls after inserting a line that exceeds the current content width', async (done) => { const { component, element, editor } = buildComponent(); element.style.width = component.getGutterContainerWidth() + @@ -1547,9 +1635,11 @@ describe('TextEditorComponent', () => { expect(component.getScrollLeft()).toBeNear( component.getScrollWidth() - component.getScrollContainerClientWidth() ); + + done(); }); - it('does not try to measure lines that do not exist when the animation frame is delivered', async () => { + it('does not try to measure lines that do not exist when the animation frame is delivered', async (done) => { const { component, editor } = buildComponent({ autoHeight: false, height: 30, @@ -1561,9 +1651,11 @@ describe('TextEditorComponent', () => { expect(component.getScrollBottom()).toBeNear( (10 + 1) * component.measurements.lineHeight ); + + done(); }); - it('accounts for the presence of horizontal scrollbars that appear during the same frame as the autoscroll', async () => { + it('accounts for the presence of horizontal scrollbars that appear during the same frame as the autoscroll', async (done) => { const { component, element, editor } = buildComponent({ autoHeight: false }); @@ -1588,6 +1680,8 @@ describe('TextEditorComponent', () => { spyOn(window, 'onerror'); await setScrollTop(component, 0); expect(window.onerror).not.toHaveBeenCalled(); + + done(); }); }); @@ -1759,7 +1853,7 @@ describe('TextEditorComponent', () => { } }); - it('inverts deltaX and deltaY when holding shift on Windows and Linux', async () => { + it('inverts deltaX and deltaY when holding shift on Windows and Linux', async (done) => { const scrollSensitivity = 50; const { component } = buildComponent({ height: 50, @@ -1903,11 +1997,13 @@ describe('TextEditorComponent', () => { ); await setScrollLeft(component, 0); } + + done(); }); }); describe('scrolling via the API', () => { - it('ignores scroll requests to NaN, null or undefined positions', async () => { + it('ignores scroll requests to NaN, null or undefined positions', async (done) => { const { component } = buildComponent({ rowsPerTile: 2, autoHeight: false @@ -1940,11 +2036,13 @@ describe('TextEditorComponent', () => { await component.getNextUpdatePromise(); expect(component.getScrollTop()).toBeNear(initialScrollTop); expect(component.getScrollLeft()).toBeNear(initialScrollLeft); + + done(); }); }); describe('line and line number decorations', () => { - it('adds decoration classes on screen lines spanned by decorated markers', async () => { + it('adds decoration classes on screen lines spanned by decorated markers', async (done) => { const { component, editor } = buildComponent({ softWrapped: true }); @@ -2089,9 +2187,11 @@ describe('TextEditorComponent', () => { expect( lineNumberNodeForScreenRow(component, 8).classList.contains('b') ).toBe(true); + + done(); }); - it('honors the onlyEmpty and onlyNonEmpty decoration options', async () => { + it('honors the onlyEmpty and onlyNonEmpty decoration options', async (done) => { const { component, editor } = buildComponent(); const marker = editor.markScreenPosition([1, 0]); editor.decorateMarker(marker, { @@ -2162,9 +2262,11 @@ describe('TextEditorComponent', () => { expect( lineNumberNodeForScreenRow(component, 2).classList.contains('c') ).toBe(true); + + done(); }); - it('honors the onlyHead option', async () => { + it('honors the onlyHead option', async (done) => { const { component, editor } = buildComponent(); const marker = editor.markScreenRange([[1, 4], [3, 4]]); editor.decorateMarker(marker, { @@ -2186,9 +2288,11 @@ describe('TextEditorComponent', () => { expect( lineNumberNodeForScreenRow(component, 3).classList.contains('a') ).toBe(true); + + done(); }); - it('only decorates the last row of non-empty ranges that end at column 0 if omitEmptyLastRow is false', async () => { + it('only decorates the last row of non-empty ranges that end at column 0 if omitEmptyLastRow is false', async (done) => { const { component, editor } = buildComponent(); const marker = editor.markScreenRange([[1, 0], [3, 0]]); editor.decorateMarker(marker, { @@ -2221,9 +2325,11 @@ describe('TextEditorComponent', () => { expect(lineNodeForScreenRow(component, 3).classList.contains('b')).toBe( true ); + + done(); }); - it('does not decorate invalidated markers', async () => { + it('does not decorate invalidated markers', async (done) => { const { component, editor } = buildComponent(); const marker = editor.markScreenRange([[1, 0], [3, 0]], { invalidate: 'touch' @@ -2243,11 +2349,13 @@ describe('TextEditorComponent', () => { expect(lineNodeForScreenRow(component, 2).classList.contains('a')).toBe( false ); + + done(); }); }); describe('highlight decorations', () => { - it('renders single-line highlights', async () => { + it('renders single-line highlights', async (done) => { const { component, element, editor } = buildComponent(); const marker = editor.markScreenRange([[1, 2], [1, 10]]); editor.decorateMarker(marker, { type: 'highlight', class: 'a' }); @@ -2288,9 +2396,11 @@ describe('TextEditorComponent', () => { clientLeftForCharacter(component, 1, 8) ); } + + done(); }); - it('renders multi-line highlights', async () => { + it('renders multi-line highlights', async (done) => { const { component, element, editor } = buildComponent({ rowsPerTile: 3 }); const marker = editor.markScreenRange([[2, 4], [3, 4]]); editor.decorateMarker(marker, { type: 'highlight', class: 'a' }); @@ -2382,9 +2492,11 @@ describe('TextEditorComponent', () => { clientLeftForCharacter(component, 5, 4) ); } + + done(); }); - it('can flash highlight decorations', async () => { + it('can flash highlight decorations', async (done) => { const { component, element, editor } = buildComponent({ rowsPerTile: 3, height: 200 @@ -2422,9 +2534,11 @@ describe('TextEditorComponent', () => { await conditionPromise(() => highlights[0].classList.contains('e')); await conditionPromise(() => !highlights[0].classList.contains('e')); + + done(); }); - it("flashing a highlight decoration doesn't unflash other highlight decorations", async () => { + it("flashing a highlight decoration doesn't unflash other highlight decorations", async (done) => { const { component, element, editor } = buildComponent({ rowsPerTile: 3, height: 200 @@ -2447,9 +2561,11 @@ describe('TextEditorComponent', () => { await component.getNextUpdatePromise(); expect(highlights[0].classList.contains('c')).toBe(true); expect(highlights[0].classList.contains('d')).toBe(true); + + done(); }); - it('supports layer decorations', async () => { + it('supports layer decorations', async (done) => { const { component, element, editor } = buildComponent({ rowsPerTile: 12 }); @@ -2482,9 +2598,11 @@ describe('TextEditorComponent', () => { await component.getNextUpdatePromise(); expect(highlights[0].classList.contains('a')).toBe(true); expect(highlights[1].classList.contains('c')).toBe(true); + + done(); }); - it('clears highlights when recycling a tile that previously contained highlights and now does not', async () => { + it('clears highlights when recycling a tile that previously contained highlights and now does not', async (done) => { const { component, element, editor } = buildComponent({ rowsPerTile: 2, autoHeight: false @@ -2498,9 +2616,11 @@ describe('TextEditorComponent', () => { await setScrollTop(component, component.getLineHeight() * 3); expect(element.querySelectorAll('.highlight.a').length).toBe(0); + + done(); }); - it('does not move existing highlights when adding or removing other highlight decorations (regression)', async () => { + it('does not move existing highlights when adding or removing other highlight decorations (regression)', async (done) => { const { component, element, editor } = buildComponent(); const marker1 = editor.markScreenRange([[1, 6], [1, 10]]); @@ -2527,9 +2647,11 @@ describe('TextEditorComponent', () => { expect( Array.from(marker1Region.parentElement.children).indexOf(marker1Region) ).toBe(0); + + done(); }); - it('correctly positions highlights that end on rows preceding or following block decorations', async () => { + it('correctly positions highlights that end on rows preceding or following block decorations', async (done) => { const { editor, element, component } = buildComponent(); const item1 = document.createElement('div'); @@ -2558,6 +2680,8 @@ describe('TextEditorComponent', () => { expect(regions[0].offsetTop).toBeNear(3 * component.getLineHeight()); expect(regions[0].offsetHeight).toBeNear(component.getLineHeight()); expect(regions[1].offsetTop).toBeNear(4 * component.getLineHeight() + 30); + + done(); }); }); @@ -2569,16 +2693,16 @@ 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; } - it('renders overlay elements at the specified screen position unless it would overflow the window', async () => { + it('renders overlay elements at the specified screen position unless it would overflow the window', async (done) => { const { component, editor } = buildComponent({ width: 200, height: 100, @@ -2678,9 +2802,11 @@ describe('TextEditorComponent', () => { decoration.setProperties({ type: 'overlay', item: overlayElement }); await component.getNextUpdatePromise(); expect(overlayWrapper.classList.contains('b')).toBe(false); + + done(); }); - it('does not attempt to avoid overflowing the window if `avoidOverflow` is false on the decoration', async () => { + it('does not attempt to avoid overflowing the window if `avoidOverflow` is false on the decoration', async (done) => { const { component, editor } = buildComponent({ width: 200, height: 100, @@ -2708,11 +2834,13 @@ describe('TextEditorComponent', () => { expect(overlayElement.getBoundingClientRect().left).toBeLessThan( fakeWindow.getBoundingClientRect().left ); + + done(); }); }); describe('custom gutter decorations', () => { - it('arranges custom gutters based on their priority', async () => { + it('arranges custom gutters based on their priority', async (done) => { const { component, editor } = buildComponent(); editor.addGutter({ name: 'e', priority: 2 }); editor.addGutter({ name: 'a', priority: -2 }); @@ -2727,9 +2855,11 @@ describe('TextEditorComponent', () => { expect( Array.from(gutters).map(g => g.getAttribute('gutter-name')) ).toEqual(['a', 'b', 'c', 'line-number', 'd', 'e']); + + done(); }); - it('adjusts the left edge of the scroll container based on changes to the gutter container width', async () => { + it('adjusts the left edge of the scroll container based on changes to the gutter container width', async (done) => { const { component, editor } = buildComponent(); const { scrollContainer, gutterContainer } = component.refs; @@ -2767,9 +2897,11 @@ describe('TextEditorComponent', () => { gutterB.destroy(); await component.getNextUpdatePromise(); checkScrollContainerLeft(); + + done(); }); - it('allows the element of custom gutters to be retrieved before being rendered in the editor component', async () => { + it('allows the element of custom gutters to be retrieved before being rendered in the editor component', async (done) => { const { component, element, editor } = buildComponent(); const [lineNumberGutter] = editor.getGutters(); const gutterA = editor.addGutter({ name: 'a', priority: -1 }); @@ -2784,9 +2916,11 @@ describe('TextEditorComponent', () => { expect(element.contains(lineNumberGutterElement)).toBe(true); expect(element.contains(gutterAElement)).toBe(true); expect(element.contains(gutterBElement)).toBe(true); + + done(); }); - it('can show and hide custom gutters', async () => { + it('can show and hide custom gutters', async (done) => { const { component, editor } = buildComponent(); const gutterA = editor.addGutter({ name: 'a', priority: -1 }); const gutterB = editor.addGutter({ name: 'b', priority: 1 }); @@ -2811,9 +2945,11 @@ describe('TextEditorComponent', () => { await component.getNextUpdatePromise(); expect(gutterAElement.style.display).toBe(''); expect(gutterBElement.style.display).toBe('none'); + + done(); }); - it('renders decorations in custom gutters', async () => { + it('renders decorations in custom gutters', async (done) => { const { component, element, editor } = buildComponent(); const gutterA = editor.addGutter({ name: 'a', priority: -1 }); const gutterB = editor.addGutter({ name: 'b', priority: 1 }); @@ -2905,9 +3041,11 @@ describe('TextEditorComponent', () => { expect(decorationNode2.className).toBe('decoration'); expect(decorationNode2.firstChild).toBeNull(); expect(gutterB.getElement().firstChild.children.length).toBe(0); + + done(); }); - it('renders custom line number gutters', async () => { + it('renders custom line number gutters', async (done) => { const { component, editor } = buildComponent(); const gutterA = editor.addGutter({ name: 'a', @@ -2954,6 +3092,8 @@ describe('TextEditorComponent', () => { 'b - 4', 'b - 5' ]); + + done(); }); it("updates the editor's soft wrap width when a custom gutter's measurement is available", () => { @@ -2980,7 +3120,7 @@ describe('TextEditorComponent', () => { }); describe('block decorations', () => { - it('renders visible block decorations between the appropriate lines, refreshing and measuring them as needed', async () => { + it('renders visible block decorations between the appropriate lines, refreshing and measuring them as needed', async (done) => { const editor = buildEditor({ autoHeight: false }); const { item: item1, @@ -3391,9 +3531,11 @@ describe('TextEditorComponent', () => { expect(item5.previousSibling).toBe(lineNodeForScreenRow(component, 7)); expect(item5.nextSibling).toBe(lineNodeForScreenRow(component, 8)); expect(item6.previousSibling).toBe(lineNodeForScreenRow(component, 12)); + + done(); }); - it('correctly positions line numbers when block decorations are located at tile boundaries', async () => { + it('correctly positions line numbers when block decorations are located at tile boundaries', async (done) => { const { editor, component } = buildComponent({ rowsPerTile: 3 }); createBlockDecorationAtScreenRow(editor, 0, { height: 5, @@ -3426,9 +3568,11 @@ describe('TextEditorComponent', () => { }, { tileStartRow: 6, height: 3 * component.getLineHeight() } ]); + + done(); }); - it('removes block decorations whose markers have been destroyed', async () => { + it('removes block decorations whose markers have been destroyed', async (done) => { const { editor, component } = buildComponent({ rowsPerTile: 3 }); const { marker } = createBlockDecorationAtScreenRow(editor, 2, { height: 5, @@ -3450,9 +3594,11 @@ describe('TextEditorComponent', () => { { tileStartRow: 3, height: 3 * component.getLineHeight() }, { tileStartRow: 6, height: 3 * component.getLineHeight() } ]); + + done(); }); - it('removes block decorations whose markers are invalidated, and adds them back when they become valid again', async () => { + it('removes block decorations whose markers are invalidated, and adds them back when they become valid again', async (done) => { const editor = buildEditor({ rowsPerTile: 3, autoHeight: false }); const { item, decoration, marker } = createBlockDecorationAtScreenRow( editor, @@ -3507,9 +3653,11 @@ describe('TextEditorComponent', () => { { tileStartRow: 3, height: 3 * component.getLineHeight() }, { tileStartRow: 6, height: 3 * component.getLineHeight() } ]); + + done(); }); - it('does not render block decorations when decorating invalid markers', async () => { + it('does not render block decorations when decorating invalid markers', async (done) => { const editor = buildEditor({ rowsPerTile: 3, autoHeight: false }); const { component } = buildComponent({ editor, rowsPerTile: 3 }); @@ -3545,9 +3693,11 @@ describe('TextEditorComponent', () => { { tileStartRow: 3, height: 3 * component.getLineHeight() }, { tileStartRow: 6, height: 3 * component.getLineHeight() } ]); + + done(); }); - it('does not try to remeasure block decorations whose markers are invalid (regression)', async () => { + it('does not try to remeasure block decorations whose markers are invalid (regression)', async (done) => { const editor = buildEditor({ rowsPerTile: 3, autoHeight: false }); const { component } = buildComponent({ editor, rowsPerTile: 3 }); createBlockDecorationAtScreenRow(editor, 2, { @@ -3565,9 +3715,11 @@ describe('TextEditorComponent', () => { { tileStartRow: 3, height: 3 * component.getLineHeight() }, { tileStartRow: 6, height: 3 * component.getLineHeight() } ]); + + done(); }); - it('does not throw exceptions when destroying a block decoration inside a marker change event (regression)', async () => { + it('does not throw exceptions when destroying a block decoration inside a marker change event (regression)', async (done) => { const { editor, component } = buildComponent({ rowsPerTile: 3 }); const marker = editor.markScreenPosition([2, 0]); @@ -3585,9 +3737,11 @@ describe('TextEditorComponent', () => { await component.getNextUpdatePromise(); expect(item.parentElement).toBeNull(); + + done(); }); - it('does not attempt to render block decorations located outside the visible range', async () => { + it('does not attempt to render block decorations located outside the visible range', async (done) => { const { editor, component } = buildComponent({ autoHeight: false, rowsPerTile: 2 @@ -3617,9 +3771,11 @@ describe('TextEditorComponent', () => { expect(component.getRenderedEndRow()).toBe(8); expect(item1.nextSibling).toBe(lineNodeForScreenRow(component, 5)); expect(item2.parentElement).toBeNull(); + + done(); }); - it('measures block decorations correctly when they are added before the component width has been updated', async () => { + it('measures block decorations correctly when they are added before the component width has been updated', async (done) => { { const { editor, component, element } = buildComponent({ autoHeight: false, @@ -3655,9 +3811,11 @@ describe('TextEditorComponent', () => { await component.getNextUpdatePromise(); assertLinesAreAlignedWithLineNumbers(component); } + + done(); }); - it('bases the width of the block decoration measurement area on the editor scroll width', async () => { + it('bases the width of the block decoration measurement area on the editor scroll width', async (done) => { const { component, element } = buildComponent({ autoHeight: false, width: 150 @@ -3671,9 +3829,11 @@ describe('TextEditorComponent', () => { expect(component.refs.blockDecorationMeasurementArea.offsetWidth).toBe( component.getScrollWidth() ); + + done(); }); - it('does not change the cursor position when clicking on a block decoration', async () => { + it('does not change the cursor position when clicking on a block decoration', async (done) => { const { editor, component } = buildComponent(); const decorationElement = document.createElement('div'); @@ -3704,9 +3864,11 @@ describe('TextEditorComponent', () => { clientY: childElementClientRect.top }); expect(editor.getCursorScreenPosition()).toEqual([0, 0]); + + done(); }); - it('uses the order property to control the order of block decorations at the same screen row', async () => { + it('uses the order property to control the order of block decorations at the same screen row', async (done) => { const editor = buildEditor({ autoHeight: false }); const { component, element } = buildComponent({ editor }); element.style.height = @@ -3812,6 +3974,8 @@ describe('TextEditorComponent', () => { expect(beforeItems[5].nextSibling).toBe( lineNodeForScreenRow(component, 2) ); + + done(); }); function createBlockDecorationAtScreenRow( @@ -3874,7 +4038,7 @@ describe('TextEditorComponent', () => { }); describe('cursor decorations', () => { - it('allows default cursors to be customized', async () => { + it('allows default cursors to be customized', async (done) => { const { component, element, editor } = buildComponent(); editor.addCursorAtScreenPosition([1, 0]); @@ -3901,9 +4065,11 @@ describe('TextEditorComponent', () => { expect(cursorNodes[1].className).toBe('cursor b'); expect(cursorNodes[1].style.visibility).toBe('hidden'); expect(cursorNodes[1].style.backgroundColor).toBe('red'); + + done(); }); - it('allows markers that are not actually associated with cursors to be decorated as if they were cursors', async () => { + it('allows markers that are not actually associated with cursors to be decorated as if they were cursors', async (done) => { const { component, element, editor } = buildComponent(); const marker = editor.markScreenPosition([1, 0]); editor.decorateMarker(marker, { type: 'cursor', class: 'a' }); @@ -3913,11 +4079,13 @@ describe('TextEditorComponent', () => { expect(cursorNodes.length).toBe(2); expect(cursorNodes[0].className).toBe('cursor'); expect(cursorNodes[1].className).toBe('cursor a'); + + done(); }); }); describe('text decorations', () => { - it('injects spans with custom class names and inline styles based on text decorations', async () => { + it('injects spans with custom class names and inline styles based on text decorations', async (done) => { const { component, element, editor } = buildComponent({ rowsPerTile: 2 }); const markerLayer = editor.addMarkerLayer(); @@ -3989,9 +4157,11 @@ describe('TextEditorComponent', () => { expect(textContentOnRowMatchingSelector(component, 3, '.b')).toBe( editor.lineTextForScreenRow(3).slice(0, 10) ); + + done(); }); - it('correctly handles text decorations starting before the first rendered row and/or ending after the last rendered row', async () => { + it('correctly handles text decorations starting before the first rendered row and/or ending after the last rendered row', async (done) => { const { component, element, editor } = buildComponent({ autoHeight: false, rowsPerTile: 1 @@ -4026,9 +4196,11 @@ describe('TextEditorComponent', () => { expect(textContentOnRowMatchingSelector(component, 8, '.b')).toBe( editor.lineTextForScreenRow(8) ); + + done(); }); - it('does not create empty spans when a text decoration contains a row but another text decoration starts or ends at the beginning of it', async () => { + it('does not create empty spans when a text decoration contains a row but another text decoration starts or ends at the beginning of it', async (done) => { const { component, element, editor } = buildComponent(); const markerLayer = editor.addMarkerLayer(); const marker1 = markerLayer.markBufferRange([[0, 2], [4, 0]]); @@ -4039,9 +4211,11 @@ describe('TextEditorComponent', () => { for (const decorationSpan of element.querySelectorAll('.a, .b')) { expect(decorationSpan.textContent).not.toBe(''); } + + done(); }); - it('does not create empty text nodes when a text decoration ends right after a text tag', async () => { + it('does not create empty text nodes when a text decoration ends right after a text tag', async (done) => { const { component, editor } = buildComponent(); const marker = editor.markBufferRange([[0, 8], [0, 29]]); editor.decorateMarker(marker, { type: 'text', class: 'a' }); @@ -4049,6 +4223,8 @@ describe('TextEditorComponent', () => { for (const textNode of textNodesForScreenRow(component, 0)) { expect(textNode.textContent).not.toBe(''); } + + done(); }); function textContentOnRowMatchingSelector(component, row, selector) { @@ -4063,7 +4239,7 @@ describe('TextEditorComponent', () => { describe('mouse input', () => { describe('on the lines', () => { describe('when there is only one cursor', () => { - it('positions the cursor on single-click or when middle-clicking', async () => { + it('positions the cursor on single-click or when middle-clicking', async (done) => { atom.config.set('editor.selectionClipboard', false); for (const button of [0, 1]) { const { component, editor } = buildComponent(); @@ -4178,6 +4354,8 @@ describe('TextEditorComponent', () => { expect(editor.testAutoscrollRequests).toEqual([]); } + + done(); }); }); @@ -4589,7 +4767,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 +4792,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 +4840,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,14 +4868,14 @@ 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)); expect(editor.getSelectedScreenRange()).toEqual([[2, 0], [5, 0]]); }); - it('destroys folds when clicking on their fold markers', async () => { + it('destroys folds when clicking on their fold markers', async (done) => { const { component, element, editor } = buildComponent(); editor.foldBufferRow(1); await component.getNextUpdatePromise(); @@ -4717,9 +4895,11 @@ describe('TextEditorComponent', () => { }); expect(editor.isFoldedAtBufferRow(1)).toBe(false); expect(editor.getCursorScreenPosition()).toEqual([0, 0]); + + done(); }); - it('autoscrolls the content when dragging near the edge of the scroll container', async () => { + it('autoscrolls the content when dragging near the edge of the scroll container', async (done) => { const { component } = buildComponent({ width: 200, height: 200 @@ -4752,7 +4932,7 @@ describe('TextEditorComponent', () => { }); const { didDrag - } = component.handleMouseDragUntilMouseUp.argsForCall[0][0]; + } = component.handleMouseDragUntilMouseUp.calls.argsFor(0)[0]; didDrag({ clientX: 199, clientY: 199 }); assertScrolledDownAndRight(); @@ -4796,11 +4976,13 @@ describe('TextEditorComponent', () => { didDrag({ clientX: 199, clientY: 199 }); expect(component.getScrollTop()).toBeNear(maxScrollTop); expect(component.getScrollLeft()).toBeNear(maxScrollLeft); + + done(); }); }); - it('pastes the previously selected text when clicking the middle mouse button on Linux', async () => { - spyOn(electron.ipcRenderer, 'send').andCallFake(function( + it('pastes the previously selected text when clicking the middle mouse button on Linux', async (done) => { + spyOn(electron.ipcRenderer, 'send').and.callFake(function( eventName, selectedText ) { @@ -4851,10 +5033,12 @@ describe('TextEditorComponent', () => { clientY: clientTopForLine(component, 10) }); expect(editor.lineTextForBufferRow(10)).toBe('var'); + + done(); }); - it('does not paste into a read only editor when clicking the middle mouse button on Linux', async () => { - spyOn(electron.ipcRenderer, 'send').andCallFake(function( + it('does not paste into a read only editor when clicking the middle mouse button on Linux', async (done) => { + spyOn(electron.ipcRenderer, 'send').and.callFake(function( eventName, selectedText ) { @@ -4882,11 +5066,13 @@ describe('TextEditorComponent', () => { // Ensure that the correct text was copied but not pasted expect(TextEditor.clipboard.read()).toBe('sort'); expect(editor.lineTextForBufferRow(10)).toBe(''); + + done(); }); }); describe('on the line number gutter', () => { - it('selects all buffer rows intersecting the clicked screen row when a line number is clicked', async () => { + it('selects all buffer rows intersecting the clicked screen row when a line number is clicked', async (done) => { const { component, editor } = buildComponent(); spyOn(component, 'handleMouseDragUntilMouseUp'); editor.setSoftWrapped(true); @@ -4912,9 +5098,11 @@ describe('TextEditorComponent', () => { }); expect(editor.getSelectedScreenRange()).toEqual([[5, 0], [6, 0]]); expect(editor.getSelectedBufferRange()).toEqual([[4, 0], [8, 0]]); + + done(); }); - it('adds new selections when a line number is meta-clicked', async () => { + it('adds new selections when a line number is meta-clicked', async (done) => { const { component, editor } = buildComponent(); editor.setSoftWrapped(true); await component.getNextUpdatePromise(); @@ -4955,9 +5143,11 @@ describe('TextEditorComponent', () => { [[3, 0], [4, 0]], [[4, 0], [8, 0]] ]); + + done(); }); - it('expands the last selection when a line number is shift-clicked', async () => { + it('expands the last selection when a line number is shift-clicked', async (done) => { const { component, editor } = buildComponent(); spyOn(component, 'handleMouseDragUntilMouseUp'); editor.setSoftWrapped(true); @@ -4984,7 +5174,7 @@ describe('TextEditorComponent', () => { const { didDrag, didStopDragging - } = component.handleMouseDragUntilMouseUp.argsForCall[0][0]; + } = component.handleMouseDragUntilMouseUp.calls.argsFor(0)[0]; didDrag({ clientY: clientTopForLine(component, 1) }); @@ -4999,9 +5189,11 @@ describe('TextEditorComponent', () => { didStopDragging(); expect(editor.getSelectedBufferRanges()).toEqual([[[2, 10], [8, 0]]]); + + done(); }); - it('expands the selection when dragging', async () => { + it('expands the selection when dragging', async (done) => { const { component, editor } = buildComponent(); spyOn(component, 'handleMouseDragUntilMouseUp'); editor.setSoftWrapped(true); @@ -5022,7 +5214,7 @@ describe('TextEditorComponent', () => { const { didDrag, didStopDragging - } = component.handleMouseDragUntilMouseUp.argsForCall[0][0]; + } = component.handleMouseDragUntilMouseUp.calls.argsFor(0)[0]; didDrag({ clientY: clientTopForLine(component, 1) @@ -5051,9 +5243,11 @@ describe('TextEditorComponent', () => { didStopDragging(); expect(editor.getSelectedScreenRanges()).toEqual([[[2, 0], [4, 4]]]); + + done(); }); - it('toggles folding when clicking on the right icon of a foldable line number', async () => { + it('toggles folding when clicking on the right icon of a foldable line number', async (done) => { const { component, element, editor } = buildComponent(); let target = element .querySelectorAll('.line-number')[1] @@ -5089,9 +5283,11 @@ describe('TextEditorComponent', () => { clientY: clientTopForLine(component, 4) }); expect(editor.isFoldedAtScreenRow(4)).toBe(false); + + done(); }); - it('autoscrolls when dragging near the top or bottom of the gutter', async () => { + it('autoscrolls when dragging near the top or bottom of the gutter', async (done) => { const { component } = buildComponent({ width: 200, height: 200 @@ -5122,7 +5318,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 }); @@ -5166,11 +5362,13 @@ describe('TextEditorComponent', () => { didDrag({ clientX: 199, clientY: 199 }); expect(component.getScrollTop()).toBeNear(maxScrollTop); expect(component.getScrollLeft()).toBeNear(maxScrollLeft); + + done(); }); }); describe('on the scrollbars', () => { - it('delegates the mousedown events to the parent component unless the mousedown was on the actual scrollbar', async () => { + it('delegates the mousedown events to the parent component unless the mousedown was on the actual scrollbar', async (done) => { const { component, editor } = buildComponent({ height: 100 }); await setEditorWidthInCharacters(component, 6); @@ -5214,6 +5412,8 @@ describe('TextEditorComponent', () => { clientX: component.refs.content.getBoundingClientRect().left }); expect(editor.getCursorScreenPosition()).toEqual([4, 0]); + + done(); }); }); }); @@ -5531,7 +5731,7 @@ describe('TextEditorComponent', () => { * Expected 7.234375 not to be 7.234375. * Expected 7.234375 not to be 7.234375. */ - xit('updates the rendered content based on new measurements when the font dimensions change', async () => { + xit('updates the rendered content based on new measurements when the font dimensions change', async (done) => { const { component, element, editor } = buildComponent({ rowsPerTile: 1, autoHeight: false @@ -5597,9 +5797,11 @@ describe('TextEditorComponent', () => { initialRenderedLineCount ); verifyCursorPosition(component, cursorNode, 1, 29); + + done(); }); - it('maintains the scrollTopRow and scrollLeftColumn when the font size changes', async () => { + it('maintains the scrollTopRow and scrollLeftColumn when the font size changes', async (done) => { const { component, element } = buildComponent({ rowsPerTile: 1, autoHeight: false @@ -5620,9 +5822,11 @@ describe('TextEditorComponent', () => { TextEditor.didUpdateStyles(); await component.getNextUpdatePromise(); expect(component.getScrollTopRow()).toBe(4); + + done(); }); - 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) => { const { component, element } = buildComponent({ autoHeight: false }); @@ -5631,9 +5835,11 @@ 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 () => { + it('does not throw an exception when the editor is soft-wrapped and changing the font size changes also the longest screen line', async (done) => { const { component, element, editor } = buildComponent({ rowsPerTile: 3, autoHeight: false @@ -5651,9 +5857,11 @@ describe('TextEditorComponent', () => { element.style.fontSize = '20px'; TextEditor.didUpdateStyles(); await component.getNextUpdatePromise(); + + done(); }); - it('updates the width of the lines div based on the longest screen line', async () => { + it('updates the width of the lines div based on the longest screen line', async (done) => { const { component, element, editor } = buildComponent({ rowsPerTile: 1, autoHeight: false @@ -5678,6 +5886,8 @@ describe('TextEditorComponent', () => { component.getBaseCharacterWidth() ); expect(actualWidth).toBe(expectedWidth + 'px'); + + done(); }); }); @@ -5716,7 +5926,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(); }); @@ -5747,7 +5957,7 @@ describe('TextEditorComponent', () => { }); describe('pixelPositionForScreenPosition(point)', () => { - it('returns the pixel position for the given point, regardless of whether or not it is currently on screen', async () => { + it('returns the pixel position for the given point, regardless of whether or not it is currently on screen', async (done) => { const { component, editor } = buildComponent({ rowsPerTile: 2, autoHeight: false @@ -5816,9 +6026,11 @@ describe('TextEditorComponent', () => { referenceContentRect.left ); } + + done(); }); - it('does not get the component into an inconsistent state when the model has unflushed changes (regression)', async () => { + it('does not get the component into an inconsistent state when the model has unflushed changes (regression)', async (done) => { const { component, editor } = buildComponent({ rowsPerTile: 2, autoHeight: false, @@ -5829,9 +6041,11 @@ describe('TextEditorComponent', () => { const updatePromise = editor.getBuffer().append('hi\n'); component.screenPositionForPixelPosition({ top: 800, left: 1 }); await updatePromise; + + done(); }); - it('does not shift cursors downward or render off-screen content when measuring off-screen lines (regression)', async () => { + it('does not shift cursors downward or render off-screen content when measuring off-screen lines (regression)', async (done) => { const { component, element } = buildComponent({ rowsPerTile: 2, autoHeight: false @@ -5857,11 +6071,13 @@ describe('TextEditorComponent', () => { ); expect(previouslyMeasuredLineElement.style.display).toBe(''); expect(previouslyMeasuredLineElement.style.visibility).toBe(''); + + done(); }); }); describe('screenPositionForPixelPosition', () => { - it('returns the screen position for the given pixel position, regardless of whether or not it is currently on screen', async () => { + it('returns the screen position for the given pixel position, regardless of whether or not it is currently on screen', async (done) => { const { component, editor } = buildComponent({ rowsPerTile: 2, autoHeight: false @@ -5927,37 +6143,43 @@ describe('TextEditorComponent', () => { [3, 4] ); } + + done(); }); }); describe('model methods that delegate to the component / element', () => { - it('delegates setHeight and getHeight to the component', async () => { + it('delegates setHeight and getHeight to the component', async (done) => { const { component, editor } = buildComponent({ autoHeight: false }); 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); + + done(); }); - it('delegates setWidth and getWidth to the component', async () => { + it('delegates setWidth and getWidth to the component', async (done) => { 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); + + done(); }); - it('delegates getFirstVisibleScreenRow, getLastVisibleScreenRow, and getVisibleRowRange to the component', async () => { + it('delegates getFirstVisibleScreenRow, getLastVisibleScreenRow, and getVisibleRowRange to the component', async (done) => { const { component, element, editor } = buildComponent({ rowsPerTile: 3, autoHeight: false @@ -5976,9 +6198,11 @@ describe('TextEditorComponent', () => { component.getFirstVisibleRow(), component.getLastVisibleRow() ]); + + done(); }); - it('assigns scrollTop on the component when calling setFirstVisibleScreenRow', async () => { + it('assigns scrollTop on the component when calling setFirstVisibleScreenRow', async (done) => { const { component, element, editor } = buildComponent({ rowsPerTile: 3, autoHeight: false @@ -6016,9 +6240,11 @@ describe('TextEditorComponent', () => { expect(component.refs.verticalScrollbar.element.scrollTop).toBeNear( 9 * component.getLineHeight() ); + + done(); }); - it('delegates setFirstVisibleScreenColumn and getFirstVisibleScreenColumn to the component', async () => { + it('delegates setFirstVisibleScreenColumn and getFirstVisibleScreenColumn to the component', async (done) => { const { component, element, editor } = buildComponent({ rowsPerTile: 3, autoHeight: false @@ -6046,11 +6272,13 @@ describe('TextEditorComponent', () => { 12 * component.getBaseCharacterWidth(), -1 ); + + done(); }); }); describe('handleMouseDragUntilMouseUp', () => { - it('repeatedly schedules `didDrag` calls on new animation frames after moving the mouse, and calls `didStopDragging` on mouseup', async () => { + it('repeatedly schedules `didDrag` calls on new animation frames after moving the mouse, and calls `didStopDragging` on mouseup', async (done) => { const { component } = buildComponent(); let dragEvents; @@ -6097,9 +6325,11 @@ describe('TextEditorComponent', () => { await getNextAnimationFramePromise(); expect(dragging).toBe(false); expect(dragEvents).toEqual([]); + + done(); }); - it('calls `didStopDragging` if the user interacts with the keyboard while dragging', async () => { + it('calls `didStopDragging` if the user interacts with the keyboard while dragging', async (done) => { const { component, editor } = buildComponent(); let dragging = false; @@ -6141,6 +6371,8 @@ describe('TextEditorComponent', () => { component.didKeydown({ key: 'Shift' }); component.didKeydown({ key: 'Meta' }); expect(dragging).toBe(true); + + done(); }); function getNextAnimationFramePromise() { diff --git a/spec/text-editor-element-spec.js b/spec/text-editor-element-spec.js index fbe7af1f7c..9be4393538 100644 --- a/spec/text-editor-element-spec.js +++ b/spec/text-editor-element-spec.js @@ -70,7 +70,7 @@ describe('TextEditorElement', () => { expect(element.getModel().isLineNumberGutterVisible()).toBe(false); }); - it("honors the 'readonly' attribute", async function() { + it("honors the 'readonly' attribute", async function(done) { jasmineContent.innerHTML = ''; const element = jasmineContent.firstChild; @@ -81,6 +81,8 @@ describe('TextEditorElement', () => { element.setAttribute('readonly', true); expect(element.getComponent().isInputEnabled()).toBe(false); + + done(); }); it('honors the text content', () => { @@ -106,11 +108,13 @@ describe('TextEditorElement', () => { }); describe('when the model is assigned', () => - it("adds the 'mini' attribute if .isMini() returns true on the model", async () => { + it("adds the 'mini' attribute if .isMini() returns true on the model", async (done) => { const element = buildTextEditorElement(); element.getModel().update({ mini: true }); await atom.views.getNextUpdatePromise(); expect(element.hasAttribute('mini')).toBe(true); + + done(); })); describe('when the editor is attached to the DOM', () => @@ -270,7 +274,7 @@ describe('TextEditorElement', () => { }); describe('when the element already has an editor', () => { - it('unbinds it and then swaps it with the supplied one', async () => { + it('unbinds it and then swaps it with the supplied one', async (done) => { const element = buildTextEditorElement({ attach: true }); const previousEditor = element.getModel(); expect(previousEditor.element).toBe(element); @@ -280,6 +284,8 @@ describe('TextEditorElement', () => { expect(previousEditor.element).not.toBe(element); expect(newEditor.element).toBe(element); expect(element.getModel()).toBe(newEditor); + + done(); }); }); }); @@ -298,7 +304,7 @@ describe('TextEditorElement', () => { expect(attachedCallback).toHaveBeenCalled(); expect(detachedCallback).not.toHaveBeenCalled(); - attachedCallback.reset(); + attachedCallback.calls.reset(); element.remove(); expect(attachedCallback).not.toHaveBeenCalled(); @@ -307,7 +313,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 +324,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(); @@ -340,7 +346,7 @@ describe('TextEditorElement', () => { }); describe('::getMaxScrollTop', () => - it('returns the maximum scroll top that can be applied to the element', async () => { + it('returns the maximum scroll top that can be applied to the element', async (done) => { const editor = new TextEditor(); editor.setText('1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16'); const element = editor.getElement(); @@ -364,10 +370,12 @@ describe('TextEditorElement', () => { element.style.height = 200 + horizontalScrollbarHeight + 'px'; await element.getNextUpdatePromise(); expect(element.getMaxScrollTop()).toBe(0); + + done(); })); describe('::setScrollTop and ::setScrollLeft', () => { - it('changes the scroll position', async () => { + it('changes the scroll position', async (done) => { const element = buildTextEditorElement(); element.getModel().update({ autoHeight: false }); element.getModel().setText('lorem\nipsum\ndolor\nsit\namet'); @@ -383,11 +391,13 @@ describe('TextEditorElement', () => { element.setScrollLeft(32); await element.getNextUpdatePromise(); expect(element.getScrollLeft()).toBe(32); + + done(); }); }); describe('on TextEditor::setMini', () => - it("changes the element's 'mini' attribute", async () => { + it("changes the element's 'mini' attribute", async (done) => { const element = buildTextEditorElement(); expect(element.hasAttribute('mini')).toBe(false); element.getModel().setMini(true); @@ -396,10 +406,12 @@ describe('TextEditorElement', () => { element.getModel().setMini(false); await element.getNextUpdatePromise(); expect(element.hasAttribute('mini')).toBe(false); + + done(); })); describe('::intersectsVisibleRowRange(start, end)', () => { - it('returns true if the given row range intersects the visible row range', async () => { + it('returns true if the given row range intersects the visible row range', async (done) => { const element = buildTextEditorElement(); const editor = element.getModel(); const horizontalScrollbarHeight = element.component.getHorizontalScrollbarHeight(); @@ -418,11 +430,13 @@ describe('TextEditorElement', () => { expect(element.intersectsVisibleRowRange(5, 8)).toBe(true); expect(element.intersectsVisibleRowRange(11, 12)).toBe(false); expect(element.intersectsVisibleRowRange(12, 13)).toBe(false); + + done(); }); }); describe('::pixelRectForScreenRange(range)', () => { - it('returns a {top/left/width/height} object describing the rectangle between two screen positions, even if they are not on screen', async () => { + it('returns a {top/left/width/height} object describing the rectangle between two screen positions, even if they are not on screen', async (done) => { const element = buildTextEditorElement(); const editor = element.getModel(); const horizontalScrollbarHeight = element.component.getHorizontalScrollbarHeight(); @@ -448,13 +462,15 @@ describe('TextEditorElement', () => { bottom + editor.getLineHeightInPixels() - top ); expect(pixelRect.width).toBeNear(right - left); + + done(); }); }); describe('events', () => { let element = null; - beforeEach(async () => { + beforeEach(async (done) => { element = buildTextEditorElement(); element.getModel().update({ autoHeight: false }); element.getModel().setText('lorem\nipsum\ndolor\nsit\namet'); @@ -462,6 +478,8 @@ describe('TextEditorElement', () => { await element.getNextUpdatePromise(); element.setWidth(20); await element.getNextUpdatePromise(); + + done(); }); describe('::onDidChangeScrollTop(callback)', () => diff --git a/spec/text-editor-registry-spec.js b/spec/text-editor-registry-spec.js index 71b921e9d1..c306c491fe 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); }); }); @@ -110,7 +110,7 @@ describe('TextEditorRegistry', function() { }); describe('.maintainConfig(editor)', function() { - it('does not update the editor when config settings change for unrelated scope selectors', async function() { + it('does not update the editor when config settings change for unrelated scope selectors', async function(done) { await atom.packages.activatePackage('language-javascript'); const editor2 = new TextEditor(); @@ -140,9 +140,11 @@ describe('TextEditorRegistry', function() { expect(editor.getEncoding()).toBe('utf16le'); expect(editor2.getEncoding()).toBe('utf16be'); + + done(); }); - it('does not update the editor before the initial packages have loaded', async function() { + it('does not update the editor before the initial packages have loaded', async function(done) { let resolveActivatePromise; initialPackageActivation = new Promise(resolve => { resolveActivatePromise = resolve; @@ -161,9 +163,11 @@ describe('TextEditorRegistry', function() { resolveActivatePromise(); await initialPackageActivation; expect(editor.getEncoding()).toBe('utf16be'); + + done(); }); - it("updates the editor's settings when its grammar changes", async function() { + it("updates the editor's settings when its grammar changes", async function(done) { await atom.packages.activatePackage('language-javascript'); registry.maintainConfig(editor); @@ -191,9 +195,11 @@ describe('TextEditorRegistry', function() { atom.grammars.assignLanguageMode(editor, 'text.plain.null-grammar'); await initialPackageActivation; expect(editor.getEncoding()).toBe('utf8'); + + done(); }); - it("preserves editor settings that haven't changed between previous and current language modes", async function() { + it("preserves editor settings that haven't changed between previous and current language modes", async function(done) { await atom.packages.activatePackage('language-javascript'); registry.maintainConfig(editor); @@ -211,9 +217,11 @@ describe('TextEditorRegistry', function() { await initialPackageActivation; expect(editor.getEncoding()).toBe('utf16le'); expect(editor.isSoftWrapped()).toBe(true); + + done(); }); - it('updates editor settings that have changed between previous and current language modes', async function() { + it('updates editor settings that have changed between previous and current language modes', async function(done) { await atom.packages.activatePackage('language-javascript'); registry.maintainConfig(editor); @@ -234,9 +242,11 @@ describe('TextEditorRegistry', function() { atom.grammars.assignLanguageMode(editor, 'source.js'); await initialPackageActivation; expect(editor.getEncoding()).toBe('utf16le'); + + done(); }); - it("returns a disposable that can be used to stop the registry from updating the editor's config", async function() { + it("returns a disposable that can be used to stop the registry from updating the editor's config", async function(done) { await atom.packages.activatePackage('language-javascript'); const previousSubscriptionCount = getSubscriptionCount(editor); @@ -258,9 +268,11 @@ describe('TextEditorRegistry', function() { expect(editor.getEncoding()).toBe('utf8'); expect(getSubscriptionCount(editor)).toBe(previousSubscriptionCount); expect(retainedEditorCount(registry)).toBe(0); + + done(); }); - it('sets the encoding based on the config', async function() { + it('sets the encoding based on the config', async function(done) { editor.update({ encoding: 'utf8' }); expect(editor.getEncoding()).toBe('utf8'); @@ -271,9 +283,11 @@ describe('TextEditorRegistry', function() { atom.config.set('core.fileEncoding', 'utf8'); expect(editor.getEncoding()).toBe('utf8'); + + done(); }); - it('sets the tab length based on the config', async function() { + it('sets the tab length based on the config', async function(done) { editor.update({ tabLength: 4 }); expect(editor.getTabLength()).toBe(4); @@ -284,24 +298,30 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.tabLength', 4); expect(editor.getTabLength()).toBe(4); + + done(); }); - it('enables soft tabs when the tabType config setting is "soft"', async function() { + it('enables soft tabs when the tabType config setting is "soft"', async function(done) { atom.config.set('editor.tabType', 'soft'); registry.maintainConfig(editor); await initialPackageActivation; expect(editor.getSoftTabs()).toBe(true); + + done(); }); - it('disables soft tabs when the tabType config setting is "hard"', async function() { + it('disables soft tabs when the tabType config setting is "hard"', async function(done) { atom.config.set('editor.tabType', 'hard'); registry.maintainConfig(editor); await initialPackageActivation; expect(editor.getSoftTabs()).toBe(false); + + done(); }); describe('when the "tabType" config setting is "auto"', function() { - it("enables or disables soft tabs based on the editor's content", async function() { + it("enables or disables soft tabs based on the editor's content", async function(done) { await initialPackageActivation; await atom.packages.activatePackage('language-javascript'); atom.grammars.assignLanguageMode(editor, 'source.js'); @@ -368,11 +388,13 @@ describe('TextEditorRegistry', function() { disposable.dispose(); disposable = registry.maintainConfig(editor); expect(editor.getSoftTabs()).toBe(true); + + done(); }); }); describe('when the "tabType" config setting is "auto"', function() { - it('enables or disables soft tabs based on the "softTabs" config setting', async function() { + it('enables or disables soft tabs based on the "softTabs" config setting', async function(done) { registry.maintainConfig(editor); await initialPackageActivation; @@ -383,10 +405,12 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.softTabs', false); expect(editor.getSoftTabs()).toBe(false); + + done(); }); }); - it('enables or disables soft tabs based on the config', async function() { + it('enables or disables soft tabs based on the config', async function(done) { editor.update({ softTabs: true }); expect(editor.getSoftTabs()).toBe(true); @@ -401,9 +425,11 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.tabType', 'auto'); atom.config.set('editor.softTabs', true); expect(editor.getSoftTabs()).toBe(true); + + done(); }); - it('enables or disables atomic soft tabs based on the config', async function() { + it('enables or disables atomic soft tabs based on the config', async function(done) { editor.update({ atomicSoftTabs: true }); expect(editor.hasAtomicSoftTabs()).toBe(true); @@ -414,9 +440,11 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.atomicSoftTabs', true); expect(editor.hasAtomicSoftTabs()).toBe(true); + + done(); }); - it('enables or disables cursor on selection visibility based on the config', async function() { + it('enables or disables cursor on selection visibility based on the config', async function(done) { editor.update({ showCursorOnSelection: true }); expect(editor.getShowCursorOnSelection()).toBe(true); @@ -427,9 +455,11 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.showCursorOnSelection', true); expect(editor.getShowCursorOnSelection()).toBe(true); + + done(); }); - it('enables or disables line numbers based on the config', async function() { + it('enables or disables line numbers based on the config', async function(done) { editor.update({ showLineNumbers: true }); expect(editor.showLineNumbers).toBe(true); @@ -440,9 +470,11 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.showLineNumbers', true); expect(editor.showLineNumbers).toBe(true); + + done(); }); - it('sets the invisibles based on the config', async function() { + it('sets the invisibles based on the config', async function(done) { const invisibles1 = { tab: 'a', cr: false, eol: false, space: false }; const invisibles2 = { tab: 'b', cr: false, eol: false, space: false }; @@ -463,9 +495,11 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.showInvisibles', false); expect(editor.getInvisibles()).toEqual({}); + + done(); }); - it('enables or disables the indent guide based on the config', async function() { + it('enables or disables the indent guide based on the config', async function(done) { editor.update({ showIndentGuide: true }); expect(editor.doesShowIndentGuide()).toBe(true); @@ -476,9 +510,11 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.showIndentGuide', true); expect(editor.doesShowIndentGuide()).toBe(true); + + done(); }); - it('enables or disables soft wrap based on the config', async function() { + it('enables or disables soft wrap based on the config', async function(done) { editor.update({ softWrapped: true }); expect(editor.isSoftWrapped()).toBe(true); @@ -489,9 +525,11 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.softWrap', true); expect(editor.isSoftWrapped()).toBe(true); + + done(); }); - it('sets the soft wrap indent length based on the config', async function() { + it('sets the soft wrap indent length based on the config', async function(done) { editor.update({ softWrapHangingIndentLength: 4 }); expect(editor.getSoftWrapHangingIndentLength()).toBe(4); @@ -502,9 +540,11 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.softWrapHangingIndent', 4); expect(editor.getSoftWrapHangingIndentLength()).toBe(4); + + done(); }); - it('enables or disables preferred line length-based soft wrap based on the config', async function() { + it('enables or disables preferred line length-based soft wrap based on the config', async function(done) { editor.update({ softWrapped: true, preferredLineLength: 80, @@ -522,9 +562,11 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.softWrapAtPreferredLineLength', true); expect(editor.getSoftWrapColumn()).toBe(80); + + done(); }); - it('allows for custom definition of maximum soft wrap based on config', async function() { + it('allows for custom definition of maximum soft wrap based on config', async function(done) { editor.update({ softWrapped: false, maxScreenLineLength: 1500 @@ -537,9 +579,11 @@ describe('TextEditorRegistry', function() { registry.maintainConfig(editor); await initialPackageActivation; expect(editor.getSoftWrapColumn()).toBe(500); + + done(); }); - it('sets the preferred line length based on the config', async function() { + it('sets the preferred line length based on the config', async function(done) { editor.update({ preferredLineLength: 80 }); expect(editor.getPreferredLineLength()).toBe(80); @@ -550,9 +594,11 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.preferredLineLength', 80); expect(editor.getPreferredLineLength()).toBe(80); + + done(); }); - it('enables or disables auto-indent based on the config', async function() { + it('enables or disables auto-indent based on the config', async function(done) { editor.update({ autoIndent: true }); expect(editor.shouldAutoIndent()).toBe(true); @@ -563,9 +609,11 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.autoIndent', true); expect(editor.shouldAutoIndent()).toBe(true); + + done(); }); - it('enables or disables auto-indent-on-paste based on the config', async function() { + it('enables or disables auto-indent-on-paste based on the config', async function(done) { editor.update({ autoIndentOnPaste: true }); expect(editor.shouldAutoIndentOnPaste()).toBe(true); @@ -576,9 +624,11 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.autoIndentOnPaste', true); expect(editor.shouldAutoIndentOnPaste()).toBe(true); + + done(); }); - it('enables or disables scrolling past the end of the buffer based on the config', async function() { + it('enables or disables scrolling past the end of the buffer based on the config', async function(done) { editor.update({ scrollPastEnd: true }); expect(editor.getScrollPastEnd()).toBe(true); @@ -589,9 +639,11 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.scrollPastEnd', true); expect(editor.getScrollPastEnd()).toBe(true); + + done(); }); - it('sets the undo grouping interval based on the config', async function() { + it('sets the undo grouping interval based on the config', async function(done) { editor.update({ undoGroupingInterval: 300 }); expect(editor.getUndoGroupingInterval()).toBe(300); @@ -602,9 +654,11 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.undoGroupingInterval', 300); expect(editor.getUndoGroupingInterval()).toBe(300); + + done(); }); - it('sets the scroll sensitivity based on the config', async function() { + it('sets the scroll sensitivity based on the config', async function(done) { editor.update({ scrollSensitivity: 50 }); expect(editor.getScrollSensitivity()).toBe(50); @@ -615,10 +669,12 @@ describe('TextEditorRegistry', function() { atom.config.set('editor.scrollSensitivity', 70); expect(editor.getScrollSensitivity()).toBe(70); + + done(); }); describe('when called twice with a given editor', function() { - it('does nothing the second time', async function() { + it('does nothing the second time', async function(done) { editor.update({ scrollSensitivity: 50 }); const disposable1 = registry.maintainConfig(editor); @@ -635,6 +691,8 @@ describe('TextEditorRegistry', function() { disposable1.dispose(); atom.config.set('editor.scrollSensitivity', 80); expect(editor.getScrollSensitivity()).toBe(70); + + done(); }); }); }); diff --git a/spec/text-editor-spec.js b/spec/text-editor-spec.js index 832c482a53..30e644f655 100644 --- a/spec/text-editor-spec.js +++ b/spec/text-editor-spec.js @@ -19,7 +19,7 @@ async function languageModeReady(editor) { describe('TextEditor', () => { let buffer, editor, lineLengths, languageMode; - beforeEach(async () => { + beforeEach(async (done) => { editor = await atom.workspace.open('sample.js'); buffer = editor.buffer; editor.update({ autoIndent: false }); @@ -33,15 +33,19 @@ describe('TextEditor', () => { if (languageMode.ready) { await languageMode.ready; } + + done(); }); - afterEach(async () => { + afterEach(async (done) => { if (languageMode?.atTransactionEnd) { await languageMode.atTransactionEnd(); } + + done(); }) - it('generates unique ids for each editor', async () => { + it('generates unique ids for each editor', async (done) => { // Deserialized editors are initialized with the serialized id. We can // initialize an editor with what we expect to be the next id: const deserialized = new TextEditor({ id: editor.id + 1 }); @@ -49,11 +53,13 @@ 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); + + done(); }); describe('when the editor is deserialized', () => { - it('restores selections and folds based on markers in the buffer', async () => { + it('restores selections and folds based on markers in the buffer', async (done) => { editor.setSelectedBufferRange([[1, 2], [3, 4]]); editor.addSelectionForBufferRange([[5, 6], [7, 5]], { reversed: true }); editor.foldBufferRow(4); @@ -79,9 +85,11 @@ describe('TextEditor', () => { expect(editor2.getSelections()[1].isReversed()).toBeTruthy(); expect(editor2.isFoldedAtBufferRow(4)).toBeTruthy(); editor2.destroy(); + + done(); }); - it("restores the editor's layout configuration", async () => { + it("restores the editor's layout configuration", async (done) => { editor.update({ softTabs: true, atomicSoftTabs: false, @@ -122,6 +130,8 @@ describe('TextEditor', () => { expect(editor2.displayLayer.softWrapColumn).toBe( editor2.getSoftWrapColumn() ); + + done(); }); it('ignores buffers with retired IDs', () => { @@ -211,7 +221,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); @@ -240,7 +250,7 @@ describe('TextEditor', () => { expect(editor.getLongTitle()).toBe('untitled'); }); - it("returns '' when opened files have identical file names", async () => { + it("returns '' when opened files have identical file names", async (done) => { const editor1 = await atom.workspace.open( path.join('sample-theme-1', 'readme') ); @@ -249,18 +259,22 @@ describe('TextEditor', () => { ); expect(editor1.getLongTitle()).toBe('readme \u2014 sample-theme-1'); expect(editor2.getLongTitle()).toBe('readme \u2014 sample-theme-2'); + + done(); }); - it("returns '' when opened files have identical file names in subdirectories", async () => { + it("returns '' when opened files have identical file names in subdirectories", async (done) => { const path1 = path.join('sample-theme-1', 'src', 'js'); const path2 = path.join('sample-theme-2', 'src', 'js'); const editor1 = await atom.workspace.open(path.join(path1, 'main.js')); const editor2 = await atom.workspace.open(path.join(path2, 'main.js')); expect(editor1.getLongTitle()).toBe(`main.js \u2014 ${path1}`); expect(editor2.getLongTitle()).toBe(`main.js \u2014 ${path2}`); + + done(); }); - it("returns '' when opened files have identical file and same parent dir name", async () => { + it("returns '' when opened files have identical file and same parent dir name", async (done) => { const editor1 = await atom.workspace.open( path.join('sample-theme-2', 'src', 'js', 'main.js') ); @@ -271,15 +285,19 @@ describe('TextEditor', () => { expect(editor2.getLongTitle()).toBe( `main.js \u2014 ${path.join('js', 'plugin')}` ); + + done(); }); - it('returns the filename when the editor is not in the workspace', async () => { + it('returns the filename when the editor is not in the workspace', async (done) => { editor.onDidDestroy(() => { expect(editor.getLongTitle()).toBe('sample.js'); }); await atom.workspace.getActivePane().close(); expect(editor.isDestroyed()).toBe(true); + + done(); }); }); @@ -366,8 +384,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 +1637,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 +2268,7 @@ describe('TextEditor', () => { spyOn( editor.getBuffer().getLanguageMode(), 'getNonWordCharacters' - ).andCallFake(function (position) { + ).and.callFake(function (position) { const result = '/()"\':,.;<>~!@#$%^&*|+=[]{}`?'; const scopes = this.scopeDescriptorForPosition( position @@ -2494,7 +2512,7 @@ describe('TextEditor', () => { ]); }); - it('takes atomic tokens into account', async () => { + it('takes atomic tokens into account', async (done) => { editor = await atom.workspace.open( 'sample-with-tabs-and-leading-comment.coffee', { autoIndent: false } @@ -2505,6 +2523,8 @@ describe('TextEditor', () => { [[2, 1], [2, 3]], [[3, 1], [3, 2]] ]); + + done(); }); }); @@ -2634,7 +2654,7 @@ describe('TextEditor', () => { ]); }); - it('takes atomic tokens into account', async () => { + it('takes atomic tokens into account', async (done) => { editor = await atom.workspace.open( 'sample-with-tabs-and-leading-comment.coffee', { autoIndent: false } @@ -2645,6 +2665,8 @@ describe('TextEditor', () => { [[3, 1], [3, 2]], [[2, 1], [2, 3]] ]); + + done(); }); }); @@ -2813,7 +2835,7 @@ describe('TextEditor', () => { }); }); - it('does not share selections between different edit sessions for the same buffer', async () => { + it('does not share selections between different edit sessions for the same buffer', async (done) => { atom.workspace.getActivePane().splitRight(); const editor2 = await atom.workspace.open(editor.getPath()); @@ -2823,6 +2845,8 @@ describe('TextEditor', () => { expect(editor2.getSelectedBufferRanges()).not.toEqual( editor.getSelectedBufferRanges() ); + + done(); }); }); @@ -3228,10 +3252,12 @@ describe('TextEditor', () => { }); describe('when there are many folds', () => { - beforeEach(async () => { + beforeEach(async (done) => { editor = await atom.workspace.open('sample-with-many-folds.js', { autoIndent: false }); + + done(); }); describe('and many selections intersects folded rows', () => @@ -3664,10 +3690,12 @@ describe('TextEditor', () => { })); describe('when there are many folds', () => { - beforeEach(async () => { + beforeEach(async (done) => { editor = await atom.workspace.open('sample-with-many-folds.js', { autoIndent: false }); + + done(); }); describe('and many selections intersects folded rows', () => @@ -4002,7 +4030,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 +4038,7 @@ describe('TextEditor', () => { const didInsertSpy = jasmine .createSpy() - .andCallFake(() => + .and.callFake(() => expect(buffer.lineForRow(1)).toBe( 'xxxvar sort = function(items) {' ) @@ -4025,18 +4053,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(); @@ -4181,13 +4209,15 @@ describe('TextEditor', () => { }); }); - it("inserts a newline below the cursor's current line, autoindents it, and moves the cursor to the end of the line", async () => { + it("inserts a newline below the cursor's current line, autoindents it, and moves the cursor to the end of the line", async (done) => { editor.update({ autoIndent: true }); editor.insertNewlineBelow(); await languageMode.atTransactionEnd(); expect(buffer.lineForRow(0)).toBe('var quicksort = function () {'); expect(buffer.lineForRow(1)).toBe(' '); expect(editor.getCursorBufferPosition()).toEqual([1, 2]); + + done(); }); }); @@ -4284,7 +4314,7 @@ describe('TextEditor', () => { expect(editor.indentationForBufferRow(1)).toBe(1); }); - it('indents the new line to the correct level when editor.autoIndent is true and using an off-side rule language', async () => { + it('indents the new line to the correct level when editor.autoIndent is true and using an off-side rule language', async (done) => { await atom.packages.activatePackage('language-coffee-script'); editor.update({ autoIndent: true }); atom.grammars.assignLanguageMode(editor, 'source.coffee'); @@ -4294,11 +4324,13 @@ describe('TextEditor', () => { expect(editor.indentationForBufferRow(1)).toBe(1); expect(editor.indentationForBufferRow(2)).toBe(0); expect(editor.indentationForBufferRow(3)).toBe(1); + + done(); }); }); describe('when a newline is appended on a line that matches the decreaseNextIndentPattern', () => { - it('indents the new line to the correct level when editor.autoIndent is true', async () => { + it('indents the new line to the correct level when editor.autoIndent is true', async (done) => { await atom.packages.activatePackage('language-go'); editor.update({ autoIndent: true }); atom.grammars.assignLanguageMode(editor, 'source.go'); @@ -4307,6 +4339,8 @@ describe('TextEditor', () => { editor.insertNewline(); expect(editor.indentationForBufferRow(1)).toBe(1); expect(editor.indentationForBufferRow(2)).toBe(0); + + done(); }); }); }); @@ -5377,7 +5411,7 @@ describe('TextEditor', () => { }); describe('when pasting line(s) above a line that matches the decreaseIndentPattern', () => - it('auto-indents based on the pasted line(s) only', async () => { + it('auto-indents based on the pasted line(s) only', async (done) => { atom.clipboard.write('a(x);\n b(x);\n c(x);\n', { indentBasis: 0 }); @@ -5389,6 +5423,8 @@ describe('TextEditor', () => { expect(editor.lineTextForBufferRow(8)).toBe(' b(x);'); expect(editor.lineTextForBufferRow(9)).toBe(' c(x);'); expect(editor.lineTextForBufferRow(10)).toBe(' }'); + + done(); })); describe('when pasting a line of text without line ending', () => @@ -5989,7 +6025,7 @@ describe('TextEditor', () => { describe('undo/redo restore selections of editor which initiated original change', () => { let editor1, editor2; - beforeEach(async () => { + beforeEach(async (done) => { editor1 = editor; editor2 = new TextEditor({ buffer: editor1.buffer }); @@ -6000,6 +6036,8 @@ describe('TextEditor', () => { dddddd eeeeee `); + + done(); }); it('[editor.transact] restore selection of change-initiated-editor', () => { @@ -6849,7 +6887,7 @@ describe('TextEditor', () => { }); - it('notifies onDidTokenize observers when retokenization is finished', async () => { + it('notifies onDidTokenize observers when retokenization is finished', async (done) => { // Exercise the full `tokenizeInBackground` code path, which bails out early if // `.setVisible` has not been called with `true`. jasmine.unspy(TextMateLanguageMode.prototype, 'tokenizeInBackground'); @@ -6864,9 +6902,11 @@ describe('TextEditor', () => { ).toBe(true); advanceClock(1); expect(events.length).toBe(1); + + done(); }); - it('notifies onDidChangeGrammar observers', async () => { + it('notifies onDidChangeGrammar observers', async (done) => { const events = []; editor.onDidChangeGrammar(grammar => events.push(grammar)); @@ -6876,6 +6916,8 @@ describe('TextEditor', () => { ).toBe(true); expect(events.length).toBe(1); expect(events[0].name).toBe('C'); + + done(); }); }); @@ -6931,12 +6973,14 @@ describe('TextEditor', () => { }); describe('when the line preceding the newline is a comment', () => { - it('maintains the indent of the commented line', async () => { + it('maintains the indent of the commented line', async (done) => { editor.setCursorBufferPosition([0, 0]); editor.insertText(' //'); editor.setCursorBufferPosition([0, Infinity]); editor.insertText('\n'); expect(editor.indentationForBufferRow(1)).toBe(2); + + done(); }); }); @@ -7402,14 +7446,16 @@ describe('TextEditor', () => { }); describe("when the editor's grammar has an injection selector", () => { - beforeEach(async () => { + beforeEach(async (done) => { atom.config.set('core.useTreeSitterParsers', false); await atom.packages.activatePackage('language-text'); await atom.packages.activatePackage('language-javascript'); + + done(); }); - it("includes the grammar's patterns when the selector matches the current scope in other grammars", async () => { + it("includes the grammar's patterns when the selector matches the current scope in other grammars", async (done) => { await atom.packages.activatePackage('language-hyperlink'); const grammar = atom.grammars.selectGrammar('text.js'); @@ -7426,10 +7472,12 @@ describe('TextEditor', () => { 'comment.line.double-slash.js', 'markup.underline.link.http.hyperlink' ]); + + done(); }); describe('when the grammar is added', () => { - it('retokenizes existing buffers that contain tokens that match the injection selector', async () => { + it('retokenizes existing buffers that contain tokens that match the injection selector', async (done) => { editor = await atom.workspace.open('sample.js'); editor.setText('// http://github.com'); let tokens = editor.tokensForScreenRow(0); @@ -7478,10 +7526,12 @@ describe('TextEditor', () => { ] } ]); + + done(); }); describe('when the grammar is updated', () => { - it('retokenizes existing buffers that contain tokens that match the injection selector', async () => { + it('retokenizes existing buffers that contain tokens that match the injection selector', async (done) => { editor = await atom.workspace.open('sample.js'); editor.setText('// SELECT * FROM OCTOCATS'); let tokens = editor.tokensForScreenRow(0); @@ -7589,6 +7639,8 @@ describe('TextEditor', () => { ] } ]); + + done(); }); }); }); @@ -8212,7 +8264,7 @@ describe('TextEditor', () => { }); describe('.getCommentDelimitersForBufferPosition', () => { - it('returns comment delimiters on a TextMate grammar', async () => { + it('returns comment delimiters on a TextMate grammar', async (done) => { atom.config.set('core.useTreeSitterParsers', false); editor = await atom.workspace.open('sample.js', { autoIndent: false }); @@ -8237,9 +8289,11 @@ describe('TextEditor', () => { line: '//', block: ['/*', '*/'] }) + + done(); }) - it('returns comment delimiters on a modern Tree-sitter grammar', async () => { + it('returns comment delimiters on a modern Tree-sitter grammar', async (done) => { jasmine.useRealClock(); atom.config.set('core.useTreeSitterParsers', true); @@ -8266,11 +8320,13 @@ describe('TextEditor', () => { line: '//', block: ['/*', '*/'] }) + + done(); }) }) describe('.syntaxTreeScopeDescriptorForBufferPosition(position)', () => { - it('returns the result of scopeDescriptorForBufferPosition() when textmate language mode is used', async () => { + it('returns the result of scopeDescriptorForBufferPosition() when textmate language mode is used', async (done) => { atom.config.set('core.useTreeSitterParsers', false); editor = await atom.workspace.open('sample.js', { autoIndent: false }); @@ -8297,9 +8353,11 @@ describe('TextEditor', () => { 'source.js', 'support.variable.property.js' ]); + + done(); }); - it('returns the result of syntaxTreeScopeDescriptorForBufferPosition() when tree-sitter language mode is used', async () => { + it('returns the result of syntaxTreeScopeDescriptorForBufferPosition() when tree-sitter language mode is used', async (done) => { jasmine.useRealClock(); editor = await atom.workspace.open('sample.js', { autoIndent: false }); await atom.packages.activatePackage('language-javascript'); @@ -8338,14 +8396,18 @@ describe('TextEditor', () => { 'member_expression', 'property_identifier' ]); + + done(); }); }); describe('.shouldPromptToSave()', () => { - beforeEach(async () => { + beforeEach(async (done) => { editor = await atom.workspace.open('sample.js'); jasmine.unspy(editor, 'shouldPromptToSave'); - spyOn(atom.stateStore, 'isConnected').andReturn(true); + spyOn(atom.stateStore, 'isConnected').and.returnValue(true); + + done(); }); it('returns true when buffer has unsaved changes', () => { @@ -8354,7 +8416,7 @@ describe('TextEditor', () => { expect(editor.shouldPromptToSave()).toBeTruthy(); }); - it("returns false when an editor's buffer is in use by more than one buffer", async () => { + it("returns false when an editor's buffer is in use by more than one buffer", async (done) => { editor.setText('changed'); atom.workspace.getActivePane().splitRight(); @@ -8365,9 +8427,11 @@ describe('TextEditor', () => { editor2.destroy(); expect(editor.shouldPromptToSave()).toBeTruthy(); + + done(); }); - it('returns true when the window is closing if the file has changed on disk', async () => { + it('returns true when the window is closing if the file has changed on disk', async (done) => { jasmine.useRealClock(); editor.setText('initial stuff'); @@ -8389,6 +8453,8 @@ describe('TextEditor', () => { projectHasPaths: true }) ).toBeTruthy(); + + done(); }); it('returns false when the window is closing and the project has one or more directory paths', () => { @@ -8413,9 +8479,11 @@ describe('TextEditor', () => { }); describe('.toggleLineCommentsInSelection()', () => { - beforeEach(async () => { + beforeEach(async (done) => { await atom.packages.activatePackage('language-javascript'); editor = await atom.workspace.open('sample.js'); + + done(); }); it('toggles comments on the selected lines', () => { @@ -8574,10 +8642,12 @@ describe('TextEditor', () => { describe('.toggleLineCommentsForBufferRows', () => { describe('xml', () => { - beforeEach(async () => { + beforeEach(async (done) => { await atom.packages.activatePackage('language-xml'); editor = await atom.workspace.open('test.xml'); editor.setText(''); + + done(); }); it('removes the leading whitespace from the comment end pattern match when uncommenting lines', () => { @@ -8653,10 +8723,12 @@ describe('TextEditor', () => { }); describe('less', () => { - beforeEach(async () => { + beforeEach(async (done) => { await atom.packages.activatePackage('language-less'); await atom.packages.activatePackage('language-css'); editor = await atom.workspace.open('sample.less'); + + done(); }); it('only uses the `commentEnd` pattern if it comes from the same grammar as the `commentStart` when commenting lines', () => { @@ -8666,9 +8738,11 @@ describe('TextEditor', () => { }); describe('css', () => { - beforeEach(async () => { + beforeEach(async (done) => { await atom.packages.activatePackage('language-css'); editor = await atom.workspace.open('css.css'); + + done(); }); it('comments/uncomments lines in the given range', () => { @@ -8726,9 +8800,11 @@ describe('TextEditor', () => { }); describe('coffeescript', () => { - beforeEach(async () => { + beforeEach(async (done) => { await atom.packages.activatePackage('language-coffee-script'); editor = await atom.workspace.open('coffee.coffee'); + + done(); }); it('comments/uncomments lines in the given range', () => { @@ -8767,9 +8843,11 @@ describe('TextEditor', () => { }); describe('javascript', () => { - beforeEach(async () => { + beforeEach(async (done) => { await atom.packages.activatePackage('language-javascript'); editor = await atom.workspace.open('sample.js'); + + done(); }); it('comments/uncomments lines in the given range', () => { @@ -8828,19 +8906,23 @@ describe('TextEditor', () => { }); describe('folding', () => { - beforeEach(async () => { + beforeEach(async (done) => { await atom.packages.activatePackage('language-javascript'); + + done(); }); - it('maintains cursor buffer position when a folding/unfolding', async () => { + it('maintains cursor buffer position when a folding/unfolding', async (done) => { editor = await atom.workspace.open('sample.js', { autoIndent: false }); editor.setCursorBufferPosition([5, 5]); editor.foldAll(); expect(editor.getCursorBufferPosition()).toEqual([5, 5]); + + done(); }); describe('.unfoldAll()', () => { - it('unfolds every folded line', async () => { + it('unfolds every folded line', async (done) => { editor = await atom.workspace.open('sample.js', { autoIndent: false }); await languageModeReady(editor); @@ -8852,9 +8934,11 @@ describe('TextEditor', () => { ); editor.unfoldAll(); expect(editor.getScreenLineCount()).toBe(initialScreenLineCount); + + done(); }); - it('unfolds every folded line with comments', async () => { + it('unfolds every folded line with comments', async (done) => { editor = await atom.workspace.open('sample-with-comments.js', { autoIndent: false }); @@ -8868,11 +8952,13 @@ describe('TextEditor', () => { ); editor.unfoldAll(); expect(editor.getScreenLineCount()).toBe(initialScreenLineCount); + + done(); }); }); describe('.foldAll()', () => { - it('folds every foldable line', async () => { + it('folds every foldable line', async (done) => { editor = await atom.workspace.open('sample.js', { autoIndent: false }); await languageModeReady(editor); @@ -8881,13 +8967,17 @@ describe('TextEditor', () => { expect([fold1.start.row, fold1.end.row]).toEqual([0, 12]); expect([fold2.start.row, fold2.end.row]).toEqual([1, 9]); expect([fold3.start.row, fold3.end.row]).toEqual([4, 7]); + + done(); }); }); describe('.foldBufferRow(bufferRow)', () => { - beforeEach(async () => { + beforeEach(async (done) => { editor = await atom.workspace.open('sample.js'); await languageModeReady(editor); + + done(); }); describe('when bufferRow can be folded', () => { @@ -8928,27 +9018,31 @@ describe('TextEditor', () => { }); describe('.foldCurrentRow()', () => { - it('creates a fold at the location of the last cursor', async () => { + it('creates a fold at the location of the last cursor', async (done) => { editor = await atom.workspace.open(); editor.setText('\nif (x) {\n y()\n}'); editor.setCursorBufferPosition([1, 0]); expect(editor.getScreenLineCount()).toBe(4); editor.foldCurrentRow(); expect(editor.getScreenLineCount()).toBe(3); + + done(); }); - it('does nothing when the current row cannot be folded', async () => { + it('does nothing when the current row cannot be folded', async (done) => { editor = await atom.workspace.open(); editor.setText('var x;\nx++\nx++'); editor.setCursorBufferPosition([0, 0]); expect(editor.getScreenLineCount()).toBe(3); editor.foldCurrentRow(); expect(editor.getScreenLineCount()).toBe(3); + + done(); }); }); describe('.foldAllAtIndentLevel(indentLevel)', () => { - it('folds blocks of text at the given indentation level', async () => { + it('folds blocks of text at the given indentation level', async (done) => { editor = await atom.workspace.open('sample.js', { autoIndent: false }); await languageModeReady(editor); @@ -8978,9 +9072,11 @@ describe('TextEditor', () => { ' if (items.length <= 1) return items;' ); expect(editor.getLastScreenRow()).toBe(9); + + done(); }); - it('does not fold anything but the indentLevel', async () => { + it('does not fold anything but the indentLevel', async (done) => { editor = await atom.workspace.open('sample-with-comments.js', { autoIndent: false }); @@ -8990,6 +9086,8 @@ describe('TextEditor', () => { const folds = editor.unfoldAll(); expect(folds.length).toBe(1); expect([folds[0].start.row, folds[0].end.row]).toEqual([0, 30]); + + done(); }); }); }); diff --git a/spec/text-mate-language-mode-spec.js b/spec/text-mate-language-mode-spec.js index ad23d91143..666981cea4 100644 --- a/spec/text-mate-language-mode-spec.js +++ b/spec/text-mate-language-mode-spec.js @@ -8,13 +8,15 @@ const dedent = require('dedent'); describe('TextMateLanguageMode', () => { let languageMode, buffer, config; - beforeEach(async () => { + beforeEach(async (done) => { config = atom.config; config.set('core.useTreeSitterParsers', false); // enable async tokenization TextMateLanguageMode.prototype.chunkSize = 5; jasmine.unspy(TextMateLanguageMode.prototype, 'tokenizeInBackground'); await atom.packages.activatePackage('language-javascript'); + + done(); }); afterEach(() => { @@ -24,7 +26,7 @@ describe('TextMateLanguageMode', () => { }); describe('when the editor is constructed with the largeFileMode option set to true', () => { - it("loads the editor but doesn't tokenize", async () => { + it("loads the editor but doesn't tokenize", async (done) => { const line = 'a b c d\n'; buffer = new TextBuffer(line.repeat(256 * 1024)); expect(buffer.getText().length).toBe(2 * 1024 * 1024); @@ -48,6 +50,8 @@ describe('TextMateLanguageMode', () => { iterator.seek({ row: 0, column: 0 }); iterator.moveToSuccessor(); expect(iterator.getPosition()).toEqual({ row: 0, column: 11 }); + + done(); }); }); @@ -427,7 +431,7 @@ describe('TextMateLanguageMode', () => { }); describe('when the buffer contains hard-tabs', () => { - beforeEach(async () => { + beforeEach(async (done) => { atom.packages.activatePackage('language-coffee-script'); buffer = atom.project.bufferForPathSync('sample-with-tabs.coffee'); @@ -437,6 +441,8 @@ describe('TextMateLanguageMode', () => { grammar: atom.grammars.grammarForScopeName('source.coffee') }); languageMode.startTokenizing(); + + done(); }); afterEach(() => { @@ -450,16 +456,18 @@ describe('TextMateLanguageMode', () => { }); describe('when tokenization completes', () => { - it('emits the `tokenized` event', async () => { + it('emits the `tokenized` event', async (done) => { const editor = await atom.workspace.open('sample.js'); 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); + + done(); }); - it("doesn't re-emit the `tokenized` event when it is re-tokenized", async () => { + it("doesn't re-emit the `tokenized` event when it is re-tokenized", async (done) => { const editor = await atom.workspace.open('sample.js'); fullyTokenize(editor.languageMode); @@ -468,11 +476,13 @@ describe('TextMateLanguageMode', () => { editor.getBuffer().insert([0, 0], "'"); fullyTokenize(editor.languageMode); expect(tokenizedHandler).not.toHaveBeenCalled(); + + done(); }); }); - describe('when the grammar is updated because a grammar it includes is activated', async () => { - it('re-emits the `tokenized` event', async () => { + describe('when the grammar is updated because a grammar it includes is activated', () => { + it('re-emits the `tokenized` event', async (done) => { let tokenizationCount = 0; const editor = await atom.workspace.open('coffee.coffee'); @@ -485,9 +495,11 @@ describe('TextMateLanguageMode', () => { await atom.packages.activatePackage('language-coffee-script'); fullyTokenize(editor.getBuffer().getLanguageMode()); expect(tokenizationCount).toBe(1); + + done(); }); - it('retokenizes the buffer', async () => { + it('retokenizes the buffer', async (done) => { await atom.packages.activatePackage('language-ruby-on-rails'); await atom.packages.activatePackage('language-ruby'); @@ -515,12 +527,14 @@ describe('TextMateLanguageMode', () => { 'punctuation.definition.tag.begin.html' ] }); + + done(); }); }); 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 +546,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(); }); }); @@ -832,7 +846,7 @@ describe('TextMateLanguageMode', () => { iterator.moveToSuccessor(); }); // ensure we don't infinitely loop (regression test) - it('does not report columns beyond the length of the line', async () => { + it('does not report columns beyond the length of the line', async (done) => { await atom.packages.activatePackage('language-coffee-script'); buffer = new TextBuffer({ text: '# hello\n# world' }); @@ -857,6 +871,8 @@ describe('TextMateLanguageMode', () => { iterator.seek(Point(0, 8)); expect(iterator.getPosition().column).toBe(7); + + done(); }); it('correctly terminates scopes at the beginning of the line (regression)', () => { @@ -998,9 +1014,11 @@ describe('TextMateLanguageMode', () => { let editor; describe('javascript', () => { - beforeEach(async () => { + beforeEach(async (done) => { editor = await atom.workspace.open('sample.js', { autoIndent: false }); await atom.packages.activatePackage('language-javascript'); + + done(); }); it('bases indentation off of the previous non-blank line', () => { @@ -1026,10 +1044,12 @@ describe('TextMateLanguageMode', () => { }); describe('css', () => { - beforeEach(async () => { + beforeEach(async (done) => { editor = await atom.workspace.open('css.css', { autoIndent: true }); await atom.packages.activatePackage('language-source'); await atom.packages.activatePackage('language-css'); + + done(); }); it('does not return negative values (regression)', () => { @@ -1117,7 +1137,7 @@ describe('TextMateLanguageMode', () => { expect(languageMode.isFoldableAtRow(8)).toBe(false); }); - it('returns true if the line starts a multi-line comment', async () => { + it('returns true if the line starts a multi-line comment', async (done) => { editor = await atom.workspace.open('sample-with-comments.js'); fullyTokenize(editor.getBuffer().getLanguageMode()); @@ -1130,21 +1150,27 @@ describe('TextMateLanguageMode', () => { expect(editor.isFoldableAtBufferRow(21)).toBe(true); expect(editor.isFoldableAtBufferRow(24)).toBe(true); expect(editor.isFoldableAtBufferRow(28)).toBe(false); + + done(); }); - it('returns true for lines that end with a comment and are followed by an indented line', async () => { + it('returns true for lines that end with a comment and are followed by an indented line', async (done) => { editor = await atom.workspace.open('sample-with-comments.js'); expect(editor.isFoldableAtBufferRow(5)).toBe(true); + + done(); }); - it("does not return true for a line in the middle of a comment that's followed by an indented line", async () => { + it("does not return true for a line in the middle of a comment that's followed by an indented line", async (done) => { editor = await atom.workspace.open('sample-with-comments.js'); fullyTokenize(editor.getBuffer().getLanguageMode()); expect(editor.isFoldableAtBufferRow(7)).toBe(false); editor.buffer.insert([8, 0], ' '); expect(editor.isFoldableAtBufferRow(7)).toBe(false); + + done(); }); }); @@ -1214,7 +1240,7 @@ describe('TextMateLanguageMode', () => { `); }); - it('folds every foldable range at a given indentLevel', async () => { + it('folds every foldable range at a given indentLevel', async (done) => { editor = await atom.workspace.open('sample-with-comments.js'); fullyTokenize(editor.getBuffer().getLanguageMode()); @@ -1226,6 +1252,8 @@ describe('TextMateLanguageMode', () => { expect([folds[2].start.row, folds[2].end.row]).toEqual([17, 20]); expect([folds[3].start.row, folds[3].end.row]).toEqual([21, 22]); expect([folds[4].start.row, folds[4].end.row]).toEqual([24, 25]); + + done(); }); }); @@ -1262,7 +1290,7 @@ describe('TextMateLanguageMode', () => { ); }); - it('works with multi-line comments', async () => { + it('works with multi-line comments', async (done) => { await atom.packages.activatePackage('language-javascript'); const editor = await atom.workspace.open('sample-with-comments.js', { autoIndent: false @@ -1280,6 +1308,8 @@ describe('TextMateLanguageMode', () => { expect([folds[5].start.row, folds[5].end.row]).toEqual([17, 20]); expect([folds[6].start.row, folds[6].end.row]).toEqual([21, 22]); expect([folds[7].start.row, folds[7].end.row]).toEqual([24, 25]); + + done(); }); }); @@ -1361,7 +1391,7 @@ describe('TextMateLanguageMode', () => { `); }); - it('works for coffee-script', async () => { + it('works for coffee-script', async (done) => { const editor = await atom.workspace.open('coffee.coffee'); await atom.packages.activatePackage('language-coffee-script'); buffer = editor.buffer; @@ -1379,9 +1409,11 @@ describe('TextMateLanguageMode', () => { expect( languageMode.getFoldableRangeContainingPoint(Point(19, Infinity), 2) ).toEqual([[19, Infinity], [20, Infinity]]); + + done(); }); - it('works for javascript', async () => { + it('works for javascript', async (done) => { const editor = await atom.workspace.open('sample.js'); await atom.packages.activatePackage('language-javascript'); buffer = editor.buffer; @@ -1411,9 +1443,11 @@ describe('TextMateLanguageMode', () => { 2 ) ).toEqual([[4, Infinity], [7, Infinity]]); + + done(); }); - it('searches upward and downward for surrounding comment lines and folds them as a single fold', async () => { + it('searches upward and downward for surrounding comment lines and folds them as a single fold', async (done) => { await atom.packages.activatePackage('language-javascript'); const editor = await atom.workspace.open('sample-with-comments.js'); editor.buffer.insert( @@ -1424,6 +1458,8 @@ describe('TextMateLanguageMode', () => { editor.foldBufferRow(1); const [fold] = editor.unfoldAll(); expect([fold.start.row, fold.end.row]).toEqual([1, 3]); + + done(); }); }); diff --git a/spec/theme-manager-spec.js b/spec/theme-manager-spec.js index 97828472a6..1be8284cb4 100644 --- a/spec/theme-manager-spec.js +++ b/spec/theme-manager-spec.js @@ -4,17 +4,18 @@ 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(done) { + await atom.themes.deactivateThemes(); + + try { + temp.cleanupSync(); + } catch (error) {} + + done(); }); describe('theme getters and setters', function() { @@ -32,15 +33,15 @@ 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(done) { + 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); + + done(); })); }); @@ -87,133 +88,117 @@ 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(done) { + 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); + 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'); - 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'); - }); + done(); }); - 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(done) { 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); + // `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'); - 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'); - }); + done(); }); }); 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 +236,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 +332,57 @@ h2 { }); describe('base style sheet loading', function() { - beforeEach(function() { + beforeEach(async function(done) { 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(); + + done(); }); - 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(done) { + 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); + done(); + }); + + describe('when there is a theme with incomplete variables', () => + it('loads the correct values from the fallback ui-variables', async function(done) { + 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 +391,10 @@ 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' - ]); + .backgroundColor + ).toBe('rgb(0, 152, 255)'); - 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)'); - }); + done(); })); }); @@ -425,7 +406,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 +414,57 @@ h2 { afterEach(() => jasmine.restoreDeprecationsSnapshot()); - it('reloads it', function() { - let styleElementAddedHandler, styleElementRemovedHandler; + it('reloads it', async function(done) { + 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); + expect(styleElementRemovedHandler).toHaveBeenCalled(); + expect( + styleElementRemovedHandler.calls.argsFor(0)[0].textContent + ).toContain('dashed'); + expect(getComputedStyle(document.body).borderStyle).toBe('none'); - runs(function() { - expect(styleElementRemovedHandler).toHaveBeenCalled(); - expect( - styleElementRemovedHandler.argsForCall[0][0].textContent - ).toContain('dashed'); - expect(getComputedStyle(document.body).borderStyle).toBe('none'); - }); + done(); }); }); @@ -491,7 +472,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 +483,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 +498,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 +512,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 +526,29 @@ 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(done) { + 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(); + + done(); }); 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 +557,12 @@ 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(done) { atom.config.set('core.themes', ['atom-light-ui', 'atom-dark-syntax']); - waitsForPromise(() => atom.themes.activateThemes()); + await atom.themes.activateThemes(); + + done(); }); it('uses the enabled themes', function() { @@ -589,13 +574,15 @@ h2 { }); describe('when the enabled UI and syntax themes are not bundled with Atom', function() { - beforeEach(function() { + beforeEach(async function(done) { atom.config.set('core.themes', [ 'installed-dark-ui', 'installed-dark-syntax' ]); - waitsForPromise(() => atom.themes.activateThemes()); + await atom.themes.activateThemes(); + + done(); }); it('uses the default dark UI and syntax themes', function() { @@ -607,13 +594,15 @@ h2 { }); describe('when the enabled UI theme is not bundled with Atom', function() { - beforeEach(function() { + beforeEach(async function(done) { atom.config.set('core.themes', [ 'installed-dark-ui', 'atom-light-syntax' ]); - waitsForPromise(() => atom.themes.activateThemes()); + await atom.themes.activateThemes(); + + done(); }); it('uses the default one-dark UI theme', function() { @@ -625,13 +614,15 @@ h2 { }); describe('when the enabled syntax theme is not bundled with Atom', function() { - beforeEach(function() { + beforeEach(async function(done) { atom.config.set('core.themes', [ 'atom-light-ui', 'installed-dark-syntax' ]); - waitsForPromise(() => atom.themes.activateThemes()); + await atom.themes.activateThemes(); + + done(); }); it('uses the default one-dark syntax theme', function() { diff --git a/spec/tree-indenter-spec.js b/spec/tree-indenter-spec.js index d59dcf104b..0e67386733 100644 --- a/spec/tree-indenter-spec.js +++ b/spec/tree-indenter-spec.js @@ -49,7 +49,7 @@ describe('TreeIndenter', () => { let editor, buffer, grammar; let languageMode, treeIndenter; - beforeEach(async () => { + beforeEach(async (done) => { editor = await atom.workspace.open(''); buffer = editor.getBuffer(); editor.displayLayer.reset({ foldCharacter: '…' }); @@ -57,6 +57,8 @@ describe('TreeIndenter', () => { grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript' }); + + done(); }); /** load a file from disk and verify that our proposed indentation diff --git a/spec/tree-sitter-language-mode-spec.js b/spec/tree-sitter-language-mode-spec.js index 3fc7451d78..29ab519ce2 100644 --- a/spec/tree-sitter-language-mode-spec.js +++ b/spec/tree-sitter-language-mode-spec.js @@ -37,16 +37,18 @@ const rustGrammarPath = require.resolve( describe('TreeSitterLanguageMode', () => { let editor, buffer; - beforeEach(async () => { + beforeEach(async (done) => { editor = await atom.workspace.open(''); buffer = editor.getBuffer(); editor.displayLayer.reset({ foldCharacter: '…' }); atom.config.set('core.useTreeSitterParsers', true); atom.config.set('core.useLegacyTreeSitter', true); + + done(); }); 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 +77,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 +129,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 +160,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 +187,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 +234,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 +265,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 +294,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 +334,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 +367,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 +400,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 +432,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: { @@ -462,7 +464,7 @@ describe('TreeSitterLanguageMode', () => { }); describe('when the buffer changes during a parse', () => { - it('immediately parses again when the current parse completes', async () => { + it('immediately parses again when the current parse completes', async (done) => { const grammar = new TreeSitterGrammar(atom.grammars, jsGrammarPath, { parser: 'tree-sitter-javascript', scopes: { @@ -524,6 +526,8 @@ describe('TreeSitterLanguageMode', () => { { text: '();', scopes: [] } ] ]); + + done(); }); }); @@ -603,7 +607,7 @@ describe('TreeSitterLanguageMode', () => { }); }); - it('highlights code inside of injection points', async () => { + it('highlights code inside of injection points', async (done) => { atom.grammars.addGrammar(jsGrammar); atom.grammars.addGrammar(htmlGrammar); buffer.setText('node.innerHTML = html `\na ${b}\n`;'); @@ -660,9 +664,11 @@ describe('TreeSitterLanguageMode', () => { ], [{ text: '`', scopes: ['string'] }, { text: ';', scopes: [] }] ]); + + done(); }); - 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
'); @@ -702,7 +708,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('updates buffers highlighting when a grammar with injectionRegExp is added', async () => { + it('updates buffers highlighting when a grammar with injectionRegExp is added', async (done) => { atom.grammars.addGrammar(jsGrammar); buffer.setText('node.innerHTML = html `\na ${b}\n`;'); @@ -756,9 +762,11 @@ describe('TreeSitterLanguageMode', () => { ], [{ text: '`', scopes: ['string'] }, { text: ';', scopes: [] }] ]); + + done(); }); - it('handles injections that intersect', async () => { + it('handles injections that intersect', () => { const ejsGrammar = new TreeSitterGrammar( atom.grammars, ejsGrammarPath, @@ -837,7 +845,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('handles injections that are empty', async () => { + it('handles injections that are empty', async (done) => { atom.grammars.addGrammar(jsGrammar); atom.grammars.addGrammar(htmlGrammar); buffer.setText('text = html'); @@ -892,9 +900,11 @@ describe('TreeSitterLanguageMode', () => { { text: ';', scopes: [] } ] ]); + + done(); }); - 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 +968,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, @@ -984,7 +994,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('reports scopes from shallower layers when they are at the start or end of an injection', async () => { + it('reports scopes from shallower layers when they are at the start or end of an injection', async (done) => { await atom.packages.activatePackage('language-javascript'); editor.setGrammar(atom.grammars.grammarForScopeName('source.js')); @@ -1021,9 +1031,11 @@ describe('TreeSitterLanguageMode', () => { } ] ]); + + done(); }); - it('respects the `includeChildren` property of injection points', async () => { + it('respects the `includeChildren` property of injection points', () => { const rustGrammar = new TreeSitterGrammar( atom.grammars, rustGrammarPath, @@ -1098,7 +1110,7 @@ describe('TreeSitterLanguageMode', () => { ]); }); - it('notifies onDidTokenize listeners the first time all syntax highlighting is done', async () => { + it('notifies onDidTokenize listeners the first time all syntax highlighting is done', async (done) => { const promise = new Promise(resolve => { editor.onDidTokenize(event => { expectTokensToEqual(editor, [ @@ -1134,6 +1146,8 @@ describe('TreeSitterLanguageMode', () => { buffer.setLanguageMode(languageMode); await promise; + + done(); }); }); }); @@ -1142,15 +1156,15 @@ 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 () => { + it('matches the highlighting of a freshly-opened editor', async (done) => { jasmine.useRealClock(); const text = fs.readFileSync( @@ -1223,21 +1237,15 @@ 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; } + + done(); }); }); 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 +1300,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 +1348,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 +1409,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 +1452,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 +1564,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 +1597,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 +1704,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 +1742,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 +1830,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 +1871,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 +1972,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 +2006,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 +2073,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 +2092,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 +2143,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 +2164,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 +2226,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 +2284,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 +2399,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 +2440,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..69adc32074 100644 --- a/spec/update-process-env-spec.js +++ b/spec/update-process-env-spec.js @@ -36,7 +36,7 @@ describe('updateProcessEnv(launchEnv)', function() { }); describe('when the launch environment appears to come from a shell', function() { - it('updates process.env to match the launch environment because PWD is set', async function() { + it('updates process.env to match the launch environment because PWD is set', async function(done) { process.env = { WILL_BE_DELETED: 'hi', NODE_ENV: 'the-node-env', @@ -68,9 +68,11 @@ describe('updateProcessEnv(launchEnv)', function() { // case-insensitive environment variable matching, so we cannot replace it // with another object. expect(process.env).toBe(initialProcessEnv); + + done(); }); - it('updates process.env to match the launch environment because PROMPT is set', async function() { + it('updates process.env to match the launch environment because PROMPT is set', async function(done) { process.env = { WILL_BE_DELETED: 'hi', NODE_ENV: 'the-node-env', @@ -100,9 +102,11 @@ describe('updateProcessEnv(launchEnv)', function() { // case-insensitive environment variable matching, so we cannot replace it // with another object. expect(process.env).toBe(initialProcessEnv); + + done(); }); - it('updates process.env to match the launch environment because PSModulePath is set', async function() { + it('updates process.env to match the launch environment because PSModulePath is set', async function(done) { process.env = { WILL_BE_DELETED: 'hi', NODE_ENV: 'the-node-env', @@ -134,9 +138,11 @@ describe('updateProcessEnv(launchEnv)', function() { // case-insensitive environment variable matching, so we cannot replace it // with another object. expect(process.env).toBe(initialProcessEnv); + + done(); }); - it('allows ATOM_HOME to be overwritten only if the new value is a valid path', async function() { + it('allows ATOM_HOME to be overwritten only if the new value is a valid path', async function(done) { let newAtomHomePath = temp.mkdirSync('atom-home'); process.env = { @@ -183,9 +189,11 @@ describe('updateProcessEnv(launchEnv)', function() { NODE_PATH: '/the/node/path', ATOM_HOME: newAtomHomePath }); + + done(); }); - it('allows ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT to be preserved if set', async function() { + it('allows ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT to be preserved if set', async function(done) { process.env = { WILL_BE_DELETED: 'hi', NODE_ENV: 'the-node-env', @@ -221,9 +229,11 @@ describe('updateProcessEnv(launchEnv)', function() { NODE_PATH: '/the/node/path', ATOM_HOME: '/the/atom/home' }); + + done(); }); - it('allows an existing env variable to be updated', async function() { + it('allows an existing env variable to be updated', async function(done) { process.env = { WILL_BE_UPDATED: 'old-value', NODE_ENV: 'the-node-env', @@ -245,15 +255,16 @@ describe('updateProcessEnv(launchEnv)', function() { await updateProcessEnv(updatedEnv); expect(process.env).toEqual(updatedEnv); + + done(); }); }); 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 +288,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 +320,27 @@ describe('updateProcessEnv(launchEnv)', function() { // Doesn't error await updateProcessEnv(null); + + done(); }); }); describe('on windows', function() { - it('does not update process.env', async function() { + it('does not update process.env', async function(done) { 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' }); + + done(); }); }); describe('shouldGetEnvFromShell()', function() { it('indicates when the environment should be fetched from the shell', function() { - if (process.platform === 'win32') return; // TestsThatFailOnWin32 + jasmine.filterByPlatform({except: ['win32']}); // TestsThatFailOnWin32 process.platform = 'darwin'; expect(shouldGetEnvFromShell({ SHELL: '/bin/sh' })).toBe(true); diff --git a/spec/uri-handler-registry-spec.js b/spec/uri-handler-registry-spec.js index 390cfd903c..100532d1ec 100644 --- a/spec/uri-handler-registry-spec.js +++ b/spec/uri-handler-registry-spec.js @@ -11,7 +11,7 @@ describe('URIHandlerRegistry', () => { registry = new URIHandlerRegistry(5); }); - it('handles URIs on a per-host basis', async () => { + it('handles URIs on a per-host basis', async (done) => { const testPackageSpy = jasmine.createSpy(); const otherPackageSpy = jasmine.createSpy(); registry.registerHostHandler('test-package', testPackageSpy); @@ -33,9 +33,11 @@ describe('URIHandlerRegistry', () => { url.parse('atom://other-package/path', true), 'atom://other-package/path' ); + + done(); }); - it('keeps track of the most recent URIs', async () => { + it('keeps track of the most recent URIs', async (done) => { const spy1 = jasmine.createSpy(); const spy2 = jasmine.createSpy(); const changeSpy = jasmine.createSpy(); @@ -55,7 +57,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,14 +72,16 @@ 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'); expect(history[4].uri).toBe(uris[1]); + + done(); }); - it('refuses to handle bad URLs', async () => { + it('refuses to handle bad URLs', async (done) => { const invalidUris = [ 'atom:package/path', 'atom:8080://package/path', @@ -96,5 +100,7 @@ describe('URIHandlerRegistry', () => { } expect(numErrors).toBe(invalidUris.length); + + done(); }); }); diff --git a/spec/view-registry-spec.js b/spec/view-registry-spec.js index 192d1b347e..d4c02f1f51 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,18 @@ 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 (done) => { + 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(); + + done(); })); }); diff --git a/spec/wasm-tree-sitter-language-mode-spec.js b/spec/wasm-tree-sitter-language-mode-spec.js index 0d4836f8b7..eeeb1725d7 100644 --- a/spec/wasm-tree-sitter-language-mode-spec.js +++ b/spec/wasm-tree-sitter-language-mode-spec.js @@ -63,12 +63,14 @@ function wait(ms) { describe('WASMTreeSitterLanguageMode', () => { let editor, buffer, grammar; - beforeEach(async () => { + beforeEach(async (done) => { grammar = null; editor = await atom.workspace.open(''); buffer = editor.getBuffer(); editor.displayLayer.reset({ foldCharacter: '…' }); atom.config.set('core.useTreeSitterParsers', true); + + done(); }); afterEach(() => { @@ -76,7 +78,7 @@ describe('WASMTreeSitterLanguageMode', () => { }); 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', async (done) => { jasmine.useRealClock(); grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -122,9 +124,11 @@ describe('WASMTreeSitterLanguageMode', () => { { text: ';', scopes: ['punctuation'] } ] ]); + + done(); }); - it('can start or end multiple scopes at the same position', async () => { + it('can start or end multiple scopes at the same position', async (done) => { jasmine.useRealClock(); grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -171,9 +175,11 @@ describe('WASMTreeSitterLanguageMode', () => { { text: ';', scopes: [] } ] ]); + + done(); }); - it('can resume highlighting on a line that starts with whitespace', async () => { + it('can resume highlighting on a line that starts with whitespace', async (done) => { jasmine.useRealClock(); grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -200,9 +206,11 @@ describe('WASMTreeSitterLanguageMode', () => { { text: '();', scopes: [] } ] ]); + + done(); }); - it('correctly skips over tokens with zero size', async () => { + it('correctly skips over tokens with zero size', async (done) => { jasmine.useRealClock(); grammar = new WASMTreeSitterGrammar(atom.grammars, cGrammarPath, cConfig); @@ -252,9 +260,11 @@ describe('WASMTreeSitterLanguageMode', () => { ], [{ text: '}', scopes: [] }] ]); + + done(); }); - it("updates lines' highlighting when they are affected by distant changes", async () => { + it("updates lines' highlighting when they are affected by distant changes", async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -292,9 +302,11 @@ describe('WASMTreeSitterLanguageMode', () => { [{ text: 'c', scopes: [] }], [{ text: ')', scopes: [] }] ]); + + done(); }); - it('updates the range of the current node in the tree when highlight.invalidateOnChange is set', async () => { + it('updates the range of the current node in the tree when highlight.invalidateOnChange is set', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -370,9 +382,11 @@ describe('WASMTreeSitterLanguageMode', () => { { text: '`', scopes: ['lorem'] }, ] ]); + + done(); }) - 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)', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -416,9 +430,11 @@ describe('WASMTreeSitterLanguageMode', () => { { text: 'c', scopes: ['property'] } ] ]); + + done(); }); - it('handles multi-line nodes with children on different lines (regression)', async () => { + it('handles multi-line nodes with children on different lines (regression)', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -450,9 +466,11 @@ describe('WASMTreeSitterLanguageMode', () => { ], [{ text: '`', scopes: ['string'] }, { text: ';', scopes: [] }] ]); + + done(); }); - it('handles folds inside of highlighted tokens', async () => { + it('handles folds inside of highlighted tokens', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -488,9 +506,11 @@ describe('WASMTreeSitterLanguageMode', () => { { text: '();', scopes: [] } ] ]); + + done(); }); - it('applies regex match rules when specified', async () => { + it('applies regex match rules when specified', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -527,9 +547,11 @@ describe('WASMTreeSitterLanguageMode', () => { { text: ')', scopes: [] } ] ]); + + done(); }); - 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', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, rubyGrammarPath, rubyConfig); @@ -559,12 +581,14 @@ describe('WASMTreeSitterLanguageMode', () => { { text: ' )', scopes: [] } ] ]); + + done(); }); // TODO: Ignoring these specs because web-tree-sitter doesn't seem to do // async. We can rehabilitate them if we ever figure it out. xdescribe('when the buffer changes during a parse', () => { - it('immediately parses again when the current parse completes', async () => { + it('immediately parses again when the current parse completes', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -635,11 +659,13 @@ describe('WASMTreeSitterLanguageMode', () => { await languageMode.atTransactionEnd(); // await wait(2000); + + done(); }); }); describe('when changes are small enough to be re-parsed synchronously', () => { - it('can incorporate multiple consecutive synchronous updates', async () => { + it('can incorporate multiple consecutive synchronous updates', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -693,13 +719,15 @@ describe('WASMTreeSitterLanguageMode', () => { expectTokensToEqual(editor, [ [{ text: 'ab', scopes: ['function'] }, { text: '()', scopes: [] }] ]); + + done(); }); }); describe('injectionPoints and injectionPatterns', () => { let jsGrammar, htmlGrammar; - beforeEach(async () => { + beforeEach(async (done) => { let tempJsConfig = { ...jsConfig }; jsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, tempJsConfig); @@ -725,9 +753,11 @@ describe('WASMTreeSitterLanguageMode', () => { `); htmlGrammar.addInjectionPoint(SCRIPT_TAG_INJECTION_POINT); + + done(); }); - it('highlights code inside of injection points', async () => { + it('highlights code inside of injection points', async (done) => { jasmine.useRealClock(); atom.grammars.addGrammar(jsGrammar); atom.grammars.addGrammar(htmlGrammar); @@ -791,9 +821,11 @@ describe('WASMTreeSitterLanguageMode', () => { ], [{ text: '`', scopes: ['string'] }, { text: ';', scopes: [] }] ]); + + done(); }); - it('highlights the content after injections', async () => { + it('highlights the content after injections', async (done) => { jasmine.useRealClock(); atom.grammars.addGrammar(jsGrammar); atom.grammars.addGrammar(htmlGrammar); @@ -834,9 +866,11 @@ describe('WASMTreeSitterLanguageMode', () => { { text: '>', scopes: ['html'] } ] ]); + + done(); }); - it('updates a buffer\'s highlighting when a grammar with injectionRegex is added', async () => { + it('updates a buffer\'s highlighting when a grammar with injectionRegex is added', async (done) => { jasmine.useRealClock(); atom.grammars.addGrammar(jsGrammar); @@ -896,9 +930,11 @@ describe('WASMTreeSitterLanguageMode', () => { ], [{ text: '`', scopes: ['string'] }, { text: ';', scopes: [] }] ]); + + done(); }); - it('handles injections that intersect', async () => { + it('handles injections that intersect', async (done) => { const ejsGrammar = new WASMTreeSitterGrammar( atom.grammars, @@ -967,9 +1003,11 @@ describe('WASMTreeSitterLanguageMode', () => { { text: '>', scopes: ['html'] } ] ]); + + done(); }); - it('handles injections that are empty', async () => { + it('handles injections that are empty', async (done) => { jasmine.useRealClock(); atom.grammars.addGrammar(jsGrammar); atom.grammars.addGrammar(htmlGrammar); @@ -1029,15 +1067,17 @@ describe('WASMTreeSitterLanguageMode', () => { { text: ';', scopes: [] } ] ]); + + done(); }); - it('handles injections with no highlights query', async () => { + it('handles injections with no highlights query', async (done) => { jasmine.useRealClock(); atom.grammars.addGrammar(jsGrammar); 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, @@ -1055,9 +1095,11 @@ describe('WASMTreeSitterLanguageMode', () => { let descriptor = editor.scopeDescriptorForBufferPosition([0, 15]); expect(descriptor.getScopesArray()).toContain('text.html.basic'); + + done(); }); - 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', async (done) => { jasmine.useRealClock(); const ejsGrammar = new WASMTreeSitterGrammar( atom.grammars, @@ -1112,9 +1154,11 @@ describe('WASMTreeSitterLanguageMode', () => { { text: '%>', scopes: ['directive'] } ] ]); + + done(); }); - 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', async (done) => { const jsdocGrammar = new WASMTreeSitterGrammar( atom.grammars, jsdocGrammarPath, @@ -1144,9 +1188,11 @@ describe('WASMTreeSitterLanguageMode', () => { [{ text: '{', scopes: [] }], [{ text: '}', scopes: [] }] ]); + + done(); }); - it('reports scopes from shallower layers when they are at the start or end of an injection', async () => { + it('reports scopes from shallower layers when they are at the start or end of an injection', async (done) => { jasmine.useRealClock(); await atom.packages.activatePackage('language-javascript'); @@ -1200,9 +1246,11 @@ describe('WASMTreeSitterLanguageMode', () => { } ] ]); + + done(); }); - it('respects the `includeChildren` property of injection points', async () => { + it('respects the `includeChildren` property of injection points', async (done) => { const rustGrammar = new WASMTreeSitterGrammar( atom.grammars, rustGrammarPath, @@ -1279,9 +1327,11 @@ describe('WASMTreeSitterLanguageMode', () => { { text: '();', scopes: [] } ] ]); + + done(); }); - it('omits the injected grammar\'s base scope when `languageScope` is `null`', async () => { + it('omits the injected grammar\'s base scope when `languageScope` is `null`', async (done) => { let customJsConfig = { ...jsConfig }; let customJsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, customJsConfig); @@ -1327,9 +1377,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect( descriptor.getScopesArray().includes('source.js') ).toBe(false); + + done(); }); - it('uses a custom base scope on the injected layer when `languageScope` is a string', async () => { + it('uses a custom base scope on the injected layer when `languageScope` is a string', async (done) => { let customJsConfig = { ...jsConfig }; let customJsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, customJsConfig); @@ -1378,9 +1430,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect( descriptor.getScopesArray().includes('source.js.embedded') ).toBe(true); + + done(); }); - it('uses a custom base scope on the injected layer when `languageScope` is a function', async () => { + it('uses a custom base scope on the injected layer when `languageScope` is a function', async (done) => { let customJsConfig = { ...jsConfig }; let customJsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, customJsConfig); @@ -1431,9 +1485,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect( descriptor.getScopesArray().includes(`source.js.custom-${timestamp}`) ).toBe(true); + + done(); }); - it('allows multiple base scopes on the injected layer when `languageScope` is a function', async () => { + it('allows multiple base scopes on the injected layer when `languageScope` is a function', async (done) => { let customJsConfig = { ...jsConfig }; let customJsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, customJsConfig); @@ -1498,9 +1554,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect( descriptor.getScopesArray().includes(`meta.line0`) ).toBe(false); + + done(); }); - it('notifies onDidTokenize listeners the first time all syntax highlighting is done', async () => { + it('notifies onDidTokenize listeners the first time all syntax highlighting is done', async (done) => { const promise = new Promise(resolve => { editor.onDidTokenize(event => { expectTokensToEqual(editor, [ @@ -1535,6 +1593,8 @@ describe('WASMTreeSitterLanguageMode', () => { }); buffer.setLanguageMode(languageMode); await promise; + + done(); }); }); }); @@ -1543,15 +1603,15 @@ 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 () => { + it('matches the highlighting of a freshly-opened editor', async (done) => { jasmine.useRealClock(); const text = fs.readFileSync( @@ -1633,16 +1693,10 @@ 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; } + + done(); }); }); @@ -1650,10 +1704,12 @@ describe('WASMTreeSitterLanguageMode', () => { let editor; describe('javascript', () => { - beforeEach(async () => { + beforeEach(async (done) => { editor = await atom.workspace.open('sample.js', { autoIndent: false }); await atom.packages.activatePackage('language-javascript'); await editor.getBuffer().getLanguageMode().ready; + + done(); }); it('bases indentation off of the previous non-blank line', () => { @@ -1679,14 +1735,16 @@ describe('WASMTreeSitterLanguageMode', () => { }); describe('css', () => { - beforeEach(async () => { + beforeEach(async (done) => { editor = await atom.workspace.open('css.css', { autoIndent: true }); await atom.packages.activatePackage('language-source'); await atom.packages.activatePackage('language-css'); await editor.getBuffer().getLanguageMode().ready; + + done(); }); - it('does not return negative values (regression)', async () => { + it('does not return negative values (regression)', async (done) => { jasmine.useRealClock(); editor.setText('.test {\npadding: 0;\n}'); await wait(0); @@ -1695,16 +1753,20 @@ describe('WASMTreeSitterLanguageMode', () => { editor.setText('@media screen {\n .test {\n padding: 0;\n }\n}'); await wait(0); expect(editor.suggestedIndentForBufferRow(3)).toBe(1); + + done(); }); }); }); describe('.suggestedIndentForBufferRows', () => { - beforeEach(async () => { + beforeEach(async (done) => { await atom.packages.activatePackage('language-javascript'); + + done(); }) - it('works correctly when straddling an injection boundary', async () => { + it('works correctly when straddling an injection boundary', async (done) => { const jsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -1749,11 +1811,13 @@ describe('WASMTreeSitterLanguageMode', () => { let map = languageMode.suggestedIndentForBufferRows(0, 5, editor.getTabLength()); expect(Array.from(map.values())).toEqual([0, 1, 1, 2, 1, 0]); + + done(); }); }); 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', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); await grammar.setQueryForTest('foldsQuery', ` @@ -1805,9 +1869,11 @@ describe('WASMTreeSitterLanguageMode', () => { getB (c,…) {…} } `); + + done(); }); - 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', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); await grammar.setQueryForTest('foldsQuery', ` @@ -1866,9 +1932,11 @@ describe('WASMTreeSitterLanguageMode', () => { } else if (c) {… } else {…} `); + + done(); }); - it('can fold nodes of specified types', async () => { + it('can fold nodes of specified types', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); await grammar.setQueryForTest('foldsQuery', ` @@ -1925,9 +1993,11 @@ describe('WASMTreeSitterLanguageMode', () => { const element2 = `); + + done(); }); - it('updates its fold cache properly when `fold.invalidateOnChange` is specified', async () => { + it('updates its fold cache properly when `fold.invalidateOnChange` is specified', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, htmlGrammarPath, htmlConfig); await grammar.setQueryForTest('foldsQuery', scm` @@ -1991,9 +2061,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect(editor.isFoldableAtBufferRow(2)).toBe(true); expect(editor.isFoldableAtBufferRow(3)).toBe(false); expect(editor.isFoldableAtBufferRow(4)).toBe(false); + + done(); }); - it('understands custom predicates', async () => { + it('understands custom predicates', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, htmlGrammarPath, htmlConfig); await grammar.setQueryForTest('foldsQuery', scm` @@ -2037,9 +2109,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect(editor.isFoldableAtBufferRow(0)).toBe(false); expect(editor.isFoldableAtBufferRow(7)).toBe(true); + + done(); }); - 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', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); await grammar.setQueryForTest('foldsQuery', ` @@ -2080,9 +2154,11 @@ describe('WASMTreeSitterLanguageMode', () => { /**… */ const x = 1 /*…*/ `); + + done(); }); - it('folds between arbitrary points in the buffer with @fold.start and @fold.end markers', async () => { + it('folds between arbitrary points in the buffer with @fold.start and @fold.end markers', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, cGrammarPath, cConfig); await grammar.setQueryForTest('foldsQuery', ` @@ -2178,9 +2254,11 @@ describe('WASMTreeSitterLanguageMode', () => { #endif `); + + done(); }); - 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', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, htmlGrammarPath, htmlConfig); await grammar.setQueryForTest('foldsQuery', ` @@ -2206,9 +2284,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect(getDisplayText(editor)).toBe(dedent` … `); + + done(); }); - it('can target named vs anonymous nodes as fold boundaries', async () => { + it('can target named vs anonymous nodes as fold boundaries', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, rubyGrammarPath, rubyConfig); await grammar.setQueryForTest('foldsQuery', ` @@ -2269,9 +2349,11 @@ describe('WASMTreeSitterLanguageMode', () => { else… end `); + + done(); }); - it('updates fold locations when the buffer changes', async () => { + it('updates fold locations when the buffer changes', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); await grammar.setQueryForTest('foldsQuery', ` @@ -2311,10 +2393,12 @@ describe('WASMTreeSitterLanguageMode', () => { expect(languageMode.isFoldableAtRow(2)).toBe(false); expect(languageMode.isFoldableAtRow(3)).toBe(true); expect(languageMode.isFoldableAtRow(4)).toBe(false); + + done(); }); 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', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, pythonGrammarPath, CSON.readFileSync(pythonGrammarPath) @@ -2378,10 +2462,12 @@ describe('WASMTreeSitterLanguageMode', () => { print 'c' print 'd' `); + + done(); }); }); - it('folds code in injected languages', async () => { + it('folds code in injected languages', async (done) => { jasmine.useRealClock(); const htmlGrammar = new WASMTreeSitterGrammar( atom.grammars, @@ -2452,11 +2538,13 @@ describe('WASMTreeSitterLanguageMode', () => { `a = html \`…\` ` ); + + done(); }); }); 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', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); await grammar.setQueryForTest('highlightsQuery', ` @@ -2499,9 +2587,11 @@ describe('WASMTreeSitterLanguageMode', () => { .scopeDescriptorForBufferPosition([0, '// baz'.length]) .getScopesArray() ).toEqual(['source.js', 'comment.block']); + + done(); }); - it('includes nodes in injected syntax trees', async () => { + it('includes nodes in injected syntax trees', async (done) => { const jsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); await jsGrammar.setQueryForTest('highlightsQuery', ` @@ -2556,9 +2646,11 @@ describe('WASMTreeSitterLanguageMode', () => { 'string.quoted', 'property.name' ]); + + done(); }); - it('reports scopes correctly at boundaries where more than one layer adds a scope', async () => { + it('reports scopes correctly at boundaries where more than one layer adds a scope', async (done) => { const jsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); await jsGrammar.setQueryForTest('highlightsQuery', ` @@ -2611,9 +2703,11 @@ describe('WASMTreeSitterLanguageMode', () => { 'text.html.basic', 'tag' ]); + + done(); }); - it('includes the root scope name even when the given position is in trailing whitespace at EOF', async () => { + it('includes the root scope name even when the given position is in trailing whitespace at EOF', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); await grammar.setQueryForTest('highlightsQuery', ` @@ -2629,9 +2723,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect( editor.scopeDescriptorForBufferPosition([0, 3]).getScopesArray() ).toEqual(['source.js']); + + done(); }); - it('works when the given position is between tokens', async () => { + it('works when the given position is between tokens', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); await grammar.setQueryForTest('highlightsQuery', ` @@ -2650,9 +2746,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect( editor.scopeDescriptorForBufferPosition([0, 3]).getScopesArray() ).toEqual(['source.js', 'comment.block']); + + done(); }); - it('works when a scope range has been adjusted', async () => { + it('works when a scope range has been adjusted', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); await grammar.setQueryForTest('highlightsQuery', ` @@ -2676,9 +2774,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect( editor.scopeDescriptorForBufferPosition([1, 2]).getScopesArray() ).toEqual(['source.js', 'comment.block']); + + done(); }); - it('ignores a parent\'s scopes if an injection layer sets `coverShallowerScopes`', async () => { + it('ignores a parent\'s scopes if an injection layer sets `coverShallowerScopes`', async (done) => { jasmine.useRealClock(); const jsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -2740,9 +2840,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect(scopes.includes('gadfly')).toBe(false); expect(scopes.includes('regex-outer')).toBe(true); expect(scopes.includes('regex-inner')).toBe(false); + + done(); }); - it('arranges scopes in the proper order when scopes from several layers were already open at a given point', async () => { + it('arranges scopes in the proper order when scopes from several layers were already open at a given point', async (done) => { jasmine.useRealClock(); const jsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -2806,12 +2908,14 @@ describe('WASMTreeSitterLanguageMode', () => { "string.regexp", "gadfly" ]); + + done(); }); }); 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', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -2847,9 +2951,11 @@ describe('WASMTreeSitterLanguageMode', () => { .syntaxTreeScopeDescriptorForBufferPosition([0, 5]) .getScopesArray() ).toEqual(['source.js', 'program', 'comment']); + + done(); }); - it('includes nodes in injected syntax trees', async () => { + it('includes nodes in injected syntax trees', async (done) => { const jsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); jsGrammar.addInjectionPoint(HTML_TEMPLATE_LITERAL_INJECTION_POINT); @@ -2905,12 +3011,14 @@ describe('WASMTreeSitterLanguageMode', () => { 'member_expression', 'property_identifier' ]); + + done(); }); }); 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', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); buffer.setText('foo({bar: baz});'); @@ -2927,9 +3035,11 @@ describe('WASMTreeSitterLanguageMode', () => { [0, 8], [0, 9] ]); + + done(); }); - it('includes nodes in injected syntax trees', async () => { + it('includes nodes in injected syntax trees', async (done) => { const jsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); jsGrammar.addInjectionPoint(HTML_TEMPLATE_LITERAL_INJECTION_POINT); @@ -2977,11 +3087,13 @@ describe('WASMTreeSitterLanguageMode', () => { expect( languageMode.bufferRangeForScopeAtPosition(null, position) ).toEqual(nameProperty); + + done(); }); }); 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', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); await grammar.setQueryForTest('highlightsQuery', ` @@ -3001,9 +3113,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect( editor.bufferRangeForScopeAtPosition('.string.quoted', [0, 6]) ).toEqual([[0, 2], [0, 24]]); + + done(); }); - it('includes nodes in injected syntax trees', async () => { + it('includes nodes in injected syntax trees', async (done) => { const jsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); jsGrammar.addInjectionPoint(HTML_TEMPLATE_LITERAL_INJECTION_POINT); await jsGrammar.setQueryForTest('highlightsQuery', ` @@ -3058,9 +3172,11 @@ describe('WASMTreeSitterLanguageMode', () => { position ) ).toEqual(buffer.findSync('\\${person\\.name}')); + + done(); }); - it('reports results correctly when scope ranges have been adjusted', async () => { + it('reports results correctly when scope ranges have been adjusted', async (done) => { jasmine.useRealClock(); const jsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -3097,9 +3213,11 @@ describe('WASMTreeSitterLanguageMode', () => { range = languageMode.bufferRangeForScopeAtPosition('comment.block', new Point(1, 0)); expect(range.toString()).toBe(`[(1, 0) - (1, 29)]`); + + done(); }); - it('ignores scopes that are not present because they are covered by a deeper layer', async () => { + it('ignores scopes that are not present because they are covered by a deeper layer', async (done) => { // A similar test to the one above, except now we expect not to see the // scope because it's being covered by the injection layer. jasmine.useRealClock(); @@ -3155,9 +3273,11 @@ describe('WASMTreeSitterLanguageMode', () => { let point = new Point(0, 15); let range = languageMode.bufferRangeForScopeAtPosition('keyword', point); expect(range).toBe(undefined); + + done(); }); - it('accepts node-matching functions as selectors', async () => { + it('accepts node-matching functions as selectors', async (done) => { const jsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); jsGrammar.addInjectionPoint(HTML_TEMPLATE_LITERAL_INJECTION_POINT); @@ -3207,12 +3327,14 @@ describe('WASMTreeSitterLanguageMode', () => { position ) ).toEqual([[3, 19], [5, 15]]); + + done(); }); }); }); 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', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); buffer.setText('foo(bar({x: 2}));'); @@ -3234,6 +3356,8 @@ describe('WASMTreeSitterLanguageMode', () => { expect( languageMode.getSyntaxNodeAtPosition([0, 6], findFoo).range ).toEqual([[0, 0], [0, buffer.getText().length - 1]]); + + done(); }); }); @@ -3247,7 +3371,7 @@ describe('WASMTreeSitterLanguageMode', () => { atom.config.unset('editor.commentEnd', { scopeSelector: '.text.html.basic' }); }); - it('returns the correct comment strings for nested languages', async () => { + it('returns the correct comment strings for nested languages', async (done) => { jasmine.useRealClock(); const jsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -3371,9 +3495,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect(languageMode.commentStringsForPosition(new Point(6, 0))).toEqual( htmlCommentStrings ); + + done(); }); - it('uses grammar comment settings when config data is missing', async () => { + it('uses grammar comment settings when config data is missing', async (done) => { jasmine.useRealClock(); const jsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -3461,9 +3587,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect(languageMode.commentStringsForPosition(new Point(6, 0))).toEqual( htmlCommentStrings ); + + done(); }); - it('constructs the right comment settings when grammar data is missing', async () => { + it('constructs the right comment settings when grammar data is missing', async (done) => { jasmine.useRealClock(); const jsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -3475,8 +3603,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', @@ -3571,12 +3699,14 @@ describe('WASMTreeSitterLanguageMode', () => { expect(languageMode.commentStringsForPosition(new Point(6, 0))).toEqual( htmlCommentStrings ); + + done(); }); }); 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', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); await grammar.setQueryForTest('highlightsQuery', ` @@ -3618,9 +3748,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect(editor.getSelectedText()).toBe('eee'); editor.selectSmallerSyntaxNode(); expect(editor.getSelectedBufferRange()).toEqual([[1, 3], [1, 3]]); + + done(); }); - it('handles injected languages', async () => { + it('handles injected languages', async (done) => { const jsGrammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); await jsGrammar.setQueryForTest('highlightsQuery', ` @@ -3676,11 +3808,13 @@ describe('WASMTreeSitterLanguageMode', () => { expect(editor.getSelectedText()).toBe('` c${def()}e${f}g `'); editor.selectLargerSyntaxNode(); expect(editor.getSelectedText()).toBe('html ` c${def()}e${f}g `'); + + done(); }); }); describe('.tokenizedLineForRow(row)', () => { - it('returns a shimmed TokenizedLine with tokens', async () => { + it('returns a shimmed TokenizedLine with tokens', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); await grammar.setQueryForTest('highlightsQuery', ` @@ -3737,16 +3871,20 @@ describe('WASMTreeSitterLanguageMode', () => { { value: ' ', scopes: ['source'] }, { value: 'b', scopes: ['source', 'variable'] } ]); + + done(); }); }); describe('indentation', () => { - beforeEach(async () => { + beforeEach(async (done) => { await atom.packages.activatePackage('whitespace'); atom.config.set('whitespace.removeTrailingWhitespace', false); + + done(); }); - it('interprets @indent and @dedent captures', async () => { + it('interprets @indent and @dedent captures', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -3787,9 +3925,11 @@ describe('WASMTreeSitterLanguageMode', () => { editor.undo(); expect(buffer.getText()).toEqual(originalText); + + done(); }); - it('allows @dedents to cancel out @indents when appropriate', async () => { + it('allows @dedents to cancel out @indents when appropriate', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -3822,9 +3962,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect( editor.getLastCursor().getBufferPosition().toString() ).toEqual('(1, 2)'); + + done(); }); - it('allows @dedent.next to decrease the indent of the next line before any typing takes place', async () => { + it('allows @dedent.next to decrease the indent of the next line before any typing takes place', async (done) => { const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); // Pretend we're in a universe where lines after comments should be @@ -3844,9 +3986,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect( editor.getLastCursor().getBufferPosition().toString() ).toEqual('(1, 0)'); + + done(); }); - it('resolves @match captures', async () => { + it('resolves @match captures', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -3880,9 +4024,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect( editor.getLastCursor().getBufferPosition().toString() ).toEqual('(2, 1)'); + + done(); }); - it('prefers a @match capture even if a @dedent matches first', async () => { + it('prefers a @match capture even if a @dedent matches first', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -3916,9 +4062,11 @@ describe('WASMTreeSitterLanguageMode', () => { expect( editor.getLastCursor().getBufferPosition().toString() ).toEqual('(2, 1)'); + + done(); }); - it('adjusts correctly when text is pasted', async () => { + it('adjusts correctly when text is pasted', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -3938,7 +4086,7 @@ describe('WASMTreeSitterLanguageMode', () => { spyOn( languageMode, 'suggestedIndentForLineAtBufferRow' - ).andReturn(9); + ).and.returnValue(9); buffer.setLanguageMode(languageMode); await languageMode.ready; @@ -3972,9 +4120,11 @@ describe('WASMTreeSitterLanguageMode', () => { await wait(0); expect(editor.getText()).toEqual(emptyClassText); + + done(); }); - it('skips trying to insert at the correct indentation level when "paste without formatting" is invoked', async () => { + it('skips trying to insert at the correct indentation level when "paste without formatting" is invoked', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -4028,10 +4178,12 @@ describe('WASMTreeSitterLanguageMode', () => { await wait(0); expect(editor.getText()).toEqual(emptyClassText); + + done(); }); - it('preserves relative indentation across pasted text', async () => { + it('preserves relative indentation across pasted text', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -4083,9 +4235,11 @@ describe('WASMTreeSitterLanguageMode', () => { await wait(0); expect(editor.getText()).toEqual(emptyClassText); + + done(); }); - it('preserves relative indentation across pasted text (when the pasted text ends in a newline)', async () => { + it('preserves relative indentation across pasted text (when the pasted text ends in a newline)', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -4136,9 +4290,11 @@ describe('WASMTreeSitterLanguageMode', () => { await wait(0); expect(editor.getText()).toEqual(emptyClassText); + + done(); }); - it('auto-indents correctly if any change in a transaction wants auto-indentation', async () => { + it('auto-indents correctly if any change in a transaction wants auto-indentation', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); editor.updateAutoIndent(true); @@ -4168,7 +4324,7 @@ describe('WASMTreeSitterLanguageMode', () => { await languageMode.ready; await wait(0); - spyOn(languageMode, 'suggestedIndentForBufferRows').andCallThrough(); + spyOn(languageMode, 'suggestedIndentForBufferRows').and.callThrough(); editor.setCursorBufferPosition([0, 15]); editor.transact(() => { @@ -4214,9 +4370,11 @@ describe('WASMTreeSitterLanguageMode', () => { await wait(0); expect(editor.getText()).toEqual(emptyClassText); + + done(); }); - it('does not auto-indent if no change in a transaction wants auto-indentation', async () => { + it('does not auto-indent if no change in a transaction wants auto-indentation', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -4263,9 +4421,11 @@ describe('WASMTreeSitterLanguageMode', () => { await wait(0); expect(editor.getText()).toEqual(emptyClassText); + + done(); }); - it('auto-dedents exactly once and not after each new insertion on a line', async () => { + it('auto-dedents exactly once and not after each new insertion on a line', async (done) => { jasmine.useRealClock(); editor.updateAutoIndent(true); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -4300,9 +4460,11 @@ describe('WASMTreeSitterLanguageMode', () => { editor.insertText(' ', { autoIndent: true }); await languageMode.atTransactionEnd(); expect(editor.lineTextForBufferRow(2)).toEqual(` } `); + + done(); }); - it('maintains indent level through multiple newlines (removeTrailingWhitespace: true)', async () => { + it('maintains indent level through multiple newlines (removeTrailingWhitespace: true)', async (done) => { jasmine.useRealClock(); editor.updateAutoIndent(true); atom.config.set('whitespace.removeTrailingWhitespace', true); @@ -4346,9 +4508,11 @@ describe('WASMTreeSitterLanguageMode', () => { await languageMode.atTransactionEnd(); await wait(0); expect(editor.lineTextForBufferRow(4)).toEqual(' '); + + done(); }); - it('does not attempt to adjust indent on pasted text without a newline', async () => { + it('does not attempt to adjust indent on pasted text without a newline', async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -4392,9 +4556,11 @@ describe('WASMTreeSitterLanguageMode', () => { await wait(0); expect(editor.getText()).toEqual(emptyClassText); + + done(); }); - it('maintains indent level through multiple newlines (removeTrailingWhitespace: false)', async () => { + it('maintains indent level through multiple newlines (removeTrailingWhitespace: false)', async (done) => { jasmine.useRealClock(); editor.updateAutoIndent(true); atom.config.set('whitespace.removeTrailingWhitespace', false); @@ -4438,9 +4604,11 @@ describe('WASMTreeSitterLanguageMode', () => { await languageMode.atTransactionEnd(); await wait(0); expect(editor.lineTextForBufferRow(4)).toEqual(' '); + + done(); }); - it(`can indent properly in a multi-cursor environment without auto-indenting large ranges of the buffer`, async () => { + it(`can indent properly in a multi-cursor environment without auto-indenting large ranges of the buffer`, async (done) => { jasmine.useRealClock(); const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); @@ -4455,7 +4623,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; @@ -4496,6 +4664,7 @@ describe('WASMTreeSitterLanguageMode', () => { return } `) + done(); }) }); diff --git a/spec/window-event-handler-spec.js b/spec/window-event-handler-spec.js index ce9f70e351..a9f58e49a0 100644 --- a/spec/window-event-handler-spec.js +++ b/spec/window-event-handler-spec.js @@ -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; @@ -29,9 +29,7 @@ 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 + jasmine.filterByPlatform({except: ['win32']}); // Win32TestFailures - can not steal focus expect(document.body.className).not.toMatch('is-blurred'); })); @@ -51,13 +49,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 +83,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 +120,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 +261,7 @@ describe('WindowEventHandler', () => { 'copy', 'paste' ]); - spyOn(atom.applicationDelegate, 'getCurrentWindow').andReturn({ + spyOn(atom.applicationDelegate, 'getCurrentWindow').and.returnValue({ webContents: webContentsSpy, on: () => {} }); @@ -279,8 +277,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..74be948bba 100644 --- a/spec/workspace-element-spec.js +++ b/spec/workspace-element-spec.js @@ -665,7 +665,7 @@ describe('WorkspaceElement', () => { ); }); - it('shows the toggle button when the dock is open', async () => { + it('shows the toggle button when the dock is open', async (done) => { await Promise.all([ atom.workspace.open({ element: document.createElement('div'), @@ -835,6 +835,8 @@ describe('WorkspaceElement', () => { await getNextUpdatePromise(); expect(bottomDock.isVisible()).toBe(true); expectToggleButtonVisible(bottomDock, 'icon-chevron-down'); + + done(); }); function moveMouse(coordinates) { @@ -864,7 +866,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(() => {}); @@ -883,52 +885,71 @@ describe('WorkspaceElement', () => { describe('editor font styling', () => { let editor, editorElement, workspaceElement; - beforeEach(async () => { + beforeEach(async (done) => { await atom.workspace.open('sample.js'); workspaceElement = atom.workspace.getElement(); jasmine.attachToDOM(workspaceElement); editor = atom.workspace.getActiveTextEditor(); editorElement = editor.getElement(); + + done(); }); - it("updates the font-size based on the 'editor.fontSize' config value", async () => { + it("updates the font-size based on the 'editor.fontSize' config value", async (done) => { const initialCharWidth = editor.getDefaultCharWidth(); expect(getComputedStyle(editorElement).fontSize).toBe( atom.config.get('editor.fontSize') + 'px' ); - atom.config.set( - 'editor.fontSize', - atom.config.get('editor.fontSize') + 5 - ); - 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' ); expect(editor.getDefaultCharWidth()).toBeGreaterThan(initialCharWidth); + + done(); }); - it("updates the font-family based on the 'editor.fontFamily' config value", async () => { + it("updates the font-family based on the 'editor.fontFamily' config value", async (done) => { const initialCharWidth = editor.getDefaultCharWidth(); 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); + + done(); }); - it("updates the line-height based on the 'editor.lineHeight' config value", async () => { + it("updates the line-height based on the 'editor.lineHeight' config value", async (done) => { 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') ); expect(editor.getLineHeightInPixels()).not.toBe(initialLineHeight); + + done(); }); it('increases or decreases the font size when a ctrl-mousewheel event occurs', () => { @@ -1098,7 +1119,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 +1130,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 +1140,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 +1150,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 5c2a3d5cfa..3a5d9dc8e2 100644 --- a/spec/workspace-spec.js +++ b/spec/workspace-spec.js @@ -16,7 +16,13 @@ describe('Workspace', () => { let workspace; let setDocumentEdited; - beforeEach(() => { + let fsGetSizeSyncSpy; + let fsOpenSyncSpy; + + beforeEach(async (done) => { + fsGetSizeSyncSpy ||= spyOn(fs, 'getSizeSync').and.callThrough(); + fsOpenSyncSpy ||= spyOn(fs, 'openSync').and.callThrough(); + workspace = atom.workspace; workspace.resetFontSize(); spyOn(atom.applicationDelegate, 'confirm'); @@ -25,12 +31,16 @@ describe('Workspace', () => { 'setWindowDocumentEdited' ); atom.project.setPaths([atom.project.getDirectories()[0].resolve('dir')]); - waits(1); - waitsForPromise(() => atom.workspace.itemLocationStore.clear()); + await atom.workspace.itemLocationStore.clear(); + + done(); }); afterEach(() => { + fsGetSizeSyncSpy.and.callThrough(); + fsOpenSyncSpy.and.callThrough(); + try { temp.cleanupSync(); } catch (e) { @@ -38,130 +48,110 @@ 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 (done) => { 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}` + ) + ); + + done(); }); }); 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 (done) => { 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); + + done(); }); }); }); @@ -172,109 +162,91 @@ 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 (done) => { 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 + } + ]); + + done(); }); }); 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 (done) => { 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 + } + ]); + + done(); }); - it('finds items in docks', () => { + it('finds items in docks', async (done) => { const dock = atom.workspace.getRightDock(); const ITEM_URI = 'atom://test'; const item = { @@ -284,30 +256,28 @@ 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); + + done(); }); }); describe("when the 'activateItem' option is false", () => { - it('adds the item to the workspace', () => { + it('adds the item to the workspace', async (done) => { 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); + + done(); }); }); @@ -316,71 +286,68 @@ describe('Workspace', () => { atom.workspace.enablePersistence = true; }); - afterEach(async () => { + afterEach(async (done) => { await atom.workspace.itemLocationStore.clear(); atom.workspace.enablePersistence = false; + + done(); }); - 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 (done) => { + 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(); + + done(); }); - it('discovers existing editors that are still opening', () => { + it('discovers existing editors that are still opening', async (done) => { 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]); + + done(); }); - it("uses the location specified by the model's `getDefaultLocation()` method", () => { + it("uses the location specified by the model's `getDefaultLocation()` method", async (done) => { 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(); + + done(); }); - it('prefers the last location the user used for that item', () => { + it('prefers the last location the user used for that item', async (done) => { const ITEM_URI = 'atom://test'; const item = { getURI: () => ITEM_URI, @@ -389,24 +356,26 @@ 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); + + done(); }); }); }); describe('when an item with the given uri exists in an inactive pane container', () => { - it("activates that item if it is in that container's active pane", async () => { + it("activates that item if it is in that container's active pane", async (done) => { const item = await atom.workspace.open('a'); atom.workspace.getLeftDock().activate(); expect( @@ -427,43 +396,37 @@ describe('Workspace', () => { 'center' ); expect(atom.workspace.getPaneItems()).toEqual([item, item2]); + + done(); }); }); }); 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 (done) => { 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); + + done(); }); - 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 (done) => { let editor0 = null; let editor1 = null; const pane0 = workspace.getActivePane(); @@ -482,52 +445,49 @@ 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]); + + done(); }); - it('activates the pane in the dock with the matching item', () => { + it('activates the pane in the dock with the matching item', async (done) => { 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() + + done(); }); }); 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 (done) => { + let editor = await workspace.open('a', { searchAllPanes: true }); + + expect(workspace.getActivePaneItem()).toBe(editor); - runs(() => expect(workspace.getActivePaneItem()).toBe(editor)); + done(); }); }); }); describe('when attempting to open an editor in a dock', () => { - it('opens the editor in the workspace center', async () => { + it('opens the editor in the workspace center', async (done) => { await atom.workspace.open('sample.txt', { location: 'right' }); expect( atom.workspace @@ -535,18 +495,22 @@ describe('Workspace', () => { .getActivePaneItem() .getFileName() ).toEqual('sample.txt'); + + done(); }); }); describe('when called with an item rather than a URI', () => { - it('adds the item itself to the workspace', async () => { + it('adds the item itself to the workspace', async (done) => { const item = document.createElement('div'); await atom.workspace.open(item); expect(atom.workspace.getActivePaneItem()).toBe(item); + + done(); }); describe('when the active pane already contains the item', () => { - it('activates the item', async () => { + it('activates the item', async (done) => { const item = document.createElement('div'); await atom.workspace.open(item); @@ -557,11 +521,13 @@ describe('Workspace', () => { await atom.workspace.open(item); expect(atom.workspace.getActivePaneItem()).toBe(item); expect(atom.workspace.getActivePane().getItems().length).toBe(2); + + done(); }); }); describe('when the item already exists in another pane', () => { - it('rejects the promise', async () => { + it('rejects the promise', async (done) => { const item = document.createElement('div'); await atom.workspace.open(item); @@ -579,48 +545,39 @@ describe('Workspace', () => { expect(rejection.message).toMatch( /The workspace can only contain one instance of item/ ); + + done(); }); }); }); 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 (done) => { 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([]); + + done(); }); }); 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 (done) => { let editor = null; const pane1 = workspace.getActivePane(); const pane2 = pane1.splitLeft(); @@ -628,54 +585,41 @@ 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]); + + done(); }); }); 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 (done) => { 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]); + + done(); }); 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 (done) => { let editor = null; const pane1 = workspace.getActivePane(); const pane2 = pane1.splitRight(); @@ -684,64 +628,49 @@ 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' }); + + 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 ); - 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 - ); - }); + done(); }); }); }); 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 (done) => { 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([]); + + done(); }); }); 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 (done) => { let editor = null; const pane1 = workspace.getActivePane(); const pane2 = pane1.splitUp(); @@ -749,54 +678,41 @@ 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]); + + done(); }); }); 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 (done) => { 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]); + + done(); }); 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 (done) => { let editor = null; const pane1 = workspace.getActivePane(); const pane2 = pane1.splitDown(); @@ -804,95 +720,69 @@ 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' }); + + 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 ); - 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 - ); - }); + done(); }); }); }); }); 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 (done) => { + 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]) + + done(); }); - it('unfolds the fold containing the line', async () => { + it('unfolds the fold containing the line', async (done) => { let editor; await workspace.open('../sample-with-many-folds.js'); @@ -906,15 +796,17 @@ describe('Workspace', () => { }); expect(editor.isFoldedAtBufferRow(2)).toBe(false); expect(editor.isFoldedAtBufferRow(3)).toBe(false); + + done(); }); }); 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 +815,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 { @@ -934,24 +826,30 @@ describe('Workspace', () => { } }; - it('prompts before opening the file', async () => { + it('prompts before opening the file', async (done) => { atom.config.set('core.warnOnLargeFileLimit', 20); await shouldPromptForFileOfSize(20, true); + + done(); }); - it("doesn't prompt on files below the limit", async () => { + it("doesn't prompt on files below the limit", async (done) => { atom.config.set('core.warnOnLargeFileLimit', 30); await shouldPromptForFileOfSize(20, false); + + done(); }); - it('prompts for smaller files with a lower limit', async () => { + it('prompts for smaller files with a lower limit', async (done) => { atom.config.set('core.warnOnLargeFileLimit', 5); await shouldPromptForFileOfSize(10, true); + + done(); }); }); 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 (done) => { const fooOpener = (pathToOpen, options) => { if (pathToOpen != null ? pathToOpen.match(/\.foo/) : undefined) { return { foo: pathToOpen, options }; @@ -965,70 +863,54 @@ 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' } + }) + + expect(await workspace.open('bar://baz')).toEqual({ bar: 'bar://baz' }) - waitsForPromise(() => - workspace - .open('bar://baz') - .then(item => expect(item).toEqual({ bar: 'bar://baz' })) - ); + done(); }); }); - 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 (done) => { 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); + + done(); }); describe('when there is an error opening the file', () => { @@ -1040,192 +922,167 @@ 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 (done) => { + 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'); + + done(); }); }); 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 (done) => { + 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'); + + done(); }); }); 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 (done) => { + 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'); + + done(); }); }); 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 (done) => { + 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'); + + done(); }); }); 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 (done) => { 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(); + + done(); }); }); 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 (done) => { let editor1 = null; let editor2 = null; - waitsForPromise(() => - atom.workspace.open('sample.txt').then(o => { - editor1 = o; - }) - ); + editor1 = await atom.workspace.open('sample.txt'); + editor2 = await atom.workspace.open('sample2.txt', { pending: true }); - waitsForPromise(() => - atom.workspace.open('sample2.txt', { pending: true }).then(o => { - editor2 = o; - }) - ); + const pane = atom.workspace.getActivePane(); + pane.activateItem(editor1); + expect(pane.getItems().length).toBe(2); + expect(pane.getItems()).toEqual([editor1, editor2]); - runs(() => { - const pane = atom.workspace.getActivePane(); - pane.activateItem(editor1); - expect(pane.getItems().length).toBe(2); - expect(pane.getItems()).toEqual([editor1, editor2]); - }); + done(); }); }); 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 (done) => { 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); + + done(); }); }); describe("when opening an editor with a buffer that isn't part of the project", () => { - it('adds the buffer to the project', async () => { + it('adds the buffer to the project', async (done) => { const buffer = new TextBuffer(); const editor = new TextEditor({ buffer }); @@ -1237,6 +1094,8 @@ describe('Workspace', () => { expect(buffer.getLanguageMode().getLanguageId()).toBe( 'text.plain.null-grammar' ); + + done(); }); }); }); @@ -1341,7 +1200,7 @@ describe('Workspace', () => { describe('::toggle(itemOrUri)', () => { describe('when the location resolves to a dock', () => { - it('adds or shows the item and its dock if it is not currently visible, and otherwise hides the containing dock', async () => { + it('adds or shows the item and its dock if it is not currently visible, and otherwise hides the containing dock', async (done) => { const item1 = { getDefaultLocation() { return 'left'; @@ -1382,11 +1241,13 @@ describe('Workspace', () => { await workspace.toggle(item2); expect(dock.isVisible()).toBe(true); expect(dock.getActivePaneItem()).toBe(item2); + + done(); }); }); describe('when the location resolves to the center', () => { - it('adds or shows the item if it is not currently the active pane item, and otherwise removes the item', async () => { + it('adds or shows the item if it is not currently the active pane item, and otherwise removes the item', async (done) => { const item1 = { getDefaultLocation() { return 'center'; @@ -1415,6 +1276,8 @@ describe('Workspace', () => { await workspace.toggle(item1); expect(workspace.paneForItem(item1)).toBeUndefined(); expect(workspace.getActivePaneItem()).toBe(item2); + + done(); }); }); }); @@ -1581,7 +1444,7 @@ describe('Workspace', () => { }); describe('the grammar-used hook', () => { - it('fires when opening a file or changing the grammar of an open file', async () => { + it('fires when opening a file or changing the grammar of an open file', async (done) => { await atom.packages.activatePackage('language-javascript'); await atom.packages.activatePackage('language-coffee-script'); @@ -1608,16 +1471,18 @@ 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'); expect(coffeeScriptGrammarUsed).toHaveBeenCalled(); + + done(); }); }); describe('the root-scope-used hook', () => { - it('fires when opening a file or changing the grammar of an open file', async () => { + it('fires when opening a file or changing the grammar of an open file', async (done) => { await atom.packages.activatePackage('language-javascript'); await atom.packages.activatePackage('language-coffee-script'); @@ -1644,16 +1509,18 @@ 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'); expect(coffeeScriptGrammarUsed).toHaveBeenCalled(); + + done(); }); }); describe('the file opened hook', () => { - it('fires when opening a file', async () => { + it('fires when opening a file', async (done) => { const packageUsed = jasmine.createSpy('my-fake-package'); atom.packages.triggerDeferredActivationHooks(); @@ -1667,78 +1534,68 @@ describe('Workspace', () => { autoIndent: false }); expect(packageUsed).toHaveBeenCalled(); + + done(); }) }); 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 (done) => { 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(); - waitsForPromise(() => workspace.reopenItem()); + // does not reopen items with no uri + expect(workspace.getActivePaneItem().getURI()).toBeUndefined(); + pane.destroyActiveItem(); + + 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') + ) + + done(); }); }); @@ -1801,11 +1658,11 @@ 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 (done) => { + await workspace.openLicense(); + expect(workspace.getActivePaneItem().getText()).toMatch(/Copyright/) + + done(); }); }); @@ -1855,18 +1712,20 @@ 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 (done) => { 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()); + + done(); }); }); @@ -1945,30 +1804,32 @@ describe('Workspace', () => { expect(observed).toEqual([]); }); - it('invokes the observer when closing the one and only text editor after deserialization', async () => { + it('invokes the observer when closing the one and only text editor after deserialization', async (done) => { 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]); + + done(); }); }); describe('when an editor is destroyed', () => { - it('removes the editor', async () => { + it('removes the editor', async (done) => { const editor = await workspace.open('a'); expect(workspace.getTextEditors()).toHaveLength(1); editor.destroy(); expect(workspace.getTextEditors()).toHaveLength(0); + + done(); }); }); describe('when an editor is copied because its pane is split', () => { - it('sets up the new editor to be configured by the text editor registry', async () => { + it('sets up the new editor to be configured by the text editor registry', async (done) => { await atom.packages.activatePackage('language-javascript'); const editor = await workspace.open('a'); @@ -1980,10 +1841,12 @@ describe('Workspace', () => { const newEditor = workspace.getActiveTextEditor(); expect(newEditor).not.toBe(editor); expect(newEditor.getGrammar().name).toBe('JavaScript'); + + done(); }); }); - it('stores the active grammars used by all the open editors', async () => { + it('stores the active grammars used by all the open editors', async (done) => { await Promise.all([ atom.packages.activatePackage('language-javascript'), atom.packages.activatePackage('language-coffee-script'), @@ -2042,6 +1905,8 @@ describe('Workspace', () => { ]); atom2.destroy(); + + done(); }); describe('document.title', () => { @@ -2058,11 +1923,11 @@ 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 (done) => { + await atom.workspace.open('b'); + atom.project.setPaths([]); + done(); + }); it("sets the title to the pane item's title plus the item's path", () => { const item = atom.workspace.getActivePaneItem(); @@ -2128,7 +1993,11 @@ describe('Workspace', () => { }); describe('when the active pane item is inside a project path', () => { - beforeEach(() => waitsForPromise(() => atom.workspace.open('b'))); + beforeEach(async (done) => { + await atom.workspace.open('b'); + + done(); + }); describe('when there is an active pane item', () => { it("sets the title to the pane item's title plus the project path", () => { @@ -2190,9 +2059,13 @@ describe('Workspace', () => { }); describe('when the workspace is deserialized', () => { - beforeEach(() => waitsForPromise(() => atom.workspace.open('a'))); + beforeEach(async (done) => { + await atom.workspace.open('a'); - it("updates the title to contain the project's path", () => { + done(); + }); + + it("updates the title to contain the project's path", async (done) => { document.title = null; const atom2 = new AtomEnvironment({ @@ -2206,25 +2079,23 @@ describe('Workspace', () => { }) }); - waitsForPromise(() => - atom2.project.deserialize(atom.project.serialize()) + await atom2.project.deserialize(atom.project.serialize()) + + 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}`) ); - 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.destroy(); - atom2.destroy(); - }); + done(); }); }); }); @@ -2233,12 +2104,13 @@ 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 (done) => { + await atom.workspace.open('a'); + await atom.workspace.open('b'); + + [item1, item2] = atom.workspace.getPaneItems(); + + done(); }); it('calls setDocumentEdited when the active item changes', () => { @@ -2468,7 +2340,7 @@ describe('Workspace', () => { } describe('when called with a regex', () => { - it('calls the callback with all regex results in all files in the project', async () => { + it('calls the callback with all regex results in all files in the project', async (done) => { const results = []; await scan( /(a)+/, @@ -2491,9 +2363,11 @@ describe('Workspace', () => { leadingContextLines: [], trailingContextLines: ['cc aa cc'] }); + + done(); }); - it('works with with escaped literals (like $ and ^)', async () => { + it('works with with escaped literals (like $ and ^)', async (done) => { const results = []; await scan( /\$\w+/, @@ -2513,9 +2387,11 @@ describe('Workspace', () => { leadingContextLines: ['cc aa cc'], trailingContextLines: [] }); + + done(); }); - it('works on evil filenames', async () => { + it('works on evil filenames', async (done) => { atom.config.set('core.excludeVcsIgnoredPaths', false); platform.generateEvilFiles(); atom.project.setPaths([ @@ -2547,17 +2423,21 @@ describe('Workspace', () => { expect(paths[3]).toMatch(/quote".txt$/m); expect(path.basename(paths[4])).toBe('utfa\u0306.md'); } + + done(); }); - it('ignores case if the regex includes the `i` flag', async () => { + it('ignores case if the regex includes the `i` flag', async (done) => { const results = []; await scan(/DOLLAR/i, {}, result => results.push(result)); expect(results).toHaveLength(1); + + done(); }); if (ripgrep) { - it('returns empty text matches', async () => { + it('returns empty text matches', async (done) => { const results = []; await scan( /^\s{0}/, @@ -2583,10 +2463,12 @@ describe('Workspace', () => { leadingContextLines: [], trailingContextLines: [] }); + + done(); }); - describe('newlines on regexps', async () => { - it('returns multiline results from regexps', async () => { + describe('newlines on regexps', () => { + it('returns multiline results from regexps', async (done) => { const results = []; await scan(/first\nsecond/, {}, result => results.push(result)); @@ -2607,9 +2489,11 @@ describe('Workspace', () => { leadingContextLines: [], trailingContextLines: [] }); + + done(); }); - it('returns correctly the context lines', async () => { + it('returns correctly the context lines', async (done) => { const results = []; await scan( @@ -2637,9 +2521,11 @@ describe('Workspace', () => { leadingContextLines: ['newline2', 'newline3'], trailingContextLines: ['newline4', 'newline5'] }); + + done(); }); - it('returns multiple results from the same line', async () => { + it('returns multiple results from the same line', async (done) => { const results = []; await scan(/line\d\nne/, {}, result => results.push(result)); @@ -2679,9 +2565,11 @@ describe('Workspace', () => { leadingContextLines: [], trailingContextLines: [] }); + + done(); }); - it('works with escaped newlines', async () => { + it('works with escaped newlines', async (done) => { const results = []; await scan(/second\\nthird/, {}, result => results.push(result)); @@ -2701,9 +2589,11 @@ describe('Workspace', () => { leadingContextLines: [], trailingContextLines: [] }); + + done(); }); - it('matches a regexp ending with a newline', async () => { + it('matches a regexp ending with a newline', async (done) => { const results = []; await scan(/newline3\n/, {}, result => results.push(result)); @@ -2723,10 +2613,12 @@ describe('Workspace', () => { leadingContextLines: [], trailingContextLines: [] }); + + done(); }); }); - describe('pcre2 enabled', async () => { - it('supports lookbehind searches', async () => { + describe('pcre2 enabled', () => { + it('supports lookbehind searches', async (done) => { const results = []; await scan(/(? @@ -2747,11 +2639,13 @@ describe('Workspace', () => { leadingContextLines: [], trailingContextLines: [] }); + + done(); }); }); } - it('returns results on lines with unicode strings', async () => { + it('returns results on lines with unicode strings', async (done) => { const results = []; await scan(/line with unico/, {}, result => results.push(result)); @@ -2769,9 +2663,11 @@ describe('Workspace', () => { leadingContextLines: [], trailingContextLines: [] }); + + done(); }); - it('returns results on files detected as binary', async () => { + it('returns results on files detected as binary', async (done) => { const results = []; await scan( @@ -2795,13 +2691,15 @@ describe('Workspace', () => { leadingContextLines: [], trailingContextLines: ['utf8Property=Fòò', 'latin1Property=F��'] }); + + done(); }); describe('when the core.excludeVcsIgnoredPaths config is used', () => { let projectPath; let ignoredPath; - beforeEach(async () => { + beforeEach(async (done) => { const sourceProjectPath = path.join( __dirname, 'fixtures', @@ -2824,6 +2722,8 @@ describe('Workspace', () => { ); ignoredPath = path.join(projectPath, 'ignored.txt'); fs.writeFileSync(ignoredPath, 'this match should not be included'); + + done(); }); afterEach(() => { @@ -2832,7 +2732,7 @@ describe('Workspace', () => { } }); - it('excludes ignored files when core.excludeVcsIgnoredPaths is true', async () => { + it('excludes ignored files when core.excludeVcsIgnoredPaths is true', async (done) => { atom.project.setPaths([projectPath]); atom.config.set('core.excludeVcsIgnoredPaths', true); const resultHandler = jasmine.createSpy('result found'); @@ -2840,9 +2740,11 @@ describe('Workspace', () => { await scan(/match/, {}, ({ filePath }) => resultHandler(filePath)); expect(resultHandler).not.toHaveBeenCalled(); + + done(); }); - it('does not exclude ignored files when core.excludeVcsIgnoredPaths is false', async () => { + it('does not exclude ignored files when core.excludeVcsIgnoredPaths is false', async (done) => { atom.project.setPaths([projectPath]); atom.config.set('core.excludeVcsIgnoredPaths', false); const resultHandler = jasmine.createSpy('result found'); @@ -2852,9 +2754,11 @@ describe('Workspace', () => { expect(resultHandler).toHaveBeenCalledWith( path.join(projectPath, 'ignored.txt') ); + + done(); }); - it('does not exclude files when searching on an ignored folder even when core.excludeVcsIgnoredPaths is true', async () => { + it('does not exclude files when searching on an ignored folder even when core.excludeVcsIgnoredPaths is true', async (done) => { fs.mkdirSync(path.join(projectPath, 'poop')); ignoredPath = path.join( path.join(projectPath, 'poop', 'whatever.txt') @@ -2870,13 +2774,15 @@ describe('Workspace', () => { ); expect(resultHandler).toHaveBeenCalledWith(ignoredPath); + + done(); }); }); describe('when the core.followSymlinks config is used', () => { let projectPath; - beforeEach(async () => { + beforeEach(async (done) => { const sourceProjectPath = path.join( __dirname, 'fixtures', @@ -2897,6 +2803,8 @@ describe('Workspace', () => { path.join(__dirname, 'fixtures', 'dir', 'b'), path.join(projectPath, 'symlink') ); + + done(); }); afterEach(() => { @@ -2905,7 +2813,7 @@ describe('Workspace', () => { } }); - it('follows symlinks when core.followSymlinks is true', async () => { + it('follows symlinks when core.followSymlinks is true', async (done) => { atom.project.setPaths([projectPath]); atom.config.set('core.followSymlinks', true); const resultHandler = jasmine.createSpy('result found'); @@ -2915,9 +2823,11 @@ describe('Workspace', () => { expect(resultHandler).toHaveBeenCalledWith( path.join(projectPath, 'symlink') ); + + done(); }); - it('does not follow symlinks when core.followSymlinks is false', async () => { + it('does not follow symlinks when core.followSymlinks is false', async (done) => { atom.project.setPaths([projectPath]); atom.config.set('core.followSymlinks', false); const resultHandler = jasmine.createSpy('result found'); @@ -2925,13 +2835,15 @@ describe('Workspace', () => { await scan(/ccc/, {}, ({ filePath }) => resultHandler(filePath)); expect(resultHandler).not.toHaveBeenCalled(); + + done(); }); }); describe('when there are hidden files', () => { let projectPath; - beforeEach(async () => { + beforeEach(async (done) => { const sourceProjectPath = path.join( __dirname, 'fixtures', @@ -2952,6 +2864,8 @@ describe('Workspace', () => { // accurately test this behaviour there, we should either use a package // like `fswin` or manually spawn an `ATTRIB` command. fs.writeFileSync(path.join(projectPath, '.hidden'), 'ccc'); + + done(); }); afterEach(() => { @@ -2960,7 +2874,7 @@ describe('Workspace', () => { } }); - it('searches on hidden files', async () => { + it('searches on hidden files', async (done) => { atom.project.setPaths([projectPath]); const resultHandler = jasmine.createSpy('result found'); @@ -2969,10 +2883,12 @@ describe('Workspace', () => { expect(resultHandler).toHaveBeenCalledWith( path.join(projectPath, '.hidden') ); + + done(); }); }); - it('includes only files when a directory filter is specified', async () => { + it('includes only files when a directory filter is specified', async (done) => { const projectPath = path.join( path.join(__dirname, 'fixtures', 'dir') ); @@ -2991,9 +2907,11 @@ describe('Workspace', () => { expect(paths.length).toBe(1); expect(paths[0]).toBe(filePath); expect(matches.length).toBe(1); + + done(); }); - it("includes files and folders that begin with a '.'", async () => { + it("includes files and folders that begin with a '.'", async (done) => { const projectPath = temp.mkdirSync('atom-spec-workspace'); const filePath = path.join(projectPath, '.text'); fs.writeFileSync(filePath, 'match this'); @@ -3009,9 +2927,11 @@ describe('Workspace', () => { expect(paths.length).toBe(1); expect(paths[0]).toBe(filePath); expect(matches.length).toBe(1); + + done(); }); - it('excludes values in core.ignoredNames', async () => { + it('excludes values in core.ignoredNames', async (done) => { const ignoredNames = atom.config.get('core.ignoredNames'); ignoredNames.push('a'); atom.config.set('core.ignoredNames', ignoredNames); @@ -3020,9 +2940,11 @@ describe('Workspace', () => { await scan(/dollar/, {}, () => resultHandler()); expect(resultHandler).not.toHaveBeenCalled(); + + done(); }); - it('scans buffer contents if the buffer is modified', async () => { + it('scans buffer contents if the buffer is modified', async (done) => { const results = []; const editor = await atom.workspace.open('a'); @@ -3037,9 +2959,11 @@ describe('Workspace', () => { ); expect(resultForA.matches).toHaveLength(1); expect(resultForA.matches[0].matchText).toBe('Elephant'); + + done(); }); - it('ignores buffers outside the project', async () => { + it('ignores buffers outside the project', async (done) => { const results = []; const editor = await atom.workspace.open(temp.openSync().path); @@ -3048,6 +2972,8 @@ describe('Workspace', () => { await scan(/Elephant/, {}, result => results.push(result)); expect(results).toHaveLength(0); + + done(); }); describe('when the project has multiple root directories', () => { @@ -3069,7 +2995,7 @@ describe('Workspace', () => { atom.project.addPath(dir2); }); - it("searches matching files in all of the project's root directories", async () => { + it("searches matching files in all of the project's root directories", async (done) => { const resultPaths = []; await scan(/aaaa/, {}, ({ filePath }) => @@ -3077,10 +3003,12 @@ describe('Workspace', () => { ); expect(resultPaths.sort()).toEqual([file1, file2].sort()); + + done(); }); describe('when an inclusion path starts with the basename of a root directory', () => { - it('interprets the inclusion path as starting from that directory', async () => { + it('interprets the inclusion path as starting from that directory', async (done) => { let resultPaths = []; await scan(/aaaa/, { paths: ['dir'] }, ({ filePath }) => { if (!resultPaths.includes(filePath)) { @@ -3128,6 +3056,8 @@ describe('Workspace', () => { ); expect(resultPaths).toEqual([file2]); + + done(); }); }); @@ -3175,11 +3105,9 @@ describe('Workspace', () => { } } ); - - waitsFor(() => atom.workspace.directorySearchers.length > 0); }); - it('can override the DefaultDirectorySearcher on a per-directory basis', async () => { + it('can override the DefaultDirectorySearcher on a per-directory basis', async (done) => { const foreignFilePath = 'ssh://foreign-directory:8080/hello.txt'; const numPathsSearchedInDir2 = 1; const numPathsToPretendToSearchInCustomDirectorySearcher = 10; @@ -3215,35 +3143,31 @@ 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 ); + + done(); }); - it('can be cancelled when the object returned by scan() has its cancel() method invoked', async () => { + it('can be cancelled when the object returned by scan() has its cancel() method invoked', async (done) => { 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); - }); + resultOfPromiseSearch = await thenable; - waitsForPromise(() => - thenable.then(promiseResult => { - resultOfPromiseSearch = promiseResult; - }) - ); + expect(resultOfPromiseSearch).toBe('cancelled'); - runs(() => expect(resultOfPromiseSearch).toBe('cancelled')); + done(); }); - 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 (done) => { // This provider's search should be cancelled when the first provider fails let cancelableSearch; let fakeSearch2 = null; @@ -3261,24 +3185,21 @@ 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); + + await new Promise((resolve) => { + cancelableSearch.then(null, resolve); + }) - waitsFor(done => promise.then(null, done)); + expect(cancelableSearchCatchSpy).toHaveBeenCalled(); + expect(fakeSearch2.cancelled).toBe(true); - runs(() => { - expect(didReject).toBe(true); - expect(fakeSearch2.cancelled).toBe(true); - }); + done(); }); }); }); @@ -3319,7 +3240,7 @@ describe('Workspace', () => { ['line 13', 'line 14', 'line 15'] ]; - it('returns valid contexts no matter how many lines are requested', async () => { + it('returns valid contexts no matter how many lines are requested', async (done) => { expect(await search({})).toEqual({ leadingContext: [[], [], [], []], trailingContext: [[], [], [], []] @@ -3380,6 +3301,8 @@ describe('Workspace', () => { result.slice(0, 3) ) }); + + done(); }); }); }); // Cancels other ongoing searches @@ -3395,67 +3318,61 @@ describe('Workspace', () => { }); describe("when a file doesn't exist", () => { - it('calls back with an error', () => { + it('calls back with an error', async (done) => { 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); + + done(); }); }); describe('when called with unopened files', () => { - it('replaces properly', () => { + it('replaces properly', async (done) => { 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); + + done(); }); - it('does not discard the multiline flag', () => { + it('does not discard the multiline flag', async (done) => { 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); + + done(); }); }); describe('when a buffer is already open', () => { - it('replaces properly and saves when not modified', () => { + it('replaces properly and saves when not modified', async (done) => { const filePath = path.join(projectDir, 'sample.js'); fs.copyFileSync( path.join(fixturesDir, 'sample.js'), @@ -3465,30 +3382,24 @@ 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(); + + done(); }); - it('does not replace when the path is not specified', () => { + it('does not replace when the path is not specified', async (done) => { const filePath = path.join(projectDir, 'sample.js'); const commentFilePath = path.join( projectDir, @@ -3501,54 +3412,44 @@ 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); + + done(); }); - it('does NOT save when modified', () => { + it('does NOT save when modified', async (done) => { 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(); + + await atom.workspace.replace(/items/gi, 'okthen', [filePath], result => { + results.push(result) }); - waitsForPromise(() => - atom.workspace.replace(/items/gi, 'okthen', [filePath], result => - results.push(result) - ) - ); + expect(results).toHaveLength(1); + expect(results[0].filePath).toBe(filePath); + expect(results[0].replacements).toBe(6); - runs(() => { - expect(results).toHaveLength(1); - expect(results[0].filePath).toBe(filePath); - expect(results[0].replacements).toBe(6); + expect(editor.isModified()).toBeTruthy(); - expect(editor.isModified()).toBeTruthy(); - }); + done(); }); }); }); @@ -3556,56 +3457,54 @@ describe('Workspace', () => { describe('::saveActivePaneItem()', () => { let editor, notificationSpy; - beforeEach(() => { - waitsForPromise(() => - atom.workspace.open('sample.js').then(o => { - editor = o; - }) - ); + beforeEach(async (done) => { + editor = await atom.workspace.open('sample.js'); notificationSpy = jasmine.createSpy('did-add-notification'); atom.notifications.onDidAddNotification(notificationSpy); + + done(); }); 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 (done) => { + 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'); + + done(); }); - 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 (done) => { + 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'); + + done(); }); - 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 (done) => { + spyOn(editor, 'save').and.callFake(() => { const error = new Error( "EACCES, permission denied '/Some/dir/and-a-file.js'" ); @@ -3614,21 +3513,21 @@ 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'); + + done(); }); - 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 (done) => { + spyOn(editor, 'save').and.callFake(() => { const error = new Error( "EPERM, operation not permitted '/Some/dir/and-a-file.js'" ); @@ -3637,21 +3536,21 @@ 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'); + + done(); }); - 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 (done) => { + spyOn(editor, 'save').and.callFake(() => { const error = new Error( "EBUSY, resource busy or locked '/Some/dir/and-a-file.js'" ); @@ -3660,21 +3559,21 @@ 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'); + + done(); }); - 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 (done) => { + spyOn(editor, 'save').and.callFake(() => { const error = new Error( "EROFS, read-only file system '/Some/dir/and-a-file.js'" ); @@ -3683,38 +3582,43 @@ 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'); + + done(); }); - 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 (done) => { + 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(); + + done(); }); }); }); describe('::closeActivePaneItemOrEmptyPaneOrWindow', () => { - beforeEach(() => { + beforeEach(async (done) => { spyOn(atom, 'close'); - waitsForPromise(() => atom.workspace.open()); + await atom.workspace.open(); + + done(); }); - 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 () => { + 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 (done) => { atom.config.set('core.destroyEmptyPanes', false); const pane1 = atom.workspace.getActivePane(); @@ -3753,6 +3657,8 @@ describe('Workspace', () => { expect(atom.workspace.getLeftDock().getPaneItems().length).toBe(2); atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); expect(atom.close).toHaveBeenCalled(); + + done(); }); }); @@ -3888,22 +3794,21 @@ 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 (done) => { 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(); + + expect(pane.getPendingItem()).toBeFalsy(); - runs(() => expect(pane.getPendingItem()).toBeFalsy()); + done(); }); }); describe('grammar activation', () => { - it('notifies the workspace of which grammar is used', async () => { + it('notifies the workspace of which grammar is used', async (done) => { atom.packages.triggerDeferredActivationHooks(); const javascriptGrammarUsed = jasmine.createSpy('js grammar used'); @@ -3945,19 +3850,23 @@ describe('Workspace', () => { 'source.ruby' ); expect(rubyGrammarUsed).toHaveBeenCalled(); + + done(); }); }); describe('.checkoutHeadRevision()', () => { let editor = null; - beforeEach(async () => { + beforeEach(async (done) => { jasmine.useRealClock(); atom.config.set('editor.confirmCheckoutHeadRevision', false); editor = await atom.workspace.open('sample-with-comments.js'); + + done(); }); - it('reverts to the version of its file checked into the project repository', async () => { + it('reverts to the version of its file checked into the project repository', async (done) => { editor.setCursorBufferPosition([0, 0]); editor.insertText('---\n'); expect(editor.lineTextForBufferRow(0)).toBe('---'); @@ -3965,10 +3874,12 @@ describe('Workspace', () => { atom.workspace.checkoutHeadRevision(editor); await conditionPromise(() => editor.lineTextForBufferRow(0) === ''); + + done(); }); 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); @@ -3983,9 +3894,11 @@ describe('Workspace', () => { atom.workspace.enablePersistence = true; }); - afterEach(async () => { + afterEach(async (done) => { await atom.workspace.itemLocationStore.clear(); atom.workspace.enablePersistence = false; + + done(); }); it("stores the new location if it's not the default", () => {