0

I'm trying to render a component in my Vue unit test with Jest, but I can't seem to get it to work.

The logs say: TypeError: Cannot read property 'length' of null

I've tried other components, and it works fine.

ErrandsListTable.vue

<template>
  <div
    v-if="!error"
    class="flex-table flex-table--white-background flex-table--green-header flex-table--with-dropdown"
    :class="{ loading: loading }"
  >
    <div class="flex-table__row flex-table--header text--level-6">
      <div class="flex-table__cell flex-table__cell--width-3">
        {{ statusHeader }}
      </div>
      <div class="flex-table__cell flex-table__cell--width-3">
        {{ idHeader }}
      </div>
      <div class="flex-table__cell flex-table__cell--width-7">
        {{ descriptionHeader }}
      </div>
      <div class="flex-table__cell flex-table__cell--width-5">
        {{ typeHeader }}
      </div>
      <div class="flex-table__cell flex-table__cell--width-3">
        {{ dateOpenedHeader }}
      </div>
      <div class="flex-table__cell flex-table__cell--width-3">
        {{ dateClosedHeader }}
      </div>
      <div class="flex-table__cell flex-table__cell--width-1" />
    </div>

    <div
      v-for="(errand, index) in sliceArray(errands)"
      :key="!errand.id ? index : errand.id"
    >
      <div
        class="flex-table__row"
        :class="{ 'flex-table__row--closed': index !== selectedErrand }"
        @click="toggleExpanded(index)"
      >
        <div class="flex-table__cell flex-table__cell--width-3">
          {{ errand.status }}
        </div>
        <div class="flex-table__cell flex-table__cell--width-3">
          {{ errand.id }}
        </div>
        <div
          class="flex-table__cell flex-table__cell--width-7 flex-table__cell--show-on-mobile"
          :data-header="descriptionHeader"
        >
          {{ errand.description }}
        </div>
        <div class="flex-table__cell flex-table__cell--width-5">
          {{ errand.type }}
        </div>
        <div
          class="flex-table__cell flex-table__cell--width-3 flex-table__cell--show-on-mobile"
          :data-header="dateOpenedHeader"
        >
          {{ errand.orderDate }}
        </div>
        <div class="flex-table__cell flex-table__cell--width-3">
          {{ errand.deliveryDate }}
        </div>
        <div
          class="flex-table__cell flex-table__cell--width-1 flex-table__cell--dropdown-show-on-mobile"
        >
          <i
            class="icon-angle__small"
            :class="{ 'icon-angle__small--down': selectedErrand === index }"
          />
        </div>
      </div>

      <ErrandsListDetails
        v-if="selectedErrand >= 0 && selectedErrand === index"
        :errand="errand"
        v-bind="$props"
      />
    </div>

    <div
      v-if="errands.length >= numberOfErrandsLoaded"
      class="flex-table--load-more text--level-5"
    >
      <a href="#" @click="loadMore">
        {{ showMoreText }}
        <i class="icon-down" />
      </a>
    </div>
    <div
      v-if="errands.length < 1 && !loading"
      class="flex-table--error text--level-5"
    >
      {{ noCasesText }}
    </div>
    <div v-if="error && !loading" class="flex-table--error text--level-5">
      {{ errorText }}
    </div>
  </div>
</template>

<script>
import _ from "lodash";
import ErrandsListDetails from "./ErrandsListDetails.vue";

export default {
  components: {
    ErrandsListDetails: ErrandsListDetails
  },
  props: {
    componentTitle: {
      type: String,
      default: () => {
        return "";
      }
    },
    showMoreText: {
      type: String,
      default: "Visa fler ärenden"
    },
    statusHeader: {
      type: String,
      default: () => {
        return "Status";
      }
    },
    dateOpenedHeader: {
      type: String,
      default: () => {
        return "Beställt/Anmält";
      }
    },
    dateClosedHeader: {
      type: String,
      default: () => {
        return "Beräknad klar";
      }
    },
    idHeader: {
      type: String,
      default: () => {
        return "Ärendenummer";
      }
    },
    descriptionHeader: {
      type: String,
      default: () => {
        return "Beskrivning";
      }
    },
    typeHeader: {
      type: String,
      default: () => {
        return "Typ";
      }
    },
    fileDetails: {
      type: String,
      default: () => {
        return "Ladda ned beställningen som PDF:";
      }
    },
    commentsDetails: {
      type: String,
      default: () => {
        return "Noteringar";
      }
    },
    wordList: {
      type: String,
      default: () => {
        return "";
      }
    },
    attachment: {
      type: String,
      default: () => {
        return "";
      }
    },
    noCasesText: {
      type: String,
      default: () => {
        return "Det finns inget att visa";
      }
    },
    errorText: {
      type: String,
      default: () => {
        return "Något gick fel";
      }
    },
    createdOnFallback: {
      type: String,
      default: () => {
        return "-";
      }
    },
    closedOnFallback: {
      type: String,
      default: () => {
        return "-";
      }
    },
    errandsCounter: {
      type: Object,
      default: () => {
        return null;
      }
    },
    errands: {
      type: Array,
      default: () => {
        return null;
      }
    },
    loading: {
      type: Boolean,
      default: () => {
        return false;
      }
    }
  },
  data() {
    return {
      selectedErrand: -1,
      error: undefined,
      numberOfErrandsLoaded: 10
    };
  },
  methods: {
    toggleExpanded: function(index) {
      this.selectedErrand = this.selectedErrand === index ? -1 : index;
    },

    loadMore: function(event) {
      event.preventDefault();
      this.numberOfErrandsLoaded += 10;
    },

    sliceArray: function(array) {
      let val = _.orderBy(array, "orderDate", "desc");
      return val.slice(0, this.numberOfErrandsLoaded);
    }
  }
};
</script>

Also ignore the fact that this component isn't done and might not necessarily involve the most relevant use cases for unit tests. I'm just curious to know why this certain component can't be loaded as a Vue instance in my test?

import { shallowMount } from "@vue/test-utils";
import ErrandsListTable from "../ErrandsListTable.vue";

let wrapper;

describe("Errandslist table", () => {
  beforeEach(() => {
    wrapper = shallowMount(ErrandsListTable);
  });

  afterEach(() => {
    wrapper.destroy();
  });
  it("is a Vue instance", () => {
    expect(wrapper.isVueInstance).toBeTruthy();
  });
});

Has anyone had the same issue? If so, can someone explain to me how you solved it?

2 Answers 2

1

In your template yourerrands array is null, so you cannot access length property. You either have to handle this case in your component or you have to pass this property to the component.

set default value

errands: {
  type: Array,
  default: () => {
    return [];
  }
}

pass default value during test

const wrapper = shallowMount(ErrandsListTable, {
  propsData: {
    errands: []
  }
})
expect(wrapper.props().errands.length).toBe(0)
Sign up to request clarification or add additional context in comments.

1 Comment

Ah I can see it now! Thanks you for the help! :)
1

The default value of errands prop is null. Since you're mounting your component without any props, the default value of null takes effect. Your template has errands.length, which results in the error you've observed.

To resolve the error, you could either mount the component with an array for the errands prop:

wrapper = shallowMount(ErrandsListTable, {
  propsData: {
    errands: []
  }
});

...or change the default value from null to an empty array:

export default {
  props: {
    errands: {
      type: Array,
      default: () => []
    }
  }
}

...or add a conditional (i.e., v-if="errands") to your template so that it is only dereferenced when truthy:

<div v-if="errands && errands.length >= numberOfErrandsLoaded">
           ^^^^^^^^^^

The last solution is defensive programming that would protect against users passing in a null/undefined prop, causing the same error you're encountering.

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.