{
+ const id: string = faker.datatype.uuid();
+ return {
+ id,
+ title: id,
+ description: faker.datatype.uuid(),
+ category: category || faker.datatype.uuid(),
+ iconProp: IconProp.Activity,
+ componentType: ComponentType.Component,
+ arguments: [],
+ returnValues: [],
+ inPorts: [],
+ outPorts: [],
+ };
+};
+
+const getComponentCategory: Function = (name?: string): ComponentCategory => {
+ return {
+ name: name || faker.datatype.uuid(),
+ description: `Description for ${name}`,
+ icon: IconProp.Activity,
+ };
+};
+
+describe('ComponentsModal', () => {
+ const mockedCategories: ComponentCategory[] = [
+ getComponentCategory(),
+ getComponentCategory(),
+ getComponentCategory(),
+ getComponentCategory(),
+ ];
+
+ const mockedComponents: ComponentMetadata[] = [
+ getComponentMetadata(mockedCategories[0]?.name),
+ getComponentMetadata(mockedCategories[1]?.name),
+ getComponentMetadata(mockedCategories[2]?.name),
+ getComponentMetadata(mockedCategories[3]?.name),
+ ];
+
+ const mockOnCloseModal: jest.Mock = jest.fn();
+ const mockOnComponentClick: jest.Mock = jest.fn();
+
+ it('should render without crashing', () => {
+ render(
+
+ );
+ });
+
+ it('should display search input', () => {
+ render(
+
+ );
+ expect(screen.getByPlaceholderText('Search...')).toBeInTheDocument();
+ });
+
+ it('should display categories and components', () => {
+ render(
+
+ );
+ for (const cat of mockedCategories) {
+ expect(screen.getByText(cat.name)).toBeInTheDocument();
+ }
+ for (const comp of mockedComponents) {
+ expect(screen.getByText(comp.title)).toBeInTheDocument();
+ }
+ });
+
+ it('should call onCloseModal when the close button is clicked', () => {
+ render(
+
+ );
+ fireEvent.click(screen.getByText('Close panel'));
+ expect(mockOnCloseModal).toHaveBeenCalled();
+ });
+
+ it('should call onComponentClick when a component is selected', () => {
+ render(
+
+ );
+ for (const [idx, comp] of mockedComponents.entries()) {
+ // simulate selecting a component
+ fireEvent.click(screen.getByText(comp.title));
+ expect(screen.getByText('Create')).not.toBeDisabled();
+
+ // simulate submitting
+ fireEvent.click(screen.getByText('Create'));
+
+ // check if onComponentClick was called with the selected component's metadata
+ expect(mockOnComponentClick).toHaveBeenNthCalledWith(idx + 1, comp);
+ }
+ });
+
+ it('should display a message when no components are available', () => {
+ render(
+
+ );
+ expect(
+ screen.getByText(
+ 'No components that match your search. If you are looking for an integration that does not exist currently - you can use Custom Code or API component to build anything you like. If you are an enterprise customer, feel free to talk to us and we will build it for you.'
+ )
+ ).toBeInTheDocument();
+ });
+
+ it('should not display categories when there are no categories', () => {
+ render(
+
+ );
+ mockedCategories.forEach((category: ComponentCategory) => {
+ expect(screen.queryByText(category.name)).not.toBeInTheDocument();
+ });
+ });
+
+ it('should display no components message when search yields no results', () => {
+ render(
+
+ );
+ fireEvent.change(screen.getByPlaceholderText('Search...'), {
+ target: { value: 'Non-existent Ccmponent' },
+ });
+ expect(
+ screen.getByText(
+ 'No components that match your search. If you are looking for an integration that does not exist currently - you can use Custom Code or API component to build anything you like. If you are an enterprise customer, feel free to talk to us and we will build it for you.'
+ )
+ ).toBeInTheDocument();
+ });
+
+ it('should disable submit button prop when no component is selected', () => {
+ render(
+
+ );
+ const submitButton: HTMLElement = screen.getByText('Create');
+ expect(submitButton).toBeDisabled();
+ });
+
+ it('should change submitButtonDisabled to false when a component is selected', () => {
+ render(
+
+ );
+ for (const comp of mockedComponents) {
+ fireEvent.click(screen.getByText(comp.title));
+ const submitButton: HTMLElement = screen.getByText('Create');
+ expect(submitButton).not.toBeDisabled();
+ }
+ });
+
+ // search tests
+
+ it('should filter components based on search input', () => {
+ render(
+
+ );
+
+ mockedComponents.forEach((comp: ComponentMetadata) => {
+ const partialTitle: string = comp.title.substring(
+ 0,
+ comp.title.length - comp.title.length / 2
+ );
+ fireEvent.change(screen.getByPlaceholderText('Search...'), {
+ target: { value: partialTitle },
+ });
+ expect(screen.getByText(comp.title)).toBeInTheDocument();
+
+ // check other components are not displayed
+ mockedComponents
+ .filter((c: ComponentMetadata) => {
+ return c.title !== comp.title;
+ })
+ .forEach((c: ComponentMetadata) => {
+ return expect(
+ screen.queryByText(c.title)
+ ).not.toBeInTheDocument();
+ });
+ });
+ });
+
+ it('should filter components based on description when searching', () => {
+ render(
+
+ );
+ mockedComponents.forEach((comp: ComponentMetadata) => {
+ fireEvent.change(screen.getByPlaceholderText('Search...'), {
+ target: { value: comp.description },
+ });
+ expect(screen.getByText(comp.title)).toBeInTheDocument();
+
+ // check other components are not displayed
+ mockedComponents
+ .filter((c: ComponentMetadata) => {
+ return c.title !== comp.title;
+ })
+ .forEach((c: ComponentMetadata) => {
+ return expect(
+ screen.queryByText(c.title)
+ ).not.toBeInTheDocument();
+ });
+ });
+ });
+
+ it('should filter components based on category when searching', () => {
+ render(
+
+ );
+ mockedComponents.forEach((comp: ComponentMetadata) => {
+ fireEvent.change(screen.getByPlaceholderText('Search...'), {
+ target: { value: comp.category },
+ });
+ expect(screen.getByText(comp.title)).toBeInTheDocument();
+
+ // check other components are not displayed
+ mockedComponents
+ .filter((c: ComponentMetadata) => {
+ return c.category !== comp.category;
+ })
+ .forEach((c: ComponentMetadata) => {
+ return expect(
+ screen.queryByText(c.title)
+ ).not.toBeInTheDocument();
+ });
+ });
+ });
+
+ it('should show all components when search is cleared', () => {
+ render(
+
+ );
+ mockedComponents.forEach((comp: ComponentMetadata) => {
+ const searchInput: HTMLElement =
+ screen.getByPlaceholderText('Search...');
+ fireEvent.change(searchInput, { target: { value: comp.title } });
+ fireEvent.change(searchInput, { target: { value: '' } }); // clear search
+
+ mockedComponents.forEach((c: ComponentMetadata) => {
+ return expect(screen.getByText(c.title)).toBeInTheDocument();
+ });
+ });
+ });
+
+ it('should return multiple components when similar titles match', () => {
+ // we add a new component where its title is a substring of another component's title
+ const commonWord: string =
+ mockedComponents[0]?.title.substring(0, 5) || '';
+ const newComponent: ComponentMetadata = getComponentMetadata(
+ mockedCategories[1]?.name
+ );
+ newComponent.title += commonWord;
+ mockedComponents.push(newComponent);
+ const componentsWithCommonWord: ComponentMetadata[] =
+ mockedComponents.filter((comp: ComponentMetadata) => {
+ return comp.title.includes(commonWord);
+ });
+
+ render(
+
+ );
+
+ fireEvent.change(screen.getByPlaceholderText('Search...'), {
+ target: { value: commonWord },
+ });
+ componentsWithCommonWord.forEach((comp: ComponentMetadata) => {
+ expect(screen.getByText(comp.title)).toBeInTheDocument();
+ });
+ });
+
+ it('should return return components with similar descriptions', () => {
+ // we add a new component where its title is a substring of another component's description
+ const partialDescription: string =
+ mockedComponents[0]?.description.substring(0, 10) || '';
+ const newComponent: ComponentMetadata = getComponentMetadata(
+ mockedCategories[1]?.name
+ );
+ newComponent.title = partialDescription || '';
+ mockedComponents.push(newComponent);
+ render(
+
+ );
+
+ fireEvent.change(screen.getByPlaceholderText('Search...'), {
+ target: { value: partialDescription },
+ });
+ expect(
+ screen.getAllByText(new RegExp(partialDescription, 'i'))
+ ).toHaveLength(2);
+ });
+
+ it('should return components with the same category', () => {
+ // we add two components with the same category as the first component
+ const commonCategory: string | undefined =
+ mockedComponents[0]?.category;
+ mockedComponents.push(getComponentMetadata(commonCategory));
+ mockedComponents.push(getComponentMetadata(commonCategory));
+ const componentsInCommonCategory: ComponentMetadata[] =
+ mockedComponents.filter((comp: ComponentMetadata) => {
+ return comp.category === commonCategory;
+ });
+
+ render(
+
+ );
+
+ fireEvent.change(screen.getByPlaceholderText('Search...'), {
+ target: { value: commonCategory },
+ });
+ componentsInCommonCategory.forEach((comp: ComponentMetadata) => {
+ expect(screen.getByText(comp.title)).toBeInTheDocument();
+ });
+ });
+});
diff --git a/CommonUI/src/Tests/Components/FilePicker.test.tsx b/CommonUI/src/Tests/Components/FilePicker.test.tsx
new file mode 100644
index 0000000000..dd96cdb777
--- /dev/null
+++ b/CommonUI/src/Tests/Components/FilePicker.test.tsx
@@ -0,0 +1,386 @@
+import React from 'react';
+import { act } from 'react-test-renderer';
+
+import { faker } from '@faker-js/faker';
+import {
+ render,
+ fireEvent,
+ screen,
+ waitFor,
+ queryByAttribute,
+ queryAllByAttribute,
+ queryByTestId,
+} from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+
+import MimeType from 'Common/Types/File/MimeType';
+import FileModel from 'Model/Models/File';
+import ModelAPI from '../../Utils/ModelAPI/ModelAPI';
+import HTTPResponse from 'Common/Types/API/HTTPResponse';
+import ObjectID from 'Common/Types/ObjectID';
+
+import FilePicker from '../../Components/FilePicker/FilePicker';
+
+const mockOnChange: jest.Mock = jest.fn();
+const mockOnBlur: jest.Mock = jest.fn();
+
+jest.mock('../../Utils/ModelAPI/ModelAPI', () => {
+ return {
+ create: jest.fn(),
+ };
+});
+
+interface DefaultProps {
+ onBlur: () => void;
+ onChange: (files: FileModel[]) => void;
+ mimeTypes: MimeType[];
+ isOpen: boolean;
+ onClose: () => void;
+ initialValue?: FileModel | FileModel[];
+ value?: FileModel[] | undefined;
+ isMultiFilePicker?: boolean;
+ readOnly?: boolean;
+}
+
+interface DataTransfer {
+ dataTransfer: {
+ files: File[];
+ types: string[];
+ };
+}
+
+const mockCreateResponse: Function = async (
+ file: File
+): Promise
> => {
+ return new HTTPResponse(
+ 200,
+ {
+ file: (await file.arrayBuffer()) as Buffer,
+ name: file.name,
+ type: file.type,
+ slug: file.name,
+ isPublic: true,
+ },
+ {}
+ );
+};
+
+const mockFileModel: Function = async (file: File): Promise => {
+ const fileModel: FileModel = new FileModel(new ObjectID('123'));
+ fileModel.name = file.name;
+ fileModel.type = file.type as MimeType;
+ fileModel.slug = file.name;
+ fileModel.isPublic = true;
+ fileModel.file = (await file.arrayBuffer()) as Buffer;
+ return fileModel;
+};
+
+const mockFile: Function = (): File => {
+ const mockArrayBuffer: jest.Mock = jest.fn();
+ mockArrayBuffer.mockResolvedValue(new ArrayBuffer(10)); // Mocked array buffer of size 10
+
+ const file: File = new File(
+ [faker.datatype.string()],
+ faker.system.commonFileName(MimeType.png),
+ { type: MimeType.png }
+ );
+ file.arrayBuffer = mockArrayBuffer;
+ return file;
+};
+
+const defaultProps: DefaultProps = {
+ onBlur: mockOnBlur,
+ onChange: mockOnChange,
+ mimeTypes: [MimeType.png],
+ isOpen: true,
+ onClose: jest.fn(),
+};
+
+describe('FilePicker', () => {
+ const MOCK_FILE_URL: string = 'https://mock-file-url';
+
+ beforeAll(() => {
+ global.URL.createObjectURL = jest.fn(() => {
+ return MOCK_FILE_URL;
+ });
+ });
+
+ afterAll(() => {
+ (
+ global.URL.createObjectURL as jest.MockedFunction<
+ typeof global.URL.createObjectURL
+ >
+ ).mockRestore();
+ });
+
+ beforeEach(() => {
+ delete defaultProps.isMultiFilePicker;
+ delete defaultProps.initialValue;
+ delete defaultProps.value;
+ delete defaultProps.readOnly;
+ });
+
+ it('should render without crashing', () => {
+ render();
+ expect(screen.getByText('Upload a file')).toBeInTheDocument();
+ expect(screen.getByRole('complementary')).toBeInTheDocument(); // aside element
+ });
+
+ it('should render with initial value', async () => {
+ defaultProps.initialValue = await mockFileModel(mockFile());
+ const { container } = render();
+ expect(
+ queryByAttribute('src', container, MOCK_FILE_URL)
+ ).toBeInTheDocument();
+ });
+
+ it('should not render if file is missing the `file` attribute', async () => {
+ const file: FileModel = await mockFileModel(mockFile());
+ delete file.file;
+ defaultProps.initialValue = file;
+ const { container } = render();
+ expect(
+ queryByAttribute('src', container, MOCK_FILE_URL)
+ ).not.toBeInTheDocument();
+ });
+
+ it('should render with initial value as array', async () => {
+ defaultProps.initialValue = [
+ await mockFileModel(mockFile()),
+ await mockFileModel(mockFile()),
+ ];
+ render();
+ const { container } = render();
+ expect(
+ queryAllByAttribute('src', container, MOCK_FILE_URL)
+ ).toHaveLength(2);
+ });
+
+ it('should render with value array with one element', async () => {
+ defaultProps.value = [await mockFileModel(mockFile())];
+ const { container } = render();
+ expect(
+ queryByAttribute('src', container, MOCK_FILE_URL)
+ ).toBeInTheDocument();
+ });
+
+ it('should render with value array with more than one element', async () => {
+ defaultProps.value = [
+ await mockFileModel(mockFile()),
+ await mockFileModel(mockFile()),
+ ];
+ render();
+ const { container } = render();
+ expect(
+ queryAllByAttribute('src', container, MOCK_FILE_URL)
+ ).toHaveLength(2);
+ });
+
+ it('should not upload file when dropped and readOnly is true', async () => {
+ defaultProps.readOnly = true;
+
+ const file: File = mockFile();
+ const data: DataTransfer = {
+ dataTransfer: {
+ files: [file],
+ types: ['Files'],
+ },
+ };
+
+ const createResponse: HTTPResponse =
+ await mockCreateResponse(file);
+ (
+ ModelAPI.create as jest.MockedFunction
+ ).mockResolvedValue(createResponse);
+
+ const { container } = render();
+
+ const dropzone: HTMLElement = screen.getByLabelText('Upload a file');
+ fireEvent.drop(dropzone, data);
+
+ await waitFor(() => {
+ expect(
+ queryByAttribute('src', container, MOCK_FILE_URL)
+ ).not.toBeInTheDocument();
+ });
+ });
+
+ it('should throw an "File too large" when uploading a file that fails on arrayBuffer()', async () => {
+ const file: File = mockFile();
+ file.arrayBuffer = jest
+ .fn()
+ .mockRejectedValue(new Error('File too large'));
+ const data: DataTransfer = {
+ dataTransfer: {
+ files: [file],
+ types: ['Files'],
+ },
+ };
+
+ const { container } = render();
+
+ const dropzone: HTMLElement = screen.getByLabelText('Upload a file');
+ fireEvent.drop(dropzone, data);
+
+ await waitFor(() => {
+ expect(
+ queryByAttribute('src', container, MOCK_FILE_URL)
+ ).not.toBeInTheDocument();
+ });
+ });
+
+ it('should upload a file when dropped', async () => {
+ const file: File = mockFile();
+ const data: DataTransfer = {
+ dataTransfer: {
+ files: [file],
+ types: ['Files'],
+ },
+ };
+
+ const createResponse: HTTPResponse =
+ await mockCreateResponse(file);
+ (
+ ModelAPI.create as jest.MockedFunction
+ ).mockResolvedValue(createResponse);
+
+ const { container } = render();
+
+ const dropzone: HTMLElement = screen.getByLabelText('Upload a file');
+ await act(async () => {
+ fireEvent.drop(dropzone, data);
+ });
+
+ await waitFor(() => {
+ expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]);
+ expect(mockOnBlur).toHaveBeenCalled();
+ expect(mockOnBlur).toHaveBeenCalled();
+ expect(
+ queryByAttribute('src', container, MOCK_FILE_URL)
+ ).toBeInTheDocument();
+ });
+ });
+
+ it('should upload a file when dropped', async () => {
+ const file: File = mockFile();
+ const data: DataTransfer = {
+ dataTransfer: {
+ files: [file],
+ types: ['Files'],
+ },
+ };
+
+ const createResponse: HTTPResponse =
+ await mockCreateResponse(file);
+ (
+ ModelAPI.create as jest.MockedFunction
+ ).mockResolvedValue(createResponse);
+
+ const { container } = render();
+
+ const dropzone: HTMLElement = screen.getByLabelText('Upload a file');
+ await act(async () => {
+ fireEvent.drop(dropzone, data);
+ });
+
+ await waitFor(() => {
+ expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]);
+ expect(mockOnBlur).toHaveBeenCalled();
+ expect(mockOnBlur).toHaveBeenCalled();
+ expect(
+ queryByAttribute('src', container, MOCK_FILE_URL)
+ ).toBeInTheDocument();
+ });
+ });
+
+ it('should show loader a file when files are being uploaded', async () => {
+ const uploadPromise: Promise = new Promise((resolve: any) => {
+ (global as any).mockUploadResolve = resolve; // Store resolve function globally or in a scope accessible outside the test
+ });
+
+ (
+ ModelAPI.create as jest.MockedFunction
+ ).mockImplementation((): any => {
+ return uploadPromise;
+ });
+
+ const file: File = mockFile();
+ const data: DataTransfer = {
+ dataTransfer: {
+ files: [file],
+ types: ['Files'],
+ },
+ };
+
+ const createResponse: HTTPResponse =
+ await mockCreateResponse(file);
+
+ const { container } = render();
+
+ const dropzone: HTMLElement = screen.getByLabelText('Upload a file');
+ await act(async () => {
+ fireEvent.drop(dropzone, data);
+ });
+
+ expect(queryByTestId(container, 'loader')).toBeInTheDocument();
+
+ (global as any).mockUploadResolve(createResponse);
+
+ await waitFor(() => {
+ expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]);
+ expect(mockOnBlur).toHaveBeenCalled();
+ expect(mockOnBlur).toHaveBeenCalled();
+ expect(
+ queryByAttribute('src', container, MOCK_FILE_URL)
+ ).toBeInTheDocument();
+ });
+ });
+
+ it('should delete an uploaded file when clicking on it', async () => {
+ const file: File = mockFile();
+ const data: DataTransfer = {
+ dataTransfer: {
+ files: [file],
+ types: ['Files'],
+ },
+ };
+
+ const createResponse: HTTPResponse =
+ await mockCreateResponse(file);
+ (
+ ModelAPI.create as jest.MockedFunction
+ ).mockResolvedValue(createResponse);
+
+ const { container } = render();
+
+ const dropzone: HTMLElement = screen.getByLabelText('Upload a file');
+ await act(async () => {
+ fireEvent.drop(dropzone, data);
+ });
+
+ await waitFor(() => {
+ // file should be in the dropzone
+ expect(
+ queryByAttribute('src', container, MOCK_FILE_URL)
+ ).toBeInTheDocument();
+ });
+
+ const deleteIcon: ChildNode = screen
+ .getByRole('icon')
+ .childNodes.item(0); // svg item
+ // remove file by clicking on it
+ if (deleteIcon) {
+ await act(async () => {
+ fireEvent.click(deleteIcon.childNodes.item(0), data);
+ });
+ }
+
+ await waitFor(() => {
+ // file should have been removed
+ expect(mockOnChange).toHaveBeenCalledWith([createResponse.data]);
+ expect(
+ queryByAttribute('src', container, MOCK_FILE_URL)
+ ).not.toBeInTheDocument();
+ });
+ });
+});
diff --git a/CommonUI/src/Tests/Components/Probe.test.tsx b/CommonUI/src/Tests/Components/Probe.test.tsx
new file mode 100644
index 0000000000..675fe81966
--- /dev/null
+++ b/CommonUI/src/Tests/Components/Probe.test.tsx
@@ -0,0 +1,65 @@
+import * as React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+import ProbeElement from '../../Components/Probe/Probe';
+import Probe from 'Model/Models/Probe';
+import ObjectID from 'Common/Types/ObjectID';
+
+describe('ProbeElement Component', () => {
+ const mockProbe: Probe = new Probe();
+ mockProbe.name = 'Test Probe';
+ mockProbe.iconFileId = new ObjectID('12345');
+
+ test('should display the probe name', () => {
+ render();
+ const probeName: HTMLElement = screen.getByTestId('probe-name');
+ expect(probeName).toBeInTheDocument();
+ });
+
+ test('should display the image when iconFileId is present', () => {
+ render();
+ const imageElement: HTMLImageElement =
+ screen.getByTestId('probe-image');
+ expect(imageElement).toBeInTheDocument();
+ expect(imageElement).toHaveAttribute('alt', 'Test Probe');
+ });
+
+ test('should display the default icon when iconFileId is not present', () => {
+ const probeWithoutIcon: Probe = new Probe();
+ probeWithoutIcon.name = 'Test Probe';
+
+ render();
+ const iconElement: HTMLElement = screen.getByTestId('probe-icon');
+ expect(iconElement).toBeInTheDocument();
+ });
+
+ test('should display "No probe found" when no probe is provided', () => {
+ render();
+ const noProbeText: HTMLElement = screen.getByTestId('probe-not-found');
+ expect(noProbeText).toBeInTheDocument();
+ });
+
+ test('should display the image with correct src when iconFileId is present', () => {
+ const probeWithIcon: Probe = new Probe();
+ probeWithIcon.iconFileId = new ObjectID('icon123');
+ probeWithIcon.name = 'Probe with Icon';
+
+ render();
+ const imageElement: HTMLImageElement =
+ screen.getByTestId('probe-image');
+ expect(imageElement).toBeInTheDocument();
+ expect(imageElement).toHaveAttribute(
+ 'src',
+ expect.stringContaining('icon123')
+ );
+ });
+
+ test('should display the default icon when iconFileId is not present', () => {
+ const probeWithoutIcon: Probe = new Probe();
+ probeWithoutIcon.name = 'Probe without Icon';
+
+ render();
+ const iconElement: HTMLElement = screen.getByTestId('probe-icon');
+ expect(iconElement).toBeInTheDocument();
+ });
+});
diff --git a/CommonUI/src/Tests/Components/ProgressBar.test.tsx b/CommonUI/src/Tests/Components/ProgressBar.test.tsx
new file mode 100644
index 0000000000..8353dc83a8
--- /dev/null
+++ b/CommonUI/src/Tests/Components/ProgressBar.test.tsx
@@ -0,0 +1,51 @@
+import * as React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+import ProgressBar from '../../Components/ProgressBar/ProgressBar';
+
+describe('ProgressBar Component', () => {
+ function getProgressBar(): HTMLElement {
+ const element: HTMLElement = screen.getByTestId('progress-bar');
+ if (!element) {
+ throw 'Not Found';
+ }
+ return element;
+ }
+
+ test('should calculate and display the correct percentage', () => {
+ render();
+ const progressBar: HTMLElement = getProgressBar();
+ expect(progressBar).toHaveStyle({ width: '0%' });
+ });
+
+ test('should display the correct count and total count with suffix', () => {
+ render();
+ const countText: HTMLElement = screen.getByTestId('progress-bar-count');
+ const totalCountText: HTMLElement = screen.getByTestId(
+ 'progress-bar-total-count'
+ );
+ expect(countText).toBeInTheDocument();
+ expect(totalCountText).toBeInTheDocument();
+
+ expect(countText.innerHTML).toEqual('30 items');
+ expect(totalCountText.innerHTML).toEqual('99 items');
+ });
+
+ test('should handle zero total count without crashing', () => {
+ render();
+ const progressBar: HTMLElement = getProgressBar();
+ expect(progressBar).toHaveStyle({ width: '100%' });
+ });
+
+ test('should round up the percentage to the nearest integer', () => {
+ render();
+ const progressBar: HTMLElement = getProgressBar();
+ expect(progressBar).toHaveStyle({ width: '33%' });
+ });
+
+ test('should cap the percentage at 100 if count exceeds total count', () => {
+ render();
+ const progressBar: HTMLElement = getProgressBar();
+ expect(progressBar).toHaveStyle({ width: '100%' });
+ });
+});
diff --git a/Dashboard/src/Components/Header/Header.tsx b/Dashboard/src/Components/Header/Header.tsx
index 9f33bb75c2..1eb24a820c 100644
--- a/Dashboard/src/Components/Header/Header.tsx
+++ b/Dashboard/src/Components/Header/Header.tsx
@@ -175,7 +175,8 @@ const DashboardHeader: FunctionComponent = (
getAllEnvVars()
) &&
props.paymentMethodsCount !== undefined &&
- props.paymentMethodsCount === 0 ? (
+ props.paymentMethodsCount === 0 &&
+ !props.selectedProject.resellerId ? (
diff --git a/Dashboard/src/Components/Monitor/MonitorTable.tsx b/Dashboard/src/Components/Monitor/MonitorTable.tsx
index c7b1b4a074..b4e6728e30 100644
--- a/Dashboard/src/Components/Monitor/MonitorTable.tsx
+++ b/Dashboard/src/Components/Monitor/MonitorTable.tsx
@@ -214,7 +214,11 @@ const MonitorsTable: FunctionComponent