@hero-design/rn-work-uikit 1.3.0 → 1.4.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/lib/index.js +48742 -29649
  3. package/package.json +5 -4
  4. package/src/__tests__/index-export.spec.ts +64 -0
  5. package/src/components/DatePicker/__tests__/__snapshots__/index.spec.tsx.snap +1602 -0
  6. package/src/components/DatePicker/__tests__/index.spec.tsx +56 -0
  7. package/src/components/DatePicker/index.tsx +12 -0
  8. package/src/components/FormGroup/__tests__/__snapshots__/index.spec.tsx.snap +880 -0
  9. package/src/components/FormGroup/__tests__/index.spec.tsx +179 -0
  10. package/src/components/FormGroup/__tests__/utils.spec.ts +73 -0
  11. package/src/components/FormGroup/index.tsx +97 -0
  12. package/src/components/FormGroup/utils.ts +67 -0
  13. package/src/components/RichTextEditor/EditorEvent.ts +7 -0
  14. package/src/components/RichTextEditor/EditorToolbar.tsx +216 -0
  15. package/src/components/RichTextEditor/MentionList.tsx +99 -0
  16. package/src/components/RichTextEditor/RichTextEditor.tsx +88 -0
  17. package/src/components/RichTextEditor/RichTextEditorInput.tsx +286 -0
  18. package/src/components/RichTextEditor/StyledRichTextEditor.tsx +18 -0
  19. package/src/components/RichTextEditor/StyledToolbar.ts +32 -0
  20. package/src/components/RichTextEditor/__mocks__/hero-editor.js +3 -0
  21. package/src/components/RichTextEditor/__mocks__/heroEditorApp.ts +2 -0
  22. package/src/components/RichTextEditor/__tests__/EditorToolbar.spec.tsx +144 -0
  23. package/src/components/RichTextEditor/__tests__/MentionList.spec.tsx +105 -0
  24. package/src/components/RichTextEditor/__tests__/RichTextEditorInput.spec.tsx +136 -0
  25. package/src/components/RichTextEditor/__tests__/__snapshots__/EditorToolbar.spec.tsx.snap +414 -0
  26. package/src/components/RichTextEditor/__tests__/__snapshots__/MentionList.spec.tsx.snap +13 -0
  27. package/src/components/RichTextEditor/constants.ts +9 -0
  28. package/src/{hero-editor.d.ts → components/RichTextEditor/hero-editor.d.ts} +6 -0
  29. package/src/components/RichTextEditor/heroEditorApp.ts +3 -0
  30. package/src/components/RichTextEditor/index.tsx +20 -0
  31. package/src/components/RichTextEditor/types.ts +87 -0
  32. package/src/components/RichTextEditor/utils/events.ts +31 -0
  33. package/src/components/RichTextEditor/utils/rnWebView.tsx +30 -0
  34. package/src/components/Select/__tests__/__snapshots__/index.spec.tsx.snap +1293 -0
  35. package/src/components/Select/__tests__/index.spec.tsx +43 -0
  36. package/src/components/TextInput/Group/__tests__/__snapshots__/index.spec.tsx.snap +0 -3
  37. package/src/components/TextInput/InputComponent.tsx +59 -18
  38. package/src/components/TextInput/InputRow.tsx +13 -7
  39. package/src/components/TextInput/StyledTextInput.tsx +0 -1
  40. package/src/components/TextInput/__tests__/__snapshots__/index.spec.tsx.snap +0 -17
  41. package/src/components/TextInput/index.tsx +20 -11
  42. package/src/components/TextInput/types.ts +29 -4
  43. package/src/components/TimePicker/__tests__/index.spec.tsx +34 -0
  44. package/src/components/TimePicker/index.tsx +12 -0
  45. package/src/index.ts +4 -1
  46. package/src/utils/functions.ts +2 -0
  47. package/stats/1.3.0/rn-work-uikit-stats.html +1 -1
  48. package/stats/1.4.0/rn-work-uikit-stats.html +4844 -0
  49. package/src/__tests__/theme-export-override.spec.ts +0 -96
@@ -0,0 +1,99 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import { View } from 'react-native';
3
+ import { useTheme } from '../../theme';
4
+ import { emitter } from './EditorEvent';
5
+ import * as Events from './utils/events';
6
+
7
+ const isEmptyString = (s: string | null): boolean => !s || s.length === 0;
8
+
9
+ interface OnSelectOptionsType<TMetaData> {
10
+ highlighted: boolean;
11
+ meta?: TMetaData;
12
+ }
13
+
14
+ export interface MentionListProps<TMetaData = unknown> {
15
+ /**
16
+ * Unique name used to communicate with webview, should be the same with the RichTextEditor component it used with
17
+ */
18
+ name: string;
19
+ /**
20
+ * Function used to render mention options
21
+ */
22
+ render: (
23
+ searchText: string,
24
+ onSelect: (
25
+ id: string,
26
+ name: string,
27
+ options?: OnSelectOptionsType<TMetaData>
28
+ ) => void
29
+ ) => JSX.Element;
30
+ }
31
+
32
+ const MentionList = <TMetaData,>({
33
+ name: eventPrefix,
34
+ render,
35
+ }: MentionListProps<TMetaData>) => {
36
+ const theme = useTheme();
37
+ const [search, setSearch] = useState<string>('');
38
+ const [target, setTarget] = useState<string | null>(null);
39
+ const normalizeEventName = useCallback(
40
+ (event: string) => `${eventPrefix}/${event}`,
41
+ [eventPrefix]
42
+ );
43
+
44
+ useEffect(() => {
45
+ const removeMentionSearchListener = Events.on(
46
+ emitter,
47
+ normalizeEventName('mention-search'),
48
+ ({
49
+ search: nextSearch,
50
+ target: nextTarget,
51
+ }: {
52
+ search?: string;
53
+ target?: string;
54
+ }) => {
55
+ setSearch(nextSearch || '');
56
+ setTarget(nextTarget || null);
57
+ }
58
+ );
59
+
60
+ return () => {
61
+ removeMentionSearchListener();
62
+ };
63
+ }, [eventPrefix]);
64
+
65
+ if (isEmptyString(search)) {
66
+ return null;
67
+ }
68
+
69
+ return (
70
+ <View>
71
+ {render(search, (id, name, options = { highlighted: false }) => {
72
+ const { highlighted, meta } = options;
73
+
74
+ const highlightStyle = {
75
+ color: theme.colors.secondary,
76
+ borderRadius: theme.__hd__.richTextEditor.radii.mention,
77
+ padding: highlighted ? theme.__hd__.richTextEditor.space.mention : 0,
78
+ background: highlighted
79
+ ? theme.colors.highlightedSurface
80
+ : 'transparent',
81
+ marginTop: theme.space.xxsmall,
82
+ marginBottom: theme.space.xxsmall,
83
+ display: 'inline-flex',
84
+ };
85
+ const data = {
86
+ id,
87
+ name,
88
+ target,
89
+ meta,
90
+ style: highlightStyle,
91
+ };
92
+
93
+ Events.emit(emitter, normalizeEventName('mention-apply'), data);
94
+ })}
95
+ </View>
96
+ );
97
+ };
98
+
99
+ export default MentionList;
@@ -0,0 +1,88 @@
1
+ import React, { useMemo } from 'react';
2
+ import type { ComponentType, ReactElement, Ref } from 'react';
3
+ import type { StyleProp, ViewStyle } from 'react-native';
4
+
5
+ import { plainSerializer } from 'hero-editor';
6
+ import TextInput from '../TextInput';
7
+ import RichTextEditorInput from './RichTextEditorInput';
8
+ import type { EditorValue, TextUnit } from './types';
9
+ import { StyledWrapper } from './StyledRichTextEditor';
10
+
11
+ export interface RichTextEditorRef {
12
+ requestBlur: VoidFunction;
13
+ insertNodes: (nodes: Record<string, unknown>[]) => void;
14
+ deleteBackward: (unit?: TextUnit) => void;
15
+ setReadOnly: (readOnly: boolean) => void;
16
+ }
17
+
18
+ export interface RichTextEditorProps {
19
+ autoFocus?: boolean;
20
+ error?: string;
21
+ value?: EditorValue;
22
+ name: string;
23
+ onChange: (data: EditorValue) => void;
24
+ onCursorChange?: (params: { position: { top: number } }) => void;
25
+ placeholder?: string;
26
+ style?: StyleProp<ViewStyle>;
27
+ label: string;
28
+ helpText?: string;
29
+ required?: boolean;
30
+ testID?: string;
31
+ forwardedRef?: Ref<RichTextEditorRef>;
32
+ }
33
+
34
+ const defaultValue: EditorValue = [
35
+ {
36
+ type: 'paragraph',
37
+ children: [{ text: '' }],
38
+ },
39
+ ];
40
+
41
+ const RichTextEditor: ComponentType<RichTextEditorProps> = ({
42
+ autoFocus = true,
43
+ name,
44
+ placeholder = '',
45
+ onChange,
46
+ onCursorChange,
47
+ error = '',
48
+ style = {},
49
+ label,
50
+ helpText,
51
+ required,
52
+ testID,
53
+ forwardedRef,
54
+ value = defaultValue,
55
+ }: RichTextEditorProps): ReactElement => {
56
+ const plain = useMemo(() => plainSerializer(value), [value]);
57
+
58
+ return (
59
+ <StyledWrapper>
60
+ <TextInput
61
+ autoFocus={autoFocus}
62
+ error={error}
63
+ label={label}
64
+ required={required}
65
+ testID={testID}
66
+ placeholder={placeholder}
67
+ value={plain}
68
+ style={style}
69
+ helpText={helpText}
70
+ renderInputValue={(inputProps, ref) => (
71
+ <RichTextEditorInput
72
+ {...inputProps}
73
+ ref={ref}
74
+ value={value}
75
+ autoFocus={autoFocus}
76
+ name={name}
77
+ onChange={onChange}
78
+ onCursorChange={onCursorChange}
79
+ forwardedRef={forwardedRef}
80
+ placeholder={placeholder}
81
+ />
82
+ )}
83
+ />
84
+ </StyledWrapper>
85
+ );
86
+ };
87
+
88
+ export default RichTextEditor;
@@ -0,0 +1,286 @@
1
+ import React, {
2
+ forwardRef,
3
+ useCallback,
4
+ useEffect,
5
+ useImperativeHandle,
6
+ useLayoutEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ } from 'react';
11
+ import type { Ref } from 'react';
12
+ import { WebView } from 'react-native-webview';
13
+
14
+ import { isAndroid } from '../../utils/helpers';
15
+ import { emitter } from './EditorEvent';
16
+ import * as Events from './utils/events';
17
+ import heroEditorApp from './heroEditorApp';
18
+ import { ToolbarEvents } from './constants';
19
+ import type { EditorValue, RichTextEditorRef, TextUnit } from './types';
20
+ import {
21
+ postMessage,
22
+ requestBlurEditor,
23
+ requestFocusEditor,
24
+ } from './utils/rnWebView';
25
+ import type { WebViewEventMessage } from './utils/rnWebView';
26
+ import { useTheme } from '../../theme';
27
+ import { StyledWebView } from './StyledRichTextEditor';
28
+ import { TextInputRef } from '../TextInput/types';
29
+
30
+ const RichTextEditorInput = forwardRef<
31
+ TextInputRef,
32
+ {
33
+ value: EditorValue;
34
+ name: string;
35
+ autoFocus: boolean;
36
+ placeholder: string;
37
+ onChange: (data: EditorValue) => void;
38
+ onCursorChange?: (params: { position: { top: number } }) => void;
39
+ forwardedRef?: Ref<RichTextEditorRef>;
40
+ onFocus?: () => void;
41
+ onBlur?: () => void;
42
+ }
43
+ >(
44
+ (
45
+ {
46
+ value,
47
+ name,
48
+ autoFocus,
49
+ placeholder,
50
+ onChange,
51
+ onCursorChange,
52
+ forwardedRef,
53
+ onFocus,
54
+ onBlur,
55
+ },
56
+ ref
57
+ ) => {
58
+ const theme = useTheme();
59
+ const webview = useRef<WebView | null>(null);
60
+ const [isFocused, setIsFocused] = useState(false);
61
+ const [webviewHeight, setWebviewHeight] = useState<number | undefined>(24);
62
+ const initialValueRef = useRef(value);
63
+
64
+ const normalizeEventName = (event: string) => `${name}/${event}`;
65
+ const postMessageToWebview = useCallback((message: WebViewEventMessage) => {
66
+ if (webview && webview.current) {
67
+ postMessage(webview.current, message);
68
+ }
69
+ }, []);
70
+
71
+ useEffect(() => {
72
+ const removeFocusListener = Events.on(
73
+ emitter,
74
+ normalizeEventName('editor-focus'),
75
+ () => {
76
+ onFocus?.();
77
+ setIsFocused(true);
78
+ }
79
+ );
80
+
81
+ const removeBlurListener = Events.on(
82
+ emitter,
83
+ normalizeEventName('editor-blur'),
84
+ () => {
85
+ setIsFocused(false);
86
+ onBlur?.();
87
+ }
88
+ );
89
+
90
+ return () => {
91
+ removeFocusListener();
92
+ removeBlurListener();
93
+ };
94
+ }, [name, onFocus, onBlur]);
95
+
96
+ const html = useMemo(() => {
97
+ const initialValueString = JSON.stringify(initialValueRef.current);
98
+
99
+ return `
100
+ <!DOCTYPE html>
101
+ <html>
102
+ <head>
103
+ <meta charset="utf-8">
104
+ <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
105
+ <style>
106
+ body {
107
+ margin: 0;
108
+ }
109
+ </style>
110
+ </head>
111
+ <body>
112
+ <div id="root"></div>
113
+ <script>
114
+ window.__editorConfigs = {
115
+ placeholder: "${placeholder}",
116
+ initialValue: ${initialValueString},
117
+ isAndroid: ${isAndroid ? 'true' : 'false'},
118
+ autoFocus: ${autoFocus},
119
+ style: {
120
+ padding: '0 !important',
121
+ fontSize: ${theme.__hd__.richTextEditor.fontSizes.editor},
122
+ color: '${theme.__hd__.richTextEditor.colors.text}'
123
+ }
124
+ };
125
+ ${heroEditorApp}
126
+ </script>
127
+ </body>
128
+ </html>
129
+ `;
130
+ }, [placeholder, autoFocus, theme]);
131
+
132
+ const requestBlur = useCallback(() => {
133
+ if (webview.current && isFocused) {
134
+ requestBlurEditor(webview.current);
135
+ }
136
+ }, [isFocused]);
137
+
138
+ const insertNodes = useCallback((nodes: Record<string, unknown>[]) => {
139
+ postMessageToWebview({
140
+ type: '@hero-editor/webview/editor-insert-nodes',
141
+ data: { nodes },
142
+ });
143
+ }, []);
144
+
145
+ const deleteBackward = useCallback((unit?: TextUnit) => {
146
+ postMessageToWebview({
147
+ type: '@hero-editor/webview/editor-delete-backward',
148
+ data: { unit },
149
+ });
150
+ }, []);
151
+
152
+ const setReadOnly = useCallback((readOnly: boolean) => {
153
+ postMessageToWebview({
154
+ type: '@hero-editor/webview/editor-read-only',
155
+ data: { readOnly },
156
+ });
157
+ }, []);
158
+
159
+ useImperativeHandle(
160
+ forwardedRef,
161
+ () => ({ requestBlur, insertNodes, deleteBackward, setReadOnly }),
162
+ [requestBlur, deleteBackward, insertNodes, setReadOnly]
163
+ );
164
+
165
+ useLayoutEffect(() => {
166
+ const toolbarEventListenerRemovers = Object.values(ToolbarEvents).map(
167
+ (eventName) =>
168
+ Events.on(emitter, normalizeEventName(eventName), (data) => {
169
+ postMessageToWebview({
170
+ type: `@hero-editor/webview/${eventName}`,
171
+ data,
172
+ });
173
+ })
174
+ );
175
+
176
+ const removeMentionApplyListener = Events.on(
177
+ emitter,
178
+ normalizeEventName('mention-apply'),
179
+ (data) =>
180
+ postMessageToWebview({
181
+ type: '@hero-editor/webview/mention-apply',
182
+ data,
183
+ })
184
+ );
185
+
186
+ return () => {
187
+ removeMentionApplyListener();
188
+ toolbarEventListenerRemovers.forEach((remover) => remover());
189
+ };
190
+ }, [name]);
191
+
192
+ const handleEditorLayoutEvent = useCallback(
193
+ (messageData: Record<string, unknown>) => {
194
+ const editorLayout = messageData
195
+ ? {
196
+ width: Number(messageData.width),
197
+ height: Number(messageData.height),
198
+ }
199
+ : undefined;
200
+
201
+ if (editorLayout) {
202
+ setWebviewHeight(editorLayout.height);
203
+ }
204
+ },
205
+ []
206
+ );
207
+
208
+ const onMessage = useCallback(
209
+ (event?: { nativeEvent?: { data?: string } }) => {
210
+ const message = event?.nativeEvent?.data
211
+ ? JSON.parse(event?.nativeEvent?.data)
212
+ : undefined;
213
+
214
+ const messageType = message?.type;
215
+ const messageData = message?.data;
216
+
217
+ switch (messageType) {
218
+ case '@hero-editor/webview/editor-focus':
219
+ Events.emit(emitter, normalizeEventName('editor-focus'), undefined);
220
+ break;
221
+ case '@hero-editor/webview/editor-blur':
222
+ Events.emit(emitter, normalizeEventName('editor-blur'), undefined);
223
+ break;
224
+ case '@hero-editor/webview/mention-search':
225
+ Events.emit(
226
+ emitter,
227
+ normalizeEventName('mention-search'),
228
+ messageData
229
+ );
230
+ break;
231
+ case '@hero-editor/webview/editor-change':
232
+ if (messageData) {
233
+ onChange(messageData.value);
234
+ }
235
+ break;
236
+ case '@hero-editor/webview/cursor-change':
237
+ onCursorChange?.(messageData);
238
+ break;
239
+ case '@hero-editor/webview/editor-layout':
240
+ handleEditorLayoutEvent(messageData);
241
+ break;
242
+ default:
243
+ break;
244
+ }
245
+ },
246
+ [name, onChange, onCursorChange, handleEditorLayoutEvent]
247
+ );
248
+
249
+ useImperativeHandle(
250
+ ref,
251
+ () => ({
252
+ focus: () => {
253
+ if (webview.current) {
254
+ requestFocusEditor(webview.current);
255
+ }
256
+ },
257
+ isFocused: () => isFocused,
258
+ clear: () => {
259
+ // Not applicable for WebView, but required by the interface
260
+ },
261
+ blur: () => {
262
+ if (webview.current) {
263
+ requestBlurEditor(webview.current);
264
+ }
265
+ },
266
+ }),
267
+ [isFocused]
268
+ );
269
+
270
+ return (
271
+ <StyledWebView
272
+ ref={webview}
273
+ testID="webview"
274
+ originWhitelist={['*']}
275
+ height={webviewHeight}
276
+ source={{ html }}
277
+ onMessage={onMessage}
278
+ scrollEnabled={false}
279
+ hideKeyboardAccessoryView
280
+ keyboardDisplayRequiresUserAction={false}
281
+ />
282
+ );
283
+ }
284
+ );
285
+
286
+ export default RichTextEditorInput;
@@ -0,0 +1,18 @@
1
+ import { styled } from '@hero-design/rn';
2
+ import { View } from 'react-native';
3
+ import { WebView } from 'react-native-webview';
4
+
5
+ export const StyledWrapper = styled(View)(({ theme }) => ({
6
+ marginBottom: theme.__hd__.richTextEditor.space.wrapperMarginBottom,
7
+ }));
8
+
9
+ export const StyledWebView = styled(WebView)<{
10
+ height: number | undefined;
11
+ }>(({ height, theme }) => ({
12
+ height,
13
+ minHeight: theme.__hd__.richTextEditor.sizes.editorMinHeight,
14
+ backgroundColor: 'transparent',
15
+ textAlignVertical: 'center',
16
+ fontSize: theme.__hd__.textInput.fontSizes.text,
17
+ marginHorizontal: theme.__hd__.textInput.space.inputHorizontalMargin,
18
+ }));
@@ -0,0 +1,32 @@
1
+ import { styled } from '@hero-design/rn';
2
+ import { StyleSheet, TouchableOpacity, View } from 'react-native';
3
+
4
+ export const StyledToolbarButton = styled(TouchableOpacity)<{
5
+ selected: boolean;
6
+ }>(({ theme, selected }) => ({
7
+ width: theme.__hd__.richTextEditor.sizes.toolbarButtonSize,
8
+ height: theme.__hd__.richTextEditor.sizes.toolbarButtonSize,
9
+ alignItems: 'center',
10
+ justifyContent: 'center',
11
+ backgroundColor: selected
12
+ ? theme.__hd__.richTextEditor.colors.toolbarButtonSelectedBackground
13
+ : undefined,
14
+ }));
15
+
16
+ export const StyledToolbar = styled(View)(({ theme }) => ({
17
+ flexDirection: 'row',
18
+ alignItems: 'center',
19
+ borderTopWidth: StyleSheet.hairlineWidth,
20
+ borderTopColor: theme.__hd__.richTextEditor.colors.toolbarBorderColor,
21
+ backgroundColor: theme.__hd__.richTextEditor.colors.toolbarBackgroundColor,
22
+ paddingHorizontal: theme.__hd__.richTextEditor.space.toolbarHorizontalPadding,
23
+ }));
24
+
25
+ export const StyledSeparator = styled(View)(({ theme }) => ({
26
+ width: theme.__hd__.richTextEditor.sizes.toolbarSeparatorWidth,
27
+ height: theme.__hd__.richTextEditor.sizes.toolbarSeparatorHeight,
28
+ flexDirection: 'row',
29
+ alignItems: 'center',
30
+ marginHorizontal: theme.space.small,
31
+ backgroundColor: theme.colors.secondaryOutline,
32
+ }));
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ plainSerializer: () => 'mocked',
3
+ };
@@ -0,0 +1,2 @@
1
+ const mockHeroEditApp = 'mockHeroEditApp';
2
+ export default mockHeroEditApp;
@@ -0,0 +1,144 @@
1
+ import React from 'react';
2
+ import { act, fireEvent, waitFor } from '@testing-library/react-native';
3
+ import type { RenderAPI } from '@testing-library/react-native';
4
+ import EditorToolbar from '../EditorToolbar';
5
+ import { emitter as editorEventEmmitor } from '../EditorEvent';
6
+ import { theme, ThemeProvider } from '../../../index';
7
+ import * as Events from '../utils/events';
8
+ import renderWithTheme from '../../../../testUtils/renderWithTheme';
9
+
10
+ describe('EditorToolbar', () => {
11
+ it('should not render toolbar when the editor is not focused', () => {
12
+ const wrapper = renderWithTheme(
13
+ <EditorToolbar testID="editor-toolbar" name="toolbar" />
14
+ );
15
+ const toolbar = wrapper.queryByTestId('editor-toolbar');
16
+ expect(toolbar).toBeNull();
17
+ });
18
+
19
+ describe('when the editor is focused', () => {
20
+ let wrapper: RenderAPI;
21
+
22
+ beforeEach(async () => {
23
+ wrapper = renderWithTheme(
24
+ <EditorToolbar name="toolbar" testID="toolbar" />
25
+ );
26
+ act(() => {
27
+ editorEventEmmitor.emit('toolbar/editor-focus');
28
+ });
29
+ await waitFor(() => wrapper.getByTestId('toolbar'));
30
+ });
31
+
32
+ it('should render toolbar', async () => {
33
+ expect(wrapper.toJSON()).toMatchSnapshot();
34
+
35
+ [
36
+ 'format-bold',
37
+ 'format-italic',
38
+ 'format-underlined',
39
+ 'format-list-bulleted',
40
+ 'format-list-numbered',
41
+ 'format-heading1',
42
+ 'format-heading2',
43
+ ].forEach((testId) => {
44
+ expect(wrapper.queryAllByTestId(testId)).toHaveLength(1);
45
+ });
46
+ });
47
+
48
+ it('should hide toolbar when blur', async () => {
49
+ act(() => {
50
+ editorEventEmmitor.emit('toolbar/editor-blur');
51
+ });
52
+ await waitFor(() => wrapper.queryAllByTestId('toolbar').length === 0);
53
+ expect(wrapper.queryAllByTestId('toolbar')).toHaveLength(0);
54
+ });
55
+
56
+ describe("should change button's background color when pressing", () => {
57
+ it('should send event and highlight buttons correctly', async () => {
58
+ const emmitedEvents: string[] = [];
59
+
60
+ const eventNameAndTestIDArray = [
61
+ {
62
+ eventName: 'toolbar/bold',
63
+ testID: 'format-bold',
64
+ },
65
+ {
66
+ eventName: 'toolbar/italic',
67
+ testID: 'format-italic',
68
+ },
69
+ {
70
+ eventName: 'toolbar/underline',
71
+ testID: 'format-underlined',
72
+ },
73
+ {
74
+ eventName: 'toolbar/bulleted-list',
75
+ testID: 'format-list-bulleted',
76
+ },
77
+ {
78
+ eventName: 'toolbar/numbered-list',
79
+ testID: 'format-list-numbered',
80
+ },
81
+ {
82
+ eventName: 'toolbar/heading-one',
83
+ testID: 'format-heading1',
84
+ },
85
+ {
86
+ eventName: 'toolbar/heading-two',
87
+ testID: 'format-heading2',
88
+ },
89
+ ];
90
+
91
+ eventNameAndTestIDArray.forEach(async ({ eventName, testID }) => {
92
+ Events.on(editorEventEmmitor, eventName, () => {
93
+ emmitedEvents.push(testID);
94
+ });
95
+
96
+ expect(wrapper.getByTestId(testID)).toHaveStyle({
97
+ backgroundColor: undefined,
98
+ });
99
+
100
+ await act(() => {
101
+ fireEvent.press(wrapper.getByTestId(testID));
102
+ });
103
+
104
+ // rerender
105
+ wrapper.rerender(
106
+ <ThemeProvider theme={theme}>
107
+ <EditorToolbar name="toolbar" testID="toolbar" />
108
+ </ThemeProvider>
109
+ );
110
+ // match snapshot
111
+ // add waitfor to ensure the style is applied
112
+ await waitFor(() => {
113
+ expect(wrapper.getByTestId(testID)).toHaveStyle({
114
+ backgroundColor:
115
+ theme.__hd__.richTextEditor.colors
116
+ .toolbarButtonSelectedBackground,
117
+ });
118
+ });
119
+ });
120
+
121
+ const standaloneButtonTestIDs = [
122
+ 'format-list-bulleted',
123
+ 'format-list-numbered',
124
+ 'format-heading1',
125
+ ];
126
+ standaloneButtonTestIDs.forEach((testID) => {
127
+ expect(wrapper.getByTestId(testID)).toHaveStyle({
128
+ backgroundColor: undefined,
129
+ });
130
+ });
131
+
132
+ expect(emmitedEvents).toMatchObject([
133
+ 'format-bold',
134
+ 'format-italic',
135
+ 'format-underlined',
136
+ 'format-list-bulleted',
137
+ 'format-list-numbered',
138
+ 'format-heading1',
139
+ 'format-heading2',
140
+ ]);
141
+ });
142
+ });
143
+ });
144
+ });