@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,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
- })