vintasend 0.4.0 → 0.4.7

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 (284) hide show
  1. package/README.md +5 -0
  2. package/dist/examples/vintasend-medplum-example/.env.example +11 -0
  3. package/dist/examples/vintasend-medplum-example/IMPLEMENTATION_PLAN_FILE_ATTACHMENTS.md +597 -0
  4. package/dist/examples/vintasend-medplum-example/README.md +190 -0
  5. package/dist/examples/vintasend-medplum-example/TUTORIAL_EMAIL_NOTIFICATIONS.md +2596 -0
  6. package/dist/examples/vintasend-medplum-example/bots/handlers/send-pending-notifications-bot.ts +39 -0
  7. package/dist/examples/vintasend-medplum-example/bots/handlers/send-task-assignment-email.ts +41 -0
  8. package/dist/examples/vintasend-medplum-example/bots/handlers/task-due-soon-notification-bot.ts +86 -0
  9. package/dist/examples/vintasend-medplum-example/bots/index.ts +53 -0
  10. package/dist/examples/vintasend-medplum-example/bots/services/emails/schedule-task-due-soon-email.ts +84 -0
  11. package/dist/examples/vintasend-medplum-example/bots/services/emails/send-task-assignment-email.test.ts +388 -0
  12. package/dist/examples/vintasend-medplum-example/bots/services/emails/send-task-assignment-email.ts +113 -0
  13. package/dist/examples/vintasend-medplum-example/bots/shared/task-due-soon-helpers.ts +115 -0
  14. package/dist/examples/vintasend-medplum-example/bots/task-assignment-bot.ts +41 -0
  15. package/dist/examples/vintasend-medplum-example/compiled-notification-templates.json +6 -0
  16. package/dist/examples/vintasend-medplum-example/esbuild-script.mjs +71 -0
  17. package/dist/examples/vintasend-medplum-example/index.html +14 -0
  18. package/dist/examples/vintasend-medplum-example/lib/constants.ts +32 -0
  19. package/dist/examples/vintasend-medplum-example/lib/extensions.ts +1 -0
  20. package/dist/examples/vintasend-medplum-example/lib/file-upload.test.ts +389 -0
  21. package/dist/examples/vintasend-medplum-example/lib/file-upload.ts +222 -0
  22. package/dist/examples/vintasend-medplum-example/lib/medplum-singleton.ts +18 -0
  23. package/dist/examples/vintasend-medplum-example/lib/notification-service.test.ts +293 -0
  24. package/dist/examples/vintasend-medplum-example/lib/notification-service.ts +284 -0
  25. package/dist/examples/vintasend-medplum-example/lib/patients.ts +20 -0
  26. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-assignment/body.html.pug +37 -0
  27. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-assignment/subject.txt.pug +4 -0
  28. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-due-soon/body.html.pug +34 -0
  29. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-due-soon/subject.txt.pug +4 -0
  30. package/dist/examples/vintasend-medplum-example/package.json +75 -0
  31. package/dist/examples/vintasend-medplum-example/plugins/gql-plugin.mjs +31 -0
  32. package/dist/examples/vintasend-medplum-example/postcss.config.mjs +21 -0
  33. package/dist/examples/vintasend-medplum-example/public/favicon.ico +0 -0
  34. package/dist/examples/vintasend-medplum-example/public/img/integrations/acuity.png +0 -0
  35. package/dist/examples/vintasend-medplum-example/public/img/integrations/auth0.png +0 -0
  36. package/dist/examples/vintasend-medplum-example/public/img/integrations/azure.png +0 -0
  37. package/dist/examples/vintasend-medplum-example/public/img/integrations/calcom.png +0 -0
  38. package/dist/examples/vintasend-medplum-example/public/img/integrations/candid.png +0 -0
  39. package/dist/examples/vintasend-medplum-example/public/img/integrations/claude.png +0 -0
  40. package/dist/examples/vintasend-medplum-example/public/img/integrations/datadog.png +0 -0
  41. package/dist/examples/vintasend-medplum-example/public/img/integrations/deepseek.png +0 -0
  42. package/dist/examples/vintasend-medplum-example/public/img/integrations/entra.png +0 -0
  43. package/dist/examples/vintasend-medplum-example/public/img/integrations/epic.png +0 -0
  44. package/dist/examples/vintasend-medplum-example/public/img/integrations/google.png +0 -0
  45. package/dist/examples/vintasend-medplum-example/public/img/integrations/healthgorilla.png +0 -0
  46. package/dist/examples/vintasend-medplum-example/public/img/integrations/healthie.png +0 -0
  47. package/dist/examples/vintasend-medplum-example/public/img/integrations/labcorp.png +0 -0
  48. package/dist/examples/vintasend-medplum-example/public/img/integrations/okta.png +0 -0
  49. package/dist/examples/vintasend-medplum-example/public/img/integrations/openai.png +0 -0
  50. package/dist/examples/vintasend-medplum-example/public/img/integrations/particle.png +0 -0
  51. package/dist/examples/vintasend-medplum-example/public/img/integrations/quest.png +0 -0
  52. package/dist/examples/vintasend-medplum-example/public/img/integrations/recaptcha.png +0 -0
  53. package/dist/examples/vintasend-medplum-example/public/img/integrations/snowflake.png +0 -0
  54. package/dist/examples/vintasend-medplum-example/public/img/integrations/stedi.png +0 -0
  55. package/dist/examples/vintasend-medplum-example/public/img/integrations/stripe.png +0 -0
  56. package/dist/examples/vintasend-medplum-example/public/img/integrations/sumo.png +0 -0
  57. package/dist/examples/vintasend-medplum-example/scripts/README.md +162 -0
  58. package/dist/examples/vintasend-medplum-example/scripts/client.ts +18 -0
  59. package/dist/examples/vintasend-medplum-example/scripts/deploy-bots.ts +171 -0
  60. package/dist/examples/vintasend-medplum-example/src/App.tsx +185 -0
  61. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemList.test.tsx +350 -0
  62. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemList.tsx +241 -0
  63. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemPanel.test.tsx +616 -0
  64. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemPanel.tsx +138 -0
  65. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionItem.test.tsx +92 -0
  66. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionItem.tsx +47 -0
  67. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionList.test.tsx +464 -0
  68. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionList.tsx +186 -0
  69. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionModal.test.tsx +80 -0
  70. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionModal.tsx +82 -0
  71. package/dist/examples/vintasend-medplum-example/src/components/DoseSpotIcon.test.tsx +100 -0
  72. package/dist/examples/vintasend-medplum-example/src/components/DoseSpotIcon.tsx +20 -0
  73. package/dist/examples/vintasend-medplum-example/src/components/IntegrationCard.module.css +3 -0
  74. package/dist/examples/vintasend-medplum-example/src/components/IntegrationCard.tsx +62 -0
  75. package/dist/examples/vintasend-medplum-example/src/components/MessageWithLinks.tsx +47 -0
  76. package/dist/examples/vintasend-medplum-example/src/components/PerformingLabInput.test.tsx +299 -0
  77. package/dist/examples/vintasend-medplum-example/src/components/PerformingLabInput.tsx +52 -0
  78. package/dist/examples/vintasend-medplum-example/src/components/ResourceFormWithRequiredProfile.tsx +82 -0
  79. package/dist/examples/vintasend-medplum-example/src/components/encounter/BillingTab.test.tsx +1016 -0
  80. package/dist/examples/vintasend-medplum-example/src/components/encounter/BillingTab.tsx +298 -0
  81. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterChart.test.tsx +732 -0
  82. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterChart.tsx +282 -0
  83. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterHeader.test.tsx +268 -0
  84. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterHeader.tsx +224 -0
  85. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignAddendum.test.tsx +255 -0
  86. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignAddendum.tsx +212 -0
  87. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignLockDialog.test.tsx +120 -0
  88. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignLockDialog.tsx +57 -0
  89. package/dist/examples/vintasend-medplum-example/src/components/encounter/VisitDetailsPanel.test.tsx +224 -0
  90. package/dist/examples/vintasend-medplum-example/src/components/encounter/VisitDetailsPanel.tsx +100 -0
  91. package/dist/examples/vintasend-medplum-example/src/components/labs/CoverageInput.test.tsx +431 -0
  92. package/dist/examples/vintasend-medplum-example/src/components/labs/CoverageInput.tsx +130 -0
  93. package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.module.css +31 -0
  94. package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.test.tsx +234 -0
  95. package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.tsx +143 -0
  96. package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.module.css +11 -0
  97. package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.test.tsx +875 -0
  98. package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.tsx +943 -0
  99. package/dist/examples/vintasend-medplum-example/src/components/labs/LabResultDetails.test.tsx +413 -0
  100. package/dist/examples/vintasend-medplum-example/src/components/labs/LabResultDetails.tsx +203 -0
  101. package/dist/examples/vintasend-medplum-example/src/components/labs/LabSelectEmpty.tsx +22 -0
  102. package/dist/examples/vintasend-medplum-example/src/components/labs/README.md +104 -0
  103. package/dist/examples/vintasend-medplum-example/src/components/labs/TestMetadataCardInput.test.tsx +318 -0
  104. package/dist/examples/vintasend-medplum-example/src/components/labs/TestMetadataCardInput.tsx +87 -0
  105. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatList.test.tsx +126 -0
  106. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatList.tsx +38 -0
  107. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.module.css +23 -0
  108. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.test.tsx +167 -0
  109. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.tsx +53 -0
  110. package/dist/examples/vintasend-medplum-example/src/components/messages/NewTopicDialog.test.tsx +94 -0
  111. package/dist/examples/vintasend-medplum-example/src/components/messages/NewTopicDialog.tsx +165 -0
  112. package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.module.css +8 -0
  113. package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.test.tsx +523 -0
  114. package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.tsx +230 -0
  115. package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.module.css +23 -0
  116. package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.test.tsx +567 -0
  117. package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.tsx +358 -0
  118. package/dist/examples/vintasend-medplum-example/src/components/plandefinition/AddPlanDefinition.module.css +40 -0
  119. package/dist/examples/vintasend-medplum-example/src/components/plandefinition/AddPlanDefinition.tsx +257 -0
  120. package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.module.css +7 -0
  121. package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.test.tsx +279 -0
  122. package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.tsx +156 -0
  123. package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.module.css +45 -0
  124. package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.test.tsx +90 -0
  125. package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.tsx +84 -0
  126. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourceBox.module.css +26 -0
  127. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourceBox.tsx +90 -0
  128. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourcePanel.test.tsx +305 -0
  129. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourcePanel.tsx +46 -0
  130. package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.module.css +262 -0
  131. package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.test.tsx +622 -0
  132. package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.tsx +286 -0
  133. package/dist/examples/vintasend-medplum-example/src/components/tasks/NewTaskModal.tsx +275 -0
  134. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskAttachmentList.tsx +132 -0
  135. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.module.css +45 -0
  136. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.test.tsx +749 -0
  137. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.tsx +416 -0
  138. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailPanel.test.tsx +278 -0
  139. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailPanel.tsx +133 -0
  140. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.module.css +16 -0
  141. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.test.tsx +255 -0
  142. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.tsx +203 -0
  143. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFileUpload.tsx +129 -0
  144. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.test.tsx +156 -0
  145. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.tsx +142 -0
  146. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.utils.ts +28 -0
  147. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskInputNote.test.tsx +134 -0
  148. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskInputNote.tsx +250 -0
  149. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.module.css +23 -0
  150. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.test.tsx +149 -0
  151. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.tsx +53 -0
  152. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskNoteItem.test.tsx +68 -0
  153. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskNoteItem.tsx +46 -0
  154. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskProperties.test.tsx +555 -0
  155. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskProperties.tsx +170 -0
  156. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskSelectEmpty.test.tsx +32 -0
  157. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskSelectEmpty.tsx +34 -0
  158. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/SimpleTask.test.tsx +47 -0
  159. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/SimpleTask.tsx +29 -0
  160. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskPanel.test.tsx +285 -0
  161. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskPanel.tsx +129 -0
  162. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskQuestionnaireForm.test.tsx +455 -0
  163. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskQuestionnaireForm.tsx +167 -0
  164. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskServiceRequest.test.tsx +435 -0
  165. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskServiceRequest.tsx +116 -0
  166. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.module.css +38 -0
  167. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.test.tsx +200 -0
  168. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.tsx +84 -0
  169. package/dist/examples/vintasend-medplum-example/src/components/utils.test.ts +176 -0
  170. package/dist/examples/vintasend-medplum-example/src/components/utils.ts +17 -0
  171. package/dist/examples/vintasend-medplum-example/src/config/constants.ts +3 -0
  172. package/dist/examples/vintasend-medplum-example/src/hooks/useDebouncedUpdateResource.test.tsx +166 -0
  173. package/dist/examples/vintasend-medplum-example/src/hooks/useDebouncedUpdateResource.ts +28 -0
  174. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounter.test.tsx +94 -0
  175. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounter.ts +11 -0
  176. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounterChart.test.tsx +477 -0
  177. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounterChart.ts +191 -0
  178. package/dist/examples/vintasend-medplum-example/src/hooks/usePatient.test.tsx +100 -0
  179. package/dist/examples/vintasend-medplum-example/src/hooks/usePatient.ts +18 -0
  180. package/dist/examples/vintasend-medplum-example/src/hooks/useThreadInbox.test.tsx +379 -0
  181. package/dist/examples/vintasend-medplum-example/src/hooks/useThreadInbox.ts +194 -0
  182. package/dist/examples/vintasend-medplum-example/src/index.css +8 -0
  183. package/dist/examples/vintasend-medplum-example/src/main.tsx +57 -0
  184. package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.module.css +6 -0
  185. package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.test.tsx +295 -0
  186. package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.tsx +124 -0
  187. package/dist/examples/vintasend-medplum-example/src/pages/SignInPage.test.tsx +77 -0
  188. package/dist/examples/vintasend-medplum-example/src/pages/SignInPage.tsx +22 -0
  189. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterChartPage.test.tsx +87 -0
  190. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterChartPage.tsx +27 -0
  191. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.module.css +16 -0
  192. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.test.tsx +287 -0
  193. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.tsx +151 -0
  194. package/dist/examples/vintasend-medplum-example/src/pages/integrations/DoseSpotFavoritesPage.test.tsx +519 -0
  195. package/dist/examples/vintasend-medplum-example/src/pages/integrations/DoseSpotFavoritesPage.tsx +179 -0
  196. package/dist/examples/vintasend-medplum-example/src/pages/integrations/FavoriteMedicationsTable.tsx +76 -0
  197. package/dist/examples/vintasend-medplum-example/src/pages/integrations/IntegrationsPage.test.tsx +234 -0
  198. package/dist/examples/vintasend-medplum-example/src/pages/integrations/IntegrationsPage.tsx +222 -0
  199. package/dist/examples/vintasend-medplum-example/src/pages/labs/OrderLabsPage.test.tsx +356 -0
  200. package/dist/examples/vintasend-medplum-example/src/pages/labs/OrderLabsPage.tsx +275 -0
  201. package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.module.css +8 -0
  202. package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.test.tsx +103 -0
  203. package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.tsx +78 -0
  204. package/dist/examples/vintasend-medplum-example/src/pages/patient/CommunicationTab.test.tsx +84 -0
  205. package/dist/examples/vintasend-medplum-example/src/pages/patient/CommunicationTab.tsx +82 -0
  206. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotAdvancedOptions.test.tsx +364 -0
  207. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotAdvancedOptions.tsx +149 -0
  208. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotTab.test.tsx +159 -0
  209. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotTab.tsx +37 -0
  210. package/dist/examples/vintasend-medplum-example/src/pages/patient/EditTab.test.tsx +140 -0
  211. package/dist/examples/vintasend-medplum-example/src/pages/patient/EditTab.tsx +72 -0
  212. package/dist/examples/vintasend-medplum-example/src/pages/patient/ExportTab.test.tsx +57 -0
  213. package/dist/examples/vintasend-medplum-example/src/pages/patient/ExportTab.tsx +14 -0
  214. package/dist/examples/vintasend-medplum-example/src/pages/patient/IntakeFormPage.test.tsx +241 -0
  215. package/dist/examples/vintasend-medplum-example/src/pages/patient/IntakeFormPage.tsx +710 -0
  216. package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.module.css +37 -0
  217. package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.test.tsx +428 -0
  218. package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.tsx +334 -0
  219. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.module.css +24 -0
  220. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.test.tsx +154 -0
  221. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.tsx +115 -0
  222. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.utils.test.ts +223 -0
  223. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.utils.ts +89 -0
  224. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientSearchPage.test.tsx +147 -0
  225. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientSearchPage.tsx +79 -0
  226. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientTabsNavigation.tsx +35 -0
  227. package/dist/examples/vintasend-medplum-example/src/pages/patient/TasksTab.test.tsx +185 -0
  228. package/dist/examples/vintasend-medplum-example/src/pages/patient/TasksTab.tsx +115 -0
  229. package/dist/examples/vintasend-medplum-example/src/pages/patient/TimelineTab.tsx +14 -0
  230. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceCreatePage.test.tsx +170 -0
  231. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceCreatePage.tsx +117 -0
  232. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceDetailPage.tsx +28 -0
  233. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceEditPage.test.tsx +131 -0
  234. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceEditPage.tsx +65 -0
  235. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceHistoryPage.test.tsx +108 -0
  236. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceHistoryPage.tsx +16 -0
  237. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.module.css +7 -0
  238. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.test.tsx +37 -0
  239. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.tsx +44 -0
  240. package/dist/examples/vintasend-medplum-example/src/pages/resource/useResourceType.ts +44 -0
  241. package/dist/examples/vintasend-medplum-example/src/pages/resource/utils.ts +9 -0
  242. package/dist/examples/vintasend-medplum-example/src/pages/schedule/SchedulePage.test.tsx +302 -0
  243. package/dist/examples/vintasend-medplum-example/src/pages/schedule/SchedulePage.tsx +416 -0
  244. package/dist/examples/vintasend-medplum-example/src/pages/spaces/ChatInput.tsx +91 -0
  245. package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.module.css +6 -0
  246. package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.test.tsx +102 -0
  247. package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.tsx +44 -0
  248. package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.module.css +7 -0
  249. package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.test.tsx +133 -0
  250. package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.tsx +91 -0
  251. package/dist/examples/vintasend-medplum-example/src/test-utils/render.tsx +20 -0
  252. package/dist/examples/vintasend-medplum-example/src/test.setup.ts +49 -0
  253. package/dist/examples/vintasend-medplum-example/src/types/encounter.ts +8 -0
  254. package/dist/examples/vintasend-medplum-example/src/types/spaces.ts +10 -0
  255. package/dist/examples/vintasend-medplum-example/src/utils/chargeitems.test.ts +141 -0
  256. package/dist/examples/vintasend-medplum-example/src/utils/chargeitems.ts +59 -0
  257. package/dist/examples/vintasend-medplum-example/src/utils/claims.test.ts +153 -0
  258. package/dist/examples/vintasend-medplum-example/src/utils/claims.ts +65 -0
  259. package/dist/examples/vintasend-medplum-example/src/utils/communication-search.ts +47 -0
  260. package/dist/examples/vintasend-medplum-example/src/utils/coverage.test.ts +48 -0
  261. package/dist/examples/vintasend-medplum-example/src/utils/coverage.ts +33 -0
  262. package/dist/examples/vintasend-medplum-example/src/utils/documentReference.test.ts +102 -0
  263. package/dist/examples/vintasend-medplum-example/src/utils/documentReference.ts +55 -0
  264. package/dist/examples/vintasend-medplum-example/src/utils/encounter.test.ts +169 -0
  265. package/dist/examples/vintasend-medplum-example/src/utils/encounter.ts +261 -0
  266. package/dist/examples/vintasend-medplum-example/src/utils/intake-form.test.ts +154 -0
  267. package/dist/examples/vintasend-medplum-example/src/utils/intake-form.ts +272 -0
  268. package/dist/examples/vintasend-medplum-example/src/utils/intake-utils.test.ts +1137 -0
  269. package/dist/examples/vintasend-medplum-example/src/utils/intake-utils.ts +827 -0
  270. package/dist/examples/vintasend-medplum-example/src/utils/notifications.test.ts +27 -0
  271. package/dist/examples/vintasend-medplum-example/src/utils/notifications.ts +15 -0
  272. package/dist/examples/vintasend-medplum-example/src/utils/spaceMessaging.ts +249 -0
  273. package/dist/examples/vintasend-medplum-example/src/utils/spacePersistence.test.ts +450 -0
  274. package/dist/examples/vintasend-medplum-example/src/utils/spacePersistence.ts +147 -0
  275. package/dist/examples/vintasend-medplum-example/src/utils/task-search.ts +63 -0
  276. package/dist/examples/vintasend-medplum-example/src/vite-env.d.ts +3 -0
  277. package/dist/examples/vintasend-medplum-example/tsconfig.bots.json +4 -0
  278. package/dist/examples/vintasend-medplum-example/tsconfig.json +19 -0
  279. package/dist/examples/vintasend-medplum-example/vercel.json +3 -0
  280. package/dist/examples/vintasend-medplum-example/vite.config.ts +44 -0
  281. package/dist/services/notification-backends/base-notification-backend.d.ts +5 -0
  282. package/dist/services/notification-service.js +11 -1
  283. package/dist/services/notification-template-renderers/base-email-template-renderer.d.ts +5 -0
  284. package/package.json +1 -1
@@ -0,0 +1,523 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { MantineProvider } from '@mantine/core';
4
+ import type { Practitioner, Patient, Reference } from '@medplum/fhirtypes';
5
+ import { MockClient } from '@medplum/mock';
6
+ import { MedplumProvider } from '@medplum/react';
7
+ import { act, render, screen, waitFor } from '@testing-library/react';
8
+ import userEvent from '@testing-library/user-event';
9
+ import { describe, expect, test, vi, beforeEach } from 'vitest';
10
+ import { ParticipantFilter } from './ParticipantFilter';
11
+ import type { WithId } from '@medplum/core';
12
+
13
+ const mockPractitioner: Practitioner = {
14
+ resourceType: 'Practitioner',
15
+ id: 'practitioner-123',
16
+ name: [{ given: ['John'], family: 'Doe' }],
17
+ };
18
+
19
+ const mockPatient: Patient = {
20
+ resourceType: 'Patient',
21
+ id: 'patient-456',
22
+ name: [{ given: ['Jane'], family: 'Smith' }],
23
+ };
24
+
25
+ const mockOnFilterChange = vi.fn();
26
+
27
+ describe('ParticipantFilter', () => {
28
+ let medplum: MockClient;
29
+
30
+ beforeEach(async () => {
31
+ medplum = new MockClient();
32
+ vi.clearAllMocks();
33
+
34
+ medplum.setProfile(mockPractitioner);
35
+ });
36
+
37
+ const setup = async (
38
+ selectedParticipants: Reference<Patient | Practitioner>[] = []
39
+ ): Promise<ReturnType<typeof userEvent.setup>> => {
40
+ const user = userEvent.setup();
41
+ await act(async () => {
42
+ render(
43
+ <MantineProvider>
44
+ <MedplumProvider medplum={medplum}>
45
+ <ParticipantFilter selectedParticipants={selectedParticipants} onFilterChange={mockOnFilterChange} />
46
+ </MedplumProvider>
47
+ </MantineProvider>
48
+ );
49
+ });
50
+ return user;
51
+ };
52
+
53
+ test('renders participant filter button', async () => {
54
+ await setup();
55
+ const button = screen.getByRole('button');
56
+ expect(button).toBeInTheDocument();
57
+ });
58
+
59
+ test('button shows light variant when no filter is active', async () => {
60
+ await setup([]);
61
+ const button = screen.getByRole('button');
62
+ expect(button).toHaveAttribute('data-variant', 'light');
63
+ });
64
+
65
+ test('button shows filled variant when filter is active', async () => {
66
+ await setup([{ reference: 'Patient/patient-456', display: 'Jane Smith' }]);
67
+ const button = screen.getByRole('button');
68
+ expect(button).toHaveAttribute('data-variant', 'filled');
69
+ });
70
+
71
+ test('opens popover when button is clicked', async () => {
72
+ const user = await setup();
73
+
74
+ const button = screen.getByRole('button');
75
+ await user.click(button);
76
+
77
+ await waitFor(() => {
78
+ expect(screen.getByText('Message Participants')).toBeInTheDocument();
79
+ });
80
+ });
81
+
82
+ test('shows current user at the top of the list', async () => {
83
+ const user = await setup();
84
+
85
+ const button = screen.getByRole('button');
86
+ await user.click(button);
87
+
88
+ await waitFor(() => {
89
+ expect(screen.getByText(/John Doe/)).toBeInTheDocument();
90
+ expect(screen.getByText('(you)')).toBeInTheDocument();
91
+ });
92
+ });
93
+
94
+ test('current user is not selected by default when no filter active', async () => {
95
+ const user = await setup([]);
96
+
97
+ const button = screen.getByRole('button');
98
+ await user.click(button);
99
+
100
+ await waitFor(() => {
101
+ const checkboxes = screen.getAllByRole('checkbox');
102
+ expect(checkboxes[0]).not.toBeChecked();
103
+ });
104
+ });
105
+
106
+ test('current user is selected when in selectedParticipants', async () => {
107
+ const user = await setup([{ reference: 'Practitioner/practitioner-123', display: 'John Doe' }]);
108
+
109
+ const button = screen.getByRole('button');
110
+ await user.click(button);
111
+
112
+ await waitFor(() => {
113
+ const checkboxes = screen.getAllByRole('checkbox');
114
+ expect(checkboxes[0]).toBeChecked();
115
+ });
116
+ });
117
+
118
+ test('calls onFilterChange when participant is selected', async () => {
119
+ const user = await setup([]);
120
+
121
+ const button = screen.getByRole('button');
122
+ await user.click(button);
123
+
124
+ await waitFor(() => {
125
+ expect(screen.getByText(/John Doe/)).toBeInTheDocument();
126
+ const checkboxes = document.querySelectorAll('input[type="checkbox"]');
127
+ expect(checkboxes.length).toBeGreaterThan(0);
128
+ });
129
+
130
+ const checkbox = document.querySelectorAll('input[type="checkbox"]')[0];
131
+ await user.click(checkbox);
132
+
133
+ expect(mockOnFilterChange).toHaveBeenCalledWith([
134
+ { reference: 'Practitioner/practitioner-123', display: 'John Doe' },
135
+ ]);
136
+ });
137
+
138
+ test('calls onFilterChange when participant is deselected', async () => {
139
+ const user = await setup([{ reference: 'Practitioner/practitioner-123', display: 'John Doe' }]);
140
+
141
+ const button = screen.getByRole('button');
142
+ await user.click(button);
143
+
144
+ await waitFor(() => {
145
+ expect(screen.getByText(/John Doe/)).toBeInTheDocument();
146
+ const checkboxes = document.querySelectorAll<HTMLInputElement>('input[type="checkbox"]');
147
+ expect(checkboxes[0]?.checked).toBe(true);
148
+ });
149
+
150
+ const checkbox = document.querySelectorAll('input[type="checkbox"]')[0];
151
+ await user.click(checkbox);
152
+
153
+ expect(mockOnFilterChange).toHaveBeenCalledWith([]);
154
+ });
155
+
156
+ test('shows search input with placeholder', async () => {
157
+ const user = await setup();
158
+
159
+ const button = screen.getByRole('button');
160
+ await user.click(button);
161
+
162
+ await waitFor(() => {
163
+ const searchInput = screen.getByPlaceholderText('Search for a Patient or Practitioner...');
164
+ expect(searchInput).toBeInTheDocument();
165
+ });
166
+ });
167
+
168
+ test('search input is available when popover opens', async () => {
169
+ const user = await setup();
170
+
171
+ const button = screen.getByRole('button');
172
+ await user.click(button);
173
+
174
+ await waitFor(() => {
175
+ expect(screen.getByText('Message Participants')).toBeInTheDocument();
176
+ const searchInput = screen.getByPlaceholderText('Search for a Patient or Practitioner...');
177
+ expect(searchInput).toBeInTheDocument();
178
+ });
179
+ });
180
+
181
+ test('searches for participants when typing in search input', async () => {
182
+ const searchSpy = vi.spyOn(medplum, 'search').mockResolvedValue({
183
+ resourceType: 'Bundle',
184
+ type: 'searchset',
185
+ entry: [{ resource: mockPatient as WithId<Patient> }],
186
+ });
187
+ const user = await setup();
188
+
189
+ const button = screen.getByRole('button');
190
+ await user.click(button);
191
+
192
+ await waitFor(() => {
193
+ expect(screen.getByText('Message Participants')).toBeInTheDocument();
194
+ });
195
+
196
+ const searchInput = screen.getByPlaceholderText('Search for a Patient or Practitioner...');
197
+ await user.type(searchInput, 'Jane');
198
+
199
+ await waitFor(() => {
200
+ expect(searchSpy).toHaveBeenCalledWith('Patient', {
201
+ _type: 'Patient,Practitioner',
202
+ name: 'Jane',
203
+ _count: '10',
204
+ });
205
+ });
206
+ });
207
+
208
+ test('displays search results', async () => {
209
+ vi.spyOn(medplum, 'search').mockResolvedValue({
210
+ resourceType: 'Bundle',
211
+ type: 'searchset',
212
+ entry: [{ resource: mockPatient as WithId<Patient> }],
213
+ });
214
+ const user = await setup();
215
+
216
+ const button = screen.getByRole('button');
217
+ await user.click(button);
218
+
219
+ await waitFor(() => {
220
+ expect(screen.getByText('Message Participants')).toBeInTheDocument();
221
+ });
222
+
223
+ const searchInput = screen.getByPlaceholderText('Search for a Patient or Practitioner...');
224
+ await user.type(searchInput, 'Jane');
225
+
226
+ await waitFor(() => {
227
+ expect(screen.getByText(/Jane Smith/)).toBeInTheDocument();
228
+ });
229
+ });
230
+
231
+ test('filters out current user from search results', async () => {
232
+ vi.spyOn(medplum, 'search').mockResolvedValue({
233
+ resourceType: 'Bundle',
234
+ type: 'searchset',
235
+ entry: [{ resource: mockPractitioner as WithId<Practitioner> }],
236
+ });
237
+ const user = await setup();
238
+
239
+ const button = screen.getByRole('button');
240
+ await user.click(button);
241
+
242
+ await waitFor(() => {
243
+ expect(screen.getByText('Message Participants')).toBeInTheDocument();
244
+ });
245
+
246
+ const searchInput = screen.getByPlaceholderText('Search for a Patient or Practitioner...');
247
+ await user.type(searchInput, 'John');
248
+
249
+ await waitFor(() => {
250
+ const johnDoeElements = screen.getAllByText(/John Doe/);
251
+ expect(johnDoeElements).toHaveLength(1);
252
+ });
253
+ });
254
+
255
+ test('shows "No results found" when search returns empty', async () => {
256
+ vi.spyOn(medplum, 'search').mockResolvedValue({
257
+ resourceType: 'Bundle',
258
+ type: 'searchset',
259
+ entry: [],
260
+ });
261
+ const user = await setup();
262
+
263
+ const button = screen.getByRole('button');
264
+ await user.click(button);
265
+
266
+ await waitFor(() => {
267
+ expect(screen.getByText('Message Participants')).toBeInTheDocument();
268
+ });
269
+
270
+ const searchInput = screen.getByPlaceholderText('Search for a Patient or Practitioner...');
271
+ await user.type(searchInput, 'nonexistent');
272
+
273
+ await waitFor(() => {
274
+ expect(screen.getByText('No results found')).toBeInTheDocument();
275
+ });
276
+ });
277
+
278
+ test('clears search input when clear button is clicked', async () => {
279
+ vi.spyOn(medplum, 'search').mockResolvedValue({
280
+ resourceType: 'Bundle',
281
+ type: 'searchset',
282
+ entry: [{ resource: mockPatient as WithId<Patient> }],
283
+ });
284
+ const user = await setup();
285
+
286
+ const button = screen.getByRole('button');
287
+ await user.click(button);
288
+
289
+ await waitFor(() => {
290
+ expect(screen.getByText('Message Participants')).toBeInTheDocument();
291
+ });
292
+
293
+ const searchInput = screen.getByPlaceholderText('Search for a Patient or Practitioner...');
294
+ await user.type(searchInput, 'Jane');
295
+
296
+ await waitFor(() => {
297
+ expect(searchInput).toHaveValue('Jane');
298
+ });
299
+
300
+ await user.clear(searchInput);
301
+
302
+ await waitFor(() => {
303
+ expect(searchInput).toHaveValue('');
304
+ });
305
+ });
306
+
307
+ test('selecting a search result calls onFilterChange', async () => {
308
+ vi.spyOn(medplum, 'search').mockResolvedValue({
309
+ resourceType: 'Bundle',
310
+ type: 'searchset',
311
+ entry: [{ resource: mockPatient as WithId<Patient> }],
312
+ });
313
+ const user = await setup([]);
314
+
315
+ const button = screen.getByRole('button');
316
+ await user.click(button);
317
+
318
+ await waitFor(() => {
319
+ expect(screen.getByText('Message Participants')).toBeInTheDocument();
320
+ });
321
+
322
+ const searchInput = screen.getByPlaceholderText('Search for a Patient or Practitioner...');
323
+ await user.type(searchInput, 'Jane');
324
+
325
+ await waitFor(() => {
326
+ expect(screen.getByText(/Jane Smith/)).toBeInTheDocument();
327
+ const checkboxes = document.querySelectorAll('input[type="checkbox"]');
328
+ expect(checkboxes.length).toBeGreaterThan(1);
329
+ });
330
+
331
+ const checkboxes = document.querySelectorAll('input[type="checkbox"]');
332
+ await user.click(checkboxes[1]);
333
+
334
+ expect(mockOnFilterChange).toHaveBeenCalledWith([{ reference: 'Patient/patient-456', display: 'Jane Smith' }]);
335
+ });
336
+
337
+ test('shows additional participants from props', async () => {
338
+ const user = await setup([{ reference: 'Patient/patient-456', display: 'Jane Smith' }]);
339
+
340
+ const button = screen.getByRole('button');
341
+ await user.click(button);
342
+
343
+ await waitFor(() => {
344
+ expect(screen.getByText(/Jane Smith/)).toBeInTheDocument();
345
+ });
346
+ });
347
+
348
+ test('multiple participants can be selected', async () => {
349
+ const user = await setup([
350
+ { reference: 'Practitioner/practitioner-123', display: 'John Doe' },
351
+ { reference: 'Patient/patient-456', display: 'Jane Smith' },
352
+ ]);
353
+
354
+ const button = screen.getByRole('button');
355
+ await user.click(button);
356
+
357
+ await waitFor(() => {
358
+ const checkboxes = screen.getAllByRole('checkbox');
359
+ expect(checkboxes[0]).toBeChecked();
360
+ expect(checkboxes[1]).toBeChecked();
361
+ });
362
+ });
363
+
364
+ test('removes participant when X button is clicked', async () => {
365
+ const user = await setup([{ reference: 'Patient/patient-456', display: 'Jane Smith' }]);
366
+
367
+ const button = screen.getByRole('button');
368
+ await user.click(button);
369
+
370
+ await waitFor(() => {
371
+ expect(screen.getByText(/Jane Smith/)).toBeInTheDocument();
372
+ });
373
+
374
+ const closeButtons = document.querySelectorAll('button[class*="CloseButton"]');
375
+ expect(closeButtons.length).toBeGreaterThan(0);
376
+ const participantCloseButton = closeButtons[closeButtons.length - 1];
377
+ await user.click(participantCloseButton);
378
+
379
+ expect(mockOnFilterChange).toHaveBeenCalledWith([]);
380
+ });
381
+
382
+ test('clears search when clear button is clicked', async () => {
383
+ vi.spyOn(medplum, 'search').mockResolvedValue({
384
+ resourceType: 'Bundle',
385
+ type: 'searchset',
386
+ entry: [{ resource: mockPatient as WithId<Patient> }],
387
+ });
388
+ const user = await setup();
389
+
390
+ const button = screen.getByRole('button');
391
+ await user.click(button);
392
+
393
+ await waitFor(() => {
394
+ expect(screen.getByText('Message Participants')).toBeInTheDocument();
395
+ });
396
+
397
+ const searchInput = screen.getByPlaceholderText('Search for a Patient or Practitioner...');
398
+ await user.type(searchInput, 'Jane');
399
+
400
+ await waitFor(() => {
401
+ expect(searchInput).toHaveValue('Jane');
402
+ });
403
+
404
+ const inputWrapper = searchInput.closest('.mantine-TextInput-root');
405
+ const clearButton = inputWrapper?.querySelector('button[class*="CloseButton"]');
406
+ expect(clearButton).toBeInTheDocument();
407
+ await user.click(clearButton as Element);
408
+
409
+ await waitFor(() => {
410
+ expect(searchInput).toHaveValue('');
411
+ });
412
+ });
413
+
414
+ test('closes popover when clicking outside', async () => {
415
+ const user = await setup();
416
+
417
+ const button = screen.getByRole('button');
418
+ await user.click(button);
419
+
420
+ await waitFor(() => {
421
+ expect(screen.getByText('Message Participants')).toBeInTheDocument();
422
+ });
423
+
424
+ await user.click(document.body);
425
+
426
+ await waitFor(() => {
427
+ expect(screen.queryByText('Message Participants')).not.toBeInTheDocument();
428
+ });
429
+ });
430
+
431
+ test('handles no profile (logged out state)', async () => {
432
+ medplum.setProfile(undefined as unknown as Practitioner);
433
+
434
+ const user = await setup();
435
+
436
+ const button = screen.getByRole('button');
437
+ await user.click(button);
438
+
439
+ await waitFor(() => {
440
+ expect(screen.getByText('Message Participants')).toBeInTheDocument();
441
+ });
442
+
443
+ expect(screen.queryByText('(you)')).not.toBeInTheDocument();
444
+ });
445
+
446
+ test('shows participant reference when display is not available', async () => {
447
+ const user = await setup([{ reference: 'Patient/unknown-patient' }]);
448
+
449
+ const button = screen.getByRole('button');
450
+ await user.click(button);
451
+
452
+ await waitFor(() => {
453
+ expect(screen.getByText('Patient/unknown-patient')).toBeInTheDocument();
454
+ });
455
+ });
456
+
457
+ test('handles search error gracefully', async () => {
458
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
459
+ vi.spyOn(medplum, 'search').mockRejectedValue(new Error('Search failed'));
460
+ const user = await setup();
461
+
462
+ const button = screen.getByRole('button');
463
+ await user.click(button);
464
+
465
+ await waitFor(() => {
466
+ expect(screen.getByText('Message Participants')).toBeInTheDocument();
467
+ });
468
+
469
+ const searchInput = screen.getByPlaceholderText('Search for a Patient or Practitioner...');
470
+ await user.type(searchInput, 'test');
471
+
472
+ await waitFor(() => {
473
+ expect(medplum.search).toHaveBeenCalled();
474
+ });
475
+
476
+ consoleSpy.mockRestore();
477
+ });
478
+
479
+ test('handles search results with undefined resources', async () => {
480
+ vi.spyOn(medplum, 'search').mockResolvedValue({
481
+ resourceType: 'Bundle',
482
+ type: 'searchset',
483
+ entry: [{ resource: mockPatient as WithId<Patient> }, { resource: undefined as unknown as WithId<Patient> }, {}],
484
+ });
485
+ const user = await setup();
486
+
487
+ const button = screen.getByRole('button');
488
+ await user.click(button);
489
+
490
+ await waitFor(() => {
491
+ expect(screen.getByText('Message Participants')).toBeInTheDocument();
492
+ });
493
+
494
+ const searchInput = screen.getByPlaceholderText('Search for a Patient or Practitioner...');
495
+ await user.type(searchInput, 'test');
496
+
497
+ await waitFor(() => {
498
+ expect(screen.getByText(/Jane Smith/)).toBeInTheDocument();
499
+ });
500
+ });
501
+
502
+ test('handles empty search query', async () => {
503
+ const searchSpy = vi.spyOn(medplum, 'search');
504
+ const user = await setup();
505
+
506
+ const button = screen.getByRole('button');
507
+ await user.click(button);
508
+
509
+ await waitFor(() => {
510
+ expect(screen.getByText('Message Participants')).toBeInTheDocument();
511
+ });
512
+
513
+ const searchInput = screen.getByPlaceholderText('Search for a Patient or Practitioner...');
514
+
515
+ await user.type(searchInput, ' ');
516
+
517
+ await new Promise<void>((resolve) => {
518
+ setTimeout(resolve, 400);
519
+ });
520
+
521
+ expect(searchSpy).not.toHaveBeenCalled();
522
+ });
523
+ });