@react-native-ama/lists 1.2.1 → 2.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/DynamicFlatList.js +2 -2
- package/dist/hooks/useDynamicList.d.ts +0 -5
- package/dist/hooks/useDynamicList.js +44 -23
- package/package.json +71 -17
- package/src/components/DynamicFlatList.test.tsx +0 -1
- package/src/components/DynamicFlatList.tsx +2 -3
- package/src/components/FlatList.test.tsx +9 -126
- package/src/components/FlatList.tsx +0 -1
- package/src/hooks/useDynamicFlatList.test.ts +38 -31
- package/src/hooks/useDynamicList.checks.test.ts +98 -0
- package/src/hooks/useDynamicList.ts +52 -24
|
@@ -20,12 +20,12 @@ const react_native_1 = require("react-native");
|
|
|
20
20
|
const useDynamicList_1 = require("../hooks/useDynamicList");
|
|
21
21
|
exports.DynamicFlatList = react_1.default.forwardRef((_a, forwardedRef) => {
|
|
22
22
|
var { data, singularMessage, pluralMessage, isPlural } = _a, rest = __rest(_a, ["data", "singularMessage", "pluralMessage", "isPlural"]);
|
|
23
|
-
|
|
23
|
+
(0, useDynamicList_1.useDynamicList)({
|
|
24
24
|
data: data !== null && data !== void 0 ? data : [],
|
|
25
25
|
pluralMessage,
|
|
26
26
|
singularMessage,
|
|
27
27
|
numColumns: rest.numColumns,
|
|
28
28
|
isPlural,
|
|
29
29
|
});
|
|
30
|
-
return __DEV__ ? (<react_native_1.FlatList data={data} {...rest} ref={forwardedRef}
|
|
30
|
+
return __DEV__ ? (<react_native_1.FlatList data={data} {...rest} ref={forwardedRef} role="list"/>) : (<react_native_1.FlatList data={data} {...rest} ref={forwardedRef}/>);
|
|
31
31
|
});
|
|
@@ -8,9 +8,4 @@ export type UseDynamicList = {
|
|
|
8
8
|
export declare const useDynamicList: ({ data, singularMessage, pluralMessage, isPlural, numColumns, }: UseDynamicList) => {
|
|
9
9
|
rowsCount: number;
|
|
10
10
|
columnsCount: number;
|
|
11
|
-
style: any;
|
|
12
|
-
} | {
|
|
13
|
-
rowsCount: number;
|
|
14
|
-
columnsCount: number;
|
|
15
|
-
style?: undefined;
|
|
16
11
|
};
|
|
@@ -4,32 +4,59 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.useDynamicList = void 0;
|
|
7
|
-
const core_1 = require("@react-native-ama/core");
|
|
8
7
|
const react_1 = __importDefault(require("react"));
|
|
9
8
|
const react_native_1 = require("react-native");
|
|
9
|
+
let _useAMAContext = null;
|
|
10
|
+
try {
|
|
11
|
+
_useAMAContext = require('@react-native-ama/core').useAMAContext;
|
|
12
|
+
}
|
|
13
|
+
catch (_a) {
|
|
14
|
+
// core is an optional peer — fall back to null
|
|
15
|
+
}
|
|
16
|
+
const getTrackError = () => {
|
|
17
|
+
var _a, _b;
|
|
18
|
+
try {
|
|
19
|
+
return (_b = (_a = _useAMAContext === null || _useAMAContext === void 0 ? void 0 : _useAMAContext()) === null || _a === void 0 ? void 0 : _a.trackError) !== null && _b !== void 0 ? _b : null;
|
|
20
|
+
}
|
|
21
|
+
catch (_c) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
10
25
|
const useDynamicList = ({ data, singularMessage, pluralMessage, isPlural = simpleIsPlural, numColumns = 1, }) => {
|
|
11
26
|
const isFirstRender = react_1.default.useRef(true);
|
|
12
27
|
const initialCount = react_1.default.useRef(data === null || data === void 0 ? void 0 : data.length);
|
|
13
28
|
const lastItemsCount = react_1.default.useRef(null);
|
|
14
|
-
const
|
|
29
|
+
const trackError = __DEV__ ? getTrackError() : null;
|
|
15
30
|
__DEV__ &&
|
|
16
31
|
react_1.default.useEffect(() => {
|
|
17
32
|
if (!(singularMessage === null || singularMessage === void 0 ? void 0 : singularMessage.includes('%count%'))) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
const rule = 'FLATLIST_NO_COUNT_IN_SINGULAR_MESSAGE';
|
|
34
|
+
if (trackError) {
|
|
35
|
+
trackError(rule);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
console.error('useDynamicFlatList', {
|
|
39
|
+
rule,
|
|
40
|
+
message: 'Special string %count% not found in singularMessage',
|
|
41
|
+
extra: singularMessage,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
23
44
|
}
|
|
24
45
|
if (!(pluralMessage === null || pluralMessage === void 0 ? void 0 : pluralMessage.includes('%count%'))) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
46
|
+
const rule = 'FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE';
|
|
47
|
+
if (trackError) {
|
|
48
|
+
trackError(rule);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.error('useDynamicFlatList', {
|
|
52
|
+
rule,
|
|
53
|
+
message: 'Special string %count% not found in pluralMessage',
|
|
54
|
+
extra: pluralMessage,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
30
57
|
}
|
|
31
58
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
32
|
-
}, [pluralMessage, singularMessage
|
|
59
|
+
}, [pluralMessage, singularMessage]);
|
|
33
60
|
react_1.default.useEffect(() => {
|
|
34
61
|
const itemsCount = (data === null || data === void 0 ? void 0 : data.length) || 0;
|
|
35
62
|
if (isFirstRender.current ||
|
|
@@ -45,16 +72,10 @@ const useDynamicList = ({ data, singularMessage, pluralMessage, isPlural = simpl
|
|
|
45
72
|
lastItemsCount.current = itemsCount;
|
|
46
73
|
}, [pluralMessage, singularMessage, data, isPlural]);
|
|
47
74
|
const rowsCount = lastItemsCount.current || initialCount.current || 0;
|
|
48
|
-
return
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
style: checks === null || checks === void 0 ? void 0 : checks.debugStyle,
|
|
53
|
-
}
|
|
54
|
-
: {
|
|
55
|
-
rowsCount: rowsCount / numColumns,
|
|
56
|
-
columnsCount: numColumns,
|
|
57
|
-
};
|
|
75
|
+
return {
|
|
76
|
+
rowsCount: rowsCount / numColumns,
|
|
77
|
+
columnsCount: numColumns,
|
|
78
|
+
};
|
|
58
79
|
};
|
|
59
80
|
exports.useDynamicList = useDynamicList;
|
|
60
81
|
function simpleIsPlural(count) {
|
package/package.json
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-native-ama/lists",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"types": "dist/index.d.ts",
|
|
6
|
-
"main": "dist/index.js",
|
|
3
|
+
"version": "2.0.0-beta.0",
|
|
4
|
+
"sideEffects": false,
|
|
7
5
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"react-native": "./src/index.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./DynamicFlatList": {
|
|
12
|
+
"types": "./dist/components/DynamicFlatList.d.ts",
|
|
13
|
+
"default": "./dist/components/DynamicFlatList.js"
|
|
14
|
+
},
|
|
15
|
+
"./FlatList": {
|
|
16
|
+
"types": "./dist/components/FlatList.d.ts",
|
|
17
|
+
"default": "./dist/components/FlatList.js"
|
|
18
|
+
},
|
|
19
|
+
"./StaticFlatList": {
|
|
20
|
+
"types": "./dist/components/StaticFlatList.d.ts",
|
|
21
|
+
"default": "./dist/components/StaticFlatList.js"
|
|
22
|
+
},
|
|
23
|
+
"./useDynamicList": {
|
|
24
|
+
"types": "./dist/hooks/useDynamicList.d.ts",
|
|
25
|
+
"default": "./dist/hooks/useDynamicList.js"
|
|
26
|
+
}
|
|
15
27
|
},
|
|
16
28
|
"files": [
|
|
17
29
|
"src",
|
|
@@ -19,18 +31,19 @@
|
|
|
19
31
|
],
|
|
20
32
|
"scripts": {
|
|
21
33
|
"build": "rm -rf dist && tsc -p ./tsconfig.build.json",
|
|
22
|
-
"typecheck": "tsc --noEmit"
|
|
23
|
-
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"@react-native-ama/core": "~1.2.1",
|
|
26
|
-
"@react-native-ama/internal": "~1.2.1"
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"test": "jest"
|
|
27
36
|
},
|
|
28
37
|
"peerDependencies": {
|
|
38
|
+
"@react-native-ama/core": "~2.0.0-beta.0",
|
|
29
39
|
"expo": ">=47.0.0",
|
|
30
40
|
"react": "*",
|
|
31
41
|
"react-native": "*"
|
|
32
42
|
},
|
|
33
43
|
"peerDependenciesMeta": {
|
|
44
|
+
"@react-native-ama/core": {
|
|
45
|
+
"optional": true
|
|
46
|
+
},
|
|
34
47
|
"expo": {
|
|
35
48
|
"optional": true
|
|
36
49
|
}
|
|
@@ -53,5 +66,46 @@
|
|
|
53
66
|
"provenance": true
|
|
54
67
|
},
|
|
55
68
|
"author": "",
|
|
56
|
-
"license": "MIT"
|
|
69
|
+
"license": "MIT",
|
|
70
|
+
"jest": {
|
|
71
|
+
"preset": "react-native",
|
|
72
|
+
"setupFilesAfterEnv": [
|
|
73
|
+
"<rootDir>/jest.setup.js"
|
|
74
|
+
],
|
|
75
|
+
"modulePathIgnorePatterns": [
|
|
76
|
+
"<rootDir>/node_modules",
|
|
77
|
+
"<rootDir>/dist/"
|
|
78
|
+
],
|
|
79
|
+
"collectCoverageFrom": [
|
|
80
|
+
"src/**/*.{ts,tsx}",
|
|
81
|
+
"!src/index.ts"
|
|
82
|
+
],
|
|
83
|
+
"coverageThreshold": {
|
|
84
|
+
"global": {
|
|
85
|
+
"statements": 100,
|
|
86
|
+
"branches": 85,
|
|
87
|
+
"functions": 100,
|
|
88
|
+
"lines": 100
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"transformIgnorePatterns": [
|
|
92
|
+
"node_modules/(?!(react-native-reanimated|expo|@expo)/)"
|
|
93
|
+
],
|
|
94
|
+
"transform": {
|
|
95
|
+
"\\.[jt]sx?$": "babel-jest"
|
|
96
|
+
},
|
|
97
|
+
"moduleDirectories": [
|
|
98
|
+
"node_modules",
|
|
99
|
+
"../../playground/node_modules"
|
|
100
|
+
],
|
|
101
|
+
"moduleNameMapper": {
|
|
102
|
+
"@react-native/js-polyfills/error-guard": "<rootDir>/../../jest-mocks/error-guard.js",
|
|
103
|
+
"^(\\.{1,2}/)*ama\\.config\\.json$": "<rootDir>/../../jest-mocks/ama-config.js",
|
|
104
|
+
"^(\\.{1,2}/)*ReactNativeAmaModule$": "<rootDir>/../../jest-mocks/ReactNativeAmaModule.js",
|
|
105
|
+
"^(\\.{1,2}/)*ReactNativeAmaView(\\.web)?$": "<rootDir>/../../jest-mocks/ReactNativeAmaModule.js",
|
|
106
|
+
"^@react-native-ama/core$": "<rootDir>/../../jest-mocks/ama-core.js",
|
|
107
|
+
"^@react-native-ama/core/(.*)$": "<rootDir>/../../packages/core/$1"
|
|
108
|
+
},
|
|
109
|
+
"verbose": true
|
|
110
|
+
}
|
|
57
111
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { FlatList, FlatListProps } from 'react-native';
|
|
3
|
-
|
|
4
3
|
import { useDynamicList } from '../hooks/useDynamicList';
|
|
5
4
|
|
|
6
5
|
type DynamicFlatListProps<T> = FlatListProps<T> & {
|
|
@@ -17,7 +16,7 @@ export const DynamicFlatList = React.forwardRef<
|
|
|
17
16
|
{ data, singularMessage, pluralMessage, isPlural, ...rest },
|
|
18
17
|
forwardedRef,
|
|
19
18
|
) => {
|
|
20
|
-
|
|
19
|
+
useDynamicList({
|
|
21
20
|
data: data ?? [],
|
|
22
21
|
pluralMessage,
|
|
23
22
|
singularMessage,
|
|
@@ -30,7 +29,7 @@ export const DynamicFlatList = React.forwardRef<
|
|
|
30
29
|
data={data}
|
|
31
30
|
{...rest}
|
|
32
31
|
ref={forwardedRef}
|
|
33
|
-
|
|
32
|
+
role="list"
|
|
34
33
|
/>
|
|
35
34
|
) : (
|
|
36
35
|
<FlatList data={data} {...rest} ref={forwardedRef} />
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { render, waitFor } from '@testing-library/react-native';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { AccessibilityInfo
|
|
4
|
-
|
|
3
|
+
import { AccessibilityInfo } from 'react-native';
|
|
5
4
|
import { FlatList } from './FlatList';
|
|
6
5
|
|
|
7
6
|
beforeEach(() => {
|
|
@@ -10,8 +9,6 @@ beforeEach(() => {
|
|
|
10
9
|
AccessibilityInfo.announceForAccessibility = jest.fn();
|
|
11
10
|
});
|
|
12
11
|
|
|
13
|
-
Platform.OS = 'android';
|
|
14
|
-
|
|
15
12
|
describe('FlatList', () => {
|
|
16
13
|
describe('listType "dynamic"', () => {
|
|
17
14
|
it('does not announce the number of items on first render', async () => {
|
|
@@ -183,9 +180,9 @@ describe('FlatList', () => {
|
|
|
183
180
|
|
|
184
181
|
describe('listType="static"', () => {
|
|
185
182
|
it.each`
|
|
186
|
-
sliceStart | sliceEnd
|
|
187
|
-
${0} | ${2}
|
|
188
|
-
${1} | ${2}
|
|
183
|
+
sliceStart | sliceEnd
|
|
184
|
+
${0} | ${2}
|
|
185
|
+
${1} | ${2}
|
|
189
186
|
`(
|
|
190
187
|
'does not announce the number of items displayed when the data changes',
|
|
191
188
|
({ sliceStart, sliceEnd }) => {
|
|
@@ -207,7 +204,7 @@ describe('FlatList', () => {
|
|
|
207
204
|
},
|
|
208
205
|
);
|
|
209
206
|
|
|
210
|
-
it('
|
|
207
|
+
it('renders without errors with rowsCount specified', () => {
|
|
211
208
|
const { toJSON } = render(
|
|
212
209
|
<FlatList
|
|
213
210
|
data={[]}
|
|
@@ -217,36 +214,10 @@ describe('FlatList', () => {
|
|
|
217
214
|
/>,
|
|
218
215
|
);
|
|
219
216
|
|
|
220
|
-
expect(toJSON()).
|
|
221
|
-
<View
|
|
222
|
-
columnsCount={1}
|
|
223
|
-
rowsCount={42}
|
|
224
|
-
>
|
|
225
|
-
<RCTScrollView
|
|
226
|
-
data={Array []}
|
|
227
|
-
getItem={[Function]}
|
|
228
|
-
getItemCount={[Function]}
|
|
229
|
-
keyExtractor={[Function]}
|
|
230
|
-
onContentSizeChange={[Function]}
|
|
231
|
-
onLayout={[Function]}
|
|
232
|
-
onMomentumScrollBegin={[Function]}
|
|
233
|
-
onMomentumScrollEnd={[Function]}
|
|
234
|
-
onScroll={[Function]}
|
|
235
|
-
onScrollBeginDrag={[Function]}
|
|
236
|
-
onScrollEndDrag={[Function]}
|
|
237
|
-
removeClippedSubviews={true}
|
|
238
|
-
renderItem={[Function]}
|
|
239
|
-
scrollEventThrottle={50}
|
|
240
|
-
stickyHeaderIndices={Array []}
|
|
241
|
-
viewabilityConfigCallbackPairs={Array []}
|
|
242
|
-
>
|
|
243
|
-
<View />
|
|
244
|
-
</RCTScrollView>
|
|
245
|
-
</View>
|
|
246
|
-
`);
|
|
217
|
+
expect(toJSON()).not.toBeNull();
|
|
247
218
|
});
|
|
248
219
|
|
|
249
|
-
it('
|
|
220
|
+
it('renders without errors with numColumns specified', () => {
|
|
250
221
|
const { toJSON } = render(
|
|
251
222
|
<FlatList
|
|
252
223
|
data={DATA}
|
|
@@ -256,61 +227,7 @@ describe('FlatList', () => {
|
|
|
256
227
|
/>,
|
|
257
228
|
);
|
|
258
229
|
|
|
259
|
-
expect(toJSON()).
|
|
260
|
-
<View
|
|
261
|
-
columnsCount={2}
|
|
262
|
-
rowsCount={2}
|
|
263
|
-
>
|
|
264
|
-
<RCTScrollView
|
|
265
|
-
data={
|
|
266
|
-
Array [
|
|
267
|
-
Object {
|
|
268
|
-
"id": "bd7acbea-c1b1-46c2-aed5-3ad53abb28ba",
|
|
269
|
-
"title": "Formidable",
|
|
270
|
-
},
|
|
271
|
-
Object {
|
|
272
|
-
"id": "3ac68afc-c605-48d3-a4f8-fbd91aa97f63",
|
|
273
|
-
"title": "React Native",
|
|
274
|
-
},
|
|
275
|
-
Object {
|
|
276
|
-
"id": "58694a0f-3da1-471f-bd96-145571e29d72",
|
|
277
|
-
"title": "Typescript",
|
|
278
|
-
},
|
|
279
|
-
]
|
|
280
|
-
}
|
|
281
|
-
getItem={[Function]}
|
|
282
|
-
getItemCount={[Function]}
|
|
283
|
-
keyExtractor={[Function]}
|
|
284
|
-
onContentSizeChange={[Function]}
|
|
285
|
-
onLayout={[Function]}
|
|
286
|
-
onMomentumScrollBegin={[Function]}
|
|
287
|
-
onMomentumScrollEnd={[Function]}
|
|
288
|
-
onScroll={[Function]}
|
|
289
|
-
onScrollBeginDrag={[Function]}
|
|
290
|
-
onScrollEndDrag={[Function]}
|
|
291
|
-
removeClippedSubviews={true}
|
|
292
|
-
renderItem={[Function]}
|
|
293
|
-
scrollEventThrottle={50}
|
|
294
|
-
stickyHeaderIndices={Array []}
|
|
295
|
-
viewabilityConfigCallbackPairs={Array []}
|
|
296
|
-
>
|
|
297
|
-
<View>
|
|
298
|
-
<View
|
|
299
|
-
onLayout={[Function]}
|
|
300
|
-
style={null}
|
|
301
|
-
/>
|
|
302
|
-
<View
|
|
303
|
-
onLayout={[Function]}
|
|
304
|
-
style={null}
|
|
305
|
-
/>
|
|
306
|
-
<View
|
|
307
|
-
onLayout={[Function]}
|
|
308
|
-
style={null}
|
|
309
|
-
/>
|
|
310
|
-
</View>
|
|
311
|
-
</RCTScrollView>
|
|
312
|
-
</View>
|
|
313
|
-
`);
|
|
230
|
+
expect(toJSON()).not.toBeNull();
|
|
314
231
|
});
|
|
315
232
|
|
|
316
233
|
it('sets rowsCount to 0 if the data is undefined', () => {
|
|
@@ -323,45 +240,11 @@ describe('FlatList', () => {
|
|
|
323
240
|
/>,
|
|
324
241
|
);
|
|
325
242
|
|
|
326
|
-
expect(toJSON()).
|
|
327
|
-
<View
|
|
328
|
-
columnsCount={2}
|
|
329
|
-
rowsCount={0}
|
|
330
|
-
>
|
|
331
|
-
<RCTScrollView
|
|
332
|
-
getItem={[Function]}
|
|
333
|
-
getItemCount={[Function]}
|
|
334
|
-
keyExtractor={[Function]}
|
|
335
|
-
onContentSizeChange={[Function]}
|
|
336
|
-
onLayout={[Function]}
|
|
337
|
-
onMomentumScrollBegin={[Function]}
|
|
338
|
-
onMomentumScrollEnd={[Function]}
|
|
339
|
-
onScroll={[Function]}
|
|
340
|
-
onScrollBeginDrag={[Function]}
|
|
341
|
-
onScrollEndDrag={[Function]}
|
|
342
|
-
removeClippedSubviews={true}
|
|
343
|
-
renderItem={[Function]}
|
|
344
|
-
scrollEventThrottle={50}
|
|
345
|
-
stickyHeaderIndices={Array []}
|
|
346
|
-
viewabilityConfigCallbackPairs={Array []}
|
|
347
|
-
>
|
|
348
|
-
<View />
|
|
349
|
-
</RCTScrollView>
|
|
350
|
-
</View>
|
|
351
|
-
`);
|
|
243
|
+
expect(toJSON()).not.toBeNull();
|
|
352
244
|
});
|
|
353
245
|
});
|
|
354
246
|
});
|
|
355
247
|
|
|
356
|
-
jest.mock('./ListWrapper', () => {
|
|
357
|
-
const { View } = jest.requireActual('react-native');
|
|
358
|
-
|
|
359
|
-
return {
|
|
360
|
-
// @ts-ignore
|
|
361
|
-
ListWrapper: props => <View {...props} />,
|
|
362
|
-
};
|
|
363
|
-
});
|
|
364
|
-
|
|
365
248
|
const DATA = [
|
|
366
249
|
{
|
|
367
250
|
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
|
|
@@ -3,7 +3,6 @@ import type {
|
|
|
3
3
|
FlatList as RNFlatList,
|
|
4
4
|
FlatListProps as RNFlatListProps,
|
|
5
5
|
} from 'react-native';
|
|
6
|
-
|
|
7
6
|
import type { UseDynamicList } from '../hooks/useDynamicList';
|
|
8
7
|
import { DynamicFlatList } from './DynamicFlatList';
|
|
9
8
|
import { StaticFlatList, StaticFlatListProps } from './StaticFlatList';
|
|
@@ -1,50 +1,57 @@
|
|
|
1
|
-
import * as UseChecks from '@react-native-ama/core/src/hooks/useChecks';
|
|
2
1
|
import { renderHook } from '@testing-library/react-native';
|
|
3
|
-
|
|
2
|
+
import { AccessibilityInfo } from 'react-native';
|
|
4
3
|
import { useDynamicList } from './useDynamicList';
|
|
5
4
|
|
|
6
5
|
beforeEach(() => {
|
|
7
6
|
jest.clearAllMocks();
|
|
8
7
|
});
|
|
9
8
|
|
|
10
|
-
const logResult = jest.fn();
|
|
11
|
-
|
|
12
9
|
describe('useDynamicFlatList', () => {
|
|
13
|
-
|
|
14
|
-
// @ts-ignore
|
|
15
|
-
jest.spyOn(UseChecks, 'useChecks').mockReturnValue({
|
|
16
|
-
logResult,
|
|
17
|
-
} as any);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('checks that the singular message contains the word %count%', () => {
|
|
10
|
+
it('does not announce on first render', () => {
|
|
21
11
|
renderHook(() =>
|
|
22
12
|
useDynamicList({
|
|
23
|
-
data: [],
|
|
24
|
-
singularMessage: '
|
|
25
|
-
pluralMessage: '%count%
|
|
13
|
+
data: [1, 2, 3],
|
|
14
|
+
singularMessage: '%count% item',
|
|
15
|
+
pluralMessage: '%count% items',
|
|
26
16
|
}),
|
|
27
17
|
);
|
|
28
18
|
|
|
29
|
-
expect(
|
|
30
|
-
extra: 'hello',
|
|
31
|
-
message: 'Special string %count% not found in singularMessage',
|
|
32
|
-
rule: 'FLATLIST_NO_COUNT_IN_SINGULAR_MESSAGE',
|
|
33
|
-
});
|
|
19
|
+
expect(AccessibilityInfo.announceForAccessibility).not.toHaveBeenCalled();
|
|
34
20
|
});
|
|
35
21
|
|
|
36
|
-
it('
|
|
37
|
-
renderHook(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
22
|
+
it('announces when data length changes from initial', () => {
|
|
23
|
+
const { rerender } = renderHook(
|
|
24
|
+
({ data }: { data: number[] }) =>
|
|
25
|
+
useDynamicList({
|
|
26
|
+
data,
|
|
27
|
+
singularMessage: '%count% item',
|
|
28
|
+
pluralMessage: '%count% items',
|
|
29
|
+
}),
|
|
30
|
+
{ initialProps: { data: [1, 2, 3] } },
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
rerender({ data: [1] });
|
|
34
|
+
|
|
35
|
+
expect(AccessibilityInfo.announceForAccessibility).toHaveBeenCalledWith(
|
|
36
|
+
'1 item',
|
|
42
37
|
);
|
|
38
|
+
});
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
it('announces plural message when count > 1', () => {
|
|
41
|
+
const { rerender } = renderHook(
|
|
42
|
+
({ data }: { data: number[] }) =>
|
|
43
|
+
useDynamicList({
|
|
44
|
+
data,
|
|
45
|
+
singularMessage: '%count% item',
|
|
46
|
+
pluralMessage: '%count% items',
|
|
47
|
+
}),
|
|
48
|
+
{ initialProps: { data: [1, 2, 3] } },
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
rerender({ data: [1, 2] });
|
|
52
|
+
|
|
53
|
+
expect(AccessibilityInfo.announceForAccessibility).toHaveBeenCalledWith(
|
|
54
|
+
'2 items',
|
|
55
|
+
);
|
|
49
56
|
});
|
|
50
57
|
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react-native';
|
|
2
|
+
|
|
3
|
+
const mockTrackError = jest.fn();
|
|
4
|
+
const mockUseAMAContext = jest.fn(() => ({ trackError: mockTrackError }));
|
|
5
|
+
|
|
6
|
+
jest.mock('@react-native-ama/core', () => ({
|
|
7
|
+
useAMAContext: () => mockUseAMAContext(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
import { useDynamicList } from './useDynamicList';
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
mockUseAMAContext.mockReturnValue({ trackError: mockTrackError });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('useDynamicList validation checks', () => {
|
|
18
|
+
it('calls trackError with FLATLIST_NO_COUNT_IN_SINGULAR_MESSAGE when singularMessage lacks %count%', () => {
|
|
19
|
+
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
20
|
+
|
|
21
|
+
renderHook(() =>
|
|
22
|
+
useDynamicList({
|
|
23
|
+
data: [1, 2],
|
|
24
|
+
singularMessage: 'one item',
|
|
25
|
+
pluralMessage: '%count% items',
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
expect(mockTrackError).toHaveBeenCalledWith(
|
|
30
|
+
'FLATLIST_NO_COUNT_IN_SINGULAR_MESSAGE',
|
|
31
|
+
);
|
|
32
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
33
|
+
|
|
34
|
+
errorSpy.mockRestore();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('calls trackError with FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE when pluralMessage lacks %count%', () => {
|
|
38
|
+
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
39
|
+
|
|
40
|
+
renderHook(() =>
|
|
41
|
+
useDynamicList({
|
|
42
|
+
data: [1, 2],
|
|
43
|
+
singularMessage: '%count% item',
|
|
44
|
+
pluralMessage: 'many items',
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
expect(mockTrackError).toHaveBeenCalledWith(
|
|
49
|
+
'FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE',
|
|
50
|
+
);
|
|
51
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
52
|
+
|
|
53
|
+
errorSpy.mockRestore();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('does not call trackError or console.error when both messages contain %count%', () => {
|
|
57
|
+
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
58
|
+
|
|
59
|
+
renderHook(() =>
|
|
60
|
+
useDynamicList({
|
|
61
|
+
data: [1, 2],
|
|
62
|
+
singularMessage: '%count% item',
|
|
63
|
+
pluralMessage: '%count% items',
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(mockTrackError).not.toHaveBeenCalled();
|
|
68
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
69
|
+
|
|
70
|
+
errorSpy.mockRestore();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('falls back to console.error when no AMAProvider is present', () => {
|
|
74
|
+
mockUseAMAContext.mockImplementation(() => {
|
|
75
|
+
throw new Error('Please wrap your app with <AMAProvider />');
|
|
76
|
+
});
|
|
77
|
+
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
78
|
+
|
|
79
|
+
renderHook(() =>
|
|
80
|
+
useDynamicList({
|
|
81
|
+
data: [1, 2],
|
|
82
|
+
singularMessage: 'one item',
|
|
83
|
+
pluralMessage: 'many items',
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(errorSpy).toHaveBeenCalledWith(
|
|
88
|
+
'useDynamicFlatList',
|
|
89
|
+
expect.objectContaining({ rule: 'FLATLIST_NO_COUNT_IN_SINGULAR_MESSAGE' }),
|
|
90
|
+
);
|
|
91
|
+
expect(errorSpy).toHaveBeenCalledWith(
|
|
92
|
+
'useDynamicFlatList',
|
|
93
|
+
expect.objectContaining({ rule: 'FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE' }),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
errorSpy.mockRestore();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -1,7 +1,30 @@
|
|
|
1
|
-
import { useChecks } from '@react-native-ama/core';
|
|
2
1
|
import React from 'react';
|
|
3
2
|
import { AccessibilityInfo } from 'react-native';
|
|
4
3
|
|
|
4
|
+
type FlatListRule =
|
|
5
|
+
| 'FLATLIST_NO_COUNT_IN_SINGULAR_MESSAGE'
|
|
6
|
+
| 'FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE';
|
|
7
|
+
|
|
8
|
+
let _useAMAContext:
|
|
9
|
+
| (() => {
|
|
10
|
+
trackError?: (rule: FlatListRule, ref?: React.RefObject<any>) => void;
|
|
11
|
+
} | null)
|
|
12
|
+
| null = null;
|
|
13
|
+
try {
|
|
14
|
+
|
|
15
|
+
_useAMAContext = require('@react-native-ama/core').useAMAContext;
|
|
16
|
+
} catch {
|
|
17
|
+
// core is an optional peer — fall back to null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const getTrackError = () => {
|
|
21
|
+
try {
|
|
22
|
+
return _useAMAContext?.()?.trackError ?? null;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
5
28
|
export type UseDynamicList = {
|
|
6
29
|
data: ArrayLike<any>;
|
|
7
30
|
singularMessage: string;
|
|
@@ -20,28 +43,39 @@ export const useDynamicList = ({
|
|
|
20
43
|
const isFirstRender = React.useRef(true);
|
|
21
44
|
const initialCount = React.useRef(data?.length);
|
|
22
45
|
const lastItemsCount = React.useRef<null | number>(null);
|
|
23
|
-
|
|
24
|
-
const checks = __DEV__ ? useChecks?.() : null;
|
|
46
|
+
const trackError = __DEV__ ? getTrackError() : null;
|
|
25
47
|
|
|
26
48
|
__DEV__ &&
|
|
27
49
|
React.useEffect(() => {
|
|
28
50
|
if (!singularMessage?.includes('%count%')) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
51
|
+
const rule = 'FLATLIST_NO_COUNT_IN_SINGULAR_MESSAGE';
|
|
52
|
+
|
|
53
|
+
if (trackError) {
|
|
54
|
+
trackError(rule);
|
|
55
|
+
} else {
|
|
56
|
+
console.error('useDynamicFlatList', {
|
|
57
|
+
rule,
|
|
58
|
+
message: 'Special string %count% not found in singularMessage',
|
|
59
|
+
extra: singularMessage,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
34
62
|
}
|
|
35
63
|
|
|
36
64
|
if (!pluralMessage?.includes('%count%')) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
65
|
+
const rule = 'FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE';
|
|
66
|
+
|
|
67
|
+
if (trackError) {
|
|
68
|
+
trackError(rule);
|
|
69
|
+
} else {
|
|
70
|
+
console.error('useDynamicFlatList', {
|
|
71
|
+
rule,
|
|
72
|
+
message: 'Special string %count% not found in pluralMessage',
|
|
73
|
+
extra: pluralMessage,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
42
76
|
}
|
|
43
77
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
44
|
-
}, [pluralMessage, singularMessage
|
|
78
|
+
}, [pluralMessage, singularMessage]);
|
|
45
79
|
|
|
46
80
|
React.useEffect(() => {
|
|
47
81
|
const itemsCount = data?.length || 0;
|
|
@@ -67,16 +101,10 @@ export const useDynamicList = ({
|
|
|
67
101
|
|
|
68
102
|
const rowsCount = lastItemsCount.current || initialCount.current || 0;
|
|
69
103
|
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
style: checks?.debugStyle,
|
|
75
|
-
}
|
|
76
|
-
: {
|
|
77
|
-
rowsCount: rowsCount / numColumns,
|
|
78
|
-
columnsCount: numColumns,
|
|
79
|
-
};
|
|
104
|
+
return {
|
|
105
|
+
rowsCount: rowsCount / numColumns,
|
|
106
|
+
columnsCount: numColumns,
|
|
107
|
+
};
|
|
80
108
|
};
|
|
81
109
|
|
|
82
110
|
function simpleIsPlural(count: number) {
|