diff options
-rw-r--r-- | test/webhelper/smoke-tests/webview.js | 90 | ||||
-rw-r--r-- | test/webhelper/testTranslate.js | 39 | ||||
-rw-r--r-- | test/webhelper/testWebActions.js | 87 | ||||
-rw-r--r-- | webhelper/webhelper.js | 126 |
4 files changed, 228 insertions, 114 deletions
diff --git a/test/webhelper/smoke-tests/webview.js b/test/webhelper/smoke-tests/webview.js index 133d4ce..a3b91e5 100644 --- a/test/webhelper/smoke-tests/webview.js +++ b/test/webhelper/smoke-tests/webview.js @@ -60,52 +60,46 @@ const TestApplication = new Lang.Class({ Name: 'TestApplication', Extends: WebHelper.Application, - _translationFunction: function(string) { - return string.italics(); + /* *** ACTIONS AVAILABLE FROM THE WEB VIEW *** */ + + /* dict['name'] is the name of the page to move to */ + moveToPage: function(dict) { + this._pm.visible_page_name = dict['name']; }, - /* *** ACTIONS AVAILABLE FROM THE WEB VIEW *** */ + /* dict['msg'] is the message to display */ + showMessageFromParameter: function(dict) { + let dialog = new Gtk.MessageDialog({ + buttons: Gtk.ButtonsType.CLOSE, + message_type: Gtk.MessageType.INFO, + text: dict['msg'] + }); + dialog.set_transient_for(this._window); + dialog.run(); + dialog.destroy(); + }, + + /* dict['id'] is the ID of the input field to use */ + showMessageFromInputField: function(dict) { + let input = this._getElementById(this._webview, dict['id']); + + // WebKitDOMHTMLInputElement + let msg = input.get_value(); - _webActions: { - /* dict['name'] is the name of the page to move to */ - moveToPage: function(dict) { - this._pm.visible_page_name = dict['name']; - }, - - /* dict['msg'] is the message to display */ - showMessageFromParameter: function(dict) { - let dialog = new Gtk.MessageDialog({ - buttons: Gtk.ButtonsType.CLOSE, - message_type: Gtk.MessageType.INFO, - text: dict['msg'] - }); - dialog.set_transient_for(this._window); - dialog.run(); - dialog.destroy(); - }, - - /* dict['id'] is the ID of the input field to use */ - showMessageFromInputField: function(dict) { - let input = this._getElementById(this._webview, dict['id']); - - // WebKitDOMHTMLInputElement - let msg = input.get_value(); - - let dialog = new Gtk.MessageDialog({ - buttons: Gtk.ButtonsType.CLOSE, - message_type: Gtk.MessageType.INFO, - text: msg - }); - dialog.set_transient_for(this._window); - dialog.run(); - dialog.destroy(); - }, - - /* dict['id'] is the ID of the element to use */ - addStars: function(dict) { - let e = this._getElementById(this._webview, dict['id']); - e.inner_text += '★ '; - } + let dialog = new Gtk.MessageDialog({ + buttons: Gtk.ButtonsType.CLOSE, + message_type: Gtk.MessageType.INFO, + text: msg + }); + dialog.set_transient_for(this._window); + dialog.run(); + dialog.destroy(); + }, + + /* dict['id'] is the ID of the element to use */ + addStars: function(dict) { + let e = this._getElementById(this._webview, dict['id']); + e.inner_text += '★ '; }, /* *************************** */ @@ -113,6 +107,16 @@ const TestApplication = new Lang.Class({ vfunc_startup: function() { this.parent(); + this.set_translation_function(function(string) { + return string.italics(); + }); + this.define_web_actions({ + moveToPage: this.moveToPage, + showMessageFromParameter: this.showMessageFromParameter, + showMessageFromInputField: this.showMessageFromInputField, + addStars: this.addStars + }); + this._webview = new WebKit.WebView(); this._webview.load_string(TEST_HTML, 'text/html', 'UTF-8', 'file://'); this._webview.connect('notify::load-status', diff --git a/test/webhelper/testTranslate.js b/test/webhelper/testTranslate.js index 10e389c..651ddf5 100644 --- a/test/webhelper/testTranslate.js +++ b/test/webhelper/testTranslate.js @@ -49,24 +49,47 @@ function setUp() { function testStringIsTranslated() { let translationFunctionWasCalled = false; let translationFunctionCalledWithString; - app._translationFunction = function(s) { + app.set_translation_function(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 +// The following test is commented out because GJS cannot catch exceptions // across FFI interfaces (e.g. in GObject callbacks.) // function testMissingTranslationFunctionIsHandled() { -// assertRaises(app.run([])); +// assertRaises(function() { +// app.run([]); +// }); // } -// function testBadTranslationFunctionIsHandled() { -// app._translationFunction = "I am not a function"; -// assertRaises(app.run([])); -// }
\ No newline at end of file +function testSetBadTranslationFunction() { + assertRaises(function() { + app.set_translation_function("I am not a function"); + }); +} + +function testGetSetTranslationFunction() { + let translationFunction = function(string) { + return string; + }; + app.set_translation_function(translationFunction); + let actualTranslationFunction = app.get_translation_function(); + assertEquals(translationFunction, actualTranslationFunction); +} + +function testTranslationFunctionIsNullByDefault() { + assertNull(app.get_translation_function()); +} + +function testGetSetNullTranslationFunction() { + app.set_translation_function(function (s) { return s; }); + assertNotNull(app.get_translation_function()); + app.set_translation_function(null); + assertNull(app.get_translation_function()); +} diff --git a/test/webhelper/testWebActions.js b/test/webhelper/testWebActions.js index 585ab00..72ea6ba 100644 --- a/test/webhelper/testWebActions.js +++ b/test/webhelper/testWebActions.js @@ -45,12 +45,10 @@ function setUp() { function testWebActionIsCalled() { let actionWasCalled = false; - app._webActions = { - quitApplication: function() { - actionWasCalled = true; - app.quit(); - } - }; + app.define_web_action('quitApplication', function() { + actionWasCalled = true; + app.quit(); + }); app.webActionToTest = 'endless://quitApplication'; app.run([]); assertTrue(actionWasCalled); @@ -58,12 +56,10 @@ function testWebActionIsCalled() { function testWebActionIsCalledWithParameter() { let actionParameter; - app._webActions = { - getParameterAndQuit: function(dict) { - actionParameter = dict['param']; - app.quit(); - } - }; + app.define_web_action('getParameterAndQuit', function(dict) { + actionParameter = dict['param']; + app.quit(); + }); app.webActionToTest = 'endless://getParameterAndQuit?param=value'; app.run([]); assertEquals('value', actionParameter); @@ -71,14 +67,12 @@ function testWebActionIsCalledWithParameter() { function testWebActionIsCalledWithManyParameters() { let firstParameter, secondParameter, thirdParameter; - app._webActions = { - getParametersAndQuit: function(dict) { - firstParameter = dict['first']; - secondParameter = dict['second']; - thirdParameter = dict['third']; - app.quit(); - } - }; + app.define_web_action('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); @@ -89,12 +83,10 @@ function testWebActionIsCalledWithManyParameters() { function testParameterNameIsUriDecoded() { let expectedParameter = 'päräm💩'; let parameterWasFound = false; - app._webActions = { - getUriDecodedParameterAndQuit: function(dict) { - parameterWasFound = (expectedParameter in dict); - app.quit(); - } - }; + app.define_web_action('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); @@ -103,12 +95,10 @@ function testParameterNameIsUriDecoded() { function testParameterValueIsUriDecoded() { let expectedValue = 'válué💩'; let actualValue; - app._webActions = { - getUriDecodedValueAndQuit: function(dict) { - actualValue = dict['param']; - app.quit(); - } - }; + app.define_web_action('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); @@ -124,17 +114,34 @@ function testParameterValueIsUriDecoded() { function testWebActionIsCalledWithBlankParameter() { let parameterWasFound = false; let parameterValue; - app._webActions = { - getBlankValueAndQuit: function(dict) { - parameterWasFound = ('param' in dict); - if(parameterWasFound) - parameterValue = dict['param']; - app.quit(); - } - }; + app.define_web_action('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); } + +function testDefineMultipleActionsOverride() { + let actionWasCalled = false; + app.define_web_actions({ + quitApplication: function() { + actionWasCalled = true; + app.quit(); + } + }); + app.webActionToTest = 'endless://quitApplication'; + app.run([]); + assertTrue(actionWasCalled); +} + +function testDefineBadAction() { + assertRaises(function() { + app.define_web_action('badAction', 'not a function'); + }); +} diff --git a/webhelper/webhelper.js b/webhelper/webhelper.js index 1992973..dc1f842 100644 --- a/webhelper/webhelper.js +++ b/webhelper/webhelper.js @@ -1,6 +1,7 @@ const Endless = imports.gi.Endless; const GLib = imports.gi.GLib; const Gtk = imports.gi.Gtk; +const Lang = imports.lang; const WebKit = imports.gi.WebKit; const EOS_URI_SCHEME = 'endless://'; @@ -17,8 +18,8 @@ const EOS_URI_SCHEME = 'endless://'; * * WebHelper solves that problem by detecting when the web page navigates to a * custom action URI. - * The custom URI corresponds to a function that you define in - * <Application._webActions>, and you can pass parameters to the + * The custom URI corresponds to a function that you define using + * <Application.define_web_action()>, and you can pass parameters to the * function. * * Another often-encountered problem is localizing text through the same API as @@ -42,18 +43,70 @@ const Application = new Lang.Class({ Name: 'WebApplication', Extends: Endless.Application, + _init: function(props) { + this._webActions = {}; + this._translationFunction = null; + this.parent(props); + }, + /** - * Property: _webActions - * Set of actions that may be invoked from a WebView + * Method: define_web_action + * Define an action that may be invoked from a WebView + * + * Parameters: + * - name: a string, which must be a valid URI location. + * - implementation: a function (see Callback Parameters below.) * - * Declare them as a function that takes a dict as a parameter, and use - * links with the format "endless://actionName?parameter=value" + * Callback Parameters: + * - dict: object containing properties corresponding to the query + * parameters that the web action was called with * - * *Note* This API will likely disappear and be replaced by a method - * add_web_action(). - * That is the reason for the underscore starting the name. + * Sets up an action that may be invoked from an HTML document inside a + * WebView, or from the in-browser Javascript environment inside a WebView. + * If you set up an action "setVolume" as follows: + * > app.define_web_action('setVolume', function(dict) { ... }); + * Then you can invoke the action inside the HTML document, e.g. as the + * target of a link, as follows: + * > <a href="endless://setVolume?volume=11">This one goes to 11</a> + * Or from the in-browser Javascript, by navigating to the action URI, as + * follows: + * > window.location = 'endless://setVolume?volume=11' + * + * In both cases, the function would then be called with the _dict_ + * parameter equal to + * > { "volume": "11" } + * + * If an action called _name_ is already defined, the new _implementation_ + * replaces the old one. */ - _webActions: { }, + define_web_action: function(name, implementation) { + if(typeof implementation != 'function') { + throw new Error('The implementation of a web action must be a ' + + 'function.'); + } + this._webActions[name] = implementation; + }, + + /** + * Method: define_web_actions + * Define several web actions at once + * + * Parameters: + * dict - an object, with web action names as property names, and their + * implementations as values + * + * Convenience method to define more than one web action at once. + * Calls <define_web_action()> on each property of _dict_. + * + * *Note* This API is Javascript-only. It will not be implemented in C. + */ + define_web_actions: function(dict) { + for(let key in dict) { + if(dict.hasOwnProperty(key)) { + this.define_web_action(key, dict[key]); + } + } + }, /** * Callback: web_actions_handler @@ -101,8 +154,9 @@ const Application = new Lang.Class({ 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)); + throw new Error("Undefined WebHelper action '%s'. Did you define " + + "it with WebHelper.Application.define_web_action()?".format( + function_name)); policy_decision.ignore(); return true; @@ -119,18 +173,43 @@ const Application = new Lang.Class({ }, /** - * Property: _translationFunction - * Function which transforms all translatable text + * Method: set_translation_function + * Define function which transforms all translatable text + * + * Parameters: + * translation_function - a function, or null * * When you plan to use the <translate_html()> function to translate text in * your web application, set this property to the translation function. * The function must take one parameter, a string, and also return a - * string -- for example, gettext(). + * string. + * The canonical example is gettext(). + * + * Pass null for _translation_function_ to unset the translation function. * - * *Note* This API will likely disappear and be replaced by a read-write - * translation_function property. - * That is the reason for the underscore starting the name. + * If this function has not been called, or has most recently been called + * with null, then it is illegal to call <translate_html()>. */ + set_translation_function: function(translation_function) { + if(translation_function !== null + && typeof translation_function !== 'function') { + throw new Error('The translation function must be a function, or ' + + 'null.'); + } + this._translationFunction = translation_function; + }, + + /** + * Method: get_translation_function + * Retrieve the currently set translation function + * + * Returns: + * the translation function previously set with + * <set_translation_function()>, or null if none is currently set. + */ + get_translation_function: function() { + return this._translationFunction; + }, /** * Method: translate_html @@ -152,12 +231,12 @@ const Application = new Lang.Class({ * * Then after the web view has finished loading, call <translate_html()> on * it, and each of the marked strings will be passed to the function that - * you specify using the <_translationFunction> property. - * The return value from <_translationFunction> will be substituted into the - * HTML instead of the original string. + * you specify using <set_translation_function()> property. + * The return value from the translation function will be substituted into + * the HTML instead of the original string. * * Example: - * > app._translationFunction = _; + * > app.set_translation_function(_); * > webview.connect('notify::load-status', * > Lang.bind(app, function(webview) { * > if(webview.load_status == WebKit.LoadStatus.FINISHED) @@ -177,7 +256,8 @@ const Application = new Lang.Class({ // 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?"); + "Did you forget to call 'set_translation_function()' on " + + "your app?"); element.inner_html = this._translationFunction(element.inner_text); } } |