diff --git a/portal/mock-server/src/all-experiments.json b/portal/mock-server/src/all-experiments.json index 1176ca9b..8c24bc1f 100644 --- a/portal/mock-server/src/all-experiments.json +++ b/portal/mock-server/src/all-experiments.json @@ -37,7 +37,7 @@ { "id": 359, "algorithm": "p256_kyber512", - "iterations": 1000, + "iterations": 10000, "message_size": 1024 }, { diff --git a/portal/package.json b/portal/package.json index 868ded62..ad27ccbd 100644 --- a/portal/package.json +++ b/portal/package.json @@ -117,7 +117,8 @@ "/src/index.tsx", "/src/reportWebVitals.ts", "/src/setupProxy.js", - "/src/environments/*.*" + "/src/environments/*.*", + "/src/gh-pages" ] } } diff --git a/portal/src/app/components/all-experiments/Experiments.test.tsx b/portal/src/app/components/all-experiments/Experiments.test.tsx index 6106dd28..8df89aa4 100644 --- a/portal/src/app/components/all-experiments/Experiments.test.tsx +++ b/portal/src/app/components/all-experiments/Experiments.test.tsx @@ -1,44 +1,102 @@ -import { render } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; import { Experiments } from './Experiments'; import { useExperimentsData } from './hooks'; import { FetchDataStatus, useFetch } from '../../shared/hooks/useFetch'; +import React from 'react'; +import { ExperimentData } from './models/experiments.interface'; +import { DeleteExperimentModal, DeleteExperimentModalProps } from '../home/components/experiment/components/delete-experiment-modal'; +import { Button } from '../../shared/components/att-button'; +import { MOCK_DATA_FOR_ALL_EXPERIMENTS, MOCK_EXPERIMENTS_DATA_FOR_ALL_EXPERIMENTS } from './__mocks__/mocks'; +import { ALL_EXPERIMENTS_TABLE_EN } from './translate/en'; jest.mock('./hooks'); +jest.mock('../../shared/components/att-button'); +jest.mock('../../shared/components/qujata-table'); +jest.mock('../home/components/experiment/components/delete-experiment-modal'); jest.mock('../../shared/hooks/useFetch'); jest.mock('react-router-dom', () => ({ useNavigate: jest.fn(), })); describe('Experiments', () => { - it('renders correctly', () => { + let mockCheckedRows = { 15: true, 16: false, 17: true }; + let mockExperimentsData: ExperimentData[] = MOCK_EXPERIMENTS_DATA_FOR_ALL_EXPERIMENTS; + + beforeEach(() => { + const setExperimentsData = jest.fn((newData) => { + (typeof newData === 'function') + ? mockExperimentsData = newData(mockExperimentsData) + : mockExperimentsData = newData; + }); + const setCheckedRows = jest.fn((newData) => { + (typeof newData === 'function') + ? mockCheckedRows = newData(mockCheckedRows) + : mockCheckedRows = newData; + }); + jest.spyOn(React, 'useState') + .mockImplementationOnce(() => [true, jest.fn()]) + .mockImplementationOnce(() => [{ 15: true, 16: false, 17: true }, setCheckedRows]) + .mockImplementationOnce(() => [mockExperimentsData, setExperimentsData]) + .mockImplementationOnce(() => [[], jest.fn()]); + + (useFetch as jest.Mock).mockReturnValue({ + post: jest.fn(), + status: FetchDataStatus.Success, + error: null, + cancelRequest: jest.fn(), + }); (useExperimentsData as jest.Mock).mockReturnValue({ - test_suites: [{ - id: 15, - name: "Experiment 1", - end_time: 1705240065192, - test_runs: [ - { - id: 354, - algorithm: "prime256v1", - iterations: 100 - }, - { - id: 355, - algorithm: "prime256v1", - iterations: 500 - } - ] - }], - status: FetchDataStatus.Fetching, + testSuites: MOCK_DATA_FOR_ALL_EXPERIMENTS, + status: FetchDataStatus.Success, }); + (Button as jest.Mock).mockImplementation(({ onButtonClick }) => { + const handleDeleteClick: React.MouseEventHandler = (event) => { + onButtonClick(event); + } + return
DeleteExperimentModal
; + }); + (DeleteExperimentModal as jest.Mock).mockImplementation((props: DeleteExperimentModalProps) => { + function handleCloseDeleteExperimentModal() { + props.onClose(true); + } + function handleCloseDeleteExperimentNoDataModal() { + props.onClose(false); + } + return ( + <> +
DeleteExperimentModal
+
DeleteExperimentModal
+ + ); + }); + }); + + test('renders Experiments correctly', () => { + const { container, getByTestId } = render(); + + const buttonElement: HTMLElement = getByTestId('button_id'); + const deleteExperimentModal: HTMLElement = getByTestId('delete_experiment_modal'); + const deleteExperimentNoDataModal: HTMLElement = getByTestId('delete_experiment_no_data_modal'); + fireEvent.click(buttonElement); + fireEvent.click(deleteExperimentModal); + fireEvent.click(deleteExperimentNoDataModal); + + expect(container).toBeTruthy(); + }); + + test('renders Experiments while still fetching', () => { (useFetch as jest.Mock).mockReturnValue({ post: jest.fn(), status: FetchDataStatus.Fetching, error: null, cancelRequest: jest.fn(), }); + (useExperimentsData as jest.Mock).mockReturnValue({ + testSuites: undefined, + status: FetchDataStatus.Fetching, + }); - const { container } = render(); - expect(container).toMatchSnapshot(); + const { container } = render();; + expect(container).toBeTruthy(); }); }); \ No newline at end of file diff --git a/portal/src/app/components/all-experiments/Experiments.tsx b/portal/src/app/components/all-experiments/Experiments.tsx index 81a02eb6..0727728b 100644 --- a/portal/src/app/components/all-experiments/Experiments.tsx +++ b/portal/src/app/components/all-experiments/Experiments.tsx @@ -5,7 +5,7 @@ import { IUseExperimentsData, useExperimentsData } from './hooks'; import { FetchDataStatus, IHttp, useFetch } from '../../shared/hooks/useFetch'; import { ALL_EXPERIMENTS_TABLE_EN } from './translate/en'; import { CellContext } from '@tanstack/react-table'; -import { Table } from '../../shared/components/table'; +import { QujataTable, TableColumn } from '../../shared/components/qujata-table'; import { Button, ButtonActionType, ButtonSize, ButtonStyleType } from '../../shared/components/att-button'; import { APIS } from '../../apis'; import { useNavigate } from 'react-router-dom'; @@ -82,7 +82,7 @@ export const Experiments: React.FC = () => { navigate('/qujata', { state: { row } }); }, [navigate]); - const headers = useMemo(() => { + const headers: TableColumn[] = useMemo(() => { const columnDefs = [ { id: ALL_EXPERIMENTS_TABLE_EN.TABLE_COLUMNS.CHECKBOX, @@ -186,7 +186,7 @@ export const Experiments: React.FC = () => { )} } - {experimentsData.length > 0 && } + {experimentsData.length > 0 && } {openDeleteModal && } diff --git a/portal/src/app/components/all-experiments/__mocks__/mocks.ts b/portal/src/app/components/all-experiments/__mocks__/mocks.ts new file mode 100644 index 00000000..74a928da --- /dev/null +++ b/portal/src/app/components/all-experiments/__mocks__/mocks.ts @@ -0,0 +1,63 @@ +import { Experiment, ExperimentData } from "../models/experiments.interface"; + +export const MOCK_DATA_FOR_ALL_EXPERIMENTS: Experiment[] = [ + { + id: 17, + name: "Experiment 3", + end_time: 1705389926549, + test_runs: [ + { + id: 366, + algorithm: "prime256v1", + iterations: 500, + message_size: 256 + }, + { + id: 367, + algorithm: "bikel3", + iterations: 1000, + message_size: 2048 + }, + { + id: 368, + algorithm: "p256_kyber512", + iterations: 10000, + message_size: 1024 + }, + { + id: 369, + algorithm: "prime256v1", + iterations: 5000, + message_size: 512 + } + ] + }, + { + id: 18, + name: "Experiment 4", + end_time: 1705389926549, + test_runs: [ + { + id: 370, + algorithm: "kyber512", + iterations: 500, + message_size: 1024 + }, + { + id: 371, + algorithm: "kyber512", + iterations: 1000, + message_size: 2048 + } + ] + } +]; + +export const MOCK_EXPERIMENTS_DATA_FOR_ALL_EXPERIMENTS: ExperimentData[] = [{ + id: 15, + name: 'test', + algorithms: ['algo1', 'algo2'], + iterations: [100, 500], + message_sizes: [1024, 2048], + end_time: 1234 +}]; diff --git a/portal/src/app/components/all-experiments/__snapshots__/Experiments.test.tsx.snap b/portal/src/app/components/all-experiments/__snapshots__/Experiments.test.tsx.snap deleted file mode 100644 index 8afcf59a..00000000 --- a/portal/src/app/components/all-experiments/__snapshots__/Experiments.test.tsx.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Experiments renders correctly 1`] = ` -
-
-
-`; diff --git a/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.test.tsx b/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.test.tsx index 74d7ffb6..2f4ab9c0 100644 --- a/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.test.tsx +++ b/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.test.tsx @@ -1,70 +1,113 @@ -import { render, RenderResult } from '@testing-library/react'; +import { fireEvent, render, RenderResult } from '@testing-library/react'; import { BarChart, generateTooltipTitle, renderTooltipLabel } from './BarChart'; +import { ChartData, ChartOptions, InteractionItem } from 'chart.js'; +import React from 'react'; +import { MOCK_DATA_FOR_CHART_UTILS } from '../../../../home/components/experiment/components/charts/utils/__mocks__'; + +let options: ChartOptions; jest.mock('react-chartjs-2', () => ({ - Bar: () =>
Chart
, + Bar: (props: { options: ChartOptions }) => { + options = props.options; + return
Chart
; + }, })); jest.mock('chart.js'); -const tooltipKeys = ['algorithm', 'iterations']; -const tooltipLabels = ['Algorithm', 'Iterations']; -const data = [ - { - "algorithm": "Algorithm1", - "iterations": 1, - "results": { - "average_cpu": 5, - "average_memory": 512, - } - }, - { - "algorithm": "Algorithm1", - "iterations": 133, - "results": { - "average_cpu": 533, - "average_memory": 512, - } - }, - { - "algorithm": "Algorithm1", - "iterations": 1000, - "results": { - "average_cpu": 25, - "average_memory": 512, - } - }, - { - "algorithm": "Algorithm2", - "iterations": 2000, - "results": { - "average_cpu": 28, - "average_memory": 512, - } - }, - { - "algorithm": "Algorithm3", - "iterations": 2000, - "results": { - "average_cpu": 28, - "average_memory": 512, - } - } -]; describe('BarChart', () => { - test('renders BarChart component', () => { - const { getByTestId }: RenderResult = render(); - const chartElement: HTMLElement = getByTestId('chart'); - expect(chartElement).toBeTruthy(); + let chartRef: any; + let tooltipKeys: string[]; + let tooltipLabels: string[]; + let titleMock: string; + + beforeAll(() => { + tooltipKeys = ['algorithm', 'iterations']; + tooltipLabels = ['Algorithm', 'Iterations']; + titleMock = 'chart'; + }); + + test('should render BarChart correctly', () => { + const { container, getByTestId }: RenderResult = render(); + + const barChartWrapperElement: HTMLElement = getByTestId('bar_chart_wrapper'); + fireEvent.mouseMove(barChartWrapperElement); + fireEvent.mouseOut(barChartWrapperElement); + + expect(container).toBeTruthy(); + expect(barChartWrapperElement.style.cursor).toBe('default'); + }); + + test('should change cursor to pointer when mouse is over a chart element', () => { + chartRef = { + current: { + getElementsAtEventForMode: jest.fn().mockReturnValue([{ + index: 0, + datasetIndex: 0, + element: { x: 1, y: 1, active: true, options: {} } + }] as InteractionItem[] ), + }, + }; + jest.spyOn(React, 'useRef').mockReturnValue(chartRef); + + const { container, getByTestId }: RenderResult = render(); + + const lineChartWrapperElement: HTMLElement = getByTestId('bar_chart_wrapper'); + fireEvent.mouseMove(lineChartWrapperElement); + + expect(container).toBeTruthy(); + expect(lineChartWrapperElement.style.cursor).toBe('pointer'); }); test('should return correct label', () => { const tooltipItem = { datasetIndex: 0, index: 0 }; - const result = renderTooltipLabel(tooltipItem, [5, 533, 25, 28, 28], tooltipKeys, tooltipLabels, data); - expect(result).toBe('5 (Algorithm: Algorithm1, Iterations: 1)'); + const result = renderTooltipLabel(tooltipItem, [5, 533, 25, 28, 28], tooltipKeys, tooltipLabels, MOCK_DATA_FOR_CHART_UTILS); + expect(result).toBe('5 (Algorithm: Algorithm1, Iterations: 1000)'); }); test('should return correct title', () => { const result = generateTooltipTitle(['label1', 'label2'], 0); expect(result).toBe('label1'); }); + + test('should pass correct options to Bar component', () => { + render(); + + expect(options).toBeDefined(); + + // Test the different scenarios for fully coverage the legend of the BarChart + let mockItem = { datasetIndex: 0, text: 'label1 (iteration1)' }; + const mockChart = { datasets: [{ label: 'label1 (iteration1)' }, { label: 'label2 (iteration2)' }] }; + let filterResult = options.plugins.legend.labels.filter(mockItem, mockChart); + expect(filterResult).toBe(true); + + mockItem = { datasetIndex: 1, text: 'label1 (iteration1)' }; + filterResult = options.plugins.legend.labels.filter(mockItem, mockChart); + expect(filterResult).toBe(false); + + let mockChartForGenerateLabels: ChartData<'bar'> = { + datasets: [ + { + label: 'label1 (iteration1)', + data: [] + } + ] + }; + let generateLabelsResult = options.plugins.legend.labels.generateLabels(mockChartForGenerateLabels); + expect(generateLabelsResult).toEqual([]); + }); + + test('should pass correct options for tooltip callbacks to Bar component', () => { + render(); + + expect(options).toBeDefined(); + + // Test the different scenarios for fully coverage the tooltip of the BarChart + let contextForTitleCallback = [{ datasetIndex: 1 }]; + let titleResult = options.plugins.tooltip.callbacks.title(contextForTitleCallback); + expect(titleResult).toBe('Algorithm2'); + + let contextForLabelCallback = { datasetIndex: 0 }; + let labelResult = options.plugins.tooltip.callbacks.label(contextForLabelCallback); + expect(labelResult).toBe('25.5 (Algorithm: Algorithm1, Iterations: 1000)'); + }); }); diff --git a/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.tsx b/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.tsx index 016929f0..687f5cb0 100644 --- a/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.tsx +++ b/portal/src/app/components/dashboard/components/charts/BarChart/BarChart.tsx @@ -21,7 +21,7 @@ export const BarChart: React.FC = (props: BarChartProps) => { const [dataValues, setDataValues] = useState(); const [datasets, setDatasets] = useState([]); const [algorithmsColors, setAlgorithmsColors] = useState<{[key: string]: string}>(); - const chartRef = useRef(null); + const chartRef = useRef>(null); useEffect(() => { const temp = data.map((obj: any) => obj.results[keyOfData]); @@ -136,6 +136,7 @@ export const BarChart: React.FC = (props: BarChartProps) => { return (
{ const elements = chartRef.current?.getElementsAtEventForMode( event.nativeEvent, @@ -154,19 +155,19 @@ export const BarChart: React.FC = (props: BarChartProps) => { (event.currentTarget as HTMLElement).style.cursor = 'default'; }} > - +
); } export function renderTooltipLabel(context: { datasetIndex : number }, dataValues: any, tooltipKeys: string[], tooltipLabels: string[], data: any): string { const index = context.datasetIndex; - const valByKey = dataValues && dataValues[index]; - const label1: string = tooltipKeys[0]; - const label2: string = tooltipKeys[1]; - const val1 = data[index][label1]; - const val2 = data[index][label2]; - return `${valByKey} (${tooltipLabels[0]}: ${val1}, ${tooltipLabels[1]}: ${val2})`; + const valByKey = dataValues && dataValues[index]; + const label1: string = tooltipKeys[0]; + const label2: string = tooltipKeys[1]; + const val1 = data[index][label1]; + const val2 = data[index][label2]; + return `${valByKey} (${tooltipLabels[0]}: ${val1}, ${tooltipLabels[1]}: ${val2})`; } export function generateTooltipTitle(labels: string[], index: number): string { diff --git a/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.test.tsx b/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.test.tsx index ca3ad806..a4e9bca4 100644 --- a/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.test.tsx +++ b/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.test.tsx @@ -1,101 +1,153 @@ -import { RenderResult, render } from '@testing-library/react'; +import { RenderResult, fireEvent, render } from '@testing-library/react'; import { LineChart, generateTooltipTitle, renderTooltipLabel } from './LineChart'; -import { PointElement, TooltipItem } from 'chart.js'; +import { ChartOptions, InteractionItem, PointElement, TooltipItem } from 'chart.js'; +import React from 'react'; +let options: ChartOptions; + +jest.mock('chart.js'); jest.mock('react-chartjs-2', () => ({ - Bar: () =>
Chart
, // add data-testid attribute - Line: () =>
Line
, // add any additional components you want to mock here + Bar: () =>
Chart
, + Line: (props: { options: ChartOptions }) => { + options = props.options; + return
Line
; + }, })); -jest.mock('chart.js'); -const mockData = { - datasets: [ +describe('LineChart', () => { + let mockData: any; + let chartRef: any; + + beforeAll(() => { + mockData = { + datasets: [ { - backgroundColor: "#05BBFF", - borderColor: "#05BBFF", - borderWidth: 1, - data: [1,2,3,4], - fill: false, - label: "Algorithm1", + backgroundColor: "#05BBFF", + borderColor: "#05BBFF", + borderWidth: 1, + data: [1, 2, 3, 4], + fill: false, + label: "Algorithm1", } - ], - labels: [12, 14, 23, 104, 200, 300, 1024], -} + ], + labels: [12, 14, 23, 104, 200, 300, 1024], + } + }); -describe('LineChart', () => { - test('renders LineChart', () => { - const { getByTestId }: RenderResult = render(); - const chartElement: HTMLElement = getByTestId('line2'); - expect(chartElement).toBeTruthy(); - }); - - test('should render tooltip title', () => { - const tooltipItem: TooltipItem<'line'> = { - raw: 12, - chart: <> as unknown as any, - label: '', - parsed: {x: 3, y: 299}, - formattedValue: '', - dataset: { - backgroundColor: "#05BBFF", - borderColor: "#05BBFF", - borderWidth: 1, - data: [1,2,3,4], - fill: false, - label: "Algorithm1", - }, + test('should render LineChart correctly', () => { + const { container, getByTestId }: RenderResult = render(); + + const lineChartWrapperElement: HTMLElement = getByTestId('line_chart_wrapper'); + fireEvent.mouseMove(lineChartWrapperElement); + fireEvent.mouseOut(lineChartWrapperElement); + + expect(container).toBeTruthy(); + expect(lineChartWrapperElement.style.cursor).toBe('default'); + }); + + test('should change cursor to pointer when mouse is over a chart element', () => { + chartRef = { + current: { + getElementsAtEventForMode: jest.fn().mockReturnValue([{ + index: 0, datasetIndex: 0, - dataIndex: 0, - element: <> as unknown as PointElement - }; + element: { x: 1, y: 1, active: true, options: {} } + }] as InteractionItem[] ), + }, + }; + jest.spyOn(React, 'useRef').mockReturnValue(chartRef); - const result = generateTooltipTitle(tooltipItem); - - expect(result).toEqual("Algorithm1"); - }); - - test('should render tooltip label', () => { - const tooltipItem: TooltipItem<'line'> = { - raw: 12, - chart: <> as unknown as any, - label: '', - parsed: {x: 3, y: 299}, - formattedValue: '', - dataset: { - backgroundColor: "#05BBFF", - borderColor: "#05BBFF", - borderWidth: 1, - data: [1,2,3,4], - fill: false, - label: "Algorithm1", - }, - datasetIndex: 0, - dataIndex: 0, - element: <> as unknown as PointElement - }; + const { container, getByTestId }: RenderResult = render(); - const result = renderTooltipLabel(tooltipItem, 'chart'); + const lineChartWrapperElement: HTMLElement = getByTestId('line_chart_wrapper'); + fireEvent.mouseMove(lineChartWrapperElement); + + expect(container).toBeTruthy(); + expect(lineChartWrapperElement.style.cursor).toBe('pointer'); + }); + + test('should render tooltip title', () => { + const tooltipItem: TooltipItem<'line'> = { + raw: 12, + chart: <> as any, + label: '', + parsed: { x: 3, y: 299 }, + formattedValue: '', + dataset: { + backgroundColor: "#05BBFF", + borderColor: "#05BBFF", + borderWidth: 1, + data: [1, 2, 3, 4], + fill: false, + label: "Algorithm1", + }, + datasetIndex: 0, + dataIndex: 0, + element: <> as any + }; + + const result = generateTooltipTitle(tooltipItem); + + expect(result).toEqual("Algorithm1"); + }); + + test('should render tooltip label', () => { + const tooltipItem: TooltipItem<'line'> = { + raw: 12, + chart: <> as unknown as any, + label: '', + parsed: {x: 3, y: 299}, + formattedValue: '', + dataset: { + backgroundColor: "#05BBFF", + borderColor: "#05BBFF", + borderWidth: 1, + data: [1,2,3,4], + fill: false, + label: "Algorithm1", + }, + datasetIndex: 0, + dataIndex: 0, + element: <> as unknown as PointElement + }; + + const result = renderTooltipLabel(tooltipItem, 'chart'); + + expect(result).toEqual("chart 12"); + }); + + test('generateTooltipTitle', () => { + const context: any = { + chart: jest.fn(), + dataIndex: 3, + dataset: { + label: "label-test", + }, + datasetIndex: 0, + element: jest.fn(), + formattedValue: "230", + label: "label-test", + parsed: {x: 3, y: 230}, + raw: 230, + }; + + const result = generateTooltipTitle(context); + + expect(result).toEqual("label-test"); + }); + + test('should pass correct options for tooltip callbacks to Line component', () => { + render(); + + expect(options).toBeDefined(); - expect(result).toEqual("chart 12"); - }); - - describe('tooltip configuration', () => { - test('generateTooltipTitle', () => { - const context: any = { - chart: jest.fn(), - dataIndex: 3, - dataset: { - label: "label-test", - }, - datasetIndex: 0, - element: jest.fn(), - formattedValue: "230", - label: "label-test", - parsed: {x: 3, y: 230}, - raw: 230, - }; - const result = generateTooltipTitle(context); - expect(result).toEqual("label-test"); - }); - }); + // Test the different scenarios for fully coverage the tooltip of the LineChart + let contextForTitleCallback = [{ dataset: { label: 'labelMock' } }]; + let titleResult = options.plugins.tooltip.callbacks.title(contextForTitleCallback); + expect(titleResult).toBe('labelMock'); + + let contextForLabelCallback = { raw: 'mockRaw' }; + let labelResult = options.plugins.tooltip.callbacks.label(contextForLabelCallback); + expect(labelResult).toBe('average_cpu mockRaw'); + }); }); \ No newline at end of file diff --git a/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.tsx b/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.tsx index 1f478d00..15bac285 100644 --- a/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.tsx +++ b/portal/src/app/components/dashboard/components/charts/LineChart/LineChart.tsx @@ -5,8 +5,8 @@ import { useRef } from 'react'; export interface LineChartProps { data: any; - tooltipLabel?: string; title?: string; + tooltipLabel: string; xAxiosTitle?: string; } @@ -59,7 +59,7 @@ export const LineChart: React.FC = (props: LineChartProps) => { return generateTooltipTitle(context[0]); }, label: function (context: TooltipItem<'line'>) { - return renderTooltipLabel(context, tooltipLabel || ''); + return renderTooltipLabel(context, tooltipLabel); }, }, }, @@ -68,6 +68,7 @@ export const LineChart: React.FC = (props: LineChartProps) => { return (
{ const elements = chartRef.current?.getElementsAtEventForMode( event.nativeEvent, @@ -96,5 +97,5 @@ export function generateTooltipTitle(tooltipItem: TooltipItem<'line'>) { } export function renderTooltipLabel(context: TooltipItem<'line'>, tooltipLabel: string): string { - return `${tooltipLabel} ${context?.raw?.toString()}` || ''; + return `${tooltipLabel} ${context?.raw?.toString()}`; } diff --git a/portal/src/app/components/dashboard/components/charts/utils/charts.utils.test.ts b/portal/src/app/components/dashboard/components/charts/utils/charts.utils.test.ts index 4d698d56..da741d00 100644 --- a/portal/src/app/components/dashboard/components/charts/utils/charts.utils.test.ts +++ b/portal/src/app/components/dashboard/components/charts/utils/charts.utils.test.ts @@ -1,7 +1,13 @@ import { getColorByName } from './charts.utils'; -describe('Charts Util Test', () => { +describe('getColorByName', () => { test('should get color by name', () => { - expect(getColorByName('bikel1')).toBe('#FF8500'); + const result = getColorByName('bikel1'); + expect(result).toBe('#FF8500'); + }); + + test('should get the default color when the name does not start with a letter', () => { + const result = getColorByName('123-test'); + expect(result).toBe('#086CE1'); }); }); diff --git a/portal/src/app/components/home/Home.module.scss b/portal/src/app/components/home/Home.module.scss index 7a77a1b9..a06d28f7 100644 --- a/portal/src/app/components/home/Home.module.scss +++ b/portal/src/app/components/home/Home.module.scss @@ -2,6 +2,9 @@ @import "src/styles/z-index"; .app_wrapper { + display: flex; + justify-content: flex-start; + padding-block-start: 20px; padding-inline-start: 80px; padding-block-end: 40px; @@ -49,3 +52,17 @@ background-color: var($attPurple); padding: 14px; } + +.protocolQueryWithDivider { + position: relative; +} + +.protocolQueryWithDivider::after { + content: ""; + position: absolute; + inset-block-start: 35px; + inset-inline-start: 900px; + inline-size: 2px; + block-size: 91%; + background: var($dividerColorGray); +} diff --git a/portal/src/app/components/home/Home.test.tsx b/portal/src/app/components/home/Home.test.tsx index 97b1445a..8f713ad3 100644 --- a/portal/src/app/components/home/Home.test.tsx +++ b/portal/src/app/components/home/Home.test.tsx @@ -2,19 +2,15 @@ import { fireEvent, render, waitFor } from '@testing-library/react'; import { Home } from './Home'; import { SubHeader, SubHeaderProps } from '../sub-header'; import { ProtocolQuery, ProtocolQueryProps } from '../protocol-query'; +import { useDashboardData } from '../../hooks/useDashboardData'; +import { FetchDataStatus } from '../../shared/hooks/useFetch'; const mockUseNavigate = jest.fn(); const mockUseLocation = jest.fn(); jest.mock('../sub-header'); jest.mock('../protocol-query'); -jest.mock('../../hooks/useDashboardData', () => ({ - useDashboardData: () => ({ - handleRunQueryClick: jest.fn(), - link: 'initialLink', - status: 'idle', - }), -})); +jest.mock('../../hooks/useDashboardData'); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: () => mockUseNavigate, @@ -22,49 +18,73 @@ jest.mock('react-router-dom', () => ({ })); describe('Home', () => { - test('should render Home', async () => { - (SubHeader as jest.Mock).mockImplementation((props: SubHeaderProps) => { - function onClick() { - props.handleCloseClick(); - } - return
SubHeader
; - }); - (ProtocolQuery as jest.Mock).mockImplementation(() =>
ProtocolQuery
); - const { container, getByTestId } = render(); - const submitButtonElement: HTMLElement = getByTestId('submit-id'); + beforeEach(() => { + (useDashboardData as jest.Mock).mockReturnValue({ + handleRunQueryClick: jest.fn(), + link: 'initialLink', + status: FetchDataStatus.Success, + testSuiteId: 'testSuiteId', + }); + }); + + test('should render Home', async () => { + (SubHeader as jest.Mock).mockImplementation((props: SubHeaderProps) => { + function onClick() { + props.handleCloseClick(); + } + return
SubHeader
; + }); + (ProtocolQuery as jest.Mock).mockImplementation(() =>
ProtocolQuery
); - await waitFor(() => { - expect(container).toBeTruthy(); - }); + const { container, getByTestId } = render(); + const submitButtonElement: HTMLElement = getByTestId('submit-id'); - await waitFor(() => { - fireEvent.click(submitButtonElement); - }); + await waitFor(() => { + fireEvent.click(submitButtonElement); }); - test('should click on run button', async () => { - (ProtocolQuery as jest.Mock).mockImplementation((props: ProtocolQueryProps) => { - function onClick() { - props.onRunClick({ - experimentName: 'test', - algorithms: { label: 'regular', value: 'regular' }, - iterationsCount: { label: 'regular', value: 'regular' }, - messageSizes: [{ label: '1024', value: '1024' }], - description: 'test' - }); - } - return
SubHeader
; - }); - (SubHeader as jest.Mock).mockImplementation(() =>
SubHeader
); - const { container, getByTestId } = render(); - const submitButtonElement: HTMLElement = getByTestId('submit-id'); + await waitFor(() => { + expect(container).toBeTruthy(); + }); + }); + + test('should click on run button', async () => { + (ProtocolQuery as jest.Mock).mockImplementation((props: ProtocolQueryProps) => { + function onClick() { + props.onRunClick({ + experimentName: 'test', + algorithms: { label: 'regular', value: 'regular' }, + iterationsCount: { label: 'regular', value: 'regular' }, + messageSizes: [{ label: '1024', value: '1024' }], + description: 'test' + }); + } + function onClickNotEnoughData() { + props.onRunClick({ + experimentName: '', + algorithms: { label: 'regular', value: 'regular' }, + iterationsCount: { label: 'regular', value: 'regular' }, + messageSizes: [], + description: 'test' + }); + } + return ( + <> +
ProtocolQuery
+
ProtocolQuery
+ + ); + }); + (SubHeader as jest.Mock).mockImplementation(() =>
SubHeader
); + const { container, getByTestId } = render(); + const submitButtonElement: HTMLElement = getByTestId('submit'); + const submitNotEnoughDataButtonElement: HTMLElement = getByTestId('submit_not_enough_data'); - await waitFor(() => { - expect(container).toBeTruthy(); - }); + fireEvent.click(submitButtonElement); + fireEvent.click(submitNotEnoughDataButtonElement); - await waitFor(() => { - fireEvent.click(submitButtonElement); - }); + await waitFor(() => { + expect(container).toBeTruthy(); }); + }); }); diff --git a/portal/src/app/components/home/Home.tsx b/portal/src/app/components/home/Home.tsx index e05f1b2b..1d54f1ad 100644 --- a/portal/src/app/components/home/Home.tsx +++ b/portal/src/app/components/home/Home.tsx @@ -1,12 +1,14 @@ -import { IUseDashboardData, useDashboardData } from "../../hooks/useDashboardData"; -import { FetchDataStatus } from "../../shared/hooks/useFetch"; -import { ITestParams } from "../../shared/models/quantum.interface"; -import { ProtocolQuery } from "../protocol-query"; -import { SubHeader } from "../sub-header"; -import { useCallback, useEffect, useState } from 'react'; import styles from './Home.module.scss'; -import { useLocation, useNavigate } from "react-router-dom"; -import { ExperimentData } from "../all-experiments/models/experiments.interface"; +import cn from 'classnames'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { IUseDashboardData, useDashboardData } from '../../hooks/useDashboardData'; +import { FetchDataStatus } from '../../shared/hooks/useFetch'; +import { ITestParams } from '../../shared/models/quantum.interface'; +import { ProtocolQuery } from '../protocol-query'; +import { SubHeader } from '../sub-header'; +import { useCallback, useEffect, useState } from 'react'; +import { ExperimentData } from '../all-experiments/models/experiments.interface'; +import { LatestExperiments } from './components'; export const Home: React.FC = () => { const [isSubHeaderOpen, setIsSubHeaderOpen] = useState(true); @@ -42,19 +44,20 @@ export const HomeContent: React.FC = () => { }, [navigate, status, testSuiteId]); const handleRunClick: (params: ITestParams) => void = useCallback((params: ITestParams): void => { - if (params.experimentName && params.algorithms && params.iterationsCount) { + if (params.experimentName && params.algorithms && params.iterationsCount && params.messageSizes) { handleRunQueryClick(params); } }, [handleRunQueryClick]); return ( -
+
+
); }; diff --git a/portal/src/app/components/home/components/experiment/Experiment.test.tsx b/portal/src/app/components/home/components/experiment/Experiment.test.tsx index d6c31b20..86079c7f 100644 --- a/portal/src/app/components/home/components/experiment/Experiment.test.tsx +++ b/portal/src/app/components/home/components/experiment/Experiment.test.tsx @@ -1,4 +1,4 @@ -import { render, waitFor } from '@testing-library/react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; import { SubHeader } from './components/sub-header'; import { Charts } from './components/charts'; import { Experiment, ExperimentContent } from './Experiment'; @@ -6,10 +6,12 @@ import { ExperimentTable } from './components/experiment-table'; import { useExperimentData } from './components/hooks/useExperimentData'; import { FetchDataStatus } from '../../../../shared/hooks/useFetch'; import { MOCK_DATA_FOR_EXPERIMENT } from './components/__mocks__/mocks'; -import { ExperimentTabs } from './components/experiment-tabs'; -import { TableOptions } from './components/table-options'; -import { SelectColumnsPopup } from './components/table-options/components/select-columns-popup'; +import { ExperimentTabs, ExperimentTabsProps } from './components/experiment-tabs'; +import { TableOptions, TableOptionsProps } from './components/table-options'; +import { SelectColumnsPopup, SelectColumnsPopupProps } from './components/table-options/components/select-columns-popup'; import { TABLE_OPTIONS_EN } from './components/table-options/translate/en'; +import { EXPERIMENT_EN } from './translate/en'; +import React from 'react'; jest.mock('./components/hooks/useExperimentData'); jest.mock('./components/sub-header'); @@ -23,12 +25,6 @@ jest.mock('./components/charts', () => ({ describe('Experiment', () => { beforeEach(() => { - (SubHeader as jest.Mock).mockImplementation(() =>
SubHeader
); - (ExperimentTabs as jest.Mock).mockImplementation(() =>
ExperimentTabs
); - (TableOptions as jest.Mock).mockImplementation(() => ); - (SelectColumnsPopup as jest.Mock).mockImplementation(() =>
SelectColumnsPopup
); - (ExperimentTable as jest.Mock).mockImplementation(() =>
ExperimentTable
); - (Charts as jest.Mock).mockImplementation(() =>
Charts
); (useExperimentData as jest.Mock).mockReturnValue({ data: MOCK_DATA_FOR_EXPERIMENT, status: FetchDataStatus.Success, @@ -52,22 +48,12 @@ describe('Experiment', () => { expect(container).toBeTruthy(); }); }); - - test('should show spinner on render data', async () => { - const { container } = render(); - - await waitFor(() => { - expect(container).toBeTruthy(); - }); - }); }); describe('ExperimentContent', () => { - let handleButtonClickMock: jest.Mock; let scrollIntoViewMock: jest.Mock; beforeEach(() => { - handleButtonClickMock = jest.fn(); scrollIntoViewMock = jest.fn(); window.HTMLElement.prototype.scrollIntoView = scrollIntoViewMock; @@ -75,16 +61,62 @@ describe('ExperimentContent', () => { data: MOCK_DATA_FOR_EXPERIMENT, status: FetchDataStatus.Success, }); - }); - - afterEach(() => { - scrollIntoViewMock.mockRestore(); + (SubHeader as jest.Mock).mockImplementation(() =>
SubHeader
); + (ExperimentTabs as jest.Mock).mockImplementation((props: ExperimentTabsProps) => { + function handleResultsDataClick() { + props.handleButtonClick(EXPERIMENT_EN.TABS.RESULTS_DATA); + } + function handleVisualizationClick() { + props.handleButtonClick(EXPERIMENT_EN.TABS.VISUALIZATION); + } + function handleNietherClick() { + props.handleButtonClick('Not Results Data or Visualization'); + } + return ( + <> +
{EXPERIMENT_EN.TABS.RESULTS_DATA}
+
{EXPERIMENT_EN.TABS.VISUALIZATION}
+
Not Results Data or Visualization
+ + ) + }); + (TableOptions as jest.Mock).mockImplementation((props: TableOptionsProps) => { + function handleSelectColumnsClick() { + props.handleSelectColumnsClick(); + } + return
TableOptions
+ }); + (SelectColumnsPopup as jest.Mock).mockImplementation((props: SelectColumnsPopupProps) => { + function onPopupClose() { + props.onPopupClose(); + } + return
{TABLE_OPTIONS_EN.SELECT_COLUMNS}
+ }); + (ExperimentTable as jest.Mock).mockImplementation(() =>
ExperimentTable
); + (Charts as jest.Mock).mockImplementation(() =>
Charts
); }); test('should render without crashing', async () => { - const { container } = render(); + const { container, getByTestId } = render(); + fireEvent.click(getByTestId('results_data_button')); + fireEvent.click(getByTestId('visualization_button')); + fireEvent.click(getByTestId('neither_results_data_nor_visualization')); + fireEvent.click(getByTestId('table_options')); + fireEvent.click(getByTestId('select_columns_button')); + await waitFor(() => { expect(container).toBeTruthy(); }); }); + + test('should close the select columns popup when clicking outside', () => { + const setState = jest.fn(); + const useStateSpy = jest.spyOn(React, 'useState'); + useStateSpy.mockImplementation(() => [true, setState]); + + render(); + fireEvent.mouseDown(document); + + expect(setState).toHaveBeenCalledWith(false); + }); }); diff --git a/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts b/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts index 0e51bbb6..0466d179 100644 --- a/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts +++ b/portal/src/app/components/home/components/experiment/components/__mocks__/mocks.ts @@ -209,30 +209,3 @@ export const MOCK_SUB_HEADER: ITestRunResult = { }, ], }; - -export const CSV_MOCK: ITestRunResultData[] = [ - { - id: 1, - algorithm: 'App1', - iterations: 1000, - message_size: 1024, - results: { - average_cpu: 2000, - average_memory: 3000, - bytes_throughput: 11, - request_throughput: 21 - }, - }, - { - id: 2, - algorithm: 'App2', - iterations: 4000, - message_size: 2048, - results: { - average_cpu: 5000, - average_memory: 6000, - bytes_throughput: 11, - request_throughput: 21 - }, - }, -]; diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.test.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.test.tsx index 6d6fc957..8849b727 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.test.tsx +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/DynamicChart.test.tsx @@ -1,33 +1,55 @@ -import { render } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; import { useDynamicChartData } from './hooks/useDynamicChartData'; import { BarChart } from '../../../../../../../dashboard/components/charts/BarChart/BarChart'; import { LineChart } from '../../../../../../../dashboard/components/charts/LineChart/LineChart'; +import { AttSelectProps } from '../../../../../../../../shared/components/att-select'; +import { DynamicChart } from './DynamicChart'; +import { MOCK_DATA_FOR_EXPERIMENT } from '../../../__mocks__/mocks'; jest.mock('../../../../../../../dashboard/components/charts/BarChart/BarChart'); jest.mock('../../../../../../../dashboard/components/charts/LineChart/LineChart'); -jest.mock('../../../../../../../../shared/components/att-select/AttSelect', () => ({ - AttSelect: jest.fn(() =>
Mocked AttSelect
), -})); jest.mock('./hooks/useDynamicChartData'); +jest.mock('../../../../../../../../shared/components/att-select/AttSelect', () => { + return { + __esModule: true, + AttSelect: (props: AttSelectProps) => { + function onChange() { + props.onChange({ label: 'averageCPU', value: 'averageCPU' }); + } + + return
Options
; + }, + }; +}); describe('DynamicChart', () => { test('should render Charts', async () => { (BarChart as jest.Mock).mockImplementation(() =>
BarChart
); (LineChart as jest.Mock).mockImplementation(() =>
LineChart
); - - - (useDynamicChartData as jest.Mock).mockReturnValue({ - yAxiosOptions: [{ - label: 'averageCPU', - value: 'averageCPU' - }, - { - label: 'averageMemory', - value: 'averageMemory' - }], + (useDynamicChartData as jest.Mock).mockReturnValue({ + yAxiosOptions: [{ + label: 'averageCPU', + value: 'averageCPU' + }, + { + label: 'averageMemory', + value: 'averageMemory' + }], }); - - const { container } = render(); - expect(container).toBeTruthy(); + const { container, getAllByTestId } = render( + + ); + + const optionsElements: HTMLElement[] = getAllByTestId('att_select_options'); + optionsElements.forEach((element) => { + fireEvent.click(element); + }); + + expect(container).toBeTruthy(); }); }); diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.test.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.test.tsx index 029bdf4b..da755704 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.test.tsx +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.test.tsx @@ -1,4 +1,4 @@ -import { render, RenderResult } from '@testing-library/react'; +import { fireEvent, render, RenderResult } from '@testing-library/react'; import { CustomDropdownIndicator } from './CustomDropdownIndicator'; import { DropdownIndicatorProps } from 'react-select'; import { AttSelectOption } from '../../../../../../../../../../shared/components/att-select'; @@ -18,13 +18,31 @@ describe('CustomDropdownIndicator', () => { isRtl: false, options: [], selectOption: jest.fn(), - selectProps: undefined as any, + selectProps: { menuIsOpen: false, onMenuOpen: jest.fn(), onMenuClose: jest.fn() } as any, setValue: jest.fn(), theme: undefined as any, }; - it('should render CustomDropdownIndicator', () => { - const { container }: RenderResult = render(); - expect(container.firstChild).toMatchSnapshot(); + test('should render CustomDropdownIndicator when the menu is close', () => { + const { container, getByTestId }: RenderResult = render(); + + const dropdownIndicatorWrapperElement: HTMLElement = getByTestId('dropdown_indicator_wrapper'); + fireEvent.click(dropdownIndicatorWrapperElement); + + expect(container).toBeTruthy(); + expect(mockProps.selectProps.onMenuOpen).toHaveBeenCalled(); + expect(mockProps.selectProps.onMenuClose).not.toHaveBeenCalled(); + }); + + test('should render CustomDropdownIndicator when the menu is open', () => { + mockProps.selectProps.menuIsOpen = true; + const { container, getByTestId }: RenderResult = render(); + + const dropdownIndicatorWrapperElement: HTMLElement = getByTestId('dropdown_indicator_wrapper'); + fireEvent.click(dropdownIndicatorWrapperElement); + + expect(container).toBeTruthy(); + expect(mockProps.selectProps.onMenuOpen).not.toHaveBeenCalled(); + expect(mockProps.selectProps.onMenuClose).toHaveBeenCalled(); }); }); diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.tsx index 83d903f9..6c161a22 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.tsx +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/CustomDropdownIndicator.tsx @@ -15,7 +15,7 @@ export const CustomDropdownIndicator: React.FC +
diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/__snapshots__/CustomDropdownIndicator.test.tsx.snap b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/__snapshots__/CustomDropdownIndicator.test.tsx.snap deleted file mode 100644 index ad0f259f..00000000 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-dropdown-indicator/__snapshots__/CustomDropdownIndicator.test.tsx.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CustomDropdownIndicator should render CustomDropdownIndicator 1`] = ` -
-
- - arrow-down-selector.svg - -
-
-`; diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.test.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.test.tsx index 34bccade..c674d7a7 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.test.tsx +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.test.tsx @@ -1,10 +1,17 @@ -import { render, RenderResult } from '@testing-library/react'; +import { fireEvent, render, RenderResult } from '@testing-library/react'; import { CustomValueContainer } from './CustomValueContainer'; -import { GroupBase, SetValueAction, ValueContainerProps } from 'react-select'; +import { GroupBase, ValueContainerProps } from 'react-select'; import { AttSelectOption } from '../../../../../../../../../../shared/components/att-select'; +import { useOutsideClick } from '../../../../../../../../../../hooks/useOutsideClick'; +import { useRef } from 'react'; + +jest.mock('../../../../../../../../../../hooks/useOutsideClick'); describe('CustomValueContainer', () => { - const mockProps: ValueContainerProps, boolean, GroupBase>> = { + let mockProps: ValueContainerProps, boolean, GroupBase>>; + + beforeEach(() => { + mockProps = { children: undefined, isDisabled: false, clearValue: jest.fn(), @@ -17,13 +24,59 @@ describe('CustomValueContainer', () => { isRtl: false, options: [], selectOption: jest.fn(), - selectProps: undefined as any, + selectProps: { menuIsOpen: false, onMenuOpen: jest.fn(), onMenuClose: jest.fn() } as any, setValue: jest.fn(), theme: undefined as any - }; + }; + }); + + test('should render CustomValueContainer', () => { + const { container, getByTestId }: RenderResult = render(); + + const valueContainerWrapperElement: HTMLElement = getByTestId('value_container_wrapper'); + fireEvent.click(valueContainerWrapperElement); + + expect(container).toBeTruthy(); + }); + + test('should not open the menu when rendering CustomValueContainer', () => { + mockProps.selectProps.menuIsOpen = true; + const { container, getByTestId }: RenderResult = render(); + + const valueContainerWrapperElement: HTMLElement = getByTestId('value_container_wrapper'); + fireEvent.click(valueContainerWrapperElement); + + expect(container).toBeTruthy(); + }); + + test('should activate useOutsideClick hook when the menu is open', () => { + mockProps.selectProps.menuIsOpen = true; + (useOutsideClick as jest.Mock).mockImplementation((ref, callback) => { + document.addEventListener('mousedown', (event) => { + if (ref.current && !ref.current.contains(event.target)) { + callback(); + } + }); + }); + + render(); + fireEvent.mouseDown(document); + + expect(mockProps.selectProps.onMenuClose).toHaveBeenCalled(); + }); + + test('should activate useOutsideClick hook when the menu is close', () => { + (useOutsideClick as jest.Mock).mockImplementation((ref, callback) => { + document.addEventListener('mousedown', (event) => { + if (ref.current && !ref.current.contains(event.target)) { + callback(); + } + }); + }); + + render(); + fireEvent.mouseDown(document); - it('should render CustomValueContainer', () => { - const { container }: RenderResult = render(); - expect(container.firstChild).toMatchSnapshot(); + expect(mockProps.selectProps.onMenuClose).not.toHaveBeenCalled(); }); }); diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx index 44768c58..ba3497f1 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/CustomValueContainer.tsx @@ -25,7 +25,7 @@ export const CustomValueContainer: React.FC +
{props.hasValue && {inputValue}} diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/__snapshots__/CustomValueContainer.test.tsx.snap b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/__snapshots__/CustomValueContainer.test.tsx.snap deleted file mode 100644 index 3de4a472..00000000 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/components/custom-value-container/__snapshots__/CustomValueContainer.test.tsx.snap +++ /dev/null @@ -1,17 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CustomValueContainer should render CustomValueContainer 1`] = ` -
-
-
- -
-
-
-`; diff --git a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.test.ts b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.test.ts index f243e082..c7225651 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.test.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/components/dynamic-chart/hooks/useDynamicChartData.test.ts @@ -7,7 +7,7 @@ describe('useDynamicChartData', () => { const { result } = renderHook(() => useDynamicChartData(MOCK_DATA_FOR_EXPERIMENT)); act(() => { - expect(result.current).toEqual( {yAxiosOptions: [{label: "Average Cpu", value: "average_cpu"}, {label: "Average Memory", value: "average_memory"}, {label: "Bytes Throughput", value: "bytes_throughput"}, {label: "Request Throughput", value: "request_throughput"}]}); + expect(result.current).toEqual( {yAxisOptions: [{label: "Average Cpu", value: "average_cpu"}, {label: "Average Memory", value: "average_memory"}, {label: "Bytes Throughput", value: "bytes_throughput"}, {label: "Request Throughput", value: "request_throughput"}]}); }); }); }); diff --git a/portal/src/app/components/home/components/experiment/components/charts/utils/__mocks__/mocks.ts b/portal/src/app/components/home/components/experiment/components/charts/utils/__mocks__/mocks.ts index 0928d5d3..c347bb4b 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/utils/__mocks__/mocks.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/utils/__mocks__/mocks.ts @@ -84,6 +84,90 @@ export const MOCK_DATA_TO_SORT_BY_ITERATION: ITestRunResultData[] = [ } ]; +export const MOCK_DATA_TO_SORT_BY_MESSAGE_SIZE: ITestRunResultData[] = [ + { + id: 1, + algorithm: "Algorithm1", + iterations: 1000, + message_size: 1024, + results: + { + average_cpu: 25.5, + average_memory: 512, + bytes_throughput: 11, + request_throughput: 21 + } + }, + { + id: 2, + algorithm: "Algorithm1", + iterations: 1000, + message_size: 512, + results: + { + average_cpu: 25, + average_memory: 52, + bytes_throughput: 11, + request_throughput: 21 + } + }, + { + id: 3, + algorithm: "Algorithm1", + iterations: 1000, + message_size: 2048, + results: + { + average_cpu: 25, + average_memory: 52, + bytes_throughput: 11, + request_throughput: 21 + } + } +]; + +export const MOCK_DATA_WITH_NO_NEED_TO_SORT: ITestRunResultData[] = [ + { + id: 1, + algorithm: "Algorithm1", + iterations: 1000, + message_size: 1024, + results: + { + average_cpu: 25.5, + average_memory: 512, + bytes_throughput: 11, + request_throughput: 21 + } + }, + { + id: 2, + algorithm: "Algorithm1", + iterations: 1000, + message_size: 1024, + results: + { + average_cpu: 25, + average_memory: 52, + bytes_throughput: 11, + request_throughput: 21 + } + }, + { + id: 3, + algorithm: "Algorithm1", + iterations: 1000, + message_size: 1024, + results: + { + average_cpu: 25, + average_memory: 52, + bytes_throughput: 11, + request_throughput: 21 + } + } +]; + export const MOCK_DATA_FOR_CHART_UTILS: ITestRunResultData[] = [ { id: 1, @@ -96,7 +180,7 @@ export const MOCK_DATA_FOR_CHART_UTILS: ITestRunResultData[] = [ average_memory: 512, bytes_throughput: 11, request_throughput: 21 - } + } }, { id: 2, diff --git a/portal/src/app/components/home/components/experiment/components/charts/utils/chart.utils.ts b/portal/src/app/components/home/components/experiment/components/charts/utils/chart.utils.ts index 1e407bd7..f8954a8e 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/utils/chart.utils.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/utils/chart.utils.ts @@ -9,6 +9,6 @@ export function getKeysOfData(results: IResult): string[] { return Object.keys(results); } -export function getChartTitleByType(type: string): string | undefined { +export function getChartTitleByType(type: string): string { return ChartTitleDisplayMapping[type as unknown as ChartKey]; } diff --git a/portal/src/app/components/home/components/experiment/components/charts/utils/test-run.utils.test.ts b/portal/src/app/components/home/components/experiment/components/charts/utils/test-run.utils.test.ts index 72420406..18be0fc4 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/utils/test-run.utils.test.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/utils/test-run.utils.test.ts @@ -1,6 +1,6 @@ import { renderHook } from '@testing-library/react'; import { sortDataByAlgorithm } from './test-run.utils'; -import { MOCK_DATA_TO_SORT_BY_ALGORITHM, MOCK_DATA_TO_SORT_BY_ITERATION } from './__mocks__'; +import { MOCK_DATA_TO_SORT_BY_ALGORITHM, MOCK_DATA_TO_SORT_BY_ITERATION, MOCK_DATA_TO_SORT_BY_MESSAGE_SIZE, MOCK_DATA_WITH_NO_NEED_TO_SORT } from './__mocks__'; describe('test-run utils', () => { test('should sort data by algorithm', () => { @@ -37,4 +37,38 @@ describe('test-run utils', () => { } }); }); + + test('should sort data by message size', () => { + const { result } = renderHook(() => sortDataByAlgorithm(MOCK_DATA_TO_SORT_BY_MESSAGE_SIZE)); + expect(result.current[0]).toEqual({ + id: 2, + algorithm: "Algorithm1", + iterations: 1000, + message_size: 512, + results: + { + average_cpu: 25, + average_memory: 52, + bytes_throughput: 11, + request_throughput: 21 + } + }); + }); + + test('should not sort data by any parameter', () => { + const { result } = renderHook(() => sortDataByAlgorithm(MOCK_DATA_WITH_NO_NEED_TO_SORT)); + expect(result.current[0]).toEqual({ + id: 1, + algorithm: "Algorithm1", + iterations: 1000, + message_size: 1024, + results: + { + average_cpu: 25.5, + average_memory: 512, + bytes_throughput: 11, + request_throughput: 21 + } + }); + }); }); diff --git a/portal/src/app/components/home/components/experiment/components/charts/utils/test-run.utils.ts b/portal/src/app/components/home/components/experiment/components/charts/utils/test-run.utils.ts index db8cd0e2..b9695b40 100644 --- a/portal/src/app/components/home/components/experiment/components/charts/utils/test-run.utils.ts +++ b/portal/src/app/components/home/components/experiment/components/charts/utils/test-run.utils.ts @@ -14,6 +14,12 @@ export function sortDataByAlgorithm(dataObject: ITestRunResultData[]) { if (a.iterations > b.iterations) { return 1; } + if (a.message_size < b.message_size) { + return -1; + } + if (a.message_size > b.message_size) { + return 1; + } return 0; }); } \ No newline at end of file diff --git a/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/DeleteExperimentModal.test.tsx b/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/DeleteExperimentModal.test.tsx index ada5f26b..a0c4275c 100644 --- a/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/DeleteExperimentModal.test.tsx +++ b/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/DeleteExperimentModal.test.tsx @@ -1,26 +1,36 @@ /* eslint-disable @typescript-eslint/typedef */ -import { act, fireEvent, render, RenderResult } from '@testing-library/react'; +import { act, fireEvent, render, RenderResult, waitFor } from '@testing-library/react'; import { DeleteExperimentModal, DeleteExperimentModalProps } from './DeleteExperimentModal'; +import { DELETE_EXPERIMENT_MODAL_EN } from './translate/en'; -describe('EditExperimentModal', () => { - test('renders edit Experiment modal correctly', () => { +describe('DeleteExperimentModal', () => { + test('renders edit Experiment modal correctly', async () => { const props: DeleteExperimentModalProps = { name: ['Test'], onClose: jest.fn(), }; - const { baseElement }: RenderResult = render(TestMe); - expect(baseElement.firstChild).toMatchSnapshot(); + const { container }: RenderResult = render(TestMe); + + await waitFor(() => { + expect(container).toBeTruthy(); + }); }); - test('click submit button', () => { + test('click submit button', async () => { const handleClose = jest.fn(); const props: DeleteExperimentModalProps = { name: ['Test'], onClose: handleClose, }; - const { getByRole }: RenderResult = render(TestMe); + + const { container, getByRole, getByText }: RenderResult = render(TestMe); act(() => { fireEvent.submit(getByRole('button', { name: /Confirm/i })); + fireEvent.click(getByText(DELETE_EXPERIMENT_MODAL_EN.SUBMIT_ACTION)); + }); + + await waitFor(() => { + expect(container).toBeTruthy(); }); }); }); diff --git a/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/__snapshots__/DeleteExperimentModal.test.tsx.snap b/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/__snapshots__/DeleteExperimentModal.test.tsx.snap deleted file mode 100644 index 38b65566..00000000 --- a/portal/src/app/components/home/components/experiment/components/delete-experiment-modal/__snapshots__/DeleteExperimentModal.test.tsx.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EditExperimentModal renders edit Experiment modal correctly 1`] = ` -
-`; diff --git a/portal/src/app/components/home/components/experiment/components/edit-experiment-modal/EditExperimentModal.test.tsx b/portal/src/app/components/home/components/experiment/components/edit-experiment-modal/EditExperimentModal.test.tsx index cb232a9e..d72be8b2 100644 --- a/portal/src/app/components/home/components/experiment/components/edit-experiment-modal/EditExperimentModal.test.tsx +++ b/portal/src/app/components/home/components/experiment/components/edit-experiment-modal/EditExperimentModal.test.tsx @@ -1,26 +1,103 @@ /* eslint-disable @typescript-eslint/typedef */ -import { act, fireEvent, render, RenderResult } from '@testing-library/react'; +import { act, fireEvent, render, RenderResult, waitFor } from '@testing-library/react'; import { EditExperimentModal, EditExperimentModalProps } from './EditExperimentModal'; +import React from 'react'; +import { FetchDataStatus, useFetch } from '../../../../../../shared/hooks/useFetch'; +import { EDIT_EXPERIMENT_MODAL_EN } from './translate/en'; + +jest.mock('../../../../../../shared/hooks/useFetch'); describe('EditExperimentModal', () => { - test('renders edit Experiment modal correctly', () => { + beforeEach(() => { + (useFetch as jest.Mock).mockReturnValue({ + put: jest.fn(), + status: FetchDataStatus.Success, + error: null, + cancelRequest: jest.fn(), + }); + }); + + test('should render EditExperimentModal correctly', async () => { const props: EditExperimentModalProps = { data: { name: 'Test', description: 'description' }, onClose: jest.fn(), }; - const { baseElement }: RenderResult = render(TestMe); - expect(baseElement.firstChild).toMatchSnapshot(); + const { container, getByTestId }: RenderResult = render(EditExperimentModal); + const editExperimentFormElement: HTMLElement = getByTestId('edit_experiment_form'); + + fireEvent.submit(editExperimentFormElement); + + await waitFor(() => { + expect(container).toBeTruthy(); + }); }); - test('click submit button', () => { + test('should not close the modal when the status is still on fetching', async () => { + (useFetch as jest.Mock).mockReturnValue({ + put: jest.fn(), + status: FetchDataStatus.Fetching, + error: null, + cancelRequest: jest.fn(), + }); + const props: EditExperimentModalProps = { + data: { name: '', description: '' }, + onClose: jest.fn(), + }; + const { container, getByText }: RenderResult = render(EditExperimentModal); + fireEvent.click(getByText(EDIT_EXPERIMENT_MODAL_EN.SUBMIT_ACTION)); + + await waitFor(() => { + expect(container).toBeTruthy(); + }); + }); + + test('should click submit button and trigger onSubmitHandler', async () => { const handleClose = jest.fn(); const props: EditExperimentModalProps = { - data: { name: 'Test', description: 'description' }, + data: { name: 'Test', description: 'description' }, onClose: handleClose, }; - const { getByRole }: RenderResult = render(TestMe); + const { container, getByRole }: RenderResult = render(TestMe); act(() => { - fireEvent.submit(getByRole('button', { name: /Save/i })); + fireEvent.change(getByRole('button', { name: /Save/i })); + }); + + await waitFor(() => { + expect(container).toBeTruthy(); }); }); + + test('should update name on change', () => { + const setName = jest.fn(); + const props: EditExperimentModalProps = { + data: { name: 'Test', description: 'description' }, + onClose: jest.fn(), + }; + + jest.spyOn(React, 'useState').mockImplementation(() => ['', setName]); + const { getByTestId } = render(TestMe); + const editExperimentFormElement: HTMLElement = getByTestId('edit_experiment_form'); + + fireEvent.change(getByTestId('experiment_name_input'), { target: { value: 'new name' } }); + fireEvent.submit(editExperimentFormElement); + + expect(setName).toHaveBeenCalledWith('new name'); + }); + + test('should update description on change', () => { + const setDescription = jest.fn(); + const props: EditExperimentModalProps = { + data: { name: 'Test', description: 'description' }, + onClose: jest.fn(), + }; + + jest.spyOn(React, 'useState').mockImplementation(() => ['', setDescription]); + const { getByTestId } = render(TestMe); + const editExperimentFormElement: HTMLElement = getByTestId('edit_experiment_form'); + + fireEvent.change(getByTestId('description_input'), { target: { value: 'new description' } }); + fireEvent.submit(editExperimentFormElement); + + expect(setDescription).toHaveBeenCalledWith('new description'); + }); }); diff --git a/portal/src/app/components/home/components/experiment/components/edit-experiment-modal/EditExperimentModal.tsx b/portal/src/app/components/home/components/experiment/components/edit-experiment-modal/EditExperimentModal.tsx index e2e10dce..e49d0cd1 100644 --- a/portal/src/app/components/home/components/experiment/components/edit-experiment-modal/EditExperimentModal.tsx +++ b/portal/src/app/components/home/components/experiment/components/edit-experiment-modal/EditExperimentModal.tsx @@ -79,14 +79,14 @@ export const EditExperimentModal: React.FC = (props: E size={BaseModalSize.SMALL} showSpinner={showSpinner} > -
+