@atproto/api 0.20.22 β†’ 0.20.25

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 (64) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/client/lexicons.d.ts +28 -12
  3. package/dist/client/lexicons.d.ts.map +1 -1
  4. package/dist/client/lexicons.js +14 -6
  5. package/dist/client/lexicons.js.map +1 -1
  6. package/dist/client/types/app/bsky/ageassurance/defs.d.ts +2 -0
  7. package/dist/client/types/app/bsky/ageassurance/defs.d.ts.map +1 -1
  8. package/dist/client/types/app/bsky/ageassurance/defs.js.map +1 -1
  9. package/package.json +22 -16
  10. package/definitions/labels.json +0 -170
  11. package/docs/moderation.md +0 -260
  12. package/jest.config.cjs +0 -23
  13. package/jest.d.ts +0 -20
  14. package/jest.setup.ts +0 -97
  15. package/scripts/code/labels.mjs +0 -77
  16. package/scripts/generate-code.mjs +0 -4
  17. package/src/age-assurance.test.ts +0 -218
  18. package/src/age-assurance.ts +0 -137
  19. package/src/agent.ts +0 -1645
  20. package/src/atp-agent.ts +0 -570
  21. package/src/bsky-agent.ts +0 -14
  22. package/src/const.ts +0 -1
  23. package/src/index.ts +0 -44
  24. package/src/mocker.ts +0 -220
  25. package/src/moderation/decision.ts +0 -389
  26. package/src/moderation/index.ts +0 -69
  27. package/src/moderation/mutewords.ts +0 -170
  28. package/src/moderation/subjects/account.ts +0 -44
  29. package/src/moderation/subjects/feed-generator.ts +0 -24
  30. package/src/moderation/subjects/notification.ts +0 -25
  31. package/src/moderation/subjects/post.ts +0 -433
  32. package/src/moderation/subjects/profile.ts +0 -26
  33. package/src/moderation/subjects/status.ts +0 -27
  34. package/src/moderation/subjects/user-list.ts +0 -48
  35. package/src/moderation/types.ts +0 -191
  36. package/src/moderation/ui.ts +0 -21
  37. package/src/moderation/util.ts +0 -111
  38. package/src/predicate.ts +0 -52
  39. package/src/rich-text/detection.ts +0 -146
  40. package/src/rich-text/rich-text.ts +0 -418
  41. package/src/rich-text/sanitization.ts +0 -42
  42. package/src/rich-text/unicode.ts +0 -47
  43. package/src/rich-text/util.ts +0 -15
  44. package/src/session-manager.ts +0 -5
  45. package/src/types.ts +0 -165
  46. package/src/util.ts +0 -96
  47. package/tests/atp-agent.test.ts +0 -3863
  48. package/tests/dispatcher.test.ts +0 -527
  49. package/tests/errors.test.ts +0 -29
  50. package/tests/moderation-behaviors.test.ts +0 -951
  51. package/tests/moderation-custom-labels.test.ts +0 -334
  52. package/tests/moderation-mutewords.test.ts +0 -1299
  53. package/tests/moderation-prefs.test.ts +0 -402
  54. package/tests/moderation-quoteposts.test.ts +0 -277
  55. package/tests/moderation.test.ts +0 -739
  56. package/tests/rich-text-detection.test.ts +0 -456
  57. package/tests/rich-text-sanitization.test.ts +0 -216
  58. package/tests/rich-text.test.ts +0 -705
  59. package/tests/util/echo-server.ts +0 -37
  60. package/tests/util/moderation-behavior.ts +0 -268
  61. package/tsconfig.build.json +0 -9
  62. package/tsconfig.build.tsbuildinfo +0 -1
  63. package/tsconfig.json +0 -7
  64. package/tsconfig.tests.json +0 -10
@@ -1,705 +0,0 @@
1
- import { RichText } from '../src/index.js'
2
-
3
- describe('RichText', () => {
4
- it('converts entities to facets correctly', () => {
5
- const rt = new RichText({
6
- text: 'test',
7
- entities: [
8
- {
9
- index: { start: 0, end: 1 },
10
- type: 'link',
11
- value: 'https://example.com',
12
- },
13
- {
14
- index: { start: 1, end: 2 },
15
- type: 'mention',
16
- value: 'did:plc:1234',
17
- },
18
- {
19
- index: { start: 2, end: 3 },
20
- type: 'other',
21
- value: 'willbedropped',
22
- },
23
- ],
24
- })
25
- expect(rt.facets).toEqual([
26
- {
27
- $type: 'app.bsky.richtext.facet',
28
- index: { byteStart: 0, byteEnd: 1 },
29
- features: [
30
- {
31
- $type: 'app.bsky.richtext.facet#link',
32
- uri: 'https://example.com',
33
- },
34
- ],
35
- },
36
- {
37
- $type: 'app.bsky.richtext.facet',
38
- index: { byteStart: 1, byteEnd: 2 },
39
- features: [
40
- {
41
- $type: 'app.bsky.richtext.facet#mention',
42
- did: 'did:plc:1234',
43
- },
44
- ],
45
- },
46
- ])
47
- })
48
-
49
- it('converts entity utf16 indices to facet utf8 indices', () => {
50
- const rt = new RichText({
51
- text: 'πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§',
52
- entities: [
53
- {
54
- index: { start: 0, end: 11 },
55
- type: 'link',
56
- value: 'https://example.com',
57
- },
58
- {
59
- index: { start: 11, end: 22 },
60
- type: 'mention',
61
- value: 'did:plc:1234',
62
- },
63
- {
64
- index: { start: 22, end: 33 },
65
- type: 'other',
66
- value: 'willbedropped',
67
- },
68
- ],
69
- })
70
- expect(rt.facets).toEqual([
71
- {
72
- $type: 'app.bsky.richtext.facet',
73
- index: { byteStart: 0, byteEnd: 25 },
74
- features: [
75
- {
76
- $type: 'app.bsky.richtext.facet#link',
77
- uri: 'https://example.com',
78
- },
79
- ],
80
- },
81
- {
82
- $type: 'app.bsky.richtext.facet',
83
- index: { byteStart: 25, byteEnd: 50 },
84
- features: [
85
- {
86
- $type: 'app.bsky.richtext.facet#mention',
87
- did: 'did:plc:1234',
88
- },
89
- ],
90
- },
91
- ])
92
- })
93
-
94
- it('calculates bytelength and grapheme length correctly', () => {
95
- {
96
- const rt = new RichText({ text: 'Hello!' })
97
- expect(rt.length).toBe(6)
98
- expect(rt.graphemeLength).toBe(6)
99
- }
100
- {
101
- const rt = new RichText({ text: 'πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§' })
102
- expect(rt.length).toBe(25)
103
- expect(rt.graphemeLength).toBe(1)
104
- }
105
- {
106
- const rt = new RichText({ text: 'πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§πŸ”₯ good!βœ…' })
107
- expect(rt.length).toBe(38)
108
- expect(rt.graphemeLength).toBe(9)
109
- }
110
- })
111
- })
112
-
113
- describe('RichText#insert', () => {
114
- const input = new RichText({
115
- text: 'hello world',
116
- facets: [
117
- { index: { byteStart: 2, byteEnd: 7 }, features: [{ $type: '' }] },
118
- ],
119
- })
120
-
121
- it('correctly adjusts facets (scenario A - before)', () => {
122
- const output = input.clone().insert(0, 'test')
123
- expect(output.text).toEqual('testhello world')
124
- expect(output.facets?.[0].index.byteStart).toEqual(6)
125
- expect(output.facets?.[0].index.byteEnd).toEqual(11)
126
- expect(
127
- output.unicodeText.slice(
128
- output.facets?.[0].index.byteStart,
129
- output.facets?.[0].index.byteEnd,
130
- ),
131
- ).toEqual('llo w')
132
- })
133
-
134
- it('correctly adjusts facets (scenario B - inner)', () => {
135
- const output = input.clone().insert(4, 'test')
136
- expect(output.text).toEqual('helltesto world')
137
- expect(output.facets?.[0].index.byteStart).toEqual(2)
138
- expect(output.facets?.[0].index.byteEnd).toEqual(11)
139
- expect(
140
- output.unicodeText.slice(
141
- output.facets?.[0].index.byteStart,
142
- output.facets?.[0].index.byteEnd,
143
- ),
144
- ).toEqual('lltesto w')
145
- })
146
-
147
- it('correctly adjusts facets (scenario C - after)', () => {
148
- const output = input.clone().insert(8, 'test')
149
- expect(output.text).toEqual('hello wotestrld')
150
- expect(output.facets?.[0].index.byteStart).toEqual(2)
151
- expect(output.facets?.[0].index.byteEnd).toEqual(7)
152
- expect(
153
- output.unicodeText.slice(
154
- output.facets?.[0].index.byteStart,
155
- output.facets?.[0].index.byteEnd,
156
- ),
157
- ).toEqual('llo w')
158
- })
159
- })
160
-
161
- describe('RichText#insert w/fat unicode', () => {
162
- const input = new RichText({
163
- text: 'oneπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ twoπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ threeπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§',
164
- facets: [
165
- { index: { byteStart: 0, byteEnd: 28 }, features: [{ $type: '' }] },
166
- { index: { byteStart: 29, byteEnd: 57 }, features: [{ $type: '' }] },
167
- { index: { byteStart: 58, byteEnd: 88 }, features: [{ $type: '' }] },
168
- ],
169
- })
170
-
171
- it('correctly adjusts facets (scenario A - before)', () => {
172
- const output = input.clone().insert(0, 'test')
173
- expect(output.text).toEqual('testoneπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ twoπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ threeπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
174
- expect(
175
- output.unicodeText.slice(
176
- output.facets?.[0].index.byteStart,
177
- output.facets?.[0].index.byteEnd,
178
- ),
179
- ).toEqual('oneπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
180
- expect(
181
- output.unicodeText.slice(
182
- output.facets?.[1].index.byteStart,
183
- output.facets?.[1].index.byteEnd,
184
- ),
185
- ).toEqual('twoπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
186
- expect(
187
- output.unicodeText.slice(
188
- output.facets?.[2].index.byteStart,
189
- output.facets?.[2].index.byteEnd,
190
- ),
191
- ).toEqual('threeπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
192
- })
193
-
194
- it('correctly adjusts facets (scenario B - inner)', () => {
195
- const output = input.clone().insert(3, 'test')
196
- expect(output.text).toEqual('onetestπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ twoπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ threeπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
197
- expect(
198
- output.unicodeText.slice(
199
- output.facets?.[0].index.byteStart,
200
- output.facets?.[0].index.byteEnd,
201
- ),
202
- ).toEqual('onetestπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
203
- expect(
204
- output.unicodeText.slice(
205
- output.facets?.[1].index.byteStart,
206
- output.facets?.[1].index.byteEnd,
207
- ),
208
- ).toEqual('twoπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
209
- expect(
210
- output.unicodeText.slice(
211
- output.facets?.[2].index.byteStart,
212
- output.facets?.[2].index.byteEnd,
213
- ),
214
- ).toEqual('threeπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
215
- })
216
-
217
- it('correctly adjusts facets (scenario C - after)', () => {
218
- const output = input.clone().insert(28, 'test')
219
- expect(output.text).toEqual('oneπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§test twoπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ threeπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
220
- expect(
221
- output.unicodeText.slice(
222
- output.facets?.[0].index.byteStart,
223
- output.facets?.[0].index.byteEnd,
224
- ),
225
- ).toEqual('oneπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
226
- expect(
227
- output.unicodeText.slice(
228
- output.facets?.[1].index.byteStart,
229
- output.facets?.[1].index.byteEnd,
230
- ),
231
- ).toEqual('twoπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
232
- expect(
233
- output.unicodeText.slice(
234
- output.facets?.[2].index.byteStart,
235
- output.facets?.[2].index.byteEnd,
236
- ),
237
- ).toEqual('threeπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
238
- })
239
- })
240
-
241
- describe('RichText#delete', () => {
242
- const input = new RichText({
243
- text: 'hello world',
244
- facets: [
245
- { index: { byteStart: 2, byteEnd: 7 }, features: [{ $type: '' }] },
246
- ],
247
- })
248
-
249
- it('correctly adjusts facets (scenario A - entirely outer)', () => {
250
- const output = input.clone().delete(0, 9)
251
- expect(output.text).toEqual('ld')
252
- expect(output.facets?.length).toEqual(0)
253
- })
254
-
255
- it('correctly adjusts facets (scenario B - entirely after)', () => {
256
- const output = input.clone().delete(7, 11)
257
- expect(output.text).toEqual('hello w')
258
- expect(output.facets?.[0].index.byteStart).toEqual(2)
259
- expect(output.facets?.[0].index.byteEnd).toEqual(7)
260
- expect(
261
- output.unicodeText.slice(
262
- output.facets?.[0].index.byteStart,
263
- output.facets?.[0].index.byteEnd,
264
- ),
265
- ).toEqual('llo w')
266
- })
267
-
268
- it('correctly adjusts facets (scenario C - partially after)', () => {
269
- const output = input.clone().delete(4, 11)
270
- expect(output.text).toEqual('hell')
271
- expect(output.facets?.[0].index.byteStart).toEqual(2)
272
- expect(output.facets?.[0].index.byteEnd).toEqual(4)
273
- expect(
274
- output.unicodeText.slice(
275
- output.facets?.[0].index.byteStart,
276
- output.facets?.[0].index.byteEnd,
277
- ),
278
- ).toEqual('ll')
279
- })
280
-
281
- it('correctly adjusts facets (scenario D - entirely inner)', () => {
282
- const output = input.clone().delete(3, 5)
283
- expect(output.text).toEqual('hel world')
284
- expect(output.facets?.[0].index.byteStart).toEqual(2)
285
- expect(output.facets?.[0].index.byteEnd).toEqual(5)
286
- expect(
287
- output.unicodeText.slice(
288
- output.facets?.[0].index.byteStart,
289
- output.facets?.[0].index.byteEnd,
290
- ),
291
- ).toEqual('l w')
292
- })
293
-
294
- it('correctly adjusts facets (scenario E - partially before)', () => {
295
- const output = input.clone().delete(1, 5)
296
- expect(output.text).toEqual('h world')
297
- expect(output.facets?.[0].index.byteStart).toEqual(1)
298
- expect(output.facets?.[0].index.byteEnd).toEqual(3)
299
- expect(
300
- output.unicodeText.slice(
301
- output.facets?.[0].index.byteStart,
302
- output.facets?.[0].index.byteEnd,
303
- ),
304
- ).toEqual(' w')
305
- })
306
-
307
- it('correctly adjusts facets (scenario F - entirely before)', () => {
308
- const output = input.clone().delete(0, 2)
309
- expect(output.text).toEqual('llo world')
310
- expect(output.facets?.[0].index.byteStart).toEqual(0)
311
- expect(output.facets?.[0].index.byteEnd).toEqual(5)
312
- expect(
313
- output.unicodeText.slice(
314
- output.facets?.[0].index.byteStart,
315
- output.facets?.[0].index.byteEnd,
316
- ),
317
- ).toEqual('llo w')
318
- })
319
- })
320
-
321
- describe('RichText#delete w/fat unicode', () => {
322
- const input = new RichText({
323
- text: 'oneπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ twoπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ threeπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§',
324
- facets: [
325
- { index: { byteStart: 29, byteEnd: 57 }, features: [{ $type: '' }] },
326
- ],
327
- })
328
-
329
- it('correctly adjusts facets (scenario A - entirely outer)', () => {
330
- const output = input.clone().delete(28, 58)
331
- expect(output.text).toEqual('oneπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§threeπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
332
- expect(output.facets?.length).toEqual(0)
333
- })
334
-
335
- it('correctly adjusts facets (scenario B - entirely after)', () => {
336
- const output = input.clone().delete(57, 88)
337
- expect(output.text).toEqual('oneπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ twoπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
338
- expect(output.facets?.[0].index.byteStart).toEqual(29)
339
- expect(output.facets?.[0].index.byteEnd).toEqual(57)
340
- expect(
341
- output.unicodeText.slice(
342
- output.facets?.[0].index.byteStart,
343
- output.facets?.[0].index.byteEnd,
344
- ),
345
- ).toEqual('twoπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
346
- })
347
-
348
- it('correctly adjusts facets (scenario C - partially after)', () => {
349
- const output = input.clone().delete(31, 88)
350
- expect(output.text).toEqual('oneπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ tw')
351
- expect(output.facets?.[0].index.byteStart).toEqual(29)
352
- expect(output.facets?.[0].index.byteEnd).toEqual(31)
353
- expect(
354
- output.unicodeText.slice(
355
- output.facets?.[0].index.byteStart,
356
- output.facets?.[0].index.byteEnd,
357
- ),
358
- ).toEqual('tw')
359
- })
360
-
361
- it('correctly adjusts facets (scenario D - entirely inner)', () => {
362
- const output = input.clone().delete(30, 32)
363
- expect(output.text).toEqual('oneπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ tπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ threeπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
364
- expect(output.facets?.[0].index.byteStart).toEqual(29)
365
- expect(output.facets?.[0].index.byteEnd).toEqual(55)
366
- expect(
367
- output.unicodeText.slice(
368
- output.facets?.[0].index.byteStart,
369
- output.facets?.[0].index.byteEnd,
370
- ),
371
- ).toEqual('tπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
372
- })
373
-
374
- it('correctly adjusts facets (scenario E - partially before)', () => {
375
- const output = input.clone().delete(28, 31)
376
- expect(output.text).toEqual('oneπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§oπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ threeπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
377
- expect(output.facets?.[0].index.byteStart).toEqual(28)
378
- expect(output.facets?.[0].index.byteEnd).toEqual(54)
379
- expect(
380
- output.unicodeText.slice(
381
- output.facets?.[0].index.byteStart,
382
- output.facets?.[0].index.byteEnd,
383
- ),
384
- ).toEqual('oπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
385
- })
386
-
387
- it('correctly adjusts facets (scenario F - entirely before)', () => {
388
- const output = input.clone().delete(0, 2)
389
- expect(output.text).toEqual('eπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ twoπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ threeπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
390
- expect(output.facets?.[0].index.byteStart).toEqual(27)
391
- expect(output.facets?.[0].index.byteEnd).toEqual(55)
392
- expect(
393
- output.unicodeText.slice(
394
- output.facets?.[0].index.byteStart,
395
- output.facets?.[0].index.byteEnd,
396
- ),
397
- ).toEqual('twoπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§')
398
- })
399
- })
400
-
401
- describe('RichText#segments', () => {
402
- it('produces an empty output for an empty input', () => {
403
- const input = new RichText({ text: '' })
404
- expect(Array.from(input.segments())).toEqual([{ text: '' }])
405
- })
406
-
407
- it('produces a single segment when no facets are present', () => {
408
- const input = new RichText({ text: 'hello' })
409
- expect(Array.from(input.segments())).toEqual([{ text: 'hello' }])
410
- })
411
-
412
- it('produces 3 segments with 1 entity in the middle', () => {
413
- const input = new RichText({
414
- text: 'one two three',
415
- facets: [
416
- { index: { byteStart: 4, byteEnd: 7 }, features: [{ $type: '' }] },
417
- ],
418
- })
419
- expect(Array.from(input.segments())).toEqual([
420
- { text: 'one ' },
421
- {
422
- text: 'two',
423
- facet: {
424
- index: { byteStart: 4, byteEnd: 7 },
425
- features: [{ $type: '' }],
426
- },
427
- },
428
- { text: ' three' },
429
- ])
430
- })
431
-
432
- it('produces 2 segments with 1 entity in the byteStart', () => {
433
- const input = new RichText({
434
- text: 'one two three',
435
- facets: [
436
- { index: { byteStart: 0, byteEnd: 7 }, features: [{ $type: '' }] },
437
- ],
438
- })
439
- expect(Array.from(input.segments())).toEqual([
440
- {
441
- text: 'one two',
442
- facet: {
443
- index: { byteStart: 0, byteEnd: 7 },
444
- features: [{ $type: '' }],
445
- },
446
- },
447
- { text: ' three' },
448
- ])
449
- })
450
-
451
- it('produces 2 segments with 1 entity in the end', () => {
452
- const input = new RichText({
453
- text: 'one two three',
454
- facets: [
455
- { index: { byteStart: 4, byteEnd: 13 }, features: [{ $type: '' }] },
456
- ],
457
- })
458
- expect(Array.from(input.segments())).toEqual([
459
- { text: 'one ' },
460
- {
461
- text: 'two three',
462
- facet: {
463
- index: { byteStart: 4, byteEnd: 13 },
464
- features: [{ $type: '' }],
465
- },
466
- },
467
- ])
468
- })
469
-
470
- it('produces 1 segments with 1 entity around the entire string', () => {
471
- const input = new RichText({
472
- text: 'one two three',
473
- facets: [
474
- { index: { byteStart: 0, byteEnd: 13 }, features: [{ $type: '' }] },
475
- ],
476
- })
477
- expect(Array.from(input.segments())).toEqual([
478
- {
479
- text: 'one two three',
480
- facet: {
481
- index: { byteStart: 0, byteEnd: 13 },
482
- features: [{ $type: '' }],
483
- },
484
- },
485
- ])
486
- })
487
-
488
- it('produces 5 segments with 3 facets covering each word', () => {
489
- const input = new RichText({
490
- text: 'one two three',
491
- facets: [
492
- { index: { byteStart: 0, byteEnd: 3 }, features: [{ $type: '' }] },
493
- { index: { byteStart: 4, byteEnd: 7 }, features: [{ $type: '' }] },
494
- { index: { byteStart: 8, byteEnd: 13 }, features: [{ $type: '' }] },
495
- ],
496
- })
497
- expect(Array.from(input.segments())).toEqual([
498
- {
499
- text: 'one',
500
- facet: {
501
- index: { byteStart: 0, byteEnd: 3 },
502
- features: [{ $type: '' }],
503
- },
504
- },
505
- { text: ' ' },
506
- {
507
- text: 'two',
508
- facet: {
509
- index: { byteStart: 4, byteEnd: 7 },
510
- features: [{ $type: '' }],
511
- },
512
- },
513
- { text: ' ' },
514
- {
515
- text: 'three',
516
- facet: {
517
- index: { byteStart: 8, byteEnd: 13 },
518
- features: [{ $type: '' }],
519
- },
520
- },
521
- ])
522
- })
523
-
524
- it('uses utf8 indices', () => {
525
- const input = new RichText({
526
- text: 'oneπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ twoπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§ threeπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§',
527
- facets: [
528
- { index: { byteStart: 0, byteEnd: 28 }, features: [{ $type: '' }] },
529
- { index: { byteStart: 29, byteEnd: 57 }, features: [{ $type: '' }] },
530
- { index: { byteStart: 58, byteEnd: 88 }, features: [{ $type: '' }] },
531
- ],
532
- })
533
- expect(Array.from(input.segments())).toEqual([
534
- {
535
- text: 'oneπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§',
536
- facet: {
537
- index: { byteStart: 0, byteEnd: 28 },
538
- features: [{ $type: '' }],
539
- },
540
- },
541
- { text: ' ' },
542
- {
543
- text: 'twoπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§',
544
- facet: {
545
- index: { byteStart: 29, byteEnd: 57 },
546
- features: [{ $type: '' }],
547
- },
548
- },
549
- { text: ' ' },
550
- {
551
- text: 'threeπŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘§',
552
- facet: {
553
- index: { byteStart: 58, byteEnd: 88 },
554
- features: [{ $type: '' }],
555
- },
556
- },
557
- ])
558
- })
559
-
560
- it('correctly identifies mentions and links', () => {
561
- const input = new RichText({
562
- text: 'one two three',
563
- facets: [
564
- {
565
- index: { byteStart: 0, byteEnd: 3 },
566
- features: [
567
- {
568
- $type: 'app.bsky.richtext.facet#mention',
569
- did: 'did:plc:123',
570
- },
571
- ],
572
- },
573
- {
574
- index: { byteStart: 4, byteEnd: 7 },
575
- features: [
576
- {
577
- $type: 'app.bsky.richtext.facet#link',
578
- uri: 'https://example.com',
579
- },
580
- ],
581
- },
582
- {
583
- index: { byteStart: 8, byteEnd: 13 },
584
- features: [{ $type: 'other' }],
585
- },
586
- ],
587
- })
588
- const segments = Array.from(input.segments())
589
- expect(segments[0].isLink()).toBe(false)
590
- expect(segments[0].isMention()).toBe(true)
591
- expect(segments[1].isLink()).toBe(false)
592
- expect(segments[1].isMention()).toBe(false)
593
- expect(segments[2].isLink()).toBe(true)
594
- expect(segments[2].isMention()).toBe(false)
595
- expect(segments[3].isLink()).toBe(false)
596
- expect(segments[3].isMention()).toBe(false)
597
- expect(segments[4].isLink()).toBe(false)
598
- expect(segments[4].isMention()).toBe(false)
599
- })
600
-
601
- it('skips facets that incorrectly overlap (left edge)', () => {
602
- const input = new RichText({
603
- text: 'one two three',
604
- facets: [
605
- { index: { byteStart: 0, byteEnd: 3 }, features: [{ $type: '' }] },
606
- { index: { byteStart: 2, byteEnd: 9 }, features: [{ $type: '' }] },
607
- { index: { byteStart: 8, byteEnd: 13 }, features: [{ $type: '' }] },
608
- ],
609
- })
610
- expect(Array.from(input.segments())).toEqual([
611
- {
612
- text: 'one',
613
- facet: {
614
- index: { byteStart: 0, byteEnd: 3 },
615
- features: [{ $type: '' }],
616
- },
617
- },
618
- {
619
- text: ' two ',
620
- },
621
- {
622
- text: 'three',
623
- facet: {
624
- index: { byteStart: 8, byteEnd: 13 },
625
- features: [{ $type: '' }],
626
- },
627
- },
628
- ])
629
- })
630
-
631
- it('skips facets that incorrectly overlap (right edge)', () => {
632
- const input = new RichText({
633
- text: 'one two three',
634
- facets: [
635
- { index: { byteStart: 0, byteEnd: 3 }, features: [{ $type: '' }] },
636
- { index: { byteStart: 4, byteEnd: 9 }, features: [{ $type: '' }] },
637
- { index: { byteStart: 8, byteEnd: 13 }, features: [{ $type: '' }] },
638
- ],
639
- })
640
- expect(Array.from(input.segments())).toEqual([
641
- {
642
- text: 'one',
643
- facet: {
644
- index: { byteStart: 0, byteEnd: 3 },
645
- features: [{ $type: '' }],
646
- },
647
- },
648
- { text: ' ' },
649
- {
650
- text: 'two t',
651
- facet: {
652
- index: { byteStart: 4, byteEnd: 9 },
653
- features: [{ $type: '' }],
654
- },
655
- },
656
- {
657
- text: 'hree',
658
- },
659
- ])
660
- })
661
-
662
- it("doesn't duplicate text when negative-length facets are present", () => {
663
- const input = {
664
- text: 'hello world',
665
- facets: [
666
- // invalid zero-length
667
- {
668
- features: [],
669
- index: {
670
- byteStart: 6,
671
- byteEnd: 0,
672
- },
673
- },
674
- // valid normal facet
675
- {
676
- features: [],
677
- index: {
678
- byteEnd: 11,
679
- byteStart: 6,
680
- },
681
- },
682
- // valid zero-length
683
- {
684
- features: [],
685
- index: {
686
- byteEnd: 0,
687
- byteStart: 0,
688
- },
689
- },
690
- ],
691
- }
692
-
693
- const rt = new RichText(input)
694
-
695
- let output = ''
696
- for (const segment of rt.segments()) {
697
- output += segment.text
698
- }
699
-
700
- expect(output).toEqual(input.text)
701
-
702
- // invalid one should have been removed
703
- expect(rt.facets?.length).toEqual(2)
704
- })
705
- })