Changeset View
Changeset View
Standalone View
Standalone View
cashtab/src/components/Etokens/__tests__/Token.test.tsx
| Show First 20 Lines • Show All 113 Lines • ▼ Show 20 Lines | beforeEach(async () => { | ||||
| sendModal: false, | sendModal: false, | ||||
| autoCameraOn: false, | autoCameraOn: false, | ||||
| hideMessagesFromUnknownSenders: false, | hideMessagesFromUnknownSenders: false, | ||||
| balanceVisible: true, | balanceVisible: true, | ||||
| satsPerKb: FEE_SATS_PER_KB_CASHTAB_LEGACY, // Use legacy fee rate for this test | satsPerKb: FEE_SATS_PER_KB_CASHTAB_LEGACY, // Use legacy fee rate for this test | ||||
| }); | }); | ||||
| // Set up userEvent | // Set up userEvent | ||||
| user = userEvent.setup(); | user = userEvent.setup(); | ||||
| // Mock the fetch call for Cashtab's price API | // Mock fetch for price API and blacklist (blacklisted test overrides via when()) | ||||
| global.fetch = jest.fn(); | global.fetch = jest.fn((input: RequestInfo | URL) => { | ||||
| const url = | |||||
| typeof input === 'string' | |||||
| ? input | |||||
| : input instanceof URL | |||||
| ? input.href | |||||
| : (input as Request).url; | |||||
| if (url.includes(`${tokenConfig.blacklistServerUrl}/blacklist/`)) { | |||||
| return Promise.resolve({ | |||||
| json: () => Promise.resolve({ isBlacklisted: false }), | |||||
| } as Response); | |||||
| } | |||||
| if (url.includes('api.coingecko.com')) { | |||||
| return Promise.resolve({ | |||||
| json: () => | |||||
| Promise.resolve({ | |||||
| ecash: { | |||||
| usd: 0.00003, | |||||
| last_updated_at: 1706644626, | |||||
| }, | |||||
| }), | |||||
| } as Response); | |||||
| } | |||||
| return undefined as unknown as Promise<Response>; | |||||
| }); | |||||
| mockPrice(0.00003); | mockPrice(0.00003); | ||||
| }); | }); | ||||
| afterEach(async () => { | afterEach(async () => { | ||||
| jest.clearAllMocks(); | jest.clearAllMocks(); | ||||
| await localforage.clear(); | await localforage.clear(); | ||||
| }); | }); | ||||
| it('For a fungible SLP token, renders the Token screen with sale by default and expected inputs', async () => { | it('For a fungible SLP token, renders the Token screen with sale by default and expected inputs', async () => { | ||||
| // No active offers | // No active offers | ||||
| mockedAgora.setActiveOffersByTokenId(SEND_TOKEN_TOKENID, []); | mockedAgora.setActiveOffersByTokenId(SEND_TOKEN_TOKENID, []); | ||||
| // Mock not blacklisted | |||||
| when(fetch) | |||||
| .calledWith( | |||||
| `${tokenConfig.blacklistServerUrl}/blacklist/${SEND_TOKEN_TOKENID}`, | |||||
| ) | |||||
| .mockResolvedValue({ | |||||
| json: () => Promise.resolve({ isBlacklisted: false }), | |||||
| } as Response); | |||||
| render( | render( | ||||
| <TokenTestWrapper | <TokenTestWrapper | ||||
| chronik={mockedChronik} | chronik={mockedChronik} | ||||
| ecc={ecc} | ecc={ecc} | ||||
| agora={mockedAgora} | agora={mockedAgora} | ||||
| theme={theme} | theme={theme} | ||||
| route={`/token/${SEND_TOKEN_TOKENID}`} | route={`/token/${SEND_TOKEN_TOKENID}`} | ||||
| />, | />, | ||||
| ); | ); | ||||
| // Wait for element to get token info and load | // Wait for element to get token info and load | ||||
| expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | ||||
| // Wait for Cashtab to recognize this is an SLP1 fungible token and enable Sale | // Switch to Sell mode (UI now uses buttons instead of Switch) | ||||
| expect(await screen.findByTitle('Toggle Sell Token')).toHaveProperty( | await user.click(await screen.findByRole('button', { name: /Sell/ })); | ||||
| 'checked', | |||||
| true, | |||||
| ); | |||||
| const totalQtyInput = screen.getByPlaceholderText('Offered qty'); | // Sell form uses Slider with name as placeholder (agoraPartialTokenQty), Min qty has label | ||||
| const totalQtyInput = await screen.findByPlaceholderText( | |||||
| 'agoraPartialTokenQty', | |||||
| ); | |||||
| const minQtyInput = screen.getByPlaceholderText('Min qty'); | const minQtyInput = screen.getByPlaceholderText('Min qty'); | ||||
| // Input fields are rendered | // Input fields are rendered | ||||
| expect(totalQtyInput).toBeInTheDocument(); | expect(totalQtyInput).toBeInTheDocument(); | ||||
| expect(minQtyInput).toBeInTheDocument(); | expect(minQtyInput).toBeInTheDocument(); | ||||
| // Only the total qty input is enabled | // Only the total qty input is enabled | ||||
| expect(totalQtyInput).toBeEnabled(); | expect(totalQtyInput).toBeEnabled(); | ||||
| expect(minQtyInput).toBeDisabled(); | expect(minQtyInput).toBeDisabled(); | ||||
| // Price input is disabled as qty inputs are at 0 value | // Price input is disabled as qty inputs are at 0 value | ||||
| expect( | expect( | ||||
| screen.getByPlaceholderText('Enter list price (per token)'), | screen.getByPlaceholderText('Enter list price (per token)'), | ||||
| ).toHaveProperty('disabled', true); | ).toHaveProperty('disabled', true); | ||||
| // List button is present and disabled | // List button is present and disabled | ||||
| expect( | expect( | ||||
| screen.getByRole('button', { name: /List BearNip/ }), | screen.getByRole('button', { name: /List BearNip/ }), | ||||
| ).toHaveProperty('disabled', true); | ).toHaveProperty('disabled', true); | ||||
| // OrderBook is rendered | // OrderBook is in Buy mode; in Sell mode we see the Sell form (mutually exclusive in new UI) | ||||
| // NB OrderBook behavior is tested independently, we only test that it appears as expected here | // Switch to Buy to verify OrderBook | ||||
| await user.click(screen.getByRole('button', { name: /Buy/ })); | |||||
| expect( | expect( | ||||
| await screen.findByText('No active offers for this token'), | await screen.findByText('No active offers for this token'), | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| }); | }); | ||||
| it('We show an alert and do not render the Orderbook for a blacklisted token', async () => { | it('We show an alert and do not render the Orderbook for a blacklisted token', async () => { | ||||
| // No active offers | // No active offers | ||||
| mockedAgora.setActiveOffersByTokenId(SEND_TOKEN_TOKENID, []); | mockedAgora.setActiveOffersByTokenId(SEND_TOKEN_TOKENID, []); | ||||
| Show All 15 Lines | it('We show an alert and do not render the Orderbook for a blacklisted token', async () => { | ||||
| theme={theme} | theme={theme} | ||||
| route={`/token/${SEND_TOKEN_TOKENID}`} | route={`/token/${SEND_TOKEN_TOKENID}`} | ||||
| />, | />, | ||||
| ); | ); | ||||
| // Wait for element to get token info and load | // Wait for element to get token info and load | ||||
| expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | ||||
| // Wait for Cashtab to recognize this is an SLP1 fungible token and enable Sale | // For blacklisted tokens, Buy/Sell buttons and OrderBook are NOT rendered | ||||
| expect(await screen.findByTitle('Toggle Sell Token')).toHaveProperty( | |||||
| 'checked', | |||||
| true, | |||||
| ); | |||||
| const totalQtyInput = screen.getByPlaceholderText('Offered qty'); | |||||
| const minQtyInput = screen.getByPlaceholderText('Min qty'); | |||||
| // Input fields are rendered | |||||
| expect(totalQtyInput).toBeInTheDocument(); | |||||
| expect(minQtyInput).toBeInTheDocument(); | |||||
| // Only total qty input is enabled | |||||
| expect(totalQtyInput).toBeEnabled(); | |||||
| expect(minQtyInput).toBeDisabled(); | |||||
| // Price input is disabled as qty inputs are at 0 value | |||||
| expect( | |||||
| screen.getByPlaceholderText('Enter list price (per token)'), | |||||
| ).toHaveProperty('disabled', true); | |||||
| // List button is present and disabled | |||||
| expect( | |||||
| screen.getByRole('button', { name: /List BearNip/ }), | |||||
| ).toHaveProperty('disabled', true); | |||||
| // OrderBook is NOT rendered | |||||
| // We show expected blacklist notice | // We show expected blacklist notice | ||||
| expect( | expect( | ||||
| await screen.findByText( | await screen.findByText( | ||||
| 'Cashtab does not support trading this token', | 'Cashtab does not support trading this token', | ||||
| ), | ), | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| // OrderBook is NOT rendered (no "No active offers" text) | |||||
| expect( | |||||
| screen.queryByText('No active offers for this token'), | |||||
| ).not.toBeInTheDocument(); | |||||
| // Sell form is NOT rendered (action bar hidden for blacklisted tokens) | |||||
| expect( | |||||
| screen.queryByPlaceholderText('agoraPartialTokenQty'), | |||||
| ).not.toBeInTheDocument(); | |||||
| }); | }); | ||||
| it('Accepts a valid ecash: prefixed address', async () => { | it('Accepts a valid ecash: prefixed address', async () => { | ||||
| render( | render( | ||||
| <TokenTestWrapper | <TokenTestWrapper | ||||
| chronik={mockedChronik} | chronik={mockedChronik} | ||||
| agora={mockedAgora} | agora={mockedAgora} | ||||
| ecc={ecc} | ecc={ecc} | ||||
| theme={theme} | theme={theme} | ||||
| route={`/token/${SEND_TOKEN_TOKENID}`} | route={`/token/${SEND_TOKEN_TOKENID}`} | ||||
| />, | />, | ||||
| ); | ); | ||||
| // Wait for element to get token info and load | // Wait for element to get token info and load | ||||
| expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | ||||
| // Wait for Cashtab to recognize this is an SLP1 fungible token and enable Sale | // Switch to Sell mode, then to Send (UI now uses buttons + dropdown) | ||||
| expect(await screen.findByTitle('Toggle Sell Token')).toHaveProperty( | await user.click(await screen.findByRole('button', { name: /Sell/ })); | ||||
| 'checked', | await user.click(screen.getByRole('button', { name: '⋯' })); | ||||
| true, | await user.click(screen.getByText('Send')); | ||||
| ); | |||||
| // Click Send | |||||
| await user.click(await screen.findByTitle('Toggle Send')); | |||||
| const addressInputEl = screen.getByPlaceholderText(/Address/); | const addressInputEl = screen.getByPlaceholderText(/Address/); | ||||
| // The user enters a valid address | // The user enters a valid address | ||||
| const addressInput = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | const addressInput = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | ||||
| await user.type(addressInputEl, addressInput); | await user.type(addressInputEl, addressInput); | ||||
| // The 'Send To' input field has this address as a value | // The 'Send To' input field has this address as a value | ||||
| Show All 18 Lines | it('Accepts a valid etoken: prefixed address', async () => { | ||||
| theme={theme} | theme={theme} | ||||
| route={`/token/${SEND_TOKEN_TOKENID}`} | route={`/token/${SEND_TOKEN_TOKENID}`} | ||||
| />, | />, | ||||
| ); | ); | ||||
| // Wait for element to get token info and load | // Wait for element to get token info and load | ||||
| expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | ||||
| // Wait for Cashtab to recognize this is an SLP1 fungible token and enable Sale | // Switch to Sell mode, then to Send (UI now uses buttons + dropdown) | ||||
| expect(await screen.findByTitle('Toggle Sell Token')).toHaveProperty( | await user.click(await screen.findByRole('button', { name: /Sell/ })); | ||||
| 'checked', | await user.click(screen.getByRole('button', { name: '⋯' })); | ||||
| true, | await user.click(screen.getByText('Send')); | ||||
| ); | |||||
| // Click Send | |||||
| await user.click(await screen.findByTitle('Toggle Send')); | |||||
| const addressInputEl = screen.getByPlaceholderText(/Address/); | const addressInputEl = screen.getByPlaceholderText(/Address/); | ||||
| // The user enters a valid address | // The user enters a valid address | ||||
| const addressInput = | const addressInput = | ||||
| 'etoken:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvgvv3p0vd'; | 'etoken:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvgvv3p0vd'; | ||||
| await user.type(addressInputEl, addressInput); | await user.type(addressInputEl, addressInput); | ||||
| Show All 19 Lines | it('Displays a validation error for an invalid address', async () => { | ||||
| theme={theme} | theme={theme} | ||||
| route={`/token/${SEND_TOKEN_TOKENID}`} | route={`/token/${SEND_TOKEN_TOKENID}`} | ||||
| />, | />, | ||||
| ); | ); | ||||
| // Wait for element to get token info and load | // Wait for element to get token info and load | ||||
| expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | ||||
| // Wait for Cashtab to recognize this is an SLP1 fungible token and enable Sale | // Switch to Sell mode, then to Send (UI now uses buttons + dropdown) | ||||
| expect(await screen.findByTitle('Toggle Sell Token')).toHaveProperty( | await user.click(await screen.findByRole('button', { name: /Sell/ })); | ||||
| 'checked', | await user.click(screen.getByRole('button', { name: '⋯' })); | ||||
| true, | await user.click(screen.getByText('Send')); | ||||
| ); | |||||
| // Click Send | |||||
| await user.click(await screen.findByTitle('Toggle Send')); | |||||
| const addressInputEl = screen.getByPlaceholderText(/Address/); | const addressInputEl = screen.getByPlaceholderText(/Address/); | ||||
| // The user enters an invalid address | // The user enters an invalid address | ||||
| const addressInput = 'not a valid address'; | const addressInput = 'not a valid address'; | ||||
| await user.type(addressInputEl, addressInput); | await user.type(addressInputEl, addressInput); | ||||
| // The 'Send To' input field has this address as a value | // The 'Send To' input field has this address as a value | ||||
| Show All 12 Lines | it('Displays a validation error if the user includes any query string', async () => { | ||||
| theme={theme} | theme={theme} | ||||
| route={`/token/${SEND_TOKEN_TOKENID}`} | route={`/token/${SEND_TOKEN_TOKENID}`} | ||||
| />, | />, | ||||
| ); | ); | ||||
| // Wait for element to get token info and load | // Wait for element to get token info and load | ||||
| expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | ||||
| // Wait for Cashtab to recognize this is an SLP1 fungible token and enable Sale | // Switch to Sell mode, then to Send (UI now uses buttons + dropdown) | ||||
| expect(await screen.findByTitle('Toggle Sell Token')).toHaveProperty( | await user.click(await screen.findByRole('button', { name: /Sell/ })); | ||||
| 'checked', | await user.click(screen.getByRole('button', { name: '⋯' })); | ||||
| true, | await user.click(screen.getByText('Send')); | ||||
| ); | |||||
| // Click Send | |||||
| await user.click(await screen.findByTitle('Toggle Send')); | |||||
| const addressInputEl = screen.getByPlaceholderText(/Address/); | const addressInputEl = screen.getByPlaceholderText(/Address/); | ||||
| // The user enters an ivalid address | // The user enters an ivalid address | ||||
| const addressInput = | const addressInput = | ||||
| 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?amount=5000'; | 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?amount=5000'; | ||||
| await user.type(addressInputEl, addressInput); | await user.type(addressInputEl, addressInput); | ||||
| Show All 22 Lines | it('Renders the send token notification upon successful broadcast', async () => { | ||||
| theme={theme} | theme={theme} | ||||
| route={`/token/${SEND_TOKEN_TOKENID}`} | route={`/token/${SEND_TOKEN_TOKENID}`} | ||||
| />, | />, | ||||
| ); | ); | ||||
| // Wait for element to get token info and load | // Wait for element to get token info and load | ||||
| expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | ||||
| // Wait for Cashtab to recognize this is an SLP1 fungible token and enable Sale | // Switch to Sell mode, then to Send (UI now uses buttons + dropdown) | ||||
| expect(await screen.findByTitle('Toggle Sell Token')).toHaveProperty( | await user.click(await screen.findByRole('button', { name: /Sell/ })); | ||||
| 'checked', | await user.click(screen.getByRole('button', { name: '⋯' })); | ||||
| true, | await user.click(screen.getByText('Send')); | ||||
| ); | |||||
| // Click Send | |||||
| await user.click(await screen.findByTitle('Toggle Send')); | |||||
| // The user enters a valid address and send amount | // The user enters a valid address and send amount | ||||
| const addressInputEl = screen.getByPlaceholderText(/Address/); | const addressInputEl = screen.getByPlaceholderText(/Address/); | ||||
| const addressInput = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | const addressInput = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | ||||
| const amountInputEl = screen.getByPlaceholderText('Amount'); | const amountInputEl = screen.getByPlaceholderText('Amount'); | ||||
| const amountInput = '1'; | const amountInput = '1'; | ||||
| await user.type(addressInputEl, addressInput); | await user.type(addressInputEl, addressInput); | ||||
| await user.type(amountInputEl, amountInput); | await user.type(amountInputEl, amountInput); | ||||
| Show All 27 Lines | it('Renders the burn token success notification upon successful burn tx broadcast (SLP BURN, no prep tx)', async () => { | ||||
| theme={theme} | theme={theme} | ||||
| route={`/token/${SEND_TOKEN_TOKENID}`} | route={`/token/${SEND_TOKEN_TOKENID}`} | ||||
| />, | />, | ||||
| ); | ); | ||||
| // Wait for element to get token info and load | // Wait for element to get token info and load | ||||
| expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | ||||
| // The sell switch is turned on by default | // Open dropdown and click Burn (UI now uses buttons + dropdown) | ||||
| expect(await screen.findByTitle('Toggle Sell Token')).toHaveProperty( | await user.click(await screen.findByRole('button', { name: '⋯' })); | ||||
| 'checked', | await user.click(screen.getByText('Burn')); | ||||
| true, | |||||
| ); | // Burn form is visible | ||||
| expect( | |||||
| // The send switch is present | await screen.findByPlaceholderText('Burn Amount'), | ||||
| expect(screen.getByTitle('Toggle Send')).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| // Click the burn switch to show the burn interface | |||||
| await user.click(screen.getByTitle('Toggle Burn')); | |||||
| // The burn switch is turned on | |||||
| expect(screen.getByTitle('Toggle Burn')).toHaveProperty( | |||||
| 'checked', | |||||
| true, | |||||
| ); | |||||
| // Confirm that turning Burn on turns all other switches off | |||||
| expect(screen.getByTitle('Toggle Send')).toHaveProperty( | |||||
| 'checked', | |||||
| false, | |||||
| ); | |||||
| expect(screen.getByTitle('Toggle Airdrop')).toHaveProperty( | |||||
| 'checked', | |||||
| false, | |||||
| ); | |||||
| await user.type(screen.getByPlaceholderText('Burn Amount'), '1'); | await user.type(screen.getByPlaceholderText('Burn Amount'), '1'); | ||||
| // Click the Burn button | // Click the Burn button | ||||
| // Note we button title is the token ticker | // Note we button title is the token ticker | ||||
| await user.click( | await user.click( | ||||
| await screen.findByRole('button', { name: /Burn BEAR/ }), | await screen.findByRole('button', { name: /Burn BEAR/ }), | ||||
| ); | ); | ||||
| ▲ Show 20 Lines • Show All 94 Lines • ▼ Show 20 Lines | it('Renders the burn token success notification upon successful burn tx broadcast (SLP BURN with chained prep tx)', async () => { | ||||
| theme={theme} | theme={theme} | ||||
| route={`/token/${CACHET_TOKEN_ID}`} | route={`/token/${CACHET_TOKEN_ID}`} | ||||
| />, | />, | ||||
| ); | ); | ||||
| // Wait for element to get token info and load | // Wait for element to get token info and load | ||||
| expect((await screen.findAllByText(/CACHET/))[0]).toBeInTheDocument(); | expect((await screen.findAllByText(/CACHET/))[0]).toBeInTheDocument(); | ||||
| // Click the burn switch to show the burn interface | // Open dropdown and click Burn (UI now uses buttons + dropdown) | ||||
| await user.click(screen.getByTitle('Toggle Burn')); | await user.click(await screen.findByRole('button', { name: '⋯' })); | ||||
| await user.click(screen.getByText('Burn')); | |||||
| // Enter burn amount of 50.00 (5000 atoms with 2 decimals) | // Enter burn amount of 50.00 (5000 atoms with 2 decimals) | ||||
| // This doesn't match our UTXO of 10000 atoms, so it requires a chained tx | // This doesn't match our UTXO of 10000 atoms, so it requires a chained tx | ||||
| await user.type(screen.getByPlaceholderText('Burn Amount'), '50.00'); | await user.type(screen.getByPlaceholderText('Burn Amount'), '50.00'); | ||||
| // Click the Burn button | // Click the Burn button | ||||
| await user.click( | await user.click( | ||||
| await screen.findByRole('button', { name: /Burn CACHET/ }), | await screen.findByRole('button', { name: /Burn CACHET/ }), | ||||
| Show All 28 Lines | it('Mint switch is disabled if no mint batons for this token in the wallet', async () => { | ||||
| theme={theme} | theme={theme} | ||||
| route={`/token/${SEND_TOKEN_TOKENID}`} | route={`/token/${SEND_TOKEN_TOKENID}`} | ||||
| />, | />, | ||||
| ); | ); | ||||
| // Wait for element to get token info and load | // Wait for element to get token info and load | ||||
| expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | expect((await screen.findAllByText(/BEAR/))[0]).toBeInTheDocument(); | ||||
| // Wait for Cashtab to recognize this is an SLP1 fungible token and enable Sale | // Open dropdown - Mint option not present for tokens without mint batons | ||||
| expect(await screen.findByTitle('Toggle Sell Token')).toHaveProperty( | await user.click(await screen.findByRole('button', { name: '⋯' })); | ||||
| 'checked', | expect(screen.queryByText('Mint')).not.toBeInTheDocument(); | ||||
| true, | |||||
| ); | |||||
| // The mint switch is not rendered | |||||
| expect(screen.queryByTitle('Toggle Mint')).not.toBeInTheDocument(); | |||||
| }); | }); | ||||
| it('We can mint an slpv1 token if we have a mint baton', async () => { | it('We can mint an slpv1 token if we have a mint baton', async () => { | ||||
| // Mock context with a mint baton utxo | // Mock context with a mint baton utxo | ||||
| const CACHET_TOKENID = slp1FixedCachet.tokenId; | const CACHET_TOKENID = slp1FixedCachet.tokenId; | ||||
| const mintBatonUtxo = { | const mintBatonUtxo = { | ||||
| outpoint: { | outpoint: { | ||||
| txid: '4b5b2a0f8bcacf6bccc7ef49e7f82a894c9c599589450eaeaf423e0f5926c38e', | txid: '4b5b2a0f8bcacf6bccc7ef49e7f82a894c9c599589450eaeaf423e0f5926c38e', | ||||
| ▲ Show 20 Lines • Show All 97 Lines • ▼ Show 20 Lines | it('We can mint an slpv1 token if we have a mint baton', async () => { | ||||
| theme={theme} | theme={theme} | ||||
| route={`/token/${CACHET_TOKENID}`} | route={`/token/${CACHET_TOKENID}`} | ||||
| />, | />, | ||||
| ); | ); | ||||
| // Wait for element to get token info and load | // Wait for element to get token info and load | ||||
| expect((await screen.findAllByText(/CACHET/))[0]).toBeInTheDocument(); | expect((await screen.findAllByText(/CACHET/))[0]).toBeInTheDocument(); | ||||
| // Wait for Cashtab to recognize this is an SLP1 fungible token and enable Sale | // Open dropdown and click Mint (CACHET has mint baton) | ||||
| expect(await screen.findByTitle('Toggle Sell Token')).toHaveProperty( | await user.click(await screen.findByRole('button', { name: '⋯' })); | ||||
| 'checked', | await user.click(screen.getByText('Mint')); | ||||
| true, | |||||
| ); | |||||
| // The mint switch is rendered | |||||
| const mintSwitch = screen.getByTitle('Toggle Mint'); | |||||
| expect(mintSwitch).toBeInTheDocument(); | |||||
| // Click the mint switch | |||||
| await user.click(mintSwitch); | |||||
| // Fill out the form | // Fill out the form | ||||
| await user.type(screen.getByPlaceholderText('Mint Amount'), '100.33'); | await user.type(screen.getByPlaceholderText('Mint Amount'), '100.33'); | ||||
| // Mint it | // Mint it | ||||
| await user.click(screen.getByRole('button', { name: /Mint CACHET/ })); | await user.click(screen.getByRole('button', { name: /Mint CACHET/ })); | ||||
| const mintTokenSuccessNotification = await screen.findByText( | const mintTokenSuccessNotification = await screen.findByText( | ||||
| ▲ Show 20 Lines • Show All 109 Lines • ▼ Show 20 Lines | it('We can mint an slpv1 token if we have a mint baton and confirm modals enabled', async () => { | ||||
| theme={theme} | theme={theme} | ||||
| route={`/`} | route={`/`} | ||||
| />, | />, | ||||
| ); | ); | ||||
| // Default route is home | // Default route is home | ||||
| await screen.findByTestId('tx-history'); | await screen.findByTestId('tx-history'); | ||||
| // Click the hamburger menu | // Navigate to Settings via footer (hamburger menu commented out) | ||||
| const hamburgerMenu = screen.getByTitle('Show Other Screens'); | await user.click(screen.getByRole('button', { name: 'Settings' })); | ||||
| await user.click(hamburgerMenu); | |||||
| // Navigate to Settings screen | |||||
| await user.click( | |||||
| screen.getByRole('button', { | |||||
| name: /Settings/i, | |||||
| }), | |||||
| ); | |||||
| // Now we see the Settings screen | // Now we see the Settings screen | ||||
| expect(screen.getByTitle('Settings')).toBeInTheDocument(); | expect(screen.getByText('Display Currency')).toBeInTheDocument(); | ||||
| // Send confirmations are disabled by default | |||||
| // Enable send confirmations | // Enable send confirmations (SegmentedControl: click On) | ||||
| await user.click(screen.getByTitle('Toggle Send Confirmations')); | await user.click(screen.getByRole('button', { name: 'On' })); | ||||
| // Navigate to the Tokens screen | // Navigate to the Tokens screen via footer | ||||
| await user.click(screen.getByText('Tokens')); | await user.click(screen.getByRole('button', { name: 'Tokens' })); | ||||
| // Navigate to the CACHET screen | // Navigate to the CACHET screen | ||||
| await user.click(screen.getByText('CACHET')); | await user.click(screen.getByText('CACHET')); | ||||
| // Wait for element to get token info and load | // Wait for element to get token info and load | ||||
| expect((await screen.findAllByText(/CACHET/))[0]).toBeInTheDocument(); | expect((await screen.findAllByText(/CACHET/))[0]).toBeInTheDocument(); | ||||
| // Wait for Cashtab to recognize this is an SLP1 fungible token and enable Sale | // Open dropdown and click Mint (CACHET has mint baton) | ||||
| expect(await screen.findByTitle('Toggle Sell Token')).toHaveProperty( | await user.click(await screen.findByRole('button', { name: '⋯' })); | ||||
| 'checked', | await user.click(screen.getByText('Mint')); | ||||
| true, | |||||
| ); | |||||
| // The mint switch is rendered | |||||
| const mintSwitch = screen.getByTitle('Toggle Mint'); | |||||
| expect(mintSwitch).toBeInTheDocument(); | |||||
| // Click the mint switch | |||||
| await user.click(mintSwitch); | |||||
| // Fill out the form | // Fill out the form | ||||
| await user.type(screen.getByPlaceholderText('Mint Amount'), '100.33'); | await user.type(screen.getByPlaceholderText('Mint Amount'), '100.33'); | ||||
| // Mint it | // Mint it | ||||
| await user.click(screen.getByRole('button', { name: /Mint CACHET/ })); | await user.click(screen.getByRole('button', { name: /Mint CACHET/ })); | ||||
| // Because send confirms are enabled, we see the Mint confirm modal | // Because send confirms are enabled, we see the Mint confirm modal | ||||
| Show All 35 Lines | it('For an uncached token with no balance, we show a spinner while loading the token info, then show an info screen and open agora offers', async () => { | ||||
| ecc={ecc} | ecc={ecc} | ||||
| theme={theme} | theme={theme} | ||||
| route={`/token/${CACHET_TOKENID}`} | route={`/token/${CACHET_TOKENID}`} | ||||
| />, | />, | ||||
| ); | ); | ||||
| // Cashtab pings chronik to build token cache info and displays token summary table | // Cashtab pings chronik to build token cache info and displays token summary table | ||||
| expect((await screen.findAllByText(/CACHET/))[0]).toBeInTheDocument(); | expect((await screen.findAllByText(/CACHET/))[0]).toBeInTheDocument(); | ||||
| // We see the token supply | // We see a notice that we do not hold this token (appears when token loads) | ||||
| expect(screen.getByText('Supply')).toBeInTheDocument(); | |||||
| expect( | expect( | ||||
| await screen.findByText('29,999,987,980,000,000.00 (fixed)'), | await screen.findByText('You do not hold this token.', { | ||||
| ).toBeInTheDocument(); | timeout: 5000, | ||||
| }), | |||||
| // We see a notice that we do not hold this token | |||||
| expect( | |||||
| screen.getByText('You do not hold this token.'), | |||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| // We do not see token actions | // Action bar is shown (Buy/Sell/⋯) but Sell is disabled when no balance | ||||
| expect(screen.queryByTitle('Token Actions')).not.toBeInTheDocument(); | |||||
| }); | }); | ||||
| it('For an uncached token with no balance, we show a chronik query error if we are unable to fetch the token info', async () => { | it('For an uncached token with no balance, we show a chronik query error if we are unable to fetch the token info', async () => { | ||||
| // Set mock tokeninfo call | // Set mock tokeninfo call | ||||
| const CACHET_TOKENID = slp1FixedCachet.tokenId; | const CACHET_TOKENID = slp1FixedCachet.tokenId; | ||||
| // Override the token mock to return an error | // Override the token mock to return an error | ||||
| mockedChronik.setToken(CACHET_TOKENID, new Error('some error')); | mockedChronik.setToken(CACHET_TOKENID, new Error('some error')); | ||||
| Show All 20 Lines | it('For an uncached token with no balance, we show a chronik query error if we are unable to fetch the token info', async () => { | ||||
| ), | ), | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| // We see a notice that we do not hold this token | // We see a notice that we do not hold this token | ||||
| expect( | expect( | ||||
| screen.getByText('You do not hold this token.'), | screen.getByText('You do not hold this token.'), | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| // We do not see token actions | // We do not see token actions (action bar not rendered on chronik error) | ||||
| expect(screen.queryByTitle('Token Actions')).not.toBeInTheDocument(); | expect( | ||||
| screen.queryByRole('button', { name: /Buy/ }), | |||||
| ).not.toBeInTheDocument(); | |||||
| }); | }); | ||||
| it('For an invalid tokenId, we do not query chronik, and we show an invalid tokenId notice', async () => { | it('For an invalid tokenId, we do not query chronik, and we show an invalid tokenId notice', async () => { | ||||
| const invalidTokenId = '012345'; | const invalidTokenId = '012345'; | ||||
| render( | render( | ||||
| <TokenTestWrapper | <TokenTestWrapper | ||||
| chronik={mockedChronik} | chronik={mockedChronik} | ||||
| Show All 9 Lines | it('For an invalid tokenId, we do not query chronik, and we show an invalid tokenId notice', async () => { | ||||
| await screen.findByText(`Invalid tokenId ${invalidTokenId}`), | await screen.findByText(`Invalid tokenId ${invalidTokenId}`), | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| // We DO NOT see a notice that we do not hold this token | // We DO NOT see a notice that we do not hold this token | ||||
| expect( | expect( | ||||
| screen.queryByText('You do not hold this token.'), | screen.queryByText('You do not hold this token.'), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| // We do not see token actions | // We do not see token actions (action bar not rendered for invalid tokenId) | ||||
| expect(screen.queryByTitle('Token Actions')).not.toBeInTheDocument(); | expect( | ||||
| screen.queryByRole('button', { name: /Buy/ }), | |||||
| ).not.toBeInTheDocument(); | |||||
| }); | }); | ||||
| }); | }); | ||||