mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Add error that redirects users to Import NFT page when they attempt to add an NFT on the Import Token page (#13271)
* Add error that redirects users to Import NFT page when they attempt to add an NFT on the Import Token page
This commit is contained in:
parent
9a3c917a48
commit
f7849a0b7c
@ -140,9 +140,6 @@
|
||||
"addMemo": {
|
||||
"message": "Προσθήκη σημειώματος"
|
||||
},
|
||||
"addNFT": {
|
||||
"message": "Προσθήκη NFT"
|
||||
},
|
||||
"addNetwork": {
|
||||
"message": "Προσθήκη Δικτύου"
|
||||
},
|
||||
|
@ -140,9 +140,6 @@
|
||||
"addMemo": {
|
||||
"message": "Add memo"
|
||||
},
|
||||
"addNFT": {
|
||||
"message": "Add NFT"
|
||||
},
|
||||
"addNetwork": {
|
||||
"message": "Add Network"
|
||||
},
|
||||
@ -458,6 +455,10 @@
|
||||
"close": {
|
||||
"message": "Close"
|
||||
},
|
||||
"collectibleAddressError": {
|
||||
"message": "This token is an NFT. Add on the $1",
|
||||
"description": "$1 is a clickable link with text defined by the 'importNFTPage' key"
|
||||
},
|
||||
"confirm": {
|
||||
"message": "Confirm"
|
||||
},
|
||||
@ -1402,6 +1403,12 @@
|
||||
"importMyWallet": {
|
||||
"message": "Import My Wallet"
|
||||
},
|
||||
"importNFT": {
|
||||
"message": "Import NFT"
|
||||
},
|
||||
"importNFTPage": {
|
||||
"message": "Import NFT page"
|
||||
},
|
||||
"importNFTs": {
|
||||
"message": "Import NFTs"
|
||||
},
|
||||
|
@ -140,9 +140,6 @@
|
||||
"addMemo": {
|
||||
"message": "Ajouter un mémo"
|
||||
},
|
||||
"addNFT": {
|
||||
"message": "Ajouter un NFT"
|
||||
},
|
||||
"addNetwork": {
|
||||
"message": "Ajouter un réseau"
|
||||
},
|
||||
|
@ -140,9 +140,6 @@
|
||||
"addMemo": {
|
||||
"message": "मेमो जोड़ें"
|
||||
},
|
||||
"addNFT": {
|
||||
"message": "NFT जोड़ें"
|
||||
},
|
||||
"addNetwork": {
|
||||
"message": "नेटवर्क जोड़ें"
|
||||
},
|
||||
|
@ -140,9 +140,6 @@
|
||||
"addMemo": {
|
||||
"message": "Tambahkan memo"
|
||||
},
|
||||
"addNFT": {
|
||||
"message": "Tambahkan NFT"
|
||||
},
|
||||
"addNetwork": {
|
||||
"message": "Tambahkan Jaringan"
|
||||
},
|
||||
|
@ -140,9 +140,6 @@
|
||||
"addMemo": {
|
||||
"message": "メモを追加"
|
||||
},
|
||||
"addNFT": {
|
||||
"message": "NFTを追加"
|
||||
},
|
||||
"addNetwork": {
|
||||
"message": "ネットワークの追加"
|
||||
},
|
||||
|
@ -140,9 +140,6 @@
|
||||
"addMemo": {
|
||||
"message": "메모 추가"
|
||||
},
|
||||
"addNFT": {
|
||||
"message": "NFT 추가"
|
||||
},
|
||||
"addNetwork": {
|
||||
"message": "네트워크 추가"
|
||||
},
|
||||
|
@ -140,9 +140,6 @@
|
||||
"addMemo": {
|
||||
"message": "Добавить примечание"
|
||||
},
|
||||
"addNFT": {
|
||||
"message": "Добавить NFT"
|
||||
},
|
||||
"addNetwork": {
|
||||
"message": "Добавить сеть"
|
||||
},
|
||||
|
@ -140,9 +140,6 @@
|
||||
"addMemo": {
|
||||
"message": "Magdagdag ng memo"
|
||||
},
|
||||
"addNFT": {
|
||||
"message": "Magdagdag ng NFT"
|
||||
},
|
||||
"addNetwork": {
|
||||
"message": "Magdagdag ng Network"
|
||||
},
|
||||
|
@ -140,9 +140,6 @@
|
||||
"addMemo": {
|
||||
"message": "Not ekleyin"
|
||||
},
|
||||
"addNFT": {
|
||||
"message": "NFT ekleyin"
|
||||
},
|
||||
"addNetwork": {
|
||||
"message": "Ağ ekleyin"
|
||||
},
|
||||
|
@ -140,9 +140,6 @@
|
||||
"addMemo": {
|
||||
"message": "Thêm bản ghi nhớ"
|
||||
},
|
||||
"addNFT": {
|
||||
"message": "Thêm NFT"
|
||||
},
|
||||
"addNetwork": {
|
||||
"message": "Thêm mạng"
|
||||
},
|
||||
|
@ -140,9 +140,6 @@
|
||||
"addMemo": {
|
||||
"message": "添加备忘录"
|
||||
},
|
||||
"addNFT": {
|
||||
"message": "添加NFT"
|
||||
},
|
||||
"addNetwork": {
|
||||
"message": "添加网络"
|
||||
},
|
||||
|
@ -220,22 +220,22 @@ export default class MetamaskController extends EventEmitter {
|
||||
onNetworkStateChange: this.networkController.store.subscribe.bind(
|
||||
this.networkController.store,
|
||||
),
|
||||
getAssetName: this.assetsContractController.getAssetName.bind(
|
||||
getERC721AssetName: this.assetsContractController.getERC721AssetName.bind(
|
||||
this.assetsContractController,
|
||||
),
|
||||
getAssetSymbol: this.assetsContractController.getAssetSymbol.bind(
|
||||
getERC721AssetSymbol: this.assetsContractController.getERC721AssetSymbol.bind(
|
||||
this.assetsContractController,
|
||||
),
|
||||
getCollectibleTokenURI: this.assetsContractController.getCollectibleTokenURI.bind(
|
||||
getERC721TokenURI: this.assetsContractController.getERC721TokenURI.bind(
|
||||
this.assetsContractController,
|
||||
),
|
||||
getOwnerOf: this.assetsContractController.getOwnerOf.bind(
|
||||
getERC721OwnerOf: this.assetsContractController.getERC721OwnerOf.bind(
|
||||
this.assetsContractController,
|
||||
),
|
||||
balanceOfERC1155Collectible: this.assetsContractController.balanceOfERC1155Collectible.bind(
|
||||
getERC1155BalanceOf: this.assetsContractController.getERC1155BalanceOf.bind(
|
||||
this.assetsContractController,
|
||||
),
|
||||
uriERC1155Collectible: this.assetsContractController.uriERC1155Collectible.bind(
|
||||
getERC1155TokenURI: this.assetsContractController.getERC1155TokenURI.bind(
|
||||
this.assetsContractController,
|
||||
),
|
||||
},
|
||||
@ -1040,6 +1040,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
appStateController,
|
||||
collectiblesController,
|
||||
collectibleDetectionController,
|
||||
assetsContractController,
|
||||
currencyRateController,
|
||||
detectTokensController,
|
||||
ensController,
|
||||
@ -1192,6 +1193,11 @@ export default class MetamaskController extends EventEmitter {
|
||||
preferencesController,
|
||||
),
|
||||
|
||||
// AssetsContractController
|
||||
getTokenStandardAndDetails: assetsContractController.getTokenStandardAndDetails.bind(
|
||||
assetsContractController,
|
||||
),
|
||||
|
||||
// CollectiblesController
|
||||
addCollectible: collectiblesController.addCollectible.bind(
|
||||
collectiblesController,
|
||||
|
@ -107,7 +107,7 @@
|
||||
"@keystonehq/metamask-airgapped-keyring": "0.2.1",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@metamask/contract-metadata": "^1.31.0",
|
||||
"@metamask/controllers": "^24.0.0",
|
||||
"@metamask/controllers": "^25.0.0",
|
||||
"@metamask/eth-ledger-bridge-keyring": "^0.10.0",
|
||||
"@metamask/eth-token-tracker": "^3.0.1",
|
||||
"@metamask/etherscan-link": "^2.1.0",
|
||||
|
@ -28,6 +28,7 @@
|
||||
right: 16px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
background-color: transparent;
|
||||
|
||||
&::after {
|
||||
content: '\00D7';
|
||||
|
@ -47,9 +47,10 @@ export default class PageContainerHeader extends Component {
|
||||
|
||||
return (
|
||||
onClose && (
|
||||
<div
|
||||
<button
|
||||
className="page-container__header-close"
|
||||
onClick={() => onClose()}
|
||||
aria-label="close"
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
@ -246,7 +246,7 @@ TextField.propTypes = {
|
||||
/**
|
||||
* Show error message
|
||||
*/
|
||||
error: PropTypes.string,
|
||||
error: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||
/**
|
||||
* Add custom CSS class
|
||||
*/
|
||||
|
@ -17,8 +17,13 @@ export default function AddCollectible() {
|
||||
const t = useI18nContext();
|
||||
const history = useHistory();
|
||||
const dispatch = useDispatch();
|
||||
const addressEnteredOnImportTokensPage =
|
||||
history?.location?.state?.addressEnteredOnImportTokensPage;
|
||||
|
||||
const [address, setAddress] = useState(
|
||||
addressEnteredOnImportTokensPage ?? '',
|
||||
);
|
||||
|
||||
const [address, setAddress] = useState('');
|
||||
const [tokenId, setTokenId] = useState('');
|
||||
const [disabled, setDisabled] = useState(true);
|
||||
|
||||
@ -47,7 +52,7 @@ export default function AddCollectible() {
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title={t('addNFT')}
|
||||
title={t('importNFT')}
|
||||
onSubmit={() => {
|
||||
handleAddCollectible();
|
||||
}}
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
} from '../../helpers/utils/util';
|
||||
import { tokenInfoGetter } from '../../helpers/utils/token-util';
|
||||
import {
|
||||
ADD_COLLECTIBLE_ROUTE,
|
||||
CONFIRM_IMPORT_TOKEN_ROUTE,
|
||||
EXPERIMENTAL_ROUTE,
|
||||
} from '../../helpers/constants/routes';
|
||||
@ -46,6 +47,8 @@ class ImportToken extends Component {
|
||||
rpcPrefs: PropTypes.object,
|
||||
tokenList: PropTypes.object,
|
||||
useTokenDetection: PropTypes.bool,
|
||||
getTokenStandardAndDetails: PropTypes.func,
|
||||
selectedAddress: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@ -62,6 +65,7 @@ class ImportToken extends Component {
|
||||
customAddressError: null,
|
||||
customSymbolError: null,
|
||||
customDecimalsError: null,
|
||||
collectibleAddressError: null,
|
||||
forceEditSymbol: false,
|
||||
symbolAutoFilled: false,
|
||||
decimalAutoFilled: false,
|
||||
@ -126,13 +130,15 @@ class ImportToken extends Component {
|
||||
customAddressError,
|
||||
customSymbolError,
|
||||
customDecimalsError,
|
||||
collectibleAddressError,
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
tokenSelectorError ||
|
||||
customAddressError ||
|
||||
customSymbolError ||
|
||||
customDecimalsError
|
||||
customDecimalsError ||
|
||||
collectibleAddressError
|
||||
);
|
||||
}
|
||||
|
||||
@ -186,11 +192,12 @@ class ImportToken extends Component {
|
||||
this.handleCustomDecimalsChange(decimals);
|
||||
}
|
||||
|
||||
handleCustomAddressChange(value) {
|
||||
async handleCustomAddressChange(value) {
|
||||
const customAddress = value.trim();
|
||||
this.setState({
|
||||
customAddress,
|
||||
customAddressError: null,
|
||||
collectibleAddressError: null,
|
||||
tokenSelectorError: null,
|
||||
symbolAutoFilled: false,
|
||||
decimalAutoFilled: false,
|
||||
@ -208,8 +215,23 @@ class ImportToken extends Component {
|
||||
|
||||
const isMainnetNetwork = this.props.chainId === '0x1';
|
||||
|
||||
let standard;
|
||||
if (addressIsValid) {
|
||||
try {
|
||||
({ standard } = await this.props.getTokenStandardAndDetails(
|
||||
standardAddress,
|
||||
this.props.selectedAddress,
|
||||
));
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
const addressIsEmpty =
|
||||
customAddress.length === 0 || customAddress === emptyAddr;
|
||||
|
||||
switch (true) {
|
||||
case !addressIsValid:
|
||||
case !addressIsValid && !addressIsEmpty:
|
||||
this.setState({
|
||||
customAddressError: this.context.t('invalidAddress'),
|
||||
customSymbol: '',
|
||||
@ -218,6 +240,28 @@ class ImportToken extends Component {
|
||||
customDecimalsError: null,
|
||||
});
|
||||
|
||||
break;
|
||||
case process.env.COLLECTIBLES_V1 &&
|
||||
(standard === 'ERC1155' || standard === 'ERC721'):
|
||||
this.setState({
|
||||
collectibleAddressError: this.context.t('collectibleAddressError', [
|
||||
<a
|
||||
className="import-token__collectible-address-error-link"
|
||||
onClick={() =>
|
||||
this.props.history.push({
|
||||
pathname: ADD_COLLECTIBLE_ROUTE,
|
||||
state: {
|
||||
addressEnteredOnImportTokensPage: this.state.customAddress,
|
||||
},
|
||||
})
|
||||
}
|
||||
key="collectibleAddressError"
|
||||
>
|
||||
{this.context.t('importNFTPage')}
|
||||
</a>,
|
||||
]),
|
||||
});
|
||||
|
||||
break;
|
||||
case isMainnetToken && !isMainnetNetwork:
|
||||
this.setState({
|
||||
@ -242,7 +286,7 @@ class ImportToken extends Component {
|
||||
|
||||
break;
|
||||
default:
|
||||
if (customAddress !== emptyAddr) {
|
||||
if (!addressIsEmpty) {
|
||||
this.attemptToAutoFillTokenParams(customAddress);
|
||||
}
|
||||
}
|
||||
@ -290,6 +334,7 @@ class ImportToken extends Component {
|
||||
symbolAutoFilled,
|
||||
decimalAutoFilled,
|
||||
mainnetTokenWarning,
|
||||
collectibleAddressError,
|
||||
} = this.state;
|
||||
|
||||
const { chainId, rpcPrefs } = this.props;
|
||||
@ -330,7 +375,9 @@ class ImportToken extends Component {
|
||||
type="text"
|
||||
value={customAddress}
|
||||
onChange={(e) => this.handleCustomAddressChange(e.target.value)}
|
||||
error={customAddressError || mainnetTokenWarning}
|
||||
error={
|
||||
customAddressError || mainnetTokenWarning || collectibleAddressError
|
||||
}
|
||||
fullWidth
|
||||
autoFocus
|
||||
margin="normal"
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { setPendingTokens, clearPendingTokens } from '../../store/actions';
|
||||
import {
|
||||
setPendingTokens,
|
||||
clearPendingTokens,
|
||||
getTokenStandardAndDetails,
|
||||
} from '../../store/actions';
|
||||
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
||||
import {
|
||||
getRpcPrefsForCurrentProvider,
|
||||
@ -17,6 +21,7 @@ const mapStateToProps = (state) => {
|
||||
provider: { chainId },
|
||||
useTokenDetection,
|
||||
tokenList,
|
||||
selectedAddress,
|
||||
},
|
||||
} = state;
|
||||
const showSearchTabCustomNetwork =
|
||||
@ -33,13 +38,15 @@ const mapStateToProps = (state) => {
|
||||
rpcPrefs: getRpcPrefsForCurrentProvider(state),
|
||||
tokenList,
|
||||
useTokenDetection,
|
||||
selectedAddress,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
setPendingTokens: (tokens) => dispatch(setPendingTokens(tokens)),
|
||||
clearPendingTokens: () => dispatch(clearPendingTokens()),
|
||||
getTokenStandardAndDetails: (address, selectedAddress) =>
|
||||
getTokenStandardAndDetails(address, selectedAddress, null),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,110 +1,177 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import sinon from 'sinon';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import { mountWithRouter } from '../../../test/lib/render-helpers';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import { renderWithProvider } from '../../../test/lib/render-helpers';
|
||||
import configureStore from '../../store/store';
|
||||
import {
|
||||
setPendingTokens,
|
||||
clearPendingTokens,
|
||||
getTokenStandardAndDetails,
|
||||
} from '../../store/actions';
|
||||
import ImportToken from './import-token.container';
|
||||
|
||||
jest.mock('../../store/actions', () => ({
|
||||
getTokenStandardAndDetails: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve({ standard: 'ERC20' })),
|
||||
setPendingTokens: jest
|
||||
.fn()
|
||||
.mockImplementation(() => ({ type: 'SET_PENDING_TOKENS' })),
|
||||
clearPendingTokens: jest
|
||||
.fn()
|
||||
.mockImplementation(() => ({ type: 'CLEAR_PENDING_TOKENS' })),
|
||||
}));
|
||||
|
||||
describe('Import Token', () => {
|
||||
let wrapper;
|
||||
|
||||
const state = {
|
||||
metamask: {
|
||||
tokens: [],
|
||||
},
|
||||
};
|
||||
|
||||
const store = configureMockStore()(state);
|
||||
|
||||
const historyStub = jest.fn();
|
||||
const props = {
|
||||
history: {
|
||||
push: sinon.stub().callsFake(() => undefined),
|
||||
push: historyStub,
|
||||
},
|
||||
setPendingTokens: sinon.spy(),
|
||||
clearPendingTokens: sinon.spy(),
|
||||
tokens: [],
|
||||
identities: {},
|
||||
mostRecentOverviewPage: '/',
|
||||
showSearchTab: true,
|
||||
tokenList: {},
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
const baseStore = {
|
||||
metamask: {
|
||||
tokens: [],
|
||||
provider: { chainId: '0x1' },
|
||||
frequentRpcListDetail: [],
|
||||
identities: {},
|
||||
selectedAddress: '0x1231231',
|
||||
},
|
||||
history: {
|
||||
mostRecentOverviewPage: '/',
|
||||
},
|
||||
};
|
||||
|
||||
const store = configureStore(baseStore);
|
||||
|
||||
return renderWithProvider(<ImportToken {...props} />, store);
|
||||
};
|
||||
|
||||
describe('Import Token', () => {
|
||||
beforeAll(() => {
|
||||
wrapper = mountWithRouter(
|
||||
<Provider store={store}>
|
||||
<ImportToken.WrappedComponent {...props} />
|
||||
</Provider>,
|
||||
store,
|
||||
);
|
||||
it('add Custom Token button is disabled when no fields are populated', () => {
|
||||
const { getByText } = render();
|
||||
const customTokenButton = getByText('Custom Token');
|
||||
fireEvent.click(customTokenButton);
|
||||
const submit = getByText('Add Custom Token');
|
||||
|
||||
wrapper.find({ name: 'customToken' }).simulate('click');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
props.history.push.reset();
|
||||
});
|
||||
|
||||
it('next button is disabled when no fields are populated', () => {
|
||||
const nextButton = wrapper.find(
|
||||
'.button.btn-primary.page-container__footer-button',
|
||||
);
|
||||
|
||||
expect(nextButton.props().disabled).toStrictEqual(true);
|
||||
expect(submit).toBeDisabled();
|
||||
});
|
||||
|
||||
it('edits token address', () => {
|
||||
const { getByText } = render();
|
||||
const customTokenButton = getByText('Custom Token');
|
||||
fireEvent.click(customTokenButton);
|
||||
|
||||
const tokenAddress = '0x617b3f8050a0BD94b6b1da02B4384eE5B4DF13F4';
|
||||
const event = { target: { value: tokenAddress } };
|
||||
const customAddress = wrapper.find('input#custom-address');
|
||||
fireEvent.change(document.getElementById('custom-address'), event);
|
||||
|
||||
customAddress.simulate('change', event);
|
||||
expect(
|
||||
wrapper.find('ImportToken').instance().state.customAddress,
|
||||
).toStrictEqual(tokenAddress);
|
||||
expect(document.getElementById('custom-address').value).toStrictEqual(
|
||||
tokenAddress,
|
||||
);
|
||||
});
|
||||
|
||||
it('edits token symbol', () => {
|
||||
const { getByText } = render();
|
||||
const customTokenButton = getByText('Custom Token');
|
||||
fireEvent.click(customTokenButton);
|
||||
|
||||
const tokenSymbol = 'META';
|
||||
const event = { target: { value: tokenSymbol } };
|
||||
const customAddress = wrapper.find('#custom-symbol');
|
||||
customAddress.last().simulate('change', event);
|
||||
fireEvent.change(document.getElementById('custom-symbol'), event);
|
||||
|
||||
expect(
|
||||
wrapper.find('ImportToken').instance().state.customSymbol,
|
||||
).toStrictEqual(tokenSymbol);
|
||||
expect(document.getElementById('custom-symbol').value).toStrictEqual(
|
||||
tokenSymbol,
|
||||
);
|
||||
});
|
||||
|
||||
it('edits token decimal precision', () => {
|
||||
const { getByText } = render();
|
||||
const customTokenButton = getByText('Custom Token');
|
||||
fireEvent.click(customTokenButton);
|
||||
|
||||
const tokenPrecision = '2';
|
||||
const event = { target: { value: tokenPrecision } };
|
||||
const customAddress = wrapper.find('#custom-decimals');
|
||||
customAddress.last().simulate('change', event);
|
||||
fireEvent.change(document.getElementById('custom-decimals'), event);
|
||||
|
||||
expect(
|
||||
wrapper.find('ImportToken').instance().state.customDecimals,
|
||||
).toStrictEqual(Number(tokenPrecision));
|
||||
});
|
||||
|
||||
it('next', () => {
|
||||
const nextButton = wrapper.find(
|
||||
'.button.btn-primary.page-container__footer-button',
|
||||
);
|
||||
nextButton.simulate('click');
|
||||
|
||||
expect(props.setPendingTokens.calledOnce).toStrictEqual(true);
|
||||
expect(props.history.push.calledOnce).toStrictEqual(true);
|
||||
expect(props.history.push.getCall(0).args[0]).toStrictEqual(
|
||||
'/confirm-import-token',
|
||||
expect(document.getElementById('custom-decimals').value).toStrictEqual(
|
||||
tokenPrecision,
|
||||
);
|
||||
});
|
||||
|
||||
it('cancels', () => {
|
||||
const cancelButton = wrapper.find('.page-container__header-close');
|
||||
cancelButton.simulate('click');
|
||||
it('adds custom tokens successfully', async () => {
|
||||
const { getByText } = render();
|
||||
const customTokenButton = getByText('Custom Token');
|
||||
fireEvent.click(customTokenButton);
|
||||
|
||||
expect(props.clearPendingTokens.calledOnce).toStrictEqual(true);
|
||||
expect(props.history.push.getCall(0).args[0]).toStrictEqual('/');
|
||||
const submit = getByText('Add Custom Token');
|
||||
expect(submit).toBeDisabled();
|
||||
|
||||
const tokenAddress = '0x617b3f8050a0BD94b6b1da02B4384eE5B4DF13F4';
|
||||
fireEvent.change(document.getElementById('custom-address'), {
|
||||
target: { value: tokenAddress },
|
||||
});
|
||||
expect(submit).not.toBeDisabled();
|
||||
|
||||
const tokenSymbol = 'META';
|
||||
fireEvent.change(document.getElementById('custom-symbol'), {
|
||||
target: { value: tokenSymbol },
|
||||
});
|
||||
|
||||
const tokenPrecision = '2';
|
||||
await fireEvent.change(document.getElementById('custom-decimals'), {
|
||||
target: { value: tokenPrecision },
|
||||
});
|
||||
|
||||
expect(submit).not.toBeDisabled();
|
||||
fireEvent.click(submit);
|
||||
expect(setPendingTokens).toHaveBeenCalledWith({
|
||||
customToken: {
|
||||
address: tokenAddress,
|
||||
decimals: Number(tokenPrecision),
|
||||
symbol: tokenSymbol,
|
||||
},
|
||||
selectedTokens: {},
|
||||
tokenAddressList: [],
|
||||
});
|
||||
expect(historyStub).toHaveBeenCalledWith('/confirm-import-token');
|
||||
});
|
||||
|
||||
it('cancels out of import token flow', () => {
|
||||
const { getByRole } = render();
|
||||
const closeButton = getByRole('button', { name: 'close' });
|
||||
fireEvent.click(closeButton);
|
||||
|
||||
expect(clearPendingTokens).toHaveBeenCalled();
|
||||
expect(historyStub).toHaveBeenCalledWith('/');
|
||||
});
|
||||
|
||||
it('sets and error when a token is an NFT', async () => {
|
||||
process.env.COLLECTIBLES_V1 = true;
|
||||
getTokenStandardAndDetails.mockImplementation(() =>
|
||||
Promise.resolve({ standard: 'ERC721' }),
|
||||
);
|
||||
|
||||
const { getByText } = render();
|
||||
const customTokenButton = getByText('Custom Token');
|
||||
fireEvent.click(customTokenButton);
|
||||
|
||||
const submit = getByText('Add Custom Token');
|
||||
expect(submit).toBeDisabled();
|
||||
|
||||
const tokenAddress = '0x617b3f8050a0BD94b6b1da02B4384eE5B4DF13F4';
|
||||
await fireEvent.change(document.getElementById('custom-address'), {
|
||||
target: { value: tokenAddress },
|
||||
});
|
||||
|
||||
expect(submit).toBeDisabled();
|
||||
|
||||
// The last part of this error message won't be found by getByText because it is wrapped as a link.
|
||||
const errorMessage = getByText('This token is an NFT. Add on the');
|
||||
expect(errorMessage).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -6,12 +6,12 @@
|
||||
&__custom-token-form {
|
||||
padding: 8px 16px 16px;
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button {
|
||||
input[type='number']::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type="number"]:hover::-webkit-inner-spin-button {
|
||||
input[type='number']:hover::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
display: none;
|
||||
}
|
||||
@ -59,4 +59,13 @@
|
||||
margin-bottom: 16px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&__collectible-address-error-link {
|
||||
color: var(--primary-blue);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--Blue-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1418,6 +1418,18 @@ export async function checkAndUpdateSingleCollectibleOwnershipStatus(
|
||||
);
|
||||
}
|
||||
|
||||
export async function getTokenStandardAndDetails(
|
||||
address,
|
||||
userAddress,
|
||||
tokenId,
|
||||
) {
|
||||
return await promisifiedBackground.getTokenStandardAndDetails(
|
||||
address,
|
||||
userAddress,
|
||||
tokenId,
|
||||
);
|
||||
}
|
||||
|
||||
export function removeToken(address) {
|
||||
return async (dispatch) => {
|
||||
dispatch(showLoadingIndication());
|
||||
|
@ -2652,10 +2652,10 @@
|
||||
web3 "^0.20.7"
|
||||
web3-provider-engine "^16.0.3"
|
||||
|
||||
"@metamask/controllers@^24.0.0":
|
||||
version "24.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-24.0.0.tgz#380d4e10bd9b903afc38b041ea7e64b30ae34dab"
|
||||
integrity sha512-gOTpvxQTNTrXOUpGOiRKcqHUgnjSiTgJcwNLxenZWqPIixz7eI2qHnjO7ZaGbV/baK7SOa4rRGokvsCNV0mafA==
|
||||
"@metamask/controllers@^25.0.0":
|
||||
version "25.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-25.0.0.tgz#764ba46a169f197253e35ca5273720c91eae7662"
|
||||
integrity sha512-BfTRaK87Iha+EXlEsu330Jp9Wtx0gks2vA+q3Lu/AKGbcz2J0cokECNwN7uEe4GQLPLsfCIAkUIAlP3pRiCvjg==
|
||||
dependencies:
|
||||
"@ethereumjs/common" "^2.3.1"
|
||||
"@ethereumjs/tx" "^3.2.1"
|
||||
|
Loading…
x
Reference in New Issue
Block a user