Vue 3 Bileşenlerini(Components) Storybook ile Dokümantasyon ve Test İşlemleri

Mert DEMİR
Berkut Teknoloji
Published in
6 min readNov 16, 2021

Storybook, UI components dokümantasyonu için tercih ettiğim araçtır. Vue.js, Storybook ekosisteminde çok iyi desteklenir ve Vuetify ve NuxtJS ile birinci sınıf entegrasyonlara sahiptir. Ayrıca, Vue.js’nin en son büyük bölümü olan Vue 3 için resmi desteğe sahiptir.

Storybook Kurulumu

Öncelikle bir Vue 3 uygulaması oluşturmamız gerekiyor. Vue.js’nin yaratıcısı Evan You’dan yeni bir derleme aracı olan Vite’ı kullanacağız:

npm init vite@latest

Mevcut bir Vue 3 projesinde Storybook kurulumu sıfır konfigürasyonla yapılabilir:

npx sb init

Bu komut, Storybook’u bağımlılıklarıyla birlikte kurar, Storybook örneğini yapılandırır ve src/stories konumunda bulunan bazı demo component ve stories oluşturur:

Artık Storybook için yerel bir geliştirme sunucusu başlatan ve onu otomatik olarak yeni bir tarayıcı sekmesinde açan aşağıdaki komutu çalıştırabiliriz:

npm run storybook

Bu oluşturulan Vue components ve stories, Vue 3 story’nin nasıl yazılacağına dair iyi örneklerdir. Size özel bir component kullanarak bazı gelişmiş doküman örnekleri göstermek istiyorum.

Custom Component Demosu

Bu makale de Storybook entegrasyonunu göstermek için bir Counter.vue demo component oluşturdum. Kaynak kodu GitHub’da mevcuttur.

Component, temel sayaç işlevselliği sağlar, iki farklı görsel varyanta ve özel içerik için iki slot’a sahiptir.

Component koduna bir göz atalım:

<template>
<p>{{ label }}</p>
<!-- @slot Slot to show content below label --> <slot name="sub-label" />
<div class="container" :class="variant">
<button @click="increment()">+</button>
<p class="value">{{ count }}</p>
<button @click="decrement()">-</button>
</div>
<!-- @slot Default slot to show any content below the counter --> <slot />
</template>
<script lang="ts">
import { ref, watch, PropType } from 'vue';
import { Variant } from './types';
/**
* This is my amazing counter component
* It can increment and decrement!
*/
export default {
props: {
/**
* The initial value for the counter
*/
initialValue: {
type: Number,
default: 0,
},
/**
* Text shown above the counter
*/
label: {
type: String,
default: 'Counter',
},
/**
* If true, the counter can show negative numbers
*/
allowNegativeValues: {
type: Boolean,
default: false,
},
/**
* Defines the visual appearance of the counter
*/
variant: {
type: String as PropType<Variant>,
default: Variant.Default,
},
},
emits: ['counter-update'],
setup(props, context) {
const count = ref(props.initialValue);
const increment = () => {
count.value += 1;
};

const decrement = () => {
const newValue = count.value - 1;
if (newValue < 0 && !props.allowNegativeValues) {
count.value = 0;
} else {
count.value -= 1;
}
};
watch(count, value => {
context.emit('counter-update', value);
});
return {
count,
increment,
decrement,
};
},
};
</script>
<style scoped></style>

Yukarıdaki kodda Vue bileşenine JSDoc yorumlarıyla açıklama eklediğimi görebilirsiniz.

Bir component story’si, component dosyasının yanında yaşayan bir story dosyasında tanımlanır.

Şimdi, Counter.stories.tsdosyamıza bir göz atalım:

import Counter from './Counter.vue';
import { Variant } from './types';
//👇 This default export determines where your story goes in the story list
export default {
title: 'Counter',
component: Counter,
//👇 Creates specific argTypes with options
argTypes: {
variant: {
options: Variant,
},
},
};
//👇 We create a “template” of how args map to rendering
const Template = args => ({
components: { Counter },
setup() {
//👇 The args will now be passed down to the template
return { args };
},
template: '<Counter v-bind="args">{{ args.slotContent }}</Counter>',
});
//👇 Each story then reuses that template
export const Default = Template.bind({});
Default.args = {
label: 'Default',
};
export const Colored = Template.bind({});
Colored.args = {
label: 'Colored',
variant: Variant.Colored,
};
export const NegativeValues = Template.bind({});
NegativeValues.args = {
allowNegativeValues: true,
initialValue: -1,
};
export const Slot = Template.bind({});
Slot.args = {
slotContent: 'SLOT CONTENT',
};

Bu kod, Component Storybook Format’ında yazılmıştır ve dört stories’den oluşur:

  • Default: Counter component varsayılan durumunda
  • Colored: Renkli varyasyondaki counter component
  • NegativeValue: Negatif değerlere izin veren counter component
  • Slot: Slot içeriğine sahip counter component

Storybook’taki dokümanlarımıza bir göz atalım:

Test İşlemi

Artık Storybook’ta dokümanlara sahip olduğumuza göre onlara karşı testler yapmaya başlayabiliriz.

Jest Kurulumu

Test işlemi için Jest’i seçtim. Hızlı ve basit bir kurulum sürecine sahiptir, bir test runner’ıdır, bir onaylama kitaplığı ve Vue componentlerimizi mount etmek için bir DOM uygulaması içerir.

Jest’i mevcut Vue 3 + Vite projemize kurmak için aşağıdaki komutu çalıştırmamız gerekiyor:

npm install jest @types/jest ts-jest vue-jest@next @vue/test-utils@next --save-dev

Ardından kök dizinde bir jest.config.js yapılandırma dosyası oluşturmamız gerekiyor:

//jest.config.jsmodule.exports = {
moduleFileExtensions: ['js', 'ts', 'json', 'vue'],
transform: {
'^.+\\.ts$': 'ts-jest',
'^.+\\.vue$': 'vue-jest',
},
collectCoverage: true,
collectCoverageFrom: ['/src/**/*.vue'],
};

Bir sonraki adım, package.json’ımızdaki testleri çalıştıran bir komut satırı eklemek olacak:

"scripts": {
"test": "jest src"
}

Storybook ile Unit(Birim) Testi

Unit testleri, component işlevsel yönlerinin doğrulanmasına yardımcı olur. Sabit bir girdi verildiğinde, bir bileşenin çıktısının aynı kaldığını kanıtlarlar.

Storybook story için basit bir birim testine bakalım:

import { mount } from '@vue/test-utils';import Counter from './Counter.vue';//👇 Imports a specific story for the test
import { Colored, Default } from './Counter.stories';
it('renders default button', () => {
const wrapper = mount(Counter, {
propsData: Default.args,
});
expect(wrapper.find('.container').classes()).toContain('default');
});
it('renders colored button', () => {
const wrapper = mount(Counter, {
propsData: Colored.args,
});
expect(wrapper.find('.container').classes()).toContain('colored');
});

Storybook da Counter.stories.ts’ye karşı Jest’in çalıştırdığı iki örnek unit(birim) testi yazdık:

  • renders default button: Component da ki CSS sınıfı default içerdiğini iddia eder
  • renders colored button: Component’in colored CSS sınıfını içerdiğini iddia eder

Test sonucu şöyle görünür:

PASS  src/components/Counter.test.ts
✓ renders default button (25 ms)
✓ renders colored button (4 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 0 | 0 | 0 | 0 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.674 s, estimated 4 s

Snapshot Test İşlemi

Oluşturma hatalarını ve uyarıları tetikleyen markup değişikliklerini belirlemenin kolay bir yoludur.

Snapshot testi, story’mizin markup’ını oluşturur, bir anlık görüntü alır ve bunu testin yanında depolanan bir referans Snapshot dosyasıyla karşılaştırır.

İki anlık görüntü eşleşmezse test durumu başarısız olur. Snapshot testinin başarısız olmasının iki tipik nedeni vardır:

  • Değişiklik bekleniyor
  • Referans anlık görüntüsünün güncellenmesi gerekiyor

Snapshot testleri için Jest kitaplığı olarak Jest Snapshot Testing’i kullanabiliriz.

Aşağıdaki komutu çalıştırarak kurulumunu yapalım:

module.exports = {
moduleFileExtensions: ['js', 'ts', 'json', 'vue'],
transform: {
'^.+\\.ts$': 'ts-jest',
'^.+\\.vue$': 'vue-jest',
},
collectCoverage: true,
collectCoverageFrom: ['/src/**/*.vue'],
snapshotSerializers: ['jest-serializer-vue']
};

Son olarak, Storybook story’si için bir snapshot testi yazabiliriz:

it('renders snapshot', () => {
const wrapper = mount(Counter, {
propsData: Colored.args,
});
expect(wrapper.element).toMatchSnapshot();
});

Şimdi testlerimizi çalıştırırsak, aşağıdaki sonucu alırız:

> vite-vue-typescript-starter@0.0.0 test
> jest src
PASS src/components/Counter.test.ts
✓ renders default button (27 ms)
✓ renders colored button (4 ms)
✓ renders snapshot (6 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 0 | 0 | 0 | 0 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 1 passed, 1 total
Time: 1.399 s, estimated 2 s

Test çalıştırması, src/components/__snapshots__konumunda bulunan snapshot referans dosyaları oluşturur.

Kaynakça:

GitHub:

--

--

Mert DEMİR
Berkut Teknoloji

Software Engineering => {#JavaScript #Vuejs #Nuxtjs #Jest}