dce-reactkit 3.9.0-beta.1 → 3.9.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/cjs/index.js +562 -257
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/Dropdown.d.ts +32 -0
- package/dist/cjs/types/components/Modal/ModalProps.d.ts +0 -2
- package/dist/cjs/types/helpers/validators/ChicagoTitleCase.d.ts +9 -0
- package/dist/cjs/types/helpers/validators/validURL.d.ts +8 -0
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +22 -0
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +33 -0
- package/dist/cjs/types/index.d.ts +4 -1
- package/dist/cjs/types/types/DropdownItemType.d.ts +6 -0
- package/dist/cjs/types/types/ReactKitErrorCode.d.ts +4 -1
- package/dist/esm/index.js +560 -259
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/Dropdown.d.ts +32 -0
- package/dist/esm/types/components/Modal/ModalProps.d.ts +0 -2
- package/dist/esm/types/helpers/validators/ChicagoTitleCase.d.ts +9 -0
- package/dist/esm/types/helpers/validators/validURL.d.ts +8 -0
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +22 -0
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +33 -0
- package/dist/esm/types/index.d.ts +4 -1
- package/dist/esm/types/types/DropdownItemType.d.ts +6 -0
- package/dist/esm/types/types/ReactKitErrorCode.d.ts +4 -1
- package/dist/index.d.ts +105 -46
- package/package.json +2 -1
- package/src/components/Dropdown.tsx +317 -0
- package/src/components/Modal/ModalProps.ts +0 -4
- package/src/components/Modal/index.tsx +3 -39
- package/src/components/MultiSwitch.tsx +1 -1
- package/src/helpers/validators/ChicagoTitleCase.test.ts +85 -0
- package/src/helpers/validators/ChicagoTitleCase.ts +32 -0
- package/src/helpers/validators/findURL.test.ts +83 -0
- package/src/helpers/validators/findURL.ts +43 -0
- package/src/helpers/validators/validURL.test.ts +90 -0
- package/src/helpers/validators/validURL.ts +18 -0
- package/src/helpers/visitEndpointOnAnotherServer/index.ts +90 -0
- package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +164 -0
- package/src/index.ts +6 -0
- package/src/types/DropdownItemType.ts +11 -0
- package/src/types/ReactKitErrorCode.tsx +6 -1
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A simple dropdown menu
|
|
3
|
+
* @author Alessandra De Lucas
|
|
4
|
+
* @author Yuen Ler Chow
|
|
5
|
+
* @author Gabe Abrams
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Import React
|
|
9
|
+
import React, { useEffect, useReducer, useRef } from 'react';
|
|
10
|
+
|
|
11
|
+
// Import helpers
|
|
12
|
+
import combineClassNames from '../helpers/combineClassNames';
|
|
13
|
+
|
|
14
|
+
// Import shared types
|
|
15
|
+
import Variant from '../types/Variant';
|
|
16
|
+
import DropdownItemType from '../types/DropdownItemType';
|
|
17
|
+
|
|
18
|
+
// Check dark mode
|
|
19
|
+
import { isDarkModeOn } from '../client/initClient';
|
|
20
|
+
|
|
21
|
+
/*------------------------------------------------------------------------*/
|
|
22
|
+
/* -------------------------------- Types ------------------------------- */
|
|
23
|
+
/*------------------------------------------------------------------------*/
|
|
24
|
+
|
|
25
|
+
// Props definition
|
|
26
|
+
type Props = {
|
|
27
|
+
// List of items in the dropdown menu
|
|
28
|
+
items: DropdownItem[],
|
|
29
|
+
// Information about the dropdown button
|
|
30
|
+
dropdownButton: {
|
|
31
|
+
// Aria label for accessibility
|
|
32
|
+
ariaLabel: string,
|
|
33
|
+
// Unique ID
|
|
34
|
+
id: string,
|
|
35
|
+
// Button content
|
|
36
|
+
content?: React.ReactNode,
|
|
37
|
+
// Variant
|
|
38
|
+
variant?: Variant,
|
|
39
|
+
},
|
|
40
|
+
// If true, no arrow shows in dropdown
|
|
41
|
+
// noArrow?: boolean,
|
|
42
|
+
// Size of dropdown
|
|
43
|
+
// size?: DropdownSize,
|
|
44
|
+
// Direction of dropdown
|
|
45
|
+
// direction?: DropdownDirection,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Details about a dropdown item
|
|
49
|
+
type DropdownItem = (
|
|
50
|
+
| {
|
|
51
|
+
// Header element
|
|
52
|
+
type: DropdownItemType.Header,
|
|
53
|
+
// Header content
|
|
54
|
+
content: React.ReactNode,
|
|
55
|
+
}
|
|
56
|
+
| {
|
|
57
|
+
// Divider element
|
|
58
|
+
type: DropdownItemType.Divider,
|
|
59
|
+
}
|
|
60
|
+
| {
|
|
61
|
+
// Item from dropdown menu
|
|
62
|
+
type: DropdownItemType.Item,
|
|
63
|
+
// Item content
|
|
64
|
+
content: React.ReactNode,
|
|
65
|
+
// Item aria label
|
|
66
|
+
ariaLabel: string,
|
|
67
|
+
// Unique item ID
|
|
68
|
+
id: string,
|
|
69
|
+
// Click function for dropdown item
|
|
70
|
+
onClick: () => void,
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Dropdown menu sizes
|
|
75
|
+
// enum DropdownSize {
|
|
76
|
+
// // Small dropdown size
|
|
77
|
+
// Small = 'Small',
|
|
78
|
+
// // Medium dropdown size
|
|
79
|
+
// Medium = 'Medium',
|
|
80
|
+
// // Large dropdown size
|
|
81
|
+
// Large = 'Large',
|
|
82
|
+
// }
|
|
83
|
+
|
|
84
|
+
// Dropdown menu direction
|
|
85
|
+
// enum DropdownDirection {
|
|
86
|
+
// // Dropdown menu appears centered
|
|
87
|
+
// Centered = 'Centered',
|
|
88
|
+
// // Dropdown menu appears to right of button
|
|
89
|
+
// Right = 'Right',
|
|
90
|
+
// // Dropdown menu appears to left of button
|
|
91
|
+
// Left = 'Left',
|
|
92
|
+
// // Dropdown menu appears above the button
|
|
93
|
+
// Up = 'Up',
|
|
94
|
+
// // Dropdown menu appears below the button
|
|
95
|
+
// Down = 'Down',
|
|
96
|
+
// }
|
|
97
|
+
|
|
98
|
+
/*------------------------------------------------------------------------*/
|
|
99
|
+
/* -------------------------------- State ------------------------------- */
|
|
100
|
+
/*------------------------------------------------------------------------*/
|
|
101
|
+
|
|
102
|
+
/* -------- State Definition -------- */
|
|
103
|
+
|
|
104
|
+
type State = {
|
|
105
|
+
// If true, the dropdown menu is open
|
|
106
|
+
isDropdownOpen: boolean,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/* ------------- Actions ------------ */
|
|
110
|
+
|
|
111
|
+
// Types of actions
|
|
112
|
+
enum ActionType {
|
|
113
|
+
// Toggle opening the dropdown menu
|
|
114
|
+
ToggleDropdown = 'ToggleDropdown',
|
|
115
|
+
// Close the dropdown menu
|
|
116
|
+
CloseDropdown = 'CloseDropdown',
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Action definitions
|
|
120
|
+
type Action = (
|
|
121
|
+
| {
|
|
122
|
+
type: ActionType.ToggleDropdown,
|
|
123
|
+
}
|
|
124
|
+
| {
|
|
125
|
+
type: ActionType.CloseDropdown,
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Reducer that executes actions
|
|
131
|
+
* @author Alessandra De Lucas
|
|
132
|
+
* @author Yuen Ler Chow
|
|
133
|
+
* @param state current state
|
|
134
|
+
* @param action action to execute
|
|
135
|
+
*/
|
|
136
|
+
const reducer = (state: State, action: Action): State => {
|
|
137
|
+
switch (action.type) {
|
|
138
|
+
case ActionType.ToggleDropdown: {
|
|
139
|
+
return {
|
|
140
|
+
...state,
|
|
141
|
+
isDropdownOpen: !state.isDropdownOpen,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
case ActionType.CloseDropdown: {
|
|
145
|
+
return {
|
|
146
|
+
...state,
|
|
147
|
+
isDropdownOpen: false,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
default: {
|
|
151
|
+
return state;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/*------------------------------------------------------------------------*/
|
|
157
|
+
/* ------------------------------ Component ----------------------------- */
|
|
158
|
+
/*------------------------------------------------------------------------*/
|
|
159
|
+
|
|
160
|
+
const Dropdown: React.FC<Props> = (props) => {
|
|
161
|
+
/*------------------------------------------------------------------------*/
|
|
162
|
+
/* -------------------------------- Setup ------------------------------- */
|
|
163
|
+
/*------------------------------------------------------------------------*/
|
|
164
|
+
|
|
165
|
+
/* -------------- Props ------------- */
|
|
166
|
+
|
|
167
|
+
// Destructure all props
|
|
168
|
+
const {
|
|
169
|
+
dropdownButton,
|
|
170
|
+
items,
|
|
171
|
+
} = props;
|
|
172
|
+
|
|
173
|
+
/* -------------- State ------------- */
|
|
174
|
+
|
|
175
|
+
// Initial state
|
|
176
|
+
const initialState: State = {
|
|
177
|
+
isDropdownOpen: false,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Initialize state
|
|
181
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
182
|
+
|
|
183
|
+
// Destructure common state
|
|
184
|
+
const {
|
|
185
|
+
isDropdownOpen,
|
|
186
|
+
} = state;
|
|
187
|
+
|
|
188
|
+
/* -------------- Refs -------------- */
|
|
189
|
+
|
|
190
|
+
// Initialize refs
|
|
191
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
192
|
+
|
|
193
|
+
/*------------------------------------------------------------------------*/
|
|
194
|
+
/* ------------------------- Component Functions ------------------------ */
|
|
195
|
+
/*------------------------------------------------------------------------*/
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Handle clicking outside of the dropdown menu to close it
|
|
199
|
+
* @author Yuen Ler Chow
|
|
200
|
+
* @param event the mouse event
|
|
201
|
+
*/
|
|
202
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
203
|
+
if (
|
|
204
|
+
// Dropdown has been rendered
|
|
205
|
+
dropdownRef.current
|
|
206
|
+
// Click occurred outside the dropdown
|
|
207
|
+
&& !dropdownRef.current.contains(event.target as Element)
|
|
208
|
+
) {
|
|
209
|
+
dispatch({ type: ActionType.CloseDropdown });
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/*------------------------------------------------------------------------*/
|
|
214
|
+
/* ------------------------- Lifecycle Functions ------------------------ */
|
|
215
|
+
/*------------------------------------------------------------------------*/
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Mount
|
|
219
|
+
* @author Yuen Ler Chow
|
|
220
|
+
*/
|
|
221
|
+
useEffect(() => {
|
|
222
|
+
// Add event listener to close dropdown when clicking outside
|
|
223
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
224
|
+
|
|
225
|
+
// Cleanup
|
|
226
|
+
return () => {
|
|
227
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
228
|
+
};
|
|
229
|
+
}, []);
|
|
230
|
+
|
|
231
|
+
/*----------------------------------------*/
|
|
232
|
+
/* --------------- Main UI -------------- */
|
|
233
|
+
/*----------------------------------------*/
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<div
|
|
237
|
+
className="dropdown"
|
|
238
|
+
ref={dropdownRef}
|
|
239
|
+
data-bs-theme={isDarkModeOn() ? 'dark' : undefined}
|
|
240
|
+
>
|
|
241
|
+
<button
|
|
242
|
+
className={
|
|
243
|
+
combineClassNames([
|
|
244
|
+
'btn dropdown-toggle border',
|
|
245
|
+
isDropdownOpen && 'show',
|
|
246
|
+
`btn-${dropdownButton.variant}`,
|
|
247
|
+
dropdownButton.variant === Variant.Light && 'text-dark',
|
|
248
|
+
])
|
|
249
|
+
}
|
|
250
|
+
type="button"
|
|
251
|
+
id={dropdownButton.id}
|
|
252
|
+
aria-expanded={isDropdownOpen}
|
|
253
|
+
aria-label={dropdownButton.ariaLabel}
|
|
254
|
+
onClick={() => {
|
|
255
|
+
dispatch({
|
|
256
|
+
type: ActionType.ToggleDropdown,
|
|
257
|
+
});
|
|
258
|
+
}}
|
|
259
|
+
>
|
|
260
|
+
{
|
|
261
|
+
dropdownButton.content
|
|
262
|
+
}
|
|
263
|
+
</button>
|
|
264
|
+
<ul
|
|
265
|
+
className={
|
|
266
|
+
combineClassNames([
|
|
267
|
+
'dropdown-menu',
|
|
268
|
+
isDarkModeOn() && 'dropdown-menu-dark',
|
|
269
|
+
isDropdownOpen && 'show',
|
|
270
|
+
])
|
|
271
|
+
}
|
|
272
|
+
>
|
|
273
|
+
{Object.values(items).map((item) => {
|
|
274
|
+
if (item.type === DropdownItemType.Header) {
|
|
275
|
+
return (
|
|
276
|
+
// TODO: Implement header
|
|
277
|
+
<span />
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
if (item.type === DropdownItemType.Divider) {
|
|
281
|
+
return (
|
|
282
|
+
// TODO: Implement divider
|
|
283
|
+
<span />
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
return (
|
|
287
|
+
<li
|
|
288
|
+
key={item.id}
|
|
289
|
+
>
|
|
290
|
+
<button
|
|
291
|
+
type="button"
|
|
292
|
+
aria-label={item.ariaLabel}
|
|
293
|
+
className="dropdown-item"
|
|
294
|
+
onClick={(e) => {
|
|
295
|
+
e.preventDefault();
|
|
296
|
+
dispatch({
|
|
297
|
+
type: ActionType.CloseDropdown,
|
|
298
|
+
});
|
|
299
|
+
item.onClick();
|
|
300
|
+
}}
|
|
301
|
+
>
|
|
302
|
+
{item.content}
|
|
303
|
+
</button>
|
|
304
|
+
</li>
|
|
305
|
+
);
|
|
306
|
+
})}
|
|
307
|
+
</ul>
|
|
308
|
+
</div>
|
|
309
|
+
);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
/*------------------------------------------------------------------------*/
|
|
313
|
+
/* ------------------------------- Wrap Up ------------------------------ */
|
|
314
|
+
/*------------------------------------------------------------------------*/
|
|
315
|
+
|
|
316
|
+
// Export component
|
|
317
|
+
export default Dropdown;
|
|
@@ -69,10 +69,6 @@ type ModalProps = {
|
|
|
69
69
|
confirmVariant?: Variant,
|
|
70
70
|
// True if modal should be on top of other modals
|
|
71
71
|
onTopOfOtherModals?: boolean,
|
|
72
|
-
// If true, the modal is loading
|
|
73
|
-
isLoading?: boolean,
|
|
74
|
-
// If true, the loading is cancelable
|
|
75
|
-
isLoadingCancelable?: boolean,
|
|
76
72
|
};
|
|
77
73
|
|
|
78
74
|
export default ModalProps;
|
|
@@ -15,7 +15,6 @@ import ReactDOM from 'react-dom';
|
|
|
15
15
|
|
|
16
16
|
// Import other components
|
|
17
17
|
import waitMs from '../../helpers/waitMs';
|
|
18
|
-
import LoadingSpinner from '../LoadingSpinner';
|
|
19
18
|
|
|
20
19
|
// Import types
|
|
21
20
|
import Variant from '../../types/Variant';
|
|
@@ -254,8 +253,6 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
254
253
|
dontAllowBackdropExit,
|
|
255
254
|
dontShowXButton,
|
|
256
255
|
onTopOfOtherModals,
|
|
257
|
-
isLoading,
|
|
258
|
-
isLoadingCancelable,
|
|
259
256
|
} = props;
|
|
260
257
|
|
|
261
258
|
// Determine if no header either
|
|
@@ -266,7 +263,6 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
266
263
|
// True if animation is in use
|
|
267
264
|
const [animatingIn, setAnimatingIn] = useState(true);
|
|
268
265
|
const [animatingPop, setAnimatingPop] = useState(false);
|
|
269
|
-
const [showModal, setShowModal] = useState(false);
|
|
270
266
|
|
|
271
267
|
/* -------------- Refs -------------- */
|
|
272
268
|
|
|
@@ -298,13 +294,6 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
298
294
|
// Update to state after animated in
|
|
299
295
|
if (mounted.current) {
|
|
300
296
|
setAnimatingIn(false);
|
|
301
|
-
if (isLoading) {
|
|
302
|
-
// wait 1 second before showing modal
|
|
303
|
-
await waitMs(1000);
|
|
304
|
-
setShowModal(true);
|
|
305
|
-
} else {
|
|
306
|
-
setShowModal(true);
|
|
307
|
-
}
|
|
308
297
|
}
|
|
309
298
|
})();
|
|
310
299
|
|
|
@@ -444,8 +433,6 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
444
433
|
handleClose(ModalButtonType.Cancel);
|
|
445
434
|
}}
|
|
446
435
|
/>
|
|
447
|
-
|
|
448
|
-
{ showModal && (
|
|
449
436
|
<div
|
|
450
437
|
className={`modal-dialog modal-${size} ${animationClass} modal-dialog-scrollable modal-dialog-centered`}
|
|
451
438
|
style={{
|
|
@@ -453,7 +440,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
453
440
|
// Override sizes for even larger for XL
|
|
454
441
|
width: (
|
|
455
442
|
size === ModalSize.ExtraLarge
|
|
456
|
-
? 'calc(100vw -
|
|
443
|
+
? 'calc(100vw - 1rem)'
|
|
457
444
|
: undefined
|
|
458
445
|
),
|
|
459
446
|
maxWidth: (
|
|
@@ -508,7 +495,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
508
495
|
{title}
|
|
509
496
|
</h5>
|
|
510
497
|
|
|
511
|
-
{(onClose && !dontShowXButton
|
|
498
|
+
{(onClose && !dontShowXButton) && (
|
|
512
499
|
<button
|
|
513
500
|
type="button"
|
|
514
501
|
className="Modal-x-button btn-close"
|
|
@@ -528,29 +515,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
528
515
|
)}
|
|
529
516
|
</div>
|
|
530
517
|
)}
|
|
531
|
-
|
|
532
|
-
{isLoading && (
|
|
533
|
-
<div
|
|
534
|
-
className="modal-body"
|
|
535
|
-
style={{
|
|
536
|
-
color: (
|
|
537
|
-
isDarkModeOn()
|
|
538
|
-
? 'white'
|
|
539
|
-
: undefined
|
|
540
|
-
),
|
|
541
|
-
backgroundColor: (
|
|
542
|
-
isDarkModeOn()
|
|
543
|
-
? '#444'
|
|
544
|
-
: undefined
|
|
545
|
-
),
|
|
546
|
-
}}
|
|
547
|
-
>
|
|
548
|
-
<LoadingSpinner />
|
|
549
|
-
<span className="sr-only">Content loading</span>
|
|
550
|
-
</div>
|
|
551
|
-
)}
|
|
552
|
-
|
|
553
|
-
{children && !isLoading && (
|
|
518
|
+
{children && (
|
|
554
519
|
<div
|
|
555
520
|
className="modal-body"
|
|
556
521
|
style={{
|
|
@@ -595,7 +560,6 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
595
560
|
)}
|
|
596
561
|
</div>
|
|
597
562
|
</div>
|
|
598
|
-
)}
|
|
599
563
|
</div>
|
|
600
564
|
);
|
|
601
565
|
|
|
@@ -153,7 +153,7 @@ const MultiSwitch: React.FC<Props> = (props) => {
|
|
|
153
153
|
|
|
154
154
|
// Constants
|
|
155
155
|
const gutterRems = heightRem * 0.1;
|
|
156
|
-
const itemWidthRems = heightRem *
|
|
156
|
+
const itemWidthRems = heightRem * 2.2;
|
|
157
157
|
const textFontSize = heightRem / 3.5;
|
|
158
158
|
const iconFontSize = heightRem / 2;
|
|
159
159
|
const borderWidthRems = 0.05;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Importing the testing function
|
|
2
|
+
// import chicagoTitleCase from './chicagoTitleCase';
|
|
3
|
+
|
|
4
|
+
// // Import constants
|
|
5
|
+
// import { INVALID_STRING_ERRORS, INVALID_REGEX_ERROR } from './shared/constants/ERROR_MESSAGES';
|
|
6
|
+
|
|
7
|
+
// /*------------------------------------------------------------------------*/
|
|
8
|
+
// /* ---------------------------- Valid Tests --------------------------- */
|
|
9
|
+
// /*------------------------------------------------------------------------*/
|
|
10
|
+
|
|
11
|
+
// const validTests: { input: string, expected: string }[] = [
|
|
12
|
+
// {
|
|
13
|
+
// input: 'this is the american language',
|
|
14
|
+
// expected: 'This the American language',
|
|
15
|
+
// },
|
|
16
|
+
// {
|
|
17
|
+
// input: 'to kill a mockingbird',
|
|
18
|
+
// expected: 'To Kill a Mockingbird',
|
|
19
|
+
// },
|
|
20
|
+
// {
|
|
21
|
+
// input: 'the lord of the rings',
|
|
22
|
+
// expected: 'The Lord of the Rings',
|
|
23
|
+
// },
|
|
24
|
+
// {
|
|
25
|
+
// input: 'pride and prejudice',
|
|
26
|
+
// expected: 'Pride and Prejudice',
|
|
27
|
+
// },
|
|
28
|
+
// {
|
|
29
|
+
// input: 'the road not taken',
|
|
30
|
+
// expected: 'The Road Not Taken',
|
|
31
|
+
// },
|
|
32
|
+
// ];
|
|
33
|
+
|
|
34
|
+
// test(
|
|
35
|
+
// 'Converts strings to Chicago title case correctly.',
|
|
36
|
+
// async () => {
|
|
37
|
+
// validTests.forEach(({ input, expected }) => {
|
|
38
|
+
// expect(chicagoTitleCase(input)).toBe(expected);
|
|
39
|
+
// });
|
|
40
|
+
// },
|
|
41
|
+
// );
|
|
42
|
+
|
|
43
|
+
// /*------------------------------------------------------------------------*/
|
|
44
|
+
// /* ---------------------------- Edge Cases ---------------------------- */
|
|
45
|
+
// /*------------------------------------------------------------------------*/
|
|
46
|
+
|
|
47
|
+
// const edgeCases: { input: string, expected: string }[] = [
|
|
48
|
+
// {
|
|
49
|
+
// input: 'x',
|
|
50
|
+
// expected: 'X',
|
|
51
|
+
// },
|
|
52
|
+
// {
|
|
53
|
+
// input: ' ',
|
|
54
|
+
// expected: ' ',
|
|
55
|
+
// },
|
|
56
|
+
// {
|
|
57
|
+
// input: '',
|
|
58
|
+
// expected: '',
|
|
59
|
+
// },
|
|
60
|
+
// {
|
|
61
|
+
// input: 'the',
|
|
62
|
+
// expected: 'The',
|
|
63
|
+
// },
|
|
64
|
+
// {
|
|
65
|
+
// input: 'THE ROAD NOT TAKEN',
|
|
66
|
+
// expected: 'The Road Not Taken',
|
|
67
|
+
// },
|
|
68
|
+
// {
|
|
69
|
+
// input: 'tO kiLL A mOckingBirD',
|
|
70
|
+
// expected: 'To Kill a Mockingbird',
|
|
71
|
+
// },
|
|
72
|
+
// {
|
|
73
|
+
// input: 'pRiDe And pReduJiCe',
|
|
74
|
+
// expected: 'Pride and Prejudice',
|
|
75
|
+
// },
|
|
76
|
+
// ];
|
|
77
|
+
|
|
78
|
+
// test(
|
|
79
|
+
// 'Handles the edge cases for the function that converts a string to Chicago Title Case.',
|
|
80
|
+
// async () => {
|
|
81
|
+
// edgeCases.forEach(({ input, expected }) => {
|
|
82
|
+
// expect(chicagoTitleCase(input)).toBe(expected);
|
|
83
|
+
// });
|
|
84
|
+
// },
|
|
85
|
+
// );
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This function converts a string to title case based on Chicago Title Case
|
|
3
|
+
* Style rules.
|
|
4
|
+
* @author Leisha Bhandari
|
|
5
|
+
* @param input: The input string that needs to be converted to Chicago title
|
|
6
|
+
* case.
|
|
7
|
+
* @returns Input string converted to title case
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const chicagoTitleCase = (input: string): string => {
|
|
12
|
+
// List of lowercase words according to Chicago Manual of Style
|
|
13
|
+
const lowerCaseWords: string[] = [
|
|
14
|
+
'in', 'nor', 'of', 'on', 'or', 'as', 'at', 'but', 'by',
|
|
15
|
+
'for', 'if', 'so', 'the', 'a', 'an', 'and', 'to', 'up',
|
|
16
|
+
'yet'
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
return input
|
|
20
|
+
// Converts given input string to lowercase
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
// Splits the given string into separate words
|
|
23
|
+
.split(' ')
|
|
24
|
+
.map((word, index, words) =>
|
|
25
|
+
// Capitalize the first word, last word, and the words that aren't
|
|
26
|
+
// in lowerCaseWords
|
|
27
|
+
index === 0 || !lowerCaseWords.includes(word) || index === words.length - 1
|
|
28
|
+
? `${word.charAt(0).toUpperCase()}${word.slice(1)}` // Capitalize the first letter
|
|
29
|
+
: word // Nothing happens to the word
|
|
30
|
+
)
|
|
31
|
+
.join(' '); // Joins the words back
|
|
32
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Import the function to be tested
|
|
2
|
+
// import findURLs from './findURL';
|
|
3
|
+
|
|
4
|
+
// // Import constants
|
|
5
|
+
// import { INVALID_STRING_ERRORS, INVALID_REGEX_ERROR } from './shared/constants/ERROR_MESSAGES';
|
|
6
|
+
|
|
7
|
+
// /*------------------------------------------------------------------------*/
|
|
8
|
+
// /* ---------------------------- Valid Tests --------------------------- */
|
|
9
|
+
// /*------------------------------------------------------------------------*/
|
|
10
|
+
|
|
11
|
+
// const validTests: { block: string, expected: { start: number, end: number }[] }[] = [
|
|
12
|
+
// {
|
|
13
|
+
// block: 'Visit http://example.com for more information.',
|
|
14
|
+
// expected: [{ start: 6, end: 23 }],
|
|
15
|
+
// },
|
|
16
|
+
// {
|
|
17
|
+
// block: 'Multiple URLs: http://one.com and http://two.com.',
|
|
18
|
+
// expected: [
|
|
19
|
+
// { start: 15, end: 29 },
|
|
20
|
+
// { start: 34, end: 48 },
|
|
21
|
+
// ],
|
|
22
|
+
// },
|
|
23
|
+
// {
|
|
24
|
+
// block: 'Localhost URLs: http://localhost and http://127.0.0.1.',
|
|
25
|
+
// expected: [
|
|
26
|
+
// { start: 16, end: 31 },
|
|
27
|
+
// { start: 36, end: 52 },
|
|
28
|
+
// ],
|
|
29
|
+
// },
|
|
30
|
+
// {
|
|
31
|
+
// block: 'Embedded URL: text https://embedded-url.com?query=1&value=2.',
|
|
32
|
+
// expected: [{ start: 13, end: 51 }],
|
|
33
|
+
// },
|
|
34
|
+
// {
|
|
35
|
+
// block: 'Secure site: https://secure-site.com/path?query=string#fragment',
|
|
36
|
+
// expected: [{ start: 13, end: 58 }],
|
|
37
|
+
// },
|
|
38
|
+
// ];
|
|
39
|
+
|
|
40
|
+
// test(
|
|
41
|
+
// 'Returns correct start and end indices for valid URLs within a string.',
|
|
42
|
+
// async () => {
|
|
43
|
+
// validTests.forEach(({ block, expected }) => {
|
|
44
|
+
// expect(findURLs(block)).toStrictEqual(expected);
|
|
45
|
+
// });
|
|
46
|
+
// },
|
|
47
|
+
// );
|
|
48
|
+
|
|
49
|
+
// /*------------------------------------------------------------------------*/
|
|
50
|
+
// /* ---------------------------- Invalid Tests --------------------------- */
|
|
51
|
+
// /*------------------------------------------------------------------------*/
|
|
52
|
+
|
|
53
|
+
// const invalidTests: { block: string, expected: { start: number, end: number }[] }[] = [
|
|
54
|
+
// {
|
|
55
|
+
// block: 'No URL.',
|
|
56
|
+
// expected: [],
|
|
57
|
+
// },
|
|
58
|
+
// {
|
|
59
|
+
// block: 'URL format is wrong: http:/example.com and htt://example.com.',
|
|
60
|
+
// expected: [],
|
|
61
|
+
// },
|
|
62
|
+
// {
|
|
63
|
+
// block: 'Any punctuation after the URL http://example.com, should not count.',
|
|
64
|
+
// expected: [{ start: 20, end: 37 }],
|
|
65
|
+
// },
|
|
66
|
+
// {
|
|
67
|
+
// block: 'Starting punctuation ,http://example.com should not count.',
|
|
68
|
+
// expected: [{ start: 22, end: 39 }],
|
|
69
|
+
// },
|
|
70
|
+
// {
|
|
71
|
+
// block: 'Spaces in the middle http://exa mple.com is invalid.',
|
|
72
|
+
// expected: [],
|
|
73
|
+
// },
|
|
74
|
+
// ];
|
|
75
|
+
|
|
76
|
+
// test(
|
|
77
|
+
// 'Returns empty array for strings without valid URLs.',
|
|
78
|
+
// async () => {
|
|
79
|
+
// invalidTests.forEach(({ block, expected }) => {
|
|
80
|
+
// expect(findURLs(block)).toStrictEqual(expected);
|
|
81
|
+
// });
|
|
82
|
+
// },
|
|
83
|
+
// );
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// /**
|
|
2
|
+
// * This function finds URLs within a given string and returns an array of
|
|
3
|
+
// * their locations.
|
|
4
|
+
// * @author Leisha Bhandari
|
|
5
|
+
// * @param block: The block of text to search for URLs
|
|
6
|
+
// * @returns Arrays where each of them contain the start and end index of the URL
|
|
7
|
+
// */
|
|
8
|
+
// function findURLs(block: string): { start: number, end: number }[] {
|
|
9
|
+
// // Expression representing the skeleton of a URL to help match and find the
|
|
10
|
+
// // URLs within the block of text (Also takes care of Unicode characters)
|
|
11
|
+
// const urlSkeleton = /(https?:\/\/[^\s\u0000-\u001F.,]+[^\s\u0000-\u001F.,]?(?:\?[^\s\u0000-\u001F]+)?(?:#[^\s\u0000-\u001F]+)?)/g;
|
|
12
|
+
// const found: { start: number, end: number }[] = [];
|
|
13
|
+
// let match: RegExpExecArray | null;
|
|
14
|
+
|
|
15
|
+
// // While loop to find the URLs in the given block of texts using the URL
|
|
16
|
+
// // skeleton
|
|
17
|
+
// while ((match === urlSkeleton.exec(block))
|
|
18
|
+
// && match !== null) {
|
|
19
|
+
// // To find the beginning index of the URL
|
|
20
|
+
// let start = match.index;
|
|
21
|
+
// // To find the last index of the URL
|
|
22
|
+
// let end = match.index + match[0].length;
|
|
23
|
+
|
|
24
|
+
// // If statements to check whether the URL is followed by some kind of
|
|
25
|
+
// // punctuation mark
|
|
26
|
+
// if (block.charAt(end) === '.' || /[,;:!?"]/.test(block.charAt(end))) {
|
|
27
|
+
// end--;
|
|
28
|
+
// }
|
|
29
|
+
|
|
30
|
+
// // While statement to make sure that the URL does not start with a
|
|
31
|
+
// // punctuation mark
|
|
32
|
+
// while (start > 0 && /[.,:;!?]/.test(block.charAt(start - 1))) {
|
|
33
|
+
// start--;
|
|
34
|
+
// }
|
|
35
|
+
|
|
36
|
+
// // The object with start and end index of the URL is found and pushed to
|
|
37
|
+
// // the found array
|
|
38
|
+
// found.push({ start, end });
|
|
39
|
+
// }
|
|
40
|
+
|
|
41
|
+
// // Returns the array of the found URLs location
|
|
42
|
+
// return found;
|
|
43
|
+
// }
|