Browse Source

feat(ui): add LightningInvoiceInput component

renovate/lint-staged-8.x
Tom Kirkpatrick 6 years ago
parent
commit
e64314ca97
No known key found for this signature in database GPG Key ID: 72203A8EC5967EA8
  1. 2
      .storybook/config.js
  2. 107
      app/components/UI/Input.js
  3. 24
      app/components/UI/Label.js
  4. 71
      app/components/UI/LightningInvoiceInput.js
  5. 97
      app/components/UI/TextArea.js
  6. 1
      app/components/UI/index.js
  7. 73
      app/lib/utils/crypto.js
  8. 1
      package.json
  9. 74
      stories/components/form.stories.js
  10. 21
      test/unit/components/UI/LightningInvoiceInput.spec.js
  11. 25
      test/unit/components/UI/__snapshots__/Input.spec.js.snap
  12. 60
      test/unit/components/UI/__snapshots__/LightningInvoiceInput.spec.js.snap
  13. 24
      test/unit/components/UI/__snapshots__/Select.spec.js.snap
  14. 25
      test/unit/components/UI/__snapshots__/TextArea.spec.js.snap
  15. 100
      test/unit/utils/crypto.spec.js
  16. 47
      yarn.lock

2
.storybook/config.js

@ -12,7 +12,7 @@ import { setIntlConfig, withIntl } from 'storybook-addon-intl'
import StoryRouter from 'storybook-react-router'
import { dark, light } from 'themes'
import { getDefaultLocale, locales } from 'lib/i18n'
import { BackgroundDark GlobalStyle } from 'components/UI'
import { BackgroundDark, GlobalStyle } from 'components/UI'
// Register supported locales.
import '../app/lib/i18n/locale'

107
app/components/UI/Input.js

@ -1,25 +1,55 @@
import React from 'react'
import { asField } from 'informed'
import { Input as Base } from 'styled-system-html'
import { withTheme } from 'styled-components'
import system from '@rebass/components'
import { styles } from 'styled-system'
import { Flex } from 'rebass'
import { FormFieldMessage } from 'components/UI'
// Create an html input element that accepts all style props from styled-system.
const SystemInput = system(
{
as: 'input',
border: 1,
borderColor: 'gray',
borderRadius: 5,
bg: 'transparent',
color: 'primaryText',
fontFamily: 'sans',
fontSize: 'm',
fontWeight: 'light',
p: 3,
width: 1
},
...Object.keys(styles)
)
/**
* @render react
* @name Input
* @example
* <Input />
*/
class Input extends React.PureComponent {
class Input extends React.Component {
static displayName = 'Input'
state = {
hasFocus: false
}
constructor(props) {
super(props)
const { forwardedRef } = this.props
this.inputRef = forwardedRef || React.createRef()
}
render() {
const {
css,
onChange,
onBlur,
onFocus,
initialValue,
field,
forwardedRef,
theme,
fieldApi,
@ -27,42 +57,40 @@ class Input extends React.PureComponent {
justifyContent,
...rest
} = this.props
const { readOnly } = this.props
const { hasFocus } = this.state
const { setValue, setTouched } = fieldApi
const { value } = fieldState
const isValid = value && !fieldState.error
// Calculate the border color based on the current field state.
let borderColor
if (fieldState.touched) {
if (fieldState.error) {
borderColor = theme.colors.superRed
} else if (value && !fieldState.error) {
borderColor = theme.colors.superGreen
}
if (readOnly) {
borderColor = theme.colors.gray
} else if (fieldState.error) {
borderColor = theme.colors.superRed
} else if (value && !fieldState.error) {
borderColor = theme.colors.superGreen
}
return (
<Flex flexDirection="column" justifyContent={justifyContent}>
<Base
outline="none"
borderRadius="5px"
borderColor={borderColor || theme.colors.white}
border="1px solid white"
bg="transparent"
color="white"
p={3}
type="text"
fontSize="m"
width={1}
css={{
'&:focus': {
<SystemInput
borderColor={borderColor || theme.colors.gray}
css={Object.assign(
{
outline: 'none',
border: `1px solid ${borderColor || theme.colors.lightningOrange} }`
}
}}
'&:not([readOnly]):not([disabled]):focus': {
border: `1px solid ${
isValid ? theme.colors.superGreen : theme.colors.lightningOrange
} }`
}
},
css
)}
{...rest}
value={!value && value !== 0 ? '' : value}
name={field}
ref={forwardedRef}
ref={this.inputRef}
value={!value && value !== 0 ? '' : initialValue || value}
onChange={e => {
setValue(e.target.value)
if (onChange) {
@ -71,14 +99,33 @@ class Input extends React.PureComponent {
}}
onBlur={e => {
setTouched()
// Make the state aware that the element is now focused.
const newHasFocus = document.activeElement === this.inputRef.current
if (hasFocus !== newHasFocus) {
this.setState({ hasFocus: newHasFocus })
}
if (onBlur) {
onBlur(e)
}
}}
onFocus={e => {
// Make the state aware that the element is no longer focused.
const newHasFocus = document.activeElement === this.inputRef.current
if (hasFocus !== newHasFocus) {
this.setState({ hasFocus: newHasFocus })
}
if (onFocus) {
onFocus(e)
}
}}
error={fieldState.error}
/>
{fieldState.error && (
<FormFieldMessage variant="error" justifyContent={justifyContent}>
<FormFieldMessage
variant={hasFocus ? 'warning' : 'error'}
justifyContent={justifyContent}
mt={2}
>
{fieldState.error}
</FormFieldMessage>
)}

24
app/components/UI/Label.js

@ -1,5 +1,20 @@
import React from 'react'
import { Label as Base } from 'styled-system-html'
import system from '@rebass/components'
import { styles } from 'styled-system'
// Create an html input element that accepts all style props from styled-system.
const SystemLabel = system(
{
as: 'label',
display: 'block',
color: 'primaryText',
fontSize: 'm',
fontWeight: 'normal',
width: 1,
mb: 1
},
...Object.keys(styles)
)
/**
* @render react
@ -7,13 +22,12 @@ import { Label as Base } from 'styled-system-html'
* @example
* <Label />
*/
class Label extends React.Component {
class Label extends React.PureComponent {
static displayName = 'Label'
render() {
return (
<Base css={{ display: 'block' }} color="white" fontWeight="bold" mb={1} {...this.props} />
)
const { readOnly } = this.props
return <SystemLabel {...this.props} opacity={readOnly ? 0.6 : null} />
}
}

71
app/components/UI/LightningInvoiceInput.js

@ -0,0 +1,71 @@
import React from 'react'
import PropTypes from 'prop-types'
import { asField } from 'informed'
import { isOnchain, isLn } from 'lib/utils/crypto'
import TextArea from 'components/UI/TextArea'
import FormFieldMessage from 'components/UI/FormFieldMessage'
/**
* @render react
* @name LightningInvoiceInput
* @example
* <LightningInvoiceInput
network="testnet"
field="testnet"
id="testnet"
validateOnChange
validateOnBlur />
*/
class LightningInvoiceInput extends React.Component {
static displayName = 'LightningInvoiceInput'
static propTypes = {
required: PropTypes.bool,
chain: PropTypes.oneOf(['bitcoin', 'litecoin']),
network: PropTypes.oneOf(['mainnet', 'testnet', 'regtest'])
}
static defaultProps = {
required: false
}
validate = value => {
const { network, chain, required } = this.props
if (required && (!value || value.trim() === '')) {
return 'This is a required field'
}
if (value && !isLn(value, chain, network) && !isOnchain(value, chain, network)) {
return 'Not a valid address.'
}
}
render() {
return (
<InformedTextArea
placeholder="Paste a Lightning Payment Request or Bitcoin Address here"
rows={5}
{...this.props}
validate={this.validate}
/>
)
}
}
const InformedTextArea = asField(({ fieldState, fieldApi, ...props }) => {
const { value } = fieldState
const { chain, network, ...rest } = props
return (
<React.Fragment>
<TextArea {...rest} />
{value &&
!fieldState.error && (
<FormFieldMessage variant="success" mt={2}>
Valid {isLn(value, chain, network) ? 'lightning' : chain} address{' '}
{network !== 'mainnet' && `(${network})`}
</FormFieldMessage>
)}
</React.Fragment>
)
})
export default LightningInvoiceInput

97
app/components/UI/TextArea.js

@ -1,10 +1,30 @@
import React from 'react'
import { asField } from 'informed'
import { TextArea as Base } from 'styled-system-html'
import system from '@rebass/components'
import { styles } from 'styled-system'
import { withTheme } from 'styled-components'
import { Flex } from 'rebass'
import { FormFieldMessage } from 'components/UI'
// Create an html textarea element that accepts all style props from styled-system.
const SystemTextArea = system(
{
as: 'textarea',
border: 1,
borderColor: 'gray',
borderRadius: '5px',
bg: 'transparent',
color: 'primaryText',
fontFamily: 'sans',
fontSize: 'm',
fontWeight: 'light',
p: 3,
width: 1,
rows: 5
},
...Object.keys(styles)
)
/**
* @render react
* @name TextArea
@ -14,10 +34,22 @@ import { FormFieldMessage } from 'components/UI'
class TextArea extends React.PureComponent {
static displayName = 'TextArea'
state = {
hasFocus: false
}
constructor(props) {
super(props)
const { forwardedRef } = this.props
this.inputRef = forwardedRef || React.createRef()
}
render() {
const {
css,
onChange,
onBlur,
onFocus,
initialValue,
forwardedRef,
theme,
@ -26,11 +58,17 @@ class TextArea extends React.PureComponent {
justifyContent,
...rest
} = this.props
const { readOnly } = this.props
const { hasFocus } = this.state
const { setValue, setTouched } = fieldApi
const { value } = fieldState
const isValid = value && !fieldState.error
// Calculate the border color based on the current field state.
let borderColor
if (fieldState.error) {
if (readOnly) {
borderColor = theme.colors.gray
} else if (fieldState.error) {
borderColor = theme.colors.superRed
} else if (value && !fieldState.error) {
borderColor = theme.colors.superGreen
@ -38,27 +76,23 @@ class TextArea extends React.PureComponent {
return (
<Flex flexDirection="column" justifyContent={justifyContent}>
<Base
outline="none"
borderRadius="5px"
borderColor={borderColor || theme.colors.white}
border="1px solid white"
bg="transparent"
color="white"
p={3}
type="text"
fontSize="m"
width={1}
rows="5"
css={{
'&:focus': {
<SystemTextArea
borderColor={borderColor || theme.colors.gray}
opacity={readOnly ? 0.6 : null}
css={Object.assign(
{
outline: 'none',
border: `1px solid ${borderColor || theme.colors.lightningOrange} }`
}
}}
'&:not([readOnly]):not([disabled]):focus': {
border: `1px solid ${
isValid ? theme.colors.superGreen : theme.colors.lightningOrange
} }`
}
},
css
)}
{...rest}
ref={forwardedRef}
value={!value && value !== 0 ? '' : value}
ref={this.inputRef}
value={!value && value !== 0 ? '' : initialValue || value}
onChange={e => {
setValue(e.target.value)
if (onChange) {
@ -67,14 +101,33 @@ class TextArea extends React.PureComponent {
}}
onBlur={e => {
setTouched()
// Make the state aware that the element is now focused.
const newHasFocus = document.activeElement === this.inputRef.current
if (hasFocus !== newHasFocus) {
this.setState({ hasFocus: newHasFocus })
}
if (onBlur) {
onBlur(e)
}
}}
onFocus={e => {
// Make the state aware that the element is no longer focused.
const newHasFocus = document.activeElement === this.inputRef.current
if (hasFocus !== newHasFocus) {
this.setState({ hasFocus: newHasFocus })
}
if (onFocus) {
onFocus(e)
}
}}
error={fieldState.error}
/>
{fieldState.error && (
<FormFieldMessage variant="error" justifyContent={justifyContent}>
<FormFieldMessage
variant={hasFocus ? 'warning' : 'error'}
justifyContent={justifyContent}
mt={2}
>
{fieldState.error}
</FormFieldMessage>
)}

1
app/components/UI/index.js

@ -9,6 +9,7 @@ export GlobalStyle from './GlobalStyle'
export Heading from './Heading'
export Input from './Input'
export Label from './Label'
export LightningInvoiceInput from './LightningInvoiceInput'
export MainContent from './MainContent'
export Menu from './Menu'
export MenuItem from './MenuItem'

73
app/lib/utils/crypto.js

@ -0,0 +1,73 @@
import bitcoin from 'bitcoinjs-lib'
import bech32 from 'lib/utils/bech32'
/**
* Test to see if a string is a valid on-chain address.
* @param {String} input string to check.
* @param {String} [network='mainnet'] network to check (mainnet, testnet).
* @return {Boolean} boolean indicating wether the address is a valid on-chain address.
*/
export const isOnchain = (input, chain = 'bitcoin', network = 'mainnet') => {
if (chain !== 'bitcoin') {
// TODO: Implement address checking for litecoin.
return true
}
try {
bitcoin.address.toOutputScript(
input,
network === 'mainnet' ? bitcoin.networks.bitcoin : bitcoin.networks.testnet
)
return true
} catch (e) {
return false
}
}
/**
* Test to see if a string is a valid lightning address.
* @param {String} input string to check.
* @param {String} [network='bitcoin'] chain to check (bitcoin, litecoin).
* @param {String} [network='mainnet'] network to check (mainnet, testnet, regtest).
* @return {Boolean} boolean indicating wether the address is a lightning address.
*/
export const isLn = (input, chain = 'bitcoin', network = 'mainnet') => {
let prefix = 'ln'
// Prefixes come from SLIP-0173
// See https://github.com/satoshilabs/slips/blob/master/slip-0173.md
if (chain === 'bitcoin') {
switch (network) {
case 'mainnet':
prefix = 'lnbc'
break
case 'testnet':
prefix = 'lntb'
break
case 'regtest':
prefix = 'lnbcrt'
break
}
} else if (chain === 'litecoin') {
switch (network) {
case 'mainnet':
prefix = 'lnltc'
break
case 'testnet':
prefix = 'lntltc'
break
case 'regtest':
prefix = 'lnrltc'
break
}
}
if (!input.startsWith(prefix)) {
return false
}
try {
bech32.decode(input)
return true
} catch (e) {
return false
}
}

1
package.json

@ -349,7 +349,6 @@
"source-map-support": "^0.5.9",
"split2": "^3.0.0",
"styled-components": "^4.0.3",
"styled-system-html": "^2.0.2",
"tildify": "^1.2.0",
"untildify": "^3.0.3",
"validator": "^10.8.0"

74
stories/components/form.stories.js

@ -8,6 +8,7 @@ import {
MainContent,
Input,
Label,
LightningInvoiceInput,
Select,
TextArea,
Button,
@ -44,6 +45,40 @@ storiesOf('Components.Form', module)
<TextArea field="fieldName" placeholder="Type here" />
</Form>
))
.add('Lightning Invoice Textarea', () => (
<React.Fragment>
<Box my={4}>
<Box>
<Label htmlFor="testnet">Bitcoin or Lightning address (testnet)</Label>
</Box>
<Form id="testnet">
<LightningInvoiceInput
chain="bitcoin"
network="testnet"
field="testnet"
id="testnet"
validateOnBlur
validateOnChange
/>
</Form>
</Box>
<Box>
<Box>
<Label htmlFor="mainnet">Bitcoin or Lightning address (mainnet)</Label>
</Box>
<Form id="testnet">
<LightningInvoiceInput
chain="bitcoin"
network="mainnet"
field="mainnet"
id="mainnet"
validateOnBlur
validateOnChange
/>
</Form>
</Box>
</React.Fragment>
))
.add('Select', () => (
<Form>
<Select field="fieldName" items={selectItems} />
@ -65,7 +100,7 @@ storiesOf('Components.Form', module)
<Form>
{({ formState }) => (
<React.Fragment>
<Box mb={3}>
<Box my={4}>
<Box>
<Label htmlFor="input1">Example Field</Label>
</Box>
@ -80,7 +115,7 @@ storiesOf('Components.Form', module)
</Box>
</Box>
<Box mb={3}>
<Box my={4}>
<Box>
<Label htmlFor="textarea1">Example Textarea</Label>
</Box>
@ -94,7 +129,34 @@ storiesOf('Components.Form', module)
</Box>
</Box>
<Box mb={3}>
<Box my={4}>
<Box>
<Label htmlFor="testnet">Bitcoin or Lightning address (testnet)</Label>
</Box>
<LightningInvoiceInput
chain="bitcoin"
network="testnet"
field="testnet"
id="testnet"
validateOnBlur
validateOnChange
/>
</Box>
<Box>
<Box>
<Label htmlFor="mainnet">Bitcoin or Lightning address (mainnet)</Label>
</Box>
<LightningInvoiceInput
chain="bitcoin"
network="mainnet"
field="mainnet"
id="mainnet"
validateOnBlur
validateOnChange
/>
</Box>
<Box my={4}>
<Box>
<Label htmlFor="selectfield1">Example Select</Label>
</Box>
@ -109,7 +171,7 @@ storiesOf('Components.Form', module)
</Box>
</Box>
<Box mb={3}>
<Box my={4}>
<Box>
<Label htmlFor="checkbox1">Example Toggle</Label>
</Box>
@ -118,7 +180,7 @@ storiesOf('Components.Form', module)
</Box>
</Box>
<Box mb={3}>
<Box my={4}>
<Box>
<Label htmlFor="slider1">Example Range</Label>
</Box>
@ -127,7 +189,7 @@ storiesOf('Components.Form', module)
</Box>
</Box>
<Box mb={3}>
<Box my={4}>
<Button>Submit</Button>
</Box>

21
test/unit/components/UI/LightningInvoiceInput.spec.js

@ -0,0 +1,21 @@
import React from 'react'
import { Form } from 'informed'
import renderer from 'react-test-renderer'
import { dark } from 'themes'
import { ThemeProvider } from 'styled-components'
import { LightningInvoiceInput } from 'components/UI'
describe('component.UI.LightningInvoiceInput', () => {
it('should render correctly', () => {
const tree = renderer
.create(
<ThemeProvider theme={dark}>
<Form>
<LightningInvoiceInput field="name" chain="bitcoin" network="mainnet" theme={dark} />
</Form>
</ThemeProvider>
)
.toJSON()
expect(tree).toMatchSnapshot()
})
})

25
test/unit/components/UI/__snapshots__/Input.spec.js.snap

@ -15,18 +15,20 @@ exports[`component.UI.Input should render correctly 1`] = `
padding: 16px;
width: 100%;
font-size: 13px;
color: #ffffff;
color: #fff;
background-color: transparent;
color: #ffffff;
color: #fff;
background-color: transparent;
border: 1px solid white;
border: 1px solid white;
border-color: #ffffff;
font-family: "Roboto Light",Roboto,system-ui,sans-serif;
font-weight: light;
border: 1px solid;
border: 1px solid;
border-color: #959595;
border-radius: 5px;
outline: none;
}
.c1:focus {
outline: none;
.c1:not([readOnly]):not([disabled]):focus {
border: 1px solid #fd9800;
}
@ -39,12 +41,15 @@ exports[`component.UI.Input should render correctly 1`] = `
>
<input
className="c1"
name="name"
color="primaryText"
fontFamily="sans"
fontSize="m"
fontWeight="light"
onBlur={[Function]}
onChange={[Function]}
outline="none"
type="text"
onFocus={[Function]}
value=""
width={1}
/>
</div>
</form>

60
test/unit/components/UI/__snapshots__/LightningInvoiceInput.spec.js.snap

@ -0,0 +1,60 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`component.UI.LightningInvoiceInput should render correctly 1`] = `
.c0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
}
.c1 {
padding: 16px;
width: 100%;
font-size: 13px;
color: #fff;
background-color: transparent;
color: #fff;
background-color: transparent;
font-family: "Roboto Light",Roboto,system-ui,sans-serif;
font-weight: light;
border: 1px solid;
border: 1px solid;
border-color: #959595;
border-radius: 5px;
outline: none;
}
.c1:not([readOnly]):not([disabled]):focus {
border: 1px solid #fd9800;
}
<form
onReset={[Function]}
onSubmit={[Function]}
>
<div
className="c0"
>
<textarea
className="c1"
color="primaryText"
fontFamily="sans"
fontSize="m"
fontWeight="light"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
opacity={null}
placeholder="Paste a Lightning Payment Request or Bitcoin Address here"
required={false}
rows={5}
value=""
width={1}
/>
</div>
</form>
`;

24
test/unit/components/UI/__snapshots__/Select.spec.js.snap

@ -15,18 +15,20 @@ exports[`component.UI.Toggle should render correctly 1`] = `
padding: 16px;
width: 100%;
font-size: 13px;
color: #ffffff;
color: #fff;
background-color: transparent;
color: #ffffff;
color: #fff;
background-color: transparent;
border: 1px solid white;
border: 1px solid white;
border-color: #ffffff;
font-family: "Roboto Light",Roboto,system-ui,sans-serif;
font-weight: light;
border: 1px solid;
border: 1px solid;
border-color: #959595;
border-radius: 5px;
outline: none;
}
.c2:focus {
outline: none;
.c2:not([readOnly]):not([disabled]):focus {
border: 1px solid #fd9800;
}
@ -132,17 +134,19 @@ exports[`component.UI.Toggle should render correctly 1`] = `
aria-labelledby="downshift-0-label"
autoComplete="off"
className="c2"
color="primaryText"
fontFamily="sans"
fontSize="m"
fontWeight="light"
id="downshift-0-input"
name="name"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
outline="none"
placeholder="Please select"
type="text"
value=""
width={1}
/>
</div>
</div>

25
test/unit/components/UI/__snapshots__/TextArea.spec.js.snap

@ -15,18 +15,20 @@ exports[`component.UI.Input should render correctly 1`] = `
padding: 16px;
width: 100%;
font-size: 13px;
color: #ffffff;
color: #fff;
background-color: transparent;
color: #ffffff;
color: #fff;
background-color: transparent;
border: 1px solid white;
border: 1px solid white;
border-color: #ffffff;
font-family: "Roboto Light",Roboto,system-ui,sans-serif;
font-weight: light;
border: 1px solid;
border: 1px solid;
border-color: #959595;
border-radius: 5px;
outline: none;
}
.c1:focus {
outline: none;
.c1:not([readOnly]):not([disabled]):focus {
border: 1px solid #fd9800;
}
@ -39,12 +41,15 @@ exports[`component.UI.Input should render correctly 1`] = `
>
<input
className="c1"
name="name"
color="primaryText"
fontFamily="sans"
fontSize="m"
fontWeight="light"
onBlur={[Function]}
onChange={[Function]}
outline="none"
type="text"
onFocus={[Function]}
value=""
width={1}
/>
</div>
</form>

100
test/unit/utils/crypto.spec.js

@ -0,0 +1,100 @@
/* eslint-disable max-len */
import { isLn, isOnchain } from 'lib/utils/crypto'
const VALID_BITCOIN_MAINNET_LN =
'lnbc10u1pduey89pp57gt0mqvh9gv4m5kkxmy9a0a46ha5jlzr3mcfcz2fx8tzu63vpjksdq8w3jhxaqcqzystfg0drarrx89nvpegwykvfr4fypvwz2d9ktcr6tj5s08f0nn8gdjnv74y9amksk3rjw7englhjrsev70k77vwf603qh2pr4tnqeue6qp5n92gy'
const VALID_BITCOIN_TESTNET_LN =
'lntb10u1pdue0gxpp5uljstna5aenp3yku3ft4wn8y63qfqdgqfh4cqqxz8z58undqp8hqdqqcqzysxqyz5vqqwaeuuh0fy52tqx6rrq6kya4lwm6v523wyqe9nesd5a3mszcq7j4e9mv8rd2vhmp7ycxswtktvs8gqq8lu5awjwfevnvfc4rzp8fmacpp4h27e'
const VALID_LITECOIN_MAINNET_LN =
'lnltc241pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66859t2d55efrxdlgqg9hdqskfstdmyssdw4fjc8qdl522ct885pqk7acn2aczh0jeht0xhuhnkmm3h0qsrxedlwm9x86787zzn4qwwwcpjkl3t2'
const VALID_LITECOIN_TESTNET_LN =
'lntltc241pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66m2eq2fx9uctzkmj30meaghyskkgsd6geap5qg9j2ae444z24a4p8xg3a6g73p8l7d689vtrlgzj0wyx2h6atq8dfty7wmkt4frx9g9sp730h5a'
describe('Crypto.isLn', () => {
describe('Bitcoin', () => {
describe('Mainnet', () => {
it('should pass with a valid invoice ', () => {
expect(isLn(VALID_BITCOIN_MAINNET_LN, 'bitcoin', 'mainnet')).toBeTruthy()
})
it('should fail with an invalid invoice ', () => {
expect(isLn(VALID_BITCOIN_TESTNET_LN, 'bitcoin', 'mainnet')).toBeFalsy()
})
})
describe('Testnet', () => {
it('should pass with a valid invoice', () => {
expect(isLn(VALID_BITCOIN_TESTNET_LN, 'bitcoin', 'testnet')).toBeTruthy()
})
it('should fail with an invalid invoice ', () => {
expect(isLn(VALID_BITCOIN_MAINNET_LN, 'bitcoin', 'testnet')).toBeFalsy()
})
})
})
describe('Litecoin', () => {
describe('Mainnet', () => {
it('should pass with a valid invoice ', () => {
expect(isLn(VALID_LITECOIN_MAINNET_LN, 'litecoin', 'mainnet')).toBeTruthy()
})
it('should fail with an invalid invoice ', () => {
expect(isLn(VALID_LITECOIN_TESTNET_LN, 'litecoin', 'mainnet')).toBeFalsy()
})
})
describe('Testnet', () => {
it('should pass with a valid invoice', () => {
expect(isLn(VALID_LITECOIN_TESTNET_LN, 'litecoin', 'testnet')).toBeTruthy()
})
it('should fail with an invalid invoice ', () => {
expect(isLn(VALID_LITECOIN_MAINNET_LN, 'litecoin', 'testnet')).toBeFalsy()
})
})
})
})
const VALID_BITCOIN_MAINNET = '3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC'
const VALID_BITCOIN_TESTNET = '2NBMEX8USTXf5uPiW16QYX2AETytrJBCK52'
const VALID_LITECOIN_MAINNET = 'LW9Q7QU8dgaGBerVkxbmVfurr2E1cFudrn'
const VALID_LITECOIN_TESTNET = '2MvCEgZGPnwA5HmZ3GDXB4EteMZzxgS54it'
describe('Crypto.isOnchain', () => {
describe('Bitcoin', () => {
describe('Mainnet', () => {
it('should pass with a valid address ', () => {
expect(isOnchain(VALID_BITCOIN_MAINNET, 'bitcoin', 'mainnet')).toBeTruthy()
})
it('should fail with an invalid address ', () => {
expect(isOnchain(VALID_BITCOIN_TESTNET, 'bitcoin', 'mainnet')).toBeFalsy()
})
})
describe('Testnet', () => {
it('should pass with a valid address', () => {
expect(isOnchain(VALID_BITCOIN_TESTNET, 'bitcoin', 'testnet')).toBeTruthy()
})
it('should fail with an invalid address ', () => {
expect(isOnchain(VALID_BITCOIN_MAINNET, 'bitcoin', 'testnet')).toBeFalsy()
})
})
})
describe('Litecoin', () => {
describe('Mainnet', () => {
it('should pass with a valid address ', () => {
expect(isOnchain(VALID_LITECOIN_MAINNET, 'litecoin', 'mainnet')).toBeTruthy()
})
// FIXME: TWe don't yet fully support litecoin, so this check always returns true for litecoin addresses
it('should pass with an invalid address ', () => {
expect(isOnchain(VALID_LITECOIN_TESTNET, 'litecoin', 'mainnet')).toBeTruthy()
})
})
describe('Testnet', () => {
it('should pass with a valid address', () => {
expect(isOnchain(VALID_LITECOIN_TESTNET, 'litecoin', 'testnet')).toBeTruthy()
})
// FIXME: TWe don't yet fully support litecoin, so this check always returns true for litecoin addresses
it('should pass with an invalid address ', () => {
expect(isOnchain(VALID_LITECOIN_MAINNET, 'litecoin', 'testnet')).toBeTruthy()
})
})
})
})

47
yarn.lock

@ -4263,7 +4263,7 @@ buffer@^4.3.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
buffer@^5.0.3, buffer@^5.1.0:
buffer@^5.1.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6"
integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==
@ -5460,7 +5460,7 @@ css-selector-tokenizer@^0.7.0:
fastparse "^1.1.1"
regexpu-core "^1.0.0"
css-to-react-native@^2.0.3, css-to-react-native@^2.2.2:
css-to-react-native@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.2.2.tgz#c077d0f7bf3e6c915a539e7325821c9dd01f9965"
integrity sha512-w99Fzop1FO8XKm0VpbQp3y5mnTnaS+rtCvS+ylSEOK76YXO5zoHQx/QMB1N54Cp+Ya9jB9922EHrh14ld4xmmw==
@ -7278,7 +7278,7 @@ fb-watchman@^2.0.0:
dependencies:
bser "^2.0.0"
fbjs@^0.8.12, fbjs@^0.8.16, fbjs@^0.8.4, fbjs@^0.8.5, fbjs@^0.8.9:
fbjs@^0.8.12, fbjs@^0.8.4, fbjs@^0.8.5, fbjs@^0.8.9:
version "0.8.17"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
@ -13333,16 +13333,16 @@ react-intl@^2.7.2:
intl-relativeformat "^2.1.0"
invariant "^2.1.1"
react-is@^16.3.1, react-is@^16.5.2:
version "16.5.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3"
integrity sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==
react-is@^16.3.2, react-is@^16.6.0:
version "16.6.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.6.0.tgz#456645144581a6e99f6816ae2bd24ee94bdd0c01"
integrity sha512-q8U7k0Fi7oxF1HvQgyBjPwDXeMplEsArnKt2iYhuIF86+GBbgLHdAmokL3XUFjTd7Q363OSNG55FOGUdONVn1g==
react-is@^16.5.2:
version "16.5.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3"
integrity sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==
react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
@ -15298,21 +15298,6 @@ style-search@^0.1.0:
resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=
styled-components@^3.4.5:
version "3.4.10"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-3.4.10.tgz#9a654c50ea2b516c36ade57ddcfa296bf85c96e1"
integrity sha512-TA8ip8LoILgmSAFd3r326pKtXytUUGu5YWuqZcOQVwVVwB6XqUMn4MHW2IuYJ/HAD81jLrdQed8YWfLSG1LX4Q==
dependencies:
buffer "^5.0.3"
css-to-react-native "^2.0.3"
fbjs "^0.8.16"
hoist-non-react-statics "^2.5.0"
prop-types "^15.5.4"
react-is "^16.3.1"
stylis "^3.5.0"
stylis-rule-sheet "^0.0.10"
supports-color "^3.2.3"
styled-components@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-4.0.3.tgz#6c1a95a93857aa613fdfc26ad40899217100d8c3"
@ -15328,15 +15313,6 @@ styled-components@^4.0.3:
stylis-rule-sheet "^0.0.10"
supports-color "^5.5.0"
styled-system-html@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/styled-system-html/-/styled-system-html-2.0.2.tgz#2a95f7720eea44b33d0e29c77289f070fe3dd318"
integrity sha512-QtqV+xc16ogG9ni+k/ZLytQ7Q5mpd+jCd1SioUUgm/6YaptSRop5+2qiKLL4acUxIV3liKuZbGyhxGsWeA0Dlg==
dependencies:
html-tags "^2.0.0"
styled-components "^3.4.5"
system-components "^3.0.0"
styled-system@^3.0.1, styled-system@^3.1.4:
version "3.1.11"
resolved "https://registry.yarnpkg.com/styled-system/-/styled-system-3.1.11.tgz#a91a38cf7a0f0e625b897a04fdd506a359a3629f"
@ -15551,13 +15527,6 @@ symbol.prototype.description@^1.0.0:
dependencies:
has-symbols "^1.0.0"
system-components@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/system-components/-/system-components-3.0.1.tgz#a4e7d8a7b184aa0612245a4f1bd54b408132e0c0"
integrity sha512-9awqnq7esNnqgmOOqU4i0w/9/YsJFm+1IS9lAyuk6a52IrcdToFqH7vFJmnr56FNLsXjOFohonmeHs0QOmvWeQ==
dependencies:
styled-system "^3.0.1"
table@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/table/-/table-4.0.3.tgz#00b5e2b602f1794b9acaf9ca908a76386a7813bc"

Loading…
Cancel
Save