Changeset View
Changeset View
Standalone View
Standalone View
cashtab/src/components/Send/__tests__/SendXec.test.js
| Show First 20 Lines • Show All 89 Lines • ▼ Show 20 Lines | it('Renders the SendXec screen with send address input', async () => { | ||||
| // Input fields are rendered | // Input fields are rendered | ||||
| expect(addressInputEl).toBeInTheDocument(); | expect(addressInputEl).toBeInTheDocument(); | ||||
| expect(amountInputEl).toBeInTheDocument(); | expect(amountInputEl).toBeInTheDocument(); | ||||
| // Inputs are not disabled | // Inputs are not disabled | ||||
| expect(addressInputEl).toHaveProperty('disabled', false); | expect(addressInputEl).toHaveProperty('disabled', false); | ||||
| expect(amountInputEl).toHaveProperty('disabled', false); | expect(amountInputEl).toHaveProperty('disabled', false); | ||||
| // The "Send to Many" switch is not disabled | |||||
| expect(screen.getByTitle('Toggle Multisend')).toHaveProperty( | |||||
| 'disabled', | |||||
| false, | |||||
| ); | |||||
| // The Send button is disabled | // The Send button is disabled | ||||
| expect(screen.getByRole('button', { name: 'Send' })).toHaveStyle( | expect(screen.getByRole('button', { name: 'Send' })).toHaveStyle( | ||||
| 'cursor: not-allowed', | 'cursor: not-allowed', | ||||
| ); | ); | ||||
| // No addr validation errors on load | // No addr validation errors on load | ||||
| for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) { | for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) { | ||||
| expect(screen.queryByText(addrErr)).not.toBeInTheDocument(); | expect(screen.queryByText(addrErr)).not.toBeInTheDocument(); | ||||
| } | } | ||||
| // No amount validation errors on load | // No amount validation errors on load | ||||
| for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) { | for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) { | ||||
| expect(screen.queryByText(amountErr)).not.toBeInTheDocument(); | expect(screen.queryByText(amountErr)).not.toBeInTheDocument(); | ||||
| } | } | ||||
| // We select op_return_raw input | // We select op_return_raw input (expand Advanced first, then click op_return_raw) | ||||
| await user.click(screen.getByTitle('Toggle op_return_raw')); | await user.click(screen.getByRole('button', { name: /Advanced/i })); | ||||
| await user.click(screen.getByRole('button', { name: 'op_return_raw' })); | |||||
| // We do not see the parsed op return raw msg area bc the op_return_raw input is empty | // We do not see the parsed op return raw msg area bc the op_return_raw input is empty | ||||
| expect( | expect( | ||||
| screen.queryByTestId('Parsed op_return_raw'), | screen.queryByTestId('Parsed op_return_raw'), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| }); | }); | ||||
| it('Pass valid address to Send To field', async () => { | it('Pass valid address to Send To field', async () => { | ||||
| // Mock the app with context at the Send screen | // Mock the app with context at the Send screen | ||||
| Show All 23 Lines | it('Pass valid address to Send To field', async () => { | ||||
| // 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 | ||||
| expect(addressInputEl).toHaveValue(addressInput); | expect(addressInputEl).toHaveValue(addressInput); | ||||
| // The "Send to Many" switch is not disabled | |||||
| expect(screen.getByTitle('Toggle Multisend')).toHaveProperty( | |||||
| 'disabled', | |||||
| false, | |||||
| ); | |||||
| // Amount input is untouched | // Amount input is untouched | ||||
| expect(amountInputEl).toHaveValue(null); | expect(amountInputEl).toHaveValue(null); | ||||
| // The amount input is NOT disabled because there is no BIP21 query string | // The amount input is NOT disabled because there is no BIP21 query string | ||||
| expect(amountInputEl).toHaveProperty('disabled', false); | expect(amountInputEl).toHaveProperty('disabled', false); | ||||
| // No addr validation errors on load | // No addr validation errors on load | ||||
| for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) { | for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) { | ||||
| Show All 36 Lines | it('Pass an invalid address to Send To field and get a validation error', async () => { | ||||
| // The user enters an invalid address | // The user enters an invalid address | ||||
| const addressInput = 'ecash:notValid'; | const addressInput = 'ecash:notValid'; | ||||
| 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 | ||||
| expect(addressInputEl).toHaveValue(addressInput); | expect(addressInputEl).toHaveValue(addressInput); | ||||
| // The "Send to Many" switch is not disabled | |||||
| expect(screen.getByTitle('Toggle Multisend')).toHaveProperty( | |||||
| 'disabled', | |||||
| false, | |||||
| ); | |||||
| // Amount input is untouched | // Amount input is untouched | ||||
| expect(amountInputEl).toHaveValue(null); | expect(amountInputEl).toHaveValue(null); | ||||
| // The amount input is NOT disabled because there is no BIP21 query string | // The amount input is NOT disabled because there is no BIP21 query string | ||||
| expect(amountInputEl).toHaveProperty('disabled', false); | expect(amountInputEl).toHaveProperty('disabled', false); | ||||
| // We get expected addr validation error | // We get expected addr validation error | ||||
| expect(screen.getByText('Invalid address')).toBeInTheDocument(); | expect(screen.getByText('Invalid address')).toBeInTheDocument(); | ||||
| Show All 31 Lines | it('Pass a valid address and bip21 query string with valid amount param to Send To field', async () => { | ||||
| // The user enters a valid BIP21 query string with a valid amount param | // The user enters a valid BIP21 query string with a valid amount param | ||||
| const addressInput = | const addressInput = | ||||
| 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?amount=500'; | 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?amount=500'; | ||||
| 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 | ||||
| expect(addressInputEl).toHaveValue(addressInput); | expect(addressInputEl).toHaveValue(addressInput); | ||||
| // The "Send to Many" switch is disabled | // The "Send to many" button is disabled (expand Advanced first to see it) | ||||
| expect(screen.getByTitle('Toggle Multisend')).toHaveProperty( | await user.click(screen.getByRole('button', { name: /Advanced/i })); | ||||
| 'disabled', | expect( | ||||
| true, | screen.getByRole('button', { name: 'Send to many' }), | ||||
| ); | ).toHaveProperty('disabled', true); | ||||
| // Amount input is the valid amount param value | // Amount input is the valid amount param value | ||||
| expect(amountInputEl).toHaveValue(500); | expect(amountInputEl).toHaveValue(500); | ||||
| // The amount input is disabled because it is set by a bip21 query string | // The amount input is disabled because it is set by a bip21 query string | ||||
| expect(amountInputEl).toHaveProperty('disabled', true); | expect(amountInputEl).toHaveProperty('disabled', true); | ||||
| // No addr validation errors on load | // No addr validation errors on load | ||||
| ▲ Show 20 Lines • Show All 54 Lines • ▼ Show 20 Lines | it('Pass a valid address and bip21 query string with invalid amount param (dust) to Send To field', async () => { | ||||
| screen.getByText(`Send amount must be at least 5.46 XEC`), | screen.getByText(`Send amount must be at least 5.46 XEC`), | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| // The Send button is disabled | // The Send button is disabled | ||||
| expect(screen.getByRole('button', { name: 'Send' })).toHaveStyle( | expect(screen.getByRole('button', { name: 'Send' })).toHaveStyle( | ||||
| 'cursor: not-allowed', | 'cursor: not-allowed', | ||||
| ); | ); | ||||
| // The "Send to Many" switch is disabled | // The "Send to many" button is disabled (expand Advanced first to see it) | ||||
| expect(screen.getByTitle('Toggle Multisend')).toHaveProperty( | await user.click(screen.getByRole('button', { name: /Advanced/i })); | ||||
| 'disabled', | expect( | ||||
| true, | screen.getByRole('button', { name: 'Send to many' }), | ||||
| ); | ).toHaveProperty('disabled', true); | ||||
| }); | }); | ||||
| it('Valid address with valid bip21 query string with valid amount param rejected if amount exceeds wallet balance', async () => { | it('Valid address with valid bip21 query string with valid amount param rejected if amount exceeds wallet balance', async () => { | ||||
| // Mock the app with context at the Send screen | // Mock the app with context at the Send screen | ||||
| const mockedChronik = await initializeCashtabStateForTests( | const mockedChronik = await initializeCashtabStateForTests( | ||||
| walletWithXecAndTokensActive, | walletWithXecAndTokensActive, | ||||
| localforage, | localforage, | ||||
| ); | ); | ||||
| render( | render( | ||||
| Show All 36 Lines | it('Valid address with valid bip21 query string with valid amount param rejected if amount exceeds wallet balance', async () => { | ||||
| ), | ), | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| // The Send button is disabled | // The Send button is disabled | ||||
| expect(screen.getByRole('button', { name: 'Send' })).toHaveStyle( | expect(screen.getByRole('button', { name: 'Send' })).toHaveStyle( | ||||
| 'cursor: not-allowed', | 'cursor: not-allowed', | ||||
| ); | ); | ||||
| // The "Send to Many" switch is disabled | // The "Send to many" button is disabled (expand Advanced first to see it) | ||||
| expect(screen.getByTitle('Toggle Multisend')).toHaveProperty( | await user.click(screen.getByRole('button', { name: /Advanced/i })); | ||||
| 'disabled', | expect( | ||||
| true, | screen.getByRole('button', { name: 'Send to many' }), | ||||
| ); | ).toHaveProperty('disabled', true); | ||||
| }); | }); | ||||
| it('Pass a valid address and an invalid bip21 query string', async () => { | it('Pass a valid address and an invalid bip21 query string', async () => { | ||||
| // Mock the app with context at the Send screen | // Mock the app with context at the Send screen | ||||
| const mockedChronik = await initializeCashtabStateForTests( | const mockedChronik = await initializeCashtabStateForTests( | ||||
| walletWithXecAndTokensActive, | walletWithXecAndTokensActive, | ||||
| localforage, | localforage, | ||||
| ); | ); | ||||
| render( | render( | ||||
| Show All 18 Lines | it('Pass a valid address and an invalid bip21 query string', async () => { | ||||
| // The user enters a badly formed query string | // The user enters a badly formed query string | ||||
| const addressInput = | const addressInput = | ||||
| 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?notaparam=500'; | 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?notaparam=500'; | ||||
| await user.type(addressInputEl, addressInput); | await user.type(addressInputEl, addressInput); | ||||
| // The Send To input value matches user input | // The Send To input value matches user input | ||||
| expect(addressInputEl).toHaveValue(addressInput); | expect(addressInputEl).toHaveValue(addressInput); | ||||
| // The "Send to Many" switch is disabled | // The "Send to many" button is disabled (expand Advanced first to see it) | ||||
| expect(screen.getByTitle('Toggle Multisend')).toHaveProperty( | await user.click(screen.getByRole('button', { name: /Advanced/i })); | ||||
| 'disabled', | expect( | ||||
| true, | screen.getByRole('button', { name: 'Send to many' }), | ||||
| ); | ).toHaveProperty('disabled', true); | ||||
| // Amount input unchanged | // Amount input unchanged | ||||
| expect(amountInputEl).toHaveValue(null); | expect(amountInputEl).toHaveValue(null); | ||||
| // The amount input is not disabled because no amount param is specified | // The amount input is not disabled because no amount param is specified | ||||
| expect(amountInputEl).toHaveProperty('disabled', false); | expect(amountInputEl).toHaveProperty('disabled', false); | ||||
| // We get expected addr validation error | // We get expected addr validation error | ||||
| expect( | expect( | ||||
| screen.getByText(`Unsupported param "notaparam"`), | screen.getByText(`Unsupported param "notaparam"`), | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| // The Send button is disabled | // The Send button is disabled | ||||
| expect(screen.getByRole('button', { name: 'Send' })).toHaveStyle( | expect(screen.getByRole('button', { name: 'Send' })).toHaveStyle( | ||||
| 'cursor: not-allowed', | 'cursor: not-allowed', | ||||
| ); | ); | ||||
| // The Cashtab Msg switch is disabled because we have a querystring address input | // The Message button is disabled because we have a querystring address input (Advanced already expanded above) | ||||
| expect(screen.getByTitle('Toggle Cashtab Msg')).toHaveProperty( | expect(screen.getByRole('button', { name: 'Message' })).toHaveProperty( | ||||
| 'disabled', | 'disabled', | ||||
| true, | true, | ||||
| ); | ); | ||||
| }); | }); | ||||
| it('Pass a valid address and bip21 query string with op_return_raw param to Send To field', async () => { | it('Pass a valid address and bip21 query string with op_return_raw param to Send To field', async () => { | ||||
| // Mock the app with context at the Send screen | // Mock the app with context at the Send screen | ||||
| const mockedChronik = await initializeCashtabStateForTests( | const mockedChronik = await initializeCashtabStateForTests( | ||||
| walletWithXecAndTokensActive, | walletWithXecAndTokensActive, | ||||
| Show All 21 Lines | it('Pass a valid address and bip21 query string with op_return_raw param to Send To field', async () => { | ||||
| // The user enters a valid BIP21 query string with a valid amount param | // The user enters a valid BIP21 query string with a valid amount param | ||||
| const op_return_raw = '0401020304'; | const op_return_raw = '0401020304'; | ||||
| const addressInput = `ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?op_return_raw=${op_return_raw}`; | const addressInput = `ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?op_return_raw=${op_return_raw}`; | ||||
| 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 | ||||
| expect(addressInputEl).toHaveValue(addressInput); | expect(addressInputEl).toHaveValue(addressInput); | ||||
| // The "Send to Many" switch is disabled | // The "Send to many" button is disabled (expand Advanced first to see it) | ||||
| expect(screen.getByTitle('Toggle Multisend')).toHaveProperty( | await user.click(screen.getByRole('button', { name: /Advanced/i })); | ||||
| 'disabled', | expect( | ||||
| true, | screen.getByRole('button', { name: 'Send to many' }), | ||||
| ); | ).toHaveProperty('disabled', true); | ||||
| // Amount input is untouched | // Amount input is untouched | ||||
| expect(amountInputEl).toHaveValue(null); | expect(amountInputEl).toHaveValue(null); | ||||
| // The amount input is not disabled because it is set by a bip21 query string | // The amount input is not disabled because it is set by a bip21 query string | ||||
| expect(amountInputEl).toHaveProperty('disabled', false); | expect(amountInputEl).toHaveProperty('disabled', false); | ||||
| // No addr validation errors on load | // No addr validation errors on load | ||||
| for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) { | for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) { | ||||
| expect(screen.queryByText(addrErr)).not.toBeInTheDocument(); | expect(screen.queryByText(addrErr)).not.toBeInTheDocument(); | ||||
| } | } | ||||
| // No amount validation errors on load | // No amount validation errors on load | ||||
| for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) { | for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) { | ||||
| expect(screen.queryByText(amountErr)).not.toBeInTheDocument(); | expect(screen.queryByText(amountErr)).not.toBeInTheDocument(); | ||||
| } | } | ||||
| const opReturnRawInput = screen.getByPlaceholderText( | const opReturnRawInputs = screen.getAllByPlaceholderText( | ||||
| `(Advanced) Enter raw hex to be included with this transaction's OP_RETURN`, | `(Advanced) Enter raw hex to be included with this transaction's OP_RETURN`, | ||||
| ); | ); | ||||
| const opReturnRawInput = opReturnRawInputs[0]; | |||||
| // The op_return_raw input is populated with this op_return_raw | // The op_return_raw input is populated with this op_return_raw | ||||
| expect(opReturnRawInput).toHaveValue(op_return_raw); | expect(opReturnRawInput).toHaveValue(op_return_raw); | ||||
| // The op_return_raw input is disabled | // The op_return_raw input is disabled | ||||
| expect(opReturnRawInput).toHaveProperty('disabled', true); | expect(opReturnRawInput).toHaveProperty('disabled', true); | ||||
| // The Send button is disabled because amount is not entered | // The Send button is disabled because amount is not entered | ||||
| expect(screen.getByRole('button', { name: 'Send' })).toHaveStyle( | expect(screen.getByRole('button', { name: 'Send' })).toHaveStyle( | ||||
| 'cursor: not-allowed', | 'cursor: not-allowed', | ||||
| ); | ); | ||||
| // The Cashtab Msg switch is disabled because op_return_raw is set | // The Message button is disabled because op_return_raw is set (Advanced already expanded above) | ||||
| expect(screen.getByTitle('Toggle Cashtab Msg')).toHaveProperty( | expect(screen.getByRole('button', { name: 'Message' })).toHaveProperty( | ||||
| 'disabled', | 'disabled', | ||||
| true, | true, | ||||
| ); | ); | ||||
| }); | }); | ||||
| it('Parses input_data_raw correctly (lokad + bet range) for XEC send', async () => { | it('Parses input_data_raw correctly (lokad + bet range) for XEC send', async () => { | ||||
| const mockedChronik = await initializeCashtabStateForTests( | const mockedChronik = await initializeCashtabStateForTests( | ||||
| walletWithXecAndTokensActive, | walletWithXecAndTokensActive, | ||||
| localforage, | localforage, | ||||
| ▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | it('Pass a valid address and bip21 query string with valid amount and op_return_raw params to Send To field', async () => { | ||||
| // The user enters a valid BIP21 query string with a valid amount param | // The user enters a valid BIP21 query string with a valid amount param | ||||
| const op_return_raw = '0401020304'; | const op_return_raw = '0401020304'; | ||||
| const addressInput = `ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?amount=500&op_return_raw=${op_return_raw}`; | const addressInput = `ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?amount=500&op_return_raw=${op_return_raw}`; | ||||
| 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 | ||||
| expect(addressInputEl).toHaveValue(addressInput); | expect(addressInputEl).toHaveValue(addressInput); | ||||
| // The "Send to Many" switch is disabled | // The "Send to many" button is disabled (expand Advanced first to see it) | ||||
| expect(screen.getByTitle('Toggle Multisend')).toHaveProperty( | await user.click(screen.getByRole('button', { name: /Advanced/i })); | ||||
| 'disabled', | expect( | ||||
| true, | screen.getByRole('button', { name: 'Send to many' }), | ||||
| ); | ).toHaveProperty('disabled', true); | ||||
| // Amount input is the valid amount param value | // Amount input is the valid amount param value | ||||
| expect(amountInputEl).toHaveValue(500); | expect(amountInputEl).toHaveValue(500); | ||||
| // The amount input is disabled because it is set by a bip21 query string | // The amount input is disabled because it is set by a bip21 query string | ||||
| expect(amountInputEl).toHaveProperty('disabled', true); | expect(amountInputEl).toHaveProperty('disabled', true); | ||||
| // No addr validation errors on load | // No addr validation errors on load | ||||
| for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) { | for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) { | ||||
| expect(screen.queryByText(addrErr)).not.toBeInTheDocument(); | expect(screen.queryByText(addrErr)).not.toBeInTheDocument(); | ||||
| } | } | ||||
| // No amount validation errors on load | // No amount validation errors on load | ||||
| for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) { | for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) { | ||||
| expect(screen.queryByText(amountErr)).not.toBeInTheDocument(); | expect(screen.queryByText(amountErr)).not.toBeInTheDocument(); | ||||
| } | } | ||||
| const opReturnRawInput = screen.getByPlaceholderText( | const opReturnRawInputs = screen.getAllByPlaceholderText( | ||||
| `(Advanced) Enter raw hex to be included with this transaction's OP_RETURN`, | `(Advanced) Enter raw hex to be included with this transaction's OP_RETURN`, | ||||
| ); | ); | ||||
| const opReturnRawInput = opReturnRawInputs[0]; | |||||
| // The op_return_raw input is populated with this op_return_raw | // The op_return_raw input is populated with this op_return_raw | ||||
| expect(opReturnRawInput).toHaveValue(op_return_raw); | expect(opReturnRawInput).toHaveValue(op_return_raw); | ||||
| // The op_return_raw input is disabled | // The op_return_raw input is disabled | ||||
| expect(opReturnRawInput).toHaveProperty('disabled', true); | expect(opReturnRawInput).toHaveProperty('disabled', true); | ||||
| // The Send button is enabled as we have valid address and amount params | // The Send button is enabled as we have valid address and amount params | ||||
| expect(screen.getByRole('button', { name: 'Send' })).not.toHaveStyle( | expect(screen.getByRole('button', { name: 'Send' })).not.toHaveStyle( | ||||
| 'cursor: not-allowed', | 'cursor: not-allowed', | ||||
| ); | ); | ||||
| // The Cashtab Msg switch is disabled because op_return_raw is set | // The Message button is disabled because op_return_raw is set (Advanced already expanded above) | ||||
| expect(screen.getByTitle('Toggle Cashtab Msg')).toHaveProperty( | expect(screen.getByRole('button', { name: 'Message' })).toHaveProperty( | ||||
| 'disabled', | 'disabled', | ||||
| true, | true, | ||||
| ); | ); | ||||
| }); | }); | ||||
| it('Pass a valid address and bip21 query string with valid amount and invalid op_return_raw params to Send To field', async () => { | it('Pass a valid address and bip21 query string with valid amount and invalid op_return_raw params to Send To field', async () => { | ||||
| // Mock the app with context at the Send screen | // Mock the app with context at the Send screen | ||||
| const mockedChronik = await initializeCashtabStateForTests( | const mockedChronik = await initializeCashtabStateForTests( | ||||
| walletWithXecAndTokensActive, | walletWithXecAndTokensActive, | ||||
| Show All 21 Lines | it('Pass a valid address and bip21 query string with valid amount and invalid op_return_raw params to Send To field', async () => { | ||||
| // The user enters a valid BIP21 query string with a valid amount param and invalid op_return_raw | // The user enters a valid BIP21 query string with a valid amount param and invalid op_return_raw | ||||
| const op_return_raw = 'notahexstring'; | const op_return_raw = 'notahexstring'; | ||||
| const addressInput = `ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?amount=500&op_return_raw=${op_return_raw}`; | const addressInput = `ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6?amount=500&op_return_raw=${op_return_raw}`; | ||||
| 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 | ||||
| expect(addressInputEl).toHaveValue(addressInput); | expect(addressInputEl).toHaveValue(addressInput); | ||||
| // The "Send to Many" switch is disabled | // The "Send to many" button is disabled (expand Advanced first to see it) | ||||
| expect(screen.getByTitle('Toggle Multisend')).toHaveProperty( | await user.click(screen.getByRole('button', { name: /Advanced/i })); | ||||
| 'disabled', | expect( | ||||
| true, | screen.getByRole('button', { name: 'Send to many' }), | ||||
| ); | ).toHaveProperty('disabled', true); | ||||
| // Amount input is the valid amount param value | // Amount input is the valid amount param value | ||||
| expect(amountInputEl).toHaveValue(500); | expect(amountInputEl).toHaveValue(500); | ||||
| // The amount input is disabled because it is set by a bip21 query string | // The amount input is disabled because it is set by a bip21 query string | ||||
| expect(amountInputEl).toHaveProperty('disabled', true); | expect(amountInputEl).toHaveProperty('disabled', true); | ||||
| const opReturnRawInput = screen.getByPlaceholderText( | const opReturnRawInputs = screen.getAllByPlaceholderText( | ||||
| `(Advanced) Enter raw hex to be included with this transaction's OP_RETURN`, | `(Advanced) Enter raw hex to be included with this transaction's OP_RETURN`, | ||||
| ); | ); | ||||
| const opReturnRawInput = opReturnRawInputs[0]; | |||||
| // The op_return_raw input is populated with this op_return_raw | // The op_return_raw input is populated with this op_return_raw | ||||
| expect(opReturnRawInput).toHaveValue(op_return_raw); | expect(opReturnRawInput).toHaveValue(op_return_raw); | ||||
| // The op_return_raw input is disabled | // The op_return_raw input is disabled | ||||
| expect(opReturnRawInput).toHaveProperty('disabled', true); | expect(opReturnRawInput).toHaveProperty('disabled', true); | ||||
| // We get expected addr validation error | // We get expected addr validation error | ||||
| expect( | expect( | ||||
| screen.getByText('Input must be lowercase hex a-f 0-9.'), | screen.getAllByText('Input must be lowercase hex a-f 0-9.')[0], | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| // The Send button is disabled as we have valid address and amount params | // The Send button is disabled as we have valid address and amount params | ||||
| expect(screen.getByRole('button', { name: 'Send' })).toHaveStyle( | expect(screen.getByRole('button', { name: 'Send' })).toHaveStyle( | ||||
| 'cursor: not-allowed', | 'cursor: not-allowed', | ||||
| ); | ); | ||||
| // The Cashtab Msg switch is disabled because op_return_raw is set | // The Message button is disabled because op_return_raw is set (Advanced already expanded above) | ||||
| expect(screen.getByTitle('Toggle Cashtab Msg')).toHaveProperty( | expect(screen.getByRole('button', { name: 'Message' })).toHaveProperty( | ||||
| 'disabled', | 'disabled', | ||||
| true, | true, | ||||
| ); | ); | ||||
| }); | }); | ||||
| it('Clicking "Send" will send a valid tx with op_return_raw after entry of a valid address and bip21 query string with valid amount and op_return_raw params to Send To field', async () => { | it('Clicking "Send" will send a valid tx with op_return_raw after entry of a valid address and bip21 query string with valid amount and op_return_raw params to Send To field', async () => { | ||||
| // Mock the app with context at the Send screen | // Mock the app with context at the Send screen | ||||
| const mockedChronik = await initializeCashtabStateForTests( | const mockedChronik = await initializeCashtabStateForTests( | ||||
| walletWithXecAndTokensActive, | walletWithXecAndTokensActive, | ||||
| ▲ Show 20 Lines • Show All 42 Lines • ▼ Show 20 Lines | it('Clicking "Send" will send a valid tx with op_return_raw after entry of a valid address and bip21 query string with valid amount and op_return_raw params to Send To field', async () => { | ||||
| 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 | ||||
| expect(addressInputEl).toHaveValue(addressInput); | expect(addressInputEl).toHaveValue(addressInput); | ||||
| // The 'Send To' input field is not disabled | // The 'Send To' input field is not disabled | ||||
| expect(addressInputEl).toHaveProperty('disabled', false); | expect(addressInputEl).toHaveProperty('disabled', false); | ||||
| // The "Send to Many" switch is disabled | // The "Send to many" button is disabled (expand Advanced first to see it) | ||||
| expect(screen.getByTitle('Toggle Multisend')).toHaveProperty( | await user.click(screen.getByRole('button', { name: /Advanced/i })); | ||||
| 'disabled', | expect( | ||||
| true, | screen.getByRole('button', { name: 'Send to many' }), | ||||
| ); | ).toHaveProperty('disabled', true); | ||||
| // Amount input is the valid amount param value | // Amount input is the valid amount param value | ||||
| expect(amountInputEl).toHaveValue(17); | expect(amountInputEl).toHaveValue(17); | ||||
| // The amount input is disabled because it is set by a bip21 query string | // The amount input is disabled because it is set by a bip21 query string | ||||
| expect(amountInputEl).toHaveProperty('disabled', true); | expect(amountInputEl).toHaveProperty('disabled', true); | ||||
| const opReturnRawInput = screen.getByPlaceholderText( | const opReturnRawInputs = screen.getAllByPlaceholderText( | ||||
| `(Advanced) Enter raw hex to be included with this transaction's OP_RETURN`, | `(Advanced) Enter raw hex to be included with this transaction's OP_RETURN`, | ||||
| ); | ); | ||||
| const opReturnRawInput = opReturnRawInputs[0]; | |||||
| // The op_return_raw input is populated with this op_return_raw | // The op_return_raw input is populated with this op_return_raw | ||||
| expect(opReturnRawInput).toHaveValue(op_return_raw); | expect(opReturnRawInput).toHaveValue(op_return_raw); | ||||
| // The op_return_raw input is disabled | // The op_return_raw input is disabled | ||||
| expect(opReturnRawInput).toHaveProperty('disabled', true); | expect(opReturnRawInput).toHaveProperty('disabled', true); | ||||
| // We see expected data in the op_return_raw preview | // We see expected data in the op_return_raw preview | ||||
| expect( | expect( | ||||
| screen.getByText('cashtab message with op_return_raw'), | screen.getAllByText('cashtab message with op_return_raw')[0], | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| // No addr validation errors on load | // No addr validation errors on load | ||||
| for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) { | for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) { | ||||
| expect(screen.queryByText(addrErr)).not.toBeInTheDocument(); | expect(screen.queryByText(addrErr)).not.toBeInTheDocument(); | ||||
| } | } | ||||
| // No amount validation errors on load | // No amount validation errors on load | ||||
| for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) { | for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) { | ||||
| expect(screen.queryByText(amountErr)).not.toBeInTheDocument(); | expect(screen.queryByText(amountErr)).not.toBeInTheDocument(); | ||||
| } | } | ||||
| // The Send button is enabled as we have valid address and amount params | // The Send button is enabled as we have valid address and amount params | ||||
| expect(screen.getByRole('button', { name: 'Send' })).not.toHaveStyle( | expect(screen.getByRole('button', { name: 'Send' })).not.toHaveStyle( | ||||
| 'cursor: not-allowed', | 'cursor: not-allowed', | ||||
| ); | ); | ||||
| // The Cashtab Msg switch is disabled because op_return_raw is set | |||||
| expect(screen.getByTitle('Toggle Cashtab Msg')).toHaveProperty( | |||||
| 'disabled', | |||||
| true, | |||||
| ); | |||||
| // Click Send | // Click Send | ||||
| await user.click( | await user.click( | ||||
| screen.getByRole('button', { name: 'Send' }), | screen.getByRole('button', { name: 'Send' }), | ||||
| addressInput, | addressInput, | ||||
| ); | ); | ||||
| // Notification is rendered with expected txid | // Notification is rendered with expected txid | ||||
| const txSuccessNotification = await screen.findByText('eCash sent'); | const txSuccessNotification = await screen.findByText('eCash sent'); | ||||
| Show All 15 Lines | it('Clicking "Send" will send a valid tx with op_return_raw after entry of a valid address and bip21 query string with valid amount and op_return_raw params to Send To field', async () => { | ||||
| // The amount input is no longer disabled | // The amount input is no longer disabled | ||||
| expect(amountInputEl).toHaveProperty('disabled', false), | expect(amountInputEl).toHaveProperty('disabled', false), | ||||
| ); | ); | ||||
| await waitFor(() => | await waitFor(() => | ||||
| // Amount input is reset | // Amount input is reset | ||||
| expect(amountInputEl).toHaveValue(null), | expect(amountInputEl).toHaveValue(null), | ||||
| ); | ); | ||||
| // The "Send to Many" switch is not disabled | |||||
| expect(screen.getByTitle('Toggle Multisend')).toHaveProperty( | |||||
| 'disabled', | |||||
| false, | |||||
| ); | |||||
| // The 'Send To' input field has been cleared | // The 'Send To' input field has been cleared | ||||
| expect(addressInputEl).toHaveValue(''); | expect(addressInputEl).toHaveValue(''); | ||||
| // The Cashtab Msg switch is not disabled because op_return_raw is not set | |||||
| expect(screen.getByTitle('Toggle Cashtab Msg')).toHaveProperty( | |||||
| 'disabled', | |||||
| false, | |||||
| ); | |||||
| }); | }); | ||||
| it('We can calculate max send amount with and without a cashtab msg, and send a max sat tx with a cashtab msg', async () => { | it('We can calculate max send amount with and without a cashtab msg, and send a max sat tx with a cashtab msg', async () => { | ||||
| // Mock the app with context at the Send screen | // Mock the app with context at the Send screen | ||||
| const mockedChronik = await initializeCashtabStateForTests( | const mockedChronik = await initializeCashtabStateForTests( | ||||
| walletWithXecAndTokensActive, | walletWithXecAndTokensActive, | ||||
| localforage, | localforage, | ||||
| ); | ); | ||||
| Show All 37 Lines | it('We can calculate max send amount with and without a cashtab msg, and send a max sat tx with a cashtab msg', async () => { | ||||
| await user.type(addressInputEl, addressInput); | await user.type(addressInputEl, addressInput); | ||||
| // We click "max" to populate the Amount field | // We click "max" to populate the Amount field | ||||
| await user.click(screen.getByText('max')); | await user.click(screen.getByText('max')); | ||||
| // Amount input is the expected max send for Cashtab's fee and no other outputs | // Amount input is the expected max send for Cashtab's fee and no other outputs | ||||
| expect(amountInputEl).toHaveValue(9509.4); | expect(amountInputEl).toHaveValue(9509.4); | ||||
| // Let's add a Cashtab message | // Let's add a Cashtab message (expand Advanced first, then click Message) | ||||
| await user.click(screen.getByTitle('Toggle Cashtab Msg')); | await user.click(screen.getByRole('button', { name: /Advanced/i })); | ||||
| await user.click(screen.getByRole('button', { name: 'Message' })); | |||||
| // Confirm that even a msg of blank spaces is added | // Confirm that even a msg of blank spaces is added | ||||
| await user.type( | await user.type( | ||||
| screen.getByPlaceholderText( | screen.getByPlaceholderText( | ||||
| 'Include a public Cashtab msg with this tx (max 215 bytes)', | 'Include a public Cashtab msg with this tx (max 215 bytes)', | ||||
| ), | ), | ||||
| ` `, | ` `, | ||||
| ); | ); | ||||
| ▲ Show 20 Lines • Show All 148 Lines • ▼ Show 20 Lines | it('We can send an XEC tx to multiple users', async () => { | ||||
| // Wait for the app to load | // Wait for the app to load | ||||
| await waitFor(() => | await waitFor(() => | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Cashtab Loading'), | screen.queryByTitle('Cashtab Loading'), | ||||
| ).not.toBeInTheDocument(), | ).not.toBeInTheDocument(), | ||||
| ); | ); | ||||
| // Select multi-send mode | // Select multi-send mode (expand Advanced first, then click Send to many) | ||||
| await user.click(await screen.findByTitle('Toggle Multisend')); | await user.click(screen.getByRole('button', { name: /Advanced/i })); | ||||
| await user.click(screen.getByRole('button', { name: 'Send to many' })); | |||||
| const multiSendInputEl = screen.getByPlaceholderText( | const multiSendInputEl = screen.getByPlaceholderText( | ||||
| /One address & amount per line/, | /One address & amount per line/, | ||||
| ); | ); | ||||
| // The user enters a send to many input | // The user enters a send to many input | ||||
| const multiSendInput = | const multiSendInput = | ||||
| 'ecash:qz2708636snqhsxu8wnlka78h6fdp77ar59jrf5035, 20\necash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6, 22'; | 'ecash:qz2708636snqhsxu8wnlka78h6fdp77ar59jrf5035, 20\necash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6, 22'; | ||||
| await user.type(multiSendInputEl, multiSendInput); | await user.type(multiSendInputEl, multiSendInput); | ||||
| Show All 13 Lines | it('We can send an XEC tx to multiple users', async () => { | ||||
| const txSuccessNotification = await screen.findByText('eCash sent'); | const txSuccessNotification = await screen.findByText('eCash sent'); | ||||
| await waitFor(() => | await waitFor(() => | ||||
| expect(txSuccessNotification).toHaveAttribute( | expect(txSuccessNotification).toHaveAttribute( | ||||
| 'href', | 'href', | ||||
| `${explorer.blockExplorerUrl}/tx/${txid}`, | `${explorer.blockExplorerUrl}/tx/${txid}`, | ||||
| ), | ), | ||||
| ); | ); | ||||
| }); | }); | ||||
| it('Closing Advanced hides send-to-many controls and op_return_raw preview and clears advanced fields', async () => { | |||||
| const mockedChronik = await initializeCashtabStateForTests( | |||||
| walletWithXecAndTokensActive, | |||||
| localforage, | |||||
| ); | |||||
| render( | |||||
| <CashtabTestWrapper | |||||
| chronik={mockedChronik} | |||||
| ecc={ecc} | |||||
| route="/send" | |||||
| />, | |||||
| ); | |||||
| await waitFor(() => | |||||
| expect( | |||||
| screen.queryByTitle('Cashtab Loading'), | |||||
| ).not.toBeInTheDocument(), | |||||
| ); | |||||
| await user.click(screen.getByRole('button', { name: /Advanced/i })); | |||||
| await user.click(screen.getByRole('button', { name: 'Send to many' })); | |||||
| const multiSendInputEl = screen.getByPlaceholderText( | |||||
| /One address & amount per line/, | |||||
| ); | |||||
| const multiSendInput = | |||||
| 'ecash:qz2708636snqhsxu8wnlka78h6fdp77ar59jrf5035, 20\necash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6, 22'; | |||||
| await user.type(multiSendInputEl, multiSendInput); | |||||
| expect(multiSendInputEl).toHaveValue(multiSendInput); | |||||
| await user.click(screen.getByRole('button', { name: 'op_return_raw' })); | |||||
| const opReturnRawInput = screen.getByPlaceholderText( | |||||
| /Enter raw hex to be included with this transaction's OP_RETURN/, | |||||
| ); | |||||
| const opReturnHex = '04deadbeef'; | |||||
| await user.type(opReturnRawInput, opReturnHex); | |||||
| expect(opReturnRawInput).toHaveValue(opReturnHex); | |||||
| expect(screen.getByText('Parsed op_return_raw')).toBeInTheDocument(); | |||||
| expect(screen.getByText('Unknown Protocol')).toBeInTheDocument(); | |||||
| await user.click(screen.getByRole('button', { name: /Advanced/i })); | |||||
| expect( | |||||
| screen.queryByRole('button', { name: 'Send to many' }), | |||||
| ).not.toBeInTheDocument(); | |||||
| expect( | |||||
| screen.queryByRole('button', { name: 'op_return_raw' }), | |||||
| ).not.toBeInTheDocument(); | |||||
| expect( | |||||
| screen.queryByText('Parsed op_return_raw'), | |||||
| ).not.toBeInTheDocument(); | |||||
| expect( | |||||
| screen.queryByPlaceholderText( | |||||
| /Enter raw hex to be included with this transaction's OP_RETURN/, | |||||
| ), | |||||
| ).not.toBeInTheDocument(); | |||||
| expect( | |||||
| screen.getByPlaceholderText(/One address & amount per line/), | |||||
| ).toHaveValue(''); | |||||
| }); | |||||
| it('If we type a Cashtab msg, then disable the switch, we send a tx without our typed Cashtab message', async () => { | it('If we type a Cashtab msg, then disable the switch, we send a tx without our typed Cashtab message', async () => { | ||||
| // Mock the app with context at the Send screen | // Mock the app with context at the Send screen | ||||
| const mockedChronik = await initializeCashtabStateForTests( | const mockedChronik = await initializeCashtabStateForTests( | ||||
| walletWithXecAndTokensActive, | walletWithXecAndTokensActive, | ||||
| localforage, | localforage, | ||||
| ); | ); | ||||
| // Mock settings to use higher fee rate (2010) for this test | // Mock settings to use higher fee rate (2010) for this test | ||||
| Show All 36 Lines | it('If we type a Cashtab msg, then disable the switch, we send a tx without our typed Cashtab message', async () => { | ||||
| await user.type(addressInputEl, addressInput); | await user.type(addressInputEl, addressInput); | ||||
| // We click "max" to populate the Amount field | // We click "max" to populate the Amount field | ||||
| await user.click(screen.getByText('max')); | await user.click(screen.getByText('max')); | ||||
| // Amount input is the expected max send for Cashtab's fee and no other outputs | // Amount input is the expected max send for Cashtab's fee and no other outputs | ||||
| expect(amountInputEl).toHaveValue(9509.4); | expect(amountInputEl).toHaveValue(9509.4); | ||||
| // Let's add a Cashtab message | // Let's add a Cashtab message (expand Advanced first, then click Message) | ||||
| await user.click(screen.getByTitle('Toggle Cashtab Msg')); | await user.click(screen.getByRole('button', { name: /Advanced/i })); | ||||
| await user.click(screen.getByRole('button', { name: 'Message' })); | |||||
| // Confirm that even a msg of blank spaces is added | // Confirm that even a msg of blank spaces is added | ||||
| await user.type( | await user.type( | ||||
| screen.getByPlaceholderText( | screen.getByPlaceholderText( | ||||
| 'Include a public Cashtab msg with this tx (max 215 bytes)', | 'Include a public Cashtab msg with this tx (max 215 bytes)', | ||||
| ), | ), | ||||
| ` `, | ` `, | ||||
| ); | ); | ||||
| Show All 19 Lines | it('If we type a Cashtab msg, then disable the switch, we send a tx without our typed Cashtab message', async () => { | ||||
| ); | ); | ||||
| // We click "max" again to recalculate the max send amount | // We click "max" again to recalculate the max send amount | ||||
| await user.click(screen.getByText('max')); | await user.click(screen.getByText('max')); | ||||
| // Amount input is now the expected max send for Cashtab's fee and a Cashtab Msg output | // Amount input is now the expected max send for Cashtab's fee and a Cashtab Msg output | ||||
| expect(amountInputEl).toHaveValue(9508.01); | expect(amountInputEl).toHaveValue(9508.01); | ||||
| // Now we turn the Cashtab Msg switch off without clearing the input field | // Now we turn the Cashtab Msg off without clearing the input field | ||||
| await user.click(screen.getByTitle('Toggle Cashtab Msg')); | await user.click(screen.getByRole('button', { name: 'Message' })); | ||||
| // Click max again to recalc max amount | // Click max again to recalc max amount | ||||
| // Note: for now, it is not expected behavior onMax to recalculate as the tx changes, onMax | // Note: for now, it is not expected behavior onMax to recalculate as the tx changes, onMax | ||||
| // is always a user input | // is always a user input | ||||
| // We click "max" to populate the Amount field | // We click "max" to populate the Amount field | ||||
| await user.click(screen.getByText('max')); | await user.click(screen.getByText('max')); | ||||
| // We are back to our max send amount for no other outputs | // We are back to our max send amount for no other outputs | ||||
| ▲ Show 20 Lines • Show All 61 Lines • ▼ Show 20 Lines | it('Entering a valid bip21 query string with multiple outputs and op_return_raw will correctly populate UI fields, and the tx can be sent', async () => { | ||||
| await user.type(addressInputEl, addressInput); | await user.type(addressInputEl, addressInput); | ||||
| // The 'Send To' input field has this bip21 query string as a value | // The 'Send To' input field has this bip21 query string as a value | ||||
| expect(addressInputEl).toHaveValue(addressInput); | expect(addressInputEl).toHaveValue(addressInput); | ||||
| // The 'Send To' input field is not disabled | // The 'Send To' input field is not disabled | ||||
| expect(addressInputEl).toHaveProperty('disabled', false); | expect(addressInputEl).toHaveProperty('disabled', false); | ||||
| // The "Send to Many" switch is disabled | // The "Send to many" button is disabled (expand Advanced first to see it) | ||||
| expect(screen.getByTitle('Toggle Multisend')).toHaveProperty( | await user.click(screen.getByRole('button', { name: /Advanced/i })); | ||||
| 'disabled', | expect( | ||||
| true, | screen.getByRole('button', { name: 'Send to many' }), | ||||
| ); | ).toHaveProperty('disabled', true); | ||||
| // Because we have multiple outputs, the amount input is not displayed | // Because we have multiple outputs, the amount input is not displayed | ||||
| expect(screen.queryByPlaceholderText('Amount')).not.toBeInTheDocument(); | expect(screen.queryByPlaceholderText('Amount')).not.toBeInTheDocument(); | ||||
| // Instead, we see a summary of total outputs and XEC to be sent | // Instead, we see a summary of total outputs and XEC to be sent | ||||
| expect( | expect( | ||||
| screen.getByText('BIP21: Sending 1,251.56 XEC to 2 outputs'), | screen.getByText('BIP21: Sending 1,251.56 XEC to 2 outputs'), | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| const opReturnRawInput = screen.getByPlaceholderText( | const opReturnRawInputs = screen.getAllByPlaceholderText( | ||||
| `(Advanced) Enter raw hex to be included with this transaction's OP_RETURN`, | `(Advanced) Enter raw hex to be included with this transaction's OP_RETURN`, | ||||
| ); | ); | ||||
| const opReturnRawInput = opReturnRawInputs[0]; | |||||
| // The op_return_raw input is populated with this op_return_raw | // The op_return_raw input is populated with this op_return_raw | ||||
| expect(opReturnRawInput).toHaveValue(op_return_raw); | expect(opReturnRawInput).toHaveValue(op_return_raw); | ||||
| // The op_return_raw input is disabled | // The op_return_raw input is disabled | ||||
| expect(opReturnRawInput).toHaveProperty('disabled', true); | expect(opReturnRawInput).toHaveProperty('disabled', true); | ||||
| // We see expected data in the op_return_raw preview | // We see expected data in the op_return_raw preview | ||||
| expect( | expect( | ||||
| screen.getByText('cashtab message with op_return_raw'), | screen.getAllByText('cashtab message with op_return_raw')[0], | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| // No addr validation errors on load | // No addr validation errors on load | ||||
| for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) { | for (const addrErr of SEND_ADDRESS_VALIDATION_ERRORS) { | ||||
| expect(screen.queryByText(addrErr)).not.toBeInTheDocument(); | expect(screen.queryByText(addrErr)).not.toBeInTheDocument(); | ||||
| } | } | ||||
| // No amount validation errors on load | // No amount validation errors on load | ||||
| for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) { | for (const amountErr of SEND_AMOUNT_VALIDATION_ERRORS) { | ||||
| expect(screen.queryByText(amountErr)).not.toBeInTheDocument(); | expect(screen.queryByText(amountErr)).not.toBeInTheDocument(); | ||||
| } | } | ||||
| // The Send button is enabled as we have valid address and amount params | // The Send button is enabled as we have valid address and amount params | ||||
| expect(screen.getByRole('button', { name: 'Send' })).not.toHaveStyle( | expect(screen.getByRole('button', { name: 'Send' })).not.toHaveStyle( | ||||
| 'cursor: not-allowed', | 'cursor: not-allowed', | ||||
| ); | ); | ||||
| // The Cashtab Msg switch is disabled because op_return_raw is set | |||||
| expect(screen.getByTitle('Toggle Cashtab Msg')).toHaveProperty( | |||||
| 'disabled', | |||||
| true, | |||||
| ); | |||||
| // We see a summary table of addresses and amounts | // We see a summary table of addresses and amounts | ||||
| expect(screen.getByText('Parsed BIP21 outputs')).toBeInTheDocument(); | expect(screen.getByText('Parsed BIP21 outputs')).toBeInTheDocument(); | ||||
| expect( | expect( | ||||
| screen.getByText('qp89xg...9nhgg6, 17.00 XEC'), | screen.getByText('qp89xg...9nhgg6, 17.00 XEC'), | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| expect( | expect( | ||||
| screen.getByText('qz2708...rf5035, 1,234.56 XEC'), | screen.getByText('qz2708...rf5035, 1,234.56 XEC'), | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| Show All 21 Lines | it('Entering a valid bip21 query string with multiple outputs and op_return_raw will correctly populate UI fields, and the tx can be sent', async () => { | ||||
| ).not.toBeInTheDocument(), | ).not.toBeInTheDocument(), | ||||
| ); | ); | ||||
| // The amount input is no longer hidden | // The amount input is no longer hidden | ||||
| expect( | expect( | ||||
| await screen.findByPlaceholderText('Amount'), | await screen.findByPlaceholderText('Amount'), | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| // Amount input is reset | // Amount input is reset | ||||
| expect(await screen.findByPlaceholderText('Amount')).toHaveValue(null); | expect(await screen.findByPlaceholderText('Amount')).toHaveValue(null); | ||||
| // The "Send to Many" switch is not disabled | |||||
| expect(screen.getByTitle('Toggle Multisend')).toHaveProperty( | |||||
| 'disabled', | |||||
| false, | |||||
| ); | |||||
| // The 'Send To' input field has been cleared | // The 'Send To' input field has been cleared | ||||
| expect(addressInputEl).toHaveValue(''); | expect(addressInputEl).toHaveValue(''); | ||||
| // The Cashtab Msg switch is not disabled because op_return_raw is not set | |||||
| expect(screen.getByTitle('Toggle Cashtab Msg')).toHaveProperty( | |||||
| 'disabled', | |||||
| false, | |||||
| ); | |||||
| }); | }); | ||||
| it('Entering a valid bip21 query string for a token send tx does not render a populated token tx and shows a query error if Cashtab is unable to fetch the token info', async () => { | it('Entering a valid bip21 query string for a token send tx does not render a populated token tx and shows a query error if Cashtab is unable to fetch the token info', async () => { | ||||
| // Mock the app with context at the Send screen | // Mock the app with context at the Send screen | ||||
| const mockedChronik = await initializeCashtabStateForTests( | const mockedChronik = await initializeCashtabStateForTests( | ||||
| walletWithXecAndTokensActive, | walletWithXecAndTokensActive, | ||||
| localforage, | localforage, | ||||
| ); | ); | ||||
| Show All 28 Lines | it('Entering a valid bip21 query string for a token send tx does not render a populated token tx and shows a query error if Cashtab is unable to fetch the token info', async () => { | ||||
| const token_decimalized_qty = '1'; | const token_decimalized_qty = '1'; | ||||
| const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | ||||
| const addressInput = `${address}?token_id=${token_id}&token_decimalized_qty=${token_decimalized_qty}`; | const addressInput = `${address}?token_id=${token_id}&token_decimalized_qty=${token_decimalized_qty}`; | ||||
| // Use fireEvent.input to set the value, then fireEvent.change to trigger the handler | // Use fireEvent.input to set the value, then fireEvent.change to trigger the handler | ||||
| // This simulates a paste/scan where the full value is set at once | // This simulates a paste/scan where the full value is set at once | ||||
| fireEvent.input(addressInputEl, { target: { value: addressInput } }); | fireEvent.input(addressInputEl, { target: { value: addressInput } }); | ||||
| fireEvent.change(addressInputEl, { target: { value: addressInput } }); | fireEvent.change(addressInputEl, { target: { value: addressInput } }); | ||||
| // Wait for token mode to be activated (toggle should be on "Tokens") | // Wait for token mode to be activated (address input populated with bip21) | ||||
| await waitFor(() => { | const tokenAddressInputEl = | ||||
| const tokenModeSwitch = screen.getByTitle('Toggle XEC/Token Mode'); | await screen.findByPlaceholderText('Address'); | ||||
| expect(tokenModeSwitch).toHaveProperty('checked', true); | |||||
| }); | |||||
| // The "Send to Many" switch should not be visible in token mode | |||||
| expect(screen.queryByTitle('Toggle Multisend')).not.toBeInTheDocument(); | |||||
| // Get the token mode address input field | |||||
| const tokenAddressInputEl = screen.getByPlaceholderText('Address'); | |||||
| // The token mode 'Send To' input field has this bip21 query string as a value | |||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect(tokenAddressInputEl).toHaveValue(addressInput); | expect(tokenAddressInputEl).toHaveValue(addressInput); | ||||
| }); | }); | ||||
| // The "Send to many" button should not be visible in token mode (XEC form not shown) | |||||
| expect( | |||||
| screen.queryByRole('button', { name: 'Send to many' }), | |||||
| ).not.toBeInTheDocument(); | |||||
| // The token mode 'Send To' input field is not disabled | // The token mode 'Send To' input field is not disabled | ||||
| expect(tokenAddressInputEl).toHaveProperty('disabled', false); | expect(tokenAddressInputEl).toHaveProperty('disabled', false); | ||||
| // Even though we have a query error, the token amount input is visible | // Even though we have a query error, the token amount input is visible | ||||
| // because the amount came from the BIP21 string (token_decimalized_qty) | // because the amount came from the BIP21 string (token_decimalized_qty) | ||||
| let amountInputEl = screen.getByPlaceholderText('Amount'); | let amountInputEl = screen.getByPlaceholderText('Amount'); | ||||
| expect(amountInputEl).toHaveValue(token_decimalized_qty); | expect(amountInputEl).toHaveValue(token_decimalized_qty); | ||||
| // The amount input should be disabled because token_decimalized_qty is specified in the BIP21 string | // The amount input should be disabled because token_decimalized_qty is specified in the BIP21 string | ||||
| ▲ Show 20 Lines • Show All 66 Lines • ▼ Show 20 Lines | it('SLP1 Fungible: Entering a valid bip21 query string for a token send tx will correcty populate the UI, and the tx can be sent', async () => { | ||||
| const token_decimalized_qty = '1'; | const token_decimalized_qty = '1'; | ||||
| const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | ||||
| const addressInput = `${address}?token_id=${token_id}&token_decimalized_qty=${token_decimalized_qty}`; | const addressInput = `${address}?token_id=${token_id}&token_decimalized_qty=${token_decimalized_qty}`; | ||||
| // Use fireEvent.input to set the value, then fireEvent.change to trigger the handler | // Use fireEvent.input to set the value, then fireEvent.change to trigger the handler | ||||
| // This simulates a paste/scan where the full value is set at once | // This simulates a paste/scan where the full value is set at once | ||||
| fireEvent.input(addressInputEl, { target: { value: addressInput } }); | fireEvent.input(addressInputEl, { target: { value: addressInput } }); | ||||
| fireEvent.change(addressInputEl, { target: { value: addressInput } }); | fireEvent.change(addressInputEl, { target: { value: addressInput } }); | ||||
| // Wait for token mode to be activated (toggle should be on "Tokens") | // Wait for token mode to be activated (address input populated with bip21) | ||||
| await waitFor(() => { | const tokenAddressInputEl = | ||||
| const tokenModeSwitch = screen.getByTitle('Toggle XEC/Token Mode'); | await screen.findByPlaceholderText('Address'); | ||||
| expect(tokenModeSwitch).toHaveProperty('checked', true); | |||||
| }); | |||||
| // The "Send to Many" switch should not be visible in token mode | |||||
| expect(screen.queryByTitle('Toggle Multisend')).not.toBeInTheDocument(); | |||||
| // Get the token mode address input field | |||||
| const tokenAddressInputEl = screen.getByPlaceholderText('Address'); | |||||
| // The token mode 'Send To' input field has this bip21 query string as a value | |||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect(tokenAddressInputEl).toHaveValue(addressInput); | expect(tokenAddressInputEl).toHaveValue(addressInput); | ||||
| }); | }); | ||||
| // The "Send to many" button should not be visible in token mode (XEC form not shown) | |||||
| expect( | |||||
| screen.queryByRole('button', { name: 'Send to many' }), | |||||
| ).not.toBeInTheDocument(); | |||||
| // The token mode 'Send To' input field is not disabled | // The token mode 'Send To' input field is not disabled | ||||
| expect(tokenAddressInputEl).toHaveProperty('disabled', false); | expect(tokenAddressInputEl).toHaveProperty('disabled', false); | ||||
| // The token amount input is visible and populated from the BIP21 string | // The token amount input is visible and populated from the BIP21 string | ||||
| const amountInputEl = screen.getByPlaceholderText('Amount'); | const amountInputEl = screen.getByPlaceholderText('Amount'); | ||||
| expect(amountInputEl).toBeInTheDocument(); | expect(amountInputEl).toBeInTheDocument(); | ||||
| expect(amountInputEl).toHaveValue(token_decimalized_qty); | expect(amountInputEl).toHaveValue(token_decimalized_qty); | ||||
| // The amount input should be disabled because token_decimalized_qty is specified in the BIP21 string | // The amount input should be disabled because token_decimalized_qty is specified in the BIP21 string | ||||
| Show All 17 Lines | it('SLP1 Fungible: Entering a valid bip21 query string for a token send tx will correcty populate the UI, and the tx can be sent', async () => { | ||||
| // The send button is enabled as we have valid bip21 token send for a token qty supported | // The send button is enabled as we have valid bip21 token send for a token qty supported | ||||
| // by the wallet | // by the wallet | ||||
| const sendButton = screen.getByRole('button', { name: 'Send' }); | const sendButton = screen.getByRole('button', { name: 'Send' }); | ||||
| expect(sendButton).toBeEnabled(); | expect(sendButton).toBeEnabled(); | ||||
| // The Cashtab Msg and op_return_raw switches are not visible in token mode | // The Cashtab Msg and op_return_raw switches are not visible in token mode | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle Cashtab Msg'), | screen.queryByRole('button', { name: 'Message' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle op_return_raw'), | screen.queryByRole('button', { name: 'op_return_raw' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| // Click Send | // Click Send | ||||
| await user.click(sendButton); | await user.click(sendButton); | ||||
| // Notification is rendered with expected txid | // Notification is rendered with expected txid | ||||
| const txSuccessNotification = await screen.findByText('eToken sent'); | const txSuccessNotification = await screen.findByText('eToken sent'); | ||||
| await waitFor(() => | await waitFor(() => | ||||
| ▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | it('SLP1 Fungible: Entering a bip21 query string with firma param shows validation error', async () => { | ||||
| const firma = | const firma = | ||||
| '534f4c304ebabba2b443691c1a9180426004d5fd3419e9f9c64e5839b853cecdaacbf745'; | '534f4c304ebabba2b443691c1a9180426004d5fd3419e9f9c64e5839b853cecdaacbf745'; | ||||
| const addressInput = `${address}?token_id=${token_id}&token_decimalized_qty=${token_decimalized_qty}&firma=${firma}`; | const addressInput = `${address}?token_id=${token_id}&token_decimalized_qty=${token_decimalized_qty}&firma=${firma}`; | ||||
| // Use fireEvent.input to set the value, then fireEvent.change to trigger the handler | // Use fireEvent.input to set the value, then fireEvent.change to trigger the handler | ||||
| // This simulates a paste/scan where the full value is set at once | // This simulates a paste/scan where the full value is set at once | ||||
| fireEvent.input(addressInputEl, { target: { value: addressInput } }); | fireEvent.input(addressInputEl, { target: { value: addressInput } }); | ||||
| fireEvent.change(addressInputEl, { target: { value: addressInput } }); | fireEvent.change(addressInputEl, { target: { value: addressInput } }); | ||||
| // Wait for token mode to be activated (toggle should be on "Tokens") | // Wait for token mode to be activated (address input populated with bip21) | ||||
| await waitFor(() => { | const tokenAddressInputEl = | ||||
| const tokenModeSwitch = screen.getByTitle('Toggle XEC/Token Mode'); | await screen.findByPlaceholderText('Address'); | ||||
| expect(tokenModeSwitch).toHaveProperty('checked', true); | |||||
| }); | |||||
| // The "Send to Many" switch should not be visible in token mode | |||||
| expect(screen.queryByTitle('Toggle Multisend')).not.toBeInTheDocument(); | |||||
| // Get the token mode address input field | |||||
| const tokenAddressInputEl = screen.getByPlaceholderText('Address'); | |||||
| // The token mode 'Send To' input field has this bip21 query string as a value | |||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect(tokenAddressInputEl).toHaveValue(addressInput); | expect(tokenAddressInputEl).toHaveValue(addressInput); | ||||
| }); | }); | ||||
| // The "Send to many" button should not be visible in token mode (XEC form not shown) | |||||
| expect( | |||||
| screen.queryByRole('button', { name: 'Send to many' }), | |||||
| ).not.toBeInTheDocument(); | |||||
| // The token mode 'Send To' input field is not disabled (user-entered BIP21) | // The token mode 'Send To' input field is not disabled (user-entered BIP21) | ||||
| expect(tokenAddressInputEl).toHaveProperty('disabled', false); | expect(tokenAddressInputEl).toHaveProperty('disabled', false); | ||||
| // Wait for token info to be fetched and validation error to appear | // Wait for token info to be fetched and validation error to appear | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect( | expect( | ||||
| screen.getByText( | screen.getByText( | ||||
| 'Cannot include firma for a token type other than ALP_TOKEN_TYPE_STANDARD', | 'Cannot include firma for a token type other than ALP_TOKEN_TYPE_STANDARD', | ||||
| ▲ Show 20 Lines • Show All 73 Lines • ▼ Show 20 Lines | it('ALP Fungible: Entering a valid bip21 query string for a token send tx will correcty populate the UI, and the tx can be sent', async () => { | ||||
| const token_decimalized_qty = '1'; | const token_decimalized_qty = '1'; | ||||
| const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | ||||
| const addressInput = `${address}?token_id=${token_id}&token_decimalized_qty=${token_decimalized_qty}`; | const addressInput = `${address}?token_id=${token_id}&token_decimalized_qty=${token_decimalized_qty}`; | ||||
| // Use fireEvent.input to set the value, then fireEvent.change to trigger the handler | // Use fireEvent.input to set the value, then fireEvent.change to trigger the handler | ||||
| // This simulates a paste/scan where the full value is set at once | // This simulates a paste/scan where the full value is set at once | ||||
| fireEvent.input(addressInputEl, { target: { value: addressInput } }); | fireEvent.input(addressInputEl, { target: { value: addressInput } }); | ||||
| fireEvent.change(addressInputEl, { target: { value: addressInput } }); | fireEvent.change(addressInputEl, { target: { value: addressInput } }); | ||||
| // Wait for token mode to be activated (toggle should be on "Tokens") | // Wait for token mode to be activated (address input populated with bip21) | ||||
| await waitFor(() => { | const tokenAddressInputEl = | ||||
| const tokenModeSwitch = screen.getByTitle('Toggle XEC/Token Mode'); | await screen.findByPlaceholderText('Address'); | ||||
| expect(tokenModeSwitch).toHaveProperty('checked', true); | |||||
| }); | |||||
| // The "Send to Many" switch should not be visible in token mode | |||||
| expect(screen.queryByTitle('Toggle Multisend')).not.toBeInTheDocument(); | |||||
| // Get the token mode address input field | |||||
| const tokenAddressInputEl = screen.getByPlaceholderText('Address'); | |||||
| // The token mode 'Send To' input field has this bip21 query string as a value | |||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect(tokenAddressInputEl).toHaveValue(addressInput); | expect(tokenAddressInputEl).toHaveValue(addressInput); | ||||
| }); | }); | ||||
| // The "Send to many" button should not be visible in token mode (XEC form not shown) | |||||
| expect( | |||||
| screen.queryByRole('button', { name: 'Send to many' }), | |||||
| ).not.toBeInTheDocument(); | |||||
| // The token mode 'Send To' input field is not disabled | // The token mode 'Send To' input field is not disabled | ||||
| expect(tokenAddressInputEl).toHaveProperty('disabled', false); | expect(tokenAddressInputEl).toHaveProperty('disabled', false); | ||||
| // The token amount input is visible and populated from the BIP21 string | // The token amount input is visible and populated from the BIP21 string | ||||
| const amountInputEl = screen.getByPlaceholderText('Amount'); | const amountInputEl = screen.getByPlaceholderText('Amount'); | ||||
| expect(amountInputEl).toBeInTheDocument(); | expect(amountInputEl).toBeInTheDocument(); | ||||
| expect(amountInputEl).toHaveValue(token_decimalized_qty); | expect(amountInputEl).toHaveValue(token_decimalized_qty); | ||||
| // The amount input should be disabled because token_decimalized_qty is specified in the BIP21 string | // The amount input should be disabled because token_decimalized_qty is specified in the BIP21 string | ||||
| Show All 17 Lines | it('ALP Fungible: Entering a valid bip21 query string for a token send tx will correcty populate the UI, and the tx can be sent', async () => { | ||||
| // The send button is enabled as we have valid bip21 token send for a token qty supported | // The send button is enabled as we have valid bip21 token send for a token qty supported | ||||
| // by the wallet | // by the wallet | ||||
| const sendButton = screen.getByRole('button', { name: 'Send' }); | const sendButton = screen.getByRole('button', { name: 'Send' }); | ||||
| expect(sendButton).toBeEnabled(); | expect(sendButton).toBeEnabled(); | ||||
| // The Cashtab Msg and op_return_raw switches are not visible in token mode | // The Cashtab Msg and op_return_raw switches are not visible in token mode | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle Cashtab Msg'), | screen.queryByRole('button', { name: 'Message' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle op_return_raw'), | screen.queryByRole('button', { name: 'op_return_raw' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| // Click Send | // Click Send | ||||
| await user.click(sendButton); | await user.click(sendButton); | ||||
| // Notification is rendered with expected txid | // Notification is rendered with expected txid | ||||
| const txSuccessNotification = await screen.findByText('eToken sent'); | const txSuccessNotification = await screen.findByText('eToken sent'); | ||||
| await waitFor(() => | await waitFor(() => | ||||
| ▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | it('ALP Fungible: We can send an ALP token using the token mode UI', async () => { | ||||
| // Wait for the app to load | // Wait for the app to load | ||||
| await waitFor(() => | await waitFor(() => | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Cashtab Loading'), | screen.queryByTitle('Cashtab Loading'), | ||||
| ).not.toBeInTheDocument(), | ).not.toBeInTheDocument(), | ||||
| ); | ); | ||||
| // Toggle to token mode | // Switch to token mode via Send Token link | ||||
| const tokenModeSwitch = await screen.findByTitle( | const sendTokenLink = await screen.findByRole('link', { | ||||
| 'Toggle XEC/Token Mode', | name: /Send Token/i, | ||||
| ); | }); | ||||
| expect(tokenModeSwitch).toBeInTheDocument(); | expect(sendTokenLink).toBeInTheDocument(); | ||||
| await user.click(tokenModeSwitch); | await user.click(sendTokenLink); | ||||
| // Wait for token mode UI to appear | // Wait for token mode UI to appear | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect( | expect( | ||||
| screen.getByPlaceholderText( | screen.getByPlaceholderText('Search by token ticker or name'), | ||||
| 'Start typing a token ticker or name', | |||||
| ), | |||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // The "Send to Many" switch should not be visible in token mode | // The "Send to Many" switch should not be visible in token mode | ||||
| expect(screen.queryByTitle('Toggle Multisend')).not.toBeInTheDocument(); | expect( | ||||
| screen.queryByRole('button', { name: 'Send to many' }), | |||||
| ).not.toBeInTheDocument(); | |||||
| // Search for the token by ticker | // Search for the token by ticker | ||||
| const tokenSearchInput = screen.getByPlaceholderText( | const tokenSearchInput = screen.getByPlaceholderText( | ||||
| 'Start typing a token ticker or name', | 'Search by token ticker or name', | ||||
| ); | ); | ||||
| await user.type(tokenSearchInput, tokenTicker); | await user.type(tokenSearchInput, tokenTicker); | ||||
| // Wait for dropdown to appear with token | // Wait for dropdown to appear with token | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // Click on the token in the dropdown to select it | // Click on the token in the dropdown to select it | ||||
| // The token ticker text should be clickable within the dropdown item | // The token ticker text should be clickable within the dropdown item | ||||
| const tokenTickerElement = screen.getByText(tokenTicker); | const tokenTickerElement = screen.getByText(tokenTicker); | ||||
| // Click on the parent div that has the onClick handler | // Click on the parent div that has the onClick handler | ||||
| await user.click(tokenTickerElement.closest('div')); | await user.click(tokenTickerElement.closest('div')); | ||||
| // Wait for token to be selected and address/amount inputs to appear | // Wait for token to be selected and address/amount inputs to appear | ||||
| // The search input should be replaced with the selected token display | // The search input should be replaced with the selected token display | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| const addressInput = screen.queryByPlaceholderText('Address'); | const addressInput = screen.queryByPlaceholderText('Address'); | ||||
| expect(addressInput).toBeInTheDocument(); | expect(addressInput).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // Verify the selected token ticker is displayed in the selected token display | // Verify the selected token ticker is displayed in the selected token display | ||||
| // The search input should no longer be visible (replaced by SelectedTokenDisplay) | // The search input should no longer be visible (replaced by SelectedTokenDisplay) | ||||
| expect( | expect( | ||||
| screen.queryByPlaceholderText( | screen.queryByPlaceholderText('Search by token ticker or name'), | ||||
| 'Start typing a token ticker or name', | |||||
| ), | |||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| // The token ticker should be visible in the selected token display | // The token ticker should be visible in the selected token display | ||||
| expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | ||||
| // Enter address | // Enter address | ||||
| const addressInputEl = screen.getByPlaceholderText('Address'); | const addressInputEl = screen.getByPlaceholderText('Address'); | ||||
| const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | ||||
| await user.type(addressInputEl, address); | await user.type(addressInputEl, address); | ||||
| expect(addressInputEl).toHaveValue(address); | expect(addressInputEl).toHaveValue(address); | ||||
| // Enter amount | // Enter amount | ||||
| const amountInputEl = screen.getByPlaceholderText('Amount'); | const amountInputEl = screen.getByPlaceholderText('Amount'); | ||||
| const token_decimalized_qty = '1'; | const token_decimalized_qty = '1'; | ||||
| await user.type(amountInputEl, token_decimalized_qty); | await user.type(amountInputEl, token_decimalized_qty); | ||||
| expect(amountInputEl).toHaveValue(token_decimalized_qty); | expect(amountInputEl).toHaveValue(token_decimalized_qty); | ||||
| // The send button should be enabled | // The send button should be enabled | ||||
| const sendButton = screen.getByRole('button', { name: 'Send' }); | const sendButton = screen.getByRole('button', { name: 'Send' }); | ||||
| expect(sendButton).toBeEnabled(); | expect(sendButton).toBeEnabled(); | ||||
| // The Cashtab Msg and op_return_raw switches should not be visible in token mode | // The Cashtab Msg and op_return_raw switches should not be visible in token mode | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle Cashtab Msg'), | screen.queryByRole('button', { name: 'Message' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle op_return_raw'), | screen.queryByRole('button', { name: 'op_return_raw' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| // Click Send | // Click Send | ||||
| await user.click(sendButton); | await user.click(sendButton); | ||||
| // Notification is rendered with expected txid | // Notification is rendered with expected txid | ||||
| const txSuccessNotification = await screen.findByText('eToken sent'); | const txSuccessNotification = await screen.findByText('eToken sent'); | ||||
| await waitFor(() => | await waitFor(() => | ||||
| ▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | it('SLP1 Fungible: We can send an SLP token using the token mode UI', async () => { | ||||
| // Wait for the app to load | // Wait for the app to load | ||||
| await waitFor(() => | await waitFor(() => | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Cashtab Loading'), | screen.queryByTitle('Cashtab Loading'), | ||||
| ).not.toBeInTheDocument(), | ).not.toBeInTheDocument(), | ||||
| ); | ); | ||||
| // Toggle to token mode | // Switch to token mode via Send Token link | ||||
| const tokenModeSwitch = await screen.findByTitle( | const sendTokenLink = await screen.findByRole('link', { | ||||
| 'Toggle XEC/Token Mode', | name: /Send Token/i, | ||||
| ); | }); | ||||
| expect(tokenModeSwitch).toBeInTheDocument(); | expect(sendTokenLink).toBeInTheDocument(); | ||||
| await user.click(tokenModeSwitch); | await user.click(sendTokenLink); | ||||
| // Wait for token mode UI to appear | // Wait for token mode UI to appear | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect( | expect( | ||||
| screen.getByPlaceholderText( | screen.getByPlaceholderText('Search by token ticker or name'), | ||||
| 'Start typing a token ticker or name', | |||||
| ), | |||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // The "Send to Many" switch should not be visible in token mode | // The "Send to Many" switch should not be visible in token mode | ||||
| expect(screen.queryByTitle('Toggle Multisend')).not.toBeInTheDocument(); | expect( | ||||
| screen.queryByRole('button', { name: 'Send to many' }), | |||||
| ).not.toBeInTheDocument(); | |||||
| // Search for the token by ticker | // Search for the token by ticker | ||||
| const tokenSearchInput = screen.getByPlaceholderText( | const tokenSearchInput = screen.getByPlaceholderText( | ||||
| 'Start typing a token ticker or name', | 'Search by token ticker or name', | ||||
| ); | ); | ||||
| await user.type(tokenSearchInput, tokenTicker); | await user.type(tokenSearchInput, tokenTicker); | ||||
| // Wait for dropdown to appear with token | // Wait for dropdown to appear with token | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // Click on the token in the dropdown to select it | // Click on the token in the dropdown to select it | ||||
| // The token ticker text should be clickable within the dropdown item | // The token ticker text should be clickable within the dropdown item | ||||
| const tokenTickerElement = screen.getByText(tokenTicker); | const tokenTickerElement = screen.getByText(tokenTicker); | ||||
| // Click on the parent div that has the onClick handler | // Click on the parent div that has the onClick handler | ||||
| await user.click(tokenTickerElement.closest('div')); | await user.click(tokenTickerElement.closest('div')); | ||||
| // Wait for token to be selected and address/amount inputs to appear | // Wait for token to be selected and address/amount inputs to appear | ||||
| // The search input should be replaced with the selected token display | // The search input should be replaced with the selected token display | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| const addressInput = screen.queryByPlaceholderText('Address'); | const addressInput = screen.queryByPlaceholderText('Address'); | ||||
| expect(addressInput).toBeInTheDocument(); | expect(addressInput).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // Verify the selected token ticker is displayed in the selected token display | // Verify the selected token ticker is displayed in the selected token display | ||||
| // The search input should no longer be visible (replaced by SelectedTokenDisplay) | // The search input should no longer be visible (replaced by SelectedTokenDisplay) | ||||
| expect( | expect( | ||||
| screen.queryByPlaceholderText( | screen.queryByPlaceholderText('Search by token ticker or name'), | ||||
| 'Start typing a token ticker or name', | |||||
| ), | |||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| // The token ticker should be visible in the selected token display | // The token ticker should be visible in the selected token display | ||||
| expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | ||||
| // Enter address | // Enter address | ||||
| const addressInputEl = screen.getByPlaceholderText('Address'); | const addressInputEl = screen.getByPlaceholderText('Address'); | ||||
| const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | ||||
| await user.type(addressInputEl, address); | await user.type(addressInputEl, address); | ||||
| expect(addressInputEl).toHaveValue(address); | expect(addressInputEl).toHaveValue(address); | ||||
| // Enter amount | // Enter amount | ||||
| const amountInputEl = screen.getByPlaceholderText('Amount'); | const amountInputEl = screen.getByPlaceholderText('Amount'); | ||||
| const token_decimalized_qty = '1'; | const token_decimalized_qty = '1'; | ||||
| await user.type(amountInputEl, token_decimalized_qty); | await user.type(amountInputEl, token_decimalized_qty); | ||||
| expect(amountInputEl).toHaveValue(token_decimalized_qty); | expect(amountInputEl).toHaveValue(token_decimalized_qty); | ||||
| // The send button should be enabled | // The send button should be enabled | ||||
| const sendButton = screen.getByRole('button', { name: 'Send' }); | const sendButton = screen.getByRole('button', { name: 'Send' }); | ||||
| expect(sendButton).toBeEnabled(); | expect(sendButton).toBeEnabled(); | ||||
| // The Cashtab Msg and op_return_raw switches should not be visible in token mode | // The Cashtab Msg and op_return_raw switches should not be visible in token mode | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle Cashtab Msg'), | screen.queryByRole('button', { name: 'Message' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle op_return_raw'), | screen.queryByRole('button', { name: 'op_return_raw' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| // EMPP switches should not be visible for SLP tokens | // EMPP buttons should not be visible for SLP tokens (only ALP tokens have them) | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle Cashtab Msg Token'), | screen.queryByRole('button', { name: 'Cashtab Msg' }), | ||||
| ).not.toBeInTheDocument(); | |||||
| expect( | |||||
| screen.queryByRole('button', { name: 'empp_raw' }), | |||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| expect(screen.queryByTitle('Toggle empp_raw')).not.toBeInTheDocument(); | |||||
| // Click Send | // Click Send | ||||
| await user.click(sendButton); | await user.click(sendButton); | ||||
| // Notification is rendered with expected txid | // Notification is rendered with expected txid | ||||
| const txSuccessNotification = await screen.findByText('eToken sent'); | const txSuccessNotification = await screen.findByText('eToken sent'); | ||||
| await waitFor(() => | await waitFor(() => | ||||
| expect(txSuccessNotification).toHaveAttribute( | expect(txSuccessNotification).toHaveAttribute( | ||||
| ▲ Show 20 Lines • Show All 83 Lines • ▼ Show 20 Lines | it('SLP1 NFT Parent: We can send an SLP NFT parent token using the token mode UI', async () => { | ||||
| // Wait for the app to load | // Wait for the app to load | ||||
| await waitFor(() => | await waitFor(() => | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Cashtab Loading'), | screen.queryByTitle('Cashtab Loading'), | ||||
| ).not.toBeInTheDocument(), | ).not.toBeInTheDocument(), | ||||
| ); | ); | ||||
| // Toggle to token mode | // Switch to token mode via Send Token link | ||||
| const tokenModeSwitch = await screen.findByTitle( | const sendTokenLink = await screen.findByRole('link', { | ||||
| 'Toggle XEC/Token Mode', | name: /Send Token/i, | ||||
| ); | }); | ||||
| expect(tokenModeSwitch).toBeInTheDocument(); | expect(sendTokenLink).toBeInTheDocument(); | ||||
| await user.click(tokenModeSwitch); | await user.click(sendTokenLink); | ||||
| // Wait for token mode UI to appear | // Wait for token mode UI to appear | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect( | expect( | ||||
| screen.getByPlaceholderText( | screen.getByPlaceholderText('Search by token ticker or name'), | ||||
| 'Start typing a token ticker or name', | |||||
| ), | |||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // The "Send to Many" switch should not be visible in token mode | // The "Send to Many" switch should not be visible in token mode | ||||
| expect(screen.queryByTitle('Toggle Multisend')).not.toBeInTheDocument(); | expect( | ||||
| screen.queryByRole('button', { name: 'Send to many' }), | |||||
| ).not.toBeInTheDocument(); | |||||
| // Search for the token by ticker | // Search for the token by ticker | ||||
| const tokenSearchInput = screen.getByPlaceholderText( | const tokenSearchInput = screen.getByPlaceholderText( | ||||
| 'Start typing a token ticker or name', | 'Search by token ticker or name', | ||||
| ); | ); | ||||
| await user.type(tokenSearchInput, tokenTicker); | await user.type(tokenSearchInput, tokenTicker); | ||||
| // Wait for dropdown to appear with token | // Wait for dropdown to appear with token | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // Click on the token in the dropdown to select it | // Click on the token in the dropdown to select it | ||||
| // The token ticker text should be clickable within the dropdown item | // The token ticker text should be clickable within the dropdown item | ||||
| const tokenTickerElement = screen.getByText(tokenTicker); | const tokenTickerElement = screen.getByText(tokenTicker); | ||||
| // Click on the parent div that has the onClick handler | // Click on the parent div that has the onClick handler | ||||
| await user.click(tokenTickerElement.closest('div')); | await user.click(tokenTickerElement.closest('div')); | ||||
| // Wait for token to be selected and address/amount inputs to appear | // Wait for token to be selected and address/amount inputs to appear | ||||
| // The search input should be replaced with the selected token display | // The search input should be replaced with the selected token display | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| const addressInput = screen.queryByPlaceholderText('Address'); | const addressInput = screen.queryByPlaceholderText('Address'); | ||||
| expect(addressInput).toBeInTheDocument(); | expect(addressInput).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // Verify the selected token ticker is displayed in the selected token display | // Verify the selected token ticker is displayed in the selected token display | ||||
| // The search input should no longer be visible (replaced by SelectedTokenDisplay) | // The search input should no longer be visible (replaced by SelectedTokenDisplay) | ||||
| expect( | expect( | ||||
| screen.queryByPlaceholderText( | screen.queryByPlaceholderText('Search by token ticker or name'), | ||||
| 'Start typing a token ticker or name', | |||||
| ), | |||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| // The token ticker should be visible in the selected token display | // The token ticker should be visible in the selected token display | ||||
| expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | ||||
| // Enter address | // Enter address | ||||
| const addressInputEl = screen.getByPlaceholderText('Address'); | const addressInputEl = screen.getByPlaceholderText('Address'); | ||||
| const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | ||||
| await user.type(addressInputEl, address); | await user.type(addressInputEl, address); | ||||
| expect(addressInputEl).toHaveValue(address); | expect(addressInputEl).toHaveValue(address); | ||||
| // Enter amount (NFT parent tokens can be sent in quantities like fungible tokens) | // Enter amount (NFT parent tokens can be sent in quantities like fungible tokens) | ||||
| const amountInputEl = screen.getByPlaceholderText('Amount'); | const amountInputEl = screen.getByPlaceholderText('Amount'); | ||||
| const token_decimalized_qty = '1'; | const token_decimalized_qty = '1'; | ||||
| await user.type(amountInputEl, token_decimalized_qty); | await user.type(amountInputEl, token_decimalized_qty); | ||||
| expect(amountInputEl).toHaveValue(token_decimalized_qty); | expect(amountInputEl).toHaveValue(token_decimalized_qty); | ||||
| // The send button should be enabled | // The send button should be enabled | ||||
| const sendButton = screen.getByRole('button', { name: 'Send' }); | const sendButton = screen.getByRole('button', { name: 'Send' }); | ||||
| expect(sendButton).toBeEnabled(); | expect(sendButton).toBeEnabled(); | ||||
| // The Cashtab Msg and op_return_raw switches should not be visible in token mode | // The Cashtab Msg and op_return_raw switches should not be visible in token mode | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle Cashtab Msg'), | screen.queryByRole('button', { name: 'Message' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle op_return_raw'), | screen.queryByRole('button', { name: 'op_return_raw' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| // Click Send | // Click Send | ||||
| await user.click(sendButton); | await user.click(sendButton); | ||||
| // Notification is rendered with expected txid | // Notification is rendered with expected txid | ||||
| const txSuccessNotification = await screen.findByText('eToken sent'); | const txSuccessNotification = await screen.findByText('eToken sent'); | ||||
| await waitFor(() => | await waitFor(() => | ||||
| ▲ Show 20 Lines • Show All 81 Lines • ▼ Show 20 Lines | it('SLP1 NFT Child: We can send an SLP NFT using the token mode UI', async () => { | ||||
| // Wait for the app to load | // Wait for the app to load | ||||
| await waitFor(() => | await waitFor(() => | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Cashtab Loading'), | screen.queryByTitle('Cashtab Loading'), | ||||
| ).not.toBeInTheDocument(), | ).not.toBeInTheDocument(), | ||||
| ); | ); | ||||
| // Toggle to token mode | // Switch to token mode via Send Token link | ||||
| const tokenModeSwitch = await screen.findByTitle( | const sendTokenLink = await screen.findByRole('link', { | ||||
| 'Toggle XEC/Token Mode', | name: /Send Token/i, | ||||
| ); | }); | ||||
| expect(tokenModeSwitch).toBeInTheDocument(); | expect(sendTokenLink).toBeInTheDocument(); | ||||
| await user.click(tokenModeSwitch); | await user.click(sendTokenLink); | ||||
| // Wait for token mode UI to appear | // Wait for token mode UI to appear | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect( | expect( | ||||
| screen.getByPlaceholderText( | screen.getByPlaceholderText('Search by token ticker or name'), | ||||
| 'Start typing a token ticker or name', | |||||
| ), | |||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // The "Send to Many" switch should not be visible in token mode | // The "Send to Many" switch should not be visible in token mode | ||||
| expect(screen.queryByTitle('Toggle Multisend')).not.toBeInTheDocument(); | expect( | ||||
| screen.queryByRole('button', { name: 'Send to many' }), | |||||
| ).not.toBeInTheDocument(); | |||||
| // Search for the token by ticker | // Search for the token by ticker | ||||
| const tokenSearchInput = screen.getByPlaceholderText( | const tokenSearchInput = screen.getByPlaceholderText( | ||||
| 'Start typing a token ticker or name', | 'Search by token ticker or name', | ||||
| ); | ); | ||||
| await user.type(tokenSearchInput, tokenTicker); | await user.type(tokenSearchInput, tokenTicker); | ||||
| // Wait for dropdown to appear with token | // Wait for dropdown to appear with token | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // Click on the token in the dropdown to select it | // Click on the token in the dropdown to select it | ||||
| // The token ticker text should be clickable within the dropdown item | // The token ticker text should be clickable within the dropdown item | ||||
| const tokenTickerElement = screen.getByText(tokenTicker); | const tokenTickerElement = screen.getByText(tokenTicker); | ||||
| // Click on the parent div that has the onClick handler | // Click on the parent div that has the onClick handler | ||||
| await user.click(tokenTickerElement.closest('div')); | await user.click(tokenTickerElement.closest('div')); | ||||
| // Wait for token to be selected and address/amount inputs to appear | // Wait for token to be selected and address/amount inputs to appear | ||||
| // The search input should be replaced with the selected token display | // The search input should be replaced with the selected token display | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| const addressInput = screen.queryByPlaceholderText('Address'); | const addressInput = screen.queryByPlaceholderText('Address'); | ||||
| expect(addressInput).toBeInTheDocument(); | expect(addressInput).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // Verify the selected token ticker is displayed in the selected token display | // Verify the selected token ticker is displayed in the selected token display | ||||
| // The search input should no longer be visible (replaced by SelectedTokenDisplay) | // The search input should no longer be visible (replaced by SelectedTokenDisplay) | ||||
| expect( | expect( | ||||
| screen.queryByPlaceholderText( | screen.queryByPlaceholderText('Search by token ticker or name'), | ||||
| 'Start typing a token ticker or name', | |||||
| ), | |||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| // The token ticker should be visible in the selected token display | // The token ticker should be visible in the selected token display | ||||
| expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | ||||
| // Enter address | // Enter address | ||||
| const addressInputEl = screen.getByPlaceholderText('Address'); | const addressInputEl = screen.getByPlaceholderText('Address'); | ||||
| const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | ||||
| await user.type(addressInputEl, address); | await user.type(addressInputEl, address); | ||||
| expect(addressInputEl).toHaveValue(address); | expect(addressInputEl).toHaveValue(address); | ||||
| // Enter amount (NFTs are always sent as 1, but the input still requires a value) | // Enter amount (NFTs are always sent as 1, but the input still requires a value) | ||||
| const amountInputEl = screen.getByPlaceholderText('Amount'); | const amountInputEl = screen.getByPlaceholderText('Amount'); | ||||
| const token_decimalized_qty = '1'; | const token_decimalized_qty = '1'; | ||||
| await user.type(amountInputEl, token_decimalized_qty); | await user.type(amountInputEl, token_decimalized_qty); | ||||
| expect(amountInputEl).toHaveValue(token_decimalized_qty); | expect(amountInputEl).toHaveValue(token_decimalized_qty); | ||||
| // The send button should be enabled | // The send button should be enabled | ||||
| const sendButton = screen.getByRole('button', { name: 'Send' }); | const sendButton = screen.getByRole('button', { name: 'Send' }); | ||||
| expect(sendButton).toBeEnabled(); | expect(sendButton).toBeEnabled(); | ||||
| // The Cashtab Msg and op_return_raw switches should not be visible in token mode | // The Cashtab Msg and op_return_raw switches should not be visible in token mode | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle Cashtab Msg'), | screen.queryByRole('button', { name: 'Message' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle op_return_raw'), | screen.queryByRole('button', { name: 'op_return_raw' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| // Click Send | // Click Send | ||||
| await user.click(sendButton); | await user.click(sendButton); | ||||
| // Notification is rendered with expected txid (NFTs show "NFT sent" instead of "eToken sent") | // Notification is rendered with expected txid (NFTs show "NFT sent" instead of "eToken sent") | ||||
| const txSuccessNotification = await screen.findByText('NFT sent'); | const txSuccessNotification = await screen.findByText('NFT sent'); | ||||
| await waitFor(() => | await waitFor(() => | ||||
| ▲ Show 20 Lines • Show All 81 Lines • ▼ Show 20 Lines | it('SLP2 Mint Vault: We can send a mint vault token using the token mode UI', async () => { | ||||
| // Wait for the app to load | // Wait for the app to load | ||||
| await waitFor(() => | await waitFor(() => | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Cashtab Loading'), | screen.queryByTitle('Cashtab Loading'), | ||||
| ).not.toBeInTheDocument(), | ).not.toBeInTheDocument(), | ||||
| ); | ); | ||||
| // Toggle to token mode | // Switch to token mode via Send Token link | ||||
| const tokenModeSwitch = await screen.findByTitle( | const sendTokenLink = await screen.findByRole('link', { | ||||
| 'Toggle XEC/Token Mode', | name: /Send Token/i, | ||||
| ); | }); | ||||
| expect(tokenModeSwitch).toBeInTheDocument(); | expect(sendTokenLink).toBeInTheDocument(); | ||||
| await user.click(tokenModeSwitch); | await user.click(sendTokenLink); | ||||
| // Wait for token mode UI to appear | // Wait for token mode UI to appear | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect( | expect( | ||||
| screen.getByPlaceholderText( | screen.getByPlaceholderText('Search by token ticker or name'), | ||||
| 'Start typing a token ticker or name', | |||||
| ), | |||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // The "Send to Many" switch should not be visible in token mode | // The "Send to Many" switch should not be visible in token mode | ||||
| expect(screen.queryByTitle('Toggle Multisend')).not.toBeInTheDocument(); | expect( | ||||
| screen.queryByRole('button', { name: 'Send to many' }), | |||||
| ).not.toBeInTheDocument(); | |||||
| // Search for the token by ticker | // Search for the token by ticker | ||||
| const tokenSearchInput = screen.getByPlaceholderText( | const tokenSearchInput = screen.getByPlaceholderText( | ||||
| 'Start typing a token ticker or name', | 'Search by token ticker or name', | ||||
| ); | ); | ||||
| await user.type(tokenSearchInput, tokenTicker); | await user.type(tokenSearchInput, tokenTicker); | ||||
| // Wait for dropdown to appear with token | // Wait for dropdown to appear with token | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // Click on the token in the dropdown to select it | // Click on the token in the dropdown to select it | ||||
| // The token ticker text should be clickable within the dropdown item | // The token ticker text should be clickable within the dropdown item | ||||
| const tokenTickerElement = screen.getByText(tokenTicker); | const tokenTickerElement = screen.getByText(tokenTicker); | ||||
| // Click on the parent div that has the onClick handler | // Click on the parent div that has the onClick handler | ||||
| await user.click(tokenTickerElement.closest('div')); | await user.click(tokenTickerElement.closest('div')); | ||||
| // Wait for token to be selected and address/amount inputs to appear | // Wait for token to be selected and address/amount inputs to appear | ||||
| // The search input should be replaced with the selected token display | // The search input should be replaced with the selected token display | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| const addressInput = screen.queryByPlaceholderText('Address'); | const addressInput = screen.queryByPlaceholderText('Address'); | ||||
| expect(addressInput).toBeInTheDocument(); | expect(addressInput).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // Verify the selected token ticker is displayed in the selected token display | // Verify the selected token ticker is displayed in the selected token display | ||||
| // The search input should no longer be visible (replaced by SelectedTokenDisplay) | // The search input should no longer be visible (replaced by SelectedTokenDisplay) | ||||
| expect( | expect( | ||||
| screen.queryByPlaceholderText( | screen.queryByPlaceholderText('Search by token ticker or name'), | ||||
| 'Start typing a token ticker or name', | |||||
| ), | |||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| // The token ticker should be visible in the selected token display | // The token ticker should be visible in the selected token display | ||||
| expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | ||||
| // Enter address | // Enter address | ||||
| const addressInputEl = screen.getByPlaceholderText('Address'); | const addressInputEl = screen.getByPlaceholderText('Address'); | ||||
| const address = 'ecash:qz2708636snqhsxu8wnlka78h6fdp77ar59jrf5035'; | const address = 'ecash:qz2708636snqhsxu8wnlka78h6fdp77ar59jrf5035'; | ||||
| await user.type(addressInputEl, address); | await user.type(addressInputEl, address); | ||||
| expect(addressInputEl).toHaveValue(address); | expect(addressInputEl).toHaveValue(address); | ||||
| // Enter amount | // Enter amount | ||||
| const amountInputEl = screen.getByPlaceholderText('Amount'); | const amountInputEl = screen.getByPlaceholderText('Amount'); | ||||
| const token_decimalized_qty = '1'; | const token_decimalized_qty = '1'; | ||||
| await user.type(amountInputEl, token_decimalized_qty); | await user.type(amountInputEl, token_decimalized_qty); | ||||
| expect(amountInputEl).toHaveValue(token_decimalized_qty); | expect(amountInputEl).toHaveValue(token_decimalized_qty); | ||||
| // The send button should be enabled | // The send button should be enabled | ||||
| const sendButton = screen.getByRole('button', { name: 'Send' }); | const sendButton = screen.getByRole('button', { name: 'Send' }); | ||||
| expect(sendButton).toBeEnabled(); | expect(sendButton).toBeEnabled(); | ||||
| // The Cashtab Msg and op_return_raw switches should not be visible in token mode | // The Cashtab Msg and op_return_raw switches should not be visible in token mode | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle Cashtab Msg'), | screen.queryByRole('button', { name: 'Message' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle op_return_raw'), | screen.queryByRole('button', { name: 'op_return_raw' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| // Click Send | // Click Send | ||||
| await user.click(sendButton); | await user.click(sendButton); | ||||
| // Notification is rendered with expected txid | // Notification is rendered with expected txid | ||||
| const txSuccessNotification = await screen.findByText('eToken sent'); | const txSuccessNotification = await screen.findByText('eToken sent'); | ||||
| await waitFor(() => | await waitFor(() => | ||||
| ▲ Show 20 Lines • Show All 65 Lines • ▼ Show 20 Lines | it('SLP1 NFT Child: Entering a valid bip21 query string for a token send tx will correcty populate the UI, and the tx can be sent', async () => { | ||||
| const token_decimalized_qty = '1'; | const token_decimalized_qty = '1'; | ||||
| const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | ||||
| const addressInput = `${address}?token_id=${token_id}&token_decimalized_qty=${token_decimalized_qty}`; | const addressInput = `${address}?token_id=${token_id}&token_decimalized_qty=${token_decimalized_qty}`; | ||||
| // Use fireEvent.input to set the value, then fireEvent.change to trigger the handler | // Use fireEvent.input to set the value, then fireEvent.change to trigger the handler | ||||
| // This simulates a paste/scan where the full value is set at once | // This simulates a paste/scan where the full value is set at once | ||||
| fireEvent.input(addressInputEl, { target: { value: addressInput } }); | fireEvent.input(addressInputEl, { target: { value: addressInput } }); | ||||
| fireEvent.change(addressInputEl, { target: { value: addressInput } }); | fireEvent.change(addressInputEl, { target: { value: addressInput } }); | ||||
| // Wait for token mode to be activated (toggle should be on "Tokens") | // Wait for token mode to be activated (address input populated with bip21) | ||||
| await waitFor(() => { | const tokenAddressInputEl = | ||||
| const tokenModeSwitch = screen.getByTitle('Toggle XEC/Token Mode'); | await screen.findByPlaceholderText('Address'); | ||||
| expect(tokenModeSwitch).toHaveProperty('checked', true); | |||||
| }); | |||||
| // The "Send to Many" switch should not be visible in token mode | |||||
| expect(screen.queryByTitle('Toggle Multisend')).not.toBeInTheDocument(); | |||||
| // Get the token mode address input field | |||||
| const tokenAddressInputEl = screen.getByPlaceholderText('Address'); | |||||
| // The token mode 'Send To' input field has this bip21 query string as a value | |||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect(tokenAddressInputEl).toHaveValue(addressInput); | expect(tokenAddressInputEl).toHaveValue(addressInput); | ||||
| }); | }); | ||||
| // The "Send to many" button should not be visible in token mode (XEC form not shown) | |||||
| expect( | |||||
| screen.queryByRole('button', { name: 'Send to many' }), | |||||
| ).not.toBeInTheDocument(); | |||||
| // The token mode 'Send To' input field is not disabled | // The token mode 'Send To' input field is not disabled | ||||
| expect(tokenAddressInputEl).toHaveProperty('disabled', false); | expect(tokenAddressInputEl).toHaveProperty('disabled', false); | ||||
| // The token amount input is visible and populated from the BIP21 string | // The token amount input is visible and populated from the BIP21 string | ||||
| const amountInputEl = screen.getByPlaceholderText('Amount'); | const amountInputEl = screen.getByPlaceholderText('Amount'); | ||||
| expect(amountInputEl).toBeInTheDocument(); | expect(amountInputEl).toBeInTheDocument(); | ||||
| expect(amountInputEl).toHaveValue(token_decimalized_qty); | expect(amountInputEl).toHaveValue(token_decimalized_qty); | ||||
| // The amount input should be disabled because token_decimalized_qty is specified in the BIP21 string | // The amount input should be disabled because token_decimalized_qty is specified in the BIP21 string | ||||
| Show All 17 Lines | it('SLP1 NFT Child: Entering a valid bip21 query string for a token send tx will correcty populate the UI, and the tx can be sent', async () => { | ||||
| // The send button is enabled as we have valid bip21 token send for a token qty supported | // The send button is enabled as we have valid bip21 token send for a token qty supported | ||||
| // by the wallet | // by the wallet | ||||
| const sendButton = screen.getByRole('button', { name: 'Send' }); | const sendButton = screen.getByRole('button', { name: 'Send' }); | ||||
| expect(sendButton).toBeEnabled(); | expect(sendButton).toBeEnabled(); | ||||
| // The Cashtab Msg and op_return_raw switches are not visible in token mode | // The Cashtab Msg and op_return_raw switches are not visible in token mode | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle Cashtab Msg'), | screen.queryByRole('button', { name: 'Message' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Toggle op_return_raw'), | screen.queryByRole('button', { name: 'op_return_raw' }), | ||||
| ).not.toBeInTheDocument(); | ).not.toBeInTheDocument(); | ||||
| // Click Send | // Click Send | ||||
| await user.click(sendButton); | await user.click(sendButton); | ||||
| // Notification is rendered with expected txid | // Notification is rendered with expected txid | ||||
| const txSuccessNotification = await screen.findByText('NFT sent'); | const txSuccessNotification = await screen.findByText('NFT sent'); | ||||
| await waitFor(() => | await waitFor(() => | ||||
| ▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | it('We can parse a valid FIRMA-USDT redeem tx from bip21 and broadcast the tx', async () => { | ||||
| ).not.toBeInTheDocument(), | ).not.toBeInTheDocument(), | ||||
| ); | ); | ||||
| const addressInputEl = await screen.findByPlaceholderText('Address'); | const addressInputEl = await screen.findByPlaceholderText('Address'); | ||||
| // Simulate pasting/scanning the full BIP21 string at once (not typing character-by-character) | // Simulate pasting/scanning the full BIP21 string at once (not typing character-by-character) | ||||
| fireEvent.input(addressInputEl, { target: { value: bip21Str } }); | fireEvent.input(addressInputEl, { target: { value: bip21Str } }); | ||||
| fireEvent.change(addressInputEl, { target: { value: bip21Str } }); | fireEvent.change(addressInputEl, { target: { value: bip21Str } }); | ||||
| // Wait for token mode to be activated (toggle should be on "Tokens") | // Wait for token mode to be activated (address input populated with bip21) | ||||
| await waitFor(() => { | const tokenAddressInputEl = | ||||
| const tokenModeSwitch = screen.getByTitle('Toggle XEC/Token Mode'); | await screen.findByPlaceholderText('Address'); | ||||
| expect(tokenModeSwitch).toHaveProperty('checked', true); | |||||
| }); | |||||
| // Get the token mode address input field | |||||
| const tokenAddressInputEl = screen.getByPlaceholderText('Address'); | |||||
| // The token mode 'Send To' input field has this bip21 query string as a value | |||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect(tokenAddressInputEl).toHaveValue(bip21Str); | expect(tokenAddressInputEl).toHaveValue(bip21Str); | ||||
| }); | }); | ||||
| // The token amount input is visible and populated from the BIP21 string | // The token amount input is visible and populated from the BIP21 string | ||||
| const amountInputEl = screen.getByPlaceholderText('Amount'); | const amountInputEl = screen.getByPlaceholderText('Amount'); | ||||
| expect(amountInputEl).toBeInTheDocument(); | expect(amountInputEl).toBeInTheDocument(); | ||||
| expect(amountInputEl).toHaveValue(token_decimalized_qty); | expect(amountInputEl).toHaveValue(token_decimalized_qty); | ||||
| ▲ Show 20 Lines • Show All 71 Lines • ▼ Show 20 Lines | it('ALP Fungible: EMPP switches are visible and cashtab msg validation works', async () => { | ||||
| // Wait for the app to load | // Wait for the app to load | ||||
| await waitFor(() => | await waitFor(() => | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Cashtab Loading'), | screen.queryByTitle('Cashtab Loading'), | ||||
| ).not.toBeInTheDocument(), | ).not.toBeInTheDocument(), | ||||
| ); | ); | ||||
| // Toggle to token mode | // Switch to token mode via Send Token link | ||||
| const tokenModeSwitch = await screen.findByTitle( | const sendTokenLink = await screen.findByRole('link', { | ||||
| 'Toggle XEC/Token Mode', | name: /Send Token/i, | ||||
| ); | }); | ||||
| expect(tokenModeSwitch).toBeInTheDocument(); | expect(sendTokenLink).toBeInTheDocument(); | ||||
| await user.click(tokenModeSwitch); | await user.click(sendTokenLink); | ||||
| // Wait for token mode UI to appear | // Wait for token mode UI to appear | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect( | expect( | ||||
| screen.getByPlaceholderText( | screen.getByPlaceholderText('Search by token ticker or name'), | ||||
| 'Start typing a token ticker or name', | |||||
| ), | |||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // Search for the token by ticker | // Search for the token by ticker | ||||
| const tokenSearchInput = screen.getByPlaceholderText( | const tokenSearchInput = screen.getByPlaceholderText( | ||||
| 'Start typing a token ticker or name', | 'Search by token ticker or name', | ||||
| ); | ); | ||||
| await user.type(tokenSearchInput, tokenTicker); | await user.type(tokenSearchInput, tokenTicker); | ||||
| // Wait for dropdown to appear with token | // Wait for dropdown to appear with token | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // Click on the token in the dropdown to select it | // Click on the token in the dropdown to select it | ||||
| const tokenTickerElement = screen.getByText(tokenTicker); | const tokenTickerElement = screen.getByText(tokenTicker); | ||||
| await user.click(tokenTickerElement.closest('div')); | await user.click(tokenTickerElement.closest('div')); | ||||
| // Wait for token to be selected and address/amount inputs to appear | // Wait for token to be selected and address/amount inputs to appear | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| const addressInput = screen.queryByPlaceholderText('Address'); | const addressInput = screen.queryByPlaceholderText('Address'); | ||||
| expect(addressInput).toBeInTheDocument(); | expect(addressInput).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // Verify EMPP switches are visible for ALP tokens | // Verify EMPP switches are visible for ALP tokens | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect( | expect( | ||||
| screen.getByTitle('Toggle Cashtab Msg Token'), | screen.getByRole('button', { name: 'Cashtab Msg' }), | ||||
| ).toBeInTheDocument(); | |||||
| expect( | |||||
| screen.getByRole('button', { name: 'empp_raw' }), | |||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| expect(screen.getByTitle('Toggle empp_raw')).toBeInTheDocument(); | |||||
| }); | }); | ||||
| // Enter address | // Enter address | ||||
| const addressInputEl = screen.getByPlaceholderText('Address'); | const addressInputEl = screen.getByPlaceholderText('Address'); | ||||
| const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | const address = 'ecash:qp89xgjhcqdnzzemts0aj378nfe2mhu9yvxj9nhgg6'; | ||||
| await user.type(addressInputEl, address); | await user.type(addressInputEl, address); | ||||
| expect(addressInputEl).toHaveValue(address); | expect(addressInputEl).toHaveValue(address); | ||||
| // Enter amount | // Enter amount | ||||
| const amountInputEl = screen.getByPlaceholderText('Amount'); | const amountInputEl = screen.getByPlaceholderText('Amount'); | ||||
| const token_decimalized_qty = '1'; | const token_decimalized_qty = '1'; | ||||
| await user.type(amountInputEl, token_decimalized_qty); | await user.type(amountInputEl, token_decimalized_qty); | ||||
| expect(amountInputEl).toHaveValue(token_decimalized_qty); | expect(amountInputEl).toHaveValue(token_decimalized_qty); | ||||
| // Enable Cashtab Msg switch | // Enable Cashtab Msg switch | ||||
| const cashtabMsgSwitch = screen.getByTitle('Toggle Cashtab Msg Token'); | const cashtabMsgButton = screen.getByRole('button', { | ||||
| await user.click(cashtabMsgSwitch); | name: 'Cashtab Msg', | ||||
| }); | |||||
| await user.click(cashtabMsgButton); | |||||
| // Wait for cashtab msg textarea to appear | // Wait for cashtab msg textarea to appear | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect( | expect( | ||||
| screen.getByPlaceholderText( | screen.getByPlaceholderText( | ||||
| /Include a Cashtab msg EMPP push with this token tx/, | /Include a Cashtab msg EMPP push with this token tx/, | ||||
| ), | ), | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| ▲ Show 20 Lines • Show All 88 Lines • ▼ Show 20 Lines | it('ALP Fungible: We can send an ALP token with empp_raw', async () => { | ||||
| // Wait for the app to load | // Wait for the app to load | ||||
| await waitFor(() => | await waitFor(() => | ||||
| expect( | expect( | ||||
| screen.queryByTitle('Cashtab Loading'), | screen.queryByTitle('Cashtab Loading'), | ||||
| ).not.toBeInTheDocument(), | ).not.toBeInTheDocument(), | ||||
| ); | ); | ||||
| // Toggle to token mode | // Switch to token mode via Send Token link | ||||
| const tokenModeSwitch = await screen.findByTitle( | const sendTokenLink = await screen.findByRole('link', { | ||||
| 'Toggle XEC/Token Mode', | name: /Send Token/i, | ||||
| ); | }); | ||||
| expect(tokenModeSwitch).toBeInTheDocument(); | expect(sendTokenLink).toBeInTheDocument(); | ||||
| await user.click(tokenModeSwitch); | await user.click(sendTokenLink); | ||||
| // Wait for token mode UI to appear | // Wait for token mode UI to appear | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect( | expect( | ||||
| screen.getByPlaceholderText( | screen.getByPlaceholderText('Search by token ticker or name'), | ||||
| 'Start typing a token ticker or name', | |||||
| ), | |||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| }); | }); | ||||
| // Search for the token by ticker | // Search for the token by ticker | ||||
| const tokenSearchInput = screen.getByPlaceholderText( | const tokenSearchInput = screen.getByPlaceholderText( | ||||
| 'Start typing a token ticker or name', | 'Search by token ticker or name', | ||||
| ); | ); | ||||
| await user.type(tokenSearchInput, tokenTicker); | await user.type(tokenSearchInput, tokenTicker); | ||||
| // Wait for dropdown to appear with token | // Wait for dropdown to appear with token | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | expect(screen.getByText(tokenTicker)).toBeInTheDocument(); | ||||
| }); | }); | ||||
| Show All 15 Lines | it('ALP Fungible: We can send an ALP token with empp_raw', async () => { | ||||
| // Enter amount | // Enter amount | ||||
| const amountInputEl = screen.getByPlaceholderText('Amount'); | const amountInputEl = screen.getByPlaceholderText('Amount'); | ||||
| const token_decimalized_qty = '1'; | const token_decimalized_qty = '1'; | ||||
| await user.type(amountInputEl, token_decimalized_qty); | await user.type(amountInputEl, token_decimalized_qty); | ||||
| expect(amountInputEl).toHaveValue(token_decimalized_qty); | expect(amountInputEl).toHaveValue(token_decimalized_qty); | ||||
| // Enable empp_raw switch | // Enable empp_raw switch | ||||
| const emppRawSwitch = screen.getByTitle('Toggle empp_raw'); | const emppRawButton = screen.getByRole('button', { | ||||
| await user.click(emppRawSwitch); | name: 'empp_raw', | ||||
| }); | |||||
| await user.click(emppRawButton); | |||||
| // Wait for empp_raw textarea to appear | // Wait for empp_raw textarea to appear | ||||
| await waitFor(() => { | await waitFor(() => { | ||||
| expect( | expect( | ||||
| screen.getByPlaceholderText(/Enter raw hex EMPP push/), | screen.getByPlaceholderText(/Enter raw hex EMPP push/), | ||||
| ).toBeInTheDocument(); | ).toBeInTheDocument(); | ||||
| }); | }); | ||||
| Show All 34 Lines | |||||