| // 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 = ''; |
| } |