diff --git a/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts b/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts index 859cd5de355..87fe3245462 100644 --- a/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts +++ b/superset-frontend/cypress-base/cypress/e2e/dashboard_list/list.test.ts @@ -89,8 +89,10 @@ describe('Dashboards list', () => { it('should bulk select in list mode', () => { toggleBulkSelect(); - cy.get('[aria-label="Select all"]').click(); - cy.get('.ant-checkbox-input') + cy.get('th.ant-table-cell input[aria-label="Select all"]').click(); + cy.get( + '.ant-checkbox-input:not(th.ant-table-measure-cell .ant-checkbox-input)', + ) .should('be.checked') .should('have.length', 6); cy.getBySel('bulk-select-copy').contains('5 Selected'); diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index a6e1920ebf0..25cc03c8994 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -57,7 +57,7 @@ "@visx/xychart": "^3.5.1", "ag-grid-community": "34.2.0", "ag-grid-react": "34.2.0", - "antd": "^5.24.9", + "antd": "^5.26.0", "chrono-node": "^2.7.8", "classnames": "^2.2.5", "content-disposition": "^0.5.4", @@ -11204,7 +11204,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz", "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.1", "rc-util": "^5.27.0" @@ -11263,14 +11262,12 @@ } }, "node_modules/@rc-component/qrcode": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.0.0.tgz", - "integrity": "sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==", - "license": "MIT", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.0.1.tgz", + "integrity": "sha512-g8eeeaMyFXVlq8cZUeaxCDhfIYjpao0l9cvm5gFwKXy/Vm1yDWV7h2sjH5jHYzdFedlVKBpATFB1VKMrHzwaWQ==", "dependencies": { "@babel/runtime": "^7.24.7", - "classnames": "^2.3.2", - "rc-util": "^5.38.0" + "classnames": "^2.3.2" }, "engines": { "node": ">=8.x" @@ -11301,10 +11298,9 @@ } }, "node_modules/@rc-component/trigger": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.6.tgz", - "integrity": "sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==", - "license": "MIT", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.3.0.tgz", + "integrity": "sha512-iwaxZyzOuK0D7lS+0AQEtW52zUWxoGqTGkke3dRyb8pYiShmRpCjB/8TzPI4R6YySCH7Vm9BZj/31VPiiQTLBg==", "dependencies": { "@babel/runtime": "^7.23.2", "@rc-component/portal": "^1.1.0", @@ -19504,10 +19500,9 @@ } }, "node_modules/antd": { - "version": "5.25.4", - "resolved": "https://registry.npmjs.org/antd/-/antd-5.25.4.tgz", - "integrity": "sha512-yXdWqq1NJSZnD1HoPZWnWuQJGVYYnB3h0Ufsz4sbt3T0N9SdJ4G9GPpLMk8Gn9zWtwBekfR4THPVZ9uzAyhBHQ==", - "license": "MIT", + "version": "5.27.6", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.27.6.tgz", + "integrity": "sha512-70HrjVbzDXvtiUQ5MP1XdNudr/wGAk9Ivaemk6f36yrAeJurJSmZ8KngOIilolLRHdGuNc6/Vk+4T1OZpSjpag==", "dependencies": { "@ant-design/colors": "^7.2.1", "@ant-design/cssinjs": "^1.23.0", @@ -19518,9 +19513,9 @@ "@babel/runtime": "^7.26.0", "@rc-component/color-picker": "~2.0.1", "@rc-component/mutate-observer": "^1.1.0", - "@rc-component/qrcode": "~1.0.0", + "@rc-component/qrcode": "~1.0.1", "@rc-component/tour": "~1.15.1", - "@rc-component/trigger": "^2.2.6", + "@rc-component/trigger": "^2.3.0", "classnames": "^2.5.1", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.11", @@ -19545,12 +19540,12 @@ "rc-resize-observer": "^1.4.3", "rc-segmented": "~2.7.0", "rc-select": "~14.16.8", - "rc-slider": "~11.1.8", + "rc-slider": "~11.1.9", "rc-steps": "~6.0.1", "rc-switch": "~4.1.0", - "rc-table": "~7.50.5", - "rc-tabs": "~15.6.1", - "rc-textarea": "~1.10.0", + "rc-table": "~7.54.0", + "rc-tabs": "~15.7.0", + "rc-textarea": "~1.10.2", "rc-tooltip": "~6.4.0", "rc-tree": "~5.13.1", "rc-tree-select": "~5.27.0", @@ -47878,7 +47873,6 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz", "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==", - "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", "@rc-component/trigger": "^2.0.0", @@ -48202,10 +48196,9 @@ } }, "node_modules/rc-slider": { - "version": "11.1.8", - "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.8.tgz", - "integrity": "sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ==", - "license": "MIT", + "version": "11.1.9", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.9.tgz", + "integrity": "sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.5", @@ -48253,10 +48246,9 @@ } }, "node_modules/rc-table": { - "version": "7.50.5", - "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.50.5.tgz", - "integrity": "sha512-FDZu8aolhSYd3v9KOc3lZOVAU77wmRRu44R0Wfb8Oj1dXRUsloFaXMSl6f7yuWZUxArJTli7k8TEOX2mvhDl4A==", - "license": "MIT", + "version": "7.54.0", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.54.0.tgz", + "integrity": "sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/context": "^1.4.0", @@ -48274,10 +48266,9 @@ } }, "node_modules/rc-tabs": { - "version": "15.6.1", - "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.6.1.tgz", - "integrity": "sha512-/HzDV1VqOsUWyuC0c6AkxVYFjvx9+rFPKZ32ejxX0Uc7QCzcEjTA9/xMgv4HemPKwzBNX8KhGVbbumDjnj92aA==", - "license": "MIT", + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.7.0.tgz", + "integrity": "sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==", "dependencies": { "@babel/runtime": "^7.11.2", "classnames": "2.x", @@ -48296,10 +48287,9 @@ } }, "node_modules/rc-textarea": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.10.0.tgz", - "integrity": "sha512-ai9IkanNuyBS4x6sOL8qu/Ld40e6cEs6pgk93R+XLYg0mDSjNBGey6/ZpDs5+gNLD7urQ14po3V6Ck2dJLt9SA==", - "license": "MIT", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.10.2.tgz", + "integrity": "sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.1", @@ -61414,7 +61404,7 @@ "typescript": "^5.0.0" }, "peerDependencies": { - "antd": "^5.24.9", + "antd": "^5.26.0", "react": "^17.0.2" } }, @@ -64203,7 +64193,7 @@ "@types/react-loadable": "*", "@types/react-window": "^1.8.8", "@types/tinycolor2": "*", - "antd": "^5.24.9", + "antd": "^5.26.0", "nanoid": "^5.0.9", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 3e684c47fdd..8e9596c4332 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -135,7 +135,7 @@ "@visx/xychart": "^3.5.1", "ag-grid-community": "34.2.0", "ag-grid-react": "34.2.0", - "antd": "^5.24.9", + "antd": "^5.26.0", "chrono-node": "^2.7.8", "classnames": "^2.2.5", "content-disposition": "^0.5.4", diff --git a/superset-frontend/packages/superset-core/package.json b/superset-frontend/packages/superset-core/package.json index 01f14ad6cea..a7449278352 100644 --- a/superset-frontend/packages/superset-core/package.json +++ b/superset-frontend/packages/superset-core/package.json @@ -22,7 +22,7 @@ "typescript": "^5.0.0" }, "peerDependencies": { - "antd": "^5.24.9", + "antd": "^5.26.0", "react": "^17.0.2" }, "scripts": { diff --git a/superset-frontend/packages/superset-ui-core/package.json b/superset-frontend/packages/superset-ui-core/package.json index 0f173993d91..0862fe177f3 100644 --- a/superset-frontend/packages/superset-ui-core/package.json +++ b/superset-frontend/packages/superset-ui-core/package.json @@ -91,7 +91,7 @@ "timezone-mock": "1.3.6" }, "peerDependencies": { - "antd": "^5.24.9", + "antd": "^5.26.0", "@emotion/cache": "^11.4.0", "@emotion/react": "^11.4.1", "@emotion/styled": "^11.14.1", diff --git a/superset-frontend/packages/superset-ui-core/src/components/Select/AsyncSelect.test.tsx b/superset-frontend/packages/superset-ui-core/src/components/Select/AsyncSelect.test.tsx index 2a1cefd83f2..48264a05428 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/Select/AsyncSelect.test.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/Select/AsyncSelect.test.tsx @@ -513,8 +513,9 @@ test('opens the select without any data', async () => { test('displays the loading indicator when opening', async () => { render(); + userEvent.click(getSelect()); + await waitFor(async () => { - await userEvent.click(getSelect()); expect(screen.getByText(LOADING)).toBeInTheDocument(); }); expect(screen.queryByText(LOADING)).not.toBeInTheDocument(); diff --git a/superset-frontend/packages/superset-ui-core/src/components/Table/Table.test.tsx b/superset-frontend/packages/superset-ui-core/src/components/Table/Table.test.tsx index 48fe7327bbd..a99f9f8f9e8 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/Table/Table.test.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/Table/Table.test.tsx @@ -68,7 +68,11 @@ test('renders with default props', async () => { ); await waitFor(() => testColumns.forEach(column => - expect(screen.getByText(column.title as string)).toBeInTheDocument(), + expect( + screen + .getAllByText(column.title as string) + .find(el => el.closest('th')), + ).toBeInTheDocument(), ), ); testData.forEach(row => { diff --git a/superset-frontend/packages/superset-ui-core/src/components/TableCollection/TableCollection.test.tsx b/superset-frontend/packages/superset-ui-core/src/components/TableCollection/TableCollection.test.tsx index 41bb917bc06..39620b9ecdf 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/TableCollection/TableCollection.test.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/TableCollection/TableCollection.test.tsx @@ -79,8 +79,8 @@ beforeEach(() => { test('Headers should be visible', () => { render(); - expect(screen.getByText('Column 1')).toBeVisible(); - expect(screen.getByText('Column 2')).toBeVisible(); + expect(screen.getByLabelText('Column 1')).toBeVisible(); + expect(screen.getByLabelText('Column 2')).toBeVisible(); }); test('Body should be visible', () => { @@ -226,7 +226,7 @@ test('should call setSortBy when clicking sortable column header', () => { render(); // Target the nested field column (the column that needs the array-to-dot conversion) - const nestedFieldHeader = screen.getByText('Nested Field'); + const nestedFieldHeader = screen.getAllByText('Nested Field')[0]; expect(nestedFieldHeader).toBeInTheDocument(); // Click on the nested field column header to trigger sorting diff --git a/superset-frontend/packages/superset-ui-core/src/components/TableView/TableView.test.tsx b/superset-frontend/packages/superset-ui-core/src/components/TableView/TableView.test.tsx index 7d0b9a30005..60d1d2634ff 100644 --- a/superset-frontend/packages/superset-ui-core/src/components/TableView/TableView.test.tsx +++ b/superset-frontend/packages/superset-ui-core/src/components/TableView/TableView.test.tsx @@ -59,9 +59,9 @@ test('should render a table', () => { test('should render the headers', () => { render(); expect(screen.getAllByRole('columnheader')).toHaveLength(3); - expect(screen.getByText('ID')).toBeInTheDocument(); - expect(screen.getByText('Age')).toBeInTheDocument(); - expect(screen.getByText('Name')).toBeInTheDocument(); + expect(screen.getByTitle('ID')).toBeInTheDocument(); + expect(screen.getByTitle('Age')).toBeInTheDocument(); + expect(screen.getByTitle('Name')).toBeInTheDocument(); }); test('should render the rows', () => { diff --git a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx index d2af4cafece..684aef7b06e 100644 --- a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx +++ b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx @@ -111,7 +111,7 @@ describe('EstimateQueryCostButton', () => { }); test('renders estimation success result', async () => { - const { queryByText, getByText } = setup( + const { queryByText, getByText, findByTitle } = setup( {}, mockStore({ ...initialState, @@ -129,7 +129,7 @@ describe('EstimateQueryCostButton', () => { expect(queryByText('Estimate cost')).toBeInTheDocument(); fireEvent.click(getByText('Estimate cost')); - - expect(queryByText('Total cost')).toBeInTheDocument(); + const totalCostTitle = await findByTitle('Total cost'); + expect(totalCostTitle).toBeInTheDocument(); }); }); diff --git a/superset-frontend/src/components/Datasource/DatasourceModal/DatasourceModal.test.jsx b/superset-frontend/src/components/Datasource/DatasourceModal/DatasourceModal.test.jsx index 5f449cc3d29..87dd400eb36 100644 --- a/superset-frontend/src/components/Datasource/DatasourceModal/DatasourceModal.test.jsx +++ b/superset-frontend/src/components/Datasource/DatasourceModal/DatasourceModal.test.jsx @@ -125,15 +125,18 @@ describe('DatasourceModal', () => { const putSpy = jest .spyOn(SupersetClient, 'put') .mockRejectedValue(new Error('Something went wrong')); + await act(async () => { const saveButton = screen.getByTestId('datasource-modal-save'); fireEvent.click(saveButton); const okButton = await screen.findByRole('button', { name: 'OK' }); okButton.click(); }); + await act(async () => { - const errorTitle = await screen.findByText('Error saving dataset'); - expect(errorTitle).toBeInTheDocument(); + const errorElements = await screen.findAllByText('Error saving dataset'); + const errorDiv = errorElements.find(el => el.closest('div')); + expect(errorDiv).toBeInTheDocument(); }); putSpy.mockRestore(); }); diff --git a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/DatasetUsageTab.test.tsx b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/DatasetUsageTab.test.tsx index a66c2da1c1e..27da480b1db 100644 --- a/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/DatasetUsageTab.test.tsx +++ b/superset-frontend/src/components/Datasource/components/DatasourceEditor/components/DatasetUsageTab/DatasetUsageTab.test.tsx @@ -183,10 +183,23 @@ test('renders correct column headers', async () => { setupTest(); await waitFor(() => { - expect(screen.getByText('Chart')).toBeInTheDocument(); - expect(screen.getByText('Chart owners')).toBeInTheDocument(); - expect(screen.getByText('Last modified')).toBeInTheDocument(); - expect(screen.getByText('Dashboard usage')).toBeInTheDocument(); + const chartHeader = screen + .getAllByText('Chart') + .find(el => el.closest('th')); + const ownersHeader = screen + .getAllByText('Chart owners') + .find(el => el.closest('th')); + const lastModifiedHeader = screen + .getAllByText('Last modified') + .find(el => el.closest('th')); + const dashboardHeader = screen + .getAllByText('Dashboard usage') + .find(el => el.closest('th')); + + expect(chartHeader).toBeInTheDocument(); + expect(ownersHeader).toBeInTheDocument(); + expect(lastModifiedHeader).toBeInTheDocument(); + expect(dashboardHeader).toBeInTheDocument(); }); }); @@ -211,16 +224,29 @@ test('displays data in correct order (last modified desc)', async () => { test('enables sorting for Chart and Last modified columns', async () => { setupTest(); - await waitFor(() => { - const chartHeader = screen.getByText('Chart').closest('th'); - const lastModifiedHeader = screen.getByText('Last modified').closest('th'); - const ownersHeader = screen.getByText('Chart owners').closest('th'); - const dashboardHeader = screen.getByText('Dashboard usage').closest('th'); + const chartHeader = screen + .getAllByText('Chart') + .find(el => el.closest('th')) + ?.closest('th'); + + const lastModifiedHeader = screen + .getAllByText('Last modified') + .find(el => el.closest('th')) + ?.closest('th'); + + const ownersHeader = screen + .getAllByText('Chart owners') + .find(el => el.closest('th')) + ?.closest('th'); + + const dashboardHeader = screen + .getAllByText('Dashboard usage') + .find(el => el.closest('th')) + ?.closest('th'); expect(chartHeader).toHaveClass('ant-table-column-has-sorters'); expect(lastModifiedHeader).toHaveClass('ant-table-column-has-sorters'); - expect(ownersHeader).not.toHaveClass('ant-table-column-has-sorters'); expect(dashboardHeader).not.toHaveClass('ant-table-column-has-sorters'); }); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx index 9a8f2e060ab..af57bee7597 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx @@ -340,8 +340,8 @@ test('validates the pre-filter value', async () => { try { defaultRender(); - await userEvent.click(screen.getByText(FILTER_SETTINGS_REGEX)); - await userEvent.click(getCheckbox(PRE_FILTER_REGEX)); + userEvent.click(screen.getByText(FILTER_SETTINGS_REGEX)); + userEvent.click(getCheckbox(PRE_FILTER_REGEX)); jest.runAllTimers(); } finally { diff --git a/superset-frontend/src/explore/components/controls/ControlPopover/ControlPopover.tsx b/superset-frontend/src/explore/components/controls/ControlPopover/ControlPopover.tsx index 75159b6c7c7..6837e350d5b 100644 --- a/superset-frontend/src/explore/components/controls/ControlPopover/ControlPopover.tsx +++ b/superset-frontend/src/explore/components/controls/ControlPopover/ControlPopover.tsx @@ -57,7 +57,6 @@ const ControlPopover: FC = ({ ...props }) => { const triggerElementRef = useRef(); - const [visible, setVisible] = useState( visibleProp === undefined ? props.defaultOpen : visibleProp, ); @@ -65,7 +64,7 @@ const ControlPopover: FC = ({ React.useState(initialPlacement); const calculatePlacement = useCallback(() => { - if (!triggerElementRef.current) return; + if (!triggerElementRef.current || !visible) return; const { yRatio, xRatio } = getVisibilityRatio(triggerElementRef.current); @@ -87,10 +86,10 @@ const ControlPopover: FC = ({ if (newPlacement !== placement) { setPlacement(newPlacement); } - }, [getVisibilityRatio]); + }, [getVisibilityRatio, visible, placement]); const changeContainerScrollStatus = useCallback( - visible => { + (visible: boolean | undefined) => { const element = getSectionContainerElement(); if (element) { element.style.setProperty( @@ -106,7 +105,6 @@ const ControlPopover: FC = ({ const handleGetPopupContainer = useCallback( (triggerNode: HTMLElement) => { triggerElementRef.current = triggerNode; - return getPopupContainer?.(triggerNode) || document.body; }, [calculatePlacement, getPopupContainer], @@ -117,7 +115,6 @@ const ControlPopover: FC = ({ if (visible === undefined) { changeContainerScrollStatus(visible); } - setVisible(!!visible); props.onOpenChange?.(!!visible); }, @@ -133,6 +130,14 @@ const ControlPopover: FC = ({ }, [props], ); + const handleAfterOpenChange = useCallback( + (open: boolean) => { + if (open) { + calculatePlacement(); + } + }, + [calculatePlacement], + ); useEffect(() => { if (visibleProp !== undefined) { @@ -157,9 +162,34 @@ const ControlPopover: FC = ({ }, [handleDocumentKeyDownListener, visible]); useEffect(() => { - if (visible) { - calculatePlacement(); - } + if (!visible || !triggerElementRef.current) return () => {}; + + const resizeObserver = new ResizeObserver(() => { + requestAnimationFrame(() => { + calculatePlacement(); + }); + }); + + const intersectionObserver = new IntersectionObserver( + entries => { + entries.forEach(entry => { + if (entry.isIntersecting) { + calculatePlacement(); + } + }); + }, + { threshold: [0, 0.25, 0.5, 0.75, 1] }, + ); + + resizeObserver.observe( + triggerElementRef.current.parentElement || document.body, + ); + intersectionObserver.observe(triggerElementRef.current); + + return () => { + resizeObserver.disconnect(); + intersectionObserver.disconnect(); + }; }, [visible, calculatePlacement]); return ( @@ -171,6 +201,7 @@ const ControlPopover: FC = ({ onOpenChange={handleOnVisibleChange} getPopupContainer={handleGetPopupContainer} destroyTooltipOnHide={destroyTooltipOnHide} + afterOpenChange={handleAfterOpenChange} /> ); }; diff --git a/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.test.tsx b/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.test.tsx index cbcc35b88d4..fbd6c5ea9af 100644 --- a/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.test.tsx +++ b/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.test.tsx @@ -140,12 +140,12 @@ describe('DatasetPanel', () => { }, ); expect(await screen.findByText(tableName)).toBeVisible(); - expect(screen.getByText(COLUMN_TITLE)).toBeVisible(); + expect(screen.getByTitle(COLUMN_TITLE)).toBeVisible(); expect( - screen.getByText(tableColumnDefinition[0].title as string), + screen.getByLabelText(tableColumnDefinition[0].title as string), ).toBeInTheDocument(); expect( - screen.getByText(tableColumnDefinition[1].title as string), + screen.getByLabelText(tableColumnDefinition[1].title as string), ).toBeInTheDocument(); exampleColumns.forEach(row => { expect(screen.getByText(row.name)).toBeInTheDocument(); diff --git a/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.tsx b/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.tsx index 8655cf84508..c1f33053568 100644 --- a/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.tsx +++ b/superset-frontend/src/features/datasets/AddDataset/DatasetPanel/DatasetPanel.tsx @@ -277,7 +277,7 @@ const DatasetPanel = ({ if (!loading && tableName && hasColumns && !hasError) { component = ( <> - {COLUMN_TITLE} + {COLUMN_TITLE} {tableWithDataset ? ( diff --git a/superset-frontend/src/features/roles/RoleListEditModal.test.tsx b/superset-frontend/src/features/roles/RoleListEditModal.test.tsx index 7e9bfb6e791..2c4d88dc9b3 100644 --- a/superset-frontend/src/features/roles/RoleListEditModal.test.tsx +++ b/superset-frontend/src/features/roles/RoleListEditModal.test.tsx @@ -203,9 +203,9 @@ describe('RoleListEditModal', () => { const usersTab = screen.getByRole('tab', { name: 'Users' }); fireEvent.click(usersTab); - expect(screen.getByText('First Name')).toBeInTheDocument(); - expect(screen.getByText('Last Name')).toBeInTheDocument(); - expect(screen.getByText('User Name')).toBeInTheDocument(); - expect(screen.getByText('Email')).toBeInTheDocument(); + expect(screen.getByTitle('First Name')).toBeInTheDocument(); + expect(screen.getByTitle('Last Name')).toBeInTheDocument(); + expect(screen.getByTitle('User Name')).toBeInTheDocument(); + expect(screen.getByTitle('Email')).toBeInTheDocument(); }); }); diff --git a/superset-frontend/src/pages/AlertReportList/AlertReportList.test.jsx b/superset-frontend/src/pages/AlertReportList/AlertReportList.test.jsx index 1748afe028c..6aab05ec375 100644 --- a/superset-frontend/src/pages/AlertReportList/AlertReportList.test.jsx +++ b/superset-frontend/src/pages/AlertReportList/AlertReportList.test.jsx @@ -247,31 +247,31 @@ describe('AlertList', () => { renderAlertList(); await screen.findByTestId('alerts-list-view'); - expect(screen.getByText('Last run')).toBeInTheDocument(); + expect(screen.getByTitle('Last run')).toBeInTheDocument(); expect( screen.getByRole('columnheader', { name: /name/i }), ).toBeInTheDocument(); - expect(screen.getByText('Schedule')).toBeInTheDocument(); - expect(screen.getByText('Notification method')).toBeInTheDocument(); - expect(screen.getByText('Owners')).toBeInTheDocument(); - expect(screen.getByText('Last modified')).toBeInTheDocument(); - expect(screen.getByText('Active')).toBeInTheDocument(); - expect(screen.getByText('Actions')).toBeInTheDocument(); + expect(screen.getByTitle('Schedule')).toBeInTheDocument(); + expect(screen.getByTitle('Notification method')).toBeInTheDocument(); + expect(screen.getByTitle('Owners')).toBeInTheDocument(); + expect(screen.getByTitle('Last modified')).toBeInTheDocument(); + expect(screen.getByTitle('Active')).toBeInTheDocument(); + expect(screen.getByTitle('Actions')).toBeInTheDocument(); }, 15000); test('renders correct column headers for reports', async () => { renderAlertList({ isReportEnabled: true }); await screen.findByTestId('alerts-list-view'); - expect(screen.getByText('Last run')).toBeInTheDocument(); + expect(screen.getByTitle('Last run')).toBeInTheDocument(); expect( screen.getByRole('columnheader', { name: /name/i }), ).toBeInTheDocument(); - expect(screen.getByText('Schedule')).toBeInTheDocument(); - expect(screen.getByText('Notification method')).toBeInTheDocument(); - expect(screen.getByText('Owners')).toBeInTheDocument(); - expect(screen.getByText('Last modified')).toBeInTheDocument(); - expect(screen.getByText('Active')).toBeInTheDocument(); - expect(screen.getByText('Actions')).toBeInTheDocument(); + expect(screen.getByTitle('Schedule')).toBeInTheDocument(); + expect(screen.getByTitle('Notification method')).toBeInTheDocument(); + expect(screen.getByTitle('Owners')).toBeInTheDocument(); + expect(screen.getByTitle('Last modified')).toBeInTheDocument(); + expect(screen.getByTitle('Active')).toBeInTheDocument(); + expect(screen.getByTitle('Actions')).toBeInTheDocument(); }, 15000); }); diff --git a/superset-frontend/src/pages/ChartList/ChartList.listview.test.tsx b/superset-frontend/src/pages/ChartList/ChartList.listview.test.tsx index 6589b77ecfb..65264947b16 100644 --- a/superset-frontend/src/pages/ChartList/ChartList.listview.test.tsx +++ b/superset-frontend/src/pages/ChartList/ChartList.listview.test.tsx @@ -219,7 +219,7 @@ describe('ChartList - List View Tests', () => { // Verify all expected headers are present expectedHeaders.forEach(headerText => { - expect(within(table).getByText(headerText)).toBeInTheDocument(); + expect(within(table).getByTitle(headerText)).toBeInTheDocument(); }); }); @@ -231,11 +231,15 @@ describe('ChartList - List View Tests', () => { }); const table = screen.getByTestId('listview-table'); - const sortableHeaders = table.querySelectorAll('.ant-table-column-sorters'); + const allHeaders = table.querySelectorAll('.ant-table-column-sorters'); + + const sortableHeaders = Array.from(allHeaders).filter( + header => !header.closest('.ant-table-measure-cell-content'), + ); expect(sortableHeaders).toHaveLength(3); - const nameHeader = within(table).getByText('Name'); + const nameHeader = within(table).getByTitle('Name'); fireEvent.click(nameHeader); await waitFor(() => { @@ -248,7 +252,7 @@ describe('ChartList - List View Tests', () => { expect(sortCalls).toHaveLength(1); }); - const typeHeader = within(table).getByText('Type'); + const typeHeader = within(table).getByTitle('Type'); fireEvent.click(typeHeader); await waitFor(() => { @@ -261,7 +265,7 @@ describe('ChartList - List View Tests', () => { expect(typeSortCalls).toHaveLength(1); }); - const lastModifiedHeader = within(table).getByText('Last modified'); + const lastModifiedHeader = within(table).getByTitle('Last modified'); fireEvent.click(lastModifiedHeader); await waitFor(() => { @@ -602,7 +606,7 @@ describe('ChartList - List View Tests', () => { const testChart = mockCharts[0]; const table = screen.getByTestId('listview-table'); - expect(within(table).getByText('Tags')).toBeInTheDocument(); + expect(within(table).getByTitle('Tags')).toBeInTheDocument(); await waitFor(() => { expect(within(table).getByText(testChart.slice_name)).toBeInTheDocument(); @@ -651,7 +655,7 @@ describe('ChartList - List View Tests', () => { }); // Use the header checkbox to select all - const selectAllCheckbox = screen.getByLabelText('Select all'); + const selectAllCheckbox = screen.getAllByLabelText('Select all')[0]; expect(selectAllCheckbox).not.toBeChecked(); fireEvent.click(selectAllCheckbox); @@ -714,7 +718,7 @@ describe('ChartList - List View Tests', () => { }); // Use select all to select multiple charts - const selectAllCheckbox = screen.getByLabelText('Select all'); + const selectAllCheckbox = screen.getAllByLabelText('Select all')[0]; fireEvent.click(selectAllCheckbox); await waitFor(() => { @@ -763,7 +767,7 @@ describe('ChartList - List View Tests', () => { }); // Use select all to select multiple charts - const selectAllCheckbox = screen.getByLabelText('Select all'); + const selectAllCheckbox = screen.getAllByLabelText('Select all')[0]; fireEvent.click(selectAllCheckbox); await waitFor(() => { diff --git a/superset-frontend/src/pages/ChartList/ChartList.permissions.test.tsx b/superset-frontend/src/pages/ChartList/ChartList.permissions.test.tsx index bb3445de615..d1730538e67 100644 --- a/superset-frontend/src/pages/ChartList/ChartList.permissions.test.tsx +++ b/superset-frontend/src/pages/ChartList/ChartList.permissions.test.tsx @@ -199,7 +199,7 @@ describe('ChartList - Permission-based UI Tests', () => { expect(screen.getByTestId('bulk-select')).toBeInTheDocument(); // Verify Actions column is visible - expect(screen.getByText('Actions')).toBeInTheDocument(); + expect(screen.getByTitle('Actions')).toBeInTheDocument(); // Verify favorite stars are rendered for each chart const favoriteStars = screen.getAllByTestId('fave-unfave-icon'); @@ -231,7 +231,7 @@ describe('ChartList - Permission-based UI Tests', () => { await renderWithPermissions(PERMISSIONS.ADMIN); await screen.findByTestId('chart-list-view'); - expect(screen.getByText('Actions')).toBeInTheDocument(); + expect(screen.getByTitle('Actions')).toBeInTheDocument(); // Wait for table to load with charts data await waitFor(() => { @@ -264,7 +264,7 @@ describe('ChartList - Permission-based UI Tests', () => { await renderWithPermissions(PERMISSIONS.WRITE_ONLY); await screen.findByTestId('chart-list-view'); - expect(screen.getByText('Actions')).toBeInTheDocument(); + expect(screen.getByTitle('Actions')).toBeInTheDocument(); // Wait for table to load with charts data await waitFor(() => { @@ -297,7 +297,7 @@ describe('ChartList - Permission-based UI Tests', () => { await renderWithPermissions(PERMISSIONS.ADMIN, 1, { tagging: true }); await screen.findByTestId('chart-list-view'); - expect(screen.getByText('Tags')).toBeInTheDocument(); + expect(screen.getByTitle('Tags')).toBeInTheDocument(); }); test('hides Tags column when TAGGING_SYSTEM feature flag is disabled', async () => { @@ -311,7 +311,7 @@ describe('ChartList - Permission-based UI Tests', () => { await renderWithPermissions(PERMISSIONS.READ_ONLY, 1, { tagging: true }); await screen.findByTestId('chart-list-view'); - expect(screen.getByText('Tags')).toBeInTheDocument(); + expect(screen.getByTitle('Tags')).toBeInTheDocument(); }); test('shows bulk select button for users with admin permissions', async () => { @@ -383,7 +383,7 @@ describe('ChartList - Permission-based UI Tests', () => { await screen.findByTestId('chart-list-view'); // Actions column should be visible - expect(screen.getByText('Actions')).toBeInTheDocument(); + expect(screen.getByTitle('Actions')).toBeInTheDocument(); // Wait for table to load with charts data await waitFor(() => { @@ -418,7 +418,7 @@ describe('ChartList - Permission-based UI Tests', () => { await screen.findByTestId('chart-list-view'); // Actions column should be visible (requires can_write) - expect(screen.getByText('Actions')).toBeInTheDocument(); + expect(screen.getByTitle('Actions')).toBeInTheDocument(); // Wait for table to load await waitFor(() => { @@ -441,7 +441,7 @@ describe('ChartList - Permission-based UI Tests', () => { expect(favoriteStars).toHaveLength(mockCharts.length); // Tags column should be visible (feature flag enabled) - expect(screen.getByText('Tags')).toBeInTheDocument(); + expect(screen.getByTitle('Tags')).toBeInTheDocument(); // Bulk select should be visible (user has can_export) expect(screen.getByTestId('bulk-select')).toBeInTheDocument(); @@ -461,8 +461,8 @@ describe('ChartList - Permission-based UI Tests', () => { await screen.findByTestId('chart-list-view'); // All permission-based elements should be hidden - expect(screen.queryByText('Actions')).not.toBeInTheDocument(); - expect(screen.queryByText('Tags')).not.toBeInTheDocument(); + expect(screen.queryByTitle('Actions')).not.toBeInTheDocument(); + expect(screen.queryByTitle('Tags')).not.toBeInTheDocument(); expect(screen.queryByTestId('bulk-select')).not.toBeInTheDocument(); expect( screen.queryByRole('button', { name: /chart/i }), diff --git a/superset-frontend/src/pages/GroupsList/GroupsList.test.tsx b/superset-frontend/src/pages/GroupsList/GroupsList.test.tsx index 78570133f92..abcfe984655 100644 --- a/superset-frontend/src/pages/GroupsList/GroupsList.test.tsx +++ b/superset-frontend/src/pages/GroupsList/GroupsList.test.tsx @@ -110,7 +110,7 @@ describe('GroupsList', () => { test('renders actions column for admin', async () => { await renderComponent(); - expect(screen.getByText('Actions')).toBeInTheDocument(); + expect(screen.getAllByText('Actions')[0]).toBeInTheDocument(); }); test('renders the filters correctly', async () => { @@ -128,9 +128,9 @@ describe('GroupsList', () => { await renderComponent(); const table = screen.getByRole('table'); - expect(await within(table).findByText('Name')).toBeInTheDocument(); - expect(await within(table).findByText('Label')).toBeInTheDocument(); - expect(await within(table).findByText('Roles')).toBeInTheDocument(); + expect(await within(table).findByTitle('Name')).toBeInTheDocument(); + expect(await within(table).findByTitle('Label')).toBeInTheDocument(); + expect(await within(table).findByTitle('Roles')).toBeInTheDocument(); }); test('opens add group modal on button click', async () => { diff --git a/superset-frontend/src/pages/RolesList/RolesList.test.tsx b/superset-frontend/src/pages/RolesList/RolesList.test.tsx index 92106fc1adb..6990fe8bda1 100644 --- a/superset-frontend/src/pages/RolesList/RolesList.test.tsx +++ b/superset-frontend/src/pages/RolesList/RolesList.test.tsx @@ -159,8 +159,8 @@ describe('RolesList', () => { const table = screen.getByRole('table'); expect(table).toBeInTheDocument(); - const nameColumn = await within(table).findByText('Name'); - const actionsColumn = await within(table).findByText('Actions'); + const nameColumn = await within(table).findByTitle('Name'); + const actionsColumn = await within(table).findByTitle('Actions'); expect(nameColumn).toBeInTheDocument(); expect(actionsColumn).toBeInTheDocument(); diff --git a/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx b/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx index bd6ae6fc320..fefb58cec19 100644 --- a/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx +++ b/superset-frontend/src/pages/RowLevelSecurityList/RowLevelSecurityList.test.tsx @@ -174,12 +174,12 @@ describe('RuleList RTL', () => { const table = screen.getByRole('table'); expect(table).toBeInTheDocument(); - const nameColumn = await within(table).findByText('Name'); - const filterTypeColumn = await within(table).findByText('Filter Type'); - const groupKeyColumn = await within(table).findByText('Group Key'); - const clauseColumn = await within(table).findByText('Clause'); - const modifiedColumn = await within(table).findByText('Last modified'); - const actionsColumn = await within(table).findByText('Actions'); + const nameColumn = await within(table).findByTitle('Name'); + const filterTypeColumn = await within(table).findByTitle('Filter Type'); + const groupKeyColumn = await within(table).findByTitle('Group Key'); + const clauseColumn = await within(table).findByTitle('Clause'); + const modifiedColumn = await within(table).findByTitle('Last modified'); + const actionsColumn = await within(table).findByTitle('Actions'); expect(nameColumn).toBeInTheDocument(); expect(filterTypeColumn).toBeInTheDocument(); diff --git a/superset-frontend/src/pages/UsersList/UsersList.test.tsx b/superset-frontend/src/pages/UsersList/UsersList.test.tsx index 2f9d56c7926..5edc25b62d4 100644 --- a/superset-frontend/src/pages/UsersList/UsersList.test.tsx +++ b/superset-frontend/src/pages/UsersList/UsersList.test.tsx @@ -152,13 +152,13 @@ describe('UsersList', () => { const table = screen.getByRole('table'); expect(table).toBeInTheDocument(); - const fnameColumn = await within(table).findByText('First name'); - const lnameColumn = await within(table).findByText('Last name'); - const usernameColumn = await within(table).findByText('Username'); - const emailColumn = await within(table).findByText('Email'); - const rolesColumn = await within(table).findByText('Roles'); - const actionsColumn = await within(table).findByText('Actions'); - const activeColumn = await within(table).findByText('Is active?'); + const fnameColumn = await within(table).findByTitle('First name'); + const lnameColumn = await within(table).findByTitle('Last name'); + const usernameColumn = await within(table).findByTitle('Username'); + const emailColumn = await within(table).findByTitle('Email'); + const rolesColumn = await within(table).findByTitle('Roles'); + const actionsColumn = await within(table).findByTitle('Actions'); + const activeColumn = await within(table).findByTitle('Is active?'); expect(fnameColumn).toBeInTheDocument(); expect(lnameColumn).toBeInTheDocument(); diff --git a/superset-frontend/src/visualizations/TimeTable/TimeTable.test.tsx b/superset-frontend/src/visualizations/TimeTable/TimeTable.test.tsx index c4f1d59fedf..b973e3b2fe0 100644 --- a/superset-frontend/src/visualizations/TimeTable/TimeTable.test.tsx +++ b/superset-frontend/src/visualizations/TimeTable/TimeTable.test.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { render, screen } from '@superset-ui/core/spec'; +import { render, screen, within } from '@superset-ui/core/spec'; import TimeTable from './TimeTable'; const mockData = { @@ -80,10 +80,25 @@ test('should render TimeTable component', () => { test('should render table headers', () => { render(); - expect(screen.getByText('Metric')).toBeInTheDocument(); - expect(screen.getByText('Time series columns')).toBeInTheDocument(); -}); + const table = screen.getByRole('table'); + + const metricHeader = within(table).getByTitle('Metric'); + expect(metricHeader).toBeInTheDocument(); + + const allTimeSeriesHeaders = within(table).getAllByText( + 'Time series columns', + ); + + const visibleTimeSeriesHeaders = allTimeSeriesHeaders.filter( + el => !el.closest('.ant-table-measure-cell-content'), + ); + + expect(visibleTimeSeriesHeaders.length).toBe(1); + visibleTimeSeriesHeaders.forEach(header => { + expect(header).toBeInTheDocument(); + }); +}); test('should render table with data rows', () => { render(); @@ -151,11 +166,23 @@ test('should render with multiple metrics', () => { test('should handle column type sparkline correctly', () => { render(); - const columnHeaders = screen.getAllByRole('columnheader'); + const table = screen.getByRole('table'); + expect(table).toBeInTheDocument(); - expect(screen.getByRole('table')).toBeInTheDocument(); + const columnHeaders = screen.getAllByRole('columnheader'); expect(columnHeaders).toHaveLength(2); - expect(screen.getByText('Time series columns')).toBeInTheDocument(); + + const allTimeSeriesElements = within(table).getAllByText( + 'Time series columns', + ); + const visibleTimeSeriesColumns = allTimeSeriesElements.filter( + el => !el.closest('.ant-table-measure-cell-content'), + ); + + expect(visibleTimeSeriesColumns.length).toBeGreaterThan(0); + visibleTimeSeriesColumns.forEach(el => { + expect(el).toBeInTheDocument(); + }); }); test('should not render empty table due to missing column id property', () => {