blob: f59e4064f4346726c99b2918c8a12e105a957132 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as assert from 'assert';
import * as vscode from 'vscode';
import { describe, it } from 'mocha';
import { createSandbox, createStubInstance, SinonSandbox, SinonStubbedInstance } from 'sinon';
import { LoggingViewProvider, TOGGLE_LOG_VIEW_COMMAND } from '../../../logging/view_provider';
import { Ffx, FuchsiaDevice } from '../../../ffx';
import { StubbedSpawn } from '../utils';
describe('Logging view provider tests', function () {
const sandbox = createSandbox();
let ffx : SinonStubbedInstance<Ffx>;
let uri : vscode.Uri;
let provider: LoggingViewProvider;
let targetEventEmitter : vscode.EventEmitter<FuchsiaDevice | null>;
let log: vscode.OutputChannel;
this.beforeEach(() => {
let logger = sandbox.spy(vscode.window.createOutputChannel);
log = logger('test_ffx');
sandbox.spy(vscode.commands);
uri = vscode.Uri.file('/fake/file');
targetEventEmitter = new vscode.EventEmitter<FuchsiaDevice | null>();
ffx = createStubInstance(Ffx);
// @ts-ignore
ffx.onSetTarget = targetEventEmitter.event;
provider = new LoggingViewProvider(uri, ffx);
});
this.afterEach(function () {
sandbox.restore();
});
describe('visibility changes', () => {
it('stops the ffx process when the view is hidden', () => {
let fakeProcess = { stop: sandbox.spy() };
// @ts-ignore
ffx.runLog.returns(fakeProcess);
let provider = new LoggingViewProvider(uri, ffx);
let emitter = new vscode.EventEmitter<void>();
let view = fakeWebviewView(sandbox, true, emitter.event);
// @ts-ignore: passing empty objects that we don't need.
provider.resolveWebviewView(view, {}, {});
// This should trigger the creation of a process.
emitter.fire();
// @ts-ignore: readonly field, but set it for testing purposes.
view.visible = false;
emitter.fire();
assert.ok(fakeProcess.stop.calledOnce);
});
it('spawns an ffx process when the view appears', () => {
let emitter = new vscode.EventEmitter<void>();
let view = fakeWebviewView(sandbox, true, emitter.event);
// @ts-ignore: passing empty objects that we don't need.
provider.resolveWebviewView(view, {}, {});
emitter.fire();
assert.ok(ffx.runLog.calledOnce);
assert.deepStrictEqual(ffx.runLog.getCall(0).args[0], undefined);
});
});
describe('add a log', () => {
it('pushes a log to the webview when received', () => {
let process = new StubbedSpawn(sandbox);
let ffx = new Ffx(log, 'foo');
// @ts-ignore: fake process
sandbox.stub(ffx, 'spawn').returns(process.spawnEvent);
let provider = new LoggingViewProvider(uri, ffx);
let emitter = new vscode.EventEmitter<void>();
let view = fakeWebviewView(sandbox, true, emitter.event);
// @ts-ignore: passing empty objects that we don't need.
provider.resolveWebviewView(view, {}, {});
// The view should be visible now.
emitter.fire();
const data = '{"data":{"TargetLog": {}}}';
process.spawnEvent.stdout?.emit('data', data);
// @ts-ignore: sinon
assert.ok(view.webview.postMessage.calledOnce);
assert.deepStrictEqual(
// @ts-ignore: sinon
view.webview.postMessage.getCall(0).args[0],
{ type: 'addLog', log: JSON.parse(data) }
);
});
it('handles logs that contain new lines', () => {
let process = new StubbedSpawn(sandbox);
let ffx = new Ffx(log, 'foo');
// @ts-ignore: fake process
sandbox.stub(ffx, 'spawn').returns(process.spawnEvent);
let provider = new LoggingViewProvider(uri, ffx);
let emitter = new vscode.EventEmitter<void>();
let view = fakeWebviewView(sandbox, true, emitter.event);
// @ts-ignore: passing empty objects that we don't need.
provider.resolveWebviewView(view, {}, {});
// Creates the process
emitter.fire();
const data = `{"data":{"TargetLog": {"payload": {"message": "foo\\nbar"}}}}
{"data": {"TargetLog": {"payload": {"message": "baz"}}}}`;
process.stdout.emit('data', data);
// @ts-ignore: sinon
assert.ok(view.webview.postMessage.calledTwice);
assert.deepStrictEqual(
// @ts-ignore: sinon
view.webview.postMessage.getCall(0).args[0],
// eslint-disable-next-line @typescript-eslint/naming-convention
{ type: 'addLog', log: {data: {TargetLog: {payload: {message: 'foo\nbar'}}}}}
);
assert.deepStrictEqual(
// @ts-ignore: sinon
view.webview.postMessage.getCall(1).args[0],
// eslint-disable-next-line @typescript-eslint/naming-convention
{ type: 'addLog', log: {data: {TargetLog: {payload: {message: 'baz'}}}}}
);
});
});
describe('#resetAndShowView', () => {
it('toggles the view when there is no previous view', () => {
provider.resetAndShowView(undefined);
// @ts-ignore: calledOnce comes from sinon
assert.ok(vscode.commands.executeCommand.calledOnce);
assert.strictEqual(
// @ts-ignore: getCall comes from sinon
vscode.commands.executeCommand.getCall(0).args[0], TOGGLE_LOG_VIEW_COMMAND);
});
it('changes the current device and toggles the view when no previous view', () => {
assert.strictEqual(provider.currentTargetDevice, undefined);
provider.resetAndShowView(new FuchsiaDevice({ nodename: 'foo' }));
assert.strictEqual(provider.currentTargetDevice, 'foo');
// @ts-ignore: calledOnce comes from sinon
assert.ok(vscode.commands.executeCommand.calledOnce);
assert.strictEqual(
// @ts-ignore: getCall comes from sinon
vscode.commands.executeCommand.getCall(0).args[0], TOGGLE_LOG_VIEW_COMMAND);
});
it('resets the view when the device changes and the view is visible', () => {
let view = fakeWebviewView(sandbox, true, new vscode.EventEmitter<any>().event);
// @ts-ignore: passing empty objects that we don't need.
provider.resolveWebviewView(view, {}, {});
provider.resetAndShowView(new FuchsiaDevice({ nodename: 'foo' }));
// @ts-ignore: calledOnce comes from sinon
assert.ok(view.webview.postMessage.calledOnce);
// @ts-ignore: getCall comes from sinon
assert.deepStrictEqual(view.webview.postMessage.getCall(0).args[0], { type: 'reset' });
assert.ok(ffx.runLog.calledOnce);
assert.deepStrictEqual(ffx.runLog.getCall(0).args[0], 'foo');
});
it('shows the view when the device changes and the view is not visible', () => {
let view = fakeWebviewView(sandbox, false, new vscode.EventEmitter<any>().event);
// @ts-ignore: passing empty objects that we don't need.
provider.resolveWebviewView(view, {}, {});
provider.resetAndShowView(new FuchsiaDevice({ nodename: 'foo' }));
// @ts-ignore: calledOnce comes from sinon
assert.ok(view.show.calledOnce);
});
});
describe('on default target change', () => {
it('changes the current device and resets the view when visible', () => {
let view = fakeWebviewView(sandbox, true, new vscode.EventEmitter<any>().event);
// @ts-ignore: passing empty objects that we don't need.
provider.resolveWebviewView(view, {}, {});
targetEventEmitter.fire(new FuchsiaDevice({ nodename: 'bar' }));
assert.strictEqual(provider.currentTargetDevice, 'bar');
// @ts-ignore: calledOnce comes from sinon
assert.ok(view.webview.postMessage.calledOnce);
// @ts-ignore: getCall comes from sinon
assert.deepStrictEqual(view.webview.postMessage.getCall(0).args[0], { type: 'reset' });
});
it('changes the current device when the view is not visible', () => {
let view = fakeWebviewView(sandbox, false, new vscode.EventEmitter<any>().event);
// @ts-ignore: passing empty objects that we don't need.
provider.resolveWebviewView(view, {}, {});
targetEventEmitter.fire(new FuchsiaDevice({ nodename: 'bar' }));
assert.strictEqual(provider.currentTargetDevice, 'bar');
// @ts-ignore: calledOnce comes from sinon
assert.ok(!view.webview.postMessage.calledOnce);
});
it('does nothing when the device is the same', () => {
let view = fakeWebviewView(sandbox, false, new vscode.EventEmitter<any>().event);
// @ts-ignore: passing empty objects that we don't need.
provider.resolveWebviewView(view, {}, {});
provider.resetAndShowView(new FuchsiaDevice({ nodename: 'foo' }));
targetEventEmitter.fire(new FuchsiaDevice({ nodename: 'foo' }));
assert.strictEqual(provider.currentTargetDevice, 'foo');
// @ts-ignore: calledOnce comes from sinon
assert.ok(!view.webview.postMessage.calledOnce);
});
});
});
/**
* Allows to fake the webview.
*
* @param sandbox the sinon sandbox backing up the test
* @param visible whether the view should be visible or not
* @param onDidChangeVisibility event fired when the visibility of the view changes
* @returns a fake webview
*/
function fakeWebviewView(
sandbox: SinonSandbox, visible: boolean,
onDidChangeVisibility: vscode.Event<any>,
): vscode.WebviewView {
let webview = createStubInstance(FakeWebview);
webview.onDidReceiveMessage = sandbox.stub();
webview.asWebviewUri.returns(vscode.Uri.file('/fake/file'));
sandbox.spy(webview.onDidReceiveMessage);
let view = <vscode.WebviewView>{ visible };
// @ts-ignore: readonly, but we need to call it.
view.onDidChangeVisibility = onDidChangeVisibility;
view.show = sandbox.spy();
// @ts-ignore: readonly, but we fake it.
view.webview = webview;
return view;
}
class FakeWebview implements vscode.Webview {
options: vscode.WebviewOptions = <vscode.WebviewOptions>{};
html: string = '';
onDidReceiveMessage: vscode.Event<any> = <vscode.Event<any>>{};
postMessage(message: any): Thenable<boolean> {
throw new Error('Method not implemented.');
}
asWebviewUri(localResource: vscode.Uri): vscode.Uri {
throw new Error('Method not implemented.');
}
cspSource: string = '';
}