I've a custom directive which is used to upload/import a binary file. The directive listens for a change event on an <input type="file"../> element.
So now I've a test which triggers a change event, which works fine and do have code coverage apart from the body of reader.onload() fn. So, can someone guide me on what to do so that ...onload() fn is trigger via unit test.
here is the listener within directive:
element.bind('change', function(changeEvt){
var reader = new FileReader();
var result = {
filename: changeEvt.target.files[0].name
};
reader.onload = function (loadEvent) {
scope.$apply(function () {
result.data = loadEvent.target.result;
scope.fileSelected({content: result});
});
};
reader.readAsArrayBuffer(changeEvt.target.files[0]);
});
test I've so far:
describe('file import', function () {
beforeEach(inject(function ($compile) {
scope.testOnFileSelected = jasmine.createSpy('testOnFileSelected');
eventListener = jasmine.createSpy();
spyOn(windowMock, 'FileReader').and.returnValue({
addEventListener: eventListener,
readAsArrayBuffer : function() {
//return console.log(file);
}
});
elm = angular.element('<div id="testImportBtn"><my-file-select-button id="testFileSelect" caption="buttonText" file-selected="testOnFileSelected(content)" ng-disabled="testDisabled"></my-file-select-button></div>');
$compile(elm)(scope);
scope.$digest();
}));
fit('should render the button and be visible', function () {
var button = elm.find('#testFileSelect');
button .triggerHandler({type: 'change', target: {files: [{name: 'some.tar.gz'}]}});
expect(windowMock.FileReader).toHaveBeenCalled();
//expect(eventListener).toHaveBeenCalled(); Fails
//expect(scope.testOnFileSelected).toHaveBeenCalledWith({data: {}, fileName: 'some.tar.gz'}); fails
});
});
Here is a view of code coverage:
You could have add onload function in spy and call directly after fileReader invoked.
spyOn(window, 'FileReader').and.returnValue({
onload: function() {
},
readAsArrayBuffer : function() {
//return console.log(file);
}
});
and "it" block you can call onload function like,
var mockedLoadEvent = { //eventdetails }
expect(window.FileReader).toHaveBeenCalled();
window.FileReader().onload(mockedLoadEvent);
This will call your custom onload function in controller/Service.
complete code below:
describe('file import', function () {
beforeEach(inject(function ($compile) {
scope.testOnFileSelected = jasmine.createSpy('testOnFileSelected');
eventListener = jasmine.createSpy();
spyOn(window, 'FileReader').and.returnValue({
onload: function() {
},
readAsArrayBuffer : function() {
//return console.log(file);
}
});
elm = angular.element('<div id="testImportBtn"><my-file-select-button id="testFileSelect" caption="buttonText" file-selected="testOnFileSelected(content)" ng-disabled="testDisabled"></my-file-select-button></div>');
$compile(elm)(scope);
scope.$digest();
}));
it('should render the button and be visible', function () {
var button = elm.find('#testFileSelect');
button .triggerHandler({type: 'change', target: {files: [{name: 'some.tar.gz'}]}});
var mockedLoadEvent = { //eventdetails }
expect(window.FileReader).toHaveBeenCalled();
window.FileReader().onload(mockedLoadEvent);
});
});
Related
I am using ionic modal in which once i open the ionic modal and submit the button the ionic remove model is not working as well as even the animation is not working in the modal.I have tried using ionic hide instead of remove but still it is not working can anyone tell me what is the issue in ionic modal.
Modal:
'use strict';
(function () {
angular.module('main')
.service('ModalService', ModalService);
ModalService.$inject = ['$ionicModal', '$log'];
function ModalService ($ionicModal, $log) {
var init = function (tpl, $scope) {
var promise;
var a = $scope;
$scope = a;
promise = $ionicModal.fromTemplateUrl(tpl, {
scope: $scope,
animation: 'slide-in-right'
}).then(function (modal) {
$scope.modal = modal;
return modal;
});
$scope.openModal = function () {
$log.log('openModal function got clicked', $scope);
$scope.modal.show();
};
$scope.closeModal = function () {
$scope.modal.hide();
};
$scope.removeModal = function () {
$scope.modal.remove();
};
$scope.$on('$destroy', function () {
$scope.modal.remove();
});
return promise;
};
return {
init: init
};
}
})();
Controller to call the ionic remove and hide:
function modalFunction (htmlpath) {
vm.modalListType = 'category';
ModalService
.init(htmlpath, $scope)
.then(function (modal) {
$log.log('modal success');
catModal = modal;
catModal.show();
vm.search = '';
});
}
function closeModal () {
catModal.hide();
}
function removeModal () {
$log.log('removeModal got called', catModal);
catModal.remove();
}
Html file :
<div class="center-align">
<button class="button trans-but m-t-10" type="submit" ng-click="vm.addProduct()">{{'save_message' | translate}}</button>
</div>
Function which call the remove function:
function addProduct () {
$log.log('addProduct called: ', vm.product);
var data = [];
data.push({field: vm.product.type, type: 'text', name: $translate.instant('{{"producttype_message" | translate}}')});
data.push({field: vm.product.count, type: 'num', amounttype: 'Advance', name: $translate.instant('{{"ecount_message" | translate}}')});
data.push({field: vm.product.rate, type: 'num', amounttype: 'Advance', name: $translate.instant('{{"eprice_message" | translate}}')});
CommonService.validate(data).then(function () {
//vm.product.total = (vm.product.count - vm.product.deduction) * vm.product.rate;
vm.products.push(vm.product);
closeModal();
removeModal();
}, function (err) {
cordova.plugins.Keyboard.close();
CommonService.toast(err);
});
}
If you try to close the modal with the function, $scope.modal.hide();
Since if you use remove(), you will have to create the modal again.
A possible solution could be:
function closeModal () {
$scope.modal.hide();
}
Or
function closeModal () {
$scope.modal.remove();
}
This would be inside your modalFunction controller.
Say that you have spyOn(obj, 'method').and.callFake(fn);. How can you subsequently revert obj.method back to it's original function?
Use case: if you are doing a callFake in a big beforeEach and want to use the original method for one of your test cases, but the fake in the rest.
test.js
var obj = {
method: function () {
return 'original';
},
}
module.exports = obj;
testSpec.js
var obj = require('../test.js');
describe('obj.method', function () {
it('should return "original" by default', function () {
expect(obj.method()).toBe('original');
});
it('should return "fake" when faked', function () {
spyOn(obj, 'method').and.callFake(function () {
return 'fake';
});
expect(obj.method()).toBe('fake');
});
it('should return "original" when reverted after being faked', function () {
spyOn(obj, 'method').and.callFake(function () {
return 'fake';
});
// what code can be written here to get the test to pass?
expect(obj.method()).toBe('original');
});
});
I'm using Jasmine v2.5.2.
Edit: Well, I suppose you could just write:
obj.method = function () {
return 'original';
};
but that feels way too not-DRY. Is there something jasmine-based like obj.method.revertToOriginal()?
You can call callThrough() on spied method to revert it to basic function.
var obj = {
method: function() {
return 'original'
}
}
describe('obj.method', function() {
it('should return "original" by default', function() {
expect(obj.method()).toBe('original');
});
it('should return "fake" when faked', function() {
spyOn(obj, 'method').and.callFake(function() {
return 'fake';
});
expect(obj.method()).toBe('fake');
});
it('should return "original" when reverted after being faked', function() {
spyOn(obj, 'method').and.callFake(function() {
return 'fake';
});
obj.method.and.callThrough() // method for revert spy
expect(obj.method()).toBe('original');
});
});
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
Hi I have a Angular service that uses another service that loads data from the local storage on init.
angular
.module('app')
.factory('localStorage', function ($window)
{
if (!$window.localStorage)
{
// throw Error
}
return $window.localStorage;
});
angular
.module('app')
.factory('session', function (localStorage)
{
var container = JSON.parse(localStorage.getItem('sessionContainer'));
return {
getUser: getUser
};
});
Now i want to test the session service.
describe('SessionService', function ()
{
var service;
var localStorageMock;
// Load the module.
beforeEach(module('appRegistration'));
// Create mocks.
beforeEach(function ()
{
logMock = {};
localStorageMock = jasmine.createSpyObj('localStorageServiceMockSpy', ['setItem', 'getItem']);
localStorageMock.getItem.and.returnValue('{}');
module(function ($provide)
{
$provide.value('localStorage', localStorageMock);
});
inject(function (_session_)
{
service = _session_;
});
});
it('should call `getItem` on the `localStorageService` service', function ()
{
expect(localStorageMock.getItem).toHaveBeenCalledWith('sessionContainer');
});
describe('getUser method', function ()
{
it('should return an empty object when the user is not set', function ()
{
var result = service.getUser();
expect(result).toEqual({});
});
it('should return the user data', function ()
{
// localStorageMock.getItem.and.returnValue('{"user":{"some":"data"}}');
var result = service.getUser();
expect(result).toEqual({some: 'user data'});
});
});
});
As you can see in the should return the user data section.
I need a way to update the container so getUser returns the expected data.
I tried to update the getItem spy, but this does not work. The localStorageMock is already injected in the session service when i want to change the spy.
Any help?
The most simple way is to have a variable with mocked value that is common for both function scopes:
var getItemValue;
beforeEach({
localStorage: {
getItem: jasmine.createSpy().and.callFake(function () {
return getItemValue;
}),
setItem: jasmine.createSpy()
}
});
...
it('should return the user data', function ()
{
getItemValue = '{"user":{"some":"data"}}';
inject(function (_session_) {
service = _session_;
});
var result = service.getUser();
expect(result).toEqual({some: 'user data'});
});
Notice that inject should be moved from beforeEach to it for all specs (the specs that don't involve getItemValue may use shorter syntax, it('...', inject(function (session) { ... }))).
This reveals the flaw in service design that makes it test-unfriendly.
The solution is to make container lazily evaluated, so there is time to mock it after the app was bootstrapped with inject:
.factory('session', function (localStorage)
{
var containerCache;
function getUser() {
...
return this.container;
}
return {
get container() {
return (containerCache === undefined)
? (containerCache = JSON.parse(localStorage.getItem('sessionContainer')))
: containerCache;
},
getUser: getUser
};
});
Additionally, this makes possible to test session.container as well. In this case localStorageMock.getItem spy value may be redefined whenever needed.
I'm trying to start using moddule pattern in my JS code from the beginning but I have problems to understand how to perform this kind of code design.
This is a simple event:
$('#docTable').on('dblclick', 'tbody tr.line', function (event) {
$('#modal1').modal({ keyboard: false, backdrop: "static", dismiss: "modal" });
$('#modal1').modal('show');
});
I've created a couple of JS files. View.js:
var task = window.task || {};
task.View = (function () {
function View(rootElement) {
var dom = {
table: $('#docTable'),
},
callbacks = {
onsubmit: undefined
};
return {
};
}
return View;
}());
and Controller.js:
$(document).on('ready', function () {
var View = task.View(document);
});
but I have no idea how to continue and catch the dblclick event.
Could anybody please help me?
Thanks in advance.
You can create 'class' View and add event binding to its prototype. After that you can use it on multiple tables. If you want to have access to element in table you can add classes to them and find them in defineDOM method:
View.js
var task = window.task || {};
task.View = function (table) {
this.$table = $(table);
this.init();
};
task.View.prototype ={
init: function () {
this.defineDOM();
this.bindEvents();
},
defineDOM: function() {
// Search for DOM elements in context of table element
this.$button = $('.docButton', this.$table);
this.$links = $('.docLinks', this.$table);
},
bindEvents: function () {
this.$table.on('dblclick', 'tbody tr.line', this.onDblClick.bind(this))
},
onDblClick: function () {
$('#modal1').modal({ keyboard: false, backdrop: "static", dismiss: "modal" });
$('#modal1').modal('show');
}
}
Usage
$(document).on('ready', function () {
new task.View('#docTable');
});
I use this directive to render Dropzone.js in page:
angular.module('dropzone', []).directive('dropzone', function () {
return function (scope, element, attrs) {
var config, dropzone;
config = scope[attrs.dropzone];
// create a Dropzone for the element with the given options
dropzone = new Dropzone(element[0], config.options);
// bind the given event handlers
angular.forEach(config.eventHandlers, function (handler, event) {
dropzone.on(event, handler);
});
};
});
and in my controller use this code:
angular.module('app', ['dropzone']);
angular.module('app').controller('SomeCtrl', function ($scope) {
$scope.dropzoneConfig = {
'options': { // passed into the Dropzone constructor
'url': 'upload.php'
},
'eventHandlers': {
'sending': function (file, xhr, formData) {
},
'success': function (file, response) {
}
}
};
});
In Dropzone to show files already stored on server use mockFile and this.emit for this. Now how to get this and run emit function?
I solved problem with this:
'use strict';
angular.module('dropzone', []).directive('dropzone', function ($timeout) {
return {
restrict:'AE',
require: 'ngModel',
link:function (scope, element, attrs, ngModel) {
var init = function () {
var config, dropzone;
config = scope[attrs.dropzone];
// create a Dropzone for the element with the given options
dropzone = new Dropzone(element[0], config.options);
// Display existing files on server
if(ngModel.$viewValue !=='' && ngModel.$viewValue !==undefined){
var mockFile = {name: ngModel.$viewValue, size: 1234};
dropzone.emit("addedfile", mockFile);
dropzone.createThumbnailFromUrl(mockFile, "uploads/" + ngModel.$viewValue);
dropzone.emit("complete", mockFile);
}
// Form submit rest dropzone event handler
scope.$on('dropzone.removeallfile', function() {
dropzone.removeAllFiles();
});
// bind the given event handlers
angular.forEach(config.eventHandlers, function (handler, event) {
dropzone.on(event, handler);
});
};
$timeout(init, 0);
}
}
});
and in controller:
$scope.dropzoneConfig = {
options: { // passed into the Dropzone constructor
url: '/api/uploadimage',
paramName: "file", // The name that will be used to transfer the file
maxFilesize: .5, // MB
acceptedFiles: 'image/jpeg,image/png,image/gif',
maxFiles: 1,
},
'eventHandlers': {
'removedfile': function (file,response) {
$http({
method : "POST",
url : "/api/uploadimage/"+$scope.customer.avatar_url
}).then(function mySucces(response) {
$scope.deleteMessage = response.data;
$scope.customer.avatar_url='';
});
},
'success': function (file, response) {
$scope.customer.avatar_url = response.filename;
}
}
};
and in your Html:
<div ng-if="customer.avatar_url!==undefined" ng-model="customer.avatar_url" dropzone="dropzoneConfig" class="dropzone"></div>