From 7f818077b222018b050edfbc96afeed2c039d336 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Thu, 25 Apr 2024 16:11:39 -0400 Subject: [PATCH 1/5] Support opening data sets in binary mode Signed-off-by: Timothy Johnson --- .../zowe-explorer/src/dataset/DatasetTree.ts | 5 +- .../src/dataset/ZoweDatasetNode.ts | 5 +- packages/zowe-explorer/src/dataset/init.ts | 4 +- packages/zowe-explorer/src/shared/utils.ts | 39 ++++++++++++++++ packages/zowe-explorer/src/uss/USSTree.ts | 25 +++++++--- packages/zowe-explorer/src/uss/ZoweUSSNode.ts | 46 ++----------------- packages/zowe-explorer/src/uss/init.ts | 4 +- 7 files changed, 70 insertions(+), 58 deletions(-) diff --git a/packages/zowe-explorer/src/dataset/DatasetTree.ts b/packages/zowe-explorer/src/dataset/DatasetTree.ts index 0911fc4bdc..b3ebc8c694 100644 --- a/packages/zowe-explorer/src/dataset/DatasetTree.ts +++ b/packages/zowe-explorer/src/dataset/DatasetTree.ts @@ -27,6 +27,7 @@ import { SortDirection, NodeSort, DatasetFilterOpts, + ZosEncoding, } from "@zowe/zowe-explorer-api"; import { Profiles } from "../Profiles"; import { ZoweExplorerApiRegister } from "../ZoweExplorerApiRegister"; @@ -1522,8 +1523,8 @@ export class DatasetTree extends ZoweTreeProvider implements IZoweTree { - const encoding = await promptForEncoding(node); + public async openWithEncoding(node: IZoweDatasetTreeNode, encoding?: ZosEncoding): Promise { + encoding = encoding ?? (await promptForEncoding(node)); if (encoding !== undefined) { node.setEncoding(encoding); await node.openDs(true, false, this); diff --git a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts index 439ccf2886..9005782777 100644 --- a/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts +++ b/packages/zowe-explorer/src/dataset/ZoweDatasetNode.ts @@ -37,7 +37,7 @@ import { ZoweLogger } from "../utils/LoggerUtils"; import * as dayjs from "dayjs"; import * as fs from "fs"; import { promiseStatus, PromiseStatuses } from "promise-status-async"; -import { getDocumentFilePath, updateOpenFiles } from "../shared/utils"; +import { getDocumentFilePath, initializeFileOpening, updateOpenFiles } from "../shared/utils"; import { IZoweDatasetTreeOpts } from "../shared/IZoweTreeOpts"; import { LocalFileManagement } from "../utils/LocalFileManagement"; @@ -596,8 +596,7 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod } statusMsg.dispose(); updateOpenFiles(datasetProvider, documentFilePath, this); - const document = await vscode.workspace.openTextDocument(documentFilePath); - await Gui.showTextDocument(document, { preview: this.wasDoubleClicked != null ? !this.wasDoubleClicked : shouldPreview }); + await initializeFileOpening(this, documentFilePath, this.wasDoubleClicked != null ? !this.wasDoubleClicked : shouldPreview); // discard ongoing action to allow new requests on this node if (this.ongoingActions) { this.ongoingActions[NodeAction.Download] = null; diff --git a/packages/zowe-explorer/src/dataset/init.ts b/packages/zowe-explorer/src/dataset/init.ts index c8bdf0ddaa..6877f1a144 100644 --- a/packages/zowe-explorer/src/dataset/init.ts +++ b/packages/zowe-explorer/src/dataset/init.ts @@ -13,7 +13,7 @@ import * as globals from "../globals"; import * as vscode from "vscode"; import * as dsActions from "./actions"; import * as refreshActions from "../shared/refresh"; -import { IZoweDatasetTreeNode, IZoweTreeNode } from "@zowe/zowe-explorer-api"; +import { IZoweDatasetTreeNode, IZoweTreeNode, ZosEncoding } from "@zowe/zowe-explorer-api"; import { Profiles } from "../Profiles"; import { DatasetTree, createDatasetTree } from "./DatasetTree"; import { ZoweDatasetNode } from "./ZoweDatasetNode"; @@ -213,7 +213,7 @@ export async function initDatasetProvider(context: vscode.ExtensionContext): Pro context.subscriptions.push( vscode.commands.registerCommand( "zowe.ds.openWithEncoding", - (node: IZoweDatasetTreeNode): Promise => datasetProvider.openWithEncoding(node) + (node: IZoweDatasetTreeNode, encoding?: ZosEncoding): Promise => datasetProvider.openWithEncoding(node, encoding) ) ); context.subscriptions.push( diff --git a/packages/zowe-explorer/src/shared/utils.ts b/packages/zowe-explorer/src/shared/utils.ts index 961791e7fa..90b7e8283a 100644 --- a/packages/zowe-explorer/src/shared/utils.ts +++ b/packages/zowe-explorer/src/shared/utils.ts @@ -544,3 +544,42 @@ export async function promptForEncoding(node: IZoweDatasetTreeNode | IZoweUSSTre } return encoding; } + +export async function initializeFileOpening( + node: IZoweDatasetTreeNode | IZoweUSSTreeNode, + documentPath: string, + previewFile?: boolean +): Promise { + ZoweLogger.trace("zowe.shared.utils.initializeFileOpening called."); + let document; + let openingTextFailed = false; + + if (!node.binary) { + try { + document = await vscode.workspace.openTextDocument(documentPath); + } catch (err) { + ZoweLogger.warn(err); + openingTextFailed = true; + } + + if (openingTextFailed) { + const yesResponse = localize("zowe.shared.utils.initializeFileOpening.yesButton", "Re-download"); + const noResponse = localize("zowe.shared.utils.initializeFileOpening.noButton", "Cancel"); + + const response = await Gui.errorMessage( + localize("zowe.shared.utils.initializeFileOpening.message", "Failed to open file as text. Re-download file as binary?"), + { items: [yesResponse, noResponse] } + ); + + if (response === yesResponse) { + const command = isZoweDatasetTreeNode(node) ? "zowe.ds.openWithEncoding" : "zowe.uss.openWithEncoding"; + await vscode.commands.executeCommand(command, node, { kind: "binary" }); + } + } else { + await Gui.showTextDocument(document, { preview: previewFile }); + } + } else { + const uriPath = vscode.Uri.file(documentPath); + await vscode.commands.executeCommand("vscode.open", uriPath); + } +} diff --git a/packages/zowe-explorer/src/uss/USSTree.ts b/packages/zowe-explorer/src/uss/USSTree.ts index 4f1bb8b8cd..b2dcbe5a96 100644 --- a/packages/zowe-explorer/src/uss/USSTree.ts +++ b/packages/zowe-explorer/src/uss/USSTree.ts @@ -15,7 +15,16 @@ import * as path from "path"; import { imperative } from "@zowe/cli"; import { FilterItem, FilterDescriptor, errorHandling, syncSessionNode } from "../utils/ProfilesUtils"; import { sortTreeItems, getAppName, checkIfChildPath, updateOpenFiles, promptForEncoding, isClosedFileDirty } from "../shared/utils"; -import { Gui, IZoweTree, IZoweTreeNode, IZoweUSSTreeNode, NodeInteraction, ValidProfileEnum, PersistenceSchemaEnum } from "@zowe/zowe-explorer-api"; +import { + Gui, + IZoweTree, + IZoweTreeNode, + IZoweUSSTreeNode, + NodeInteraction, + ValidProfileEnum, + PersistenceSchemaEnum, + ZosEncoding, +} from "@zowe/zowe-explorer-api"; import { Profiles } from "../Profiles"; import { ZoweExplorerApiRegister } from "../ZoweExplorerApiRegister"; import { ZoweUSSNode } from "./ZoweUSSNode"; @@ -956,13 +965,15 @@ export class USSTree extends ZoweTreeProvider implements IZoweTree { - const ussApi = ZoweExplorerApiRegister.getUssApi(node.getProfile()); - let taggedEncoding: string; - if (ussApi.getTag != null) { - taggedEncoding = await ussApi.getTag(node.fullPath); + public async openWithEncoding(node: IZoweUSSTreeNode, encoding?: ZosEncoding): Promise { + if (encoding == null) { + const ussApi = ZoweExplorerApiRegister.getUssApi(node.getProfile()); + let taggedEncoding: string; + if (ussApi.getTag != null) { + taggedEncoding = await ussApi.getTag(node.fullPath); + } + encoding = await promptForEncoding(node, taggedEncoding !== "untagged" ? taggedEncoding : undefined); } - const encoding = await promptForEncoding(node, taggedEncoding !== "untagged" ? taggedEncoding : undefined); if (encoding !== undefined) { node.setEncoding(encoding); await node.openUSS(true, false, this); diff --git a/packages/zowe-explorer/src/uss/ZoweUSSNode.ts b/packages/zowe-explorer/src/uss/ZoweUSSNode.ts index 715d91b743..b7d5a24540 100644 --- a/packages/zowe-explorer/src/uss/ZoweUSSNode.ts +++ b/packages/zowe-explorer/src/uss/ZoweUSSNode.ts @@ -34,7 +34,7 @@ import { closeOpenedTextFile } from "../utils/workspace"; import * as nls from "vscode-nls"; import { UssFileTree, UssFileType, UssFileUtils } from "./FileStructure"; import { ZoweLogger } from "../utils/LoggerUtils"; -import { updateOpenFiles } from "../shared/utils"; +import { initializeFileOpening, updateOpenFiles } from "../shared/utils"; import { IZoweUssTreeOpts } from "../shared/IZoweTreeOpts"; import { TreeProviders } from "../shared/TreeProviders"; import { LocalFileManagement } from "../utils/LocalFileManagement"; @@ -542,7 +542,7 @@ export class ZoweUSSNode extends ZoweTreeNode implements IZoweUSSTreeNode { ussFileProvider.getTreeView().reveal(this, { select: true, focus: true, expand: false }); updateOpenFiles(ussFileProvider, documentFilePath, this); - await this.initializeFileOpening(documentFilePath, shouldPreview); + await initializeFileOpening(this, documentFilePath, shouldPreview); } } catch (err) { await errorHandling(err, this.getProfileName()); @@ -604,10 +604,10 @@ export class ZoweUSSNode extends ZoweTreeNode implements IZoweUSSTreeNode { this.downloaded = true; if (isDirty) { - await this.initializeFileOpening(ussDocumentFilePath, true); + await initializeFileOpening(this, ussDocumentFilePath, true); } } else if (wasSaved) { - await this.initializeFileOpening(ussDocumentFilePath, true); + await initializeFileOpening(this, ussDocumentFilePath, true); } } catch (err) { if (err instanceof Error && err.message.includes(localize("refreshUSS.error.notFound", "not found"))) { @@ -621,44 +621,6 @@ export class ZoweUSSNode extends ZoweTreeNode implements IZoweUSSTreeNode { } } - public async initializeFileOpening(documentPath: string, previewFile?: boolean): Promise { - ZoweLogger.trace("ZoweUSSNode.initializeFileOpening called."); - let document; - let openingTextFailed = false; - - if (!this.binary) { - try { - document = await vscode.workspace.openTextDocument(documentPath); - } catch (err) { - ZoweLogger.warn(err); - openingTextFailed = true; - } - - if (openingTextFailed) { - const yesResponse = localize("openUSS.log.info.failedToOpenAsText.yes", "Re-download"); - const noResponse = localize("openUSS.log.info.failedToOpenAsText.no", "Cancel"); - - const response = await Gui.errorMessage( - localize("openUSS.log.info.failedToOpenAsText", "Failed to open file as text. Re-download file as binary?"), - { items: [yesResponse, noResponse] } - ); - - if (response === yesResponse) { - await vscode.commands.executeCommand("zowe.uss.binary", this); - } - } else { - if (previewFile === true) { - await Gui.showTextDocument(document); - } else { - await Gui.showTextDocument(document, { preview: false }); - } - } - } else { - const uriPath = vscode.Uri.file(documentPath); - await vscode.commands.executeCommand("vscode.open", uriPath); - } - } - /** * Returns the local file path for the ZoweUSSNode * diff --git a/packages/zowe-explorer/src/uss/init.ts b/packages/zowe-explorer/src/uss/init.ts index 5687523fc8..2e8c2c9d86 100644 --- a/packages/zowe-explorer/src/uss/init.ts +++ b/packages/zowe-explorer/src/uss/init.ts @@ -13,7 +13,7 @@ import * as globals from "../globals"; import * as vscode from "vscode"; import * as ussActions from "./actions"; import * as refreshActions from "../shared/refresh"; -import { IZoweUSSTreeNode, IZoweTreeNode } from "@zowe/zowe-explorer-api"; +import { IZoweUSSTreeNode, IZoweTreeNode, ZosEncoding } from "@zowe/zowe-explorer-api"; import { Profiles } from "../Profiles"; import * as contextuals from "../shared/context"; import { getSelectedNodeList } from "../shared/utils"; @@ -194,7 +194,7 @@ export async function initUSSProvider(context: vscode.ExtensionContext): Promise context.subscriptions.push( vscode.commands.registerCommand( "zowe.uss.openWithEncoding", - (node: IZoweUSSTreeNode): Promise => ussFileProvider.openWithEncoding(node) + (node: IZoweUSSTreeNode, encoding?: ZosEncoding): Promise => ussFileProvider.openWithEncoding(node, encoding) ) ); context.subscriptions.push( From 0c8138bd6bf0e20db6aaba101fbb520828c58943 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Thu, 25 Apr 2024 16:11:54 -0400 Subject: [PATCH 2/5] Update unit tests for shared utils Signed-off-by: Timothy Johnson --- .../__unit__/dataset/init.unit.test.ts | 2 +- .../__unit__/shared/utils.unit.test.ts | 167 ++++++++++++++++++ .../__unit__/uss/ZoweUSSNode.unit.test.ts | 78 -------- .../__tests__/__unit__/uss/init.unit.test.ts | 2 +- 4 files changed, 169 insertions(+), 80 deletions(-) diff --git a/packages/zowe-explorer/__tests__/__unit__/dataset/init.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/dataset/init.unit.test.ts index 581d5f54d7..601667475f 100644 --- a/packages/zowe-explorer/__tests__/__unit__/dataset/init.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/dataset/init.unit.test.ts @@ -264,7 +264,7 @@ describe("Test src/dataset/extension", () => { }, { name: "zowe.ds.openWithEncoding", - mock: [{ spy: jest.spyOn(dsProvider, "openWithEncoding"), arg: [test.value] }], + mock: [{ spy: jest.spyOn(dsProvider, "openWithEncoding"), arg: [test.value, undefined] }], }, { name: "onDidChangeConfiguration", diff --git a/packages/zowe-explorer/__tests__/__unit__/shared/utils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/shared/utils.unit.test.ts index 5c6b49d134..751822c5e7 100644 --- a/packages/zowe-explorer/__tests__/__unit__/shared/utils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/shared/utils.unit.test.ts @@ -44,6 +44,10 @@ async function createGlobalMocks() { mockGetInstance: jest.fn(), mockProfileInstance: null, mockProfilesCache: null, + mockExecuteCommand: jest.fn(), + openTextDocument: jest.fn(), + mockShowTextDocument: jest.fn(), + mockTextDocument: { fileName: `/test/path/temp/_U_/sestest/test/node`, isDirty: true }, }; newMocks.mockProfilesCache = new ProfilesCache(imperative.Logger.getAppLogger()); newMocks.mockProfileInstance = createInstanceOfProfile(createIProfile()); @@ -56,6 +60,18 @@ async function createGlobalMocks() { value: () => newMocks.mockProfileInstance, configurable: true, }); + Object.defineProperty(vscode.commands, "executeCommand", { + value: newMocks.mockExecuteCommand, + configurable: true, + }); + Object.defineProperty(vscode.workspace, "openTextDocument", { + value: newMocks.openTextDocument, + configurable: true, + }); + Object.defineProperty(Gui, "showTextDocument", { + value: newMocks.mockShowTextDocument, + configurable: true, + }); Object.defineProperty(newMocks.mockProfilesCache, "getConfigInstance", { value: jest.fn(() => { @@ -1018,3 +1034,154 @@ describe("Shared utils unit tests - function promptForEncoding", () => { expect(blockMocks.showQuickPick.mock.calls[0][1]).toEqual(expect.objectContaining({ placeHolder: "Current encoding is IBM-1047" })); }); }); + +describe("Shared utils unit tests - function initializeFileOpening", () => { + it("successfully handles binary data sets that should be re-downloaded", async () => { + const globalMocks = await createGlobalMocks(); + + jest.spyOn(vscode.workspace, "openTextDocument").mockRejectedValue("Test error!"); + jest.spyOn(Gui, "errorMessage").mockResolvedValue("Re-download"); + + // Creating a test node + const rootNode = new ZoweDatasetNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session: globalMocks.session, + profile: globalMocks.profileOne, + }); + rootNode.contextValue = globals.DS_SESSION_CONTEXT; + const testNode = new ZoweDatasetNode({ + label: "TEST.DS", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: rootNode, + profile: globalMocks.profileOne, + }); + + await sharedUtils.initializeFileOpening(testNode, testNode.fullPath); + expect(globalMocks.mockExecuteCommand).toHaveBeenCalledWith("zowe.ds.openWithEncoding", testNode, { kind: "binary" }); + }); + + it("successfully handles text data sets that should be previewed", async () => { + const globalMocks = await createGlobalMocks(); + + jest.spyOn(vscode.workspace, "openTextDocument").mockResolvedValue(globalMocks.mockTextDocument as vscode.TextDocument); + + // Creating a test node + const rootNode = new ZoweDatasetNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session: globalMocks.session, + profile: globalMocks.profileOne, + }); + rootNode.contextValue = globals.DS_SESSION_CONTEXT; + const testNode = new ZoweDatasetNode({ + label: "TEST.DS", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: rootNode, + profile: globalMocks.profileOne, + }); + + await sharedUtils.initializeFileOpening(testNode, testNode.fullPath, true); + expect(globalMocks.mockShowTextDocument).toBeCalledWith(globalMocks.mockTextDocument, { preview: true }); + }); + + it("successfully handles text data sets that shouldn't be previewed", async () => { + const globalMocks = await createGlobalMocks(); + + jest.spyOn(vscode.workspace, "openTextDocument").mockResolvedValue(globalMocks.mockTextDocument as vscode.TextDocument); + + // Creating a test node + const rootNode = new ZoweDatasetNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session: globalMocks.session, + profile: globalMocks.profileOne, + }); + rootNode.contextValue = globals.DS_SESSION_CONTEXT; + const testNode = new ZoweDatasetNode({ + label: "TEST.DS", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: rootNode, + profile: globalMocks.profileOne, + }); + + await sharedUtils.initializeFileOpening(testNode, testNode.fullPath, false); + expect(globalMocks.mockShowTextDocument).toBeCalledWith(globalMocks.mockTextDocument, { preview: false }); + }); + + it("successfully handles binary USS files that should be re-downloaded", async () => { + const globalMocks = await createGlobalMocks(); + + jest.spyOn(vscode.workspace, "openTextDocument").mockRejectedValue("Test error!"); + jest.spyOn(Gui, "errorMessage").mockResolvedValue("Re-download"); + + // Creating a test node + const rootNode = new ZoweUSSNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session: globalMocks.session, + profile: globalMocks.profileOne, + }); + rootNode.contextValue = globals.USS_SESSION_CONTEXT; + const testNode = new ZoweUSSNode({ + label: "testFile", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: rootNode, + profile: globalMocks.profileOne, + }); + testNode.fullPath = "test/testFile"; + + await sharedUtils.initializeFileOpening(testNode, testNode.fullPath); + expect(globalMocks.mockExecuteCommand).toHaveBeenCalledWith("zowe.uss.openWithEncoding", testNode, { kind: "binary" }); + }); + + it("successfully handles text USS files that should be previewed", async () => { + const globalMocks = await createGlobalMocks(); + + jest.spyOn(vscode.workspace, "openTextDocument").mockResolvedValue(globalMocks.mockTextDocument as vscode.TextDocument); + + // Creating a test node + const rootNode = new ZoweUSSNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session: globalMocks.session, + profile: globalMocks.profileOne, + }); + rootNode.contextValue = globals.USS_SESSION_CONTEXT; + const testNode = new ZoweUSSNode({ + label: "testFile", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: rootNode, + profile: globalMocks.profileOne, + }); + testNode.fullPath = "test/testFile"; + + await sharedUtils.initializeFileOpening(testNode, testNode.fullPath, true); + expect(globalMocks.mockShowTextDocument).toBeCalledWith(globalMocks.mockTextDocument, { preview: true }); + }); + + it("successfully handles text USS files that shouldn't be previewed", async () => { + const globalMocks = await createGlobalMocks(); + + jest.spyOn(vscode.workspace, "openTextDocument").mockResolvedValue(globalMocks.mockTextDocument as vscode.TextDocument); + + // Creating a test node + const rootNode = new ZoweUSSNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session: globalMocks.session, + profile: globalMocks.profileOne, + }); + rootNode.contextValue = globals.USS_SESSION_CONTEXT; + const testNode = new ZoweUSSNode({ + label: "testFile", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: rootNode, + profile: globalMocks.profileOne, + }); + testNode.fullPath = "test/testFile"; + + await sharedUtils.initializeFileOpening(testNode, testNode.fullPath, false); + expect(globalMocks.mockShowTextDocument).toBeCalledWith(globalMocks.mockTextDocument, { preview: false }); + }); +}); diff --git a/packages/zowe-explorer/__tests__/__unit__/uss/ZoweUSSNode.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/uss/ZoweUSSNode.unit.test.ts index 03512b2315..83ba247d41 100644 --- a/packages/zowe-explorer/__tests__/__unit__/uss/ZoweUSSNode.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/uss/ZoweUSSNode.unit.test.ts @@ -1386,84 +1386,6 @@ describe("ZoweUSSNode Unit Tests - Function node.openedDocumentInstance()", () = }); }); -describe("ZoweUSSNode Unit Tests - Function node.initializeFileOpening()", () => { - it("Tests that node.initializeFileOpening() successfully handles binary files that should be re-downloaded", async () => { - const globalMocks = await createGlobalMocks(); - - jest.spyOn(vscode.workspace, "openTextDocument").mockRejectedValue("Test error!"); - jest.spyOn(Gui, "errorMessage").mockResolvedValue("Re-download"); - - // Creating a test node - const rootNode = new ZoweUSSNode({ - label: "root", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - session: globalMocks.session, - profile: globalMocks.profileOne, - }); - rootNode.contextValue = globals.USS_SESSION_CONTEXT; - const testNode = new ZoweUSSNode({ - label: globals.DS_PDS_CONTEXT, - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - parentNode: rootNode, - profile: globalMocks.profileOne, - }); - testNode.fullPath = "test/node"; - - await testNode.initializeFileOpening(testNode.fullPath); - expect(globalMocks.mockExecuteCommand).toHaveBeenCalledWith("zowe.uss.binary", testNode); - }); - - it("Tests that node.initializeFileOpening() successfully handles text files that should be previewed", async () => { - const globalMocks = await createGlobalMocks(); - - jest.spyOn(vscode.workspace, "openTextDocument").mockResolvedValue(globalMocks.mockTextDocument as vscode.TextDocument); - - // Creating a test node - const rootNode = new ZoweUSSNode({ - label: "root", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - session: globalMocks.session, - profile: globalMocks.profileOne, - }); - rootNode.contextValue = globals.USS_SESSION_CONTEXT; - const testNode = new ZoweUSSNode({ - label: globals.DS_PDS_CONTEXT, - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - parentNode: rootNode, - profile: globalMocks.profileOne, - }); - testNode.fullPath = "test/node"; - - await testNode.initializeFileOpening(testNode.fullPath, true); - expect(globalMocks.mockShowTextDocument).toBeCalledWith(globalMocks.mockTextDocument); - }); - - it("Tests that node.initializeFileOpening() successfully handles text files that shouldn't be previewed", async () => { - const globalMocks = await createGlobalMocks(); - - jest.spyOn(vscode.workspace, "openTextDocument").mockResolvedValue(globalMocks.mockTextDocument as vscode.TextDocument); - - // Creating a test node - const rootNode = new ZoweUSSNode({ - label: "root", - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - session: globalMocks.session, - profile: globalMocks.profileOne, - }); - rootNode.contextValue = globals.USS_SESSION_CONTEXT; - const testNode = new ZoweUSSNode({ - label: globals.DS_PDS_CONTEXT, - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - parentNode: rootNode, - profile: globalMocks.profileOne, - }); - testNode.fullPath = "test/node"; - - await testNode.initializeFileOpening(testNode.fullPath, false); - expect(globalMocks.mockShowTextDocument).toBeCalledWith(globalMocks.mockTextDocument, { preview: false }); - }); -}); - describe("ZoweUSSNode Unit Tests - Function node.pasteUssTree()", () => { function createIProfileFakeEncoding(): zowe.imperative.IProfileLoaded { return { diff --git a/packages/zowe-explorer/__tests__/__unit__/uss/init.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/uss/init.unit.test.ts index 831100acde..7c3f6d9660 100644 --- a/packages/zowe-explorer/__tests__/__unit__/uss/init.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/uss/init.unit.test.ts @@ -207,7 +207,7 @@ describe("Test src/dataset/extension", () => { }, { name: "zowe.uss.openWithEncoding", - mock: [{ spy: jest.spyOn(ussFileProvider, "openWithEncoding"), arg: [test.value] }], + mock: [{ spy: jest.spyOn(ussFileProvider, "openWithEncoding"), arg: [test.value, undefined] }], }, { name: "onDidChangeConfiguration", From bb4e5a881812e27f89b75710aae6ddf8f1649894 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Thu, 25 Apr 2024 16:30:50 -0400 Subject: [PATCH 3/5] Update changelog and improve coverage Signed-off-by: Timothy Johnson --- packages/zowe-explorer/CHANGELOG.md | 2 + .../__unit__/shared/utils.unit.test.ts | 47 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index 13da055955..5aba49bf76 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -8,6 +8,8 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen ### Bug fixes +- Fixed issue where data sets or members containing binary content cannot be opened. [#2696](https://github.com/zowe/zowe-explorer-vscode/issues/2696) + ## `2.15.4` ### New features and enhancements diff --git a/packages/zowe-explorer/__tests__/__unit__/shared/utils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/shared/utils.unit.test.ts index 751822c5e7..285e7704c4 100644 --- a/packages/zowe-explorer/__tests__/__unit__/shared/utils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/shared/utils.unit.test.ts @@ -1061,6 +1061,29 @@ describe("Shared utils unit tests - function initializeFileOpening", () => { expect(globalMocks.mockExecuteCommand).toHaveBeenCalledWith("zowe.ds.openWithEncoding", testNode, { kind: "binary" }); }); + it("successfully handles binary data sets that should be previewed", async () => { + const globalMocks = await createGlobalMocks(); + + // Creating a test node + const rootNode = new ZoweDatasetNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session: globalMocks.session, + profile: globalMocks.profileOne, + }); + rootNode.contextValue = globals.DS_SESSION_CONTEXT; + const testNode = new ZoweDatasetNode({ + label: "TEST.DS", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: rootNode, + profile: globalMocks.profileOne, + encoding: { kind: "binary" }, + }); + + await sharedUtils.initializeFileOpening(testNode, testNode.fullPath, true); + expect(globalMocks.mockExecuteCommand).toHaveBeenCalledWith("vscode.open", { path: "" }); + }); + it("successfully handles text data sets that should be previewed", async () => { const globalMocks = await createGlobalMocks(); @@ -1135,6 +1158,30 @@ describe("Shared utils unit tests - function initializeFileOpening", () => { expect(globalMocks.mockExecuteCommand).toHaveBeenCalledWith("zowe.uss.openWithEncoding", testNode, { kind: "binary" }); }); + it("successfully handles binary USS files that should be previewed", async () => { + const globalMocks = await createGlobalMocks(); + + // Creating a test node + const rootNode = new ZoweUSSNode({ + label: "root", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + session: globalMocks.session, + profile: globalMocks.profileOne, + }); + rootNode.contextValue = globals.USS_SESSION_CONTEXT; + const testNode = new ZoweUSSNode({ + label: "testFile", + collapsibleState: vscode.TreeItemCollapsibleState.None, + parentNode: rootNode, + profile: globalMocks.profileOne, + encoding: { kind: "binary" }, + }); + testNode.fullPath = "test/testFile"; + + await sharedUtils.initializeFileOpening(testNode, testNode.fullPath, true); + expect(globalMocks.mockExecuteCommand).toHaveBeenCalledWith("vscode.open", { path: testNode.fullPath }); + }); + it("successfully handles text USS files that should be previewed", async () => { const globalMocks = await createGlobalMocks(); From 5e193e66039a75ffce689b6c7e13069476dc20ec Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 May 2024 12:26:03 -0400 Subject: [PATCH 4/5] Add binary check when refreshing data sets Signed-off-by: Timothy Johnson --- .../__unit__/dataset/actions.unit.test.ts | 45 ++++++++++++------- .../i18n/sample/src/shared/utils.i18n.json | 5 ++- .../i18n/sample/src/uss/ZoweUSSNode.i18n.json | 3 -- packages/zowe-explorer/src/dataset/actions.ts | 7 +-- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/packages/zowe-explorer/__tests__/__unit__/dataset/actions.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/dataset/actions.unit.test.ts index c0d045976b..32bbfd7a0f 100644 --- a/packages/zowe-explorer/__tests__/__unit__/dataset/actions.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/dataset/actions.unit.test.ts @@ -71,6 +71,7 @@ function createGlobalMocks() { statusBarMsgSpy: null, mvsApi: null, mockShowWarningMessage: jest.fn(), + mockTextDocuments: [], concatChildNodes: jest.spyOn(sharedUtils, "concatChildNodes"), }; @@ -100,6 +101,7 @@ function createGlobalMocks() { Object.defineProperty(vscode.workspace, "openTextDocument", { value: jest.fn(), configurable: true }); Object.defineProperty(vscode.workspace, "getConfiguration", { value: jest.fn(), configurable: true }); Object.defineProperty(vscode.window, "showTextDocument", { value: jest.fn(), configurable: true }); + Object.defineProperty(vscode.workspace, "textDocuments", { value: newMocks.mockTextDocuments, configurable: true }); Object.defineProperty(vscode.window, "showQuickPick", { value: jest.fn(), configurable: true }); Object.defineProperty(vscode.window, "createQuickPick", { value: jest.fn(), configurable: true }); Object.defineProperty(vscode.commands, "executeCommand", { value: jest.fn(), configurable: true }); @@ -267,15 +269,16 @@ describe("Dataset Actions Unit Tests - Function refreshPS", () => { it("Checking common PS dataset refresh", async () => { globals.defineGlobals(""); - createGlobalMocks(); + const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocksShared(); const node = new ZoweDatasetNode({ label: "HLQ.TEST.AFILE7", collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: blockMocks.datasetSessionNode, }); + const documentFilePath = path.join(globals.DS_DIR, node.getSessionNode().label.toString(), node.label.toString()); - mocked(vscode.workspace.openTextDocument).mockResolvedValueOnce({ isDirty: true } as any); + globalMocks.mockTextDocuments.push({ fileName: documentFilePath, isDirty: true } as any); mocked(zowe.Download.dataSet).mockResolvedValueOnce({ success: true, commandResponse: null, @@ -290,27 +293,26 @@ describe("Dataset Actions Unit Tests - Function refreshPS", () => { blockMocks.zosmfSession, node.label, expect.objectContaining({ - file: path.join(globals.DS_DIR, node.getSessionNode().label.toString(), node.label.toString()), + file: documentFilePath, returnEtag: true, }) ); - expect(mocked(vscode.workspace.openTextDocument)).toBeCalledWith( - path.join(globals.DS_DIR, node.getSessionNode().label.toString(), node.label.toString()) - ); + expect(mocked(vscode.workspace.openTextDocument)).toBeCalledWith(documentFilePath); expect(mocked(vscode.window.showTextDocument)).toBeCalledTimes(2); expect(mocked(vscode.commands.executeCommand)).toBeCalledWith("workbench.action.closeActiveEditor"); }); it("Checking duplicate PS dataset refresh attempt", async () => { globals.defineGlobals(""); - createGlobalMocks(); + const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocksShared(); const node = new ZoweDatasetNode({ label: "HLQ.TEST.AFILE7", collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: blockMocks.datasetSessionNode, }); + const documentFilePath = path.join(globals.DS_DIR, node.getSessionNode().label.toString(), node.label.toString()); - mocked(vscode.workspace.openTextDocument).mockResolvedValueOnce({ isDirty: false } as any); + globalMocks.mockTextDocuments.push({ fileName: documentFilePath, isDirty: false } as any); mocked(zowe.Download.dataSet).mockResolvedValueOnce({ success: true, commandResponse: null, @@ -332,8 +334,9 @@ describe("Dataset Actions Unit Tests - Function refreshPS", () => { collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: blockMocks.datasetSessionNode, }); + const documentFilePath = path.join(globals.DS_DIR, node.getSessionNode().label.toString(), node.label.toString()); - mocked(vscode.workspace.openTextDocument).mockResolvedValueOnce({ isDirty: true } as any); + globalMocks.mockTextDocuments.push({ fileName: documentFilePath, isDirty: true } as any); mocked(zowe.Download.dataSet).mockRejectedValueOnce(Error("not found")); globalMocks.getContentsSpy.mockRejectedValueOnce(new Error("not found")); @@ -345,7 +348,7 @@ describe("Dataset Actions Unit Tests - Function refreshPS", () => { }); it("Checking failed attempt to refresh PDS Member", async () => { globals.defineGlobals(""); - createGlobalMocks(); + const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocksShared(); const parent = new ZoweDatasetNode({ label: "parent", @@ -353,8 +356,9 @@ describe("Dataset Actions Unit Tests - Function refreshPS", () => { parentNode: blockMocks.datasetSessionNode, }); const child = new ZoweDatasetNode({ label: "child", collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: parent }); + const documentFilePath = path.join(globals.DS_DIR, child.getSessionNode().label.toString(), child.label.toString()); - mocked(vscode.workspace.openTextDocument).mockResolvedValueOnce({ isDirty: true } as any); + globalMocks.mockTextDocuments.push({ fileName: documentFilePath, isDirty: true } as any); mocked(zowe.Download.dataSet).mockRejectedValueOnce(Error("")); await dsActions.refreshPS(child); @@ -371,16 +375,17 @@ describe("Dataset Actions Unit Tests - Function refreshPS", () => { }); it("Checking favorite empty PDS refresh", async () => { globals.defineGlobals(""); - createGlobalMocks(); + const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocksShared(); const node = new ZoweDatasetNode({ label: "HLQ.TEST.AFILE7", collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: blockMocks.datasetSessionNode, }); + const documentFilePath = path.join(globals.DS_DIR, node.getSessionNode().label.toString(), node.label.toString()); node.contextValue = globals.DS_PDS_CONTEXT + globals.FAV_SUFFIX; - mocked(vscode.workspace.openTextDocument).mockResolvedValueOnce({ isDirty: true } as any); + globalMocks.mockTextDocuments.push({ fileName: documentFilePath, isDirty: true } as any); mocked(zowe.Download.dataSet).mockResolvedValueOnce({ success: true, commandResponse: null, @@ -396,7 +401,7 @@ describe("Dataset Actions Unit Tests - Function refreshPS", () => { }); it("Checking favorite PDS Member refresh", async () => { globals.defineGlobals(""); - createGlobalMocks(); + const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocksShared(); const parent = new ZoweDatasetNode({ label: "parent", @@ -404,9 +409,14 @@ describe("Dataset Actions Unit Tests - Function refreshPS", () => { parentNode: blockMocks.datasetSessionNode, }); const child = new ZoweDatasetNode({ label: "child", collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: parent }); + const documentFilePath = path.join( + globals.DS_DIR, + child.getSessionNode().label.toString(), + parent.label.toString() + "(" + child.label.toString() + ")" + ); parent.contextValue = globals.DS_PDS_CONTEXT + globals.FAV_SUFFIX; - mocked(vscode.workspace.openTextDocument).mockResolvedValueOnce({ isDirty: true } as any); + globalMocks.mockTextDocuments.push({ fileName: documentFilePath, isDirty: true } as any); mocked(zowe.Download.dataSet).mockResolvedValueOnce({ success: true, commandResponse: null, @@ -422,7 +432,7 @@ describe("Dataset Actions Unit Tests - Function refreshPS", () => { }); it("Checking favorite PS refresh", async () => { globals.defineGlobals(""); - createGlobalMocks(); + const globalMocks = createGlobalMocks(); const blockMocks = createBlockMocksShared(); const parent = new ZoweDatasetNode({ label: "parent", @@ -430,9 +440,10 @@ describe("Dataset Actions Unit Tests - Function refreshPS", () => { parentNode: blockMocks.datasetSessionNode, }); const child = new ZoweDatasetNode({ label: "child", collapsibleState: vscode.TreeItemCollapsibleState.None, parentNode: parent }); + const documentFilePath = path.join(globals.DS_DIR, child.getSessionNode().label.toString(), child.label.toString()); child.contextValue = globals.DS_FAV_CONTEXT; - mocked(vscode.workspace.openTextDocument).mockResolvedValueOnce({ isDirty: true } as any); + globalMocks.mockTextDocuments.push({ fileName: documentFilePath, isDirty: true } as any); mocked(zowe.Download.dataSet).mockResolvedValueOnce({ success: true, commandResponse: null, diff --git a/packages/zowe-explorer/i18n/sample/src/shared/utils.i18n.json b/packages/zowe-explorer/i18n/sample/src/shared/utils.i18n.json index 832df65df5..a79a1054f9 100644 --- a/packages/zowe-explorer/i18n/sample/src/shared/utils.i18n.json +++ b/packages/zowe-explorer/i18n/sample/src/shared/utils.i18n.json @@ -27,5 +27,8 @@ "zowe.shared.utils.promptForEncoding.tagged.description": "USS file tag", "zowe.shared.utils.promptForEncoding.qp.title": "Choose encoding for {0}", "zowe.shared.utils.promptForEncoding.qp.placeHolder": "Current encoding is {0}", - "zowe.shared.utils.promptForEncoding.input.placeHolder": "Enter a codepage (e.g., 1047, IBM-1047)" + "zowe.shared.utils.promptForEncoding.input.placeHolder": "Enter a codepage (e.g., 1047, IBM-1047)", + "zowe.shared.utils.initializeFileOpening.yesButton": "Re-download", + "zowe.shared.utils.initializeFileOpening.noButton": "Cancel", + "zowe.shared.utils.initializeFileOpening.message": "Failed to open file as text. Re-download file as binary?" } diff --git a/packages/zowe-explorer/i18n/sample/src/uss/ZoweUSSNode.i18n.json b/packages/zowe-explorer/i18n/sample/src/uss/ZoweUSSNode.i18n.json index 12ae0c3133..692ba86a65 100644 --- a/packages/zowe-explorer/i18n/sample/src/uss/ZoweUSSNode.i18n.json +++ b/packages/zowe-explorer/i18n/sample/src/uss/ZoweUSSNode.i18n.json @@ -15,9 +15,6 @@ "refreshUSS.error.notFound": "not found", "refreshUSS.file1": "Unable to find file: ", "refreshUSS.file2": " was probably deleted.", - "openUSS.log.info.failedToOpenAsText.yes": "Re-download", - "openUSS.log.info.failedToOpenAsText.no": "Cancel", - "openUSS.log.info.failedToOpenAsText": "Failed to open file as text. Re-download file as binary?", "paste.missingApis": "Required API functions for pasting (fileList, copy and/or putContent) were not found.", "uploadFile.putContents": "Uploading USS files...", "copyUssFile.error": "Error uploading files" diff --git a/packages/zowe-explorer/src/dataset/actions.ts b/packages/zowe-explorer/src/dataset/actions.ts index 53da0c8482..750e104145 100644 --- a/packages/zowe-explorer/src/dataset/actions.ts +++ b/packages/zowe-explorer/src/dataset/actions.ts @@ -26,6 +26,7 @@ import { JOB_SUBMIT_DIALOG_OPTS, getDefaultUri, uploadContent, + initializeFileOpening, } from "../shared/utils"; import { ZoweExplorerApiRegister } from "../ZoweExplorerApiRegister"; import { Profiles } from "../Profiles"; @@ -1217,10 +1218,10 @@ export async function refreshPS(node: api.IZoweDatasetTreeNode): Promise { }); node.setEtag(response.apiResponse.etag); - const document = await vscode.workspace.openTextDocument(documentFilePath); - api.Gui.showTextDocument(document, { preview: false }); + await initializeFileOpening(node, documentFilePath, false); + const document = vscode.workspace.textDocuments.find((doc) => doc.fileName === documentFilePath); // if there are unsaved changes, vscode won't automatically display the updates, so close and reopen - if (document.isDirty) { + if (document?.isDirty) { await vscode.commands.executeCommand("workbench.action.closeActiveEditor"); api.Gui.showTextDocument(document, { preview: false }); } From 46f5dc0c361ea91125c824342cf574b0d1cb5baf Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 May 2024 13:11:43 -0400 Subject: [PATCH 5/5] Use nullish coalescing assignment operator Signed-off-by: Timothy Johnson --- packages/zowe-explorer/src/dataset/DatasetTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zowe-explorer/src/dataset/DatasetTree.ts b/packages/zowe-explorer/src/dataset/DatasetTree.ts index b3ebc8c694..ea2c023fe6 100644 --- a/packages/zowe-explorer/src/dataset/DatasetTree.ts +++ b/packages/zowe-explorer/src/dataset/DatasetTree.ts @@ -1524,7 +1524,7 @@ export class DatasetTree extends ZoweTreeProvider implements IZoweTree { - encoding = encoding ?? (await promptForEncoding(node)); + encoding ??= await promptForEncoding(node); if (encoding !== undefined) { node.setEncoding(encoding); await node.openDs(true, false, this);