@atproto/api 0.1.2 β†’ 0.2.0

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 (205) hide show
  1. package/README.md +179 -25
  2. package/build.js +10 -1
  3. package/dist/agent.d.ts +11 -6
  4. package/dist/bsky-agent.d.ts +43 -0
  5. package/dist/client/index.d.ts +127 -215
  6. package/dist/client/lexicons.d.ts +1496 -1347
  7. package/dist/client/types/app/bsky/actor/defs.d.ts +47 -0
  8. package/dist/client/types/app/bsky/actor/getProfile.d.ts +2 -2
  9. package/dist/client/types/app/bsky/actor/getProfiles.d.ts +2 -2
  10. package/dist/client/types/app/bsky/actor/getSuggestions.d.ts +2 -2
  11. package/dist/client/types/app/bsky/actor/profile.d.ts +4 -60
  12. package/dist/client/types/app/bsky/actor/searchActors.d.ts +22 -0
  13. package/dist/client/types/app/bsky/actor/searchActorsTypeahead.d.ts +20 -0
  14. package/dist/client/types/app/bsky/actor/updateProfile.d.ts +4 -11
  15. package/dist/client/types/app/bsky/embed/external.d.ts +9 -13
  16. package/dist/client/types/app/bsky/embed/images.d.ts +9 -13
  17. package/dist/client/types/app/bsky/embed/record.d.ts +41 -0
  18. package/dist/client/types/app/bsky/embed/recordWithMedia.d.ts +24 -0
  19. package/dist/client/types/app/bsky/feed/defs.d.ts +77 -0
  20. package/dist/client/types/app/bsky/feed/getAuthorFeed.d.ts +4 -4
  21. package/dist/client/types/app/bsky/feed/getLikes.d.ts +34 -0
  22. package/dist/client/types/app/bsky/feed/getPostThread.d.ts +2 -24
  23. package/dist/client/types/app/bsky/feed/getRepostedBy.d.ts +3 -3
  24. package/dist/client/types/app/bsky/feed/getTimeline.d.ts +3 -3
  25. package/dist/client/types/app/bsky/feed/like.d.ts +9 -0
  26. package/dist/client/types/app/bsky/feed/post.d.ts +5 -29
  27. package/dist/client/types/app/bsky/graph/follow.d.ts +1 -2
  28. package/dist/client/types/app/bsky/graph/getFollowers.d.ts +5 -5
  29. package/dist/client/types/app/bsky/graph/getFollows.d.ts +5 -5
  30. package/dist/client/types/app/bsky/graph/getMutes.d.ts +3 -3
  31. package/dist/client/types/app/bsky/graph/muteActor.d.ts +17 -0
  32. package/dist/client/types/app/bsky/graph/unmuteActor.d.ts +17 -0
  33. package/dist/client/types/app/bsky/notification/getUnreadCount.d.ts +17 -0
  34. package/dist/client/types/app/bsky/notification/listNotifications.d.ts +35 -0
  35. package/dist/client/types/app/bsky/richtext/facet.d.ts +30 -0
  36. package/dist/client/types/app/bsky/unspecced/getPopular.d.ts +21 -0
  37. package/dist/client/types/com/atproto/admin/defs.d.ts +181 -0
  38. package/dist/client/types/com/atproto/admin/getModerationAction.d.ts +2 -2
  39. package/dist/client/types/com/atproto/admin/getModerationActions.d.ts +3 -3
  40. package/dist/client/types/com/atproto/admin/getModerationReport.d.ts +2 -2
  41. package/dist/client/types/com/atproto/admin/getModerationReports.d.ts +3 -3
  42. package/dist/client/types/com/atproto/admin/getRecord.d.ts +2 -2
  43. package/dist/client/types/com/atproto/admin/getRepo.d.ts +2 -2
  44. package/dist/client/types/com/atproto/admin/resolveModerationReports.d.ts +2 -2
  45. package/dist/client/types/com/atproto/admin/reverseModerationAction.d.ts +2 -2
  46. package/dist/client/types/com/atproto/admin/searchRepos.d.ts +3 -3
  47. package/dist/client/types/com/atproto/admin/takeModerationAction.d.ts +5 -6
  48. package/dist/client/types/com/atproto/identity/resolveHandle.d.ts +18 -0
  49. package/dist/client/types/com/atproto/identity/updateHandle.d.ts +17 -0
  50. package/dist/client/types/com/atproto/moderation/createReport.d.ts +38 -0
  51. package/dist/client/types/com/atproto/moderation/defs.d.ts +3 -0
  52. package/dist/client/types/com/atproto/repo/applyWrites.d.ts +47 -0
  53. package/dist/client/types/com/atproto/repo/createRecord.d.ts +7 -2
  54. package/dist/client/types/com/atproto/repo/deleteRecord.d.ts +7 -2
  55. package/dist/client/types/com/atproto/repo/describeRepo.d.ts +22 -0
  56. package/dist/client/types/com/atproto/repo/getRecord.d.ts +1 -1
  57. package/dist/client/types/com/atproto/repo/listRecords.d.ts +3 -3
  58. package/dist/client/types/com/atproto/repo/putRecord.d.ts +7 -2
  59. package/dist/client/types/com/atproto/repo/uploadBlob.d.ts +20 -0
  60. package/dist/client/types/com/atproto/server/createAccount.d.ts +44 -0
  61. package/dist/client/types/com/atproto/server/createInviteCode.d.ts +22 -0
  62. package/dist/client/types/com/atproto/server/createSession.d.ts +29 -0
  63. package/dist/client/types/com/atproto/server/deleteAccount.d.ts +25 -0
  64. package/dist/client/types/com/atproto/server/deleteSession.d.ts +13 -0
  65. package/dist/client/types/com/atproto/server/describeServer.d.ts +27 -0
  66. package/dist/client/types/com/atproto/server/getSession.d.ts +18 -0
  67. package/dist/client/types/com/atproto/server/refreshSession.d.ts +24 -0
  68. package/dist/client/types/com/atproto/server/requestAccountDelete.d.ts +13 -0
  69. package/dist/client/types/com/atproto/server/requestPasswordReset.d.ts +17 -0
  70. package/dist/client/types/com/atproto/server/resetPassword.d.ts +24 -0
  71. package/dist/client/types/com/atproto/sync/getBlob.d.ts +15 -0
  72. package/dist/client/types/com/atproto/sync/getBlocks.d.ts +15 -0
  73. package/dist/client/types/com/atproto/sync/listBlobs.d.ts +20 -0
  74. package/dist/client/types/com/atproto/sync/notifyOfUpdate.d.ts +13 -0
  75. package/dist/client/types/com/atproto/sync/requestCrawl.d.ts +13 -0
  76. package/dist/client/types/com/atproto/sync/subscribeAllRepos.d.ts +6 -17
  77. package/dist/client/types/com/atproto/sync/subscribeRepos.d.ts +58 -0
  78. package/dist/helpers/bsky.d.ts +20 -0
  79. package/dist/index.d.ts +6 -0
  80. package/dist/index.js +8222 -3608
  81. package/dist/index.js.map +4 -4
  82. package/dist/mixins/bsky.d.ts +23 -0
  83. package/dist/rich-text/detection.d.ts +4 -0
  84. package/dist/rich-text/rich-text.d.ts +39 -0
  85. package/dist/rich-text/sanitization.d.ts +4 -0
  86. package/dist/rich-text/sanitize.d.ts +4 -0
  87. package/dist/rich-text/unicode.d.ts +11 -0
  88. package/dist/types.d.ts +2 -2
  89. package/docs/rn-fetch-handler.ts +88 -0
  90. package/package.json +4 -1
  91. package/src/agent.ts +51 -15
  92. package/src/bsky-agent.ts +228 -0
  93. package/src/client/index.ts +371 -581
  94. package/src/client/lexicons.ts +1920 -1746
  95. package/src/client/types/app/bsky/actor/defs.ts +97 -0
  96. package/src/client/types/app/bsky/actor/getProfile.ts +4 -3
  97. package/src/client/types/app/bsky/actor/getProfiles.ts +4 -3
  98. package/src/client/types/app/bsky/actor/getSuggestions.ts +4 -3
  99. package/src/client/types/app/bsky/actor/profile.ts +5 -95
  100. package/src/client/types/app/bsky/actor/{searchTypeahead.ts β†’ searchActors.ts} +6 -3
  101. package/src/client/types/app/bsky/actor/{search.ts β†’ searchActorsTypeahead.ts} +4 -5
  102. package/src/client/types/app/bsky/embed/external.ts +14 -13
  103. package/src/client/types/app/bsky/embed/images.ts +14 -15
  104. package/src/client/types/app/bsky/embed/record.ts +90 -0
  105. package/src/client/types/app/bsky/embed/recordWithMedia.ts +53 -0
  106. package/src/client/types/app/bsky/feed/defs.ts +156 -0
  107. package/src/client/types/app/bsky/feed/getAuthorFeed.ts +6 -5
  108. package/src/client/types/app/bsky/feed/{getVotes.ts β†’ getLikes.ts} +11 -12
  109. package/src/client/types/app/bsky/feed/getPostThread.ts +5 -48
  110. package/src/client/types/app/bsky/feed/getRepostedBy.ts +5 -4
  111. package/src/client/types/app/bsky/feed/getTimeline.ts +5 -4
  112. package/src/client/types/app/bsky/feed/{vote.ts β†’ like.ts} +4 -4
  113. package/src/client/types/app/bsky/feed/post.ts +12 -51
  114. package/src/client/types/app/bsky/feed/repost.ts +2 -1
  115. package/src/client/types/app/bsky/graph/follow.ts +3 -3
  116. package/src/client/types/app/bsky/graph/getFollowers.ts +7 -6
  117. package/src/client/types/app/bsky/graph/getFollows.ts +7 -6
  118. package/src/client/types/app/bsky/graph/getMutes.ts +5 -4
  119. package/src/client/types/app/bsky/graph/{mute.ts β†’ muteActor.ts} +3 -2
  120. package/src/client/types/app/bsky/graph/{unmute.ts β†’ unmuteActor.ts} +3 -2
  121. package/src/client/types/app/bsky/notification/{getCount.ts β†’ getUnreadCount.ts} +2 -1
  122. package/src/client/types/app/bsky/notification/{list.ts β†’ listNotifications.ts} +13 -9
  123. package/src/client/types/app/bsky/notification/updateSeen.ts +2 -1
  124. package/src/client/types/app/bsky/richtext/facet.ts +81 -0
  125. package/src/client/types/app/bsky/unspecced/getPopular.ts +38 -0
  126. package/src/client/types/com/atproto/admin/defs.ts +366 -0
  127. package/src/client/types/com/atproto/admin/getModerationAction.ts +4 -3
  128. package/src/client/types/com/atproto/admin/getModerationActions.ts +5 -4
  129. package/src/client/types/com/atproto/admin/getModerationReport.ts +4 -3
  130. package/src/client/types/com/atproto/admin/getModerationReports.ts +5 -4
  131. package/src/client/types/com/atproto/admin/getRecord.ts +4 -3
  132. package/src/client/types/com/atproto/admin/getRepo.ts +4 -3
  133. package/src/client/types/com/atproto/admin/resolveModerationReports.ts +4 -3
  134. package/src/client/types/com/atproto/admin/reverseModerationAction.ts +4 -3
  135. package/src/client/types/com/atproto/admin/searchRepos.ts +5 -4
  136. package/src/client/types/com/atproto/admin/takeModerationAction.ts +10 -10
  137. package/src/client/types/com/atproto/{handle/resolve.ts β†’ identity/resolveHandle.ts} +2 -1
  138. package/src/client/types/com/atproto/{handle/update.ts β†’ identity/updateHandle.ts} +2 -1
  139. package/src/client/types/com/atproto/{report/create.ts β†’ moderation/createReport.ts} +10 -10
  140. package/src/client/types/com/atproto/moderation/defs.ts +17 -0
  141. package/src/client/types/com/atproto/repo/{batchWrite.ts β†’ applyWrites.ts} +21 -12
  142. package/src/client/types/com/atproto/repo/createRecord.ts +15 -3
  143. package/src/client/types/com/atproto/repo/deleteRecord.ts +15 -3
  144. package/src/client/types/com/atproto/repo/{describe.ts β†’ describeRepo.ts} +3 -2
  145. package/src/client/types/com/atproto/repo/getRecord.ts +4 -3
  146. package/src/client/types/com/atproto/repo/listRecords.ts +7 -6
  147. package/src/client/types/com/atproto/repo/putRecord.ts +18 -6
  148. package/src/client/types/com/atproto/repo/strongRef.ts +2 -1
  149. package/src/client/types/com/atproto/{blob/upload.ts β†’ repo/uploadBlob.ts} +3 -2
  150. package/src/client/types/com/atproto/{account/create.ts β†’ server/createAccount.ts} +9 -1
  151. package/src/client/types/com/atproto/{account β†’ server}/createInviteCode.ts +2 -1
  152. package/src/client/types/com/atproto/{session/create.ts β†’ server/createSession.ts} +2 -1
  153. package/src/client/types/com/atproto/{account/delete.ts β†’ server/deleteAccount.ts} +2 -1
  154. package/src/client/types/com/atproto/{account/requestDelete.ts β†’ server/deleteSession.ts} +2 -1
  155. package/src/client/types/com/atproto/server/{getAccountsConfig.ts β†’ describeServer.ts} +4 -3
  156. package/src/client/types/com/atproto/{session/get.ts β†’ server/getSession.ts} +2 -1
  157. package/src/client/types/com/atproto/{session/refresh.ts β†’ server/refreshSession.ts} +2 -1
  158. package/src/client/types/com/atproto/{session/delete.ts β†’ server/requestAccountDelete.ts} +2 -1
  159. package/src/client/types/com/atproto/{account β†’ server}/requestPasswordReset.ts +2 -1
  160. package/src/client/types/com/atproto/{account β†’ server}/resetPassword.ts +2 -1
  161. package/src/client/types/com/atproto/sync/getBlob.ts +33 -0
  162. package/src/client/types/com/atproto/sync/getBlocks.ts +32 -0
  163. package/src/client/types/com/atproto/sync/getCheckout.ts +2 -1
  164. package/src/client/types/com/atproto/sync/getCommitPath.ts +2 -1
  165. package/src/client/types/com/atproto/sync/getHead.ts +2 -1
  166. package/src/client/types/com/atproto/sync/getRecord.ts +2 -1
  167. package/src/client/types/com/atproto/sync/getRepo.ts +2 -1
  168. package/src/client/types/com/atproto/sync/listBlobs.ts +40 -0
  169. package/src/client/types/com/atproto/sync/notifyOfUpdate.ts +30 -0
  170. package/src/client/types/com/atproto/{account/get.ts β†’ sync/requestCrawl.ts} +6 -2
  171. package/src/client/types/com/atproto/sync/subscribeRepos.ts +131 -0
  172. package/src/index.ts +12 -0
  173. package/src/rich-text/detection.ts +83 -0
  174. package/src/rich-text/rich-text.ts +401 -0
  175. package/src/rich-text/sanitization.ts +40 -0
  176. package/src/rich-text/unicode.ts +47 -0
  177. package/src/types.ts +2 -2
  178. package/tests/agent.test.ts +10 -6
  179. package/tests/bsky-agent.test.ts +140 -0
  180. package/tests/errors.test.ts +4 -4
  181. package/tests/rich-text-detection.test.ts +229 -0
  182. package/tests/rich-text-sanitization.test.ts +211 -0
  183. package/tests/rich-text.test.ts +661 -0
  184. package/tsconfig.build.tsbuildinfo +1 -1
  185. package/src/client/types/app/bsky/actor/ref.ts +0 -64
  186. package/src/client/types/app/bsky/actor/updateProfile.ts +0 -71
  187. package/src/client/types/app/bsky/feed/feedViewPost.ts +0 -64
  188. package/src/client/types/app/bsky/feed/setVote.ts +0 -40
  189. package/src/client/types/app/bsky/graph/assertCreator.ts +0 -9
  190. package/src/client/types/app/bsky/graph/assertMember.ts +0 -9
  191. package/src/client/types/app/bsky/graph/assertion.ts +0 -27
  192. package/src/client/types/app/bsky/graph/confirmation.ts +0 -28
  193. package/src/client/types/app/bsky/system/actorUser.ts +0 -9
  194. package/src/client/types/app/bsky/system/declRef.ts +0 -26
  195. package/src/client/types/app/bsky/system/declaration.ts +0 -24
  196. package/src/client/types/com/atproto/admin/blob.ts +0 -84
  197. package/src/client/types/com/atproto/admin/moderationAction.ts +0 -118
  198. package/src/client/types/com/atproto/admin/moderationReport.ts +0 -64
  199. package/src/client/types/com/atproto/admin/record.ts +0 -92
  200. package/src/client/types/com/atproto/admin/repo.ts +0 -103
  201. package/src/client/types/com/atproto/repo/recordRef.ts +0 -25
  202. package/src/client/types/com/atproto/repo/repoRef.ts +0 -24
  203. package/src/client/types/com/atproto/report/reasonType.ts +0 -16
  204. package/src/client/types/com/atproto/report/subject.ts +0 -66
  205. package/src/client/types/com/atproto/sync/subscribeAllRepos.ts +0 -48
@@ -0,0 +1,661 @@
1
+ import { RichText } from '../src'
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
+ })