mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 01:47:00 +01:00
Continue converting tests from enzyme to @testing-library/react (#16753)
This commit is contained in:
parent
1e173afc95
commit
8fde322907
@ -10,7 +10,7 @@ module.exports = {
|
||||
coverageReporters: ['html', 'text-summary', 'json-summary'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 44,
|
||||
branches: 48,
|
||||
functions: 46,
|
||||
lines: 52,
|
||||
statements: 52,
|
||||
|
@ -18,6 +18,27 @@
|
||||
},
|
||||
"warning": null
|
||||
},
|
||||
"confirmTransaction": {
|
||||
"txData": {
|
||||
"id": 3111025347726181,
|
||||
"time": 1620723786838,
|
||||
"status": "unapproved",
|
||||
"metamaskNetworkId": "5",
|
||||
"chainId": "0x5",
|
||||
"loadingDefaults": false,
|
||||
"txParams": {
|
||||
"from": "0x64a845a5b02460acf8a3d84503b0d68d028b4bb4",
|
||||
"to": "0xaD6D458402F60fD3Bd25163575031ACDce07538D",
|
||||
"value": "0x0",
|
||||
"data": "0x095ea7b30000000000000000000000009bc5baf874d2da8d216ae9f137804184ee5afef40000000000000000000000000000000000000000000000000000000000011170",
|
||||
"gas": "0xea60",
|
||||
"gasPrice": "0x4a817c800"
|
||||
},
|
||||
"type": "transfer",
|
||||
"origin": "https://metamask.github.io",
|
||||
"transactionCategory": "approve"
|
||||
}
|
||||
},
|
||||
"history": {
|
||||
"mostRecentOverviewPage": "/mostRecentOverviewPage"
|
||||
},
|
||||
|
@ -1,4 +1,7 @@
|
||||
{
|
||||
"ENS": {
|
||||
"resolution": ""
|
||||
},
|
||||
"appState": {
|
||||
"networkDropdownOpen": false,
|
||||
"gasIsLoading": false,
|
||||
@ -19,6 +22,8 @@
|
||||
"mostRecentOverviewPage": "/mostRecentOverviewPage"
|
||||
},
|
||||
"metamask": {
|
||||
"ipfsGateway": "",
|
||||
"dismissSeedBackUpReminder": false,
|
||||
"usePhishDetect": true,
|
||||
"participateInMetaMetrics": false,
|
||||
"gasEstimateType": "fee-market",
|
||||
|
@ -11,6 +11,7 @@ export default function Dialog(props) {
|
||||
'dialog--error': type === 'error',
|
||||
'dialog--warning': type === 'warning',
|
||||
})}
|
||||
data-testid="dialog-message"
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
|
@ -425,7 +425,10 @@ export default function GasDisplay({ gasError }) {
|
||||
/>
|
||||
</Box>
|
||||
{(gasError || isInsufficientTokenError) && (
|
||||
<Box className="gas-display__warning-message">
|
||||
<Box
|
||||
className="gas-display__warning-message"
|
||||
data-testid="gas-warning-message"
|
||||
>
|
||||
<Box
|
||||
paddingTop={0}
|
||||
paddingRight={4}
|
||||
|
@ -0,0 +1,347 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SendContent Component render should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="page-container__content"
|
||||
>
|
||||
<div
|
||||
class="send-v2__form"
|
||||
>
|
||||
<div
|
||||
class="send-v2__form-row"
|
||||
>
|
||||
<div
|
||||
class="send-v2__form-label"
|
||||
>
|
||||
Asset:
|
||||
</div>
|
||||
<div
|
||||
class="send-v2__form-field"
|
||||
>
|
||||
<div
|
||||
class="send-v2__asset-dropdown"
|
||||
>
|
||||
<div
|
||||
class="send-v2__asset-dropdown__input-wrapper"
|
||||
>
|
||||
<div
|
||||
class="send-v2__asset-dropdown__asset"
|
||||
>
|
||||
<div
|
||||
class="send-v2__asset-dropdown__asset-icon"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
class="identicon"
|
||||
src="./images/eth_logo.svg"
|
||||
style="height: 36px; width: 36px; border-radius: 18px;"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="send-v2__asset-dropdown__asset-data"
|
||||
>
|
||||
<div
|
||||
class="send-v2__asset-dropdown__symbol"
|
||||
>
|
||||
ETH
|
||||
</div>
|
||||
<div
|
||||
class="send-v2__asset-dropdown__name"
|
||||
>
|
||||
<span
|
||||
class="send-v2__asset-dropdown__name__label"
|
||||
>
|
||||
Balance:
|
||||
</span>
|
||||
<div
|
||||
class="currency-display-component"
|
||||
title="0 ETH"
|
||||
>
|
||||
<span
|
||||
class="currency-display-component__prefix"
|
||||
/>
|
||||
<span
|
||||
class="currency-display-component__text"
|
||||
>
|
||||
0
|
||||
</span>
|
||||
<span
|
||||
class="currency-display-component__suffix"
|
||||
>
|
||||
ETH
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<i
|
||||
class="fa fa-caret-down fa-lg send-v2__asset-dropdown__caret"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="send-v2__form-row"
|
||||
>
|
||||
<div
|
||||
class="send-v2__form-label"
|
||||
>
|
||||
Amount:
|
||||
<button
|
||||
class="send-v2__amount-max"
|
||||
>
|
||||
<input
|
||||
readonly=""
|
||||
type="checkbox"
|
||||
/>
|
||||
<div
|
||||
class="send-v2__amount-max__button"
|
||||
>
|
||||
Max
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="send-v2__form-field-container"
|
||||
>
|
||||
<div
|
||||
class="send-v2__form-field"
|
||||
>
|
||||
<div
|
||||
class="unit-input"
|
||||
>
|
||||
<div
|
||||
class="unit-input__inputs"
|
||||
>
|
||||
<div
|
||||
class="unit-input__input-container"
|
||||
>
|
||||
<input
|
||||
class="unit-input__input"
|
||||
data-testid="currency-input"
|
||||
dir="ltr"
|
||||
placeholder="0"
|
||||
style="width: 1.5ch;"
|
||||
type="number"
|
||||
value="1"
|
||||
/>
|
||||
<div
|
||||
class="unit-input__suffix"
|
||||
>
|
||||
ETH
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="currency-input__conversion-component"
|
||||
>
|
||||
No conversion rate available
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="currency-input__swap-component"
|
||||
data-testid="currency-swap"
|
||||
>
|
||||
<i
|
||||
class="fa fa-retweet fa-lg"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="send-v2__form-row"
|
||||
>
|
||||
<div
|
||||
class="send-v2__form-label"
|
||||
>
|
||||
Hex data:
|
||||
</div>
|
||||
<div
|
||||
class="send-v2__form-field-container"
|
||||
>
|
||||
<div
|
||||
class="send-v2__form-field"
|
||||
>
|
||||
<textarea
|
||||
class="send-v2__hex-data__input"
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="box gas-display box--flex-direction-row"
|
||||
>
|
||||
<div
|
||||
class="transaction-detail"
|
||||
>
|
||||
<div
|
||||
class="transaction-detail-rows"
|
||||
>
|
||||
<div
|
||||
class="transaction-detail-item"
|
||||
>
|
||||
<div
|
||||
class="transaction-detail-item__row"
|
||||
>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--display-flex box--flex-direction-row box--flex-wrap-nowrap box--align-items-center typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
|
||||
>
|
||||
Estimated gas fee
|
||||
<div
|
||||
class="info-tooltip"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-describedby="tippy-tooltip-1"
|
||||
class="info-tooltip__tooltip-container"
|
||||
data-original-title="null"
|
||||
data-tooltipped=""
|
||||
style="display: inline;"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 10 10"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5 0C2.2 0 0 2.2 0 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 2c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.2-.7-.6.3-.8.7-.8zm.7 6H4.3V4.3h1.5V8z"
|
||||
fill="var(--color-icon-alternative)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h6>
|
||||
<div
|
||||
class="transaction-detail-item__detail-values"
|
||||
>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h6 typography--weight-normal typography--style-normal typography--color-text-alternative"
|
||||
>
|
||||
<div
|
||||
class="box gas-display__currency-container box--display-flex box--flex-direction-column box--height-max"
|
||||
>
|
||||
<div
|
||||
class="currency-display-component"
|
||||
title="0.0000315"
|
||||
>
|
||||
<span
|
||||
class="currency-display-component__prefix"
|
||||
/>
|
||||
<span
|
||||
class="currency-display-component__text"
|
||||
>
|
||||
0.0000315
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</h6>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--margin-left-1 box--flex-direction-row box--text-align-right typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
|
||||
>
|
||||
<div
|
||||
class="box gas-display__currency-container box--display-flex box--flex-direction-column box--height-max"
|
||||
>
|
||||
<div
|
||||
class="currency-display-component"
|
||||
title="0.000032 ETH"
|
||||
>
|
||||
<span
|
||||
class="currency-display-component__prefix"
|
||||
/>
|
||||
<span
|
||||
class="currency-display-component__text"
|
||||
>
|
||||
0.000032
|
||||
</span>
|
||||
<span
|
||||
class="currency-display-component__suffix"
|
||||
>
|
||||
ETH
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="transaction-detail-item__row"
|
||||
>
|
||||
<div>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography gas-timing gas-timing--negative typography--h7 typography--weight-normal typography--style-normal typography--color-text-default"
|
||||
>
|
||||
Unknown processing time
|
||||
|
||||
<div
|
||||
class="info-tooltip"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-describedby="tippy-tooltip-2"
|
||||
class="info-tooltip__tooltip-container"
|
||||
data-original-title="null"
|
||||
data-tooltipped=""
|
||||
style="display: inline;"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 10 10"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5 0C2.2 0 0 2.2 0 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 2c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.2-.7-.6.3-.8.7-.8zm.7 6H4.3V4.3h1.5V8z"
|
||||
fill="var(--color-icon-alternative)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h6>
|
||||
</div>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography transaction-detail-item__row-subText typography--h7 typography--weight-normal typography--style-normal typography--align-end typography--color-text-alternative"
|
||||
>
|
||||
<strong>
|
||||
Max fee:
|
||||
</strong>
|
||||
<div
|
||||
class="box gas-display__currency-container box--display-flex box--flex-direction-column box--height-max"
|
||||
>
|
||||
<div
|
||||
class="currency-display-component"
|
||||
title="0.0000315 ETH"
|
||||
>
|
||||
<span
|
||||
class="currency-display-component__prefix"
|
||||
/>
|
||||
<span
|
||||
class="currency-display-component__text"
|
||||
>
|
||||
0.0000315
|
||||
</span>
|
||||
<span
|
||||
class="currency-display-component__suffix"
|
||||
>
|
||||
ETH
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SendContent Component render should match snapshot 2`] = `<div />`;
|
@ -146,7 +146,7 @@ export default class SendContent extends Component {
|
||||
const { acknowledgeRecipientWarning } = this.props;
|
||||
const { t } = this.context;
|
||||
return (
|
||||
<div className="send__warning-container">
|
||||
<div className="send__warning-container" data-testid="send-warning">
|
||||
<ActionableMessage
|
||||
type="danger"
|
||||
useIcon
|
||||
|
@ -1,181 +1,286 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import PageContainerContent from '../../../components/ui/page-container/page-container-content.component';
|
||||
import Dialog from '../../../components/ui/dialog';
|
||||
import SendContent from './send-content.component';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
|
||||
import SendAmountRow from './send-amount-row/send-amount-row.container';
|
||||
import SendHexDataRow from './send-hex-data-row/send-hex-data-row.container';
|
||||
import SendAssetRow from './send-asset-row/send-asset-row.container';
|
||||
import { renderWithProvider } from '../../../../test/lib/render-helpers';
|
||||
import mockSendState from '../../../../test/data/mock-send-state.json';
|
||||
import { INSUFFICIENT_FUNDS_ERROR } from '../send.constants';
|
||||
import SendContent from '.';
|
||||
|
||||
jest.mock('../../../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest.fn().mockResolvedValue(),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
removePollingTokenFromAppState: jest.fn(),
|
||||
createTransactionEventFragment: jest.fn(),
|
||||
getGasFeeTimeEstimate: jest.fn().mockResolvedValue('unknown'),
|
||||
}));
|
||||
|
||||
describe('SendContent Component', () => {
|
||||
let wrapper;
|
||||
describe('render', () => {
|
||||
const mockStore = configureMockStore()(mockSendState);
|
||||
|
||||
const defaultProps = {
|
||||
showHexData: true,
|
||||
it('should match snapshot', async () => {
|
||||
const props = {
|
||||
gasIsExcessive: false,
|
||||
networkAndAccountSupports1559: true,
|
||||
asset: { type: 'NATIVE' },
|
||||
recipient: {
|
||||
mode: 'CONTACT_LIST',
|
||||
userInput: '0x31A2764925BD47CCBd57b2F277702dB46e9C5F66',
|
||||
address: '0x31A2764925BD47CCBd57b2F277702dB46e9C5F66',
|
||||
nickname: 'John Doe',
|
||||
showHexData: true,
|
||||
};
|
||||
|
||||
const { container } = renderWithProvider(
|
||||
<SendContent {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SendHexDataRow', () => {
|
||||
const tokenAssetState = {
|
||||
...mockSendState,
|
||||
send: {
|
||||
...mockSendState.send,
|
||||
draftTransactions: {
|
||||
'1-tx': {
|
||||
...mockSendState.send.draftTransactions['1-tx'],
|
||||
asset: {
|
||||
balance: '0x3635c9adc5dea00000',
|
||||
details: {
|
||||
address: '0xAddress',
|
||||
decimals: 18,
|
||||
symbol: 'TST',
|
||||
balance: '1',
|
||||
standard: 'ERC20',
|
||||
},
|
||||
error: null,
|
||||
warning: null,
|
||||
type: 'TOKEN',
|
||||
},
|
||||
tokenAddressList: {
|
||||
'0x32e6c34cd57087abbd59b5a4aecc4cb495924356': {
|
||||
name: 'BitBase',
|
||||
symbol: 'BTBS',
|
||||
decimals: 18,
|
||||
address: '0x32E6C34Cd57087aBBD59B5A4AECC4cB495924356',
|
||||
iconUrl: 'BTBS.svg',
|
||||
occurrences: null,
|
||||
},
|
||||
'0x3fa400483487a489ec9b1db29c4129063eec4654': {
|
||||
name: 'Cryptokek.com',
|
||||
symbol: 'KEK',
|
||||
decimals: 18,
|
||||
address: '0x3fa400483487A489EC9b1dB29C4129063EEC4654',
|
||||
iconUrl: 'cryptokek.svg',
|
||||
occurrences: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<SendContent {...defaultProps} />, {
|
||||
context: { t: (str) => `${str}_t` },
|
||||
});
|
||||
});
|
||||
|
||||
describe('render', () => {
|
||||
it('should render a PageContainerContent component', () => {
|
||||
expect(wrapper.find(PageContainerContent)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should render a div with a .send-v2__form class as a child of PageContainerContent', () => {
|
||||
const PageContainerContentChild = wrapper
|
||||
.find(PageContainerContent)
|
||||
.children();
|
||||
expect(PageContainerContentChild.is('div')).toStrictEqual(true);
|
||||
expect(PageContainerContentChild.is('.send-v2__form')).toStrictEqual(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render the correct row components as grandchildren of the PageContainerContent component', () => {
|
||||
const PageContainerContentChild = wrapper
|
||||
.find(PageContainerContent)
|
||||
.children();
|
||||
expect(PageContainerContentChild.childAt(0).is(Dialog)).toStrictEqual(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
PageContainerContentChild.childAt(1).is(SendAssetRow),
|
||||
).toStrictEqual(true);
|
||||
expect(
|
||||
PageContainerContentChild.childAt(2).is(SendAmountRow),
|
||||
).toStrictEqual(true);
|
||||
expect(
|
||||
PageContainerContentChild.childAt(3).is(SendHexDataRow),
|
||||
).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it('should not render the SendHexDataRow if props.showHexData is false', () => {
|
||||
wrapper.setProps({ showHexData: false });
|
||||
const PageContainerContentChild = wrapper
|
||||
.find(PageContainerContent)
|
||||
.children();
|
||||
expect(PageContainerContentChild.childAt(0).is(Dialog)).toStrictEqual(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
PageContainerContentChild.childAt(1).is(SendAssetRow),
|
||||
).toStrictEqual(true);
|
||||
expect(
|
||||
PageContainerContentChild.childAt(2).is(SendAmountRow),
|
||||
).toStrictEqual(true);
|
||||
expect(wrapper.find(SendHexDataRow)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should not render the SendHexDataRow if the asset type is TOKEN (ERC-20)', () => {
|
||||
wrapper.setProps({ asset: { type: 'TOKEN' } });
|
||||
const PageContainerContentChild = wrapper
|
||||
.find(PageContainerContent)
|
||||
.children();
|
||||
expect(PageContainerContentChild.childAt(0).is(Dialog)).toStrictEqual(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
PageContainerContentChild.childAt(1).is(SendAssetRow),
|
||||
).toStrictEqual(true);
|
||||
expect(
|
||||
PageContainerContentChild.childAt(2).is(SendAmountRow),
|
||||
).toStrictEqual(true);
|
||||
expect(wrapper.find(SendHexDataRow)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should not render the Dialog if contact has a name', () => {
|
||||
wrapper.setProps({
|
||||
it('should not render the SendHexDataRow if props.showHexData is false', async () => {
|
||||
const props = {
|
||||
gasIsExcessive: false,
|
||||
showHexData: false,
|
||||
contact: { name: 'testName' },
|
||||
});
|
||||
const PageContainerContentChild = wrapper
|
||||
.find(PageContainerContent)
|
||||
.children();
|
||||
expect(
|
||||
PageContainerContentChild.childAt(0).is(SendAssetRow),
|
||||
).toStrictEqual(true);
|
||||
expect(
|
||||
PageContainerContentChild.childAt(1).is(SendAmountRow),
|
||||
).toStrictEqual(true);
|
||||
expect(wrapper.find(Dialog)).toHaveLength(0);
|
||||
});
|
||||
};
|
||||
|
||||
it('should not render the Dialog if it is an ownedAccount', () => {
|
||||
wrapper.setProps({
|
||||
showHexData: false,
|
||||
isOwnedAccount: true,
|
||||
});
|
||||
const PageContainerContentChild = wrapper
|
||||
.find(PageContainerContent)
|
||||
.children();
|
||||
expect(
|
||||
PageContainerContentChild.childAt(0).is(SendAssetRow),
|
||||
).toStrictEqual(true);
|
||||
expect(
|
||||
PageContainerContentChild.childAt(1).is(SendAmountRow),
|
||||
).toStrictEqual(true);
|
||||
expect(wrapper.find(Dialog)).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
const mockStore = configureMockStore()(mockSendState);
|
||||
|
||||
it('should not render the asset dropdown if token length is 0', () => {
|
||||
wrapper.setProps({ tokens: [] });
|
||||
const PageContainerContentChild = wrapper
|
||||
.find(PageContainerContent)
|
||||
.children();
|
||||
expect(PageContainerContentChild.childAt(1).is(SendAssetRow)).toStrictEqual(
|
||||
true,
|
||||
const { queryByText } = renderWithProvider(
|
||||
<SendContent {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
expect(
|
||||
PageContainerContentChild.childAt(2).find(
|
||||
'send-v2__asset-dropdown__single-asset',
|
||||
),
|
||||
).toHaveLength(0);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('Hex data:')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render warning', () => {
|
||||
wrapper.setProps({
|
||||
warning: 'watchout',
|
||||
it('should not render the SendHexDataRow if the asset type is TOKEN (ERC-20)', async () => {
|
||||
const props = {
|
||||
gasIsExcessive: false,
|
||||
showHexData: true,
|
||||
};
|
||||
|
||||
// Use token draft transaction asset
|
||||
const mockState = configureMockStore()(tokenAssetState);
|
||||
|
||||
const { queryByText } = renderWithProvider(
|
||||
<SendContent {...props} />,
|
||||
mockState,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByText('Hex data:')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const dialog = wrapper.find(Dialog).at(0);
|
||||
describe('Gas Error', () => {
|
||||
it('should show gas warning when gasIsExcessive prop is true.', async () => {
|
||||
const props = {
|
||||
gasIsExcessive: true,
|
||||
showHexData: false,
|
||||
};
|
||||
|
||||
expect(dialog.props().type).toStrictEqual('warning');
|
||||
expect(dialog.props().children).toStrictEqual('watchout_t');
|
||||
expect(dialog).toHaveLength(1);
|
||||
const mockStore = configureMockStore()(mockSendState);
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<SendContent {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
const gasWarning = queryByTestId('gas-warning-message');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(gasWarning).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show gas warning for none gasEstimateType in state', async () => {
|
||||
const props = {
|
||||
gasIsExcessive: false,
|
||||
showHexData: false,
|
||||
};
|
||||
|
||||
const noGasPriceState = {
|
||||
...mockSendState,
|
||||
metamask: {
|
||||
...mockSendState.metamask,
|
||||
gasEstimateType: 'none',
|
||||
},
|
||||
};
|
||||
|
||||
const mockStore = configureMockStore()(noGasPriceState);
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<SendContent {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
const gasWarning = queryByTestId('gas-warning-message');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(gasWarning).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show gas warning for gas error state in draft transaction', async () => {
|
||||
const props = {
|
||||
gasIsExcessive: false,
|
||||
showHexData: false,
|
||||
};
|
||||
|
||||
const gasErrorState = {
|
||||
...mockSendState,
|
||||
send: {
|
||||
...mockSendState.send,
|
||||
draftTransactions: {
|
||||
'1-tx': {
|
||||
...mockSendState.send.draftTransactions['1-tx'],
|
||||
gas: {
|
||||
error: INSUFFICIENT_FUNDS_ERROR,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockStore = configureMockStore()(gasErrorState);
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<SendContent {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
const gasWarning = queryByTestId('gas-warning-message');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(gasWarning).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Recipient Warning', () => {
|
||||
it('should show recipient warning with knownAddressRecipient state in draft transaction state', async () => {
|
||||
const props = {
|
||||
gasIsExcessive: false,
|
||||
showHexData: false,
|
||||
};
|
||||
|
||||
const knownRecipientWarningState = {
|
||||
...mockSendState,
|
||||
send: {
|
||||
...mockSendState.send,
|
||||
draftTransactions: {
|
||||
'1-tx': {
|
||||
...mockSendState.send.draftTransactions['1-tx'],
|
||||
recipient: {
|
||||
...mockSendState.send.draftTransactions['1-tx'].recipient,
|
||||
warning: 'knownAddressRecipient',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockStore = configureMockStore()(knownRecipientWarningState);
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<SendContent {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
const sendWarning = queryByTestId('send-warning');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sendWarning).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Assert Error', () => {
|
||||
it('should render dialog error with asset error in draft transaction state', async () => {
|
||||
const props = {
|
||||
gasIsExcessive: false,
|
||||
showHexData: false,
|
||||
};
|
||||
|
||||
const assertErrorState = {
|
||||
...mockSendState,
|
||||
send: {
|
||||
...mockSendState.send,
|
||||
draftTransactions: {
|
||||
'1-tx': {
|
||||
...mockSendState.send.draftTransactions['1-tx'],
|
||||
asset: {
|
||||
...mockSendState.send.draftTransactions['1-tx'].asset,
|
||||
error: 'transactionError',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockStore = configureMockStore()(assertErrorState);
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<SendContent {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
const dialogMessage = queryByTestId('dialog-message');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(dialogMessage).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Warning', () => {
|
||||
it('should display warning dialog message from warning prop', async () => {
|
||||
const props = {
|
||||
gasIsExcessive: false,
|
||||
showHexData: false,
|
||||
warning: 'warning',
|
||||
};
|
||||
|
||||
const mockStore = configureMockStore()(mockSendState);
|
||||
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<SendContent {...props} />,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
const dialogMessage = queryByTestId('dialog-message');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(dialogMessage).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,125 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SendGasRow Component render should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="send-v2__form-row"
|
||||
>
|
||||
<div
|
||||
class="send-v2__form-label"
|
||||
/>
|
||||
<div
|
||||
class="send-v2__form-field"
|
||||
>
|
||||
<div
|
||||
class="advanced-gas-inputs__gas-edit-rows"
|
||||
>
|
||||
<div
|
||||
class="advanced-gas-inputs__gas-edit-row"
|
||||
>
|
||||
<div
|
||||
class="advanced-gas-inputs__gas-edit-row__label"
|
||||
>
|
||||
Gas price (GWEI)
|
||||
<div>
|
||||
<div
|
||||
aria-describedby="tippy-tooltip-1"
|
||||
class=""
|
||||
data-original-title="Gas price specifies the amount of Ether you are willing to pay for each unit of gas."
|
||||
data-tooltipped=""
|
||||
style="display: inline;"
|
||||
tabindex="0"
|
||||
>
|
||||
<i
|
||||
class="fa fa-info-circle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="advanced-gas-inputs__gas-edit-row__input-wrapper"
|
||||
>
|
||||
<input
|
||||
class="advanced-gas-inputs__gas-edit-row__input"
|
||||
data-testid="gas-price"
|
||||
min="0"
|
||||
type="number"
|
||||
value="0"
|
||||
/>
|
||||
<div
|
||||
class="advanced-gas-inputs__gas-edit-row__input-arrows"
|
||||
>
|
||||
<div
|
||||
class="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap"
|
||||
>
|
||||
<i
|
||||
class="fa fa-sm fa-angle-up"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap"
|
||||
>
|
||||
<i
|
||||
class="fa fa-sm fa-angle-down"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="advanced-gas-inputs__gas-edit-row"
|
||||
>
|
||||
<div
|
||||
class="advanced-gas-inputs__gas-edit-row__label"
|
||||
>
|
||||
Gas limit
|
||||
<div>
|
||||
<div
|
||||
aria-describedby="tippy-tooltip-2"
|
||||
class=""
|
||||
data-original-title="Gas limit is the maximum amount of units of gas you are willing to spend."
|
||||
data-tooltipped=""
|
||||
style="display: inline;"
|
||||
tabindex="0"
|
||||
>
|
||||
<i
|
||||
class="fa fa-info-circle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="advanced-gas-inputs__gas-edit-row__input-wrapper"
|
||||
>
|
||||
<input
|
||||
class="advanced-gas-inputs__gas-edit-row__input"
|
||||
data-testid="gas-limit"
|
||||
min="0"
|
||||
type="number"
|
||||
value="21000"
|
||||
/>
|
||||
<div
|
||||
class="advanced-gas-inputs__gas-edit-row__input-arrows"
|
||||
>
|
||||
<div
|
||||
class="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap"
|
||||
>
|
||||
<i
|
||||
class="fa fa-sm fa-angle-up"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="advanced-gas-inputs__gas-edit-row__input-arrows__i-wrap"
|
||||
>
|
||||
<i
|
||||
class="fa fa-sm fa-angle-down"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -1,32 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component';
|
||||
import { GAS_INPUT_MODES } from '../../../../ducks/send';
|
||||
import SendGasRow from './send-gas-row.component';
|
||||
|
||||
describe('SendGasRow Component', () => {
|
||||
let wrapper;
|
||||
|
||||
describe('render', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<SendGasRow
|
||||
conversionRate={20}
|
||||
convertedCurrency="mockConvertedCurrency"
|
||||
gasFeeError
|
||||
gasInputMode={GAS_INPUT_MODES.INLINE}
|
||||
/>,
|
||||
{ context: { t: (str) => `${str}_t`, trackEvent: () => ({}) } },
|
||||
);
|
||||
wrapper.setProps({ isMainnet: true });
|
||||
});
|
||||
|
||||
it('should render a SendRowWrapper component', () => {
|
||||
expect(wrapper.is(SendRowWrapper)).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it('should render an AdvancedGasInputs as a child of the SendRowWrapper', () => {
|
||||
expect(wrapper.first().childAt(0)).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,71 +0,0 @@
|
||||
import sinon from 'sinon';
|
||||
|
||||
import {
|
||||
setCustomGasPrice,
|
||||
setCustomGasLimit,
|
||||
} from '../../../../ducks/gas/gas.duck';
|
||||
|
||||
import { updateGasPrice, updateGasLimit } from '../../../../ducks/send';
|
||||
|
||||
let mapDispatchToProps;
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
connect: (_, md) => {
|
||||
mapDispatchToProps = md;
|
||||
return () => ({});
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../../../ducks/send', () => {
|
||||
const original = jest.requireActual('../../../../ducks/send');
|
||||
return {
|
||||
...original,
|
||||
getSendMaxModeState: (s) => `mockMaxModeOn:${s}`,
|
||||
updateGasPrice: jest.fn(),
|
||||
updateGasLimit: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../../../ducks/gas/gas.duck', () => ({
|
||||
setCustomGasPrice: jest.fn(),
|
||||
setCustomGasLimit: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../send.utils.js', () => ({
|
||||
isBalanceSufficient: ({ amount, gasTotal, balance, conversionRate }) =>
|
||||
`${amount}:${gasTotal}:${balance}:${conversionRate}`,
|
||||
|
||||
calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice,
|
||||
}));
|
||||
|
||||
require('./send-gas-row.container');
|
||||
|
||||
describe('send-gas-row container', () => {
|
||||
describe('mapDispatchToProps()', () => {
|
||||
let dispatchSpy;
|
||||
let mapDispatchToPropsObject;
|
||||
|
||||
beforeEach(() => {
|
||||
dispatchSpy = sinon.spy();
|
||||
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy);
|
||||
});
|
||||
|
||||
describe('updateGasPrice()', () => {
|
||||
it('should dispatch an action', () => {
|
||||
mapDispatchToPropsObject.updateGasPrice('mockNewPrice');
|
||||
expect(dispatchSpy.calledTwice).toStrictEqual(true);
|
||||
expect(updateGasPrice).toHaveBeenCalled();
|
||||
expect(setCustomGasPrice).toHaveBeenCalledWith('mockNewPrice');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateGasLimit()', () => {
|
||||
it('should dispatch an action', () => {
|
||||
mapDispatchToPropsObject.updateGasLimit('mockNewLimit');
|
||||
expect(dispatchSpy.calledTwice).toStrictEqual(true);
|
||||
expect(updateGasLimit).toHaveBeenCalled();
|
||||
expect(setCustomGasLimit).toHaveBeenCalledWith('mockNewLimit');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
22
ui/pages/send/send-content/send-gas-row/send-gas-row.test.js
Normal file
22
ui/pages/send/send-content/send-gas-row/send-gas-row.test.js
Normal file
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import { renderWithProvider } from '../../../../../test/lib/render-helpers';
|
||||
import mockSendState from '../../../../../test/data/mock-send-state.json';
|
||||
import SendGasRow from '.';
|
||||
|
||||
jest.mock('../../../../ducks/send', () => ({
|
||||
...jest.requireActual('../../../../ducks/send'),
|
||||
getGasInputMode: jest.fn().mockReturnValue('INLINE'),
|
||||
}));
|
||||
|
||||
describe('SendGasRow Component', () => {
|
||||
describe('render', () => {
|
||||
const mockStore = configureMockStore()(mockSendState);
|
||||
|
||||
it('should match snapshot', () => {
|
||||
const { container } = renderWithProvider(<SendGasRow />, mockStore);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,57 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SendContent Component render should render a SendRowErrorMessage with and errorType props if showError is true 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="send-v2__form-row"
|
||||
>
|
||||
<div
|
||||
class="send-v2__form-label"
|
||||
>
|
||||
mockLabel
|
||||
</div>
|
||||
<div
|
||||
class="send-v2__form-field-container"
|
||||
>
|
||||
<div
|
||||
class="send-v2__form-field"
|
||||
>
|
||||
<span>
|
||||
Mock Form Field
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="send-v2__error send-v2__error-amount"
|
||||
>
|
||||
Insufficient funds.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SendContent Component render should render with children 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="send-v2__form-row"
|
||||
>
|
||||
<div
|
||||
class="send-v2__form-label"
|
||||
>
|
||||
mockLabel
|
||||
<span>
|
||||
Mock Custom Label Content
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="send-v2__form-field"
|
||||
>
|
||||
<span>
|
||||
Mock Form Field
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -1,111 +0,0 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import SendRowWrapper from './send-row-wrapper.component';
|
||||
|
||||
import SendRowErrorMessage from './send-row-error-message/send-row-error-message.container';
|
||||
|
||||
describe('SendContent Component', () => {
|
||||
let wrapper;
|
||||
|
||||
describe('render', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<SendRowWrapper
|
||||
errorType="mockErrorType"
|
||||
label="mockLabel"
|
||||
showError={false}
|
||||
>
|
||||
<span>Mock Form Field</span>
|
||||
</SendRowWrapper>,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a div with a send-v2__form-row class', () => {
|
||||
expect(wrapper.find('div.send-v2__form-row')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should render two children of the root div, with send-v2_form label and field classes', () => {
|
||||
expect(
|
||||
wrapper.find('.send-v2__form-row > .send-v2__form-label'),
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
wrapper.find('.send-v2__form-row > .send-v2__form-field'),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should render the label as a child of the send-v2__form-label', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find('.send-v2__form-row > .send-v2__form-label')
|
||||
.childAt(0)
|
||||
.text(),
|
||||
).toStrictEqual('mockLabel');
|
||||
});
|
||||
|
||||
it('should render its first child as a child of the send-v2__form-field', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find('.send-v2__form-row > .send-v2__form-field')
|
||||
.childAt(0)
|
||||
.text(),
|
||||
).toStrictEqual('Mock Form Field');
|
||||
});
|
||||
|
||||
it('should not render a SendRowErrorMessage if showError is false', () => {
|
||||
expect(wrapper.find(SendRowErrorMessage)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should render a SendRowErrorMessage with and errorType props if showError is true', () => {
|
||||
wrapper.setProps({ showError: true });
|
||||
expect(wrapper.find(SendRowErrorMessage)).toHaveLength(1);
|
||||
|
||||
const expectedSendRowErrorMessage = wrapper
|
||||
.find('.send-v2__form-row > .send-v2__form-label')
|
||||
.childAt(1);
|
||||
expect(expectedSendRowErrorMessage.is(SendRowErrorMessage)).toStrictEqual(
|
||||
true,
|
||||
);
|
||||
expect(expectedSendRowErrorMessage.props()).toStrictEqual({
|
||||
errorType: 'mockErrorType',
|
||||
});
|
||||
});
|
||||
|
||||
it('should render its second child as a child of the send-v2__form-field, if it has two children', () => {
|
||||
wrapper = shallow(
|
||||
<SendRowWrapper
|
||||
errorType="mockErrorType"
|
||||
label="mockLabel"
|
||||
showError={false}
|
||||
>
|
||||
<span>Mock Custom Label Content</span>
|
||||
<span>Mock Form Field</span>
|
||||
</SendRowWrapper>,
|
||||
);
|
||||
expect(
|
||||
wrapper
|
||||
.find('.send-v2__form-row > .send-v2__form-field')
|
||||
.childAt(0)
|
||||
.text(),
|
||||
).toStrictEqual('Mock Form Field');
|
||||
});
|
||||
|
||||
it('should render its first child as the last child of the send-v2__form-label, if it has two children', () => {
|
||||
wrapper = shallow(
|
||||
<SendRowWrapper
|
||||
errorType="mockErrorType"
|
||||
label="mockLabel"
|
||||
showError={false}
|
||||
>
|
||||
<span>Mock Custom Label Content</span>
|
||||
<span>Mock Form Field</span>
|
||||
</SendRowWrapper>,
|
||||
);
|
||||
expect(
|
||||
wrapper
|
||||
.find('.send-v2__form-row > .send-v2__form-label')
|
||||
.childAt(1)
|
||||
.text(),
|
||||
).toStrictEqual('Mock Custom Label Content');
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import { renderWithProvider } from '../../../../../test/lib/render-helpers';
|
||||
import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../../helpers/constants/error-keys';
|
||||
import SendRowWrapper from '.';
|
||||
|
||||
describe('SendContent Component', () => {
|
||||
describe('render', () => {
|
||||
it('should render with children', () => {
|
||||
const props = {
|
||||
errorType: 'mockErrorType',
|
||||
label: 'mockLabel',
|
||||
showError: false,
|
||||
};
|
||||
const { container } = renderWithProvider(
|
||||
<SendRowWrapper {...props}>
|
||||
<span>Mock Custom Label Content</span>
|
||||
<span>Mock Form Field</span>
|
||||
</SendRowWrapper>,
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render a SendRowErrorMessage with and errorType props if showError is true', () => {
|
||||
const mockState = {
|
||||
send: {
|
||||
currentTransactionUUID: '1-tx',
|
||||
draftTransactions: {
|
||||
'1-tx': {
|
||||
gas: {
|
||||
error: INSUFFICIENT_FUNDS_ERROR_KEY,
|
||||
},
|
||||
amount: {
|
||||
error: INSUFFICIENT_FUNDS_ERROR_KEY,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockStore = configureMockStore()(mockState);
|
||||
|
||||
const props = {
|
||||
errorType: 'amount',
|
||||
label: 'mockLabel',
|
||||
showError: true,
|
||||
};
|
||||
const { container } = renderWithProvider(
|
||||
<SendRowWrapper {...props}>
|
||||
<span>Mock Form Field</span>
|
||||
</SendRowWrapper>,
|
||||
mockStore,
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user