В этой серии из четырех частей давайте создадим простой компонент для создания заметок Vue.js под названием Annotate, используя TDD с Unite.js. Здесь я сосредоточусь на функциональной части и не буду беспокоиться о стилях или внутренней реализации.

ТОС

  1. Часть 1. Базовый компонент
  2. Часть 2: Удаление, фильтрация и передача реквизита
  3. Часть 3: Рефакторинг нашего компонента
  4. Часть 4: Подключение к серверной части и тестирование запросов Ajax

Я попытался сделать эти примеры поэтапно, показывая ошибки и решения TDD. Это получилось немного длинным, но я надеюсь, вам понравится!

Запуск проекта

Для начала создадим папку проекта. Если вы используете терминал:

mkdir annotate && cd annotate

И чтобы запустить npm, запустите:

npm init -y

Если все прошло хорошо, должно получиться что-то вроде этого:

Открыв папку в редакторе кода, у вас должно получиться что-то вроде этого:

Установка зависимостей

В этом проекте мы будем использовать Vue.js и Unite.js. Итак, через терминал потянем Unite.js:

npm install --save-dev unite.js

Этот пакет содержит все необходимое для начала разработки.

Настройка тестовой среды

Теперь откройте ваш ./package.json файл и измените объект scripts на:

// ./package.json
"scripts": {
    "test": "unite"
},

Теперь нам нужно создать папку с нашими компонентами. Давайте создадим папку src, в которой будут располагаться наши файлы компонентов:

mkdir src

Наконец, нам нужно указать Unite.js использовать этот src каталог в качестве папки тестирования. Для этого создадим unite.config.js файл в корне проекта:

touch unite.config.js

Теперь откройте его в своем редакторе и добавьте:

// ./unite.config.js
module.exports = {
    path: "./src"
}

Теперь, если вы откроете свой терминал и запустите npm run test, вы должны получить:

Аннотировать тесты SFC

Теперь, когда у нас настроена среда тестирования, давайте создадим файл нашего компонента в нашей src папке:

touch src/annotate.tests.vue

Откройте наш annotate.tests.vue файл в своем редакторе и добавьте базовую структуру компонентов:

Чтобы убедиться, что все работает правильно, давайте создадим два теста в нашем теге tests: успешный и неудачный:

Давайте рассмотрим код:

test tag содержит:

  • let wrapper;
    Определяет переменную `wrapper`, которую мы будем использовать для монтирования нашего компонента.
  • Unite.beforeEachTest();
    Определяет функцию, которая будет запускаться перед каждым тестом. Здесь мы можем монтировать наш компонент перед каждым тестом.
  • Unite.$mount();
    Это функция vue-test-utils mount, которая монтирует компонент vue и возвращает `Wrapper`
  • $component
    Это наш компонент vue, который определяется как переменная unite.js внутри *.tests.vue files
  • Unite.test();
    Определяет новый тест. Он принимает два параметра: `name`: имя теста; callback: тестовая функция;

Давайте посмотрим на тест:

@test: это действительный тест:

  • expect(wrapper.text()).toEqual("Was mounted correctly");
    Ожидается, что текст wrapper будет равен «Был смонтирован правильно».

@test: это недопустимый тест

  • expect(wrapper.text()).toEqual("This will fail");
    Ожидается, что текст wrapper будет равен «Это не удастся».

Теперь, если мы запустим npm run test и вы должны получить что-то вроде этого:

Как и ожидалось, мы получили один провальный тест. Теперь, когда мы знаем, что все работает, давайте начнем самое интересное и создадим наш компонент !!

Выбор структуры компонентов

Первое, что нам нужно сделать, это решить, как мы хотим, чтобы наш компонент работал. Базовый функционал, который мы собираемся реализовать здесь:

  • Объект заметки должен содержать уникальный идентификатор, заголовок и тело.
  • Список заметок
  • Форма для создания / редактирования заметок
  • Когда создается новая заметка, ее нужно добавить в список
  • При нажатии на заметку мы должны увидеть ее в форме
  • Когда заметка редактируется и сохраняется, она должна обновлять соответствующую заметку в списке.
  • При нажатии новой кнопки мы должны увидеть пустую форму

I. Объект заметки должен содержать уникальный идентификатор, заголовок и тело.

Как мы описали, каждая нота должна содержать title и body. Для простоты объект note будет:

{
  id: {UNIQUE ID},
  title: "Note title",
  body: "Note body"
}

II. Список заметок

Давайте подумаем о списке заметок:

  • Это должен быть массив, содержащий note объектов.
  • Каждый note объект должен отображаться внутри .notes элемента в шаблоне.
  • Каждый .notes дочерний элемент должен отображать свой title

Имея это в виду, давайте создадим тест, утверждающий, что для каждого note объекта мы должны видеть дочерний элемент .notes, содержащий заголовок note:

Давайте рассмотрим код:

@test: отображает все примечания:

  • addNote("Note 1", "Note body");
    Добавляет новую заметку с помощью addNote helper (см. Ниже)
  • addNote("Note 2", "Note body");
    Добавляет новую заметку с помощью addNote helper (см. Ниже)
  • let notes = wrapper.find(".notes");
    Находит .notes элемент DOM.
  • expect(notes.vnode.children.length).toEqual(2);
    Ожидается, что элемент .notes DOM содержит двух дочерних

@helper: addNote (заголовок, текст):

  • let noteKey = wrapper.vm.notes.length;
    Получает текущую длину массива ntoes.
  • wrapper.vm.notes.push();
    P Отправляет новую заметку в массив notes:
    - id:(new Date).getTime(),
    Присваивает ей уникальный id (для использования в качестве key в цикле v-for)
    - title,
    Устанавливает объект note title на заданное title
    - body
    Устанавливает для объекта note body заданное значение body
  • wrapper.update();
    Triggers vue для обновления DOM
  • return wrapper.vm.notes[noteKey];
    Возвращает последний объект `note`

Время запачкать руки !!

  1. npm run test
// ERROR
1) annotate.tests.vue @ It displays all the notes
TypeError: Cannot read property ‘push’ of undefined

Это означает, что мы пытаемся использовать push для неопределенной переменной. В нашем коде push используется в помощнике addNote для передачи объекта note в свойство notes. Итак, давайте создадим свойство notes:

2. npm run test

// ERROR
1) annotate.tests.vue @ It displays all the notes
TypeError: Cannot read property ‘children’ of undefined

Теперь мы пытаемся прочитать свойство children неопределенной переменной. Это означает, что vue-test-utils не удалось найти элемент .notes.

Давайте создадим .notes:

3. npm run test

// ERROR
1) annotate.tests.vue @ It displays all the notes
TypeError: Cannot read property ‘length’ of undefined

Сейчас мы пытаемся получить длину children, но их нет. В этом случае vue-test-utils устанавливает children как неопределенное. Итак, давайте создадим цикл v-for для заметок:

4. npm run test

Получаем зелёный!

III. Форма для создания / редактирования заметок

Теперь нам нужна форма для создания и редактирования заметок. Итак, давайте создадим тест, подтверждающий, что он видит форму, содержащую входные данные для заголовка и тела:

Давайте рассмотрим код:

@test: видит форму для создания / редактирования заметок:

  • wrapper.find("notes");
    Получает оболочку `.form`
  • expect(form.find("input[name=id]").exists()).toBe(true);
    Мы ожидаем, что существует вход с именем id
  • expect(form.find("input[name=title]").exists()).toBe(true);
    Мы ожидаем, что существует вход с именем title
  • expect(form.find("textarea[name=body]").exists()).toBe(true);
    Мы ожидаем, что существует вход с именем body
  • expect(form.find("button").exists()).toBe(true);
    Мы ожидаем, что button будет существовать

Давайте код!

  1. npm run test
// ERROR
1) annotate.tests.vue @ It sees the form to create/edit notes
Error: [vue-test-utils]: find did not return .form, cannot call find() on empty Wrapper

vute-test-utils не удалось найти .form элемент. Добавим:

2. npm run test

// ERROR
1) annotate.tests.vue @ It sees the form to create/edit notes
expect(received).toBe(expected)
Expected value to be (using ===):
  true
Received:
  false

Здесь наши ожидания не оправдываются! Итак, давайте продолжим и создадим наши входные данные:

3. npm run test

И мы становимся зелеными!

IV. Когда создается новая заметка, ее нужно добавить в список

Теперь, когда у нас есть форма, если мы заполним ее и нажмем button, мы ожидаем, что наша заметка будет добавлена ​​в список notes. Напишем тест:

@test: при нажатии кнопки добавляется новая заметка:

  • type("input[name=title]", "New Note");
    В поле title введите «New Note»
  • type("textarea[name=body]", "New note body");
    В поле body введите "Основная часть заметки"
  • expect(wrapper.find(".notes").isEmpty()).toBe(true);
    Ожидайте, что элемент .notes DOM не содержит дочерних элементов
  • wrapper.find("button").trigger("click");
    Запускает click событие на кнопке
  • expect(wrapper.find(".notes").vnode.children.length).toEqual(1);
    Ожидайте, что .notes элемент DOM имеет одного дочернего элемента
  • expect(wrapper.find(".note:first-child").text()).toEqual("New Note");
    Ожидайте, что первый .note дочерний элемент DOM будет иметь текстовое содержание «Новое примечание».

@helper: тип (селектор, значение):

  • let node = wrapper.find(selector);
    Находит элемент с заданным селектором
  • node.element.value = value;
    Устанавливает значение элемента с заданным значением
  • node.trigger("input");
    Запускает событие vue `input`

Давайте рассмотрим ошибки, чтобы реализовать это!

1. npm run test

// ERROR
1) annotate.tests.vue @ It adds a new note when the button is clicked
expect(received).toEqual(expected)
Expected value to equal:
  1
Received:
  0

Мы ожидали, что при нажатии кнопки будет создана заметка и будет создан дочерний элемент .notes. Но прямо сейчас ничего не было спускового механизма.

Первое, что нам нужно сделать, это запустить функцию при нажатии button:

Давайте создадим метод save:

Чтобы сохранить заметку, сначала нам нужно знать входное содержимое. Давайте создадим note переменную в экземпляре vue и добавим v-модели во входные данные. Затем мы можем использовать метод save, чтобы поместить новый note в список заметок:

@methods: save ():

  • this.note.id = (new Date).getTime();
    Устанавливает уникальный идентификатор для нового note объекта.
  • this.notes.push(this.note);
    Он помещает новый объект note в список заметок

2. npm run test

Теперь мы должны стать зелеными!

V. При нажатии на заметку мы должны увидеть ее в форме

Итак, если у нас есть заметка, когда мы щелкаем по дочернему элементу .notes, мы должны видеть данные заметки на входах .form. Напишем тест:

@test: показывает нажатую заметку в форме:

  • let note = addNote("Note 1", "Note's body");
    Добавляет новую заметку с помощью помощника addNote
  • wrapper.find(".note:first-child").trigger("click");
    Обнаруживает первого дочернего элемента DOM `.notes` и запускает событие click
  • let form = wrapper.find(".form");
    Обнаруживает .form элемент DOM
  • expect(form.find("input[name=id]").element.value).toEqual(String(node.id));
    Ожидается, что значение ввода с именем id должно быть равно идентификатору добавленной заметки (id будет преобразован в строку)
  • expect(form.find("input[name=title]").element.value).toEqual(node.title);
    Ожидается, что значение ввода с именем title должно быть равно заголовку добавленной заметки.
  • expect(form.find("textarea[name=body]").element.value).toEqual(node.body);
    Ожидается, что значение ввода с именем body должно быть равно тексту добавленной заметки.

Приступим к тестам:

1. npm run test

// ERROR
1) annotate.tests.vue @ It shows the clicked note on the form
expect(received).toEqual(expected)
Expected value to equal:
  1508775148045
Received:
  ""

Эта ошибка показывает нам, что после того, как мы щелкнули элемент .note, вход с именем id не имеет ожидаемого значения. Прямо сейчас, когда мы нажимаем на элемент .note, ничего не происходит. Итак, давайте вызовем edit метод при щелчке по элементу .note:

А теперь создадим метод edit:

@methods: edit (примечание)

  • this.note = note;
    Устанавливает объект `note` для данной заметки

2. npm run test

Теперь мы стали зелеными!

VI. Когда заметка редактируется и сохраняется, она должна обновлять соответствующую заметку в списке.

Итак, учитывая, что у нас есть заметка и мы ее редактируем, когда мы изменяем входные данные и щелкаем button, мы ожидаем, что объект note будет обновлен с новым входным значением.

Напишем тест:

@test: обновляет объект заметки входными данными:

  • addNote("Note 1", "Note's body");
    Добавляет новый объект note в список notes.
  • addNote("Note 2", "Note's body");
    Добавляет новый объект note в список notes.
  • addNote("Note 3", "Note's body");
    Добавляет новый объект note в список notes.
  • wrapper.find(".note:nth-child(2)").trigger("click");
    Обнаруживает второй .note элемент DOM и запускает click событие
  • type("input[name=title]", "New Title");
    Устанавливает для ввода формы заголовка значение «Новый заголовок»
  • wrapper.find("button").trigger("click");
    Запускает событие click в элементе button.
  • expect(wrapper.vm.notes.length).toEqual(3);
    Утверждает, что количество нот - 3
  • expect(wrapper.find(".note:nth-child(2)").text()).toEqual("New Title");
    Утверждается, что заголовок второй заметки - «Новый заголовок».

Запустим тесты:

  1. npm run test
// ERROR
1) annotate.tests.vue @ It updates a note object with the input data
expect(received).toEqual(expected)
Expected value to equal:
  3
Received:
  4

Это показывает, что вместо обновления второй заметки мы добавляем в список новый объект note! Чтобы исправить это, давайте обновим функцию save:

Давайте рассмотрим новый код:

@methods: save:

  • let note = this.note;
    Получает значения формы
  • if(note.id !== "") Если id примечания не пустое
    - this.notes = this.notes.map()
    Он сопоставляется с элементами списка `notes`
    - if(item.id === note.id)
    Если` id` элемента равен `id` заметки: return note;
    else: return item
  • else Если примечание id пусто
    - note.id = (new Date).getTime();
    Устанавливает уникальный id
    - this.notes.push(note);
    Он помещает новый объект note в список notes

2. npm run test:

Теперь мы стали зелеными!

VII. Когда нажимается кнопка `new`, мы должны увидеть пустую форму

Наконец, нам нужна кнопка для создания новой заметки. Когда мы щелкаем по ней, мы очищаем форму и можем начать новую заметку.

Приступим к тесту:

@test: при нажатии новой кнопки отображается пустая форма:

  • addNote("Note Title", "Note's body");
    Добавляет новую заметку со вспомогательной функцией addNote.
  • wrapper.find(".note:first-child").trigger("click");
    Обнаруживает первый элемент DOM `.note` и запускает событие щелчка
  • expect(wrapper.find("input[name=title]").element.value).toEqual("Note Title");
    Ожидается, что значение ввода `title` является значением заметки
  • wrapper.find(".new-note").trigger("click");
    Запускает событие щелчка на элементе `.new-note`
  • expect(wrapper.find("input[name=id]").element.value).toEqual("");
    Ожидается, что входное значение id будет пустым
  • expect(wrapper.find("input[name=title]").element.value).toEqual("");
    Ожидается, что входное значение title будет пустым
  • expect(wrapper.find("textarea[name=body]").element.value).toEqual("");
    Ожидается, что входное значение body будет пустым

Приступим к тестированию:

1. npm run test

// ERROR
1) annotate.tests.vue @ It shows the blank form when the new button is clicked
Error: [vue-test-utils]: find did not return .new-note, cannot call trigger() on empty Wrapper

Он пытается найти элемент .new-note и вызвать событие click, но его не существует! Создадим его:

2. npm run test

// ERROR
1) annotate.tests.vue @ It shows the blank form when the new button is clicked
expect(received).toEqual(expected)
Expected value to equal:
  ""
Received:
  "1508781636415"

После того, как он щелкнул .new-button, он ожидал, что ввод формы будет очищен. Но в настоящее время он ничего не делает. Давайте добавим триггер в newNote и создадим метод:

@methods: newПримечание:

  • this.note = {id: "", title: "", body: ""}
    Устанавливает для переменной note пустой note объект.

3. npm run test

И мы на зеленом!

Заключительный компонент

Выводы

В этом примере мы создали очень простой компонент Vue.js для заметок, используя TDD с Unite.js. Исходный код можно найти здесь!

Отсюда необходимо сделать две вещи:

  • Мы не беспокоились о стилизации нашего компонента. Так что нам нужно сделать его красивым!
  • Мы сосредоточились на интерфейсе, и заметки нигде не хранятся. Для этого вы должны изменить save method для отправки запроса ajax в серверную часть, где вы можете сохранить данные.

Так что покажите свой собственный компонент Annotate ниже! Кроме того, если у вас есть какие-либо вопросы или идеи по улучшению нашего компонента «Аннотации», оставьте свои мысли ниже!

Если вам понравилось, покажите свою поддержку и расскажите своим друзьям!

Спасибо!