@shopify/ui-extensions-server-kit 0.0.0-nightly-20250605112924

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 (260) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/README.md +74 -0
  3. package/dist/ExtensionServerClient/ExtensionServerClient.cjs.js +1 -0
  4. package/dist/ExtensionServerClient/ExtensionServerClient.d.ts +28 -0
  5. package/dist/ExtensionServerClient/ExtensionServerClient.es.js +133 -0
  6. package/dist/ExtensionServerClient/ExtensionServerClient.test.d.ts +1 -0
  7. package/dist/ExtensionServerClient/index.d.ts +2 -0
  8. package/dist/ExtensionServerClient/types.cjs.js +1 -0
  9. package/dist/ExtensionServerClient/types.d.ts +124 -0
  10. package/dist/ExtensionServerClient/types.es.js +4 -0
  11. package/dist/context/ExtensionServerProvider.cjs.js +1 -0
  12. package/dist/context/ExtensionServerProvider.d.ts +2 -0
  13. package/dist/context/ExtensionServerProvider.es.js +30 -0
  14. package/dist/context/ExtensionServerProvider.test.d.ts +1 -0
  15. package/dist/context/constants.cjs.js +1 -0
  16. package/dist/context/constants.d.ts +3 -0
  17. package/dist/context/constants.es.js +14 -0
  18. package/dist/context/index.d.ts +3 -0
  19. package/dist/context/types.d.ts +11 -0
  20. package/dist/hooks/index.d.ts +5 -0
  21. package/dist/hooks/useExtensionClient.cjs.js +1 -0
  22. package/dist/hooks/useExtensionClient.d.ts +1 -0
  23. package/dist/hooks/useExtensionClient.es.js +8 -0
  24. package/dist/hooks/useExtensionServerContext.cjs.js +1 -0
  25. package/dist/hooks/useExtensionServerContext.d.ts +1 -0
  26. package/dist/hooks/useExtensionServerContext.es.js +6 -0
  27. package/dist/hooks/useExtensionServerEvent.cjs.js +1 -0
  28. package/dist/hooks/useExtensionServerEvent.d.ts +1 -0
  29. package/dist/hooks/useExtensionServerEvent.es.js +9 -0
  30. package/dist/hooks/useExtensionServerState.cjs.js +1 -0
  31. package/dist/hooks/useExtensionServerState.d.ts +1 -0
  32. package/dist/hooks/useExtensionServerState.es.js +9 -0
  33. package/dist/hooks/useIsomorphicLayoutEffect.cjs.js +1 -0
  34. package/dist/hooks/useIsomorphicLayoutEffect.d.ts +2 -0
  35. package/dist/hooks/useIsomorphicLayoutEffect.es.js +5 -0
  36. package/dist/i18n.cjs.js +1 -0
  37. package/dist/i18n.d.ts +93 -0
  38. package/dist/i18n.es.js +61 -0
  39. package/dist/i18n.test.d.ts +1 -0
  40. package/dist/index.cjs.js +1 -0
  41. package/dist/index.cjs2.js +1 -0
  42. package/dist/index.d.ts +7 -0
  43. package/dist/index.es.js +55 -0
  44. package/dist/index.es2.js +8 -0
  45. package/dist/state/actions/actions.cjs.js +1 -0
  46. package/dist/state/actions/actions.d.ts +7 -0
  47. package/dist/state/actions/actions.es.js +44 -0
  48. package/dist/state/actions/index.d.ts +2 -0
  49. package/dist/state/actions/types.d.ts +25 -0
  50. package/dist/state/index.d.ts +2 -0
  51. package/dist/state/reducers/constants.cjs.js +1 -0
  52. package/dist/state/reducers/constants.d.ts +2 -0
  53. package/dist/state/reducers/constants.es.js +7 -0
  54. package/dist/state/reducers/extensionServerReducer.cjs.js +1 -0
  55. package/dist/state/reducers/extensionServerReducer.d.ts +3 -0
  56. package/dist/state/reducers/extensionServerReducer.es.js +57 -0
  57. package/dist/state/reducers/extensionServerReducer.test.d.ts +1 -0
  58. package/dist/state/reducers/index.d.ts +3 -0
  59. package/dist/state/reducers/types.d.ts +6 -0
  60. package/dist/testing/MockExtensionServerProvider.cjs.js +1 -0
  61. package/dist/testing/MockExtensionServerProvider.d.ts +7 -0
  62. package/dist/testing/MockExtensionServerProvider.es.js +24 -0
  63. package/dist/testing/app.cjs.js +1 -0
  64. package/dist/testing/app.d.ts +2 -0
  65. package/dist/testing/app.es.js +16 -0
  66. package/dist/testing/extensions.cjs.js +1 -0
  67. package/dist/testing/extensions.d.ts +6 -0
  68. package/dist/testing/extensions.es.js +65 -0
  69. package/dist/testing/index.d.ts +3 -0
  70. package/dist/types.cjs.js +1 -0
  71. package/dist/types.d.ts +181 -0
  72. package/dist/types.es.js +4 -0
  73. package/dist/utilities/assetToString.cjs.js +1 -0
  74. package/dist/utilities/assetToString.d.ts +2 -0
  75. package/dist/utilities/assetToString.es.js +7 -0
  76. package/dist/utilities/assetToString.test.d.ts +1 -0
  77. package/dist/utilities/groupByKey.cjs.js +1 -0
  78. package/dist/utilities/groupByKey.d.ts +3 -0
  79. package/dist/utilities/groupByKey.es.js +6 -0
  80. package/dist/utilities/index.d.ts +7 -0
  81. package/dist/utilities/isUIExtension.cjs.js +1 -0
  82. package/dist/utilities/isUIExtension.d.ts +1 -0
  83. package/dist/utilities/isUIExtension.es.js +6 -0
  84. package/dist/utilities/isValidSurface.cjs.js +1 -0
  85. package/dist/utilities/isValidSurface.d.ts +2 -0
  86. package/dist/utilities/isValidSurface.es.js +7 -0
  87. package/dist/utilities/noop.cjs.js +1 -0
  88. package/dist/utilities/noop.d.ts +1 -0
  89. package/dist/utilities/noop.es.js +5 -0
  90. package/dist/utilities/replaceUpdated.cjs.js +1 -0
  91. package/dist/utilities/replaceUpdated.d.ts +1 -0
  92. package/dist/utilities/replaceUpdated.es.js +14 -0
  93. package/dist/utilities/replaceUpdated.test.d.ts +1 -0
  94. package/dist/utilities/set.cjs.js +1 -0
  95. package/dist/utilities/set.d.ts +4 -0
  96. package/dist/utilities/set.es.js +18 -0
  97. package/dist/utilities/set.test.d.ts +1 -0
  98. package/index.d.ts +1 -0
  99. package/index.js +1 -0
  100. package/index.mjs +1 -0
  101. package/node_modules/@shopify/react-testing/LICENSE.md +21 -0
  102. package/node_modules/@shopify/react-testing/README.md +711 -0
  103. package/node_modules/@shopify/react-testing/build/cjs/TestWrapper.js +52 -0
  104. package/node_modules/@shopify/react-testing/build/cjs/_virtual/_rollupPluginBabelHelpers.js +47 -0
  105. package/node_modules/@shopify/react-testing/build/cjs/compat.js +14 -0
  106. package/node_modules/@shopify/react-testing/build/cjs/destroy.js +13 -0
  107. package/node_modules/@shopify/react-testing/build/cjs/element.js +225 -0
  108. package/node_modules/@shopify/react-testing/build/cjs/index.js +21 -0
  109. package/node_modules/@shopify/react-testing/build/cjs/matchers/components.js +46 -0
  110. package/node_modules/@shopify/react-testing/build/cjs/matchers/context.js +25 -0
  111. package/node_modules/@shopify/react-testing/build/cjs/matchers/index.js +16 -0
  112. package/node_modules/@shopify/react-testing/build/cjs/matchers/props.js +38 -0
  113. package/node_modules/@shopify/react-testing/build/cjs/matchers/strings.js +42 -0
  114. package/node_modules/@shopify/react-testing/build/cjs/matchers/utilities.js +110 -0
  115. package/node_modules/@shopify/react-testing/build/cjs/mount.js +76 -0
  116. package/node_modules/@shopify/react-testing/build/cjs/root.js +284 -0
  117. package/node_modules/@shopify/react-testing/build/cjs/toReactString.js +86 -0
  118. package/node_modules/@shopify/react-testing/build/cjs/types.js +28 -0
  119. package/node_modules/@shopify/react-testing/build/esm/TestWrapper.mjs +44 -0
  120. package/node_modules/@shopify/react-testing/build/esm/_virtual/_rollupPluginBabelHelpers.mjs +42 -0
  121. package/node_modules/@shopify/react-testing/build/esm/compat.mjs +10 -0
  122. package/node_modules/@shopify/react-testing/build/esm/destroy.mjs +9 -0
  123. package/node_modules/@shopify/react-testing/build/esm/element.mjs +221 -0
  124. package/node_modules/@shopify/react-testing/build/esm/index.mjs +5 -0
  125. package/node_modules/@shopify/react-testing/build/esm/matchers/components.mjs +41 -0
  126. package/node_modules/@shopify/react-testing/build/esm/matchers/context.mjs +21 -0
  127. package/node_modules/@shopify/react-testing/build/esm/matchers/index.mjs +14 -0
  128. package/node_modules/@shopify/react-testing/build/esm/matchers/props.mjs +33 -0
  129. package/node_modules/@shopify/react-testing/build/esm/matchers/strings.mjs +37 -0
  130. package/node_modules/@shopify/react-testing/build/esm/matchers/utilities.mjs +101 -0
  131. package/node_modules/@shopify/react-testing/build/esm/mount.mjs +70 -0
  132. package/node_modules/@shopify/react-testing/build/esm/root.mjs +275 -0
  133. package/node_modules/@shopify/react-testing/build/esm/toReactString.mjs +80 -0
  134. package/node_modules/@shopify/react-testing/build/esm/types.mjs +26 -0
  135. package/node_modules/@shopify/react-testing/build/esnext/TestWrapper.esnext +44 -0
  136. package/node_modules/@shopify/react-testing/build/esnext/compat.esnext +10 -0
  137. package/node_modules/@shopify/react-testing/build/esnext/destroy.esnext +9 -0
  138. package/node_modules/@shopify/react-testing/build/esnext/element.esnext +221 -0
  139. package/node_modules/@shopify/react-testing/build/esnext/index.esnext +5 -0
  140. package/node_modules/@shopify/react-testing/build/esnext/matchers/components.esnext +41 -0
  141. package/node_modules/@shopify/react-testing/build/esnext/matchers/context.esnext +21 -0
  142. package/node_modules/@shopify/react-testing/build/esnext/matchers/index.esnext +14 -0
  143. package/node_modules/@shopify/react-testing/build/esnext/matchers/props.esnext +33 -0
  144. package/node_modules/@shopify/react-testing/build/esnext/matchers/strings.esnext +37 -0
  145. package/node_modules/@shopify/react-testing/build/esnext/matchers/utilities.esnext +99 -0
  146. package/node_modules/@shopify/react-testing/build/esnext/mount.esnext +71 -0
  147. package/node_modules/@shopify/react-testing/build/esnext/root.esnext +275 -0
  148. package/node_modules/@shopify/react-testing/build/esnext/toReactString.esnext +80 -0
  149. package/node_modules/@shopify/react-testing/build/esnext/types.esnext +26 -0
  150. package/node_modules/@shopify/react-testing/build/ts/TestWrapper.d.ts +17 -0
  151. package/node_modules/@shopify/react-testing/build/ts/TestWrapper.d.ts.map +1 -0
  152. package/node_modules/@shopify/react-testing/build/ts/compat.d.ts +3 -0
  153. package/node_modules/@shopify/react-testing/build/ts/compat.d.ts.map +1 -0
  154. package/node_modules/@shopify/react-testing/build/ts/destroy.d.ts +2 -0
  155. package/node_modules/@shopify/react-testing/build/ts/destroy.d.ts.map +1 -0
  156. package/node_modules/@shopify/react-testing/build/ts/element.d.ts +42 -0
  157. package/node_modules/@shopify/react-testing/build/ts/element.d.ts.map +1 -0
  158. package/node_modules/@shopify/react-testing/build/ts/index.d.ts +7 -0
  159. package/node_modules/@shopify/react-testing/build/ts/index.d.ts.map +1 -0
  160. package/node_modules/@shopify/react-testing/build/ts/matchers/components.d.ts +12 -0
  161. package/node_modules/@shopify/react-testing/build/ts/matchers/components.d.ts.map +1 -0
  162. package/node_modules/@shopify/react-testing/build/ts/matchers/context.d.ts +8 -0
  163. package/node_modules/@shopify/react-testing/build/ts/matchers/context.d.ts.map +1 -0
  164. package/node_modules/@shopify/react-testing/build/ts/matchers/index.d.ts +20 -0
  165. package/node_modules/@shopify/react-testing/build/ts/matchers/index.d.ts.map +1 -0
  166. package/node_modules/@shopify/react-testing/build/ts/matchers/props.d.ts +10 -0
  167. package/node_modules/@shopify/react-testing/build/ts/matchers/props.d.ts.map +1 -0
  168. package/node_modules/@shopify/react-testing/build/ts/matchers/strings.d.ts +11 -0
  169. package/node_modules/@shopify/react-testing/build/ts/matchers/strings.d.ts.map +1 -0
  170. package/node_modules/@shopify/react-testing/build/ts/matchers/utilities.d.ts +17 -0
  171. package/node_modules/@shopify/react-testing/build/ts/matchers/utilities.d.ts.map +1 -0
  172. package/node_modules/@shopify/react-testing/build/ts/mount.d.ts +39 -0
  173. package/node_modules/@shopify/react-testing/build/ts/mount.d.ts.map +1 -0
  174. package/node_modules/@shopify/react-testing/build/ts/root.d.ts +55 -0
  175. package/node_modules/@shopify/react-testing/build/ts/root.d.ts.map +1 -0
  176. package/node_modules/@shopify/react-testing/build/ts/toReactString.d.ts +5 -0
  177. package/node_modules/@shopify/react-testing/build/ts/toReactString.d.ts.map +1 -0
  178. package/node_modules/@shopify/react-testing/build/ts/types.d.ts +89 -0
  179. package/node_modules/@shopify/react-testing/build/ts/types.d.ts.map +1 -0
  180. package/node_modules/@shopify/react-testing/index.esnext +1 -0
  181. package/node_modules/@shopify/react-testing/index.js +1 -0
  182. package/node_modules/@shopify/react-testing/index.mjs +1 -0
  183. package/node_modules/@shopify/react-testing/matchers.esnext +1 -0
  184. package/node_modules/@shopify/react-testing/matchers.js +1 -0
  185. package/node_modules/@shopify/react-testing/matchers.mjs +1 -0
  186. package/node_modules/@shopify/react-testing/package.json +69 -0
  187. package/node_modules/@shopify/ui-extensions-test-utils/CHANGELOG.md +66 -0
  188. package/node_modules/@shopify/ui-extensions-test-utils/dist/index.d.ts +3 -0
  189. package/node_modules/@shopify/ui-extensions-test-utils/dist/index.js +3 -0
  190. package/node_modules/@shopify/ui-extensions-test-utils/dist/render.d.ts +2 -0
  191. package/node_modules/@shopify/ui-extensions-test-utils/dist/render.js +5 -0
  192. package/node_modules/@shopify/ui-extensions-test-utils/dist/renderHook.d.ts +17 -0
  193. package/node_modules/@shopify/ui-extensions-test-utils/dist/renderHook.js +20 -0
  194. package/node_modules/@shopify/ui-extensions-test-utils/dist/withProviders.d.ts +9 -0
  195. package/node_modules/@shopify/ui-extensions-test-utils/dist/withProviders.js +7 -0
  196. package/node_modules/@shopify/ui-extensions-test-utils/package.json +40 -0
  197. package/node_modules/@shopify/ui-extensions-test-utils/project.json +39 -0
  198. package/node_modules/@types/react/LICENSE +21 -0
  199. package/node_modules/@types/react/README.md +16 -0
  200. package/node_modules/@types/react/experimental.d.ts +192 -0
  201. package/node_modules/@types/react/global.d.ts +151 -0
  202. package/node_modules/@types/react/index.d.ts +3175 -0
  203. package/node_modules/@types/react/jsx-dev-runtime.d.ts +2 -0
  204. package/node_modules/@types/react/jsx-runtime.d.ts +2 -0
  205. package/node_modules/@types/react/package.json +149 -0
  206. package/node_modules/@vitejs/plugin-react-refresh/LICENSE +21 -0
  207. package/node_modules/@vitejs/plugin-react-refresh/README.md +73 -0
  208. package/node_modules/@vitejs/plugin-react-refresh/index.d.ts +14 -0
  209. package/node_modules/@vitejs/plugin-react-refresh/index.js +239 -0
  210. package/node_modules/@vitejs/plugin-react-refresh/package.json +35 -0
  211. package/package.json +67 -0
  212. package/project.json +72 -0
  213. package/scripts/create-entry-files.ts +44 -0
  214. package/src/ExtensionServerClient/ExtensionServerClient.test.ts +730 -0
  215. package/src/ExtensionServerClient/ExtensionServerClient.ts +311 -0
  216. package/src/ExtensionServerClient/index.ts +2 -0
  217. package/src/ExtensionServerClient/types.ts +161 -0
  218. package/src/context/ExtensionServerProvider.test.tsx +173 -0
  219. package/src/context/ExtensionServerProvider.tsx +48 -0
  220. package/src/context/constants.ts +15 -0
  221. package/src/context/index.ts +3 -0
  222. package/src/context/types.ts +13 -0
  223. package/src/hooks/index.ts +5 -0
  224. package/src/hooks/useExtensionClient.ts +6 -0
  225. package/src/hooks/useExtensionServerContext.ts +4 -0
  226. package/src/hooks/useExtensionServerEvent.ts +11 -0
  227. package/src/hooks/useExtensionServerState.ts +6 -0
  228. package/src/hooks/useIsomorphicLayoutEffect.ts +6 -0
  229. package/src/i18n.test.ts +417 -0
  230. package/src/i18n.ts +208 -0
  231. package/src/index.ts +7 -0
  232. package/src/state/actions/actions.ts +43 -0
  233. package/src/state/actions/index.ts +2 -0
  234. package/src/state/actions/types.ts +37 -0
  235. package/src/state/index.ts +2 -0
  236. package/src/state/reducers/constants.ts +6 -0
  237. package/src/state/reducers/extensionServerReducer.test.ts +174 -0
  238. package/src/state/reducers/extensionServerReducer.ts +87 -0
  239. package/src/state/reducers/index.ts +3 -0
  240. package/src/state/reducers/types.ts +7 -0
  241. package/src/testing/MockExtensionServerProvider.tsx +36 -0
  242. package/src/testing/app.ts +15 -0
  243. package/src/testing/extensions.ts +70 -0
  244. package/src/testing/index.ts +3 -0
  245. package/src/types.ts +180 -0
  246. package/src/utilities/assetToString.test.ts +16 -0
  247. package/src/utilities/assetToString.ts +8 -0
  248. package/src/utilities/groupByKey.ts +3 -0
  249. package/src/utilities/index.ts +7 -0
  250. package/src/utilities/isUIExtension.ts +7 -0
  251. package/src/utilities/isValidSurface.ts +7 -0
  252. package/src/utilities/noop.ts +1 -0
  253. package/src/utilities/replaceUpdated.test.ts +26 -0
  254. package/src/utilities/replaceUpdated.ts +17 -0
  255. package/src/utilities/set.test.ts +19 -0
  256. package/src/utilities/set.ts +30 -0
  257. package/testing.d.ts +1 -0
  258. package/testing.js +1 -0
  259. package/testing.mjs +1 -0
  260. package/tests/setup.ts +6 -0
@@ -0,0 +1,730 @@
1
+ import {ExtensionServerClient} from './ExtensionServerClient'
2
+ import {mockApp} from '../testing'
3
+ import WS from 'jest-websocket-mock'
4
+ import {Localization} from 'i18n.js'
5
+
6
+ const defaultOptions = {
7
+ connection: {url: 'ws://example-host.com:8000/extensions/'},
8
+ }
9
+
10
+ describe('ExtensionServerClient', () => {
11
+ let socket: WS
12
+
13
+ async function setup(options: ExtensionServer.Options = defaultOptions) {
14
+ if (!options.connection.url) {
15
+ throw new Error('Please set a URL')
16
+ }
17
+ socket = new WS(options.connection.url, {jsonProtocol: true})
18
+ const client = new ExtensionServerClient(options)
19
+ if (options.connection.automaticConnect !== false) {
20
+ await socket.connected
21
+ }
22
+
23
+ return {socket, client, options}
24
+ }
25
+
26
+ afterEach(() => {
27
+ socket.close()
28
+ })
29
+
30
+ describe('initialization', () => {
31
+ test('connects to the target websocket', async () => {
32
+ const {socket, client} = await setup()
33
+
34
+ expect(client.connection).toBeDefined()
35
+ expect(socket.server.clients()).toHaveLength(1)
36
+
37
+ socket.close()
38
+ })
39
+
40
+ test('does not connect to the target websocket if "automaticConnect" is false', async () => {
41
+ const {client, socket} = await setup({
42
+ connection: {automaticConnect: false, url: 'ws://example-host.com:8000/extensions/'},
43
+ })
44
+
45
+ expect(client.connection).toBeUndefined()
46
+ expect(socket.server.clients()).toHaveLength(0)
47
+
48
+ socket.close()
49
+ })
50
+ })
51
+
52
+ describe('on()', () => {
53
+ test('sends data with extensions filtered by surface option on "connected" event', async () => {
54
+ const {socket, client} = await setup({...defaultOptions, surface: 'admin'})
55
+ const connectSpy = vi.fn()
56
+ const data = {
57
+ app: mockApp(),
58
+ extensions: [
59
+ {uuid: '123', surface: 'admin'},
60
+ {uuid: '456', surface: 'checkout'},
61
+ {uuid: '456', surface: '', extensionPoints: [{surface: 'admin'}]},
62
+ ],
63
+ }
64
+
65
+ client.on('connected', connectSpy)
66
+ socket.send({event: 'connected', data})
67
+
68
+ expect(connectSpy).toHaveBeenCalledTimes(1)
69
+ expect(connectSpy).toHaveBeenCalledWith(
70
+ expect.objectContaining({
71
+ extensions: [
72
+ {uuid: '123', surface: 'admin'},
73
+ {uuid: '456', surface: '', extensionPoints: [{surface: 'admin'}]},
74
+ ],
75
+ }),
76
+ )
77
+
78
+ socket.close()
79
+ })
80
+
81
+ test('sends data with all extensions when surface option is not valid on "connected" event', async () => {
82
+ const {socket, client} = await setup({...defaultOptions, surface: 'abc' as any})
83
+ const connectSpy = vi.fn()
84
+ const data = {
85
+ app: mockApp(),
86
+ extensions: [
87
+ {uuid: '123', surface: 'admin'},
88
+ {uuid: '456', surface: 'checkout'},
89
+ ],
90
+ }
91
+
92
+ client.on('connected', connectSpy)
93
+ socket.send({event: 'connected', data})
94
+
95
+ expect(connectSpy).toHaveBeenCalledTimes(1)
96
+ expect(connectSpy).toHaveBeenCalledWith(
97
+ expect.objectContaining({
98
+ extensions: data.extensions,
99
+ }),
100
+ )
101
+
102
+ socket.close()
103
+ })
104
+
105
+ test('sends data with translatable props as-is for UI extensions when locales option is not provided on "connected" event', async () => {
106
+ const {socket, client} = await setup()
107
+ const connectSpy = vi.fn()
108
+ const localization: Localization = {
109
+ defaultLocale: 'en',
110
+ translations: {
111
+ ja: {
112
+ welcome: 'いらっしゃいませ!',
113
+ },
114
+ en: {
115
+ welcome: 'Welcome!',
116
+ },
117
+ fr: {
118
+ welcome: 'Bienvenue!',
119
+ },
120
+ },
121
+ lastUpdated: 1684164163736,
122
+ }
123
+
124
+ const data = {
125
+ app: mockApp(),
126
+ extensions: [
127
+ {
128
+ uuid: '123',
129
+ type: 'ui_extension',
130
+ localization,
131
+ extensionPoints: [{localization}],
132
+ },
133
+ {uuid: '456', type: 'ui_extension', localization: null, extensionPoints: [{localization: null}]},
134
+ {uuid: '789', type: 'product_subscription'},
135
+ ],
136
+ }
137
+
138
+ client.on('connected', connectSpy)
139
+ socket.send({event: 'connected', data})
140
+
141
+ expect(connectSpy).toHaveBeenCalledTimes(1)
142
+ expect(connectSpy).toHaveBeenCalledWith(
143
+ expect.objectContaining({
144
+ extensions: data.extensions,
145
+ }),
146
+ )
147
+
148
+ socket.close()
149
+ })
150
+
151
+ test('sends data with translated props for UI extensions when locales option is provided on "connected" event', async () => {
152
+ const {socket, client} = await setup({...defaultOptions, locales: {user: 'ja', shop: 'fr'}})
153
+ const connectSpy = vi.fn()
154
+ const localization: Localization = {
155
+ defaultLocale: 'en',
156
+ translations: {
157
+ ja: {
158
+ welcome: 'いらっしゃいませ!',
159
+ description: '拡張子の説明',
160
+ },
161
+ en: {
162
+ welcome: 'Welcome!',
163
+ description: 'Extension description',
164
+ },
165
+ fr: {
166
+ welcome: 'Bienvenue!',
167
+ description: "Description de l'extension",
168
+ },
169
+ },
170
+ lastUpdated: 1684164163736,
171
+ }
172
+
173
+ const translatedLocalization = {
174
+ extensionLocale: 'ja',
175
+ translations: '{"welcome":"いらっしゃいませ!","description":"拡張子の説明"}',
176
+ lastUpdated: localization.lastUpdated,
177
+ }
178
+
179
+ const data = {
180
+ app: mockApp(),
181
+ extensions: [
182
+ {
183
+ uuid: '123',
184
+ type: 'ui_extension',
185
+ name: 't:welcome',
186
+ description: 't:description',
187
+ localization,
188
+ extensionPoints: [{localization}],
189
+ },
190
+ {
191
+ uuid: '456',
192
+ type: 'ui_extension',
193
+ name: 'Fixed name t:',
194
+ localization: null,
195
+ extensionPoints: [{localization: null, name: 'Fixed name t:'}],
196
+ },
197
+ {uuid: '789', type: 'product_subscription', name: 'Extension 789'},
198
+ ],
199
+ }
200
+
201
+ client.on('connected', connectSpy)
202
+ socket.send({event: 'connected', data})
203
+
204
+ expect(connectSpy).toHaveBeenCalledTimes(1)
205
+ expect(connectSpy).toHaveBeenCalledWith(
206
+ expect.objectContaining({
207
+ extensions: [
208
+ {
209
+ uuid: '123',
210
+ type: 'ui_extension',
211
+ name: 'いらっしゃいませ!',
212
+ description: '拡張子の説明',
213
+ localization: translatedLocalization,
214
+ extensionPoints: [
215
+ {
216
+ localization: translatedLocalization,
217
+ name: 'いらっしゃいませ!',
218
+ description: '拡張子の説明',
219
+ },
220
+ ],
221
+ },
222
+ {
223
+ uuid: '456',
224
+ type: 'ui_extension',
225
+ name: 'Fixed name t:',
226
+ localization: null,
227
+ extensionPoints: [{localization: null, name: 'Fixed name t:'}],
228
+ },
229
+ {uuid: '789', type: 'product_subscription', name: 'Extension 789'},
230
+ ],
231
+ }),
232
+ )
233
+
234
+ socket.close()
235
+ })
236
+
237
+ test('sends data with extensions filtered by surface option on "update" event', async () => {
238
+ const {socket, client} = await setup({...defaultOptions, surface: 'admin'})
239
+ const updateSpy = vi.fn()
240
+ const data = {
241
+ app: mockApp(),
242
+ extensions: [
243
+ {uuid: '123', surface: 'admin'},
244
+ {uuid: '456', surface: 'checkout'},
245
+ {uuid: '789', surface: '', extensionPoints: [{surface: 'admin'}]},
246
+ ],
247
+ }
248
+
249
+ client.on('update', updateSpy)
250
+ socket.send({event: 'update', data})
251
+
252
+ expect(updateSpy).toHaveBeenCalledTimes(1)
253
+ expect(updateSpy).toHaveBeenCalledWith(
254
+ expect.objectContaining({
255
+ extensions: [
256
+ {uuid: '123', surface: 'admin'},
257
+ {uuid: '789', surface: '', extensionPoints: [{surface: 'admin'}]},
258
+ ],
259
+ }),
260
+ )
261
+
262
+ socket.close()
263
+ })
264
+
265
+ test('sends data with all extensions when surface option is not valid on "update" event', async () => {
266
+ const {socket, client} = await setup({...defaultOptions, surface: 'abc' as any})
267
+ const updateSpy = vi.fn()
268
+ const data = {
269
+ app: mockApp(),
270
+ extensions: [
271
+ {uuid: '123', surface: 'admin'},
272
+ {uuid: '456', surface: 'checkout'},
273
+ ],
274
+ }
275
+
276
+ client.on('update', updateSpy)
277
+ socket.send({event: 'update', data})
278
+
279
+ expect(updateSpy).toHaveBeenCalledTimes(1)
280
+ expect(updateSpy).toHaveBeenCalledWith(
281
+ expect.objectContaining({
282
+ extensions: data.extensions,
283
+ }),
284
+ )
285
+
286
+ socket.close()
287
+ })
288
+
289
+ test('sends data with translatable props as-is when locales option is not provided on "update" event', async () => {
290
+ const {socket, client} = await setup()
291
+ const updateSpy = vi.fn()
292
+ const localization: Localization = {
293
+ defaultLocale: 'en',
294
+ translations: {
295
+ ja: {
296
+ welcome: 'いらっしゃいませ!',
297
+ description: '拡張子の説明',
298
+ },
299
+ en: {
300
+ welcome: 'Welcome!',
301
+ description: 'Extension description',
302
+ },
303
+ fr: {
304
+ welcome: 'Bienvenue!',
305
+ description: "Description de l'extension",
306
+ },
307
+ },
308
+ lastUpdated: 1684164163736,
309
+ }
310
+
311
+ const data = {
312
+ app: mockApp(),
313
+ extensions: [
314
+ {
315
+ uuid: '123',
316
+ type: 'ui_extension',
317
+ name: 't:welcome',
318
+ description: 't:description',
319
+ localization,
320
+ extensionPoints: [{localization}],
321
+ },
322
+ {
323
+ uuid: '456',
324
+ type: 'ui_extension',
325
+ name: 'Extension 456',
326
+ localization: null,
327
+ extensionPoints: [{localization: null}],
328
+ },
329
+ {uuid: '789', type: 'product_subscription'},
330
+ ],
331
+ }
332
+
333
+ client.on('update', updateSpy)
334
+ socket.send({event: 'update', data})
335
+
336
+ expect(updateSpy).toHaveBeenCalledTimes(1)
337
+ expect(updateSpy).toHaveBeenCalledWith(
338
+ expect.objectContaining({
339
+ extensions: data.extensions,
340
+ }),
341
+ )
342
+
343
+ socket.close()
344
+ })
345
+
346
+ test('sends data with translated props when locales option is provided on "update" event', async () => {
347
+ const {socket, client} = await setup({...defaultOptions, locales: {user: 'ja', shop: 'fr'}})
348
+ const updateSpy = vi.fn()
349
+ const localization: Localization = {
350
+ defaultLocale: 'en',
351
+ translations: {
352
+ ja: {
353
+ welcome: 'いらっしゃいませ!',
354
+ description: '拡張子の説明',
355
+ },
356
+ en: {
357
+ welcome: 'Welcome!',
358
+ description: 'Extension description',
359
+ },
360
+ fr: {
361
+ welcome: 'Bienvenue!',
362
+ description: "Description de l'extension",
363
+ },
364
+ },
365
+ lastUpdated: 1684164163736,
366
+ }
367
+
368
+ const translatedLocalization = {
369
+ extensionLocale: 'ja',
370
+ translations: '{"welcome":"いらっしゃいませ!","description":"拡張子の説明"}',
371
+ lastUpdated: localization.lastUpdated,
372
+ }
373
+
374
+ const data = {
375
+ app: mockApp(),
376
+ extensions: [
377
+ {
378
+ uuid: '123',
379
+ type: 'ui_extension',
380
+ name: 't:welcome',
381
+ description: 't:description',
382
+ localization,
383
+ extensionPoints: [{localization}],
384
+ },
385
+ {
386
+ uuid: '456',
387
+ type: 'ui_extension',
388
+ name: 'Extension 456',
389
+ description: 'This is a test extension',
390
+ localization: null,
391
+ extensionPoints: [{localization: null}],
392
+ },
393
+ {uuid: '789', name: 'Extension 789', type: 'product_subscription'},
394
+ ],
395
+ }
396
+
397
+ client.on('update', updateSpy)
398
+ socket.send({event: 'update', data})
399
+
400
+ expect(updateSpy).toHaveBeenCalledTimes(1)
401
+ expect(updateSpy).toHaveBeenCalledWith(
402
+ expect.objectContaining({
403
+ extensions: [
404
+ {
405
+ uuid: '123',
406
+ type: 'ui_extension',
407
+ name: 'いらっしゃいませ!',
408
+ description: '拡張子の説明',
409
+ localization: translatedLocalization,
410
+ extensionPoints: [
411
+ {localization: translatedLocalization, name: 'いらっしゃいませ!', description: '拡張子の説明'},
412
+ ],
413
+ },
414
+ {
415
+ uuid: '456',
416
+ type: 'ui_extension',
417
+ name: 'Extension 456',
418
+ description: 'This is a test extension',
419
+ localization: null,
420
+ extensionPoints: [{localization: null}],
421
+ },
422
+ {uuid: '789', type: 'product_subscription', name: 'Extension 789'},
423
+ ],
424
+ }),
425
+ )
426
+
427
+ socket.close()
428
+ })
429
+
430
+ test('sends data with translated props when locales option is provided on subsequent "update" events', async () => {
431
+ const {socket, client} = await setup({...defaultOptions, locales: {user: 'ja', shop: 'fr'}})
432
+ const updateSpy = vi.fn()
433
+ const localization: Localization = {
434
+ defaultLocale: 'en',
435
+ translations: {
436
+ ja: {
437
+ welcome: 'いらっしゃいませ!',
438
+ description: '拡張子の説明',
439
+ },
440
+ en: {
441
+ welcome: 'Welcome!',
442
+ description: 'Extension description',
443
+ },
444
+ fr: {
445
+ welcome: 'Bienvenue!',
446
+ description: "Description de l'extension",
447
+ },
448
+ },
449
+ lastUpdated: 1684164163736,
450
+ }
451
+
452
+ const translatedLocalization = {
453
+ extensionLocale: 'ja',
454
+ translations: '{"welcome":"いらっしゃいませ!","description":"拡張子の説明"}',
455
+ lastUpdated: localization.lastUpdated,
456
+ }
457
+
458
+ const data = {
459
+ app: mockApp(),
460
+ extensions: [
461
+ {
462
+ uuid: '123',
463
+ type: 'ui_extension',
464
+ name: 't:welcome',
465
+ description: 't:description',
466
+ localization,
467
+ extensionPoints: [{localization}],
468
+ },
469
+ {
470
+ uuid: '456',
471
+ type: 'ui_extension',
472
+ name: 'Extension 456',
473
+ description: 'This is a test extension',
474
+ localization: null,
475
+ extensionPoints: [{localization: null}],
476
+ },
477
+ {uuid: '789', type: 'product_subscription', name: 'Extension 789'},
478
+ ],
479
+ }
480
+
481
+ client.on('update', updateSpy)
482
+ socket.send({event: 'update', data})
483
+ socket.send({event: 'update', data})
484
+
485
+ expect(updateSpy).toHaveBeenNthCalledWith(
486
+ 2,
487
+ expect.objectContaining({
488
+ extensions: [
489
+ {
490
+ uuid: '123',
491
+ type: 'ui_extension',
492
+ name: 'いらっしゃいませ!',
493
+ description: '拡張子の説明',
494
+ localization: translatedLocalization,
495
+ extensionPoints: [
496
+ {localization: translatedLocalization, name: 'いらっしゃいませ!', description: '拡張子の説明'},
497
+ ],
498
+ },
499
+ {
500
+ uuid: '456',
501
+ type: 'ui_extension',
502
+ name: 'Extension 456',
503
+ description: 'This is a test extension',
504
+ localization: null,
505
+ extensionPoints: [{localization: null}],
506
+ },
507
+ {uuid: '789', type: 'product_subscription', name: 'Extension 789'},
508
+ ],
509
+ }),
510
+ )
511
+
512
+ socket.close()
513
+ })
514
+
515
+ test('listens to persist events', async () => {
516
+ const {socket, client} = await setup()
517
+ const updateSpy = vi.fn()
518
+ const data = {
519
+ app: mockApp(),
520
+ }
521
+
522
+ client.on('update', updateSpy)
523
+ socket.send({event: 'update', data})
524
+
525
+ expect(updateSpy).toHaveBeenCalledTimes(1)
526
+ expect(updateSpy).toHaveBeenCalledWith(data)
527
+
528
+ socket.close()
529
+ })
530
+
531
+ test('unsubscribes from persist events', async () => {
532
+ const {socket, client} = await setup()
533
+ const updateSpy = vi.fn()
534
+ const unsubscribe = client.on('update', updateSpy)
535
+
536
+ unsubscribe()
537
+ socket.send({
538
+ event: 'update',
539
+ data: {
540
+ app: mockApp(),
541
+ },
542
+ })
543
+
544
+ expect(updateSpy).toHaveBeenCalledTimes(0)
545
+
546
+ socket.close()
547
+ })
548
+
549
+ test('listens to dispatch events', async () => {
550
+ const {socket, client} = await setup()
551
+ const unfocusSpy = vi.fn()
552
+
553
+ client.on('unfocus', unfocusSpy)
554
+ socket.send({event: 'dispatch', data: {type: 'unfocus'}})
555
+
556
+ expect(unfocusSpy).toHaveBeenCalledTimes(1)
557
+ expect(unfocusSpy).toHaveBeenCalledWith(undefined)
558
+
559
+ socket.close()
560
+ })
561
+ })
562
+
563
+ describe('emit()', () => {
564
+ test('emits an event', async () => {
565
+ const {socket, client} = await setup()
566
+ const data = {data: {type: 'unfocus'}, event: 'dispatch'}
567
+
568
+ client.emit('unfocus')
569
+
570
+ await expect(socket).toReceiveMessage(data)
571
+
572
+ socket.close()
573
+ })
574
+
575
+ test('warns if trying to "emit" a persist event', async () => {
576
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
577
+ const {socket, client} = await setup()
578
+
579
+ client.emit('update' as any, {})
580
+
581
+ expect(warnSpy).toHaveBeenCalled()
582
+
583
+ socket.close()
584
+ warnSpy.mockRestore()
585
+ })
586
+ })
587
+
588
+ describe('persist()', () => {
589
+ test('persists a mutation', async () => {
590
+ const {socket, client} = await setup()
591
+ const data = {event: 'update', data: {extensions: [{uuid: '123'}]}}
592
+
593
+ client.persist('update', {extensions: [{uuid: '123'}]})
594
+
595
+ await expect(socket).toReceiveMessage(data)
596
+
597
+ socket.close()
598
+ })
599
+
600
+ test('warns if trying to "persist" a dispatch event', async () => {
601
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
602
+ const {socket, client} = await setup()
603
+
604
+ client.persist('unfocus' as any, {})
605
+
606
+ expect(warnSpy).toHaveBeenCalled()
607
+
608
+ socket.close()
609
+ warnSpy.mockRestore()
610
+ })
611
+
612
+ test('remove translated props from the UI extensions payload when locales are provided in the client options', async () => {
613
+ const {socket, client} = await setup({connection: defaultOptions.connection, locales: {user: 'ja', shop: 'fr'}})
614
+ const data = {
615
+ event: 'update',
616
+ data: {
617
+ extensions: [{uuid: '123', type: 'ui_extension', extensionPoints: [{}]}],
618
+ },
619
+ }
620
+
621
+ client.persist('update', {
622
+ extensions: [
623
+ {
624
+ uuid: '123',
625
+ type: 'ui_extension',
626
+ name: 'いらっしゃいませ!',
627
+ description: '拡張子の説明',
628
+ localization: {},
629
+ extensionPoints: [{localization: {}, name: 'いらっしゃいませ!', description: '拡張子の説明'}],
630
+ },
631
+ ],
632
+ })
633
+
634
+ await expect(socket).toReceiveMessage(data)
635
+
636
+ socket.close()
637
+ })
638
+
639
+ test('leave translatable props as-is in the UI extensions payload when locales are not provided in the client options', async () => {
640
+ const {socket, client} = await setup()
641
+ const data = {
642
+ event: 'update',
643
+ data: {
644
+ extensions: [{uuid: '123', type: 'ui_extension', localization: {}, extensionPoints: [{localization: {}}]}],
645
+ },
646
+ }
647
+
648
+ client.persist('update', {
649
+ extensions: [{uuid: '123', type: 'ui_extension', localization: {}, extensionPoints: [{localization: {}}]}],
650
+ })
651
+
652
+ await expect(socket).toReceiveMessage(data)
653
+
654
+ socket.close()
655
+ })
656
+ })
657
+
658
+ describe('connect()', () => {
659
+ test('updates the client options', () => {
660
+ const client = new ExtensionServerClient()
661
+
662
+ client.connect({connection: {automaticConnect: false}})
663
+
664
+ expect(client.options).toMatchObject({
665
+ connection: {
666
+ automaticConnect: false,
667
+ protocols: [],
668
+ },
669
+ })
670
+ })
671
+
672
+ test('does not attempt to connect if the URL is undefined', () => {
673
+ const client = new ExtensionServerClient()
674
+
675
+ client.connect()
676
+
677
+ expect(client.connection).toBeUndefined()
678
+ })
679
+
680
+ test('does not attempt to connect if the URL is empty', () => {
681
+ const client = new ExtensionServerClient({connection: {url: ''}})
682
+
683
+ client.connect()
684
+
685
+ expect(client.connection).toBeUndefined()
686
+ })
687
+
688
+ test('re-use existing connection if connect options have not changed', async () => {
689
+ const initialURL = 'ws://initial.socket.com'
690
+ const initialSocket = new WS(initialURL)
691
+ const client = new ExtensionServerClient({connection: {url: initialURL}})
692
+
693
+ vi.spyOn(initialSocket, 'close')
694
+
695
+ await initialSocket.connected
696
+
697
+ expect(initialSocket.server.clients()).toHaveLength(1)
698
+
699
+ client.connect({connection: {url: initialURL}})
700
+
701
+ expect(initialSocket.server.clients()).toHaveLength(1)
702
+ expect(initialSocket.close).not.toHaveBeenCalled()
703
+
704
+ initialSocket.close()
705
+ })
706
+
707
+ test('creates a new connection if the URL has changed', async () => {
708
+ const initialURL = 'ws://initial.socket.com'
709
+ const initialSocket = new WS(initialURL)
710
+ const updatedURL = 'ws://updated.socket.com'
711
+ const updatedSocket = new WS(updatedURL)
712
+ const client = new ExtensionServerClient({connection: {url: initialURL}})
713
+
714
+ await initialSocket.connected
715
+
716
+ expect(initialSocket.server.clients()).toHaveLength(1)
717
+ expect(updatedSocket.server.clients()).toHaveLength(0)
718
+
719
+ client.connect({connection: {url: updatedURL}})
720
+
721
+ await initialSocket.closed
722
+
723
+ expect(initialSocket.server.clients()).toHaveLength(0)
724
+ expect(updatedSocket.server.clients()).toHaveLength(1)
725
+
726
+ initialSocket.close()
727
+ updatedSocket.close()
728
+ })
729
+ })
730
+ })