mocks.ts 5.8 KB

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