dce-reactkit 3.9.0-beta-loading-modal.2 → 3.9.0-beta-loading-modal.3

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 (62) hide show
  1. package/dist/cjs/index.js +783 -201
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/client/initClient.d.ts +16 -2
  4. package/dist/cjs/types/components/AppWrapper.d.ts +32 -0
  5. package/dist/cjs/types/components/CheckboxButton.d.ts +1 -0
  6. package/dist/cjs/types/components/Dropdown.d.ts +32 -0
  7. package/dist/cjs/types/components/RadioButton.d.ts +2 -0
  8. package/dist/cjs/types/helpers/genRouteHandler.d.ts +8 -0
  9. package/dist/cjs/types/helpers/validators/ChicagoTitleCase.d.ts +9 -0
  10. package/dist/cjs/types/helpers/validators/validURL.d.ts +8 -0
  11. package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +24 -0
  12. package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +33 -0
  13. package/dist/cjs/types/index.d.ts +5 -2
  14. package/dist/cjs/types/types/DropdownItemType.d.ts +6 -0
  15. package/dist/cjs/types/types/ModalType.d.ts +1 -0
  16. package/dist/cjs/types/types/ReactKitErrorCode.d.ts +6 -1
  17. package/dist/esm/index.js +779 -202
  18. package/dist/esm/index.js.map +1 -1
  19. package/dist/esm/types/client/initClient.d.ts +16 -2
  20. package/dist/esm/types/components/AppWrapper.d.ts +32 -0
  21. package/dist/esm/types/components/CheckboxButton.d.ts +1 -0
  22. package/dist/esm/types/components/Dropdown.d.ts +32 -0
  23. package/dist/esm/types/components/RadioButton.d.ts +2 -0
  24. package/dist/esm/types/helpers/genRouteHandler.d.ts +8 -0
  25. package/dist/esm/types/helpers/validators/ChicagoTitleCase.d.ts +9 -0
  26. package/dist/esm/types/helpers/validators/validURL.d.ts +8 -0
  27. package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +24 -0
  28. package/dist/esm/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +33 -0
  29. package/dist/esm/types/index.d.ts +5 -2
  30. package/dist/esm/types/types/DropdownItemType.d.ts +6 -0
  31. package/dist/esm/types/types/ModalType.d.ts +1 -0
  32. package/dist/esm/types/types/ReactKitErrorCode.d.ts +6 -1
  33. package/dist/index.d.ts +163 -46
  34. package/package.json +2 -1
  35. package/src/client/initClient.tsx +49 -11
  36. package/src/components/AppWrapper.tsx +258 -2
  37. package/src/components/CheckboxButton.tsx +24 -6
  38. package/src/components/Dropdown.tsx +317 -0
  39. package/src/components/IntelliTable.tsx +1 -1
  40. package/src/components/ItemPicker/NestableItemList.tsx +1 -0
  41. package/src/components/LogReviewer.tsx +17 -4
  42. package/src/components/Modal/index.tsx +4 -1
  43. package/src/components/MultiSwitch.tsx +1 -1
  44. package/src/components/RadioButton.tsx +33 -6
  45. package/src/components/SimpleDateChooser.tsx +8 -2
  46. package/src/helpers/genRouteHandler.ts +116 -17
  47. package/src/helpers/logClientEvent.tsx +8 -0
  48. package/src/helpers/validators/ChicagoTitleCase.test.ts +85 -0
  49. package/src/helpers/validators/ChicagoTitleCase.ts +32 -0
  50. package/src/helpers/validators/findURL.test.ts +83 -0
  51. package/src/helpers/validators/findURL.ts +43 -0
  52. package/src/helpers/validators/validURL.test.ts +90 -0
  53. package/src/helpers/validators/validURL.ts +18 -0
  54. package/src/helpers/visitEndpointOnAnotherServer/index.ts +93 -0
  55. package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +164 -0
  56. package/src/helpers/visitServerEndpoint.tsx +1 -0
  57. package/src/index.ts +8 -0
  58. package/src/server/initLogCollection.ts +5 -0
  59. package/src/server/initServer.ts +11 -5
  60. package/src/types/DropdownItemType.ts +11 -0
  61. package/src/types/ModalType.tsx +1 -0
  62. package/src/types/ReactKitErrorCode.tsx +8 -1
@@ -53,6 +53,9 @@ const modalTypeToModalButtonTypes: {
53
53
  [ModalType.Okay]: [
54
54
  ModalButtonType.Okay,
55
55
  ],
56
+ [ModalType.Cancel]: [
57
+ ModalButtonType.Cancel,
58
+ ],
56
59
  [ModalType.OkayCancel]: [
57
60
  ModalButtonType.Okay,
58
61
  ModalButtonType.Cancel,
@@ -460,7 +463,7 @@ const Modal: React.FC<ModalProps> = (props) => {
460
463
  // Override sizes for even larger for XL
461
464
  width: (
462
465
  size === ModalSize.ExtraLarge
463
- ? 'calc(100vw - 3rem)'
466
+ ? 'calc(100vw - 1rem)'
464
467
  : undefined
465
468
  ),
466
469
  maxWidth: (
@@ -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 * 1.3;
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;
@@ -32,6 +32,8 @@ type Props = {
32
32
  selected?: boolean,
33
33
  // The id of the button
34
34
  id?: string,
35
+ // Custom class name of the button
36
+ className?: string,
35
37
  // If true, no margin on right
36
38
  noMarginOnRight?: boolean,
37
39
  // Variant for when radio is selected
@@ -40,6 +42,8 @@ type Props = {
40
42
  unselectedVariant?: Variant,
41
43
  // True if using a small button
42
44
  small?: boolean,
45
+ // If true, use complex formatting (tabs, newlines) in the text
46
+ useComplexFormatting?: boolean,
43
47
  };
44
48
 
45
49
  /*------------------------------------------------------------------------*/
@@ -60,6 +64,7 @@ const RadioButton: React.FC<Props> = (props) => {
60
64
  title,
61
65
  selected,
62
66
  id,
67
+ className,
63
68
  noMarginOnRight,
64
69
  selectedVariant = (
65
70
  isDarkModeOn()
@@ -72,6 +77,7 @@ const RadioButton: React.FC<Props> = (props) => {
72
77
  : Variant.Light
73
78
  ),
74
79
  small,
80
+ useComplexFormatting,
75
81
  } = props;
76
82
 
77
83
  /*------------------------------------------------------------------------*/
@@ -87,7 +93,7 @@ const RadioButton: React.FC<Props> = (props) => {
87
93
  type="button"
88
94
  id={id}
89
95
  title={title}
90
- className={`btn btn-${selected ? selectedVariant : unselectedVariant}${selected ? ' selected' : ''}${small ? ' btn-sm' : ''} m-0${noMarginOnRight ? '' : ' me-1'}`}
96
+ className={`btn btn-${selected ? selectedVariant : unselectedVariant}${selected ? ' selected' : ''}${small ? ' btn-sm' : ''} m-0${noMarginOnRight ? '' : ' me-1'} ${className ?? ''}`}
91
97
  aria-label={`${ariaLabel}${selected ? ': currently selected' : ''}`}
92
98
  onClick={() => {
93
99
  if (!selected) {
@@ -95,11 +101,32 @@ const RadioButton: React.FC<Props> = (props) => {
95
101
  }
96
102
  }}
97
103
  >
98
- <FontAwesomeIcon
99
- icon={selected ? faDotCircle : faCircle}
100
- className="me-1"
101
- />
102
- {text}
104
+ <div className="d-flex flex-row align-items-center">
105
+ <div className="me-1">
106
+ <FontAwesomeIcon
107
+ icon={selected ? faDotCircle : faCircle}
108
+ />
109
+ </div>
110
+ {
111
+ useComplexFormatting
112
+ ? (
113
+ <pre
114
+ className="ps-1 text-start text-break m-0"
115
+ style={{
116
+ whiteSpace: 'pre-wrap',
117
+ tabSize: 2,
118
+ }}
119
+ >
120
+ {text}
121
+ </pre>
122
+ )
123
+ : (
124
+ <div className="flex-grow-1 text-start text-break">
125
+ {text}
126
+ </div>
127
+ )
128
+ }
129
+ </div>
103
130
  </button>
104
131
  );
105
132
  };
@@ -94,7 +94,13 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
94
94
  }
95
95
  const monthName = getMonthName(month).full;
96
96
  // Year is start year +1 for each 12 months
97
- const year = (startYear + (Math.floor(unmoddedMonth / 12)));
97
+ let yearsToAdd = 0;
98
+ let monthsOfYearsToAdd = unmoddedMonth;
99
+ while (monthsOfYearsToAdd > 12) {
100
+ monthsOfYearsToAdd -= 12;
101
+ yearsToAdd += 1;
102
+ }
103
+ const year = startYear + yearsToAdd;
98
104
 
99
105
  // Figure out which days are allowed
100
106
  const days = [];
@@ -174,7 +180,7 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
174
180
  return (
175
181
  <div
176
182
  className="SimpleDateChooser d-inline-block"
177
- aria-label={`date chooser with selected date: ${month} ${day}, ${year}`}
183
+ aria-label={`date chooser with selected date: ${month}/${day}/${year}`}
178
184
  >
179
185
  {/* Month Chooser */}
180
186
  <select
@@ -39,6 +39,12 @@ import LogLevel from '../types/LogLevel';
39
39
  * @param opts.handler function that processes the request
40
40
  * @param [opts.skipSessionCheck] if true, skip the session check (allow users
41
41
  * to not be logged in and launched via LTI)
42
+ * @param [opts.allowedHosts] if included, only allow requests from these hosts
43
+ * (start a hostname with a "*" to only check the end of the hostname)
44
+ * you can include just one string instead of an array
45
+ * @param [opts.bannedHosts] if included, do not allow requests from these hosts
46
+ * (start a hostname with a "*" to only check the end of the hostname)
47
+ * you can include just one string instead of an array
42
48
  * @param [opts.unhandledErrorMessagePrefix] if included, when an error that
43
49
  * is not of type ErrorWithCode is thrown, the client will receive an error
44
50
  * where the error message is prefixed with this string. For example,
@@ -102,6 +108,8 @@ const genRouteHandler = (
102
108
  },
103
109
  ) => any,
104
110
  skipSessionCheck?: boolean,
111
+ allowedHosts?: string[] | string,
112
+ bannedHosts?: string[] | string,
105
113
  unhandledErrorMessagePrefix?: string,
106
114
  },
107
115
  ) => {
@@ -110,6 +118,97 @@ const genRouteHandler = (
110
118
  // Output params
111
119
  const output: { [k in string]: any } = {};
112
120
 
121
+ /*----------------------------------------*/
122
+ /* ----------- Hostname Check ----------- */
123
+ /*----------------------------------------*/
124
+
125
+ // Get hostnames
126
+ const originURL = String(
127
+ req.get('origin')
128
+ || req.headers.origin
129
+ || req.headers.referer,
130
+ );
131
+ const originHostname = (
132
+ originURL
133
+ // Remove protocol
134
+ .replace(/(^\w+:|^)\/\//, '')
135
+ // Remove port
136
+ .replace(/:\d+$/, '')
137
+ );
138
+ const serverHostname = String(req.hostname);
139
+
140
+ // Check allowed
141
+ if (opts.allowedHosts) {
142
+ // Only accept requests from allowed hosts
143
+ const allowedArray = (
144
+ Array.isArray(opts.allowedHosts)
145
+ ? opts.allowedHosts
146
+ : [opts.allowedHosts]
147
+ );
148
+
149
+ // Check if server is localhost
150
+ if (serverHostname === 'localhost') {
151
+ // Allow localhost
152
+ allowedArray.push('localhost');
153
+ }
154
+
155
+ // Check if current host is allowed
156
+ const allowed = allowedArray.some((allowedHost) => {
157
+ if (allowedHost.startsWith('*')) {
158
+ // Check end of hostname
159
+ return originHostname.endsWith(allowedHost.substring(1));
160
+ }
161
+
162
+ // Check full hostname
163
+ return originHostname.toLowerCase() === allowedHost.toLowerCase();
164
+ });
165
+
166
+ // If not allowed, return error
167
+ if (!allowed) {
168
+ return handleError(
169
+ res,
170
+ {
171
+ message: 'You are not allowed to access this endpoint.',
172
+ code: ReactKitErrorCode.HostNotAllowed,
173
+ status: 403,
174
+ },
175
+ );
176
+ }
177
+ }
178
+
179
+ // Check banned
180
+ if (opts.bannedHosts) {
181
+ // Do not allow requests from banned hosts
182
+ const bannedArray = (
183
+ Array.isArray(opts.bannedHosts)
184
+ ? opts.bannedHosts
185
+ : [opts.bannedHosts]
186
+ );
187
+
188
+ // Check if current host is banned
189
+ const banned = bannedArray.some((bannedHost) => {
190
+ if (bannedHost.startsWith('*')) {
191
+ // Check end of hostname
192
+ return originHostname.endsWith(bannedHost.substring(1));
193
+ }
194
+
195
+ // Check full hostname
196
+ return originHostname.toLowerCase() === bannedHost.toLowerCase();
197
+ });
198
+
199
+ // If banned, return error
200
+ if (banned) {
201
+ return handleError(
202
+ res,
203
+ {
204
+ message: 'You are not allowed to access this endpoint.',
205
+ code: ReactKitErrorCode.HostBanned,
206
+ status: 403,
207
+ },
208
+ );
209
+ }
210
+ }
211
+
113
212
  /*----------------------------------------*/
114
213
  /* ------------ Parse Params ------------ */
115
214
  /*----------------------------------------*/
@@ -354,22 +453,22 @@ const genRouteHandler = (
354
453
  output.userId = (
355
454
  launchInfo
356
455
  ? launchInfo.userId
357
- : undefined
456
+ : (output.userId ?? undefined)
358
457
  );
359
458
  output.userFirstName = (
360
459
  launchInfo
361
460
  ? launchInfo.userFirstName
362
- : undefined
461
+ : (output.userFirstName ?? undefined)
363
462
  );
364
463
  output.userLastName = (
365
464
  launchInfo
366
465
  ? launchInfo.userLastName
367
- : undefined
466
+ : (output.userLastName ?? undefined)
368
467
  );
369
468
  output.userEmail = (
370
469
  launchInfo
371
470
  ? launchInfo.userEmail
372
- : undefined
471
+ : (output.userEmail ?? undefined)
373
472
  );
374
473
  output.userAvatarURL = (
375
474
  launchInfo
@@ -377,32 +476,32 @@ const genRouteHandler = (
377
476
  launchInfo.userImage
378
477
  ?? 'http://www.gravatar.com/avatar/?d=identicon'
379
478
  )
380
- : undefined
479
+ : (output.userAvatarURL ?? undefined)
381
480
  );
382
481
  output.isLearner = (
383
482
  launchInfo
384
483
  ? !!launchInfo.isLearner
385
- : undefined
484
+ : (output.isLearner ?? undefined)
386
485
  );
387
486
  output.isTTM = (
388
487
  launchInfo
389
488
  ? !!launchInfo.isTTM
390
- : undefined
489
+ : (output.isTTM ?? undefined)
391
490
  );
392
491
  output.isAdmin = (
393
492
  launchInfo
394
493
  ? !!launchInfo.isAdmin
395
- : undefined
494
+ : (output.isAdmin ?? undefined)
396
495
  );
397
496
  output.courseId = (
398
497
  launchInfo
399
498
  ? (output.courseId ?? launchInfo.courseId)
400
- : undefined
499
+ : (output.courseId ?? undefined)
401
500
  );
402
501
  output.courseName = (
403
502
  launchInfo
404
503
  ? launchInfo.contextLabel
405
- : undefined
504
+ : (output.courseName ?? undefined)
406
505
  );
407
506
 
408
507
  // Add other session variables
@@ -526,16 +625,16 @@ const genRouteHandler = (
526
625
 
527
626
  // Main log info
528
627
  const mainLogInfo: LogMainInfo = {
529
- id: `${launchInfo ? 'unknown' : launchInfo.userId}-${Date.now()}-${Math.floor(Math.random() * 100000)}-${Math.floor(Math.random() * 100000)}`,
530
- userFirstName: (launchInfo ? 'unknown' : launchInfo.userFirstName),
531
- userLastName: (launchInfo ? 'unknown' : launchInfo.userLastName),
532
- userEmail: (launchInfo ? 'unknown' : launchInfo.userEmail),
533
- userId: (launchInfo ? 'unknown' : launchInfo.userId),
628
+ id: `${launchInfo ? launchInfo.userId : 'unknown'}-${Date.now()}-${Math.floor(Math.random() * 100000)}-${Math.floor(Math.random() * 100000)}`,
629
+ userFirstName: (launchInfo ? launchInfo.userFirstName: 'unknown'),
630
+ userLastName: (launchInfo ? launchInfo.userLastName : 'unknown'),
631
+ userEmail: (launchInfo ? launchInfo.userEmail : 'unknown'),
632
+ userId: (launchInfo ? launchInfo.userId : 'unknown'),
534
633
  isLearner: (launchInfo && !!launchInfo.isLearner),
535
634
  isAdmin: (launchInfo && !!launchInfo.isAdmin),
536
635
  isTTM: (launchInfo && !!launchInfo.isTTM),
537
- courseId: (launchInfo ? 'unknown' : launchInfo.courseId),
538
- courseName: (launchInfo ? 'unknown' : launchInfo.contextLabel),
636
+ courseId: (launchInfo ? launchInfo.courseId : 'unknown' ),
637
+ courseName: (launchInfo ? launchInfo.contextLabel : 'unknown'),
539
638
  browser,
540
639
  device,
541
640
  year,
@@ -7,6 +7,9 @@ import LogLevel from '../types/LogLevel';
7
7
  // Import shared functions
8
8
  import visitServerEndpoint from './visitServerEndpoint';
9
9
 
10
+ // Import status
11
+ import { appHasNoServer } from '../client/initClient';
12
+
10
13
  /* ------- Metadata Populator ------- */
11
14
 
12
15
  // Type of a metadata populator function
@@ -36,6 +39,11 @@ export const setClientEventMetadataPopulator = (
36
39
  * @author Gabe Abrams
37
40
  */
38
41
  const logClientEvent: LogFunction = async (opts) => {
42
+ // Don't write to the server if there is none
43
+ if (appHasNoServer()) {
44
+ return;
45
+ }
46
+
39
47
  // Populate metadata
40
48
  let metadata = (opts.metadata ?? {});
41
49
  if (metadataPopulator) {
@@ -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
+ // }