@peers-app/peers-ui 0.27.3 → 0.27.5

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 (311) hide show
  1. package/dist/screens/network-viewer/group-details-modal.d.ts +4 -0
  2. package/dist/screens/network-viewer/group-details-modal.js +6 -46
  3. package/dist/screens/network-viewer/network-viewer-ipc.d.ts +3 -2
  4. package/dist/screens/network-viewer/network-viewer-ipc.js +6 -0
  5. package/dist/screens/network-viewer/sync-recovery.js +103 -1
  6. package/dist/screens/variables/variable-details.js +2 -1
  7. package/dist/tabs-layout/network-status-indicator.js +26 -12
  8. package/dist/tabs-layout/use-network-status.d.ts +6 -1
  9. package/dist/tabs-layout/use-network-status.js +27 -4
  10. package/package.json +9 -3
  11. package/.claude/settings.local.json +0 -34
  12. package/.github/README.md +0 -52
  13. package/.github/workflows/delete-all-artifacts.yml +0 -49
  14. package/.github/workflows/publish.yml +0 -51
  15. package/babel.config.js +0 -7
  16. package/biome.json +0 -198
  17. package/dist/app-updates/app-updates-consumer.test.d.ts +0 -1
  18. package/dist/app-updates/app-updates-consumer.test.js +0 -56
  19. package/dist/app-updates/app-updates-settings.test.d.ts +0 -1
  20. package/dist/app-updates/app-updates-settings.test.js +0 -93
  21. package/dist/app-updates/remote-app-update-panel.test.d.ts +0 -1
  22. package/dist/app-updates/remote-app-update-panel.test.js +0 -165
  23. package/dist/components/access-level-field.test.d.ts +0 -1
  24. package/dist/components/access-level-field.test.js +0 -28
  25. package/dist/components/message-logs/tool-access-approval-card.test.d.ts +0 -1
  26. package/dist/components/message-logs/tool-access-approval-card.test.js +0 -102
  27. package/dist/components/responsive-section-nav.test.d.ts +0 -1
  28. package/dist/components/responsive-section-nav.test.js +0 -45
  29. package/dist/globals-hash.test.d.ts +0 -1
  30. package/dist/globals-hash.test.js +0 -16
  31. package/dist/hooks/use-observable-state.test.d.ts +0 -1
  32. package/dist/hooks/use-observable-state.test.js +0 -64
  33. package/dist/hooks/use-observable.test.d.ts +0 -1
  34. package/dist/hooks/use-observable.test.js +0 -191
  35. package/dist/hooks/use-rows.test.d.ts +0 -1
  36. package/dist/hooks/use-rows.test.js +0 -175
  37. package/dist/hooks/use-tool.test.d.ts +0 -1
  38. package/dist/hooks/use-tool.test.js +0 -190
  39. package/dist/host-shell/host-shell-consumer.test.d.ts +0 -1
  40. package/dist/host-shell/host-shell-consumer.test.js +0 -28
  41. package/dist/root-layout/root-layout-host.test.d.ts +0 -1
  42. package/dist/root-layout/root-layout-host.test.js +0 -86
  43. package/dist/screens/account/account-screen.test.d.ts +0 -1
  44. package/dist/screens/account/account-screen.test.js +0 -51
  45. package/dist/screens/assistants/assistant-config.test.d.ts +0 -1
  46. package/dist/screens/assistants/assistant-config.test.js +0 -57
  47. package/dist/screens/contacts/contact-list.test.d.ts +0 -1
  48. package/dist/screens/contacts/contact-list.test.js +0 -48
  49. package/dist/screens/device-pairing/device-pairing.test.d.ts +0 -1
  50. package/dist/screens/device-pairing/device-pairing.test.js +0 -119
  51. package/dist/screens/devices/devices-list.test.d.ts +0 -1
  52. package/dist/screens/devices/devices-list.test.js +0 -53
  53. package/dist/screens/groups/group-list.test.d.ts +0 -1
  54. package/dist/screens/groups/group-list.test.js +0 -38
  55. package/dist/screens/identity/identity-routes.test.d.ts +0 -1
  56. package/dist/screens/identity/identity-routes.test.js +0 -51
  57. package/dist/screens/identity/identity-screen.test.d.ts +0 -1
  58. package/dist/screens/identity/identity-screen.test.js +0 -103
  59. package/dist/screens/invites/accept-invite.test.d.ts +0 -1
  60. package/dist/screens/invites/accept-invite.test.js +0 -103
  61. package/dist/screens/invites/activity-view.test.d.ts +0 -1
  62. package/dist/screens/invites/activity-view.test.js +0 -108
  63. package/dist/screens/invites/invite-classify.test.d.ts +0 -1
  64. package/dist/screens/invites/invite-classify.test.js +0 -115
  65. package/dist/screens/network-viewer/network-viewer-ipc.test.d.ts +0 -1
  66. package/dist/screens/network-viewer/network-viewer-ipc.test.js +0 -61
  67. package/dist/screens/network-viewer/network-viewer.section.test.d.ts +0 -1
  68. package/dist/screens/network-viewer/network-viewer.section.test.js +0 -63
  69. package/dist/screens/network-viewer/sync-recovery.test.d.ts +0 -1
  70. package/dist/screens/network-viewer/sync-recovery.test.js +0 -104
  71. package/dist/screens/packages/package-helpers.test.d.ts +0 -1
  72. package/dist/screens/packages/package-helpers.test.js +0 -53
  73. package/dist/screens/packages/package-runtime-status.test.d.ts +0 -1
  74. package/dist/screens/packages/package-runtime-status.test.js +0 -50
  75. package/dist/screens/packages/packages-ipc.test.d.ts +0 -1
  76. package/dist/screens/packages/packages-ipc.test.js +0 -43
  77. package/dist/screens/settings/settings-page.test.d.ts +0 -1
  78. package/dist/screens/settings/settings-page.test.js +0 -19
  79. package/dist/screens/tools/tool-access-ipc.test.d.ts +0 -1
  80. package/dist/screens/tools/tool-access-ipc.test.js +0 -40
  81. package/dist/screens/tools/tool-catalog-ipc.test.d.ts +0 -1
  82. package/dist/screens/tools/tool-catalog-ipc.test.js +0 -52
  83. package/dist/tabs-layout/network-status-indicator.test.d.ts +0 -1
  84. package/dist/tabs-layout/network-status-indicator.test.js +0 -665
  85. package/dist/tabs-layout/tabs-state.test.d.ts +0 -1
  86. package/dist/tabs-layout/tabs-state.test.js +0 -110
  87. package/dist/tabs-layout/use-network-status.test.d.ts +0 -1
  88. package/dist/tabs-layout/use-network-status.test.js +0 -45
  89. package/dist/ui-router/ui-loader.test.d.ts +0 -1
  90. package/dist/ui-router/ui-loader.test.js +0 -91
  91. package/jest.config.js +0 -25
  92. package/release-guard.js +0 -10
  93. package/src/app-updates/app-update-controls.tsx +0 -73
  94. package/src/app-updates/app-updates-consumer.test.ts +0 -83
  95. package/src/app-updates/app-updates-consumer.ts +0 -121
  96. package/src/app-updates/app-updates-settings.test.tsx +0 -109
  97. package/src/app-updates/app-updates-settings.tsx +0 -136
  98. package/src/app-updates/remote-app-update-panel.test.tsx +0 -205
  99. package/src/app-updates/remote-app-update-panel.tsx +0 -210
  100. package/src/app-updates/use-app-update-status.ts +0 -16
  101. package/src/command-palette/command-palette-ui.tsx +0 -564
  102. package/src/command-palette/command-palette.ts +0 -242
  103. package/src/components/access-level-field.test.tsx +0 -32
  104. package/src/components/access-level-field.tsx +0 -68
  105. package/src/components/checkbox.tsx +0 -25
  106. package/src/components/group-switcher.tsx +0 -526
  107. package/src/components/input-date.tsx +0 -29
  108. package/src/components/input-number.tsx +0 -83
  109. package/src/components/input.tsx +0 -25
  110. package/src/components/inverse-lazy-list.tsx +0 -107
  111. package/src/components/io-schema-values.tsx +0 -103
  112. package/src/components/io-schema.tsx +0 -259
  113. package/src/components/lazy-list.tsx +0 -115
  114. package/src/components/list-screen.tsx +0 -126
  115. package/src/components/loading-indicator.tsx +0 -7
  116. package/src/components/markdown-editor/autolink-plugin.tsx +0 -29
  117. package/src/components/markdown-editor/editor-inline.tsx +0 -9
  118. package/src/components/markdown-editor/editor.tsx +0 -158
  119. package/src/components/markdown-editor/markdown-plugin.tsx +0 -202
  120. package/src/components/markdown-editor/mention-node.ts +0 -204
  121. package/src/components/markdown-editor/mentions-plugin.tsx +0 -354
  122. package/src/components/markdown-editor/move-line-plugin.tsx +0 -186
  123. package/src/components/markdown-editor/select-line-boundary-plugin.tsx +0 -95
  124. package/src/components/markdown-editor/theme.ts +0 -46
  125. package/src/components/markdown-editor/toolbar.tsx +0 -269
  126. package/src/components/markdown-with-mentions.tsx +0 -198
  127. package/src/components/message-logs/message-logs.tsx +0 -523
  128. package/src/components/message-logs/tool-access-approval-card.test.tsx +0 -127
  129. package/src/components/message-logs/tool-access-approval-card.tsx +0 -178
  130. package/src/components/messages/avatar.tsx +0 -171
  131. package/src/components/messages/channel-message-list.tsx +0 -189
  132. package/src/components/messages/channel-view.tsx +0 -92
  133. package/src/components/messages/message-compose.tsx +0 -192
  134. package/src/components/messages/message-display.tsx +0 -231
  135. package/src/components/messages/thread-message-list.tsx +0 -135
  136. package/src/components/qr-scanner.tsx +0 -180
  137. package/src/components/responsive-section-nav.test.tsx +0 -88
  138. package/src/components/responsive-section-nav.tsx +0 -92
  139. package/src/components/router.tsx +0 -175
  140. package/src/components/save-button.tsx +0 -134
  141. package/src/components/sortable-list.tsx +0 -130
  142. package/src/components/tabs.tsx +0 -76
  143. package/src/components/tooltip.tsx +0 -43
  144. package/src/components/trust-level-badge.tsx +0 -44
  145. package/src/components/trust-level-dropdown.tsx +0 -136
  146. package/src/components/typeahead.tsx +0 -357
  147. package/src/globals-hash.test.ts +0 -19
  148. package/src/globals.tsx +0 -226
  149. package/src/hooks/index.ts +0 -8
  150. package/src/hooks/use-host-contract.ts +0 -75
  151. package/src/hooks/use-observable-state.test.tsx +0 -101
  152. package/src/hooks/use-observable-state.ts +0 -29
  153. package/src/hooks/use-observable.test.tsx +0 -258
  154. package/src/hooks/use-observable.ts +0 -67
  155. package/src/hooks/use-on-screen.ts +0 -24
  156. package/src/hooks/use-promise.ts +0 -34
  157. package/src/hooks/use-rows.test.tsx +0 -196
  158. package/src/hooks/use-rows.ts +0 -120
  159. package/src/hooks/use-subscription.ts +0 -24
  160. package/src/hooks/use-tool.test.tsx +0 -225
  161. package/src/hooks/use-tool.ts +0 -116
  162. package/src/host-shell/host-shell-consumer.test.ts +0 -32
  163. package/src/host-shell/host-shell-consumer.ts +0 -53
  164. package/src/index.tsx +0 -42
  165. package/src/mention-configs.ts +0 -185
  166. package/src/operator-console/operator-console-state.ts +0 -119
  167. package/src/operator-console/operator-console.tsx +0 -233
  168. package/src/peers-services-registration.ts +0 -6
  169. package/src/root-layout/global-overlays.ts +0 -50
  170. package/src/root-layout/layout-picker.tsx +0 -47
  171. package/src/root-layout/layouts/full-screen-layout.tsx +0 -144
  172. package/src/root-layout/layouts/tabs-with-console-layout.tsx +0 -260
  173. package/src/root-layout/root-layout-host.test.tsx +0 -108
  174. package/src/root-layout/root-layout-host.tsx +0 -147
  175. package/src/root-layout/root-layout-registry.ts +0 -51
  176. package/src/root-layout/root-layout-state.ts +0 -55
  177. package/src/root-layout/root-layout.ts +0 -32
  178. package/src/screens/account/account-screen.test.tsx +0 -65
  179. package/src/screens/account/account-screen.tsx +0 -942
  180. package/src/screens/assistants/assistant-config.test.tsx +0 -54
  181. package/src/screens/assistants/assistant-config.tsx +0 -121
  182. package/src/screens/assistants/assistant-details.tsx +0 -89
  183. package/src/screens/assistants/assistant-info.tsx +0 -66
  184. package/src/screens/assistants/assistant-list.tsx +0 -145
  185. package/src/screens/assistants/assistant-tools.tsx +0 -82
  186. package/src/screens/console-logs/console-logs-list.tsx +0 -374
  187. package/src/screens/console-logs/log-display.tsx +0 -137
  188. package/src/screens/console-logs/log-filters.tsx +0 -64
  189. package/src/screens/console-logs/mobile-log-card.tsx +0 -161
  190. package/src/screens/console-logs/resizable-table-header.tsx +0 -110
  191. package/src/screens/contacts/contact-details.tsx +0 -123
  192. package/src/screens/contacts/contact-list.test.tsx +0 -59
  193. package/src/screens/contacts/contact-list.tsx +0 -286
  194. package/src/screens/contacts/index.ts +0 -2
  195. package/src/screens/data-explorer/data-explorer.tsx +0 -633
  196. package/src/screens/data-explorer/index.ts +0 -6
  197. package/src/screens/data-explorer/query-executor.tsx +0 -520
  198. package/src/screens/device-pairing/device-pairing.test.tsx +0 -143
  199. package/src/screens/device-pairing/device-pairing.tsx +0 -461
  200. package/src/screens/device-pairing/identity-ipc.ts +0 -25
  201. package/src/screens/device-pairing/index.ts +0 -2
  202. package/src/screens/device-pairing/use-device-pairing-state.ts +0 -15
  203. package/src/screens/devices/devices-list.test.tsx +0 -68
  204. package/src/screens/devices/devices-list.tsx +0 -365
  205. package/src/screens/devices/index.ts +0 -1
  206. package/src/screens/groups/group-details.tsx +0 -160
  207. package/src/screens/groups/group-invite-panel.tsx +0 -311
  208. package/src/screens/groups/group-list.test.tsx +0 -44
  209. package/src/screens/groups/group-list.tsx +0 -317
  210. package/src/screens/groups/group-members.tsx +0 -343
  211. package/src/screens/groups/index.ts +0 -3
  212. package/src/screens/identity/identity-routes.test.ts +0 -70
  213. package/src/screens/identity/identity-routes.ts +0 -301
  214. package/src/screens/identity/identity-screen.test.tsx +0 -138
  215. package/src/screens/identity/identity-screen.tsx +0 -282
  216. package/src/screens/identity/index.ts +0 -2
  217. package/src/screens/identity/invite-contact-panel.tsx +0 -45
  218. package/src/screens/identity/profile-invite.tsx +0 -93
  219. package/src/screens/invites/accept-invite.test.tsx +0 -115
  220. package/src/screens/invites/accept-invite.tsx +0 -318
  221. package/src/screens/invites/activity-view.test.tsx +0 -127
  222. package/src/screens/invites/activity-view.tsx +0 -162
  223. package/src/screens/invites/index.ts +0 -6
  224. package/src/screens/invites/invite-capture.ts +0 -104
  225. package/src/screens/invites/invite-classify.test.ts +0 -129
  226. package/src/screens/invites/invite-share.tsx +0 -104
  227. package/src/screens/invites/invites-ipc.ts +0 -25
  228. package/src/screens/invites/pending-invites-list.tsx +0 -256
  229. package/src/screens/network-viewer/connection-troubleshooter.tsx +0 -395
  230. package/src/screens/network-viewer/cpu-usage-graph.tsx +0 -294
  231. package/src/screens/network-viewer/device-details-modal.tsx +0 -339
  232. package/src/screens/network-viewer/group-details-modal.tsx +0 -421
  233. package/src/screens/network-viewer/index.ts +0 -8
  234. package/src/screens/network-viewer/network-viewer-ipc.test.ts +0 -72
  235. package/src/screens/network-viewer/network-viewer-ipc.ts +0 -39
  236. package/src/screens/network-viewer/network-viewer-section.ts +0 -12
  237. package/src/screens/network-viewer/network-viewer.section.test.tsx +0 -68
  238. package/src/screens/network-viewer/network-viewer.tsx +0 -708
  239. package/src/screens/network-viewer/remote-logs-panel.tsx +0 -190
  240. package/src/screens/network-viewer/sync-recovery.test.tsx +0 -114
  241. package/src/screens/network-viewer/sync-recovery.tsx +0 -461
  242. package/src/screens/network-viewer/usage-graph.tsx +0 -455
  243. package/src/screens/packages/local-folder-grants-panel.tsx +0 -290
  244. package/src/screens/packages/local-folders-ipc.ts +0 -28
  245. package/src/screens/packages/package-details.tsx +0 -166
  246. package/src/screens/packages/package-helpers.test.ts +0 -57
  247. package/src/screens/packages/package-helpers.ts +0 -137
  248. package/src/screens/packages/package-info.tsx +0 -354
  249. package/src/screens/packages/package-list.tsx +0 -519
  250. package/src/screens/packages/package-network-panel.tsx +0 -82
  251. package/src/screens/packages/package-new-local.tsx +0 -100
  252. package/src/screens/packages/package-runtime-status.test.tsx +0 -58
  253. package/src/screens/packages/package-runtime-status.tsx +0 -68
  254. package/src/screens/packages/package-versions.tsx +0 -298
  255. package/src/screens/packages/packages-ipc.test.ts +0 -47
  256. package/src/screens/packages/packages-ipc.ts +0 -125
  257. package/src/screens/peer-types/peer-type-details.tsx +0 -172
  258. package/src/screens/peer-types/peer-type-list.tsx +0 -64
  259. package/src/screens/search/global-search.tsx +0 -406
  260. package/src/screens/settings/color-mode-dropdown.tsx +0 -82
  261. package/src/screens/settings/settings-page.test.tsx +0 -24
  262. package/src/screens/settings/settings-page.tsx +0 -426
  263. package/src/screens/setup-user.tsx +0 -355
  264. package/src/screens/tools/tool-access-ipc.test.ts +0 -55
  265. package/src/screens/tools/tool-access-ipc.ts +0 -27
  266. package/src/screens/tools/tool-catalog-ipc.test.ts +0 -52
  267. package/src/screens/tools/tool-catalog-ipc.ts +0 -51
  268. package/src/screens/tools/tool-details.tsx +0 -52
  269. package/src/screens/tools/tool-info.tsx +0 -45
  270. package/src/screens/tools/tool-list.tsx +0 -106
  271. package/src/screens/tools/tool-schema.tsx +0 -44
  272. package/src/screens/tools/tool-test-details.tsx +0 -89
  273. package/src/screens/tools/tool-test-list.tsx +0 -73
  274. package/src/screens/variables/variable-details.tsx +0 -289
  275. package/src/screens/variables/variable-list.tsx +0 -110
  276. package/src/screens/welcome-modal.tsx +0 -294
  277. package/src/screens/workflows/workflow-details.tsx +0 -115
  278. package/src/screens/workflows/workflow-info.tsx +0 -22
  279. package/src/screens/workflows/workflow-instructions.tsx +0 -134
  280. package/src/screens/workflows/workflow-list.tsx +0 -117
  281. package/src/setupTests.ts +0 -38
  282. package/src/system-apps/assistants.app.ts +0 -7
  283. package/src/system-apps/console-logs.app.ts +0 -7
  284. package/src/system-apps/data-explorer.app.ts +0 -8
  285. package/src/system-apps/identity.app.ts +0 -9
  286. package/src/system-apps/index.ts +0 -77
  287. package/src/system-apps/mobile-settings.app.ts +0 -7
  288. package/src/system-apps/network-viewer.app.ts +0 -8
  289. package/src/system-apps/packages.app.ts +0 -7
  290. package/src/system-apps/search.app.ts +0 -8
  291. package/src/system-apps/settings.app.ts +0 -7
  292. package/src/system-apps/threads.app.ts +0 -7
  293. package/src/system-apps/tools.app.ts +0 -7
  294. package/src/system-apps/types.app.ts +0 -7
  295. package/src/system-apps/variables.app.ts +0 -7
  296. package/src/system-apps/workflows.app.ts +0 -7
  297. package/src/tabs-layout/network-status-indicator.test.tsx +0 -799
  298. package/src/tabs-layout/network-status-indicator.tsx +0 -756
  299. package/src/tabs-layout/tabs-layout.tsx +0 -857
  300. package/src/tabs-layout/tabs-state.test.ts +0 -152
  301. package/src/tabs-layout/tabs-state.ts +0 -587
  302. package/src/tabs-layout/use-network-status.test.tsx +0 -58
  303. package/src/tabs-layout/use-network-status.ts +0 -68
  304. package/src/ui-defaults/index.ts +0 -2
  305. package/src/ui-defaults/list-screen.tsx +0 -94
  306. package/src/ui-defaults/markdown-field.tsx +0 -87
  307. package/src/ui-router/routes-loader.ts +0 -193
  308. package/src/ui-router/ui-loader.test.tsx +0 -108
  309. package/src/ui-router/ui-loader.tsx +0 -606
  310. package/src/utils.ts +0 -95
  311. package/tsconfig.json +0 -18
@@ -1,165 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const jsx_runtime_1 = require("react/jsx-runtime");
4
- const react_1 = require("@testing-library/react");
5
- const remote_app_update_panel_1 = require("./remote-app-update-panel");
6
- const getDeviceUpdateStatus = jest.fn();
7
- const installUpdateAndRestartOnDevice = jest.fn();
8
- jest.mock("./app-updates-consumer", () => ({
9
- ...jest.requireActual("./app-updates-consumer"),
10
- getAppUpdatesConsumer: () => Promise.resolve({
11
- tools: {
12
- getDeviceUpdateStatus: (...args) => getDeviceUpdateStatus(...args),
13
- installUpdateAndRestartOnDevice: (...args) => installUpdateAndRestartOnDevice(...args),
14
- },
15
- }),
16
- }));
17
- function remoteStatus(overrides = {}) {
18
- return {
19
- supported: true,
20
- canRestart: true,
21
- platform: "win32",
22
- currentVersion: "1.0.0",
23
- latestVersion: "1.1.0",
24
- downloadedVersion: "1.1.0",
25
- state: "downloaded",
26
- installOnRestart: true,
27
- installOnQuitArmed: true,
28
- remoteRestartAllowed: true,
29
- ...overrides,
30
- };
31
- }
32
- describe("RemoteAppUpdatePanel", () => {
33
- beforeEach(() => {
34
- getDeviceUpdateStatus.mockReset();
35
- installUpdateAndRestartOnDevice.mockReset();
36
- installUpdateAndRestartOnDevice.mockResolvedValue({ action: "installing" });
37
- jest.spyOn(window, "confirm").mockReturnValue(true);
38
- });
39
- afterEach(() => {
40
- jest.restoreAllMocks();
41
- });
42
- it("updates the other device after confirmation", async () => {
43
- getDeviceUpdateStatus.mockResolvedValue(remoteStatus());
44
- const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(remote_app_update_panel_1.RemoteAppUpdatePanel, { deviceId: "desktop", deviceName: "Desktop" }));
45
- const button = await view.findByRole("button", { name: /Update & restart/ });
46
- expect(view.getByTestId("remote-app-update")).toHaveTextContent("Version 1.0.0 · Version 1.1.0 is ready to install");
47
- expect(getDeviceUpdateStatus).toHaveBeenCalledWith({ deviceId: "desktop" });
48
- react_1.fireEvent.click(button);
49
- expect(window.confirm).toHaveBeenCalledWith("Update Desktop to 1.1.0 and restart it?");
50
- await (0, react_1.waitFor)(() => expect(installUpdateAndRestartOnDevice).toHaveBeenCalledWith({ deviceId: "desktop" }));
51
- await view.findByText("Request sent");
52
- });
53
- it("offers a plain restart when the device is up to date", async () => {
54
- getDeviceUpdateStatus.mockResolvedValue(remoteStatus({ state: "up-to-date" }));
55
- const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(remote_app_update_panel_1.RemoteAppUpdatePanel, { deviceId: "desktop" }));
56
- react_1.fireEvent.click(await view.findByRole("button", { name: /^Restart$/ }));
57
- expect(window.confirm).toHaveBeenCalledWith("Restart desktop? It will check for an update first.");
58
- await (0, react_1.waitFor)(() => expect(installUpdateAndRestartOnDevice).toHaveBeenCalled());
59
- });
60
- it("does nothing when the confirmation is declined", async () => {
61
- window.confirm.mockReturnValue(false);
62
- getDeviceUpdateStatus.mockResolvedValue(remoteStatus());
63
- const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(remote_app_update_panel_1.RemoteAppUpdatePanel, { deviceId: "desktop" }));
64
- react_1.fireEvent.click(await view.findByRole("button", { name: /Update & restart/ }));
65
- expect(installUpdateAndRestartOnDevice).not.toHaveBeenCalled();
66
- });
67
- it("explains why the action is disabled", async () => {
68
- getDeviceUpdateStatus.mockResolvedValue(remoteStatus({ remoteRestartAllowed: false }));
69
- const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(remote_app_update_panel_1.RemoteAppUpdatePanel, { deviceId: "desktop" }));
70
- expect(await view.findByRole("button", { name: /Update & restart/ })).toBeDisabled();
71
- expect(view.getByTestId("remote-app-update")).toHaveTextContent("does not allow remote restarts");
72
- getDeviceUpdateStatus.mockResolvedValue(remoteStatus({ installNeedsElevation: true }));
73
- const elevated = (0, react_1.render)((0, jsx_runtime_1.jsx)(remote_app_update_panel_1.RemoteAppUpdatePanel, { deviceId: "linux-box" }));
74
- await (0, react_1.waitFor)(() => expect(elevated.getAllByRole("button", { name: /Update & restart/ })[1]).toBeDisabled());
75
- expect(elevated.getAllByTestId("remote-app-update")[1]).toHaveTextContent("administrator password");
76
- });
77
- it.each([
78
- [
79
- "a device that cannot restart itself",
80
- () => Promise.resolve(remoteStatus({ canRestart: false })),
81
- ],
82
- [
83
- "a device that does not trust this user",
84
- () => Promise.reject(new Error("Permission denied: tool 'getUpdateStatus'.run on contract x")),
85
- ],
86
- [
87
- "a build without App Updates",
88
- () => Promise.reject(new Error("No contract provider registered for x v1")),
89
- ],
90
- ])("renders nothing for %s", async (_label, result) => {
91
- getDeviceUpdateStatus.mockImplementation(result);
92
- const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(remote_app_update_panel_1.RemoteAppUpdatePanel, { deviceId: "desktop" }));
93
- await (0, react_1.waitFor)(() => expect(view.queryByTestId("remote-app-update")).toBeNull());
94
- });
95
- it("reports an unreachable device", async () => {
96
- getDeviceUpdateStatus.mockRejectedValue(new Error("Device desktop is not connected"));
97
- const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(remote_app_update_panel_1.RemoteAppUpdatePanel, { deviceId: "desktop" }));
98
- await view.findByText("Not reachable: Device desktop is not connected");
99
- getDeviceUpdateStatus.mockResolvedValue(remoteStatus());
100
- react_1.fireEvent.click(view.getByRole("button", { name: "Retry" }));
101
- await view.findByRole("button", { name: /Update & restart/ });
102
- });
103
- it("clears the previous device's action while a new target loads", async () => {
104
- getDeviceUpdateStatus.mockResolvedValueOnce(remoteStatus());
105
- const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(remote_app_update_panel_1.RemoteAppUpdatePanel, { deviceId: "desktop" }));
106
- await view.findByRole("button", { name: /Update & restart/ });
107
- let finish;
108
- getDeviceUpdateStatus.mockImplementationOnce(() => new Promise((resolve) => {
109
- finish = resolve;
110
- }));
111
- view.rerender((0, jsx_runtime_1.jsx)(remote_app_update_panel_1.RemoteAppUpdatePanel, { deviceId: "another-desktop" }));
112
- expect(view.queryByRole("button", { name: /Update & restart/ })).toBeNull();
113
- expect(view.getByText("Checking this device…")).toBeTruthy();
114
- await (0, react_1.act)(async () => {
115
- await Promise.resolve();
116
- finish(remoteStatus({ remoteRestartAllowed: false }));
117
- });
118
- expect(view.getByRole("button", { name: /Update & restart/ })).toBeDisabled();
119
- });
120
- it("accepts slow poll responses without overlapping requests and cleans up timers", async () => {
121
- jest.useFakeTimers();
122
- try {
123
- getDeviceUpdateStatus.mockResolvedValue(remoteStatus());
124
- const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(remote_app_update_panel_1.RemoteAppUpdatePanel, { deviceId: "desktop" }));
125
- await (0, react_1.act)(async () => { });
126
- let finish;
127
- getDeviceUpdateStatus.mockImplementationOnce(() => new Promise((resolve) => {
128
- finish = resolve;
129
- }));
130
- react_1.fireEvent.click(view.getByRole("button", { name: /Update & restart/ }));
131
- await (0, react_1.act)(async () => { });
132
- await (0, react_1.act)(async () => {
133
- jest.advanceTimersByTime(remote_app_update_panel_1.REMOTE_UPDATE_POLL_MS * 3);
134
- });
135
- expect(getDeviceUpdateStatus).toHaveBeenCalledTimes(2);
136
- await (0, react_1.act)(async () => {
137
- finish(remoteStatus({ currentVersion: "1.1.0", state: "up-to-date" }));
138
- });
139
- expect(view.getByText("Version 1.1.0 · Up to date")).toBeTruthy();
140
- await (0, react_1.act)(async () => {
141
- jest.advanceTimersByTime(remote_app_update_panel_1.REMOTE_UPDATE_POLL_MS);
142
- });
143
- expect(getDeviceUpdateStatus).toHaveBeenCalledTimes(3);
144
- view.unmount();
145
- expect(jest.getTimerCount()).toBe(0);
146
- }
147
- finally {
148
- jest.useRealTimers();
149
- }
150
- });
151
- it("confirms the newer downloading version instead of the older cached version", async () => {
152
- getDeviceUpdateStatus.mockResolvedValue(remoteStatus({ state: "downloading", latestVersion: "1.2.0", downloadedVersion: "1.1.0" }));
153
- const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(remote_app_update_panel_1.RemoteAppUpdatePanel, { deviceId: "desktop", deviceName: "Desktop" }));
154
- react_1.fireEvent.click(await view.findByRole("button", { name: /Update & restart/ }));
155
- expect(window.confirm).toHaveBeenCalledWith("Update Desktop to 1.2.0 and restart it?");
156
- await view.findByText("Request sent");
157
- });
158
- it("shows a failed request", async () => {
159
- getDeviceUpdateStatus.mockResolvedValue(remoteStatus());
160
- installUpdateAndRestartOnDevice.mockRejectedValue(new Error("Permission denied: this device does not allow other devices to update"));
161
- const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(remote_app_update_panel_1.RemoteAppUpdatePanel, { deviceId: "desktop" }));
162
- react_1.fireEvent.click(await view.findByRole("button", { name: /Update & restart/ }));
163
- await view.findByText(/does not allow other devices to update/);
164
- });
165
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,28 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const jsx_runtime_1 = require("react/jsx-runtime");
4
- const peers_sdk_1 = require("@peers-app/peers-sdk");
5
- const react_1 = require("@testing-library/react");
6
- const access_level_field_1 = require("./access-level-field");
7
- describe("AccessLevelField", () => {
8
- it("shows the named preset and current label", () => {
9
- (0, react_1.render)((0, jsx_runtime_1.jsx)(access_level_field_1.AccessLevelField, { value: (0, peers_sdk_1.observable)(40) }));
10
- expect(react_1.screen.getByRole("combobox")).toHaveValue("40");
11
- expect(react_1.screen.getByText(/Unclassified records default to Self/)).toHaveTextContent("Writer (40)");
12
- });
13
- it("writes a named preset onto the observable", () => {
14
- const value = (0, peers_sdk_1.observable)(40);
15
- (0, react_1.render)((0, jsx_runtime_1.jsx)(access_level_field_1.AccessLevelField, { value: value }));
16
- react_1.fireEvent.change(react_1.screen.getByRole("combobox"), { target: { value: "60" } });
17
- expect(value()).toBe(60);
18
- expect(react_1.screen.getByText(/Unclassified records default to Self/)).toHaveTextContent("Admin (60)");
19
- });
20
- it("edits a custom integer between named tiers", () => {
21
- const value = (0, peers_sdk_1.observable)(45);
22
- (0, react_1.render)((0, jsx_runtime_1.jsx)(access_level_field_1.AccessLevelField, { value: value }));
23
- expect(react_1.screen.getByRole("combobox")).toHaveValue("custom");
24
- react_1.fireEvent.change(react_1.screen.getByRole("spinbutton"), { target: { value: "47" } });
25
- expect(value()).toBe(47);
26
- expect(react_1.screen.getByText(/Writer\+ \(47\)/)).toBeInTheDocument();
27
- });
28
- });
@@ -1,102 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const jsx_runtime_1 = require("react/jsx-runtime");
4
- const peers_sdk_1 = require("@peers-app/peers-sdk");
5
- const react_1 = require("@testing-library/react");
6
- const tool_access_approval_card_1 = require("./tool-access-approval-card");
7
- const approve = jest.fn(async () => ({ result: { status: "executed" } }));
8
- const reject = jest.fn(async () => ({ result: { status: "rejected" } }));
9
- const getDataContext = jest.fn((dataContextId) => ({ dataContextId }));
10
- const getToolAccessControlConsumer = jest.fn(async (_dataContextId) => ({
11
- tools: { approve, reject },
12
- }));
13
- jest.mock("../../screens/tools/tool-access-ipc", () => ({
14
- getToolAccessControlConsumer: (dataContextId) => getToolAccessControlConsumer(dataContextId),
15
- }));
16
- jest.mock("@peers-app/peers-sdk", () => {
17
- const actual = jest.requireActual("@peers-app/peers-sdk");
18
- return {
19
- ...actual,
20
- getUserContext: async () => ({
21
- getDataContext,
22
- }),
23
- getCurrentUserAccessLevel: async () => actual.AccessLevel.Self,
24
- };
25
- });
26
- const pending = {
27
- toolAccessApprovalId: "00testapproval0000000001",
28
- userId: "00testuser000000000000001",
29
- assistantId: "00testassistant000000001",
30
- toolId: "00testtooladmin00000000",
31
- toolName: "queryDatabase",
32
- dataContextId: "00testdatacontext00000001",
33
- argsHash: "hash",
34
- argsPreview: '{"sql":"select 1"}',
35
- frozenArgs: { sql: "select 1" },
36
- requiredAccessLevel: peers_sdk_1.AccessLevel.Owner,
37
- scope: peers_sdk_1.ToolAccessApprovalScope.once,
38
- status: peers_sdk_1.ToolAccessApprovalStatus.pending,
39
- nonce: "nonce-from-table",
40
- expiresAt: new Date(Date.now() + 60_000),
41
- createdAt: new Date(),
42
- };
43
- describe("ToolAccessApprovalCard", () => {
44
- beforeEach(() => {
45
- approve.mockClear();
46
- reject.mockClear();
47
- getDataContext.mockClear();
48
- getToolAccessControlConsumer.mockClear();
49
- });
50
- it("renders a pending card with actions", () => {
51
- (0, react_1.render)((0, jsx_runtime_1.jsx)(tool_access_approval_card_1.ToolAccessApprovalCard, { approval: pending }));
52
- expect(react_1.screen.getByText("Approval required")).toBeInTheDocument();
53
- expect(react_1.screen.getByText("queryDatabase")).toBeInTheDocument();
54
- expect(react_1.screen.getByText(/Owner \(80\)/)).toBeInTheDocument();
55
- expect(react_1.screen.getByRole("button", { name: "Allow once" })).toBeInTheDocument();
56
- expect(react_1.screen.getByRole("button", { name: "Allow in thread (15 min)" })).toBeInTheDocument();
57
- expect(react_1.screen.getByRole("button", { name: "Allow in this context (15 min)" })).toBeInTheDocument();
58
- expect(react_1.screen.getByRole("button", { name: "Deny" })).toBeInTheDocument();
59
- });
60
- it("evaluates the user role in the approval context", async () => {
61
- (0, react_1.render)((0, jsx_runtime_1.jsx)(tool_access_approval_card_1.ToolAccessApprovalCard, { approval: pending }));
62
- await (0, react_1.waitFor)(() => {
63
- expect(getDataContext).toHaveBeenCalledWith(pending.dataContextId);
64
- });
65
- });
66
- it("approves once using the table nonce", async () => {
67
- (0, react_1.render)((0, jsx_runtime_1.jsx)(tool_access_approval_card_1.ToolAccessApprovalCard, { approval: pending }));
68
- react_1.fireEvent.click(react_1.screen.getByRole("button", { name: "Allow once" }));
69
- await (0, react_1.waitFor)(() => {
70
- expect(getToolAccessControlConsumer).toHaveBeenCalledWith(pending.dataContextId);
71
- expect(approve).toHaveBeenCalledWith({
72
- approvalId: pending.toolAccessApprovalId,
73
- nonce: "nonce-from-table",
74
- scope: "once",
75
- });
76
- });
77
- });
78
- it("denies using the table nonce", async () => {
79
- (0, react_1.render)((0, jsx_runtime_1.jsx)(tool_access_approval_card_1.ToolAccessApprovalCard, { approval: pending }));
80
- react_1.fireEvent.click(react_1.screen.getByRole("button", { name: "Deny" }));
81
- await (0, react_1.waitFor)(() => {
82
- expect(reject).toHaveBeenCalledWith({
83
- approvalId: pending.toolAccessApprovalId,
84
- nonce: "nonce-from-table",
85
- });
86
- });
87
- });
88
- it("hides empty argument previews", () => {
89
- (0, react_1.render)((0, jsx_runtime_1.jsx)(tool_access_approval_card_1.ToolAccessApprovalCard, { approval: { ...pending, argsPreview: "{}", frozenArgs: {} } }));
90
- expect(react_1.screen.queryByText("{}")).not.toBeInTheDocument();
91
- });
92
- it("shows a decided card without actions", () => {
93
- (0, react_1.render)((0, jsx_runtime_1.jsx)(tool_access_approval_card_1.ToolAccessApprovalCard, { approval: {
94
- ...pending,
95
- status: peers_sdk_1.ToolAccessApprovalStatus.rejected,
96
- resultSummary: "Rejected by user",
97
- } }));
98
- expect(react_1.screen.getByText("Access rejected")).toBeInTheDocument();
99
- expect(react_1.screen.getByText("Rejected by user")).toBeInTheDocument();
100
- expect(react_1.screen.queryByRole("button", { name: "Allow once" })).not.toBeInTheDocument();
101
- });
102
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,45 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const jsx_runtime_1 = require("react/jsx-runtime");
7
- const react_1 = require("@testing-library/react");
8
- const user_event_1 = __importDefault(require("@testing-library/user-event"));
9
- const responsive_section_nav_1 = require("./responsive-section-nav");
10
- jest.mock("../globals", () => {
11
- const { observable: createObservable } = jest.requireActual("@peers-app/peers-sdk");
12
- return { isDesktop: createObservable(true) };
13
- });
14
- const globals_1 = require("../globals");
15
- const sections = [
16
- { id: "activity", label: "Activity", iconClassName: "bi-inbox", badge: 2 },
17
- { id: "people", label: "People", iconClassName: "bi-person" },
18
- { id: "groups", label: "Groups" },
19
- { id: "devices", label: "Devices" },
20
- ];
21
- describe("ResponsiveSectionNav", () => {
22
- beforeEach(() => {
23
- (0, globals_1.isDesktop)(true);
24
- });
25
- it("uses a vertical sidebar on desktop and marks the selected section", () => {
26
- (0, react_1.render)((0, jsx_runtime_1.jsx)(responsive_section_nav_1.ResponsiveSectionNav, { label: "Identity", sections: sections, activeSectionId: "people", onSelect: () => undefined, toolbar: (0, jsx_runtime_1.jsx)("div", { children: "Quick actions" }), children: (0, jsx_runtime_1.jsx)("div", { children: "People body" }) }));
27
- expect(react_1.screen.getByRole("navigation", { name: "Identity" }).querySelector("ul")).toHaveClass("nav-pills", "flex-column");
28
- expect(react_1.screen.getByRole("button", { name: /People/ })).toHaveAttribute("aria-current", "page");
29
- expect(react_1.screen.getByText("People body")).toBeInTheDocument();
30
- expect(react_1.screen.getByText("Quick actions")).toBeInTheDocument();
31
- expect(react_1.screen.getByText("Quick actions").closest("[data-section-scroll-container]")).toBeNull();
32
- expect(react_1.screen.getByText("People body").closest("[data-section-scroll-container]")).toHaveClass("overflow-auto");
33
- });
34
- it("uses a non-wrapping horizontal strip on a narrow screen", () => {
35
- (0, globals_1.isDesktop)(false);
36
- (0, react_1.render)((0, jsx_runtime_1.jsx)(responsive_section_nav_1.ResponsiveSectionNav, { label: "Identity", sections: sections, activeSectionId: "activity", onSelect: () => undefined, children: (0, jsx_runtime_1.jsx)("div", { children: "Activity body" }) }));
37
- expect(react_1.screen.getByRole("navigation", { name: "Identity" }).querySelector("ul")).toHaveClass("nav-tabs", "flex-nowrap", "overflow-x-auto", "overflow-y-hidden");
38
- });
39
- it("reports the section chosen from the keyboard-focusable button", async () => {
40
- const onSelect = jest.fn();
41
- (0, react_1.render)((0, jsx_runtime_1.jsx)(responsive_section_nav_1.ResponsiveSectionNav, { label: "Identity", sections: sections, activeSectionId: "activity", onSelect: onSelect, children: (0, jsx_runtime_1.jsx)("div", { children: "Activity body" }) }));
42
- await user_event_1.default.click(react_1.screen.getByRole("button", { name: /Groups/ }));
43
- expect(onSelect).toHaveBeenCalledWith("groups");
44
- });
45
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,16 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- jest.mock("./tabs-layout/tabs-state", () => ({
4
- handleMainPathChanged: (_oldPath, newPath, storage) => storage(newPath),
5
- openNewTab: jest.fn(),
6
- }));
7
- const globals_1 = require("./globals");
8
- describe("initial hash navigation", () => {
9
- it("lets an incoming legacy deep link win and canonicalizes it", () => {
10
- (0, globals_1.mainContentPath)("identity/activity");
11
- window.history.replaceState(null, "", "/#contacts/abcdefghijklmnopqrstuvwxy?from=link");
12
- (0, globals_1.initializeHashNavigation)();
13
- expect((0, globals_1.mainContentPath)()).toBe("identity/people/abcdefghijklmnopqrstuvwxy?from=link");
14
- expect(window.location.hash).toBe("#identity/people/abcdefghijklmnopqrstuvwxy?from=link");
15
- });
16
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,64 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const jsx_runtime_1 = require("react/jsx-runtime");
4
- const react_1 = require("@testing-library/react");
5
- const use_observable_1 = require("./use-observable");
6
- const use_observable_state_1 = require("./use-observable-state");
7
- // Helper component to test useObservableState
8
- function ObservableStateTestComponent({ initialValue }) {
9
- const obs = (0, use_observable_state_1.useObservableState)(initialValue);
10
- const [value] = (0, use_observable_1.useObservable)(obs);
11
- return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { "data-testid": "state-value", children: value }), (0, jsx_runtime_1.jsx)("button", { type: "button", "data-testid": "increment", onClick: () => obs(obs() + 1), children: "Increment" })] }));
12
- }
13
- describe("useObservableState", () => {
14
- it("should create an observable with the initial value", () => {
15
- (0, react_1.render)((0, jsx_runtime_1.jsx)(ObservableStateTestComponent, { initialValue: 42 }));
16
- expect(react_1.screen.getByTestId("state-value")).toHaveTextContent("42");
17
- });
18
- it("should re-render when the observable is updated", async () => {
19
- (0, react_1.render)((0, jsx_runtime_1.jsx)(ObservableStateTestComponent, { initialValue: 0 }));
20
- expect(react_1.screen.getByTestId("state-value")).toHaveTextContent("0");
21
- (0, react_1.act)(() => {
22
- react_1.screen.getByTestId("increment").click();
23
- });
24
- await (0, react_1.waitFor)(() => {
25
- expect(react_1.screen.getByTestId("state-value")).toHaveTextContent("1");
26
- });
27
- });
28
- it("should maintain the same observable instance across re-renders", async () => {
29
- let capturedObs = null;
30
- function CaptureObsComponent() {
31
- const obs = (0, use_observable_state_1.useObservableState)(0);
32
- if (!capturedObs) {
33
- capturedObs = obs;
34
- }
35
- const [value] = (0, use_observable_1.useObservable)(obs);
36
- return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { "data-testid": "value", children: value }), (0, jsx_runtime_1.jsx)("span", { "data-testid": "same-obs", children: obs === capturedObs ? "same" : "different" }), (0, jsx_runtime_1.jsx)("button", { type: "button", "data-testid": "update", onClick: () => obs(obs() + 1), children: "Update" })] }));
37
- }
38
- (0, react_1.render)((0, jsx_runtime_1.jsx)(CaptureObsComponent, {}));
39
- expect(react_1.screen.getByTestId("same-obs")).toHaveTextContent("same");
40
- // Trigger a re-render by updating the observable
41
- (0, react_1.act)(() => {
42
- react_1.screen.getByTestId("update").click();
43
- });
44
- await (0, react_1.waitFor)(() => {
45
- expect(react_1.screen.getByTestId("value")).toHaveTextContent("1");
46
- expect(react_1.screen.getByTestId("same-obs")).toHaveTextContent("same");
47
- });
48
- });
49
- it("should not subscribe when doNotSubscribe is true", () => {
50
- function NoSubscribeComponent() {
51
- const obs = (0, use_observable_state_1.useObservableState)(0, true); // doNotSubscribe = true
52
- return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { "data-testid": "value", children: obs() }), (0, jsx_runtime_1.jsx)("button", { type: "button", "data-testid": "update", onClick: () => obs(obs() + 1), children: "Update" })] }));
53
- }
54
- (0, react_1.render)((0, jsx_runtime_1.jsx)(NoSubscribeComponent, {}));
55
- expect(react_1.screen.getByTestId("value")).toHaveTextContent("0");
56
- // Update won't cause re-render since we're not subscribed
57
- (0, react_1.act)(() => {
58
- react_1.screen.getByTestId("update").click();
59
- });
60
- // Value in DOM should still show 0 since component didn't re-render
61
- // But the observable itself has been updated
62
- expect(react_1.screen.getByTestId("value")).toHaveTextContent("0");
63
- });
64
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,191 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const jsx_runtime_1 = require("react/jsx-runtime");
4
- const peers_sdk_1 = require("@peers-app/peers-sdk");
5
- const react_1 = require("@testing-library/react");
6
- const use_observable_1 = require("./use-observable");
7
- // Helper component to test useObservable
8
- function TestComponent({ obs, testId = "value" }) {
9
- const [value, setValue] = (0, use_observable_1.useObservable)(obs);
10
- return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { "data-testid": testId, children: JSON.stringify(value) }), (0, jsx_runtime_1.jsx)("button", { type: "button", "data-testid": "setter", onClick: () => setValue("new-value"), children: "Set" })] }));
11
- }
12
- // Helper component to test array behavior
13
- function ArrayTestComponent({ obs }) {
14
- const [value] = (0, use_observable_1.useObservable)(obs);
15
- return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("span", { "data-testid": "array-value", children: JSON.stringify(value) }), (0, jsx_runtime_1.jsx)("span", { "data-testid": "array-length", children: value.length })] }));
16
- }
17
- describe("useObservable", () => {
18
- it("reads and follows a read-only observable without allowing typed writes", () => {
19
- const source = (0, peers_sdk_1.observable)(1);
20
- const view = source;
21
- const { result } = (0, react_1.renderHook)(() => (0, use_observable_1.useObservable)(view));
22
- const checkReadonly = () => {
23
- // @ts-expect-error the read-only hook's setter accepts no values
24
- result.current[1](2);
25
- if ((0, peers_sdk_1.isSubscribable)(view)) {
26
- // @ts-expect-error testing subscribability must not make a read-only view writable
27
- view(2);
28
- }
29
- };
30
- void checkReadonly;
31
- const initial = result.current[0];
32
- expect(initial).toBe(1);
33
- (0, react_1.act)(() => source(3));
34
- expect(result.current[0]).toBe(3);
35
- });
36
- describe("basic functionality", () => {
37
- it("should return the current value of an observable", () => {
38
- const obs = (0, peers_sdk_1.observable)("hello");
39
- (0, react_1.render)((0, jsx_runtime_1.jsx)(TestComponent, { obs: obs }));
40
- expect(react_1.screen.getByTestId("value")).toHaveTextContent('"hello"');
41
- });
42
- it("should return the value when passed a plain value (non-observable)", () => {
43
- (0, react_1.render)((0, jsx_runtime_1.jsx)(TestComponent, { obs: "plain-value" }));
44
- expect(react_1.screen.getByTestId("value")).toHaveTextContent('"plain-value"');
45
- });
46
- it("should re-render when observable value changes", async () => {
47
- const obs = (0, peers_sdk_1.observable)("initial");
48
- (0, react_1.render)((0, jsx_runtime_1.jsx)(TestComponent, { obs: obs }));
49
- expect(react_1.screen.getByTestId("value")).toHaveTextContent('"initial"');
50
- (0, react_1.act)(() => {
51
- obs("updated");
52
- });
53
- await (0, react_1.waitFor)(() => {
54
- expect(react_1.screen.getByTestId("value")).toHaveTextContent('"updated"');
55
- });
56
- });
57
- it("should update the observable when setter is called", async () => {
58
- const obs = (0, peers_sdk_1.observable)("initial");
59
- (0, react_1.render)((0, jsx_runtime_1.jsx)(TestComponent, { obs: obs }));
60
- (0, react_1.act)(() => {
61
- react_1.screen.getByTestId("setter").click();
62
- });
63
- await (0, react_1.waitFor)(() => {
64
- expect(obs()).toBe("new-value");
65
- expect(react_1.screen.getByTestId("value")).toHaveTextContent('"new-value"');
66
- });
67
- });
68
- it("should handle undefined values", () => {
69
- const obs = (0, peers_sdk_1.observable)(undefined);
70
- (0, react_1.render)((0, jsx_runtime_1.jsx)(TestComponent, { obs: obs }));
71
- // undefined serializes to empty string in JSON.stringify, but actually shows as nothing
72
- expect(react_1.screen.getByTestId("value")).toHaveTextContent("");
73
- });
74
- it("should handle object values", async () => {
75
- const obs = (0, peers_sdk_1.observable)({ name: "test", count: 5 });
76
- (0, react_1.render)((0, jsx_runtime_1.jsx)(TestComponent, { obs: obs }));
77
- expect(react_1.screen.getByTestId("value")).toHaveTextContent('{"name":"test","count":5}');
78
- (0, react_1.act)(() => {
79
- obs({ name: "updated", count: 10 });
80
- });
81
- await (0, react_1.waitFor)(() => {
82
- expect(react_1.screen.getByTestId("value")).toHaveTextContent('{"name":"updated","count":10}');
83
- });
84
- });
85
- });
86
- describe("array handling", () => {
87
- it("should handle array values", () => {
88
- const obs = (0, peers_sdk_1.observable)(["a", "b", "c"]);
89
- (0, react_1.render)((0, jsx_runtime_1.jsx)(ArrayTestComponent, { obs: obs }));
90
- expect(react_1.screen.getByTestId("array-value")).toHaveTextContent('["a","b","c"]');
91
- expect(react_1.screen.getByTestId("array-length")).toHaveTextContent("3");
92
- });
93
- it("should re-render when array is replaced with new reference", async () => {
94
- const obs = (0, peers_sdk_1.observable)(["a", "b"]);
95
- (0, react_1.render)((0, jsx_runtime_1.jsx)(ArrayTestComponent, { obs: obs }));
96
- expect(react_1.screen.getByTestId("array-length")).toHaveTextContent("2");
97
- (0, react_1.act)(() => {
98
- obs(["a", "b", "c"]); // New array reference
99
- });
100
- await (0, react_1.waitFor)(() => {
101
- expect(react_1.screen.getByTestId("array-length")).toHaveTextContent("3");
102
- });
103
- });
104
- it("should NOT re-render when array is mutated in place (requires immutable updates)", async () => {
105
- // This tests the case where an array is mutated in place and notifySubscribers is called.
106
- //
107
- // IMPORTANT BEHAVIORAL NOTE:
108
- // The OLD implementation (useState + useEffect) had special handling that would
109
- // spread the array when it detected same-reference mutation:
110
- // if (isArray(newData) && newData === data) setData([...newData])
111
- //
112
- // The NEW implementation (useSyncExternalStore) does NOT do this.
113
- // useSyncExternalStore uses Object.is() comparison on getSnapshot results.
114
- // If the reference is the same, React won't re-render.
115
- //
116
- // This is actually the CORRECT behavior - it encourages immutable data patterns
117
- // which are required for React 18 concurrent rendering to work correctly.
118
- // Mutating data in place can cause tearing in concurrent mode.
119
- const obs = (0, peers_sdk_1.observable)(["a", "b"]);
120
- (0, react_1.render)((0, jsx_runtime_1.jsx)(ArrayTestComponent, { obs: obs }));
121
- expect(react_1.screen.getByTestId("array-length")).toHaveTextContent("2");
122
- (0, react_1.act)(() => {
123
- // Mutate the array in place (BAD PRACTICE - don't do this!)
124
- const arr = obs();
125
- arr.push("c");
126
- obs.notifySubscribers();
127
- });
128
- // The observable itself has the mutated value
129
- expect(obs()).toEqual(["a", "b", "c"]);
130
- // But the component did NOT re-render because the reference didn't change
131
- // This is expected with useSyncExternalStore - it requires immutable updates
132
- expect(react_1.screen.getByTestId("array-length")).toHaveTextContent("2");
133
- });
134
- it("should re-render when array is properly updated with spread", async () => {
135
- // This is the recommended pattern for updating arrays
136
- const obs = (0, peers_sdk_1.observable)(["a", "b"]);
137
- (0, react_1.render)((0, jsx_runtime_1.jsx)(ArrayTestComponent, { obs: obs }));
138
- expect(react_1.screen.getByTestId("array-length")).toHaveTextContent("2");
139
- (0, react_1.act)(() => {
140
- obs([...obs(), "c"]); // Proper immutable update
141
- });
142
- await (0, react_1.waitFor)(() => {
143
- expect(react_1.screen.getByTestId("array-length")).toHaveTextContent("3");
144
- expect(react_1.screen.getByTestId("array-value")).toHaveTextContent('["a","b","c"]');
145
- });
146
- });
147
- });
148
- describe("subscription management", () => {
149
- it("should unsubscribe when component unmounts", () => {
150
- const obs = (0, peers_sdk_1.observable)("test");
151
- const initialSubscriberCount = obs.subscriberCount();
152
- const { unmount } = (0, react_1.render)((0, jsx_runtime_1.jsx)(TestComponent, { obs: obs }));
153
- // Should have subscribed
154
- expect(obs.subscriberCount()).toBeGreaterThan(initialSubscriberCount);
155
- unmount();
156
- // Should have unsubscribed
157
- expect(obs.subscriberCount()).toBe(initialSubscriberCount);
158
- });
159
- it("should handle multiple components subscribing to same observable", async () => {
160
- const obs = (0, peers_sdk_1.observable)("shared");
161
- (0, react_1.render)((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(TestComponent, { obs: obs, testId: "value1" }), (0, jsx_runtime_1.jsx)(TestComponent, { obs: obs, testId: "value2" })] }));
162
- expect(react_1.screen.getByTestId("value1")).toHaveTextContent('"shared"');
163
- expect(react_1.screen.getByTestId("value2")).toHaveTextContent('"shared"');
164
- (0, react_1.act)(() => {
165
- obs("updated");
166
- });
167
- await (0, react_1.waitFor)(() => {
168
- expect(react_1.screen.getByTestId("value1")).toHaveTextContent('"updated"');
169
- expect(react_1.screen.getByTestId("value2")).toHaveTextContent('"updated"');
170
- });
171
- });
172
- });
173
- describe("rapid updates", () => {
174
- it("should handle rapid sequential updates correctly", async () => {
175
- const obs = (0, peers_sdk_1.observable)(0);
176
- function CounterComponent() {
177
- const [value] = (0, use_observable_1.useObservable)(obs);
178
- return (0, jsx_runtime_1.jsx)("span", { "data-testid": "counter", children: value });
179
- }
180
- (0, react_1.render)((0, jsx_runtime_1.jsx)(CounterComponent, {}));
181
- (0, react_1.act)(() => {
182
- for (let i = 1; i <= 10; i++) {
183
- obs(i);
184
- }
185
- });
186
- await (0, react_1.waitFor)(() => {
187
- expect(react_1.screen.getByTestId("counter")).toHaveTextContent("10");
188
- });
189
- });
190
- });
191
- });
@@ -1 +0,0 @@
1
- export {};