english, Software Development, web engineering

Harry Potter and the JavaScript Fatigue  – Part 1

At the time of writing, it is about two years after everyone was talking about JavaScript fatigue. And yes, back then my team was no exception. We fatigued by the ever growing complexity in the React.JS ecosystem and had to learn our lessons the hard way. But today things look much brighter. 

Best practices spread fast and everyone seems to test their code now. But if you do not live in a JavaScript bubble, it is still very hard to get things right. Especially, if your team needs a frontend that is well tested, scalable and maintainable even if design decisions and opinions change.

This text cannot achieve all that. But it can present recipes for building and testing a Vue.JS frontend. With a focus on modern state handling, side-effecting and testing. And at the very least, it can be Harry Potter fan-fiction!

hogwarts-gruenblau


Part 1: The Philosophers UI

You are in the first year of Hogwarts. Being raised by geeky muggles, you have the great idea to write an app to track all your magical homework. You call it TodoMVC (the M stands for magical)! Over the course of the next few days you try out a lot of UI frontends. Finally you find that Vue.JS is the winner being the only UI frontend magical enough to run on campus.


Setup

So you install the Vue.JS cli and use it to create a webpack + vue.js project

mkdir todomvc
cd todomvc
vue init webpack frontend
? Project name frontend
? Project description Todo MVC magical edition
? Author <Name> <Mail>
? Vue build runtime
? Install vue-router? No
? Use ESLint to lint your code? No
? Setup unit tests Yes
? Pick a test runner jest
? Setup e2e tests with Nightwatch? No

You can pull the App from here! If you do so, run git checkout part1 for this part.

In your project folder you run yarn install to pull some dependencies. And yarn dev to start a hot-loading web server for development. In another console you start jest --watch for your interactive testing console.

The first component

At Hogwarts you learned that you can start with a definition and things pop into existence. All by themselves and the means of magic! So you start with your app by writing a single unit test that defines what you want.

import TodoListItem from '@/components/TodoListItem'
import {Task} from "@/domain/Task";
import {mount} from 'vue-test-utils'

describe('TodoListItem.vue', () => {
  it('renders snapshots', () => {
    const cmp = mount(TodoListItem, {
      propsData: {
        task: new Task("Some Title")
      }
    });
    expect(cmp.element).toMatchSnapshot();
    expect(cmp.find('label').text()).toBe("Some Title");
  })
});


This test first imports the component under test and a model of a task. Both do not exist yet and to your surprise the test did not bring them into existence out of thin air. But you have a pretty good idea now on how they should look like. The item should take as props a task object that contains a title property and it should render it.

Now that you specified everything you need to write the missing parts. And with everything you already know you can create the model

export class Task {
  title: string;

  constructor(title: string) {
    this.title = title;
  }
}


and the component

<template>
  <div class="todo-list-item">
    <label></label>
  </div>
</template>

<script lang="ts">
  export default {
    name: 'TodoListItem',
    props: ['task']
  }
</script>


The test worked like a charm.

From one to many

For a list of tasks you end up with the following test

import TodoList from '@/components/TodoList'
import TodoListItem from '@/components/TodoListItem'
import {Task} from "@/domain/Task";
import {shallow} from 'vue-test-utils'

const task = new Task("Some Title");

describe('TodoList.vue', () => {
  it('renders empty lists', () => {
    const cmp = shallow(TodoList,  {
      propsData: {
        tasks: []
      }
    });
    expect(cmp.element).toMatchSnapshot();
    expect(cmp.vm.$el.childNodes.length).toBe(0);
  });

  it('renders three element lists', () => {
    const cmp = shallow(TodoList,  {
      propsData: {
        tasks: [task,task,task]
      }
    });
    expect(cmp.element).toMatchSnapshot();
    expect(cmp.find(TodoListItem).vm.task.title).toEqual('Some Title');
    expect(cmp.findAll(TodoListItem).length).toBe(3);
  });
});



This time you could even verify that the list correctly fills its sub-components. Shallow rendering from the vue-test-utils is very helpful for exactly this purpose. The component gets rendered to html but not its sub-components. This keeps the snapshot tests more succinct, while you can still find the components and check their props.

This is a Todo List you could end up with.

<template>
  <ul class="todo-list">
    <li class="task" v-for="task in tasks">
      todo-list-item :task="task"></todo-list-item>
    </li>
  </ul>
</template>

<script lang="ts">
  import TodoListItem from './TodoListItem'
  export default {
    name: 'TodoList',
    props: ['tasks'],
    components: {
      TodoListItem
    }
  }
</script>


The v-for directive iterates over a collection of things. Here you iterate overtasks and use the :task="task" expression to bind the current task to a prop named task.

Being in style

The todo list still looks somewhat ugly. So you install bootstrap yarn add bootstrap-vue bootstrap@4.0.0-beta.2 and include it in the main.ts

import Vue from 'vue'
import App from './App'
import BootstrapVue from 'bootstrap-vue'

import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Vue.config.productionTip = false;
Vue.use(BootstrapVue);

new Vue({
  el: '#app',
  render: h => h(App)
});



Now the bootstrap markup is available everywhere.


<b-list-group>
  <b-list-group-item class="task" v-for="task in tasks" :key="task.name">
    <todo-list-item :task="task">
</b-list-group-item>
</b-list-group>



You are happy with your app now. It renders your tasks and does not give instant eye cancer to every passerby!

The next thing is to build mechanisms to also change the Todolist. Because applying the proper spells every time you have a new task is tedious. And some of the spells in data manipulation magic are still way too hard for a freshman.

Part 2, "The Chamber of Storage", is already online – read on!

    
About Maximilian Schuler

I am working at itemis as a software engineer at the web engineering team in Hamburg. As a full-stack developer I am at home in all domains of web development: from client-side development over data-intensive backend applications to platform engineering.