this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at patch-1 661 lines 20 kB view raw
1import { RichText } from '../src' 2 3describe('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 113describe('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 161describe('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 241describe('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 321describe('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 401describe('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})