Vue.js Testing Resources

Found a few helpful resources on testing applications running the Vue framework. Logging here for reference, because I have too many tabs open.

Vue Testing Handbook
https://lmiller1990.github.io/vue-testing-handbook/setting-up-for-tdd.html#making-assertions

Vue Test Utils
https://vue-test-utils.vuejs.org/installation/#using-vue-test-utils-with-jest-recommended

North 47 KB Article -- Vue 2 and Jest Testing
https://www.north-47.com/knowledge-base/starting-with-unit-testing-using-vue-js-2-and-jest/

TestDriven.io -- Guide to Unit Testing Vue Components
https://testdriven.io/blog/vue-unit-testing/

Digital Ocean Tutorial -- How To Test Vue Components with Jest
https://www.digitalocean.com/community/tutorials/vuejs-vue-testing

Doximity Article -- Five Traps to Avoid While Unit Testing Vue.js (2017)
https://technology.doximity.com/articles/five-traps-to-avoid-while-unit-testing-vue-js

Testing Library -- jest-dom
https://github.com/testing-library/jest-dom

Basic Vue Component Recipe and Workflow

A simple recipe to add a component to a Vue project or application.

Basic File Structure

Add component file in the components/ diectory on the same project level as the main App.vue and main.js, note the Vue.js Style Guide essential rule on using multi-word component names. This rule prevents potential clashes with HTML elements, which are specfied as single words

src
├── App.vue
├── components
│   └── MyComponent.vue
└── main.js

Refer to these sources to get more background on Vue project structure:

Article on itnext.io by Sandoche ADITTANE
Application structure principles for Vuex
Vue.js Style Guide

Basic Development Workflow

A good basic Vue development workflow to follow takes 3 steps:

  1. Add structure
  2. Add logic
  3. Add style

1. Add Structure

The structure of a Vue component is defined in its <template> tag. Note that the <template> tag can contain only one root level element. In the following example, it is the first (and only) <div> element:

<template>
  <div>
    <h1>My Component</h1>
    <p>My component paragraph text</p>
    <button>Button</button>
  </div>
</template>
//...

Besides the HTML structure, Vue will also need some application configuration to use the new component.

Import and register the component in the main.js file:

//...
import MyComponent from './components/MyComponent';
//...
app.component('my-component', MyComponent);
//...

And finally, add the component tag to the App.vue file:

<template>
    <div id="app">
        <h1>My App</h1>
        <my-component />
    </div>
</template>

As a side note, pay attention to the deliberate use of kebab-case and camelCase or PascalCase. The case used is important to allow Vue to run code as HTML in some instances, and Javascript in others.

2. Add Logic

Before writing any code for the logic, consider what functionality the new component will need.

  • What data will be sent from the parent App.vue to the component?
  • What data will the component need to send to the rest of the app?
  • Which parts of the component will be dynamic or interactive?
  • Which methods or functions will the main app and the component need to communicate with each other?

Props

Props allow the parent app to pass data to its children components. In its simplest form, props are added to a component in three parts:

Include the props option and the prop attribute in the component script:

<script>
export default {
  props: ["textContent"],
//...

Add the data to pass to the app, as a custom attribute of the component:

<template>
//...
  <my-component text-content="Lorem ipsum dolor sit amet." />  

Render the data dyamically in the component, with a template tag:

<template>
//...
    <p>{{ textContent }}</p>

Now the parent app sends the text-content data to the child component, and Vue renders the result to the page.

Click Event

Adding a click event to the component markup is relatively straightforward:

<template>
  <div>
    <h1>My Component</h1>
    <p>{{ textContent }}</p>
    <button @click="replaceText">Replace Text</button>
  </div>
</template>

It is also a good opportunity to change the button label, as the nature of the button event is clarified.

The tricky part comes next, to properly handle passing data changes back to the parent app.

Emits

Emits send data from the child component back to the parent.

Add the click handler to the component element

Add the this.$emit() to the component methods, with reference arguments for the component click event, and any data to pass to the parent app:

//...
  methods: {
    replaceText() {
      // emit custom event
      this.$emit("replace-text", this.textContent);
    },
  },
//...

Add the event listener attribute to the instance of the component in the parent app:

<template>
    <div id="app">
        <my-component
        :text-content="textString"
        @replaceText="replaceComponentText"
        />
//...

Add a method to the parent app, so the parent can run the method from the child component:

//...
methods: {
    replaceComponentText(currentTextContent) {
      // always replace the text content, i.e. avoid duplicates
      while (currentTextContent === this.textString) {
        const rand = getRandomInt(3);
        if (rand === 0) {
          this.textString = this.textStringA;
        } else if (rand === 1) {
          this.textString = this.textStringB;
        } else if (rand === 2) {
          this.textString = this.textStringC;
        }
      }
    },
  },
//...

Optional, but recommended: define emits with the emits property in the component. This helps clarify and make obvious the purpose and function of the emits in the component:

//...
  emits: ["replace-text"],
//...

3. Add Style

Not just style, but dynamic style...

Bind a class to the element and add style properties that depend on dynamic values

<template>
//...
    <p class="dynamic" :class="textContentClass">{{ textContent }}</p>
//...

Set computed properties to influence the dynamic style values:

<script>
//...
  computed: {
    textContentClass() {
      if (this.textContent[0] === "J") {
        return "green-machine";
      } else if (this.textContent[0] === "W") {
        return "bold-and-spicy";
      } else if (this.textContent[0] === "R") {
        return "cool-as-ice";
      } else {
        return "no-style";
      }
    },
  },
//...

And of course add the styles to the component file:

<style>
.no-style {
  font-weight: normal;
  color: inherit;
}
.bold-and-spicy {
  font-weight: bold;
  color: crimson;
}
.cool-as-ice {
  font-style: italic;
  color: rgb(20, 163, 220);
}
.green-machine {
  font-style: normal;
  color: rgb(13, 114, 9);
}
</style>

Rewriting Vue/Jest Tutorial Code

While learning how to set up Jest testing for Vue, I found some helpful , though a little outdated* tutorial code at Digital Ocean

describe('Mounted App', () => {
  const wrapper = mount(App);

  test('is a Vue instance', () => {
    expect(wrapper.isVueInstance()).toBeTruthy()
  })

  it('renders the correct markup', () => {
    expect(wrapper.html()).toContain('What is the sum of the two numbers?')
  })

  // it's also easy to check for the existence of elements
  it('has a button', () => {
    expect(wrapper.contains('button')).toBe(true)
  })
})

What with the blazing fast evolution of, well, everything related to web development -- I found myself working with slightly newer versions of Vue and Jest, I think. The result were some confusing error messages until I tracked down the culprits: deprecated properties of the testing objects...

The isVueIntance() method was recently taken out of circulation, so it caused some errors when trying to call the method from the test wrapper. Primarily, this error:

TypeError: wrapper.isVueInstance is not a function

As a Jest and Vue n00b, this threw me off until I found the deprecation warning in the documentation for Vue Test Utils.

After running the wrapper constant through a few console.log outputs, I was able to determine why things weren't working.

From console.log(wrapper):

  console.log tests/unit/example.spec.js:38
    VueWrapper {
      isDisabled: [Function],
      wrapperElement: HTMLDivElement {},
      __app: {
        _uid: 0,
        _component: { name: 'VTU_ROOT', render: [Function: render] },
        _props: null,
        _container: HTMLDivElement { _vnode: [Object], __vue_app__: [Circular] },
        _context: {
          app: [Circular],
          config: [Object],
          mixins: [Array],
          components: [Object],
          directives: {},
          provides: [Object: null prototype] {},
          optionsCache: [WeakMap],
          propsCache: [WeakMap],
          emitsCache: [WeakMap],
          reload: [Function]
        },
        _instance: {
          uid: 0,
          vnode: [Object],
          type: [Object],
          parent: null,
          appContext: [Object],
          root: [Circular],
          next: null,
          subTree: [Object],
          update: [Function],
          scope: [EffectScope],
          render: [Function: render],
          proxy: {},
          exposed: null,
          exposeProxy: null,
          withProxy: null,
          provides: Object <Complex prototype> {},
          accessCache: [Object: null prototype] {},
          renderCache: [],
          components: null,
          directives: null,
          propsOptions: [Array],
          emitsOptions: null,
          emit: [Function: bound emit$1],
          emitted: null,
          propsDefaults: [Object: null prototype] {},
          inheritAttrs: undefined,
          ctx: {},
          data: {},
          props: {},
          attrs: {},
          slots: {},
          refs: [Object],
          setupState: {},
          setupContext: null,
          suspense: null,
          suspenseId: 0,
          asyncDep: null,
          asyncResolved: false,
          isMounted: true,
          isUnmounted: false,
          isDeactivated: false,
          bc: null,
          c: null,
          bm: null,
          m: null,
          bu: null,
          u: null,
          um: null,
          bum: null,
          da: null,
          a: null,
          rtg: null,
          rtc: null,
          ec: null,
          sp: null
        },
        version: '3.2.20',
        config: [Getter/Setter],
        use: [Function: use],
        mixin: [Function: mixin],
        component: [Function: component],
        directive: [Function: directive],
        mount: [Function],
        unmount: [Function: unmount],
        provide: [Function: provide]
      },
      rootVM: {},
      componentVM: {
        check: [Function: bound check],
        refresh: [Function: bound refresh],
        x1: [Getter/Setter],
        x2: [Getter/Setter],
        guess: [Getter/Setter],
        message: [Getter/Setter],
        hasOwnProperty: [Function]
      },
      __setProps: [Function: setProps]
    }

And console.log(typeof (wrapper)):

  console.log tests/unit/example.spec.js:39
    object

Finally console.log(wrapper.html())

console.log tests/unit/example.spec.js:40
    <div id="app">
      <div>
        <h3>Let us test your arithmetic.</h3>
        <p>What is the sum of the two numbers?</p>
        <div class="inline">
          <p>26 + 85 =</p><input><button>Check Answer</button>
        </div><button>Refresh</button>
        <p></p>
      </div>
    </div>

With the console log output, I was able to kinda sorta infer what the issue was, and by looking up different, non-deprecated methods and matchers re-wrote the tests in a way that worked:

describe('Mounted App', () => {
  const wrapper = mount(App);
  test('The wrapper exists', () => {
    expect(wrapper.find('does-not-exist').exists()).toBe(false)
  })

  it('renders the correct markup', () => {
    const element = wrapper.find('#app p:first-of-type')
    expect(element.text()).toBe('What is the sum of the two numbers?')
  })

  it('has a button', () => {
    const button = wrapper.find('button')
    expect(button.exists()).toBe(true)
  })

})

Not quite as concise as the original author's examples, but it works (for now)!

*UPDATE--
The original tutorial at Digital Ocean was re-written less than 24 hours after I let them know about the deprecation issue. Check it out: https://www.digitalocean.com/community/tutorials/vuejs-vue-testing -- Thanks Parthiv at D.O.!

Vue.js The Most Fundamental Idea

Vue.js is Declaritive

Declaritive programming -- From Wikipedia:

... declaritive programming is a programming paradigm that expresses the logic of a computation without describing its control flow.

In more straightforward terms, declaritive programs say what should happen, rather than how it should happen.

In contrast, imperitive programming specifically lists the steps to perform a function.

Vue.js Declaritive Manifestion

Rather than, for example, using vanilla Javascript to step-by-step: find objects in the DOM, update those objects, etc. -- Vue.js takes a simplified, abstracted approach.

  1. Modify HTML tags and structure with special attributes.
  2. Use Vue.js to apply functionality to the HTML with abstracted functions.

An Example of Vue.js Declaritive Rendering

Extracted directly from the guide at vuejs.org -- this example provides a most basic demonstration of declaritive rendering.

HTML

<div id="app">
  {{ message }}
</div>

JS

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

The "declartive'" part of declaritive rendering is written into the HTML of the example.

In English, it says:

"Put the message into the app"

The accompanying Javascript carries out the duties of making it happen.

VS Code Keyboard Moves to Navigate Terminal Windows

These days I'm trying to keep my hands on the keyboard as much as possible. Here are some notes to remind myself how to get around the VS Code interface between code editing panes and terminal windows.

Custom Keybinding to Toggle from Code Pane to Terminal Window

I added this JSON snippet (found somewhere on the Internet, thank you kind stranger) to the VS Code keybindings file. Here is how I did it:

Open the VS Code keybindings JSON file for editing with ctrl+shift+p > Preferences: Open Keyboard Shortcuts (JSON)

Add the following to the file:

[
    {
	"key": "ctrl+;",
	"command": "workbench.action.focusActiveEditorGroup",
	"when": "terminalFocus"
    },
]

Now the ctrl+; key combination smoothly toggles from the code pane to the terminal window/s.

Using Built-In VS Code Keyboard Shortcuts for Terminal Window Navigation

VS Code provides excellent documentation for using the intergated terminal and clear instructions for the defaul keyboard shortcuts to navigate multiple terminals.

From One Group to Another

Navigate between terminal groups using focus next Ctrl+PageDown and focus previous Ctrl+PageUp.

From One Terminal to Another in a Group

Navigate between terminals in a group by focusing the previous pane, Alt+Left, and focusing the next pane, Alt+Right.

Customizing Keybindings for VIM-like Motion across Terminal Groups

Adding this code snippet to VS Code keybindings allows for VIM-like motion keyboard shortcuts to move from one split terminal to the next, and back.

Open the VS Code keybindings JSON file for editing with ctrl+shift+p > Preferences: Open Keyboard Shortcuts (JSON)

[
    {
        "key": "ctrl+shift+j",
	"command": "workbench.action.terminal.focusNext"
    },
    {
	"key": "ctrl+shift+k",
	"command": "workbench.action.terminal.focusPrevious"
    },
]

The VIM paragdigm breaks down at the bottom or top of the terminal group, because VS Code will just loop back to the beginning. Oh well.

Handy Django Test Command Examples

Quick reference of the syntax for running different Django test commands. These were scooped from the excellent book Practical Django 2 and Channels 2 by Federico Marani

$ # Run all the views tests
$ ./manage.py test main.tests.test_views.TestPage

$ # Run the homepage test (specific test run)
$ ./manage.py test main.tests.test_views.TestPage.test_home_page_works

$ # Run all the tests
$ ./mange.py test

PRO TIPS:

--keepdb to keep test db between runs
--failfast to stop tests at first failure