diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index e3e84e53..90e3ac9b 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -38,7 +38,7 @@ jobs: id: generate-token uses: actions/create-github-app-token@v3 with: - app-id: ${{ inputs.app_id }} + client-id: ${{ inputs.app_id }} private-key: ${{ secrets.app_secret }} repositories: ab_service_web diff --git a/src/plugins/index.js b/src/plugins/index.js index c5351263..a5631cac 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -1,3 +1,7 @@ +import CsvExporterEditor from "./web_view_csvExporter/FNAbviewcsvexporterEditor.js"; +import CsvExporterProperties from "./web_view_csvExporter/FNAbviewcsvexporter.js"; +import CsvImporterEditor from "./web_view_csvImporter/FNAbviewcsvimporterEditor.js"; +import CsvImporterProperties from "./web_view_csvImporter/FNAbviewcsvimporter.js"; import viewCarouselEditor from "./web_view_carousel/FNAbviewcarouselEditor.js"; import viewCarouselProperties from "./web_view_carousel/FNAbviewcarousel.js"; import viewCommentEditor from "./web_view_comment/FNAbviewcommentEditor.js"; @@ -31,6 +35,10 @@ import viewTextEditor from "./web_view_text/FNAbviewtextEditor.js"; import viewTextProperties from "./web_view_text/FNAbviewtext.js"; const AllPlugins = [ + CsvExporterEditor, + CsvExporterProperties, + CsvImporterEditor, + CsvImporterProperties, viewCarouselEditor, viewCarouselProperties, viewCommentEditor, diff --git a/src/rootPages/Designer/properties/views/ABViewCSVExporter.js b/src/plugins/web_view_csvExporter/FNAbviewcsvexporter.js similarity index 95% rename from src/rootPages/Designer/properties/views/ABViewCSVExporter.js rename to src/plugins/web_view_csvExporter/FNAbviewcsvexporter.js index f37e75ab..666157c3 100644 --- a/src/rootPages/Designer/properties/views/ABViewCSVExporter.js +++ b/src/plugins/web_view_csvExporter/FNAbviewcsvexporter.js @@ -1,18 +1,25 @@ -/* - * ABViewCSVExporter - * A Property manager for our ABViewCSVExporter widget - */ - -import FViewClass from "./ABView"; - -export default function (AB) { +// FNAbviewcsvexporter Properties +// A properties side import for an ABView. +// +export default function FNAbviewcsvexporterProperties({ + AB, + ABViewPropertiesPlugin, +}) { const BASE_ID = "properties_abview_csvexporter"; - const ABViewClassProperty = FViewClass(AB); const uiConfig = AB.Config.uiSettings(); - const L = ABViewClassProperty.L(); + const L = AB.Label(); + + return class ABAbviewcsvexporterProperties extends ABViewPropertiesPlugin { + static getPluginKey() { + return this.key; + } + + static getPluginType() { + return "properties-view"; + // properties-view : will display in the properties panel of the ABDesigner + } - class ABViewCSVExporterProperty extends ABViewClassProperty { constructor(baseID) { super(baseID ?? BASE_ID, { datacollection: "", @@ -22,6 +29,7 @@ export default function (AB) { width: "", buttonFilter: "", fields: "", + dataviewID: "", }); this.AB = AB; @@ -340,7 +348,5 @@ export default function (AB) { ViewClass() { return super._ViewClass("csvExporter"); } - } - - return ABViewCSVExporterProperty; + }; } diff --git a/src/plugins/web_view_csvExporter/FNAbviewcsvexporterEditor.js b/src/plugins/web_view_csvExporter/FNAbviewcsvexporterEditor.js new file mode 100644 index 00000000..513ea8ba --- /dev/null +++ b/src/plugins/web_view_csvExporter/FNAbviewcsvexporterEditor.js @@ -0,0 +1,50 @@ +// FNAbviewcsvexporter Editor +// An Editor wrapper for the ABView Component. +// The Editor is displayed in the ABDesigner as a view is worked on. +// The Editor allows a widget to be moved and placed on the canvas. +// +export default function FNAbviewcsvexporterEditor({ ABViewEditorPlugin }) { + return class ABAbviewcsvexporterEditor extends ABViewEditorPlugin { + static getPluginKey() { + return this.key; + } + + /** + * @method getPluginType + * return the plugin type for this editor. + * plugin types are how our ClassManager knows how to store + * the plugin. + * @return {string} plugin type + */ + static getPluginType() { + return "editor-view"; + // editor-view : will display in the editor panel of the ABDesigner + } + + static get key() { + return "csvExporter"; + } + + constructor(view, base = "interface_editor_csvExporter") { + // base: {string} unique base id reference + super(view, base); + } + + ui() { + return super.ui(); + } + + async init(AB) { + this.AB = AB; + await super.init(AB); + } + + detatch() { + super.detatch(); + } + + onShow() { + super.onShow(); + } + }; +} diff --git a/src/plugins/web_view_csvImporter/CSVImporter.js b/src/plugins/web_view_csvImporter/CSVImporter.js new file mode 100644 index 00000000..b8847c7e --- /dev/null +++ b/src/plugins/web_view_csvImporter/CSVImporter.js @@ -0,0 +1,155 @@ +const FieldTypeTool = require("./FieldTypeTool"); + +module.exports = function FCSVImporter({ AB }) { + return class CSVImporter { + constructor(fileReader = FileReader) { + this._AB = AB; + this._FileReader = fileReader; + } + + L(...params) { + return this._AB.Multilingual.labelPlugin("ABDesigner", ...params); + } + + getSeparateItems() { + return [ + { id: ",", value: this.L("Comma (,)") }, + { + id: "\t", + value: this.L("Tab (      )"), + }, + { id: ";", value: this.L("Semicolon (;)") }, + { id: "s", value: this.L("Space ( )") }, + ]; + } + + /** + * @method validateFile + * Validate file extension + * + * @param {*} fileInfo - https://docs.webix.com/api__ui.uploader_onbeforefileadd_event.html + * + * @return {boolean} + */ + validateFile(fileInfo) { + if (!fileInfo || !fileInfo.file || !fileInfo.file.type) return false; + + // validate file type + let extensionType = fileInfo.file.type.toLowerCase(); + if ( + extensionType == "text/csv" || + extensionType == "application/vnd.ms-excel" + ) { + return true; + } else { + return false; + } + } + + /** + * @method getDataRows + * Pull data rows from the CSV file + * + * @param {Object} fileInfo - https://docs.webix.com/api__ui.uploader_onbeforefileadd_event.html + * @param {string} separatedBy + * + * @return {Promise} -[ + * ["Value 1.1", "Value 1.2", "Value 1.3"], + * ["Value 2.1", "Value 2.2", "Value 2.3"], + * ] + */ + async getDataRows(fileInfo, separatedBy) { + if (!this.validateFile(fileInfo)) + return Promise.reject(this.L(".fileInfo parameter is invalid")); + + return new Promise((resolve, reject) => { + // read CSV file + let reader = new this._FileReader(); + reader.onload = (e) => { + const result = this.convertToArray(reader.result, separatedBy); + + resolve(result); + }; + reader.readAsText(fileInfo.file); + }); + } + + /** + * @method convertToArray + * Pull data rows from the CSV file + * + * @param {string} text + * @param {string} separatedBy + * + * @return {Promise} -[ + * ["Value 1.1", "Value 1.2", "Value 1.3"], + * ["Value 2.1", "Value 2.2", "Value 2.3"], + * ] + */ + convertToArray(text = "", separatedBy = ",") { + let result = []; + + // split lines + let dataRows = text + .split(/\r\n|\n|\r/) // CRLF = \r\n; LF = \n; CR = \r; + .filter((row) => row && row.length > 0); + + // split columns + (dataRows || []).forEach((row) => { + let dataCols = []; + if (separatedBy == ",") { + // NOTE: if the file contains ,, .match(), then can not recognize this empty string + row = row.replace(/,,/g, ", ,"); + + // https://stackoverflow.com/questions/11456850/split-a-string-by-commas-but-ignore-commas-within-double-quotes-using-javascript#answer-11457952 + dataCols = row.match(/(".*?"|[^",]+)(?=\s*,|\s*$)/g); + } else { + dataCols = row.split(separatedBy); + } + + result.push(dataCols.map((dCol) => this.reformat(dCol))); + }); + + return result; + } + + /** + * @method getGuessDataType + * + * @param dataRows {Array} - [ + * ["Value 1.1", "Value 1.2", "Value 1.3"], + * ["Value 2.1", "Value 2.2", "Value 2.3"], + * ] + * @param colIndex {Number} + * + * @return {string} + */ + getGuessDataType(dataRows, colIndex) { + var data, + repeatNum = 10; + + // Loop to find a value + for (var i = 1; i <= repeatNum; i++) { + var line = dataRows[i]; + if (!line) break; + + data = line[colIndex]; + + if (data != null && data.length > 0) break; + } + + return FieldTypeTool.getFieldType(data); + } + + /** + * @method reformat + * + * @param {string} str + */ + reformat(str) { + if (!str) return ""; + + return str.trim().replace(/"/g, "").replace(/'/g, ""); + } + }; +}; diff --git a/src/plugins/web_view_csvImporter/FNAbviewcsvimporter.js b/src/plugins/web_view_csvImporter/FNAbviewcsvimporter.js new file mode 100644 index 00000000..6d42375c --- /dev/null +++ b/src/plugins/web_view_csvImporter/FNAbviewcsvimporter.js @@ -0,0 +1,369 @@ +// FNAbviewcsvimporter Properties +// A properties side import for an ABView. +// +import FABViewRuleListFormRecordRules from "../../rootPages/Designer/properties/rules/ABViewRuleListFormRecordRules.js"; + +export default function FNAbviewcsvimporterProperties({ + AB, + ABViewPropertiesPlugin, +}) { + const BASE_ID = "properties_abview_csvimporter"; + + const uiConfig = AB.Config.uiSettings(); + const L = AB.Label(); + const PopupRecordRule = FABViewRuleListFormRecordRules( + AB, + `${BASE_ID}_popupRecordRule` + ); + + return class ABAbviewcsvimporterProperties extends ABViewPropertiesPlugin { + static getPluginKey() { + return this.key; + } + + static getPluginType() { + return "properties-view"; + // properties-view : will display in the properties panel of the ABDesigner + } + + constructor(baseID) { + super(baseID ?? BASE_ID, { + datacollection: "", + fields: "", + buttonLabel: "", + buttonRecordRules: "", + width: "", + }); + + this.AB = AB; + } + + static get key() { + return "csvImporter"; + } + + ui() { + const ids = this.ids; + + return super.ui([ + { + view: "fieldset", + label: L("Data:"), + labelWidth: uiConfig.labelWidthLarge, + body: { + id: ids.datacollection, + name: "datacollection", + view: "richselect", + label: L("Data Source"), + labelWidth: uiConfig.labelWidthLarge, + skipAutoSave: true, + on: { + onChange: (newVal) => this.selectSource(newVal), + }, + }, + }, + { + view: "fieldset", + label: L("Available Fields:"), + labelWidth: uiConfig.labelWidthLarge, + body: { + type: "clean", + padding: 10, + rows: [ + { + id: ids.fields, + name: "fields", + view: "list", + select: false, + minHeight: 250, + template: this.listTemplate.bind(this), + type: { + markCheckbox: function (item) { + return ``; + }, + }, + onClick: { + check: this.check.bind(this), + }, + }, + ], + }, + }, + { + view: "fieldset", + label: L("Rules:"), + labelWidth: uiConfig.labelWidthLarge, + body: { + type: "clean", + padding: 10, + rows: [ + { + cols: [ + { + view: "label", + label: L("Record Rules:"), + width: uiConfig.labelWidthLarge, + }, + { + id: ids.buttonRecordRules, + view: "button", + name: "buttonRecordRules", + css: "webix_primary", + label: L("Settings"), + icon: "fa fa-gear", + type: "icon", + badge: 0, + click: this.recordRuleShow.bind(this), + }, + ], + }, + ], + }, + }, + { + view: "fieldset", + label: L("Customize Display:"), + labelWidth: uiConfig.labelWidthLarge, + body: { + type: "clean", + padding: 10, + rows: [ + { + id: ids.buttonLabel, + name: "buttonLabel", + view: "text", + label: L("Label"), + labelWidth: uiConfig.labelWidthXLarge, + on: { + onChange: () => this.onChange(), + }, + }, + { + id: ids.width, + view: "counter", + name: "width", + label: L("Width:"), + labelWidth: uiConfig.labelWidthXLarge, + on: { + onChange: () => this.onChange(), + }, + }, + ], + }, + }, + ]); + } + + async init(AB) { + this.AB = AB; + + await super.init(AB); + + PopupRecordRule.init(AB); + PopupRecordRule.on("save", () => { + this.populateBadgeNumber(); + }); + } + + selectSource(dcId) { + const view = this.CurrentView; + view.settings = view.settings ?? {}; + view.settings.dataviewID = dcId; + + this.updateRules(); + this.populateAvailableFields({ selectAll: true }); + this.onChange(); + } + + updateRules() { + // Populate values to rules + const selectedDv = this.CurrentView.datacollection; + if (selectedDv?.datasource) { + PopupRecordRule.objectLoad(selectedDv.datasource); + } + + PopupRecordRule.fromSettings( + this.CurrentView?.settings?.recordRules ?? [] + ); + } + + populateAvailableFields(options = {}) { + const ids = this.ids; + const view = this.CurrentView; + + const datacollection = this.AB.datacollectionByID( + view.settings.dataviewID + ); + const object = datacollection?.datasource; + + view.settings = view.settings ?? {}; + const availableFields = view.settings.availableFieldIds ?? []; + + // Pull field list + const fieldOptions = object?.fields()?.map((f) => { + f.selected = options.selectAll + ? true + : availableFields.filter((fieldId) => f.id == fieldId).length > + 0; + + return f; + }); + + $$(ids.fields).clearAll(); + $$(ids.fields).parse(fieldOptions); + } + + populateBadgeNumber() { + const ids = this.ids; + const view = this.CurrentView; + if (!view) return; + + $$(ids.buttonRecordRules).define( + "badge", + view.settings?.recordRules?.length ?? null + ); + $$(ids.buttonRecordRules).refresh(); + } + + listTemplate(field, $common) { + const fieldComponent = field.formComponent(); + if (fieldComponent == null) + return ` ${field.label}
Disable
`; + + const componentKey = fieldComponent.common().key; + const formComponent = this.CurrentApplication.viewAll( + (v) => v.common().key == componentKey + )[0]; + + return `${$common.markCheckbox(field)} ${ + field.label + }
${ + formComponent ? L(formComponent.common().labelKey ?? "Label") : "" + }
`; + } + + check(e, fieldId) { + const ids = this.ids; + + // update UI list + let item = $$(ids.fields).getItem(fieldId); + item.selected = item.selected ? 0 : 1; + $$(ids.fields).updateItem(fieldId, item); + this.onChange(); + } + + recordRuleShow() { + this.updateRules(); + if (PopupRecordRule.CurrentObject) PopupRecordRule.show(); + + // Workaround + PopupRecordRule.qbFixAfterShow(); + } + + populate(view) { + super.populate(view); + + const ids = this.ids; + + view.settings = view.settings ?? {}; + + this.populateDataCollections(); + this.populateAvailableFields(); + + $$(ids.buttonLabel).setValue(view.settings.buttonLabel); + $$(ids.width).setValue(view.settings.width); + + view.settings.availableFieldIds = []; + let fields = $$(ids.fields).find({ selected: true }); + (fields || []).forEach((f) => { + view.settings.availableFieldIds.push(f.id); + }); + } + + populateDataCollections() { + const ids = this.ids; + const view = this.CurrentView; + + const datacollections = + this.CurrentApplication.datacollectionsIncluded().map((dc) => { + return { + id: dc.id, + value: dc.label, + icon: + dc.sourceType === "query" + ? "fa fa-filter" + : "fa fa-database", + }; + }); + + const $d = $$(ids.datacollection); + $d.define("options", datacollections); + $d.define("value", view.settings.dataviewID); + $d.refresh(); + } + + defaultValues() { + const values = { + dataviewID: null, + buttonLabel: "Upload CSV", + width: 0, + recordRules: [], + availableFieldIds: [], + }; + + const FieldClass = this.ViewClass(); + if (FieldClass) { + const fcValues = FieldClass.defaultValues(); + Object.keys(fcValues).forEach((k) => { + values[k] = fcValues[k]; + }); + } + + return values; + } + + /** + * @method values + * return the values for this form. + * @return {obj} + */ + values() { + const ids = this.ids; + const values = super.values(); + + values.settings = values.settings ?? {}; + values.settings.dataviewID = $$(ids.datacollection).getValue(); + values.settings.recordRules = PopupRecordRule.toSettings(); + values.settings.buttonLabel = $$(ids.buttonLabel).getValue(); + values.settings.width = $$(ids.width).getValue(); + + values.settings.availableFieldIds = []; + $$(ids.fields) + .find({ selected: true }) + .forEach((f) => { + values.settings.availableFieldIds.push(f.id); + }); + + return values; + } + + /** + * @method FieldClass() + * A method to return the proper ABViewXXX Definition. + * NOTE: Must be overwritten by the Child Class + */ + ViewClass() { + return super._ViewClass("csvImporter"); + } + + toSettings() { + var base = this.defaults(); + base.settings = this.defaultValues(); + return base; + } + }; +} diff --git a/src/plugins/web_view_csvImporter/FNAbviewcsvimporterEditor.js b/src/plugins/web_view_csvImporter/FNAbviewcsvimporterEditor.js new file mode 100644 index 00000000..9dec4ddc --- /dev/null +++ b/src/plugins/web_view_csvImporter/FNAbviewcsvimporterEditor.js @@ -0,0 +1,51 @@ +// FNAbviewcsvimporter Editor +// An Editor wrapper for the ABView Component. +// The Editor is displayed in the ABDesigner as a view is worked on. +// The Editor allows a widget to be moved and placed on the canvas. +// +export default function FNAbviewcsvimporterEditor({ ABViewEditorPlugin }) { + return class ABAbviewcsvimporterEditor extends ABViewEditorPlugin { + static getPluginKey() { + return this.key; + } + + /** + * @method getPluginType + * return the plugin type for this editor. + * plugin types are how our ClassManager knows how to store + * the plugin. + * @return {string} plugin type + */ + static getPluginType() { + return "editor-view"; + // editor-view : will display in the editor panel of the ABDesigner + } + + static get key() { + return "csvImporter"; + } + + constructor(view, base = "interface_editor_csvImporter") { + // base: {string} unique base id reference + + super(view, base); + } + + ui() { + return super.ui(); + } + + async init(AB) { + this.AB = AB; + await super.init(AB); + } + + detatch() { + super.detatch(); + } + + onShow() { + super.onShow(); + } + }; +} diff --git a/src/plugins/web_view_csvImporter/FieldTypeTool.js b/src/plugins/web_view_csvImporter/FieldTypeTool.js new file mode 100644 index 00000000..ea374473 --- /dev/null +++ b/src/plugins/web_view_csvImporter/FieldTypeTool.js @@ -0,0 +1,23 @@ +module.exports = class FieldTypeTool { + static getFieldType(value) { + if (value === null || value === undefined || value === "") { + return "string"; + } else if ( + value == 0 || + value == 1 || + value == true || + value == false || + value == "checked" || + value == "unchecked" + ) { + return "boolean"; + } else if (!isNaN(value)) { + return "number"; + } else if (Date.parse(value)) { + return "date"; + } else { + if (value.length > 100) return "LongText"; + else return "string"; + } + } +}; diff --git a/src/rootPages/Designer/editors/EditorManager.js b/src/rootPages/Designer/editors/EditorManager.js index 65143033..759e2ee2 100644 --- a/src/rootPages/Designer/editors/EditorManager.js +++ b/src/rootPages/Designer/editors/EditorManager.js @@ -21,8 +21,6 @@ export default function (AB) { // require("./views/ABViewComment"), require("./views/ABViewConditionalContainer"), require("./views/ABViewContainer"), - require("./views/ABViewCSVExporter"), - require("./views/ABViewCSVImporter"), // require("./views/ABViewDataSelect"), // require("./views/ABViewDataview"), // require("./views/ABViewDetail"), diff --git a/src/rootPages/Designer/editors/views/ABViewCSVExporter.js b/src/rootPages/Designer/editors/views/ABViewCSVExporter.js deleted file mode 100644 index 92e411da..00000000 --- a/src/rootPages/Designer/editors/views/ABViewCSVExporter.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * ABViewCSVExporterEditor - * The widget that displays the UI Editor Component on the screen - * when designing the UI. - */ -let myClass = null; -// {singleton} -// we will want to call this factory fn() repeatedly in our imports, -// but we only want to define 1 Class reference. - -import UI_Class from "../../ui_class"; - -export default function (AB) { - if (!myClass) { - const UIClass = UI_Class(AB); - // var L = UIClass.L(); - // var L = ABViewContainer.L(); - - myClass = class ABViewCSVExporterEditor extends UIClass { - static get key() { - return "csvExporter"; - } - - constructor(view, base = "interface_editor_csvExporter") { - // base: {string} unique base id reference - - super(view, base); - - this.view = view; - this.component = this.view.component(); - } - - ui() { - return this.component.ui(); - } - - init(AB) { - this.AB = AB; - - this.component.init(this.AB); - - // this.component.onShow(); - // in our editor, we provide accessLv = 2 - } - - detatch() { - this.component?.detatch?.(); - } - - onShow() { - this.component?.onShow?.(); - } - }; - } - - return myClass; -} diff --git a/src/rootPages/Designer/editors/views/ABViewCSVImporter.js b/src/rootPages/Designer/editors/views/ABViewCSVImporter.js deleted file mode 100644 index 8d0a0662..00000000 --- a/src/rootPages/Designer/editors/views/ABViewCSVImporter.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * ABViewCSVImporterEditor - * The widget that displays the UI Editor Component on the screen - * when designing the UI. - */ -let myClass = null; -// {singleton} -// we will want to call this factory fn() repeatedly in our imports, -// but we only want to define 1 Class reference. - -import UI_Class from "../../ui_class"; - -export default function (AB) { - if (!myClass) { - const UIClass = UI_Class(AB); - // var L = UIClass.L(); - // var L = ABViewContainer.L(); - - myClass = class ABViewCSVImporterEditor extends UIClass { - static get key() { - return "csvImporter"; - } - - constructor(view, base = "interface_editor_csvImporter") { - // base: {string} unique base id reference - - super(view, base); - - this.view = view; - this.component = this.view.component(); - } - - ui() { - return this.component.ui(); - } - - init(AB) { - this.AB = AB; - - this.component.init(this.AB); - - // this.component.onShow(); - // in our editor, we provide accessLv = 2 - } - - detatch() { - this.component?.detatch?.(); - } - - onShow() { - this.component?.onShow?.(); - } - }; - } - - return myClass; -} diff --git a/src/rootPages/Designer/properties/PropertyManager.js b/src/rootPages/Designer/properties/PropertyManager.js index edbaca5e..046d8915 100644 --- a/src/rootPages/Designer/properties/PropertyManager.js +++ b/src/rootPages/Designer/properties/PropertyManager.js @@ -79,8 +79,6 @@ export default function (AB) { // require("./views/ABViewComment"), require("./views/ABViewConditionalContainer"), require("./views/ABViewContainer"), - require("./views/ABViewCSVExporter"), - require("./views/ABViewCSVImporter"), require("./views/ABViewDataFilter"), // require("./views/ABViewDataSelect"), // require("./views/ABViewDataview"), diff --git a/src/rootPages/Designer/ui_work_object_list_newObject_csv.js b/src/rootPages/Designer/ui_work_object_list_newObject_csv.js index c8b3cf69..d52af93b 100644 --- a/src/rootPages/Designer/ui_work_object_list_newObject_csv.js +++ b/src/rootPages/Designer/ui_work_object_list_newObject_csv.js @@ -5,7 +5,7 @@ * */ import UI_Class from "./ui_class"; -import CSVImporter from "../../utils/CSVImporter.js"; +import CSVImporter from "../../utils/CSVImporter"; export default function (AB) { const UIClass = UI_Class(AB); diff --git a/src/rootPages/Designer/ui_work_object_workspace.js b/src/rootPages/Designer/ui_work_object_workspace.js index 9513c00c..4c861940 100644 --- a/src/rootPages/Designer/ui_work_object_workspace.js +++ b/src/rootPages/Designer/ui_work_object_workspace.js @@ -238,9 +238,6 @@ export default function (AB, ibase, init_settings) { AB, `${base}_import` ); - // this.PopupImportObjectComponent.on("done", () => { - // this.populateObjectWorkspace(this.CurrentObject); - // }); this.PopupViewSettingsComponent = FPopupViewSettings( AB, diff --git a/src/rootPages/Designer/ui_work_object_workspace_popupImport.js b/src/rootPages/Designer/ui_work_object_workspace_popupImport.js index 842d28ee..2948a851 100644 --- a/src/rootPages/Designer/ui_work_object_workspace_popupImport.js +++ b/src/rootPages/Designer/ui_work_object_workspace_popupImport.js @@ -4,15 +4,15 @@ * Manage the Import CSV data to our currently selected ABObject. * */ + import UI_Class from "./ui_class"; import FViewProperties from "./properties/views/ABViewCSVImporter"; export default function (AB, ibase) { ibase = ibase || "ui_work_object_workspace_popupImport"; const UIClass = UI_Class(AB); - // var L = UIClass.L(); - const ViewProperties = FViewProperties(AB); - const viewProperties = new ViewProperties(); + const ViewPropertiesClass = FViewProperties(AB); + const viewProperties = new ViewPropertiesClass(); class UI_Work_Object_Workspace_PopupImport extends UIClass { constructor(base) { diff --git a/src/utils/CSVImporter.js b/src/utils/CSVImporter.js index 4dbd549e..7b007346 100644 --- a/src/utils/CSVImporter.js +++ b/src/utils/CSVImporter.js @@ -150,4 +150,4 @@ module.exports = class CSVImporter { return str.trim().replace(/"/g, "").replace(/'/g, ""); } -}; +}; \ No newline at end of file diff --git a/test/_mock/AB.js b/test/_mock/AB.js index f9243166..286b4d02 100644 --- a/test/_mock/AB.js +++ b/test/_mock/AB.js @@ -1,4 +1,4 @@ -const EventEmitter = require("events").EventEmitter; +import { EventEmitter } from "events"; export default class AB { constructor(definitions) { @@ -19,6 +19,14 @@ export default class AB { this.Config = new Config(); this.Multilingual = Multilingual; } + + Label() { + return (key) => key; + } + + getPluginAPI() { + return { AB: this }; + } } class ClassUI extends EventEmitter { diff --git a/test/_mock/ABViewEditorPlugin.js b/test/_mock/ABViewEditorPlugin.js new file mode 100644 index 00000000..722a9750 --- /dev/null +++ b/test/_mock/ABViewEditorPlugin.js @@ -0,0 +1,26 @@ +import sinon from "sinon"; + +export default class ABViewEditorPlugin { + constructor(view, base) { + this.view = view; + this.base = base; + this.component = { + ui: sinon.stub().returns({}), + init: sinon.stub(), + detatch: sinon.stub(), + onShow: sinon.stub(), + }; + } + + ui() { + return { view: "mock-editor-ui" }; + } + + async init(AB) { + this.AB = AB; + } + + detatch() {} + + onShow() {} +} diff --git a/test/_mock/ABViewPropertiesPlugin.js b/test/_mock/ABViewPropertiesPlugin.js new file mode 100644 index 00000000..830595b8 --- /dev/null +++ b/test/_mock/ABViewPropertiesPlugin.js @@ -0,0 +1,30 @@ +export default class ABViewPropertiesPlugin { + constructor(baseID, defaults) { + this.baseID = baseID; + this.defaults = defaults; + this.ids = {}; + Object.keys(defaults || {}).forEach((k) => { + this.ids[k] = `${baseID}_${k}`; + }); + } + + ui() { + return []; + } + + async init(AB) { + this.AB = AB; + } + + _ViewClass() { + return null; + } + + values() { + return { settings: {} }; + } + + populate() {} + + onChange() {} +} diff --git a/test/_setup.js b/test/_setup.js index b7c26f40..613fa701 100644 --- a/test/_setup.js +++ b/test/_setup.js @@ -1,7 +1,40 @@ +import Module from "module"; import { JSDOM } from "jsdom"; import webix from "./_mock/webix"; import webixElement from "./_mock/webix_element"; +const origLoad = Module._load; +Module._load = function (request, parent, isMain) { + if (request.endsWith(".css")) { + return {}; + } + if (request === "bpmn-js" || request.startsWith("bpmn-js/")) { + return function BpmnMock() { + return new Proxy( + function () { }, + { + get: () => () => { }, + apply: () => { }, + } + ); + }; + } + if (request.includes("ABViewRuleListFormRecordRules")) { + const popupStub = { + init() { }, + on() { }, + toSettings: () => [], + fromSettings() { }, + objectLoad() { }, + qbFixAfterShow() { }, + }; + const factory = () => popupStub; + factory.default = factory; + return factory; + } + return origLoad.apply(this, arguments); +}; + // Set web browser environment const dom = new JSDOM(""); global.window = dom.window; diff --git a/test/plugins/web_view_csvImporter/FNAbviewcsvimporter.test.js b/test/plugins/web_view_csvImporter/FNAbviewcsvimporter.test.js new file mode 100644 index 00000000..33e73e3d --- /dev/null +++ b/test/plugins/web_view_csvImporter/FNAbviewcsvimporter.test.js @@ -0,0 +1,51 @@ +import "@babel/polyfill"; +import assert from "assert"; + +import AB from "../../_mock/AB.js"; +import ABViewPropertiesPlugin from "../../_mock/ABViewPropertiesPlugin.js"; +import FNAbviewcsvimporterProperties from "../../../src/plugins/web_view_csvImporter/FNAbviewcsvimporter.js"; + +function recordRulesFactory() { + return { + init() {}, + on() {}, + toSettings: () => [], + fromSettings() {}, + objectLoad() {}, + qbFixAfterShow() {}, + }; +} + +function buildPropertiesClass() { + const ab = new AB(); + return FNAbviewcsvimporterProperties({ + AB: ab, + ABViewPropertiesPlugin, + ABViewRuleListFormRecordRules: recordRulesFactory, + }); +} + +describe("FNAbviewcsvimporterProperties", function () { + it("exposes csvImporter plugin metadata", function () { + const PropertiesClass = buildPropertiesClass(); + + assert.equal(PropertiesClass.key, "csvImporter"); + assert.equal(PropertiesClass.getPluginKey(), "csvImporter"); + assert.equal(PropertiesClass.getPluginType(), "properties-view"); + }); + + it("defaultValues() returns expected settings shape", function () { + const PropertiesClass = buildPropertiesClass(); + const props = new PropertiesClass(); + + const values = props.defaultValues(); + + assert.deepEqual(values, { + dataviewID: null, + buttonLabel: "Upload CSV", + width: 0, + recordRules: [], + availableFieldIds: [], + }); + }); +}); diff --git a/test/plugins/web_view_csvImporter/FNAbviewcsvimporterEditor.test.js b/test/plugins/web_view_csvImporter/FNAbviewcsvimporterEditor.test.js new file mode 100644 index 00000000..f9ae6a51 --- /dev/null +++ b/test/plugins/web_view_csvImporter/FNAbviewcsvimporterEditor.test.js @@ -0,0 +1,70 @@ +import "@babel/polyfill"; +import assert from "assert"; +import sinon from "sinon"; + +import AB from "../../_mock/AB.js"; +import ABViewEditorPlugin from "../../_mock/ABViewEditorPlugin.js"; +import FNAbviewcsvimporterEditor from "../../../src/plugins/web_view_csvImporter/FNAbviewcsvimporterEditor.js"; + +function buildEditorClass() { + const ab = new AB(); + return FNAbviewcsvimporterEditor({ + AB: ab, + ABViewEditorPlugin, + }); +} + +describe("FNAbviewcsvimporterEditor", function () { + let EditorClass; + let uiSpy; + let initSpy; + let detatchSpy; + let onShowSpy; + + beforeEach(function () { + EditorClass = buildEditorClass(); + uiSpy = sinon.spy(ABViewEditorPlugin.prototype, "ui"); + initSpy = sinon.spy(ABViewEditorPlugin.prototype, "init"); + detatchSpy = sinon.spy(ABViewEditorPlugin.prototype, "detatch"); + onShowSpy = sinon.spy(ABViewEditorPlugin.prototype, "onShow"); + }); + + afterEach(function () { + sinon.restore(); + }); + + it("exposes csvImporter plugin metadata", function () { + assert.equal(EditorClass.key, "csvImporter"); + assert.equal(EditorClass.getPluginKey(), "csvImporter"); + assert.equal(EditorClass.getPluginType(), "editor-view"); + }); + + it("delegates ui() to the editor base class", function () { + const editor = new EditorClass({}, "interface_editor_csvImporter"); + const result = editor.ui(); + + assert.equal(true, uiSpy.calledOnce); + assert.deepEqual(result, { view: "mock-editor-ui" }); + }); + + it("init(AB) sets AB and delegates to super.init", async function () { + const ab = new AB(); + const editor = new EditorClass({}, "interface_editor_csvImporter"); + + await editor.init(ab); + + assert.equal(ab, editor.AB); + assert.equal(true, initSpy.calledOnce); + assert.equal(ab, initSpy.firstCall.args[0]); + }); + + it("detatch() and onShow() delegate to super", function () { + const editor = new EditorClass({}, "interface_editor_csvImporter"); + + editor.detatch(); + editor.onShow(); + + assert.equal(true, detatchSpy.calledOnce); + assert.equal(true, onShowSpy.calledOnce); + }); +}); diff --git a/test/utils/CSVImporter.test.js b/test/utils/CSVImporter.test.js index 4c1c4dc5..b70f1423 100644 --- a/test/utils/CSVImporter.test.js +++ b/test/utils/CSVImporter.test.js @@ -3,7 +3,7 @@ import assert from "assert"; import sinon from "sinon"; import AB from "../_mock/AB.js"; -import CSVImporter from "../../src/utils/CSVImporter.js"; +import FCSVImporter from "../../src/plugins/web_view_csvImporter/CSVImporter.js"; function getMockAB() { return new AB(); @@ -11,20 +11,23 @@ function getMockAB() { function getTarget() { const ab = getMockAB(); - return new CSVImporter(ab); + const CSVImporter = FCSVImporter(ab.getPluginAPI()); + return new CSVImporter(); } describe("CSVImporter", function () { it(".constructor - should store AB to a local variable", function () { const ab = getMockAB(); - const target = new CSVImporter(ab); + const CSVImporter = FCSVImporter(ab.getPluginAPI()); + const target = new CSVImporter(); assert.equal(ab, target._AB); }); it(".L - should pass valid parameters and return result of .labelPlugin function", function () { const ab = getMockAB(); - const target = new CSVImporter(ab); + const CSVImporter = FCSVImporter(ab.getPluginAPI()); + const target = new CSVImporter(); const pluginKey = "ABDesigner"; const expectParams = ["A", "B", "C"]; const expectResult = "RESULT";