summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am9
-rw-r--r--configure.ac1
-rw-r--r--test/Makefile.am20
-rw-r--r--test/smoke-tests/webview/first_page.html54
-rw-r--r--test/tools/eos-run-test/sanitycheck.js3
-rw-r--r--test/webhelper/smoke-tests/webview.js (renamed from test/smoke-tests/webview.js)63
-rw-r--r--test/webhelper/testTranslate.js72
-rw-r--r--test/webhelper/testWebActions.js140
-rw-r--r--tools/Makefile.am.inc3
-rw-r--r--tools/eos-run-test.in150
-rw-r--r--webhelper/webhelper.js78
12 files changed, 489 insertions, 105 deletions
diff --git a/.gitignore b/.gitignore
index 73220f3..5f1f7aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ endless/eosresource.c
endless/eosresource-private.h
data/eos-wikipedia-domain.gresource
wikipedia/config.js
+tools/eos-run-test
*.py[cod]
diff --git a/Makefile.am b/Makefile.am
index feee832..b15a47d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -146,6 +146,10 @@ gjsoverridedir = ${datadir}/gjs-1.0/overrides
dist_gjsoverride_DATA = \
overrides/Endless.js
+# # # SDK TOOLS # # #
+
+include $(top_srcdir)/tools/Makefile.am.inc
+
# # # INSTALLED M4 MACROS # # #
m4dir = ${datadir}/aclocal
@@ -156,8 +160,3 @@ m4_DATA = \
# # # TESTS # # #
include $(top_srcdir)/test/Makefile.am
-
-# Run tests when running 'make check'
-TESTS = test/run-tests
-LOG_COMPILER = gtester
-AM_LOG_FLAGS = -k --verbose
diff --git a/configure.ac b/configure.ac
index eb450a6..a74e3e0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -210,6 +210,7 @@ AC_CONFIG_FILES([
docs/reference/endless/version.xml
$EOS_SDK_API_NAME.pc
])
+AC_CONFIG_FILES([tools/eos-run-test], [chmod +x tools/eos-run-test])
AC_CONFIG_HEADERS([config.h]) dnl Header with system-dependent #defines
# Do the output
AC_OUTPUT
diff --git a/test/Makefile.am b/test/Makefile.am
index b4217c1..4124c52 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -24,3 +24,23 @@ test_run_tests_LDADD = $(TEST_LIBS)
test_smoke_tests_hello_SOURCES = test/smoke-tests/hello.c
test_smoke_tests_hello_CPPFLAGS = $(TEST_FLAGS)
test_smoke_tests_hello_LDADD = $(TEST_LIBS)
+
+javascript_tests = \
+ test/tools/eos-run-test/sanitycheck.js \
+ test/webhelper/testTranslate.js \
+ test/webhelper/testWebActions.js \
+ $(NULL)
+EXTRA_DIST += $(javascript_tests)
+
+# Run tests when running 'make check'
+TESTS = \
+ test/run-tests \
+ $(javascript_tests) \
+ $(NULL)
+TEST_EXTENSIONS = .js
+JS_LOG_COMPILER = tools/eos-run-test
+AM_JS_LOG_FLAGS = \
+ --include-path=$(top_srcdir)/webhelper \
+ $(NULL)
+LOG_COMPILER = gtester
+AM_LOG_FLAGS = -k --verbose
diff --git a/test/smoke-tests/webview/first_page.html b/test/smoke-tests/webview/first_page.html
deleted file mode 100644
index 33a6a8f..0000000
--- a/test/smoke-tests/webview/first_page.html
+++ /dev/null
@@ -1,54 +0,0 @@
-<html>
-
-<head>
-<title>First page</title>
-
-<style>
-p, form {
- width: 50%;
- padding: 1em;
- background: #FFFFFF;
-}
-
-body {
- background: #EEEEEE;
-}
-</style>
-</head>
-
-<body>
-
-<h1>First page</h1>
-
-<p>
-<a href="endless://moveToPage?name=page2">Move to page 2</a>
-</p>
-
-<p>
-<a href="endless://showMessageFromParameter?msg=This%20is%20a%20message%20from%20the%20URL%20parameter">Show message from parameter in this URL</a>
-</p>
-
-<form action="endless://showMessageFromParameter">
-<input name="msg" value="I am in a form!"/>
-<input type="submit" value="Show message using a form"/>
-</form>
-
-<p>
-<input id="inputformessage" value="my ID is inputformessage"/>
-<a href="endless://showMessageFromInputField?id=inputformessage">Show message using the &lt;input&gt;'s ID</a>
-</p>
-
-<p>
-<a href="http://wikipedia.org">Regular link to a Web site</a>
-</p>
-
-<p>
-<a href="endless://addStars?id=starspan">I want stars!</a> <span id="starspan" />
-</p>
-
-<p>
-This is text that will be translated: <span name="translatable">Hello, world!</span>
-</p>
-
-</body>
-</html> \ No newline at end of file
diff --git a/test/tools/eos-run-test/sanitycheck.js b/test/tools/eos-run-test/sanitycheck.js
new file mode 100644
index 0000000..4bb3a80
--- /dev/null
+++ b/test/tools/eos-run-test/sanitycheck.js
@@ -0,0 +1,3 @@
+function testNothing() {
+ assertEquals(2, 2);
+} \ No newline at end of file
diff --git a/test/smoke-tests/webview.js b/test/webhelper/smoke-tests/webview.js
index 98226f3..fe2a655 100644
--- a/test/smoke-tests/webview.js
+++ b/test/webhelper/smoke-tests/webview.js
@@ -11,10 +11,61 @@ const WebHelper = imports.webhelper;
const TEST_APPLICATION_ID = 'com.endlessm.example.test-webview';
+const TEST_HTML = '\
+<html> \
+<head> \
+<title>First page</title> \
+<style> \
+p, form { \
+ width: 50%; \
+ padding: 1em; \
+ background: #FFFFFF; \
+} \
+body { \
+ background: #EEEEEE; \
+} \
+</style> \
+</head> \
+\
+<body> \
+<h1>First page</h1> \
+\
+<p><a href="endless://moveToPage?name=page2">Move to page 2</a></p> \
+\
+<p><a \
+href="endless://showMessageFromParameter?msg=This%20is%20a%20message%20from%20the%20URL%20parameter">Show \
+message from parameter in this URL</a></p> \
+\
+<form action="endless://showMessageFromParameter"> \
+<input name="msg" value="I am in a form!"/> \
+<input type="submit" value="Show message using a form"/> \
+</form> \
+\
+<p> \
+<input id="inputformessage" value="my ID is inputformessage"/> \
+<a href="endless://showMessageFromInputField?id=inputformessage">Show message \
+using the &lt;input&gt;\'s ID</a> \
+</p> \
+\
+<p><a href="http://wikipedia.org">Regular link to a Web site</a></p> \
+\
+<p><a href="endless://addStars?id=starspan">I want \
+stars!</a> <span id="starspan"/></p> \
+\
+<p>This is text that will be italicized: <span name="translatable">Hello, \
+world!</span></p> \
+\
+</body> \
+</html>';
+
const TestApplication = new Lang.Class({
Name: 'TestApplication',
Extends: WebHelper.Application,
+ _translationFunction: function(string) {
+ return string.italics();
+ },
+
/* *** ACTIONS AVAILABLE FROM THE WEB VIEW *** */
_webActions: {
@@ -64,21 +115,17 @@ const TestApplication = new Lang.Class({
this.parent();
this._webview = new WebKit.WebView();
-
- let cwd = GLib.get_current_dir();
- let target = cwd + '/test/smoke-tests/webview/first_page.html';
- this._webview.load_uri(GLib.filename_to_uri(target, null));
-
- this._webview.connect('notify::load-status',
+ this._webview.load_string(TEST_HTML, 'text/html', 'UTF-8', 'file://');
+ this._webview.connect('notify::load-status',
Lang.bind(this, function (web_view, status) {
if (web_view.load_status == WebKit.LoadStatus.FINISHED) {
// now we translate to Brazilian Portuguese
- this._translateHTML (web_view, 'pt_BR');
+ this.translate_html(web_view);
}
}));
this._webview.connect('navigation-policy-decision-requested',
- Lang.bind(this, this._onNavigationRequested));
+ Lang.bind(this, this.web_actions_handler));
this._page1 = new Gtk.ScrolledWindow();
this._page1.add(this._webview);
diff --git a/test/webhelper/testTranslate.js b/test/webhelper/testTranslate.js
new file mode 100644
index 0000000..10e389c
--- /dev/null
+++ b/test/webhelper/testTranslate.js
@@ -0,0 +1,72 @@
+const Endless = imports.gi.Endless;
+const GLib = imports.gi.GLib;
+const Gtk = imports.gi.Gtk;
+const Lang = imports.lang;
+const WebHelper = imports.webhelper;
+const WebKit = imports.gi.WebKit;
+
+const TestClass = new Lang.Class({
+ Name: 'testclass',
+ Extends: WebHelper.Application,
+
+ vfunc_startup: function() {
+ this.parent();
+ this.webview = new WebKit.WebView();
+ let string = '<html><body><p name="translatable">Translate Me</p></body></html>';
+ this.webview.load_string(string, 'text/html', 'UTF-8', 'file://');
+ this.win = new Endless.Window({
+ application: this
+ });
+ this.scrolled = new Gtk.ScrolledWindow();
+ this.scrolled.add(this.webview);
+ this.win.page_manager.add(this.scrolled);
+
+ this.webview.connect('notify::load-status', Lang.bind(this, function() {
+ if(this.webview.load_status === WebKit.LoadStatus.FINISHED) {
+ this.translate_html(this.webview);
+ this.quit();
+ }
+ }));
+
+ // Add an upper bound on how long the app runs, in case app.quit() does
+ // not get called
+ GLib.timeout_add_seconds(GLib.PRIORITY_HIGH, 5, Lang.bind(this, function() {
+ this.quit();
+ }));
+ }
+});
+
+let app;
+
+function setUp() {
+ // Generate a unique ID for each app instance that we test
+ let id_string = 'com.endlessm.webhelper.test' + GLib.get_real_time();
+ app = new TestClass({
+ application_id: id_string
+ });
+}
+
+function testStringIsTranslated() {
+ let translationFunctionWasCalled = false;
+ let translationFunctionCalledWithString;
+ app._translationFunction = function(s) {
+ translationFunctionWasCalled = true;
+ translationFunctionCalledWithString = s;
+ return s;
+ };
+ app.run([]);
+ assertTrue(translationFunctionWasCalled);
+ assertEquals('Translate Me', translationFunctionCalledWithString);
+}
+
+// The following two tests are commented out because GJS cannot catch exceptions
+// across FFI interfaces (e.g. in GObject callbacks.)
+
+// function testMissingTranslationFunctionIsHandled() {
+// assertRaises(app.run([]));
+// }
+
+// function testBadTranslationFunctionIsHandled() {
+// app._translationFunction = "I am not a function";
+// assertRaises(app.run([]));
+// } \ No newline at end of file
diff --git a/test/webhelper/testWebActions.js b/test/webhelper/testWebActions.js
new file mode 100644
index 0000000..585ab00
--- /dev/null
+++ b/test/webhelper/testWebActions.js
@@ -0,0 +1,140 @@
+const Endless = imports.gi.Endless;
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
+const Gtk = imports.gi.Gtk;
+const Lang = imports.lang;
+const WebHelper = imports.webhelper;
+const WebKit = imports.gi.WebKit;
+
+const TestClass = new Lang.Class({
+ Name: 'testclass',
+ Extends: WebHelper.Application,
+
+ vfunc_startup: function() {
+ this.parent();
+ this.webview = new WebKit.WebView();
+ this.webview.connect('navigation-policy-decision-requested',
+ Lang.bind(this, this.web_actions_handler));
+ let string = ('<html><head><meta http-equiv="refresh" content="0;url=' +
+ this.webActionToTest + '"></head><body></body></html>');
+ this.webview.load_string(string, 'text/html', 'UTF-8', 'file://');
+ this.win = new Endless.Window({
+ application: this
+ });
+ this.scrolled = new Gtk.ScrolledWindow();
+ this.scrolled.add(this.webview);
+ this.win.page_manager.add(this.scrolled);
+
+ // Add an upper bound on how long the app runs, in case app.quit() does
+ // not get called
+ GLib.timeout_add_seconds(GLib.PRIORITY_HIGH, 5, Lang.bind(this, function() {
+ this.quit();
+ }));
+ }
+});
+
+let app;
+
+function setUp() {
+ // Generate a unique ID for each app instance that we test
+ let id_string = 'com.endlessm.webhelper.test' + GLib.get_real_time();
+ app = new TestClass({
+ application_id: id_string
+ });
+}
+
+function testWebActionIsCalled() {
+ let actionWasCalled = false;
+ app._webActions = {
+ quitApplication: function() {
+ actionWasCalled = true;
+ app.quit();
+ }
+ };
+ app.webActionToTest = 'endless://quitApplication';
+ app.run([]);
+ assertTrue(actionWasCalled);
+}
+
+function testWebActionIsCalledWithParameter() {
+ let actionParameter;
+ app._webActions = {
+ getParameterAndQuit: function(dict) {
+ actionParameter = dict['param'];
+ app.quit();
+ }
+ };
+ app.webActionToTest = 'endless://getParameterAndQuit?param=value';
+ app.run([]);
+ assertEquals('value', actionParameter);
+}
+
+function testWebActionIsCalledWithManyParameters() {
+ let firstParameter, secondParameter, thirdParameter;
+ app._webActions = {
+ getParametersAndQuit: function(dict) {
+ firstParameter = dict['first'];
+ secondParameter = dict['second'];
+ thirdParameter = dict['third'];
+ app.quit();
+ }
+ };
+ app.webActionToTest = 'endless://getParametersAndQuit?first=thefirst&second=thesecond&third=thethird';
+ app.run([]);
+ assertEquals('thefirst', firstParameter);
+ assertEquals('thesecond', secondParameter);
+ assertEquals('thethird', thirdParameter);
+}
+
+function testParameterNameIsUriDecoded() {
+ let expectedParameter = 'päräm💩';
+ let parameterWasFound = false;
+ app._webActions = {
+ getUriDecodedParameterAndQuit: function(dict) {
+ parameterWasFound = (expectedParameter in dict);
+ app.quit();
+ }
+ };
+ app.webActionToTest = 'endless://getUriDecodedParameterAndQuit?p%C3%A4r%C3%A4m%F0%9F%92%A9=value';
+ app.run([]);
+ assertTrue(parameterWasFound);
+}
+
+function testParameterValueIsUriDecoded() {
+ let expectedValue = 'válué💩';
+ let actualValue;
+ app._webActions = {
+ getUriDecodedValueAndQuit: function(dict) {
+ actualValue = dict['param'];
+ app.quit();
+ }
+ };
+ app.webActionToTest = 'endless://getUriDecodedValueAndQuit?param=v%C3%A1lu%C3%A9%F0%9F%92%A9';
+ app.run([]);
+ assertEquals(expectedValue, actualValue);
+}
+
+// This is commented out because GJS cannot catch exceptions across FFI
+// interfaces (e.g. in GObject callbacks.)
+// function testBadActionIsNotCalled() {
+// app.webActionToTest = 'endless://nonexistentAction?param=value';
+// assertRaises(function() { app.run([]); });
+// }
+
+function testWebActionIsCalledWithBlankParameter() {
+ let parameterWasFound = false;
+ let parameterValue;
+ app._webActions = {
+ getBlankValueAndQuit: function(dict) {
+ parameterWasFound = ('param' in dict);
+ if(parameterWasFound)
+ parameterValue = dict['param'];
+ app.quit();
+ }
+ };
+ app.webActionToTest = 'endless://getBlankValueAndQuit?param=';
+ app.run([]);
+ assertTrue(parameterWasFound);
+ assertNotUndefined(parameterValue);
+ assertEquals('', parameterValue);
+}
diff --git a/tools/Makefile.am.inc b/tools/Makefile.am.inc
new file mode 100644
index 0000000..1a790ce
--- /dev/null
+++ b/tools/Makefile.am.inc
@@ -0,0 +1,3 @@
+# Copyright 2013 Endless Mobile, Inc.
+
+bin_SCRIPTS = tools/eos-run-test
diff --git a/tools/eos-run-test.in b/tools/eos-run-test.in
new file mode 100644
index 0000000..888e53b
--- /dev/null
+++ b/tools/eos-run-test.in
@@ -0,0 +1,150 @@
+#!/usr/bin/gjs
+const Format = imports.format;
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
+const JsUnit = imports.jsUnit;
+const Lang = imports.lang;
+const System = imports.system;
+
+String.prototype.format = Format.format;
+// Monkeypatch System.programInvocationName if not in this version of GJS
+if(!('programInvocationName' in System))
+ System.programInvocationName = 'eos-run-test';
+
+const PACKAGE_VERSION = '@PACKAGE_VERSION@';
+const JS_EXTENSION = '.js';
+
+/**
+ * usage:
+ *
+ * Print command-line help message.
+ */
+function usage() {
+ print('Run a jsUnit unit test.\n');
+ print('Usage: %s [options] TEST_FILES\n'.format(
+ System.programInvocationName));
+ print('Options:');
+ print(' --help This help message');
+ print(' --version Print version and exit');
+ throw(0); // FIXME System.exit(0);
+}
+
+/**
+ * version:
+ *
+ * Print command-line version output.
+ */
+function version() {
+ print('%s %s - Discover unit tests in a source tree'.format(
+ System.programInvocationName, PACKAGE_VERSION));
+ throw(0); // FIXME System.exit(0);
+}
+
+if(ARGV.indexOf('--help') != -1)
+ usage();
+if(ARGV.indexOf('--version') != -1)
+ version();
+if(ARGV.length < 1)
+ usage();
+
+// Import JsUnit into global namespace
+if(!('assertEquals' in this))
+ Lang.copyPublicProperties(JsUnit, this);
+
+function printError(error) {
+ const SYNTAX_ERROR = '[object Error]';
+ print(" " + error.message);
+
+ if(error.stackTrace) {
+ let stackTrace = error.stackTrace.split('\n');
+ stackTrace.forEach(function(line) {
+ if(line.length > 0){
+ let prefix = ' --';
+ if (error.type == SYNTAX_ERROR)
+ prefix += '> ';
+
+ print(prefix + line);
+ }
+ });
+ }
+}
+
+function executeTest(testModule, test) {
+ let result = { name: test };
+ print("Running:", test);
+ if(testModule.setUp) {
+ testModule.setUp();
+ }
+ let startTime = GLib.get_real_time();
+ try {
+ testModule[test]();
+ } catch(e) {
+ print(" ERROR! >>>>>", test, "<<<<<");
+
+ result.error = {
+ type: Object.toString(e),
+ message: e.message || e.jsUnitMessage,
+ stackTrace: e.stack || e.stackTrace
+ };
+ printError(result.error);
+ print('\n');
+ } finally {
+ if(testModule.tearDown) {
+ testModule.tearDown();
+ }
+ }
+
+ result.time = GLib.get_real_time() - startTime;
+ return result;
+}
+
+function executeTestsForFile(file) {
+ let testFile = file.get_basename();
+ let testModuleName = testFile.slice(0, testFile.indexOf('.js'));
+ print('File:', testFile);
+ let oldSearchPath = imports.searchPath;
+ imports.searchPath.unshift(file.get_parent().get_path());
+ let testModule = imports[testModuleName];
+ imports.searchPath = oldSearchPath;
+
+ let results = [];
+ Object.keys(testModule).forEach(function(key) {
+ if(key.indexOf('test') === 0) {
+ results.push(executeTest(testModule, key));
+ }
+ });
+
+ return results;
+}
+
+function getTotalsFromResults(results) {
+ let testsRunCount = 0;
+ let testsFailedCount = 0;
+ let testsPassedCount = 0;
+
+ results.forEach(function(result) {
+ if ('error' in result) {
+ testsFailedCount++;
+ }
+ testsRunCount++;
+ });
+
+ testsPassedCount = testsRunCount - testsFailedCount;
+ return {
+ testsRunCount: testsRunCount,
+ testsFailedCount: testsFailedCount,
+ testsPassedCount: testsPassedCount
+ };
+}
+
+let fileToTest = Gio.File.new_for_path(ARGV[0]);
+let results = executeTestsForFile(fileToTest);
+let totals = getTotalsFromResults(results);
+let totalsString = "Ran %d tests (%d Passed, %d Failed)".format(
+ totals.testsRunCount, totals.testsPassedCount, totals.testsFailedCount);
+print(totalsString);
+
+if (totals.testsFailedCount > 0){
+ printerr("Test(s) did not complete successfully");
+ throw(1); // FIXME System.exit(1);
+}
diff --git a/webhelper/webhelper.js b/webhelper/webhelper.js
index 8b78931..ccce9e7 100644
--- a/webhelper/webhelper.js
+++ b/webhelper/webhelper.js
@@ -16,47 +16,46 @@ const Application = new Lang.Class({
_webActions: { },
-// This callback does the translation from URI to action
-// this._webview.connect('navigation-policy-decision-requested',
-// Lang.bind(this, this._webHelper.onNavigationRequested));
+ // This callback does the translation from URI to action
+ // webview.connect('navigation-policy-decision-requested',
+ // Lang.bind(this, this.web_actions_handler));
- _onNavigationRequested : function(web_view, frame, request,
- navigation_action, policy_decision,
- user_data) {
+ web_actions_handler: function(webview, frame, request, action, policy_decision) {
let uri = request.get_uri();
- if(uri.indexOf(EOS_URI_SCHEME) == 0) {
- // get the name and parameters for the desired function
- let f_call = uri.substring(EOS_URI_SCHEME.length, uri.length).split('?');
- var function_name = f_call[0];
- var parameters = {};
-
- if(f_call[1]) {
- // there are parameters
- let params = f_call[1].split('&');
- params.forEach(function(entry) {
- let param = entry.split('=');
-
- if(param.length == 2) {
- param[0] = decodeURIComponent(param[0]);
- param[1] = decodeURIComponent(param[1]);
- // and now we add it...
- parameters[param[0]] = param[1];
- }
- });
- }
-
- if(this._webActions[function_name])
- Lang.bind(this, this._webActions[function_name])(parameters);
- else
- print('Unknown function '+function_name);
-
- policy_decision.ignore();
- return true;
- } else {
+ if(uri.indexOf(EOS_URI_SCHEME) !== 0) {
// this is a regular URL, just navigate there
return false;
}
+
+ // get the name and parameters for the desired function
+ let f_call = uri.substring(EOS_URI_SCHEME.length, uri.length).split('?');
+ var function_name = f_call[0];
+ var parameters = {};
+
+ if(f_call[1]) {
+ // there are parameters
+ let params = f_call[1].split('&');
+ params.forEach(function(entry) {
+ let param = entry.split('=');
+
+ if(param.length == 2) {
+ param[0] = decodeURIComponent(param[0]);
+ param[1] = decodeURIComponent(param[1]);
+ // and now we add it...
+ parameters[param[0]] = param[1];
+ }
+ });
+ }
+
+ if(this._webActions[function_name])
+ Lang.bind(this, this._webActions[function_name])(parameters);
+ else
+ throw new Error("Undefined WebHelper action '%s'. Did you add it " +
+ "to your app's _webActions object?".format(function_name));
+
+ policy_decision.ignore();
+ return true;
},
// convenience functions
@@ -69,7 +68,7 @@ const Application = new Lang.Class({
return dom.get_element_by_id(id);
},
- _translateHTML: function(webview, lang) {
+ translate_html: function(webview) {
let dom = webview.get_dom_document();
// WebKit.DOMNodeList
@@ -79,8 +78,11 @@ const Application = new Lang.Class({
// WebKit.DOMNode
let element = translatable.item(i);
- // TODO here is where we would do the translation
- element.inner_html = '<i>' + element.inner_text + '</i>';
+ // Translate the text
+ if(typeof this._translationFunction !== 'function')
+ throw new Error("No suitable translation function was found. " +
+ "Did you forget to set '_translationFunction' on your app?");
+ element.inner_html = this._translationFunction(element.inner_text);
}
}
});