Browse Source

Merge branch 'feat/test-env-updates'

liyasthomas 3 years ago
parent
commit
86d4fa5883

+ 12 - 1
packages/hoppscotch-app/components/environments/Add.vue

@@ -38,12 +38,18 @@
 
 <script lang="ts">
 import { defineComponent } from "@nuxtjs/composition-api"
-import { createEnvironment } from "~/newstore/environments"
+import { useReadonlyStream } from "~/helpers/utils/composables"
+import { createEnvironment, environments$ } from "~/newstore/environments"
 
 export default defineComponent({
   props: {
     show: Boolean,
   },
+  setup() {
+    return {
+      envList: useReadonlyStream(environments$, []),
+    }
+  },
   data() {
     return {
       name: null as string | null,
@@ -56,6 +62,11 @@ export default defineComponent({
         return
       }
       createEnvironment(this.name)
+      // TODO: find better way to get index of new environment
+      this.$emit("environment-added", {
+        name: this.name,
+        index: this.envList.length - 1,
+      })
       this.hideModal()
     },
     hideModal() {

+ 158 - 3
packages/hoppscotch-app/components/http/TestResult.vue

@@ -3,7 +3,9 @@
     <div
       v-if="
         testResults &&
-        (testResults.expectResults.length || testResults.tests.length)
+        (testResults.expectResults.length ||
+          testResults.tests.length ||
+          haveEnvVariables)
       "
     >
       <div
@@ -20,6 +22,78 @@
         />
       </div>
       <div class="border-b divide-y-4 divide-dividerLight border-dividerLight">
+        <div v-if="haveEnvVariables" class="flex flex-col">
+          <details class="flex flex-col divide-y divide-dividerLight" open>
+            <summary
+              class="flex items-center justify-between flex-1 min-w-0 cursor-pointer transition focus:outline-none text-secondaryLight text-tiny group"
+            >
+              <span
+                class="px-4 py-2 truncate transition group-hover:text-secondary capitalize-first"
+              >
+                {{ t("environment.title") }}
+              </span>
+            </summary>
+            <div class="divide-y divide-dividerLight">
+              <div
+                v-if="noEnvSelected && !globalHasAdditions"
+                class="flex bg-error p-4 text-secondaryDark"
+              >
+                <i class="mr-4 material-icons"> warning </i>
+                <div class="flex flex-col">
+                  <p>
+                    {{ t("environment.no_environment_description") }}
+                  </p>
+                  <p class="space-x-2 flex mt-3">
+                    <ButtonSecondary
+                      :label="t('environment.add_to_global')"
+                      class="text-tiny !bg-primary"
+                      filled
+                      @click.native="addEnvToGlobal()"
+                    />
+                    <ButtonSecondary
+                      :label="t('environment.create_new')"
+                      class="text-tiny !bg-primary"
+                      filled
+                      @click.native="displayModalAdd(true)"
+                    />
+                  </p>
+                </div>
+              </div>
+              <HttpTestResultEnv
+                v-for="(env, index) in testResults.envDiff.global.additions"
+                :key="`env-${env.key}-${index}`"
+                :env="env"
+                status="additions"
+                global
+              />
+              <HttpTestResultEnv
+                v-for="(env, index) in testResults.envDiff.global.updations"
+                :key="`env-${env.key}-${index}`"
+                :env="env"
+                status="updations"
+                global
+              />
+              <HttpTestResultEnv
+                v-for="(env, index) in testResults.envDiff.selected.additions"
+                :key="`env-${env.key}-${index}`"
+                :env="env"
+                status="additions"
+              />
+              <HttpTestResultEnv
+                v-for="(env, index) in testResults.envDiff.selected.updations"
+                :key="`env-${env.key}-${index}`"
+                :env="env"
+                status="updations"
+              />
+              <HttpTestResultEnv
+                v-for="(env, index) in testResults.envDiff.selected.deletions"
+                :key="`env-${env.key}-${index}`"
+                :env="env"
+                status="deletions"
+              />
+            </div>
+          </details>
+        </div>
         <div v-if="testResults.tests" class="divide-y-4 divide-dividerLight">
           <HttpTestResultEntry
             v-for="(result, index) in testResults.tests"
@@ -107,16 +181,97 @@
         class="my-4"
       />
     </div>
+    <EnvironmentsAdd
+      :show="showModalAdd"
+      @hide-modal="displayModalAdd(false)"
+      @environment-added="createNewEnv($event)"
+    />
   </div>
 </template>
 
 <script setup lang="ts">
-import { useReadonlyStream, useI18n } from "~/helpers/utils/composables"
+import { computed, Ref, ref } from "@nuxtjs/composition-api"
+import isEqual from "lodash/isEqual"
+import {
+  useReadonlyStream,
+  useI18n,
+  useStream,
+} from "~/helpers/utils/composables"
+import {
+  globalEnv$,
+  selectedEnvIndex$,
+  setCurrentEnvironment,
+  setGlobalEnvVariables,
+  updateEnvironment,
+} from "~/newstore/environments"
 import { restTestResults$, setRESTTestResults } from "~/newstore/RESTSession"
+import { HoppTestResult } from "~/helpers/types/HoppTestResult"
 
 const t = useI18n()
 
-const testResults = useReadonlyStream(restTestResults$, null)
+const showModalAdd = ref(false)
+
+const displayModalAdd = (shouldDisplay: boolean) => {
+  showModalAdd.value = shouldDisplay
+}
+
+const testResults = useReadonlyStream(
+  restTestResults$,
+  null
+) as Ref<HoppTestResult | null>
 
 const clearContent = () => setRESTTestResults(null)
+
+const haveEnvVariables = computed(() => {
+  if (!testResults.value) return false
+  return (
+    testResults.value.envDiff.global.additions.length ||
+    testResults.value.envDiff.global.updations.length ||
+    testResults.value.envDiff.global.deletions.length ||
+    testResults.value.envDiff.selected.additions.length ||
+    testResults.value.envDiff.selected.updations.length ||
+    testResults.value.envDiff.selected.deletions.length
+  )
+})
+
+const selectedEnvironmentIndex = useStream(
+  selectedEnvIndex$,
+  -1,
+  setCurrentEnvironment
+)
+
+const globalEnvVars = useReadonlyStream(globalEnv$, []) as Ref<
+  Array<{
+    key: string
+    value: string
+  }>
+>
+
+const noEnvSelected = computed(() => selectedEnvironmentIndex.value === -1)
+
+const globalHasAdditions = computed(() => {
+  if (!testResults.value?.envDiff.selected.additions) return false
+  return (
+    testResults.value.envDiff.selected.additions.every(
+      (x) => globalEnvVars.value.findIndex((y) => isEqual(x, y)) !== -1
+    ) ?? false
+  )
+})
+
+const addEnvToGlobal = () => {
+  if (!testResults.value?.envDiff.selected.additions) return
+  setGlobalEnvVariables([
+    ...globalEnvVars.value,
+    ...testResults.value.envDiff.selected.additions,
+  ])
+}
+
+const createNewEnv = ({ name, index }: { name: string; index: number }) => {
+  if (!testResults.value?.envDiff.selected.additions) return
+  updateEnvironment(index, {
+    name,
+    variables: testResults.value.envDiff.selected.additions,
+  })
+  setCurrentEnvironment(index)
+}
 </script>

+ 83 - 0
packages/hoppscotch-app/components/http/TestResultEnv.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="flex items-center justify-between px-4 py-2">
+    <div class="flex items-center">
+      <i
+        v-tippy="{ theme: 'tooltip' }"
+        class="mr-4 material-icons cursor-help"
+        :class="getStyle(status)"
+        :title="`${t(getTooltip(status))}`"
+      >
+        {{ getIcon(status) }}
+      </i>
+      <span class="text-secondaryDark">
+        {{ env.key }}
+      </span>
+      <span class="text-secondaryDark">
+        {{ ` \xA0 — \xA0 ${env.value}` }}
+      </span>
+      <span v-if="status === 'updations'" class="text-secondaryLight">
+        {{ ` \xA0 ← \xA0 ${env.previousValue}` }}
+      </span>
+    </div>
+    <span
+      v-if="global"
+      class="bg-accentLight px-1 rounded text-accentContrast text-tiny"
+    >
+      Global
+    </span>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useI18n } from "~/helpers/utils/composables"
+
+type Status = "updations" | "additions" | "deletions"
+type Props = {
+  env: {
+    key: string
+    value: string
+    previousValue?: string
+  }
+  status: Status
+  global: boolean
+}
+
+withDefaults(defineProps<Props>(), {
+  global: false,
+})
+
+const t = useI18n()
+
+const getIcon = (status: Status) => {
+  switch (status) {
+    case "additions":
+      return "add_circle"
+    case "updations":
+      return "check_circle"
+    case "deletions":
+      return "remove_circle"
+  }
+}
+
+const getStyle = (status: Status) => {
+  switch (status) {
+    case "additions":
+      return "text-green-500"
+    case "updations":
+      return "text-yellow-500"
+    case "deletions":
+      return "text-red-500"
+  }
+}
+
+const getTooltip = (status: Status) => {
+  switch (status) {
+    case "additions":
+      return "environment.added"
+    case "updations":
+      return "environment.updated"
+    case "deletions":
+      return "environment.deleted"
+  }
+}
+</script>

+ 5 - 1
packages/hoppscotch-app/components/lenses/ResponseBodyRenderer.vue

@@ -24,7 +24,11 @@
       :label="$t('test.results')"
       :indicator="
         testResults &&
-        (testResults.expectResults.length || testResults.tests.length)
+        (testResults.expectResults.length ||
+          testResults.tests.length ||
+          testResults.envDiff.selected.additions.length ||
+          testResults.envDiff.selected.updations.length ||
+          testResults.envDiff.global.updations.length)
           ? true
           : false
       "

+ 115 - 7
packages/hoppscotch-app/helpers/RequestRunner.ts

@@ -1,10 +1,17 @@
 import { Observable } from "rxjs"
 import { filter } from "rxjs/operators"
 import { chain, right, TaskEither } from "fp-ts/lib/TaskEither"
-import { pipe } from "fp-ts/function"
+import { flow, pipe } from "fp-ts/function"
 import * as O from "fp-ts/Option"
-import { runTestScript, TestDescriptor } from "@hoppscotch/js-sandbox"
+import * as A from "fp-ts/Array"
+import { Environment } from "@hoppscotch/data"
+import {
+  SandboxTestResult,
+  runTestScript,
+  TestDescriptor,
+} from "@hoppscotch/js-sandbox"
 import { isRight } from "fp-ts/Either"
+import cloneDeep from "lodash/cloneDeep"
 import {
   getCombinedEnvVariables,
   getFinalEnvsFromPreRequest,
@@ -15,6 +22,14 @@ import { createRESTNetworkRequestStream } from "./network"
 import { HoppTestData, HoppTestResult } from "./types/HoppTestResult"
 import { isJSONContentType } from "./utils/contenttypes"
 import { getRESTRequest, setRESTTestResults } from "~/newstore/RESTSession"
+import {
+  environmentsStore,
+  getCurrentEnvironment,
+  getEnviroment,
+  getGlobalVariables,
+  setGlobalEnvVariables,
+  updateEnvironment,
+} from "~/newstore/environments"
 
 const getTestableBody = (
   res: HoppRESTResponse & { type: "success" | "fail" }
@@ -43,6 +58,11 @@ const getTestableBody = (
   return x
 }
 
+const combineEnvVariables = (env: {
+  global: Environment["variables"]
+  selected: Environment["variables"]
+}) => [...env.selected, ...env.global]
+
 export const runRESTRequest$ = (): TaskEither<
   string | Error,
   Observable<HoppRESTResponse>
@@ -55,7 +75,7 @@ export const runRESTRequest$ = (): TaskEither<
     chain((envs) => {
       const effectiveRequest = getEffectiveRESTRequest(getRESTRequest(), {
         name: "Env",
-        variables: envs,
+        variables: combineEnvVariables(envs),
       })
 
       const stream = createRESTNetworkRequestStream(effectiveRequest)
@@ -65,7 +85,7 @@ export const runRESTRequest$ = (): TaskEither<
         .pipe(filter((res) => res.type === "success" || res.type === "fail"))
         .subscribe(async (res) => {
           if (res.type === "success" || res.type === "fail") {
-            const runResult = await runTestScript(res.req.testScript, {
+            const runResult = await runTestScript(res.req.testScript, envs, {
               status: res.statusCode,
               body: getTestableBody(res),
               headers: res.headers,
@@ -73,11 +93,38 @@ export const runRESTRequest$ = (): TaskEither<
 
             if (isRight(runResult)) {
               setRESTTestResults(translateToSandboxTestResults(runResult.right))
+
+              setGlobalEnvVariables(runResult.right.envs.global)
+
+              if (environmentsStore.value.currentEnvironmentIndex !== -1) {
+                const env = getEnviroment(
+                  environmentsStore.value.currentEnvironmentIndex
+                )
+                updateEnvironment(
+                  environmentsStore.value.currentEnvironmentIndex,
+                  {
+                    name: env.name,
+                    variables: runResult.right.envs.selected,
+                  }
+                )
+              }
             } else {
               setRESTTestResults({
                 description: "",
                 expectResults: [],
                 tests: [],
+                envDiff: {
+                  global: {
+                    additions: [],
+                    deletions: [],
+                    updations: [],
+                  },
+                  selected: {
+                    additions: [],
+                    deletions: [],
+                    updations: [],
+                  },
+                },
                 scriptError: true,
               })
             }
@@ -90,8 +137,47 @@ export const runRESTRequest$ = (): TaskEither<
     })
   )
 
+const getAddedEnvVariables = (
+  current: Environment["variables"],
+  updated: Environment["variables"]
+) => updated.filter((x) => current.findIndex((y) => y.key === x.key) === -1)
+
+const getRemovedEnvVariables = (
+  current: Environment["variables"],
+  updated: Environment["variables"]
+) => current.filter((x) => updated.findIndex((y) => y.key === x.key) === -1)
+
+const getUpdatedEnvVariables = (
+  current: Environment["variables"],
+  updated: Environment["variables"]
+) =>
+  pipe(
+    updated,
+    A.filterMap(
+      flow(
+        O.of,
+        O.bindTo("env"),
+        O.bind("index", ({ env }) =>
+          pipe(
+            current.findIndex((x) => x.key === env.key),
+            O.fromPredicate((x) => x !== -1)
+          )
+        ),
+        O.chain(
+          O.fromPredicate(
+            ({ env, index }) => env.value !== current[index].value
+          )
+        ),
+        O.map(({ env, index }) => ({
+          ...env,
+          previousValue: current[index].value,
+        }))
+      )
+    )
+  )
+
 function translateToSandboxTestResults(
-  testDesc: TestDescriptor
+  testDesc: SandboxTestResult
 ): HoppTestResult {
   const translateChildTests = (child: TestDescriptor): HoppTestData => {
     return {
@@ -100,10 +186,32 @@ function translateToSandboxTestResults(
       tests: child.children.map(translateChildTests),
     }
   }
+
+  const globals = cloneDeep(getGlobalVariables())
+  const env = cloneDeep(getCurrentEnvironment())
+
   return {
     description: "",
-    expectResults: testDesc.expectResults,
-    tests: testDesc.children.map(translateChildTests),
+    expectResults: testDesc.tests.expectResults,
+    tests: testDesc.tests.children.map(translateChildTests),
     scriptError: false,
+    envDiff: {
+      global: {
+        additions: getAddedEnvVariables(globals, testDesc.envs.global),
+        deletions: getRemovedEnvVariables(globals, testDesc.envs.global),
+        updations: getUpdatedEnvVariables(globals, testDesc.envs.global),
+      },
+      selected: {
+        additions: getAddedEnvVariables(env.variables, testDesc.envs.selected),
+        deletions: getRemovedEnvVariables(
+          env.variables,
+          testDesc.envs.selected
+        ),
+        updations: getUpdatedEnvVariables(
+          env.variables,
+          testDesc.envs.selected
+        ),
+      },
+    },
   }
 }

+ 10 - 19
packages/hoppscotch-app/helpers/preRequest.ts

@@ -1,29 +1,20 @@
 import { runPreRequestScript } from "@hoppscotch/js-sandbox"
+import { Environment } from "@hoppscotch/data"
+import cloneDeep from "lodash/cloneDeep"
 import {
   getCurrentEnvironment,
   getGlobalVariables,
 } from "~/newstore/environments"
 
-export const getCombinedEnvVariables = () => {
-  const variables: { key: string; value: string }[] = [...getGlobalVariables()]
-
-  for (const variable of getCurrentEnvironment().variables) {
-    const index = variables.findIndex((v) => variable.key === v.key)
-
-    if (index === -1) {
-      variables.push({
-        key: variable.key,
-        value: variable.value,
-      })
-    } else {
-      variables[index].value = variable.value
-    }
-  }
-
-  return variables
-}
+export const getCombinedEnvVariables = () => ({
+  global: cloneDeep(getGlobalVariables()),
+  selected: cloneDeep(getCurrentEnvironment().variables),
+})
 
 export const getFinalEnvsFromPreRequest = (
   script: string,
-  envs: { key: string; value: string }[]
+  envs: {
+    global: Environment["variables"]
+    selected: Environment["variables"]
+  }
 ) => runPreRequestScript(script, envs)

+ 4 - 1
packages/hoppscotch-app/helpers/terndoc/pw-pre.json

@@ -2,7 +2,10 @@
   "!name": "pw-pre",
   "pw": {
     "env": {
-      "set": "fn(key: string, value: string)"
+      "set": "fn(key: string, value: string)",
+      "get": "fn(key: string) -> string",
+      "getResolve": "fn(key: string) -> string",
+      "resolve": "fn(value: string) -> string"
     }
   }
 }

+ 6 - 0
packages/hoppscotch-app/helpers/terndoc/pw-test.json

@@ -19,6 +19,12 @@
       "headers": "?",
       "body": "?"
     },
+    "env": {
+      "set": "fn(key: string, value: string)",
+      "get": "fn(key: string) -> string",
+      "getResolve": "fn(key: string) -> string",
+      "resolve": "fn(value: string) -> string"
+    },
     "test": "fn(name: string, func: fn())"
   }
 }

+ 5 - 0
packages/hoppscotch-app/helpers/testSnippets.js

@@ -1,4 +1,9 @@
 export default [
+  {
+    name: "Environment: Set an environment variable",
+    script: `\n\n// Set an environment variable
+pw.env.set("variable", "value");`,
+  },
   {
     name: "Response: Status code is 200",
     script: `\n\n// Check status code is 200

+ 19 - 0
packages/hoppscotch-app/helpers/types/HoppTestResult.ts

@@ -1,3 +1,5 @@
+import { Environment } from "@hoppscotch/data"
+
 export type HoppTestExpectResult = {
   status: "fail" | "pass" | "error"
   message: string
@@ -14,4 +16,21 @@ export type HoppTestResult = {
   expectResults: HoppTestExpectResult[]
   description: string
   scriptError: boolean
+
+  envDiff: {
+    global: {
+      additions: Environment["variables"]
+      updations: Array<
+        Environment["variables"][number] & { previousValue: string }
+      >
+      deletions: Environment["variables"]
+    }
+    selected: {
+      additions: Environment["variables"]
+      updations: Array<
+        Environment["variables"][number] & { previousValue: string }
+      >
+      deletions: Environment["variables"]
+    }
+  }
 }

Some files were not shown because too many files changed in this diff