@factorialco/f0-react-native 0.34.0 → 0.36.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/README.md +7 -5
- package/lib/module/components/Button/index.js +1 -1
- package/lib/module/components/Button/index.js.map +1 -1
- package/lib/module/components/F0Button/F0Button.js +2 -0
- package/lib/module/components/F0Button/F0Button.js.map +1 -0
- package/lib/module/components/F0Button/F0Button.md +163 -0
- package/lib/module/components/F0Button/F0Button.styles.js +2 -0
- package/lib/module/components/F0Button/F0Button.styles.js.map +1 -0
- package/lib/module/components/F0Button/F0Button.types.js +2 -0
- package/lib/module/components/F0Button/F0Button.types.js.map +1 -0
- package/lib/module/components/F0Button/index.js +2 -0
- package/lib/module/components/F0Button/index.js.map +1 -0
- package/lib/module/components/Icon/index.js.map +1 -1
- package/lib/module/components/exports.js +1 -1
- package/lib/module/components/exports.js.map +1 -1
- package/lib/module/components/primitives/F0Image/F0Image.js +2 -0
- package/lib/module/components/primitives/F0Image/F0Image.js.map +1 -0
- package/lib/module/components/primitives/F0Image/F0Image.md +40 -0
- package/lib/module/components/primitives/F0Image/F0Image.types.js +2 -0
- package/lib/module/components/primitives/F0Image/F0Image.types.js.map +1 -0
- package/lib/module/components/primitives/F0Image/index.js +2 -0
- package/lib/module/components/primitives/F0Image/index.js.map +1 -0
- package/lib/typescript/components/Button/index.d.ts +18 -0
- package/lib/typescript/components/Button/index.d.ts.map +1 -1
- package/lib/typescript/components/F0Button/F0Button.d.ts +6 -0
- package/lib/typescript/components/F0Button/F0Button.d.ts.map +1 -0
- package/lib/typescript/components/F0Button/F0Button.styles.d.ts +161 -0
- package/lib/typescript/components/F0Button/F0Button.styles.d.ts.map +1 -0
- package/lib/typescript/components/F0Button/F0Button.types.d.ts +47 -0
- package/lib/typescript/components/F0Button/F0Button.types.d.ts.map +1 -0
- package/lib/typescript/components/F0Button/index.d.ts +4 -0
- package/lib/typescript/components/F0Button/index.d.ts.map +1 -0
- package/lib/typescript/components/Icon/index.d.ts +5 -0
- package/lib/typescript/components/Icon/index.d.ts.map +1 -1
- package/lib/typescript/components/exports.d.ts +2 -0
- package/lib/typescript/components/exports.d.ts.map +1 -1
- package/lib/typescript/components/primitives/F0Image/F0Image.d.ts +11 -0
- package/lib/typescript/components/primitives/F0Image/F0Image.d.ts.map +1 -0
- package/lib/typescript/components/primitives/F0Image/F0Image.types.d.ts +21 -0
- package/lib/typescript/components/primitives/F0Image/F0Image.types.d.ts.map +1 -0
- package/lib/typescript/components/primitives/F0Image/index.d.ts +3 -0
- package/lib/typescript/components/primitives/F0Image/index.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/components/Button/__snapshots__/index.spec.tsx.snap +11 -11
- package/src/components/Button/index.tsx +22 -2
- package/src/components/F0Button/F0Button.md +163 -0
- package/src/components/F0Button/F0Button.styles.ts +141 -0
- package/src/components/F0Button/F0Button.tsx +228 -0
- package/src/components/F0Button/F0Button.types.ts +82 -0
- package/src/components/F0Button/__tests__/F0Button.spec.tsx +285 -0
- package/src/components/F0Button/__tests__/__snapshots__/F0Button.spec.tsx.snap +966 -0
- package/src/components/F0Button/index.ts +7 -0
- package/src/components/Icon/index.tsx +5 -0
- package/src/components/exports.ts +2 -0
- package/src/components/primitives/F0Image/F0Image.md +40 -0
- package/src/components/primitives/F0Image/F0Image.tsx +48 -0
- package/src/components/primitives/F0Image/F0Image.types.ts +23 -0
- package/src/components/primitives/F0Image/__tests__/F0Image.spec.tsx +46 -0
- package/src/components/primitives/F0Image/index.ts +2 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { render, fireEvent, screen, act } from "@testing-library/react-native"
|
|
2
|
+
import React from "react"
|
|
3
|
+
|
|
4
|
+
import { F0Button } from "../"
|
|
5
|
+
import type { IconType } from "../../primitives/F0Icon"
|
|
6
|
+
|
|
7
|
+
jest.mock("../../primitives/F0Icon", () => ({
|
|
8
|
+
F0Icon: () => null,
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
const mockIcon: IconType = "check" as unknown as IconType
|
|
12
|
+
const mockOnPress = jest.fn()
|
|
13
|
+
|
|
14
|
+
describe("F0Button", () => {
|
|
15
|
+
const defaultProps = {
|
|
16
|
+
label: "Test Button",
|
|
17
|
+
onPress: mockOnPress,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
jest.clearAllMocks()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it("Snapshot - default button", () => {
|
|
25
|
+
const { toJSON } = render(<F0Button {...defaultProps} />)
|
|
26
|
+
expect(toJSON()).toMatchSnapshot()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("Snapshot - outline variant", () => {
|
|
30
|
+
const { toJSON } = render(<F0Button {...defaultProps} variant="outline" />)
|
|
31
|
+
expect(toJSON()).toMatchSnapshot()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("Snapshot - critical variant", () => {
|
|
35
|
+
const { toJSON } = render(<F0Button {...defaultProps} variant="critical" />)
|
|
36
|
+
expect(toJSON()).toMatchSnapshot()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it("Snapshot - with icon", () => {
|
|
40
|
+
const { toJSON } = render(<F0Button {...defaultProps} icon={mockIcon} />)
|
|
41
|
+
expect(toJSON()).toMatchSnapshot()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it("Snapshot - with emoji", () => {
|
|
45
|
+
const { toJSON } = render(<F0Button {...defaultProps} emoji="👋" />)
|
|
46
|
+
expect(toJSON()).toMatchSnapshot()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it("Snapshot - disabled state", () => {
|
|
50
|
+
const { toJSON } = render(<F0Button {...defaultProps} disabled />)
|
|
51
|
+
expect(toJSON()).toMatchSnapshot()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it("Snapshot - loading state", () => {
|
|
55
|
+
const { toJSON } = render(<F0Button {...defaultProps} loading />)
|
|
56
|
+
expect(toJSON()).toMatchSnapshot()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it("renders loading indicator and hides content when loading", () => {
|
|
60
|
+
render(<F0Button {...defaultProps} loading />)
|
|
61
|
+
|
|
62
|
+
expect(screen.getByTestId("f0-button-loading-indicator")).toBeDefined()
|
|
63
|
+
expect(screen.getByTestId("f0-button-content").props.className).toContain(
|
|
64
|
+
"opacity-0"
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it("Snapshot - different sizes", () => {
|
|
69
|
+
const sizes = ["sm", "md", "lg"] as const
|
|
70
|
+
sizes.forEach((size) => {
|
|
71
|
+
const { toJSON } = render(<F0Button {...defaultProps} size={size} />)
|
|
72
|
+
expect(toJSON()).toMatchSnapshot()
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it("Snapshot - round button with hidden label", () => {
|
|
77
|
+
const { toJSON } = render(<F0Button {...defaultProps} round hideLabel />)
|
|
78
|
+
expect(toJSON()).toMatchSnapshot()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it("renders correctly with required props", () => {
|
|
82
|
+
render(<F0Button {...defaultProps} />)
|
|
83
|
+
const button = screen.getByText("Test Button")
|
|
84
|
+
expect(button).toBeDefined()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it("handles press events", () => {
|
|
88
|
+
render(<F0Button {...defaultProps} />)
|
|
89
|
+
fireEvent.press(screen.getByRole("button"))
|
|
90
|
+
expect(mockOnPress).toHaveBeenCalled()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it("does not call onPress when disabled", () => {
|
|
94
|
+
render(<F0Button {...defaultProps} disabled />)
|
|
95
|
+
fireEvent.press(screen.getByRole("button"))
|
|
96
|
+
expect(mockOnPress).not.toHaveBeenCalled()
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it("shows correct accessibility label when disabled", () => {
|
|
100
|
+
render(<F0Button {...defaultProps} disabled />)
|
|
101
|
+
const button = screen.getByRole("button")
|
|
102
|
+
expect(button.props.accessibilityLabel).toBe("Test Button, disabled")
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it("shows correct accessibility label when loading", () => {
|
|
106
|
+
render(<F0Button {...defaultProps} loading />)
|
|
107
|
+
const button = screen.getByRole("button")
|
|
108
|
+
expect(button.props.accessibilityLabel).toBe(
|
|
109
|
+
"Test Button, disabled, loading"
|
|
110
|
+
)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it("sets correct accessibilityState when disabled", () => {
|
|
114
|
+
render(<F0Button {...defaultProps} disabled />)
|
|
115
|
+
const button = screen.getByRole("button")
|
|
116
|
+
expect(button.props.accessibilityState).toMatchObject({
|
|
117
|
+
disabled: true,
|
|
118
|
+
busy: false,
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it("sets correct accessibilityState when loading", () => {
|
|
123
|
+
render(<F0Button {...defaultProps} loading />)
|
|
124
|
+
const button = screen.getByRole("button")
|
|
125
|
+
expect(button.props.accessibilityState).toMatchObject({
|
|
126
|
+
disabled: true,
|
|
127
|
+
busy: true,
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it("hides label text when hideLabel is true", () => {
|
|
132
|
+
render(<F0Button {...defaultProps} hideLabel />)
|
|
133
|
+
expect(screen.queryByText("Test Button")).toBeNull()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it("uses label as accessibilityLabel even when hideLabel is true", () => {
|
|
137
|
+
render(<F0Button {...defaultProps} hideLabel />)
|
|
138
|
+
const button = screen.getByRole("button")
|
|
139
|
+
expect(button.props.accessibilityLabel).toBe("Test Button")
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it("renders with testID", () => {
|
|
143
|
+
render(<F0Button {...defaultProps} testID="my-button" />)
|
|
144
|
+
expect(screen.getByTestId("my-button")).toBeDefined()
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it("renders badge for outline variant with showBadge", () => {
|
|
148
|
+
render(<F0Button {...defaultProps} variant="outline" showBadge />)
|
|
149
|
+
expect(screen.getByLabelText("Notification Badge")).toBeDefined()
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it("does not render badge for non-outline variant even with showBadge", () => {
|
|
153
|
+
render(<F0Button {...defaultProps} variant="default" showBadge />)
|
|
154
|
+
expect(screen.queryByLabelText("Notification Badge")).toBeNull()
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it("does not render badge when showBadge is false", () => {
|
|
158
|
+
render(<F0Button {...defaultProps} variant="outline" />)
|
|
159
|
+
expect(screen.queryByLabelText("Notification Badge")).toBeNull()
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it("auto-enters loading state for async onPress", async () => {
|
|
163
|
+
let resolvePromise: () => void
|
|
164
|
+
const asyncOnPress = jest.fn(
|
|
165
|
+
() =>
|
|
166
|
+
new Promise<void>((resolve) => {
|
|
167
|
+
resolvePromise = resolve
|
|
168
|
+
})
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
render(<F0Button label="Async" onPress={asyncOnPress} />)
|
|
172
|
+
|
|
173
|
+
await act(async () => {
|
|
174
|
+
fireEvent.press(screen.getByRole("button"))
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
expect(asyncOnPress).toHaveBeenCalled()
|
|
178
|
+
expect(screen.getByTestId("f0-button-loading-indicator")).toBeDefined()
|
|
179
|
+
expect(screen.getByTestId("f0-button-content").props.className).toContain(
|
|
180
|
+
"opacity-0"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
await act(async () => {
|
|
184
|
+
resolvePromise!()
|
|
185
|
+
await asyncOnPress.mock.results[0].value
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
expect(screen.queryByTestId("f0-button-loading-indicator")).toBeNull()
|
|
189
|
+
expect(screen.getByTestId("f0-button-content").props.className).toContain(
|
|
190
|
+
"opacity-100"
|
|
191
|
+
)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it("handles rejected async onPress without leaking rejections", async () => {
|
|
195
|
+
const onPressError = new Error("Async failure")
|
|
196
|
+
const asyncOnPress = jest.fn().mockRejectedValue(onPressError)
|
|
197
|
+
render(<F0Button label="Async reject" onPress={asyncOnPress} />)
|
|
198
|
+
|
|
199
|
+
await act(async () => {
|
|
200
|
+
fireEvent.press(screen.getByRole("button"))
|
|
201
|
+
await Promise.resolve()
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
expect(asyncOnPress).toHaveBeenCalled()
|
|
205
|
+
expect(screen.queryByTestId("f0-button-loading-indicator")).toBeNull()
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it("handles sync throw in onPress without throwing from press handler", () => {
|
|
209
|
+
const onPressError = new Error("Sync failure")
|
|
210
|
+
const throwingOnPress = jest.fn(() => {
|
|
211
|
+
throw onPressError
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
render(<F0Button label="Sync throw" onPress={throwingOnPress} />)
|
|
215
|
+
expect(() => {
|
|
216
|
+
fireEvent.press(screen.getByRole("button"))
|
|
217
|
+
}).not.toThrow()
|
|
218
|
+
|
|
219
|
+
expect(throwingOnPress).toHaveBeenCalled()
|
|
220
|
+
expect(screen.queryByTestId("f0-button-loading-indicator")).toBeNull()
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it("renders all six variants without error", () => {
|
|
224
|
+
const allVariants = [
|
|
225
|
+
"default",
|
|
226
|
+
"outline",
|
|
227
|
+
"critical",
|
|
228
|
+
"neutral",
|
|
229
|
+
"ghost",
|
|
230
|
+
"promote",
|
|
231
|
+
] as const
|
|
232
|
+
|
|
233
|
+
allVariants.forEach((variant) => {
|
|
234
|
+
const { unmount } = render(<F0Button label={variant} variant={variant} />)
|
|
235
|
+
expect(screen.getByText(variant)).toBeDefined()
|
|
236
|
+
unmount()
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it("ignores className passed via unsafe cast", () => {
|
|
241
|
+
const unsafeProps = {
|
|
242
|
+
...defaultProps,
|
|
243
|
+
className: "bg-red-500 p-9",
|
|
244
|
+
} as unknown as React.ComponentProps<typeof F0Button>
|
|
245
|
+
render(<F0Button {...unsafeProps} />)
|
|
246
|
+
|
|
247
|
+
const button = screen.getByRole("button")
|
|
248
|
+
expect(button.props.className).toBe("overflow-hidden")
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it("ignores style passed via unsafe cast", () => {
|
|
252
|
+
const unsafeProps = {
|
|
253
|
+
...defaultProps,
|
|
254
|
+
style: { padding: 999 },
|
|
255
|
+
} as unknown as React.ComponentProps<typeof F0Button>
|
|
256
|
+
render(<F0Button {...unsafeProps} />)
|
|
257
|
+
|
|
258
|
+
const button = screen.getByRole("button")
|
|
259
|
+
expect(button.props.style?.[1]).toBeUndefined()
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it("ignores onPressIn passed via unsafe cast", () => {
|
|
263
|
+
const unsafeOnPressIn = jest.fn()
|
|
264
|
+
const unsafeProps = {
|
|
265
|
+
...defaultProps,
|
|
266
|
+
onPressIn: unsafeOnPressIn,
|
|
267
|
+
} as unknown as React.ComponentProps<typeof F0Button>
|
|
268
|
+
render(<F0Button {...unsafeProps} />)
|
|
269
|
+
|
|
270
|
+
const button = screen.getByRole("button")
|
|
271
|
+
fireEvent(button, "pressIn")
|
|
272
|
+
expect(unsafeOnPressIn).not.toHaveBeenCalled()
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
it("ignores accessibilityLabel passed via unsafe cast", () => {
|
|
276
|
+
const unsafeProps = {
|
|
277
|
+
...defaultProps,
|
|
278
|
+
accessibilityLabel: "Unsafe label override",
|
|
279
|
+
} as unknown as React.ComponentProps<typeof F0Button>
|
|
280
|
+
render(<F0Button {...unsafeProps} />)
|
|
281
|
+
|
|
282
|
+
const button = screen.getByRole("button")
|
|
283
|
+
expect(button.props.accessibilityLabel).toBe("Test Button")
|
|
284
|
+
})
|
|
285
|
+
})
|