Sluman_Slumun_Slamet
Sluman_Slumun_Slamet

Reputation: 59

Test Vue Component(created using TypeScript) with Vue-test-utils

I'm trying to test props with multiple data types in a Vue component (which was created with TypeScript) using Vue-test-utils package. I tried using using expect().tobe() but there is still an uncovered line:

DropDownList.vue

<template>
  <v-select
    :id="dropDownListID"
    :label="labelDisplay"
    :placeholder="getPlaceHolder"
    :item-text="itemtext"
    :item-value="itemvalue"
    :items="items"
    :value="value"
    @input="handleInput"
    :required="required"
    :rules="[required ? rules.required :false,additionalRulesFormated]"
    :class="{ 'roboto10':true,'xddl':true,['xddl-'+size] :true}"
    :return-object="returnobjectvalue"
    append-icon="expand_more"
    :disabled="disabled"
    :clearable="clearableValue"
  >
    <template slot="item" slot-scope="data">
      <v-flex>{{ data.item[itemtext] }}</v-flex>
    </template>
    <template slot="selection" slot-scope="data">
      <v-flex xs2 v-if="itemicon">
        <v-icon color="#3F3F3F" size="25px">{{ data.item[itemicon] }}</v-icon>
      </v-flex>
      <v-flex>{{ data.item[itemtext] }}</v-flex>
    </template>
  </v-select>
</template>

<script lang="ts">
import Vue from "vue";
import { logger } from "@/app/helpers/Logger";

export default Vue.extend({
  name: "x-drop-down-list",
  data: function() {
    return {
      rules: {
        required: (value: any) => {
          let label = (this as any).getRulesLabel;
          return !!value || label + " is required";
        }
      }
    };
  },
  props: {
    id: String,
    value: [String, Number, Boolean, Object, Array],
    size: String,
    label: String,
    placeholder: String,
    required: Boolean,
    itemtext: String,
    itemvalue: String,
    itemicon: String,
    items: Array,
    returnobject: String,
    ruleslabel: String || null,
    disabled: Boolean,
    additionalRules: [String, Boolean] || null,
    clearable: Boolean
  },
  computed: {
    dropDownListID: function() {
      let lbl = String(this.label === undefined || this.label === null ? null : String(this.label).replace(/ /g, ""));
      let id = String(this.id === undefined || this.id === null ? lbl : this.id);
      return id;
    },
    returnobjectvalue: function() {
      let ret = this.returnobject === undefined || this.returnobject === null || this.returnobject === "" ? false : String(this.returnobject).toLowerCase() == "true" ? true : false;
      return ret;
    },
    getRulesLabel: function() {
      let id = this.label || this.id;
      let c = this.ruleslabel == undefined || this.ruleslabel == null ? id : this.ruleslabel;
      return c;
    },
    getPlaceHolder: function() {
      if (this.placeholder === undefined || this.placeholder === null || this.placeholder === this.label) return " ";

       let lbl = this.label == undefined || this.label == null ? "" : String(this.label).replace(" *", "");
      let c = this.placeholder == undefined || this.placeholder == null ? lbl : this.placeholder;
      return c;
    },
    labelDisplay: function() {
      return (this.label || "") != "" ? this.label + (this.required ? " *" : "") : "";
    },
    additionalRulesFormated: function() {
      return String(this.additionalRules || "") != "" ? this.additionalRules : false;
    },
    clearableValue: function() {
      return this.clearable === undefined || this.clearable === null ? true : this.clearable;
    }
  },
  methods: {
    handleInput: function(val: string) {
      this.$emit("input", val);
    }
  }
});
</script>

<style lang="scss">
</style>

dropdownlist.spec.ts

/**
 * Unit test for component DropdownList.vue.
 */
import Vue from "vue";
import Vuetify from "vuetify";
import DropdownList from "@/components/dropdown/DropdownList.vue";
import { createLocalVue, shallowMount, Wrapper } from "@vue/test-utils";

describe("DropdownList.vue", () => {
  let vuetify: any;
  let mountFunction: (options?: object) => Wrapper<Vue>;
  const localVue = createLocalVue();
  Vue.use(Vuetify);

  const id = "dropdownlistcomponentid";
 
  beforeEach(() => {
    vuetify = new Vuetify();
    mountFunction = (options = {}) => {
      return shallowMount(DropdownList, { localVue, vuetify, ...options });
    };
  });

  it("props.ruleslabel as string || null", () => {
    var mywrapper = mountFunction({ 
      propsData: { 
        id: id, 
        ruleslabel: "ruleslabel" || null 
      } });
    
    expect(mywrapper.vm.$props.ruleslabel).toBe("ruleslabel" || null);
  });
});

To test the props: ruleslabel and additionalRules, I ran npm run test:unit. The test passed!, but line 59 is still an uncovered line:

enter image description here

Upvotes: 0

Views: 1141

Answers (2)

tony19
tony19

Reputation: 138656

String || null is actually a conditional expression that short-circuits to String because the String constructor is truthy, so || null has no meaningful value here and should be removed to avoid being counted as an expression that would need code coverage. Similarly, || null should be removed from additionalRules and in your test for "ruleslabel".

I think you might've been trying to specify a nullable string, as in something like this:

let ruleslabel: string | null

...but the props declaration in this context is a dictionary of strings to constructors (not types). String is a constructor, but string | null is a type.

The proper way to specify this prop type is with PropType:

import Vue, { PropType } from 'vue'

export default Vue.extend({
  props: {
    ruleslabel: String as PropType<string | null>,
  }
})

However, props are already optional, so there's no need to specify that it could be null. Further, considering that x === undefined || x === null is equivalent to !x, getRulesLabel() could be simplified to eliminate the explicit check for null (or undefined) in ruleslabel:

export default {
  computed: {
    getRulesLabel() {
      // BEFORE:
      //let id = this.label || this.id;
      //let c = this.ruleslabel == undefined || this.ruleslabel == null ? id : this.ruleslabel;
      //return c;

      return this.ruleslabel || this.label || this.id;
    }
  }
}

Much of the other conditions in your code could be simplified following this example.

Upvotes: 1

ippi
ippi

Reputation: 10177

To test the "falsy" fork, you need to test at least once when ruleslabel is falsy. In your test you use ruleslabel: "ruleslabel" || null, here the expression "ruleslabel" || null will always be truthy.

You could test it like this:

it("works when props.ruleslabel is a string", () => {
    ...
    ruleslabel: "ruleslabel"
    ...

it("also works when props.ruleslabel is falsy", () => {
    ...
    ruleslabel: false
    ...

When using the || operator, if the first value is truthy, the second value will be ignored, so for all intents and purposes, those two expressions are equal:

ruleslabel: "ruleslabel" || null
ruleslabel: "ruleslabel"

Any time you see a guaranteedTruthy || anythingElse you know anythingElse will never be used. In javascript all strings are truthy, except the empty one "".

Upvotes: 0

Related Questions