Skip to content

Commit

Permalink
feat: interactive loop and tests modified
Browse files Browse the repository at this point in the history
  • Loading branch information
saintsebastian committed Apr 12, 2017
1 parent 2a0994d commit 7dbb6ee
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 110 deletions.
103 changes: 52 additions & 51 deletions src/cmd/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,16 @@ export type WatcherCreatorParams = {|
onSourceChange?: OnSourceChangeFn,
ignoreFiles?: Array<string>,
createFileFilter?: FileFilterCreatorFn,
addonReload?: typeof defaultAddonReload,
addonReload: typeof defaultAddonReload,
|};

export type WatcherCreatorFn = (params: WatcherCreatorParams) => Watchpack;

export function defaultWatcherCreator(
{
addonId, client, sourceDir, artifactsDir, ignoreFiles,
addonId, addonReload, client, sourceDir, artifactsDir, ignoreFiles,
onSourceChange = defaultSourceWatcher,
createFileFilter = defaultFileFilterCreator,
addonReload = defaultAddonReload,
}: WatcherCreatorParams
): Watchpack {
const fileFilter = createFileFilter(
Expand All @@ -76,23 +75,24 @@ export type ReloadParams = {|
desktopNotifications?: typeof defaultDesktopNotifications,
|};

export function defaultAddonReload(
export async function defaultAddonReload(
{
addonId, client,
desktopNotifications = defaultDesktopNotifications,
}: ReloadParams
): Promise<void> {
log.debug(`Reloading add-on ID ${addonId}`);
return client.reloadAddon(addonId)
.catch((error) => {
log.error('\n');
log.error(error.stack);
desktopNotifications({
title: 'web-ext run: error occurred',
message: error.message,
});
throw error;
try {
await client.reloadAddon(addonId);
} catch (reloadError) {
log.error('\n');
log.error(reloadError.stack);
desktopNotifications({
title: 'web-ext run: error occurred',
message: reloadError.message,
});
throw reloadError;
}
}


Expand All @@ -109,8 +109,10 @@ export type ReloadStrategyParams = {|
|};

export type ReloadStrategyOptions = {|
addonReload?: typeof defaultAddonReload,
createWatcher?: WatcherCreatorFn,
createFileFilter?: FileFilterCreatorFn,
stdin: stream$Readable,
|};

export function defaultReloadStrategy(
Expand All @@ -119,18 +121,49 @@ export function defaultReloadStrategy(
sourceDir, artifactsDir, ignoreFiles,
}: ReloadStrategyParams,
{
addonReload = defaultAddonReload,
createWatcher = defaultWatcherCreator,
stdin = process.stdin,
}: ReloadStrategyOptions = {}
): void {
const watcher: Watchpack = (
createWatcher({addonId, client, sourceDir, artifactsDir, ignoreFiles})
);
const watcher: Watchpack = createWatcher({
addonId, addonReload, client, sourceDir, artifactsDir, ignoreFiles,
});

firefoxProcess.on('close', () => {
client.disconnect();
watcher.close();
stdin.pause();
});

if (stdin.isTTY && stdin instanceof tty.ReadStream) {
readline.emitKeypressEvents(stdin);
stdin.setRawMode(true);

// NOTE: this `Promise.resolve().then(...)` is basically used to spawn a "co-routine" that is executed
// before the callback attached to the Promise returned by this function (and it allows the `run` function
// to not be stuck in the while loop).
Promise.resolve().then(async function() {
log.info('Press R to reload (and Ctrl-C to quit)');

let userExit = false;

while (!userExit) {
const keyPressed = await new Promise((resolve) => {
stdin.once('keypress', (str, key) => resolve(key));
});

if (keyPressed.ctrl && keyPressed.name === 'c') {
userExit = true;
} else if (keyPressed.name === 'r' && addonId) {
await addonReload({addonId, client});
}
}

log.info('\nExiting web-ext on user request');
firefoxProcess.kill();
});
}
}


Expand Down Expand Up @@ -195,30 +228,28 @@ export type CmdRunParams = {|
customPrefs?: FirefoxPreferences,
startUrl?: string | Array<string>,
ignoreFiles?: Array<string>,
stdin: stream$Readable,
|};

export type CmdRunOptions = {|
firefoxApp: typeof defaultFirefoxApp,
firefoxClient: typeof defaultFirefoxClient,
reloadStrategy: typeof defaultReloadStrategy,
addonReload: typeof defaultAddonReload,
AddonRunner: typeof ExtensionRunner
ExtensionRunnerClass?: typeof ExtensionRunner,
|};

export default async function run(
{
sourceDir, artifactsDir, firefox, firefoxProfile,
keepProfileChanges = false, preInstall = false, noReload = false,
browserConsole = false, customPrefs, startUrl, ignoreFiles,
stdin = process.stdin,
}: CmdRunParams,
{
firefoxApp = defaultFirefoxApp,
firefoxClient = defaultFirefoxClient,
reloadStrategy = defaultReloadStrategy,
addonReload = defaultAddonReload,
AddonRunner = ExtensionRunner,
ExtensionRunnerClass = ExtensionRunner,
}: CmdRunOptions = {}): Promise<Object> {

log.info(`Running web extension from ${sourceDir}`);
Expand All @@ -237,7 +268,7 @@ export default async function run(

const manifestData = await getValidatedManifest(sourceDir);

const runner = new AddonRunner({
const runner = new ExtensionRunnerClass({
sourceDir,
firefoxApp,
firefox,
Expand Down Expand Up @@ -293,36 +324,6 @@ export default async function run(
);
}

if (stdin.isTTY && stdin instanceof tty.ReadStream) {
readline.emitKeypressEvents(stdin);
stdin.setRawMode(true);

// NOTE: this `Promise.resolve().then(...)` is basically used to spawn a "co-routine" that is executed
// before the callback attached to the Promise returned by this function (and it allows the `run` function
// to not be stuck in the while loop).
Promise.resolve().then(async function() {
log.info('Press R to reload (and Ctrl-C to quit)');

let userExit = false;

while (!userExit) {
const keyPressed = await new Promise((resolve) => {
stdin.once('keypress', (str, key) => resolve(key));
});

if (keyPressed.ctrl && keyPressed.name === 'c') {
userExit = true;
} else if (keyPressed.name === 'r' && addonId) {
await addonReload({addonId, client});
}
}

log.info('\nExiting web-ext on user request');
runningFirefox.kill();
stdin.pause();
});
}

log.info('The extension will reload if any source file changes');
reloadStrategy({
firefoxProcess: runningFirefox,
Expand Down
98 changes: 39 additions & 59 deletions tests/unit/test-cmd/test.run.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@ import {onlyInstancesOf, WebExtError, RemoteTempInstallNotSupported}
from '../../../src/errors';
import run, {
defaultFirefoxClient, defaultWatcherCreator, defaultReloadStrategy,
defaultAddonReload, ExtensionRunner,
defaultAddonReload,
} from '../../../src/cmd/run';
import * as defaultFirefoxApp from '../../../src/firefox';
import type {
FirefoxProcess, // eslint-disable-line import/named
} from '../../../src/firefox/index';
import {RemoteFirefox} from '../../../src/firefox/remote';
import {TCPConnectError, fakeFirefoxClient, makeSureItFails, fake, fixturePath}
from '../helpers';
Expand All @@ -37,15 +34,11 @@ describe('run', () => {

function prepareRun(fakeInstallResult) {
const sourceDir = fixturePath('minimal-web-ext');
const fakeStdin = new tty.ReadStream();
sinon.spy(fakeStdin, 'pause');
sinon.spy(fakeStdin, 'setRawMode');

const argv = {
artifactsDir: path.join(sourceDir, 'web-ext-artifacts'),
sourceDir,
noReload: true,
stdin: fakeStdin,
keepProfileChanges: false,
};
const options = {
Expand Down Expand Up @@ -246,55 +239,6 @@ describe('run', () => {
});
});

it('can reload when user presses R in shell console', () => {
const cmd = prepareRun();
const {addonReload} = cmd.options;
const fakeStdin = cmd.argv.stdin;

return cmd.run({noReload: false})
.then(() => {
fakeStdin.emit('keypress', 'r', {name: 'r', ctrl: false});
})
.then(() => {
assert.ok(fakeStdin.setRawMode.called);
assert.ok(addonReload.called);
assert.equal(addonReload.firstCall.args[0].addonId,
tempInstallResult.addon.id);
});
});


it('logs and exits on user request (CTRL+C in shell console)', () => {
const cmd = prepareRun();
const fakeStdin = cmd.argv.stdin;
const {reloadStrategy} = cmd.options;

class StubChildProcess extends EventEmitter {
stderr = new EventEmitter();
stdout = new EventEmitter();
kill = sinon.spy(() => Promise.resolve());
}
const fakeFirefox = new StubChildProcess();

class FakeExtensionRunner extends ExtensionRunner {
run(): Promise<FirefoxProcess> {
return Promise.resolve(fakeFirefox);
}
}

return cmd.run({noReload: false}, {AddonRunner: FakeExtensionRunner})
.then(() => {
fakeStdin.emit('keypress', 'c', {name: 'c', ctrl: true});
})
.then(() => {
assert.ok(fakeStdin.pause.called);
assert.ok(reloadStrategy.called);
assert.equal(reloadStrategy.firstCall.args[0].firefoxProcess,
fakeFirefox);
assert.ok(fakeFirefox.kill.called);
});
});

it('raise an error on addonId missing from installTemporaryAddon result',
() => {
const cmd = prepareRun(tempInstallResultMissingAddonId);
Expand Down Expand Up @@ -430,6 +374,10 @@ describe('run', () => {
const watcher = {
close: sinon.spy(() => {}),
};
const fakeStdin = new tty.ReadStream();
sinon.spy(fakeStdin, 'pause');
sinon.spy(fakeStdin, 'setRawMode');

const args = {
addonId: 'some-addon@test-suite',
client,
Expand All @@ -440,14 +388,16 @@ describe('run', () => {
ignoreFiles: ['first/file', 'second/file'],
};
const options = {
addonReload: sinon.spy(() => Promise.resolve()),
createWatcher: sinon.spy(() => watcher),
stdin: fakeStdin,
};
return {
...args,
...options,
client,
watcher,
reloadStrategy: (argOverride = {}, optOverride = {}) => {
reloadStrategy: async (argOverride = {}, optOverride = {}) => {
return defaultReloadStrategy(
{...args, ...argOverride},
{...options, ...optOverride});
Expand All @@ -456,11 +406,14 @@ describe('run', () => {
}

it('cleans up connections when firefox closes', () => {
const {firefoxProcess, client, watcher, reloadStrategy} = prepare();
const {
firefoxProcess, client, watcher, reloadStrategy, stdin,
} = prepare();
reloadStrategy();
firefoxProcess.emit('close');
assert.equal(client.client.disconnect.called, true);
assert.equal(watcher.close.called, true);
assert.ok(stdin.pause.called);
});

it('configures a watcher', () => {
Expand All @@ -479,6 +432,33 @@ describe('run', () => {
assert.deepEqual(receivedArgs.ignoreFiles, sentArgs.ignoreFiles);
});

it('can reload when user presses R in shell console', () => {
const {addonReload, stdin, reloadStrategy} = prepare();

return reloadStrategy()
.then(() => {
stdin.emit('keypress', 'r', {name: 'r', ctrl: false});
})
.then(() => {
assert.ok(stdin.setRawMode.called);
assert.ok(addonReload.called);
assert.equal(addonReload.firstCall.args[0].addonId,
tempInstallResult.addon.id);
});
});


it('shuts down firefox on user request (CTRL+C in shell console)', () => {
const {firefoxProcess, stdin, reloadStrategy} = prepare();

return reloadStrategy()
.then(() => {
stdin.emit('keypress', 'c', {name: 'c', ctrl: true});
}).then(() => {
assert.ok(firefoxProcess.kill.called);
});
});

});

describe('defaultAddonReload', () => {
Expand Down

0 comments on commit 7dbb6ee

Please sign in to comment.