/* globals assert */ const path = require('path') const fs = require('fs-plus') const url = require('url') const {EventEmitter} = require('events') const temp = require('temp').track() const {sandbox} = require('sinon') const dedent = require('dedent') const AtomWindow = require('../../src/main-process/atom-window') const {emitterEventPromise} = require('../async-spec-helpers') describe('AtomWindow', function () { let sinon, app, service beforeEach(function () { sinon = sandbox.create() app = new StubApplication(sinon) service = new StubRecoveryService(sinon) }) afterEach(function () { sinon.restore() }) describe('creating a real window', function () { let resourcePath, windowInitializationScript, atomHome let original this.timeout(10 * 1000) beforeEach(async function () { original = { ATOM_HOME: process.env.ATOM_HOME, ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: process.env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT } resourcePath = path.resolve(__dirname, '../..') windowInitializationScript = require.resolve( path.join(resourcePath, 'src/initialize-application-window') ) atomHome = await new Promise((resolve, reject) => { temp.mkdir('launch-', (err, rootPath) => { if (err) { reject(err) } else { resolve(rootPath) } }) }) await new Promise((resolve, reject) => { const config = dedent` '*': core: automaticallyUpdate: false telemetryConsent: "no" welcome: showOnStartup: false ` fs.writeFile(path.join(atomHome, 'config.cson'), config, {encoding: 'utf8'}, err => { if (err) { reject(err) } else { resolve() } }) }) await new Promise((resolve, reject) => { fs.symlink( path.join(original.ATOM_HOME, 'compile-cache'), path.join(atomHome, 'compile-cache'), 'junction', err => { if (err) { reject(err) } else { resolve() } } ) }) process.env.ATOM_HOME = atomHome process.env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT = 'true' }) afterEach(async function () { process.env.ATOM_HOME = original.ATOM_HOME process.env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT = original.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT }) it('creates a real, properly configured BrowserWindow', async function () { const w = new AtomWindow(app, service, { resourcePath, windowInitializationScript, headless: true, extra: 'extra-load-setting' }) const {browserWindow} = w assert.isFalse(browserWindow.isVisible()) assert.strictEqual(browserWindow.getTitle(), 'Atom') const settings = JSON.parse(browserWindow.loadSettingsJSON) assert.strictEqual(settings.userSettings, 'stub-config') assert.strictEqual(settings.extra, 'extra-load-setting') assert.strictEqual(settings.resourcePath, resourcePath) assert.strictEqual(settings.atomHome, atomHome) assert.isFalse(settings.devMode) assert.isFalse(settings.safeMode) assert.isFalse(settings.clearWindowState) await emitterEventPromise(browserWindow, 'ready-to-show') assert.strictEqual(browserWindow.webContents.getURL(), url.format({ protocol: 'file', pathname: path.join(resourcePath, 'static/index.html'), slashes: true })) }) }) describe('launch behavior', function () { if (process.platform === 'darwin') { it('sets titleBarStyle to "hidden" for a custom title bar on non-spec windows', function () { app.config['core.titleBar'] = 'custom' const {browserWindow: w0} = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow }) assert.strictEqual(w0.options.titleBarStyle, 'hidden') const {browserWindow: w1} = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, isSpec: true }) assert.isUndefined(w1.options.titleBarStyle) }) it('sets titleBarStyle to "hiddenInset" for a custom inset title bar on non-spec windows', function () { app.config['core.titleBar'] = 'custom-inset' const {browserWindow: w0} = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow }) assert.strictEqual(w0.options.titleBarStyle, 'hiddenInset') const {browserWindow: w1} = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, isSpec: true }) assert.isUndefined(w1.options.titleBarStyle) }) it('sets frame to "false" for a hidden title bar on non-spec windows', function () { app.config['core.titleBar'] = 'hidden' const {browserWindow: w0} = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow }) assert.isFalse(w0.options.frame) const {browserWindow: w1} = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, isSpec: true }) assert.isUndefined(w1.options.frame) }) } else { it('ignores title bar style settings', function () { for (const value of ['custom', 'custom-inset', 'hidden']) { app.config['core.titleBar'] = value const {browserWindow} = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow }) assert.isUndefined(browserWindow.options.titleBarStyle) assert.isUndefined(browserWindow.options.frame) } }) } it('opens initial locations', async function () { const locationsToOpen = [ { pathToOpen: 'file.txt', initialLine: 1, initialColumn: 2, mustBeDirectory: false, forceAddToWindow: false, hasWaitSession: false }, { pathToOpen: '/directory', initialLine: null, initialColumn: null, mustBeDirectory: true, forceAddToWindow: false, hasWaitSession: false } ] const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) const loadPromise = emitterEventPromise(w, 'window:loaded') w.browserWindow.emit('window:loaded') await loadPromise assert.deepEqual(w.browserWindow.sent, [['message', 'open-locations', locationsToOpen]]) }) it('does not open an initial null location', async function () { const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen: [{ pathToOpen: null }] }) const loadPromise = emitterEventPromise(w, 'window:loaded') w.browserWindow.emit('window:loaded') await loadPromise assert.lengthOf(w.browserWindow.sent, 0) }) it('does not open initial locations in spec mode', async function () { const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen: [{ pathToOpen: 'file.txt' }], isSpec: true }) const loadPromise = emitterEventPromise(w, 'window:loaded') w.browserWindow.emit('window:loaded') await loadPromise assert.lengthOf(w.browserWindow.sent, 0) }) it('focuses the webView for specs', function () { const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, isSpec: true }) assert.isTrue(w.browserWindow.behavior.focusOnWebView) }) }) describe('project root tracking', function () { it('knows when it has no roots', function () { const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow }) assert.isFalse(w.hasProjectPaths()) }) it('is initialized from directories in the initial locationsToOpen', function () { const locationsToOpen = [ { pathToOpen: 'file.txt', exists: true, isFile: true }, { pathToOpen: 'directory0', exists: true, isDirectory: true }, { pathToOpen: 'directory1', exists: true, isDirectory: true }, { pathToOpen: 'new-file.txt' }, { pathToOpen: null } ] const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) assert.deepEqual(w.projectRoots, ['directory0', 'directory1']) assert.deepEqual(w.loadSettings.initialProjectRoots, ['directory0', 'directory1']) assert.isTrue(w.hasProjectPaths()) }) it('is updated by setProjectRoots', function () { const locationsToOpen = [ { pathToOpen: 'directory0', exists: true, isDirectory: true } ] const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) assert.deepEqual(w.projectRoots, ['directory0']) assert.deepEqual(w.loadSettings.initialProjectRoots, ['directory0']) w.setProjectRoots(['directory1', 'directory0', 'directory2']) assert.deepEqual(w.projectRoots, ['directory0', 'directory1', 'directory2']) assert.deepEqual(w.loadSettings.initialProjectRoots, ['directory0', 'directory1', 'directory2']) }) it('never reports that it owns the empty path', function () { const locationsToOpen = [ { pathToOpen: 'directory0', exists: true, isDirectory: true }, { pathToOpen: 'directory1', exists: true, isDirectory: true }, { pathToOpen: null } ] const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) assert.isFalse(w.containsLocation({pathToOpen: null})) }) it('discovers an exact path match', function () { const locationsToOpen = [ { pathToOpen: 'directory0', exists: true, isDirectory: true }, { pathToOpen: 'directory1', exists: true, isDirectory: true } ] const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) assert.isTrue(w.containsLocation({pathToOpen: 'directory0'})) assert.isFalse(w.containsLocation({pathToOpen: 'directory2'})) }) it('discovers the path of a file within any project root', function () { const locationsToOpen = [ { pathToOpen: 'directory0', exists: true, isDirectory: true }, { pathToOpen: 'directory1', exists: true, isDirectory: true } ] const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) assert.isTrue(w.containsLocation({ pathToOpen: path.join('directory0/file-0.txt'), exists: true, isFile: true })) assert.isTrue(w.containsLocation({ pathToOpen: path.join('directory0/deep/file-0.txt'), exists: true, isFile: true })) assert.isFalse(w.containsLocation({ pathToOpen: path.join('directory2/file-9.txt'), exists: true, isFile: true })) assert.isFalse(w.containsLocation({ pathToOpen: path.join('directory2/deep/file-9.txt'), exists: true, isFile: true })) }) it('reports that it owns nonexistent paths within a project root', function () { const locationsToOpen = [ { pathToOpen: 'directory0', exists: true, isDirectory: true }, { pathToOpen: 'directory1', exists: true, isDirectory: true } ] const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) assert.isTrue(w.containsLocation({ pathToOpen: path.join('directory0/file-1.txt'), exists: false })) assert.isTrue(w.containsLocation({ pathToOpen: path.join('directory1/subdir/file-0.txt'), exists: false })) }) it('never reports that it owns directories within a project root', function () { const locationsToOpen = [ { pathToOpen: 'directory0', exists: true, isDirectory: true }, { pathToOpen: 'directory1', exists: true, isDirectory: true } ] const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) assert.isFalse(w.containsLocation({ pathToOpen: path.join('directory0/subdir-0'), exists: true, isDirectory: true })) }) it('checks a full list of paths and reports if it owns all of them', function () { const locationsToOpen = [ { pathToOpen: 'directory0', exists: true, isDirectory: true }, { pathToOpen: 'directory1', exists: true, isDirectory: true } ] const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen }) assert.isTrue(w.containsLocations([ { pathToOpen: 'directory0' }, { pathToOpen: path.join('directory1/file-0.txt'), exists: true, isFile: true } ])) assert.isFalse(w.containsLocations([ { pathToOpen: 'directory2' }, { pathToOpen: 'directory0' } ])) assert.isFalse(w.containsLocations([ { pathToOpen: 'directory2' }, { pathToOpen: 'directory1' } ])) }) }) }) class StubApplication { constructor (sinon) { this.config = { 'core.titleBar': 'native', get: key => this.config[key] || null } this.configFile = { get () { return 'stub-config' } } this.removeWindow = sinon.spy() this.saveCurrentWindowOptions = sinon.spy() } } class StubRecoveryService { constructor (sinon) { this.didCloseWindow = sinon.spy() this.didCrashWindow = sinon.spy() } } class StubBrowserWindow extends EventEmitter { constructor (options) { super() this.options = options this.sent = [] this.behavior = { focusOnWebView: false } this.webContents = new EventEmitter() this.webContents.send = (...args) => { this.sent.push(args) } this.webContents.setVisualZoomLevelLimits = () => {} } loadURL () {} focusOnWebView () { this.behavior.focusOnWebView = true } }