Skip to content

Commit 51eece7

Browse files
committed
WIP: add Example and TDD for ex.
1 parent 5ce72c3 commit 51eece7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+12534
-0
lines changed

Example/.buckconfig

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
[android]
3+
target = Google Inc.:Google APIs:23
4+
5+
[maven_repositories]
6+
central = https://repo1.maven.org/maven2

Example/.eslintrc.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
root: true,
3+
extends: '@react-native-community',
4+
};

Example/.flowconfig

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
[ignore]
2+
; We fork some components by platform
3+
.*/*[.]android.js
4+
5+
; Ignore "BUCK" generated dirs
6+
<PROJECT_ROOT>/\.buckd/
7+
8+
; Ignore polyfills
9+
node_modules/react-native/Libraries/polyfills/.*
10+
11+
; These should not be required directly
12+
; require from fbjs/lib instead: require('fbjs/lib/warning')
13+
node_modules/warning/.*
14+
15+
; Flow doesn't support platforms
16+
.*/Libraries/Utilities/LoadingView.js
17+
18+
[untyped]
19+
.*/node_modules/@react-native-community/cli/.*/.*
20+
21+
[include]
22+
23+
[libs]
24+
node_modules/react-native/interface.js
25+
node_modules/react-native/flow/
26+
27+
[options]
28+
emoji=true
29+
30+
esproposal.optional_chaining=enable
31+
esproposal.nullish_coalescing=enable
32+
33+
module.file_ext=.js
34+
module.file_ext=.json
35+
module.file_ext=.ios.js
36+
37+
munge_underscores=true
38+
39+
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/node_modules/react-native/\1'
40+
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/node_modules/react-native/Libraries/Image/RelativeImageStub'
41+
42+
suppress_type=$FlowIssue
43+
suppress_type=$FlowFixMe
44+
suppress_type=$FlowFixMeProps
45+
suppress_type=$FlowFixMeState
46+
47+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
48+
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
49+
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
50+
51+
[lints]
52+
sketchy-null-number=warn
53+
sketchy-null-mixed=warn
54+
sketchy-number=warn
55+
untyped-type-import=warn
56+
nonstrict-import=warn
57+
deprecated-type=warn
58+
unsafe-getters-setters=warn
59+
unnecessary-invariant=warn
60+
signature-verification-failure=warn
61+
deprecated-utility=error
62+
63+
[strict]
64+
deprecated-type
65+
nonstrict-import
66+
sketchy-null
67+
unclear-type
68+
unsafe-getters-setters
69+
untyped-import
70+
untyped-type-import
71+
72+
[version]
73+
^0.122.0

Example/.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.pbxproj -text

Example/.gitignore

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# OSX
2+
#
3+
.DS_Store
4+
5+
# Xcode
6+
#
7+
build/
8+
*.pbxuser
9+
!default.pbxuser
10+
*.mode1v3
11+
!default.mode1v3
12+
*.mode2v3
13+
!default.mode2v3
14+
*.perspectivev3
15+
!default.perspectivev3
16+
xcuserdata
17+
*.xccheckout
18+
*.moved-aside
19+
DerivedData
20+
*.hmap
21+
*.ipa
22+
*.xcuserstate
23+
24+
# Android/IntelliJ
25+
#
26+
build/
27+
.idea
28+
.gradle
29+
local.properties
30+
*.iml
31+
32+
# node.js
33+
#
34+
node_modules/
35+
npm-debug.log
36+
yarn-error.log
37+
38+
# BUCK
39+
buck-out/
40+
\.buckd/
41+
*.keystore
42+
!debug.keystore
43+
44+
# fastlane
45+
#
46+
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
47+
# screenshots whenever they are needed.
48+
# For more information about the recommended setup visit:
49+
# https://docs.fastlane.tools/best-practices/source-control/
50+
51+
*/fastlane/report.xml
52+
*/fastlane/Preview.html
53+
*/fastlane/screenshots
54+
55+
# Bundle artifact
56+
*.jsbundle
57+
58+
# CocoaPods
59+
/ios/Pods/

Example/.prettierrc.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
bracketSpacing: false,
3+
jsxBracketSameLine: true,
4+
singleQuote: true,
5+
trailingComma: 'all',
6+
};

Example/.watchmanconfig

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

Example/App.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from 'react';
2+
import {SafeAreaView, StyleSheet} from 'react-native';
3+
4+
import ReviewApp from './src/screens/reviewApp';
5+
6+
const App = () => {
7+
return (
8+
<SafeAreaView style={styles.MainAppContainer}>
9+
<ReviewApp />
10+
</SafeAreaView>
11+
);
12+
};
13+
14+
const styles = StyleSheet.create({MainAppContainer: {flex: 1}});
15+
16+
export default App;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
const asMock = {
2+
__INTERNAL_MOCK_STORAGE__: {},
3+
4+
setItem: jest.fn(async (key, value, callback) => {
5+
const setResult = await asMock.multiSet([[key, value]], undefined);
6+
7+
callback && callback(setResult);
8+
return setResult;
9+
}),
10+
11+
getItem: jest.fn(async (key, callback) => {
12+
const getResult = await asMock.multiGet([key], undefined);
13+
14+
const result = getResult[0] ? getResult[0][1] : null;
15+
16+
callback && callback(null, result);
17+
return result;
18+
}),
19+
20+
removeItem: jest.fn((key, callback) => asMock.multiRemove([key], callback)),
21+
mergeItem: jest.fn((key, value, callback) =>
22+
asMock.multiMerge([[key, value]], callback),
23+
),
24+
25+
clear: jest.fn(_clear),
26+
getAllKeys: jest.fn(_getAllKeys),
27+
flushGetRequests: jest.fn(),
28+
29+
multiGet: jest.fn(_multiGet),
30+
multiSet: jest.fn(_multiSet),
31+
multiRemove: jest.fn(_multiRemove),
32+
multiMerge: jest.fn(_multiMerge),
33+
useAsyncStorage: jest.fn((key) => {
34+
return {
35+
getItem: (...args) => asMock.getItem(key, ...args),
36+
setItem: (...args) => asMock.setItem(key, ...args),
37+
mergeItem: (...args) => asMock.mergeItem(key, ...args),
38+
removeItem: (...args) => asMock.removeItem(key, ...args),
39+
};
40+
}),
41+
};
42+
43+
async function _multiSet(keyValuePairs, callback) {
44+
keyValuePairs.forEach((keyValue) => {
45+
const key = keyValue[0];
46+
47+
asMock.__INTERNAL_MOCK_STORAGE__[key] = keyValue[1];
48+
});
49+
callback && callback(null);
50+
return null;
51+
}
52+
53+
async function _multiGet(keys, callback) {
54+
const values = keys.map((key) => [
55+
key,
56+
asMock.__INTERNAL_MOCK_STORAGE__[key] || null,
57+
]);
58+
callback && callback(null, values);
59+
60+
return values;
61+
}
62+
63+
async function _multiRemove(keys, callback) {
64+
keys.forEach((key) => {
65+
if (asMock.__INTERNAL_MOCK_STORAGE__[key]) {
66+
delete asMock.__INTERNAL_MOCK_STORAGE__[key];
67+
}
68+
});
69+
70+
callback && callback(null);
71+
return null;
72+
}
73+
74+
async function _clear(callback) {
75+
asMock.__INTERNAL_MOCK_STORAGE__ = {};
76+
77+
callback && callback(null);
78+
79+
return null;
80+
}
81+
82+
async function _getAllKeys() {
83+
return Object.keys(asMock.__INTERNAL_MOCK_STORAGE__);
84+
}
85+
86+
async function _multiMerge(keyValuePairs, callback) {
87+
keyValuePairs.forEach((keyValue) => {
88+
const key = keyValue[0];
89+
const value = JSON.parse(keyValue[1]);
90+
91+
const oldValue = JSON.parse(asMock.__INTERNAL_MOCK_STORAGE__[key]);
92+
93+
asMock.__INTERNAL_MOCK_STORAGE__[key] = JSON.stringify(
94+
_deepMergeInto(oldValue, value),
95+
);
96+
});
97+
98+
callback && callback(null);
99+
return null;
100+
}
101+
102+
const _isObject = (obj) => typeof obj === 'object' && !Array.isArray(obj);
103+
const _deepMergeInto = (oldObject, newObject) => {
104+
const newKeys = Object.keys(newObject);
105+
const mergedObject = oldObject;
106+
107+
newKeys.forEach((key) => {
108+
const oldValue = mergedObject[key];
109+
const newValue = newObject[key];
110+
111+
if (_isObject(oldValue) && _isObject(newValue)) {
112+
mergedObject[key] = _deepMergeInto(oldValue, newValue);
113+
} else {
114+
mergedObject[key] = newValue;
115+
}
116+
});
117+
118+
return mergedObject;
119+
};
120+
121+
module.exports = asMock;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
RequestInAppReview: jest.fn(),
3+
isAvailable: jest.fn(),
4+
};
+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import AsyncStorage from '@react-native-async-storage/async-storage';
2+
import {renderHook, act} from '@testing-library/react-hooks';
3+
import useAppReview from '../src/screens/reviewApp/useAppReview';
4+
import InAppReview from 'react-native-in-app-review';
5+
6+
describe('App Review Hook Behavoir', () => {
7+
const currentDate = new Date('2021-01-10T11:01:58.135Z');
8+
global.Date = class extends Date {
9+
constructor(date) {
10+
if (date) {
11+
return super(date);
12+
}
13+
14+
return currentDate;
15+
}
16+
};
17+
afterAll(() => {
18+
jest.resetModule();
19+
});
20+
21+
beforeEach(() => {
22+
jest.clearAllMocks();
23+
});
24+
25+
it('should trigger InAppReview in cross platform in first time', async () => {
26+
const expectItemSavedToAsync = ['in_App_Review', new Date().toString()];
27+
28+
const {result} = renderHook(() => useAppReview());
29+
30+
await act(() => result.current.onReview());
31+
expect(AsyncStorage.getItem).toBeCalledWith('in_App_Review');
32+
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
33+
...expectItemSavedToAsync,
34+
);
35+
36+
expect(InAppReview.RequestInAppReview).toHaveBeenCalled();
37+
});
38+
39+
it('should not trigger InAppReview before 15 days user already get InAppReview', async () => {
40+
const expectedItem = '2021-01-05';
41+
jest
42+
.spyOn(AsyncStorage, 'getItem')
43+
.mockReturnValueOnce(Promise.resolve(expectedItem));
44+
45+
const {result} = renderHook(() => useAppReview());
46+
47+
await act(() => result.current.onReview());
48+
expect(AsyncStorage.getItem).toBeCalledWith('in_App_Review');
49+
50+
expect(InAppReview.RequestInAppReview).not.toHaveBeenCalled();
51+
});
52+
53+
it('should trigger InAppReview after 15 days user get InAppReview and save Date to async Storage', async () => {
54+
const expectedItem = '2021-01-26';
55+
const expectItemSavedToAsync = ['in_App_Review', new Date().toString()];
56+
57+
jest
58+
.spyOn(AsyncStorage, 'getItem')
59+
.mockReturnValueOnce(Promise.resolve(expectedItem));
60+
61+
jest.spyOn(AsyncStorage, 'setItem');
62+
63+
const {result} = renderHook(() => useAppReview());
64+
65+
await act(() => result.current.onReview());
66+
67+
expect(AsyncStorage.getItem).toBeCalledWith('in_App_Review');
68+
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
69+
...expectItemSavedToAsync,
70+
);
71+
expect(InAppReview.RequestInAppReview).toHaveBeenCalled();
72+
});
73+
});

0 commit comments

Comments
 (0)