digital-tools 2.1.3 → 2.4.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 (294) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +19 -0
  3. package/README.md +2 -0
  4. package/dist/client.d.ts +109 -0
  5. package/dist/client.d.ts.map +1 -0
  6. package/dist/client.js +69 -0
  7. package/dist/client.js.map +1 -0
  8. package/dist/define.d.ts +2 -2
  9. package/dist/define.d.ts.map +1 -1
  10. package/dist/define.js +21 -11
  11. package/dist/define.js.map +1 -1
  12. package/dist/function-ref.d.ts +229 -0
  13. package/dist/function-ref.d.ts.map +1 -0
  14. package/dist/function-ref.js +28 -0
  15. package/dist/function-ref.js.map +1 -0
  16. package/dist/function-sugar.d.ts +57 -0
  17. package/dist/function-sugar.d.ts.map +1 -0
  18. package/dist/function-sugar.js +79 -0
  19. package/dist/function-sugar.js.map +1 -0
  20. package/dist/index.d.ts +10 -3
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +24 -4
  23. package/dist/index.js.map +1 -1
  24. package/dist/providers/analytics/mixpanel.d.ts.map +1 -1
  25. package/dist/providers/analytics/mixpanel.js +21 -18
  26. package/dist/providers/analytics/mixpanel.js.map +1 -1
  27. package/dist/providers/calendar/cal-com.d.ts.map +1 -1
  28. package/dist/providers/calendar/cal-com.js +10 -10
  29. package/dist/providers/calendar/cal-com.js.map +1 -1
  30. package/dist/providers/calendar/google-calendar.d.ts.map +1 -1
  31. package/dist/providers/calendar/google-calendar.js +4 -4
  32. package/dist/providers/calendar/google-calendar.js.map +1 -1
  33. package/dist/providers/crm/hubspot.d.ts.map +1 -1
  34. package/dist/providers/crm/hubspot.js +107 -85
  35. package/dist/providers/crm/hubspot.js.map +1 -1
  36. package/dist/providers/development/github.d.ts.map +1 -1
  37. package/dist/providers/development/github.js +40 -43
  38. package/dist/providers/development/github.js.map +1 -1
  39. package/dist/providers/ecommerce/shopify.d.ts.map +1 -1
  40. package/dist/providers/ecommerce/shopify.js +79 -62
  41. package/dist/providers/ecommerce/shopify.js.map +1 -1
  42. package/dist/providers/email/resend.d.ts.map +1 -1
  43. package/dist/providers/email/resend.js +20 -16
  44. package/dist/providers/email/resend.js.map +1 -1
  45. package/dist/providers/email/sendgrid.d.ts.map +1 -1
  46. package/dist/providers/email/sendgrid.js +12 -9
  47. package/dist/providers/email/sendgrid.js.map +1 -1
  48. package/dist/providers/finance/stripe.d.ts.map +1 -1
  49. package/dist/providers/finance/stripe.js +44 -42
  50. package/dist/providers/finance/stripe.js.map +1 -1
  51. package/dist/providers/forms/typeform.d.ts.map +1 -1
  52. package/dist/providers/forms/typeform.js +68 -58
  53. package/dist/providers/forms/typeform.js.map +1 -1
  54. package/dist/providers/knowledge/notion.d.ts.map +1 -1
  55. package/dist/providers/knowledge/notion.js +75 -41
  56. package/dist/providers/knowledge/notion.js.map +1 -1
  57. package/dist/providers/marketing/mailchimp.d.ts.map +1 -1
  58. package/dist/providers/marketing/mailchimp.js +74 -61
  59. package/dist/providers/marketing/mailchimp.js.map +1 -1
  60. package/dist/providers/media/cloudinary.d.ts.map +1 -1
  61. package/dist/providers/media/cloudinary.js +30 -28
  62. package/dist/providers/media/cloudinary.js.map +1 -1
  63. package/dist/providers/messaging/slack.d.ts.map +1 -1
  64. package/dist/providers/messaging/slack.js +75 -58
  65. package/dist/providers/messaging/slack.js.map +1 -1
  66. package/dist/providers/messaging/twilio-sms.d.ts.map +1 -1
  67. package/dist/providers/messaging/twilio-sms.js +33 -15
  68. package/dist/providers/messaging/twilio-sms.js.map +1 -1
  69. package/dist/providers/project-management/linear.d.ts.map +1 -1
  70. package/dist/providers/project-management/linear.js +31 -27
  71. package/dist/providers/project-management/linear.js.map +1 -1
  72. package/dist/providers/spreadsheet/google-sheets.d.ts.map +1 -1
  73. package/dist/providers/spreadsheet/google-sheets.js +21 -18
  74. package/dist/providers/spreadsheet/google-sheets.js.map +1 -1
  75. package/dist/providers/spreadsheet/xlsx.d.ts.map +1 -1
  76. package/dist/providers/spreadsheet/xlsx.js +4 -4
  77. package/dist/providers/spreadsheet/xlsx.js.map +1 -1
  78. package/dist/providers/storage/index.js +1 -0
  79. package/dist/providers/storage/index.js.map +1 -1
  80. package/dist/providers/storage/s3.d.ts.map +1 -1
  81. package/dist/providers/storage/s3.js +36 -27
  82. package/dist/providers/storage/s3.js.map +1 -1
  83. package/dist/providers/support/zendesk.d.ts.map +1 -1
  84. package/dist/providers/support/zendesk.js +24 -25
  85. package/dist/providers/support/zendesk.js.map +1 -1
  86. package/dist/providers/tasks/todoist.d.ts.map +1 -1
  87. package/dist/providers/tasks/todoist.js +18 -18
  88. package/dist/providers/tasks/todoist.js.map +1 -1
  89. package/dist/providers/video-conferencing/google-meet.d.ts.map +1 -1
  90. package/dist/providers/video-conferencing/google-meet.js +11 -11
  91. package/dist/providers/video-conferencing/google-meet.js.map +1 -1
  92. package/dist/providers/video-conferencing/jitsi.js +14 -14
  93. package/dist/providers/video-conferencing/jitsi.js.map +1 -1
  94. package/dist/providers/video-conferencing/teams.d.ts.map +1 -1
  95. package/dist/providers/video-conferencing/teams.js +9 -7
  96. package/dist/providers/video-conferencing/teams.js.map +1 -1
  97. package/dist/providers/video-conferencing/zoom.d.ts.map +1 -1
  98. package/dist/providers/video-conferencing/zoom.js +26 -24
  99. package/dist/providers/video-conferencing/zoom.js.map +1 -1
  100. package/dist/tools/data.d.ts.map +1 -1
  101. package/dist/tools/data.js +5 -12
  102. package/dist/tools/data.js.map +1 -1
  103. package/dist/tools/index.d.ts +1 -0
  104. package/dist/tools/index.d.ts.map +1 -1
  105. package/dist/tools/index.js +1 -0
  106. package/dist/tools/index.js.map +1 -1
  107. package/dist/tools/system.d.ts +289 -0
  108. package/dist/tools/system.d.ts.map +1 -0
  109. package/dist/tools/system.js +752 -0
  110. package/dist/tools/system.js.map +1 -0
  111. package/dist/tools/web.d.ts.map +1 -1
  112. package/dist/tools/web.js +22 -10
  113. package/dist/tools/web.js.map +1 -1
  114. package/dist/track-record.d.ts +101 -0
  115. package/dist/track-record.d.ts.map +1 -0
  116. package/dist/track-record.js +17 -0
  117. package/dist/track-record.js.map +1 -0
  118. package/dist/types.d.ts +210 -9
  119. package/dist/types.d.ts.map +1 -1
  120. package/dist/verb-registration.d.ts +122 -0
  121. package/dist/verb-registration.d.ts.map +1 -0
  122. package/dist/verb-registration.js +176 -0
  123. package/dist/verb-registration.js.map +1 -0
  124. package/dist/worker.d.ts +93 -0
  125. package/dist/worker.d.ts.map +1 -0
  126. package/dist/worker.js +315 -0
  127. package/dist/worker.js.map +1 -0
  128. package/dist/wrap.d.ts +89 -0
  129. package/dist/wrap.d.ts.map +1 -0
  130. package/dist/wrap.js +225 -0
  131. package/dist/wrap.js.map +1 -0
  132. package/package.json +31 -14
  133. package/src/client.ts +136 -0
  134. package/src/define.ts +30 -24
  135. package/src/function-ref.ts +264 -0
  136. package/src/function-sugar.ts +134 -0
  137. package/src/index.ts +132 -10
  138. package/src/providers/analytics/mixpanel.ts +19 -18
  139. package/src/providers/calendar/cal-com.ts +29 -18
  140. package/src/providers/calendar/google-calendar.ts +20 -14
  141. package/src/providers/crm/hubspot.ts +225 -99
  142. package/src/providers/development/github.ts +206 -135
  143. package/src/providers/ecommerce/shopify.ts +250 -89
  144. package/src/providers/email/resend.ts +101 -28
  145. package/src/providers/email/sendgrid.ts +12 -9
  146. package/src/providers/finance/stripe.ts +128 -49
  147. package/src/providers/forms/typeform.ts +74 -58
  148. package/src/providers/knowledge/notion.ts +340 -88
  149. package/src/providers/marketing/mailchimp.ts +86 -70
  150. package/src/providers/media/cloudinary.ts +99 -41
  151. package/src/providers/messaging/slack.ts +283 -85
  152. package/src/providers/messaging/twilio-sms.ts +35 -15
  153. package/src/providers/project-management/linear.ts +143 -55
  154. package/src/providers/spreadsheet/google-sheets.ts +222 -56
  155. package/src/providers/spreadsheet/xlsx.ts +47 -16
  156. package/src/providers/storage/s3.ts +119 -47
  157. package/src/providers/support/zendesk.ts +196 -46
  158. package/src/providers/tasks/todoist.ts +20 -26
  159. package/src/providers/video-conferencing/google-meet.ts +17 -20
  160. package/src/providers/video-conferencing/jitsi.ts +14 -14
  161. package/src/providers/video-conferencing/teams.ts +14 -13
  162. package/src/providers/video-conferencing/zoom.ts +54 -49
  163. package/src/tools/data.ts +6 -16
  164. package/src/tools/index.ts +1 -0
  165. package/src/tools/system.ts +887 -0
  166. package/src/tools/web.ts +22 -10
  167. package/src/track-record.ts +106 -0
  168. package/src/types.ts +241 -13
  169. package/src/verb-registration.ts +197 -0
  170. package/src/worker.ts +370 -0
  171. package/src/wrap.ts +260 -0
  172. package/test/client.test.ts +146 -0
  173. package/test/communication-tools-extended.test.ts +734 -0
  174. package/test/data-tools-extended.test.ts +743 -0
  175. package/test/define-extended.test.ts +819 -0
  176. package/test/define.test.ts +150 -41
  177. package/test/entities.test.ts +623 -0
  178. package/test/extended-entities.test.ts +1228 -0
  179. package/test/provider-implementations.test.ts +725 -0
  180. package/test/provider-registry-extended.test.ts +583 -0
  181. package/test/providers/google-sheets.test.ts +851 -0
  182. package/test/providers/helpers.ts +554 -0
  183. package/test/providers/hubspot.test.ts +576 -0
  184. package/test/providers/slack.test.ts +932 -0
  185. package/test/providers/stripe.test.ts +701 -0
  186. package/test/providers.test.ts +578 -0
  187. package/test/system-tools-extended.test.ts +632 -0
  188. package/test/system.test.ts +673 -0
  189. package/test/tools.test.ts +15 -11
  190. package/test/types.test.ts +402 -0
  191. package/test/verb-registration.test.ts +395 -0
  192. package/test/web-tools.test.ts +553 -0
  193. package/test/worker-extended.test.ts +699 -0
  194. package/test/worker.test.ts +576 -0
  195. package/test/wrap.test.ts +366 -0
  196. package/tsconfig.json +3 -13
  197. package/vitest.config.ts +37 -0
  198. package/wrangler.jsonc +9 -0
  199. package/LICENSE +0 -21
  200. package/dist/providers/voice/vapi.d.ts +0 -27
  201. package/dist/providers/voice/vapi.d.ts.map +0 -1
  202. package/dist/providers/voice/vapi.js +0 -440
  203. package/dist/providers/voice/vapi.js.map +0 -1
  204. package/src/define.js +0 -259
  205. package/src/entities/advertising.js +0 -999
  206. package/src/entities/ai.js +0 -756
  207. package/src/entities/analytics.js +0 -1588
  208. package/src/entities/automation.js +0 -601
  209. package/src/entities/communication.js +0 -1150
  210. package/src/entities/crm.js +0 -1386
  211. package/src/entities/design.js +0 -546
  212. package/src/entities/development.js +0 -2212
  213. package/src/entities/document.js +0 -874
  214. package/src/entities/ecommerce.js +0 -1429
  215. package/src/entities/experiment.js +0 -1039
  216. package/src/entities/finance.js +0 -3478
  217. package/src/entities/forms.js +0 -1892
  218. package/src/entities/hr.js +0 -661
  219. package/src/entities/identity.js +0 -997
  220. package/src/entities/index.js +0 -282
  221. package/src/entities/infrastructure.js +0 -1153
  222. package/src/entities/knowledge.js +0 -1438
  223. package/src/entities/marketing.js +0 -1610
  224. package/src/entities/media.js +0 -1634
  225. package/src/entities/notification.js +0 -1199
  226. package/src/entities/presentation.js +0 -1274
  227. package/src/entities/productivity.js +0 -1317
  228. package/src/entities/project-management.js +0 -1136
  229. package/src/entities/recruiting.js +0 -736
  230. package/src/entities/shipping.js +0 -509
  231. package/src/entities/signature.js +0 -1102
  232. package/src/entities/site.js +0 -222
  233. package/src/entities/spreadsheet.js +0 -1341
  234. package/src/entities/storage.js +0 -1198
  235. package/src/entities/support.js +0 -1166
  236. package/src/entities/video-conferencing.js +0 -1750
  237. package/src/entities/video.js +0 -950
  238. package/src/entities.js +0 -1663
  239. package/src/index.js +0 -74
  240. package/src/providers/analytics/index.js +0 -17
  241. package/src/providers/analytics/mixpanel.js +0 -255
  242. package/src/providers/calendar/cal-com.js +0 -303
  243. package/src/providers/calendar/google-calendar.js +0 -335
  244. package/src/providers/calendar/index.js +0 -20
  245. package/src/providers/crm/hubspot.js +0 -566
  246. package/src/providers/crm/index.js +0 -17
  247. package/src/providers/development/github.js +0 -472
  248. package/src/providers/development/index.js +0 -17
  249. package/src/providers/ecommerce/index.js +0 -17
  250. package/src/providers/ecommerce/shopify.js +0 -378
  251. package/src/providers/email/index.js +0 -20
  252. package/src/providers/email/resend.js +0 -258
  253. package/src/providers/email/sendgrid.js +0 -161
  254. package/src/providers/finance/index.js +0 -17
  255. package/src/providers/finance/stripe.js +0 -549
  256. package/src/providers/forms/index.js +0 -17
  257. package/src/providers/forms/typeform.js +0 -500
  258. package/src/providers/index.js +0 -123
  259. package/src/providers/knowledge/index.js +0 -17
  260. package/src/providers/knowledge/notion.js +0 -389
  261. package/src/providers/marketing/index.js +0 -17
  262. package/src/providers/marketing/mailchimp.js +0 -443
  263. package/src/providers/media/cloudinary.js +0 -318
  264. package/src/providers/media/index.js +0 -17
  265. package/src/providers/messaging/index.js +0 -20
  266. package/src/providers/messaging/slack.js +0 -393
  267. package/src/providers/messaging/twilio-sms.js +0 -249
  268. package/src/providers/project-management/index.js +0 -17
  269. package/src/providers/project-management/linear.js +0 -575
  270. package/src/providers/registry.js +0 -86
  271. package/src/providers/spreadsheet/google-sheets.js +0 -375
  272. package/src/providers/spreadsheet/index.js +0 -20
  273. package/src/providers/spreadsheet/xlsx.js +0 -423
  274. package/src/providers/storage/index.js +0 -24
  275. package/src/providers/storage/s3.js +0 -419
  276. package/src/providers/support/index.js +0 -17
  277. package/src/providers/support/zendesk.js +0 -373
  278. package/src/providers/tasks/index.js +0 -17
  279. package/src/providers/tasks/todoist.js +0 -286
  280. package/src/providers/types.js +0 -9
  281. package/src/providers/video-conferencing/google-meet.js +0 -286
  282. package/src/providers/video-conferencing/index.js +0 -31
  283. package/src/providers/video-conferencing/jitsi.js +0 -254
  284. package/src/providers/video-conferencing/teams.js +0 -270
  285. package/src/providers/video-conferencing/zoom.js +0 -332
  286. package/src/registry.js +0 -128
  287. package/src/tools/communication.js +0 -184
  288. package/src/tools/data.js +0 -205
  289. package/src/tools/index.js +0 -11
  290. package/src/tools/web.js +0 -137
  291. package/src/types.js +0 -10
  292. package/test/define.test.js +0 -306
  293. package/test/registry.test.js +0 -357
  294. package/test/tools.test.js +0 -363
@@ -0,0 +1,553 @@
1
+ /**
2
+ * Comprehensive Tests for Web Tools
3
+ *
4
+ * Tests fetchUrl, parseHtml, and readUrl tools with real network calls
5
+ * where available, and comprehensive edge case testing.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach } from 'vitest'
9
+ import {
10
+ fetchUrl,
11
+ parseHtml,
12
+ readUrl,
13
+ webTools,
14
+ registry,
15
+ registerBuiltinTools,
16
+ } from '../src/index.js'
17
+
18
+ // Check if we have network access for integration tests
19
+ const hasNetwork = true // We'll test with real fetch
20
+
21
+ describe('Web Tools - fetchUrl', () => {
22
+ describe('metadata', () => {
23
+ it('has correct id', () => {
24
+ expect(fetchUrl.id).toBe('web.fetch')
25
+ })
26
+
27
+ it('has correct name', () => {
28
+ expect(fetchUrl.name).toBe('Fetch URL')
29
+ })
30
+
31
+ it('has correct category', () => {
32
+ expect(fetchUrl.category).toBe('web')
33
+ })
34
+
35
+ it('has correct subcategory', () => {
36
+ expect(fetchUrl.subcategory).toBe('fetch')
37
+ })
38
+
39
+ it('has description', () => {
40
+ expect(fetchUrl.description).toBe('Fetch content from a URL using HTTP')
41
+ })
42
+
43
+ it('is for both audiences', () => {
44
+ expect(fetchUrl.audience).toBe('both')
45
+ })
46
+
47
+ it('has http tag', () => {
48
+ expect(fetchUrl.tags).toContain('http')
49
+ })
50
+
51
+ it('has network tag', () => {
52
+ expect(fetchUrl.tags).toContain('network')
53
+ })
54
+
55
+ it('has api tag', () => {
56
+ expect(fetchUrl.tags).toContain('api')
57
+ })
58
+ })
59
+
60
+ describe('parameters', () => {
61
+ it('has url parameter', () => {
62
+ const urlParam = fetchUrl.parameters.find((p) => p.name === 'url')
63
+ expect(urlParam).toBeDefined()
64
+ })
65
+
66
+ it('url parameter is required', () => {
67
+ const urlParam = fetchUrl.parameters.find((p) => p.name === 'url')
68
+ expect(urlParam?.required).toBe(true)
69
+ })
70
+
71
+ it('has method parameter', () => {
72
+ const methodParam = fetchUrl.parameters.find((p) => p.name === 'method')
73
+ expect(methodParam).toBeDefined()
74
+ })
75
+
76
+ it('method parameter is optional', () => {
77
+ const methodParam = fetchUrl.parameters.find((p) => p.name === 'method')
78
+ expect(methodParam?.required).toBe(false)
79
+ })
80
+
81
+ it('has headers parameter', () => {
82
+ const headersParam = fetchUrl.parameters.find((p) => p.name === 'headers')
83
+ expect(headersParam).toBeDefined()
84
+ })
85
+
86
+ it('has body parameter', () => {
87
+ const bodyParam = fetchUrl.parameters.find((p) => p.name === 'body')
88
+ expect(bodyParam).toBeDefined()
89
+ })
90
+
91
+ it('has 4 parameters total', () => {
92
+ expect(fetchUrl.parameters).toHaveLength(4)
93
+ })
94
+ })
95
+
96
+ describe('handler - real network tests', () => {
97
+ it('fetches a real URL with GET', async () => {
98
+ const result = await fetchUrl.handler({
99
+ url: 'https://httpbin.org/get',
100
+ })
101
+
102
+ expect(result.status).toBe(200)
103
+ expect(result.body).toBeDefined()
104
+ expect(typeof result.body).toBe('string')
105
+ expect(result.headers).toBeDefined()
106
+ })
107
+
108
+ it('fetches with custom headers', async () => {
109
+ const result = await fetchUrl.handler({
110
+ url: 'https://httpbin.org/headers',
111
+ headers: {
112
+ 'X-Custom-Header': 'test-value',
113
+ },
114
+ })
115
+
116
+ expect(result.status).toBe(200)
117
+ const body = JSON.parse(result.body)
118
+ expect(body.headers['X-Custom-Header']).toBe('test-value')
119
+ })
120
+
121
+ it('performs POST request', async () => {
122
+ const result = await fetchUrl.handler({
123
+ url: 'https://httpbin.org/post',
124
+ method: 'POST',
125
+ headers: {
126
+ 'Content-Type': 'application/json',
127
+ },
128
+ body: JSON.stringify({ test: 'data' }),
129
+ })
130
+
131
+ expect(result.status).toBe(200)
132
+ const body = JSON.parse(result.body)
133
+ expect(body.json).toEqual({ test: 'data' })
134
+ })
135
+
136
+ it('handles 404 response', async () => {
137
+ const result = await fetchUrl.handler({
138
+ url: 'https://httpbin.org/status/404',
139
+ })
140
+
141
+ expect(result.status).toBe(404)
142
+ })
143
+
144
+ it('returns headers as object', async () => {
145
+ const result = await fetchUrl.handler({
146
+ url: 'https://httpbin.org/get',
147
+ })
148
+
149
+ expect(typeof result.headers).toBe('object')
150
+ expect(result.headers['content-type']).toBeDefined()
151
+ })
152
+ })
153
+ })
154
+
155
+ describe('Web Tools - parseHtml', () => {
156
+ describe('metadata', () => {
157
+ it('has correct id', () => {
158
+ expect(parseHtml.id).toBe('web.parse-html')
159
+ })
160
+
161
+ it('has correct name', () => {
162
+ expect(parseHtml.name).toBe('Parse HTML')
163
+ })
164
+
165
+ it('has correct category', () => {
166
+ expect(parseHtml.category).toBe('web')
167
+ })
168
+
169
+ it('has correct subcategory', () => {
170
+ expect(parseHtml.subcategory).toBe('scrape')
171
+ })
172
+
173
+ it('has html tag', () => {
174
+ expect(parseHtml.tags).toContain('html')
175
+ })
176
+
177
+ it('has parse tag', () => {
178
+ expect(parseHtml.tags).toContain('parse')
179
+ })
180
+
181
+ it('has extract tag', () => {
182
+ expect(parseHtml.tags).toContain('extract')
183
+ })
184
+ })
185
+
186
+ describe('parameters', () => {
187
+ it('has html parameter', () => {
188
+ const htmlParam = parseHtml.parameters.find((p) => p.name === 'html')
189
+ expect(htmlParam).toBeDefined()
190
+ expect(htmlParam?.required).toBe(true)
191
+ })
192
+
193
+ it('has selector parameter', () => {
194
+ const selectorParam = parseHtml.parameters.find((p) => p.name === 'selector')
195
+ expect(selectorParam).toBeDefined()
196
+ expect(selectorParam?.required).toBe(false)
197
+ })
198
+ })
199
+
200
+ describe('handler - text extraction', () => {
201
+ it('extracts text from simple HTML', async () => {
202
+ const result = await parseHtml.handler({
203
+ html: '<p>Hello World</p>',
204
+ })
205
+
206
+ expect(result.text).toBe('Hello World')
207
+ })
208
+
209
+ it('extracts text from nested elements', async () => {
210
+ const result = await parseHtml.handler({
211
+ html: '<div><p>First</p><span>Second</span></div>',
212
+ })
213
+
214
+ expect(result.text).toContain('First')
215
+ expect(result.text).toContain('Second')
216
+ })
217
+
218
+ it('handles multiple whitespace', async () => {
219
+ const result = await parseHtml.handler({
220
+ html: '<p>Hello World</p>',
221
+ })
222
+
223
+ expect(result.text).toBe('Hello World')
224
+ })
225
+
226
+ it('handles newlines in HTML', async () => {
227
+ const result = await parseHtml.handler({
228
+ html: '<p>Line 1</p>\n<p>Line 2</p>',
229
+ })
230
+
231
+ expect(result.text).toContain('Line 1')
232
+ expect(result.text).toContain('Line 2')
233
+ })
234
+
235
+ it('strips all HTML tags', async () => {
236
+ const result = await parseHtml.handler({
237
+ html: '<b>Bold</b> and <i>italic</i>',
238
+ })
239
+
240
+ expect(result.text).toBe('Bold and italic')
241
+ })
242
+ })
243
+
244
+ describe('handler - link extraction', () => {
245
+ it('extracts links from href attributes', async () => {
246
+ const result = await parseHtml.handler({
247
+ html: '<a href="https://example.com">Link</a>',
248
+ })
249
+
250
+ expect(result.links).toContain('https://example.com')
251
+ })
252
+
253
+ it('extracts multiple links', async () => {
254
+ const result = await parseHtml.handler({
255
+ html: '<a href="https://a.com">A</a><a href="https://b.com">B</a>',
256
+ })
257
+
258
+ expect(result.links).toHaveLength(2)
259
+ expect(result.links).toContain('https://a.com')
260
+ expect(result.links).toContain('https://b.com')
261
+ })
262
+
263
+ it('extracts relative links', async () => {
264
+ const result = await parseHtml.handler({
265
+ html: '<a href="/path/to/page">Page</a>',
266
+ })
267
+
268
+ expect(result.links).toContain('/path/to/page')
269
+ })
270
+
271
+ it('returns empty array when no links', async () => {
272
+ const result = await parseHtml.handler({
273
+ html: '<p>No links here</p>',
274
+ })
275
+
276
+ expect(result.links).toEqual([])
277
+ })
278
+ })
279
+
280
+ describe('handler - image extraction', () => {
281
+ it('extracts jpg images', async () => {
282
+ const result = await parseHtml.handler({
283
+ html: '<img src="image.jpg" />',
284
+ })
285
+
286
+ expect(result.images).toContain('image.jpg')
287
+ })
288
+
289
+ it('extracts png images', async () => {
290
+ const result = await parseHtml.handler({
291
+ html: '<img src="photo.png" />',
292
+ })
293
+
294
+ expect(result.images).toContain('photo.png')
295
+ })
296
+
297
+ it('extracts gif images', async () => {
298
+ const result = await parseHtml.handler({
299
+ html: '<img src="animation.gif" />',
300
+ })
301
+
302
+ expect(result.images).toContain('animation.gif')
303
+ })
304
+
305
+ it('extracts webp images', async () => {
306
+ const result = await parseHtml.handler({
307
+ html: '<img src="modern.webp" />',
308
+ })
309
+
310
+ expect(result.images).toContain('modern.webp')
311
+ })
312
+
313
+ it('extracts svg images', async () => {
314
+ const result = await parseHtml.handler({
315
+ html: '<img src="icon.svg" />',
316
+ })
317
+
318
+ expect(result.images).toContain('icon.svg')
319
+ })
320
+
321
+ it('extracts jpeg images', async () => {
322
+ const result = await parseHtml.handler({
323
+ html: '<img src="picture.jpeg" />',
324
+ })
325
+
326
+ expect(result.images).toContain('picture.jpeg')
327
+ })
328
+
329
+ it('extracts multiple images', async () => {
330
+ const result = await parseHtml.handler({
331
+ html: '<img src="a.jpg" /><img src="b.png" />',
332
+ })
333
+
334
+ expect(result.images).toHaveLength(2)
335
+ })
336
+
337
+ it('does not extract non-image src attributes', async () => {
338
+ const result = await parseHtml.handler({
339
+ html: '<script src="app.js"></script>',
340
+ })
341
+
342
+ expect(result.images).not.toContain('app.js')
343
+ })
344
+
345
+ it('returns empty array when no images', async () => {
346
+ const result = await parseHtml.handler({
347
+ html: '<p>No images here</p>',
348
+ })
349
+
350
+ expect(result.images).toEqual([])
351
+ })
352
+ })
353
+
354
+ describe('handler - complex HTML', () => {
355
+ it('handles complete HTML document', async () => {
356
+ const html = `
357
+ <!DOCTYPE html>
358
+ <html>
359
+ <head><title>Test Page</title></head>
360
+ <body>
361
+ <h1>Welcome</h1>
362
+ <p>This is content.</p>
363
+ <a href="https://example.com">Link</a>
364
+ <img src="photo.jpg" />
365
+ </body>
366
+ </html>
367
+ `
368
+
369
+ const result = await parseHtml.handler({ html })
370
+
371
+ expect(result.text).toContain('Welcome')
372
+ expect(result.text).toContain('This is content')
373
+ expect(result.links).toContain('https://example.com')
374
+ expect(result.images).toContain('photo.jpg')
375
+ })
376
+
377
+ it('handles empty HTML', async () => {
378
+ const result = await parseHtml.handler({ html: '' })
379
+
380
+ expect(result.text).toBe('')
381
+ expect(result.links).toEqual([])
382
+ expect(result.images).toEqual([])
383
+ })
384
+ })
385
+ })
386
+
387
+ describe('Web Tools - readUrl', () => {
388
+ describe('metadata', () => {
389
+ it('has correct id', () => {
390
+ expect(readUrl.id).toBe('web.read')
391
+ })
392
+
393
+ it('has correct name', () => {
394
+ expect(readUrl.name).toBe('Read URL')
395
+ })
396
+
397
+ it('has correct category', () => {
398
+ expect(readUrl.category).toBe('web')
399
+ })
400
+
401
+ it('has correct subcategory', () => {
402
+ expect(readUrl.subcategory).toBe('scrape')
403
+ })
404
+
405
+ it('is idempotent', () => {
406
+ expect(readUrl.idempotent).toBe(true)
407
+ })
408
+
409
+ it('has read tag', () => {
410
+ expect(readUrl.tags).toContain('read')
411
+ })
412
+
413
+ it('has scrape tag', () => {
414
+ expect(readUrl.tags).toContain('scrape')
415
+ })
416
+
417
+ it('has extract tag', () => {
418
+ expect(readUrl.tags).toContain('extract')
419
+ })
420
+ })
421
+
422
+ describe('parameters', () => {
423
+ it('has url parameter', () => {
424
+ const urlParam = readUrl.parameters.find((p) => p.name === 'url')
425
+ expect(urlParam).toBeDefined()
426
+ expect(urlParam?.required).toBe(true)
427
+ })
428
+
429
+ it('has only 1 parameter', () => {
430
+ expect(readUrl.parameters).toHaveLength(1)
431
+ })
432
+ })
433
+
434
+ describe('handler - real network tests', () => {
435
+ it('reads a real webpage', async () => {
436
+ const result = await readUrl.handler({
437
+ url: 'https://example.com',
438
+ })
439
+
440
+ expect(result.title).toBeDefined()
441
+ expect(result.text).toBeDefined()
442
+ expect(result.links).toBeDefined()
443
+ })
444
+
445
+ it('extracts title from page', async () => {
446
+ const result = await readUrl.handler({
447
+ url: 'https://example.com',
448
+ })
449
+
450
+ expect(typeof result.title).toBe('string')
451
+ expect(result.title.length).toBeGreaterThan(0)
452
+ })
453
+
454
+ it('extracts text from page body', async () => {
455
+ const result = await readUrl.handler({
456
+ url: 'https://example.com',
457
+ })
458
+
459
+ expect(typeof result.text).toBe('string')
460
+ expect(result.text.length).toBeGreaterThan(0)
461
+ })
462
+
463
+ it('extracts unique links', async () => {
464
+ const result = await readUrl.handler({
465
+ url: 'https://example.com',
466
+ })
467
+
468
+ expect(Array.isArray(result.links)).toBe(true)
469
+ // Check for uniqueness
470
+ const uniqueLinks = [...new Set(result.links)]
471
+ expect(uniqueLinks.length).toBe(result.links.length)
472
+ })
473
+
474
+ it('limits text to 10000 characters', async () => {
475
+ const result = await readUrl.handler({
476
+ url: 'https://example.com',
477
+ })
478
+
479
+ expect(result.text.length).toBeLessThanOrEqual(10000)
480
+ })
481
+ })
482
+ })
483
+
484
+ describe('Web Tools Array', () => {
485
+ it('contains fetchUrl', () => {
486
+ expect(webTools.map((t) => t.id)).toContain('web.fetch')
487
+ })
488
+
489
+ it('contains parseHtml', () => {
490
+ expect(webTools.map((t) => t.id)).toContain('web.parse-html')
491
+ })
492
+
493
+ it('contains readUrl', () => {
494
+ expect(webTools.map((t) => t.id)).toContain('web.read')
495
+ })
496
+
497
+ it('has exactly 3 tools', () => {
498
+ expect(webTools).toHaveLength(3)
499
+ })
500
+
501
+ it('all tools have web category', () => {
502
+ expect(webTools.every((t) => t.category === 'web')).toBe(true)
503
+ })
504
+
505
+ it('all tools have handlers', () => {
506
+ expect(webTools.every((t) => typeof t.handler === 'function')).toBe(true)
507
+ })
508
+
509
+ it('all tools have parameters', () => {
510
+ expect(webTools.every((t) => Array.isArray(t.parameters))).toBe(true)
511
+ })
512
+ })
513
+
514
+ describe('Web Tools Registry Integration', () => {
515
+ beforeEach(() => {
516
+ registry.clear()
517
+ })
518
+
519
+ it('can register web tools', () => {
520
+ for (const tool of webTools) {
521
+ registry.register(tool)
522
+ }
523
+
524
+ expect(registry.has('web.fetch')).toBe(true)
525
+ expect(registry.has('web.parse-html')).toBe(true)
526
+ expect(registry.has('web.read')).toBe(true)
527
+ })
528
+
529
+ it('can query web tools by category', () => {
530
+ registerBuiltinTools()
531
+
532
+ const tools = registry.byCategory('web')
533
+ expect(tools.length).toBeGreaterThanOrEqual(3)
534
+ })
535
+
536
+ it('can find tools by subcategory', () => {
537
+ registerBuiltinTools()
538
+
539
+ const fetchTools = registry.query({ subcategory: 'fetch' })
540
+ expect(fetchTools.some((t) => t.id === 'web.fetch')).toBe(true)
541
+
542
+ const scrapeTools = registry.query({ subcategory: 'scrape' })
543
+ expect(scrapeTools.some((t) => t.id === 'web.parse-html')).toBe(true)
544
+ expect(scrapeTools.some((t) => t.id === 'web.read')).toBe(true)
545
+ })
546
+
547
+ it('can search for tools by text', () => {
548
+ registerBuiltinTools()
549
+
550
+ const results = registry.query({ search: 'fetch' })
551
+ expect(results.some((t) => t.id === 'web.fetch')).toBe(true)
552
+ })
553
+ })