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,875 @@
1
+ // SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { MantineProvider } from '@mantine/core';
4
+ import { act, render, screen, waitFor } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+ import { MedplumProvider } from '@medplum/react';
7
+ import type {
8
+ Patient,
9
+ ServiceRequest,
10
+ Practitioner,
11
+ DiagnosticReport,
12
+ DocumentReference,
13
+ QuestionnaireResponse,
14
+ Observation,
15
+ } from '@medplum/fhirtypes';
16
+ import { MockClient } from '@medplum/mock';
17
+ import { MemoryRouter } from 'react-router';
18
+ import { describe, expect, test, vi, beforeEach } from 'vitest';
19
+ import { LabOrderDetails } from './LabOrderDetails';
20
+
21
+ // Mock the documentReference utility
22
+ vi.mock('../../utils/documentReference', () => ({
23
+ fetchLabOrderRequisitionDocuments: vi.fn(),
24
+ getHealthGorillaRequisitionId: vi.fn(),
25
+ }));
26
+
27
+ const mockPatient: Patient = {
28
+ resourceType: 'Patient',
29
+ id: 'patient-123',
30
+ name: [{ given: ['John'], family: 'Doe' }],
31
+ gender: 'male',
32
+ birthDate: '1990-01-01',
33
+ };
34
+
35
+ const mockPractitioner: Practitioner = {
36
+ resourceType: 'Practitioner',
37
+ id: 'practitioner-123',
38
+ name: [{ given: ['Dr. Jane'], family: 'Smith' }],
39
+ };
40
+
41
+ const mockActiveServiceRequest: ServiceRequest = {
42
+ resourceType: 'ServiceRequest',
43
+ id: 'service-request-123',
44
+ status: 'active',
45
+ intent: 'order',
46
+ code: {
47
+ coding: [
48
+ {
49
+ system: 'http://loinc.org',
50
+ code: '24323-8',
51
+ display: 'Complete Blood Count',
52
+ },
53
+ ],
54
+ text: 'CBC with Differential',
55
+ },
56
+ subject: {
57
+ reference: 'Patient/patient-123',
58
+ display: 'John Doe',
59
+ },
60
+ requester: {
61
+ reference: 'Practitioner/practitioner-123',
62
+ display: 'Dr. Jane Smith',
63
+ },
64
+ authoredOn: '2024-01-15T10:00:00Z',
65
+ priority: 'routine',
66
+ reasonCode: [
67
+ {
68
+ text: 'Annual physical exam',
69
+ },
70
+ ],
71
+ note: [
72
+ {
73
+ text: 'Patient is fasting',
74
+ },
75
+ ],
76
+ performer: [
77
+ {
78
+ display: 'Quest Diagnostics',
79
+ },
80
+ ],
81
+ requisition: {
82
+ system: 'https://www.healthgorilla.com',
83
+ value: 'HG-REQ-12345',
84
+ },
85
+ };
86
+
87
+ const mockCompletedServiceRequest: ServiceRequest = {
88
+ ...mockActiveServiceRequest,
89
+ status: 'completed',
90
+ meta: {
91
+ lastUpdated: '2024-01-20T14:30:00Z',
92
+ },
93
+ };
94
+
95
+ const mockDiagnosticReport: DiagnosticReport = {
96
+ resourceType: 'DiagnosticReport',
97
+ id: 'diagnostic-report-123',
98
+ status: 'final',
99
+ code: {
100
+ coding: [
101
+ {
102
+ system: 'http://loinc.org',
103
+ code: '24323-8',
104
+ display: 'Complete Blood Count',
105
+ },
106
+ ],
107
+ },
108
+ subject: {
109
+ reference: 'Patient/patient-123',
110
+ },
111
+ effectiveDateTime: '2024-01-18T09:00:00Z',
112
+ issued: '2024-01-19T15:00:00Z',
113
+ basedOn: [
114
+ {
115
+ reference: 'ServiceRequest/service-request-123',
116
+ },
117
+ ],
118
+ result: [
119
+ {
120
+ reference: 'Observation/observation-123',
121
+ },
122
+ ],
123
+ conclusion: 'All values within normal range',
124
+ presentedForm: [
125
+ {
126
+ contentType: 'application/pdf',
127
+ url: 'https://example.com/report.pdf',
128
+ title: 'Lab Report',
129
+ },
130
+ ],
131
+ };
132
+
133
+ const mockObservation: Observation = {
134
+ resourceType: 'Observation',
135
+ id: 'observation-123',
136
+ status: 'final',
137
+ code: {
138
+ coding: [
139
+ {
140
+ system: 'http://loinc.org',
141
+ code: '6690-2',
142
+ display: 'White Blood Cell Count',
143
+ },
144
+ ],
145
+ },
146
+ subject: {
147
+ reference: 'Patient/patient-123',
148
+ },
149
+ valueQuantity: {
150
+ value: 7.5,
151
+ unit: 'x10^3/uL',
152
+ system: 'http://unitsofmeasure.org',
153
+ },
154
+ };
155
+
156
+ const mockLabOrderRequisitionDoc: DocumentReference = {
157
+ resourceType: 'DocumentReference',
158
+ id: 'doc-ref-123',
159
+ status: 'current',
160
+ type: {
161
+ coding: [
162
+ {
163
+ code: 'LabOrderRequisition',
164
+ display: 'Lab Order Requisition',
165
+ },
166
+ ],
167
+ },
168
+ subject: {
169
+ reference: 'Patient/patient-123',
170
+ },
171
+ content: [
172
+ {
173
+ attachment: {
174
+ contentType: 'application/pdf',
175
+ url: 'https://example.com/requisition.pdf',
176
+ title: 'Lab Requisition',
177
+ },
178
+ },
179
+ ],
180
+ identifier: [
181
+ {
182
+ system: 'https://www.healthgorilla.com',
183
+ value: 'HG-REQ-12345',
184
+ },
185
+ ],
186
+ };
187
+
188
+ const mockSpecimenLabelDoc: DocumentReference = {
189
+ resourceType: 'DocumentReference',
190
+ id: 'doc-ref-456',
191
+ status: 'current',
192
+ type: {
193
+ coding: [
194
+ {
195
+ code: 'SpecimenLabel',
196
+ display: 'Specimen Label',
197
+ },
198
+ ],
199
+ },
200
+ subject: {
201
+ reference: 'Patient/patient-123',
202
+ },
203
+ content: [
204
+ {
205
+ attachment: {
206
+ contentType: 'application/pdf',
207
+ url: 'https://example.com/specimen-label.pdf',
208
+ title: 'Specimen Label',
209
+ },
210
+ },
211
+ ],
212
+ identifier: [
213
+ {
214
+ system: 'https://www.healthgorilla.com',
215
+ value: 'HG-REQ-12345',
216
+ },
217
+ ],
218
+ };
219
+
220
+ const mockQuestionnaireResponse: QuestionnaireResponse = {
221
+ resourceType: 'QuestionnaireResponse',
222
+ id: 'questionnaire-response-123',
223
+ status: 'completed',
224
+ authored: '2024-01-15T09:30:00Z',
225
+ item: [
226
+ {
227
+ linkId: 'question-1',
228
+ text: 'Is the patient fasting?',
229
+ answer: [
230
+ {
231
+ valueBoolean: true,
232
+ },
233
+ ],
234
+ },
235
+ {
236
+ linkId: 'question-2',
237
+ text: 'Current medications',
238
+ answer: [
239
+ {
240
+ valueString: 'Aspirin 81mg daily',
241
+ },
242
+ ],
243
+ },
244
+ ],
245
+ };
246
+
247
+ describe('LabOrderDetails', () => {
248
+ let medplum: MockClient;
249
+ let fetchLabOrderRequisitionDocumentsMock: any;
250
+ let getHealthGorillaRequisitionIdMock: any;
251
+
252
+ beforeEach(async () => {
253
+ medplum = new MockClient();
254
+ vi.clearAllMocks();
255
+
256
+ const documentReferenceUtils = await import('../../utils/documentReference');
257
+ fetchLabOrderRequisitionDocumentsMock = vi.mocked(documentReferenceUtils.fetchLabOrderRequisitionDocuments);
258
+ getHealthGorillaRequisitionIdMock = vi.mocked(documentReferenceUtils.getHealthGorillaRequisitionId);
259
+
260
+ // Setup default mocks
261
+ fetchLabOrderRequisitionDocumentsMock.mockResolvedValue([]);
262
+ getHealthGorillaRequisitionIdMock.mockReturnValue('HG-REQ-12345');
263
+ medplum.searchResources = vi.fn().mockResolvedValue([]);
264
+ });
265
+
266
+ const setup = (props: Partial<Parameters<typeof LabOrderDetails>[0]> = {}): ReturnType<typeof render> => {
267
+ return render(
268
+ <MemoryRouter>
269
+ <MedplumProvider medplum={medplum}>
270
+ <MantineProvider>
271
+ <LabOrderDetails order={mockActiveServiceRequest} {...props} />
272
+ </MantineProvider>
273
+ </MedplumProvider>
274
+ </MemoryRouter>
275
+ );
276
+ };
277
+
278
+ describe('Basic rendering', () => {
279
+ test('renders order details with active status', async () => {
280
+ medplum.readResource = vi.fn().mockImplementation((resourceType: string, id: string) => {
281
+ if (resourceType === 'Patient' && id === 'patient-123') {
282
+ return Promise.resolve(mockPatient);
283
+ }
284
+ if (resourceType === 'Practitioner' && id === 'practitioner-123') {
285
+ return Promise.resolve(mockPractitioner);
286
+ }
287
+ return Promise.reject(new Error('Not found'));
288
+ });
289
+
290
+ await act(async () => {
291
+ setup();
292
+ });
293
+
294
+ await waitFor(() => {
295
+ expect(screen.getByText('CBC with Differential')).toBeInTheDocument();
296
+ });
297
+
298
+ expect(screen.getByText(/Ordered/i)).toBeInTheDocument();
299
+ expect(screen.getByText('Active')).toBeInTheDocument();
300
+ });
301
+
302
+ test('renders order details with completed status', async () => {
303
+ medplum.readResource = vi.fn().mockResolvedValue(mockPatient);
304
+
305
+ await act(async () => {
306
+ setup({ order: mockCompletedServiceRequest });
307
+ });
308
+
309
+ await waitFor(() => {
310
+ expect(screen.getByText('CBC with Differential')).toBeInTheDocument();
311
+ });
312
+
313
+ expect(screen.getByText('Completed')).toBeInTheDocument();
314
+ });
315
+
316
+ test('displays multiple test codes', async () => {
317
+ const orderWithMultipleCodes: ServiceRequest = {
318
+ ...mockActiveServiceRequest,
319
+ code: {
320
+ coding: [
321
+ {
322
+ system: 'http://loinc.org',
323
+ code: '24323-8',
324
+ display: 'Complete Blood Count',
325
+ },
326
+ {
327
+ system: 'http://loinc.org',
328
+ code: '2345-7',
329
+ display: 'Glucose',
330
+ },
331
+ ],
332
+ },
333
+ };
334
+
335
+ await act(async () => {
336
+ setup({ order: orderWithMultipleCodes });
337
+ });
338
+
339
+ await waitFor(() => {
340
+ expect(screen.getByText(/Complete Blood Count, Glucose/)).toBeInTheDocument();
341
+ });
342
+ });
343
+
344
+ test('falls back to first code display when no text field', async () => {
345
+ const orderWithoutText: ServiceRequest = {
346
+ ...mockActiveServiceRequest,
347
+ code: {
348
+ coding: [
349
+ {
350
+ system: 'http://loinc.org',
351
+ code: '24323-8',
352
+ display: 'Complete Blood Count',
353
+ },
354
+ ],
355
+ },
356
+ };
357
+
358
+ await act(async () => {
359
+ setup({ order: orderWithoutText });
360
+ });
361
+
362
+ await waitFor(() => {
363
+ expect(screen.getByText('Complete Blood Count')).toBeInTheDocument();
364
+ });
365
+ });
366
+ });
367
+
368
+ describe('Tab switching', () => {
369
+ test('defaults to progress tracker for active orders', async () => {
370
+ await act(async () => {
371
+ setup({ order: mockActiveServiceRequest });
372
+ });
373
+
374
+ await waitFor(() => {
375
+ const progressButton = screen.getByText('Progress Tracker');
376
+ expect(progressButton).toBeInTheDocument();
377
+ });
378
+ });
379
+
380
+ test('defaults to report tab for completed orders', async () => {
381
+ medplum.searchResources = vi.fn().mockResolvedValue([mockDiagnosticReport]);
382
+
383
+ await act(async () => {
384
+ setup({ order: mockCompletedServiceRequest });
385
+ });
386
+
387
+ await waitFor(() => {
388
+ const reportButton = screen.getByText('Report');
389
+ expect(reportButton).toBeInTheDocument();
390
+ });
391
+ });
392
+
393
+ test('switches to order details tab when clicked', async () => {
394
+ const user = userEvent.setup();
395
+ medplum.readResource = vi.fn().mockResolvedValue(mockPractitioner);
396
+
397
+ await act(async () => {
398
+ setup();
399
+ });
400
+
401
+ await waitFor(() => {
402
+ expect(screen.getByText('Progress Tracker')).toBeInTheDocument();
403
+ });
404
+
405
+ const orderDetailsButton = screen.getByText('Order Details');
406
+ await user.click(orderDetailsButton);
407
+
408
+ await waitFor(() => {
409
+ expect(screen.getByText('Order Date')).toBeInTheDocument();
410
+ expect(screen.getByText('Test Code')).toBeInTheDocument();
411
+ });
412
+ });
413
+ });
414
+
415
+ describe('Order details tab', () => {
416
+ beforeEach(() => {
417
+ medplum.readResource = vi.fn().mockImplementation((resourceType: string, id: string) => {
418
+ if (resourceType === 'Patient' && id === 'patient-123') {
419
+ return Promise.resolve(mockPatient);
420
+ }
421
+ if (resourceType === 'Practitioner' && id === 'practitioner-123') {
422
+ return Promise.resolve(mockPractitioner);
423
+ }
424
+ return Promise.reject(new Error('Not found'));
425
+ });
426
+ });
427
+
428
+ test('displays order information correctly', async () => {
429
+ const user = userEvent.setup();
430
+
431
+ await act(async () => {
432
+ setup();
433
+ });
434
+
435
+ const orderDetailsButton = screen.getByText('Order Details');
436
+ await user.click(orderDetailsButton);
437
+
438
+ await waitFor(() => {
439
+ expect(screen.getByText('Order Date')).toBeInTheDocument();
440
+ expect(screen.getByText('Test Code')).toBeInTheDocument();
441
+ expect(screen.getByText('Ordering provider')).toBeInTheDocument();
442
+ expect(screen.getByText('Performing lab')).toBeInTheDocument();
443
+ expect(screen.getByText('Requisition ID')).toBeInTheDocument();
444
+ expect(screen.getByText('Patient')).toBeInTheDocument();
445
+ expect(screen.getByText('Priority')).toBeInTheDocument();
446
+ });
447
+
448
+ expect(screen.getByText('Dr. Jane Smith')).toBeInTheDocument();
449
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
450
+ expect(screen.getByText('Quest Diagnostics')).toBeInTheDocument();
451
+ expect(screen.getByText('HG-REQ-12345')).toBeInTheDocument();
452
+ expect(screen.getByText('routine')).toBeInTheDocument();
453
+ });
454
+
455
+ test('displays reason code and notes', async () => {
456
+ const user = userEvent.setup();
457
+
458
+ await act(async () => {
459
+ setup();
460
+ });
461
+
462
+ const orderDetailsButton = screen.getByText('Order Details');
463
+ await user.click(orderDetailsButton);
464
+
465
+ await waitFor(() => {
466
+ expect(screen.getByText('Reason')).toBeInTheDocument();
467
+ expect(screen.getByText('Annual physical exam')).toBeInTheDocument();
468
+ expect(screen.getByText('Notes')).toBeInTheDocument();
469
+ expect(screen.getByText('Patient is fasting')).toBeInTheDocument();
470
+ });
471
+ });
472
+
473
+ test('displays order detail if present', async () => {
474
+ const user = userEvent.setup();
475
+ const orderWithDetails: ServiceRequest = {
476
+ ...mockActiveServiceRequest,
477
+ orderDetail: [
478
+ {
479
+ text: 'Stat processing required',
480
+ },
481
+ ],
482
+ };
483
+
484
+ await act(async () => {
485
+ setup({ order: orderWithDetails });
486
+ });
487
+
488
+ const orderDetailsButton = screen.getByRole('button', { name: 'Order Details' });
489
+ await user.click(orderDetailsButton);
490
+
491
+ await waitFor(() => {
492
+ expect(screen.getByText('Stat processing required')).toBeInTheDocument();
493
+ });
494
+ });
495
+ });
496
+
497
+ describe('Progress tracker', () => {
498
+ test('displays progress tracker for active orders', async () => {
499
+ await act(async () => {
500
+ setup({ order: mockActiveServiceRequest });
501
+ });
502
+
503
+ await waitFor(() => {
504
+ expect(screen.getByText('Order Sent')).toBeInTheDocument();
505
+ expect(screen.getByText('Order Acknowledged')).toBeInTheDocument();
506
+ expect(screen.getByText('Testing')).toBeInTheDocument();
507
+ expect(screen.getByText('Final')).toBeInTheDocument();
508
+ });
509
+ });
510
+
511
+ test('marks steps as completed based on available data', async () => {
512
+ fetchLabOrderRequisitionDocumentsMock.mockResolvedValue([mockLabOrderRequisitionDoc]);
513
+ medplum.searchResources = vi.fn().mockResolvedValue([mockDiagnosticReport]);
514
+
515
+ await act(async () => {
516
+ setup({ order: mockActiveServiceRequest });
517
+ });
518
+
519
+ await waitFor(() => {
520
+ expect(screen.getByText('Order Sent')).toBeInTheDocument();
521
+ expect(screen.getByText('Order Acknowledged')).toBeInTheDocument();
522
+ });
523
+ });
524
+
525
+ test('shows timestamps for completed steps', async () => {
526
+ await act(async () => {
527
+ setup({ order: mockActiveServiceRequest });
528
+ });
529
+
530
+ await waitFor(() => {
531
+ expect(screen.getByText('Order Sent')).toBeInTheDocument();
532
+ });
533
+ });
534
+ });
535
+
536
+ describe('Diagnostic report', () => {
537
+ test('displays diagnostic report for completed orders', async () => {
538
+ medplum.searchResources = vi.fn().mockResolvedValue([mockDiagnosticReport]);
539
+ medplum.readResource = vi.fn().mockResolvedValue(mockObservation);
540
+
541
+ await act(async () => {
542
+ setup({ order: mockCompletedServiceRequest });
543
+ });
544
+
545
+ await waitFor(
546
+ () => {
547
+ expect(screen.getByText('Report Status')).toBeInTheDocument();
548
+ const finalStatuses = screen.getAllByText('final');
549
+ expect(finalStatuses.length).toBeGreaterThan(0);
550
+ },
551
+ { timeout: 3000 }
552
+ );
553
+ });
554
+
555
+ test('displays issue date', async () => {
556
+ medplum.searchResources = vi.fn().mockResolvedValue([mockDiagnosticReport]);
557
+
558
+ await act(async () => {
559
+ setup({ order: mockCompletedServiceRequest });
560
+ });
561
+
562
+ await waitFor(() => {
563
+ expect(screen.getByText('Issue Date')).toBeInTheDocument();
564
+ });
565
+ });
566
+
567
+ test('displays conclusion if present', async () => {
568
+ medplum.searchResources = vi.fn().mockResolvedValue([mockDiagnosticReport]);
569
+
570
+ await act(async () => {
571
+ setup({ order: mockCompletedServiceRequest });
572
+ });
573
+
574
+ await waitFor(
575
+ () => {
576
+ const interpretations = screen.getAllByText('Interpretation');
577
+ expect(interpretations.length).toBeGreaterThan(0);
578
+ expect(screen.getByText('All values within normal range')).toBeInTheDocument();
579
+ },
580
+ { timeout: 3000 }
581
+ );
582
+ });
583
+
584
+ test('displays lab document section with presentedForm', async () => {
585
+ medplum.searchResources = vi.fn().mockResolvedValue([mockDiagnosticReport]);
586
+
587
+ await act(async () => {
588
+ setup({ order: mockCompletedServiceRequest });
589
+ });
590
+
591
+ await waitFor(() => {
592
+ expect(screen.getByText('Lab Document')).toBeInTheDocument();
593
+ });
594
+ });
595
+ });
596
+
597
+ describe('Document references', () => {
598
+ test('fetches and displays lab order requisition documents', async () => {
599
+ const user = userEvent.setup();
600
+ fetchLabOrderRequisitionDocumentsMock.mockResolvedValue([mockLabOrderRequisitionDoc]);
601
+
602
+ await act(async () => {
603
+ setup();
604
+ });
605
+
606
+ const orderDetailsButton = screen.getByText('Order Details');
607
+ await user.click(orderDetailsButton);
608
+
609
+ await waitFor(() => {
610
+ expect(fetchLabOrderRequisitionDocumentsMock).toHaveBeenCalledWith(medplum, mockActiveServiceRequest);
611
+ expect(screen.getByText('Requisition Document')).toBeInTheDocument();
612
+ });
613
+ });
614
+
615
+ test('fetches and displays specimen label documents', async () => {
616
+ const user = userEvent.setup();
617
+ medplum.searchResources = vi.fn().mockImplementation(async (resourceType: string, params: any) => {
618
+ const searchParams = new URLSearchParams(params);
619
+ if (resourceType === 'DocumentReference' && searchParams.get('category') === 'SpecimenLabel') {
620
+ return [mockSpecimenLabelDoc];
621
+ }
622
+ return [];
623
+ });
624
+
625
+ await act(async () => {
626
+ setup();
627
+ });
628
+
629
+ const orderDetailsButton = screen.getByRole('button', { name: 'Order Details' });
630
+ await user.click(orderDetailsButton);
631
+
632
+ await waitFor(
633
+ () => {
634
+ const specimenLabels = screen.getAllByText('Specimen Label');
635
+ expect(specimenLabels.length).toBeGreaterThan(0);
636
+ },
637
+ { timeout: 3000 }
638
+ );
639
+ });
640
+
641
+ test('displays message when no specimen label documents found', async () => {
642
+ const user = userEvent.setup();
643
+
644
+ await act(async () => {
645
+ setup();
646
+ });
647
+
648
+ const orderDetailsButton = screen.getByText('Order Details');
649
+ await user.click(orderDetailsButton);
650
+
651
+ await waitFor(() => {
652
+ expect(screen.getByText('No specimen label documents found.')).toBeInTheDocument();
653
+ });
654
+ });
655
+ });
656
+
657
+ describe('Questionnaire response', () => {
658
+ test('fetches and displays questionnaire response from current order', async () => {
659
+ const user = userEvent.setup();
660
+ const orderWithQuestionnaire: ServiceRequest = {
661
+ ...mockActiveServiceRequest,
662
+ supportingInfo: [
663
+ {
664
+ reference: 'QuestionnaireResponse/questionnaire-response-123',
665
+ },
666
+ ],
667
+ };
668
+ medplum.readResource = vi.fn().mockResolvedValue(mockQuestionnaireResponse);
669
+
670
+ await act(async () => {
671
+ setup({ order: orderWithQuestionnaire });
672
+ });
673
+
674
+ const orderDetailsButton = screen.getByText('Order Details');
675
+ await user.click(orderDetailsButton);
676
+
677
+ await waitFor(() => {
678
+ expect(screen.getByText('Order Entry Questions')).toBeInTheDocument();
679
+ expect(screen.getByText('Is the patient fasting?')).toBeInTheDocument();
680
+ expect(screen.getByText('true')).toBeInTheDocument();
681
+ expect(screen.getByText('Current medications')).toBeInTheDocument();
682
+ expect(screen.getByText('Aspirin 81mg daily')).toBeInTheDocument();
683
+ });
684
+ });
685
+
686
+ test('searches for questionnaire response from original order', async () => {
687
+ const user = userEvent.setup();
688
+ const originalOrder: ServiceRequest = {
689
+ ...mockActiveServiceRequest,
690
+ id: 'original-order-123',
691
+ supportingInfo: [
692
+ {
693
+ reference: 'QuestionnaireResponse/questionnaire-response-123',
694
+ },
695
+ ],
696
+ basedOn: [
697
+ {
698
+ reference: 'ServiceRequest/service-request-123',
699
+ },
700
+ ],
701
+ };
702
+
703
+ medplum.searchResources = vi.fn().mockResolvedValue([originalOrder]);
704
+ medplum.readResource = vi.fn().mockResolvedValue(mockQuestionnaireResponse);
705
+
706
+ await act(async () => {
707
+ setup();
708
+ });
709
+
710
+ const orderDetailsButton = screen.getByText('Order Details');
711
+ await user.click(orderDetailsButton);
712
+
713
+ await waitFor(() => {
714
+ expect(medplum.searchResources).toHaveBeenCalledWith('ServiceRequest', expect.any(Object));
715
+ });
716
+ });
717
+
718
+ test('handles different answer types in questionnaire response', async () => {
719
+ const user = userEvent.setup();
720
+ const questionnaireWithMultipleAnswerTypes: QuestionnaireResponse = {
721
+ ...mockQuestionnaireResponse,
722
+ item: [
723
+ {
724
+ linkId: 'q1',
725
+ text: 'Boolean question',
726
+ answer: [{ valueBoolean: true }],
727
+ },
728
+ {
729
+ linkId: 'q2',
730
+ text: 'String question',
731
+ answer: [{ valueString: 'Answer text' }],
732
+ },
733
+ {
734
+ linkId: 'q3',
735
+ text: 'Integer question',
736
+ answer: [{ valueInteger: 42 }],
737
+ },
738
+ {
739
+ linkId: 'q4',
740
+ text: 'Decimal question',
741
+ answer: [{ valueDecimal: 3.14 }],
742
+ },
743
+ {
744
+ linkId: 'q5',
745
+ text: 'Date question',
746
+ answer: [{ valueDate: '2024-01-15' }],
747
+ },
748
+ {
749
+ linkId: 'q6',
750
+ text: 'Coding question',
751
+ answer: [{ valueCoding: { code: 'CODE-1', display: 'Display text' } }],
752
+ },
753
+ {
754
+ linkId: 'q7',
755
+ text: 'Quantity question',
756
+ answer: [{ valueQuantity: { value: 100, unit: 'mg' } }],
757
+ },
758
+ ],
759
+ };
760
+
761
+ const orderWithQuestionnaire: ServiceRequest = {
762
+ ...mockActiveServiceRequest,
763
+ supportingInfo: [
764
+ {
765
+ reference: 'QuestionnaireResponse/questionnaire-response-123',
766
+ },
767
+ ],
768
+ };
769
+
770
+ medplum.readResource = vi.fn().mockResolvedValue(questionnaireWithMultipleAnswerTypes);
771
+
772
+ await act(async () => {
773
+ setup({ order: orderWithQuestionnaire });
774
+ });
775
+
776
+ const orderDetailsButton = screen.getByText('Order Details');
777
+ await user.click(orderDetailsButton);
778
+
779
+ await waitFor(() => {
780
+ expect(screen.getByText('Boolean question')).toBeInTheDocument();
781
+ expect(screen.getByText('true')).toBeInTheDocument();
782
+ expect(screen.getByText('String question')).toBeInTheDocument();
783
+ expect(screen.getByText('Answer text')).toBeInTheDocument();
784
+ expect(screen.getByText('Integer question')).toBeInTheDocument();
785
+ expect(screen.getByText('42')).toBeInTheDocument();
786
+ expect(screen.getByText('Decimal question')).toBeInTheDocument();
787
+ expect(screen.getByText('3.14')).toBeInTheDocument();
788
+ expect(screen.getByText('Date question')).toBeInTheDocument();
789
+ expect(screen.getByText('2024-01-15')).toBeInTheDocument();
790
+ expect(screen.getByText('Coding question')).toBeInTheDocument();
791
+ expect(screen.getByText('Display text')).toBeInTheDocument();
792
+ expect(screen.getByText('Quantity question')).toBeInTheDocument();
793
+ expect(screen.getByText('100 mg')).toBeInTheDocument();
794
+ });
795
+ });
796
+ });
797
+
798
+ describe('Status badges', () => {
799
+ test.each([
800
+ ['active', 'Active'],
801
+ ['completed', 'Completed'],
802
+ ['draft', 'Draft'],
803
+ ['requested', 'Requested'],
804
+ ['on-hold', 'On Hold'],
805
+ ['cancelled', 'Cancelled'],
806
+ ['revoked', 'Revoked'],
807
+ ['entered-in-error', 'Error'],
808
+ ['unknown', 'Unknown'],
809
+ ])('displays %s status correctly', async (status, expectedText) => {
810
+ const orderWithStatus: ServiceRequest = {
811
+ ...mockActiveServiceRequest,
812
+ status: status as any,
813
+ };
814
+
815
+ await act(async () => {
816
+ setup({ order: orderWithStatus });
817
+ });
818
+
819
+ await waitFor(() => {
820
+ expect(screen.getByText(expectedText)).toBeInTheDocument();
821
+ });
822
+ });
823
+ });
824
+
825
+ describe('Error handling', () => {
826
+ test('handles error when fetching requisition documents fails', async () => {
827
+ const user = userEvent.setup();
828
+ fetchLabOrderRequisitionDocumentsMock.mockRejectedValue(new Error('Fetch failed'));
829
+
830
+ await act(async () => {
831
+ setup();
832
+ });
833
+
834
+ const orderDetailsButton = screen.getByText('Order Details');
835
+ await user.click(orderDetailsButton);
836
+
837
+ await waitFor(() => {
838
+ expect(screen.getByText('No lab order requisition documents found.')).toBeInTheDocument();
839
+ });
840
+ });
841
+ });
842
+
843
+ describe('Cleanup', () => {
844
+ test('cleans up documents when order changes', async () => {
845
+ const { rerender } = await act(async () => {
846
+ return setup();
847
+ });
848
+
849
+ await waitFor(() => {
850
+ expect(fetchLabOrderRequisitionDocumentsMock).toHaveBeenCalledTimes(1);
851
+ });
852
+
853
+ const newOrder: ServiceRequest = {
854
+ ...mockActiveServiceRequest,
855
+ id: 'new-order-456',
856
+ };
857
+
858
+ await act(async () => {
859
+ rerender(
860
+ <MemoryRouter>
861
+ <MedplumProvider medplum={medplum}>
862
+ <MantineProvider>
863
+ <LabOrderDetails order={newOrder} />
864
+ </MantineProvider>
865
+ </MedplumProvider>
866
+ </MemoryRouter>
867
+ );
868
+ });
869
+
870
+ await waitFor(() => {
871
+ expect(fetchLabOrderRequisitionDocumentsMock).toHaveBeenCalledWith(medplum, newOrder);
872
+ });
873
+ });
874
+ });
875
+ });