TurnerJS - An angular components and directives testing kit, makes it super easy to create tests which are maintainable, reusable and readable.
The presentation provides high level view of the framework, the API and the basic use of it.
20. it('should default the hours and minutes as 0 when duration is not defined', inject(function ($compile) {
scope.item = {
};
element = angular.element('<boost-duration-picker item="item"></boost-duration-picker>');
element = $compile(element)(scope);
scope.$digest();
expect(element.find('[data-hook="duration-picker-hours"]').val()).toBe('0');
expect(element.find('[data-hook="duration-picker-minutes"]').val()).toBe('0');
}));
it('should update the duration of the model', inject(function ($compile) {
scope.item = {
duration: 20
};
element = angular.element('<boost-duration-picker item="item"></boost-duration-picker>');
element = $compile(element)(scope);
scope.$digest();
element.find('[data-hook="duration-picker-hours"]').val('1').change();
element.find('[data-hook="duration-picker-minutes"]').val('35').change();
scope.$digest();
expect(scope.item.duration).toBe(95);
}));
21. it('should validate that at least one of the inputs should be 1 and above and the other is 0 and above', inject(function
($compile) {
scope.item = {};
element = angular.element('<boost-duration-picker item="item"></boost-duration-picker>');
element = $compile(element)(scope);
scope.$digest();
let hoursInput = element.find('[data-hook="duration-picker-hours"]');
let minutesInput = element.find('[data-hook="duration-picker-minutes"]');
expect(hoursInput.attr('ng-min')).toBe('1');
expect(minutesInput.attr('ng-min')).toBe('1');
hoursInput.val('2').change();
scope.$digest();
expect(minutesInput.attr('ng-min')).toBe('0');
minutesInput.val('2').change();
scope.$digest();
expect(hoursInput.attr('ng-min')).toBe('0');
}));
22. It’s nice, but…
• A mix of selectors and expectations
• Complex setup for each test
• Not reusable in the testing of component that wraps
the duration picker
23. Introducing
TurnerComponentDriver
• Component oriented testing kit
• Removes the hassle of having to
deal with compilation and rendering
of the templates
• Supports hierarchies
• Easy to use selectors and
appending/detaching from body
Made by Carmel
25. API
• renderFromTemplate – renders a template with attributes which
are loaded to the scope and initializes the driver and its child
drivers
• findByDataHook/ All – selectors by data-hook attribute
• connectToBody, disconnectFromBody – connects the template to
the html body – useful when testing height and position
• defineChild/defineChildren – define a child driver (single or array)
in order to reuse drivers
Documentation: https://github.com/wix-private/turnerjs/blob/master/README.md#base-driver-methods-members
27. 'use strict';
describe('Directive: durationPicker', () => {
let scope, element;
beforeEach(function () {
module('schedulerOwnerAppInternal');
});
beforeEach(inject(function ($rootScope) {
scope = $rootScope.$new();
}));
it('should split duration into hours and minutes', inject(function ($compile) {
scope.item = {
duration: 65
};
element = angular.element(
'<boost-duration-picker item="item"></boost-duration-picker>');
element = $compile(element)(scope);
scope.$digest();
expect(element.find('[data-hook="duration-picker-hours"]').val()).toBe('1');
expect(element.find('[data-hook="duration-picker-minutes"]').val()).toBe('5');
}));
let driver: DurationPickDriver;
beforeEach(function () {
module('schedulerOwnerAppInternal');
driver = new DurationPickDriver();
});
it('should split duration into hours and minutes', (() => {
let item = {
duration: 65
};
driver.render(item);
expect(driver.hours).toBe('1');
expect(driver.minutes).toBe('5');
}));
28. it('should default the hours and minutes as 0 when duration is not defined', inject(function ($compile) {
scope.item = {
};
element = angular.element('<boost-duration-picker item="item"></boost-duration-picker>');
element = $compile(element)(scope);
scope.$digest();
expect(element.find('[data-hook="duration-picker-hours"]').val()).toBe('0');
expect(element.find('[data-hook="duration-picker-minutes"]').val()).toBe('0');
}));
it('should update the duration of the model', inject(function ($compile) {
scope.item = {
duration: 20
};
element = angular.element('<boost-duration-picker item="item"></boost-duration-picker>');
element = $compile(element)(scope);
scope.$digest();
element.find('[data-hook="duration-picker-hours"]').val('1').change();
element.find('[data-hook="duration-picker-minutes"]').val('35').change();
scope.$digest();
expect(scope.item.duration).toBe(95);
}));
it('should default the hours and minutes as 0 when duration is not defined', () => {
let item = {
};
driver.render(item);
expect(driver.hours).toBe('0');
expect(driver.minutes).toBe('0');
});
it('should update the duration of the model', () => {
let item = {
duration: 20
};
driver.render(item);
driver.minutes = '35';
driver.hours = '1';
expect(item.duration).toBe(95);
});
29. it('should validate that at least one of the inputs should be 1 and above and the other is 0 and above', inject(function
($compile) {
scope.item = {};
element = angular.element('<boost-duration-picker item="item"></boost-duration-picker>');
element = $compile(element)(scope);
scope.$digest();
let hoursInput = element.find('[data-hook="duration-picker-hours"]');
let minutesInput = element.find('[data-hook="duration-picker-minutes"]');
expect(hoursInput.attr('ng-min')).toBe('1');
expect(minutesInput.attr('ng-min')).toBe('1');
hoursInput.val('2').change();
scope.$digest();
expect(minutesInput.attr('ng-min')).toBe('0');
minutesInput.val('2').change();
scope.$digest();
expect(hoursInput.attr('ng-min')).toBe('0');
}));
it('should validate that at least one of the inputs should be 1 and above and the other is 0
and above', () => {
let item = {};
driver.render(item);
expect(driver.getMinimumHours()).toBe('1');
expect(driver.getMinimumMinutes()).toBe('1');
driver.hours = '2';
expect(driver.getMinimumMinutes()).toBe('0');
driver.minutes = '2';
expect(driver.getMinimumHours()).toBe('0');
});
30. class ClassFormDriver extends TurnerComponentDriver {
private durationPickerDriver: DurationPickDriver;
constructor() {
super();
this.durationPickerDriver = this.defineChild(new DurationPickDriver());
}
render() {
this.renderFromTemplate('<class-form-page></class-form-page>')
}
fillForm() {
this.durationPickerDriver.hours = '1';
...
}
}
And it can be reused!
31. Known Limitations
• CSS classes – ng-hide is not really
hiding the element
○ ng-if works just fine
• Overlapping elements are clickable
• Page navigation - one can check the
route but not really run a flow
○ Selenium is still needed…
Know your limits
33. Summary
• An AngularJS page should be considered as a set of components
○ Each component should be tested separately
• Component testing level is used in order to test the components
via the DOM rather than controller functions
• By utilizing component testing, one can minimize the amount of
tests needed in the higher levels which are heavy, slow and hard
to maintain
○ The ‘cone’ is maintained
TurnerJS is making it easy, maintainable and quick