1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756 |
- /* globals assert */
- const path = require('path');
- const { EventEmitter } = require('events');
- const temp = require('temp').track();
- const fs = require('fs-plus');
- const electron = require('electron');
- const sandbox = require('sinon').createSandbox();
- const AtomApplication = require('../../src/main-process/atom-application');
- const parseCommandLine = require('../../src/main-process/parse-command-line');
- const {
- emitterEventPromise,
- conditionPromise
- } = require('../async-spec-helpers');
- // These tests use a utility class called LaunchScenario, defined below, to manipulate AtomApplication instances that
- // (1) are stubbed to only simulate AtomWindow creation and (2) allow you to use a shorthand notation to assert the
- // application state after certain launch actions.
- //
- // Each scenario instance has access to a small set of directories and files created within a dedicated temporary
- // directory. For convenience, you may use short names to refer to any of its contents (their basenames, basically).
- // Check `LaunchScenario::init()` to see what directories and files are available.
- //
- // To create an application and its first window, call `await scenario.launch({})`. "Launch" may open multiple windows,
- // so it returns a Promise that resolves to an array of StubWindows. Its options argument may be created by
- // `parseCommandLine()` from a simulated argv string, or built by hand to include `{pathsToOpen}` and so on.
- //
- // To create additional windows, call `await scenario.open({})` with similar arguments. `LaunchScenario::open()` returns
- // a Promise that resolves to the opened or re-used StubWindows. The one exception is if `urlsToOpen` are provided in the open
- // arguments; then it resolves to an Array of StubWindows, because AtomApplication processes each URL individually.
- //
- // To ensure that the expected windows have been created, call `await scenario.assert('')` with a string specifying the
- // expected window contents. The specification shorthand language is as follows:
- //
- // * '[_ _]' describes a single window with no project roots and no open editors.
- // * '[_ 1.md]' describes a single window with no project roots and a single editor open on the file `./a/1.md` within
- // the LaunchScenario temporary directory.
- // * '[a _]' describes a single window with one project root - the directory `./a` within the LaunchScenario temporary
- // directory - and no open editors.
- // * '[a,b 1.md,2.md]' describes a single window with two project roots - the directories `./a` and `./b` - and two
- // open editors - `./a/1.md` and `./b/2.md`.
- // * '[a _] [b,c 2.md]' describes two windows, one with a project root of `./a` and no open editors, and another with
- // two project roots, `./b` and `./c`, and one open editor on `./b/2.md`. The windows are listed in their expected
- // creation order.
- describe('AtomApplication', function() {
- let scenario, sinon;
- if (process.env.CI) {
- this.timeout(10 * 1000);
- }
- beforeEach(async function() {
- sinon = sandbox;
- scenario = await LaunchScenario.create(sinon);
- });
- afterEach(async function() {
- await scenario.destroy();
- sinon.restore();
- });
- describe('command-line interface behavior', function() {
- describe('with no open windows', function() {
- // This is also the case when a user selects the application from the OS shell
- it('opens an empty window', async function() {
- await scenario.launch(parseCommandLine([]));
- await scenario.assert('[_ _]');
- });
- // This is also the case when a user clicks on a file in their file manager
- it('opens a file', async function() {
- await scenario.open(parseCommandLine(['a/1.md']));
- await scenario.assert('[_ 1.md]');
- });
- // This is also the case when a user clicks on a folder in their file manager
- // (or, on macOS, drags the folder to Atom in their doc)
- it('opens a directory', async function() {
- await scenario.open(parseCommandLine(['a']));
- await scenario.assert('[a _]');
- });
- it('opens a file with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'a/1.md']));
- await scenario.assert('[_ 1.md]');
- });
- it('opens a directory with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'a']));
- await scenario.assert('[a _]');
- });
- it('opens a file with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'a/1.md']));
- await scenario.assert('[_ 1.md]');
- });
- it('opens a directory with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'a']));
- await scenario.assert('[a _]');
- });
- describe('with previous window state', function() {
- let app;
- beforeEach(function() {
- app = scenario.addApplication({
- applicationJson: {
- version: '1',
- windows: [
- { projectRoots: [scenario.convertRootPath('b')] },
- { projectRoots: [scenario.convertRootPath('c')] }
- ]
- }
- });
- });
- describe('with core.restorePreviousWindowsOnStart set to "no"', function() {
- beforeEach(function() {
- app.config.set('core.restorePreviousWindowsOnStart', 'no');
- });
- it("doesn't restore windows when launched with no arguments", async function() {
- await scenario.launch({ app });
- await scenario.assert('[_ _]');
- });
- it("doesn't restore windows when launched with paths to open", async function() {
- await scenario.launch({ app, pathsToOpen: ['a/1.md'] });
- await scenario.assert('[_ 1.md]');
- });
- it("doesn't restore windows when --new-window is provided", async function() {
- await scenario.launch({ app, newWindow: true });
- await scenario.assert('[_ _]');
- });
- });
- describe('with core.restorePreviousWindowsOnStart set to "yes"', function() {
- beforeEach(function() {
- app.config.set('core.restorePreviousWindowsOnStart', 'yes');
- });
- it('restores windows when launched with no arguments', async function() {
- await scenario.launch({ app });
- await scenario.assert('[b _] [c _]');
- });
- it("doesn't restore windows when launched with paths to open", async function() {
- await scenario.launch({ app, pathsToOpen: ['a/1.md'] });
- await scenario.assert('[_ 1.md]');
- });
- it("doesn't restore windows when --new-window is provided", async function() {
- await scenario.launch({ app, newWindow: true });
- await scenario.assert('[_ _]');
- });
- });
- describe('with core.restorePreviousWindowsOnStart set to "always"', function() {
- beforeEach(function() {
- app.config.set('core.restorePreviousWindowsOnStart', 'always');
- });
- it('restores windows when launched with no arguments', async function() {
- await scenario.launch({ app });
- await scenario.assert('[b _] [c _]');
- });
- it('restores windows when launched with a project path to open', async function() {
- await scenario.launch({ app, pathsToOpen: ['a'] });
- await scenario.assert('[b _] [c _] [a _]');
- });
- it('restores windows when launched with a file path to open', async function() {
- await scenario.launch({ app, pathsToOpen: ['a/1.md'] });
- await scenario.assert('[b _] [c 1.md]');
- });
- it('collapses new paths into restored windows when appropriate', async function() {
- await scenario.launch({ app, pathsToOpen: ['b/2.md'] });
- await scenario.assert('[b 2.md] [c _]');
- });
- it("doesn't restore windows when --new-window is provided", async function() {
- await scenario.launch({ app, newWindow: true });
- await scenario.assert('[_ _]');
- });
- it("doesn't restore windows on open, just launch", async function() {
- await scenario.launch({ app, pathsToOpen: ['a'], newWindow: true });
- await scenario.open(parseCommandLine(['b']));
- await scenario.assert('[a _] [b _]');
- });
- });
- });
- describe('with unversioned application state', function() {
- it('reads "initialPaths" as project roots', async function() {
- const app = scenario.addApplication({
- applicationJson: [
- { initialPaths: [scenario.convertRootPath('a')] },
- {
- initialPaths: [
- scenario.convertRootPath('b'),
- scenario.convertRootPath('c')
- ]
- }
- ]
- });
- app.config.set('core.restorePreviousWindowsOnStart', 'always');
- await scenario.launch({ app });
- await scenario.assert('[a _] [b,c _]');
- });
- it('filters file paths from project root lists', async function() {
- const app = scenario.addApplication({
- applicationJson: [
- {
- initialPaths: [
- scenario.convertRootPath('b'),
- scenario.convertEditorPath('a/1.md')
- ]
- }
- ]
- });
- app.config.set('core.restorePreviousWindowsOnStart', 'always');
- await scenario.launch({ app });
- await scenario.assert('[b _]');
- });
- });
- });
- describe('with one empty window', function() {
- beforeEach(async function() {
- await scenario.preconditions('[_ _]');
- });
- // This is also the case when a user selects the application from the OS shell
- it('opens a new, empty window', async function() {
- await scenario.open(parseCommandLine([]));
- await scenario.assert('[_ _] [_ _]');
- });
- // This is also the case when a user clicks on a file in their file manager
- it('opens a file', async function() {
- await scenario.open(parseCommandLine(['a/1.md']));
- await scenario.assert('[_ 1.md]');
- });
- // This is also the case when a user clicks on a folder in their file manager
- it('opens a directory', async function() {
- await scenario.open(parseCommandLine(['a']));
- await scenario.assert('[a _]');
- });
- it('opens a file with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'a/1.md']));
- await scenario.assert('[_ 1.md]');
- });
- it('opens a directory with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'a']));
- await scenario.assert('[a _]');
- });
- it('opens a file with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'a/1.md']));
- await scenario.assert('[_ _] [_ 1.md]');
- });
- it('opens a directory with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'a']));
- await scenario.assert('[_ _] [a _]');
- });
- });
- describe('with one window that has a project root', function() {
- beforeEach(async function() {
- await scenario.preconditions('[a _]');
- });
- // This is also the case when a user selects the application from the OS shell
- it('opens a new, empty window', async function() {
- await scenario.open(parseCommandLine([]));
- await scenario.assert('[a _] [_ _]');
- });
- // This is also the case when a user clicks on a file within the project root in their file manager
- it('opens a file within the project root', async function() {
- await scenario.open(parseCommandLine(['a/1.md']));
- await scenario.assert('[a 1.md]');
- });
- // This is also the case when a user clicks on a project root folder in their file manager
- it('opens a directory that matches the project root', async function() {
- await scenario.open(parseCommandLine(['a']));
- await scenario.assert('[a _]');
- });
- // This is also the case when a user clicks on a file outside the project root in their file manager
- it('opens a file outside the project root', async function() {
- await scenario.open(parseCommandLine(['b/2.md']));
- await scenario.assert('[a 2.md]');
- });
- // This is also the case when a user clicks on a new folder in their file manager
- it('opens a directory other than the project root', async function() {
- await scenario.open(parseCommandLine(['b']));
- await scenario.assert('[a _] [b _]');
- });
- it('opens a file within the project root with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'a/1.md']));
- await scenario.assert('[a 1.md]');
- });
- it('opens a directory that matches the project root with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'a']));
- await scenario.assert('[a _]');
- });
- it('opens a file outside the project root with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'b/2.md']));
- await scenario.assert('[a 2.md]');
- });
- it('opens a directory other than the project root with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'b']));
- await scenario.assert('[a,b _]');
- });
- it('opens a file within the project root with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'a/1.md']));
- await scenario.assert('[a _] [_ 1.md]');
- });
- it('opens a directory that matches the project root with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'a']));
- await scenario.assert('[a _] [a _]');
- });
- it('opens a file outside the project root with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'b/2.md']));
- await scenario.assert('[a _] [_ 2.md]');
- });
- it('opens a directory other than the project root with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'b']));
- await scenario.assert('[a _] [b _]');
- });
- });
- describe('with two windows, one with a project root and one empty', function() {
- beforeEach(async function() {
- await scenario.preconditions('[a _] [_ _]');
- });
- // This is also the case when a user selects the application from the OS shell
- it('opens a new, empty window', async function() {
- await scenario.open(parseCommandLine([]));
- await scenario.assert('[a _] [_ _] [_ _]');
- });
- // This is also the case when a user clicks on a file within the project root in their file manager
- it('opens a file within the project root', async function() {
- await scenario.open(parseCommandLine(['a/1.md']));
- await scenario.assert('[a 1.md] [_ _]');
- });
- // This is also the case when a user clicks on a project root folder in their file manager
- it('opens a directory that matches the project root', async function() {
- await scenario.open(parseCommandLine(['a']));
- await scenario.assert('[a _] [_ _]');
- });
- // This is also the case when a user clicks on a file outside the project root in their file manager
- it('opens a file outside the project root', async function() {
- await scenario.open(parseCommandLine(['b/2.md']));
- await scenario.assert('[a _] [_ 2.md]');
- });
- // This is also the case when a user clicks on a new folder in their file manager
- it('opens a directory other than the project root', async function() {
- await scenario.open(parseCommandLine(['b']));
- await scenario.assert('[a _] [b _]');
- });
- it('opens a file within the project root with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'a/1.md']));
- await scenario.assert('[a 1.md] [_ _]');
- });
- it('opens a directory that matches the project root with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'a']));
- await scenario.assert('[a _] [_ _]');
- });
- it('opens a file outside the project root with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'b/2.md']));
- await scenario.assert('[a _] [_ 2.md]');
- });
- it('opens a directory other than the project root with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'b']));
- await scenario.assert('[a _] [b _]');
- });
- it('opens a file within the project root with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'a/1.md']));
- await scenario.assert('[a _] [_ _] [_ 1.md]');
- });
- it('opens a directory that matches the project root with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'a']));
- await scenario.assert('[a _] [_ _] [a _]');
- });
- it('opens a file outside the project root with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'b/2.md']));
- await scenario.assert('[a _] [_ _] [_ 2.md]');
- });
- it('opens a directory other than the project root with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'b']));
- await scenario.assert('[a _] [_ _] [b _]');
- });
- });
- describe('with two windows, one empty and one with a project root', function() {
- beforeEach(async function() {
- await scenario.preconditions('[_ _] [a _]');
- });
- // This is also the case when a user selects the application from the OS shell
- it('opens a new, empty window', async function() {
- await scenario.open(parseCommandLine([]));
- await scenario.assert('[_ _] [a _] [_ _]');
- });
- // This is also the case when a user clicks on a file within the project root in their file manager
- it('opens a file within the project root', async function() {
- await scenario.open(parseCommandLine(['a/1.md']));
- await scenario.assert('[_ _] [a 1.md]');
- });
- // This is also the case when a user clicks on a project root folder in their file manager
- it('opens a directory that matches the project root', async function() {
- await scenario.open(parseCommandLine(['a']));
- await scenario.assert('[_ _] [a _]');
- });
- // This is also the case when a user clicks on a file outside the project root in their file manager
- it('opens a file outside the project root', async function() {
- await scenario.open(parseCommandLine(['b/2.md']));
- await scenario.assert('[_ 2.md] [a _]');
- });
- // This is also the case when a user clicks on a new folder in their file manager
- it('opens a directory other than the project root', async function() {
- await scenario.open(parseCommandLine(['b']));
- await scenario.assert('[b _] [a _]');
- });
- it('opens a file within the project root with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'a/1.md']));
- await scenario.assert('[_ _] [a 1.md]');
- });
- it('opens a directory that matches the project root with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'a']));
- await scenario.assert('[_ _] [a _]');
- });
- it('opens a file outside the project root with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'b/2.md']));
- await scenario.assert('[_ _] [a 2.md]');
- });
- it('opens a directory other than the project root with --add', async function() {
- await scenario.open(parseCommandLine(['--add', 'b']));
- await scenario.assert('[_ _] [a,b _]');
- });
- it('opens a file within the project root with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'a/1.md']));
- await scenario.assert('[_ _] [a _] [_ 1.md]');
- });
- it('opens a directory that matches the project root with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'a']));
- await scenario.assert('[_ _] [a _] [a _]');
- });
- it('opens a file outside the project root with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'b/2.md']));
- await scenario.assert('[_ _] [a _] [_ 2.md]');
- });
- it('opens a directory other than the project root with --new-window', async function() {
- await scenario.open(parseCommandLine(['--new-window', 'b']));
- await scenario.assert('[_ _] [a _] [b _]');
- });
- });
- describe('--wait', function() {
- it('kills the specified pid after a newly-opened window is closed', async function() {
- const [w0] = await scenario.launch(
- parseCommandLine(['--new-window', '--wait', '--pid', '101'])
- );
- const w1 = await scenario.open(
- parseCommandLine(['--new-window', '--wait', '--pid', '202'])
- );
- assert.lengthOf(scenario.killedPids, 0);
- w0.browserWindow.emit('closed');
- assert.deepEqual(scenario.killedPids, [101]);
- w1.browserWindow.emit('closed');
- assert.deepEqual(scenario.killedPids, [101, 202]);
- });
- it('kills the specified pid after all newly-opened files in an existing window are closed', async function() {
- const [w] = await scenario.launch(
- parseCommandLine(['--new-window', 'a'])
- );
- await scenario.open(
- parseCommandLine([
- '--add',
- '--wait',
- '--pid',
- '303',
- 'a/1.md',
- 'b/2.md'
- ])
- );
- await scenario.assert('[a 1.md,2.md]');
- assert.lengthOf(scenario.killedPids, 0);
- scenario
- .getApplication(0)
- .windowDidClosePathWithWaitSession(
- w,
- scenario.convertEditorPath('b/2.md')
- );
- assert.lengthOf(scenario.killedPids, 0);
- scenario
- .getApplication(0)
- .windowDidClosePathWithWaitSession(
- w,
- scenario.convertEditorPath('a/1.md')
- );
- assert.deepEqual(scenario.killedPids, [303]);
- });
- it('kills the specified pid after a newly-opened directory in an existing window is closed', async function() {
- const [w] = await scenario.launch(
- parseCommandLine(['--new-window', 'a'])
- );
- await scenario.open(
- parseCommandLine(['--add', '--wait', '--pid', '404', 'b'])
- );
- await scenario.assert('[a,b _]');
- assert.lengthOf(scenario.killedPids, 0);
- scenario
- .getApplication(0)
- .windowDidClosePathWithWaitSession(w, scenario.convertRootPath('b'));
- assert.deepEqual(scenario.killedPids, [404]);
- });
- });
- describe('atom:// URLs', function() {
- describe('with a package-name host', function() {
- it("loads the package's urlMain in a new window", async function() {
- await scenario.launch({});
- const app = scenario.getApplication(0);
- app.packages = {
- getAvailablePackageMetadata: () => [
- { name: 'package-with-url-main', urlMain: 'some/url-main' }
- ],
- resolvePackagePath: () =>
- path.resolve('dot-atom/package-with-url-main')
- };
- const [w1, w2] = await scenario.open(
- parseCommandLine([
- 'atom://package-with-url-main/test1',
- 'atom://package-with-url-main/test2'
- ])
- );
- assert.strictEqual(
- w1.loadSettings.windowInitializationScript,
- path.resolve('dot-atom/package-with-url-main/some/url-main')
- );
- assert.strictEqual(
- w1.loadSettings.urlToOpen,
- 'atom://package-with-url-main/test1'
- );
- assert.strictEqual(
- w2.loadSettings.windowInitializationScript,
- path.resolve('dot-atom/package-with-url-main/some/url-main')
- );
- assert.strictEqual(
- w2.loadSettings.urlToOpen,
- 'atom://package-with-url-main/test2'
- );
- });
- it('sends a URI message to the most recently focused non-spec window', async function() {
- const [w0] = await scenario.launch({});
- const w1 = await scenario.open(parseCommandLine(['--new-window']));
- const w2 = await scenario.open(parseCommandLine(['--new-window']));
- const w3 = await scenario.open(
- parseCommandLine(['--test', 'a/1.md'])
- );
- const app = scenario.getApplication(0);
- app.packages = {
- getAvailablePackageMetadata: () => []
- };
- const [uw] = await scenario.open(
- parseCommandLine(['atom://package-without-url-main/test'])
- );
- assert.strictEqual(uw, w2);
- assert.isTrue(
- w2.sendURIMessage.calledWith('atom://package-without-url-main/test')
- );
- assert.strictEqual(w2.focus.callCount, 2);
- for (const other of [w0, w1, w3]) {
- assert.isFalse(other.sendURIMessage.called);
- }
- });
- it('creates a new window and sends a URI message to it once it loads', async function() {
- const [w0] = await scenario.launch(
- parseCommandLine(['--test', 'a/1.md'])
- );
- const app = scenario.getApplication(0);
- app.packages = {
- getAvailablePackageMetadata: () => []
- };
- const [uw] = await scenario.open(
- parseCommandLine(['atom://package-without-url-main/test'])
- );
- assert.notStrictEqual(uw, w0);
- assert.strictEqual(
- uw.loadSettings.windowInitializationScript,
- path.resolve(
- __dirname,
- '../../src/initialize-application-window.js'
- )
- );
- uw.emit('window:loaded');
- assert.isTrue(
- uw.sendURIMessage.calledWith('atom://package-without-url-main/test')
- );
- });
- });
- describe('with a "core" host', function() {
- it('sends a URI message to the most recently focused non-spec window that owns the open locations', async function() {
- const [w0] = await scenario.launch(parseCommandLine(['a']));
- const w1 = await scenario.open(
- parseCommandLine(['--new-window', 'a'])
- );
- const w2 = await scenario.open(
- parseCommandLine(['--new-window', 'b'])
- );
- const uri = `atom://core/open/file?filename=${encodeURIComponent(
- scenario.convertEditorPath('a/1.md')
- )}`;
- const [uw] = await scenario.open(parseCommandLine([uri]));
- assert.strictEqual(uw, w1);
- assert.isTrue(w1.sendURIMessage.calledWith(uri));
- for (const other of [w0, w2]) {
- assert.isFalse(other.sendURIMessage.called);
- }
- });
- it('creates a new window and sends a URI message to it once it loads', async function() {
- const [w0] = await scenario.launch(
- parseCommandLine(['--test', 'a/1.md'])
- );
- const uri = `atom://core/open/file?filename=${encodeURIComponent(
- scenario.convertEditorPath('b/2.md')
- )}`;
- const [uw] = await scenario.open(parseCommandLine([uri]));
- assert.notStrictEqual(uw, w0);
- uw.emit('window:loaded');
- assert.isTrue(uw.sendURIMessage.calledWith(uri));
- });
- });
- });
- it('opens a file to a specific line number', async function() {
- await scenario.open(parseCommandLine(['a/1.md:10']));
- await scenario.assert('[_ 1.md]');
- const w = scenario.getWindow(0);
- assert.lengthOf(w._locations, 1);
- assert.strictEqual(w._locations[0].initialLine, 9);
- assert.isNull(w._locations[0].initialColumn);
- });
- it('opens a file to a specific line number and column', async function() {
- await scenario.open(parseCommandLine(['b/2.md:12:5']));
- await scenario.assert('[_ 2.md]');
- const w = scenario.getWindow(0);
- assert.lengthOf(w._locations, 1);
- assert.strictEqual(w._locations[0].initialLine, 11);
- assert.strictEqual(w._locations[0].initialColumn, 4);
- });
- it('opens a directory with a non-file protocol', async function() {
- await scenario.open(
- parseCommandLine(['remote://server:3437/some/directory/path'])
- );
- const w = scenario.getWindow(0);
- assert.lengthOf(w._locations, 1);
- assert.strictEqual(
- w._locations[0].pathToOpen,
- 'remote://server:3437/some/directory/path'
- );
- assert.isFalse(w._locations[0].exists);
- assert.isFalse(w._locations[0].isDirectory);
- assert.isFalse(w._locations[0].isFile);
- });
- it('truncates trailing whitespace and colons', async function() {
- await scenario.open(parseCommandLine(['b/2.md:: ']));
- await scenario.assert('[_ 2.md]');
- const w = scenario.getWindow(0);
- assert.lengthOf(w._locations, 1);
- assert.isNull(w._locations[0].initialLine);
- assert.isNull(w._locations[0].initialColumn);
- });
- it('disregards test and benchmark windows', async function() {
- await scenario.launch(parseCommandLine(['--test', 'b']));
- await scenario.open(parseCommandLine(['--new-window']));
- await scenario.open(parseCommandLine(['--test', 'c']));
- await scenario.open(parseCommandLine(['--benchmark', 'b']));
- await scenario.open(parseCommandLine(['a/1.md']));
- // Test StubWindows are visible as empty editor windows here.
- // (Benchmark mode has been removed, and will no-longer open new windows.)
- await scenario.assert('[_ _] [_ 1.md] [_ _]');
- });
- });
- if (process.platform === 'darwin' || process.platform === 'win32') {
- it('positions new windows at an offset from the previous window', async function() {
- const [w0] = await scenario.launch(parseCommandLine(['a']));
- w0.setSize(400, 400);
- const d0 = w0.getDimensions();
- const w1 = await scenario.open(parseCommandLine(['b']));
- const d1 = w1.getDimensions();
- assert.isAbove(d1.x, d0.x);
- assert.isAbove(d1.y, d0.y);
- });
- }
- if (process.platform === 'darwin') {
- describe('with no windows open', function() {
- let app;
- beforeEach(async function() {
- const [w] = await scenario.launch(parseCommandLine([]));
- app = scenario.getApplication(0);
- app.removeWindow(w);
- sinon.stub(app, 'promptForPathToOpen');
- global.atom = { workspace: { getActiveTextEditor() {} } };
- });
- it('opens a new file', function() {
- app.emit('application:open-file');
- assert.isTrue(
- app.promptForPathToOpen.calledWith('file', {
- devMode: false,
- safeMode: false,
- window: null
- })
- );
- });
- it('opens a new directory', function() {
- app.emit('application:open-folder');
- assert.isTrue(
- app.promptForPathToOpen.calledWith('folder', {
- devMode: false,
- safeMode: false,
- window: null
- })
- );
- });
- it('opens a new file or directory', function() {
- app.emit('application:open');
- assert.isTrue(
- app.promptForPathToOpen.calledWith('all', {
- devMode: false,
- safeMode: false,
- window: null
- })
- );
- });
- it('reopens a project in a new window', async function() {
- const paths = scenario.convertPaths(['a', 'b']);
- app.emit('application:reopen-project', { paths });
- await conditionPromise(() => app.getAllWindows().length > 0);
- assert.deepEqual(
- app.getAllWindows().map(w => Array.from(w._rootPaths)),
- [paths]
- );
- });
- });
- }
- describe('existing application re-use', function() {
- let createApplication;
- const version = electron.app.getVersion();
- beforeEach(function() {
- createApplication = async options => {
- options.version = version;
- const app = scenario.addApplication(options);
- await app.listenForArgumentsFromNewProcess(options);
- await app.launch(options);
- return app;
- };
- });
- it('creates a new application when no socket is present', async function() {
- const app0 = await AtomApplication.open({ createApplication, version });
- await app0.deleteSocketSecretFile();
- const app1 = await AtomApplication.open({ createApplication, version });
- assert.isNotNull(app1);
- assert.notStrictEqual(app0, app1);
- });
- it('creates a new application for spec windows', async function() {
- const app0 = await AtomApplication.open({ createApplication, version });
- const app1 = await AtomApplication.open({
- createApplication,
- version,
- ...parseCommandLine(['--test', 'a'])
- });
- assert.isNotNull(app1);
- assert.notStrictEqual(app0, app1);
- });
- it('sends a request to an existing application when a socket is present', async function() {
- const app0 = await AtomApplication.open({ createApplication, version });
- assert.lengthOf(app0.getAllWindows(), 1);
- const app1 = await AtomApplication.open({
- createApplication,
- version,
- ...parseCommandLine(['--new-window'])
- });
- assert.isNull(app1);
- assert.isTrue(electron.app.quit.called);
- await conditionPromise(() => app0.getAllWindows().length === 2);
- await scenario.assert('[_ _] [_ _]');
- });
- });
- describe('IPC handling', function() {
- let w0, w1, w2, app;
- beforeEach(async function() {
- w0 = (await scenario.launch(parseCommandLine(['a'])))[0];
- w1 = await scenario.open(parseCommandLine(['--new-window']));
- w2 = await scenario.open(parseCommandLine(['--new-window', 'b']));
- app = scenario.getApplication(0);
- sinon.spy(app, 'openPaths');
- sinon
- .stub(app, 'promptForPath')
- .callsFake((_type, callback, defaultPath) => callback([defaultPath]));
- });
- // This is the IPC message used to handle:
- // * application:reopen-project
- // * choosing "open in new window" when adding a folder that has previously saved state
- // * drag and drop
- // * deprecated call links in deprecation-cop
- // * other direct callers of `atom.open()`
- it('"open" opens a fixed path by the standard opening rules', async function() {
- sinon.stub(app, 'atomWindowForEvent').callsFake(() => w1);
- electron.ipcMain.emit(
- 'open',
- {},
- { pathsToOpen: [scenario.convertEditorPath('a/1.md')] }
- );
- await app.openPaths.lastCall.returnValue;
- await scenario.assert('[a 1.md] [_ _] [b _]');
- electron.ipcMain.emit(
- 'open',
- {},
- { pathsToOpen: [scenario.convertRootPath('c')] }
- );
- await app.openPaths.lastCall.returnValue;
- await scenario.assert('[a 1.md] [c _] [b _]');
- electron.ipcMain.emit(
- 'open',
- {},
- { pathsToOpen: [scenario.convertRootPath('d')], here: true }
- );
- await app.openPaths.lastCall.returnValue;
- await scenario.assert('[a 1.md] [c,d _] [b _]');
- });
- it('"open" without any option open the prompt for selecting a path', async function() {
- sinon.stub(app, 'atomWindowForEvent').callsFake(() => w1);
- electron.ipcMain.emit('open', {});
- assert.strictEqual(app.promptForPath.lastCall.args[0], 'all');
- });
- it('"open-chosen-any" opens a file in the sending window', async function() {
- sinon.stub(app, 'atomWindowForEvent').callsFake(() => w2);
- electron.ipcMain.emit(
- 'open-chosen-any',
- {},
- scenario.convertEditorPath('a/1.md')
- );
- await conditionPromise(() => app.openPaths.called);
- await app.openPaths.lastCall.returnValue;
- await scenario.assert('[a _] [_ _] [b 1.md]');
- assert.isTrue(app.promptForPath.called);
- assert.strictEqual(app.promptForPath.lastCall.args[0], 'all');
- });
- it('"open-chosen-any" opens a directory by the standard opening rules', async function() {
- sinon.stub(app, 'atomWindowForEvent').callsFake(() => w1);
- // Open unrecognized directory in empty window
- electron.ipcMain.emit(
- 'open-chosen-any',
- {},
- scenario.convertRootPath('c')
- );
- await conditionPromise(() => app.openPaths.callCount > 0);
- await app.openPaths.lastCall.returnValue;
- await scenario.assert('[a _] [c _] [b _]');
- assert.strictEqual(app.promptForPath.callCount, 1);
- assert.strictEqual(app.promptForPath.lastCall.args[0], 'all');
- // Open unrecognized directory in new window
- electron.ipcMain.emit(
- 'open-chosen-any',
- {},
- scenario.convertRootPath('d')
- );
- await conditionPromise(() => app.openPaths.callCount > 1);
- await app.openPaths.lastCall.returnValue;
- await scenario.assert('[a _] [c _] [b _] [d _]');
- assert.strictEqual(app.promptForPath.callCount, 2);
- assert.strictEqual(app.promptForPath.lastCall.args[0], 'all');
- // Open recognized directory in existing window
- electron.ipcMain.emit(
- 'open-chosen-any',
- {},
- scenario.convertRootPath('a')
- );
- await conditionPromise(() => app.openPaths.callCount > 2);
- await app.openPaths.lastCall.returnValue;
- await scenario.assert('[a _] [c _] [b _] [d _]');
- assert.strictEqual(app.promptForPath.callCount, 3);
- assert.strictEqual(app.promptForPath.lastCall.args[0], 'all');
- });
- it('"open-chosen-file" opens a file chooser and opens the chosen file in the sending window', async function() {
- sinon.stub(app, 'atomWindowForEvent').callsFake(() => w0);
- electron.ipcMain.emit(
- 'open-chosen-file',
- {},
- scenario.convertEditorPath('b/2.md')
- );
- await app.openPaths.lastCall.returnValue;
- await scenario.assert('[a 2.md] [_ _] [b _]');
- assert.isTrue(app.promptForPath.called);
- assert.strictEqual(app.promptForPath.lastCall.args[0], 'file');
- });
- it('"open-chosen-folder" opens a directory chooser and opens the chosen directory', async function() {
- sinon.stub(app, 'atomWindowForEvent').callsFake(() => w0);
- electron.ipcMain.emit(
- 'open-chosen-folder',
- {},
- scenario.convertRootPath('c')
- );
- await app.openPaths.lastCall.returnValue;
- await scenario.assert('[a _] [c _] [b _]');
- assert.isTrue(app.promptForPath.called);
- assert.strictEqual(app.promptForPath.lastCall.args[0], 'folder');
- });
- });
- describe('window state serialization', function() {
- it('occurs immediately when adding a window', async function() {
- await scenario.launch(parseCommandLine(['a']));
- const promise = emitterEventPromise(
- scenario.getApplication(0),
- 'application:did-save-state'
- );
- await scenario.open(parseCommandLine(['c', 'b']));
- await promise;
- assert.isTrue(
- scenario
- .getApplication(0)
- .storageFolder.store.calledWith('application.json', {
- version: '1',
- windows: [
- { projectRoots: [scenario.convertRootPath('a')] },
- {
- projectRoots: [
- scenario.convertRootPath('b'),
- scenario.convertRootPath('c')
- ]
- }
- ]
- })
- );
- });
- it('occurs immediately when removing a window', async function() {
- await scenario.launch(parseCommandLine(['a']));
- const w = await scenario.open(parseCommandLine(['b']));
- const promise = emitterEventPromise(
- scenario.getApplication(0),
- 'application:did-save-state'
- );
- scenario.getApplication(0).removeWindow(w);
- await promise;
- assert.isTrue(
- scenario
- .getApplication(0)
- .storageFolder.store.calledWith('application.json', {
- version: '1',
- windows: [{ projectRoots: [scenario.convertRootPath('a')] }]
- })
- );
- });
- it('occurs when the window is blurred', async function() {
- const [w] = await scenario.launch(parseCommandLine(['a']));
- const promise = emitterEventPromise(
- scenario.getApplication(0),
- 'application:did-save-state'
- );
- w.browserWindow.emit('blur');
- await promise;
- });
- });
- describe('when closing the last window', function() {
- if (process.platform === 'linux' || process.platform === 'win32') {
- it('quits the application', async function() {
- const [w] = await scenario.launch(parseCommandLine(['a']));
- scenario.getApplication(0).removeWindow(w);
- assert.isTrue(electron.app.quit.called);
- });
- } else if (process.platform === 'darwin') {
- it('leaves the application open', async function() {
- const [w] = await scenario.launch(parseCommandLine(['a']));
- scenario.getApplication(0).removeWindow(w);
- assert.isFalse(electron.app.quit.called);
- });
- }
- });
- describe('quitting', function() {
- it('waits until all windows have saved their state before quitting', async function() {
- const [w0] = await scenario.launch(parseCommandLine(['a']));
- const w1 = await scenario.open(parseCommandLine(['b']));
- assert.notStrictEqual(w0, w1);
- sinon.spy(w0, 'close');
- let resolveUnload0;
- w0.prepareToUnload = () =>
- new Promise(resolve => {
- resolveUnload0 = resolve;
- });
- sinon.spy(w1, 'close');
- let resolveUnload1;
- w1.prepareToUnload = () =>
- new Promise(resolve => {
- resolveUnload1 = resolve;
- });
- const evt = { preventDefault: sinon.spy() };
- electron.app.emit('before-quit', evt);
- await new Promise(process.nextTick);
- assert.isTrue(evt.preventDefault.called);
- assert.isFalse(electron.app.quit.called);
- resolveUnload1(true);
- await new Promise(process.nextTick);
- assert.isFalse(electron.app.quit.called);
- resolveUnload0(true);
- await scenario.getApplication(0).lastBeforeQuitPromise;
- assert.isTrue(electron.app.quit.called);
- assert.isTrue(w0.close.called);
- assert.isTrue(w1.close.called);
- });
- it('prevents a quit if a user cancels when prompted to save', async function() {
- const [w] = await scenario.launch(parseCommandLine(['a']));
- let resolveUnload;
- w.prepareToUnload = () =>
- new Promise(resolve => {
- resolveUnload = resolve;
- });
- const evt = { preventDefault: sinon.spy() };
- electron.app.emit('before-quit', evt);
- await new Promise(process.nextTick);
- assert.isTrue(evt.preventDefault.called);
- resolveUnload(false);
- await scenario.getApplication(0).lastBeforeQuitPromise;
- assert.isFalse(electron.app.quit.called);
- });
- it('closes successfully unloaded windows', async function() {
- const [w0] = await scenario.launch(parseCommandLine(['a']));
- const w1 = await scenario.open(parseCommandLine(['b']));
- sinon.spy(w0, 'close');
- let resolveUnload0;
- w0.prepareToUnload = () =>
- new Promise(resolve => {
- resolveUnload0 = resolve;
- });
- sinon.spy(w1, 'close');
- let resolveUnload1;
- w1.prepareToUnload = () =>
- new Promise(resolve => {
- resolveUnload1 = resolve;
- });
- const evt = { preventDefault() {} };
- electron.app.emit('before-quit', evt);
- resolveUnload0(false);
- resolveUnload1(true);
- await scenario.getApplication(0).lastBeforeQuitPromise;
- assert.isFalse(electron.app.quit.called);
- assert.isFalse(w0.close.called);
- assert.isTrue(w1.close.called);
- });
- });
- });
- class StubWindow extends EventEmitter {
- constructor(sinon, loadSettings, options) {
- super();
- this.loadSettings = loadSettings;
- this._dimensions = Object.assign({}, loadSettings.windowDimensions) || {
- x: 100,
- y: 100
- };
- this._position = { x: 0, y: 0 };
- this._locations = [];
- this._rootPaths = new Set();
- this._editorPaths = new Set();
- let resolveClosePromise;
- this.closedPromise = new Promise(resolve => {
- resolveClosePromise = resolve;
- });
- this.minimize = sinon.spy();
- this.maximize = sinon.spy();
- this.center = sinon.spy();
- this.focus = sinon.spy();
- this.show = sinon.spy();
- this.hide = sinon.spy();
- this.prepareToUnload = sinon.spy();
- this.close = resolveClosePromise;
- this.replaceEnvironment = sinon.spy();
- this.disableZoom = sinon.spy();
- this.isFocused = sinon
- .stub()
- .returns(options.isFocused !== undefined ? options.isFocused : false);
- this.isMinimized = sinon
- .stub()
- .returns(options.isMinimized !== undefined ? options.isMinimized : false);
- this.isMaximized = sinon
- .stub()
- .returns(options.isMaximized !== undefined ? options.isMaximized : false);
- this.sendURIMessage = sinon.spy();
- this.didChangeUserSettings = sinon.spy();
- this.didFailToReadUserSettings = sinon.spy();
- this.isSpec =
- loadSettings.isSpec !== undefined ? loadSettings.isSpec : false;
- this.devMode =
- loadSettings.devMode !== undefined ? loadSettings.devMode : false;
- this.safeMode =
- loadSettings.safeMode !== undefined ? loadSettings.safeMode : false;
- this.browserWindow = new EventEmitter();
- this.browserWindow.webContents = new EventEmitter();
- const locationsToOpen = this.loadSettings.locationsToOpen || [];
- if (
- !(
- locationsToOpen.length === 1 && locationsToOpen[0].pathToOpen == null
- ) &&
- !this.isSpec
- ) {
- this.openLocations(locationsToOpen);
- }
- }
- openPath(pathToOpen, initialLine, initialColumn) {
- return this.openLocations([{ pathToOpen, initialLine, initialColumn }]);
- }
- openLocations(locations) {
- this._locations.push(...locations);
- for (const location of locations) {
- if (location.pathToOpen) {
- if (location.isDirectory) {
- this._rootPaths.add(location.pathToOpen);
- } else if (location.isFile) {
- this._editorPaths.add(location.pathToOpen);
- }
- }
- }
- this.projectRoots = Array.from(this._rootPaths);
- this.projectRoots.sort();
- this.emit('window:locations-opened');
- }
- setSize(x, y) {
- this._dimensions = { x, y };
- }
- setPosition(x, y) {
- this._position = { x, y };
- }
- isSpecWindow() {
- return this.isSpec;
- }
- hasProjectPaths() {
- return this._rootPaths.size > 0;
- }
- containsLocations(locations) {
- return locations.every(location => this.containsLocation(location));
- }
- containsLocation(location) {
- if (!location.pathToOpen) return false;
- return Array.from(this._rootPaths).some(projectPath => {
- if (location.pathToOpen === projectPath) return true;
- if (location.pathToOpen.startsWith(path.join(projectPath, path.sep))) {
- if (!location.exists) return true;
- if (!location.isDirectory) return true;
- }
- return false;
- });
- }
- getDimensions() {
- return Object.assign({}, this._dimensions);
- }
- }
- class LaunchScenario {
- static async create(sandbox) {
- const scenario = new this(sandbox);
- await scenario.init();
- return scenario;
- }
- constructor(sandbox) {
- this.sinon = sandbox;
- this.applications = new Set();
- this.windows = new Set();
- this.root = null;
- this.atomHome = null;
- this.projectRootPool = new Map();
- this.filePathPool = new Map();
- this.killedPids = [];
- this.originalAtomHome = null;
- }
- async init() {
- if (this.root !== null) {
- return this.root;
- }
- this.root = await new Promise((resolve, reject) => {
- temp.mkdir('launch-', (err, rootPath) => {
- if (err) {
- reject(err);
- } else {
- resolve(rootPath);
- }
- });
- });
- this.atomHome = path.join(this.root, '.atom');
- await new Promise((resolve, reject) => {
- fs.makeTree(this.atomHome, err => {
- if (err) {
- reject(err);
- } else {
- resolve();
- }
- });
- });
- this.originalAtomHome = process.env.ATOM_HOME;
- process.env.ATOM_HOME = this.atomHome;
- await Promise.all(
- ['a', 'b', 'c', 'd'].map(
- dirPath =>
- new Promise((resolve, reject) => {
- const fullDirPath = path.join(this.root, dirPath);
- fs.makeTree(fullDirPath, err => {
- if (err) {
- reject(err);
- } else {
- this.projectRootPool.set(dirPath, fullDirPath);
- resolve();
- }
- });
- })
- )
- );
- await Promise.all(
- ['a/1.md', 'b/2.md'].map(
- filePath =>
- new Promise((resolve, reject) => {
- const fullFilePath = path.join(this.root, filePath);
- fs.writeFile(
- fullFilePath,
- `file: ${filePath}\n`,
- { encoding: 'utf8' },
- err => {
- if (err) {
- reject(err);
- } else {
- this.filePathPool.set(filePath, fullFilePath);
- this.filePathPool.set(path.basename(filePath), fullFilePath);
- resolve();
- }
- }
- );
- })
- )
- );
- this.sinon.stub(electron.app, 'quit');
- }
- async preconditions(source) {
- const app = this.addApplication();
- const windowPromises = [];
- for (const windowSpec of this.parseWindowSpecs(source)) {
- if (windowSpec.editors.length === 0) {
- windowSpec.editors.push(null);
- }
- windowPromises.push(
- ((theApp, foldersToOpen, pathsToOpen) => {
- return theApp.openPaths({
- newWindow: true,
- foldersToOpen,
- pathsToOpen
- });
- })(app, windowSpec.roots, windowSpec.editors)
- );
- }
- await Promise.all(windowPromises);
- }
- launch(options) {
- const app = options.app || this.addApplication();
- delete options.app;
- if (options.pathsToOpen) {
- options.pathsToOpen = this.convertPaths(options.pathsToOpen);
- }
- return app.launch(options);
- }
- open(options) {
- if (this.applications.size === 0) {
- return this.launch(options);
- }
- let app = options.app;
- if (!app) {
- const apps = Array.from(this.applications);
- app = apps[apps.length - 1];
- } else {
- delete options.app;
- }
- if (options.pathsToOpen) {
- options.pathsToOpen = this.convertPaths(options.pathsToOpen);
- }
- options.preserveFocus = true;
- return app.openWithOptions(options);
- }
- async assert(source) {
- const windowSpecs = this.parseWindowSpecs(source);
- let specIndex = 0;
- const windowPromises = [];
- for (const window of this.windows) {
- windowPromises.push(
- (async (theWindow, theSpec) => {
- const {
- _rootPaths: rootPaths,
- _editorPaths: editorPaths
- } = theWindow;
- const comparison = {
- ok: true,
- extraWindow: false,
- missingWindow: false,
- extraRoots: [],
- missingRoots: [],
- extraEditors: [],
- missingEditors: [],
- roots: rootPaths,
- editors: editorPaths
- };
- if (!theSpec) {
- comparison.ok = false;
- comparison.extraWindow = true;
- comparison.extraRoots = rootPaths;
- comparison.extraEditors = editorPaths;
- } else {
- const [missingRoots, extraRoots] = this.compareSets(
- theSpec.roots,
- rootPaths
- );
- const [missingEditors, extraEditors] = this.compareSets(
- theSpec.editors,
- editorPaths
- );
- comparison.ok =
- missingRoots.length === 0 &&
- extraRoots.length === 0 &&
- missingEditors.length === 0 &&
- extraEditors.length === 0;
- comparison.extraRoots = extraRoots;
- comparison.missingRoots = missingRoots;
- comparison.extraEditors = extraEditors;
- comparison.missingEditors = missingEditors;
- }
- return comparison;
- })(window, windowSpecs[specIndex++])
- );
- }
- const comparisons = await Promise.all(windowPromises);
- for (; specIndex < windowSpecs.length; specIndex++) {
- const spec = windowSpecs[specIndex];
- comparisons.push({
- ok: false,
- extraWindow: false,
- missingWindow: true,
- extraRoots: [],
- missingRoots: spec.roots,
- extraEditors: [],
- missingEditors: spec.editors,
- roots: null,
- editors: null
- });
- }
- const shorthandParts = [];
- const descriptionParts = [];
- for (const comparison of comparisons) {
- if (comparison.roots !== null && comparison.editors !== null) {
- const shortRoots = Array.from(comparison.roots, r =>
- path.basename(r)
- ).join(',');
- const shortPaths = Array.from(comparison.editors, e =>
- path.basename(e)
- ).join(',');
- shorthandParts.push(`[${shortRoots} ${shortPaths}]`);
- }
- if (comparison.ok) {
- continue;
- }
- let parts = [];
- if (comparison.extraWindow) {
- parts.push('extra window\n');
- } else if (comparison.missingWindow) {
- parts.push('missing window\n');
- } else {
- parts.push('incorrect window\n');
- }
- const shorten = fullPaths =>
- fullPaths.map(fullPath => path.basename(fullPath)).join(', ');
- if (comparison.extraRoots.length > 0) {
- parts.push(`* extra roots ${shorten(comparison.extraRoots)}\n`);
- }
- if (comparison.missingRoots.length > 0) {
- parts.push(`* missing roots ${shorten(comparison.missingRoots)}\n`);
- }
- if (comparison.extraEditors.length > 0) {
- parts.push(`* extra editors ${shorten(comparison.extraEditors)}\n`);
- }
- if (comparison.missingEditors.length > 0) {
- parts.push(`* missing editors ${shorten(comparison.missingEditors)}\n`);
- }
- descriptionParts.push(parts.join(''));
- }
- if (descriptionParts.length !== 0) {
- descriptionParts.unshift(shorthandParts.join(' ') + '\n');
- descriptionParts.unshift('Launched windows did not match spec\n');
- }
- assert.isTrue(descriptionParts.length === 0, descriptionParts.join(''));
- }
- async destroy() {
- await Promise.all(Array.from(this.applications, app => app.destroy()));
- if (this.originalAtomHome) {
- process.env.ATOM_HOME = this.originalAtomHome;
- }
- }
- addApplication(options = {}) {
- const app = new AtomApplication({
- resourcePath: path.resolve(__dirname, '../..'),
- atomHomeDirPath: this.atomHome,
- preserveFocus: true,
- killProcess: pid => {
- this.killedPids.push(pid);
- },
- ...options
- });
- this.sinon.stub(app, 'createWindow').callsFake(loadSettings => {
- const newWindow = new StubWindow(this.sinon, loadSettings, options);
- this.windows.add(newWindow);
- return newWindow;
- });
- this.sinon
- .stub(app.storageFolder, 'load')
- .callsFake(() =>
- Promise.resolve(
- options.applicationJson || { version: '1', windows: [] }
- )
- );
- this.sinon
- .stub(app.storageFolder, 'store')
- .callsFake(() => Promise.resolve());
- this.applications.add(app);
- return app;
- }
- getApplication(index) {
- const app = Array.from(this.applications)[index];
- if (!app) {
- throw new Error(`Application ${index} does not exist`);
- }
- return app;
- }
- getWindow(index) {
- const window = Array.from(this.windows)[index];
- if (!window) {
- throw new Error(`Window ${index} does not exist`);
- }
- return window;
- }
- compareSets(expected, actual) {
- const expectedItems = new Set(expected);
- const extra = [];
- const missing = [];
- for (const actualItem of actual) {
- if (!expectedItems.delete(actualItem)) {
- // actualItem was present, but not expected
- extra.push(actualItem);
- }
- }
- for (const remainingItem of expectedItems) {
- // remainingItem was expected, but not present
- missing.push(remainingItem);
- }
- return [missing, extra];
- }
- convertRootPath(shortRootPath) {
- if (
- shortRootPath.startsWith('atom://') ||
- shortRootPath.startsWith('remote://')
- ) {
- return shortRootPath;
- }
- const fullRootPath = this.projectRootPool.get(shortRootPath);
- if (!fullRootPath) {
- throw new Error(`Unexpected short project root path: ${shortRootPath}`);
- }
- return fullRootPath;
- }
- convertEditorPath(shortEditorPath) {
- const [truncatedPath, ...suffix] = shortEditorPath.split(/(?=:)/);
- const fullEditorPath = this.filePathPool.get(truncatedPath);
- if (!fullEditorPath) {
- throw new Error(`Unexpected short editor path: ${shortEditorPath}`);
- }
- return fullEditorPath + suffix.join('');
- }
- convertPaths(paths) {
- return paths.map(shortPath => {
- if (
- shortPath.startsWith('atom://') ||
- shortPath.startsWith('remote://')
- ) {
- return shortPath;
- }
- const fullRoot = this.projectRootPool.get(shortPath);
- if (fullRoot) {
- return fullRoot;
- }
- const [truncatedPath, ...suffix] = shortPath.split(/(?=:)/);
- const fullEditor = this.filePathPool.get(truncatedPath);
- if (fullEditor) {
- return fullEditor + suffix.join('');
- }
- throw new Error(`Unexpected short path: ${shortPath}`);
- });
- }
- parseWindowSpecs(source) {
- const specs = [];
- const rx = /\s*\[(?:_|(\S+)) (?:_|(\S+))\]/g;
- let match = rx.exec(source);
- while (match) {
- const roots = match[1]
- ? match[1].split(',').map(shortPath => this.convertRootPath(shortPath))
- : [];
- const editors = match[2]
- ? match[2]
- .split(',')
- .map(shortPath => this.convertEditorPath(shortPath))
- : [];
- specs.push({ roots, editors });
- match = rx.exec(source);
- }
- return specs;
- }
- }
|