Skip to content

Commit

Permalink
Merge pull request #1270 from violetadev/violetadev/testsetup
Browse files Browse the repository at this point in the history
[Tests] extract jest config to file, add jest commands, add mocks and add missing tests
  • Loading branch information
manojVivek authored Aug 5, 2024
2 parents c29486f + cada3b5 commit 1d0b031
Show file tree
Hide file tree
Showing 7 changed files with 443 additions and 34 deletions.
21 changes: 21 additions & 0 deletions desktop-app/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module.exports = {
moduleDirectories: ['node_modules', 'release/app/node_modules', 'src'],
moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json'],
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/.erb/mocks/fileMock.js',
'\\.(css|less|sass|scss)$': 'identity-obj-proxy',
},
setupFiles: [
'./.erb/scripts/check-build-exists.ts',
'<rootDir>/setupTests.js',
],
testEnvironment: 'jsdom',
testEnvironmentOptions: {
url: 'http://localhost/',
},
testPathIgnorePatterns: ['release/app/dist', '.erb/dll'],
transform: {
'\\.(ts|tsx|js|jsx)$': 'ts-jest',
},
};
36 changes: 3 additions & 33 deletions desktop-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
"start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts",
"start:preloadWebview": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload-webview.dev.ts",
"start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts",
"test": "jest",
"test": "jest --config ./jest.config.js",
"test:watch": "jest --watch --config ./jest.config.js",
"test:coverage": "jest --coverage --config ./jest.config.js",
"typecheck": "tsc --noEmit"
},
"lint-staged": {
Expand Down Expand Up @@ -70,38 +72,6 @@
}
]
},
"jest": {
"moduleDirectories": [
"node_modules",
"release/app/node_modules",
"src"
],
"moduleFileExtensions": [
"js",
"jsx",
"ts",
"tsx",
"json"
],
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/.erb/mocks/fileMock.js",
"\\.(css|less|sass|scss)$": "identity-obj-proxy"
},
"setupFiles": [
"./.erb/scripts/check-build-exists.ts"
],
"testEnvironment": "jsdom",
"testEnvironmentOptions": {
"url": "http://localhost/"
},
"testPathIgnorePatterns": [
"release/app/dist",
".erb/dll"
],
"transform": {
"\\.(ts|tsx|js|jsx)$": "ts-jest"
}
},
"dependencies": {
"@fontsource/lato": "^5.0.17",
"@headlessui-float/react": "^0.12.0",
Expand Down
24 changes: 24 additions & 0 deletions desktop-app/setupTests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
window.electron = {
ipcRenderer: {
sendMessage: jest.fn(),
on: jest.fn(),
once: jest.fn(),
invoke: jest.fn(),
removeListener: jest.fn(),
removeAllListeners: jest.fn(),
},
store: {
set: jest.fn(),
get: jest.fn(),
},
};

global.IntersectionObserver = jest.fn(() => ({
root: null,
rootMargin: '',
thresholds: [],
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
takeRecords: jest.fn(),
}));
87 changes: 87 additions & 0 deletions desktop-app/src/renderer/components/Button/Button.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import '@testing-library/jest-dom';
import { act, render, screen } from '@testing-library/react';
import Button from './index';

jest.mock('@iconify/react', () => ({
Icon: () => <div data-testid="icon" />,
}));

describe('Button Component', () => {
it('renders with default props', () => {
render(<Button>Click me</Button>);
const buttonElement = screen.getByRole('button', { name: /click me/i });
expect(buttonElement).toBeInTheDocument();
});

it('applies custom class name', () => {
render(<Button className="custom-class">Click me</Button>);
const buttonElement = screen.getByRole('button', { name: /click me/i });
expect(buttonElement).toHaveClass('custom-class');
});

it('renders loading icon when isLoading is true', () => {
render(<Button isLoading>Click me</Button>);
const loadingIcon = screen.getByTestId('icon');
expect(loadingIcon).toBeInTheDocument();
});

it('renders confirmation icon when loading is done', () => {
jest.useFakeTimers();
const { rerender } = render(<Button isLoading>Click me</Button>);

act(() => {
rerender(<Button isLoading={false}>Click me</Button>);
jest.runAllTimers(); // Use act to advance timers
});

const confirmationIcon = screen.getByTestId('icon');
expect(confirmationIcon).toBeInTheDocument();
jest.useRealTimers();
});

it('applies primary button styles', () => {
render(<Button isPrimary>Click me</Button>);
const buttonElement = screen.getByRole('button', { name: /click me/i });
expect(buttonElement).toHaveClass('bg-emerald-500');
expect(buttonElement).toHaveClass('text-white');
});

it('applies action button styles', () => {
render(<Button isActionButton>Click me</Button>);
const buttonElement = screen.getByRole('button', { name: /click me/i });
expect(buttonElement).toHaveClass('bg-slate-200');
});

it('applies subtle hover styles', () => {
render(<Button subtle>Click me</Button>);
const buttonElement = screen.getByRole('button', { name: /click me/i });
expect(buttonElement).toHaveClass('hover:bg-slate-200');
});

it('disables hover effects when disableHoverEffects is true', () => {
render(
<Button disableHoverEffects subtle>
Click me
</Button>
);
const buttonElement = screen.getByRole('button', { name: /click me/i });
expect(buttonElement).not.toHaveClass('hover:bg-slate-200');
});

it('renders children correctly when not loading or loading done', () => {
render(<Button>Click me</Button>);
const buttonElement = screen.getByText('Click me');
expect(buttonElement).toBeInTheDocument();
});

it('does not render children when loading or loading done', () => {
const { rerender } = render(<Button isLoading>Click me</Button>);
expect(screen.queryByText('Click me')).not.toBeInTheDocument();

act(() => {
rerender(<Button isLoading={false}>Click me</Button>);
});

expect(screen.queryByText('Click me')).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import '@testing-library/jest-dom';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { Device } from 'common/deviceList';
import { Provider, useDispatch } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import {
addSuites,
deleteAllSuites,
} from 'renderer/store/features/device-manager';
import { ReactNode } from 'react';
import { transformFile } from './utils';
import { ManageSuitesTool } from './ManageSuitesTool';

jest.mock('renderer/store/features/device-manager', () => ({
addSuites: jest.fn(() => ({ type: 'addSuites' })),
deleteAllSuites: jest.fn(() => ({ type: 'deleteAllSuites' })),
default: jest.fn((state = {}) => state), // Mock the reducer as a function
}));

jest.mock('./utils', () => ({
transformFile: jest.fn(),
}));

jest.mock('renderer/components/FileUploader', () => ({
FileUploader: ({
handleFileUpload,
}: {
handleFileUpload: (file: File) => void;
}) => (
<button
type="button"
data-testid="mock-file-uploader"
onClick={() =>
handleFileUpload(
new File(['{}'], 'test.json', { type: 'application/json' })
)
}
>
Mock File Uploader
</button>
),
}));

jest.mock('./helpers', () => ({
onFileDownload: jest.fn(),
setCustomDevices: jest.fn(),
}));

jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useDispatch: jest.fn(),
}));

const renderWithRedux = (
component:
| string
| number
| boolean
| Iterable<ReactNode>
| JSX.Element
| null
| undefined
) => {
const store = configureStore({
reducer: {
deviceManager: jest.requireMock('renderer/store/features/device-manager')
.default,
},
});

return {
...render(<Provider store={store}>{component}</Provider>),
store,
};
};

describe('ManageSuitesTool', () => {
let setCustomDevicesStateMock: jest.Mock<() => void, Device[]>;
const dispatchMock = jest.fn();

beforeEach(() => {
(useDispatch as jest.Mock).mockReturnValue(dispatchMock);

setCustomDevicesStateMock = jest.fn();

renderWithRedux(
<ManageSuitesTool setCustomDevicesState={setCustomDevicesStateMock} />
);
});

it('renders the component correctly', () => {
expect(screen.getByTestId('download-btn')).toBeInTheDocument();
expect(screen.getByTestId('upload-btn')).toBeInTheDocument();
expect(screen.getByTestId('reset-btn')).toBeInTheDocument();
});

it('opens the modal when download button is clicked', () => {
fireEvent.click(screen.getByTestId('download-btn'));
expect(screen.getByText('Import your devices')).toBeInTheDocument();
});

it('opens the reset confirmation dialog when reset button is clicked', () => {
fireEvent.click(screen.getByTestId('reset-btn'));
expect(
screen.getByText('Do you want to reset all settings?')
).toBeInTheDocument();
});

it('closes the reset confirmation dialog when the close button is clicked', () => {
fireEvent.click(screen.getByTestId('reset-btn'));
fireEvent.click(screen.getByText('Cancel'));
expect(
screen.queryByText('Do you want to reset all settings?')
).not.toBeInTheDocument();
});

it('dispatches deleteAllSuites and clears custom devices on reset confirmation', async () => {
fireEvent.click(screen.getByTestId('reset-btn'));
fireEvent.click(screen.getByText('Confirm'));

await waitFor(() => {
expect(deleteAllSuites).toHaveBeenCalled();
expect(setCustomDevicesStateMock).toHaveBeenCalledWith([]);
});
});

it('handles successful file upload and processes custom devices and suites', async () => {
const mockSuites = [
{ id: '1', name: 'first suite', devices: [] },
{ id: '2', name: 'second suite', devices: [] },
];

(transformFile as jest.Mock).mockResolvedValue({
customDevices: ['device1', 'device2'],
suites: mockSuites,
});

fireEvent.click(screen.getByTestId('download-btn'));
fireEvent.click(screen.getByTestId('mock-file-uploader'));

await waitFor(() => {
expect(transformFile).toHaveBeenCalledWith(expect.any(File));
expect(dispatchMock).toHaveBeenCalledWith(addSuites(mockSuites));
});
});

it('handles error in file upload', async () => {
(transformFile as jest.Mock).mockRejectedValue(
new Error('File upload failed')
);

fireEvent.click(screen.getByTestId('download-btn'));

fireEvent.click(screen.getByTestId('mock-file-uploader'));

await waitFor(() => {
expect(transformFile).toHaveBeenCalledWith(expect.any(File));
expect(
screen.getByText('There has been an error, please try again.')
).toBeInTheDocument();
});

fireEvent.click(screen.getByText('Close'));

expect(
screen.queryByText('There has been an error, please try again.')
).not.toBeInTheDocument();
});
});
Loading

0 comments on commit 1d0b031

Please sign in to comment.