В тази поредица от четири части нека създадем прост компонент Vue.js за водене на бележки, наречен Annotate, използвайки TDD с Unite.js. Тук ще се съсредоточа върху функционалната част и няма да се тревожа за стила или бек-енд изпълнението.

TOC

  1. Част 1: Основен компонент
  2. Част 2: Изтриване, филтриране и предаване на реквизити
  3. Част 3: Рефакторинг на нашия компонент
  4. Част 4: Свързване към Back-end и тестване на 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 съдържа:

  • let wrapper;
    Дефинира променлива `wrapper`, която ще използваме за монтиране на нашия компонент
  • Unite.beforeEachTest();
    Дефинира функция, която ще се изпълнява преди всеки тест. Тук можем да монтираме нашия компонент преди всеки тест.
  • Unite.$mount();
    Тази функция е функцията за монтиране vue-test-utils, която монтира компонент vue и връща `Wrapper`
  • $component
    Това е нашият vue компонент, дефиниран като променлива от unite.js в *.tests.vue files
  • Unite.test();
    Определя нов тест. Приема два параметъра: `name`: Името на теста; `обратно повикване`: тестовата функция;

Нека да разгледаме теста:

@test: Това е валиден тест:

  • expect(wrapper.text()).toEqual("Was mounted correctly");
    Очаква се текстът wrapper да е равен на „Беше монтирано правилно“.

@test: Това е невалиден тест

  • expect(wrapper.text()).toEqual("This will fail");
    Той очаква, че текстът wrapper е равен на „Това ще се провали“.

Сега, ако стартираме npm run test и трябва да получите нещо подобно:

Както се очакваше, получаваме един неуспешен тест. Сега, след като знаем, че всичко работи, нека започнем забавната част и да създадем нашия компонент!!

Определяне на структурата на компонентите

Първото нещо, което трябва да направим, е да решим как искаме да работи нашият компонент. Основната функционалност, която ще приложим тук:

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

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

Както описахме, всяка бележка трябва да съдържа title и body. За простота, noteobject ще бъде:

{
  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(title, body):

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

Време е да си изцапаме ръцете!!

  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 „Нова бележка“
  • 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");
    Очаквайте, че първият дъщерен DOM елемент .note има текстово съдържание на „Нова бележка“

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

  • let node = wrapper.find(selector);
    Намира елемента с дадения селектор
  • node.element.value = value;
    Задава стойността на елемента с дадената стойност
  • node.trigger("input");
    Задейства събитието `input` на vue

Нека да прегледаме грешките, за да приложим това!

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` и задейства събитието кликване
  • 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: редактиране(бележка)

  • 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()
    Той преобразува елементите от списъка на `бележките`
    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. Когато се щракне върху бутон „нов“, трябва да видим празен формуляр

И накрая, имаме нужда от бутон за създаване на нова бележка. Когато щракнете върху него, изчистваме формуляра, за да можем да започнем новата бележка.

Да започнем с теста:

@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: нова бележка:

  • this.note = {id: "", title: "", body: ""}
    Задава променливата note на празен обект note

3. npm run test

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

Краен компонент

Изводи

В този пример създадохме много прост Vue.js компонент за водене на бележки, използвайки TDD с Unite.js. Изходният код може да бъде намерен тук!

Оттук нататък има две неща, които трябва да се направят:

  • Не се тревожехме за стилизирането на нашия компонент. Така че трябва да го направим красив!
  • Фокусирахме се върху предния край и бележките не се съхраняват никъде. За да направите това, трябва да промените save method, за да изпратите ajax заявка до задния край, където можете да запазите данните.

Така че покажете своя собствен компонент за анотиране по-долу! Също така, ако имате някакви въпроси или идеи за подобряване на нашия компонент Annotate, оставете вашите мисли по-долу!

Ако ви е харесало това, покажете своята подкрепа и кажете на приятелите си!

Благодаря ти!