Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,6 @@ import renderWithProvider from '../../../../../util/test/renderWithProvider';
import PredictBalance from './PredictBalance';
import { strings } from '../../../../../../locales/i18n';

// Mock React Query
jest.mock('@tanstack/react-query', () => ({
useQueryClient: () => ({
invalidateQueries: jest.fn(),
}),
}));

// Mock React Navigation
jest.mock('@react-navigation/native', () => ({
...jest.requireActual('@react-navigation/native'),
Expand Down
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We wouldn't need any changes in Predict code if we didn't change to useQuery?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No but I couldn't find any clean alternative.
I modified those 2 test files after making the changes in renderWithProvider.tsx

Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ jest.mock('../../hooks/usePredictBalance', () => ({

const mockInvalidateQueries = jest.fn();
jest.mock('@tanstack/react-query', () => ({
...jest.requireActual('@tanstack/react-query'),
useQueryClient: () => ({
invalidateQueries: mockInvalidateQueries,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { simpleSendTransactionControllerMock } from '../../__mocks__/controllers
import { transactionApprovalControllerMock } from '../../__mocks__/controllers/approval-controller-mock';
import { emptySignatureControllerMock } from '../../__mocks__/controllers/signature-controller-mock';
import { useIsTransactionPayLoading } from '../../hooks/pay/useTransactionPayData';
import { useIsGaslessLoading } from '../../hooks/gas/useIsGaslessLoading';

const mockConfirmSpy = jest.fn();
const mockRejectSpy = jest.fn();
Expand Down Expand Up @@ -78,6 +79,12 @@ jest.mock('../../hooks/ui/useFullScreenConfirmation', () => ({
})),
}));

jest.mock('../../hooks/gas/useIsGaslessLoading', () => ({
useIsGaslessLoading: jest.fn(() => ({
isGaslessLoading: false,
})),
}));

const mockTrackAlertMetrics = jest.fn();

(useConfirmationAlertMetrics as jest.Mock).mockReturnValue({
Expand All @@ -101,6 +108,7 @@ describe('Footer', () => {
const useIsTransactionPayLoadingMock = jest.mocked(
useIsTransactionPayLoading,
);
const useIsGaslessLoadingMock = jest.mocked(useIsGaslessLoading);

beforeEach(() => {
jest.clearAllMocks();
Expand All @@ -124,6 +132,7 @@ describe('Footer', () => {
});

useIsTransactionPayLoadingMock.mockReturnValue(false);
useIsGaslessLoadingMock.mockReturnValue({ isGaslessLoading: false });
});

it('should render correctly', () => {
Expand Down Expand Up @@ -228,9 +237,29 @@ describe('Footer', () => {
).toBe(true);
});

it('disables confirm button if quotes are loading', () => {
it('disables confirm button if transaction pay is loading', () => {
useIsTransactionPayLoadingMock.mockReturnValue(true);
useIsGaslessLoadingMock.mockReturnValue({ isGaslessLoading: false });
const state = merge(
{},
simpleSendTransactionControllerMock,
transactionApprovalControllerMock,
emptySignatureControllerMock,
{ securityAlerts: { alerts: {} } },
);

const { getByTestId } = renderWithProvider(<Footer />, {
state,
});

expect(
getByTestId(ConfirmationFooterSelectorIDs.CONFIRM_BUTTON).props.disabled,
).toBe(true);
});

it('disables confirm button if gasless support is loading', () => {
useIsTransactionPayLoadingMock.mockReturnValue(false);
useIsGaslessLoadingMock.mockReturnValue({ isGaslessLoading: true });
const state = merge(
{},
simpleSendTransactionControllerMock,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { PredictClaimFooter } from '../predict-confirmations/predict-claim-foote
import { useIsTransactionPayLoading } from '../../hooks/pay/useTransactionPayData';
import { Skeleton } from '../../../../../component-library/components-temp/Skeleton';
import { useQRHardwareContext } from '../../context/qr-hardware-context';
import { useIsGaslessLoading } from '../../hooks/gas/useIsGaslessLoading';

const HIDE_FOOTER_BY_DEFAULT_TYPES = [
TransactionType.moneyAccountDeposit,
Expand Down Expand Up @@ -71,6 +72,7 @@ export const Footer = () => {
TRANSFER_TRANSACTION_TYPES.includes(transactionType) &&
transactionMetadata?.origin === MMM_ORIGIN;
const isPayLoading = useIsTransactionPayLoading();
const { isGaslessLoading } = useIsGaslessLoading();

const { isFooterVisible: isFooterVisibleFlag, isTransactionValueUpdating } =
useConfirmationContext();
Expand Down Expand Up @@ -158,7 +160,8 @@ export const Footer = () => {
needsCameraPermission ||
hasBlockingAlerts ||
isTransactionValueUpdating ||
isPayLoading;
isPayLoading ||
isGaslessLoading;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this have larger implications if mobile wasn't previously disabled while gas station was loading?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it might actually feel less flaky on slower devices - the "Confirm" button blinks much more on Mobile than on Extension, maybe due to isTransactionValueUpdating. If isGaslessLoading remains "true" longer than the time for isTransactionValueUpdating to go true then false for example.
But on Tempo this "blinking" is more than UX gimmick, it makes the tx fail if the user taps during one of those blinks.


const buttons = [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ jest.mock('@react-navigation/native', () => ({
}),
}));

jest.mock('../../hooks/gas/useIsGaslessLoading', () => ({
useIsGaslessLoading: jest.fn(() => ({
isGaslessLoading: false,
})),
}));

const mockPendingScanRequest: QrScanRequest = {
request: {
requestId: 'c95ecc76-d6e9-4a0a-afa3-31429bc80566',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { GasFeeToken } from '@metamask/transaction-controller';
import { Hex } from '@metamask/utils';

import { useIsGaslessSupported } from './useIsGaslessSupported';
import { useHasInsufficientBalance } from '../useHasInsufficientBalance';
import { useIsGaslessLoading } from './useIsGaslessLoading';
import { renderHookWithProvider } from '../../../../../util/test/renderWithProvider';
import { useTransactionMetadataRequest } from '../transactions/useTransactionMetadataRequest';
import { selectUseTransactionSimulations } from '../../../../../selectors/preferencesController';

jest.mock('./useIsGaslessSupported');
jest.mock('../useHasInsufficientBalance');
jest.mock('../transactions/useTransactionMetadataRequest');
jest.mock('../../../../../selectors/preferencesController');

const mockedUseIsGaslessSupported = jest.mocked(useIsGaslessSupported);
const mockedUseHasInsufficientBalance = jest.mocked(useHasInsufficientBalance);
const mockedUseTransactionMetadataRequest = jest.mocked(
useTransactionMetadataRequest,
);
const mockSelectUseTransactionSimulations = jest.mocked(
selectUseTransactionSimulations,
);

async function runHook({
simulationEnabled,
gaslessSupported,
insufficientBalance,
pending = false,
isSmartTransaction = true,
gasFeeTokens,
excludeNativeTokenForFee,
selectedGasFeeToken,
}: {
simulationEnabled: boolean;
gaslessSupported: boolean;
insufficientBalance: boolean;
pending?: boolean;
isSmartTransaction?: boolean;
gasFeeTokens?: GasFeeToken[];
excludeNativeTokenForFee?: boolean;
selectedGasFeeToken?: Hex;
}) {
mockedUseIsGaslessSupported.mockReturnValue({
isSupported: gaslessSupported,
isSmartTransaction,
pending,
});
mockedUseHasInsufficientBalance.mockReturnValue({
hasInsufficientBalance: insufficientBalance,
nativeCurrency: 'USD',
});
mockedUseTransactionMetadataRequest.mockReturnValue({
gasFeeTokens,
excludeNativeTokenForFee,
selectedGasFeeToken,
} as unknown as ReturnType<typeof useTransactionMetadataRequest>);
mockSelectUseTransactionSimulations.mockReturnValue(simulationEnabled);
const { result } = renderHookWithProvider(useIsGaslessLoading);
return result.current;
}

describe('useIsGaslessLoading', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('returns true if pending', async () => {
const result = await runHook({
simulationEnabled: true,
gaslessSupported: true,
insufficientBalance: true,
pending: true,
});

expect(result.isGaslessLoading).toBe(true);
});

it('returns false if simulation is disabled', async () => {
const result = await runHook({
simulationEnabled: false,
gaslessSupported: true,
insufficientBalance: true,
});

expect(result.isGaslessLoading).toBe(false);
});

it('returns false if gasless is not supported', async () => {
const result = await runHook({
simulationEnabled: true,
gaslessSupported: false,
insufficientBalance: true,
});

expect(result.isGaslessLoading).toBe(false);
});

it('returns false if there is no insufficient balance', async () => {
const result = await runHook({
simulationEnabled: true,
gaslessSupported: true,
insufficientBalance: false,
});

expect(result.isGaslessLoading).toBe(false);
});

it('returns true if gas fee tokens are undefined (still loading)', async () => {
const result = await runHook({
simulationEnabled: true,
gaslessSupported: true,
insufficientBalance: true,
gasFeeTokens: undefined, // this triggers loading
});

expect(result.isGaslessLoading).toBe(true);
});

it('returns false if gas fee tokens are present', async () => {
const result = await runHook({
simulationEnabled: true,
gaslessSupported: true,
insufficientBalance: true,
gasFeeTokens: [{ tokenAddress: '0x123' }] as unknown as GasFeeToken[],
});

expect(result.isGaslessLoading).toBe(false);
});

it('returns false if gas fee tokens is an empty array', async () => {
const result = await runHook({
simulationEnabled: true,
gaslessSupported: true,
insufficientBalance: true,
gasFeeTokens: [],
});

expect(result.isGaslessLoading).toBe(false);
});

it('returns false if gas fee tokens are present and dont match selectedGasFeeToken (non-reg)', async () => {
const result = await runHook({
simulationEnabled: true,
gaslessSupported: true,
insufficientBalance: true,
gasFeeTokens: [{ tokenAddress: '0x123' }] as unknown as GasFeeToken[],
selectedGasFeeToken: '0x456',
});

expect(result.isGaslessLoading).toBe(false);
});

it('returns true if gas fee tokens are present and dont match selectedGasFeeToken with excludeNativeTokenForFee being true (Tempo)', async () => {
const result = await runHook({
simulationEnabled: true,
gaslessSupported: true,
insufficientBalance: true,
gasFeeTokens: [{ tokenAddress: '0x123' }] as unknown as GasFeeToken[],
selectedGasFeeToken: '0x456',
excludeNativeTokenForFee: true,
});

expect(result.isGaslessLoading).toBe(true);
});

it('returns false if gas fee tokens are present AND match selectedGasFeeToken with excludeNativeTokenForFee being true (Tempo)', async () => {
const result = await runHook({
simulationEnabled: true,
gaslessSupported: true,
insufficientBalance: true,
gasFeeTokens: [
{ tokenAddress: '0x123' },
{ tokenAddress: '0x789' },
] as unknown as GasFeeToken[],
// Matches the first one
selectedGasFeeToken: '0x123',
excludeNativeTokenForFee: true,
});

expect(result.isGaslessLoading).toBe(false);
});

it('returns false if gas fee tokens are present AND selectedGasFeeToken is undefined with excludeNativeTokenForFee being true (Tempo)', async () => {
const result = await runHook({
simulationEnabled: true,
gaslessSupported: true,
insufficientBalance: true,
gasFeeTokens: [{ tokenAddress: '0x123' }] as unknown as GasFeeToken[],
selectedGasFeeToken: undefined,
excludeNativeTokenForFee: true,
});

expect(result.isGaslessLoading).toBe(false);
});

it('returns false if gas fee tokens is empty array with excludeNativeTokenForFee being true (Tempo)', async () => {
const result = await runHook({
simulationEnabled: true,
gaslessSupported: true,
insufficientBalance: true,
gasFeeTokens: [] as unknown as GasFeeToken[],
selectedGasFeeToken: '0x456',
excludeNativeTokenForFee: true,
});

expect(result.isGaslessLoading).toBe(false);
});
});
Loading
Loading