@atproto/api 0.20.23 β†’ 0.20.26

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 (74) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/agent.d.ts +4 -0
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +17 -0
  5. package/dist/agent.js.map +1 -1
  6. package/dist/client/lexicons.d.ts +8 -0
  7. package/dist/client/lexicons.d.ts.map +1 -1
  8. package/dist/client/lexicons.js +4 -0
  9. package/dist/client/lexicons.js.map +1 -1
  10. package/dist/client/types/app/bsky/actor/defs.d.ts +2 -0
  11. package/dist/client/types/app/bsky/actor/defs.d.ts.map +1 -1
  12. package/dist/client/types/app/bsky/actor/defs.js.map +1 -1
  13. package/dist/rich-text/detection.d.ts.map +1 -1
  14. package/dist/rich-text/detection.js +4 -1
  15. package/dist/rich-text/detection.js.map +1 -1
  16. package/dist/types.d.ts +1 -0
  17. package/dist/types.d.ts.map +1 -1
  18. package/dist/types.js.map +1 -1
  19. package/package.json +22 -16
  20. package/definitions/labels.json +0 -170
  21. package/docs/moderation.md +0 -260
  22. package/jest.config.cjs +0 -23
  23. package/jest.d.ts +0 -20
  24. package/jest.setup.ts +0 -97
  25. package/scripts/code/labels.mjs +0 -77
  26. package/scripts/generate-code.mjs +0 -4
  27. package/src/age-assurance.test.ts +0 -218
  28. package/src/age-assurance.ts +0 -137
  29. package/src/agent.ts +0 -1645
  30. package/src/atp-agent.ts +0 -570
  31. package/src/bsky-agent.ts +0 -14
  32. package/src/const.ts +0 -1
  33. package/src/index.ts +0 -44
  34. package/src/mocker.ts +0 -220
  35. package/src/moderation/decision.ts +0 -389
  36. package/src/moderation/index.ts +0 -69
  37. package/src/moderation/mutewords.ts +0 -170
  38. package/src/moderation/subjects/account.ts +0 -44
  39. package/src/moderation/subjects/feed-generator.ts +0 -24
  40. package/src/moderation/subjects/notification.ts +0 -25
  41. package/src/moderation/subjects/post.ts +0 -433
  42. package/src/moderation/subjects/profile.ts +0 -26
  43. package/src/moderation/subjects/status.ts +0 -27
  44. package/src/moderation/subjects/user-list.ts +0 -48
  45. package/src/moderation/types.ts +0 -191
  46. package/src/moderation/ui.ts +0 -21
  47. package/src/moderation/util.ts +0 -111
  48. package/src/predicate.ts +0 -52
  49. package/src/rich-text/detection.ts +0 -146
  50. package/src/rich-text/rich-text.ts +0 -418
  51. package/src/rich-text/sanitization.ts +0 -42
  52. package/src/rich-text/unicode.ts +0 -47
  53. package/src/rich-text/util.ts +0 -15
  54. package/src/session-manager.ts +0 -5
  55. package/src/types.ts +0 -165
  56. package/src/util.ts +0 -96
  57. package/tests/atp-agent.test.ts +0 -3863
  58. package/tests/dispatcher.test.ts +0 -527
  59. package/tests/errors.test.ts +0 -29
  60. package/tests/moderation-behaviors.test.ts +0 -951
  61. package/tests/moderation-custom-labels.test.ts +0 -334
  62. package/tests/moderation-mutewords.test.ts +0 -1299
  63. package/tests/moderation-prefs.test.ts +0 -402
  64. package/tests/moderation-quoteposts.test.ts +0 -277
  65. package/tests/moderation.test.ts +0 -739
  66. package/tests/rich-text-detection.test.ts +0 -456
  67. package/tests/rich-text-sanitization.test.ts +0 -216
  68. package/tests/rich-text.test.ts +0 -705
  69. package/tests/util/echo-server.ts +0 -37
  70. package/tests/util/moderation-behavior.ts +0 -268
  71. package/tsconfig.build.json +0 -9
  72. package/tsconfig.build.tsbuildinfo +0 -1
  73. package/tsconfig.json +0 -7
  74. package/tsconfig.tests.json +0 -10
@@ -1,456 +0,0 @@
1
- import {
2
- isLink,
3
- isMention,
4
- isTag,
5
- } from '../src/client/types/app/bsky/richtext/facet.js'
6
- import { AtpAgent, RichText, RichTextSegment } from '../src/index.js'
7
-
8
- describe('detectFacets', () => {
9
- const agent = new AtpAgent({ service: 'http://localhost' })
10
-
11
- // Mock handle resolution
12
- agent.com.atproto.identity.resolveHandle = async (params) => ({
13
- success: true,
14
- headers: {},
15
- data: { did: `did:fake:${params?.handle}` },
16
- })
17
-
18
- const inputs = [
19
- 'no mention',
20
- '@handle.com middle end',
21
- 'start @handle.com end',
22
- 'start middle @handle.com',
23
- '@handle.com @handle.com @handle.com',
24
- '@full123-chars.test',
25
- 'not@right',
26
- '@handle.com!@#$chars',
27
- '@handle.com\n@handle.com',
28
- 'parenthetical (@handle.com)',
29
- 'πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ @handle.com πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§',
30
-
31
- 'start https://middle.com end',
32
- 'start https://middle.com/foo/bar end',
33
- 'start https://middle.com/foo/bar?baz=bux end',
34
- 'start https://middle.com/foo/bar?baz=bux#hash end',
35
- 'https://start.com/foo/bar?baz=bux#hash middle end',
36
- 'start middle https://end.com/foo/bar?baz=bux#hash',
37
- 'https://newline1.com\nhttps://newline2.com',
38
- 'πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ https://middle.com πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§',
39
-
40
- 'start middle.com end',
41
- 'start middle.com/foo/bar end',
42
- 'start middle.com/foo/bar?baz=bux end',
43
- 'start middle.com/foo/bar?baz=bux#hash end',
44
- 'start.com/foo/bar?baz=bux#hash middle end',
45
- 'start middle end.com/foo/bar?baz=bux#hash',
46
- 'newline1.com\nnewline2.com',
47
- 'a example.com/index.php php link',
48
- 'a trailing bsky.app: colon',
49
-
50
- 'not.. a..url ..here',
51
- 'e.g.',
52
- 'something-cool.jpg',
53
- 'website.com.jpg',
54
- 'e.g./foo',
55
- 'website.com.jpg/foo',
56
-
57
- 'Classic article https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/',
58
- 'Classic article https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/ ',
59
- 'https://foo.com https://bar.com/whatever https://baz.com',
60
- 'punctuation https://foo.com, https://bar.com/whatever; https://baz.com.',
61
- 'parenthentical (https://foo.com)',
62
- 'except for https://foo.com/thing_(cool)',
63
- ]
64
- const outputs: string[][][] = [
65
- [['no mention']],
66
- [['@handle.com', 'did:fake:handle.com'], [' middle end']],
67
- [['start '], ['@handle.com', 'did:fake:handle.com'], [' end']],
68
- [['start middle '], ['@handle.com', 'did:fake:handle.com']],
69
- [
70
- ['@handle.com', 'did:fake:handle.com'],
71
- [' '],
72
- ['@handle.com', 'did:fake:handle.com'],
73
- [' '],
74
- ['@handle.com', 'did:fake:handle.com'],
75
- ],
76
- [['@full123-chars.test', 'did:fake:full123-chars.test']],
77
- [['not@right']],
78
- [['@handle.com', 'did:fake:handle.com'], ['!@#$chars']],
79
- [
80
- ['@handle.com', 'did:fake:handle.com'],
81
- ['\n'],
82
- ['@handle.com', 'did:fake:handle.com'],
83
- ],
84
- [['parenthetical ('], ['@handle.com', 'did:fake:handle.com'], [')']],
85
- [['πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ '], ['@handle.com', 'did:fake:handle.com'], [' πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§']],
86
-
87
- [['start '], ['https://middle.com', 'https://middle.com'], [' end']],
88
- [
89
- ['start '],
90
- ['https://middle.com/foo/bar', 'https://middle.com/foo/bar'],
91
- [' end'],
92
- ],
93
- [
94
- ['start '],
95
- [
96
- 'https://middle.com/foo/bar?baz=bux',
97
- 'https://middle.com/foo/bar?baz=bux',
98
- ],
99
- [' end'],
100
- ],
101
- [
102
- ['start '],
103
- [
104
- 'https://middle.com/foo/bar?baz=bux#hash',
105
- 'https://middle.com/foo/bar?baz=bux#hash',
106
- ],
107
- [' end'],
108
- ],
109
- [
110
- [
111
- 'https://start.com/foo/bar?baz=bux#hash',
112
- 'https://start.com/foo/bar?baz=bux#hash',
113
- ],
114
- [' middle end'],
115
- ],
116
- [
117
- ['start middle '],
118
- [
119
- 'https://end.com/foo/bar?baz=bux#hash',
120
- 'https://end.com/foo/bar?baz=bux#hash',
121
- ],
122
- ],
123
- [
124
- ['https://newline1.com', 'https://newline1.com'],
125
- ['\n'],
126
- ['https://newline2.com', 'https://newline2.com'],
127
- ],
128
- [['πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ '], ['https://middle.com', 'https://middle.com'], [' πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§']],
129
-
130
- [['start '], ['middle.com', 'https://middle.com'], [' end']],
131
- [
132
- ['start '],
133
- ['middle.com/foo/bar', 'https://middle.com/foo/bar'],
134
- [' end'],
135
- ],
136
- [
137
- ['start '],
138
- ['middle.com/foo/bar?baz=bux', 'https://middle.com/foo/bar?baz=bux'],
139
- [' end'],
140
- ],
141
- [
142
- ['start '],
143
- [
144
- 'middle.com/foo/bar?baz=bux#hash',
145
- 'https://middle.com/foo/bar?baz=bux#hash',
146
- ],
147
- [' end'],
148
- ],
149
- [
150
- [
151
- 'start.com/foo/bar?baz=bux#hash',
152
- 'https://start.com/foo/bar?baz=bux#hash',
153
- ],
154
- [' middle end'],
155
- ],
156
- [
157
- ['start middle '],
158
- ['end.com/foo/bar?baz=bux#hash', 'https://end.com/foo/bar?baz=bux#hash'],
159
- ],
160
- [
161
- ['newline1.com', 'https://newline1.com'],
162
- ['\n'],
163
- ['newline2.com', 'https://newline2.com'],
164
- ],
165
- [
166
- ['a '],
167
- ['example.com/index.php', 'https://example.com/index.php'],
168
- [' php link'],
169
- ],
170
- [['a trailing '], ['bsky.app', 'https://bsky.app'], [': colon']],
171
-
172
- [['not.. a..url ..here']],
173
- [['e.g.']],
174
- [['something-cool.jpg']],
175
- [['website.com.jpg']],
176
- [['e.g./foo']],
177
- [['website.com.jpg/foo']],
178
-
179
- [
180
- ['Classic article '],
181
- [
182
- 'https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/',
183
- 'https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/',
184
- ],
185
- ],
186
- [
187
- ['Classic article '],
188
- [
189
- 'https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/',
190
- 'https://socket3.wordpress.com/2018/02/03/designing-windows-95s-user-interface/',
191
- ],
192
- [' '],
193
- ],
194
- [
195
- ['https://foo.com', 'https://foo.com'],
196
- [' '],
197
- ['https://bar.com/whatever', 'https://bar.com/whatever'],
198
- [' '],
199
- ['https://baz.com', 'https://baz.com'],
200
- ],
201
- [
202
- ['punctuation '],
203
- ['https://foo.com', 'https://foo.com'],
204
- [', '],
205
- ['https://bar.com/whatever', 'https://bar.com/whatever'],
206
- ['; '],
207
- ['https://baz.com', 'https://baz.com'],
208
- ['.'],
209
- ],
210
- [['parenthentical ('], ['https://foo.com', 'https://foo.com'], [')']],
211
- [
212
- ['except for '],
213
- ['https://foo.com/thing_(cool)', 'https://foo.com/thing_(cool)'],
214
- ],
215
- ]
216
- it('correctly handles a set of text inputs', async () => {
217
- for (let i = 0; i < inputs.length; i++) {
218
- const input = inputs[i]
219
- const rt = new RichText({ text: input })
220
- await rt.detectFacets(agent)
221
- expect(Array.from(rt.segments(), segmentToOutput)).toEqual(outputs[i])
222
- }
223
- })
224
-
225
- describe('correctly detects tags inline', () => {
226
- const inputs: [
227
- string,
228
- string[],
229
- { byteStart: number; byteEnd: number }[],
230
- ][] = [
231
- ['#a', ['a'], [{ byteStart: 0, byteEnd: 2 }]],
232
- [
233
- '#a #b',
234
- ['a', 'b'],
235
- [
236
- { byteStart: 0, byteEnd: 2 },
237
- { byteStart: 3, byteEnd: 5 },
238
- ],
239
- ],
240
- ['#1', [], []],
241
- ['#1a', ['1a'], [{ byteStart: 0, byteEnd: 3 }]],
242
- ['#tag', ['tag'], [{ byteStart: 0, byteEnd: 4 }]],
243
- ['body #tag', ['tag'], [{ byteStart: 5, byteEnd: 9 }]],
244
- ['#tag body', ['tag'], [{ byteStart: 0, byteEnd: 4 }]],
245
- ['body #tag body', ['tag'], [{ byteStart: 5, byteEnd: 9 }]],
246
- ['body #1', [], []],
247
- ['body #1a', ['1a'], [{ byteStart: 5, byteEnd: 8 }]],
248
- ['body #a1', ['a1'], [{ byteStart: 5, byteEnd: 8 }]],
249
- ['#', [], []],
250
- ['#?', [], []],
251
- ['text #', [], []],
252
- ['text # text', [], []],
253
- [
254
- 'body #thisisa64characterstring_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
255
- ['thisisa64characterstring_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'],
256
- [{ byteStart: 5, byteEnd: 70 }],
257
- ],
258
- [
259
- 'body #thisisa65characterstring_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab',
260
- [],
261
- [],
262
- ],
263
- [
264
- 'body #thisisa64characterstring_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!',
265
- ['thisisa64characterstring_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'],
266
- [{ byteStart: 5, byteEnd: 70 }],
267
- ],
268
- [
269
- 'its a #double#rainbow',
270
- ['double#rainbow'],
271
- [{ byteStart: 6, byteEnd: 21 }],
272
- ],
273
- ['##hashash', ['#hashash'], [{ byteStart: 0, byteEnd: 9 }]],
274
- ['##', [], []],
275
- ['some #n0n3s@n5e!', ['n0n3s@n5e'], [{ byteStart: 5, byteEnd: 15 }]],
276
- [
277
- 'works #with,punctuation',
278
- ['with,punctuation'],
279
- [{ byteStart: 6, byteEnd: 23 }],
280
- ],
281
- [
282
- 'strips trailing #punctuation, #like. #this!',
283
- ['punctuation', 'like', 'this'],
284
- [
285
- { byteStart: 16, byteEnd: 28 },
286
- { byteStart: 30, byteEnd: 35 },
287
- { byteStart: 37, byteEnd: 42 },
288
- ],
289
- ],
290
- [
291
- 'strips #multi_trailing___...',
292
- ['multi_trailing'],
293
- [{ byteStart: 7, byteEnd: 22 }],
294
- ],
295
- [
296
- 'works with #πŸ¦‹ emoji, and #butterπŸ¦‹fly',
297
- ['πŸ¦‹', 'butterπŸ¦‹fly'],
298
- [
299
- { byteStart: 11, byteEnd: 16 },
300
- { byteStart: 28, byteEnd: 42 },
301
- ],
302
- ],
303
- [
304
- '#same #same #but #diff',
305
- ['same', 'same', 'but', 'diff'],
306
- [
307
- { byteStart: 0, byteEnd: 5 },
308
- { byteStart: 6, byteEnd: 11 },
309
- { byteStart: 12, byteEnd: 16 },
310
- { byteStart: 17, byteEnd: 22 },
311
- ],
312
- ],
313
- ['this #️⃣tag should not be a tag', [], []],
314
- [
315
- 'this ##️⃣tag should be a tag',
316
- ['#️⃣tag'],
317
- [
318
- {
319
- byteStart: 5,
320
- byteEnd: 16,
321
- },
322
- ],
323
- ],
324
- [
325
- 'this #t\nag should be a tag',
326
- ['t'],
327
- [
328
- {
329
- byteStart: 5,
330
- byteEnd: 7,
331
- },
332
- ],
333
- ],
334
- ['no match (\\u200B): #​', [], []],
335
- ['no match (\\u200Ba): #​a', [], []],
336
- ['match (a\\u200Bb): #a​b', ['a'], [{ byteStart: 18, byteEnd: 20 }]],
337
- ['match (ab\\u200B): #ab​', ['ab'], [{ byteStart: 18, byteEnd: 21 }]],
338
- ['no match (\\u20e2tag): #βƒ’tag', [], []],
339
- ['no match (a\\u20e2b): #a⃒b', ['a'], [{ byteStart: 21, byteEnd: 23 }]],
340
- [
341
- 'match full width number sign (tag): οΌƒtag',
342
- ['tag'],
343
- [{ byteStart: 36, byteEnd: 42 }],
344
- ],
345
- [
346
- 'match full width number sign (tag): οΌƒ#️⃣tag',
347
- ['#️⃣tag'],
348
- [{ byteStart: 36, byteEnd: 49 }],
349
- ],
350
- ['no match 1?: #1?', [], []],
351
- ]
352
-
353
- it.each(inputs)('%s', async (input, tags, indices) => {
354
- const rt = new RichText({ text: input })
355
- await rt.detectFacets(agent)
356
-
357
- const detectedTags: string[] = []
358
- const detectedIndices: { byteStart: number; byteEnd: number }[] = []
359
-
360
- for (const { facet } of rt.segments()) {
361
- if (!facet) continue
362
- for (const feature of facet.features) {
363
- if (isTag(feature)) {
364
- detectedTags.push(feature.tag)
365
- }
366
- }
367
- detectedIndices.push(facet.index)
368
- }
369
-
370
- expect(detectedTags).toEqual(tags)
371
- expect(detectedIndices).toEqual(indices)
372
- })
373
- })
374
-
375
- describe('correctly detects cashtags inline', () => {
376
- const inputs: [
377
- string,
378
- string[],
379
- { byteStart: number; byteEnd: number }[],
380
- ][] = [
381
- ['$AAPL', ['$AAPL'], [{ byteStart: 0, byteEnd: 5 }]],
382
- ['$aapl', ['$AAPL'], [{ byteStart: 0, byteEnd: 5 }]], // normalized to uppercase
383
- ['$A', ['$A'], [{ byteStart: 0, byteEnd: 2 }]],
384
- ['$a', ['$A'], [{ byteStart: 0, byteEnd: 2 }]], // single char normalized
385
- [
386
- '$BTC $ETH',
387
- ['$BTC', '$ETH'],
388
- [
389
- { byteStart: 0, byteEnd: 4 },
390
- { byteStart: 5, byteEnd: 9 },
391
- ],
392
- ],
393
- ['$100', [], []], // starts with digit - not a cashtag
394
- ['$GOOGL', ['$GOOGL'], [{ byteStart: 0, byteEnd: 6 }]], // 5 chars - max length
395
- ['$TOOLONG', [], []], // >5 chars
396
- ['check $LEGO now', ['$LEGO'], [{ byteStart: 6, byteEnd: 11 }]],
397
- ['($GOOG)', ['$GOOG'], [{ byteStart: 1, byteEnd: 6 }]],
398
- ['$AAPL.', ['$AAPL'], [{ byteStart: 0, byteEnd: 5 }]], // trailing punctuation
399
- [
400
- '$AAPL, $MSFT!',
401
- ['$AAPL', '$MSFT'],
402
- [
403
- { byteStart: 0, byteEnd: 5 },
404
- { byteStart: 7, byteEnd: 12 },
405
- ],
406
- ],
407
- ['no$SPACE', [], []], // must have leading space or start
408
- ['$', [], []], // just dollar sign
409
- ['$ AAPL', [], []], // space after $
410
- ['$123ABC', [], []], // starts with digit
411
- ['$ABC12', ['$ABC12'], [{ byteStart: 0, byteEnd: 6 }]], // digits after letters OK (5 chars)
412
- ['$ABC123', [], []], // 6 chars - too long
413
- ]
414
-
415
- it.each(inputs)('%s', (input, tags, indices) => {
416
- const rt = new RichText({ text: input })
417
- rt.detectFacetsWithoutResolution()
418
-
419
- const detectedTags: string[] = []
420
- const detectedIndices: { byteStart: number; byteEnd: number }[] = []
421
-
422
- for (const { facet } of rt.segments()) {
423
- if (!facet) continue
424
- for (const feature of facet.features) {
425
- if (isTag(feature) && feature.tag.startsWith('$')) {
426
- detectedTags.push(feature.tag)
427
- }
428
- }
429
- if (
430
- facet.features.some(
431
- (f) => isTag(f) && (f as any).tag?.startsWith('$'),
432
- )
433
- ) {
434
- detectedIndices.push(facet.index)
435
- }
436
- }
437
-
438
- expect(detectedTags).toEqual(tags)
439
- expect(detectedIndices).toEqual(indices)
440
- })
441
- })
442
- })
443
-
444
- function segmentToOutput(segment: RichTextSegment): string[] {
445
- if (segment.facet) {
446
- return [
447
- segment.text,
448
- segment.facet?.features.map((f) => {
449
- if (isMention(f)) return f.did
450
- if (isLink(f)) return f.uri
451
- return undefined
452
- })?.[0] || '',
453
- ]
454
- }
455
- return [segment.text]
456
- }
@@ -1,216 +0,0 @@
1
- import {
2
- Facet,
3
- RichText,
4
- UnicodeString,
5
- sanitizeRichText,
6
- } from '../src/index.js'
7
-
8
- describe('sanitizeRichText: cleanNewlines', () => {
9
- it('removes more than two consecutive new lines', () => {
10
- const input = new RichText({
11
- text: 'test\n\n\n\n\ntest\n\n\n\n\n\n\ntest\n\n\n\n\n\n\ntest\n\n\n\n\n\n\ntest',
12
- })
13
- const output = sanitizeRichText(input, { cleanNewlines: true })
14
- expect(String(output.unicodeText)).toEqual(
15
- 'test\n\ntest\n\ntest\n\ntest\n\ntest',
16
- )
17
- })
18
-
19
- it('removes more than two consecutive new lines w/fat unicode', () => {
20
- const input = new RichText({
21
- text: 'testπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§\n\n\n\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\n\n\n\n\n\ntestπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§\n\n\n\n\n\n\ntest\n\n\n\n\n\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test',
22
- })
23
- const output = sanitizeRichText(input, { cleanNewlines: true })
24
- expect(String(output.unicodeText)).toEqual(
25
- 'testπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\ntestπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§\n\ntest\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test',
26
- )
27
- })
28
-
29
- it('removes more than two consecutive new lines with spaces', () => {
30
- const input = new RichText({
31
- text: 'test\n\n\n\n\ntest\n \n \n \n \n\n\ntest\n\n\n\n\n\n\ntest\n\n\n\n\n \n\ntest',
32
- })
33
- const output = sanitizeRichText(input, { cleanNewlines: true })
34
- expect(String(output.unicodeText)).toEqual(
35
- 'test\n\ntest\n\ntest\n\ntest\n\ntest',
36
- )
37
- })
38
-
39
- it('returns original string if there are no consecutive new lines', () => {
40
- const input = new RichText({ text: 'test\n\ntest\n\ntest\n\ntest\n\ntest' })
41
- const output = sanitizeRichText(input, { cleanNewlines: true })
42
- expect(String(output.unicodeText)).toEqual(String(input.unicodeText))
43
- })
44
-
45
- it('returns original string if there are no new lines', () => {
46
- const input = new RichText({ text: 'test test test test test' })
47
- const output = sanitizeRichText(input, { cleanNewlines: true })
48
- expect(String(output.unicodeText)).toEqual(String(input.unicodeText))
49
- })
50
-
51
- it('returns empty string if input is empty', () => {
52
- const input = new RichText({ text: '' })
53
- const output = sanitizeRichText(input, { cleanNewlines: true })
54
- expect(String(output.unicodeText)).toEqual('')
55
- })
56
-
57
- it('works with different types of new line characters', () => {
58
- const input = new RichText({
59
- text: 'test\r\ntest\n\rtest\rtest\n\n\n\ntest\n\r \n \n \n \n\n\ntest',
60
- })
61
- const output = sanitizeRichText(input, { cleanNewlines: true })
62
- expect(String(output.unicodeText)).toEqual(
63
- 'test\r\ntest\n\rtest\rtest\n\ntest\n\ntest',
64
- )
65
- })
66
-
67
- it('removes more than two consecutive new lines with zero width space', () => {
68
- const input = new RichText({
69
- text: 'test\n\n\n\n\ntest\n\u200B\u200B\n\n\n\ntest\n \u200B\u200B \n\n\n\ntest\n\n\n\n\n\n\ntest',
70
- })
71
- const output = sanitizeRichText(input, { cleanNewlines: true })
72
- expect(String(output.unicodeText)).toEqual(
73
- 'test\n\ntest\n\ntest\n\ntest\n\ntest',
74
- )
75
- })
76
-
77
- it('removes more than two consecutive new lines with zero width non-joiner', () => {
78
- const input = new RichText({
79
- text: 'test\n\n\n\n\ntest\n\u200C\u200C\n\n\n\ntest\n \u200C\u200C \n\n\n\ntest\n\n\n\n\n\n\ntest',
80
- })
81
- const output = sanitizeRichText(input, { cleanNewlines: true })
82
- expect(String(output.unicodeText)).toEqual(
83
- 'test\n\ntest\n\ntest\n\ntest\n\ntest',
84
- )
85
- })
86
-
87
- it('removes more than two consecutive new lines with zero width joiner', () => {
88
- const input = new RichText({
89
- text: 'test\n\n\n\n\ntest\n\u200D\u200D\n\n\n\ntest\n \u200D\u200D \n\n\n\ntest\n\n\n\n\n\n\ntest',
90
- })
91
- const output = sanitizeRichText(input, { cleanNewlines: true })
92
- expect(String(output.unicodeText)).toEqual(
93
- 'test\n\ntest\n\ntest\n\ntest\n\ntest',
94
- )
95
- })
96
-
97
- it('removes more than two consecutive new lines with soft hyphen', () => {
98
- const input = new RichText({
99
- text: 'test\n\n\n\n\ntest\n\u00AD\u00AD\n\n\n\ntest\n \u00AD\u00AD \n\n\n\ntest\n\n\n\n\n\n\ntest',
100
- })
101
- const output = sanitizeRichText(input, { cleanNewlines: true })
102
- expect(String(output.unicodeText)).toEqual(
103
- 'test\n\ntest\n\ntest\n\ntest\n\ntest',
104
- )
105
- })
106
-
107
- it('removes more than two consecutive new lines with word joiner', () => {
108
- const input = new RichText({
109
- text: 'test\n\n\n\n\ntest\n\u2060\u2060\n\n\n\ntest\n \u2060\u2060 \n\n\n\ntest\n\n\n\n\n\n\ntest',
110
- })
111
- const output = sanitizeRichText(input, { cleanNewlines: true })
112
- expect(String(output.unicodeText)).toEqual(
113
- 'test\n\ntest\n\ntest\n\ntest\n\ntest',
114
- )
115
- })
116
- })
117
-
118
- describe('sanitizeRichText w/facets: cleanNewlines', () => {
119
- it('preserves entities as expected', () => {
120
- const input = new RichText({
121
- text: 'test\n\n\n\n\ntest\n\n\n\n\n\n\ntest\n\n\n\n\n\n\ntest\n\n\n\n\n\n\ntest',
122
- facets: [
123
- { index: { byteStart: 0, byteEnd: 13 }, features: [{ $type: '' }] },
124
- { index: { byteStart: 13, byteEnd: 24 }, features: [{ $type: '' }] },
125
- { index: { byteStart: 9, byteEnd: 15 }, features: [{ $type: '' }] },
126
- { index: { byteStart: 4, byteEnd: 9 }, features: [{ $type: '' }] },
127
- ],
128
- })
129
- const output = sanitizeRichText(input, { cleanNewlines: true })
130
- expect(facetToStr(String(input.unicodeText), input.facets?.[0])).toEqual(
131
- 'test\n\n\n\n\ntest',
132
- )
133
- expect(facetToStr(String(input.unicodeText), input.facets?.[1])).toEqual(
134
- '\n\n\n\n\n',
135
- )
136
- expect(facetToStr(String(input.unicodeText), input.facets?.[2])).toEqual(
137
- 'test\n\n',
138
- )
139
- expect(facetToStr(String(input.unicodeText), input.facets?.[3])).toEqual(
140
- '\n\n\n\n\n\n\ntest',
141
- )
142
- expect(String(output.unicodeText)).toEqual(
143
- 'test\n\ntest\n\ntest\n\ntest\n\ntest',
144
- )
145
- expect(facetToStr(String(output.unicodeText), output.facets?.[0])).toEqual(
146
- 'test\n\ntest',
147
- )
148
- expect(facetToStr(String(output.unicodeText), output.facets?.[1])).toEqual(
149
- 'test',
150
- )
151
- expect(facetToStr(String(output.unicodeText), output.facets?.[2])).toEqual(
152
- 'test',
153
- )
154
- expect(output.facets?.[3]).toEqual(undefined)
155
- })
156
-
157
- it('preserves entities as expected w/fat unicode', () => {
158
- const str = new UnicodeString(
159
- 'πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\n\n\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\n\n\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\n\n\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\n\n\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\n\n\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\n\n\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\n\n\n\n',
160
- )
161
- let lastI = 0
162
- const makeFacet = (match: string) => {
163
- const i = str.utf16.indexOf(match, lastI)
164
- lastI = i + match.length
165
- const byteStart = str.utf16IndexToUtf8Index(i)
166
- const byteEnd = byteStart + new UnicodeString(match).length
167
- return {
168
- index: { byteStart, byteEnd },
169
- features: [{ $type: '' }],
170
- }
171
- }
172
-
173
- const input = new RichText({
174
- text: str.utf16,
175
- facets: [
176
- makeFacet('πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\n\n\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test'),
177
- makeFacet('\n\n\n\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test'),
178
- makeFacet('πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\n'),
179
- makeFacet('\n\n'),
180
- ],
181
- })
182
- const output = sanitizeRichText(input, { cleanNewlines: true })
183
- expect(facetToStr(String(input.unicodeText), input.facets?.[0])).toEqual(
184
- 'πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\n\n\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test',
185
- )
186
- expect(facetToStr(String(input.unicodeText), input.facets?.[1])).toEqual(
187
- '\n\n\n\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test',
188
- )
189
- expect(facetToStr(String(input.unicodeText), input.facets?.[2])).toEqual(
190
- 'πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\n',
191
- )
192
- expect(facetToStr(String(input.unicodeText), input.facets?.[3])).toEqual(
193
- '\n\n',
194
- )
195
- expect(String(output.unicodeText)).toEqual(
196
- 'πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\n',
197
- )
198
- expect(facetToStr(String(output.unicodeText), output.facets?.[0])).toEqual(
199
- 'πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test\n\nπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test',
200
- )
201
- expect(facetToStr(String(output.unicodeText), output.facets?.[1])).toEqual(
202
- 'πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test',
203
- )
204
- expect(facetToStr(String(output.unicodeText), output.facets?.[2])).toEqual(
205
- 'πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test',
206
- )
207
- expect(output.facets?.[3]).toEqual(undefined)
208
- })
209
- })
210
-
211
- function facetToStr(str: string, ent?: Facet) {
212
- if (!ent) {
213
- return ''
214
- }
215
- return new UnicodeString(str).slice(ent.index.byteStart, ent.index.byteEnd)
216
- }