3

I would like to setup a proper testing environment for a Sencha Touch 2 app using jasmine.

I used the first three parts of this tutorial for my first steps:

My actual problem is the following: two config entries for two of my classes (one store and one view) need to call methods / read properties of my main app object respectively the Ext.Viewport object.

Concrete:

1.) one of my stores reads a value on the main namespace of my app (MyAppName.app.backendUrl)

Ext.define('MyAppName.store.MyStore', {
    extend: 'Ext.data.Store',

    config: {
        model: 'MyAppName.model.MyModel',

        proxy: {
            type: 'ajax',
            url: MyAppName.app.backendUrl + '/data.json',
            reader: 'json'
        },

        autoLoad: true
    }
});

2.) one of my views does call a method (Ext.Viewport.getOrientation()) on Ext.Viewport:

Ext.define('MyAppName.view.LoginView', {
    extend: 'Ext.form.Panel',
    alias: "widget.loginview",
    config: {
        title: 'Login',
        items: [
            {
                xtype: 'image',
                src: Ext.Viewport.getOrientation() == 'portrait' ? '../../../img/login.png' : '../../../img/login-small.png',
                style: Ext.Viewport.getOrientation() == 'portrait' ? 'width:80px;height:80px;margin:auto' : 'width:40px;height:40px;margin:auto'
            }
        ]
    }
});

Unfortunately, this crashes, because both objects (MyAppName and Ext.Viewport) are not yet defined when these calls are made. This is only the case for the testing setup (as the tutorial outlines, there is a specific app.js just for the testing). When I run the actual app in the browser (via the 'normal' app.js), this problem does not occur.

How could this be fixed (so: how can I make sure that my views/store files are run AFTER MyAppname.app and Ext.Viewport already exist)?

Thanks a lot.

Dave Powers
  • 2,051
  • 2
  • 30
  • 34
spaudanjo
  • 764
  • 7
  • 13

3 Answers3

2

I found that running Ext.application usually opens views that you typically don't want during a unit test - otherwise you're venturing into integration testing so I avoid use of the Sencha development loader. Instead I use Karma to load the unit tests and application class files. You configure these files inside the karma.conf.js file (example below).

I've adapted the examples from the excellent unit test tutorials from Pivotal Labs. Since Karma has a built in web server you don't need Rails, Rake or pow as their 1st tutorial describes. Using Karma means you can easily integrate your unit tests with Javascript tools like IntelliJ IDEA or WebStorm as well as CI systems and cloud testing like https://saucelabs.com/ . You can also configure it to watch your code files and auto rerun unit tests when you update them. You can also use karma-istanbul to perform code coverage analysis.

Using a trick I learned here, I run a setup.js file that is configured in my karma.conf.js file to load before the unit tests. It creates a fake application object so that controllers can assign themselves to an application instance and it intentionally does not have a launch() method. It also include the SpecHelper.js code from the Pivotal Labs example.

// Create (but don't launch) the app
Ext.application({name: 'MyAppName' });

For the view unit test question, you can create a fake Ext.Viewport object and add a spyOn().andReturn() to fake the Ext.Viewport.getOrientation() method required by the view during testing. This then means your unit tests can easily cover both orientation cases. You also add a renderTo: property during testing to inspect the rendered view:

describe("when portrait orientation", function() {
   var view;
   beforeEach(function () {
     if (!Ext.Viewport) Ext.Viewport = {};      
     spyOn(Ext.Viewport, 'getOrientation').andReturn('portrait');
     view = Ext.create('MyAppName.view.LoginView', {
         renderTo: 'jasmine_content'
     }
   }

   it("should render large image", function() { 
      expect(Ext.DomQuery.select('...')).toContain('img/login.png');
   });

   it("should render 80px style", function() {
      expect(Ext.DomQuery.select('...')).toContain('80px');
   });        
});

View unit tests (explains how to use the renderTo property).

My setup.js file show below, includes code from SpecHelper.js shown here. You'll need this to use the renderTo property.

Controller unit tests covers how to connect a controller to your fake application instance.

setup.js This code steals a Karma loading trick from here but unlike their example it avoids use of the development loader.

Ext.Loader.setConfig({
    enabled: true,                  // Turn on Ext.Loader
    disableCaching: false           // Turn OFF cache BUSTING
});

// 'base' is set by Karma to be __dirname of karm.conf.js file
Ext.Loader.setPath({
    'Ext':  'base/touch/src',
    'MyAppName':   'base/app'
});

// Create (but don't launch) the app
Ext.application({name: 'MyAppName' });

Ext.require('Ext.data.Model');
afterEach(function () {
    Ext.data.Model.cache = {};      // Clear any cached models
});

var domEl;
beforeEach(function () {            // Reset the div with a new one.
    domEl = document.createElement('div');
    domEl.setAttribute('id', 'jasmine_content');
    var oldEl = document.getElementById('jasmine_content');
    if (oldEl) oldEl.parentNode.replaceChild(domEl, oldEl);
});

afterEach(function () {             // Make the test runner look pretty
    domEl.setAttribute('style', 'display:none;');
});

// Karma normally starts the tests right after all files specified in 'karma.config.js' have been loaded
// We only want the tests to start after Sencha Touch/ExtJS has bootstrapped the application.
// 1. We temporary override the '__karma__.loaded' function
// 2. When Ext is ready we call the '__karma__.loaded' function manually
var karmaLoadedFunction = window.__karma__.loaded;
window.__karma__.loaded = function () {};

Ext.onReady( function () {
    console.info("Starting Tests ...");
    window.__karma__.loaded = karmaLoadedFunction;
    window.__karma__.loaded();
});

karma.conf.js:

module.exports = function(config) {
    config.set({

        // base path that will be used to resolve all patterns (eg. files, exclude)
        basePath: '',

        // frameworks to use
        // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
        frameworks: ['jasmine'],

        // Don't use Sencha Touch dynamic loading
        files: [
            'touch/sencha-touch-all-debug.js',
            'spec/Setup.js',      // Load stubbed app - does not call app.launch()
            { pattern: 'spec/**/*.js',          watched: true,  served: true, included: true },
            { pattern: 'app/**/*.js',           watched: true,  served: true, included: false},
            // Some class are not loaded by sencha-touch-all-debug.js
            // this tell Karma web server that it's ok to serve them.
            { pattern: 'touch/src/**/*.*',      watched: false, served: true, included: false}
        ],

//        // Use Sencha Touch static 'testing' app.js
//        files: [
//            './build/testing/PT/app.js',
//            './spec/SetUp.js',
//            './spec/**/*.js'
//        ],

        // list of files to exclude
        exclude: [
        ],

        // preprocess matching files before serving them to the browser
        // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
        preprocessors: {
        },

        // test results reporter to use
        // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
        // available reporters: https://npmjs.org/browse/keyword/karma-reporter
        reporters: ['progress'],

        // web server port
        port: 9876,

        // enable / disable colors in the output (reporters and logs)
        colors: true,

        // level of logging
        // possible values: config.LOG_DISABLE/.LOG_ERROR/.LOG_WARN/.LOG_INFO/.LOG_DEBUG
        logLevel: config.LOG_INFO,

        // enable / disable watching file and executing tests whenever any file changes
        autoWatch: true,

        // start these browsers
        // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
        // Start these browsers, currently available:
        // - Chrome
        // - ChromeCanary
        // - Firefox
        // - Opera (has to be installed with `npm install karma-opera-launcher`)
        // - Safari (only Mac; has to be installed with `npm install
        // karma-safari-launcher`)
        // - PhantomJS
        // - IE (only Windows; has to be installed with `npm install
        // karma-ie-launcher`)
        //browsers: [ 'PhantomJS' ],
        browsers: ['Chrome'],

        // If browser does not capture in given timeout [ms], kill it
        captureTimeout: 60000,

        // Continuous Integration mode
        // if true, Karma captures browsers, runs the tests and exits
        singleRun: false
    });
};
Dave Powers
  • 2,051
  • 2
  • 30
  • 34
Tony O'Hagan
  • 21,638
  • 3
  • 67
  • 78
  • If you're using IntelliJ IDEA/WebStorm/PhpStorm you might want to check out these JetBrains articles on [code coverage](https://www.jetbrains.com/idea/webhelp/monitoring-code-coverage-for-javascript.html) and [Karma](http://blog.jetbrains.com/webstorm/2013/10/running-javascript-tests-with-karma-in-webstorm-7) – Tony O'Hagan Oct 09 '14 at 03:12
1

You need to spec/javascripts/support/jasmime.yml the files you need in the right order :

src_files:
    - touch/sencha-touch-all-debug.js   # Load Sencha library
    - spec/app.js                   # Load our spec Ext.Application
    - app/util/Urls.js #custom dependency
    - app/**/*.js                   # Load source files
  • Thanks a lot for the hint. I have the following order in the yml file:     - touch/sencha-touch-all-debug.js     - spec/app.js     - app/**/*.js But my problem is not an external dependency (except of the Sencha framework itself, but this should be included with the first line 'touch/sencha-touch-all-debug.js'), but the fact that things like Ext.Viewport.getOrientation() does not work when using them on the config hash level (as described in my question), because Ext is then not yet defined (it says "Cannot call method 'getOrientation' of undefined"). I don't understand why. – spaudanjo Aug 05 '13 at 12:24
  • Oh ok. I think it's because the viewport (and therefore Ext.Viewport) is only created when you call the method launch on Ext.application. Maybe you need to trigger it manually in your jasmine tests. http://docs.sencha.com/touch/2.2.1/source/Application.html#Ext-app-Application here is the source code for this, if you search for "autoCreateViewport" you'll see that now it's created at launch time. Hope that helps. – Florent Lamoureux Aug 06 '13 at 17:55
0

One way to get around the problem would be to define items from initComponent. That way it won't be called until instantiated, instead of at startup.

Ext.define('MyAppName.view.LoginView', {
    extend: 'Ext.form.Panel',
    alias: "widget.loginview",
    config: {
        title: 'Login'
    },

    initComponent: function() {
        this.items = [
            {
                xtype: 'image',
                src: Ext.Viewport.getOrientation() == 'portrait' ? '../../../img/login.png' : '../../../img/login-small.png',
                style: Ext.Viewport.getOrientation() == 'portrait' ? 'width:80px;height:80px;margin:auto' : 'width:40px;height:40px;margin:auto'
            }
        ];
        this.callParent();

    }
});

And the same thing for the store, but in the constructor

Ext.define('MyAppName.store.MyStore', {
    extend: 'Ext.data.Store',

    config: {
        model: 'MyAppName.model.MyModel',

        autoLoad: true
    },

    constructor: function(cfg) {
        this.proxy = {
            type: 'ajax',
            url: MyAppName.app.backendUrl + '/data.json',
            reader: 'json'
        };
        this.callParent(arguments)
    }
});
Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217