diff --git a/cypress.config.js b/cypress.config.js index 43a62ced8..0717507a8 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -67,7 +67,7 @@ module.exports = defineConfig({ // Enabled to reduce the risk of out-of-memory issues experimentalMemoryManagement: true, // Set to a low number to reduce the risk of out-of-memory issues - numTestsKeptInMemory: 5, + numTestsKeptInMemory: 4, /* When allowing 1 retry on CI, the test suite will pass if * it's flaky. And/but we also get to identify flaky tests on the * Cypress Dashboard. */ diff --git a/cypress/elements/event_layer.js b/cypress/elements/event_layer.js index f3a0fc47f..6f7db422c 100644 --- a/cypress/elements/event_layer.js +++ b/cypress/elements/event_layer.js @@ -3,6 +3,7 @@ import { Layer } from './layer.js' export class EventLayer extends Layer { selectProgram(program) { cy.get('[data-test="programselect"]').click() + cy.contains(program).scrollIntoView() cy.contains(program).click() return this @@ -22,4 +23,11 @@ export class EventLayer extends Layer { return this } + + selectPeriodType(periodType) { + cy.getByDataTest('relative-period-select-content').click() + cy.contains(periodType).click() + + return this + } } diff --git a/cypress/integration/dataTable.cy.js b/cypress/integration/dataTable.cy.js index ecfff1d48..7f5717f49 100644 --- a/cypress/integration/dataTable.cy.js +++ b/cypress/integration/dataTable.cy.js @@ -1,4 +1,15 @@ -import { EXTENDED_TIMEOUT } from '../support/util.js' +import { EventLayer } from '../elements/event_layer.js' +import { CURRENT_YEAR, EXTENDED_TIMEOUT } from '../support/util.js' + +Cypress.on('uncaught:exception', (err) => { + if ( + err.message.includes( + 'ResizeObserver loop completed with undelivered notifications.' + ) + ) { + return false + } +}) const map = { id: 'eDlFx0jTtV9', @@ -8,7 +19,7 @@ const map = { } describe('data table', () => { - it('opens data table', () => { + it('opens data table and filters and sorts', () => { cy.visit(`/#/${map.id}`, EXTENDED_TIMEOUT) cy.get('canvas', EXTENDED_TIMEOUT).should('be.visible') @@ -29,19 +40,226 @@ describe('data table', () => { // check number of columns cy.getByDataTest('bottom-panel') - .find('[role="columnheader"]') + .findByDataTest('dhis2-uicore-datatablecellhead') .should('have.length', 10) - // try the filtering + // Filter by name + cy.getByDataTest('data-table-column-filter-input-Name') + .find('input') + .type('bar') + + // check that the filter returned the correct number of rows + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .findByDataTest('dhis2-uicore-datatablerow') + .should('have.length', 7) + + // confirm that the sort order is initially ascending by Name + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .find('tr') + .first() + .find('td') + .eq(1) + .should('contain', 'Bargbe') + + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .find('tr') + .last() + .find('td') + .eq(1) + .should('contain', 'Upper Bambara') + + // Sort by name + cy.get('button[title="Sort by Name"]').click() + + // confirm that the rows are sorted by Name descending + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .find('tr') + .first() + .find('td') + .eq(1) + .should('contain', 'Upper Bambara') + + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .find('tr') + .last() + .find('td') + .eq(1) + .should('contain', 'Bargbe') + + // filter by Value (numeric) + cy.getByDataTest('data-table-column-filter-input-Value') + .find('input') + .type('>26') + + // check that the (combined) filter returned the correct number of rows + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .findByDataTest('dhis2-uicore-datatablerow') + .should('have.length', 5) + + // Sort by value + cy.get('button[title="Sort by Value"]').click() + + // check that the rows are sorted by Value ascending + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .find('tr') + .first() + .find('td') + .eq(3) + .should('contain', '35') + + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .find('tr') + .last() + .find('td') + .eq(3) + .should('contain', '76') + + // click on a row + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .findByDataTest('dhis2-uicore-datatablerow') + .first() + .click() + + // check that the org unit profile drawer is opened + cy.getByDataTest('org-unit-profile').should('be.visible') + }) + + it('opens the data table for an Event layer', () => { + cy.visit('/', EXTENDED_TIMEOUT) + + const Layer = new EventLayer() + + Layer.openDialog('Events') + .selectProgram('Malaria case registration') + .validateStage('Malaria case registration') + .selectTab('Period') + .selectPeriodType('Start/end dates') + .typeStartDate(`${CURRENT_YEAR - 1}-01-01`) + .typeEndDate(`${CURRENT_YEAR - 1}-01-15`) + .selectTab('Org Units') + .selectOu('Bo') + .addToMap() + + Layer.validateDialogClosed(true) + + Layer.validateCardTitle('Malaria case registration') + + cy.getByDataTest('moremenubutton').first().click() + + cy.getByDataTest('more-menu') + .find('li') + .contains('Show data table') + .not('disabled') + .click() + + cy.getByDataTest('bottom-panel').should('be.visible') + + // check number of columns + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-datatablecellhead') + .should('have.length', 9) + + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-datatablecellhead') + .contains('Age in years', { matchCase: false }) + .should('be.visible') + + // filter by Org unit + const ouName = 'Benduma' + cy.getByDataTest('data-table-column-filter-input-Org unit') + .find('input') + .type(ouName) + + // check that all the rows have Org unit Yakaji + + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .find('tr') + .first() + .find('td') + .eq(1) + .should('contain', ouName) + + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .find('tr') + .last() + .find('td') + .eq(1) + .should('contain', ouName) + cy.getByDataTest('bottom-panel') - .find('[role="columnheader"]') - .containsExact('Name') - .siblings('input') - .type('Kakua') + .findByDataTest('dhis2-uicore-tablebody') + .findByDataTest('dhis2-uicore-datatablerow') + .should('have.length', 5) + + // filter by Gender + cy.getByDataTest('data-table-column-filter-input-Gender') + .find('input') + .type('Female') + + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .findByDataTest('dhis2-uicore-datatablerow') + .should('have.length', 4) + + cy.getByDataTest('data-table-column-filter-input-Gender') + .find('input') + .clear() - // check that the filter worked cy.getByDataTest('bottom-panel') - .find('.ReactVirtualized__Table__row') - .should('have.length', 1) + .findByDataTest('dhis2-uicore-tablebody') + .findByDataTest('dhis2-uicore-datatablerow') + .should('have.length', 5) + + // filter by Age in years (numeric) + cy.getByDataTest('data-table-column-filter-input-Age in years') + .find('input') + .type('<51') + + // check that the filter returned the correct number of rows + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .findByDataTest('dhis2-uicore-datatablerow') + .should('have.length', 3) + + // Sort by Age in years + cy.get('button[title="Sort by Age in years"]').click() + + // confirm that the rows are sorted by Age in years descending + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .find('tr') + .first() + .find('td') + .eq(7) + .should('contain', '44') + + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .find('tr') + .last() + .find('td') + .eq(7) + .should('contain', '6') + + // click on a row + cy.getByDataTest('bottom-panel') + .findByDataTest('dhis2-uicore-tablebody') + .findByDataTest('dhis2-uicore-datatablerow') + .first() + .click() + + // check that the org unit profile drawer is NOT opened + cy.getByDataTest('org-unit-profile').should('not.exist') }) }) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 50b4f5a19..1243879df 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -2,7 +2,7 @@ import '@dhis2/cypress-commands' import 'cypress-wait-until' Cypress.Commands.add('getByDataTest', (selector, ...args) => - cy.get(`[data-test=${selector}]`, ...args) + cy.get(`[data-test="${selector}"]`, ...args) ) Cypress.Commands.add( 'findByDataTest', diff --git a/i18n/en.pot b/i18n/en.pot index 4ae87b4de..71c7fd25c 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-01-11T09:20:59.829Z\n" -"PO-Revision-Date: 2024-01-11T09:20:59.829Z\n" +"POT-Creation-Date: 2024-02-12T09:30:18.362Z\n" +"PO-Revision-Date: 2024-02-12T09:30:18.362Z\n" msgid "Untitled map, {{date}}" msgstr "Untitled map, {{date}}" @@ -131,23 +131,23 @@ msgstr "Date" msgid "Data set" msgstr "Data set" -msgid "Index" -msgstr "Index" +msgid "No results found" +msgstr "No results found" -msgid "Org unit" -msgstr "Org unit" +msgid "Sort by {{column}}" +msgstr "Sort by {{column}}" -msgid "Id" -msgstr "Id" +msgid "Something went wrong" +msgstr "Something went wrong" -msgid "Event time" -msgstr "Event time" +msgid "Search" +msgstr "Search" -msgid "Legend" -msgstr "Legend" +msgid "Index" +msgstr "Index" -msgid "Range" -msgstr "Range" +msgid "Id" +msgstr "Id" msgid "Level" msgstr "Level" @@ -158,9 +158,27 @@ msgstr "Parent" msgid "Type" msgstr "Type" +msgid "Legend" +msgstr "Legend" + +msgid "Range" +msgstr "Range" + +msgid "Org unit" +msgstr "Org unit" + +msgid "Event time" +msgstr "Event time" + msgid "Data table is not supported when events are grouped on the server." msgstr "Data table is not supported when events are grouped on the server." +msgid "No valid data was found for the current layer configuration." +msgstr "No valid data was found for the current layer configuration." + +msgid "Data table is not supported for this layer type." +msgstr "Data table is not supported for this layer type." + msgid "Items" msgstr "Items" diff --git a/package.json b/package.json index 0678986a0..438188f05 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@krakenjs/post-robot": "^11.0.0", "@reportportal/agent-js-cypress": "git+https://github.com/dhis2/agent-js-cypress.git#develop", "@reportportal/agent-js-jest": "^5.0.7", + "@testing-library/react-hooks": "^8.0.1", "abortcontroller-polyfill": "^1.7.5", "array-move": "^4.0.0", "classnames": "^2.3.2", @@ -75,6 +76,7 @@ "react-redux": "^8.1.2", "react-sortable-hoc": "^1.11.0", "react-virtualized": "^9.22.5", + "react-virtuoso": "^4.6.2", "redux": "^4.2.1", "redux-logger": "^3.0.6", "redux-thunk": "^2.4.2", diff --git a/src/components/datatable/BottomPanel.js b/src/components/datatable/BottomPanel.js index 62160bcbf..31b2be27c 100644 --- a/src/components/datatable/BottomPanel.js +++ b/src/components/datatable/BottomPanel.js @@ -8,8 +8,9 @@ import { LAYERS_PANEL_WIDTH, RIGHT_PANEL_WIDTH, } from '../../constants/layout.js' -import DataTable from '../datatable/DataTable.js' import { useWindowDimensions } from '../WindowDimensionsProvider.js' +import DataTable from './DataTable.js' +import ErrorBoundary from './ErrorBoundary.js' import ResizeHandle from './ResizeHandle.js' import styles from './styles/BottomPanel.module.css' @@ -39,21 +40,28 @@ const BottomPanel = () => {
{i18n.t('Something went wrong')}
+