diff --git a/webapp/controller/App.Controller.js b/webapp/controller/App.Controller.js index 0710987..8881f5b 100644 --- a/webapp/controller/App.Controller.js +++ b/webapp/controller/App.Controller.js @@ -1,10 +1,30 @@ -sap.ui.define([ - "com/mrb/UI5-Testing/controller/BaseController" -], function( - BaseController -) { - "use strict"; +sap.ui.define( + [ + "./BaseController", + "sap/ui/model/json/JSONModel", + ], + function (BaseController, JSONModel) { + "use strict"; - return BaseController.extend("com.mrb.UI5-Testing.controller.App.Controller", { - }); -}); \ No newline at end of file + return BaseController.extend( + "com.mrb.UI5-Testing.controller.App.Controller", + { + onInit: function () { + var oViewModel = new JSONModel({ + busy: true, + delay: 0, + }); + + this.setModel(oViewModel, "appView"); + + this.getOwnerComponent() + .getModel() + .metadataLoaded() + .then(function () { + oViewModel.setProperty("/busy", false); + }); + }, + } + ); + } +); diff --git a/webapp/controller/BaseController.js b/webapp/controller/BaseController.js index a62ca88..6c3918a 100644 --- a/webapp/controller/BaseController.js +++ b/webapp/controller/BaseController.js @@ -1,70 +1,78 @@ -sap.ui.define([ - "sap/ui/core/mvc/Controller", - "sap/ui/core/routing/History", - "sap/ui/core/UIComponent", - "com/mrb/UI5-Testing/model/formatter" -], function(Controller, History, UIComponent, formatter) { - "use strict"; +sap.ui.define( + [ + "sap/ui/core/mvc/Controller", + "sap/ui/core/routing/History", + "sap/ui/core/UIComponent", + "com/mrb/UI5-Testing/model/formatter", + ], + function (Controller, History, UIComponent, formatter) { + "use strict"; - return Controller.extend("com.mrb.UI5-Testing.controller.BaseController", { + return Controller.extend("com.mrb.UI5-Testing.controller.BaseController", { + formatter: formatter, + /** + * Convenience method for accessing the event bus. + * @public + * @returns {sap.ui.core.EventBus} the event bus for this component + */ + getEventBus: function () { + return this.getOwnerComponent().getEventBus(); + }, - formatter: formatter, + /** + * Convenience method for getting the view model by name in every controller of the application. + * @public + * @param {string} sName the model name + * @returns {sap.ui.model.Model} the model instance + */ + getModel: function (sName) { + return this.getView().getModel(sName); + }, - /** - * Convenience method for getting the view model by name in every controller of the application. - * @public - * @param {string} sName the model name - * @returns {sap.ui.model.Model} the model instance - */ - getModel: function(sName) { - return this.getView().getModel(sName); - }, + /** + * Convenience method for setting the view model in every controller of the application. + * @public + * @param {sap.ui.model.Model} oModel the model instance + * @param {string} sName the model name + * @returns {sap.ui.mvc.View} the view instance + */ + setModel: function (oModel, sName) { + return this.getView().setModel(oModel, sName); + }, - /** - * Convenience method for setting the view model in every controller of the application. - * @public - * @param {sap.ui.model.Model} oModel the model instance - * @param {string} sName the model name - * @returns {sap.ui.mvc.View} the view instance - */ - setModel: function(oModel, sName) { - return this.getView().setModel(oModel, sName); - }, + /** + * Convenience method for getting the resource bundle. + * @public + * @returns {sap.ui.model.resource.ResourceModel} the resourceModel of the component + */ + getResourceBundle: function () { + return this.getOwnerComponent().getModel("i18n").getResourceBundle(); + }, - /** - * Convenience method for getting the resource bundle. - * @public - * @returns {sap.ui.model.resource.ResourceModel} the resourceModel of the component - */ - getResourceBundle: function() { - return this.getOwnerComponent().getModel("i18n").getResourceBundle(); - }, + /** + * Method for navigation to specific view + * @public + * @param {string} psTarget Parameter containing the string for the target navigation + * @param {mapping} pmParameters? Parameters for navigation + * @param {boolean} pbReplace? Defines if the hash should be replaced (no browser history entry) or set (browser history entry) + */ + navTo: function (psTarget, pmParameters, pbReplace) { + this.getRouter().navTo(psTarget, pmParameters, pbReplace); + }, - /** - * Method for navigation to specific view - * @public - * @param {string} psTarget Parameter containing the string for the target navigation - * @param {mapping} pmParameters? Parameters for navigation - * @param {boolean} pbReplace? Defines if the hash should be replaced (no browser history entry) or set (browser history entry) - */ - navTo: function(psTarget, pmParameters, pbReplace) { - this.getRouter().navTo(psTarget, pmParameters, pbReplace); - }, + getRouter: function () { + return UIComponent.getRouterFor(this); + }, - getRouter: function() { - return UIComponent.getRouterFor(this); - }, + onNavBack: function () { + var sPreviousHash = History.getInstance().getPreviousHash(); - onNavBack: function() { - var sPreviousHash = History.getInstance().getPreviousHash(); - - if (sPreviousHash !== undefined) { - window.history.back(); - } else { - this.getRouter().navTo("appHome", {}, true /*no history*/ ); - } - } - - }); - -}); + if (sPreviousHash !== undefined) { + window.history.back(); + } else { + this.getRouter().navTo("appHome", {}, true /*no history*/); + } + }, + }); + } +); diff --git a/webapp/controller/Home.controller.js b/webapp/controller/Home.controller.js deleted file mode 100644 index 3a542dd..0000000 --- a/webapp/controller/Home.controller.js +++ /dev/null @@ -1,7 +0,0 @@ -sap.ui.define([ - "com/mrb/UI5-Testing/controller/BaseController" -], function(Controller) { - "use strict"; - - return Controller.extend("com.mrb.UI5-Testing.controller.Home", {}); -}); diff --git a/webapp/controller/Worklist.controller.js b/webapp/controller/Worklist.controller.js new file mode 100644 index 0000000..26b4e1e --- /dev/null +++ b/webapp/controller/Worklist.controller.js @@ -0,0 +1,114 @@ +sap.ui.define( + [ + "./BaseController", + "sap/ui/model/json/JSONModel", + "../model/formatter", + "sap/m/library", + ], + function (BaseController, JSONModel, formatter, mobileLibrary) { + "use strict"; + + return BaseController.extend("com.mrb.UI5-Testing.controller.Worklist", { + /** + * Called when the worklist controller is instantiated. + * @public + */ + onInit: function () { + var oViewModel, + iOriginalBusyDelay, + oTable = this.byId("table"); + + // Put down worklist table's original value for busy indicator delay, + // so it can be restored later on. Busy handling on the table is + // taken care of by the table itself. + iOriginalBusyDelay = oTable.getBusyIndicatorDelay(); + + // Model used to manipulate control states + oViewModel = new JSONModel({ + worklistTableTitle: this.getResourceBundle().getText( + "worklistTableTitle" + ), + shareSendEmailSubject: this.getResourceBundle().getText( + "shareSendEmailWorklistSubject" + ), + shareSendEmailMessage: this.getResourceBundle().getText( + "shareSendEmailWorklistMessage", + [window.location.href] + ), + tableBusyDelay: 0, + }); + this.setModel(oViewModel, "worklistView"); + + // Make sure, busy indication is showing immediately so there is no + // break after the busy indication for loading the view's meta data is + // ended (see promise 'oWhenMetadataIsLoaded' in AppController) + oTable.attachEventOnce("updateFinished", function () { + // Restore original busy indicator delay for worklist's table + oViewModel.setProperty("/tableBusyDelay", iOriginalBusyDelay); + }); + }, + + /* =========================================================== */ + /* event handlers */ + /* =========================================================== */ + + /** + * Triggered by the table's 'updateFinished' event: after new table + * data is available, this handler method updates the table counter. + * This should only happen if the update was successful, which is + * why this handler is attached to 'updateFinished' and not to the + * table's list binding's 'dataReceived' method. + * + * @param {sap.ui.base.Event} oEvent the update finished event + * @public + */ + onUpdateFinished: function (oEvent) { + // update the worklist's object counter after the table update + var sTitle, + oTable = oEvent.getSource(), + iTotalItems = oEvent.getParameter("total"); + // only update the counter if the length is final and + // the table is not empty + if (iTotalItems && oTable.getBinding("items").isLengthFinal()) { + sTitle = this.getResourceBundle().getText("worklistTableTitleCount", [ + iTotalItems, + ]); + } else { + sTitle = this.getResourceBundle().getText("worklistTableTitle"); + } + this.getModel("worklistView").setProperty( + "/worklistTableTitle", + sTitle + ); + }, + /** + * Sets the item count on the worklist view header + * @param {int} iTotalItems the total number of items in the table + * @private + */ + _updateListItemCount: function (iTotalItems) { + var sTitle; + // only update the counter if the length is final + if (this._oTable.getBinding("items").isLengthFinal()) { + sTitle = this.getResourceBundle().getText("worklistTableTitleCount", [ + iTotalItems, + ]); + this.oViewModel.setProperty("/worklistTableTitle", sTitle); + } + }, + + /** + * Event handler when the share by E-Mail button has been clicked + * @public + */ + onShareEmailPress: function () { + var oViewModel = this.getModel("worklistView"); + mobileLibrary.URLHelper.triggerEmail( + null, + oViewModel.getProperty("/shareSendEmailSubject"), + oViewModel.getProperty("/shareSendEmailMessage") + ); + }, + }); + } +); diff --git a/webapp/i18n/i18n.properties b/webapp/i18n/i18n.properties index 6ed1aa5..3c58b16 100644 --- a/webapp/i18n/i18n.properties +++ b/webapp/i18n/i18n.properties @@ -1,3 +1,37 @@ title=UI5-Testing appTitle=UI5-Testing appDescription=UI5 Testing Walkthrough + +# This is the resource bundle for the worklist app + +#~~~ Worklist View ~~~~~~~~~~~~~~~~~~~~~~~~~~ +#XTIT: Table view title - Not used as we're using the Repository 'UI5-Testing' title +worklistViewTitle=Bulletin Board + +#XTIT: Table view title +worklistTableTitle=Posts + +#XTIT: Table view title with placeholder for the number of items +worklistTableTitleCount=Posts ({0}) + +#XTIT: The title of the column containing the Names of objects +TableNameColumnTitle=Name + +#XTIT: The title of the column containing the Category of objects +TableCategoryColumnTitle=Category + +#XTIT: The title of the column containing the unit number and the unit of measure +TableUnitNumberColumnTitle=Price + +#~~~ Object View ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +#XTIT: Object view title +objectTitle=Post + +#~~~ Footer Options ~~~~~~~~~~~~~~~~~~~~~~~ + +#XTIT: Send E-Mail subject +shareSendEmailWorklistSubject= + +#YMSG: Send E-Mail message +shareSendEmailWorklistMessage=\r\n{0} \ No newline at end of file diff --git a/webapp/index.html b/webapp/index.html index 017cc71..762306c 100644 --- a/webapp/index.html +++ b/webapp/index.html @@ -1,32 +1,31 @@ - - - - + Bulletin Board + + + + - UI5-Testing + +
+

Bulletin Board

+

This sample app for the testing will test application functionality
with QUnit, OPA5, and the mock + server

- - +

Entry points

+

Can be used to run the app or the automated tests as needed:

- -
-
+ +

For more documentation please read the UI5 Developer Guide. +

+
\ No newline at end of file diff --git a/webapp/localService/metadata.xml b/webapp/localService/metadata.xml new file mode 100644 index 0000000..dbc635b --- /dev/null +++ b/webapp/localService/metadata.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/webapp/localService/mockdata/Posts.json b/webapp/localService/mockdata/Posts.json new file mode 100644 index 0000000..5145be7 --- /dev/null +++ b/webapp/localService/mockdata/Posts.json @@ -0,0 +1,255 @@ +[ + { + "PostID": "PostID_1", + "Title": "29'er Mountain Bike (red)", + "Timestamp": "/Date(1428223780000)/", + "Description": "A great mountainbike, barely used and good as new. Pedals and saddle included", + "Category": "Bicycles", + "Contact": "contact.me07@gmail.com", + "Currency": "USD", + "Price": 81, + "Flagged": 0 + }, + { + "PostID": "PostID_2", + "Title": "Football (rare with signatures)", + "Timestamp": "/Date(1428504382000)/", + "Description": "A trophy for collectors, 2014 football with original signatures from the german national soccer team and the spirit of the world cup.", + "Category": "Sports", + "Contact": "soccernerd@hotmail.de", + "Currency": "EUR", + "Price": 420, + "Flagged": 0 + }, + { + "PostID": "PostID_3", + "Title": "Video Games", + "Timestamp": "/Date(1433357393000)/", + "Description": "A collection of 22 classic video games from 1986 to 1992, old but still a lot of fun", + "Category": "Multimedia", + "Contact": "RogerRoger@gmail.com", + "Currency": "USD", + "Price": 27, + "Flagged": 0 + }, + { + "PostID": "PostID_4", + "Title": "Fluffy Teddy Bear", + "Timestamp": "/Date(1429892877000)/", + "Description": "This little companion is looking for a new friend, it is brown and has black eyes. One ear is missing.", + "Category": "Toys", + "Contact": "rainbow.glitter03@gmail.com", + "Currency": "USD", + "Price": 13, + "Flagged": 1 + }, + { + "PostID": "PostID_5", + "Title": "Car Tires, 22 Inch", + "Timestamp": "/Date(1423579562000)/", + "Description": "Spare winter tires for a compact-size car, 4 tires with good profile.", + "Category": "Car parts", + "Contact": "superpacer@gmail.com", + "Currency": "USD", + "Price": 121, + "Flagged": 0 + }, + { + "PostID": "PostID_6", + "Title": "Garage Door with Blue Stripes, 4m x 2,2m", + "Timestamp": "/Date(1431603971000)/", + "Description": "Good as new, a garage door for a standard double garage, keeps cars dry and carjackers away.", + "Category": "Car parts", + "Contact": "john.doe@gmail.com", + "Currency": "USD", + "Price": 481, + "Flagged": 1 + }, + { + "PostID": "PostID_7", + "Title": "Kids Toys, a Whole Box of Stuff", + "Timestamp": "/Date(1435603415000)/", + "Description": "Best suited for kids aged from 4-10, a whole box of toys including model cars, marble balls, toy figures, and much more.", + "Category": "Toys", + "Contact": "AGreatDeal@gmail.com", + "Currency": "USD", + "Price": 63, + "Flagged": 0 + }, + { + "PostID": "PostID_8", + "Title": "Screwdrivers", + "Timestamp": "/Date(1439273334000)/", + "Description": "20-Piece Multibit Ratcheting Screwdriver Set", + "Category": "Miscellaneous", + "Contact": "houseofparts@gmail.com", + "Currency": "USD", + "Price": 28, + "Flagged": 0 + }, + { + "PostID": "PostID_9", + "Title": "Comfortable Bike Saddle", + "Timestamp": "/Date(1426606523000)/", + "Description": "A brand-new, unused bike saddle with black covering", + "Category": "Bicycles", + "Contact": "gotIt@gmail.com", + "Currency": "USD", + "Price": 25, + "Flagged": 0 + }, + { + "PostID": "PostID_10", + "Title": "Bike Rack", + "Timestamp": "/Date(1429871021000)/", + "Description": "Suitable for camper or RV, used", + "Category": "Bicycles", + "Contact": "MajorRefactoring@gmail.com", + "Currency": "USD", + "Price": 106, + "Flagged": 1 + }, + { + "PostID": "PostID_11", + "Title": "DVD: Trains of Europe", + "Timestamp": "/Date(1425446058000)/", + "Description": "An amazing collection of train models all around Europe, total runtime 412 minutes.", + "Category": "Multimedia", + "Contact": "hopOnTheTrain@gmail.com", + "Currency": "USD", + "Price": 61, + "Flagged": 0 + }, + { + "PostID": "PostID_12", + "Title": "Matress", + "Timestamp": "/Date(1435612710000)/", + "Description": "Bed Mattress 30 x 70 inch, filled with natural fibers, barely used", + "Category": "Miscellaneous", + "Contact": "abc@gmail.com", + "Currency": "USD", + "Price": 306, + "Flagged": 0 + }, + { + "PostID": "PostID_13", + "Title": "High-End Gamer PC", + "Timestamp": "/Date(1436435262000)/", + "Description": "3Ghz dual core, 16gb RAM, high tower. Great for playing the latest games.", + "Category": "Furniture", + "Contact": "player.mike@gmail.com", + "Currency": "USD", + "Price": 256, + "Flagged": 0 + }, + { + "PostID": "PostID_14", + "Title": "Cooking Pot Set", + "Timestamp": "/Date(1427938348000)/", + "Description": "Stainless steel cooking pots (10 pcs) with a matching lid each.", + "Category": "Miscellaneous", + "Contact": "abc@gmail.com", + "Currency": "USD", + "Price": 234, + "Flagged": 0 + }, + { + "PostID": "PostID_15", + "Title": "Jeans", + "Timestamp": "/Date(1437280068000)/", + "Description": "Used-look Jeans european size 32x34, only worn once.", + "Category": "Clothing", + "Contact": "sellTwoPairs@gmail.com", + "Currency": "EUR", + "Price": 34, + "Flagged": 0 + }, + { + "PostID": "PostID_16", + "Title": "Moving Boxes", + "Timestamp": "/Date(1424653593000)/", + "Description": "100 Cardboard boxes perfect for relocating, only used once and in a pretty good shape.", + "Category": "Miscellaneous", + "Contact": "overhaul08@gmail.com", + "Currency": "USD", + "Price": 60, + "Flagged": 0 + }, + { + "PostID": "PostID_17", + "Title": "Car VW Golf (white)", + "Timestamp": "/Date(1436927019000)/", + "Description": "Only 160.000 km and in really good shape, grip shift, contact me for appointment and more details.", + "Category": "Car Parts", + "Contact": "james.McGillan@gmail.com", + "Currency": "USD", + "Price": 3006, + "Flagged": 0 + }, + { + "PostID": "PostID_18", + "Title": "Swimming Pool", + "Timestamp": "/Date(1436402641000)/", + "Description": "Perfect for your very own pool parties in the garden, measures: 5x10m, fill with ~500.000l water and enjoy.", + "Category": "Miscellaneous", + "Contact": "poollov3r@gmail.com", + "Currency": "USD", + "Price": 4587, + "Flagged": 0 + }, + { + "PostID": "PostID_19", + "Title": "Travel Suitcases", + "Timestamp": "/Date(1426837938000)/", + "Description": "New and in original packaging, a set of 5 high-quality suitcases from small to large sizes.", + "Category": "Miscellaneous", + "Contact": "aroundtheworld@hotmail.com", + "Currency": "USD", + "Price": 560, + "Flagged": 0 + }, + { + "PostID": "PostID_20", + "Title": "Rainbow Stickers", + "Timestamp": "/Date(1430961733000)/", + "Description": "A vast collection of rainbow stickers for collectors, about 2.000 individual pieces in all shapes and sizes", + "Category": "Miscellaneous", + "Contact": "james.holden@gmail.com", + "Currency": "USD", + "Price": 45, + "Flagged": 1 + }, + { + "PostID": "PostID_21", + "Title": "Notebook", + "Timestamp": "/Date(1438286726000)/", + "Description": "Used notebook with broken display, needs repair. A bargain for the DIY tech guy.", + "Category": "Multimedia", + "Contact": "to.felldown@gmail.com", + "Currency": "USD", + "Price": 23, + "Flagged": 0 + }, + { + "PostID": "PostID_22", + "Title": "Plasma TV 60\"!", + "Timestamp": "/Date(1425501468000)/", + "Description": "I got a larger one, so selling this one cheap for all the movie lovers out there", + "Category": "Multimedia", + "Contact": "large.one@gmail.com", + "Currency": "USD", + "Price": 360, + "Flagged": 0 + }, + { + "PostID": "PostID_23", + "Title": "Cheap Boat", + "Timestamp": "/Date(1439561313000)/", + "Description": "Living close to a lake or the ocean? This dream of a yacht (30ft long!) comes with lots of extras. Get it and fulfill yourself a dream.", + "Category": "Miscellaneous", + "Contact": "gotboats@gmail.com", + "Currency": "USD", + "Price": 26263, + "Flagged": 0 + } +] diff --git a/webapp/localService/mockserver.js b/webapp/localService/mockserver.js new file mode 100644 index 0000000..be678fa --- /dev/null +++ b/webapp/localService/mockserver.js @@ -0,0 +1,116 @@ +sap.ui.define([ + "sap/ui/core/util/MockServer", + "sap/ui/model/json/JSONModel", + "sap/base/util/UriParameters", + "sap/base/Log" +], function (MockServer, JSONModel, UriParameters, Log) { + "use strict"; + + var oMockServer, + _sAppPath = "com/mrb/UI5-Testing/", + _sJsonFilesPath = _sAppPath + "localService/mockdata"; + + var oMockServerInterface = { + + /** + * Initializes the mock server asynchronously. + * You can configure the delay with the URL parameter "serverDelay". + * The local mock data in this folder is returned instead of the real data for testing. + * @protected + * @param {object} [oOptionsParameter] init parameters for the mockserver + * @returns{Promise} a promise that is resolved when the mock server has been started + */ + init : function (oOptionsParameter) { + var oOptions = oOptionsParameter || {}; + + return new Promise(function(fnResolve, fnReject) { + var sManifestUrl = sap.ui.require.toUrl(_sAppPath + "manifest.json"), + oManifestModel = new JSONModel(sManifestUrl); + + oManifestModel.attachRequestCompleted(function () { + var oUriParameters = UriParameters.fromQuery(window.location.search), + // parse manifest for local metadata URI + sJsonFilesUrl = sap.ui.require.toUrl(_sJsonFilesPath), + oMainDataSource = oManifestModel.getProperty("/sap.app/dataSources/mainService"), + sMetadataUrl = sap.ui.require.toUrl(_sAppPath + oMainDataSource.settings.localUri), + // ensure there is a trailing slash + sMockServerUrl = /.*\/$/.test(oMainDataSource.uri) ? oMainDataSource.uri : oMainDataSource.uri + "/"; + + // create a mock server instance or stop the existing one to reinitialize + if (!oMockServer) { + oMockServer = new MockServer({ + rootUri: sMockServerUrl + }); + } else { + oMockServer.stop(); + } + + // configure mock server with the given options or a default delay of 0.5s + MockServer.config({ + autoRespond : true, + autoRespondAfter : (oOptions.delay || oUriParameters.get("serverDelay") || 500) + }); + + // simulate all requests using mock data + oMockServer.simulate(sMetadataUrl, { + sMockdataBaseUrl : sJsonFilesUrl, + bGenerateMissingMockData : true + }); + + var aRequests = oMockServer.getRequests(); + + // compose an error response for requesti + var fnResponse = function (iErrCode, sMessage, aRequest) { + aRequest.response = function(oXhr){ + oXhr.respond(iErrCode, {"Content-Type": "text/plain;charset=utf-8"}, sMessage); + }; + }; + + // simulate metadata errors + if (oOptions.metadataError || oUriParameters.get("metadataError")) { + aRequests.forEach(function (aEntry) { + if (aEntry.path.toString().indexOf("$metadata") > -1) { + fnResponse(500, "metadata Error", aEntry); + } + }); + } + + // simulate request errors + var sErrorParam = oOptions.errorType || oUriParameters.get("errorType"), + iErrorCode = sErrorParam === "badRequest" ? 400 : 500; + if (sErrorParam) { + aRequests.forEach(function (aEntry) { + fnResponse(iErrorCode, sErrorParam, aEntry); + }); + } + + // custom mock behaviour may be added here + + // set requests and start the server + oMockServer.setRequests(aRequests); + oMockServer.start(); + + Log.info("Running the app with mock data"); + fnResolve(); + }); + + oManifestModel.attachRequestFailed(function () { + var sError = "Failed to load application manifest"; + + Log.error(sError); + fnReject(new Error(sError)); + }); + }); + }, + + /** + * @public returns the mockserver of the app, should be used in integration tests + * @returns {sap.ui.core.util.MockServer} the mockserver instance + */ + getMockServer : function () { + return oMockServer; + } + }; + + return oMockServerInterface; +}); \ No newline at end of file diff --git a/webapp/manifest.json b/webapp/manifest.json index 660a4e2..feaf2e2 100644 --- a/webapp/manifest.json +++ b/webapp/manifest.json @@ -8,12 +8,22 @@ "version": "1.0.0" }, "title": "{{appTitle}}", - "description": "{{appDescription}}" + "description": "{{appDescription}}", + "dataSources": { + "mainService": { + "uri": "/here/goes/your/serviceUrl/", + "type": "OData", + "settings": { + "odataVersion": "2.0", + "localUri": "localService/metadata.xml" + } + } + } }, "sap.ui": { "technology": "UI5", "icons": { - "icon": "", + "icon": "sap-icon://task", "favIcon": "", "phone": "", "phone@2": "", @@ -51,6 +61,14 @@ "settings": { "bundleName": "com.mrb.UI5-Testing.i18n.i18n" } + }, + "": { + "dataSource": "mainService", + "settings": { + "metadataUrlParams": { + "sap-documentation": "heading" + } + } } }, "resources": { @@ -83,9 +101,9 @@ "viewType": "XML", "viewLevel": 1, "viewId": "home", - "viewName": "Home" + "viewName": "Worklist" } } } } -} +} \ No newline at end of file diff --git a/webapp/model/formatter.js b/webapp/model/formatter.js index 51eddfd..5824e7f 100644 --- a/webapp/model/formatter.js +++ b/webapp/model/formatter.js @@ -1,4 +1,18 @@ sap.ui.define([], function () { - "use strict"; - return {}; + "use strict"; + return { + /** + * Rounds the number unit value to 2 digits + * + * @public + * @param {string} sValue the number string to be rounded + * @returns {string} sValue with 2 digits rounded + */ + numberUnit: function (sValue) { + if (!sValue) { + return ""; + } + return parseFloat(sValue).toFixed(2); + }, + }; }); diff --git a/webapp/test/initMockServer.js b/webapp/test/initMockServer.js new file mode 100644 index 0000000..cc1bc34 --- /dev/null +++ b/webapp/test/initMockServer.js @@ -0,0 +1,18 @@ +sap.ui.define([ + "com/mrb/UI5-Testing/localService/mockserver", + "sap/m/MessageBox" +], function (mockserver, MessageBox) { + "use strict"; + + var aMockservers = []; + + // initialize the mock server + aMockservers.push(mockserver.init()); + + Promise.all(aMockservers).catch(function (oError) { + MessageBox.error(oError.message); + }).finally(function () { + // initialize the embedded component on the HTML page + sap.ui.require(["sap/ui/core/ComponentSupport"]); + }); +}); \ No newline at end of file diff --git a/webapp/test/integration/AllJourneys.js b/webapp/test/integration/AllJourneys.js index 16e232d..9c10ac8 100644 --- a/webapp/test/integration/AllJourneys.js +++ b/webapp/test/integration/AllJourneys.js @@ -1,13 +1,13 @@ sap.ui.define([ - "sap/ui/test/Opa5", - "com/mrb/UI5-Testing/test/integration/arrangements/Startup", - "com/mrb/UI5-Testing/test/integration/BasicJourney" -], function(Opa5, Startup) { - "use strict"; - - Opa5.extendConfig({ - arrangements: new Startup(), - pollingInterval: 1 - }); + "sap/ui/test/Opa5", + "./arrangements/Startup", + "./WorklistJourney" +], function (Opa5, Startup) { + "use strict"; + Opa5.extendConfig({ + arrangements: new Startup(), + viewNamespace: "com.mrb.UI5-Testing.view.", + autoWait: true + }); }); diff --git a/webapp/test/integration/BasicJourney.js b/webapp/test/integration/BasicJourney.js deleted file mode 100644 index 9db143e..0000000 --- a/webapp/test/integration/BasicJourney.js +++ /dev/null @@ -1,19 +0,0 @@ -sap.ui.define([ - "sap/ui/test/opaQunit", - "com/mrb/UI5-Testing/test/integration/pages/App" -], function (opaTest) { - "use strict"; - - opaTest("should show correct number of nested pages", function (Given, When, Then) { - - // Arrangements - Given.iStartMyApp(); - - // Assertions - Then.onTheAppPage.iShouldSeePageCount(1); - - // Cleanup - Then.iTeardownMyApp(); - }); - -}); diff --git a/webapp/test/integration/WorklistJourney.js b/webapp/test/integration/WorklistJourney.js new file mode 100644 index 0000000..e729e64 --- /dev/null +++ b/webapp/test/integration/WorklistJourney.js @@ -0,0 +1,22 @@ +/*global QUnit*/ +sap.ui.define([ + "sap/ui/test/opaQunit", + "./pages/Worklist" +], function (opaTest) { + "use strict"; + + QUnit.module("Posts"); + + opaTest("Should see the table with all posts", function (Given, When, Then) { + // Arrangements + Given.iStartMyApp(); + + // Assertions + Then.onTheWorklistPage.theTableShouldHaveAllEntries(). + and.theTitleShouldDisplayTheTotalAmountOfItems(); + + // Cleanup + Then.iTeardownMyApp(); + }); + +}); diff --git a/webapp/test/integration/arrangements/Startup.js b/webapp/test/integration/arrangements/Startup.js index 6d8e5c1..c095db2 100644 --- a/webapp/test/integration/arrangements/Startup.js +++ b/webapp/test/integration/arrangements/Startup.js @@ -1,19 +1,38 @@ sap.ui.define([ - "sap/ui/test/Opa5" -], function(Opa5) { - "use strict"; + "sap/ui/test/Opa5", + "com/mrb/UI5-Testing//localService/mockserver", + "sap/ui/model/odata/v2/ODataModel" +], function(Opa5, mockserver, ODataModel) { + "use strict"; - return Opa5.extend("com.mrb.UI5-Testing.test.integration.arrangements.Startup", { + return Opa5.extend("com.mrb.UI5-Testing.test.integration.arrangements.Startup", { - iStartMyApp: function () { - this.iStartMyUIComponent({ - componentConfig: { - name: "com.mrb.UI5-Testing", - async: true, - manifest: true - } - }); - } + iStartMyApp: function (oOptionsParameter) { + var oOptions = oOptionsParameter || {}; - }); + this._clearSharedData(); + + // start the app with a minimal delay to make tests fast but still async to discover basic timing issues + oOptions.delay = oOptions.delay || 50; + + // configure mock server with the current options + var oMockserverInitialized = mockserver.init(oOptions); + + this.iWaitForPromise(oMockserverInitialized); + // start the app UI component + this.iStartMyUIComponent({ + componentConfig: { + name: "com.mrb.UI5-Testing", + async: true + }, + hash: oOptions.hash, + autoWait: oOptions.autoWait + }); + }, + + _clearSharedData: function () { + // clear shared metadata in ODataModel to allow tests for loading the metadata + ODataModel.mSharedData = { server: {}, service: {}, meta: {} }; + } + }); }); diff --git a/webapp/test/integration/opaTests.qunit.html b/webapp/test/integration/opaTests.qunit.html index 938944e..8fd7ae1 100644 --- a/webapp/test/integration/opaTests.qunit.html +++ b/webapp/test/integration/opaTests.qunit.html @@ -2,26 +2,25 @@ - - Integration tests for Todo App + Integration tests for Bulletin Board + - + + + + + - - - - - - -
-
+
+
\ No newline at end of file diff --git a/webapp/test/integration/opaTests.qunit.js b/webapp/test/integration/opaTests.qunit.js index f6eaa2f..c669203 100644 --- a/webapp/test/integration/opaTests.qunit.js +++ b/webapp/test/integration/opaTests.qunit.js @@ -1,13 +1,12 @@ /* global QUnit */ - QUnit.config.autostart = false; sap.ui.getCore().attachInit(function() { - "use strict"; + "use strict"; - sap.ui.require([ - "com/mrb/UI5-Testing/test/integration/AllJourneys" - ], function() { - QUnit.start(); - }); + sap.ui.require([ + "com/mrb/UI5-Testing/test/integration/AllJourneys" + ], function() { + QUnit.start(); + }); }); diff --git a/webapp/test/integration/pages/App.js b/webapp/test/integration/pages/App.js deleted file mode 100644 index 6428962..0000000 --- a/webapp/test/integration/pages/App.js +++ /dev/null @@ -1,34 +0,0 @@ -sap.ui.require([ - "sap/ui/test/Opa5", - "sap/ui/test/matchers/AggregationLengthEquals" -], function (Opa5, AggregationLengthEquals) { - "use strict"; - - var sViewName = "com.mrb.UI5-Testing.view.Home"; - var sAppId = "idAppControl"; - - Opa5.createPageObjects({ - onTheAppPage: { - - assertions: { - - iShouldSeePageCount: function(iItemCount) { - return this.waitFor({ - id: sAppId, - viewName: sViewName, - matchers: [new AggregationLengthEquals({ - name: "pages", - length: iItemCount - })], - success: function() { - Opa5.assert.ok(true, "The app contains one page"); - }, - errorMessage: "App does not have expected number of pages '" + iItemCount + "'." - }); - } - } - - } - }); - -}); diff --git a/webapp/test/integration/pages/Worklist.js b/webapp/test/integration/pages/Worklist.js new file mode 100644 index 0000000..c2ee413 --- /dev/null +++ b/webapp/test/integration/pages/Worklist.js @@ -0,0 +1,53 @@ +sap.ui.define([ + 'sap/ui/test/Opa5', + 'sap/ui/test/matchers/AggregationLengthEquals', + 'sap/ui/test/matchers/I18NText' + ], + function (Opa5, + AggregationLengthEquals, + I18NText) { + "use strict"; + + var sViewName = "Worklist", + sTableId = "table"; + + Opa5.createPageObjects({ + onTheWorklistPage: { + actions: {}, + assertions: { + theTableShouldHaveAllEntries: function () { + return this.waitFor({ + id: sTableId, + viewName: sViewName, + matchers: new AggregationLengthEquals({ + name: "items", + length: 23 + }), + success: function () { + Opa5.assert.ok(true, "The table has 23 items"); + }, + errorMessage: "The table does not contain all items." + }); + }, + + theTitleShouldDisplayTheTotalAmountOfItems: function () { + return this.waitFor({ + id: "tableHeader", + viewName: sViewName, + matchers: new I18NText({ + key: "worklistTableTitleCount", + propertyName: "text", + parameters: [23] + }), + success: function () { + Opa5.assert.ok(true, "The table header has 23 items"); + }, + errorMessage: "The table header does not contain the number of items: 23" + }); + } + + } + } + }); + + }); diff --git a/webapp/test/mockServer.html b/webapp/test/mockServer.html new file mode 100644 index 0000000..ebf60c4 --- /dev/null +++ b/webapp/test/mockServer.html @@ -0,0 +1,25 @@ + + + + + + Bulletin Board + + + + + +
+ + \ No newline at end of file diff --git a/webapp/test/testsuite.qunit.html b/webapp/test/testsuite.qunit.html deleted file mode 100644 index 9441c66..0000000 --- a/webapp/test/testsuite.qunit.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - QUnit test suite for Worklist - - - - - - diff --git a/webapp/test/testsuite.qunit.js b/webapp/test/testsuite.qunit.js deleted file mode 100644 index b075333..0000000 --- a/webapp/test/testsuite.qunit.js +++ /dev/null @@ -1,12 +0,0 @@ -// eslint-disable-next-line -window.suite = function() { - "use strict"; - - // eslint-disable-next-line - var oSuite = new parent.jsUnitTestSuite(), - sContextPath = location.pathname.substring(0, location.pathname.lastIndexOf("/") + 1); - - oSuite.addTestPage(sContextPath + "integration/opaTests.qunit.html"); - - return oSuite; -}; diff --git a/webapp/test/unit/AllTests.js b/webapp/test/unit/AllTests.js new file mode 100644 index 0000000..fe53fde --- /dev/null +++ b/webapp/test/unit/AllTests.js @@ -0,0 +1,6 @@ +sap.ui.define([ + "./model/models", + "./model/formatter" +], function() { + "use strict"; +}); diff --git a/webapp/test/unit/model/formatter.js b/webapp/test/unit/model/formatter.js new file mode 100644 index 0000000..1ed2ac1 --- /dev/null +++ b/webapp/test/unit/model/formatter.js @@ -0,0 +1,37 @@ +/*global QUnit*/ + +sap.ui.define([ + "com/mrb/UI5-Testing/model/formatter" +], function (formatter) { + "use strict"; + + QUnit.module("Number unit"); + + function numberUnitValueTestCase(assert, sValue, fExpectedNumber) { + // Act + var fNumber = formatter.numberUnit(sValue); + + // Assert + assert.strictEqual(fNumber, fExpectedNumber, "The rounding was correct"); + } + + QUnit.test("Should round down a 3 digit number", function (assert) { + numberUnitValueTestCase.call(this, assert, "3.123", "3.12"); + }); + + QUnit.test("Should round up a 3 digit number", function (assert) { + numberUnitValueTestCase.call(this, assert, "3.128", "3.13"); + }); + + QUnit.test("Should round a negative number", function (assert) { + numberUnitValueTestCase.call(this, assert, "-3", "-3.00"); + }); + + QUnit.test("Should round an empty string", function (assert) { + numberUnitValueTestCase.call(this, assert, "", ""); + }); + + QUnit.test("Should round a zero", function (assert) { + numberUnitValueTestCase.call(this, assert, "0", "0.00"); + }); +}); diff --git a/webapp/test/unit/model/models.js b/webapp/test/unit/model/models.js new file mode 100644 index 0000000..e49d98e --- /dev/null +++ b/webapp/test/unit/model/models.js @@ -0,0 +1,60 @@ +/*global QUnit*/ +sap.ui.define([ + "com/mrb/UI5-Testing/model/models", + "sap/ui/Device" +], function (models, Device) { + "use strict"; + + QUnit.module("createDeviceModel", { + afterEach : function () { + this.oDeviceModel.destroy(); + } + }); + + function isPhoneTestCase(assert, bIsPhone) { + // Arrange + this.stub(Device, "system", {phone: bIsPhone}); + + // System under test + this.oDeviceModel = models.createDeviceModel(); + + // Assert + assert.strictEqual(this.oDeviceModel.getData().system.phone, bIsPhone, "IsPhone property is correct"); + } + + QUnit.test("Should initialize a device model for desktop", function (assert) { + isPhoneTestCase.call(this, assert, false); + }); + + QUnit.test("Should initialize a device model for phone", function (assert) { + isPhoneTestCase.call(this, assert, true); + }); + + function isTouchTestCase(assert, bIsTouch) { + // Arrange + this.stub(Device, "support", {touch: bIsTouch}); + + // System under test + this.oDeviceModel = models.createDeviceModel(); + + // Assert + assert.strictEqual(this.oDeviceModel.getData().support.touch, bIsTouch, "IsTouch property is correct"); + } + + QUnit.test("Should initialize a device model for non touch devices", function (assert) { + isTouchTestCase.call(this, assert, false); + }); + + QUnit.test("Should initialize a device model for touch devices", function (assert) { + isTouchTestCase.call(this, assert, true); + }); + + QUnit.test("The binding mode of the device model should be one way", function (assert) { + + // System under test + this.oDeviceModel = models.createDeviceModel(); + + // Assert + assert.strictEqual(this.oDeviceModel.getDefaultBindingMode(), "OneWay", "Binding mode is correct"); + }); +}); \ No newline at end of file diff --git a/webapp/test/unit/unitTests.qunit.html b/webapp/test/unit/unitTests.qunit.html new file mode 100644 index 0000000..647824d --- /dev/null +++ b/webapp/test/unit/unitTests.qunit.html @@ -0,0 +1,33 @@ + + + + + Unit tests for Bulletin Board + + + + + + + + + + + + + + + + +
+
+ + diff --git a/webapp/test/unit/unitTests.qunit.js b/webapp/test/unit/unitTests.qunit.js new file mode 100644 index 0000000..43a4193 --- /dev/null +++ b/webapp/test/unit/unitTests.qunit.js @@ -0,0 +1,12 @@ +/* global QUnit */ +QUnit.config.autostart = false; + +sap.ui.getCore().attachInit(function () { + "use strict"; + + sap.ui.require([ + "com/mrb/UI5-Testing/test/unit/AllTests" + ], function () { + QUnit.start(); + }); +}); \ No newline at end of file diff --git a/webapp/view/App.view.xml b/webapp/view/App.view.xml index af08ffb..28d85f7 100644 --- a/webapp/view/App.view.xml +++ b/webapp/view/App.view.xml @@ -1,9 +1,7 @@ - - - + + + \ No newline at end of file diff --git a/webapp/view/Home.view.xml b/webapp/view/Home.view.xml deleted file mode 100644 index 47011b1..0000000 --- a/webapp/view/Home.view.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/webapp/view/Worklist.view.xml b/webapp/view/Worklist.view.xml new file mode 100644 index 0000000..aaa4f9f --- /dev/null +++ b/webapp/view/Worklist.view.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
\ No newline at end of file