diff --git a/app/package-lock.json b/app/package-lock.json index 712b38ce5a..67ea0861bf 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -8736,9 +8736,9 @@ ] }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" diff --git a/app/views/components/datagrid/example-fixed-header.html b/app/views/components/datagrid/example-fixed-header.html index c011d3e6da..74b4b91c93 100644 --- a/app/views/components/datagrid/example-fixed-header.html +++ b/app/views/components/datagrid/example-fixed-header.html @@ -1,4 +1,32 @@
+
@@ -24,14 +52,22 @@ columns.push({ id: 'action', name: 'Action Item', field: 'action'}); //Init and get the api for the grid - $('#datagrid').datagrid({ + grid = $('#datagrid').datagrid({ columns: columns, columnReorder: true, dataset: data, paging: true, pagesize: 100, toolbar: {title: 'Compressors', actions: true, rowHeight: true, results: true } - }); + }).data('datagrid'); + }); + + $('#scroll').on('click', () => { + if (grid) { + const s = grid.settings; + const lastRow = s.pagesize > s.dataset.length ? s.dataset.length - 1 : s.pagesize - 1; + grid.scrollRowIntoView(lastRow); + } }); }); diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 52f51d195e..2e7ba1dab6 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,7 @@ ## v4.99.0 Features +- `[Datagrid]` Added `scrollRowIntoView`. ([NG#1761](https://github.com/infor-design/enterprise-ng/issues/1761)) - `[FileuploadAdvanced]` Added beforefileupload event in handleFileUpload method. ([#8904](https://github.com/infor-design/enterprise/issues/8904)) - `[Listview]` Added compact layout in listview. ([#8959](https://github.com/infor-design/enterprise/issues/8959)) - `[Module Tabs]` Updated tab colors. ([#8831](https://github.com/infor-design/enterprise/issues/8831)) @@ -17,11 +18,13 @@ - `[Datagrid]` Updated styles so pager works on cards. ([NG#1756](https://github.com/infor-design/enterprise-ng/issues/1756)) - `[Datagrid]` Fixed a bug where text is misaligned in action dropdown . ([#8957](https://github.com/infor-design/enterprise-ng/issues/8957)) - `[Datagrid]` Fixed auto sizing on columns in tree grid. ([#8646](https://github.com/infor-design/enterprise/issues/8646)) +- `[Datagrid]` Fixed special characters being encoded in editor. ([#8963](https://github.com/infor-design/enterprise/issues/8963)) - `[General]` Upgrade dependencies, this involved updating sass and test dependencies with a few css deprecations that were fixed. ([#8947](https://github.com/infor-design/enterprise-ng/issues/8947)) - `[Locale]` Fixed translation issue of `small` into Spanish. ([#8962](https://github.com/infor-design/enterprise-wc/issues/8962) - `[Locale]` Fixed translation issue of `Available` into Thai and Italian. ([#8786](https://github.com/infor-design/enterprise-wc/issues/8786) - `[Message]` Updated docs in settings. ([#8839](https://github.com/infor-design/enterprise/issues/8839)) - `[ModuleNav]` Fixed a bug where the tooltip was not showing when collapsed by default. ([NG#1678](https://github.com/infor-design/enterprise-ng/issues/1678)) +- `[Swaplist]` Improved the size of the swaplist's drop area when there is no existing items. ([#8956](https://github.com/infor-design/enterprise/issues/8956)) - `[Toolbar]` Fixed uncaught error when destroying the toolbar. ([#8946](https://github.com/infor-design/enterprise/issues/8946)) - `[Tabs]` Fixed a bug where focus state is undefined when using breadcrumbs startup. ([#8910](https://github.com/infor-design/enterprise/issues/8910)) - `[Validation]` Add guards for new setting in case it is undefined. ([#8981](https://github.com/infor-design/enterprise/issues/8981)) diff --git a/docs/TESTING.md b/docs/TESTING.md index 1310964b60..64b17499dd 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -44,10 +44,79 @@ npm run test For development purposes you may just want to run one test your focusing on. ```sh -npm run test +npm run test -- component_name +``` + +## Visual Testing with Percy and Playwright + +This project demonstrates how to perform visual testing using Percy and Playwright. Visual testing helps ensure that UI changes do not introduce unexpected visual regressions. The provided example captures visual snapshots of different UI states to verify consistent rendering. + +### Table of Contents + +- [Introduction](#introduction) +- [Example Test](#example-test) +- [Usage](#usage) +- [Best Practices](#best-practices-for-visual-testing) + +### Introduction + +Visual testing is a crucial part of maintaining a consistent user interface. By comparing the appearance of web pages or components against known good baselines, visual testing tools like Percy can automatically detect unintended visual changes. + +### Example Test + +```javascript +test.describe('snapshot tests', () => { + test('should match the visual snapshot in percy', async ({ page, browserName }) => { + if (browserName !== 'chromium') return; + + // Function to handle the actions and snapshot for specific row option + const captureSnapshot = async (option, snapshotName) => { + await page.locator('#custom-id-actions').click(); + await page.locator(`a[data-option="row-${option}"]`).click(); + + // Capture a Percy snapshot with the specified name + await percySnapshot(page, snapshotName); + }; + + // Capture snapshot for "extra-small" row height + await captureSnapshot('extra-small', 'datagrid-expandable-row-extra-small-padding'); + + // Capture snapshot for "small" row height + await captureSnapshot('small', 'datagrid-expandable-row-small-padding'); + + // Capture snapshot for "medium" row height + await captureSnapshot('medium', 'datagrid-expandable-row-medium-padding'); + + // Capture snapshot for "large" / "Normal" row height + await captureSnapshot('large', 'datagrid-expandable-row-large-padding'); + }); +}); +``` + +### Usage + +To run the visual tests locally, you can use the following command: + +Run all visual tests: + +```sh +PERCY_TOKEN=your-percy-token npm run test:visual ``` -## Visual Regression testing +Run specific tests: + +```sh +PERCY_TOKEN=your-percy-token npm run test:visual -- component_name +``` + +For the visual tests to work, you need to set the `PERCY_TOKEN` environment variable to your Percy token. You can find your Percy token in your Percy project settings. + +### Best Practices for Visual Testing + +- Run Visual Tests in a Stable Environment: Ensure that tests are run in a consistent and stable environment to avoid flaky results. +- Test Across Browsers: Consider capturing snapshots in multiple browsers to catch cross-browser inconsistencies. +- Review Snapshots Regularly: Regularly review and approve or reject snapshot changes to keep the baseline up-to-date. +- Use Percy's Review Workflow: Use Percy's review workflow to collaborate with your team and ensure that visual changes are intentional. ## Debugging Functional Tests @@ -74,6 +143,10 @@ Each component should have a passing test with Axe. This tool will verify a few If your having an issue with one of these tests you can either debug and follow the above steps for debugging a test or you can install the [Axe Chrome Dev Tools Plugin](https://chrome.google.com/webstore/detail/axe/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US). This tool should give you the same output as the test when you run the page on port 4000 instead of 3000 as the tests do. +## Contributing + + Contributions are welcome! If you have suggestions or improvements, please submit a pull request. + ## Reusing Tests Across Repos Listing some common differences in reusing the tests. diff --git a/package.json b/package.json index 35843f62be..8823b8c75d 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "test:start-server": "node app/server.js --test-port", "test": "PERCY_LOGLEVEL=silent npx playwright test --reporter=list", "test:build": "npm run build:demoapp && npm run build", + "test:visual": "PERCY_LOGLEVEL=silent npx percy exec -- npx playwright test --reporter=list", "test:coverage": "npx rimraf .nyc_output && npm run build:coverage && npm run test && npx nyc report && open coverage/index.html", "test:coverage:no-build": "npx rimraf .nyc_output && npm run test && npx nyc report && open coverage/index.html", "test:ci": "PERCY_LOGLEVEL=silent npx rimraf .nyc_output && npm run build:coverage && npm run test && npx nyc report", diff --git a/src/components/datagrid/datagrid.js b/src/components/datagrid/datagrid.js index 13f80c12e9..2cbf0e8b95 100644 --- a/src/components/datagrid/datagrid.js +++ b/src/components/datagrid/datagrid.js @@ -10903,7 +10903,7 @@ Datagrid.prototype = { } if (this.editor.useValue) { - cellValue = this.fieldValue(rowData, col.field); + cellValue = this.fieldValue(rowData, col.field, false); } this.editor.val(cellValue); @@ -12486,6 +12486,16 @@ Datagrid.prototype = { self.element.trigger('activecellchange', { node: this.activeCell.node, row: this.activeCell.row, cell: this.activeCell.cell, api: self }); }, + /** + * Scroll to row. + * @param {number} row The row index + */ + scrollRowIntoView(row) { + if (row) { + this.setActiveCell(row, 0, true); + } + }, + /** * Sets focus to the next active cell, depending on a key. * @private diff --git a/src/components/swaplist/_swaplist-new.scss b/src/components/swaplist/_swaplist-new.scss index 27445bb6ef..3c41c07c3b 100644 --- a/src/components/swaplist/_swaplist-new.scss +++ b/src/components/swaplist/_swaplist-new.scss @@ -25,7 +25,7 @@ .card-content .listview, .widget-content .listview { - li:first-child { + li:first-child:not(.over) { border-top-color: transparent; } } @@ -58,3 +58,23 @@ } } } + +html[dir='rtl'] { + .swaplist { + .card, + .widget { + &:nth-child(1) { + border-radius: 0 6px 0 0; + } + } + + &.one-third { + .card, + .widget { + &:nth-child(3) { + border-radius: 6px 0 0; + } + } + } + } +} diff --git a/src/components/swaplist/_swaplist.scss b/src/components/swaplist/_swaplist.scss index 7fee91f6c5..3344103fc6 100755 --- a/src/components/swaplist/_swaplist.scss +++ b/src/components/swaplist/_swaplist.scss @@ -37,6 +37,21 @@ .card-content { width: 100%; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + + .listview { + li { + border-radius: 0; + + &:last-child { + &.over { + border: 1px dashed $swaplist-border-color !important; + border-bottom-color: inherit; + } + } + } + } } .card-header { @@ -244,9 +259,10 @@ .is-multiselect { background-color: $listview-bg-color; border-top: thin solid $swaplist-border-color-card; + height: 100%; ul { - min-height: 100%; + min-height: 40px; padding: 0 0 5px; &.is-not-droppable { @@ -326,7 +342,8 @@ } .over { - border: 1px dashed $swaplist-border-color; + border: 1px dashed $swaplist-border-color !important; + min-height: 40px; } } diff --git a/src/components/swaplist/swaplist.js b/src/components/swaplist/swaplist.js index 9536d5be6f..c0f02a8c2a 100644 --- a/src/components/swaplist/swaplist.js +++ b/src/components/swaplist/swaplist.js @@ -102,9 +102,10 @@ SwapList.prototype = { init() { const s = this.settings; + const additionalClass = `${s.additionalClass} .listview`; s.draggable = $.extend(true, SWAPLIST_DEFAULTS.draggable, s.draggable); this.isTouch = env.features.touch; - this.isAdditional = $(`${s.additionalClass} .listview`, this.element).length > 0; + this.isAdditional = $(additionalClass, this.element).length > 0; if (this.isTouch) { this.element.addClass('is-touch'); @@ -136,10 +137,12 @@ SwapList.prototype = { for (let i = 0, l = containers.length; i < l; i++) { const c = containers[i]; - const lv = $(`${c.class} .listview`, this.element); + const listviewContainerClass = `${c.class} .listview`; + const lv = $(listviewContainerClass, this.element); const list = lv.data('listview'); + const searchfieldContainerClass = `${c.class} .searchfield`; const options = { dataset: c.dataset || [], selectable: 'multiple', showCheckboxes: false }; - const isSearchable = ((s.searchable === true || s.searchable === 'true') && ($(`${c.class} .searchfield`, this.element).length > 0)); + const isSearchable = ((s.searchable === true || s.searchable === 'true') && ($(searchfieldContainerClass, this.element).length > 0)); if (isSearchable) { options.searchable = true; @@ -219,17 +222,23 @@ SwapList.prototype = { this.offset = null; - this.containers = $(`${s.availableClass},${ - s.selectedClass},${ - s.additionalClass}`, this.element); + const { availableClass, selectedClass, additionalClass } = s; + const { availableBtn, additionalBtn, selectedBtnLeft, selectedBtnRight } = s; - this.actionButtons = $(`${s.availableBtn},${ - s.additionalBtn},${ - s.selectedBtnLeft},${ - s.selectedBtnRight}`, this.element); + this.containers = $( + `${availableClass}, ${selectedClass}, ${additionalClass}`, + this.element + ); - this.selectedButtons = $(`${s.selectedBtnLeft},${ - s.selectedBtnRight}`, this.element); + this.actionButtons = $( + `${availableBtn},${additionalBtn},${selectedBtnLeft},${selectedBtnRight}`, + this.element + ); + + this.selectedButtons = $( + `${selectedBtnLeft},${selectedBtnRight}`, + this.element + ); this.tabButtonsStr = `${ s.availableBtn}, ${ @@ -672,7 +681,8 @@ SwapList.prototype = { for (let i = 0, l = containers.length; i < l; i++) { const c = containers[i]; - const nodes = $(`${c.class} .listview li`, this.element); + const classSelector = `${c.class} .listview li`; + const nodes = $(classSelector, this.element); for (let nodeIndex = 0, l2 = nodes.length; nodeIndex < l2; nodeIndex++) { let data; @@ -961,7 +971,8 @@ SwapList.prototype = { for (let i = 0, l = containers.length; i < l; i++) { const c = containers[i]; - const lv = $(`${c.class} .listview`, this.element); + const classSelector = `${c.class} .listview`; + const lv = $(classSelector, this.element); const api = lv.data('listview'); if (api) { @@ -1187,8 +1198,11 @@ SwapList.prototype = { return; } - $(`.${settings.numOfSelectionsClass}`, settings.itemContentTempl).html(selections.items.length); - $(`.${settings.numOfSelectionsClass}-text`, settings.itemContentTempl).text(Locale.translate('ItemsSelected')); + const selectionClass = `.${settings.numOfSelectionsClass}`; + const selectionClassText = `${selectionClass}-text`; + + $(selectionClass, settings.itemContentTempl).html(selections.items.length); + $(selectionClassText, settings.itemContentTempl).text(Locale.translate('ItemsSelected')); self.addDropeffects(); if (!self.isTouch) { @@ -1196,7 +1210,8 @@ SwapList.prototype = { e.originalEvent.dataTransfer.setData('text', ''); if (selections.items.length > 1) { - $(`.${settings.itemContentClass}`, selections.dragged).html(settings.itemContentTempl.html()); + const itemContentClass = `.${settings.itemContentClass}`; + $(itemContentClass, selections.dragged).html(settings.itemContentTempl.html()); } } else { rect = target[0].getBoundingClientRect(); @@ -1241,8 +1256,17 @@ SwapList.prototype = { } self.element.triggerHandler('draggingswap', [selections.move]); selections.related = e.target; + const $target = $(selections.related); + $('ul, li', self.element).removeClass('over'); - $(e.target).closest('ul, li').addClass('over'); + $target.closest('ul, li').addClass('over'); + + // Set the height to 100% if the target element has any
  • children + // to cover the entire card content area. + if ($target.find('li').length > 0) { + $target.closest('ul').css('height', '100%'); + } + selections.droptarget = $(selections.related).closest('.card'); $('[aria-grabbed="true"]', self.element).not(selections.dragged).slideUp(); e.stopPropagation(); @@ -1265,7 +1289,8 @@ SwapList.prototype = { if (!selections.isInSelection) { self.draggedMakeSelected(list, selections.dragged); selections.items = list.selectedItems; - $(`.${settings.numOfSelectionsClass}`, settings.itemContentTempl).html(selections.items.length); + const selectionClass = `.${settings.numOfSelectionsClass}`; + $(selectionClass, settings.itemContentTempl).html(selections.items.length); } touch = e.originalEvent.touches[0]; @@ -1282,7 +1307,8 @@ SwapList.prototype = { .slideUp(); if (selections.items.length > 1) { - $(`.${settings.itemContentClass}`, (selections.placeholderTouch.add('#sl-placeholder-touch2'))) + const itemContentClass = `.${settings.itemContentClass}`; + $(itemContentClass, (selections.placeholderTouch.add('#sl-placeholder-touch2'))) .html(settings.itemContentTempl.html()); $('#sl-placeholder-touch2').show(); @@ -1331,7 +1357,8 @@ SwapList.prototype = { }); if (selections.items.length > 1) { - $(`.${settings.itemContentClass}`, selections.dragged).html($(`.${settings.itemContentClass}`, selections.placeholder).html()); + const itemContentClass = `.${settings.itemContentClass}`; + $(itemContentClass, selections.dragged).html($(itemContentClass, selections.placeholder).html()); if (self.isTouch) { selections.dragged.show(); } diff --git a/tests/datagrid/datagrid.spec.js b/tests/datagrid/datagrid.spec.js index f687848bbb..73fbbd6a86 100644 --- a/tests/datagrid/datagrid.spec.js +++ b/tests/datagrid/datagrid.spec.js @@ -80,4 +80,26 @@ test.describe('Datagrid tests', () => { }); }); }); + + test.describe('Inline editor page tests', () => { + const url = '/components/datagrid/test-editable-with-inline-editor.html'; + + test.beforeEach(async ({ page }) => { + await page.goto(url); + }); + + test.describe('edit cell tests', () => { + test('value should stay the same', async ({ page }) => { + const val = '&<>'; + const input = page.locator('#datagrid-inline-input-2-2'); + + await input.click(); + await input.fill(val); + await page.locator('body').click({ position: { x: 0, y: 0 } }); + await input.click(); + + await expect(input).toHaveValue(val); + }); + }); + }); });