@react-native-ama/lists 1.2.0 → 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.
@@ -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
- const dynamicList = (0, useDynamicList_1.useDynamicList)({
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} style={dynamicList.style}/>) : (<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 checks = __DEV__ ? core_1.useChecks === null || core_1.useChecks === void 0 ? void 0 : (0, core_1.useChecks)() : null;
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
- checks === null || checks === void 0 ? void 0 : checks.logResult('useDynamicFlatList', {
19
- rule: 'FLATLIST_NO_COUNT_IN_SINGULAR_MESSAGE',
20
- message: 'Special string %count% not found in singularMessage',
21
- extra: singularMessage,
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
- checks === null || checks === void 0 ? void 0 : checks.logResult('useDynamicFlatList', {
26
- rule: 'FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE',
27
- message: 'Special string %count% not found in pluralMessage',
28
- extra: pluralMessage,
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, checks === null || checks === void 0 ? void 0 : checks.logResult]);
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 __DEV__
49
- ? {
50
- rowsCount: rowsCount / numColumns,
51
- columnsCount: numColumns,
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": "1.2.0",
4
- "react-native": "src/index",
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
- "imports": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
12
- },
13
- "./dist/index.js"
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.0",
26
- "@react-native-ama/internal": "~1.2.0"
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 { render } from '@testing-library/react-native';
2
2
  import * as React from 'react';
3
-
4
3
  import * as UseDynamicList from '../hooks/useDynamicList';
5
4
  import { DynamicFlatList } from './DynamicFlatList';
6
5
 
@@ -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
- const dynamicList = useDynamicList({
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
- style={dynamicList.style}
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, Platform } from 'react-native';
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 | count | type
187
- ${0} | ${2} | ${2} | ${'plural'}
188
- ${1} | ${2} | ${1} | ${'singular'}
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('uses the given rowCount when specified', () => {
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()).toMatchInlineSnapshot(`
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('divides the number of items by the number of columns', () => {
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()).toMatchInlineSnapshot(`
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()).toMatchInlineSnapshot(`
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
- beforeEach(function () {
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: 'hello',
25
- pluralMessage: '%count% me',
13
+ data: [1, 2, 3],
14
+ singularMessage: '%count% item',
15
+ pluralMessage: '%count% items',
26
16
  }),
27
17
  );
28
18
 
29
- expect(logResult).toHaveBeenCalledWith('useDynamicFlatList', {
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('checks that the plural message contains the word %count%', () => {
37
- renderHook(() =>
38
- useDynamicList({
39
- data: [],
40
- accessibilitySingularMessage: '%count% ok',
41
- } as any),
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
- expect(logResult).toHaveBeenCalledWith('useDynamicFlatList', {
45
- extra: undefined,
46
- message: 'Special string %count% not found in pluralMessage',
47
- rule: 'FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE',
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
- checks?.logResult('useDynamicFlatList', {
30
- rule: 'FLATLIST_NO_COUNT_IN_SINGULAR_MESSAGE',
31
- message: 'Special string %count% not found in singularMessage',
32
- extra: singularMessage,
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
- checks?.logResult('useDynamicFlatList', {
38
- rule: 'FLATLIST_NO_COUNT_IN_PLURAL_MESSAGE',
39
- message: 'Special string %count% not found in pluralMessage',
40
- extra: pluralMessage,
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, checks?.logResult]);
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 __DEV__
71
- ? {
72
- rowsCount: rowsCount / numColumns,
73
- columnsCount: numColumns,
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) {