How to write test cases for titanium with alloy - mocha.js

I am stuck while writing test cases for alloy framework as i am not getting how to use controllers and alloy files in mocha testing framework. I searched on google and few links suggested below code to moke a controller but it throws error that "TypeError: alloy.createController is not a function".
var alloy = require('../../alloy');
it('Verify row controller', function() {
console.log(JSON.stringify(alloy))
var controller = alloy.createController('login', {
name : "uniqueName",
});
// if(controller.passwordTest.value !== "uniqueName"){
// throw new ("Verify row controller FAILED");
// }
});

Currently, I can show you a (slightly modified) example of our codebase.
First of all, our controller tests are pure javascript tests. All calls to the Ti api are executed against a mock. We solely focus on the controller under test and all dependencies are mocked.
We rely on jasmine and jasmine-npm for that.
install jasmine-npm; see https://github.com/jasmine/jasmine-npm
create 'spec' folder in the root of your project (folder with tiapp.xml)
place all your test files inside this folder
the filenames of test files must end with _spec.js
run the jasmine command from within the root folder of your project
describe('authenticate controller test', function() {
var USER_NAME = "John Doe";
var fooControllerMock = {
getView: function(){}
};
var fooViewMock = {
open: function(){}
}
Ti = {
// create here a mock for all Ti* functions and properties you invoke in your controller
}
Alloy = {
CFG: {
timeout: 100
},
Globals: {
loading: {
hide: function(){}
},
networkClient: {
hasAutoLogin: function(){}
},
notifications: {
showError: function(){}
}
},
createController: function(){}
};
var controllerUnderTest; // class under test
$ = {
btnAuthenticate: {
addEventListener: function(){}
},
authenticate: {
addEventListener: function(){},
close: function(){}
},
username: {
addEventListener: function(){},
getValue: function(){}
},
password: {
addEventListener: function(){}
},
windowContainer: {
addEventListener: function(){}
}
};
L = function(s){
return s;
};
beforeEach(function () {
controllerUnderTest = require('../app/controllers/auth');
});
it('should create foo controller when authentication was succesful', function(){
spyOn(Alloy.Globals.loading, 'hide');
spyOn(Alloy, 'createController').and.returnValue(fooControllerMock);
spyOn(fooControllerMock, 'getView').and.returnValue(fooViewMock);
spyOn($.username, 'getValue').and.returnValue(USER_NAME);
spyOn($.auth, 'close');
controllerUnderTest.test._onAuthSuccess();
expect(Alloy.Globals.loading.hide).toHaveBeenCalled();
expect(Alloy.createController).toHaveBeenCalledWith('foo');
expect(fooControllerMock.getView).toHaveBeenCalled();
expect($.auth.close).toHaveBeenCalled();
});
it('should show error message when a login error has occured', function(){
spyOn(Alloy.Globals.loading, 'hide');
spyOn(Alloy.Globals.notifications, 'showError');
controllerUnderTest.test._onAuthLoginError();
expect(Alloy.Globals.loading.hide).toHaveBeenCalled();
expect(Alloy.Globals.notifications.showError).toHaveBeenCalledWith('msg.auth.failure');
});
});
Be sure to write a mock implementation(just empty) for all Ti* stuff you call from within your controller. I know this is quite cumbersome but a solution is on it's way.
Note that we already created an npm package which has a generated mock for this (based on api.jsca), together with some code with which you can mock all required dependencies, together with a set of testing best practices. However we will validate this code internally before we opensource it. I hope we can write our blog post and expose our accompanying github repo within a few weeks. Just keep an eye on tiSlack.
Controller code:
function _onAuthSuccess() {
Alloy.Globals.loading.hide();
Alloy.createController('foo').getView().open();
$.authenticate.close();
}
function _onAuthLoginError() {
Alloy.Globals.loading.hide();
Alloy.Globals.notifications.showError(L('msg.auth.failure'));
}
function _onAuthTokenValidationFailure() {
Alloy.Globals.loading.hide();
}
function _authenticate() {
var username = $.username.value;
var password = $.password.value;
if(Alloy.Globals.validationEmail.isValidEmailAddress(username)){
Alloy.Globals.loading.show(L('authenticate.msg.logging.in'), false);
} else {
Alloy.Globals.notifications.showError(L('app.error.invalid.email'));
}
}
function _onNetworkAbsent() {
Alloy.Globals.loading.hide();
Alloy.Globals.notifications.showError(L('global.no.network.connection.available'));
}
function _hideKeyboard() {
$.username.blur();
$.password.blur();
}
function _focusPassword() {
$.username.blur();
$.password.focus();
}
function _init() {
Ti.App.addEventListener('auth:success', _onAuthSuccess);
Ti.App.addEventListener('auth:loginFailed', _onAuthLoginError);
Ti.App.addEventListener('app:parseError', _onAppParseError);
Ti.App.addEventListener('network:none', _onNetworkAbsent);
$.btnAuthenticate.addEventListener('click', ..);
$.authenticate.addEventListener('close', _cleanup);
$.username.addEventListener('return', _focusPassword);
$.password.addEventListener('return', _authenticate);
$.windowContainer.addEventListener('touchstart', _hideKeyboard);
}
_init();
function _cleanup() {
Ti.API.info('Closing and destroying the auth controller');
...
$.windowContainer.removeEventListener('touchstart', _hideKeyboard);
$.destroy();
$.off();
}
module.exports = {
test: {
_onAuthSuccess: _onAuthSuccess,
_onAuthLoginError: _onAuthLoginError
}
}
and the corresponding view:
<Alloy>
<Window>
<View id="windowContainer">
<TextField id="username" />
<TextField id="password" >
<Button id="btnAuthenticate" />
</View>
</Window>
</Alloy>

Titanium internally uses Ti-Mocha to write unit-tests for all of it's components. It's a modified version of Mocha and uses test-suites, test-cases, chaining and much more to test suitable code-coverage. Give it a try!

Related

Watch Value In Vue.js 3, Equivalent In Pinia?

I have a checkbox list of domain tlds, such as com, net, io, etc. I also have a search text input, where I can drill down the list of 500 or so domains to a smaller amount. For example, if I start to type co in to my search text input, I will get back results that match co, such as co, com, com.au, etc. I am using Laravel and Vue,js 3 to achieve this with a watcher. It works beautifully. How can an achieve the same within a Pinia store?
Here is my code currently:
watch: {
'filters.searchedTlds': function(after, before) {
this.fetchsearchedTlds();
}
},
This is inside my vue component.
Next is the code to fetch searched tlds:
fetchsearchedTlds() {
self = this;
axios.get('/fetch-checked-tlds', { params: { searchedTlds: self.filters.searchedTlds } })
.then(function (response) {
self.filters.tlds = response.data.tlds;
console.log(response.data.tlds);
})
.catch(function (error) {
console.log(error);
})
.then(function () {
// always executed
});
},
And finally, the code inside my Laravel controller:
public function fetchCheckedTlds(Request $request)
{
$data['tlds'] = Tld::where('tld', 'LIKE','%'.$request->input('searchedTlds').'%')->pluck('tld');
return response()->json($data);
}
I am converting my code to use a Pinia store and I am stuck on how to convert my vue component watcher to Pinia?
Many thanks in advance.
To watch a pinia status, you may watch a computed attribute based on pinia or use watch getter
Your pinia may look like the one below.
~/store/filters.js
export const useFilters = defineStore('filters', {
state: () => {
return {
_filters: {},
};
},
getters: {
filters: state => state._filters,
},
...
}
In where you want to watch
<script setup>
import { computed, watch } from 'vue';
import { useFilters } from '~/store/filters.js';
const filters = useFilters();
// watch a computed attributes instead
const searchedTlds = computed(() => {
return filters.filters?.searchedTlds || '';
});
watch(
searchedTlds,
(newValue, oldValue) {
fetchsearchedTlds();
}
);
// or use watch getter
watch(
() => {
return filters.filters?.searchedTlds || '';
},
(newValue, oldValue) {
fetchsearchedTlds();
}
);
</script>
The first parameter of watch() can be a single ref or a getter function, or an array of getter functions, for more details, please view the Watch Source Types.

Using custom commands in sections

Having a simple custom command like this (file pressTab.js):
exports.command = function() {
this.keys(this.Keys.TAB);
return this;
};
I am defining a section in a page and try to call this command from the section:
module.exports = {
url: "...",
commands: [{
testCommandInSection: function(){
this.section.testSection.callPressTab();
return this;
}
}],
sections: {
testSection: {
selector: ".mySectionCssSelector",
commands: [{
callPressTab: function() {
this.pressTab();
return this;
}
}]
}
}
}
If I now use
myPage.testCommandInSection();
an error is thrown before starting the nightwatch queue:
Error while running testCommandInSection command: Cannot read property 'toString' of undefined
But this error does not show up, if I add a dummy parameter to the pressTab call:
callPressTab: function() {
this.pressTab("dummy");
return this;
}
and this doesn't happen, if I call this.pressTab() directly from the page, but not from the section. Why is that?
Problem with "this" object :
In custom commands, "this" usually is browser
In pageobject, it depends .
*In your case, your firstthis.section.testSection.callPressTab(); is your page object, and your second one this.pressTab(); is your section object.
If you want to call custom commands with Browser object, you should try "this.api.YourCustomCommand"
testSection: {
selector: ".mySectionCssSelector",
commands: [{
callPressTab: function() {
this.api.pressTab();
return this;
}
}]
}

How to refresh content when using CrossroadJS and HasherJS with KnockoutJS

I was following Lazy Blogger for getting started with routing in knockoutJS using crossroads and hasher and it worked correctly.
Now I needed to refresh the content using ajax for Home and Settings page every time they are clicked. So I googled but could not find some useful resources. Only these two links
Stack Overflow Here I could not understand where to place the ignoreState property and tried these. But could not make it work.
define(["jquery", "knockout", "crossroads", "hasher"], function ($, ko, crossroads, hasher) {
return new Router({
routes:
[
{ url: '', params: { page: 'product' } },
{ url: 'log', params: { page: 'log' } }
]
});
function Router(config) {
var currentRoute = this.currentRoute = ko.observable({});
ko.utils.arrayForEach(config.routes, function (route) {
crossroads.addRoute(route.url, function (requestParams) {
currentRoute(ko.utils.extend(requestParams, route.params));
});
});
activateCrossroads();
}
function activateCrossroads() {
function parseHash(newHash, oldHash) {
//crossroads.ignoreState = true; First try
crossroads.parse(newHash);
}
crossroads.normalizeFn = crossroads.NORM_AS_OBJECT;
hasher.initialized.add(parseHash);
hasher.changed.add(parseHash);
hasher.init();
$('a').on('click', function (e) {
crossroads.ignoreState = true; //Second try
});
}
});
Crossroads Official Page Here too I could not find where this property need to be set.
If you know then please point me to some url where I can get more details about this.

How to handle server side validation in Ember-Data 1.0.0

I'm using Ember 1.2.0 and the latest Ember Data Beta and wonder, how to handle server side errors (from API calls).
This question is quite similar, but it doesn't work.
At first, the becameInvalid method doesn't triggered. I'm using ember-validations (do I have to?)
My API sends an 422 status code and responses like that:
{"errors":{"name":["has already been taken"],"initial":["has already been taken"]}}
model.js
Docket.Customer = DS.Model.extend( Ember.Validations, {
name: DS.attr('string'),
initial: DS.attr('string'),
description: DS.attr('string'),
validations: {
name: {
presence: true
}
},
becameError: function() {
alert('there was an error!');
},
becameInvalid: function(errors) {
alert("Record was invalid because: " + errors);
}
});
controller.js
Docket.OrganizationCustomersController = Ember.ArrayController.extend({
actions: {
save: function () {
var customer = this.store.createRecord('customer');
customer.set('name', this.get('name'));
customer.set('initial', this.get('initial'));
customer.set('description', this.get('description'));
customer.save().then(function() {
console.log('jeah')
}, function() {
console.log('nooo')
});
}
}
});
The becameError method gets fired, but the becameInvalid method doesn't.
The second problem: even if the error is triggered, Ember.js adds the new record to the DOM. How can I prevent this behaviour?
Your errors json is ok, I think you are using the DS.RESTAdapter, and it doesn't implement the becameInvalid based in json with errors.
Just DS.ActiveModelAdapter have implemented in the moment, so I recommend you to change your adapter configuration to:
Docket.ApplicationAdapter = DS.ActiveModelAdapter;
In order to keep DS.RestAdapter, you can override its ajaxError method with the one from ActiveModelAdapter.
As for today the code, slightly adapted because some dependencies are needed, would be :
App.ApplicationAdapter = DS.RESTAdapter.extend({
// ... your own customizations,
ajaxError: function(jqXHR) {
var error = this._super(jqXHR);
if (jqXHR && jqXHR.status === 422) {
var response = Ember.$.parseJSON(jqXHR.responseText),
errors = {};
if (response.errors !== undefined) {
var jsonErrors = response.errors;
Ember.EnumerableUtils.forEach(Ember.keys(jsonErrors), function(key) {
errors[Ember.String.camelize(key)] = jsonErrors[key];
});
}
return new DS.InvalidError(errors);
} else {
return error;
}
}
});
Obviously you have a chance here to adapt to your backend specifics: HTTP code (422 is not a standard one) and format.
Source :
http://discuss.emberjs.com/t/how-to-handle-failure-to-save-on-server/3789

jQuery Mobile iScrollView error

I am using jQuery Mobile and iScrollview together,
I used iscrollView
The scrolling work fine.
Problem 1:
when I click on a input text/password field, I get an extra box overlapping the whole elements, which has the same content as the input.
same problem found in
here no solution
Problem 2:
when navigating to next page, the previous page remains behind new page and when tap the mobile screen the previous page goes off from the device. this is not happening in the webbrowsers.
Any suggestions,
code for main.js
require.config({
paths: {
jquery: '../lib/jquery',
'jquery.mobile-config': 'helper/jqm-config',
'jquery.mobile': '../lib/jquery.mobile-1.2.1.min',
underscore: '../lib/underscore-min',
backbone: '../lib/backbone-min',
templates: '../templates',
text: 'helper/text',
config: 'helper/config',
'backbone.subroute': '../lib/backbone.subroute',
'cookie': '../lib/jquery.cookie',
'maskInput': '../lib/Jquerymaskinput',
'iscroll': '../lib/iscroll',
'iscrollview': '../lib/jquery.mobile.iscrollview',
}
,
shim: {
'underscore': {
exports: "_"
},
'backbone': {
//These script dependencies should be loaded before loading
//backbone.js
deps: ['jquery', 'underscore'],
//Once loaded, use the global 'Backbone' as the
//module value.
exports: 'Backbone'
},
'jquery.mobile-config': ['jquery'],
'jquery.mobile': ['jquery', 'jquery.mobile-config'],
'backbone.subroute': ['jquery', 'underscore', 'backbone'],
//'backbone.oauth':['jquery','underscore','backbone'],
'iscroll': {
deps: ['jquery.mobile']
},
'iscrollview': {
deps: ['iscroll']
},
'config': {
exports: 'Config'
}
}
});
requirejs(['jquery', 'iscroll', 'jquery.mobile', 'iscrollview'], function($, iScroll) {
var elements = jQuery(document).find(":jqmData(iscroll)");
elements.iscrollview();
});
require([
'app'
], function(App) {
App.initialize();
});
for router
define([
'jquery',
'underscore',
'backbone',
'backbone.subroute'
], function($, _, Backbone) {
var AppRouter = Backbone.Router.extend({
routes: {
// general routes
'': 'defaultAction',
'login':'login',
'menu': 'mainMenu',
// Default
'*actions': 'defaultAction'
}
});
var initialize = function() {
$('.back').live('click', function(event) {
event.preventDefault();
window.history.back();
return false;
});
var app_router = new AppRouter;
app_router.on('route:defaultAction', function(actions) {
require(['views/home/register'], function(RegisterView) {
// We have no matching route, lets display the home page
console.log('At defaultAction');
var registerView = new RegisterView();
registerView.render();
/// this.changePage(loginView, 'slide');
});
});
app_router.on('route:login', function(actions) {
require(['views/home/login'], function(LoginView) {
// We have no matching route, lets display the home page
console.log('At defaultAction');
var loginView = new LoginView();
loginView.render();
/// this.changePage(loginView, 'slide');
});
});
app_router.on('route:mainMenu', function(actions) {
require(['views/home/menu'], function(MainMenuView) {
console.log('At mainMenu::router');
var mainMenuView = new MainMenuView();
mainMenuView.render();
// this.changePage(mainMenuView, 'slide');
});
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
I rewrite the codes but, its more or less solved my problem updated the jqm to 1.3 and jquery to 1.9.
rewrite all css files.
Navigation to next page is fine
and at least its working now.
Thanks to Omar who helped me.
gracias mi amigo

Resources