@terreno/ui 0.11.1 → 0.11.2
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/Common.d.ts +12 -12
- package/dist/Common.js.map +1 -1
- package/package.json +1 -1
- package/src/Common.ts +34 -13
- package/src/Modal.test.tsx +46 -1
- package/src/useConsentForms.test.ts +41 -19
- package/src/useSubmitConsent.test.ts +16 -5
package/dist/Common.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CountryCode } from "libphonenumber-js";
|
|
2
2
|
import type React from "react";
|
|
3
3
|
import type { ReactElement, ReactNode } from "react";
|
|
4
|
-
import type { ListRenderItemInfo, StyleProp, TextStyle, ViewStyle } from "react-native";
|
|
4
|
+
import type { ImageStyle, ListRenderItemInfo, StyleProp, TextInput, TextStyle, ViewStyle } from "react-native";
|
|
5
5
|
import type { DimensionValue } from "react-native/Libraries/StyleSheet/StyleSheetTypes";
|
|
6
6
|
import type { Styles } from "react-native-google-places-autocomplete";
|
|
7
7
|
import type { SvgProps } from "react-native-svg";
|
|
@@ -418,7 +418,7 @@ export interface BoxPropsBase {
|
|
|
418
418
|
zIndex?: number | "auto";
|
|
419
419
|
onClick?: () => void | Promise<void>;
|
|
420
420
|
className?: string;
|
|
421
|
-
style?:
|
|
421
|
+
style?: StyleProp<ViewStyle>;
|
|
422
422
|
onHoverStart?: () => void | Promise<void>;
|
|
423
423
|
onHoverEnd?: () => void | Promise<void>;
|
|
424
424
|
scroll?: boolean;
|
|
@@ -442,7 +442,7 @@ export type BoxProps = (BoxPropsBase & {
|
|
|
442
442
|
} & AccessibilityProps);
|
|
443
443
|
export type BoxColor = SurfaceColor | "transparent";
|
|
444
444
|
export interface ErrorBoundaryProps {
|
|
445
|
-
onError?: (error: Error, stack:
|
|
445
|
+
onError?: (error: Error, stack: string) => void;
|
|
446
446
|
children?: ReactNode;
|
|
447
447
|
}
|
|
448
448
|
export interface IconProps {
|
|
@@ -515,7 +515,7 @@ export interface TextFieldProps extends BaseFieldProps, HelperTextProps, ErrorTe
|
|
|
515
515
|
grow?: boolean;
|
|
516
516
|
multiline?: boolean;
|
|
517
517
|
rows?: number;
|
|
518
|
-
inputRef?:
|
|
518
|
+
inputRef?: (ref: TextInput | null) => void;
|
|
519
519
|
trimOnBlur?: boolean;
|
|
520
520
|
aiSuggestion?: AiSuggestionProps;
|
|
521
521
|
}
|
|
@@ -605,7 +605,7 @@ export interface ImageProps {
|
|
|
605
605
|
size?: string;
|
|
606
606
|
srcSet?: string;
|
|
607
607
|
fullWidth?: boolean;
|
|
608
|
-
style?:
|
|
608
|
+
style?: ImageStyle;
|
|
609
609
|
}
|
|
610
610
|
export interface BackButtonInterface {
|
|
611
611
|
onBack: () => void;
|
|
@@ -666,7 +666,7 @@ export interface SplitPageProps {
|
|
|
666
666
|
renderListViewHeader?: () => ReactElement | null;
|
|
667
667
|
renderContent?: (index?: number) => ReactElement | ReactElement[] | null;
|
|
668
668
|
listViewData: any[];
|
|
669
|
-
listViewExtraData?:
|
|
669
|
+
listViewExtraData?: unknown;
|
|
670
670
|
listViewWidth?: number;
|
|
671
671
|
listViewMaxWidth?: number;
|
|
672
672
|
renderChild?: () => ReactChild;
|
|
@@ -686,7 +686,7 @@ export interface AddressInterface {
|
|
|
686
686
|
export interface TransformValueOptions {
|
|
687
687
|
func?: (value: string) => string;
|
|
688
688
|
options?: {
|
|
689
|
-
[key: string]:
|
|
689
|
+
[key: string]: unknown;
|
|
690
690
|
};
|
|
691
691
|
}
|
|
692
692
|
export type ReactChild = ReactNode;
|
|
@@ -1624,11 +1624,11 @@ export interface PageProps {
|
|
|
1624
1624
|
color?: SurfaceColor;
|
|
1625
1625
|
maxWidth?: number | string;
|
|
1626
1626
|
keyboardOffset?: number;
|
|
1627
|
-
footer?:
|
|
1627
|
+
footer?: ReactNode;
|
|
1628
1628
|
rightButton?: string;
|
|
1629
1629
|
rightButtonOnClick?: () => void;
|
|
1630
|
-
children?:
|
|
1631
|
-
onError?: (error: Error, stack:
|
|
1630
|
+
children?: ReactChildren;
|
|
1631
|
+
onError?: (error: Error, stack: string) => void;
|
|
1632
1632
|
}
|
|
1633
1633
|
export interface ProgressBarProps {
|
|
1634
1634
|
color: SurfaceColor;
|
|
@@ -1647,7 +1647,7 @@ export interface RadioFieldProps {
|
|
|
1647
1647
|
export interface SignatureFieldProps {
|
|
1648
1648
|
disabled?: boolean;
|
|
1649
1649
|
value?: string;
|
|
1650
|
-
onChange: (value:
|
|
1650
|
+
onChange: (value: string) => void;
|
|
1651
1651
|
title?: string;
|
|
1652
1652
|
onStart?: () => void;
|
|
1653
1653
|
onEnd?: () => void;
|
|
@@ -2027,7 +2027,7 @@ export interface APIError {
|
|
|
2027
2027
|
pointer?: string;
|
|
2028
2028
|
parameter?: string;
|
|
2029
2029
|
meta?: {
|
|
2030
|
-
[id: string]:
|
|
2030
|
+
[id: string]: unknown;
|
|
2031
2031
|
};
|
|
2032
2032
|
};
|
|
2033
2033
|
}
|
package/dist/Common.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Common.js","sourceRoot":"","sources":["../src/Common.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Common.js","sourceRoot":"","sources":["../src/Common.ts"],"names":[],"mappings":"AA4XA,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,CAAC,EAAE,CAAC;IACJ,CAAC,EAAE,CAAC;IACJ,CAAC,EAAE,CAAC;IACJ,CAAC,EAAE,EAAE;IACL,CAAC,EAAE,EAAE;IACL,CAAC,EAAE,EAAE;IACL,CAAC,EAAE,EAAE;IACL,CAAC,EAAE,EAAE;IACL,CAAC,EAAE,EAAE;IACL,CAAC,EAAE,EAAE;IACL,EAAE,EAAE,EAAE;IACN,EAAE,EAAE,EAAE;IACN,EAAE,EAAE,EAAE;CACP,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,OAAqB;IAC9C,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,OAAO,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAmB,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,WAAW,CAAC,OAAyB,CAAC,CAAC;AAChD,CAAC;AAqBD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAe,EAAE,EAAE;IAClD,OAAO;QACL,KAAK,EAAE,EAAE;QACT,EAAE,EAAE,EAAE;QACN,EAAE,EAAE,EAAE;QACN,EAAE,EAAE,EAAE;QACN,EAAE,EAAE,EAAE;QACN,EAAE,EAAE,CAAC;KACN,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;AAClB,CAAC,CAAC;AAsTF,MAAM,YAAY,GAAG;IACnB,KAAK,EAAE,GAAG;IACV,KAAK,EAAE,GAAG;IACV,OAAO,EAAE,CAAC;IACV,IAAI,EAAE,EAAE;IACR,EAAE,EAAE,EAAE;IACN,EAAE,EAAE,CAAC;IACL,OAAO,EAAE,CAAC;IACV,OAAO,EAAE,GAAG;IACZ,EAAE,EAAE,CAAC;IACL,EAAE,EAAE,EAAE;CACP,CAAC;AAEF,MAAM,UAAU,WAAW,CAAC,QAAkB;IAC5C,OAAO,YAAY,CAAC,QAAQ,CAAC,CAAC;AAChC,CAAC;AAqoED;;GAEG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;CACV,CAAC"}
|
package/package.json
CHANGED
package/src/Common.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type {CountryCode} from "libphonenumber-js";
|
|
2
2
|
import type React from "react";
|
|
3
3
|
import type {ReactElement, ReactNode} from "react";
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
ImageStyle,
|
|
6
|
+
ListRenderItemInfo,
|
|
7
|
+
StyleProp,
|
|
8
|
+
TextInput,
|
|
9
|
+
TextStyle,
|
|
10
|
+
ViewStyle,
|
|
11
|
+
} from "react-native";
|
|
5
12
|
import type {DimensionValue} from "react-native/Libraries/StyleSheet/StyleSheetTypes";
|
|
6
13
|
import type {Styles} from "react-native-google-places-autocomplete";
|
|
7
14
|
import type {SvgProps} from "react-native-svg";
|
|
@@ -456,6 +463,7 @@ export interface BoxPropsBase {
|
|
|
456
463
|
lgColumn?: UnsignedUpTo12;
|
|
457
464
|
dangerouslySetInlineStyle?: {
|
|
458
465
|
__style: {
|
|
466
|
+
// noExplicitAny: escape hatch for arbitrary inline style values
|
|
459
467
|
[key: string]: any;
|
|
460
468
|
};
|
|
461
469
|
};
|
|
@@ -528,7 +536,7 @@ export interface BoxPropsBase {
|
|
|
528
536
|
|
|
529
537
|
onClick?: () => void | Promise<void>;
|
|
530
538
|
className?: string;
|
|
531
|
-
style?:
|
|
539
|
+
style?: StyleProp<ViewStyle>;
|
|
532
540
|
onHoverStart?: () => void | Promise<void>;
|
|
533
541
|
onHoverEnd?: () => void | Promise<void>;
|
|
534
542
|
scroll?: boolean;
|
|
@@ -554,7 +562,7 @@ export type BoxProps =
|
|
|
554
562
|
export type BoxColor = SurfaceColor | "transparent";
|
|
555
563
|
|
|
556
564
|
export interface ErrorBoundaryProps {
|
|
557
|
-
onError?: (error: Error, stack:
|
|
565
|
+
onError?: (error: Error, stack: string) => void;
|
|
558
566
|
children?: ReactNode;
|
|
559
567
|
}
|
|
560
568
|
|
|
@@ -652,7 +660,7 @@ export interface TextFieldProps extends BaseFieldProps, HelperTextProps, ErrorTe
|
|
|
652
660
|
multiline?: boolean;
|
|
653
661
|
rows?: number;
|
|
654
662
|
|
|
655
|
-
inputRef?:
|
|
663
|
+
inputRef?: (ref: TextInput | null) => void;
|
|
656
664
|
trimOnBlur?: boolean;
|
|
657
665
|
|
|
658
666
|
aiSuggestion?: AiSuggestionProps;
|
|
@@ -782,7 +790,7 @@ export interface ImageProps {
|
|
|
782
790
|
size?: string;
|
|
783
791
|
srcSet?: string;
|
|
784
792
|
fullWidth?: boolean;
|
|
785
|
-
style?:
|
|
793
|
+
style?: ImageStyle;
|
|
786
794
|
}
|
|
787
795
|
|
|
788
796
|
export interface BackButtonInterface {
|
|
@@ -850,14 +858,17 @@ export interface SplitPageProps {
|
|
|
850
858
|
loading?: boolean;
|
|
851
859
|
color?: SurfaceColor;
|
|
852
860
|
keyboardOffset?: number;
|
|
861
|
+
// noExplicitAny: ListRenderItemInfo generic type depends on the consumer's data shape
|
|
853
862
|
renderListViewItem: (itemInfo: ListRenderItemInfo<any>) => ReactElement | null;
|
|
854
863
|
renderListViewHeader?: () => ReactElement | null;
|
|
855
864
|
renderContent?: (index?: number) => ReactElement | ReactElement[] | null;
|
|
865
|
+
// noExplicitAny: list data type varies by consumer's data model
|
|
856
866
|
listViewData: any[];
|
|
857
|
-
listViewExtraData?:
|
|
867
|
+
listViewExtraData?: unknown;
|
|
858
868
|
listViewWidth?: number;
|
|
859
869
|
listViewMaxWidth?: number;
|
|
860
870
|
renderChild?: () => ReactChild;
|
|
871
|
+
// noExplicitAny: callback value type varies by consumer's data model
|
|
861
872
|
onSelectionChange?: (value?: any) => void | Promise<void>;
|
|
862
873
|
}
|
|
863
874
|
|
|
@@ -896,7 +907,7 @@ export interface AddressInterface {
|
|
|
896
907
|
export interface TransformValueOptions {
|
|
897
908
|
func?: (value: string) => string;
|
|
898
909
|
options?: {
|
|
899
|
-
[key: string]:
|
|
910
|
+
[key: string]: unknown;
|
|
900
911
|
};
|
|
901
912
|
}
|
|
902
913
|
|
|
@@ -1745,6 +1756,7 @@ export interface HeightActionSheetProps {
|
|
|
1745
1756
|
|
|
1746
1757
|
export interface HyperlinkProps {
|
|
1747
1758
|
linkDefault?: boolean;
|
|
1759
|
+
// noExplicitAny: type comes from external linkify-it library which lacks an exported type
|
|
1748
1760
|
linkify?: any;
|
|
1749
1761
|
linkStyle?: StyleProp<any>;
|
|
1750
1762
|
linkText?: string | ((url: string) => string);
|
|
@@ -1901,10 +1913,12 @@ export interface ModalProps {
|
|
|
1901
1913
|
/**
|
|
1902
1914
|
* The function to call when the primary button is clicked.
|
|
1903
1915
|
*/
|
|
1916
|
+
// noExplicitAny: callback value type varies by consumer context
|
|
1904
1917
|
primaryButtonOnClick?: (value?: any) => void | Promise<void>;
|
|
1905
1918
|
/**
|
|
1906
1919
|
* The function to call when the secondary button is clicked.
|
|
1907
1920
|
*/
|
|
1921
|
+
// noExplicitAny: callback value type varies by consumer context
|
|
1908
1922
|
secondaryButtonOnClick?: (value?: any) => void | Promise<void>;
|
|
1909
1923
|
}
|
|
1910
1924
|
|
|
@@ -1917,6 +1931,7 @@ export interface NumberPickerActionSheetProps {
|
|
|
1917
1931
|
}
|
|
1918
1932
|
|
|
1919
1933
|
export interface PageProps {
|
|
1934
|
+
// noExplicitAny: React Navigation type varies by navigation stack configuration
|
|
1920
1935
|
navigation?: any;
|
|
1921
1936
|
scroll?: boolean;
|
|
1922
1937
|
loading?: boolean;
|
|
@@ -1930,11 +1945,11 @@ export interface PageProps {
|
|
|
1930
1945
|
color?: SurfaceColor;
|
|
1931
1946
|
maxWidth?: number | string;
|
|
1932
1947
|
keyboardOffset?: number;
|
|
1933
|
-
footer?:
|
|
1948
|
+
footer?: ReactNode;
|
|
1934
1949
|
rightButton?: string;
|
|
1935
1950
|
rightButtonOnClick?: () => void;
|
|
1936
|
-
children?:
|
|
1937
|
-
onError?: (error: Error, stack:
|
|
1951
|
+
children?: ReactChildren;
|
|
1952
|
+
onError?: (error: Error, stack: string) => void;
|
|
1938
1953
|
}
|
|
1939
1954
|
|
|
1940
1955
|
export interface ProgressBarProps {
|
|
@@ -1957,7 +1972,7 @@ export interface RadioFieldProps {
|
|
|
1957
1972
|
export interface SignatureFieldProps {
|
|
1958
1973
|
disabled?: boolean; // default "default"
|
|
1959
1974
|
value?: string;
|
|
1960
|
-
onChange: (value:
|
|
1975
|
+
onChange: (value: string) => void;
|
|
1961
1976
|
title?: string; // default "Signature"
|
|
1962
1977
|
onStart?: () => void;
|
|
1963
1978
|
onEnd?: () => void;
|
|
@@ -2356,16 +2371,19 @@ export type TapToEditProps =
|
|
|
2356
2371
|
|
|
2357
2372
|
export interface BaseTapToEditProps extends Omit<FieldProps, "onChange" | "value"> {
|
|
2358
2373
|
title: string;
|
|
2374
|
+
// noExplicitAny: value type varies across TapToEdit field types (text, number, date, etc.)
|
|
2359
2375
|
value: any;
|
|
2360
2376
|
|
|
2361
2377
|
/**
|
|
2362
2378
|
* Not required if not editable.
|
|
2363
2379
|
*/
|
|
2380
|
+
// noExplicitAny: value type varies across TapToEdit field types
|
|
2364
2381
|
setValue?: (value: any) => void;
|
|
2365
2382
|
|
|
2366
2383
|
/**
|
|
2367
2384
|
* Not required if not editable.
|
|
2368
2385
|
*/
|
|
2386
|
+
// noExplicitAny: value type varies across TapToEdit field types
|
|
2369
2387
|
onSave?: (value: any) => void | Promise<void>;
|
|
2370
2388
|
|
|
2371
2389
|
/**
|
|
@@ -2378,6 +2396,7 @@ export interface BaseTapToEditProps extends Omit<FieldProps, "onChange" | "value
|
|
|
2378
2396
|
* Enable edit mode from outside the component.
|
|
2379
2397
|
*/
|
|
2380
2398
|
isEditing?: boolean;
|
|
2399
|
+
// noExplicitAny: input value type varies across TapToEdit field types
|
|
2381
2400
|
transform?: (value: any) => string;
|
|
2382
2401
|
/**
|
|
2383
2402
|
* Show a confirmation modal before saving the value.
|
|
@@ -2433,7 +2452,7 @@ export interface APIError {
|
|
|
2433
2452
|
source?: string;
|
|
2434
2453
|
pointer?: string;
|
|
2435
2454
|
parameter?: string;
|
|
2436
|
-
meta?: {[id: string]:
|
|
2455
|
+
meta?: {[id: string]: unknown};
|
|
2437
2456
|
};
|
|
2438
2457
|
}
|
|
2439
2458
|
|
|
@@ -2466,6 +2485,7 @@ export interface ModelFields {
|
|
|
2466
2485
|
|
|
2467
2486
|
export interface OpenAPISpec {
|
|
2468
2487
|
paths: {
|
|
2488
|
+
// noExplicitAny: OpenAPI path items are deeply accessed with chained property lookups
|
|
2469
2489
|
[key: string]: any;
|
|
2470
2490
|
};
|
|
2471
2491
|
}
|
|
@@ -2498,7 +2518,8 @@ export interface ModelAdminFieldConfig {
|
|
|
2498
2518
|
|
|
2499
2519
|
// The props for a custom column component for ModelAdmin.
|
|
2500
2520
|
export interface ModelAdminCustomComponentProps extends Omit<FieldProps, "name"> {
|
|
2501
|
-
|
|
2521
|
+
// noExplicitAny: document shape varies by model used with ModelAdmin
|
|
2522
|
+
doc: any;
|
|
2502
2523
|
fieldKey: string; // Dot notation representation of the field.
|
|
2503
2524
|
// user: User;
|
|
2504
2525
|
editing: boolean; // Allow for inline editing of the field.
|
package/src/Modal.test.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {describe, expect, it, mock} from "bun:test";
|
|
1
|
+
import {afterEach, describe, expect, it, mock} from "bun:test";
|
|
2
2
|
import {fireEvent} from "@testing-library/react-native";
|
|
3
3
|
|
|
4
|
+
import {isMobileDevice} from "./MediaQuery";
|
|
4
5
|
import {Modal} from "./Modal";
|
|
5
6
|
import {Text} from "./Text";
|
|
6
7
|
import {renderWithTheme} from "./test-utils";
|
|
@@ -355,3 +356,47 @@ describe("Modal", () => {
|
|
|
355
356
|
expect(stopPropagation).not.toHaveBeenCalled();
|
|
356
357
|
});
|
|
357
358
|
});
|
|
359
|
+
|
|
360
|
+
describe("Modal mobile branch", () => {
|
|
361
|
+
afterEach(() => {
|
|
362
|
+
(isMobileDevice as ReturnType<typeof mock>).mockImplementation(() => false);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("renders ActionSheet when isMobileDevice is true", () => {
|
|
366
|
+
(isMobileDevice as ReturnType<typeof mock>).mockImplementation(() => true);
|
|
367
|
+
const {toJSON} = renderWithTheme(
|
|
368
|
+
<Modal onDismiss={() => {}} title="Mobile Modal" visible>
|
|
369
|
+
<Text>Mobile Content</Text>
|
|
370
|
+
</Modal>
|
|
371
|
+
);
|
|
372
|
+
expect(toJSON()).toBeTruthy();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("renders ActionSheet with title and buttons on mobile", () => {
|
|
376
|
+
(isMobileDevice as ReturnType<typeof mock>).mockImplementation(() => true);
|
|
377
|
+
const {toJSON} = renderWithTheme(
|
|
378
|
+
<Modal
|
|
379
|
+
onDismiss={() => {}}
|
|
380
|
+
primaryButtonOnClick={() => {}}
|
|
381
|
+
primaryButtonText="Save"
|
|
382
|
+
secondaryButtonOnClick={() => {}}
|
|
383
|
+
secondaryButtonText="Cancel"
|
|
384
|
+
title="Mobile Actions"
|
|
385
|
+
visible
|
|
386
|
+
>
|
|
387
|
+
<Text>Content</Text>
|
|
388
|
+
</Modal>
|
|
389
|
+
);
|
|
390
|
+
expect(toJSON()).toBeTruthy();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("renders ActionSheet with persistOnBackgroundClick enabled", () => {
|
|
394
|
+
(isMobileDevice as ReturnType<typeof mock>).mockImplementation(() => true);
|
|
395
|
+
const {toJSON} = renderWithTheme(
|
|
396
|
+
<Modal onDismiss={() => {}} persistOnBackgroundClick title="Persistent Mobile" visible>
|
|
397
|
+
<Text>Content</Text>
|
|
398
|
+
</Modal>
|
|
399
|
+
);
|
|
400
|
+
expect(toJSON()).toBeTruthy();
|
|
401
|
+
});
|
|
402
|
+
});
|
|
@@ -1,32 +1,53 @@
|
|
|
1
1
|
import {describe, expect, it, mock} from "bun:test";
|
|
2
2
|
import {renderHook} from "@testing-library/react-native";
|
|
3
3
|
|
|
4
|
+
import type {ConsentFormPublic} from "./useConsentForms";
|
|
4
5
|
import {detectLocale, useConsentForms} from "./useConsentForms";
|
|
5
6
|
|
|
7
|
+
type ConsentFormsApi = Parameters<typeof useConsentForms>[0];
|
|
8
|
+
|
|
9
|
+
interface GlobalThisWithNavigator {
|
|
10
|
+
navigator: {language: string} | undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface MockQueryDef {
|
|
14
|
+
onQueryStarted?: (
|
|
15
|
+
_arg: unknown,
|
|
16
|
+
helpers: {queryFulfilled: Promise<{data: ConsentFormPublic[]}> | Promise<never>}
|
|
17
|
+
) => Promise<void>;
|
|
18
|
+
query: () => string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface MockInjectOpts {
|
|
22
|
+
endpoints: (build: {query: (def: MockQueryDef) => string}) => Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
|
|
6
25
|
mock.module("expo-localization", () => ({
|
|
7
26
|
getLocales: () => [{languageTag: "es-ES"}],
|
|
8
27
|
}));
|
|
9
28
|
|
|
10
29
|
describe("detectLocale", () => {
|
|
11
30
|
it("returns locale from expo-localization in native test env", () => {
|
|
12
|
-
const
|
|
13
|
-
|
|
31
|
+
const g = globalThis as unknown as GlobalThisWithNavigator;
|
|
32
|
+
const originalNavigator = g.navigator;
|
|
33
|
+
g.navigator = undefined;
|
|
14
34
|
const locale = detectLocale();
|
|
15
|
-
|
|
35
|
+
g.navigator = originalNavigator;
|
|
16
36
|
expect(typeof locale).toBe("string");
|
|
17
37
|
expect(locale.length).toBeGreaterThan(0);
|
|
18
38
|
});
|
|
19
39
|
|
|
20
40
|
it("falls back to en when expo-localization throws and no navigator", () => {
|
|
21
|
-
const
|
|
22
|
-
|
|
41
|
+
const g = globalThis as unknown as GlobalThisWithNavigator;
|
|
42
|
+
const originalNavigator = g.navigator;
|
|
43
|
+
g.navigator = undefined;
|
|
23
44
|
mock.module("expo-localization", () => ({
|
|
24
45
|
getLocales: () => {
|
|
25
46
|
throw new Error("not available");
|
|
26
47
|
},
|
|
27
48
|
}));
|
|
28
49
|
const locale = detectLocale();
|
|
29
|
-
|
|
50
|
+
g.navigator = originalNavigator;
|
|
30
51
|
// Reset mock
|
|
31
52
|
mock.module("expo-localization", () => ({
|
|
32
53
|
getLocales: () => [{languageTag: "es-ES"}],
|
|
@@ -35,10 +56,11 @@ describe("detectLocale", () => {
|
|
|
35
56
|
});
|
|
36
57
|
|
|
37
58
|
it("returns navigator.language when available", () => {
|
|
38
|
-
const
|
|
39
|
-
|
|
59
|
+
const g = globalThis as unknown as GlobalThisWithNavigator;
|
|
60
|
+
const originalNavigator = g.navigator;
|
|
61
|
+
g.navigator = {language: "fr-FR"};
|
|
40
62
|
const locale = detectLocale();
|
|
41
|
-
|
|
63
|
+
g.navigator = originalNavigator;
|
|
42
64
|
expect(locale).toBe("fr-FR");
|
|
43
65
|
});
|
|
44
66
|
});
|
|
@@ -54,10 +76,10 @@ describe("useConsentForms", () => {
|
|
|
54
76
|
}));
|
|
55
77
|
const api = {
|
|
56
78
|
enhanceEndpoints: mock(() => ({
|
|
57
|
-
injectEndpoints: mock((opts:
|
|
79
|
+
injectEndpoints: mock((opts: MockInjectOpts) => {
|
|
58
80
|
// Call endpoint builder to exercise provides/onQueryStarted
|
|
59
81
|
const build = {
|
|
60
|
-
query: mock((def:
|
|
82
|
+
query: mock((def: MockQueryDef) => {
|
|
61
83
|
// Call onQueryStarted with a successful result
|
|
62
84
|
if (def.onQueryStarted) {
|
|
63
85
|
void def.onQueryStarted(undefined, {
|
|
@@ -78,21 +100,21 @@ describe("useConsentForms", () => {
|
|
|
78
100
|
};
|
|
79
101
|
|
|
80
102
|
it("returns an array of forms when response is an array", () => {
|
|
81
|
-
const {api} = buildApi({data: [{id: "1", title: "Form 1"} as
|
|
82
|
-
const {result} = renderHook(() => useConsentForms(api as
|
|
103
|
+
const {api} = buildApi({data: [{id: "1", title: "Form 1"} as unknown as ConsentFormPublic]});
|
|
104
|
+
const {result} = renderHook(() => useConsentForms(api as unknown as ConsentFormsApi));
|
|
83
105
|
expect(result.current.forms).toBeDefined();
|
|
84
106
|
expect(Array.isArray(result.current.forms)).toBe(true);
|
|
85
107
|
});
|
|
86
108
|
|
|
87
109
|
it("unwraps .data property when response is object shape", () => {
|
|
88
|
-
const {api} = buildApi({data: {data: [{id: "2"} as
|
|
89
|
-
const {result} = renderHook(() => useConsentForms(api as
|
|
110
|
+
const {api} = buildApi({data: {data: [{id: "2"} as unknown as ConsentFormPublic]}});
|
|
111
|
+
const {result} = renderHook(() => useConsentForms(api as unknown as ConsentFormsApi, "/api"));
|
|
90
112
|
expect(Array.isArray(result.current.forms)).toBe(true);
|
|
91
113
|
});
|
|
92
114
|
|
|
93
115
|
it("returns empty array when no data is present", () => {
|
|
94
116
|
const {api} = buildApi({data: undefined, isLoading: true});
|
|
95
|
-
const {result} = renderHook(() => useConsentForms(api as
|
|
117
|
+
const {result} = renderHook(() => useConsentForms(api as unknown as ConsentFormsApi));
|
|
96
118
|
expect(result.current.forms).toEqual([]);
|
|
97
119
|
expect(result.current.isLoading).toBe(true);
|
|
98
120
|
});
|
|
@@ -101,9 +123,9 @@ describe("useConsentForms", () => {
|
|
|
101
123
|
const refetch = mock(() => {});
|
|
102
124
|
const api = {
|
|
103
125
|
enhanceEndpoints: mock(() => ({
|
|
104
|
-
injectEndpoints: mock((opts:
|
|
126
|
+
injectEndpoints: mock((opts: MockInjectOpts) => {
|
|
105
127
|
const build = {
|
|
106
|
-
query: mock((def:
|
|
128
|
+
query: mock((def: MockQueryDef) => {
|
|
107
129
|
if (def.onQueryStarted) {
|
|
108
130
|
void def.onQueryStarted(undefined, {
|
|
109
131
|
queryFulfilled: Promise.reject(new Error("failed")),
|
|
@@ -124,7 +146,7 @@ describe("useConsentForms", () => {
|
|
|
124
146
|
}),
|
|
125
147
|
})),
|
|
126
148
|
};
|
|
127
|
-
const {result} = renderHook(() => useConsentForms(api as
|
|
149
|
+
const {result} = renderHook(() => useConsentForms(api as unknown as ConsentFormsApi));
|
|
128
150
|
expect(result.current.error).toBe("error");
|
|
129
151
|
});
|
|
130
152
|
});
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import {describe, expect, it, mock} from "bun:test";
|
|
2
2
|
import {renderHook} from "@testing-library/react-native";
|
|
3
3
|
|
|
4
|
+
import type {SubmitConsentBody} from "./useSubmitConsent";
|
|
4
5
|
import {useSubmitConsent} from "./useSubmitConsent";
|
|
5
6
|
|
|
7
|
+
type SubmitConsentApi = Parameters<typeof useSubmitConsent>[0];
|
|
8
|
+
|
|
9
|
+
interface MockMutationDef {
|
|
10
|
+
query: (body: SubmitConsentBody) => {method: string; url: string};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface MockInjectOpts {
|
|
14
|
+
endpoints: (build: {mutation: (def: MockMutationDef) => string}) => Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
6
17
|
describe("useSubmitConsent", () => {
|
|
7
18
|
const buildApi = () => {
|
|
8
19
|
const unwrap = mock(async () => ({success: true}));
|
|
@@ -13,9 +24,9 @@ describe("useSubmitConsent", () => {
|
|
|
13
24
|
]);
|
|
14
25
|
const api = {
|
|
15
26
|
enhanceEndpoints: mock(() => ({
|
|
16
|
-
injectEndpoints: mock((opts:
|
|
27
|
+
injectEndpoints: mock((opts: MockInjectOpts) => {
|
|
17
28
|
const build = {
|
|
18
|
-
mutation: mock((def:
|
|
29
|
+
mutation: mock((def: MockMutationDef) => {
|
|
19
30
|
// Exercise the query builder
|
|
20
31
|
const result = def.query({
|
|
21
32
|
agreed: true,
|
|
@@ -37,7 +48,7 @@ describe("useSubmitConsent", () => {
|
|
|
37
48
|
|
|
38
49
|
it("returns submit function and state", () => {
|
|
39
50
|
const {api} = buildApi();
|
|
40
|
-
const {result} = renderHook(() => useSubmitConsent(api as
|
|
51
|
+
const {result} = renderHook(() => useSubmitConsent(api as unknown as SubmitConsentApi));
|
|
41
52
|
expect(typeof result.current.submit).toBe("function");
|
|
42
53
|
expect(result.current.isSubmitting).toBe(false);
|
|
43
54
|
expect(result.current.error).toBeUndefined();
|
|
@@ -45,7 +56,7 @@ describe("useSubmitConsent", () => {
|
|
|
45
56
|
|
|
46
57
|
it("calls submit mutation when submit is invoked", async () => {
|
|
47
58
|
const {api, submitMutation, unwrap} = buildApi();
|
|
48
|
-
const {result} = renderHook(() => useSubmitConsent(api as
|
|
59
|
+
const {result} = renderHook(() => useSubmitConsent(api as unknown as SubmitConsentApi, "/api"));
|
|
49
60
|
const response = await result.current.submit({
|
|
50
61
|
agreed: true,
|
|
51
62
|
consentFormId: "f1",
|
|
@@ -58,7 +69,7 @@ describe("useSubmitConsent", () => {
|
|
|
58
69
|
|
|
59
70
|
it("uses empty baseUrl when none provided", () => {
|
|
60
71
|
const {api} = buildApi();
|
|
61
|
-
const {result} = renderHook(() => useSubmitConsent(api as
|
|
72
|
+
const {result} = renderHook(() => useSubmitConsent(api as unknown as SubmitConsentApi));
|
|
62
73
|
expect(result.current.submit).toBeDefined();
|
|
63
74
|
});
|
|
64
75
|
});
|