You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.spec.ts 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. import {
  2. asyncRunSafe,
  3. canFindTool,
  4. correctModelProvider,
  5. correctToolProvider,
  6. fetchWithRetry,
  7. getPurifyHref,
  8. getTextWidthWithCanvas,
  9. randomString,
  10. removeSpecificQueryParam,
  11. sleep,
  12. } from './index'
  13. describe('sleep', () => {
  14. it('should wait for the specified time', async () => {
  15. const timeVariance = 10
  16. const sleepTime = 100
  17. const start = Date.now()
  18. await sleep(sleepTime)
  19. const elapsed = Date.now() - start
  20. expect(elapsed).toBeGreaterThanOrEqual(sleepTime - timeVariance)
  21. })
  22. })
  23. describe('asyncRunSafe', () => {
  24. it('should return [null, result] when promise resolves', async () => {
  25. const result = await asyncRunSafe(Promise.resolve('success'))
  26. expect(result).toEqual([null, 'success'])
  27. })
  28. it('should return [error] when promise rejects', async () => {
  29. const error = new Error('test error')
  30. const result = await asyncRunSafe(Promise.reject(error))
  31. expect(result).toEqual([error])
  32. })
  33. it('should return [Error] when promise rejects with undefined', async () => {
  34. // eslint-disable-next-line prefer-promise-reject-errors
  35. const result = await asyncRunSafe(Promise.reject())
  36. expect(result[0]).toBeInstanceOf(Error)
  37. expect(result[0]?.message).toBe('unknown error')
  38. })
  39. })
  40. describe('getTextWidthWithCanvas', () => {
  41. let originalCreateElement: typeof document.createElement
  42. beforeEach(() => {
  43. // Store original implementation
  44. originalCreateElement = document.createElement
  45. // Mock canvas and context
  46. const measureTextMock = jest.fn().mockReturnValue({ width: 100 })
  47. const getContextMock = jest.fn().mockReturnValue({
  48. measureText: measureTextMock,
  49. font: '',
  50. })
  51. document.createElement = jest.fn().mockReturnValue({
  52. getContext: getContextMock,
  53. })
  54. })
  55. afterEach(() => {
  56. // Restore original implementation
  57. document.createElement = originalCreateElement
  58. })
  59. it('should return the width of text', () => {
  60. const width = getTextWidthWithCanvas('test text')
  61. expect(width).toBe(100)
  62. })
  63. it('should return 0 if context is not available', () => {
  64. // Override mock for this test
  65. document.createElement = jest.fn().mockReturnValue({
  66. getContext: () => null,
  67. })
  68. const width = getTextWidthWithCanvas('test text')
  69. expect(width).toBe(0)
  70. })
  71. })
  72. describe('randomString', () => {
  73. it('should generate string of specified length', () => {
  74. const result = randomString(10)
  75. expect(result.length).toBe(10)
  76. })
  77. it('should only contain valid characters', () => {
  78. const result = randomString(100)
  79. const validChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'
  80. for (const char of result)
  81. expect(validChars).toContain(char)
  82. })
  83. it('should generate different strings on consecutive calls', () => {
  84. const result1 = randomString(20)
  85. const result2 = randomString(20)
  86. expect(result1).not.toEqual(result2)
  87. })
  88. })
  89. describe('getPurifyHref', () => {
  90. it('should return empty string for falsy input', () => {
  91. expect(getPurifyHref('')).toBe('')
  92. expect(getPurifyHref(undefined as any)).toBe('')
  93. })
  94. it('should escape HTML characters', () => {
  95. expect(getPurifyHref('<script>alert("xss")</script>')).not.toContain('<script>')
  96. })
  97. })
  98. describe('fetchWithRetry', () => {
  99. it('should return successfully on first try', async () => {
  100. const successData = { status: 'success' }
  101. const promise = Promise.resolve(successData)
  102. const result = await fetchWithRetry(promise)
  103. expect(result).toEqual([null, successData])
  104. })
  105. // it('should retry and succeed on second attempt', async () => {
  106. // let attemptCount = 0
  107. // const mockFn = new Promise((resolve, reject) => {
  108. // attemptCount++
  109. // if (attemptCount === 1)
  110. // reject(new Error('First attempt failed'))
  111. // else
  112. // resolve('success')
  113. // })
  114. // const result = await fetchWithRetry(mockFn)
  115. // expect(result).toEqual([null, 'success'])
  116. // expect(attemptCount).toBe(2)
  117. // })
  118. // it('should stop after max retries and return last error', async () => {
  119. // const testError = new Error('Test error')
  120. // const promise = Promise.reject(testError)
  121. // const result = await fetchWithRetry(promise, 2)
  122. // expect(result).toEqual([testError])
  123. // })
  124. // it('should handle non-Error rejection with custom error', async () => {
  125. // const stringError = 'string error message'
  126. // const promise = Promise.reject(stringError)
  127. // const result = await fetchWithRetry(promise, 0)
  128. // expect(result[0]).toBeInstanceOf(Error)
  129. // expect(result[0]?.message).toBe('unknown error')
  130. // })
  131. // it('should use default 3 retries when retries parameter is not provided', async () => {
  132. // let attempts = 0
  133. // const mockFn = () => new Promise((resolve, reject) => {
  134. // attempts++
  135. // reject(new Error(`Attempt ${attempts} failed`))
  136. // })
  137. // await fetchWithRetry(mockFn())
  138. // expect(attempts).toBe(4) // Initial attempt + 3 retries
  139. // })
  140. })
  141. describe('correctModelProvider', () => {
  142. it('should return empty string for falsy input', () => {
  143. expect(correctModelProvider('')).toBe('')
  144. })
  145. it('should return the provider if it already contains a slash', () => {
  146. expect(correctModelProvider('company/model')).toBe('company/model')
  147. })
  148. it('should format google provider correctly', () => {
  149. expect(correctModelProvider('google')).toBe('langgenius/gemini/google')
  150. })
  151. it('should format standard providers correctly', () => {
  152. expect(correctModelProvider('openai')).toBe('langgenius/openai/openai')
  153. })
  154. })
  155. describe('correctToolProvider', () => {
  156. it('should return empty string for falsy input', () => {
  157. expect(correctToolProvider('')).toBe('')
  158. })
  159. it('should return the provider if toolInCollectionList is true', () => {
  160. expect(correctToolProvider('any-provider', true)).toBe('any-provider')
  161. })
  162. it('should return the provider if it already contains a slash', () => {
  163. expect(correctToolProvider('company/tool')).toBe('company/tool')
  164. })
  165. it('should format special tool providers correctly', () => {
  166. expect(correctToolProvider('stepfun')).toBe('langgenius/stepfun_tool/stepfun')
  167. expect(correctToolProvider('jina')).toBe('langgenius/jina_tool/jina')
  168. })
  169. it('should format standard tool providers correctly', () => {
  170. expect(correctToolProvider('standard')).toBe('langgenius/standard/standard')
  171. })
  172. })
  173. describe('canFindTool', () => {
  174. it('should match when IDs are identical', () => {
  175. expect(canFindTool('tool-id', 'tool-id')).toBe(true)
  176. })
  177. it('should match when provider ID is formatted with standard pattern', () => {
  178. expect(canFindTool('langgenius/tool-id/tool-id', 'tool-id')).toBe(true)
  179. })
  180. it('should match when provider ID is formatted with tool pattern', () => {
  181. expect(canFindTool('langgenius/tool-id_tool/tool-id', 'tool-id')).toBe(true)
  182. })
  183. it('should not match when IDs are completely different', () => {
  184. expect(canFindTool('provider-a', 'tool-b')).toBe(false)
  185. })
  186. })
  187. describe('removeSpecificQueryParam', () => {
  188. let originalLocation: Location
  189. let originalReplaceState: typeof window.history.replaceState
  190. beforeEach(() => {
  191. originalLocation = window.location
  192. originalReplaceState = window.history.replaceState
  193. const mockUrl = new URL('https://example.com?param1=value1&param2=value2&param3=value3')
  194. // Mock window.location using defineProperty to handle URL properly
  195. delete (window as any).location
  196. Object.defineProperty(window, 'location', {
  197. writable: true,
  198. value: {
  199. ...originalLocation,
  200. href: mockUrl.href,
  201. search: mockUrl.search,
  202. toString: () => mockUrl.toString(),
  203. },
  204. })
  205. window.history.replaceState = jest.fn()
  206. })
  207. afterEach(() => {
  208. Object.defineProperty(window, 'location', {
  209. writable: true,
  210. value: originalLocation,
  211. })
  212. window.history.replaceState = originalReplaceState
  213. })
  214. it('should remove a single query parameter', () => {
  215. removeSpecificQueryParam('param2')
  216. expect(window.history.replaceState).toHaveBeenCalledTimes(1)
  217. const replaceStateCall = (window.history.replaceState as jest.Mock).mock.calls[0]
  218. expect(replaceStateCall[0]).toBe(null)
  219. expect(replaceStateCall[1]).toBe('')
  220. expect(replaceStateCall[2]).toMatch(/param1=value1/)
  221. expect(replaceStateCall[2]).toMatch(/param3=value3/)
  222. expect(replaceStateCall[2]).not.toMatch(/param2=value2/)
  223. })
  224. it('should remove multiple query parameters', () => {
  225. removeSpecificQueryParam(['param1', 'param3'])
  226. expect(window.history.replaceState).toHaveBeenCalledTimes(1)
  227. const replaceStateCall = (window.history.replaceState as jest.Mock).mock.calls[0]
  228. expect(replaceStateCall[2]).toMatch(/param2=value2/)
  229. expect(replaceStateCall[2]).not.toMatch(/param1=value1/)
  230. expect(replaceStateCall[2]).not.toMatch(/param3=value3/)
  231. })
  232. it('should handle non-existent parameters gracefully', () => {
  233. removeSpecificQueryParam('nonexistent')
  234. expect(window.history.replaceState).toHaveBeenCalledTimes(1)
  235. const replaceStateCall = (window.history.replaceState as jest.Mock).mock.calls[0]
  236. expect(replaceStateCall[2]).toMatch(/param1=value1/)
  237. expect(replaceStateCall[2]).toMatch(/param2=value2/)
  238. expect(replaceStateCall[2]).toMatch(/param3=value3/)
  239. })
  240. })