Skip to content

Commit

Permalink
More refactoring, started adding tests
Browse files Browse the repository at this point in the history
  • Loading branch information
khawkins committed Oct 25, 2023
1 parent 0620c87 commit bda8c72
Show file tree
Hide file tree
Showing 3 changed files with 309 additions and 91 deletions.
191 changes: 185 additions & 6 deletions src/test/suite/webviews.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@

import * as assert from 'assert';
import * as sinon from 'sinon';
import { Uri, env, languages } from 'vscode';
import { InstructionsWebviewProvider } from '../../webviews/instructions';
import { Uri, WebviewPanel, env } from 'vscode';
import { afterEach, beforeEach } from 'mocha';
import * as fs from 'fs';
import {
WebviewMessageHandler,
WebviewProcessor
} from '../../webviews/processor';

suite('InstructionsWebviewProvider Test Suite', () => {
const extensionUri = Uri.parse('file:///tmp/testdir');
Expand All @@ -28,8 +31,8 @@ suite('InstructionsWebviewProvider Test Suite', () => {
const fsExistStub = sinon.stub(fs, 'existsSync');
fsExistStub.returns(true);

const provider = new InstructionsWebviewProvider(extensionUri);
const path = provider.getLocaleContentPath(extensionUri, 'test.html');
const processor = new WebviewProcessor(extensionUri);
const path = processor.getLocaleContentPath('test.html');

assert.equal(path, 'test.es.html');
});
Expand All @@ -41,9 +44,185 @@ suite('InstructionsWebviewProvider Test Suite', () => {
const fsExistStub = sinon.stub(fs, 'existsSync');
fsExistStub.returns(false);

const provider = new InstructionsWebviewProvider(extensionUri);
const path = provider.getLocaleContentPath(extensionUri, 'test.html');
const processor = new WebviewProcessor(extensionUri);
const path = processor.getLocaleContentPath('test.html');

assert.equal(path, 'test.html');
});

test('No responsive message handlers', async () => {
const messageHandlerType = 'testType';
const testMessage = 'A test messsage for someNonResponsiveType';
const messageHandler: WebviewMessageHandler = {
type: messageHandlerType,
action: (_panel, _data) => {
assert.fail('This callback should not have been executed.');
}
};
const data = {
type: 'someNonResponsiveType',
testMessage: testMessage
};

const processor = new WebviewProcessor(extensionUri);
const panel = processor.createWebviewPanel('someViewType', 'someTitle');
processor.onWebviewReceivedMessage(data, panel, [messageHandler]);
panel.dispose();
});

test('One message handler for web view, no callback', async () => {
const messageHandlerType = 'testType';
const testMessage = 'A test messsage for testType';
const messageHandler: WebviewMessageHandler = {
type: messageHandlerType,
action: (_panel, data, callback) => {
const testData = data as { type: string; testMessage: string };
assert.ok(testData && testData.testMessage === testMessage);
assert.ok(callback === undefined);
}
};
const data = {
type: messageHandlerType,
testMessage: testMessage
};

const processor = new WebviewProcessor(extensionUri);
const panel = processor.createWebviewPanel('someViewType', 'someTitle');
processor.onWebviewReceivedMessage(data, panel, [messageHandler]);
panel.dispose();
});

test('One message handler for web view, callback', async () => {
const processor = new WebviewProcessor(extensionUri);
const panel = processor.createWebviewPanel('someViewType', 'someTitle');
const postMessageStub = sinon
.stub(panel.webview, 'postMessage')
.callsFake((message) => {
return new Promise((resolve) => {
assert.ok(message.callbackId === callbackId);
assert.ok(
message.testResponseMessage === testResponseMessage
);
return resolve(true);
});
});
const messageHandlerType = 'testType';
const testMessage = 'A test messsage for testType';
const testResponseMessage = 'A test response';
const testResponseObj = { testResponseMessage };
const callbackId = 13;
const messageHandler: WebviewMessageHandler = {
type: messageHandlerType,
action: (_panel, data, callback) => {
const testData = data as { type: string; testMessage: string };
assert.ok(testData && testData.testMessage === testMessage);
assert.ok(!!callback);
callback(testResponseObj);
}
};
const data = {
type: messageHandlerType,
testMessage,
callbackId
};

processor.onWebviewReceivedMessage(data, panel, [messageHandler]);
panel.dispose();
postMessageStub.restore();
});

test('Multiple message handlers', async () => {
type HandlerData = {
type: string;
testMessage: string;
testResponseMessage: string;
testResponseObj: { testResponseMessage: string };
callbackId: number;
};
const processor = new WebviewProcessor(extensionUri);
const panel = processor.createWebviewPanel('someViewType', 'someTitle');
const handler1Data: HandlerData = {
type: 'testType1',
testMessage: 'A test messsage for testType1',
testResponseMessage: 'A test response for testType1',
testResponseObj: {
testResponseMessage: 'A test messsage for testType1'
},
callbackId: 7
};
const handler2Data: HandlerData = {
type: 'testType2',
testMessage: 'A test messsage for testType2',
testResponseMessage: 'A test response for testType2',
testResponseObj: {
testResponseMessage: 'A test messsage for testType2'
},
callbackId: 8
};
const postMessageStub = sinon
.stub(panel.webview, 'postMessage')
.callsFake((message) => {
return new Promise((resolve) => {
const handlerData = [handler1Data, handler2Data].find(
(data) => {
return data.callbackId === message.callbackId;
}
);
assert.ok(!!handlerData);
assert.ok(
message.testResponseMessage ===
handlerData.testResponseMessage
);
return resolve(true);
});
});
const messageHandlers: WebviewMessageHandler[] = [
{
type: handler1Data.type,
action: (_panel, data, callback) => {
const testData = data as {
type: string;
testMessage: string;
};
assert.ok(
testData &&
testData.testMessage === handler1Data.testMessage
);
assert.ok(!!callback);
callback(handler1Data.testResponseObj);
}
},
{
type: handler2Data.type,
action: (_panel, data, callback) => {
const testData = data as {
type: string;
testMessage: string;
};
assert.ok(
testData &&
testData.testMessage === handler2Data.testMessage
);
assert.ok(!!callback);
callback(handler2Data.testResponseObj);
}
}
];
const data1 = {
type: handler1Data.type,
testMessage: handler1Data.testMessage,
callbackId: handler1Data.callbackId
};
const data2 = {
type: handler2Data.type,
testMessage: handler2Data.testMessage,
callbackId: handler2Data.callbackId
};

for (const data of [data1, data2]) {
processor.onWebviewReceivedMessage(data, panel, messageHandlers);
}
panel.dispose();
postMessageStub.restore();
});
});
97 changes: 12 additions & 85 deletions src/webviews/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,17 @@

import * as vscode from 'vscode';
import * as fs from 'fs';
import { WebviewMessageHandler, WebviewProcessor } from './processor';

export const MESSAGING_SCRIPT_PATH_DEMARCATOR = '--- MESSAGING_SCRIPT_SRC ---';
export const MESSAGING_JS_PATH = 'resources/instructions/webviewMessaging.js';
const INSTRUCTION_VIEW_TYPE = 'instructionsView';

export type WebviewMessageCallback = (responseData?: object) => void;

export type WebviewMessageHandler = {
type: string;
action: (
panel: vscode.WebviewPanel,
data?: object,
callback?: WebviewMessageCallback
) => void;
};

export class InstructionsWebviewProvider {
extensionUri: vscode.Uri;
processor: WebviewProcessor;

constructor(extensionUri: vscode.Uri) {
constructor(extensionUri: vscode.Uri, processor?: WebviewProcessor) {
this.extensionUri = extensionUri;
this.processor = processor ?? new WebviewProcessor(extensionUri);
}

public showInstructionWebview(
Expand All @@ -36,58 +26,23 @@ export class InstructionsWebviewProvider {
messageHandlers: WebviewMessageHandler[]
) {
this.validateMessageHanders(messageHandlers);
const panel = vscode.window.createWebviewPanel(
const panel = this.processor.createWebviewPanel(
INSTRUCTION_VIEW_TYPE,
title,
vscode.ViewColumn.Beside,
{
enableScripts: true,
localResourceRoots: [this.extensionUri]
}
title
);

panel.webview.onDidReceiveMessage((data) => {
const responsiveHandlers = messageHandlers.filter(
(messageHandler) => data.type === messageHandler.type
this.processor.onWebviewReceivedMessage(
data,
panel,
messageHandlers
);
if (responsiveHandlers.length > 0) {
const handler = responsiveHandlers[0];
let callback: WebviewMessageCallback | undefined;
if (data.callbackId) {
const returnedCallbackId = data.callbackId;
delete data.callbackId;
callback = (responseData?: object) => {
const fullResponseMessage = {
callbackId: returnedCallbackId,
...responseData
};
panel.webview.postMessage(fullResponseMessage);
};
}
handler.action(panel, data, callback);
}
});

const localeContentPath = this.getLocaleContentPath(
this.extensionUri,
const webviewContent = this.processor.getWebviewContent(
panel,
contentPath
);
const htmlPath = vscode.Uri.joinPath(
this.extensionUri,
localeContentPath
);
const messagingJsPath = vscode.Uri.joinPath(
this.extensionUri,
MESSAGING_JS_PATH
);

let webviewContent = fs.readFileSync(htmlPath.fsPath, {
encoding: 'utf-8'
});
webviewContent = webviewContent.replace(
MESSAGING_SCRIPT_PATH_DEMARCATOR,
panel.webview.asWebviewUri(messagingJsPath).toString()
);
panel.webview.html = webviewContent;
}

Expand All @@ -111,34 +66,6 @@ export class InstructionsWebviewProvider {
});
}

/**
* Check to see if a locale-specific file exists, otherwise return the default.
* @param extensionUri Uri representing the path to this extension, supplied by vscode.
* @param contentPath The relative path (and filename) of the content to display.
*/
getLocaleContentPath(
extensionUri: vscode.Uri,
contentPath: string
): string {
const language = vscode.env.language;

// check to see if a file exists for this locale.
const localeContentPath = contentPath.replace(
/\.html$/,
`.${language}.html`
);

const fullPath = vscode.Uri.joinPath(extensionUri, localeContentPath);

if (fs.existsSync(fullPath.fsPath)) {
// a file exists for this locale, so return it instead.
return localeContentPath;
} else {
// fall back
return contentPath;
}
}

private validateMessageHanders(messageHandlers: WebviewMessageHandler[]) {
const handlerMap: { [type: string]: boolean } = {};
for (const handler of messageHandlers) {
Expand Down
Loading

0 comments on commit bda8c72

Please sign in to comment.