Lots of updates

This commit is contained in:
Zhiyuan Zheng 2021-01-10 02:12:14 +01:00
parent 4a6229514f
commit 541e2a5601
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
28 changed files with 1001 additions and 530 deletions

View File

@ -43,9 +43,12 @@ const App: React.FC = () => {
}, [])
const onBeforeLift = useCallback(async () => {
const netInfoRes = await netInfo()
let netInfoRes = undefined
try {
netInfoRes = await netInfo()
} catch {}
if (netInfoRes.corrupted && netInfoRes.corrupted.length) {
if (netInfoRes && netInfoRes.corrupted && netInfoRes.corrupted.length) {
setLocalCorrupt(netInfoRes.corrupted)
}

View File

@ -30,7 +30,7 @@ describe('Testing component button', () => {
it('with icon only', () => {
const onPress = jest.fn()
const { getByTestId, toJSON } = render(
<Button type='icon' content='x' onPress={onPress} />
<Button type='icon' content='X' onPress={onPress} />
)
fireEvent.press(getByTestId('base'))

View File

@ -16,6 +16,7 @@ exports[`Testing component menu header with text only 1`] = `
Object {
"fontSize": 14,
"fontWeight": "600",
"lineHeight": 20,
},
Object {
"color": "rgb(135, 135, 135)",

View File

@ -15,7 +15,7 @@ exports[`Testing component menu row loading state 1`] = `
onStartShouldSetResponder={[Function]}
style={
Object {
"height": 50,
"minHeight": 50,
}
}
testID="base"
@ -34,28 +34,35 @@ exports[`Testing component menu row loading state 1`] = `
style={
Object {
"alignItems": "center",
"flex": 1,
"flexBasis": "70%",
"flex": 2,
"flexDirection": "row",
}
}
>
<Text
numberOfLines={1}
<View
style={
Array [
Object {
"flex": 1,
"fontSize": 16,
},
Object {
"color": "rgb(18, 18, 18)",
},
]
Object {
"flex": 1,
}
}
>
test
</Text>
<Text
numberOfLines={1}
style={
Array [
Object {
"fontSize": 16,
"lineHeight": 22,
},
Object {
"color": "rgb(18, 18, 18)",
},
]
}
>
test
</Text>
</View>
</View>
</View>
</View>
@ -76,7 +83,7 @@ exports[`Testing component menu row on press event 1`] = `
onStartShouldSetResponder={[Function]}
style={
Object {
"height": 50,
"minHeight": 50,
}
}
testID="base"
@ -95,28 +102,35 @@ exports[`Testing component menu row on press event 1`] = `
style={
Object {
"alignItems": "center",
"flex": 1,
"flexBasis": "70%",
"flex": 2,
"flexDirection": "row",
}
}
>
<Text
numberOfLines={1}
<View
style={
Array [
Object {
"flex": 1,
"fontSize": 16,
},
Object {
"color": "rgb(18, 18, 18)",
},
]
Object {
"flex": 1,
}
}
>
test
</Text>
<Text
numberOfLines={1}
style={
Array [
Object {
"fontSize": 16,
"lineHeight": 22,
},
Object {
"color": "rgb(18, 18, 18)",
},
]
}
>
test
</Text>
</View>
</View>
</View>
</View>
@ -137,7 +151,7 @@ exports[`Testing component menu row with title and content 1`] = `
onStartShouldSetResponder={[Function]}
style={
Object {
"height": 50,
"minHeight": 50,
}
}
testID="base"
@ -156,37 +170,44 @@ exports[`Testing component menu row with title and content 1`] = `
style={
Object {
"alignItems": "center",
"flex": 1,
"flexBasis": "70%",
"flex": 2,
"flexDirection": "row",
}
}
>
<Text
numberOfLines={1}
<View
style={
Array [
Object {
"flex": 1,
"fontSize": 16,
},
Object {
"color": "rgb(18, 18, 18)",
},
]
Object {
"flex": 1,
}
}
>
test title
</Text>
<Text
numberOfLines={1}
style={
Array [
Object {
"fontSize": 16,
"lineHeight": 22,
},
Object {
"color": "rgb(18, 18, 18)",
},
]
}
>
test title
</Text>
</View>
</View>
<View
style={
Object {
"alignItems": "center",
"flex": 1,
"flexBasis": "30%",
"flexDirection": "row",
"justifyContent": "flex-end",
"marginLeft": 16,
}
}
>
@ -196,6 +217,7 @@ exports[`Testing component menu row with title and content 1`] = `
Array [
Object {
"fontSize": 16,
"lineHeight": 22,
},
Object {
"color": "rgb(135, 135, 135)",
@ -226,7 +248,7 @@ exports[`Testing component menu row with title only 1`] = `
onStartShouldSetResponder={[Function]}
style={
Object {
"height": 50,
"minHeight": 50,
}
}
testID="base"
@ -245,28 +267,35 @@ exports[`Testing component menu row with title only 1`] = `
style={
Object {
"alignItems": "center",
"flex": 1,
"flexBasis": "70%",
"flex": 2,
"flexDirection": "row",
}
}
>
<Text
numberOfLines={1}
<View
style={
Array [
Object {
"flex": 1,
"fontSize": 16,
},
Object {
"color": "rgb(18, 18, 18)",
},
]
Object {
"flex": 1,
}
}
>
test title
</Text>
<Text
numberOfLines={1}
style={
Array [
Object {
"fontSize": 16,
"lineHeight": 22,
},
Object {
"color": "rgb(18, 18, 18)",
},
]
}
>
test title
</Text>
</View>
</View>
</View>
</View>

View File

@ -0,0 +1,59 @@
import React from 'react'
import {
toBeDisabled,
toContainElement,
toHaveStyle,
toHaveTextContent
} from '@testing-library/jest-native'
import { cleanup, render } from '@testing-library/react-native/pure'
import Card from '@components/Timelines/Timeline/Shared/Card'
expect.extend({
toBeDisabled,
toContainElement,
toHaveStyle,
toHaveTextContent
})
describe('Testing component timeline card', () => {
afterEach(cleanup)
it('with text only', () => {
const { getByTestId, queryByTestId, toJSON } = render(
<Card
card={{
url: 'http://example.com',
title: 'Title'
}}
/>
)
expect(queryByTestId('image')).toBeNull()
expect(getByTestId('base')).toContainElement(getByTestId('title'))
expect(queryByTestId('description')).toBeNull()
expect(getByTestId('title')).toHaveTextContent('Title')
expect(toJSON()).toMatchSnapshot()
})
it('with text and description', () => {
const { getByTestId, queryByTestId, toJSON } = render(
<Card
card={{
url: 'http://example.com',
title: 'Title',
description: 'Description'
}}
/>
)
expect(queryByTestId('image')).toBeNull()
expect(getByTestId('base')).toContainElement(getByTestId('title'))
expect(getByTestId('base')).toContainElement(getByTestId('description'))
expect(getByTestId('title')).toHaveTextContent('Title')
expect(getByTestId('description')).toHaveTextContent('Description')
expect(toJSON()).toMatchSnapshot()
})
})

View File

@ -0,0 +1,155 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Testing component timeline card with text and description 1`] = `
<View
accessible={true}
focusable={true}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Array [
Object {
"borderRadius": 6,
"borderWidth": 0.5,
"flex": 1,
"flexDirection": "row",
"height": 104,
"marginTop": 16,
},
Object {
"borderColor": "rgba(18, 18, 18, 0.3)",
},
]
}
testID="base"
>
<View
style={
Object {
"flex": 1,
"padding": 8,
}
}
>
<Text
numberOfLines={2}
style={
Array [
Object {
"fontWeight": "600",
"marginBottom": 4,
},
Object {
"color": "rgb(18, 18, 18)",
},
]
}
testID="title"
>
Title
</Text>
<Text
numberOfLines={1}
style={
Array [
Object {
"marginBottom": 4,
},
Object {
"color": "rgb(18, 18, 18)",
},
]
}
testID="description"
>
Description
</Text>
<Text
numberOfLines={1}
style={
Object {
"color": "rgb(135, 135, 135)",
}
}
>
http://example.com
</Text>
</View>
</View>
`;
exports[`Testing component timeline card with text only 1`] = `
<View
accessible={true}
focusable={true}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Array [
Object {
"borderRadius": 6,
"borderWidth": 0.5,
"flex": 1,
"flexDirection": "row",
"height": 104,
"marginTop": 16,
},
Object {
"borderColor": "rgba(18, 18, 18, 0.3)",
},
]
}
testID="base"
>
<View
style={
Object {
"flex": 1,
"padding": 8,
}
}
>
<Text
numberOfLines={2}
style={
Array [
Object {
"fontWeight": "600",
"marginBottom": 4,
},
Object {
"color": "rgb(18, 18, 18)",
},
]
}
testID="title"
>
Title
</Text>
<Text
numberOfLines={1}
style={
Object {
"color": "rgb(135, 135, 135)",
}
}
>
http://example.com
</Text>
</View>
</View>
`;

View File

@ -1,393 +1,474 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Testing component button static button apply custom styling 1`] = `
<View
accessible={true}
focusable={true}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Array [
Object {
"alignItems": "center",
"borderRadius": 100,
"flexDirection": "row",
"justifyContent": "center",
},
Object {
"backgroundColor": "rgb(250, 250, 250)",
"borderColor": "rgb(18, 18, 18)",
"borderWidth": 1,
"paddingHorizontal": 16,
"paddingVertical": 8,
},
Object {
"backgroundColor": "black",
},
]
}
testID="base"
>
<Text
<View>
<View
accessible={true}
focusable={true}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"color": "rgb(18, 18, 18)",
"fontSize": 16,
"fontWeight": undefined,
"opacity": 1,
}
Array [
Object {
"alignItems": "center",
"borderRadius": 100,
"flexDirection": "row",
"justifyContent": "center",
},
Object {
"backgroundColor": "rgb(250, 250, 250)",
"borderColor": "rgb(18, 18, 18)",
"borderWidth": 1,
"paddingHorizontal": 16,
"paddingVertical": 8,
},
Object {
"backgroundColor": "black",
},
]
}
testID="text"
testID="base"
>
test
</Text>
<Text
style={
Object {
"color": "rgb(18, 18, 18)",
"fontSize": 16,
"fontWeight": undefined,
"opacity": 1,
}
}
testID="text"
>
test
</Text>
</View>
</View>
`;
exports[`Testing component button static button disabled state 1`] = `
<View
accessible={true}
focusable={true}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Array [
Object {
"alignItems": "center",
"borderRadius": 100,
"flexDirection": "row",
"justifyContent": "center",
},
Object {
"backgroundColor": "rgb(250, 250, 250)",
"borderColor": "rgb(135, 135, 135)",
"borderWidth": 1,
"paddingHorizontal": 16,
"paddingVertical": 8,
},
undefined,
]
}
testID="base"
>
<Text
<View>
<View
accessible={true}
focusable={true}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"color": "rgb(135, 135, 135)",
"fontSize": 16,
"fontWeight": undefined,
"opacity": 1,
}
Array [
Object {
"alignItems": "center",
"borderRadius": 100,
"flexDirection": "row",
"justifyContent": "center",
},
Object {
"backgroundColor": "rgb(250, 250, 250)",
"borderColor": "rgb(135, 135, 135)",
"borderWidth": 1,
"paddingHorizontal": 16,
"paddingVertical": 8,
},
undefined,
]
}
testID="text"
testID="base"
>
test
</Text>
<Text
style={
Object {
"color": "rgb(135, 135, 135)",
"fontSize": 16,
"fontWeight": undefined,
"opacity": 1,
}
}
testID="text"
>
test
</Text>
</View>
</View>
`;
exports[`Testing component button static button loading state 1`] = `
<View
accessible={true}
focusable={true}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Array [
Object {
"alignItems": "center",
"borderRadius": 100,
"flexDirection": "row",
"justifyContent": "center",
},
Object {
"backgroundColor": "rgb(250, 250, 250)",
"borderColor": "rgb(135, 135, 135)",
"borderWidth": 1,
"paddingHorizontal": 16,
"paddingVertical": 8,
},
undefined,
]
}
testID="base"
>
<Text
style={
Object {
"color": "rgb(18, 18, 18)",
"fontSize": 16,
"fontWeight": undefined,
"opacity": 0,
}
}
testID="text"
>
test
</Text>
<View>
<View
accessible={true}
focusable={true}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"position": "absolute",
}
Array [
Object {
"alignItems": "center",
"borderRadius": 100,
"flexDirection": "row",
"justifyContent": "center",
},
Object {
"backgroundColor": "rgb(250, 250, 250)",
"borderColor": "rgb(135, 135, 135)",
"borderWidth": 1,
"paddingHorizontal": 16,
"paddingVertical": 8,
},
undefined,
]
}
testID="base"
>
<Text
style={
Object {
"color": "rgb(18, 18, 18)",
"fontSize": 16,
"fontWeight": undefined,
"opacity": 0,
}
}
testID="text"
>
test
</Text>
<View
style={
Object {
"alignItems": "center",
"height": 16,
"justifyContent": "center",
"opacity": 1,
"transform": Array [
Object {
"rotate": "0deg",
},
],
"width": 16,
"position": "absolute",
}
}
>
<View
style={
Object {
"backgroundColor": "rgb(135, 135, 135)",
"borderRadius": 2,
"height": 4,
"position": "absolute",
"transform": Array [
Object {
"rotate": "73.27536734311887deg",
},
Object {
"translateY": -6,
},
Object {
"scale": 0.7,
},
],
"width": 4,
}
}
/>
<View
style={
Object {
"backgroundColor": "rgb(135, 135, 135)",
"borderRadius": 2,
"height": 4,
"position": "absolute",
"transform": Array [
Object {
"rotate": "46.49829517703514deg",
},
Object {
"translateY": -6,
},
Object {
"scale": 0.8008696779414123,
},
],
"width": 4,
}
}
/>
<View
style={
Object {
"backgroundColor": "rgb(135, 135, 135)",
"borderRadius": 2,
"height": 4,
"position": "absolute",
"transform": Array [
Object {
"rotate": "25.743213498935145deg",
},
Object {
"translateY": -6,
},
Object {
"scale": 0.8875624559768125,
},
],
"width": 4,
}
}
/>
<View
style={
Object {
"backgroundColor": "rgb(135, 135, 135)",
"borderRadius": 2,
"height": 4,
"position": "absolute",
"transform": Array [
Object {
"rotate": "11.201058030774364deg",
},
Object {
"translateY": -6,
},
Object {
"scale": 0.9510040862404615,
},
],
"width": 4,
}
}
/>
<View
style={
Object {
"backgroundColor": "rgb(135, 135, 135)",
"borderRadius": 2,
"height": 4,
"position": "absolute",
"transform": Array [
Object {
"rotate": "2.731234791722257deg",
},
Object {
"translateY": -6,
},
Object {
"scale": 0.9881665278710133,
},
],
"width": 4,
}
}
/>
<View
style={
Object {
"backgroundColor": "rgb(135, 135, 135)",
"borderRadius": 2,
"height": 4,
"position": "absolute",
"alignItems": "center",
"height": 16,
"justifyContent": "center",
"opacity": 1,
"transform": Array [
Object {
"rotate": "0deg",
},
Object {
"translateY": -6,
},
Object {
"scale": 1,
},
],
"width": 4,
"width": 16,
}
}
/>
>
<View
style={
Object {
"backgroundColor": "rgb(135, 135, 135)",
"borderRadius": 2,
"height": 4,
"position": "absolute",
"transform": Array [
Object {
"rotate": "73.27536734311887deg",
},
Object {
"translateY": -6,
},
Object {
"scale": 0.7,
},
],
"width": 4,
}
}
/>
<View
style={
Object {
"backgroundColor": "rgb(135, 135, 135)",
"borderRadius": 2,
"height": 4,
"position": "absolute",
"transform": Array [
Object {
"rotate": "46.49829517703514deg",
},
Object {
"translateY": -6,
},
Object {
"scale": 0.8008696779414123,
},
],
"width": 4,
}
}
/>
<View
style={
Object {
"backgroundColor": "rgb(135, 135, 135)",
"borderRadius": 2,
"height": 4,
"position": "absolute",
"transform": Array [
Object {
"rotate": "25.743213498935145deg",
},
Object {
"translateY": -6,
},
Object {
"scale": 0.8875624559768125,
},
],
"width": 4,
}
}
/>
<View
style={
Object {
"backgroundColor": "rgb(135, 135, 135)",
"borderRadius": 2,
"height": 4,
"position": "absolute",
"transform": Array [
Object {
"rotate": "11.201058030774364deg",
},
Object {
"translateY": -6,
},
Object {
"scale": 0.9510040862404615,
},
],
"width": 4,
}
}
/>
<View
style={
Object {
"backgroundColor": "rgb(135, 135, 135)",
"borderRadius": 2,
"height": 4,
"position": "absolute",
"transform": Array [
Object {
"rotate": "2.731234791722257deg",
},
Object {
"translateY": -6,
},
Object {
"scale": 0.9881665278710133,
},
],
"width": 4,
}
}
/>
<View
style={
Object {
"backgroundColor": "rgb(135, 135, 135)",
"borderRadius": 2,
"height": 4,
"position": "absolute",
"transform": Array [
Object {
"rotate": "0deg",
},
Object {
"translateY": -6,
},
Object {
"scale": 1,
},
],
"width": 4,
}
}
/>
</View>
</View>
</View>
</View>
`;
exports[`Testing component button static button with icon only 1`] = `
<View
accessible={true}
focusable={true}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Array [
Object {
"alignItems": "center",
"borderRadius": 100,
"flexDirection": "row",
"justifyContent": "center",
},
Object {
"backgroundColor": "rgb(250, 250, 250)",
"borderColor": "rgb(18, 18, 18)",
"borderWidth": 1,
"paddingHorizontal": 16,
"paddingVertical": 8,
},
undefined,
]
}
testID="base"
>
<Text />
<View>
<View
accessible={true}
focusable={true}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Array [
Object {
"alignItems": "center",
"borderRadius": 100,
"flexDirection": "row",
"justifyContent": "center",
},
Object {
"backgroundColor": "rgb(250, 250, 250)",
"borderColor": "rgb(18, 18, 18)",
"borderWidth": 1,
"paddingHorizontal": 16,
"paddingVertical": 8,
},
undefined,
]
}
testID="base"
>
<View
style={
Array [
Object {
"opacity": 1,
},
Object {
"alignItems": "center",
"height": 16,
"justifyContent": "center",
"width": 16,
},
]
}
>
<RNSVGSvgView
align="xMidYMid"
bbHeight={16}
bbWidth={16}
className=""
color={4279374354}
focusable={false}
height={16}
meetOrSlice={0}
minX={0}
minY={0}
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
style={
Array [
Object {
"backgroundColor": "transparent",
"borderWidth": 0,
},
Object {
"flex": 0,
"height": 16,
"width": 16,
},
]
}
tintColor={4279374354}
vbHeight={24}
vbWidth={24}
width={16}
>
<RNSVGGroup
propList={
Array [
"stroke",
"strokeWidth",
"strokeLinecap",
"strokeLinejoin",
]
}
stroke={
Array [
2,
]
}
strokeLinecap={1}
strokeLinejoin={1}
strokeWidth={2}
>
<RNSVGPath
d="M18 6L6 18M6 6l12 12"
/>
</RNSVGGroup>
</RNSVGSvgView>
</View>
</View>
</View>
`;
exports[`Testing component button static button with text only 1`] = `
<View
accessible={true}
focusable={true}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Array [
Object {
"alignItems": "center",
"borderRadius": 100,
"flexDirection": "row",
"justifyContent": "center",
},
Object {
"backgroundColor": "rgb(250, 250, 250)",
"borderColor": "rgb(18, 18, 18)",
"borderWidth": 1,
"paddingHorizontal": 16,
"paddingVertical": 8,
},
undefined,
]
}
testID="base"
>
<Text
<View>
<View
accessible={true}
focusable={true}
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"color": "rgb(18, 18, 18)",
"fontSize": 16,
"fontWeight": undefined,
"opacity": 1,
}
Array [
Object {
"alignItems": "center",
"borderRadius": 100,
"flexDirection": "row",
"justifyContent": "center",
},
Object {
"backgroundColor": "rgb(250, 250, 250)",
"borderColor": "rgb(18, 18, 18)",
"borderWidth": 1,
"paddingHorizontal": 16,
"paddingVertical": 8,
},
undefined,
]
}
testID="text"
testID="base"
>
Test Button
</Text>
<Text
style={
Object {
"color": "rgb(18, 18, 18)",
"fontSize": 16,
"fontWeight": undefined,
"opacity": 1,
}
}
testID="text"
>
Test Button
</Text>
</View>
</View>
`;

31
jest.config.js Normal file
View File

@ -0,0 +1,31 @@
module.exports = {
preset: 'jest-expo',
collectCoverage: true,
collectCoverageFrom: [
'src /**/*.{ts,tsx}',
'!**/coverage /**',
'!**/node_modules /**',
'!**/app.config.ts',
'!**/babel.config.js',
'!**/jest.setup.ts'
],
setupFiles: [
'<rootDir>/jest/async-storage.js',
'<rootDir>/jest/react-native.js',
'<rootDir>/jest/react-navigation.js'
],
transformIgnorePatterns: [
'node_modules/(?!(jest-)?react-native' +
'|react-clone-referenced-element' +
'|@react-native-community' +
'|expo(nent)?' +
'|@expo(nent)?/.*' +
'|react-navigation' +
'|@react-navigation/.*|@unimodules/.*|unimodules' +
'|sentry-expo' +
'|native-base' +
'|@sentry/.*' +
'|redux-persist-expo-securestore' +
')'
]
}

3
jest/async-storage.js Normal file
View File

@ -0,0 +1,3 @@
import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock'
jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage)

View File

@ -6,7 +6,7 @@
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject",
"test": "jest"
"test": "jest --watchAll"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.13.2",
@ -95,27 +95,9 @@
"chalk": "^4.1.0",
"jest": "^26.6.3",
"jest-expo": "^40.0.1",
"nock": "^13.0.5",
"react-test-renderer": "^16.13.1",
"typescript": "~4.1.3"
},
"jest": {
"preset": "jest-expo",
"collectCoverage": true,
"collectCoverageFrom": [
"src /**/*.{ts,tsx}",
"!**/coverage /**",
"!**/node_modules /**",
"!**/app.config.ts",
"!**/babel.config.js",
"!**/jest.setup.ts"
],
"setupFiles": [
"<rootDir>/jest/react-native.js",
"<rootDir>/jest/react-navigation.js"
],
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*)"
]
},
"private": true
}
}

View File

@ -58,7 +58,7 @@ const Button: React.FC<Props> = ({
} else {
mounted.current = true
}
}, [content, loading, disabled])
}, [content, loading, disabled, active])
const loadingSpinkit = useMemo(
() => (

View File

@ -5,15 +5,19 @@ import { useNavigation } from '@react-navigation/native'
import hookApps from '@utils/queryHooks/apps'
import hookInstance from '@utils/queryHooks/instance'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { InstanceLocal, remoteUpdate } from '@utils/slices/instancesSlice'
import {
getLocalInstances,
InstanceLocal,
remoteUpdate
} from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import { debounce } from 'lodash'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Image, StyleSheet, Text, TextInput, View } from 'react-native'
import { Alert, Image, StyleSheet, Text, TextInput, View } from 'react-native'
import { useQueryClient } from 'react-query'
import { useDispatch } from 'react-redux'
import { useDispatch, useSelector } from 'react-redux'
import InstanceAuth from './Instance/Auth'
import InstanceInfo from './Instance/Info'
import { toast } from './toast'
@ -36,6 +40,7 @@ const ComponentInstance: React.FC<Props> = ({
const { theme } = useTheme()
const [instanceDomain, setInstanceDomain] = useState<string | undefined>()
const [appData, setApplicationData] = useState<InstanceLocal['appData']>()
const localInstances = useSelector(getLocalInstances)
const instanceQuery = hookInstance({
instanceDomain,
@ -79,12 +84,32 @@ const ComponentInstance: React.FC<Props> = ({
const processUpdate = useCallback(() => {
if (instanceDomain) {
haptics('Success')
switch (type) {
case 'local':
applicationQuery.refetch()
return
if (
localInstances &&
localInstances.filter(instance => instance.url === instanceDomain)
.length
) {
Alert.alert(
'域名已存在',
'可以登录同个域名的另外一个账户,现有账户🈚️用',
[
{ text: '取消', style: 'cancel' },
{
text: '继续',
onPress: () => {
applicationQuery.refetch()
}
}
]
)
} else {
applicationQuery.refetch()
}
break
case 'remote':
haptics('Success')
const queryKey: QueryKeyTimeline = [
'Timeline',
{ page: 'RemotePublic' }
@ -92,8 +117,8 @@ const ComponentInstance: React.FC<Props> = ({
dispatch(remoteUpdate(instanceDomain))
queryClient.resetQueries(queryKey)
toast({ type: 'success', message: '重置成功' })
navigation.navigate('Screen-Remote-Root')
return
navigation.navigate('Screen-Public', { screen: 'Screen-Public-Root' })
break
}
}
}, [instanceDomain])
@ -160,7 +185,7 @@ const ComponentInstance: React.FC<Props> = ({
content={buttonContent}
onPress={processUpdate}
disabled={!instanceQuery.data?.uri}
loading={instanceQuery.isFetching || applicationQuery.isFetching}
loading={instanceQuery.isLoading || applicationQuery.isLoading}
/>
</View>
<View>

View File

@ -1,12 +1,12 @@
import Icon from '@components/Icon'
import openLink from '@components/openLink'
import ParseEmojis from '@components/Parse/Emojis'
import { useNavigation } from '@react-navigation/native'
import { useNavigation, useRoute } from '@react-navigation/native'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import { LinearGradient } from 'expo-linear-gradient'
import React, { useCallback, useState } from 'react'
import { Image, Pressable, Text, View } from 'react-native'
import { Pressable, Text, View } from 'react-native'
import HTMLView from 'react-native-htmlview'
import Animated, {
useAnimatedStyle,
@ -16,6 +16,7 @@ import Animated, {
// Prevent going to the same hashtag multiple times
const renderNode = ({
routeParams,
theme,
node,
index,
@ -26,6 +27,7 @@ const renderNode = ({
showFullLink,
disableDetails
}: {
routeParams?: any
theme: any
node: any
index: number
@ -42,6 +44,10 @@ const renderNode = ({
const href = node.attribs.href
if (classes) {
if (classes.includes('hashtag')) {
const tag = href.split(new RegExp(/\/tag\/(.*)|\/tags\/(.*)/))
const differentTag = routeParams?.hashtag
? routeParams.hashtag !== tag[1] && routeParams.hashtag !== tag[2]
: true
return (
<Text
key={index}
@ -50,8 +56,8 @@ const renderNode = ({
...StyleConstants.FontStyle[size]
}}
onPress={() => {
const tag = href.split(new RegExp(/\/tag\/(.*)|\/tags\/(.*)/))
!disableDetails &&
differentTag &&
navigation.push('Screen-Shared-Hashtag', {
hashtag: tag[1] || tag[2]
})
@ -65,6 +71,9 @@ const renderNode = ({
const accountIndex = mentions.findIndex(
mention => mention.url === href
)
const differentAccount = routeParams?.account
? routeParams.account.id !== mentions[accountIndex].id
: true
return (
<Text
key={index}
@ -75,6 +84,7 @@ const renderNode = ({
onPress={() => {
accountIndex !== -1 &&
!disableDetails &&
differentAccount &&
navigation.push('Screen-Shared-Account', {
account: mentions[accountIndex]
})
@ -151,11 +161,13 @@ const ParseHTML: React.FC<Props> = ({
disableDetails = false
}) => {
const navigation = useNavigation()
const route = useRoute()
const { theme } = useTheme()
const renderNodeCallback = useCallback(
(node, index) =>
renderNode({
routeParams: route.params,
theme,
node,
index,

View File

@ -2,6 +2,7 @@ import client from '@api/client'
import Button from '@components/Button'
import haptics from '@components/haptics'
import { toast } from '@components/toast'
import { QueryKeyRelationship } from '@utils/queryHooks/relationship'
import { StyleConstants } from '@utils/styles/constants'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
@ -15,7 +16,7 @@ export interface Props {
const RelationshipIncoming: React.FC<Props> = ({ id }) => {
const { t } = useTranslation()
const relationshipQueryKey = ['Relationship', { id }]
const queryKeyRelationship: QueryKeyRelationship = ['Relationship', { id }]
const queryClient = useQueryClient()
const fireMutation = useCallback(
@ -31,7 +32,7 @@ const RelationshipIncoming: React.FC<Props> = ({ id }) => {
const mutation = useMutation(fireMutation, {
onSuccess: res => {
haptics('Success')
queryClient.setQueryData(relationshipQueryKey, res)
queryClient.setQueryData(queryKeyRelationship, res)
queryClient.refetchQueries(['Notifications'])
},
onError: (err: any, { type }) => {

View File

@ -2,7 +2,9 @@ import client from '@api/client'
import Button from '@components/Button'
import haptics from '@components/haptics'
import { toast } from '@components/toast'
import hookRelationship from '@utils/queryHooks/relationship'
import hookRelationship, {
QueryKeyRelationship
} from '@utils/queryHooks/relationship'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useMutation, useQueryClient } from 'react-query'
@ -14,7 +16,7 @@ export interface Props {
const RelationshipOutgoing: React.FC<Props> = ({ id }) => {
const { t } = useTranslation()
const relationshipQueryKey = ['Relationship', { id }]
const queryKeyRelationship: QueryKeyRelationship = ['Relationship', { id }]
const query = hookRelationship({ id })
const queryClient = useQueryClient()
@ -31,7 +33,7 @@ const RelationshipOutgoing: React.FC<Props> = ({ id }) => {
const mutation = useMutation(fireMutation, {
onSuccess: res => {
haptics('Success')
queryClient.setQueryData(relationshipQueryKey, res)
queryClient.setQueryData(queryKeyRelationship, res)
},
onError: (err: any, { type }) => {
haptics('Error')

View File

@ -16,11 +16,12 @@ const Stack = createNativeStackNavigator<
>()
export interface Props {
name: 'Screen-Local-Root' | 'Screen-Public-Root'
content: { title: string; page: App.Pages }[]
name: 'Local' | 'Public'
content: { title: string; page: App.Pages; remote?: boolean }[]
}
const Timelines: React.FC<Props> = ({ name, content }) => {
const remoteUrl = useSelector(getRemoteUrl)
const navigation = useNavigation()
const { mode } = useTheme()
const localActiveIndex = useSelector(getLocalActiveIndex)
@ -71,16 +72,19 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
<Stack.Navigator screenOptions={{ headerHideShadow: true }}>
<Stack.Screen
// @ts-ignore
name={name}
name={`Screen-${name}-Root`}
component={screenComponent}
options={{
headerTitle: name === 'Screen-Public-Root' ? publicDomain : '',
headerTitle: name === 'Public' ? publicDomain : '',
...(localActiveIndex !== null && {
headerCenter: () => (
<View style={styles.segmentsContainer}>
<SegmentedControl
appearance={mode}
values={[content[0].title, content[1].title]}
values={[
content[0].title,
content[1].remote ? remoteUrl : content[1].title
]}
selectedIndex={segment}
onChange={({ nativeEvent }) =>
setSegment(nativeEvent.selectedSegmentIndex)
@ -102,7 +106,7 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
const styles = StyleSheet.create({
segmentsContainer: {
flexBasis: '60%'
flexBasis: '65%'
}
})

View File

@ -16,11 +16,9 @@ const TimelineHeader = React.memo(
{' '}
<Text
style={{ color: theme.blue }}
onPress={() =>
navigation.navigate('Screen-Me', {
screen: 'Screen-Me-Settings-UpdateRemote'
})
}
onPress={() => {
navigation.navigate('Screen-Me')
}}
>
{' '}
<Icon

View File

@ -13,22 +13,14 @@ export interface Props {
const TimelineCard: React.FC<Props> = ({ card }) => {
const { theme } = useTheme()
let isMounted = false
useEffect(() => {
isMounted = true
return () => {
isMounted = false
}
})
const [imageLoaded, setImageLoaded] = useState(false)
useEffect(() => {
const preFetch = () =>
card.image &&
isMounted &&
Image.getSize(card.image, () => isMounted && setImageLoaded(true))
preFetch()
}, [isMounted])
const preFetch = () => Image.getSize(card.image, () => setImageLoaded(true))
if (card.image) {
preFetch()
}
}, [])
const cardVisual = useMemo(() => {
if (imageLoaded) {
return <Image source={{ uri: card.image }} style={styles.image} />
@ -45,12 +37,18 @@ const TimelineCard: React.FC<Props> = ({ card }) => {
<Pressable
style={[styles.card, { borderColor: theme.border }]}
onPress={async () => await openLink(card.url)}
testID='base'
>
{card.image && <View style={styles.left}>{cardVisual}</View>}
{card.image && (
<View style={styles.left} testID='image'>
{cardVisual}
</View>
)}
<View style={styles.right}>
<Text
numberOfLines={2}
style={[styles.rightTitle, { color: theme.primary }]}
testID='title'
>
{card.title}
</Text>
@ -58,6 +56,7 @@ const TimelineCard: React.FC<Props> = ({ card }) => {
<Text
numberOfLines={1}
style={[styles.rightDescription, { color: theme.primary }]}
testID='description'
>
{card.description}
</Text>

View File

@ -7,7 +7,7 @@ const ScreenLocal: React.FC = () => {
return (
<Timelines
name='Screen-Local-Root'
name='Local'
content={[
{ title: t('local:heading.segments.left'), page: 'Following' },
{ title: t('local:heading.segments.right'), page: 'Local' }

View File

@ -6,8 +6,8 @@ import ScreenMeLists from '@screens/Me/Lists'
import ScreenMeRoot from '@screens/Me/Root'
import ScreenMeListsList from '@screens/Me/Root/Lists/List'
import ScreenMeSettings from '@screens/Me/Settings'
import UpdateRemote from '@screens/Me/Settings/UpdateRemote'
import ScreenMeSwitch from '@screens/Me/Switch'
import UpdateRemote from '@screens/Me/UpdateRemote'
import sharedScreens from '@screens/Shared/sharedScreens'
import React from 'react'
import { useTranslation } from 'react-i18next'

View File

@ -20,13 +20,13 @@ import { useDispatch, useSelector } from 'react-redux'
interface Props {
index: NonNullable<InstancesState['local']['activeIndex']>
instance: InstanceLocal
active?: boolean
disabled?: boolean
}
const AccountButton: React.FC<Props> = ({
index,
instance,
active = false
disabled = false
}) => {
const queryClient = useQueryClient()
const navigation = useNavigation()
@ -40,7 +40,7 @@ const AccountButton: React.FC<Props> = ({
return (
<Button
type='text'
active={active}
disabled={disabled}
loading={isLoading}
style={styles.button}
content={`@${data?.acct || '...'}@${instance.url}`}
@ -78,7 +78,7 @@ const ScreenMeSwitchRoot = () => {
key={index}
index={index}
instance={instance}
active={localActiveIndex === index}
disabled={localActiveIndex === index}
/>
))
: null}

View File

@ -1,4 +1,5 @@
import Timelines from '@components/Timelines'
import { getRemoteUrl } from '@utils/slices/instancesSlice'
import React from 'react'
import { useTranslation } from 'react-i18next'
@ -7,10 +8,14 @@ const ScreenPublic: React.FC = () => {
return (
<Timelines
name='Screen-Public-Root'
name='Public'
content={[
{ title: t('public:heading.segments.left'), page: 'LocalPublic' },
{ title: t('public:heading.segments.right'), page: 'RemotePublic' }
{
title: t('public:heading.segments.right'),
page: 'RemotePublic',
remote: true
}
]}
/>
)

View File

@ -17,6 +17,7 @@ import {
Text,
View
} from 'react-native'
import { Chase } from 'react-native-animated-spinkit'
import { FlatList, ScrollView } from 'react-native-gesture-handler'
import { SafeAreaView } from 'react-native-safe-area-context'
import { useMutation } from 'react-query'
@ -199,6 +200,20 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
[]
)
const ListEmptyComponent = useCallback(() => {
return (
<View
style={{
width: Dimensions.get('screen').width,
justifyContent: 'center',
alignItems: 'center'
}}
>
<Chase size={StyleConstants.Font.Size.L} color={theme.secondary} />
</View>
)
}, [])
return (
<SafeAreaView style={[styles.base, { backgroundColor: theme.background }]}>
<View style={[styles.header, { height: bottomTabBarHeight }]}>
@ -211,6 +226,7 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
renderItem={renderItem}
showsHorizontalScrollIndicator={false}
onMomentumScrollEnd={onMomentumScrollEnd}
ListEmptyComponent={ListEmptyComponent}
/>
<View style={[styles.indicators, { height: bottomTabBarHeight }]}>
{data && data.length > 1 ? (

View File

@ -17,7 +17,7 @@ import {
NativeStackNavigatorProps
} from 'react-native-screens/lib/typescript/types'
type BaseScreens =
export type BaseScreens =
| Nav.LocalStackParamList
| Nav.RemoteStackParamList
| Nav.NotificationsStackParamList

View File

@ -2,12 +2,15 @@ import client from '@api/client'
import { AxiosError } from 'axios'
import { useQuery, UseQueryOptions } from 'react-query'
export type QueryKey = ['Relationship', { id: Mastodon.Account['id'] }]
export type QueryKeyRelationship = [
'Relationship',
{ id: Mastodon.Account['id'] }
]
const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
const queryFunction = ({ queryKey }: { queryKey: QueryKeyRelationship }) => {
const { id } = queryKey[1]
return client<Mastodon.Relationship>({
return client<Mastodon.Relationship[]>({
method: 'get',
instance: 'local',
url: `accounts/relationships`,
@ -17,14 +20,21 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
})
}
const hookRelationship = <TData = Mastodon.Relationship>({
const hookRelationship = ({
options,
...queryKeyParams
}: QueryKey[1] & {
options?: UseQueryOptions<Mastodon.Relationship, AxiosError, TData>
}: QueryKeyRelationship[1] & {
options?: UseQueryOptions<
Mastodon.Relationship[],
AxiosError,
Mastodon.Relationship
>
}) => {
const queryKey: QueryKey = ['Relationship', { ...queryKeyParams }]
return useQuery(queryKey, queryFunction, options)
const queryKey: QueryKeyRelationship = ['Relationship', { ...queryKeyParams }]
return useQuery(queryKey, queryFunction, {
...options,
select: data => data[0]
})
}
export default hookRelationship

View File

@ -55,9 +55,10 @@ export const localAddInstance = createAsyncThunk(
url: InstanceLocal['url']
token: InstanceLocal['token']
appData: InstanceLocal['appData']
}): Promise<InstanceLocal> => {
const store = require('@root/store')
const state = store.getState().instances
}): Promise<{ type: 'add' | 'overwrite'; data: InstanceLocal }> => {
const { store } = require('@root/store')
const instanceLocal: InstancesState['local'] = store.getState().instances
.local
const { id } = await client<Mastodon.Account>({
method: 'get',
@ -67,14 +68,24 @@ export const localAddInstance = createAsyncThunk(
headers: { Authorization: `Bearer ${token}` }
})
// Overwrite existing account?
// if (
// state.local.instances.filter(
// instance => instance && instance.account && instance.account.id === id
// ).length
// ) {
// return Promise.reject()
// }
let type: 'add' | 'overwrite'
if (
instanceLocal.instances.filter(instance => {
if (instance) {
if (instance.url === url && instance.account.id === id) {
return true
} else {
return false
}
} else {
return false
}
}).length
) {
type = 'overwrite'
} else {
type = 'add'
}
const preferences = await client<Mastodon.Preferences>({
method: 'get',
@ -85,15 +96,18 @@ export const localAddInstance = createAsyncThunk(
})
return Promise.resolve({
appData,
url,
token,
account: {
id,
preferences
},
notification: {
unread: false
type,
data: {
appData,
url,
token,
account: {
id,
preferences
},
notification: {
unread: false
}
}
})
}
@ -101,31 +115,37 @@ export const localAddInstance = createAsyncThunk(
export const localRemoveInstance = createAsyncThunk(
'instances/localRemoveInstance',
async (index?: InstancesState['local']['activeIndex']): Promise<number> => {
const store = require('@root/store')
const local = store.getState().instances.local
const { store } = require('@root/store')
const instanceLocal: InstancesState['local'] = store.getState().instances
.local
if (index) {
return Promise.resolve(index)
} else {
if (local.activeIndex !== null) {
const currentInstance = local.instances[local.activeIndex]
if (instanceLocal.activeIndex !== null) {
const currentInstance =
instanceLocal.instances[instanceLocal.activeIndex]
let revoked = undefined
try {
revoked = await AuthSession.revokeAsync(
{
clientId: currentInstance.appData.clientId,
clientSecret: currentInstance.appData.clientSecret,
token: currentInstance.token,
scopes: ['read', 'write', 'follow', 'push']
},
{
revocationEndpoint: `https://${currentInstance.url}/oauth/revoke`
}
)
} catch {}
const revoked = await AuthSession.revokeAsync(
{
clientId: currentInstance.appData.clientId,
clientSecret: currentInstance.appData.clientSecret,
token: currentInstance.token,
scopes: ['read', 'write', 'follow', 'push']
},
{
revocationEndpoint: `https://${currentInstance.url}/oauth/revoke`
}
)
if (!revoked) {
console.warn('Revoking error')
}
return Promise.resolve(local.activeIndex)
return Promise.resolve(instanceLocal.activeIndex)
} else {
throw new Error('Active index invalid, cannot remove instance')
}
@ -176,8 +196,23 @@ const instancesSlice = createSlice({
extraReducers: builder => {
builder
.addCase(localAddInstance.fulfilled, (state, action) => {
state.local.instances.push(action.payload)
state.local.activeIndex = state.local.instances.length - 1
switch (action.payload.type) {
case 'add':
state.local.instances.push(action.payload.data)
state.local.activeIndex = state.local.instances.length - 1
break
case 'overwrite':
state.local.instances = state.local.instances.map(instance => {
if (
instance.url === action.payload.data.url &&
instance.account.id === action.payload.data.account.id
) {
return action.payload.data
} else {
return instance
}
})
}
analytics('login')
})

View File

@ -6513,7 +6513,7 @@ json-stable-stringify@^1.0.1:
dependencies:
jsonify "~0.0.0"
json-stringify-safe@~5.0.1:
json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
@ -6715,6 +6715,11 @@ lodash.pick@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=
lodash.set@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=
lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
@ -7384,6 +7389,16 @@ nocache@^2.1.0:
resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.1.0.tgz#120c9ffec43b5729b1d5de88cd71aa75a0ba491f"
integrity sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==
nock@^13.0.5:
version "13.0.5"
resolved "https://registry.yarnpkg.com/nock/-/nock-13.0.5.tgz#a618c6f86372cb79fac04ca9a2d1e4baccdb2414"
integrity sha512-1ILZl0zfFm2G4TIeJFW0iHknxr2NyA+aGCMTjDVUsBY4CkMRispF1pfIYkTRdAR/3Bg+UzdEuK0B6HczMQZcCg==
dependencies:
debug "^4.1.0"
json-stringify-safe "^5.0.1"
lodash.set "^4.3.2"
propagate "^2.0.0"
node-fetch@2.6.1, node-fetch@^2.0.0-alpha.8, node-fetch@^2.2.0, node-fetch@^2.6.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
@ -8032,6 +8047,11 @@ prop-types@^15.6.2, prop-types@^15.7.2:
object-assign "^4.1.1"
react-is "^16.8.1"
propagate@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45"
integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"