Alat yang ampuh untuk menyederhanakan tes sudut Anda
Spectator membantu Anda menyingkirkan semua pekerjaan kasar boilerplate, meninggalkan Anda dengan tes unit yang dapat dibaca, ramping, dan ramping.
✅ Dukungan untuk menguji komponen, arahan dan layanan sudut
✅ dom query yang mudah
✅ Bersihkan API untuk memicu keyboard/mouse/acara sentuh
✅ Menguji ng-content
✅ Kustom Jasmine/Jest Matchers (Tohaveclass, ToBedisabled ..)
✅ Dukungan Pengujian Routing
✅ Dukungan Pengujian HTTP
✅ Dukungan bawaan untuk komponen masuk
✅ Dukungan bawaan untuk penyedia komponen
✅ Penyedia Auto-Mocking
✅ Sangat diketik
✅ Dukungan Jest
Bantuan sponsor dalam pengembangan dan pemeliharaan perpustakaan NGNEAT yang berkelanjutan. Pertimbangkan untuk meminta perusahaan Anda untuk mensponsori NGNEAT sebagai intinya untuk pengembangan bisnis dan aplikasi mereka.
Tinggikan dukungan Anda dengan menjadi sponsor emas dan minta logo Anda ditampilkan secara jelas pada readme kami di 5 repositori teratas.
Tingkatkan dukungan Anda dengan menjadi sponsor emas dan nikmati sorotan dengan logo Anda yang dipamerkan secara jelas di 3 repositori teratas di ReadMe kami.

Jadilah sponsor perunggu dan dapatkan logo Anda di readme kami di GitHub.
Fitur
Daftar isi
Instalasi
NPM
Benang
Komponen pengujian
Tampilan yang dapat ditunda bersarang
Pemilih string
Ketik pemilih
Pemilih dom
Menguji elemen pilih
Komponen mengejek
Menguji komponen tunggal/modul sudut arahan
Acara Kustom
Pencipta acara
Acara API
Pembantu keyboard
Pembantu tikus
Kueri
Tampilan yang dapat ditunda
Pengujian dengan host
Komponen Host Kustom
Pengujian dengan perutean
Memicu navigasi
Pengujian Integrasi dengan RouterTestingModule
Opsi routing
Arahan pengujian
Layanan Pengujian
Opsi tambahan
Pengujian pipa
Menggunakan Komponen Host Kustom
Penyedia mengejek
Mengejek dependensi onInit
Ketergantungan konstruktor yang mengejek
Dukungan Jest
Pengujian dengan http
Suntikan global
Penyedia komponen
Pencocokan khusus
Skema
Koleksi Skema Default
Perbandingan Repo dan Karma Sampel Penonton & Jest yang Bekerja
Tim inti
Kontributor
npm install @ngneat/spectator --save-dev
yarn add @ngneat/spectator --dev
Buat pabrik komponen dengan menggunakan fungsi createComponentFactory() , lulus kelas komponen yang ingin Anda uji. createComponentFactory() mengembalikan fungsi yang akan membuat komponen baru di setiap blok it :
import {spectator, createComponentFactory} dari '@ngneat/spectator'; import {tombolComponent} dari './button.component'; rencanakan('buttonComponent', () => {
Biarkan Spectator: Spectator <ButtonComponent>;
Const CreateComponent = CreateComponentFactory (ButtonComponent);
Sebelumeach (() => spectator = createComponent ());
itu ('harus memiliki kelas sukses secara default', () => {harapkan (spectator.query ('tombol')). Tohaveclass ('Success');
});
itu ('harus mengatur nama kelas sesuai dengan input [className]', () => {spectator.setInput ('className', 'bahaya'); harapkan (spectator.query ('tombol')). Tohaveclass (' bahaya '); harapkan (spectator.query (' tombol ')). not.tohaveclass (' Success ');
});}); Fungsi createComponentFactory dapat secara opsional mengambil opsi berikut yang memperluas opsi modul pengujian sudut dasar:
Const CreateComponent = CreateComponentFactory ({
Komponen: ButtonComponent,
Impor: [],
Penyedia: [],
Deklarasi: [],
Komponen entri: [],
ComponentProviders: [], // angkanya penyedia komponen
ComponentViewProviders: [], // Mengesampingkan Penyedia Tampilan Komponen
Overridemodules: [], // Override Modules
OverderComponents: [], // Override Components Jika pengujian komponen mandiri
Overridedirectives: [], // Override Directives Dalam kasus pengujian arahan mandiri
Overridepipes: [], // Override Pipes Dalam kasus pengujian pipa mandiri
Mocks: [], // penyedia yang secara otomatis akan diejek
ComponentMocks: [], // penyedia komponen yang secara otomatis akan diejek
ComponentViewProvidersMocks: [], // Penyedia tampilan komponen yang akan secara otomatis diejek
DetectChanges: false, // default ke true
DeklareComponent: false, // default ke true
DisableAnimations: false, // default ke true
Dangkal: Benar, // default ke false
deferblockbehavior: deferblockbehavior // default ke deferblockbehavior.playThrough}); Fungsi createComponent() secara opsional mengambil opsi berikut:
itu ('harus ...', () => {
spectator = createComponent ({// komponen inputsprops: {title: 'click'}, // angkanya penyedia komponen // Perhatikan bahwa Anda harus mendeklarasikannya sekali dalam `createComponentFactory` pendamping: [], // Apakah akan menjalankan deteksi perubahan` createComponentFactory` (default ke true) detectchanges: false
});
harapkan (spectator.query ('tombol')). Tohavetext ('klik');}); Dengan memberikan opsi overrideComponents dalam ruang lingkup fungsi createComponent() kami, kami dapat menentukan cara menimpa komponen mandiri dan ketergantungannya
@Komponen({
Selector: `app-standalone-with-import`,
Template: `<Div id =" mandiri "> komponen mandiri dengan impor! </div> <app-standalone-with-dependency> </app-standalone-with-dependency>`,
Impor: [mandiri komponen dengan ketergantungan],
Standalone: true,}) Ekspor kelas StandAnonewithImportScomponent {} @component ({
Selector: `app-standalone-with-dependency`,
Template: `<Div id =" standAlonewithDependency "> komponen mandiri dengan ketergantungan! </div>`,
mandiri: true,}) Kelas ekspor mandirionecomponentWithDependency {
konstruktor (kueri publik: queryservice) {}}@component ({
Selector: `app-standalone-with-dependency`,
Template: `<Div id =" standAlonewithDependency "> komponen mandiri dengan ketergantungan override! </div>`,
mandiri: true,}) kelas ekspor mockstandAnoneComponentWithDependency {
constructor () {}} it ('harus ...', () => {
const spectator = createHostFactory ({component: standAloneWithImportScomponent, template: `<verv> <P app-standalone-with-mport> </app-standalone-with-imports> </div>`, overridecomponents: [standAloneWithImportscompon- {div> {overridecomponents: [[standAloneWithScompon, ’{overridecomponents: [standAloneWithScompon- {overDecomponents: {impor: [mandallecomponentWithDependency]}, tambahkan: {impor: [mockstandAnoneComponentWithDependency]},},],],
});
harapkan (host.query ('#mandiri')). Tocontaintext ('komponen mandiri dengan impor!');
hargai (host.query ('#mandirionewithDependency')). Tocontaintext ('komponen mandiri dengan ketergantungan override!');}); Metode createComponent() mengembalikan instance Spectator yang memperlihatkan API berikut:
fixture - perlengkapan komponen yang diuji
component - contoh komponen yang diuji
element - Elemen asli komponen yang diuji
debugElement - Elemen Debug Fixture yang Diuji
flushEffects() - Menyediakan pembungkus untuk TestBed.flushEffects()
inject() - menyediakan pembungkus untuk TestBed.inject() :
const service = spectator.Inject (queryService); const fromComponentInjector = true; const service = spectator.inject (queryservice, fromComponentInjector);
detectChanges() - Berjalan DetectChange pada elemen/host yang diuji:
Spectator.DetectChanges ();
detectComponentChanges() - Menjalankan detectChanges pada komponen yang diuji (bukan pada host ). Anda akan memerlukan metode ini dalam kasus yang jarang terjadi saat menggunakan host dan komponen yang diuji adalah onPush , dan Anda ingin memaksanya untuk menjalankan siklus deteksi perubahan.
Spectator.DetectComponentChanges ();
setInput() - Mengubah nilai @Input () dari komponen yang diuji. Metode menjalankan ngOnChanges dengan SimpleChanges secara manual jika ada.
itu ('harus ...', () => {
spectator.setInput ('className', 'bahaya');
spectator.setInput ({className: 'Danger'
});}); output - Mengembalikan @output () yang dapat diamati dari komponen yang diuji:
itu ('harus memancarkan $ event pada klik', () => {
biarkan output;
spectator.output ('klik'). Berlangganan (result => (output = hasil));
spectator.component.onClick ({type: 'klik'});
harapkan (output) .toequal ({type: 'klik'});}); tick(millis?: number) - Jalankan fungsi Fakeasync tick() dan detectChanges() :
itu ('harus bekerja dengan centang', fakeasync (() => {
Spectator = CreateComponent (ZippyComponent);
spectator.component.update ();
harapkan (spectator.component.updatedAsync) .tobefalsy ();
Spectator.tick (6000);
harapkan (spectator.component.updatedAsync) .not.tobefalsy ();}))) Masing -masing acara dapat menerima SpectatorElement yang dapat menjadi salah satu dari yang berikut:
Ketik SpectatorElement = String | Elemen | Debugelement | ElementRef | Jendela | Dokumen | Domselector;
Jika tidak disediakan, elemen default akan menjadi elemen host dari komponen yang diuji.
click() - Memicu Acara Klik:
spectator.click (spectatorElement); spectator.click (bytext ('elemen')); blur() - memicu acara blur:
spectator.blur (SpectatorElement); spectator.blur (bytext ('elemen'));Perhatikan bahwa jika menggunakan kerangka kerja Jest, Blur () hanya berfungsi jika elemen difokuskan. Detail.
focus() - memicu acara fokus:
spectator.focus (spectatorElement); spectator.focus (bytext ('elemen')); typeInElement() - mensimulasikan pengetikan pengguna:
spectator.typeIneLement (nilai, spectatorElement); spectator.typeIneLement (nilai, bytext ('elemen')); dispatchMouseEvent() - memicu acara mouse:
Spectator.DispatchMouseEvent (SpectatorElement, 'Mouseout'); Spectator.DispatchMouseEvent (SpectatorElement, 'Mouseout'), x, y, acara); spectator.dispatchmouseEvent (bytext ('elemen'), 'mouseout'); spectator.dispatchment ('Elemen'), 'mouseout', x, y, acara); dispatchKeyboardEvent() - Memicu acara keyboard:
Spectator.DispatchKeyBoardEvent (SpectatorElement, 'Keyup', 'Escape'); Spectator.DispatchKeyboardEvent (SpectatorElement, 'Keyup', {Key: 'Escape', KeyCode: 27}) Spectator.DispatchKeyBoardEvent (bytext ('Element'), ' ',' Escape '); Spectator.DispatchKeyBoardEvent (bytext (' elemen '),' keyup ', {key:' Escape ', KeyCode: 27}) dispatchTouchEvent() - memicu acara sentuh:
Spectator.DispatchTouchEvent (SpectatorElement, Type, X, Y); Spectator.DispatchTouchEvent (bytext ('Element'), type, x, y);Anda dapat memicu acara khusus (@output () komponen anak) menggunakan metode berikut:
spectator.triggereventhandler (mychildComponent, 'mycustomevent', 'eventValue'); spectator.triggereventhandler (mychildcomponent, 'mycustomEvent', 'eventValue', {root: true}); spectator.triggerevent ('app-comp-comp-compoment', 'app-compoment', 'app-compoment', 'appon-compon- complilon (' appon-compoment ' ',' eventValue '); spectator.triggereventhandler (' Aplikasi-komponen ',' MyCustomEvent ',' EventValue ', {root: true});Jika Anda ingin menguji acara secara independen dari templat apa pun (misalnya dalam layanan presenter), Anda dapat mundur pada pencipta acara yang mendasarinya. Mereka pada dasarnya menyediakan tanda tangan yang sama tanpa elemen sebelumnya.
const keyboardevent = createKeyboardEvent ('keyup', 'arrowdown'/ *, targetElement */); const MouseEvent = createMouseEvent ('mouseout'); const touchevent = createTouchEvent ('touchmove'); const fakeEvent = createFakeEvent ('input'); spectator.keyboard.pressenter (); spectator.keyboard.pressescape (); spectator.keyboard.presstab (); spectator.keyboard.pressbackspace (); spectator.keyboard.presskey ('a'); spectator.keyboard.presskey (' ctrl.a '); spectator.keyboard.presskey (' ctrl.shift.a '); spectator.mouse.contextMenu ('. selector'); spectator.mouse.dblClick ('. selector'); Perhatikan bahwa masing -masing metode di atas juga akan menjalankan detectChanges() .
API Spectator mencakup metode yang nyaman untuk menanyakan DOM sebagai bagian dari tes: query , queryAll , queryLast , queryHost dan queryHostAll . Semua metode kueri bersifat polimorfik dan memungkinkan Anda untuk meminta menggunakan salah satu teknik berikut.
Lewati pemilih string (dengan gaya yang sama seperti saat menggunakan jQuery atau document.querySelector) untuk meminta elemen yang cocok dengan jalur itu di DOM. Metode untuk kueri ini setara dengan predikat Sangular by.css. Perhatikan bahwa elemen HTML asli akan dikembalikan. Misalnya:
// Mengembalikan satu htmlelementspectator.Query ('Div> ul.nav Li: First-Child'); // Mengembalikan array dari semua HTMLelementsspectator.QueryAll yang cocok ('Div> ul.nav li'); // Query dari Document ContextSpectator.Query ('Div', {root: true}); spectator.query ('app-child', {baca: childserviceservice}); Lewati tipe (seperti komponen, arahan atau kelas penyedia) untuk meminta contoh jenis itu di DOM. Ini setara dengan Angular's by. Predikat By.directive . Anda secara opsional dapat melewati parameter kedua untuk membaca token injeksi spesifik dari injektor elemen yang cocok. Misalnya:
// Mengembalikan satu instance myComponent (jika ada) spectator.query (myComponent); // mengembalikan instance `someservice` yang ditemukan dalam contoh` myComponent` yang ada di dom (jika ada) spectator.query (myComponent myComponent` yang ada di dom (jika ada) , {baca: someservice}); spectator.query (myComponent, {baca: elementRef}); host.querylast (childComponent); host.queryAll (childComponent);Spectator memungkinkan Anda untuk meminta elemen menggunakan selektor yang terinspirasi oleh dom-testing-library. Selektor yang tersedia adalah:
Spectator.Query (byplaceHolder ('Harap masukkan alamat email Anda')); spectator.query (byvalue ('by value')); spectator.query (bytitle ('dengan judul')); spectator.query (byalttext ('by by (oleh byaltext (' oleh alt text ')); spectator.query (bylabel (' by label ')); spectator.query (bytext (' by text ')); spectator.query (bytext (' dengan teks ', {selector:' #some. selector '})); spectator.query (bytextContent (' dengan konten teks ', {selector:' #some .selector '})); spectator.query (byrole (' centang kotak ', {checked: true})); Perbedaan antara byText dan byTextContent adalah bahwa yang pertama tidak cocok dengan teks di dalam elemen bersarang.
Misalnya, dalam html byText('foobar', {selector: 'div'}) tidak akan cocok dengan div berikut, tetapi byTextContent akan:
<div> <span> foo </span> <span> bar </span> </div>
Spectator memungkinkan Anda untuk meminta unsur -unsur bersarang dalam elemen induk. Ini berguna ketika Anda memiliki beberapa contoh komponen yang sama di halaman dan Anda ingin meminta untuk anak -anak dalam yang tertentu. Pemilih induk adalah pemilih string yang digunakan untuk menemukan elemen induk. Pemilih induk dilewatkan sebagai parameter kedua ke metode kueri. Misalnya:
Spectator.Query (ChildComponent, {Parentselector: '#Parent-Component-1'}); Spectator.QueryAll (ChildComponent, {Parentselector: '#Parent-Component-1'}); Spectator memungkinkan Anda untuk menguji <select></select> elemen dengan mudah, dan mendukung Multi Select.
Contoh:
itu ('harus mengatur opsi yang benar pada multi select', () => {
const select = spectator.query ('#uji-multi-select') sebagai htmlselectElement;
spectator.selectOption (pilih, ['1', '2']);
harapkan (pilih) .tohaveselectedOptions (['1', '2']);}); it ('harus mengatur opsi yang benar pada standar pilih', () => {
const select = spectator.query ('#uji-single-select') sebagai htmlselectElement;
spectator.selectOption (pilih, '1');
harapkan (pilih) .tohaveselectedOptions ('1');}); Ini juga memungkinkan Anda untuk memeriksa apakah event handler change Anda bertindak dengan benar untuk setiap item yang dipilih. Anda dapat menonaktifkan ini jika Anda perlu melakukan pra -set pilihan tanpa mengirim acara perubahan.
API:
spectator.selectOption (selectElement: htmlselectElement, opsi: string | string [] | htmloptionElement | htmloptionElement [], config: {emitevents: boolean} = {emitevents: true});Contoh:
itu ('harus mengirimkan jumlah peristiwa perubahan yang benar', () => {
const onchangespy = spyon (spectator.component, 'handlechange');
const select = spectator.query ('#test-onchange-select') sebagai htmlselectElement;
spectator.selectOption (pilih, ['1', '2'], {emitevents: true});
harapkan (pilih) .tohaveselectedOptions (['1', '2']);
harapkan (onchangespy) .tohavebeencalledTimes (2);}); itu ('tidak boleh mengirimkan jumlah peristiwa perubahan yang benar', () => {
const onchangespy = spyon (spectator.component, 'handlechange');
const select = spectator.query ('#test-onchange-select') sebagai htmlselectElement;
spectator.selectOption (pilih, ['1', '2'], {emitevents: false});
harapkan (pilih) .tohaveselectedOptions (['1', '2']);
harapkan (onchangespy) .not.tohavebeencalledTimes (2);}); Anda juga dapat meneruskan HTMLOptionElement sebagai argumen untuk selectOption dan pencocokan toHaveSelectedOptions . Ini sangat berguna saat Anda menggunakan [ngValue] yang mengikat pada <option> :
itu ('harus mengatur opsi yang benar pada single select saat melewati elemen', () => {
const select = spectator.Query ('#uji-single-select-element') sebagai htmlselectElement;
spectator.selectOption (pilih, spectator.query (bytext ('dua')) sebagai htmloptionElement);
harapkan (pilih) .tohaveselectedOptions (spectator.query (bytext ('dua')) sebagai htmloptionElement);}); Jika Anda perlu mengejek komponen, Anda dapat menggunakan pustaka NG-Mocks. Alih-alih menggunakan CUSTOM_ELEMENTS_SCHEMA , yang mungkin menyembunyikan beberapa masalah dan tidak akan membantu Anda untuk mengatur input, output, dll., ng-mocks akan mengotori input, output, dll. Untuk Anda.
Contoh:
Impor {createHostFactory} dari '@ngneat/spectator'; import {mockComponent} dari 'ng-mocks'; import {foocomponent} dari './path/to/foo.conponent';const createHost = createHostFactory ({{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
Komponen: YourComponentTotest,
Deklarasi: [mockComponent (foocomponent)
]});Komponen (atau arahan) yang dinyatakan dalam modul mereka sendiri dapat diuji dengan mendefinisikan modul komponen dalam daftar impor pabrik komponen bersama dengan komponen. Misalnya:
Const CreateComponent = CreateComponentFactory ({
Komponen: ButtonComponent,
Impor: [ButtonComponentModule],}); Namun, ketika digunakan seperti ini, Spectator secara internal menambahkan komponen ButtonComponent ke deklarasi modul baru yang dibuat secara internal. Karenanya, Anda akan melihat kesalahan berikut:
Type ButtonComponent is part of the declarations of 2 modules [...]
Dimungkinkan untuk memberi tahu penonton untuk tidak menambahkan komponen ke deklarasi modul internal dan, sebaliknya, menggunakan modul yang ditentukan secara eksplisit sebagaimana adanya. Cukup atur properti declareComponent dari opsi pabrik ke false :
Const CreateComponent = CreateComponentFactory ({
Komponen: ButtonComponent,
Impor: [ButtonComponentModule],
DeclareComponent: false,}); Saat menggunakan CreateDirectiveFactory, atur properti declareDirective dari opsi pabrik menjadi false :
const createrective = createDirectiveFactory ({
Komponen: SoroteComponent,
Impor: [SoroteComponentModule],
Decledirective: false,}); Spectator menyediakan API yang nyaman untuk mengakses tampilan yang dapat ditunda ( @defer {} ).
Akses blok Defer yang diinginkan menggunakan metode spectator.deferBlock(optionalIndex) . Parameter optionalIndex adalah opsional dan memungkinkan Anda untuk menentukan indeks blok Defer yang ingin Anda akses.
Mengakses Blok Disper Pertama : Cukup hubungi spectator.deferBlock() .
Mengakses blok penundaan berikutnya : Gunakan indeks yang sesuai sebagai argumen. Misalnya, spectator.deferBlock(1) mengakses blok kedua (pengindeksan berbasis nol).
The spectator.deferBlock(optionalIndex) mengembalikan empat metode untuk merender berbagai keadaan dari blok penundaan yang ditentukan:
renderComplete() - merender status lengkap blok penundaan.
renderPlaceholder() - Merender State Placeholder dari Blok Defer.
renderLoading() - Render status pemuatan blok penundaan.
renderError() - membuat keadaan kesalahan blok penundaan.
Contoh:
@Component ({selector: 'app-cmp', template: `@defer (di viewport) {<vet> Status lengkap dari blok tundukan pertama </div> <!-status induk lengkap->} @placeHolder { <dv> placeholder </div>} `,}) kelas dummyComponent {} const createComponent = createComponentFactory ({component: dummyComponent, deferblockbehavior: deferblockbehavior.manual,}); it ('harus membuat status lengkap', async () = = manual,}); it ('harus membuat keadaan lengkap', async () = = = = > {// atur const spectator = createComponent (); Untuk mengakses negara -negara dalam blok penundaan bersarang, hubungi metode deferBlock rantai dari metode status blok yang dikembalikan.
Contoh: Mengakses Negara Lengkap bersarang:
// dengan asumsi `spectator.deferblock (0) .renderComplete ()` membuat keadaan lengkap dari orangtua penundaan blockconst induk completeState = menunggu spectator.deferblock (). RenderComplete (); // akses nested state lengkap dari blockcons nestedcompletate (); = Await ParentCompleteState.RenderComplete (). Deferblock ();
Contoh Lengkap :
@Component ({selector: 'app-cmp', template: `@defer (di viewport) {<div> Status lengkap dari blok penundaan pertama </div> <!-status lengkap induk-> @defer {< Div> Lengkapi keadaan blok penundaan bersarang </div> <!-Nested Nested Complete State->}} @placeholder {<ver> placeholder </div>} `,}) kelas DummyComponent {} const createComponent = createComponentFactory ( {Component: DummyComponent, DeferBlockBehavior: DeferblockBehavior.Manual,}); itu ('harus membuat keadaan lengkap bersarang pertama', async () => {// atur const spectator = createComponent (); // act // meating induk const const completestate = menunggu spectator.deferblock (). renderComplete (); Defer Block ');});Menguji komponen dengan komponen host adalah teknik yang lebih elegan dan kuat untuk menguji komponen Anda. Ini pada dasarnya memberi Anda kemampuan untuk menulis tes Anda dengan cara yang sama seperti Anda menulis kode Anda. Mari kita lihat beraksi:
impor {createHostFactory, spectatorhost} dari '@ngneat/spectator'; jelas ('zippycomponent', () => {
Biarkan Spectator: SpectatorHost <SlipyComponent>;
const createHost = createHostFactory (zippycomponent);
itu ('harus menampilkan judul dari properti host', () => {spectator = createHost (`<zippy [title] =" title "> </mellow>`, {hostprops: {title: 'spectator is awesome'} }); harapkan (spectator.query ('. Zippy__title')). Tohavetext ('Spectator is Awesome');
});
itu ('harus menampilkan kata "tutup" jika terbuka', () => {spectator = createHost (`<zippy title =" judul zippy "> konten zippy </mellpy>`); spectator.click ('. Zippy__title' ); harapkan (spectator.query ('. panah')). tohavetext ('tutup'); harapkan (spectator.query ('. panah')). not.tohavetext ('terbuka');
});}); Metode host mengembalikan instance SpectatorHost yang memperluas Spectator dengan API tambahan berikut:
hostFixture - perlengkapan host
hostComponent - instance komponen host
hostElement - Elemen Asli Host
hostDebugElement - Elemen Debug Fixture Host
setHostInput - mengubah nilai @Input() dari komponen host
queryHost - Baca lebih lanjut tentang permintaan di Spectator
queryHostAll - Baca lebih lanjut tentang permintaan di Spectator
Mengatur input secara langsung pada komponen menggunakan setInput atau props tidak dimungkinkan saat menguji dengan komponen host. Input harus diatur melalui hostProps atau setHostInput sebagai gantinya, dan diteruskan ke komponen Anda di templat.
Terkadang sangat membantu untuk melewati implementasi host Anda sendiri. Kami dapat meneruskan komponen host khusus ke createHostFactory() yang akan menggantikan yang default:
@Component ({selector: 'custom-host', template: ''}) kelas customhostcomponent {
title = 'custom hostComponent';} jelas ('dengan komponen host khusus', function () {
Biarkan Spectator: SpectatorHost <ZippyComponent, CustomHostComponent>;
const createHost = createHostFactory ({component: zippycomponent, host: customHostComponent
});
itu ('harus menampilkan judul komponen host', () => {spectator = createHost (`<zippy [title] =" title "> </litpy>`); harapkan (spectator.query ('. zippy__title'))) .tohavetext ('Custom HostComponent');
});}); Untuk komponen yang menggunakan routing, ada pabrik khusus yang tersedia yang memperluas yang default, dan menyediakan rutin ActivatedRoute rontok sehingga Anda dapat mengkonfigurasi opsi perutean tambahan.
jelaskan ('ProductDetailScomponent', () => {
Biarkan Spectator: Spectatorouting <PrododDetailScomponent>;
const createComponent = createroutingfactory ({component: productDetailScomponent, params: {productId: '3'}, data: {title: 'beberapa judul'}
});
Sebelumeach (() => spectator = createComponent ());
itu ('harus menampilkan judul data rute', () => {harapkan (spectator.query ('. judul')). Tohavetext ('beberapa judul');
});
itu ('harus bereaksi terhadap perubahan rute', () => {spectator.setrouteparam ('productId', '5'); // tes Anda di sini ...
});}); API SpectatorRouting mencakup metode yang nyaman untuk memperbarui rute saat ini:
Antarmuka Spectatrouting <c> memperluas penonton <c> {
/*** mensimulasikan navigasi rute dengan memperbarui aliran params, queryparams dan data yang dapat diamati. */
triggerNavigasi (opsi?: routeOptions): batal;
/*** memperbarui rute params dan memicu navigasi rute. */
setRouteParam (Nama: String, Value: String): void;
/*** memperbarui rute kueri params dan memicu navigasi rute. */
setRoutequeryParam (nama: string, value: string): void;
/*** memperbarui data rute dan memicu navigasi rute. */
setRoutEdata (Nama: String, Nilai: any): batal;
/*** memperbarui fragmen rute dan memicu navigasi rute. */
setRoutefragment (fragmen: string | null): batal;
/*** memperbarui URL rute dan memicu navigasi rute. */
setRouteurl (url: urlsegment []): void;}RouterTestingModule Jika Anda mengatur opsi stubsEnabled ke false , Anda dapat melewati konfigurasi routing nyata dan mengatur tes integrasi menggunakan RouterTestingModule dari sudut.
Perhatikan bahwa ini membutuhkan janji untuk menyelesaikannya. Salah satu cara untuk menangani ini, adalah dengan membuat tes Anda Async:
jelaskan ('tes integrasi perutean', () => {
const createComponent = createroutingfactory ({component: myComponent, deklarasi: [OtherComponent], stubsenabled: false, rute: [{path: '', komponen: myComponent}, {path: 'foo', komponen: OtherComponent}]
});
itu ('harus menavigasi menggunakan router link', async () => {const spectator = createComponent (); // tunggu janji untuk menyelesaikan ... menunggu spectator.fixture.whenstable (); // uji rute saat ini dengan menegaskan locationexpect (spectator.inject (location) .path ()). tobe ('/'); // klik pada router linkspectator.click ('. Link-1'); // Jangan lupa menunggu untuk menunggu janji untuk menyelesaikan ... menunggu spectator.fixture.whenstable (); // uji rute baru dengan menegaskan LocationExpect (Spectator.Inject (Location) .path ()). Tobe ('/foo');
});}); Fungsi createRoutesFactory dapat mengambil opsi berikut, di atas opsi penonton default:
params : Param awal yang akan digunakan di Stub ActivatedRoute
queryParams : Param kueri awal untuk digunakan dalam rintisan ActivatedRoute
data : Data awal untuk digunakan dalam rintisan ActivatedRoute
fragment : Fragmen Awal untuk Digunakan di ActivatedRoute Stub
url : Segmen URL Awal untuk digunakan dalam Stub ActivatedRoute
root : Nilai root untuk rintisan ActivatedRoute
parent : Nilai untuk parent untuk rintisan ActivatedRoute
children : Nilai untuk children untuk rintisan ActivatedRoute
firstChild : Nilai untuk firstChild untuk Stub ActivatedRoute
stubsEnabled (default: true ): Mengaktifkan rintisan ActivatedRoute , jika diatur ke false menggunakan RouterTestingModule sebagai gantinya
routes : Jika stubsEnabled disetel ke false, Anda dapat melewati konfigurasi Routes untuk RouterTestingModule
Ada pabrik uji khusus untuk arahan pengujian. Katakanlah kita memiliki arahan berikut:
@Directive ({selector: '[highlight]'}) Ekspor Kelas Sorotan {
@Hostbinding ('style.background-color') BackgroundColor: string;
@Hostlistener ('mouseover')
onhover () {this.backgroundColor = '#000000';
}
@Hostlistener ('mouseout')
onleave () {this.backgroundColor = '#ffffff';
}}Mari kita lihat bagaimana kita dapat menguji arahan dengan mudah dengan penonton:
Jelaskan ('Sorotan Direktif', () => {
Biarkan Spectator: Spectatordirective <ToroughDirective>;
const createRirective = createDirectiveFactory (sorotan);
Sebelumeach (() => {Spectator = CreateDirective (`<Div Sorotan> Pengujian Sorot Arahan </Div>`);
});
itu ('harus mengubah warna latar belakang', () => {spectator.dispatchmouseEvent (spectator.element, 'mouseover'); harapkan (spectator.element) .tohavestyle ({{latar belakangcolor: 'RGBA (0,0,0, 0,1 ) '}); spectator.dispatchmouseEvent (spectator.element,' mouseout '); harapkan (spectator.element) .tohavestyle ({backgroundColor:' #fff '});
});
itu ('harus mendapatkan instance', () => {const instance = spectator.directive; harapkan (instance) .tobedefined ();
});}); Mengatur input secara langsung pada arahan menggunakan setInput atau props tidak dimungkinkan. Input harus diatur melalui hostProps atau setHostInput sebagai gantinya, dan diteruskan ke arahan Anda di templat.
Contoh berikut menunjukkan cara menguji layanan dengan penonton:
impor {createServiceFactory, spectatorservice} dari '@ngneat/spectator'; import {authservice} dari 'auth.service.ts'; jelas ('authservice', () => {
Biarkan Spectator: SPECTATORSERVICE <InownService>;
const createService = createServiceFactory (AuthService);
Sebelumeach (() => spectator = createService ());
itu ('tidak boleh masuk', () => {harapkan (spectator.service.isloggedin ()). Tobefalsy ();
});}); Fungsi createService() mengembalikan SpectatorService dengan sifat -sifat berikut:
service - Dapatkan contoh layanan
inject() - proxy untuk TestBed.inject()
Dimungkinkan juga untuk lulus objek dengan opsi. Misalnya, saat menguji layanan, Anda sering ingin mengejek ketergantungannya, karena kami fokus pada layanan yang sedang diuji.
Misalnya:
@Injectable () Kelas Ekspor AuthService {
Konstruktor (Private DateService: DateService) {}
isLoggedIn () {if (this.dateservice.isexpired ('timestamp')) {return false;} return true;
}} Dalam hal ini kita dapat mengejek ketergantungan DateService .
impor {createServiceFactory, spectatorservice} dari '@ngneat/spectator'; import {authservice} dari 'auth.service.ts'; jelas ('authservice', () => {
Biarkan Spectator: SPECTATORSERVICE <InownService>;
const createService = createServiceFactory ({service: authservice, penyedia: [], entrycomponents: [], mocks: [DateService]
});
Sebelumeach (() => spectator = createService ());
itu ('harus masuk', () => {const dateService = spectator.inject (DateService); DateService.isexpired.and.ReturnValue (false); harapkan (spectator.service.isloggedin ()). Tobetruthy ();
});});Contoh berikut menunjukkan cara menguji pipa dengan penonton:
impor {spectatorpipe, createPipeFactory} dari '@ngneat/spectator'; import {statsservice} dari './stats.service'; Import {Sumpipe} dari' ./sum.pipe'; recordrescribe('sumpipe ', () => {
Biarkan Spectator: SpectatorPipe <Sumpipe>;
createPipe const = createPipeFactory (Sumpipe);
itu ('harus merangkum daftar angka yang diberikan (templat)', () => {spectator = createPipe (`{{[1, 2, 3] | sum}}`); harapkan (spectator.element) .tohaveText ('6');
});
itu ('harus merangkum daftar angka yang diberikan (prop)', () => {spectator = createPipe (`{{prop | sum}}`, {hostprops: {prop: [1, 2, 3]}} ); harapkan (spectator.element) .tohavetext ('6');
});
itu ('harus mendelegasikan penjumlahan ke layanan', () => {const sum = () => 42; const provider = {berikan: statsservice, usevalue: {sum}}; spectator = createPipe (`{{{prop | sum}} `, {hostprops: {prop: [2, 40]}, penyedia: [penyedia]}); harapkan (spectator.element) .tohavetext ('42 ');
});}); Fungsi createPipe() mengembalikan SpectatorPipe dengan sifat -sifat berikut:
hostComponent - contoh komponen host
debugElement - Elemen debug fixture di sekitar komponen host
element - Elemen asli komponen host
detectChanges() - Proksi untuk TestBed.fixture.detectChanges()
inject() - proxy untuk TestBed.inject()
Mengatur input langsung pada pipa menggunakan setInput atau props tidak dimungkinkan. Input harus diatur melalui hostProps atau setHostInput sebagai gantinya, dan diteruskan ke pipa Anda di templat.
Contoh berikut menggambarkan cara menguji pipa menggunakan komponen host khusus:
import {komponen, input} dari '@angular/core'; import {spectatorpipe, createPipeFactory} dari '@ngneat/spectator'; import {averagePipe} dari './Arage.pipe'; Import {StatsService} dari' .Sstats .service ';@component ({
Template: `<verv> {{prop | rata -rata}} </div> `}) kelas CustomHostComponent {
@Input () PROP PUBLIK: NUMBER [] = [1, 2, 3];} Jelaskan ('AverAgePipe', () => {
Biarkan Spectator: SpectatorPipe <AveragePipe>;
const createPipe = createPipeFactory ({PIPE: AVERAGEPIPE, HOST: CustomHostComponent
});
itu ('harus menghitung rata -rata daftar angka yang diberikan', () => {spectator = createPipe (); harapkan (spectator.element) .tohavetext ('2');
});
itu ('harus menghasilkan 0 ketika daftar angka kosong', () => {spectator = createPipe ({hostprops: {prop: []}}); harapkan (spectator.element) .tohavetext ('0');
});
itu ('harus mendelegasikan perhitungan ke layanan', () => {const avg = () => 42; const provider = {berikan: statsservice, usevalue: {avg}}; spectator = createPipe ({penyedia: [penyedia ]}); harapkan (spectator.element) .tohavetext ('42 ');
});});Untuk setiap pabrik penonton, kami dapat dengan mudah mengejek penyedia apa pun.
Setiap layanan yang kami lewati ke properti mocks akan diejek menggunakan fungsi mockProvider() . Fungsi mockProvider() mengubah setiap metode menjadi mata -mata melati. (yaitu jasmine.createSpy() ).
Berikut adalah beberapa metode yang terpapar:
DateService.isexpired.and.callthrough (); DateService.isexpired.and.callfake (() => palsu); DateService.isexpired.and.throwerror ('error'); DateService.isexpired.andcallfake (() => palsu) ; Namun, jika Anda menggunakan Jest sebagai kerangka kerja tes dan Anda ingin memanfaatkan mekanisme mengejeknya, impor mockProvider() dari @ngneat/spectator/jest . Ini akan secara otomatis menggunakan fungsi jest.fn() untuk membuat mock yang kompatibel dengan lelucon.
mockProvider() tidak termasuk properti. Jika Anda perlu memiliki properti pada tiruan Anda, Anda dapat menggunakan argumen ke -2:
const createService = createServiceFactory ({
Layanan: AuthService,
Penyedia: [MockProvider (Lainnya Layanan, {Name: 'Martin', Emitter: New Subject (), MockedMethod: () => 'Mocked'})
],});Jika komponen bergantung pada layanan yang diejek dalam metode siklus hidup Oninit, deteksi perubahan perlu dinonaktifkan sampai setelah layanan disuntikkan.
Untuk mengonfigurasi ini, ubah metode createComponent untuk memiliki opsi detectChanges diatur ke False dan kemudian secara manual panggilan detectChanges pada penonton setelah menyiapkan layanan yang disuntikkan.
Const CreateComponent = CreateComponentFactory ({
Komponen: weatherDashboardComponent}); it ('harus memanggil API cuaca di init', () => {
const spectator = createComponent ({detectchanges: false
});
const weatherService = spectator.inject (weatherDataapi);
WeatherService.getweatherdata.andreturn (dari (mockweatherdata));
Spectator.DetectChanges ();
harapkan (WeatherService.getWeatherData) .tohaveEntalled ();});Jika komponen bergantung pada layanan yang diejek dalam konstruktornya, Anda perlu membuat dan mengonfigurasi tiruan, dan untuk memberikan tiruan saat membuat komponen.
Const CreateComponent = CreateComponentFactory ({
Komponen: weatherDashboardComponent}); it ('harus memanggil API cuaca di konstruktor', () => {
const weatherService = createSpyObject (weatherDataapi);
WeatherService.getweatherdata.andreturn (dari (mockweatherdata));
spectator = createComponent ({penyedia: [{berikan: weatherDataapi, usevalue: WeatherService}]
});
harapkan (WeatherService.getWeatherData) .tohaveEntalled ();});Secara default, Spectator menggunakan Jasmine untuk membuat mata -mata. Jika Anda menggunakan Jest sebagai Test Framework sebagai gantinya, Anda dapat membiarkan Spectator membuat mata-mata yang kompatibel dengan Jest.
Cukup impor salah satu fungsi berikut dari @ngneat/spectator/jest (bukan @ngneat/spectator), dan itu akan menggunakan Jest, bukan melati. createComponentFactory() , createHostFactory() , createServiceFactory() , createHttpFactory() , mockProvider() .
Impor {createServiceFactory, spectatorservice} dari '@ngneat/spectator/jest'; import {authservice} dari './Auth.Service'; Import {DateService} dari' ./date.service'; recordcribe('authservice ', () => {
Biarkan Spectator: SPECTATORSERVICE <InownService>;
const createService = createServiceFactory ({service: authservice, mocks: [DateService]
});
Sebelumeach (() => spectator = createService ());
itu ('tidak boleh masuk', () => {const DateService = spectator.Inject <DateService> (DateService); DateService.isexpired.mockreturnValue (true); harapkan (spectator.service.isloggedin ()). TOBLEFALSY ( );
});
itu ('harus dicatat', () => {const dateService = spectator.Inject <DateService> (DateService); DateService.isexpired.mockreturnValue (false); harapkan (spectator.service.isloggedin ()). TOBETRUTHY () ;
});}); Saat menggunakan skema komponen, Anda dapat menentukan bendera --jest agar impor jest digunakan. Untuk Jest mengimpor default, perbarui angular.json :
"skema": {"@ngneat/spectator: spectator-component": {"jest": true
}
}Spectator membuat layanan data pengujian, yang menggunakan modul HTTP sudut, jauh lebih mudah. Misalnya, katakanlah Anda memiliki layanan dengan tiga metode, satu melakukan get, satu pos dan seseorang melakukan permintaan bersamaan:
Kelas Ekspor TodosDataService {
konstruktor (private httpClient: httpclient) {}
getTodos () {return this.httpclient.get ('API/Todos');
}
posttodo (id: number) {return this.httpclient.post ('API/Todos', {id});
}
collectTodos () {return gabungan (this.httpclient.get ('/api1/todos'), this.httpclient.get ('/api2/todos'));
}}Tes untuk layanan di atas akan terlihat seperti:
impor {createHttpfactory, httpmethod} dari '@ngneat/spectator'; import {todosdataSerervice} dari './todos-data.service'; uji cobarriken tes, () => {
Biarkan Spectator: SpectatorHttp <TDoSDataService>;
const createHttp = createHttpFactory (todosdataService);
Sebelumeach (() => spectator = createHttp ());
itu ('dapat menguji httpclient.get', () => {spectator.service.gettodos (). Subscribe (); spectator.ExpecTone ('API/Todos', httpmethod.get);
});
itu ('dapat menguji httpclient.post', () => {spectator.service.posttodo (1) .subscribe (); const req = spectator.expectone ('API/Todos', httpmethod.post); harapkan (req. request.body ['id']). Toequal (1);
});
itu ('dapat menguji permintaan http saat ini', () => {spectator.service.gettodos (). Subscribe (); const reqs = spectator.ExpectConcurrent ([{url: '/api1/todos', metode: httpmethod.get }, {Url: '/api2/todos', metode: httpmethod.get}]); spectator.flushall (reqs, [{}, {}, {}]);
});}); Kita perlu membuat pabrik HTTP dengan menggunakan fungsi createHttpFactory() , melewati layanan yang ingin Anda uji. createHttpFactory() mengembalikan fungsi yang dapat dipanggil untuk mendapatkan instance spectatorhttp dengan properti berikut:
controller - Proxy untuk sudut HttpTestingController
httpClient - proxy untuk HttpClient sudut
service - Contoh Layanan
inject() - proxy untuk TestBed.inject()
expectOne() - mengharapkan satu permintaan yang dibuat yang cocok dengan URL yang diberikan dan metode itu, dan mengembalikan permintaan tiruannya
Dimungkinkan untuk mendefinisikan suntikan yang akan tersedia untuk setiap tes tanpa perlu memecahkannya kembali di setiap tes:
// test.tsimport {DefineGlobalSinjections} dari '@ngneat/spectator'; import {translocomodule} dari '@ngnneat/transloco'; defineglobalsinjections ({
impor: [translocomodule],}); Perlu diketahui, bahwa defineGlobalsInjections() harus dipanggil sebelum modul dimuat. Dalam test.ts sudut default.ts ini berarti sebelum baris ini:
context.keys (). Map (konteks);
Secara default, penyedia komponen asli (misalnya providers pada @Component ) tidak disentuh.
Namun, dalam kebanyakan kasus, Anda ingin mengakses penyedia komponen dalam pengujian Anda atau menggantinya dengan tiruan.
Misalnya:
@Komponen({
templat: '...',
penyedia: [fooservice]}) kelas foocomponent {
konstruktor (fooservice pribadi: fooservice} {}
// ...} Gunakan componentProviders untuk menggantikan penyedia FooService :
Const CreateComponent = CreateComponentFactory ({
Komponen: foocomponent,
ComponentProviders: [{berikan: fooservice, usevalue: Somethingelse}
]}) Atau mengejek layanan dengan menggunakan componentMocks :
Const CreateComponent = CreateComponentFactory ({
Komponen: foocomponent,
ComponentMocks: [fooservice]}); Untuk mengakses penyedia, dapatkan dari injektor komponen menggunakan parameter fromComponentInjector :
Spectator.Inject (fooservice, true)
Dengan cara yang sama Anda juga dapat mengganti penyedia tampilan komponen dengan menggunakan componentViewProviders dan componentViewProvidersMocks .
Aturan yang sama juga berlaku untuk arahan menggunakan Parameter directiveProviders dan directiveMocks .
harapkan ('. Zippy__content'). not.toexist (); harapkan ('. Zippy__content'). Tohavelength (3); harapkan ('. Zippy__content'). Tohaveid ('id'); harapkan (spectator.query ('. zippy ')). tohaveattribute (' id ',' zippy '); harapkan (spectator.query ('. zippy ')). tohaveattribute ({id:' zippy '}); harapkan (spectator.query ('. kotak centang ' ). )). TocontainProperty ({src: 'myimg.jpg'}); // Perhatikan bahwa tohaveclass menerima kelas hanya dalam urutan yang ketat. Jika urutan tidak relevan, nonaktifkan mode ketat secara manual. zippy__content '). not.tohaveclass (' class-b, class-a '); harapkan ('. ) .not.tohaveclass (['class-b', 'class-a']); harapkan ('. Zippy__content'). tohaveclass ('class', {strict: false}); harapkan ('. zippy__content'). tohaveclass ('class-a, class-b', {strict: false}); harapkan ('. zippy__content '). tohaveclass ([' class-b ',' class-a '], {strict: false}); harapkan ('. , {strict: false}); // Perhatikan bahwa tohavetext hanya mencari keberadaan string, bukan jika string persis sama. Jika Anda ingin memverifikasi bahwa string itu sama sekali sama, gunakan tohaveexactext.// Catatan bahwa jika Anda ingin memverifikasi bahwa string itu sama sekali sama, tetapi dipangkas terlebih dahulu, gunakan tohaveexacttrimmedtext.// Perhatikan bahwa jika Anda memberikan banyak nilai, Penonton memeriksa teks dari setiap elemen array terhadap indeks elemen yang ditemukan. Expect ('. Zippy__content'). Tohavetext ('konten'); harapkan ('. ']); harapkan (' ') .tocontaintext ([' konten a ',' konten b ']); harapkan ('. Konten b ']); harapkan ('. ) .tohavevalue ('value'); harapkan ('. ('.zippy__content'). tohavevalue ([['nilai a', 'value b']); harapkan ('. ) .tohavestyle ({BackgroundColor: 'rgba (0, 0, 0, 0, 0.1)'}); harapkan ('. Zippy__content'). Tohavedata ({data: 'peran', val: 'admin'}); harapkan (' .Checkbox '). Tobechecked (); harapkan ('. Kotak centang '). Tobeindeterminate (); harapkan ('. Tombol '). ToBedisabled (); harapkan (' div '). Tobeempty (); harapkan (' div ') .tobehidden (); harapkan ('elemen'). Tobeselected (); // Perhatikan bahwa karena pembatasan dalam JEST (tidak menerapkan logika tata letak aktual dalam DOM virtual), pencocokan tertentu dapat mengakibatkan positif palsu. Misalnya lebar dan tinggi diatur ke 0 Expect ('elemen'). Tobevisible (); harapkan ('input'). Tobefocused (); harapkan ('div'). Tobematchedby ('. JS-Something'); harapkan (penonton. component.object) .tobepartial ({aproperty: 'avalue'}); harapkan ('div'). Tohavedescendant ('. Child'); harapkan ('div'). TohavedescendantwithText ({selector: '.child', teks: 'teks'});Hasilkan komponen, layanan, dan arahan dengan templat spectator spec dengan sudut CLI: (saat menggunakannya sebagai default)
Komponen
Spesifikasi Default: ng g cs dashrized-name
Spec dengan host: ng g cs dashrized-name --withHost=true
Spec dengan host khusus: ng g cs dashrized-name --withCustomHost=true
Melayani:
Spesifikasi Default: ng g ss dashrized-name
Spec untuk menguji layanan data http: ng g ss dashrized-name --isDataService=true
Direktif:
ng g ds dashrized-name
Untuk menggunakan spectator sebagai koleksi default dalam proyek CLI Angular Anda, tambahkan ke angular.json Anda.
NG config cli.defaultcollection @ngneat/spectator
Skema spectator memperluas koleksi @schematics/angular default. Jika Anda ingin mengatur default untuk skema seperti menghasilkan komponen dengan file SCSS, Anda harus mengubah nama paket skema dari @schematics/angular ke @ngneat/spectator di angular.json :
"skema": {"@ngneat/spectator: spectator-component": {"style": "scss"
}
}Contoh -contoh dalam karma dari panduan pengembang pengujian dokumen sudut telah direproduksi dalam penonton dan lelucon. (Untuk kenyamanan, ini adalah versi lokal dari contoh karma.)
Versi Penonton & Jest dapat diakses di sini.
Netanel Basal | Dirk Luijk | Ben Elliott |
Terima kasih kepada orang -orang luar biasa ini (Kunci Emoji): ~~~~
I. Sinai ? ? | Valentin Buryakov ? | Ben Grynhaus ? | Martin Nuc | Lars Gyrup Brink Nielsen ? | Andrew Grekov ? | Jeroen Zwartepoorte |
Oliver Schlegel | Rex Ye ? | tchmura | Yoeri nijs | Anders Skarby | Gregor Woiwode | Alexander Sheremetev ? |
Mike | Mehmet Erim | Brett Eckert | Ismail Faizi | Maxime | Jonathan Bonnefoy | Feri Colum |
Chris Cooper | Marc Scheib | dgsmith2 | Dedwardstech ? | tamasfoldi ? | Paolo Caleffi | Toni Villena |
Itu oded | Guillaume de Jabrun | Anand Tiwary | Ales Doganoc | Zoltan | Vitalii Baziuk | clementlemarc-certua |
Yuriy Grunin | Andrey Chalkin | Steven Harris | Richard Sahrakorpi | Dominik Kremer | Mehmet Ozan Turhan | Vlad Lashko |
William Tjondrosuharto | Chaz Gatian | Pavel Korobov | Enno Lohmann | Pawel Boguslawski | Tobias Wittwer | Mateo Tibaquirá |
Proyek ini mengikuti spesifikasi semua-kontributor. Kontribusi apa pun yang baik!