How to test AngularJS Directive with scrolling - testing

I have an infinite scroll directive that I am trying to unit test. Currently I have this:
describe('Infinite Scroll', function(){
var $compile, $scope;
beforeEach(module('nag.infiniteScroll'));
beforeEach(inject(function($injector) {
$scope = $injector.get('$rootScope');
$compile = $injector.get('$compile');
$scope.scrolled = false;
$scope.test = function() {
$scope.scrolled = true;
};
}));
var setupElement = function(scope) {
var element = $compile('<div><div id="test" style="height:50px; width: 50px;overflow: auto" nag-infinite-scroll="test()">a<br><br><br>c<br><br><br><br>e<br><br>v<br><br>f<br><br>g<br><br>m<br>f<br><br>y<br></div></div>')(scope);
scope.$digest();
return element;
}
it('should have proper initial structure', function() {
var element = setupElement($scope);
element.find('#test').scrollTop(10000);
expect($scope.scrolled).toBe(true);
});
});
However the .scrollTop(10000); does not seem to trigger anything.
Is there anyway to unit test this type of functionality (I am able to unit test other directives with similar interactions like clicking on elements)?
In case it is relative, this is the infinite scroll code:
angular.module('nag.infiniteScroll', [])
.directive('nagInfiniteScroll', [
function() {
return function(scope, elm, attr) {
var raw = elm[0];
elm.bind('scroll', function() {
if (raw.scrollTop + raw.offsetHeight >= raw.scrollHeight) {
scope.$apply(attr.nagInfiniteScroll);
}
});
};
}
]);

You have to trigger the scroll event on your element manually in your test:
element.find('#test').scrollTop(10000);
element.find('#test').triggerHandler('scroll');

Had the same issue recently. For the scrolling to work, you will need to set some dimensions on the body tag, so the window can be scrolled.
var scrollEvent = document.createEvent( 'CustomEvent' ); // MUST be 'CustomEvent'
scrollEvent.initCustomEvent( 'scroll', false, false, null );
var expectedLeft = 123;
var expectedTop = 456;
mockWindow.document.body.style.minHeight = '9000px';
mockWindow.document.body.style.minWidth = '9000px';
mockWindow.scrollTo( expectedLeft, expectedTop );
mockWindow.dispatchEvent( scrollEvent );
Unfortunately this does not work in PhantomJS.
If you are running your tests on Travis CI, you can also use Chrome by adding the following to your .travis.yml
before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
And a custom Chrome launcher in your karma config:
module.exports = function(config) {
var configuration = {
// ... your default content
// This is the new content for your travis-ci configuration test
// Custom launcher for Travis-CI
customLaunchers: {
Chrome_travis_ci: {
base: 'Chrome',
flags: ['--no-sandbox']
}
},
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: true
};
if(process.env.TRAVIS){
configuration.browsers = ['Chrome_travis_ci'];
}
config.set( configuration );
};

Related

Getting reactivity from watch in Vue.js

Trying to make a component in Vue.js, which first shows image via thumbnail, loading full image in background, and when loaded, show full image.
The thing which does not work, component does not react on change of showThumb flag in watch section. What is wrong?
Vue.component('page-image',
{
props: ['data'],
template:
'<img v-if="showThumb == true" v-bind:src="thumbSrc"></img>'+
'<img v-else v-bind:src="fullSrc"></img>',
data: function()
{
return { thumbSrc: '', fullSrc: '', showThumb: true };
},
watch:
{
data: function()
{
this.thumbSrc = data.thumbImg.url;
this.fullSrc = data.fullImg.url;
this.showThumb = true;
var imgElement = new Image();
imgElement.src = this.fullSrc;
imgElement.onload = (function()
{
this.showThumb = false; // <<-- this part is broken
} );
}
}
} );
Note: there is a reason why I do it via 2 img tags - this example is simplified.
Your onload callback will have a different scope than the surrounding watch function, so you cannot set your data property like this. Change it to an arrow function to keep scope:
imgElement.onload = () =>
{
this.showThumb = false;
};
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

What to use as css selector in protractor when writing a code

What to use in element(by.css("") for writing tests in protractor if the class am referring to is <a class="button button-large button-secondary has-shield download-btn"
var HomePage = function() {
this.centerStageButtons = element(by.css(".text-center"));
this.tryTheAngular = this.centerStageButtons.all(by.css(".button.button-large.button-primary.has-shield.has-shadow")).get(0);
this.downloadButton = this.centerStageButtons.element(by.css("..button.button-large.button-secondary.has-shield.download-btn"));
describe('angularjs.org', function() {
var homePage = new HomePage();
beforeEach(function() {
browser.get('https://angularjs.org/');
});
it('should have two buttons', function() {
//expect(homePage.tryTheAngular.isDisplayed()).toBe(true);
expect(homePage.downloadButton.isDisplayed()).toBe(false);
});
});
};
It is giving me a false positive as test is passed
This worked
this.downloadButton = element(by.css(".button.button-large.button-secondary.has-shield.download-btn"));

Karma Chai How to access DOM element

I am writing test cases using Karma Mocha.
Following is my function:
fun : function()
{
if(a == 1)
$("#test").hide();
}
We set the DOM element property based on some condition.
While writing its test:
it('fun', function (){
var a = 1;
// how do I test the DOM element.
// Is it possible to access the DOM element of the source file in the test file.
})
I tried using chai-jquery but it accesses only body and not the other elements.I guess it works on DOM elements of test file.
Can anyone please help.?
I assume you have your jQuery loaded upon testing then you would select you element with $('#test') and then do you tests.
Like so:
describe('obj.fun', function (){
before(function() {
$('<div id="test"></div>').appendTo(document.body);
});
after(function() {
$('#test').remove();
});
it('should hide the element when a is 1', function() {
var $test = $('#test');
expect( $test.is(':hidden') ).to.be.false;
obj.a = 1;
obj.fun();
expect( $test.is(':hidden') ).to.be.true;
});
});

How do we do e2e testing with polymer and selenium?

Angular has protractor to listen to lifecycle events in Angular.
eg: ptor.waitForAngular();
Is their a way to get selenium tests to wait for the various life-cycle events in polymer?
Currently we can have simple e2e tests running mocha, to do this is embed the tests directly in the html like how they do it in the source code.
You can just run your tests like they do here.
<script>
document.addEventListener('polymer-ready', function() {
mocha.run();
});
</script>
Eg to check for correct updates in a change watcher you can do this:
Polymer('x-test', {
bar: '',
ready: function() {
this.bar = 'bar';
setTimeout(function() {
this.zonk = 'zonk';
}.bind(this));
},
barChanged: function() {
chai.assert.equal(this.bar, 'bar', 'change in ready calls *Changed');
checkDone();
},
zonkChanged: function() {
chai.assert.equal(this.zonk, 'zonk', 'change calls *Changed without prototype value')
checkDone();
}
});
and for eg if you wanted to check a computer property is correct after the ready event you can do this:
<x-foo foo="mee" bar="too" count=3></x-foo>
<polymer-element name="x-foo" attributes="foo bar count">
<template>{{ fooBar }}:{{ fooBarCounted }}</template>
<script>
Polymer('x-foo', {
computed: {
fooBarCounted: 'repeat(fooBar, count)',
fooBar: "foo + '-' + bar"
},
repeat: function(str, count) {
var retval = '';
for (var i = 0; i < count; i++) {
retval += (i ? ' ' : '') + str + '(' + i + ')';
}
return retval;
},
ready: function() {
chai.assert.equal(this.shadowRoot.innerHTML, 'mee-too:mee-too(0) mee-too(1) mee-too(2)');
done();
}
})
</script>
</polymer-element>
Actually selenium (and other libs based on on it) can wait elements to appear on the page.
E.g. browser.waitForExist('#selector')
See API documentation here http://webdriver.io/api/utility/waitForExist.html
Hope it might be useful for you.

Routing/Modularity in Dojo (Single Page Application)

I worked with backbone before and was wondering if there's a similar way to achieve this kind of pattern in dojo. Where you have a router and pass one by one your view separately (like layers) and then you can add their intern functionality somewhere else (e.g inside the view) so the code is very modular and can be change/add new stuff very easily. This code is actually in jquery (and come from a previous project) and it's a "common" base pattern to develop single application page under jquery/backbone.js .
main.js
var AppRouter = Backbone.Router.extend({
routes: {
"home" : "home"},
home: function(){
if (!this.homeView) {
this.homeView= new HomeView();
}
$('#content').html(this.homeView.el);
this.homeView.selectMenuItem('home-link');
}};
utils.loadTemplate(['HomeView'], function() {
app = new AppRouter();
Backbone.history.start();
});
utils.js
loadTemplate: function(views, callback) {
var deferreds = [];
$.each(views, function(index, view) {
if (window[view]) {
deferreds.push($.get('tpl/' + view + '.html', function(data) {
window[view].prototype.template = _.template(data);
}));
} else {
alert(view + " not found");
}
});
$.when.apply(null, deferreds).done(callback);
}};
HomeView.js
window.HomeView = Backbone.View.extend({
initialize:function () {
this.render();
},
render:function () {
$(this.el).html(this.template());
return this;
}
});
And basically, you just pass the html template. This pattern can be called anywhere with this link:
<li class="active"><i class="icon-home"></i> Dashboard</li>
Or, what is the best way to implement this using dojo boilerplate.
The 'boilerplate' on this subject is a dojox.mvc app. Reference is here.
From another aspect, see my go at it a while back, ive setup an abstract for 'controller' which then builds a view in its implementation.
Abstract
Then i have an application controller, which does following on its menu.onClick
which fires loading icon,
unloads current pane (if forms are not dirty)
loads modules it needs (defined 'routes' in a main-menu-store)
setup view pane with a new, requested one
Each view is either simply a server-html page or built with a declared 'oocms' controller module. Simplest example of abstract implementation here . Each implements an unload feature and a startup feature where we would want to dereference stores or eventhooks in teardown - and in turn, assert stores gets loaded etc in the setup.
If you wish to use templates, then base your views on the dijit._TemplatedMixin
edit
Here is a simplified clarification of my oocms setup, where instead of basing it on BorderLayout, i will make it ContentPanes:
Example JSON for the menu, with a single item representing the above declared view
{
identifier: 'view',
label: 'name',
items: [
{ name: 'myForm', view: 'App.view.MyForm', extraParams: { foo: 'bar' } }
]
}
Base Application Controller in file 'AppPackagePath/Application.js'
Note, the code has not been tested but should give a good impression of how such a setup can be implemented
define(['dojo/_base/declare',
"dojo/_base/lang",
"dijit/registry",
"OoCmS/messagebus", // dependency mixin which will monitor 'notify/progress' topics'
"dojo/topic",
"dojo/data/ItemFileReadStore",
"dijit/tree/ForestStoreModel",
"dijit/Tree"
], function(declare, lang, registry, msgbus, dtopic, itemfilereadstore, djforestmodel, djtree) {
return declare("App.Application", [msgbus], {
paneContainer: NULL,
treeContainer: NULL,
menuStoreUrl: '/path/to/url-list',
_widgetInUse: undefined,
defaultPaneProps: {},
loading: false, // ismple mutex
constructor: function(args) {
lang.mixin(this, args);
if(!this.treeContainer || !this.paneContainer) {
console.error("Dont know where to place components")
}
this.defaultPaneProps = {
id: 'mainContentPane'
}
this.buildRendering();
},
buildRendering: function() {
this.menustore = new itemfilereadstore({
id: 'appMenuStore',
url:this.menuStoreUrl
});
this.menumodel = new djforestmodel({
id: 'appMenuModel',
store: this.menustore
});
this.menu = new djtree( {
model: this.menumodel,
showRoot: false,
autoExpand: true,
onClick: lang.hitch(this, this.paneRequested) // passes the item
})
// NEEDS a construct ID HERE
this.menu.placeAt(this.treeContainer)
},
paneRequested: function(item) {
if(this.loading || !item) {
console.warn("No pane to load, give me a menustore item");
return false;
}
if(!this._widgetInUse || !this._widgetInUse.isDirty()) {
dtopic.publish("notify/progress/loading");
this.loading = true;
}
if(typeof this._widgetInUse != "undefined") {
if(!this._widgetInUse.unload()) {
// bail out if widget says 'no' (isDirty)
return false;
}
this._widgetInUse.destroyRecursive();
delete this._widgetInUse;
}
var self = this,
modules = [this.menustore.getValue(item, 'view')];
require(modules, function(viewPane) {
self._widgetInUse = new viewPane(self.defaultProps);
// NEEDS a construct ID HERE
self._widgetInUse.placeAt(this.paneContainer)
self._widgetInUse.ready.then(function() {
self.paneLoaded();
})
});
return true;
},
paneLoaded: function() {
// hide ajax icons
dtopic.publish("notify/progress/done");
// assert widget has started
this._widgetInUse.startup();
this.loading = false;
}
})
})
AbstractView in file 'AppPackagePath/view/AbstractView.js':
define(["dojo/_base/declare",
"dojo/_base/Deferred",
"dojo/_base/lang",
"dijit/registry",
"dijit/layout/ContentPane"], function(declare, deferred, lang, registry, contentpane) {
return declare("App.view.AbstractView", [contentpane], {
observers: [], // all programmatic events handles should be stored for d/c on unload
parseOnLoad: false,
constructor: function(args) {
lang.mixin(this, args)
// setup ready.then resolve
this.ready = new deferred();
// once ready, create
this.ready.then(lang.hitch(this, this.postCreate));
// the above is actually not nescessary, since we could simply use onLoad in contentpane
if(typeof this.content != "undefined") {
this.set("content", this.content);
this.onLoad();
} else if(typeof 'href' == "undefined") {
console.warn("No contents nor href set in construct");
}
},
startup : function startup() {
this.inherited(arguments);
},
// if you override this, make sure to this.inherited(arguments);
onLoad: function() {
dojo.parser.parse(this.contentNode);
// alert the application, that loading is done
this.ready.resolve(null);
// and call render
this.render();
},
render: function() {
console.info('no custom rendering performed in ' + this.declaredClass)
},
isDirty: function() { return false; },
unload: function() {
dojo.forEach(this.observers, dojo.disconnect);
return true;
},
addObserver: function() {
// simple passthrough, adding the connect to handles
var handle = dojo.connect.call(dojo.window.get(dojo.doc),
arguments[0], arguments[1], arguments[2]);
this.observers.push(handle);
}
});
});
View implementation sample in file 'AppPackagePath/view/MyForm.js':
define(["dojo/_base/declare",
"dojo/_base/lang",
"App/view/AbstractView",
// the contentpane href will pull in some html
// in the html can be markup, which will be renderered when ready
// pull in requirements here
"dijit/form/Form", // markup require
"dijit/form/Button" // markup require
], function(declare, lang, baseinterface) {
return declare("App.view.MyForm", [baseinterface], {
// using an external HTML file
href: 'dojoform.html',
_isDirty : false,
isDirty: function() {
return this._isDirty;
},
render: function() {
var self = this;
this.formWidget = dijit.byId('embeddedForm') // hook up with loaded markup
// observer for children
dojo.forEach(this.formWidget._getDescendantFormWidgets(), function(widget){
if(! lang.isFunction(widget.onChange) )
console.log('unable to observe ' + widget.id);
self.addObserver(widget, 'onChange', function() {
self._isDirty = true;
});
});
//
},
// #override
unload: function() {
if(this.isDirty()) {
var go = confirm("Sure you wish to leave page before save?")
if(!go) return false;
}
return this.inherited(arguments);
}
})
});