diff --git a/examples/test/specs/download/DownloadSpreadsheetListReport.test.js b/examples/test/specs/download/DownloadSpreadsheetListReport.test.js index 3a6a3815..a35dea39 100644 --- a/examples/test/specs/download/DownloadSpreadsheetListReport.test.js +++ b/examples/test/specs/download/DownloadSpreadsheetListReport.test.js @@ -2,6 +2,18 @@ const path = require("path"); const fs = require("fs"); const XLSX = require("xlsx"); const Base = require("./../Objects/Base"); +const { wdi5 } = require("wdio-ui5-service"); + +// Test Constants +const TEST_CONSTANTS = { + EXPECTED_FILE_NAME: "Orders12.xlsx", + DOWNLOAD_TIMEOUT: 20000, + EXPECTED_ORDER_NUMBER: "2", + EXPECTED_FIELDS: { + ID: "ID[ID]", + ORDER_NUMBER: "Order Number[OrderNo]" + } +}; describe("Download Spreadsheet List Report", () => { let downloadDir; @@ -9,8 +21,7 @@ describe("Download Spreadsheet List Report", () => { before(async () => { BaseClass = new Base(); scenario = global.scenario; - // If you need it globally, you can set it on browser.config, or just reuse the same path logic as in wdio.conf.js. - downloadDir = path.resolve(__dirname, "../../downloads"); // Adjust the relative path if needed + downloadDir = path.resolve(__dirname, "../../downloads"); }); it("should trigger download button", async () => { @@ -30,21 +41,30 @@ describe("Download Spreadsheet List Report", () => { }); it("Download spreadsheet and verify content", async () => { - const expectedFileName = "Orders12.xlsx"; - - // Wait until the specific file is downloaded - await browser.waitUntil( - () => { - const files = fs.readdirSync(downloadDir); - return files.includes(expectedFileName); - }, - { - timeout: 20000, - timeoutMsg: `Expected ${expectedFileName} to be downloaded within 20s` - } - ); + const expectedFileName = TEST_CONSTANTS.EXPECTED_FILE_NAME; + + try { + await browser.waitUntil( + async () => { + try { + const files = await fs.promises.readdir(downloadDir); + return files.includes(expectedFileName); + } catch (error) { + console.warn(`Error reading directory: ${error.message}`); + return false; + } + }, + { + timeout: TEST_CONSTANTS.DOWNLOAD_TIMEOUT, + timeoutMsg: `Expected ${expectedFileName} to be downloaded within ${TEST_CONSTANTS.DOWNLOAD_TIMEOUT}ms`, + interval: 500 // Check every 500ms + } + ); + } catch (error) { + throw new Error(`Download failed: ${error.message}`); + } - const filePath = path.join(downloadDir, expectedFileName); + const filePath = path.join(downloadDir, TEST_CONSTANTS.EXPECTED_FILE_NAME); expect(fs.existsSync(filePath)).toBeTruthy(); const workbook = XLSX.readFile(filePath); @@ -55,8 +75,8 @@ describe("Download Spreadsheet List Report", () => { expect(data.length).toBeGreaterThan(0); if (data[0]) { - expect(data[0]["ID[ID]"]).toBeDefined(); - expect(data[0]["Order Number[OrderNo]"]).toBe("2"); + expect(data[0][TEST_CONSTANTS.EXPECTED_FIELDS.ID]).toBeDefined(); + expect(data[0][TEST_CONSTANTS.EXPECTED_FIELDS.ORDER_NUMBER]).toBe(TEST_CONSTANTS.EXPECTED_ORDER_NUMBER); } }); diff --git a/examples/test/specs/updatefreestyle/DownloadAndUpdateSpreadsheetListReport.test.js b/examples/test/specs/updatefreestyle/DownloadAndUpdateSpreadsheetListReport.test.js index f0356844..412352b6 100644 --- a/examples/test/specs/updatefreestyle/DownloadAndUpdateSpreadsheetListReport.test.js +++ b/examples/test/specs/updatefreestyle/DownloadAndUpdateSpreadsheetListReport.test.js @@ -2,7 +2,42 @@ const path = require("path"); const fs = require("fs"); const XLSX = require("xlsx"); const Base = require("./../Objects/Base"); -const { wdi5 } = require("wdio-ui5-service") +const { wdi5 } = require("wdio-ui5-service"); + +// Test Constants +const TEST_CONSTANTS = { + FILE: { + NAME: "Orders.xlsx", + TIMEOUT: 20000 + }, + UPDATES: { + ORDER_NUMBER: 100, + USER_ID: "Customer 123" + }, + DOWNLOAD: { + WAIT_AFTER_UPLOAD: 4000, + TIMEOUT: 20000, + WAIT_AFTER_DOWNLOAD: 4000, + WAIT_AFTER_UPLOAD_AGAIN: 4000, + WAIT_AFTER_UPLOAD_AGAIN_2: 4000, + WAIT_AFTER_UPLOAD_AGAIN_3: 4000 + }, + SELECTORS: { + DOWNLOAD_BUTTON: ".*downloadButtonWithoutDialog$", + UPLOAD_DIALOG: { + BUTTON_ID: "container-ordersv4freestyle---OrdersTable--updatedButtonCode4", + UPLOADER_ID: "__uploader1", + UPLOAD_BUTTON_TEXT: "Upload" + } + }, + API: { + BASE_URL: "http://localhost:4004/odata/v4/orders/Orders", + ROW_1_ID: "64e718c9-ff99-47f1-8ca3-950c850777d4", + ROW_2_ID: "64e718c9-ff99-47f1-8ca3-950c850777d5", + ROW3_ID: "64e718c9-ff99-47f1-8ca3-950c850777d6", + ROW4_ID: "64e718c9-ff99-47f1-8ca3-950c850777d7" + } +}; describe("Download Spreadsheet List Report", () => { let downloadDir; @@ -12,36 +47,32 @@ describe("Download Spreadsheet List Report", () => { scenario = global.scenario; downloadDir = path.resolve(__dirname, "../../downloads"); - await wdi5.goTo("#/orders") + await wdi5.goTo("#/orders"); }); it("should trigger download button", async () => { - // Trigger the download const object = await browser.asControl({ forceSelect: true, selector: { - id: new RegExp(".*downloadButtonWithoutDialog$") + id: new RegExp(TEST_CONSTANTS.SELECTORS.DOWNLOAD_BUTTON) } }); await object.press(); }); it("Download spreadsheet and verify content", async () => { - const expectedFileName = "Orders.xlsx"; - - // Wait until the specific file is downloaded await browser.waitUntil( () => { const files = fs.readdirSync(downloadDir); - return files.includes(expectedFileName); + return files.includes(TEST_CONSTANTS.FILE.NAME); }, { - timeout: 20000, - timeoutMsg: `Expected ${expectedFileName} to be downloaded within 20s` + timeout: TEST_CONSTANTS.FILE.TIMEOUT, + timeoutMsg: `Expected ${TEST_CONSTANTS.FILE.NAME} to be downloaded within ${TEST_CONSTANTS.FILE.TIMEOUT}ms` } ); - this.filePath = path.join(downloadDir, expectedFileName); + this.filePath = path.join(downloadDir, TEST_CONSTANTS.FILE.NAME); expect(fs.existsSync(this.filePath)).toBeTruthy(); const workbook = XLSX.readFile(this.filePath); @@ -57,9 +88,9 @@ describe("Download Spreadsheet List Report", () => { expect(row["IsActiveEntity[IsActiveEntity]"]).toBe(true); }); // change first row of Order Number[OrderNo] to 100 - data[0]["Order Number[OrderNo]"] = 100; + data[0]["Order Number[OrderNo]"] = TEST_CONSTANTS.UPDATES.ORDER_NUMBER; // change second row of Benutzer-ID[buyer] to "Customer 123" - data[1]["User ID[buyer]"] = "Customer 123"; + data[1]["User ID[buyer]"] = TEST_CONSTANTS.UPDATES.USER_ID; // save the file const workbookNew = XLSX.utils.book_new(); @@ -69,8 +100,7 @@ describe("Download Spreadsheet List Report", () => { }); it("upload file", async () => { - // Open upload dialog - await BaseClass.pressById("container-ordersv4freestyle---OrdersTable--updatedButtonCode4"); + await BaseClass.pressById(TEST_CONSTANTS.SELECTORS.UPLOAD_DIALOG.BUTTON_ID); const spreadsheetUploadDialog = await browser.asControl({ selector: { controlType: "sap.m.Dialog", @@ -84,7 +114,7 @@ describe("Download Spreadsheet List Report", () => { // Remove block layer if present try { - browser.execute(function () { + await browser.execute(() => { const blockLayerPopup = document.getElementById("sap-ui-blocklayer-popup"); if (blockLayerPopup) { blockLayerPopup.remove(); @@ -94,27 +124,32 @@ describe("Download Spreadsheet List Report", () => { console.log("sap-ui-blocklayer-popup removed"); } - // Upload the file + // Fix: Correct file upload process const uploader = await browser.asControl({ forceSelect: true, selector: { interaction: "root", controlType: "sap.ui.unified.FileUploader", - id: "__uploader1" + id: TEST_CONSTANTS.SELECTORS.UPLOAD_DIALOG.UPLOADER_ID } }); - const remoteFilePath = await browser.uploadFile(this.filePath); - const $uploader = await uploader.getWebElement(); - const $fileInput = await $uploader.$("input[type=file]"); - await $fileInput.setValue(remoteFilePath); + + // Fix: Use the correct uploadFile syntax + await browser.execute( + function (filePath) { + document.querySelector('input[type=file]').style.display = 'block'; + } + ); + + const input = await $('input[type=file]'); + await input.setValue(this.filePath); - // Click upload button await browser .asControl({ selector: { controlType: "sap.m.Button", properties: { - text: "Upload" + text: TEST_CONSTANTS.SELECTORS.UPLOAD_DIALOG.UPLOAD_BUTTON_TEXT } } }) @@ -124,19 +159,186 @@ describe("Download Spreadsheet List Report", () => { it("check if the file is uploaded", async () => { await BaseClass.dummyWait(4000); - // check if the file is uploaded - const row1 = await fetch("http://localhost:4004/odata/v4/orders/Orders(ID=64e718c9-ff99-47f1-8ca3-950c850777d4,IsActiveEntity=true)"); - const row2 = await fetch("http://localhost:4004/odata/v4/orders/Orders(ID=64e718c9-ff99-47f1-8ca3-950c850777d5,IsActiveEntity=true)"); + const row1 = await fetch(`${TEST_CONSTANTS.API.BASE_URL}(ID=${TEST_CONSTANTS.API.ROW_1_ID},IsActiveEntity=true)`); + const row2 = await fetch(`${TEST_CONSTANTS.API.BASE_URL}(ID=${TEST_CONSTANTS.API.ROW_2_ID},IsActiveEntity=true)`); const row1Data = await row1.json(); const row2Data = await row2.json(); - expect(row1Data.OrderNo).toBe("100"); - expect(row2Data.buyer).toBe("Customer 123"); + + expect(row1Data.OrderNo).toBe(TEST_CONSTANTS.UPDATES.ORDER_NUMBER.toString()); + expect(row2Data.buyer).toBe(TEST_CONSTANTS.UPDATES.USER_ID); + }); + + it("set entity to draft", async () => { + try { + // Make a POST request to create a draft version + const url = `${TEST_CONSTANTS.API.BASE_URL}(ID=${TEST_CONSTANTS.API.ROW3_ID},IsActiveEntity=true)/OrdersService.draftEdit`; + console.log(url); + const response = await fetch(url, { + method: "POST", + headers: { + 'Accept': 'application/json;odata.metadata=minimal;IEEE754Compatible=true', + 'Content-Type': 'application/json;charset=UTF-8;IEEE754Compatible=true', + 'Accept-Language': 'en', + 'Prefer': 'handling=strict' + }, + body: JSON.stringify({ + PreserveChanges: true + }) + }); + + if (!response.ok) { + throw new Error(`Failed to set entity to draft: ${response.status} ${response.statusText}`); + } + + await BaseClass.dummyWait(1000); + } catch (error) { + throw new Error(`Failed to set entity to draft: ${error.message}`); + } + }); + + it("delete Orders.xlsx file", async () => { + try { + const filePath = path.join(downloadDir, TEST_CONSTANTS.FILE.NAME); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } + expect(fs.existsSync(filePath)).toBeFalsy(); + } catch (error) { + throw new Error(`Failed to delete file: ${error.message}`); + } + }); + + it("should trigger download button again", async () => { + try { + const object = await browser.asControl({ + forceSelect: true, + selector: { + id: new RegExp(TEST_CONSTANTS.SELECTORS.DOWNLOAD_BUTTON) + } + }); + await object.press(); + } catch (error) { + throw new Error(`Failed to trigger download: ${error.message}`); + } + }); + + it("change excel file and save", async () => { + try { + // Wait for download and verify file exists + await browser.waitUntil( + () => { + const files = fs.readdirSync(downloadDir); + return files.includes(TEST_CONSTANTS.FILE.NAME); + }, + { + timeout: TEST_CONSTANTS.DOWNLOAD.TIMEOUT, + timeoutMsg: `Expected ${TEST_CONSTANTS.FILE.NAME} to be downloaded within ${TEST_CONSTANTS.DOWNLOAD.TIMEOUT}ms` + } + ); + + this.filePath = path.join(downloadDir, TEST_CONSTANTS.FILE.NAME); + const workbook = XLSX.readFile(this.filePath); + const firstSheet = workbook.Sheets[workbook.SheetNames[0]]; + const data = XLSX.utils.sheet_to_json(firstSheet); + + // Update the draft and active entities + data.forEach((row) => { + if (row["ID[ID]"] === TEST_CONSTANTS.API.ROW3_ID) { + row["Order Number[OrderNo]"] = "999"; + } + if (row["ID[ID]"] === TEST_CONSTANTS.API.ROW4_ID) { + row["Order Number[OrderNo]"] = "888"; + } + }); + + // Save updated file + const workbookNew = XLSX.utils.book_new(); + const worksheetNew = XLSX.utils.json_to_sheet(data); + XLSX.utils.book_append_sheet(workbookNew, worksheetNew, TEST_CONSTANTS.FILE.SHEET_NAME); + XLSX.writeFile(workbookNew, this.filePath); + } catch (error) { + throw new Error(`Failed to update excel file: ${error.message}`); + } + }); + + it("upload file again", async () => { + try { + await BaseClass.pressById(TEST_CONSTANTS.SELECTORS.UPLOAD_DIALOG.BUTTON_ID); + const spreadsheetUploadDialog = await browser.asControl({ + selector: { + controlType: "sap.m.Dialog", + properties: { + contentWidth: "40vw" + }, + searchOpenDialogs: true + } + }); + expect(spreadsheetUploadDialog.isOpen()).toBeTruthy(); + + // Remove block layer if present + await browser.execute(() => { + const blockLayerPopup = document.getElementById("sap-ui-blocklayer-popup"); + if (blockLayerPopup) { + blockLayerPopup.remove(); + } + }); + + // Upload the file + const uploader = await browser.asControl({ + forceSelect: true, + selector: { + interaction: "root", + controlType: "sap.ui.unified.FileUploader", + id: TEST_CONSTANTS.SELECTORS.FILE_UPLOADER + } + }); + const remoteFilePath = await browser.uploadFile(this.filePath); + const $uploader = await uploader.getWebElement(); + const $fileInput = await $uploader.$("input[type=file]"); + await $fileInput.setValue(remoteFilePath); + + // Click upload button + await browser + .asControl({ + selector: { + controlType: "sap.m.Button", + properties: { + text: TEST_CONSTANTS.SELECTORS.UPLOAD_DIALOG.UPLOAD_BUTTON_TEXT + } + }, + forceSelect: true + }) + .press(); + } catch (error) { + throw new Error(`Failed to upload file: ${error.message}`); + } + }); + + it("check if the file is correctly uploaded", async () => { + try { + await BaseClass.dummyWait(TEST_CONSTANTS.DOWNLOAD.WAIT_AFTER_UPLOAD); + + const urlRow3 = `${TEST_CONSTANTS.API.BASE_URL}(ID=${TEST_CONSTANTS.API.ROW3_ID},IsActiveEntity=false)`; + const urlRow4 = `${TEST_CONSTANTS.API.BASE_URL}(ID=${TEST_CONSTANTS.API.ROW4_ID},IsActiveEntity=true)`; + + // Check both updated rows + const [row3Draft, row4Active] = await Promise.all([ + fetch(urlRow3), + fetch(urlRow4) + ]); + + const [row3Data, row4Data] = await Promise.all([row3Draft.json(), row4Active.json()]); + + expect(row3Data.OrderNo).toBe("999"); + expect(row4Data.OrderNo).toBe("888"); + } catch (error) { + throw new Error(`Failed to verify uploaded data: ${error.message}`); + } }); after(async () => { - // Clean up - only delete test files - const testFiles = ['Orders.xlsx']; - testFiles.forEach(file => { + const testFiles = [TEST_CONSTANTS.FILE.NAME]; + testFiles.forEach((file) => { const filePath = path.join(downloadDir, file); if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); diff --git a/packages/ui5-cc-spreadsheetimporter/src/Component.ts b/packages/ui5-cc-spreadsheetimporter/src/Component.ts index 74e05f49..0ba68bf2 100644 --- a/packages/ui5-cc-spreadsheetimporter/src/Component.ts +++ b/packages/ui5-cc-spreadsheetimporter/src/Component.ts @@ -11,6 +11,7 @@ import Button from "sap/m/Button"; import Controller from "sap/ui/core/mvc/Controller"; import View from "sap/ui/core/mvc/View"; import Util from "./controller/Util"; +import { DefaultConfigs } from "./enums"; /** * @namespace cc.spreadsheetimporter.XXXnamespaceXXX */ @@ -24,6 +25,8 @@ export default class Component extends UIComponent { constructor(idOrSettings?: string | $ComponentSettings); constructor(id?: string, settings?: $ComponentSettings); constructor(id?: string, settings?: $ComponentSettings) { + id.deepDownloadConfig = Util.mergeDeepDownloadConfig(DefaultConfigs.DeepDownload, id.deepDownloadConfig); + id.updateConfig = Util.mergeUpdateConfig(DefaultConfigs.Update, id.updateConfig); this.settingsFromContainer = id; super(id, settings); } @@ -74,7 +77,7 @@ export default class Component extends UIComponent { bindingCustom: { type: "object" }, showDownloadButton: { type: "boolean", defaultValue: false }, deepDownloadConfig: { type: "object", defaultValue: {} }, - updateConfig: { type: "object", defaultValue: {} }, + updateConfig: { type: "object", defaultValue: {} } //Pro Configurations }, aggregations: { @@ -180,24 +183,10 @@ export default class Component extends UIComponent { this.setShowOptions(true); } - const defaultDeepDownloadConfig: DeepDownloadConfig = { - addKeysToExport: false, - setDraftStatus: true, - deepExport: false, - deepLevel: 1, - showOptions: true, - columns: [] - }; - - const defaultUpdateConfig: UpdateConfig = { - fullUpdate: false, - columns: [] - }; - - const mergedDeepDownloadConfig = Util.mergeDeepDownloadConfig(defaultDeepDownloadConfig, compData.deepDownloadConfig) + const mergedDeepDownloadConfig = Util.mergeDeepDownloadConfig(DefaultConfigs.DeepDownload, compData.deepDownloadConfig) this.setDeepDownloadConfig(mergedDeepDownloadConfig); - const mergedUpdateConfig = Util.mergeUpdateConfig(defaultUpdateConfig, compData.updateConfig) + const mergedUpdateConfig = Util.mergeUpdateConfig(DefaultConfigs.Update, compData.updateConfig) this.setUpdateConfig(mergedUpdateConfig); // // we could create a device model and use it @@ -282,7 +271,7 @@ export default class Component extends UIComponent { await this.spreadsheetUpload.initializeComponent(); Log.debug("triggerDownloadSpreadsheet", undefined, "SpreadsheetUpload: Component"); if (deepDownloadConfig) { - this.setDeepDownloadConfig(deepDownloadConfig); + this.setDeepDownloadConfig(Util.mergeDeepDownloadConfig(this.getDeepDownloadConfig() as DeepDownloadConfig, deepDownloadConfig)); } this.spreadsheetUpload.triggerDownloadSpreadsheet(); } diff --git a/packages/ui5-cc-spreadsheetimporter/src/controller/SpreadsheetUpload.ts b/packages/ui5-cc-spreadsheetimporter/src/controller/SpreadsheetUpload.ts index 05359f8b..7306c71a 100644 --- a/packages/ui5-cc-spreadsheetimporter/src/controller/SpreadsheetUpload.ts +++ b/packages/ui5-cc-spreadsheetimporter/src/controller/SpreadsheetUpload.ts @@ -1,7 +1,7 @@ import ManagedObject from "sap/ui/base/ManagedObject"; import Component from "../Component"; import XMLView from "sap/ui/core/mvc/XMLView"; -import { Messages, ListObject, ComponentData } from "../types"; +import { Messages, ListObject, ComponentData, DeepDownloadConfig } from "../types"; import ResourceModel from "sap/ui/model/resource/ResourceModel"; import ResourceBundle from "sap/base/i18n/ResourceBundle"; import OData from "./odata/OData"; @@ -320,6 +320,9 @@ export default class SpreadsheetUpload extends ManagedObject { if (options.hasOwnProperty("updateConfig")) { this.component.setUpdateConfig(options.updateConfig); } + if (options.hasOwnProperty("deepDownloadConfig")) { + this.component.setDeepDownloadConfig(Util.mergeDeepDownloadConfig(this.component.getDeepDownloadConfig() as DeepDownloadConfig, options.deepDownloadConfig)); + } // Special case for showOptions if (options.availableOptions && options.availableOptions.length > 0) { diff --git a/packages/ui5-cc-spreadsheetimporter/src/enums.ts b/packages/ui5-cc-spreadsheetimporter/src/enums.ts index 76f0f8b4..02776fb0 100644 --- a/packages/ui5-cc-spreadsheetimporter/src/enums.ts +++ b/packages/ui5-cc-spreadsheetimporter/src/enums.ts @@ -111,3 +111,18 @@ export enum Action { Update = "UPDATE", Delete = "DELETE" } + +export const DefaultConfigs = { + DeepDownload: { + addKeysToExport: false, + setDraftStatus: true, + deepExport: false, + deepLevel: 0, + showOptions: true, + columns: [] + }, + Update: { + fullUpdate: false, + columns: [] + } +} as const;