diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/datanodes/Datanodes.test.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/datanodes/Datanodes.test.tsx index a169e1ce34da..4ba0486db3dd 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/datanodes/Datanodes.test.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/datanodes/Datanodes.test.tsx @@ -28,9 +28,9 @@ import { vi } from 'vitest'; import Datanodes from '@/v2/pages/datanodes/datanodes'; import * as commonUtils from '@/utils/common'; -import { datanodeServer } from '@/__tests__/mocks/datanodeMocks/datanodeServer'; -import { datanodeLocators, searchInputLocator } from '@/__tests__/locators/locators'; -import { waitForDNTable } from '@/__tests__/utils/datanodes.utils'; +import { datanodeServer } from '@tests/mocks/datanodeMocks/datanodeServer'; +import { datanodeLocators, searchInputLocator } from '@tests//locators/locators'; +import { waitForDNTable } from '@tests/utils/datanodes.utils'; // Mock utility functions vi.spyOn(commonUtils, 'showDataFetchError'); diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/datanodes/DatanodesTable.test.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/datanodes/DatanodesTable.test.tsx index f1be5362ec1d..555ab850e82d 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/datanodes/DatanodesTable.test.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/datanodes/DatanodesTable.test.tsx @@ -27,8 +27,8 @@ import { import { DatanodeTableProps } from '@/v2/types/datanode.types'; import DatanodesTable from '@/v2/components/tables/datanodesTable'; -import { datanodeServer } from '@/__tests__/mocks/datanodeMocks/datanodeServer'; -import { waitForDNTable } from '@/__tests__/utils/datanodes.utils'; +import { datanodeServer } from '@tests/mocks/datanodeMocks/datanodeServer'; +import { waitForDNTable } from '@tests/utils/datanodes.utils'; const defaultProps: DatanodeTableProps = { loading: false, diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/locators/locators.ts b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/locators/locators.ts index 83b2bc507747..b80eebdb3717 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/locators/locators.ts +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/locators/locators.ts @@ -48,6 +48,12 @@ export const datanodeLocators = { datanodeTableRow: (uuid: string) => `dntable-${uuid}` } +export const pipelineLocators = { + 'pipelineTable': 'pipelines-table', + 'pipelineRowRegex': /pipelinetable-/, + pipelineTableRow: (uuid: string) => `pipelinetable-${uuid}` +} + export const autoReloadPanelLocators = { 'autoreloadPanel': 'autoreload-panel', 'refreshButton': 'autoreload-panel-refresh', diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/mocks/pipelineMocks/pipelineResponseMocks.ts b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/mocks/pipelineMocks/pipelineResponseMocks.ts new file mode 100644 index 000000000000..e4077df78a19 --- /dev/null +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/mocks/pipelineMocks/pipelineResponseMocks.ts @@ -0,0 +1,532 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const PipelinesResponse = { + "totalCount": 6, + "pipelines": [ + { + "pipelineId": "pipeline-1", + "status": "OPEN", + "leaderNode": "ozone-datanode-1.ozone_default", + "datanodes": [ + { + "level": 3, + "parent": null, + "cost": 0, + "uuid": "a5421a9b-ac50-4242-8d0e-a528e40da8c1", + "uuidString": "a5421a9b-ac50-4242-8d0e-a528e40da8c1", + "ipAddress": "0.0.0.0", + "hostName": "ozone-datanode-1.ozone_default", + "ports": [ + { + "name": "HTTP", + "value": 9882 + }, + { + "name": "CLIENT_RPC", + "value": 19864 + }, + { + "name": "REPLICATION", + "value": 9886 + }, + { + "name": "RATIS", + "value": 9858 + }, + { + "name": "RATIS_ADMIN", + "value": 9857 + }, + { + "name": "RATIS_SERVER", + "value": 9856 + }, + { + "name": "RATIS_DATASTREAM", + "value": 9855 + }, + { + "name": "STANDALONE", + "value": 9859 + } + ], + "certSerialId": null, + "version": null, + "setupTime": 0, + "revision": null, + "persistedOpState": "IN_SERVICE", + "persistedOpStateExpiryEpochSec": 0, + "initialVersion": 0, + "currentVersion": 2, + "ratisPort": { + "name": "RATIS", + "value": 9858 + }, + "decommissioned": false, + "maintenance": false, + "ipAddressAsByteString": { + "string": "0.0.0.0", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "hostNameAsByteString": { + "string": "ozone-datanode-1.ozone_default", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "restPort": null, + "standalonePort": { + "name": "STANDALONE", + "value": 9859 + }, + "networkName": "mock-network-1", + "networkLocation": "/default-rack", + "networkLocationAsByteString": { + "string": "/default-rack", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "networkNameAsByteString": { + "string": "mock-network-1", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "networkFullPath": "/default-rack/mock-network-1", + "numOfLeaves": 1 + } + ], + "lastLeaderElection": 0, + "duration": 2700000, + "leaderElections": 0, + "replicationType": "RATIS", + "replicationFactor": "THREE", + "containers": 0 + }, + { + "pipelineId": "pipeline-2", + "status": "CLOSED", + "leaderNode": "ozone-datanode-2.ozone_default", + "datanodes": [ + { + "level": 3, + "parent": null, + "cost": 0, + "uuid": "c8254ecb-6c24-4c54-9834-76f3e74fd37a", + "uuidString": "c8254ecb-6c24-4c54-9834-76f3e74fd37a", + "ipAddress": "0.0.0.1", + "hostName": "ozone-datanode-2.ozone_default", + "ports": [ + { + "name": "HTTP", + "value": 9882 + }, + { + "name": "CLIENT_RPC", + "value": 19864 + }, + { + "name": "REPLICATION", + "value": 9886 + }, + { + "name": "RATIS", + "value": 9858 + }, + { + "name": "RATIS_ADMIN", + "value": 9857 + }, + { + "name": "RATIS_SERVER", + "value": 9856 + }, + { + "name": "RATIS_DATASTREAM", + "value": 9855 + }, + { + "name": "STANDALONE", + "value": 9859 + } + ], + "certSerialId": null, + "version": null, + "setupTime": 0, + "revision": null, + "persistedOpState": "IN_SERVICE", + "persistedOpStateExpiryEpochSec": 0, + "initialVersion": 0, + "currentVersion": 2, + "ratisPort": { + "name": "RATIS", + "value": 9858 + }, + "decommissioned": false, + "maintenance": false, + "ipAddressAsByteString": { + "string": "0.0.0.1", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "hostNameAsByteString": { + "string": "ozone-datanode-2.ozone_default", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "restPort": null, + "standalonePort": { + "name": "STANDALONE", + "value": 9859 + }, + "networkName": "mock-network-2", + "networkLocation": "/default-rack", + "networkLocationAsByteString": { + "string": "/default-rack", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "networkNameAsByteString": { + "string": "mock-network-2", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "networkFullPath": "/default-rack/mock-network-2", + "numOfLeaves": 1 + } + ], + "lastLeaderElection": 0, + "duration": 2700000, + "leaderElections": 0, + "replicationType": "RATIS", + "replicationFactor": "ONE", + "containers": 1 + }, + { + "pipelineId": "pipeline-3", + "status": "OPEN", + "leaderNode": "ozone-datanode-3.ozone_default", + "datanodes": [ + { + "level": 3, + "parent": null, + "cost": 0, + "uuid": "b557edd1-cce3-4320-834b-2e6f13ba98f1", + "uuidString": "b557edd1-cce3-4320-834b-2e6f13ba98f1", + "ipAddress": "0.0.0.2", + "hostName": "ozone-datanode-3.ozone_default", + "ports": [ + { + "name": "HTTP", + "value": 9882 + }, + { + "name": "CLIENT_RPC", + "value": 19864 + }, + { + "name": "REPLICATION", + "value": 9886 + }, + { + "name": "RATIS", + "value": 9858 + }, + { + "name": "RATIS_ADMIN", + "value": 9857 + }, + { + "name": "RATIS_SERVER", + "value": 9856 + }, + { + "name": "RATIS_DATASTREAM", + "value": 9855 + }, + { + "name": "STANDALONE", + "value": 9859 + } + ], + "certSerialId": null, + "version": null, + "setupTime": 0, + "revision": null, + "persistedOpState": "IN_SERVICE", + "persistedOpStateExpiryEpochSec": 0, + "initialVersion": 0, + "currentVersion": 2, + "ratisPort": { + "name": "RATIS", + "value": 9858 + }, + "decommissioned": false, + "maintenance": false, + "ipAddressAsByteString": { + "string": "0.0.0.2", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "hostNameAsByteString": { + "string": "ozone-datanode-3.ozone_default", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "restPort": null, + "standalonePort": { + "name": "STANDALONE", + "value": 9859 + }, + "networkName": "mock-network-3", + "networkLocation": "/default-rack", + "networkLocationAsByteString": { + "string": "/default-rack", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "networkNameAsByteString": { + "string": "mock-network-3", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "networkFullPath": "/default-rack/mock-network-3", + "numOfLeaves": 1 + }, + { + "level": 3, + "parent": null, + "cost": 0, + "uuid": "c8254ecb-6c24-4c54-9834-76f3e74fd37a", + "uuidString": "c8254ecb-6c24-4c54-9834-76f3e74fd37a", + "ipAddress": "0.0.0.1", + "hostName": "ozone-datanode-2.ozone_default", + "ports": [ + { + "name": "HTTP", + "value": 9882 + }, + { + "name": "CLIENT_RPC", + "value": 19864 + }, + { + "name": "REPLICATION", + "value": 9886 + }, + { + "name": "RATIS", + "value": 9858 + }, + { + "name": "RATIS_ADMIN", + "value": 9857 + }, + { + "name": "RATIS_SERVER", + "value": 9856 + }, + { + "name": "RATIS_DATASTREAM", + "value": 9855 + }, + { + "name": "STANDALONE", + "value": 9859 + } + ], + "certSerialId": null, + "version": null, + "setupTime": 0, + "revision": null, + "persistedOpState": "IN_SERVICE", + "persistedOpStateExpiryEpochSec": 0, + "initialVersion": 0, + "currentVersion": 2, + "ratisPort": { + "name": "RATIS", + "value": 9858 + }, + "decommissioned": false, + "maintenance": false, + "ipAddressAsByteString": { + "string": "0.0.0.4", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "hostNameAsByteString": { + "string": "ozone-datanode-2.ozone_default", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "restPort": null, + "standalonePort": { + "name": "STANDALONE", + "value": 9859 + }, + "networkName": "mock-network-2", + "networkLocation": "/default-rack", + "networkLocationAsByteString": { + "string": "/default-rack", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "networkNameAsByteString": { + "string": "mock-network-2", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "networkFullPath": "/default-rack/mock-network-2", + "numOfLeaves": 1 + }, + { + "level": 3, + "parent": null, + "cost": 0, + "uuid": "fc26783d-0ecf-43fe-9915-a76d6e276e39", + "uuidString": "fc26783d-0ecf-43fe-9915-a76d6e276e39", + "ipAddress": "0.0.0.3", + "hostName": "ozone-datanode-4.ozone_default", + "ports": [ + { + "name": "HTTP", + "value": 9882 + }, + { + "name": "CLIENT_RPC", + "value": 19864 + }, + { + "name": "REPLICATION", + "value": 9886 + }, + { + "name": "RATIS", + "value": 9858 + }, + { + "name": "RATIS_ADMIN", + "value": 9857 + }, + { + "name": "RATIS_SERVER", + "value": 9856 + }, + { + "name": "RATIS_DATASTREAM", + "value": 9855 + }, + { + "name": "STANDALONE", + "value": 9859 + } + ], + "certSerialId": null, + "version": null, + "setupTime": 0, + "revision": null, + "persistedOpState": "IN_SERVICE", + "persistedOpStateExpiryEpochSec": 0, + "initialVersion": 0, + "currentVersion": 2, + "ratisPort": { + "name": "RATIS", + "value": 9858 + }, + "decommissioned": false, + "maintenance": false, + "ipAddressAsByteString": { + "string": "0.0.0.3", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "hostNameAsByteString": { + "string": "ozone-datanode-4.ozone_default", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "restPort": null, + "standalonePort": { + "name": "STANDALONE", + "value": 9859 + }, + "networkName": "mock-network-4", + "networkLocation": "/default-rack", + "networkLocationAsByteString": { + "string": "/default-rack", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "networkNameAsByteString": { + "string": "mock-network-4", + "bytes": { + "validUtf8": true, + "empty": false + } + }, + "networkFullPath": "/default-rack/mock-network-4", + "numOfLeaves": 1 + } + ], + "lastLeaderElection": 0, + "duration": 3600000, + "leaderElections": 0, + "replicationType": "RATIS", + "replicationFactor": "THREE", + "containers": 0 + } + ] +} diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/mocks/pipelineMocks/pipelinesServer.ts b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/mocks/pipelineMocks/pipelinesServer.ts new file mode 100644 index 000000000000..1b2c48484ed5 --- /dev/null +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/mocks/pipelineMocks/pipelinesServer.ts @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { setupServer } from "msw/node"; +import { rest } from "msw"; + +import * as mockResponses from "./pipelineResponseMocks"; + +const handlers = [ + rest.get("api/v1/pipelines", (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json(mockResponses.PipelinesResponse) + ); + }) +]; + + +//This will configure a request mocking server using MSW +export const pipelineServer = setupServer(...handlers); diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/pipelines/Pipelines.test.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/pipelines/Pipelines.test.tsx new file mode 100644 index 000000000000..f703ec4102cb --- /dev/null +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/pipelines/Pipelines.test.tsx @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { + render, + screen, + waitFor, + fireEvent +} from '@testing-library/react'; +import { rest } from 'msw'; +import { vi } from 'vitest'; + +import Pipelines from '@/v2/pages/pipelines/pipelines'; +import * as commonUtils from '@/utils/common'; +import { pipelineServer } from '@tests/mocks/pipelineMocks/pipelinesServer'; +import { pipelineLocators, searchInputLocator } from '@tests/locators/locators'; +import { waitForPipelineTable } from '@tests/utils/pipelines.utils'; + +// Mock utility functions +vi.spyOn(commonUtils, 'showDataFetchError'); + +vi.mock('@/components/autoReloadPanel/autoReloadPanel', () => ({ + default: () =>
, +})); +vi.mock('@/v2/components/select/multiSelect.tsx', () => ({ + default: ({ onChange }: { onChange: Function }) => ( + + ), +})); + +describe('Pipelines Component', () => { + // Start and stop MSW server before and after all tests + beforeAll(() => pipelineServer.listen()); + afterEach(async () => pipelineServer.resetHandlers()); + afterAll(() => pipelineServer.close()); + + test('renders component correctly', () => { + render(); + + // Verify core components are rendered + expect(screen.getByText(/Pipelines/)).toBeInTheDocument(); + expect(screen.getByTestId('auto-reload-panel')).toBeInTheDocument(); + expect(screen.getByTestId('multi-select')).toBeInTheDocument(); + expect(screen.getByTestId(searchInputLocator)).toBeInTheDocument(); + }); + + test('Loads data on mount', async () => { + render(); + + // Wait for the data to load into the table + const table = await waitForPipelineTable(); + expect(table).toBeInTheDocument(); + + // Verify data is displayed, from pipelineMocks + expect(table).toHaveTextContent('pipeline-1'); + expect(table).toHaveTextContent('CLOSED'); + }); + + test('Renders table with correct number of rows', async () => { + render(); + + // Wait for the data to load into the table + const rows = await waitFor(() => screen.getAllByTestId(pipelineLocators.pipelineRowRegex)); + expect(rows).toHaveLength(3); // Based on the mocked Pipeline response + }); + + test('Displays no data message if the pipelines API returns an empty array', async () => { + pipelineServer.use( + rest.get('api/v1/pipelines', (req, res, ctx) => { + return res(ctx.status(200), ctx.json({ totalCount: 0, pipelines: [] })); + }) + ); + + render(); + + // Wait for the no data message + await waitFor(() => expect(screen.getByText('No Data')).toBeInTheDocument()); + }); + + test('Handles search input change', async () => { + render(); + await waitForPipelineTable(); + + const searchInput = screen.getByTestId(searchInputLocator); + fireEvent.change(searchInput, { + target: { value: 'pipeline-1' } + }); + // Sleep for 310ms to allow debounced search to take effect + await new Promise((r) => { setTimeout(r, 310) }); + const rows = await waitFor(() => screen.getAllByTestId(pipelineLocators.pipelineRowRegex)); + await waitFor(() => expect(rows).toHaveLength(1)); + }); + + test('Displays a message when no results match the search term', async () => { + render(); + const searchInput = screen.getByTestId(searchInputLocator); + + // Type a term that doesn't match any datanode + fireEvent.change(searchInput, { + target: { value: 'nonexistent-pipeline' } + }); + + // Verify that no results message is displayed + await waitFor(() => expect(screen.getByText('No Data')).toBeInTheDocument()); + }); + + test('Handles API errors gracefully by showing error message', async () => { + // Set up MSW to return an error for the datanode API + pipelineServer.use( + rest.get('api/v1/pipelines', (req, res, ctx) => { + return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' })); + }) + ); + + render(); + + // Wait for the error to be handled + await waitFor(() => + expect(commonUtils.showDataFetchError).toHaveBeenCalledWith('AxiosError: Request failed with status code 500') + ); + }); +}); \ No newline at end of file diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/pipelines/PipelinesTable.test.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/pipelines/PipelinesTable.test.tsx new file mode 100644 index 000000000000..3cd72a209380 --- /dev/null +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/pipelines/PipelinesTable.test.tsx @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import PipelinesTable from '@/v2/components/tables/pipelinesTable'; +import { Pipeline, PipelinesTableProps } from '@/v2/types/pipelines.types'; +import { pipelineLocators } from '@tests/locators/locators'; +import { userEvent } from '@testing-library/user-event'; + +// Mock to disable scroll behaviour +// We are running test inside virtual DOM, so scrolling isn't available as a normal browser +// AntD tries to scroll to the top after changing pages. This disables the behaviour. +vi.mock('antd/es/_util/scrollTo', () => ({ + default: vi.fn() +})); + + +const defaultProps: PipelinesTableProps = { + loading: false, + data: [], + selectedColumns: [ + { label: 'Pipeline ID', value: 'pipelineId' }, + { label: 'Status', value: 'status' }, + ], + searchTerm: '', +}; + +function getPipelineWith( + id: string, + status: 'OPEN' | 'CLOSING' | 'QUASI_CLOSED' | 'CLOSED' | 'UNHEALTHY' | 'INVALID' | 'DELETED' | 'DORMANT', + replicationFactor: string +): Pipeline { + return { + pipelineId: id, + status, + containers: 10, + datanodes: [], + leaderNode: 'node-1', + replicationFactor, + replicationType: 'RATIS', + duration: 10000, + leaderElections: 1, + lastLeaderElection: 1000, + }; +} + +describe('PipelinesTable Component', () => { + test('Renders table with data', async () => { + render( + + ); + + // Verify table is rendered + await waitFor(() => screen.getByText('pipeline-1')); + expect(screen.getByText('UNHEALTHY')).toBeInTheDocument(); + }); + + test('Filters data based on search term', async () => { + render( + + ); + + // Verify only matching rows are displayed + expect(screen.getByText('pipeline-1')).toBeInTheDocument(); + expect(screen.queryByText('pipeline-2')).not.toBeInTheDocument(); + }); + + test('Renders correct columns based on selectedColumns', async () => { + render( + + ); + + // Verify only the selected columns are rendered + expect(screen.getByText('Pipeline ID')).toBeInTheDocument(); + expect(screen.queryByText('Replication Type & Factor')).not.toBeInTheDocument(); + expect(screen.queryByText('Status')).not.toBeInTheDocument(); + }); + + test('Sorts table rows by pipeline ID', async () => { + render( + + ); + + // Simulate sorting by pipeline ID + const pipelineIdHeader = screen.getByText('Pipeline ID'); + fireEvent.click(pipelineIdHeader); + + // Verify rows are sorted + const rows = screen.getAllByTestId(pipelineLocators.pipelineRowRegex); + expect(rows[0]).toHaveTextContent('pipeline-1'); + expect(rows[1]).toHaveTextContent('pipeline-2'); + }); + + test('Sorts table rows by pipeline status', async () => { + render( + + ); + + // Simulate sorting by pipeline ID + const pipelineIdHeader = screen.getByText('Status'); + fireEvent.click(pipelineIdHeader); + + // Verify rows are sorted + const rows = screen.getAllByTestId(pipelineLocators.pipelineRowRegex); + expect(rows[0]).toHaveTextContent('CLOSED'); + expect(rows[1]).toHaveTextContent('DELETED'); + }); + + test('Pagination works properly', async () => { + render( + + ); + + try { + // Simulate clicking the next page button + const nextPageBtn = screen.getByTitle('Next Page'); + userEvent.click(nextPageBtn, { pointerEventsCheck: 0 }); + // Verify rows are sorted + const rows = screen.getAllByTestId(pipelineLocators.pipelineRowRegex); + expect(rows[0]).toHaveTextContent('pipeline-11'); + } catch(err) { + if (err instanceof ReferenceError) { + console.log("Encountered error: ", err); + } + } + }); +}); diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/utils/pipelines.utils.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/utils/pipelines.utils.tsx new file mode 100644 index 000000000000..9042ef6dce3b --- /dev/null +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/__tests__/utils/pipelines.utils.tsx @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { waitFor, screen } from "@testing-library/react"; + +export const waitForPipelineTable = async () => { + return waitFor(() => screen.getByTestId('pipelines-table')); +} \ No newline at end of file diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/pipelinesTable.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/pipelinesTable.tsx index 6c07749436d5..e5a3cf8195b4 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/pipelinesTable.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/v2/components/tables/pipelinesTable.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ -import React from 'react'; +import React, { HTMLAttributes } from 'react'; import Table, { ColumnsType, @@ -203,7 +203,11 @@ const PipelinesTable: React.FC = ({ rowKey='pipelineId' pagination={paginationConfig} scroll={{ x: 'max-content', scrollToFirstRowOnChange: true }} - locale={{ filterTitle: '' }} /> + locale={{ filterTitle: '' }} + onRow={(record: Pipeline) => ({ + 'data-testid': `pipelinetable-${record.pipelineId}` + } as HTMLAttributes)} + data-testid='pipelines-table'/>
) }