Skip to content

Commit

Permalink
Covers clusters screen with unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
frague committed Nov 24, 2016
1 parent 9fd1051 commit aaf6df0
Show file tree
Hide file tree
Showing 8 changed files with 304 additions and 4 deletions.
1 change: 1 addition & 0 deletions ui/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ jspm_packages
link-checker-results.txt
**/*npm-debug.log.*
app/**/*.js
testing/**/*.js
*.js.map
e2e/**/*.js
e2e/**/*.js.map
Expand Down
141 changes: 141 additions & 0 deletions ui/app/clusters/clusters.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { async, inject, TestBed, ComponentFixture } from '@angular/core/testing';
import { APP_BASE_HREF } from '@angular/common';

import { ClustersComponent } from './clusters';
import { AppModule } from '../app.module';
import { DataService, pagedResult } from '../services/data';
import { Cluster } from '../models';
import * as _ from 'lodash';

import { MockDataService, amount } from '../../testing/mock.data';
import { DOMHelper } from '../../testing/dom';

describe('Clusters Component', () => {
let fixture: ComponentFixture<ClustersComponent>;
let component: ClustersComponent;
let mockData: any;
let clustersTable: any;
let noClustersDiv: any;
let dataService: any;
let dom: DOMHelper;

beforeEach(
done => {
DataService
return TestBed.configureTestingModule({
imports: [AppModule],
providers: [
{provide: APP_BASE_HREF, useValue: '/'},
{provide: DataService, useClass: MockDataService}
]
})
.compileComponents()
.then(done);
}
);

beforeEach(() => {
fixture = TestBed.createComponent(ClustersComponent);
component = fixture.componentInstance;
dataService = TestBed.get(DataService);
dom = new DOMHelper(fixture);
});

it('allows new clusters creation', () => {
dom.click('.page-title .btn-primary')
.then(() => {
expect(dom.modal().isVisible).toBeTruthy();
dom.click('.modal-footer .btn-primary');
expect(dataService.cluster().postCreate).toHaveBeenCalledTimes(1);
})
});

describe('with existing clusters', () => {
beforeEach(done => {
return component.fetchData()
.then(() => {
fixture.detectChanges();
clustersTable = fixture.nativeElement.querySelector('div.clusters');
noClustersDiv = fixture.nativeElement.querySelector('div.no-clusters');
done();
});
});

it('displays received data in the tabular view', inject([DataService], (data: DataService) => {
expect(component.clusters.length).toEqual(amount);
expect(clustersTable).not.toBeNull();
expect(noClustersDiv).toBeNull();
}));

it('displays exactly perPage records per page', inject([DataService], (data: DataService) => {
let perPage = component.pagedData.per_page;
let clusterRows = fixture.nativeElement.querySelectorAll('.clusters div.box');
expect(perPage).toBeGreaterThan(0);
expect(clusterRows.length).toEqual(perPage);
}));

it('makes request for data upon page switching', inject([DataService], (data: DataService) => {
dom.select('ul.pagination li:nth-child(3) a')
.then(function() {
expect(this.innerText).toEqual('3');
})
.click();
fixture.detectChanges();
expect(dataService.cluster().findAll).toHaveBeenCalledWith(jasmine.objectContaining({page: 3}));
}));

it('allows cluster name editing', inject([DataService], (data: DataService) => {
let dummyClusterName = 'new cluster name';

dom.select('.clusters a .edit-icon').parent().click();
expect(dom.modal().isVisible).toBeTruthy();
fixture.detectChanges();
component.newCluster.data.name = dummyClusterName;
dom.click('.modal-footer .btn-primary');
fixture.detectChanges();
expect(dataService.cluster().postUpdate).toHaveBeenCalled();
}));

it('lets expand single cluster\'s configuration', () => {
let expandCluster = (number?: number) => {
if (number) {
dom.select('.clusters .box:nth-child(' + number + ') a .glyphicon-triangle-right').parent();
}
dom.click();
fixture.detectChanges();
};
expect(component.shownClusterId).toBeNull();
expandCluster(3);
expect(component.shownClusterId).toEqual('id1');
expandCluster(6);
expect(component.shownClusterId).toEqual('id4');
expandCluster();
expect(component.shownClusterId).toBeNull();
});
});

describe('for for single cluster configuration', () => {
let [server1, server2, server3] = [{server_id: 1}, {server_id: 2}, {server_id: 3}];
let dummyCluster = new Cluster({
data: {
configuration: {
zxy_role: [server1],
afh_role: [server1, server2],
role1: [server2, server3],
anotherRole: []
}
}
});

it('calculates size in servers properly', () => {
expect(component.getSize(dummyCluster)).toEqual(3);
});

it('displays keys sorted and in two columns', () => {
expect(component.getKeyHalfsets(dummyCluster)).toEqual([
['afh_role', 'anotherRole'], ['role1', 'zxy_role']
]);
});
});

});
2 changes: 1 addition & 1 deletion ui/app/clusters/clusters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class ClustersComponent {
}

fetchData() {
this.data.cluster().findAll({
return this.data.cluster().findAll({
filter: _.get(this.filter, 'query', {}),
page: _.get(this.pager, 'page', 1)
})
Expand Down
2 changes: 1 addition & 1 deletion ui/app/dashboard/password_reset.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('Password Reset Component', () => {
});
}));

describe('and is correct - ', () => {
describe('and is correct -', () => {
let prerequisites: any;
let updateButton: any;
let dummyPassword = 'dummy_password';
Expand Down
6 changes: 6 additions & 0 deletions ui/app/directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as jQuery from 'jquery';
import * as _ from 'lodash';
import 'bootstrap';
import globals = require('./services/globals');
import { BaseModel } from './models';

// Bootstrap modal methods
@Component({
Expand Down Expand Up @@ -135,6 +136,11 @@ export class Pager {
this.page = page;
this.onChange.emit();
}

getPageItems(allItems: BaseModel[]): BaseModel[] {
let perPage = this.pagingData.per_page;
return _.slice(allItems, (this.page - 1) * perPage, perPage);
}
}

// Long data view
Expand Down
4 changes: 2 additions & 2 deletions ui/app/templates/clusters.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<pager [pagingData]="pagedData" (onChange)="fetchData()"></pager>

<div class="row" *ngIf="clusters && clusters.length">
<div class="clusters row" *ngIf="clusters && clusters.length">
<div class="col-xs-12">

<div class="table-help">
Expand All @@ -21,7 +21,7 @@

<div
class="box"
*ngFor="let cluster of clusters"
*ngFor="let cluster of pager.getPageItems(clusters)"
[ngClass]="{open: cluster.id === shownClusterId}"
>
<div class="col-xs-10 name">
Expand Down
75 changes: 75 additions & 0 deletions ui/testing/dom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Component } from '@angular/core';
import { ComponentFixture } from '@angular/core/testing';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';


export class DOMHelper {
fixture: ComponentFixture<Component>;
element: HTMLElement;

public get isVisible(): boolean {
return !!this.element;
}

public get innerText(): string {
if (!this.element) {
throw 'No element selected!';
}
return this.element.textContent;
}

public get value(): string {
if (!this.element) {
throw 'No element selected!';
}
return (this.element as HTMLInputElement).value;
}

constructor(componentFixture: ComponentFixture<Component>) {
this.fixture = componentFixture;
}


select(css: string): DOMHelper {
this.element = this.fixture.nativeElement.querySelector(css);
return this;
}

parent(): DOMHelper {
this.element = this.element.parentElement;
return this;
}

setValue(value: string): Promise<any> {
if (this.isVisible) {
console.log((this.element as HTMLInputElement).value);
(this.element as HTMLInputElement).value = value;
this.element.dispatchEvent(new Event('input'));
this.fixture.detectChanges();
return this.fixture.whenStable();
}
throw 'No element selected';
}

click(css?: string): DOMHelper {
if (!css && !this.isVisible) {
throw 'No element to click selected';
}
if (css) {
return this.select(css).click();
}

(this.element as HTMLButtonElement).click();
return this;
}

then(f: Function): any {
let result = f.call(this);
return result ? result : this;
}

modal(): DOMHelper {
return this.select('.modal');
}
};
77 changes: 77 additions & 0 deletions ui/testing/mock.data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { BaseModel } from '../app/models';
import { pagedResult } from '../app/services/data';
import { Cluster } from '../app/models';
import * as _ from 'lodash';

function createFakeModelData(index: number): Object {
return {
id: 'id' + index,
data: {
name: 'Dummy Name ' + index,
execution_id: 'dummy_execution_id_' + index,
configuration: {
field1: 'field1',
field2: 'field2',
field3: 'field3',
field4: 'field4'
}
}
}
}

export function createFakeData(
howMany: number,
Model: {new(params?: Object): BaseModel},
perPage = 10
): pagedResult {
let result = {
page: 1,
per_page: perPage,
total: howMany
} as pagedResult;
result.items = _.map(_.range(howMany), (index) => {
return new Model(createFakeModelData(index));
});
return result;
}

export var amount = 50;

export class MockDataService {
findAll = jasmine.createSpy('findAll');
find = jasmine.createSpy('find');
postCreate = jasmine.createSpy('postCreate');
postUpdate = jasmine.createSpy('postUpdate');

mappers: any[] = [];
mapperFactory(Model: {new(params?: Object): BaseModel}, name: string): any {
if (this.mappers[name]) return this.mappers[name];
return {
findAll: this.findAll.and.returnValue(
Promise.resolve(createFakeData(amount, Model))
),
find: this.find.and.returnValue(
Promise.resolve(_.first(createFakeData(1, Model).items))
),
postUpdate: this.postUpdate.and.returnValue(
Promise.resolve()
),
postCreate: this.postCreate.and.returnValue(
Promise.resolve()
),
create: this.postCreate.and.returnValue(
Promise.resolve()
),
update: this.postCreate.and.returnValue(
Promise.resolve()
),
destroy: this.postCreate.and.returnValue(
Promise.resolve()
)
};
}

cluster() {
return this.mapperFactory(Cluster, 'cluster');
}
}

0 comments on commit aaf6df0

Please sign in to comment.