ferns-ui 1.10.0 → 1.12.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/Accordion.js +1 -1
- package/dist/Accordion.js.map +1 -1
- package/dist/Accordion.test.d.ts +1 -0
- package/dist/Accordion.test.js +71 -0
- package/dist/Accordion.test.js.map +1 -0
- package/dist/AddressField.test.d.ts +1 -0
- package/dist/AddressField.test.js +65 -0
- package/dist/AddressField.test.js.map +1 -0
- package/dist/Avatar.js +2 -2
- package/dist/Avatar.js.map +1 -1
- package/dist/Avatar.test.d.ts +1 -0
- package/dist/Avatar.test.js +131 -0
- package/dist/Avatar.test.js.map +1 -0
- package/dist/Badge.d.ts +1 -1
- package/dist/Badge.js +3 -3
- package/dist/Badge.js.map +1 -1
- package/dist/Badge.test.d.ts +1 -0
- package/dist/Badge.test.js +76 -0
- package/dist/Badge.test.js.map +1 -0
- package/dist/Box.test.d.ts +1 -0
- package/dist/Box.test.js +528 -0
- package/dist/Box.test.js.map +1 -0
- package/dist/Common.d.ts +101 -2
- package/dist/Common.js.map +1 -1
- package/dist/DateTimeField.js +15 -2
- package/dist/DateTimeField.js.map +1 -1
- package/dist/Heading.js +2 -0
- package/dist/Heading.js.map +1 -1
- package/dist/InfoModalIcon.js +1 -1
- package/dist/InfoModalIcon.js.map +1 -1
- package/dist/MarkdownView.d.ts +5 -0
- package/dist/MarkdownView.js +44 -0
- package/dist/MarkdownView.js.map +1 -0
- package/dist/Slider.d.ts +3 -0
- package/dist/Slider.js +94 -0
- package/dist/Slider.js.map +1 -0
- package/dist/Text.js +2 -0
- package/dist/Text.js.map +1 -1
- package/dist/TextField.test.js +9 -9
- package/dist/TextField.test.js.map +1 -1
- package/dist/Tooltip.js +2 -0
- package/dist/Tooltip.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/setupTests.js +40 -2
- package/dist/setupTests.js.map +1 -1
- package/dist/test-utils.js.map +1 -1
- package/package.json +2 -47
- package/src/Accordion.test.tsx +104 -0
- package/src/Accordion.tsx +1 -0
- package/src/AddressField.test.tsx +89 -0
- package/src/Avatar.test.tsx +163 -0
- package/src/Avatar.tsx +2 -0
- package/src/Badge.test.tsx +116 -0
- package/src/Badge.tsx +3 -1
- package/src/Box.test.tsx +665 -0
- package/src/Common.ts +115 -2
- package/src/DateTimeField.tsx +15 -2
- package/src/Heading.tsx +2 -0
- package/src/InfoModalIcon.tsx +1 -0
- package/src/MarkdownView.tsx +67 -0
- package/src/Slider.tsx +205 -0
- package/src/Text.tsx +2 -0
- package/src/TextField.test.tsx +59 -71
- package/src/Tooltip.tsx +2 -0
- package/src/__snapshots__/Accordion.test.tsx.snap +120 -0
- package/src/__snapshots__/AddressField.test.tsx.snap +464 -0
- package/src/__snapshots__/Avatar.test.tsx.snap +78 -0
- package/src/__snapshots__/Badge.test.tsx.snap +44 -0
- package/src/__snapshots__/Box.test.tsx.snap +159 -0
- package/src/__snapshots__/TextArea.test.tsx.snap +12 -0
- package/src/__snapshots__/TextField.test.tsx.snap +38 -1
- package/src/index.tsx +2 -0
- package/src/setupTests.ts +45 -2
- package/src/test-utils.tsx +1 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {act, fireEvent} from "@testing-library/react-native";
|
|
2
|
+
// import {ImageManipulator} from "expo-image-manipulator";
|
|
3
|
+
// Import the actual implementation for SaveFormat
|
|
4
|
+
import * as ImageManipulator from "expo-image-manipulator";
|
|
5
|
+
import React from "react";
|
|
6
|
+
|
|
7
|
+
import {Avatar} from "./Avatar";
|
|
8
|
+
import {renderWithTheme} from "./test-utils";
|
|
9
|
+
|
|
10
|
+
// Mock expo-image-picker
|
|
11
|
+
jest.mock("expo-image-picker", () => ({
|
|
12
|
+
launchImageLibraryAsync: jest.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
// Mock expo-image-manipulator
|
|
16
|
+
const mockResize = jest.fn();
|
|
17
|
+
const mockRenderAsync = jest.fn();
|
|
18
|
+
const mockSaveAsync = jest.fn();
|
|
19
|
+
|
|
20
|
+
jest.mock("expo-image-manipulator", () => {
|
|
21
|
+
const actual = jest.requireActual("expo-image-manipulator");
|
|
22
|
+
return {
|
|
23
|
+
...actual,
|
|
24
|
+
ImageManipulator: {
|
|
25
|
+
manipulate: jest.fn().mockImplementation(() => ({
|
|
26
|
+
resize: mockResize.mockImplementation(() => ({
|
|
27
|
+
renderAsync: mockRenderAsync.mockResolvedValue({
|
|
28
|
+
saveAsync: mockSaveAsync.mockResolvedValue({
|
|
29
|
+
uri: "test-uri",
|
|
30
|
+
base64: "test-base64",
|
|
31
|
+
}),
|
|
32
|
+
}),
|
|
33
|
+
})),
|
|
34
|
+
})),
|
|
35
|
+
},
|
|
36
|
+
SaveFormat: {
|
|
37
|
+
PNG: "png",
|
|
38
|
+
JPEG: "jpeg",
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
jest.mock("expo-image-picker", () => ({
|
|
44
|
+
launchImageLibraryAsync: jest.fn().mockResolvedValue({
|
|
45
|
+
canceled: false,
|
|
46
|
+
assets: [
|
|
47
|
+
{
|
|
48
|
+
uri: "test-uri",
|
|
49
|
+
width: 100,
|
|
50
|
+
height: 100,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
}),
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
// Mock the LinearGradient component
|
|
57
|
+
jest.mock("expo-linear-gradient", () => ({
|
|
58
|
+
LinearGradient: "LinearGradient",
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
describe("Avatar", () => {
|
|
62
|
+
const defaultProps = {
|
|
63
|
+
name: "John Doe",
|
|
64
|
+
src: "https://example.com/avatar.jpg",
|
|
65
|
+
testID: "avatar",
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
jest.clearAllMocks();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("renders correctly with default props", () => {
|
|
73
|
+
const {toJSON} = renderWithTheme(<Avatar {...defaultProps} />);
|
|
74
|
+
expect(toJSON()).toMatchSnapshot();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("renders initials when no image is provided", () => {
|
|
78
|
+
const {getByText} = renderWithTheme(<Avatar name="John Doe" testID="avatar" />);
|
|
79
|
+
expect(getByText("JD")).toBeTruthy();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("renders image when src is provided", () => {
|
|
83
|
+
const {getByTestId} = renderWithTheme(<Avatar {...defaultProps} />);
|
|
84
|
+
const image = getByTestId("avatar-image");
|
|
85
|
+
expect(image).toBeTruthy();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("shows initials when image fails to load", () => {
|
|
89
|
+
const {getByText, getByTestId} = renderWithTheme(<Avatar {...defaultProps} name="John Doe" />);
|
|
90
|
+
|
|
91
|
+
// Simulate image load error
|
|
92
|
+
fireEvent(getByTestId("avatar-image"), "onError");
|
|
93
|
+
|
|
94
|
+
expect(getByText("JD")).toBeTruthy();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("applies correct size class", () => {
|
|
98
|
+
const size = "lg";
|
|
99
|
+
const {getByTestId} = renderWithTheme(<Avatar {...defaultProps} size={size} />);
|
|
100
|
+
const avatar = getByTestId("avatar-image");
|
|
101
|
+
// Check if the style contains the expected size
|
|
102
|
+
expect(avatar.props.style).toMatchObject({
|
|
103
|
+
height: 72, // lg size from the sizes object
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("shows status indicator when status is provided", () => {
|
|
108
|
+
const {getByTestId} = renderWithTheme(<Avatar {...defaultProps} status="online" />);
|
|
109
|
+
expect(getByTestId("status-indicator")).toBeTruthy();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("shows edit icon when status is imagePicker and size is xl", () => {
|
|
113
|
+
const {getByText} = renderWithTheme(
|
|
114
|
+
<Avatar {...defaultProps} size="xl" status="imagePicker" />
|
|
115
|
+
);
|
|
116
|
+
expect(getByText("Upload Image")).toBeTruthy();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("calls onChange when edit icon is pressed", async () => {
|
|
120
|
+
const mockOnChange = jest.fn();
|
|
121
|
+
const {getByText} = renderWithTheme(
|
|
122
|
+
<Avatar {...defaultProps} size="xl" status="imagePicker" onChange={mockOnChange} />
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
await act(async () => {
|
|
126
|
+
fireEvent.press(getByText("Upload Image"));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// The onChange should be called with the processed image
|
|
130
|
+
expect(mockOnChange).toHaveBeenCalledWith({
|
|
131
|
+
avatarImageFormat: "png",
|
|
132
|
+
uri: "data:image/png;base64,test-base64",
|
|
133
|
+
base64: "test-base64",
|
|
134
|
+
});
|
|
135
|
+
expect(ImageManipulator.ImageManipulator.manipulate).toHaveBeenCalled();
|
|
136
|
+
expect(mockResize).toHaveBeenCalled();
|
|
137
|
+
expect(mockRenderAsync).toHaveBeenCalled();
|
|
138
|
+
expect(mockSaveAsync).toHaveBeenCalledWith({
|
|
139
|
+
format: "png",
|
|
140
|
+
base64: true,
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("applies border when hasBorder is true", () => {
|
|
145
|
+
const {getByTestId} = renderWithTheme(<Avatar {...defaultProps} hasBorder />);
|
|
146
|
+
const avatar = getByTestId("avatar-image");
|
|
147
|
+
// Check if the style contains border properties
|
|
148
|
+
expect(avatar.props.style).toMatchObject({
|
|
149
|
+
borderWidth: expect.any(Number),
|
|
150
|
+
borderColor: expect.any(String),
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("shows warning when imagePicker status is used with non-xl size", () => {
|
|
155
|
+
const consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
156
|
+
renderWithTheme(<Avatar {...defaultProps} size="lg" status="imagePicker" />);
|
|
157
|
+
|
|
158
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
159
|
+
"Avatars with the status of 'imagePicker' should also have an onChange property."
|
|
160
|
+
);
|
|
161
|
+
consoleWarnSpy.mockRestore();
|
|
162
|
+
});
|
|
163
|
+
});
|
package/src/Avatar.tsx
CHANGED
|
@@ -175,6 +175,7 @@ export const Avatar: FC<AvatarProps> = ({
|
|
|
175
175
|
right: 0,
|
|
176
176
|
zIndex: 5,
|
|
177
177
|
}}
|
|
178
|
+
testID="status-indicator"
|
|
178
179
|
>
|
|
179
180
|
{icon({
|
|
180
181
|
doNotDisturb,
|
|
@@ -213,6 +214,7 @@ export const Avatar: FC<AvatarProps> = ({
|
|
|
213
214
|
height: avatarImageDiameter,
|
|
214
215
|
overflow: "hidden",
|
|
215
216
|
}}
|
|
217
|
+
testID="avatar-image"
|
|
216
218
|
onError={handleImageError}
|
|
217
219
|
/>
|
|
218
220
|
) : (
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import {Badge} from "./Badge";
|
|
4
|
+
import {renderWithTheme} from "./test-utils";
|
|
5
|
+
|
|
6
|
+
describe("Badge", () => {
|
|
7
|
+
const defaultProps = {
|
|
8
|
+
value: "Test",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
it("renders correctly with default props", () => {
|
|
12
|
+
const {toJSON} = renderWithTheme(<Badge {...defaultProps} />);
|
|
13
|
+
expect(toJSON()).toMatchSnapshot();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("renders text value correctly", () => {
|
|
17
|
+
const {getByText} = renderWithTheme(<Badge {...defaultProps} />);
|
|
18
|
+
expect(getByText("Test")).toBeTruthy();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("renders number value correctly", () => {
|
|
22
|
+
const {getByText} = renderWithTheme(<Badge value={42} />);
|
|
23
|
+
expect(getByText("42")).toBeTruthy();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("truncates large numbers with maxValue", () => {
|
|
27
|
+
const {getByText} = renderWithTheme(<Badge maxValue={100} value={150} variant="numberOnly" />);
|
|
28
|
+
expect(getByText("100+")).toBeTruthy();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("does not truncate numbers below maxValue", () => {
|
|
32
|
+
const {getByText} = renderWithTheme(<Badge maxValue={100} value={50} variant="numberOnly" />);
|
|
33
|
+
expect(getByText("50")).toBeTruthy();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("applies correct status colors", () => {
|
|
37
|
+
const statuses = ["error", "warning", "info", "success", "neutral"] as const;
|
|
38
|
+
|
|
39
|
+
statuses.forEach((status) => {
|
|
40
|
+
let {getByTestId} = renderWithTheme(
|
|
41
|
+
<Badge {...defaultProps} status={status} testID="badge" />
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// Test primary variant
|
|
45
|
+
const badge = getByTestId("badge");
|
|
46
|
+
expect(badge).toHaveStyle({backgroundColor: expect.any(String)});
|
|
47
|
+
|
|
48
|
+
// Test secondary variant
|
|
49
|
+
({getByTestId} = renderWithTheme(
|
|
50
|
+
<Badge {...defaultProps} secondary status={status} testID="badge-secondary" />
|
|
51
|
+
));
|
|
52
|
+
const secondaryBadge = getByTestId("badge-secondary");
|
|
53
|
+
expect(secondaryBadge).toHaveStyle({backgroundColor: expect.any(String)});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("renders icon when iconName is provided", () => {
|
|
58
|
+
const {getByTestId} = renderWithTheme(
|
|
59
|
+
<Badge {...defaultProps} iconName="check" testID="badge-with-icon" />
|
|
60
|
+
);
|
|
61
|
+
expect(getByTestId("icon")).toBeTruthy();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("renders icon only when variant is iconOnly", () => {
|
|
65
|
+
const {getByTestId, queryByText} = renderWithTheme(
|
|
66
|
+
<Badge {...defaultProps} iconName="check" testID="icon-only-badge" variant="iconOnly" />
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
expect(getByTestId("icon")).toBeTruthy();
|
|
70
|
+
expect(queryByText("Test")).toBeNull();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("applies custom colors when status is custom", () => {
|
|
74
|
+
const customColors = {
|
|
75
|
+
customBackgroundColor: "#123456",
|
|
76
|
+
customTextColor: "#ffffff",
|
|
77
|
+
customBorderColor: "#654321",
|
|
78
|
+
customIconColor: "#ffcc00",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const {getByTestId} = renderWithTheme(
|
|
82
|
+
<Badge
|
|
83
|
+
{...defaultProps}
|
|
84
|
+
iconName="star"
|
|
85
|
+
secondary
|
|
86
|
+
status="custom"
|
|
87
|
+
testID="custom-badge"
|
|
88
|
+
{...(customColors as any)}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const badge = getByTestId("custom-badge");
|
|
93
|
+
expect(badge).toHaveStyle({
|
|
94
|
+
backgroundColor: customColors.customBackgroundColor,
|
|
95
|
+
borderColor: customColors.customBorderColor,
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("applies correct border radius based on variant", () => {
|
|
100
|
+
// Default variant
|
|
101
|
+
let {getByTestId} = renderWithTheme(<Badge {...defaultProps} testID="default-badge" />);
|
|
102
|
+
expect(getByTestId("default-badge")).toHaveStyle({borderRadius: expect.any(Number)});
|
|
103
|
+
|
|
104
|
+
// Icon only variant
|
|
105
|
+
({getByTestId} = renderWithTheme(
|
|
106
|
+
<Badge {...defaultProps} iconName="check" testID="icon-only-badge" variant="iconOnly" />
|
|
107
|
+
));
|
|
108
|
+
expect(getByTestId("icon-only-badge")).toHaveStyle({borderRadius: expect.any(Number)});
|
|
109
|
+
|
|
110
|
+
// Number only variant
|
|
111
|
+
({getByTestId} = renderWithTheme(
|
|
112
|
+
<Badge {...defaultProps} testID="number-only-badge" variant="numberOnly" />
|
|
113
|
+
));
|
|
114
|
+
expect(getByTestId("number-only-badge")).toHaveStyle({borderRadius: expect.any(Number)});
|
|
115
|
+
});
|
|
116
|
+
});
|
package/src/Badge.tsx
CHANGED
|
@@ -17,6 +17,7 @@ export const Badge = ({
|
|
|
17
17
|
customBorderColor,
|
|
18
18
|
customIconColor,
|
|
19
19
|
customIconName,
|
|
20
|
+
testID,
|
|
20
21
|
}: BadgeProps): React.ReactElement => {
|
|
21
22
|
const {theme} = useTheme();
|
|
22
23
|
const isIconOnly = variant === "iconOnly";
|
|
@@ -110,9 +111,10 @@ export const Badge = ({
|
|
|
110
111
|
isIconOnly && {height: 16, width: 16},
|
|
111
112
|
secondary && {borderWidth: 1, borderColor},
|
|
112
113
|
]}
|
|
114
|
+
testID={testID}
|
|
113
115
|
>
|
|
114
116
|
{Boolean(variant !== "numberOnly" && iconName) && (
|
|
115
|
-
<View style={{marginRight: variant === "iconOnly" ? 0 : theme.spacing.sm}}>
|
|
117
|
+
<View style={{marginRight: variant === "iconOnly" ? 0 : theme.spacing.sm}} testID="icon">
|
|
116
118
|
<Icon color={iconColor} iconName={customIconName ?? iconName!} size="xs" />
|
|
117
119
|
</View>
|
|
118
120
|
)}
|