import test_js
import test_menu
import test_serving_base
-import test_ui
+++ /dev/null
-QUnitSuite is a ``unittest.TestSuite`` able to run QUnit_ test suites
-within the normal unittest process, through PhantomJS_.
-
-QUnitSuite is built upon `Ben Alman`_'s work of for the interfacing
-between PhantomJS_ and the host/reporting code: the shims and the
-PhantomJS_ configuration files are those of grunt_'s ``qunit`` task.
-
-Why
----
-
-You're a Python shop or developer, you have tools and tests built
-around unittest (or compatible with unittests) and your testing
-pipeline is predicated upon that, you're doing web development of some
-sort these days (as so many are) and you'd like to do some testing of
-your web stuff.
-
-But you don't really want to redo your whole testing stack just for
-that.
-
-QUnitSuite simply grafts QUnit_-based tests, run in PhantomJS_, in
-your existing ``unittest``-based architecture.
-
-What
-----
-
-QUnitSuite currently provides a single object as part of its API:
-``qunitsuite.QUnitSuite(testfile[, timeout])``.
-
-This produces a ``unittest.TestSuite`` suitable for all the usual
-stuff (running it, and giving it to an other test suite which will run
-it, that is).
-
-``testfile`` is the HTML file bootstrapping your qunit tests, as would
-usually be accessed via a browser. It can be either a local
-(``file:``) url, or an HTTP one. As long as a regular browser can open
-and execute it, PhantomJS_ will manage.
-
-``timeout`` is a check passed to the PhantomJS_ runner: if the runner
-produces no information for longer than ``timeout`` milliseconds, the
-run will be cancelled and a test error will be generated. This
-situation usually means either your ``testfile`` is not a qunit test
-file, qunit is not running or qunit's runner was stopped (for an async
-test) and never restarted.
-
-The default value is very conservative, most tests should run
-correctly with lower timeouts (especially if all tests are
-synchronous).
-
-How
----
-
-``unittest``'s autodiscovery protocol does not directly work with test
-suites (it looks for test cases). If you want autodiscovery to work
-correctly, you will have to use the ``load_tests`` protocol::
-
- # in a testing module
- def load_tests(loader, tests, pattern):
- tests.addTest(QUnitSuite(qunit_test_path.html))
- return tests
-
-outside of that specific case, you can use a ``QUnitSuite`` as a
-standard ``TestSuite`` instance, running it, adding it to an other
-suite or passing it to a ``TestRunner``
-
-Complaints and Grievances
--------------------------
-
-Speed
-~~~~~
-
-Starting up a phantomjs instance and running a suite turns out to have
-a rather high overhead, on the order of a second on this machine
-(2.4GHz, 8GB RAM and an SSD).
-
-As each ``QUnitSuite`` currently creates its own phantomjs instance,
-it's probably a good idea to create bigger suites (put many modules &
-tests in the same QUnit html file, which doesn't preclude splitting
-them across multiple js files).
-
-Hacks
-~~~~~
-
-QUnitSuite contains a pretty big hack which may or may not cause
-problem depending on your exact setup: in case of case failure or
-error, ``unittest.TestResult`` formats the error traceback provided
-alongside the test object. This goes through Python's
-traceback-formatting code and there are no hooks there.
-
-One could expect to use a custom ``TestResult``, but for test suites
-the ``TestResult`` instance must be provided by the caller, so there
-is no direct hook onto it.
-
-This leaves three options:
-
-* Create a custom ``TestResult`` class and require that it be the one
- provided to the test suite. This requires altered work flows,
- customization of the test runner and (as far as I know) isn't
- available through Python 2.7's autodiscovery. It's the cleanest
- option but completely fails on practicality.
-
-* Create a custom ``TestResult`` which directly alters the original
- result's ``errors`` and ``failures`` attributes as they're part of
- the testrunner API. This would work but may put custom results in a
- strange state and break e.g. unittest2's ``@failfast``.
-
-* Lastly, monkeypatch the undocumented and implementation detail
- ``_exc_info_to_string`` on the provided ``result``. This is the
- route taken, at least for now.
-
-.. _QUnit: http://qunitjs.com/
-
-.. _PhantomJS: http://phantomjs.org/
-
-.. _Ben Alman: http://benalman.com/
-
-.. _grunt: http://gruntjs.com/
+++ /dev/null
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2012 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * http://benalman.com/about/license/
- */
-
-/*global phantom:true*/
-
-'use strict';
-
-var fs = require('fs');
-
-// The page .html file to load.
-var url = phantom.args[0];
-// Extra, optionally overridable stuff.
-var options = JSON.parse(phantom.args[1] || {});
-
-// Default options.
-if (!options.timeout) { options.timeout = 5000; }
-
-// Keep track of the last time a client message was sent.
-var last = new Date();
-
-// Messages are sent to the parent by appending them to the tempfile.
-var sendMessage = function(arg) {
- var args = Array.isArray(arg) ? arg : [].slice.call(arguments);
- last = new Date();
- console.log(JSON.stringify(args));
-};
-
-// This allows grunt to abort if the PhantomJS version isn't adequate.
-sendMessage('private', 'version', phantom.version);
-
-// Abort if the page doesn't send any messages for a while.
-setInterval(function() {
- if (new Date() - last > options.timeout) {
- sendMessage('fail.timeout');
- phantom.exit();
- }
-}, 100);
-
-// Create a new page.
-var page = require('webpage').create();
-
-// The client page must send its messages via alert(jsonstring).
-page.onAlert = function(args) {
- sendMessage(JSON.parse(args));
-};
-
-// Keep track if the client-side helper script already has been injected.
-var injected;
-page.onUrlChanged = function(newUrl) {
- injected = false;
- sendMessage('onUrlChanged', newUrl);
-};
-
-// Relay console logging messages.
-page.onConsoleMessage = function(message) {
- sendMessage('console', message);
-};
-
-// For debugging.
-page.onResourceRequested = function(request) {
- sendMessage('onResourceRequested', request.url);
-};
-
-page.onResourceReceived = function(request) {
- if (request.stage === 'end') {
- sendMessage('onResourceReceived', request.url);
- }
-};
-
-// Run when the page has finished loading.
-page.onLoadFinished = function(status) {
- // The window has loaded.
- sendMessage('onLoadFinished', status);
- if (status === 'success') {
- if (options.inject && !injected) {
- // Inject client-side helper script, but only if it has not yet been
- // injected.
- sendMessage('inject', options.inject);
- page.injectJs(options.inject);
- }
- } else {
- // File loading failure.
- sendMessage('fail.load', url);
- phantom.exit();
- }
-};
-
-// Actually load url.
-page.open(url);
+++ /dev/null
-Copyright (c) 2012 "Cowboy" Ben Alman
-
-Permission is hereby granted, free of charge, to any person
-obtaining a copy of this software and associated documentation
-files (the "Software"), to deal in the Software without
-restriction, including without limitation the rights to use,
-copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the
-Software is furnished to do so, subject to the following
-conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
+++ /dev/null
-{}
\ No newline at end of file
+++ /dev/null
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2012 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * http://benalman.com/about/license/
- */
-
-/*global QUnit:true, alert:true*/
-
-'use strict';
-
-// Don't re-order tests.
-QUnit.config.reorder = false;
-// Run tests serially, not in parallel.
-QUnit.config.autorun = false;
-
-// Send messages to the parent PhantomJS process via alert! Good times!!
-function sendMessage() {
- var args = [].slice.call(arguments);
- alert(JSON.stringify(args));
-}
-
-// These methods connect QUnit to PhantomJS.
-QUnit.log(function(obj) {
- // What is this I don’t even
- if (obj.message === '[object Object], undefined:undefined') { return; }
- // Parse some stuff before sending it.
- var actual = QUnit.jsDump.parse(obj.actual);
- var expected = QUnit.jsDump.parse(obj.expected);
- // Send it.
- sendMessage('qunit.log', obj.result, actual, expected, obj.message, obj.source);
-});
-
-QUnit.testStart(function(obj) {
- sendMessage('qunit.testStart', obj.name);
-});
-
-QUnit.testDone(function(obj) {
- sendMessage('qunit.testDone', obj.name, obj.failed, obj.passed, obj.total);
-});
-
-QUnit.moduleStart(function(obj) {
- sendMessage('qunit.moduleStart', obj.name);
-});
-
-QUnit.moduleDone(function(obj) {
- sendMessage('qunit.moduleDone', obj.name, obj.failed, obj.passed, obj.total);
-});
-
-QUnit.begin(function() {
- sendMessage('qunit.begin');
-});
-
-QUnit.done(function(obj) {
- sendMessage('qunit.done', obj.failed, obj.passed, obj.total, obj.runtime);
-});
-
-// PhantomJS (up to and including 1.7) uses a version of webkit so old
-// it does not have Function.prototype.bind:
-// http://code.google.com/p/phantomjs/issues/detail?id=522
-
-// Use moz polyfill:
-// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
-if (!Function.prototype.bind) {
- Function.prototype.bind = function (oThis) {
- if (typeof this !== "function") {
- // closest thing possible to the ECMAScript 5 internal IsCallable function
- throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
- }
-
- var aArgs = Array.prototype.slice.call(arguments, 1),
- fToBind = this,
- fNOP = function () {},
- fBound = function () {
- return fToBind.apply(this instanceof fNOP && oThis
- ? this
- : oThis,
- aArgs.concat(Array.prototype.slice.call(arguments)));
- };
-
- fNOP.prototype = this.prototype;
- fBound.prototype = new fNOP();
-
- return fBound;
- };
-}
+++ /dev/null
-import json
-import subprocess
-import unittest
-import os
-import time
-
-ROOT = os.path.join(os.path.dirname(__file__), 'grunt')
-
-__all__ = ['QUnitSuite']
-
-def _exc_info_to_string(err, test):
- return err
-
-class QUnitTest(unittest.TestCase):
- def __init__(self, module, name):
- self.module = module
- self.name = name
- self.failed = False
- def shortDescription(self):
- return None
- def __repr__(self):
- return '<QUnitTest %s:%s>' % (self.module, self.name)
- def __str__(self):
- return '%s: %s' % (self.module, self.name)
-
-class QUnitSuite(unittest.TestSuite):
- def __init__(self, qunitfile, timeout=5000):
- super(QUnitSuite, self).__init__()
- self.testfile = qunitfile
- self.timeout = timeout
- self._module = None
- self._test = None
-
- def run(self, result):
- try:
- subprocess.call(['phantomjs', '-v'],
- stdout=open(os.devnull, 'w'),
- stderr=subprocess.STDOUT)
- except OSError:
- test = QUnitTest('phantomjs', 'javascript tests')
- result.startTest(test)
- result.startTest(test)
- result.addSkip(test , "phantomjs command not found")
- result.stopTest(test)
- return
-
- result._exc_info_to_string = _exc_info_to_string
- try:
- self._run(result)
- finally:
- del result._exc_info_to_string
-
- def _run(self, result):
- phantom = subprocess.Popen([
- 'phantomjs',
- '--config=%s' % os.path.join(ROOT, 'phantomjs.json'),
- os.path.join(ROOT, 'bootstrap.js'), self.testfile,
- json.dumps({
- 'timeout': self.timeout,
- 'inject': os.path.join(ROOT, 'qunit-phantomjs-bridge.js')
- })
- ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-
- try:
- while True:
- line = phantom.stdout.readline()
- if line:
- if self.process(line, result):
- break
- else:
- time.sleep(0.1)
- finally:
- # If the phantomjs process hasn't quit, kill it
- if phantom.poll() is None:
- phantom.terminate()
-
- def process(self, line, result):
- try:
- args = json.loads(line)
- except ValueError: # phantomjs stderr
- if 'CoreText' not in line:
- print line
- return False
- event_name = args[0]
-
- if event_name == 'qunit.done':
- return True
- elif event_name == 'fail.load':
- self.add_error(result, "PhantomJS unable to load %s" % args[1])
- return True
- elif event_name == 'fail.timeout':
- self.add_error(result, "PhantomJS timed out, possibly due to a"
- " missing QUnit start() call")
- return True
-
- elif event_name == 'qunit.moduleStart':
- self._module = args[1].encode('utf-8') if args[1] else ''
- elif event_name == 'qunit.moduleStop':
- self._test = None
- self._module = None
- elif event_name == 'qunit.testStart':
- self._test = QUnitTest(self._module, args[1].encode('utf-8'))
- result.startTest(self._test)
- elif event_name == 'qunit.testDone':
- if not self._test.failed:
- result.addSuccess(self._test)
- result.stopTest(self._test)
- self._test = None
- elif event_name == 'qunit.log':
- if args[1]:
- return False
-
- self._test.failed = True
- result.addFailure(
- self._test, self.failure_to_str(*args[2:]))
- elif event_name == 'console':
- print args[1]
-
- return False
-
- def add_error(self, result, s):
- test = QUnitTest('phantomjs', 'startup')
- result.startTest(test)
- result.addError(test, s)
- result.stopTest(test)
-
- def failure_to_str(self, actual, expected, message, source):
- if message or actual == expected:
- formatted = str(message or '')
- else:
- formatted = "%s != %s" % (actual, expected)
-
- if source:
- formatted += '\n\n' + source
-
- return formatted
-import urllib
-import urlparse
-from openerp import sql_db, tools
-from qunitsuite.suite import QUnitSuite
+import openerp
-class WebSuite(QUnitSuite):
- def __init__(self, module):
- url = urlparse.urlunsplit([
- 'http',
- 'localhost:{port}'.format(port=tools.config['xmlrpc_port']),
- '/web/tests',
- urllib.urlencode({
- 'mod': module,
- 'source': tools.config['db_name'],
- 'supadmin': tools.config['admin_passwd'],
- 'password': 'admin',
- }),
- ''
- ])
- super(WebSuite, self).__init__(url, 50000)
+class WebSuite(openerp.tests.HttpCase):
+ def test_01_js(self):
+ self.phantom_js('/web/tests?mod=web',"","", login='admin')
-def load_tests(loader, standard_tests, _):
- standard_tests.addTest(WebSuite('web'))
- return standard_tests
+++ /dev/null
-# -*- coding: utf-8 -*-
-import os
-
-import openerp.tests
-
-class TestUi(openerp.tests.HttpCase):
- def test_01_jsfile_ui_hello(self):
- self.phantom_jsfile(os.path.join(os.path.dirname(__file__), 'test_ui_hello.js'))
- def test_02_jsfile_ui_load(self):
- self.phantom_jsfile(os.path.join(os.path.dirname(__file__), 'test_ui_load.js'))
- def test_03_js_public(self):
- self.phantom_js('/',"console.log('ok')","console")
- def test_04_js_admin(self):
- self.phantom_js('/',"console.log('ok')","openerp.client.action_manager.inner_widget.views.form", login='admin')
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+++ /dev/null
-console.log("hello from test_phantom_hello.js");
-console.log("ok");
-// For a failure use:
-//console.log("error");
-phantom.exit();
+++ /dev/null
-// Load helper
-phantom.injectJs(phantom.args[0]);
-pt = new PhantomTest();
-pt.run("/", "console.log('ok')", "console");
+++ /dev/null
-{
- 'name': 'Hello',
- 'category': 'Hidden',
- 'description':"""
-OpenERP Web example module.
-===========================
-
-""",
- 'version': '2.0',
- 'depends': [],
- 'js': ['static/*/*.js', 'static/*/js/*.js'],
- 'css': [],
- 'auto_install': False,
- 'web_preload': False,
-}
+++ /dev/null
-/*---------------------------------------------------------
- * OpenERP base_hello (Example module)
- *---------------------------------------------------------*/
-
-openerp.web_hello = function(instance) {
-
-instance.web.SearchView = instance.web.SearchView.extend({
- init:function() {
- this._super.apply(this,arguments);
- this.on('search_data', this, function(){console.log('hello');});
- }
-});
-
-};
-
-// vim:et fdc=0 fdl=0:
# -*- coding: utf-8 -*-
+import tests
""",
'version': '2.0',
- 'depends': [],
+ 'depends': ['web', 'web_kanban'],
'js': ['static/src/js/*.js'],
'css': ['static/src/css/*.css'],
'auto_install': True,
--- /dev/null
+# -*- coding: utf-8 -*-
+import test_ui
--- /dev/null
+# -*- coding: utf-8 -*-
+import os
+
+import openerp.tests
+
+class TestUi(openerp.tests.HttpCase):
+ def test_01_jsfile_ui_hello(self):
+ self.phantom_jsfile(os.path.join(os.path.dirname(__file__), 'test_ui_hello.js'))
+ def test_02_jsfile_ui_load(self):
+ self.phantom_jsfile(os.path.join(os.path.dirname(__file__), 'test_ui_load.js'))
+ def test_03_js_public(self):
+ self.phantom_js('/',"console.log('ok')","console")
+ def test_04_js_admin(self):
+ self.phantom_js('/',"console.log('ok')","openerp.client.action_manager.inner_widget.views.form", login='admin')
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
--- /dev/null
+console.log("hello from test_phantom_hello.js");
+console.log("ok");
+// For a failure use:
+//console.log("error");
+phantom.exit();
--- /dev/null
+// Load helper
+phantom.injectJs(phantom.args[0]);
+pt = new PhantomTest();
+pt.run("/", "console.log('ok')", "console");
+++ /dev/null
-# -*- coding: utf-8 -*-
-from openerp.addons.web.tests.test_js import WebSuite
-
-def load_tests(loader, standard_tests, _):
- standard_tests.addTest(WebSuite('web_tests_demo'))
- return standard_tests