/* globals assert */ const temp = require('temp').track() const season = require('season') const dedent = require('dedent') const electron = require('electron') const fs = require('fs-plus') const path = require('path') const sinon = require('sinon') const AtomApplication = require('../../src/main-process/atom-application') const parseCommandLine = require('../../src/main-process/parse-command-line') const { timeoutPromise, conditionPromise, emitterEventPromise } = require('../async-spec-helpers') const ATOM_RESOURCE_PATH = path.resolve(__dirname, '..', '..') describe.skip('AtomApplication', function () { this.timeout(60 * 1000) let originalAppQuit, originalShowMessageBox, originalAtomHome, atomApplicationsToDestroy beforeEach(() => { originalAppQuit = electron.app.quit originalShowMessageBox = electron.dialog.showMessageBox mockElectronAppQuit() originalAtomHome = process.env.ATOM_HOME process.env.ATOM_HOME = makeTempDir('atom-home') // Symlinking the compile cache into the temporary home dir makes the windows load much faster fs.symlinkSync( path.join(originalAtomHome, 'compile-cache'), path.join(process.env.ATOM_HOME, 'compile-cache'), 'junction' ) season.writeFileSync(path.join(process.env.ATOM_HOME, 'config.cson'), { '*': { welcome: { showOnStartup: false }, core: { telemetryConsent: 'no' } } }) atomApplicationsToDestroy = [] }) afterEach(async () => { process.env.ATOM_HOME = originalAtomHome for (let atomApplication of atomApplicationsToDestroy) { await atomApplication.destroy() } await clearElectronSession() electron.app.quit = originalAppQuit electron.dialog.showMessageBox = originalShowMessageBox }) describe('launch', () => { describe('with no paths', () => { // Covered it('reopens any previously opened windows', async () => { if (process.platform === 'win32') return // Test is too flakey on Windows const tempDirPath1 = makeTempDir() const tempDirPath2 = makeTempDir() const atomApplication1 = buildAtomApplication() const [app1Window1] = await atomApplication1.launch( parseCommandLine([tempDirPath1]) ) await emitterEventPromise(app1Window1, 'window:locations-opened') const [app1Window2] = await atomApplication1.launch( parseCommandLine([tempDirPath2]) ) await emitterEventPromise(app1Window2, 'window:locations-opened') await Promise.all([ app1Window1.prepareToUnload(), app1Window2.prepareToUnload() ]) const atomApplication2 = buildAtomApplication() const [app2Window1, app2Window2] = await atomApplication2.launch( parseCommandLine([]) ) await Promise.all([ emitterEventPromise(app2Window1, 'window:locations-opened'), emitterEventPromise(app2Window2, 'window:locations-opened') ]) assert.deepEqual(await getTreeViewRootDirectories(app2Window1), [ tempDirPath1 ]) assert.deepEqual(await getTreeViewRootDirectories(app2Window2), [ tempDirPath2 ]) }) // Covered it('when windows already exist, opens a new window with a single untitled buffer', async () => { const atomApplication = buildAtomApplication() const [window1] = await atomApplication.launch(parseCommandLine([])) await focusWindow(window1) const window1EditorTitle = await evalInWebContents( window1.browserWindow.webContents, sendBackToMainProcess => { sendBackToMainProcess( atom.workspace.getActiveTextEditor().getTitle() ) } ) assert.equal(window1EditorTitle, 'untitled') const window2 = atomApplication.openWithOptions(parseCommandLine([])) await window2.loadedPromise const window2EditorTitle = await evalInWebContents( window1.browserWindow.webContents, sendBackToMainProcess => { sendBackToMainProcess( atom.workspace.getActiveTextEditor().getTitle() ) } ) assert.equal(window2EditorTitle, 'untitled') assert.deepEqual(atomApplication.getAllWindows(), [window2, window1]) }) // Covered it('when no windows are open but --new-window is passed, opens a new window with a single untitled buffer', async () => { // Populate some saved state const tempDirPath1 = makeTempDir() const tempDirPath2 = makeTempDir() const atomApplication1 = buildAtomApplication() const [app1Window1] = await atomApplication1.launch( parseCommandLine([tempDirPath1]) ) await emitterEventPromise(app1Window1, 'window:locations-opened') const [app1Window2] = await atomApplication1.launch( parseCommandLine([tempDirPath2]) ) await emitterEventPromise(app1Window2, 'window:locations-opened') await Promise.all([ app1Window1.prepareToUnload(), app1Window2.prepareToUnload() ]) // Launch with --new-window const atomApplication2 = buildAtomApplication() const appWindows2 = await atomApplication2.launch( parseCommandLine(['--new-window']) ) assert.lengthOf(appWindows2, 1) const [appWindow2] = appWindows2 await appWindow2.loadedPromise const window2EditorTitle = await evalInWebContents( appWindow2.browserWindow.webContents, sendBackToMainProcess => { sendBackToMainProcess( atom.workspace.getActiveTextEditor().getTitle() ) } ) assert.equal(window2EditorTitle, 'untitled') }) // Covered it('does not open an empty editor if core.openEmptyEditorOnStart is false', async () => { const configPath = path.join(process.env.ATOM_HOME, 'config.cson') const config = season.readFileSync(configPath) if (!config['*'].core) config['*'].core = {} config['*'].core.openEmptyEditorOnStart = false season.writeFileSync(configPath, config) const atomApplication = buildAtomApplication() const [window1] = await atomApplication.launch(parseCommandLine([])) await focusWindow(window1) // wait a bit just to make sure we don't pass due to querying the render process before it loads await timeoutPromise(1000) const itemCount = await evalInWebContents( window1.browserWindow.webContents, sendBackToMainProcess => { sendBackToMainProcess( atom.workspace.getActivePane().getItems().length ) } ) assert.equal(itemCount, 0) }) }) describe('with file or folder paths', () => { // Covered it('shows all directories in the tree view when multiple directory paths are passed to Atom', async () => { const dirAPath = makeTempDir('a') const dirBPath = makeTempDir('b') const dirBSubdirPath = path.join(dirBPath, 'c') fs.mkdirSync(dirBSubdirPath) const atomApplication = buildAtomApplication() const [window1] = await atomApplication.launch( parseCommandLine([dirAPath, dirBPath]) ) await focusWindow(window1) await conditionPromise( async () => (await getTreeViewRootDirectories(window1)).length === 2 ) assert.deepEqual(await getTreeViewRootDirectories(window1), [ dirAPath, dirBPath ]) }) // Covered it('can open to a specific line number of a file', async () => { const filePath = path.join(makeTempDir(), 'new-file') fs.writeFileSync(filePath, '1\n2\n3\n4\n') const atomApplication = buildAtomApplication() const [window] = await atomApplication.launch( parseCommandLine([filePath + ':3']) ) await focusWindow(window) const cursorRow = await evalInWebContents( window.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.observeTextEditors(textEditor => { sendBackToMainProcess(textEditor.getCursorBufferPosition().row) }) } ) assert.equal(cursorRow, 2) }) // Covered it('can open to a specific line and column of a file', async () => { const filePath = path.join(makeTempDir(), 'new-file') fs.writeFileSync(filePath, '1\n2\n3\n4\n') const atomApplication = buildAtomApplication() const [window] = await atomApplication.launch( parseCommandLine([filePath + ':2:2']) ) await focusWindow(window) const cursorPosition = await evalInWebContents( window.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.observeTextEditors(textEditor => { sendBackToMainProcess(textEditor.getCursorBufferPosition()) }) } ) assert.deepEqual(cursorPosition, { row: 1, column: 1 }) }) it('removes all trailing whitespace and colons from the specified path', async () => { let filePath = path.join(makeTempDir(), 'new-file') fs.writeFileSync(filePath, '1\n2\n3\n4\n') const atomApplication = buildAtomApplication() const [window] = await atomApplication.launch( parseCommandLine([filePath + ':: ']) ) await focusWindow(window) const openedPath = await evalInWebContents( window.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.observeTextEditors(textEditor => { sendBackToMainProcess(textEditor.getPath()) }) } ) assert.equal(openedPath, filePath) }) // Covered it('opens an empty text editor when launched with a new file path', async () => { // Choosing "Don't save" mockElectronShowMessageBox({ response: 2 }) const atomApplication = buildAtomApplication() const newFilePath = path.join(makeTempDir(), 'new-file') const [window] = await atomApplication.launch( parseCommandLine([newFilePath]) ) await focusWindow(window) const { editorTitle, editorText } = await evalInWebContents( window.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.observeTextEditors(editor => { sendBackToMainProcess({ editorTitle: editor.getTitle(), editorText: editor.getText() }) }) } ) assert.equal(editorTitle, path.basename(newFilePath)) assert.equal(editorText, '') assert.deepEqual(await getTreeViewRootDirectories(window), []) }) }) describe('when the --add option is specified', () => { // Covered it('adds folders to existing windows when the --add option is used', async () => { const dirAPath = makeTempDir('a') const dirBPath = makeTempDir('b') const dirCPath = makeTempDir('c') const existingDirCFilePath = path.join(dirCPath, 'existing-file') fs.writeFileSync(existingDirCFilePath, 'this is an existing file') const atomApplication = buildAtomApplication() const [window1] = await atomApplication.launch( parseCommandLine([dirAPath]) ) await focusWindow(window1) await conditionPromise( async () => (await getTreeViewRootDirectories(window1)).length === 1 ) assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath]) // When opening *files* with --add, reuses an existing window let [reusedWindow] = await atomApplication.launch( parseCommandLine([existingDirCFilePath, '--add']) ) assert.equal(reusedWindow, window1) assert.deepEqual(atomApplication.getAllWindows(), [window1]) let activeEditorPath = await evalInWebContents( window1.browserWindow.webContents, sendBackToMainProcess => { const subscription = atom.workspace.onDidChangeActivePaneItem( textEditor => { sendBackToMainProcess(textEditor.getPath()) subscription.dispose() } ) } ) assert.equal(activeEditorPath, existingDirCFilePath) assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath]) // When opening *directories* with --add, reuses an existing window and adds the directory to the project reusedWindow = (await atomApplication.launch( parseCommandLine([dirBPath, '-a']) ))[0] assert.equal(reusedWindow, window1) assert.deepEqual(atomApplication.getAllWindows(), [window1]) await conditionPromise( async () => (await getTreeViewRootDirectories(reusedWindow)).length === 2 ) assert.deepEqual(await getTreeViewRootDirectories(window1), [ dirAPath, dirBPath ]) }) }) if (process.platform === 'darwin' || process.platform === 'win32') { // Covered it('positions new windows at an offset distance from the previous window', async () => { const atomApplication = buildAtomApplication() const [window1] = await atomApplication.launch( parseCommandLine([makeTempDir()]) ) await focusWindow(window1) window1.browserWindow.setBounds({ width: 400, height: 400, x: 0, y: 0 }) const [window2] = await atomApplication.launch( parseCommandLine([makeTempDir()]) ) await focusWindow(window2) assert.notEqual(window1, window2) const window1Dimensions = window1.getDimensions() const window2Dimensions = window2.getDimensions() assert.isAbove(window2Dimensions.x, window1Dimensions.x) assert.isAbove(window2Dimensions.y, window1Dimensions.y) }) } // Covered it('persists window state based on the project directories', async () => { // Choosing "Don't save" mockElectronShowMessageBox({ response: 2 }) const tempDirPath = makeTempDir() const atomApplication = buildAtomApplication() const nonExistentFilePath = path.join(tempDirPath, 'new-file') const [window1] = await atomApplication.launch( parseCommandLine([tempDirPath, nonExistentFilePath]) ) await evalInWebContents( window1.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.observeTextEditors(textEditor => { textEditor.insertText('Hello World!') sendBackToMainProcess(null) }) } ) await window1.prepareToUnload() window1.close() await window1.closedPromise // Restore unsaved state when opening the same project directory const [window2] = await atomApplication.launch( parseCommandLine([tempDirPath]) ) await window2.loadedPromise const window2Text = await evalInWebContents( window2.browserWindow.webContents, sendBackToMainProcess => { const textEditor = atom.workspace.getActiveTextEditor() textEditor.moveToBottom() textEditor.insertText(' How are you?') sendBackToMainProcess(textEditor.getText()) } ) assert.equal(window2Text, 'Hello World! How are you?') }) // Covered it('adds a remote directory to the project when launched with a remote directory', async () => { const packagePath = path.join( __dirname, '..', 'fixtures', 'packages', 'package-with-directory-provider' ) const packagesDirPath = path.join(process.env.ATOM_HOME, 'packages') fs.mkdirSync(packagesDirPath) fs.symlinkSync( packagePath, path.join(packagesDirPath, 'package-with-directory-provider'), 'junction' ) const atomApplication = buildAtomApplication() atomApplication.config.set('core.disabledPackages', ['fuzzy-finder']) const remotePath = 'remote://server:3437/some/directory/path' let [window] = await atomApplication.launch( parseCommandLine([remotePath]) ) await focusWindow(window) await conditionPromise( async () => (await getProjectDirectories()).length > 0 ) let directories = await getProjectDirectories() assert.deepEqual(directories, [ { type: 'FakeRemoteDirectory', path: remotePath } ]) await window.reload() await focusWindow(window) directories = await getProjectDirectories() assert.deepEqual(directories, [ { type: 'FakeRemoteDirectory', path: remotePath } ]) function getProjectDirectories () { return evalInWebContents( window.browserWindow.webContents, sendBackToMainProcess => { sendBackToMainProcess( atom.project .getDirectories() .map(d => ({ type: d.constructor.name, path: d.getPath() })) ) } ) } }) // Covered it('does not reopen any previously opened windows when launched with no path and `core.restorePreviousWindowsOnStart` is no', async () => { const atomApplication1 = buildAtomApplication() const [app1Window1] = await atomApplication1.launch( parseCommandLine([makeTempDir()]) ) await focusWindow(app1Window1) const [app1Window2] = await atomApplication1.launch( parseCommandLine([makeTempDir()]) ) await focusWindow(app1Window2) const configPath = path.join(process.env.ATOM_HOME, 'config.cson') const config = season.readFileSync(configPath) if (!config['*'].core) config['*'].core = {} config['*'].core.restorePreviousWindowsOnStart = 'no' season.writeFileSync(configPath, config) const atomApplication2 = buildAtomApplication() const [app2Window] = await atomApplication2.launch(parseCommandLine([])) await focusWindow(app2Window) assert.deepEqual(app2Window.initialProjectRoots, []) }) describe('when the `--wait` flag is passed', () => { let killedPids, atomApplication, onDidKillProcess beforeEach(() => { killedPids = [] onDidKillProcess = null atomApplication = buildAtomApplication({ killProcess (pid) { killedPids.push(pid) if (onDidKillProcess) onDidKillProcess() } }) }) // Covered it('kills the specified pid after a newly-opened window is closed', async () => { const [window1] = await atomApplication.launch( parseCommandLine(['--wait', '--pid', '101']) ) await focusWindow(window1) const [window2] = await atomApplication.launch( parseCommandLine(['--new-window', '--wait', '--pid', '102']) ) await focusWindow(window2) assert.deepEqual(killedPids, []) let processKillPromise = new Promise(resolve => { onDidKillProcess = resolve }) window1.close() await processKillPromise assert.deepEqual(killedPids, [101]) processKillPromise = new Promise(resolve => { onDidKillProcess = resolve }) window2.close() await processKillPromise assert.deepEqual(killedPids, [101, 102]) }) // Covered it('kills the specified pid after a newly-opened file in an existing window is closed', async () => { const projectDir = makeTempDir('existing') const filePath1 = path.join(projectDir, 'file-1') const filePath2 = path.join(projectDir, 'file-2') fs.writeFileSync(filePath1, 'File 1') fs.writeFileSync(filePath2, 'File 2') const [window] = await atomApplication.launch( parseCommandLine(['--wait', '--pid', '101', projectDir]) ) await focusWindow(window) const [reusedWindow] = await atomApplication.launch( parseCommandLine([ '--add', '--wait', '--pid', '102', filePath1, filePath2 ]) ) assert.equal(reusedWindow, window) const activeEditorPath = await evalInWebContents( window.browserWindow.webContents, send => { const subscription = atom.workspace.onDidChangeActivePaneItem( editor => { send(editor.getPath()) subscription.dispose() } ) } ) assert([filePath1, filePath2].includes(activeEditorPath)) assert.deepEqual(killedPids, []) await evalInWebContents(window.browserWindow.webContents, send => { atom.workspace.getActivePaneItem().destroy() send() }) await timeoutPromise(100) assert.deepEqual(killedPids, []) let processKillPromise = new Promise(resolve => { onDidKillProcess = resolve }) await evalInWebContents(window.browserWindow.webContents, send => { atom.workspace.getActivePaneItem().destroy() send() }) await processKillPromise assert.deepEqual(killedPids, [102]) processKillPromise = new Promise(resolve => { onDidKillProcess = resolve }) window.close() await processKillPromise assert.deepEqual(killedPids, [102, 101]) }) // Covered it('kills the specified pid after a newly-opened directory in an existing window is closed', async () => { const [window] = await atomApplication.launch(parseCommandLine([])) await focusWindow(window) const dirPath1 = makeTempDir() const [reusedWindow] = await atomApplication.launch( parseCommandLine(['--add', '--wait', '--pid', '101', dirPath1]) ) assert.equal(reusedWindow, window) await conditionPromise( async () => (await getTreeViewRootDirectories(window)).length === 1 ) assert.deepEqual(await getTreeViewRootDirectories(window), [dirPath1]) assert.deepEqual(killedPids, []) const dirPath2 = makeTempDir() await evalInWebContents( window.browserWindow.webContents, (send, dirPath1, dirPath2) => { atom.project.setPaths([dirPath1, dirPath2]) send() }, dirPath1, dirPath2 ) await timeoutPromise(100) assert.deepEqual(killedPids, []) let processKillPromise = new Promise(resolve => { onDidKillProcess = resolve }) await evalInWebContents( window.browserWindow.webContents, (send, dirPath2) => { atom.project.setPaths([dirPath2]) send() }, dirPath2 ) await processKillPromise assert.deepEqual(killedPids, [101]) }) }) describe('when closing the last window', () => { if (process.platform === 'linux' || process.platform === 'win32') { // Covered it('quits the application', async () => { const atomApplication = buildAtomApplication() const [window] = await atomApplication.launch( parseCommandLine([path.join(makeTempDir('a'), 'file-a')]) ) await focusWindow(window) await emitterEventPromise(window, 'window:locations-opened') // Choosing "Don't save" mockElectronShowMessageBox({ response: 2 }) window.close() await window.closedPromise await atomApplication.lastBeforeQuitPromise assert(electron.app.didQuit()) }) } else if (process.platform === 'darwin') { // Covered it('leaves the application open', async () => { const atomApplication = buildAtomApplication() const [window] = await atomApplication.launch( parseCommandLine([path.join(makeTempDir('a'), 'file-a')]) ) await focusWindow(window) await emitterEventPromise(window, 'window:locations-opened') // Choosing "Don't save" mockElectronShowMessageBox({ response: 2 }) window.close() await window.closedPromise await timeoutPromise(1000) assert(!electron.app.didQuit()) }) } }) describe('when adding or removing project folders', () => { // Covered it('stores the window state immediately', async () => { const dirA = makeTempDir() const dirB = makeTempDir() const atomApplication = buildAtomApplication() const [window0] = await atomApplication.launch( parseCommandLine([dirA, dirB]) ) await focusWindow(window0) await conditionPromise( async () => (await getTreeViewRootDirectories(window0)).length === 2 ) assert.deepEqual(await getTreeViewRootDirectories(window0), [ dirA, dirB ]) const saveStatePromise = emitterEventPromise( atomApplication, 'application:did-save-state' ) await evalInWebContents( window0.browserWindow.webContents, sendBackToMainProcess => { atom.project.removePath(atom.project.getPaths()[0]) sendBackToMainProcess(null) } ) assert.deepEqual(await getTreeViewRootDirectories(window0), [dirB]) await saveStatePromise // Window state should be saved when the project folder is removed const atomApplication2 = buildAtomApplication() const [window2] = await atomApplication2.launch(parseCommandLine([])) await focusWindow(window2) await conditionPromise( async () => (await getTreeViewRootDirectories(window2)).length === 1 ) assert.deepEqual(await getTreeViewRootDirectories(window2), [dirB]) }) }) describe('when opening atom:// URLs', () => { it('loads the urlMain file in a new window', async () => { const packagePath = path.join( __dirname, '..', 'fixtures', 'packages', 'package-with-url-main' ) const packagesDirPath = path.join(process.env.ATOM_HOME, 'packages') fs.mkdirSync(packagesDirPath) fs.symlinkSync( packagePath, path.join(packagesDirPath, 'package-with-url-main'), 'junction' ) const atomApplication = buildAtomApplication() const launchOptions = parseCommandLine([]) launchOptions.urlsToOpen = ['atom://package-with-url-main/test'] let [windows] = await atomApplication.launch(launchOptions) await windows[0].loadedPromise let reached = await evalInWebContents( windows[0].browserWindow.webContents, sendBackToMainProcess => { sendBackToMainProcess(global.reachedUrlMain) } ) assert.isTrue(reached) windows[0].close() }) it('triggers /core/open/file in the correct window', async function () { const dirAPath = makeTempDir('a') const dirBPath = makeTempDir('b') const atomApplication = buildAtomApplication() const [window1] = await atomApplication.launch( parseCommandLine([path.join(dirAPath)]) ) await focusWindow(window1) const [window2] = await atomApplication.launch( parseCommandLine([path.join(dirBPath)]) ) await focusWindow(window2) const fileA = path.join(dirAPath, 'file-a') const uriA = `atom://core/open/file?filename=${fileA}` const fileB = path.join(dirBPath, 'file-b') const uriB = `atom://core/open/file?filename=${fileB}` sinon.spy(window1, 'sendURIMessage') sinon.spy(window2, 'sendURIMessage') atomApplication.launch(parseCommandLine(['--uri-handler', uriA])) await conditionPromise( () => window1.sendURIMessage.calledWith(uriA), `window1 to be focused from ${fileA}` ) atomApplication.launch(parseCommandLine(['--uri-handler', uriB])) await conditionPromise( () => window2.sendURIMessage.calledWith(uriB), `window2 to be focused from ${fileB}` ) }) }) }) it('waits until all the windows have saved their state before quitting', async () => { const dirAPath = makeTempDir('a') const dirBPath = makeTempDir('b') const atomApplication = buildAtomApplication() const [window1] = await atomApplication.launch(parseCommandLine([dirAPath])) await focusWindow(window1) const [window2] = await atomApplication.launch(parseCommandLine([dirBPath])) await focusWindow(window2) electron.app.quit() await new Promise(process.nextTick) assert(!electron.app.didQuit()) await Promise.all([ window1.lastPrepareToUnloadPromise, window2.lastPrepareToUnloadPromise ]) assert(!electron.app.didQuit()) await atomApplication.lastBeforeQuitPromise await new Promise(process.nextTick) assert(electron.app.didQuit()) }) it('prevents quitting if user cancels when prompted to save an item', async () => { const atomApplication = buildAtomApplication() const [window1] = await atomApplication.launch(parseCommandLine([])) const [window2] = await atomApplication.launch(parseCommandLine([])) await Promise.all([window1.loadedPromise, window2.loadedPromise]) await evalInWebContents( window1.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.getActiveTextEditor().insertText('unsaved text') sendBackToMainProcess() } ) // Choosing "Cancel" mockElectronShowMessageBox({ response: 1 }) electron.app.quit() await atomApplication.lastBeforeQuitPromise assert(!electron.app.didQuit()) assert.equal(electron.app.quit.callCount, 1) // Ensure choosing "Cancel" doesn't try to quit the electron app more than once (regression) // Choosing "Don't save" mockElectronShowMessageBox({ response: 2 }) electron.app.quit() await atomApplication.lastBeforeQuitPromise assert(electron.app.didQuit()) }) it('closes successfully unloaded windows when quitting', async () => { const atomApplication = buildAtomApplication() const [window1] = await atomApplication.launch(parseCommandLine([])) const [window2] = await atomApplication.launch(parseCommandLine([])) await Promise.all([window1.loadedPromise, window2.loadedPromise]) await evalInWebContents( window1.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.getActiveTextEditor().insertText('unsaved text') sendBackToMainProcess() } ) // Choosing "Cancel" mockElectronShowMessageBox({ response: 1 }) electron.app.quit() await atomApplication.lastBeforeQuitPromise assert(atomApplication.getAllWindows().length === 1) // Choosing "Don't save" mockElectronShowMessageBox({ response: 2 }) electron.app.quit() await atomApplication.lastBeforeQuitPromise assert(atomApplication.getAllWindows().length === 0) }) if (process.platform === 'darwin') { it('allows opening a new folder after all windows are closed', async () => { const atomApplication = buildAtomApplication() sinon.stub(atomApplication, 'promptForPathToOpen') // Open a window and then close it, leaving the app running const [window] = await atomApplication.launch(parseCommandLine([])) await focusWindow(window) window.close() await window.closedPromise atomApplication.emit('application:open') await conditionPromise(() => atomApplication.promptForPathToOpen.calledWith('all') ) atomApplication.promptForPathToOpen.reset() atomApplication.emit('application:open-file') await conditionPromise(() => atomApplication.promptForPathToOpen.calledWith('file') ) atomApplication.promptForPathToOpen.reset() atomApplication.emit('application:open-folder') await conditionPromise(() => atomApplication.promptForPathToOpen.calledWith('folder') ) atomApplication.promptForPathToOpen.reset() }) it('allows reopening an existing project after all windows are closed', async () => { const tempDirPath = makeTempDir('reopen') const atomApplication = buildAtomApplication() sinon.stub(atomApplication, 'promptForPathToOpen') // Open a window and then close it, leaving the app running const [window] = await atomApplication.launch(parseCommandLine([])) await focusWindow(window) window.close() await window.closedPromise // Reopen one of the recent projects atomApplication.emit('application:reopen-project', { paths: [tempDirPath] }) const windows = atomApplication.getAllWindows() assert(windows.length === 1) await focusWindow(windows[0]) await conditionPromise( async () => (await getTreeViewRootDirectories(windows[0])).length === 1 ) // Check that the project was opened correctly. assert.deepEqual( await evalInWebContents( windows[0].browserWindow.webContents, send => { send(atom.project.getPaths()) } ), [tempDirPath] ) }) } it('reuses the main process between invocations', async () => { const tempDirPath1 = makeTempDir() const tempDirPath2 = makeTempDir() const options = { pathsToOpen: [tempDirPath1] } // Open the main application const originalApplication = buildAtomApplication(options) await originalApplication.initialize(options) // Wait until the first window gets opened await conditionPromise( () => originalApplication.getAllWindows().length === 1 ) // Open another instance of the application on a different path. AtomApplication.open({ resourcePath: ATOM_RESOURCE_PATH, atomHomeDirPath: process.env.ATOM_HOME, pathsToOpen: [tempDirPath2] }) await conditionPromise( () => originalApplication.getAllWindows().length === 2 ) // Check that the original application now has the two opened windows. assert.notEqual( originalApplication.getAllWindows().find( window => window.loadSettings.initialProjectRoots[0] === tempDirPath1 ), undefined ) assert.notEqual( originalApplication.getAllWindows().find( window => window.loadSettings.initialProjectRoots[0] === tempDirPath2 ), undefined ) }) function buildAtomApplication (params = {}) { const atomApplication = new AtomApplication( Object.assign( { resourcePath: ATOM_RESOURCE_PATH, atomHomeDirPath: process.env.ATOM_HOME }, params ) ) // Make sure that the app does not get updated automatically. atomApplication.config.set('core.automaticallyUpdate', false) atomApplicationsToDestroy.push(atomApplication) return atomApplication } async function focusWindow (window) { window.focus() await window.loadedPromise await conditionPromise( () => window.atomApplication.getLastFocusedWindow() === window ) } function mockElectronAppQuit () { let didQuit = false electron.app.quit = function () { this.quit.callCount++ let defaultPrevented = false this.emit('before-quit', { preventDefault () { defaultPrevented = true } }) if (!defaultPrevented) didQuit = true } electron.app.quit.callCount = 0 electron.app.didQuit = () => didQuit } function mockElectronShowMessageBox ({ response }) { electron.dialog.showMessageBox = (window, options, callback) => { callback(response) } } function makeTempDir (name) { return fs.realpathSync(temp.mkdirSync(name)) } let channelIdCounter = 0 function evalInWebContents (webContents, source, ...args) { const channelId = 'eval-result-' + channelIdCounter++ return new Promise(resolve => { electron.ipcMain.on(channelId, receiveResult) function receiveResult (event, result) { electron.ipcMain.removeListener('eval-result', receiveResult) resolve(result) } const js = dedent` function sendBackToMainProcess (result) { require('electron').ipcRenderer.send('${channelId}', result) } (${source})(sendBackToMainProcess, ${args .map(JSON.stringify) .join(', ')}) ` // console.log(`about to execute:\n${js}`) webContents.executeJavaScript(js) }) } function getTreeViewRootDirectories (atomWindow) { return evalInWebContents( atomWindow.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.getLeftDock().observeActivePaneItem(treeView => { if (treeView) { sendBackToMainProcess( Array.from( treeView.element.querySelectorAll( '.project-root > .header .name' ) ).map(element => element.dataset.path) ) } else { sendBackToMainProcess([]) } }) } ) } function clearElectronSession () { return new Promise(resolve => { electron.session.defaultSession.clearStorageData(() => { // Resolve promise on next tick, otherwise the process stalls. This // might be a bug in Electron, but it's probably fixed on the newer // versions. process.nextTick(resolve) }) }) } })