diff --git a/package.json b/package.json index fa32481b..40578dcd 100644 --- a/package.json +++ b/package.json @@ -546,6 +546,19 @@ "dark": "./resources/dark/datasource.svg", "light": "./resources/light/datasource.svg" } + }, + { + "id": "kdbplot", + "aliases": [ + "kdbplot" + ], + "extensions": [ + ".plot" + ], + "icon": { + "dark": "./resources/dark/plot.svg", + "light": "./resources/light/plot.svg" + } } ], "grammars": [ @@ -935,6 +948,16 @@ } ], "priority": "default" + }, + { + "viewType": "kdb.chartEditor", + "displayName": "Chart Viewer", + "selector": [ + { + "filenamePattern": "*.plot" + } + ], + "priority": "default" } ] }, diff --git a/resources/dark/plot.svg b/resources/dark/plot.svg new file mode 100644 index 00000000..e0f9af88 --- /dev/null +++ b/resources/dark/plot.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/evaluate.q b/resources/evaluate.q index 26fa7311..d22b223d 100644 --- a/resources/evaluate.q +++ b/resources/evaluate.q @@ -117,15 +117,21 @@ (`result; ::); (`errored; 1b); (`error; err); - (`backtrace; .Q.sbt userCode)) + (`backtrace; .Q.sbt userCode); + (`base64; 0b)) }[suffix; prefix]]; if [isLastLine or result`errored; system "d ", cachedCtx; : result]; index +: 1]; }; - result: evalInContext[ctx; splitExpression stripTrailingSemi wrapLines removeMultilineComments code]; - if [(not result `errored) and stringify; - result[`result]: toString result `result]; + result: evalInContext[ctx; splitExpression stripTrailingSemi wrapLines removeMultilineComments code]; + if[result `errored; :result]; + if[type[result[`result]] = 99h; + if[`output in key result[`result]; + if[type[result[`result][`output]] = 99h; + if[`bytes in key result[`result][`output]; + result[`base64]:1b; result[`result]: .Q.btoa result[`result][`output][`bytes]; :result]]]]; + if [stringify; result[`result]: toString result `result]; result } diff --git a/resources/light/plot.svg b/resources/light/plot.svg new file mode 100644 index 00000000..1d204db7 --- /dev/null +++ b/resources/light/plot.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/classes/localConnection.ts b/src/classes/localConnection.ts index eef32929..1501f20a 100644 --- a/src/classes/localConnection.ts +++ b/src/classes/localConnection.ts @@ -175,6 +175,7 @@ export class LocalConnection { isPython?: boolean, ): Promise { let result; + let base64 = false; await this.waitForConnection(); if (!this.connection) { @@ -202,6 +203,7 @@ export class LocalConnection { ); } else { result = res.result === null ? "" : res.result; + base64 = res.base64 || false; } }); @@ -213,6 +215,10 @@ export class LocalConnection { this.updateGlobal(); + if (base64) { + return { base64, result }; + } + if (ext.isResultsTabVisible && stringify) { if (this.isError) { this.isError = false; diff --git a/src/commands/serverCommand.ts b/src/commands/serverCommand.ts index 396a897c..09a7a6f0 100644 --- a/src/commands/serverCommand.ts +++ b/src/commands/serverCommand.ts @@ -80,6 +80,14 @@ import { ServerType, } from "../models/connectionsModels"; import * as fs from "fs"; +import { ChartEditorProvider } from "../services/chartEditorProvider"; +import { + addWorkspaceFile, + openWith, + setUriContent, + workspaceHas, +} from "../utils/workspace"; +import { Plot } from "../models/plot"; export async function addNewConnection(): Promise { NewConnectionPannel.close(); @@ -848,7 +856,25 @@ export async function executeQuery( ); } else { /* istanbul ignore next */ - if (ext.isResultsTabVisible) { + if (results.base64) { + const active = ext.activeTextEditor; + if (active) { + const data = `data:image/png;base64,${results.result}`; + const plot = { + charts: [{ data }], + }; + const uri = await addWorkspaceFile( + active.document.uri, + "plot", + ".plot", + ); + if (!workspaceHas(uri)) { + await workspace.openTextDocument(uri); + await openWith(uri, ChartEditorProvider.viewType, ViewColumn.Beside); + } + await setUriContent(uri, JSON.stringify(plot)); + } + } else if (ext.isResultsTabVisible) { writeQueryResultsToView( results, query, diff --git a/src/extension.ts b/src/extension.ts index 0e387974..46189266 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,10 +17,8 @@ import { ConfigurationTarget, EventEmitter, ExtensionContext, - Range, TextDocumentContentProvider, Uri, - WorkspaceEdit, commands, extensions, languages, @@ -93,7 +91,6 @@ import { DataSourceEditorProvider } from "./services/dataSourceEditorProvider"; import { FileTreeItem, WorkspaceTreeProvider, - addWorkspaceFile, } from "./services/workspaceTreeProvider"; import { ConnectionLensProvider, @@ -102,6 +99,7 @@ import { importOldDSFiles, pickConnection, runActiveEditor, + setServerForUri, } from "./commands/workspaceCommand"; import { createDefaultDataSourceFile } from "./models/dataSource"; import { connectBuildTools, lintCommand } from "./commands/buildToolsCommand"; @@ -116,13 +114,19 @@ import { renameLabel, setLabelColor, } from "./utils/connLabel"; -import { activateTextDocument } from "./utils/workspace"; +import { + activateTextDocument, + addWorkspaceFile, + openWith, + setUriContent, +} from "./utils/workspace"; import { InsightDetails, Insights, Server, ServerDetails, } from "./models/connectionsModels"; +import { ChartEditorProvider } from "./services/chartEditorProvider"; let client: LanguageClient; @@ -472,26 +476,19 @@ export async function activate(context: ExtensionContext) { "kdb.createDataSource", async (item: FileTreeItem) => { if (hasWorkspaceOrShowOption("adding datasources")) { - const uri = await addWorkspaceFile(item, "datasource", ".kdb.json"); - - if (uri) { - const edit = new WorkspaceEdit(); - - edit.replace( - uri, - new Range(0, 0, 1, 0), - JSON.stringify(createDefaultDataSourceFile(), null, 2), - ); - - workspace.applyEdit(edit); - - await commands.executeCommand( - "vscode.openWith", - uri, - DataSourceEditorProvider.viewType, - ); - await commands.executeCommand("workbench.action.files.save", uri); - } + const uri = await addWorkspaceFile( + item ? item.resourceUri : undefined, + "datasource", + ".kdb.json", + ); + await workspace.openTextDocument(uri); + await setUriContent( + uri, + JSON.stringify(createDefaultDataSourceFile(), null, 2), + ); + await openWith(uri, DataSourceEditorProvider.viewType); + await commands.executeCommand("workbench.action.files.save", uri); + await setServerForUri(uri, undefined); } }, ), @@ -502,11 +499,15 @@ export async function activate(context: ExtensionContext) { "kdb.createScratchpad", async (item: FileTreeItem) => { if (hasWorkspaceOrShowOption("adding workbooks")) { - const uri = await addWorkspaceFile(item, "workbook", ".kdb.q"); - if (uri) { - await window.showTextDocument(uri); - await commands.executeCommand("workbench.action.files.save", uri); - } + const uri = await addWorkspaceFile( + item ? item.resourceUri : undefined, + "workbook", + ".kdb.q", + ); + await workspace.openTextDocument(uri); + await window.showTextDocument(uri); + await commands.executeCommand("workbench.action.files.save", uri); + await setServerForUri(uri, undefined); } }, ), @@ -514,11 +515,15 @@ export async function activate(context: ExtensionContext) { "kdb.createPythonScratchpad", async (item: FileTreeItem) => { if (hasWorkspaceOrShowOption("adding workbooks")) { - const uri = await addWorkspaceFile(item, "workbook", ".kdb.py"); - if (uri) { - await window.showTextDocument(uri); - await commands.executeCommand("workbench.action.files.save", uri); - } + const uri = await addWorkspaceFile( + item ? item.resourceUri : undefined, + "workbook", + ".kdb.py", + ); + await workspace.openTextDocument(uri); + await window.showTextDocument(uri); + await commands.executeCommand("workbench.action.files.save", uri); + await setServerForUri(uri, undefined); } }, ), @@ -534,11 +539,7 @@ export async function activate(context: ExtensionContext) { commands.registerCommand("kdb.renameFile", async (item: FileTreeItem) => { if (item && item.resourceUri) { if (item.resourceUri.path.endsWith(".kdb.json")) { - await commands.executeCommand( - "vscode.openWith", - item.resourceUri, - DataSourceEditorProvider.viewType, - ); + await openWith(item.resourceUri, DataSourceEditorProvider.viewType); } else { const document = await workspace.openTextDocument(item.resourceUri); await window.showTextDocument(document); @@ -550,11 +551,7 @@ export async function activate(context: ExtensionContext) { commands.registerCommand("kdb.deleteFile", async (item: FileTreeItem) => { if (item && item.resourceUri) { if (item.resourceUri.path.endsWith(".kdb.json")) { - await commands.executeCommand( - "vscode.openWith", - item.resourceUri, - DataSourceEditorProvider.viewType, - ); + await openWith(item.resourceUri, DataSourceEditorProvider.viewType); } else { const document = await workspace.openTextDocument(item.resourceUri); await window.showTextDocument(document); @@ -565,6 +562,7 @@ export async function activate(context: ExtensionContext) { }), DataSourceEditorProvider.register(context), + ChartEditorProvider.register(context), languages.registerCodeLensProvider( { pattern: "**/*.kdb.{q,py}" }, diff --git a/src/models/plot.ts b/src/models/plot.ts new file mode 100644 index 00000000..0d98fa95 --- /dev/null +++ b/src/models/plot.ts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +export interface Chart { + data: string; +} + +export interface Plot { + charts: Chart[]; +} diff --git a/src/models/queryResult.ts b/src/models/queryResult.ts index 29946719..cb459b2d 100644 --- a/src/models/queryResult.ts +++ b/src/models/queryResult.ts @@ -26,6 +26,7 @@ export type QueryResult = { text: string; index: number; }[]; + base64?: boolean; }; export enum QueryResultType { diff --git a/src/services/chartEditorProvider.ts b/src/services/chartEditorProvider.ts new file mode 100644 index 00000000..481cb4c6 --- /dev/null +++ b/src/services/chartEditorProvider.ts @@ -0,0 +1,102 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +import { + ColorThemeKind, + CustomTextEditorProvider, + Disposable, + ExtensionContext, + TextDocument, + Webview, + WebviewPanel, + window, + workspace, +} from "vscode"; +import { getUri } from "../utils/getUri"; +import { getNonce } from "../utils/getNonce"; + +export class ChartEditorProvider implements CustomTextEditorProvider { + static readonly viewType = "kdb.chartEditor"; + + public static register(context: ExtensionContext): Disposable { + const provider = new ChartEditorProvider(context); + return window.registerCustomEditorProvider( + ChartEditorProvider.viewType, + provider, + ); + } + + constructor(private readonly context: ExtensionContext) {} + + async resolveCustomTextEditor( + document: TextDocument, + webviewPanel: WebviewPanel, + ): Promise { + const webview = webviewPanel.webview; + webview.options = { enableScripts: true }; + webview.html = this.getWebviewContent(webview); + + const updateWebview = () => { + webview.postMessage(document.getText()); + }; + + const changeDocumentSubscription = workspace.onDidChangeTextDocument( + (event) => { + if (event.document.uri.toString() === document.uri.toString()) { + updateWebview(); + } + }, + ); + + webviewPanel.onDidChangeViewState(() => { + if (webviewPanel.active) { + updateWebview(); + } + }); + + webviewPanel.onDidDispose(() => { + changeDocumentSubscription.dispose(); + }); + + webview.onDidReceiveMessage(async (_msg: any) => {}); + + updateWebview(); + } + + private getWebviewContent(webview: Webview) { + const getResource = (resource: string) => + getUri(webview, this.context.extensionUri, ["out", resource]); + + return /* html */ ` + + + + + + + + + DataSource + + + + + + `; + } +} diff --git a/src/services/workspaceTreeProvider.ts b/src/services/workspaceTreeProvider.ts index c89e0359..20a5b04d 100644 --- a/src/services/workspaceTreeProvider.ts +++ b/src/services/workspaceTreeProvider.ts @@ -23,10 +23,7 @@ import { } from "vscode"; import Path from "path"; import { getWorkspaceIconsState } from "../utils/core"; -import { - getConnectionForUri, - setServerForUri, -} from "../commands/workspaceCommand"; +import { getConnectionForUri } from "../commands/workspaceCommand"; import { ext } from "../extensionVariables"; export class WorkspaceTreeProvider implements TreeDataProvider { @@ -65,7 +62,7 @@ export class WorkspaceTreeProvider implements TreeDataProvider { } export class FileTreeItem extends TreeItem { - private declare pattern?: RelativePattern; + declare private pattern?: RelativePattern; constructor( resourceUri: Uri, @@ -144,47 +141,3 @@ export class FileTreeItem extends TreeItem { return []; } } - -export async function addWorkspaceFile( - item: FileTreeItem, - name: string, - ext: string, - directory = ".kx", -) { - const folders = workspace.workspaceFolders; - if (folders) { - const folder = - item && item.resourceUri - ? workspace.getWorkspaceFolder(item.resourceUri) - : folders[0]; - if (folder) { - let i = 1; - while (true) { - const files = await workspace.findFiles( - `${directory}/${name}-${i}${ext}`, - ); - if (files.length === 0) { - break; - } - i++; - if (i > 100) { - throw new Error("No available file name found"); - } - } - - const uri = Uri.joinPath( - folder.uri, - directory, - `${name}-${i}${ext}`, - ).with({ - scheme: "untitled", - }); - - await workspace.openTextDocument(uri); - await setServerForUri(uri, undefined); - return uri; - } - } else { - throw new Error("No workspace has been opened"); - } -} diff --git a/src/utils/workspace.ts b/src/utils/workspace.ts index 8ca042e1..041f8cdd 100644 --- a/src/utils/workspace.ts +++ b/src/utils/workspace.ts @@ -11,7 +11,16 @@ * specific language governing permissions and limitations under the License. */ -import { Uri, window, workspace } from "vscode"; +import { + commands, + Range, + TextDocumentShowOptions, + Uri, + ViewColumn, + window, + workspace, + WorkspaceEdit, +} from "vscode"; export function getWorkspaceRoot( ignoreException: boolean = false, @@ -46,3 +55,61 @@ export async function activateTextDocument(item: Uri) { } } } + +export async function addWorkspaceFile( + uri: Uri | undefined, + name: string, + ext: string, + directory = ".kx", +) { + const folders = workspace.workspaceFolders; + if (folders) { + const folder = uri ? workspace.getWorkspaceFolder(uri) : folders[0]; + if (folder) { + let i = 1; + while (true) { + const files = await workspace.findFiles( + `${directory}/${name}-${i}${ext}`, + ); + if (files.length === 0) { + break; + } + i++; + if (i > 100) { + throw new Error("No available file name found"); + } + } + + const uri = Uri.joinPath( + folder.uri, + directory, + `${name}-${i}${ext}`, + ).with({ + scheme: "untitled", + }); + + return uri; + } + } + throw new Error("No workspace has been opened"); +} + +export async function setUriContent(uri: Uri, content: string) { + const edit = new WorkspaceEdit(); + edit.replace(uri, new Range(0, 0, 1, 0), content); + await workspace.applyEdit(edit); +} + +export function workspaceHas(uri: Uri) { + return workspace.textDocuments.some( + (doc) => doc.uri.toString() == uri.toString(), + ); +} + +export async function openWith( + uri: Uri, + type: string, + options?: TextDocumentShowOptions | ViewColumn, +) { + await commands.executeCommand("vscode.openWith", uri, type, options); +} diff --git a/src/webview/components/kdbChartView.ts b/src/webview/components/kdbChartView.ts new file mode 100644 index 00000000..809a595b --- /dev/null +++ b/src/webview/components/kdbChartView.ts @@ -0,0 +1,57 @@ +/* + * Copyright (c) 1998-2023 Kx Systems Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +import { html, LitElement } from "lit"; +import { customElement, state } from "lit/decorators.js"; +import { chartStyles, shoelaceStyles } from "./styles"; +import { Plot } from "../../models/plot"; + +@customElement("kdb-chart-view") +export class KdbChartView extends LitElement { + static styles = [shoelaceStyles, chartStyles]; + + readonly vscode = acquireVsCodeApi(); + + @state() + plot: Plot = { charts: [] }; + + connectedCallback() { + super.connectedCallback(); + window.addEventListener("message", this.message); + } + + disconnectedCallback() { + super.disconnectedCallback(); + window.removeEventListener("message", this.message); + } + + readonly message = (event: MessageEvent) => { + this.plot = JSON.parse(event.data); + }; + + render() { + return html` +
+ ${this.plot.charts.map( + (chart) => html``, + )} +
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "kdb-chart-view": KdbChartView; + } +} diff --git a/src/webview/components/styles.ts b/src/webview/components/styles.ts index f340968d..bc9630e0 100644 --- a/src/webview/components/styles.ts +++ b/src/webview/components/styles.ts @@ -133,6 +133,18 @@ export const dataSourceStyles = css` } `; +export const chartStyles = css` + .frame { + width: 100vw; + height: 100vh; + } + + .plot { + width: auto; + max-height: 100%; + } +`; + export const vscodeStyles = css` .dropdown-container { box-sizing: border-box; diff --git a/src/webview/main.ts b/src/webview/main.ts index f98d874e..fa328308 100644 --- a/src/webview/main.ts +++ b/src/webview/main.ts @@ -32,5 +32,6 @@ import { } from "@vscode/webview-ui-toolkit"; import "./components/kdbDataSourceView"; import "./components/kdbNewConnectionView"; +import "./components/kdbChartView"; provideVSCodeDesignSystem().register(allComponents); diff --git a/test/suite/services.test.ts b/test/suite/services.test.ts index 814c6322..07616dfd 100644 --- a/test/suite/services.test.ts +++ b/test/suite/services.test.ts @@ -53,10 +53,7 @@ import { LocalConnection } from "../../src/classes/localConnection"; import { Telemetry } from "../../src/utils/telemetryClient"; import { InsightsConnection } from "../../src/classes/insightsConnection"; import { DataSourceEditorProvider } from "../../src/services/dataSourceEditorProvider"; -import { - WorkspaceTreeProvider, - addWorkspaceFile, -} from "../../src/services/workspaceTreeProvider"; +import { WorkspaceTreeProvider } from "../../src/services/workspaceTreeProvider"; import Path from "path"; import * as utils from "../../src/utils/getUri"; import { MetaInfoType, MetaObject } from "../../src/models/meta"; @@ -73,6 +70,7 @@ import * as coreUtils from "../../src/utils/core"; import { KdbTreeService } from "../../src/services/kdbTreeService"; import * as serverCommand from "../../src/commands/serverCommand"; import { ServerObject } from "../../src/models/serverObject"; +import { ChartEditorProvider } from "../../src/services/chartEditorProvider"; // eslint-disable-next-line @typescript-eslint/no-var-requires const codeFlow = require("../../src/services/kdbInsights/codeFlowLogin"); @@ -1804,37 +1802,37 @@ describe("connectionManagerService", () => { }); }); -describe("dataSourceEditorProvider", () => { - let context: ExtensionContext; - - function createPanel() { - const listeners = { - onDidReceiveMessage: undefined, - postMessage: undefined, - onDidChangeViewState: undefined, - onDidDispose: undefined, - }; - const panel = { - webview: { - onDidReceiveMessage(e) { - listeners.onDidReceiveMessage = e; - }, - postMessage(e) { - listeners.postMessage = e; - }, - }, - onDidChangeViewState(e) { - listeners.onDidChangeViewState = e; +function createPanel() { + const listeners = { + onDidReceiveMessage: undefined, + postMessage: undefined, + onDidChangeViewState: undefined, + onDidDispose: undefined, + }; + const panel = { + webview: { + onDidReceiveMessage(e) { + listeners.onDidReceiveMessage = e; }, - onDidDispose(e) { - listeners.onDidDispose = e; + postMessage(e) { + listeners.postMessage = e; }, - }; - return { - panel, - listeners, - }; - } + }, + onDidChangeViewState(e) { + listeners.onDidChangeViewState = e; + }, + onDidDispose(e) { + listeners.onDidDispose = e; + }, + }; + return { + panel, + listeners, + }; +} + +describe("dataSourceEditorProvider", () => { + let context: ExtensionContext; beforeEach(() => { context = {}; @@ -2062,6 +2060,47 @@ describe("dataSourceEditorProvider", () => { }); }); +describe("ChartEditorProvider", () => { + let context: ExtensionContext; + + beforeEach(() => { + context = {}; + }); + + afterEach(() => { + sinon.restore(); + }); + + describe("register", () => { + it("should register the provider", () => { + let result = undefined; + sinon + .stub(window, "registerCustomEditorProvider") + .value(() => (result = true)); + ChartEditorProvider.register(context); + assert.ok(result); + }); + }); + + describe("resolveCustomTextEditor", () => { + it("should resolve", async () => { + const provider = new ChartEditorProvider(context); + const document = await workspace.openTextDocument({ + language: "kdbplot", + content: "{}", + }); + sinon.stub(utils, "getUri").value(() => ""); + const panel = createPanel(); + await assert.doesNotReject( + provider.resolveCustomTextEditor(document, panel.panel), + ); + panel.listeners.onDidReceiveMessage({}); + panel.listeners.onDidChangeViewState(); + panel.listeners.onDidDispose(); + }); + }); +}); + describe("workspaceTreeProvider", () => { let provider: WorkspaceTreeProvider; @@ -2111,13 +2150,6 @@ describe("workspaceTreeProvider", () => { assert.strictEqual(result.length, 1); }); }); - - describe("addWorkspaceFile", () => { - it("should break after try", async () => { - stubWorkspaceFile("/workspace/test.kdb.q"); - await assert.rejects(() => addWorkspaceFile(undefined, "test", ".kdb.q")); - }); - }); }); describe("CompletionProvider", () => { diff --git a/test/suite/webview.test.ts b/test/suite/webview.test.ts index 1bed0d54..1533f569 100644 --- a/test/suite/webview.test.ts +++ b/test/suite/webview.test.ts @@ -36,6 +36,7 @@ import { MetaObjectPayload } from "../../src/models/meta"; import { html, TemplateResult } from "lit"; import { ext } from "../../src/extensionVariables"; import { InsightDetails, ServerType } from "../../src/models/connectionsModels"; +import { KdbChartView } from "../../src/webview/components/kdbChartView"; describe("KdbDataSourceView", () => { let view: KdbDataSourceView; @@ -968,3 +969,43 @@ describe("KdbNewConnectionView", () => { describe("createLabel", () => {}); }); + +describe("kdbChartView.ts", () => { + let view: KdbChartView; + + beforeEach(async () => { + view = new KdbChartView(); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe("connectedCallback", () => { + it("should add an event listener", () => { + let result = undefined; + sinon.stub(window, "addEventListener").value(() => (result = true)); + view.connectedCallback(); + assert.ok(result); + }); + }); + + describe("disconnectedCallback", () => { + it("should remove an event listener", () => { + let result = undefined; + sinon.stub(window, "removeEventListener").value(() => (result = true)); + view.disconnectedCallback(); + assert.ok(result); + }); + }); + + it("should update from message", () => { + const data = { charts: [{ data: "test" }] }; + view.message({ + data: JSON.stringify(data), + }); + assert.deepStrictEqual(view.plot, data); + const result = view.render(); + assert.ok(result); + }); +}); diff --git a/test/suite/workspace.test.ts b/test/suite/workspace.test.ts index fdf55a58..af75b23b 100644 --- a/test/suite/workspace.test.ts +++ b/test/suite/workspace.test.ts @@ -20,14 +20,10 @@ describe("Workspace tests", () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const testWorkspaceFolder: any[] = [ { - uri: { - fsPath: "testPath1", - }, + uri: vscode.Uri.file("testPath1"), }, { - uri: { - fsPath: "testPath2", - }, + uri: vscode.Uri.file("testPath2"), }, ]; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -67,14 +63,57 @@ describe("Workspace tests", () => { const result = workspaceHelper.isWorkspaceOpen(); assert.strictEqual(result, true); }); -}); -describe("activateTextDocument", () => { - it("should activate document", async () => { - sinon.stub(vscode.workspace, "openTextDocument").value(() => ({})); - const stub = sinon.stub(vscode.window, "showTextDocument"); - const uri = vscode.Uri.file("/test/test.q"); - await workspaceHelper.activateTextDocument(uri); - assert.strictEqual(stub.calledOnce, true); + describe("activateTextDocument", () => { + it("should activate document", async () => { + sinon.stub(vscode.workspace, "openTextDocument").value(() => ({})); + const stub = sinon.stub(vscode.window, "showTextDocument"); + const uri = vscode.Uri.file("/test/test.q"); + await workspaceHelper.activateTextDocument(uri); + assert.strictEqual(stub.calledOnce, true); + }); + }); + + describe("addWorkspaceFile", () => { + it("should reject when no workspace", async () => { + await assert.rejects( + workspaceHelper.addWorkspaceFile(undefined, "test", ".q"), + ); + }); + it("should return file uri", async () => { + workspaceMock.value(testWorkspaceFolder); + sinon + .stub(vscode.workspace, "getWorkspaceFolder") + .returns(testWorkspaceFolder[0]); + const uri = vscode.Uri.file("test.q"); + const result = await workspaceHelper.addWorkspaceFile(uri, "test", ".q"); + assert.ok(result.fsPath.endsWith("test-1.q")); + }); + }); + + describe("setUriContent", () => { + it("should reject when no workspace", async () => { + const applyEdit = sinon.stub(vscode.workspace, "applyEdit"); + const uri = vscode.Uri.file("test.q"); + await workspaceHelper.setUriContent(uri, "test"); + assert.ok(applyEdit.calledOnce); + }); + }); + + describe("workspaceHas", () => { + it("should return false", async () => { + const uri = vscode.Uri.file("test.q"); + const result = workspaceHelper.workspaceHas(uri); + assert.strictEqual(result, false); + }); + }); + + describe("openWith", () => { + it("should call command", async () => { + const executeCommand = sinon.stub(vscode.commands, "executeCommand"); + const uri = vscode.Uri.file("test.q"); + await workspaceHelper.openWith(uri, "test"); + assert.strictEqual(executeCommand.calledOnce, true); + }); }); });