0

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

0

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

Comments

0

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 "".

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.