diff --git a/sonar-project.properties b/sonar-project.properties index 37fec5778..34446f8a6 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,3 +3,5 @@ sonar.javascript.lcov.reportPaths=./coverage/lcov.info sonar.exclusions=node_modules/**, dist/**, public/**, home/**, storybook-static/** sonar.sources=src sonar.tests=tests +sonar.cpd.exclusions=src/stories/** +sonar.coverage.exclusions=src/stories/** diff --git a/src/assets/css/custom.css b/src/assets/css/custom.css index 38ec225ce..09fecf462 100644 --- a/src/assets/css/custom.css +++ b/src/assets/css/custom.css @@ -8,4 +8,7 @@ } .page-dropdown-menu { min-width: 333px; + max-height: 26rem; + overflow-y: auto; + scrollbar-width: thin; } \ No newline at end of file diff --git a/src/components/TabsBar.vue b/src/components/TabsBar.vue index bfbd9cd51..ce4359648 100644 --- a/src/components/TabsBar.vue +++ b/src/components/TabsBar.vue @@ -196,9 +196,10 @@ export default { }, visualThreshold); }); }, - closeTab(pageId) { - this.localOpenedPages.splice(this.localOpenedPages.indexOf(pageId), 1); - this.$emit("tab-closed", this.pages[pageId], this.localOpenedPages); + closeTab(tabIndex) { + const pageIndex = this.localOpenedPages[tabIndex]; + this.localOpenedPages.splice(tabIndex, 1); + this.$emit("tab-closed", this.pages[pageIndex], this.localOpenedPages); }, updateTabsReferences(pageDelete) { this.localOpenedPages = this.localOpenedPages.map((page) => diff --git a/src/components/sortable/sortableList/SortableList.vue b/src/components/sortable/sortableList/SortableList.vue index e712ab005..18e044df7 100644 --- a/src/components/sortable/sortableList/SortableList.vue +++ b/src/components/sortable/sortableList/SortableList.vue @@ -24,15 +24,16 @@
{{ item.name }}
@@ -63,7 +64,7 @@ export default { }, data() { return { - originalName: '', + newName: '', draggedItem: 0, draggedOverItem: 0, editRowIndex: null, @@ -79,14 +80,14 @@ export default { }, methods: { validateState(name, item) { - const isEmpty = !name; + const isEmpty = !name?.trim(); const isDuplicated = this.items .filter((i) => i !== item) .find((i) => i.name === name); return isEmpty || isDuplicated ? false : null; }, validateError(name, item) { - const isEmpty = !name; + const isEmpty = !name?.trim(); if (!isEmpty) { return this.$t("The Page Name field is required."); } @@ -98,19 +99,25 @@ export default { } return ''; }, - onFocus(name, item) { - this.originalName = name; + onFocus(item) { + this.newName = item.name; }, async onBlur(name, item) { if (this.validateState(name, item) === false) { + this.newName = item.name; + } else { // eslint-disable-next-line no-param-reassign - item.name = this.originalName; + item.name = name; } await this.$nextTick(); setTimeout(() => { this.editRowIndex = null; }, 250); }, + async onCancel(item) { + this.newName = item.name; + this.editRowIndex = null; + }, onClick(item, index) { this.editRowIndex = index; this.$emit("item-edit", item); @@ -162,6 +169,14 @@ export default { itemsSortedClone[draggedItemIndex].order = tempOrder; } + + // Update order of the items + const clone = [...itemsSortedClone]; + clone.sort((a, b) => a.order - b.order); + clone.forEach((item, index) => { + // eslint-disable-next-line no-param-reassign + item.order = index + 1; + }); } this.$emit('ordered', itemsSortedClone); diff --git a/src/components/vue-form-builder.vue b/src/components/vue-form-builder.vue index 964efb35c..d8926f559 100644 --- a/src/components/vue-form-builder.vue +++ b/src/components/vue-form-builder.vue @@ -881,6 +881,7 @@ export default { config.forEach((page) => this.replaceFormText(page.items)); config.forEach((page) => this.migrateFormSubmit(page.items)); config.forEach((page) => this.updateFieldNameValidation(page.items)); + this.updatePageOrder(config); config.forEach((page) => this.removeDataVariableFromNestedScreens(page.items) ); @@ -889,6 +890,18 @@ export default { page.order = page.order || index + 1; }); }, + updatePageOrder(pages) { + const clone = [...pages]; + clone.sort((a, b) => { + const aOrder = a.order || pages.indexOf(a) + 1; + const bOrder = b.order || pages.indexOf(b) + 1; + return aOrder - bOrder; + }); + clone.forEach((item, index) => { + // eslint-disable-next-line no-param-reassign + item.order = index + 1; + }); + }, updateFieldNameValidation(items) { items.forEach((item) => { if (item.inspector) { @@ -1148,9 +1161,9 @@ export default { this.updateState(); }, addPage(e) { - this.showAddPageValidations = true; const error = this.checkPageName(this.addPageName, true); if (error) { + this.showAddPageValidations = true; e.preventDefault(); return; } @@ -1227,6 +1240,7 @@ export default { globalObject.ProcessMaker.alert(error.message, "danger"); return; } + this.updatePageOrder(this.config); this.$store.dispatch("undoRedoModule/pushState", { config: JSON.stringify(this.config), currentPage: this.currentPage, diff --git a/src/stories/DropdownAndPages.stories.js b/src/stories/DropdownAndPages.stories.js index 5db2901b6..8de866df5 100644 --- a/src/stories/DropdownAndPages.stories.js +++ b/src/stories/DropdownAndPages.stories.js @@ -80,7 +80,6 @@ export const OpenPageUsingDropdown = { "[data-test=page-dropdown] button" ); let selectorAddPage = canvasElement.querySelector("[data-test=page-Page3]"); - console.log(selectorAddPage); await selector.click(selector); await selectorAddPage.click(selectorAddPage); // Open Page 3 (index=2) @@ -111,3 +110,37 @@ export const OpenPageUsingDropdown = { }); } }; + +// Open a page using the PageDropdown(index) +export const ScrollWithMoreThanTenPages = { + args: { + pages: [ + { name: "Page1" }, + { name: "Page2" }, + { name: "Page3" }, + { name: "Page4" }, + { name: "Page5" }, + { name: "Page6" }, + { name: "Page7" }, + { name: "Page8" }, + { name: "Page9" }, + { name: "Page10" }, + { name: "Page11" }, + { name: "Page12" } + ], + initialOpenedPages: [0] + }, + play: async ({ canvasElement }) => { + const selector = canvasElement.querySelector( + "[data-test=page-dropdown] button" + ); + await selector.click(selector); + // Test .page-dropdown-menu has scroll (scrollHeight > clientHeight) + await waitFor(() => { + const dropdownMenu = canvasElement.querySelector(".page-dropdown-menu"); + expect(dropdownMenu.scrollHeight).toBeGreaterThan( + dropdownMenu.clientHeight + ); + }); + } +}; diff --git a/src/stories/PageTabs.stories.js b/src/stories/PageTabs.stories.js index 49c9b2449..15833dd82 100644 --- a/src/stories/PageTabs.stories.js +++ b/src/stories/PageTabs.stories.js @@ -336,3 +336,60 @@ export const WithoutAnyPageOpened = { ); } }; + +// User can close tabs +export const UserCanCloseTabs = { + args: { + pages: [ + { name: "Page 1" }, + { name: "Page 2" }, + { name: "Page 3" }, + { name: "Page 4" }, + { name: "Page 5" } + ], + initialOpenedPages: [0, 1, 2, 3, 4] + }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + // Close Tab #1 = Page 1 (tab index=0) + await step("Close Page 1 (tab index=0)", async () => { + canvas.getByTestId("close-tab-0").click(); + await waitFor( + () => { + expect(canvas.getByTestId("tab-content")).toContainHTML( + "Here comes content of Page 2 (#1)" + ); + }, + { timeout: 1000 } + ); + }); + + // Close Tab #1 = Page 2 (tab index=0) + await step("Close Page 2 (tab index=0)", async () => { + canvas.getByTestId("close-tab-0").click(); + await waitFor( + () => { + expect(canvas.getByTestId("tab-content")).toContainHTML( + "Here comes content of Page 3 (#2)" + ); + }, + { timeout: 1000 } + ); + }); + + // Close Tab #2 = Page 4 (tab index=1) + await step("Close Page 4 (tab index=1)", async () => { + canvas.getByTestId("close-tab-1").click(); + await waitFor( + () => { + // keep focus in the tab #1 + expect(canvas.getByTestId("tab-content")).toContainHTML( + "Here comes content of Page 3 (#2)" + ); + }, + { timeout: 1000 } + ); + }); + } +};