Skip to content

Commit

Permalink
chore(extension): add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
vetalcore committed May 31, 2023
1 parent 35bfd44 commit 65bdf1e
Show file tree
Hide file tree
Showing 50 changed files with 2,172 additions and 265 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Expand Up @@ -28,11 +28,11 @@ jobs:
LACE_EXTENSION_KEY: ${{ secrets.MANIFEST_PUBLIC_KEY }}
- name: Check for linter issues
run: yarn lint
- name: Run unit tests
- name: Run unit tests, generate test coverage report
env:
AVAILABLE_CHAINS: 'Preprod,Preview,Mainnet'
DEFAULT_CHAIN: 'Preprod'
run: yarn test --maxWorkers=2
run: yarn test:coverage --maxWorkers=2
- name: Upload build
uses: actions/upload-artifact@v3
with:
Expand Down
2 changes: 1 addition & 1 deletion apps/browser-extension-wallet/src/config.ts
Expand Up @@ -8,7 +8,7 @@ type CardanoServiceUrls = {
Preview: string;
};

type Config = {
export type Config = {
TOAST_DURATION: number;
CHAIN: Wallet.ChainName;
MNEMONIC_LENGTH: number;
Expand Down
@@ -0,0 +1,111 @@
/* eslint-disable unicorn/numeric-separators-style */
/* eslint-disable no-magic-numbers */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable import/imports-first */
const mockNotify = jest.fn();
const mockUseTranslation = jest.fn();
import { renderHook } from '@testing-library/react-hooks';
import { ActionExecutionArgs, useActionExecution } from '@hooks/useActionExecution';
import { ToastProps } from '@lace/common';

jest.mock('react-i18next', () => {
const original = jest.requireActual('@lace/common');
return {
__esModule: true,
...original,
useTranslation: mockUseTranslation
};
});

jest.mock('@lace/common', () => {
const original = jest.requireActual('@lace/common');
return {
__esModule: true,
...original,
toast: {
notify: mockNotify
}
};
});

describe('Testing useActionExecution hook', () => {
beforeEach(() => {
jest.resetAllMocks();
});

test('should execute action and return proper result', async () => {
mockUseTranslation.mockReturnValue({ t: jest.fn() });
const hook = renderHook((withProps = true) =>
useActionExecution(
withProps
? ({
shouldDisplayToastOnSuccess: true,
toastDuration: 'toastDuration'
} as unknown as ActionExecutionArgs)
: undefined
)
);
const result = 'result';
const action = jest.fn().mockImplementation(async () => await result);
const actionParams = { duration: 'duration', text: 'text' } as unknown as ToastProps;

expect(await hook.result.current[0](action, actionParams)).toEqual(result);
expect(mockNotify).toBeCalledWith(actionParams);

expect(await hook.result.current[0](action)).toEqual(result);
expect(mockNotify).toBeCalledTimes(1);

hook.rerender(false);

expect(await hook.result.current[0](action, actionParams)).toEqual(result);
expect(mockNotify).toBeCalledTimes(1);
});

test('should handle error scenarios', async () => {
const errorResult = 'errorResult';
const getErrorMessage = jest.fn().mockReturnValue(errorResult);

const t = jest.fn().mockImplementation((res) => res);
mockUseTranslation.mockReturnValue({
t
});

const hook = renderHook((withProps = true) =>
useActionExecution(
withProps
? ({
getErrorMessage,
toastDuration: 'toastDuration'
} as unknown as ActionExecutionArgs)
: undefined
)
);

const errorMessage = 'errorMessage';
const action = jest.fn().mockImplementation(async () => {
throw new Error(errorMessage);
});
try {
await hook.result.current[0](action);
} catch (error: any) {
expect(error).toEqual(errorResult);
}
expect(mockNotify).toBeCalledWith({
text: errorResult,
duration: 'toastDuration',
icon: 'test-file-stub'
});

hook.rerender(false);
try {
await hook.result.current[0](action);
} catch (error: any) {
expect(error).toEqual(errorMessage);
}
expect(mockNotify).toBeCalledWith({
text: errorMessage,
duration: 3,
icon: 'test-file-stub'
});
});
});
@@ -0,0 +1,46 @@
/* eslint-disable no-magic-numbers */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { renderHook } from '@testing-library/react-hooks';
import { useAddressValidation } from '@hooks/useAddressValidation';
import * as addressValidators from '@src/utils/validators';

jest.mock('@src/utils/validators', () => {
const original = jest.requireActual('@src/utils/validators');
return {
__esModule: true,
...original
};
});

describe('Testing useAddressValidation hook', () => {
beforeEach(() => {
jest.resetAllMocks();
});

test('should execute action and return proper result', async () => {
const isAddressValidResult = 'isAddressValidResult';
const initialAddress = 'initialAddress';
const isValidAddressSpy = jest
.spyOn(addressValidators, 'isValidAddress')
.mockReturnValueOnce(isAddressValidResult as any);
const hook = renderHook((address: string) => useAddressValidation(address), {
initialProps: initialAddress
});

expect(await hook.result.current.valid).toEqual(isAddressValidResult);
expect(isValidAddressSpy).toBeCalledWith(initialAddress);

const newAddress = 'newAddress';
const newIsAddressValidResult = 'newIsAddressValidResult';
isValidAddressSpy.mockReturnValueOnce(newIsAddressValidResult as any);

hook.rerender(newAddress);

expect(await hook.result.current.valid).toEqual(newIsAddressValidResult);
expect(isValidAddressSpy).toBeCalledWith(newAddress);
expect(isValidAddressSpy).toBeCalledTimes(2);

hook.rerender(newAddress);
expect(isValidAddressSpy).toBeCalledTimes(2);
});
});
@@ -0,0 +1,93 @@
/* eslint-disable max-statements */
/* eslint-disable no-magic-numbers */
/* eslint-disable unicorn/no-useless-undefined */
/* eslint-disable unicorn/no-null */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { renderHook } from '@testing-library/react-hooks';
import { useBalances } from '../useBalances';
import { BehaviorSubject } from 'rxjs';
import { act } from 'react-dom/test-utils';
import * as apiTransformers from '../../api/transformers';

const total$ = new BehaviorSubject({ coins: 111 });
const available$ = new BehaviorSubject({ coins: 222 });
const deposit$ = new BehaviorSubject(333);
const rewards$ = new BehaviorSubject(444);

const inMemoryWallet = {
balance: {
utxo: {
total$,
available$
},
rewardAccounts: {
deposit$,
rewards$
}
}
};

jest.mock('../../stores', () => ({
...jest.requireActual<any>('../../stores'),
useWalletStore: () => ({
inMemoryWallet
})
}));

describe('Testing useBalances hook', () => {
test('should return proper balances', () => {
const mockedTotal = 'mockedTotal';
const mockedAvailable = 'mockedAvailable';
const mockedRewards = 'mockedRewards';
const mockedDeposit = 'mockedDeposit';
const walletBalanceTransformerSpy = jest
.spyOn(apiTransformers, 'walletBalanceTransformer')
.mockReturnValueOnce(mockedTotal as any)
.mockReturnValueOnce(mockedAvailable as any)
.mockReturnValueOnce(mockedRewards as any)
.mockReturnValueOnce(mockedDeposit as any);

const fiatPrice = 'fiatPrice';
const hook = renderHook(() => useBalances(fiatPrice as any));

expect(hook.result.current.balance.total).toEqual(mockedTotal);
expect(walletBalanceTransformerSpy).toHaveBeenNthCalledWith(1, BigInt(111 + 444).toString(), fiatPrice);
expect(hook.result.current.balance.available).toEqual(mockedAvailable);
expect(walletBalanceTransformerSpy).toHaveBeenNthCalledWith(2, BigInt(222 + 444).toString(), fiatPrice);
expect(hook.result.current.rewards).toEqual(mockedRewards);
expect(walletBalanceTransformerSpy).toHaveBeenNthCalledWith(3, '444', fiatPrice);
expect(hook.result.current.deposit).toEqual(mockedDeposit);
expect(walletBalanceTransformerSpy).toHaveBeenNthCalledWith(4, '333', fiatPrice);

const newMockedTotal = 'newMockedTotal';
const newMockedAvailable = 'newMockedAvailable';
walletBalanceTransformerSpy.mockReset();
walletBalanceTransformerSpy
.mockReturnValueOnce(newMockedTotal as any)
.mockReturnValueOnce(newMockedAvailable as any);

act(() => {
total$.next({ coins: undefined });
available$.next({ coins: undefined });
rewards$.next(undefined);
});
hook.rerender();
expect(hook.result.current.balance.total).toEqual(newMockedTotal);
expect(walletBalanceTransformerSpy).toHaveBeenNthCalledWith(1, BigInt(0).toString(), fiatPrice);
expect(hook.result.current.balance.available).toEqual(newMockedAvailable);
expect(walletBalanceTransformerSpy).toHaveBeenNthCalledWith(2, BigInt(0).toString(), fiatPrice);

walletBalanceTransformerSpy.mockReset();
act(() => {
total$.next(undefined);
available$.next(undefined);
deposit$.next(undefined);
rewards$.next(undefined);
});
hook.rerender();
expect(hook.result.current.balance).toEqual(undefined);
expect(hook.result.current.rewards).toEqual(undefined);
expect(hook.result.current.deposit).toEqual(undefined);
expect(walletBalanceTransformerSpy).not.toBeCalled();
});
});
@@ -1,22 +1,38 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable import/imports-first */
const mockInitializeTx = jest.fn();
const mockBuildDelegation = jest.fn();
import { renderHook } from '@testing-library/react-hooks';
import { useBuildDelegation } from '../useBuildDelegation';
import { cardanoStakePoolMock } from '../../utils/mocks/test-helpers';

jest.mock('@lace/cardano', () => {
const actual = jest.requireActual<any>('@lace/cardano');
return {
__esModule: true,
...actual,
Wallet: {
...actual.Wallet,
buildDelegation: mockBuildDelegation
}
};
});

jest.mock('../../features/delegation/stores', () => ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...jest.requireActual<any>('../../features/delegation/stores'),
useDelegationStore: () => ({
selectedStakePool: cardanoStakePoolMock
selectedStakePool: cardanoStakePoolMock.pageResults[0]
})
}));

const inMemoryWallet = {
initializeTx: mockInitializeTx
};

jest.mock('../../stores', () => ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...jest.requireActual<any>('../../stores'),
useWalletStore: () => ({
inMemoryWallet: {
initializeTx: jest.fn()
}
inMemoryWallet
})
}));

Expand All @@ -28,4 +44,17 @@ describe('Testing useBuildDelegation hook', () => {
const { result } = renderHook(() => useBuildDelegation());
expect(result.current).toBeDefined();
});

describe('Testing build delegation transaction function', () => {
test('should build delegation using buildDelegation util and return initialized tx', async () => {
const mockedTxConfig = 'txConfig';
mockBuildDelegation.mockImplementation(async () => await mockedTxConfig);
const { result } = renderHook(() => useBuildDelegation());

await result.current();

expect(mockBuildDelegation).toBeCalledWith(inMemoryWallet, cardanoStakePoolMock.pageResults[0].id);
expect(mockInitializeTx).toBeCalledWith(mockedTxConfig);
});
});
});

0 comments on commit 65bdf1e

Please sign in to comment.