Angular Test case for image map resizer - unit-testing

I'm using the image-map-resizer plugin for a responsive image mapping. And I need help in writing testcase for it.
This is my html :
<img src="...." width="100%" usemap="#usaMap" (load)="imageResized();">
This is my TS file :
declare function imageMapResize(): void;
#Component({...
})
...
imageResized() {
imageMapResize(); // Javascript function in imageMapResizer.min.js
}
and this is my test file :
it('should call imageMapResize() on imageResized method', () => {
spyOn(component, 'imageMapResize').and.callThrough();
component.imageResized();
expect(component.imageMapResize).toHaveBeenCalled()
});
And this is the error that I'm getting on compilation :
Argument of type '"imageMapResize"' is not assignable to parameter of type '"ngOnInit" | "currentMap" | "mapDataObj" | "imageResized"'.
-- spyOn(component, 'imageMapResize').and.callThrough();
I know it's pretty simple, but I recently started unit testing

The issue is that you can only spy on public methods and the error tells you that the only methods you can spy on are: '"ngOnInit" | "currentMap" | "mapDataObj" | "imageResized"'.
I see you are spying on imageMapResize and that's not a public method.
I would do this if I were you.
import { By } from '#angular/platform-browser';
....
it('should call imageResized on load', () => {
// make the CSS selector more specific if there are multiple images
const resizeSpy = spyOn(component, 'imageResized');
const img = fixture.debugElement.query(By.css('img'));
img.triggerEventHandler('load', {});
expect(resizeSpy).toHaveBeenCalled();
});

Related

How to mock a ref variable so that we can test conditions based on it?

I have an file input element which is bound to a ref variable. Based on the files uploaded, in the onChange event, the file contents are processed . Currently I am writing unit test cases to test this functionality.
App.js
export class Dashboard extends React.Component {
constructor(props) {
this.uploadFile = React.createRef();
//Constructing...
}
readFileContents() {
const files = this.uploadFile.current.files;
for (let key in files) {
if (files.hasOwnProperty(key)) {
const file = files[key];
const reader = new FileReader();
let settings;
// eslint-disable-next-line no-loop-func
reader.onload = e => {
const extension = file.name.split('.')[1];
//OnLoad Handler
};
console.log(this.uploadFile.current.files)
reader.readAsText(file); //TypeError: Failed to execute 'readAsText' on 'FileReader': parameter 1 is not of type 'Blob'.
}
}
};
render() {
return (
<div className="dashboard wrapper m-padding">
<div className="dashboard-header clearfix">
<input
type="file"
ref={this.uploadFile}
webkitdirectory="true"
mozdirectory="true"
hidden
onChange={this.readFileContents}
onClick={this.reset}
/>
<Button
outline
className="toggle-btn float-right"
onClick={this.openFileDialog}
>
Choose folder
</Button>
</div>
</div>
);
}
}
I started off with this stack overflow answer and was able to mock the FileReader.
I initially thought simulating the change event with the target files as below, will automatically reflect on the values for this.uploadFile .
const file = new Blob([fileContents], {type : 'application/json'});
var event = {"target": {"files": []}};
event.target.files.push(file);
DashboardWrapper.find('input').first().simulate('change', event);
But the behaviour wasnt as I expected and got the below error.
TypeError: Failed to execute 'readAsText' on 'FileReader': parameter 1 is not of type 'Blob'.
Following this I have been trying to change the files key in the ref variable directly from the test file, with no results and the same error.
I would like to first understand if my approach is right. If not, what is the right way to do it?
As far as I can understand, testing the actual file upload is not recommended in a unit test. After all, these inputs should be thoroughly tested already.
That being said, I had a similar requirement and I solved it like so (I am using VueJS and Jest, but the approach should be similar):
Code:
<img v-if="showLogo && currentFile" class="image-preview" :src="currentFile"/>
<input
class="file-input"
type="file"
ref="fileInput"
#change="handleFileUpload()"/>
Test:
it('should render the logo if it got uploaded', async () => {
const wrapper = shallowMount(ApplicationLogoUpload, {
store,
localVue,
propsData: {
showLogo: true
}
});
const fileInput = wrapper.find('.file-input');
const mockedGet = jest.fn();
mockedGet.mockReturnValue(['file1']);
Object.defineProperty(fileInput.element, 'files', {
get: mockedGet
});
fileInput.trigger('change');
const imagePreview = wrapper.find('.image-preview');
expect(imagePreview.attributes().src).toEqual('file1');
});
Most importantly, I mocked the uploaded files using
const mockedGet = jest.fn();
mockedGet.mockReturnValue(['file1']);
Object.defineProperty(fileInput.element, 'files', {
get: mockedGet
});
I trigger the upload by calling fileInput.trigger('change');
Afterwards, the assertion can be done: src being equal to the mocked file.

vue.js test:unit w test-utils & Jest : [vue-test-utils]: wrapper.destroy() can only be called on a Vue instance

In order to test a beforeDestroy() hook in my component, I wrote the following spec :
it("should test lifecycle when audio tag is destroyed", () => {
// jsdom doesn't support any loading or playback media operations.
// As a workaround you can add a few stubs in your test setup:
window.HTMLMediaElement.prototype.removeEventListener = () => { /* do nothing */ };
// given
const wrapper = mount(AudioPlayer, {
// attachToDocument: true,
propsData: {
autoPlay: false,
file: file,
ended,
canPlay
}
});
wrapper.vm.loaded = true; // enable buttons
const player = wrapper.find("#player");
expect(wrapper.contains('#playPauseBtn')).toBe(true);
// when
player.destroy()
// then
expect(wrapper.contains('#playPauseBtn')).toBe(false);
});
but I am getting an error, even if the destroy() is used as in the doc ...
[vue-test-utils]: wrapper.destroy() can only be called on a Vue instance
177 | expect(wrapper.contains('#playPauseBtn')).toBe(true); // OK
178 | // when
> 179 | player.destroy()
where am I wrong ?
thanks for feedback
const player = wrapper.find("#player"); returns a wrapper of DOM element, so basically HTML.
destroy() destroys a Vue component instance.
You cannot call destroy function on an "HTML element". I believe you wanted to write wrapper.destroy()

How to mock DialogService.open(...).whenClosed(...) with Jasmine?

We have some TypeScript code using the Aurelia framework and Dialog plugin that we are trying to test with Jasmine, but can't work out how to do properly.
This is the source function:
openDialog(action: string) {
this._dialogService.open({ viewModel: AddAccountWizard })
.whenClosed(result => {
if (!result.wasCancelled && result.output) {
const step = this.steps.find((i) => i.action === action);
if (step) {
step.isCompleted = true;
}
}
});
}
We can create a DialogService spy, and verify the open method easily - but we can't work out how to make the spy invoke the whenClosed method with a mocked result parameter so that we can then assert that the step is completed.
This is the current Jasmine code:
it("opens a dialog when clicking on incomplete bank account", async done => {
// arrange
arrangeMemberVerificationStatus();
await component.create(bootstrap);
const vm = component.viewModel as GettingStartedCustomElement;
dialogService.open.and.callFake(() => {
return { whenClosed: () => Promise.resolve({})};
});
// act
$(".link, .-arrow")[0].click();
// assert
expect(dialogService.open).toHaveBeenCalledWith({ viewModel: AddAccountWizard });
expect(vm.steps[2].isCompleted).toBeTruthy(); // FAILS
done();
});
We've just recently updated our DialogService and ran into the same issue, so we've made this primitive mock that suited our purposes so far. It's fairly limited and doesn't do well for mocking multiple calls with different results, but should work for your above case:
export class DialogServiceMock {
shouldCancelDialog = false;
leaveDialogOpen = false;
desiredOutput = {};
open = () => {
let result = { wasCancelled: this.shouldCancelDialog, output: this.desiredOutput };
let closedPromise = this.leaveDialogOpen ? new Promise((r) => { }) : Promise.resolve(result);
let resultPromise = Promise.resolve({ closeResult: closedPromise });
resultPromise.whenClosed = (callback) => {
return this.leaveDialogOpen ? new Promise((r) => { }) : Promise.resolve(typeof callback == "function" ? callback(result) : null);
};
return resultPromise;
};
}
This mock can be configured to test various responses, when a user cancels the dialog, and scenarios where the dialog is still open.
We haven't done e2e testing yet, so I don't know of a good way to make sure you wait until the .click() call finishes so you don't have a race condition between your expect()s and the whenClosed() logic, but I think you should be able to use the mock in the test like so:
it("opens a dialog when clicking on incomplete bank account", async done => {
// arrange
arrangeMemberVerificationStatus();
await component.create(bootstrap);
const vm = component.viewModel as GettingStartedCustomElement;
let mockDialogService = new MockDialogService();
vm.dialogService = mockDialogService; //Or however you're injecting the mock into the constructor; I don't see the code where you're doing that right now.
spyOn(mockDialogService, 'open').and.callThrough();
// act
$(".link, .-arrow")[0].click();
browser.sleep(100)//I'm guessing there's a better way to verify that it's finished with e2e testing, but the point is to make sure it finishes before we assert.
// assert
expect(mockDialogService.open).toHaveBeenCalledWith({ viewModel: AddAccountWizard });
expect(vm.steps[2].isCompleted).toBeTruthy(); // FAILS
done();
});

Yeoman testing times out anywhere but locally

I have some testing with the following structure:
describe('yeoman:subyeoman', function () {
before(function (done) {
helpers.run(path)
.inTmpDir(function (dir) {
** some file copying **
})
.withOptions({
'option': options
})
.withArguments(argumentsJson)
.on('ready', function (generator) {
generator.conflicter.force = true;
var html = "some html";
var dir = generator.destinationPath('app');
var file = generator.destinationPath('app/file.html');
if (!fs.existsSync(dir)) fs.mkDir(dir);
fs.writeFile(file, html);
})
.on('end', function () {
fse.removeSync(somePath);
done();
});
});
it('.....');
});
The on('ready') piece does its work both locally and inside the docker container, but inside the container never calls generator.run() and throws the following error:
Error: timeout of 20000ms exceeded. Ensure the done() callback is being called in this test.
I've tried changing the timeout and doing it the async way but the output its still the same.
Any help will be appreciated .
This seems to happen if you have an error in your code that doesn't bubble up with testing. Check any manipulation to the this context especially.

unit testing typescript directive template karma-jasmine, html is not defined

Recently i started unit testing on my typescript code using karma-jasmine. After creating and running test case for a service and a simple directive, i created one test case for custom directive which has one controller(which is injecting one service) and is using 4 scope variable for communicating with outside world.
It's a simple unit test case to check whether directive is rendering its template or not.
While running this unit test case, karma throws some error
09 03 2016 19:59:27.056:INFO [framework.browserify]: bundle built
09 03 2016 19:59:27.063:INFO [karma]: Karma v0.13.21 server started at http://localhost:9876/
09 03 2016 19:59:29.964:INFO [Chrome 49.0.2623 (Linux 0.0.0)]: Connected on socket /#4OCi116hP6TDqCsmAAAA with id manual-1348
LOG: Object{0: <normal-directive></normal-directive>, length: 1}
Chrome 49.0.2623 (Linux 0.0.0) normal should render the template FAILED
Error: [$injector:unpr] Unknown provider: FacadeServiceProvider <- FacadeService
http://errors.angularjs.org/1.5.0/$injector/unpr?p0=FacadeServiceProvider%20%3C-%20FacadeService
//some reference to file
TypeError: Cannot read property 'html' of undefined
at Object.<anonymous> (/tmp/5c59a59c62f48798a123b52b0468515b.browserify:476:23
While debugging it, i get to know that it is considering "normal-directive" as normal text not as a html tag.
normal-directive.spec.ts
import {appName} from '../../../../main';
import NormalController from '../../../../components/normalManager/normalList/NormalController';
describe('normalManager.normalList', () => {
let $compile:angular.ICompileService,
$rootScope:any,
template:angular.IAugmentedJQuery,
element:angular.IAugmentedJQuery,
controller:NormalController,
controllerSpy:jasmine.Spy;
beforeEach(() => {
angular.mock.module(appName);
inject((_$compile_:ng.ICompileService, _$rootScope_:ng.IRootScopeService) => {
$compile = _$compile_;
$rootScope = _$rootScope_;
});
template = angular.element('<div normal-directive></div>');
element = $compile(template)($rootScope);//getting error on this line.
controller = element.controller('normalList');
$rootScope.$digest();
});
it('should render the component', () => {
expect(element.html()).toContain('<!-- normalManager.normalList -->');
});
});
normal-directive.ts
import * as angular from 'angular';
import {normalController} from './normalController';
import {html} from './normal.html'
module normal {
"use strict";
export class normal {
public link: (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes) => void;
public template = html;
public scope = {
ngNormalVariables: '=',
ngNormalData: '=',
ngDifferentType: '=',
ngType: '='
};
public restrict: string = 'EA';
public controller = normalController;
public controllerAs: string = 'vm';
public bindToController:boolean = true;
constructor() {
normal.prototype.link = (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes) => {
};
}
public static Factory() {
var directive = () => {
return new normal();
};
directive['$inject'] = [];
return directive;
}
}
}
export default normal;
normalController.ts
import {IfcNormalFacadeService} from '../../../normal_core/services/NormalFacadeService/IfcNormalFacadeService'
export class normalController {
//Variable injection
private normalFacadeService: IfcNormalFacadeService;
public xVariableVal = null;
public yVariableVal = null;
//Scope variables
private ngNormalData = {x:null, y:null, t:null, z:null};
private ngNormalVariables = {x: [], y:[], t:[], z:[]};
private ngType = null;
private ngDifferentType = null;
constructor(normalFacadeService: IfcNormalFacadeService) {
console.log("Inside Normal controller");
this.normalFacadeService = normalFacadeService;
}
....//Remaining code
}
I am refering this repo to write test case and typescript custom directive code.
If you require more info, do tell me. If you know any concrete blog/site to learn more about karma-jasmine unit testing for typescript, please do tell me.
Thanks for giving your precious time in reading it.
Regards
Ajay
I needed to make a mock for facadeService.
Demo mocks.ts
import {IfcFacadeService} from 'filePath'
export const facadeServiceMock: IfcFacadeService {
methodName: (): any => {};
}
To use this mock, import it
Demo .html.spec.ts
import {facadeServiceMock} from 'mockPathName'
beforeEach(()=>{
angular.mock.module(appName), {FacadeService: facadeServiceMock});
})