TestResult.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. <template>
  2. <div>
  3. <div
  4. v-if="
  5. testResults &&
  6. (testResults.expectResults.length ||
  7. testResults.tests.length ||
  8. haveEnvVariables)
  9. "
  10. >
  11. <div
  12. class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight top-lowerSecondaryStickyFold"
  13. >
  14. <label class="font-semibold text-secondaryLight">
  15. {{ t("test.report") }}
  16. </label>
  17. <ButtonSecondary
  18. v-tippy="{ theme: 'tooltip' }"
  19. :title="t('action.clear')"
  20. svg="trash-2"
  21. @click.native="clearContent()"
  22. />
  23. </div>
  24. <div class="border-b divide-y-4 divide-dividerLight border-dividerLight">
  25. <div v-if="haveEnvVariables" class="flex flex-col">
  26. <details class="flex flex-col divide-y divide-dividerLight" open>
  27. <summary
  28. class="flex items-center justify-between flex-1 min-w-0 cursor-pointer transition focus:outline-none text-secondaryLight text-tiny group"
  29. >
  30. <span
  31. class="px-4 py-2 truncate transition group-hover:text-secondary capitalize-first"
  32. >
  33. {{ t("environment.title") }}
  34. </span>
  35. </summary>
  36. <div class="divide-y divide-dividerLight">
  37. <div
  38. v-if="noEnvSelected && !globalHasAdditions"
  39. class="flex p-4 bg-error text-secondaryDark"
  40. role="alert"
  41. >
  42. <i class="mr-4 material-icons"> warning </i>
  43. <div class="flex flex-col">
  44. <p>
  45. {{ t("environment.no_environment_description") }}
  46. </p>
  47. <p class="flex mt-3 space-x-2">
  48. <ButtonSecondary
  49. :label="t('environment.add_to_global')"
  50. class="text-tiny !bg-primary"
  51. filled
  52. @click.native="addEnvToGlobal()"
  53. />
  54. <ButtonSecondary
  55. :label="t('environment.create_new')"
  56. class="text-tiny !bg-primary"
  57. filled
  58. @click.native="displayModalAdd(true)"
  59. />
  60. </p>
  61. </div>
  62. </div>
  63. <HttpTestResultEnv
  64. v-for="(env, index) in testResults.envDiff.global.additions"
  65. :key="`env-${env.key}-${index}`"
  66. :env="env"
  67. status="additions"
  68. global
  69. />
  70. <HttpTestResultEnv
  71. v-for="(env, index) in testResults.envDiff.global.updations"
  72. :key="`env-${env.key}-${index}`"
  73. :env="env"
  74. status="updations"
  75. global
  76. />
  77. <HttpTestResultEnv
  78. v-for="(env, index) in testResults.envDiff.selected.additions"
  79. :key="`env-${env.key}-${index}`"
  80. :env="env"
  81. status="additions"
  82. />
  83. <HttpTestResultEnv
  84. v-for="(env, index) in testResults.envDiff.selected.updations"
  85. :key="`env-${env.key}-${index}`"
  86. :env="env"
  87. status="updations"
  88. />
  89. <HttpTestResultEnv
  90. v-for="(env, index) in testResults.envDiff.selected.deletions"
  91. :key="`env-${env.key}-${index}`"
  92. :env="env"
  93. status="deletions"
  94. />
  95. </div>
  96. </details>
  97. </div>
  98. <div v-if="testResults.tests" class="divide-y-4 divide-dividerLight">
  99. <HttpTestResultEntry
  100. v-for="(result, index) in testResults.tests"
  101. :key="`result-${index}`"
  102. :test-results="result"
  103. />
  104. </div>
  105. <div
  106. v-if="testResults.expectResults"
  107. class="divide-y divide-dividerLight"
  108. >
  109. <HttpTestResultReport
  110. v-if="testResults.expectResults.length"
  111. :test-results="testResults"
  112. />
  113. <div
  114. v-for="(result, index) in testResults.expectResults"
  115. :key="`result-${index}`"
  116. class="flex items-center px-4 py-2"
  117. >
  118. <i
  119. class="mr-4 material-icons"
  120. :class="
  121. result.status === 'pass' ? 'text-green-500' : 'text-red-500'
  122. "
  123. >
  124. {{ result.status === "pass" ? "check" : "close" }}
  125. </i>
  126. <span v-if="result.message" class="text-secondaryDark">
  127. {{ result.message }}
  128. </span>
  129. <span class="text-secondaryLight">
  130. {{
  131. ` \xA0 — \xA0 ${
  132. result.status === "pass" ? t("test.passed") : t("test.failed")
  133. }`
  134. }}
  135. </span>
  136. </div>
  137. </div>
  138. </div>
  139. </div>
  140. <div
  141. v-else-if="testResults && testResults.scriptError"
  142. class="flex flex-col items-center justify-center flex-1 p-4"
  143. >
  144. <img
  145. :src="`/images/states/${$colorMode.value}/youre_lost.svg`"
  146. loading="lazy"
  147. class="inline-flex flex-col object-contain object-center w-32 h-32 my-4"
  148. :alt="`${t('error.test_script_fail')}`"
  149. />
  150. <span class="mb-2 font-semibold text-center">
  151. {{ t("error.test_script_fail") }}
  152. </span>
  153. <span
  154. class="max-w-sm mb-6 text-center whitespace-normal text-secondaryLight"
  155. >
  156. {{ t("helpers.test_script_fail") }}
  157. </span>
  158. </div>
  159. <div
  160. v-else
  161. class="flex flex-col items-center justify-center p-4 text-secondaryLight"
  162. >
  163. <img
  164. :src="`/images/states/${$colorMode.value}/validation.svg`"
  165. loading="lazy"
  166. class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
  167. :alt="`${t('empty.tests')}`"
  168. />
  169. <span class="pb-2 text-center">
  170. {{ t("empty.tests") }}
  171. </span>
  172. <span class="pb-4 text-center">
  173. {{ t("helpers.tests") }}
  174. </span>
  175. <ButtonSecondary
  176. outline
  177. :label="`${t('action.learn_more')}`"
  178. to="https://docs.hoppscotch.io/features/tests"
  179. blank
  180. svg="external-link"
  181. reverse
  182. class="my-4"
  183. />
  184. </div>
  185. <EnvironmentsDetails
  186. :show="showModalDetails"
  187. action="new"
  188. :env-vars="getAdditionVars"
  189. @hide-modal="displayModalAdd(false)"
  190. />
  191. </div>
  192. </template>
  193. <script setup lang="ts">
  194. import { computed, Ref, ref } from "@nuxtjs/composition-api"
  195. import isEqual from "lodash/isEqual"
  196. import {
  197. useReadonlyStream,
  198. useI18n,
  199. useStream,
  200. } from "~/helpers/utils/composables"
  201. import {
  202. globalEnv$,
  203. selectedEnvIndex$,
  204. setCurrentEnvironment,
  205. setGlobalEnvVariables,
  206. } from "~/newstore/environments"
  207. import { restTestResults$, setRESTTestResults } from "~/newstore/RESTSession"
  208. import { HoppTestResult } from "~/helpers/types/HoppTestResult"
  209. const t = useI18n()
  210. const showModalDetails = ref(false)
  211. const displayModalAdd = (shouldDisplay: boolean) => {
  212. showModalDetails.value = shouldDisplay
  213. }
  214. const testResults = useReadonlyStream(
  215. restTestResults$,
  216. null
  217. ) as Ref<HoppTestResult | null>
  218. /**
  219. * Get the "addition" environment variables
  220. * @returns Array of objects with key-value pairs of arguments
  221. */
  222. const getAdditionVars = () =>
  223. testResults?.value?.envDiff?.selected?.additions
  224. ? testResults.value.envDiff.selected.additions
  225. : []
  226. const clearContent = () => setRESTTestResults(null)
  227. const haveEnvVariables = computed(() => {
  228. if (!testResults.value) return false
  229. return (
  230. testResults.value.envDiff.global.additions.length ||
  231. testResults.value.envDiff.global.updations.length ||
  232. testResults.value.envDiff.global.deletions.length ||
  233. testResults.value.envDiff.selected.additions.length ||
  234. testResults.value.envDiff.selected.updations.length ||
  235. testResults.value.envDiff.selected.deletions.length
  236. )
  237. })
  238. const selectedEnvironmentIndex = useStream(
  239. selectedEnvIndex$,
  240. -1,
  241. setCurrentEnvironment
  242. )
  243. const globalEnvVars = useReadonlyStream(globalEnv$, []) as Ref<
  244. Array<{
  245. key: string
  246. value: string
  247. }>
  248. >
  249. const noEnvSelected = computed(() => selectedEnvironmentIndex.value === -1)
  250. const globalHasAdditions = computed(() => {
  251. if (!testResults.value?.envDiff.selected.additions) return false
  252. return (
  253. testResults.value.envDiff.selected.additions.every(
  254. (x) => globalEnvVars.value.findIndex((y) => isEqual(x, y)) !== -1
  255. ) ?? false
  256. )
  257. })
  258. const addEnvToGlobal = () => {
  259. if (!testResults.value?.envDiff.selected.additions) return
  260. setGlobalEnvVariables([
  261. ...globalEnvVars.value,
  262. ...testResults.value.envDiff.selected.additions,
  263. ])
  264. }
  265. </script>