mocks.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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/core"
  21. *
  22. * afterEach(() => {
  23. * clearMocks()
  24. * })
  25. *
  26. * test("mocked command", () => {
  27. * mockIPC((cmd, payload) => {
  28. * switch (cmd) {
  29. * case "add":
  30. * return (payload.a as number) + (payload.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/core"
  44. *
  45. * afterEach(() => {
  46. * clearMocks()
  47. * })
  48. *
  49. * test("mocked command", () => {
  50. * mockIPC((cmd, payload) => {
  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, payload: Record<string, unknown>) => unknown
  65. ): void {
  66. // eslint-disable-next-line @typescript-eslint/no-misused-promises
  67. window.__TAURI_INTERNALS__.ipc = async ({
  68. cmd,
  69. callback,
  70. error,
  71. payload
  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, payload))
  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 === "plugin:event|emit") {
  111. * console.log('emit event', args?.event, args?.payload);
  112. * }
  113. * });
  114. *
  115. * const { emit } = await import("@tauri-apps/api/event");
  116. * await emit('loaded'); // this will cause the mocked IPC handler to log to the console.
  117. * ```
  118. *
  119. * @param current Label of window this JavaScript context is running in.
  120. * @param additionalWindows Label of additional windows the app has.
  121. *
  122. * @since 1.0.0
  123. */
  124. export function mockWindows(
  125. current: string,
  126. ...additionalWindows: string[]
  127. ): void {
  128. window.__TAURI_INTERNALS__.metadata = {
  129. windows: [current, ...additionalWindows].map((label) => ({ label })),
  130. currentWindow: { label: current }
  131. }
  132. }
  133. /**
  134. * Mock `convertFileSrc` function
  135. *
  136. *
  137. * @example
  138. * ```js
  139. * import { mockConvertFileSrc } from "@tauri-apps/api/mocks";
  140. * import { convertFileSrc } from "@tauri-apps/api/core";
  141. *
  142. * mockConvertFileSrc("windows")
  143. *
  144. * const url = convertFileSrc("C:\\Users\\user\\file.txt")
  145. * ```
  146. *
  147. * @param osName The operating system to mock, can be one of linux, macos, or windows
  148. *
  149. * @since 1.6.0
  150. */
  151. export function mockConvertFileSrc(osName: string): void {
  152. window.__TAURI_INTERNALS__ = window.__TAURI_INTERNALS__ ?? {}
  153. window.__TAURI_INTERNALS__.convertFileSrc = function (
  154. filePath,
  155. protocol = 'asset'
  156. ) {
  157. const path = encodeURIComponent(filePath)
  158. return osName === 'windows'
  159. ? `http://${protocol}.localhost/${path}`
  160. : `${protocol}://localhost/${path}`
  161. }
  162. }
  163. /**
  164. * Clears mocked functions/data injected by the other functions in this module.
  165. * When using a test runner that doesn't provide a fresh window object for each test, calling this function will reset tauri specific properties.
  166. *
  167. * # Example
  168. *
  169. * ```js
  170. * import { mockWindows, clearMocks } from "@tauri-apps/api/mocks"
  171. *
  172. * afterEach(() => {
  173. * clearMocks()
  174. * })
  175. *
  176. * test("mocked windows", () => {
  177. * mockWindows("main", "second", "third");
  178. *
  179. * expect(window.__TAURI_INTERNALS__).toHaveProperty("metadata")
  180. * })
  181. *
  182. * test("no mocked windows", () => {
  183. * expect(window.__TAURI_INTERNALS__).not.toHaveProperty("metadata")
  184. * })
  185. * ```
  186. *
  187. * @since 1.0.0
  188. */
  189. export function clearMocks(): void {
  190. if (typeof window.__TAURI_INTERNALS__ !== 'object') {
  191. return
  192. }
  193. // @ts-expect-error "The operand of a 'delete' operator must be optional' does not matter in this case
  194. delete window.__TAURI_INTERNALS__.convertFileSrc
  195. // @ts-expect-error "The operand of a 'delete' operator must be optional' does not matter in this case
  196. delete window.__TAURI_INTERNALS__.ipc
  197. // @ts-expect-error "The operand of a 'delete' operator must be optional' does not matter in this case
  198. delete window.__TAURI_INTERNALS__.metadata
  199. }