From 29b69c9c974b40727b95e1f96b16058a34ab9a54 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 17 Dec 2013 14:40:47 -0800 Subject: Move to JS tests to Jasmine [endlessm/eos-sdk#444] --- test/Makefile.am.inc | 7 +- test/smoke-tests/webhelper/webview.js | 160 +++++++++++++ test/webhelper/smoke-tests/webview.js | 160 ------------- test/webhelper/testTranslate.js | 117 +++++---- test/webhelper/testWebActions.js | 215 ++++++++--------- test/wikipedia/models/testArticleModel.js | 172 +++++++------ test/wikipedia/models/testCategoryModel.js | 248 +++++++++++++------ test/wikipedia/models/testDomainWikiModel.js | 346 ++++++++++++++++----------- 8 files changed, 814 insertions(+), 611 deletions(-) create mode 100644 test/smoke-tests/webhelper/webview.js delete mode 100644 test/webhelper/smoke-tests/webview.js (limited to 'test') diff --git a/test/Makefile.am.inc b/test/Makefile.am.inc index 826824f..08dd5e8 100644 --- a/test/Makefile.am.inc +++ b/test/Makefile.am.inc @@ -14,7 +14,6 @@ include test/demos/Makefile.am.inc include test/smoke-tests/Makefile.am.inc javascript_tests = \ - test/tools/eos-run-test/sanitycheck.js \ test/tools/eos-application-manifest/testInit.js \ test/webhelper/testTranslate.js \ test/webhelper/testWebActions.js \ @@ -29,8 +28,7 @@ TESTS = \ test/endless/run-tests \ $(javascript_tests) \ $(NULL) -TEST_EXTENSIONS = .js -JS_LOG_COMPILER = tools/eos-run-test +TEST_EXTENSIONS = AM_JS_LOG_FLAGS = \ --include-path=$(top_srcdir)/webhelper \ --include-path=$(top_srcdir) \ @@ -38,6 +36,9 @@ AM_JS_LOG_FLAGS = \ LOG_COMPILER = gtester AM_LOG_FLAGS = -k --verbose +JASMINE_SUBMODULE_PATH = $(top_srcdir)/test/jasmine +include test/jasmine/Makefile-jasmine.am.inc + # Use locally built versions of Endless-0.gir and libraries; this may need to be # changed to AM_TESTS_ENVIRONMENT in a future version of Automake # Set XDG_CONFIG_HOME so as to avoid cluttering the user's actual config diff --git a/test/smoke-tests/webhelper/webview.js b/test/smoke-tests/webhelper/webview.js new file mode 100644 index 0000000..a3b91e5 --- /dev/null +++ b/test/smoke-tests/webhelper/webview.js @@ -0,0 +1,160 @@ +//Copyright 2013 Endless Mobile, Inc. + +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 TEST_APPLICATION_ID = 'com.endlessm.example.test-webview'; + +const TEST_HTML = '\ + \ + \ +First page \ + \ + \ +\ + \ +

First page

\ +\ +

Move to page 2

\ +\ +

Show \ +message from parameter in this URL

\ +\ +
\ + \ + \ +
\ +\ +

\ + \ +Show message \ +using the <input>\'s ID \ +

\ +\ +

Regular link to a Web site

\ +\ +

I want \ +stars!

\ +\ +

This is text that will be italicized: Hello, \ +world!

\ +\ + \ +'; + +const TestApplication = new Lang.Class({ + Name: 'TestApplication', + Extends: WebHelper.Application, + + /* *** 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']; + }, + + /* 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 += '★ '; + }, + + /* *************************** */ + + 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', + Lang.bind(this, function (webview) { + if (webview.load_status == WebKit.LoadStatus.FINISHED) + this.translate_html(webview); + })); + + this._webview.connect('navigation-policy-decision-requested', + Lang.bind(this, this.web_actions_handler)); + + this._page1 = new Gtk.ScrolledWindow(); + this._page1.add(this._webview); + + this._page2 = new Gtk.Grid(); + let back_button = new Gtk.Button({ label:"Go back to page 1" }); + back_button.connect('clicked', Lang.bind(this, function() { + this._pm.visible_page_name = 'page1'; + })); + this._page2.add(back_button); + + this._window = new Endless.Window({ + application: this, + border_width: 16 + }); + + this._pm = this._window.page_manager; + this._pm.set_transition_type(Endless.PageManagerTransitionType.CROSSFADE); + this._pm.add(this._page1, { name: 'page1' }); + this._pm.add(this._page2, { name: 'page2' }); + this._pm.visible_page = this._page1; + + this._window.show_all(); + } +}); + +let app = new TestApplication({ + application_id: TEST_APPLICATION_ID, + flags: 0 +}); +app.run(ARGV); diff --git a/test/webhelper/smoke-tests/webview.js b/test/webhelper/smoke-tests/webview.js deleted file mode 100644 index a3b91e5..0000000 --- a/test/webhelper/smoke-tests/webview.js +++ /dev/null @@ -1,160 +0,0 @@ -//Copyright 2013 Endless Mobile, Inc. - -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 TEST_APPLICATION_ID = 'com.endlessm.example.test-webview'; - -const TEST_HTML = '\ - \ - \ -First page \ - \ - \ -\ - \ -

First page

\ -\ -

Move to page 2

\ -\ -

Show \ -message from parameter in this URL

\ -\ -
\ - \ - \ -
\ -\ -

\ - \ -Show message \ -using the <input>\'s ID \ -

\ -\ -

Regular link to a Web site

\ -\ -

I want \ -stars!

\ -\ -

This is text that will be italicized: Hello, \ -world!

\ -\ - \ -'; - -const TestApplication = new Lang.Class({ - Name: 'TestApplication', - Extends: WebHelper.Application, - - /* *** 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']; - }, - - /* 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 += '★ '; - }, - - /* *************************** */ - - 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', - Lang.bind(this, function (webview) { - if (webview.load_status == WebKit.LoadStatus.FINISHED) - this.translate_html(webview); - })); - - this._webview.connect('navigation-policy-decision-requested', - Lang.bind(this, this.web_actions_handler)); - - this._page1 = new Gtk.ScrolledWindow(); - this._page1.add(this._webview); - - this._page2 = new Gtk.Grid(); - let back_button = new Gtk.Button({ label:"Go back to page 1" }); - back_button.connect('clicked', Lang.bind(this, function() { - this._pm.visible_page_name = 'page1'; - })); - this._page2.add(back_button); - - this._window = new Endless.Window({ - application: this, - border_width: 16 - }); - - this._pm = this._window.page_manager; - this._pm.set_transition_type(Endless.PageManagerTransitionType.CROSSFADE); - this._pm.add(this._page1, { name: 'page1' }); - this._pm.add(this._page2, { name: 'page2' }); - this._pm.visible_page = this._page1; - - this._window.show_all(); - } -}); - -let app = new TestApplication({ - application_id: TEST_APPLICATION_ID, - flags: 0 -}); -app.run(ARGV); diff --git a/test/webhelper/testTranslate.js b/test/webhelper/testTranslate.js index 009efaf..f4d4f68 100644 --- a/test/webhelper/testTranslate.js +++ b/test/webhelper/testTranslate.js @@ -5,14 +5,20 @@ const Lang = imports.lang; const WebHelper = imports.webhelper; const WebKit = imports.gi.WebKit; -const TestClass = new Lang.Class({ - Name: 'testclass', +const WebHelperApplicationWithTranslatableText = new Lang.Class({ + Name: 'WebHelperApplicationWithTranslatableText', Extends: WebHelper.Application, + + get_translation_string: function() { + return 'Translate Me'; + }, vfunc_startup: function() { this.parent(); this.webview = new WebKit.WebView(); - let string = '

Translate Me

'; + let string = '

' + + this.get_translation_string() + + '

'; this.webview.load_string(string, 'text/html', 'UTF-8', 'file://'); this.win = new Endless.Window({ application: this @@ -36,62 +42,67 @@ const TestClass = new Lang.Class({ } }); -let app; +describe("Translation strategy", function() { + let app; -function setUp() { - // Generate a unique ID for each app instance that we test - let fake_pid = GLib.random_int(); - // FIXME In this version of GJS there is no Posix module, so fake the PID - let id_string = 'com.endlessm.webhelper.test' + GLib.get_real_time() + fake_pid; - app = new TestClass({ - application_id: id_string + beforeEach(function() { + // FIXME In this version of GJS there is no Posix module, so fake the PID + let id_string = 'com.endlessm.webhelper.test' + GLib.get_real_time() + fake_pid; + // Generate a unique ID for each app instance that we test + let fake_pid = GLib.random_int(); + app = new WebHelperApplicationWithTranslatableText({ + application_id: id_string + }); }); -} - -function testStringIsTranslated() { - let translationFunctionWasCalled = false; - let translationFunctionCalledWithString; - app.set_translation_function(function(s) { - translationFunctionWasCalled = true; - translationFunctionCalledWithString = s; - return s; + + describe("translation function", function() { + let translationFunctionSpy; + beforeEach(function() { + translationFunctionSpy = jasmine.createSpy('translate').and.returnValue('Translated'); + }); + it("gets called with string to translate on run", function() { + app.set_translation_function(translationFunctionSpy); + app.run([]); + expect(translationFunctionSpy).toHaveBeenCalledWith(app.get_translation_string()); + }); }); - app.run([]); - assertTrue(translationFunctionWasCalled); - assertEquals('Translate Me', translationFunctionCalledWithString); -} - -// The following test is commented out because GJS cannot catch exceptions -// across FFI interfaces (e.g. in GObject callbacks.) -// function testMissingTranslationFunctionIsHandled() { -// assertRaises(function() { -// app.run([]); -// }); -// } + it("throws when an incompatible type is set as the translation function", function() { + expect(function() { + app.set_translation_function({}); + }).toThrow(); + }); -function testSetBadTranslationFunction() { - assertRaises(function() { - app.set_translation_function("I am not a function"); + // Can't test this right now as there is no support for propagating exceptions across + // GI interfaces + xit("throws when there isn't a translation function set", function() { + expect(function() { + app.run([]); + }).toThrow(); }); -} -function testGetSetTranslationFunction() { - let translationFunction = function(string) { - return string; - }; - app.set_translation_function(translationFunction); - let actualTranslationFunction = app.get_translation_function(); - assertEquals(translationFunction, actualTranslationFunction); -} + it("has a null translation function by default", function() { + expect(app.get_translation_function()).toBe(null); + }); -function testTranslationFunctionIsNullByDefault() { - assertNull(app.get_translation_function()); -} + it("stores the expected translation function", function() { + let translation = function(str) { + return str; + }; + + app.set_translation_function(translation); + expect(app.get_translation_function()).toBe(translation); + }); -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()); -} + it("allows us to store a null translation function", function() { + let nonNullTranslation = function(str) { + return str; + } + + // set a non-null translation function first so that we get + // the non-default behaviour for get_translation_function + app.set_translation_function(nonNullTranslation); + app.set_translation_function(null); + expect(app.get_translation_function()).toBe(null); + }); +}); diff --git a/test/webhelper/testWebActions.js b/test/webhelper/testWebActions.js index 8c790b2..b2ddaf0 100644 --- a/test/webhelper/testWebActions.js +++ b/test/webhelper/testWebActions.js @@ -6,8 +6,8 @@ const Lang = imports.lang; const WebHelper = imports.webhelper; const WebKit = imports.gi.WebKit; -const TestClass = new Lang.Class({ - Name: 'testclass', +const WebActionTestApplication = new Lang.Class({ + Name: 'WebActionTestApplication', Extends: WebHelper.Application, vfunc_startup: function() { @@ -33,128 +33,113 @@ const TestClass = new Lang.Class({ } }); -let app; +// TODO: These tests depend on a running X Server and Window Manager. That means +// that they are not runnable in a continuous-integration server +describe("Web Actions Bindings", function() { + let app; + let webActionSpy; + + beforeEach(function() { + // Generate a unique ID for each app instance that we test + let fake_pid = GLib.random_int(); + // FIXME In this version of GJS there is no Posix module, so fake the PID + let id_string = 'com.endlessm.webhelper.test' + GLib.get_real_time() + fake_pid; + app = new WebActionTestApplication({ + application_id: id_string + }); + webActionSpy = jasmine.createSpy('quitAction').and.callFake(function() { + app.quit(); + }); + }); + + let RunApplicationWithWebAction = function(app, action) { + app.webActionToTest = action; + app.run([]); + } + it("has a working quitApplication uri upon defining quitApplication as a string", function() { + app.define_web_action('quitApplication', webActionSpy); + RunApplicationWithWebAction(app, 'endless://quitApplication'); -function setUp() { - // Generate a unique ID for each app instance that we test - let fake_pid = GLib.random_int(); - // FIXME In this version of GJS there is no Posix module, so fake the PID - let id_string = 'com.endlessm.webhelper.test' + GLib.get_real_time() + fake_pid; - app = new TestClass({ - application_id: id_string + expect(webActionSpy).toHaveBeenCalled(); }); -} -function testWebActionIsCalled() { - let actionWasCalled = false; - app.define_web_action('quitApplication', function() { - actionWasCalled = true; - app.quit(); + it("is called with a parameter", function() { + app.define_web_action('getParameterAndQuit', webActionSpy); + RunApplicationWithWebAction(app, 'endless://getParameterAndQuit?param=value'); + + expect(webActionSpy).toHaveBeenCalledWith(new jasmine.ObjectContaining({ param: 'value' })); }); - app.webActionToTest = 'endless://quitApplication'; - app.run([]); - assertTrue(actionWasCalled); -} - -function testWebActionIsCalledWithParameter() { - let actionParameter; - app.define_web_action('getParameterAndQuit', function(dict) { - actionParameter = dict['param']; - app.quit(); + + it("can be called with many parameters", function() { + app.define_web_action('getParametersAndQuit', webActionSpy); + RunApplicationWithWebAction(app, 'endless://getParametersAndQuit?first=thefirst&second=thesecond&third=thethird'); + + expect(webActionSpy).toHaveBeenCalledWith(new jasmine.ObjectContaining({ + first: 'thefirst', + second: 'thesecond', + third: 'thethird' + })); }); - app.webActionToTest = 'endless://getParameterAndQuit?param=value'; - app.run([]); - assertEquals('value', actionParameter); -} - -function testWebActionIsCalledWithManyParameters() { - let firstParameter, secondParameter, thirdParameter; - app.define_web_action('getParametersAndQuit', function(dict) { - firstParameter = dict['first']; - secondParameter = dict['second']; - thirdParameter = dict['third']; - app.quit(); + + it("decodes parameter URI names", function() { + app.define_web_action('getUriDecodedParameterAndQuit', webActionSpy); + RunApplicationWithWebAction(app, 'endless://getUriDecodedParameterAndQuit?p%C3%A4r%C3%A4m%F0%9F%92%A9=value'); + + expect(webActionSpy).toHaveBeenCalledWith(new jasmine.ObjectContaining({ + 'päräm💩' : 'value' + })); }); - 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.define_web_action('getUriDecodedParameterAndQuit', function(dict) { - parameterWasFound = (expectedParameter in dict); - app.quit(); + + it("decodes parameter URI values", function() { + app.define_web_action('getUriDecodedParameterValueAndQuit', webActionSpy); + RunApplicationWithWebAction(app, 'endless://getUriDecodedParameterValueAndQuit?param=v%C3%A1lu%C3%A9%F0%9F%92%A9'); + + expect(webActionSpy).toHaveBeenCalledWith(new jasmine.ObjectContaining({ + param : 'válué💩' + })); }); - 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.define_web_action('getUriDecodedValueAndQuit', function(dict) { - actualValue = dict['param']; - app.quit(); + + // We currently can't catch exceptions across GObject-Introspection callbacks + xit('bad action is not called', function() { + expect(function() { RunApplicationWithWebAction(app, 'endless://nonexistentWebAction') }).toThrow(); }); - 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.define_web_action('getBlankValueAndQuit', function(dict) { - parameterWasFound = ('param' in dict); - if(parameterWasFound) - parameterValue = dict['param']; - app.quit(); + + describe("with blank parameters", function() { + beforeEach(function() { + app.define_web_action('getBlankValueAndQuit', webActionSpy); + RunApplicationWithWebAction(app, 'endless://getBlankValueAndQuit?param='); + }); + + it("can be called", function() { + expect(webActionSpy).toHaveBeenCalled(); + }); + + it("is called with a paramater that is an empty string", function() { + expect(webActionSpy).toHaveBeenCalledWith(new jasmine.ObjectContaining({ + 'param' : '' + })); + }); }); - app.webActionToTest = 'endless://getBlankValueAndQuit?param='; - app.run([]); - assertTrue(parameterWasFound); - assertNotUndefined(parameterValue); - assertEquals('', parameterValue); -} - -function testWebActionIsUriDecoded() { - let actionWasCalled = false; - app.define_web_action('äction💩Quit', function(dict) { - actionWasCalled = true; - app.quit(); + + it("URI decodes the action", function() { + app.define_web_action('äction💩Quit', webActionSpy); + RunApplicationWithWebAction(app, 'endless://%C3%A4ction%F0%9F%92%A9Quit'); + expect(webActionSpy).toHaveBeenCalled(); }); - app.webActionToTest = 'endless://%C3%A4ction%F0%9F%92%A9Quit'; - app.run([]); - assertTrue(actionWasCalled); -} - -function testDefineMultipleActionsOverride() { - let actionWasCalled = false; - app.define_web_actions({ - quitApplication: function() { - actionWasCalled = true; - app.quit(); - } + + it("allows web actions to be defined as object properties", function() { + app.define_web_actions({ + quitApplication: webActionSpy + }); + + RunApplicationWithWebAction(app, 'endless://quitApplication'); + + expect(webActionSpy).toHaveBeenCalled(); }); - app.webActionToTest = 'endless://quitApplication'; - app.run([]); - assertTrue(actionWasCalled); -} - -function testDefineBadAction() { - assertRaises(function() { - app.define_web_action('badAction', 'not a function'); + + it("throws an error when trying to define an action that is not a function", function() { + expect(function() { + app.define_web_action('action', {}); + }).toThrow(); }); -} +}); diff --git a/test/wikipedia/models/testArticleModel.js b/test/wikipedia/models/testArticleModel.js index 655f187..f916369 100644 --- a/test/wikipedia/models/testArticleModel.js +++ b/test/wikipedia/models/testArticleModel.js @@ -1,72 +1,110 @@ const ArticleModel = imports.wikipedia.models.article_model; -let mockJsonData = { - title: 'Article Title', - url: 'file:///', - source: 'Mock data', - categories: [ - 'Category One', - 'Category Two' - ] -}; - -function _assertCategoryListHasIds(categoryList, idList) { - assertEquals(idList.length, categoryList.length); - idList.forEach(function (id) { - assertTrue(categoryList.some(function (actualId) { - return actualId == id; - })); - }); -} - -function testNewModelFromJson() { - let model = ArticleModel.newFromJson(mockJsonData); - assertTrue(model instanceof ArticleModel.ArticleModel); - assertEquals('Article Title', model.title); - assertEquals('file:///', model.uri); - _assertCategoryListHasIds(model.getCategories(), - ['Category One', 'Category Two']); -} - -function testNewWithProperties() { - let model = new ArticleModel.ArticleModel({ +describe("Wikipedia article model", function() { + let mockJsonData = { title: 'Article Title', - uri: 'file:///' + url: 'file:///', + source: 'Mock data', + categories: [ + 'Category One', + 'Category Two' + ] + }; + + describe("from JSON", function() { + let model; + + beforeEach(function() { + model = ArticleModel.newFromJson(mockJsonData); + }); + + it("has an article title", function() { + expect(model.title).toEqual(mockJsonData.title); + }); + + it("has a uri", function() { + expect(model.uri).toEqual(mockJsonData.url); + }); + + it("has a list of categories", function() { + expect(model.getCategories()).toEqual(mockJsonData.categories); + }); + }); + + describe("from properties", function() { + let model; + beforeEach(function() { + model = new ArticleModel.ArticleModel({ + title: 'Article Title', + uri: 'file://' + }); + }); + + it("is an instance of an ArticleModel", function() { + expect(model instanceof ArticleModel.ArticleModel).toBeTruthy(); + }); + + it("has a title", function() { + expect(model.title).toEqual('Article Title'); + }); + + it("has a URI", function() { + expect(model.uri).toEqual('file://'); + }); + + it("has no categories", function() { + expect(model.getCategories().length).toEqual(0); + }); + }); + + describe("setCategories method", function() { + let model; + + beforeEach(function() { + model = new ArticleModel.ArticleModel(); + }); + + it("adds categories", function() { + let expectedCategories = ['One', 'Two', 'Three']; + model.setCategories(expectedCategories); + expect(model.getCategories()).toEqual(expectedCategories); + }); + + it("replaces existing categories", function() { + model.setCategories(['One', 'Two']); + let expectedCategories = ['One', 'Two', 'Three']; + model.setCategories(expectedCategories); + expect(model.getCategories()).toEqual(expectedCategories); + }); }); - assertEquals('Article Title', model.title); - assertEquals('file:///', model.uri); - assertEquals(0, model.getCategories().length); -} - -function testSetAndGetCategories() { - let model = new ArticleModel.ArticleModel(); - let expectedCategories = ['One', 'Two', 'Three']; - model.setCategories(expectedCategories); - _assertCategoryListHasIds(model.getCategories(), expectedCategories); -} - -function testSetCategoriesWipesPreviousCategories() { - let model = new ArticleModel.ArticleModel(); - let firstCategories = ['One', 'Two', 'Three']; - model.setCategories(firstCategories); - let expectedCategories = ['A', 'B', 'C', 'D']; - model.setCategories(expectedCategories); - _assertCategoryListHasIds(model.getCategories(), expectedCategories); -} - -function testAddAndGetCategories() { - let model = new ArticleModel.ArticleModel(); - model.addCategory('One'); - model.addCategory('Two'); - model.addCategory('Three'); - _assertCategoryListHasIds(model.getCategories(), ['One', 'Two', 'Three']); -} - -function testHasCategories() { - let model = new ArticleModel.ArticleModel(); - let expectedCategories = ['One', 'Two', 'Three']; - model.setCategories(expectedCategories); - expectedCategories.forEach(function (id) { - assertTrue(model.hasCategory(id)); + + it("appends new categories on addCategory", function() { + let model = new ArticleModel.ArticleModel(); + + model.addCategory('One'); + model.addCategory('Two'); + model.addCategory('Three'); + expect(model.getCategories()).toEqual(['One', 'Two', 'Three']); + }); + describe("hasCategory method", function() { + let model; + let expectedCategories = ['One', 'Two', 'Three']; + + beforeEach(function() { + model = new ArticleModel.ArticleModel; + model.setCategories(expectedCategories); + }); + + expectedCategories.forEach(function(category) { + (function(categoryName) { + it("returns true for category named " + categoryName, function() { + expect(model.hasCategory(categoryName)).toBeTruthy(); + }); + }); + }); + + it("returns false for an unexpected category", function() { + expect(model.hasCategory('unexpected')).toBeFalsy(); + }); }); -} +}); diff --git a/test/wikipedia/models/testCategoryModel.js b/test/wikipedia/models/testCategoryModel.js index 8d15665..4ffc1a0 100644 --- a/test/wikipedia/models/testCategoryModel.js +++ b/test/wikipedia/models/testCategoryModel.js @@ -1,76 +1,180 @@ const CategoryModel = imports.wikipedia.models.category_model; -let mockJsonData = { - category_name: 'Category Name', - content_text: 'Lorem Ipsum', - image_file: 'file:///image.jpg', - image_thumb_uri: 'file:///thumb.jpg', - is_main_category: false, - subcategories: [ 'Category Two' ] -}; - -function testNewModelFromJson() { - let model = CategoryModel.newFromJson(mockJsonData); - assertTrue(model instanceof CategoryModel.CategoryModel); - assertEquals('Category Name', model.id); - assertEquals(0, model.getSubcategories().length); -} - -function testNewWithProperties() { - let model = new CategoryModel.CategoryModel({ - id: 'id', - title: 'title', - description: 'description', - image_uri: 'image-uri', - image_thumbnail_uri: 'image-thumbnail-uri', - is_main_category: true, - has_articles: true +describe("Category Model", function() { + let mockJsonData = { + category_name: 'Category Name', + content_text: 'Lorem Ipsum', + image_file: 'file:///image.jpg', + image_thumb_uri: 'file:///thumb.jpg', + is_main_category: false, + subcategories: [ 'Category Two' ] + }; + describe("from JSON", function() { + + let model; + beforeEach(function() { + model = CategoryModel.newFromJson(mockJsonData); + }); + + it("is a CategoryModel", function() { + expect(model instanceof CategoryModel.CategoryModel).toBeTruthy(); + }); + + it("has an id", function() { + expect(model.id).toEqual(mockJsonData.category_name); + }); + + it("has no subcategories", function() { + expect(model.getSubcategories().length).toEqual(0); + }); }); - assertEquals('id', model.id); - assertEquals('title', model.title); - assertEquals('description', model.description); - assertEquals('image-uri', model.image_uri); - assertEquals('image-thumbnail-uri', model.image_thumbnail_uri); - assertEquals(true, model.is_main_category); - assertEquals(true, model.has_articles); - - model.has_articles = false; - assertEquals(false, model.has_articles); -} - -function testGetSubcategoriesEmpty() { - let model = new CategoryModel.CategoryModel(); - assertEquals(0, model.getSubcategories().length); -} - -function _assertCategoryListContainsCategoryIds(categoryList, idList) { - assertEquals(idList.length, categoryList.length); - idList.forEach(function (id) { - assertTrue(categoryList.some(function (categoryModel) { - return categoryModel.id == id; - })); + + describe("from properties", function() { + let model; + + beforeEach(function() { + model = new CategoryModel.CategoryModel({ + id: 'id', + title: 'title', + description: 'description', + image_uri: 'image-uri', + image_thumbnail_uri: 'image-thumbnail-uri', + is_main_category: true, + has_articles: true + }); + }); + + it("has an id", function() { + expect(model.id).toEqual('id'); + }); + + it("has a title", function() { + expect(model.title).toEqual('title'); + }); + + it("has a description", function() { + expect(model.description).toEqual('description'); + }); + + it("has an image uri", function() { + expect(model.image_uri).toEqual('image-uri'); + }); + + it("has an image thumbnail uri", function() { + expect(model.image_thumbnail_uri).toEqual('image-thumbnail-uri'); + }); + + it("is a main category", function() { + expect(model.is_main_category).toBeTruthy(); + }); + + it("has articles", function() { + expect(model.has_articles).toBeTruthy(); + }); + + // FIXME: This seems to be a fairly useless test. Does it actually + // test anything? + it("does not have articles once the flag is unset", function() { + model.has_articles = false; + expect(model.has_articles).toBeFalsy(); + }); + }); + + it("starts with no subcategories", function() { + let model = new CategoryModel.CategoryModel(); + + expect(model.getSubcategories().length).toEqual(0); + }); + + describe("in a tree-like structure", function() { + let parent; + + beforeEach(function() { + jasmine.addMatchers({ + toContainCategoriesWithNames: function() { + return { + compare: function(actual, names) { + let result = { + pass: (function() { + let outer_pass = true; + names.forEach(function (id) { + let categories = actual.getSubcategories(); + if (!categories.some(function(category) { + return category.id == id; + })) { + outer_pass = false; + } + }); + return outer_pass; + })(), + + message: (function() { + let msg = "Expected categories with the following names\n"; + names.forEach(function(name) { + msg += " " + name + "\n"; + }); + msg += "Object actually has the following categories\n"; + actual.getSubcategories().forEach(function(category) { + msg += " " + category.id + "\n"; + }); + return msg; + })() + } + + return result; + } + } + }, + toHaveOnlyTheFollowingCategoriesInOrder: function() { + return { + compare: function(actual, names) { + let result = { + pass: (function() { + let categories = actual.getSubcategories(); + if (categories.length != names.length) + return false; + + for (let i = 0; i < categories.length; i++) { + if (categories[i].id != names[i]) + return false; + } + + return true; + })(), + + message: (function() { + let msg = "Expected exactly the following category names\n"; + names.forEach(function(name) { + msg += " " + name + "\n"; + }); + + msg += "Actually had the following category names\n"; + actual.getSubcategories().forEach(function(category) { + msg += " " + category.id + "\n"; + }); + + return msg; + })() + } + + return result; + } + } + } + }); + + parent = new CategoryModel.CategoryModel({ id: 'Category One' }); + parent.addSubcategory(new CategoryModel.CategoryModel({ id: 'Category Two' })); + parent.addSubcategory(new CategoryModel.CategoryModel({ id: 'Category Three' })); + }); + + it("has subcategories", function() { + expect(parent).toContainCategoriesWithNames(['Category Two', 'Category Three']); + }); + + it("silently does not add duplicates", function() { + parent.addSubcategory(new CategoryModel.CategoryModel({ id: 'Category Two' })); + expect(parent).toHaveOnlyTheFollowingCategoriesInOrder(['Category Two', 'Category Three']); + }); }); -} - -function testAddAndGetSubcategories() { - let model1 = new CategoryModel.CategoryModel({ id: 'Category One' }); - let model2 = new CategoryModel.CategoryModel({ id: 'Category Two' }); - let model3 = new CategoryModel.CategoryModel({ id: 'Category Three' }); - model1.addSubcategory(model2); - model1.addSubcategory(model3); - - let categories = model1.getSubcategories(); - _assertCategoryListContainsCategoryIds(categories, - ['Category Two', 'Category Three']); -} - -function testAddSubcategoryDoesNothingForDuplicate() { - let model1 = new CategoryModel.CategoryModel({ id: 'Category One' }); - let model2 = new CategoryModel.CategoryModel({ id: 'Category Two' }); - let model3 = new CategoryModel.CategoryModel({ id: 'Category Two' }); - model1.addSubcategory(model2); - model1.addSubcategory(model3); - - let categories = model1.getSubcategories(); - _assertCategoryListContainsCategoryIds(categories, ['Category Two']); -} +}); diff --git a/test/wikipedia/models/testDomainWikiModel.js b/test/wikipedia/models/testDomainWikiModel.js index e4e4d3f..f30d1bd 100644 --- a/test/wikipedia/models/testDomainWikiModel.js +++ b/test/wikipedia/models/testDomainWikiModel.js @@ -1,144 +1,208 @@ const DomainWikiModel = imports.wikipedia.models.domain_wiki_model; -let model; - -let mockJsonData = { - categories: [ - { - category_name: 'Main Category', - content_text: 'Lorem Ipsum', - image_file: 'file:///image.jpg', - image_thumb_uri: 'file:///image_thumb.jpg', - is_main_category: true, - subcategories: [ - 'Category One', - 'Category Two' - ] - }, - { - category_name: 'Category One', - content_text: 'Lorem Ipsum', - image_file: 'file:///image.jpg', - image_thumb_uri: 'file:///image_thumb.jpg', - is_main_category: false, - subcategories: [] - }, - { - category_name: 'Category Two', - content_text: 'Lorem Ipsum', - image_file: 'file:///image.jpg', - image_thumb_uri: 'file:///image_thumb.jpg', - is_main_category: false, - subcategories: [ - 'Category Three' - ] - }, - { - category_name: 'Category Three', - content_text: 'Lorem Ipsum', - image_file: 'file:///image.jpg', - image_thumb_uri: 'file:///image_thumb.jpg', - is_main_category: false, - subcategories: [] - }, - ], - articles: [ - { - title: 'Article One', - url: 'file:///article1.html', - source: 'Mock data', - categories: [ - 'Category One' - ] - }, - { - title: 'Article Two', - url: 'file:///article2.html', - source: 'Mock data', - categories: [ - 'Category One', - 'Category Two' - ] - }, - { - title: 'Article Three', - url: 'file:///article3.html', - source: 'Mock data', - categories: [ - 'Category Two' - ] - } - ] -}; - -function setUp() { - model = new DomainWikiModel.DomainWikiModel(); -} - -function _assertArticleListContainsArticleTitles(articleList, titleList) { - assertEquals(titleList.length, articleList.length); - titleList.forEach(function (title) { - assertTrue(articleList.some(function (articleModel) { - return articleModel.title == title; - })); +describe('Domain Wiki Model', function () { + const mockJsonData = { + categories: [ + { + category_name: 'Main Category', + content_text: 'Lorem Ipsum', + image_file: 'file:///image.jpg', + image_thumb_uri: 'file:///image_thumb.jpg', + is_main_category: true, + subcategories: [ + 'Category One', + 'Category Two' + ] + }, + { + category_name: 'Category One', + content_text: 'Lorem Ipsum', + image_file: 'file:///image.jpg', + image_thumb_uri: 'file:///image_thumb.jpg', + is_main_category: false, + subcategories: [] + }, + { + category_name: 'Category Two', + content_text: 'Lorem Ipsum', + image_file: 'file:///image.jpg', + image_thumb_uri: 'file:///image_thumb.jpg', + is_main_category: false, + subcategories: [ + 'Category Three' + ] + }, + { + category_name: 'Category Three', + content_text: 'Lorem Ipsum', + image_file: 'file:///image.jpg', + image_thumb_uri: 'file:///image_thumb.jpg', + is_main_category: false, + subcategories: [] + } + ], + articles: [ + { + title: 'Article One', + url: 'file:///article1.html', + source: 'Mock data', + categories: [ + 'Category One' + ] + }, + { + title: 'Article Two', + url: 'file:///article2.html', + source: 'Mock data', + categories: [ + 'Category One', + 'Category Two' + ] + }, + { + title: 'Article Three', + url: 'file:///article3.html', + source: 'Mock data', + categories: [ + 'Category Two' + ] + } + ] + }; + beforeEach(function () { + let model = new DomainWikiModel.DomainWikiModel(); + + jasmine.addMatchers({ + toHaveObjectsContainingProperties: function () { + return { + compare: function (actual, propertyMap) { + let result = { + pass: (function () { + for (let property in propertyMap) { + let allValuesListedHaveAMatchForObject = actual.some(function (object) { + if (object[property] == 'undefined') { + return false; + } + + let propertyValueMatchedForObject = + propertyMap[property].some(function (value) { + return object[property] == value; + }); + + return propertyValueMatchedForObject; + }); + + if (!allValuesListedHaveAMatchForObject) + return false; + } + + return true; + })(), + + message: (function () { + let msg = 'Expected objects to have the following values for the following properties \n'; + for (let property in propertyMap) { + msg += ' - Property: ' + property + '\n'; + for (let value in propertyMap[property]) { + msg += ' * Value: ' + propertyMap[property][value].toString() + '\n'; + } + } + + msg += 'Object actually has the following toplevel properties\n'; + + for (let i = 0; i < actual.length; i++) { + let object = actual[i]; + msg += ' Object in position ' + i + '\n'; + for (let property in object) { + msg += ' - ' + property + ' : ' + object[property] + '\n'; + } + } + + return msg; + })() + }; + + return result; + } + }; + } + }); + }); + + describe('when loaded from some mock JSON data', function () { + let model; + beforeEach(function () { + model = new DomainWikiModel.DomainWikiModel(); + model.loadFromJson(mockJsonData); + }); + + it('returns all articles when getting articles', function () { + let articles = model.getArticles(); + expect(articles).toHaveObjectsContainingProperties({ + title: [ 'Article One', 'Article Two', 'Article Three' ] + }); + }); + + it('can get articles for a category', function () { + let articles = model.getArticlesForCategory('Category One'); + expect(articles).toHaveObjectsContainingProperties({ + title: [ 'Article One', 'Article Two' ] + }); + }); + + it('has no articles on a category that does not have articles', function () { + let articles = model.getArticlesForCategory('Main Category'); + expect(articles.length).toEqual(0); + }); + + it('has no articles for a category that does not exist', function () { + let articles = model.getArticlesForCategory('Nonexistent'); + expect(articles.length).toEqual(0); + }); + + it('can check whether or not a category has articles', function () { + expect(model._getCategoryHasArticles('Category Two')).toBeTruthy(); + }); + + it('can check whether or not a category does not have articles', function () { + expect(model._getCategoryHasArticles('Category Three')).toBeFalsy(); + }); + + it('verifies that a category that does not exist has no articles', function () { + expect(model._getCategoryHasArticles('Nonexistent')).toBeFalsy(); + }); + + describe('category fetch', function () { + let category; + + beforeEach(function () { + category = model.getCategory('Category One'); + }); + + it('actually returns a category', function () { + expect(category.__name__).toEqual('CategoryModel'); + }); + + it('returns the right category', function () { + expect(category.title).toEqual('Category One'); + }); + }); + + it("returns an undefined value if we try to get a category that doesn't exist", function () { + expect(model.getCategory('Nonexistent')).toBeUndefined(); + }); + + it("returns 'Main Category' when getting the main category", function () { + let category = model.getMainCategory(); + + expect(category).toEqual(new jasmine.ObjectContaining({ + 'title' : 'Main Category' + })); + }); + }); + + it('returns null when the Main Category is unset', function () { + let model = new DomainWikiModel.DomainWikiModel(); + expect(model.getMainCategory()).toBeNull(); }); -} - -function testGetArticlesReturnsAllArticles() { - model.loadFromJson(mockJsonData); - let articles = model.getArticles(); - _assertArticleListContainsArticleTitles(articles, - [ 'Article One', 'Article Two', 'Article Three' ]); -} - -function testGetArticlesForCategoryWithArticles() { - model.loadFromJson(mockJsonData); - let articles = model.getArticlesForCategory('Category One'); - _assertArticleListContainsArticleTitles(articles, - [ 'Article One', 'Article Two' ]); -} - -function testGetArticlesForCategoryWithoutArticles() { - model.loadFromJson(mockJsonData); - assertEquals(0, model.getArticlesForCategory('Main Category').length); -} - -function testGetArticlesForCategoryWithNonexistentId() { - assertEquals(0, model.getArticlesForCategory('Nonexistent').length); -} - -function testCategoryHasArticlesReturnsTrue() { - model.loadFromJson(mockJsonData); - assertTrue(model._getCategoryHasArticles('Category Two')); -} - -function testCategoryHasArticlesReturnsFalse() { - model.loadFromJson(mockJsonData); - assertFalse(model._getCategoryHasArticles('Category Three')); -} - -function testCategoryHasArticlesWithNonexistentId() { - assertFalse(model._getCategoryHasArticles('Nonexistent')); -} - -function testGetCategory() { - model.loadFromJson(mockJsonData); - let category = model.getCategory('Category One'); - assertEquals('CategoryModel', category.__name__); - assertEquals('Category One', category.title); -} - -function testGetNonexistentCategory() { - assertUndefined(model.getCategory('Nonexistent')); -} - -function testGetMainCategory() { - model.loadFromJson(mockJsonData); - let category = model.getMainCategory(); - assertTrue(category.__name__ == 'CategoryModel'); - assertEquals('Main Category', category.title); -} - -function testGetUnsetMainCategory() { - assertNull(model.getMainCategory()); -} +}); -- cgit v1.2.3