mocks.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. import { InvokeArgs, InvokeOptions } from './core'
  5. function mockInternals() {
  6. window.__TAURI_INTERNALS__ = window.__TAURI_INTERNALS__ ?? {}
  7. }
  8. /**
  9. * Intercepts all IPC requests with the given mock handler.
  10. *
  11. * This function can be used when testing tauri frontend applications or when running the frontend in a Node.js context during static site generation.
  12. *
  13. * # Examples
  14. *
  15. * Testing setup using vitest:
  16. * ```js
  17. * import { mockIPC, clearMocks } from "@tauri-apps/api/mocks"
  18. * import { invoke } from "@tauri-apps/api/core"
  19. *
  20. * afterEach(() => {
  21. * clearMocks()
  22. * })
  23. *
  24. * test("mocked command", () => {
  25. * mockIPC((cmd, payload) => {
  26. * switch (cmd) {
  27. * case "add":
  28. * return (payload.a as number) + (payload.b as number);
  29. * default:
  30. * break;
  31. * }
  32. * });
  33. *
  34. * expect(invoke('add', { a: 12, b: 15 })).resolves.toBe(27);
  35. * })
  36. * ```
  37. *
  38. * The callback function can also return a Promise:
  39. * ```js
  40. * import { mockIPC, clearMocks } from "@tauri-apps/api/mocks"
  41. * import { invoke } from "@tauri-apps/api/core"
  42. *
  43. * afterEach(() => {
  44. * clearMocks()
  45. * })
  46. *
  47. * test("mocked command", () => {
  48. * mockIPC((cmd, payload) => {
  49. * if(cmd === "get_data") {
  50. * return fetch("https://example.com/data.json")
  51. * .then((response) => response.json())
  52. * }
  53. * });
  54. *
  55. * expect(invoke('get_data')).resolves.toBe({ foo: 'bar' });
  56. * })
  57. * ```
  58. *
  59. * @since 1.0.0
  60. */
  61. export function mockIPC(
  62. cb: <T>(cmd: string, payload?: InvokeArgs) => Promise<T>
  63. ): void {
  64. mockInternals()
  65. window.__TAURI_INTERNALS__.transformCallback = function transformCallback(
  66. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  67. callback?: (response: any) => void,
  68. once = false
  69. ) {
  70. const identifier = window.crypto.getRandomValues(new Uint32Array(1))[0]
  71. const prop = `_${identifier}`
  72. Object.defineProperty(window, prop, {
  73. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  74. value: (result: any) => {
  75. if (once) {
  76. Reflect.deleteProperty(window, prop)
  77. }
  78. return callback && callback(result)
  79. },
  80. writable: false,
  81. configurable: true
  82. })
  83. return identifier
  84. }
  85. window.__TAURI_INTERNALS__.invoke = function <T>(
  86. cmd: string,
  87. args?: InvokeArgs,
  88. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  89. options?: InvokeOptions
  90. ): Promise<T> {
  91. return cb(cmd, args)
  92. }
  93. }
  94. /**
  95. * Mocks one or many window labels.
  96. * In non-tauri context it is required to call this function *before* using the `@tauri-apps/api/window` module.
  97. *
  98. * This function only mocks the *presence* of windows,
  99. * window properties (e.g. width and height) can be mocked like regular IPC calls using the `mockIPC` function.
  100. *
  101. * # Examples
  102. *
  103. * ```js
  104. * import { mockWindows } from "@tauri-apps/api/mocks";
  105. * import { getCurrentWindow } from "@tauri-apps/api/window";
  106. *
  107. * mockWindows("main", "second", "third");
  108. *
  109. * const win = getCurrentWindow();
  110. *
  111. * win.label // "main"
  112. * ```
  113. *
  114. * ```js
  115. * import { mockWindows } from "@tauri-apps/api/mocks";
  116. *
  117. * mockWindows("main", "second", "third");
  118. *
  119. * mockIPC((cmd, args) => {
  120. * if (cmd === "plugin:event|emit") {
  121. * console.log('emit event', args?.event, args?.payload);
  122. * }
  123. * });
  124. *
  125. * const { emit } = await import("@tauri-apps/api/event");
  126. * await emit('loaded'); // this will cause the mocked IPC handler to log to the console.
  127. * ```
  128. *
  129. * @param current Label of window this JavaScript context is running in.
  130. *
  131. * @since 1.0.0
  132. */
  133. export function mockWindows(
  134. current: string,
  135. ..._additionalWindows: string[]
  136. ): void {
  137. mockInternals()
  138. window.__TAURI_INTERNALS__.metadata = {
  139. currentWindow: { label: current },
  140. currentWebview: { windowLabel: current, label: current }
  141. }
  142. }
  143. /**
  144. * Mock `convertFileSrc` function
  145. *
  146. *
  147. * @example
  148. * ```js
  149. * import { mockConvertFileSrc } from "@tauri-apps/api/mocks";
  150. * import { convertFileSrc } from "@tauri-apps/api/core";
  151. *
  152. * mockConvertFileSrc("windows")
  153. *
  154. * const url = convertFileSrc("C:\\Users\\user\\file.txt")
  155. * ```
  156. *
  157. * @param osName The operating system to mock, can be one of linux, macos, or windows
  158. *
  159. * @since 1.6.0
  160. */
  161. export function mockConvertFileSrc(osName: string): void {
  162. mockInternals()
  163. window.__TAURI_INTERNALS__.convertFileSrc = function (
  164. filePath,
  165. protocol = 'asset'
  166. ) {
  167. const path = encodeURIComponent(filePath)
  168. return osName === 'windows'
  169. ? `http://${protocol}.localhost/${path}`
  170. : `${protocol}://localhost/${path}`
  171. }
  172. }
  173. /**
  174. * Clears mocked functions/data injected by the other functions in this module.
  175. * When using a test runner that doesn't provide a fresh window object for each test, calling this function will reset tauri specific properties.
  176. *
  177. * # Example
  178. *
  179. * ```js
  180. * import { mockWindows, clearMocks } from "@tauri-apps/api/mocks"
  181. *
  182. * afterEach(() => {
  183. * clearMocks()
  184. * })
  185. *
  186. * test("mocked windows", () => {
  187. * mockWindows("main", "second", "third");
  188. *
  189. * expect(window.__TAURI_INTERNALS__).toHaveProperty("metadata")
  190. * })
  191. *
  192. * test("no mocked windows", () => {
  193. * expect(window.__TAURI_INTERNALS__).not.toHaveProperty("metadata")
  194. * })
  195. * ```
  196. *
  197. * @since 1.0.0
  198. */
  199. export function clearMocks(): void {
  200. if (typeof window.__TAURI_INTERNALS__ !== 'object') {
  201. return
  202. }
  203. if (window.__TAURI_INTERNALS__?.convertFileSrc)
  204. // @ts-expect-error "The operand of a 'delete' operator must be optional' does not matter in this case
  205. delete window.__TAURI_INTERNALS__.convertFileSrc
  206. if (window.__TAURI_INTERNALS__?.invoke)
  207. // @ts-expect-error "The operand of a 'delete' operator must be optional' does not matter in this case
  208. delete window.__TAURI_INTERNALS__.invoke
  209. if (window.__TAURI_INTERNALS__?.metadata)
  210. // @ts-expect-error "The operand of a 'delete' operator must be optional' does not matter in this case
  211. delete window.__TAURI_INTERNALS__.metadata
  212. }