From 2ff8fde5301ee65dc7f90f358f27c9823e3fd344 Mon Sep 17 00:00:00 2001 From: Yigit Ertoer Date: Mon, 31 Aug 2026 15:10:13 +0300 Subject: [PATCH] fix(odatav4): review and fix navigation tutorial --- packages/odatav4/README.md | 2 +- .../odatav4/steps/01/webapp/index-cdn.html | 1 - packages/odatav4/steps/01/webapp/index.html | 1 - .../webapp/localService/mockdata/people.json | 40 +- .../01/webapp/localService/mockserver.ts | 83 +++- packages/odatav4/steps/02/README.md | 92 ++-- .../02/webapp/controller/App.controller.ts | 4 +- .../odatav4/steps/02/webapp/index-cdn.html | 1 - packages/odatav4/steps/02/webapp/index.html | 1 - .../webapp/localService/mockdata/people.json | 40 +- .../02/webapp/localService/mockserver.ts | 83 +++- packages/odatav4/steps/03/README.md | 11 +- .../03/webapp/controller/App.controller.ts | 4 +- .../odatav4/steps/03/webapp/index-cdn.html | 1 - packages/odatav4/steps/03/webapp/index.html | 1 - .../webapp/localService/mockdata/people.json | 40 +- .../03/webapp/localService/mockserver.ts | 83 +++- packages/odatav4/steps/04/README.md | 227 +++++---- .../04/webapp/controller/App.controller.ts | 3 +- .../odatav4/steps/04/webapp/index-cdn.html | 1 - packages/odatav4/steps/04/webapp/index.html | 1 - .../webapp/localService/mockdata/people.json | 40 +- .../04/webapp/localService/mockserver.ts | 83 +++- .../odatav4/steps/04/webapp/view/App.view.xml | 4 +- packages/odatav4/steps/05/README.md | 10 +- .../05/webapp/controller/App.controller.ts | 3 +- .../odatav4/steps/05/webapp/index-cdn.html | 1 - packages/odatav4/steps/05/webapp/index.html | 1 - .../webapp/localService/mockdata/people.json | 40 +- .../05/webapp/localService/mockserver.ts | 83 +++- .../odatav4/steps/05/webapp/view/App.view.xml | 4 +- packages/odatav4/steps/06/README.md | 449 +++++++++--------- .../06/webapp/controller/App.controller.ts | 6 +- .../odatav4/steps/06/webapp/index-cdn.html | 1 - packages/odatav4/steps/06/webapp/index.html | 1 - .../webapp/localService/mockdata/people.json | 40 +- .../06/webapp/localService/mockserver.ts | 83 +++- .../odatav4/steps/06/webapp/view/App.view.xml | 6 +- packages/odatav4/steps/07/README.md | 193 +++++--- .../07/webapp/controller/App.controller.ts | 6 +- .../odatav4/steps/07/webapp/index-cdn.html | 1 - packages/odatav4/steps/07/webapp/index.html | 1 - .../webapp/localService/mockdata/people.json | 40 +- .../07/webapp/localService/mockserver.ts | 83 +++- packages/odatav4/steps/08/README.md | 84 ++-- .../08/webapp/controller/App.controller.ts | 6 +- .../odatav4/steps/08/webapp/index-cdn.html | 1 - packages/odatav4/steps/08/webapp/index.html | 1 - .../webapp/localService/mockdata/people.json | 40 +- .../08/webapp/localService/mockserver.ts | 83 +++- .../08/webapp/test/integration/AllJourneys.js | 13 - .../test/integration/TutorialJourney.js | 122 ----- .../test/integration/arrangements/Startup.js | 24 - .../test/integration/opaTests.qunit.html | 27 -- .../webapp/test/integration/opaTests.qunit.js | 11 - .../webapp/test/integration/pages/Tutorial.js | 300 ------------ .../odatav4/steps/08/webapp/view/App.view.xml | 2 +- packages/odatav4/steps/09/README.md | 167 ++++--- .../09/webapp/controller/App.controller.ts | 20 +- .../odatav4/steps/09/webapp/index-cdn.html | 1 - packages/odatav4/steps/09/webapp/index.html | 1 - .../webapp/localService/mockdata/people.json | 40 +- .../09/webapp/localService/mockserver.ts | 83 +++- .../odatav4/steps/09/webapp/view/App.view.xml | 2 +- packages/odatav4/steps/10/README.md | 122 +++-- .../10/webapp/controller/App.controller.ts | 22 +- .../odatav4/steps/10/webapp/index-cdn.html | 1 - packages/odatav4/steps/10/webapp/index.html | 1 - .../webapp/localService/mockdata/people.json | 40 +- .../10/webapp/localService/mockserver.ts | 83 +++- .../odatav4/steps/10/webapp/view/App.view.xml | 2 +- .../11/webapp/controller/App.controller.ts | 22 +- .../odatav4/steps/11/webapp/index-cdn.html | 1 - packages/odatav4/steps/11/webapp/index.html | 1 - .../webapp/localService/mockdata/people.json | 40 +- .../11/webapp/localService/mockserver.ts | 83 +++- .../test/integration/TutorialJourney.js | 94 +++- .../test/integration/opaTests.qunit.html | 8 +- .../webapp/test/integration/pages/Tutorial.js | 26 +- .../odatav4/steps/11/webapp/view/App.view.xml | 2 +- 80 files changed, 1747 insertions(+), 1728 deletions(-) delete mode 100644 packages/odatav4/steps/08/webapp/test/integration/AllJourneys.js delete mode 100644 packages/odatav4/steps/08/webapp/test/integration/TutorialJourney.js delete mode 100644 packages/odatav4/steps/08/webapp/test/integration/arrangements/Startup.js delete mode 100644 packages/odatav4/steps/08/webapp/test/integration/opaTests.qunit.html delete mode 100644 packages/odatav4/steps/08/webapp/test/integration/opaTests.qunit.js delete mode 100644 packages/odatav4/steps/08/webapp/test/integration/pages/Tutorial.js diff --git a/packages/odatav4/README.md b/packages/odatav4/README.md index 4ac636ccc..d02aad5f6 100644 --- a/packages/odatav4/README.md +++ b/packages/odatav4/README.md @@ -6,7 +6,7 @@ OData is a standard protocol for creating and consuming data using simple HTTP a We start with an initial app that simply retrieves data from an OData V4 service and displays it as a plain list, and progressively grow it across 11 steps. -> 💡 +> 💡 > You don't have to do all tutorial steps sequentially, you can also jump directly to any step you want. Just download the code from the previous step, and start there. > > You can view and download the files for all steps in the Demo Kit at [OData V4](https://sdk.openui5.org/#/entity/sap.ui.core.tutorial.odatav4). diff --git a/packages/odatav4/steps/01/webapp/index-cdn.html b/packages/odatav4/steps/01/webapp/index-cdn.html index e4ce80170..d550b4a6f 100644 --- a/packages/odatav4/steps/01/webapp/index-cdn.html +++ b/packages/odatav4/steps/01/webapp/index-cdn.html @@ -7,7 +7,6 @@ - - - - - - - - - -
-
- - diff --git a/packages/odatav4/steps/08/webapp/test/integration/opaTests.qunit.js b/packages/odatav4/steps/08/webapp/test/integration/opaTests.qunit.js deleted file mode 100644 index 908edf09e..000000000 --- a/packages/odatav4/steps/08/webapp/test/integration/opaTests.qunit.js +++ /dev/null @@ -1,11 +0,0 @@ -QUnit.config.autostart = false; - -sap.ui.require([ - "sap/ui/core/Core", - "ui5/tutorial/odatav4/test/integration/AllJourneys" -], function (Core) { - "use strict"; - Core.ready().then(function () { - QUnit.start(); - }); -}); diff --git a/packages/odatav4/steps/08/webapp/test/integration/pages/Tutorial.js b/packages/odatav4/steps/08/webapp/test/integration/pages/Tutorial.js deleted file mode 100644 index 29e33dacd..000000000 --- a/packages/odatav4/steps/08/webapp/test/integration/pages/Tutorial.js +++ /dev/null @@ -1,300 +0,0 @@ -sap.ui.define([ - "sap/ui/test/Opa5", - "sap/ui/test/matchers/AggregationLengthEquals", - "sap/ui/test/matchers/PropertyStrictEquals", - "sap/ui/test/matchers/BindingPath", - "sap/ui/test/actions/Press", - "sap/ui/test/actions/EnterText" -], function (Opa5, AggregationLengthEquals, PropertyStrictEquals, BindingPath, Press, EnterText) { - "use strict"; - - var sViewName = "App", - sTableId = "peopleList"; - - function getListBinding(oTable) { - return oTable.getBinding("items"); - } - - function getFirstTableEntry(oTable) { - return getListBinding(oTable).getCurrentContexts()[0]; - } - - Opa5.createPageObjects({ - onTheTutorialPage : { - actions : { - iPressOnMoreData : function () { - // Press action hits the "more" trigger on a table - return this.waitFor({ - id : sTableId, - viewName : sViewName, - actions : new Press(), - errorMessage : "Table not found or it does not have a 'See More' trigger" - }); - }, - - iPressOnSort : function () { - return this.waitFor({ - id : "sortUsersButton", - viewName : sViewName, - actions : new Press(), - errorMessage : "Could not find the 'Sort' button" - }); - }, - - iPressOnAdd : function () { - return this.waitFor({ - id : "addUserButton", - viewName : sViewName, - actions : new Press(), - errorMessage : "Could not find the 'Add' button" - }); - }, - - iPressOnDelete : function () { - return this.waitFor({ - id : "deleteUserButton", - viewName : sViewName, - actions : new Press(), - errorMessage : "Could not find the 'Delete' button" - }); - }, - - iPressOnSave : function () { - return this.waitFor({ - id : "saveButton", - viewName : sViewName, - actions : new Press(), - errorMessage : "Could not find the 'Save' button" - }); - }, - - iPressOnCancel : function () { - return this.waitFor({ - id : "doneButton", - viewName : sViewName, - actions : new Press(), - errorMessage : "Could not find the 'Cancel' button" - }); - }, - - iEnterSomeData : function (sValue) { - return this.waitFor({ - controlType : "sap.m.Input", - viewName : sViewName, - matchers : [ - // Find the input fields for the new entry - function (oControl) { - return oControl.getBindingContext().getIndex() === 0; - }, - // Keep only empty input fields - function (oItem) { - return !oItem.getValue(); - } - ], - actions : new EnterText({ - text : sValue - }), - errorMessage : "Could not find Input controls to enter data" - }); - }, - - iSearchFor : function (sSearchString) { - return this.waitFor({ - id : "searchField", - viewName : sViewName, - actions : new EnterText({ - text : sSearchString - }), - errorMessage : "SearchField was not found" - }); - }, - - iSelectUser : function (sKey) { - return this.waitFor({ - controlType : "sap.m.ColumnListItem", - viewName : sViewName, - matchers : new BindingPath({ - path : "/People('" + sKey + "')" - }), - actions : function (oItem) { - oItem.setSelected(true); - }, - errorMessage : "Could not find a user with the key '" + sKey + "'" - }); - }, - - iChangeAUserKey : function (sOldKey, sNewKey) { - return this.waitFor({ - controlType : "sap.m.Input", - viewName : sViewName, - matchers : new PropertyStrictEquals({ - name : "value", - value : sOldKey - }), - actions : new EnterText({ - text : sNewKey - }), - errorMessage : "Could not find a user with the key '" + sOldKey + "'" - }); - }, - - iCloseTheServiceError : function () { - return this.waitFor({ - id : "serviceErrorMessageBox", - success : function () { - this.waitFor({ - controlType : "sap.m.Button", - searchOpenDialogs : true, - // The error MessageBox has only one button, which closes the box - actions : new Press(), - errorMessage : "Cannot find the 'Close' button" - }); - }, - errorMessage : "Could not see the service error dialog" - }); - } - }, - assertions : { - theTableShouldHavePagination : function () { - return this.waitFor({ - id : sTableId, - viewName : sViewName, - matchers : new PropertyStrictEquals({ - name : "growing", - value : true - }), - success : function () { - Opa5.assert.ok(true, "The table is paginated"); - }, - errorMessage : "Table not found or it is not paginated" - }); - }, - - theTableShouldShowUsers : function (iNumber) { - return this.waitFor({ - id : sTableId, - viewName : sViewName, - matchers : new AggregationLengthEquals({ - name : "items", - length : iNumber - }), - success : function () { - Opa5.assert.ok(true, "The table has " - + iNumber + " items"); - }, - errorMessage : "Table not found or it does not have " - + iNumber + " entries" - }); - }, - - theTableShouldShowTotalUsers : function (iNumber) { - return this.waitFor({ - id : sTableId, - viewName : sViewName, - matchers : function (oTable) { - var oListBinding = getListBinding(oTable); - - return oListBinding && oListBinding.getLength() === iNumber; - }, - success : function () { - Opa5.assert.ok(true, "The table shows a total of " + iNumber - + " users"); - }, - errorMessage : "Table not found or it does not show " + iNumber - + " total users" - }); - }, - - theTableShouldStartWith : function (sLastName) { - return this.waitFor({ - id : sTableId, - viewName : sViewName, - matchers : function (oTable) { - var oFirstItem = getFirstTableEntry(oTable); - - return oFirstItem && oFirstItem.getProperty("LastName") === sLastName; - }, - success : function () { - Opa5.assert.ok(true, "The table is sorted correctly"); - }, - errorMessage : "Table not found or it is not sorted correctl." - }); - }, - - thePageFooterShouldBeVisible : function (bVisible) { - var sDesiredState = bVisible ? "visible" : "invisible"; - - return this.waitFor({ - controlType : "sap.m.Toolbar", - viewName : sViewName, - visible : false, - matchers : new PropertyStrictEquals({ - name : "visible", - value : bVisible - }), - success : function () { - Opa5.assert.ok(true, "The toolbar is " + sDesiredState); - }, - errorMessage : "Toolbar not found or is not " + sDesiredState - }); - }, - - theTableToolbarItemsShouldBeEnabled : function (bEnabled) { - var sDesiredState = bEnabled ? "enabled" : "disabled"; - - return this.waitFor({ - id : /searchField$|refreshUsersButton$|sortUsersButton$/, - viewName : sViewName, - autoWait : false, // Needed because we want to find disabled controls, too - matchers : new PropertyStrictEquals({ - name : "enabled", - value : bEnabled - }), - check : function (aControls) { - // Validate that ALL controls have the right state - return aControls.length === 3; - }, - success : function () { - Opa5.assert.ok(true, "All controls in the table toolbar are " - + sDesiredState); - }, - errorMessage : "Not all controls in the table toolbar could be found or " - + "not all are " + sDesiredState - }); - }, - - theMessageToastShouldShow : function (sTextId, sArg0) { - return this.waitFor({ - autoWait : false, - id : sTableId, - viewName : sViewName, - check : function (oControl) { - // Locate the message toast using its CSS class name and content - var sText = oControl.getModel("i18n").getResourceBundle() - .getText(sTextId, [sArg0]), - sSelector = ".sapMMessageToast:contains('" + sText + "')"; - - return !!Opa5.getJQuery()(sSelector).length; - }, - success : function () { - Opa5.assert.ok(true, "Could see the MessageToast showing text with ID " - + sTextId); - }, - errorMessage : "Could not see a MessageToast showing text with ID " - + sTextId - }); - }, - - iShouldSeeAServiceError : function () { - return this.waitFor({ - id : "serviceErrorMessageBox", - success : function () { - Opa5.assert.ok(true, "Could see the service error dialog"); - }, - errorMessage : "Could not see the service error dialog" - }); - } - } - } - }); -}); diff --git a/packages/odatav4/steps/08/webapp/view/App.view.xml b/packages/odatav4/steps/08/webapp/view/App.view.xml index 5159a0bab..3ab385c15 100644 --- a/packages/odatav4/steps/08/webapp/view/App.view.xml +++ b/packages/odatav4/steps/08/webapp/view/App.view.xml @@ -12,7 +12,7 @@ id="resetChangesButton" text="{i18n>resetChangesButtonText}" enabled="{= !${appView>/hasUIChanges}}" - press="onResetDataSource" + press=".onResetDataSource" type="Emphasized"> diff --git a/packages/odatav4/steps/09/README.md b/packages/odatav4/steps/09/README.md index 664f096d3..381e30674 100644 --- a/packages/odatav4/steps/09/README.md +++ b/packages/odatav4/steps/09/README.md @@ -19,117 +19,112 @@ You can download the solution for this step here: [📥 Do ```ts // webapp/controller/App.controller.ts ... - onDelete() { - const oContext, - oPeopleList = this.byId("peopleList"), - oSelected = oPeopleList.getSelectedItem(), - sUserName; - - if (oSelected) { - oContext = oSelected.getBindingContext(); - sUserName = oContext.getProperty("UserName"); - oContext.delete().then(function () { - MessageToast.show(this._getText("deletionSuccessMessage", sUserName)); - }.bind(this), function (oError) { - if (oContext === oPeopleList.getSelectedItem().getBindingContext()) { - this._setDetailArea(oContext); + onDelete(): void { + const peopleList = this.byId("peopleList") as List; + const selected = peopleList.getSelectedItem() as ColumnListItem | null; + + if (selected) { + const context = selected.getBindingContext() as Context; + const userName = context.getProperty("UserName") as string; + void context.delete().then(() => { + MessageToast.show(this._getText("deletionSuccessMessage", [userName])); + }, (error2: Error & { canceled?: boolean }) => { + const currentSelected = peopleList.getSelectedItem() as ColumnListItem | null; + if (currentSelected && context === currentSelected.getBindingContext()) { + this._setDetailArea(context); } this._setUIChanges(); - if (oError.canceled) { - MessageToast.show(this._getText("deletionRestoredMessage", sUserName)); + if (error2.canceled) { + MessageToast.show(this._getText("deletionRestoredMessage", [userName])); return; } - MessageBox.error(oError.message + ": " + sUserName); - }.bind(this)); + MessageBox.error(error2.message + ": " + userName); + }); this._setDetailArea(); - this._setUIChanges(true); + this._setUIChanges(); } }, ... - onMessageBindingChange(oEvent) { + onMessageBindingChange(event: Event): void { ... }, - onSelectionChange(oEvent) { - this._setDetailArea(oEvent.getParameter("listItem").getBindingContext()); + onSelectionChange(event: Event<{ listItem: ColumnListItem }>): void { + const listItem = event.getParameter("listItem"); + this._setDetailArea(listItem.getBindingContext() as Context); }, ... - /** - * Toggles the visibility of the detail area - * - * @param {object} [oUserContext] - the current user context - */ - _setDetailArea(oUserContext) { - const oDetailArea = this.byId("detailArea"), - oLayout = this.byId("defaultLayout"), - oSearchField = this.byId("searchField"); - - oDetailArea.setBindingContext(oUserContext || null); + _setDetailArea(userContext?: Context): void { + const detailArea = this.byId("detailArea") as Control; + const layout = this.byId("defaultLayout") as SplitterLayoutData; + const searchField = this.byId("searchField") as SearchField; + + if (!detailArea) { + return; // do nothing during view destruction + } + + detailArea.setBindingContext(userContext || null); // resize view - oDetailArea.setVisible(!!oUserContext); - oLayout.setSize(oUserContext ? "60%" : "100%"); - oLayout.setResizable(!!oUserContext); - oSearchField.setWidth(oUserContext ? "40%" : "20%"); + detailArea.setVisible(!!userContext); + layout.setSize(userContext ? "60%" : "100%"); + layout.setResizable(!!userContext); + searchField.setWidth(userContext ? "40%" : "20%"); } ``` ```js // webapp/controller/App.controller.js ... - onDelete : function () { - var oContext, - oPeopleList = this.byId("peopleList"), - oSelected = oPeopleList.getSelectedItem(), - sUserName; - - if (oSelected) { - oContext = oSelected.getBindingContext(); - sUserName = oContext.getProperty("UserName"); - oContext.delete().then(function () { - MessageToast.show(this._getText("deletionSuccessMessage", sUserName)); - }.bind(this), function (oError) { - if (oContext === oPeopleList.getSelectedItem().getBindingContext()) { - this._setDetailArea(oContext); - } - this._setUIChanges(); - if (oError.canceled) { - MessageToast.show(this._getText("deletionRestoredMessage", sUserName)); - return; - } - MessageBox.error(oError.message + ": " + sUserName); - }.bind(this)); - this._setDetailArea(); - this._setUIChanges(true); - } - }, + onDelete() { + const peopleList = this.byId("peopleList"); + const selected = peopleList.getSelectedItem(); + if (selected) { + const context = selected.getBindingContext(); + const userName = context.getProperty("UserName"); + void context.delete().then(() => { + MessageToast.show(this._getText("deletionSuccessMessage", [userName])); + }, error2 => { + const currentSelected = peopleList.getSelectedItem(); + if (currentSelected && context === currentSelected.getBindingContext()) { + this._setDetailArea(context); + } + this._setUIChanges(); + if (error2.canceled) { + MessageToast.show(this._getText("deletionRestoredMessage", [userName])); + return; + } + MessageBox.error(error2.message + ": " + userName); + }); + this._setDetailArea(); + this._setUIChanges(); + } + }, ... - onMessageBindingChange : function (oEvent) { - ... - }, + onMessageBindingChange(event) { + ... + }, - onSelectionChange : function (oEvent) { - this._setDetailArea(oEvent.getParameter("listItem").getBindingContext()); - }, + onSelectionChange(event) { + const listItem = event.getParameter("listItem"); + this._setDetailArea(listItem.getBindingContext()); + }, ... - /** - * Toggles the visibility of the detail area - * - * @param {object} [oUserContext] - the current user context - */ - _setDetailArea : function (oUserContext) { - var oDetailArea = this.byId("detailArea"), - oLayout = this.byId("defaultLayout"), - oSearchField = this.byId("searchField"); - - oDetailArea.setBindingContext(oUserContext || null); - // resize view - oDetailArea.setVisible(!!oUserContext); - oLayout.setSize(oUserContext ? "60%" : "100%"); - oLayout.setResizable(!!oUserContext); - oSearchField.setWidth(oUserContext ? "40%" : "20%"); - } + _setDetailArea(userContext) { + const detailArea = this.byId("detailArea"); + const layout = this.byId("defaultLayout"); + const searchField = this.byId("searchField"); + if (!detailArea) { + return; // do nothing during view destruction + } + detailArea.setBindingContext(userContext || null); + // resize view + detailArea.setVisible(!!userContext); + layout.setSize(userContext ? "60%" : "100%"); + layout.setResizable(!!userContext); + searchField.setWidth(userContext ? "40%" : "20%"); + } ``` The `onSelectionChange` event handler retrieves the context of the selected list item and passes it to a new `_setDetailArea` function. Within `_setDetailArea`, the given context is passed as binding context for the semantic page detail area. diff --git a/packages/odatav4/steps/09/webapp/controller/App.controller.ts b/packages/odatav4/steps/09/webapp/controller/App.controller.ts index 664d4e8bf..7de283657 100644 --- a/packages/odatav4/steps/09/webapp/controller/App.controller.ts +++ b/packages/odatav4/steps/09/webapp/controller/App.controller.ts @@ -19,13 +19,15 @@ import type Input from "sap/m/Input"; import type Context from "sap/ui/model/odata/v4/Context"; import type ODataModel from "sap/ui/model/odata/v4/ODataModel"; import type ODataListBinding from "sap/ui/model/odata/v4/ODataListBinding"; +import type SplitterLayoutData from "sap/ui/layout/SplitterLayoutData"; +import type Control from "sap/ui/core/Control"; /** * @namespace ui5.tutorial.odatav4.controller */ export default class App extends Controller { - private _bTechnicalErrors = false; + private _bTechnicalErrors: boolean; /** * Hook for initializing the controller @@ -108,8 +110,8 @@ export default class App extends Controller { /** * Lock UI when changing data in the input controls */ - onInputChange(evt: Event): void { - if ((evt as unknown as { getParameter(n: string): unknown }).getParameter("escPressed")) { + onInputChange(evt: Event<{ escPressed: boolean }>): void { + if (evt.getParameter("escPressed")) { this._setUIChanges(); } else { this._setUIChanges(true); @@ -239,8 +241,8 @@ export default class App extends Controller { messageOpen = true; } - onSelectionChange(event: Event): void { - const listItem = (event as unknown as { getParameter(n: string): unknown }).getParameter("listItem") as ColumnListItem; + onSelectionChange(event: Event<{ listItem: ColumnListItem }>): void { + const listItem = event.getParameter("listItem"); this._setDetailArea(listItem.getBindingContext() as Context); } @@ -285,17 +287,17 @@ export default class App extends Controller { * @param oUserContext - the current user context */ _setDetailArea(userContext?: Context): void { - const detailArea = this.byId("detailArea"); - const layout = this.byId("defaultLayout") as unknown as { setSize(s: string): void; setResizable(b: boolean): void }; + const detailArea = this.byId("detailArea") as Control; + const layout = this.byId("defaultLayout") as SplitterLayoutData; const searchField = this.byId("searchField") as SearchField; if (!detailArea) { return; // do nothing during view destruction } - (detailArea as unknown as { setBindingContext(c: Context | null): void }).setBindingContext(userContext || null); + detailArea.setBindingContext(userContext || null); // resize view - (detailArea as unknown as { setVisible(b: boolean): void }).setVisible(!!userContext); + detailArea.setVisible(!!userContext); layout.setSize(userContext ? "60%" : "100%"); layout.setResizable(!!userContext); searchField.setWidth(userContext ? "40%" : "20%"); diff --git a/packages/odatav4/steps/09/webapp/index-cdn.html b/packages/odatav4/steps/09/webapp/index-cdn.html index e4ce80170..d550b4a6f 100644 --- a/packages/odatav4/steps/09/webapp/index-cdn.html +++ b/packages/odatav4/steps/09/webapp/index-cdn.html @@ -7,7 +7,6 @@ - + - - + + diff --git a/packages/odatav4/steps/11/webapp/test/integration/pages/Tutorial.js b/packages/odatav4/steps/11/webapp/test/integration/pages/Tutorial.js index fedf45bed..d13d480af 100644 --- a/packages/odatav4/steps/11/webapp/test/integration/pages/Tutorial.js +++ b/packages/odatav4/steps/11/webapp/test/integration/pages/Tutorial.js @@ -32,6 +32,15 @@ sap.ui.define([ }); }, + iPressOnRefresh : function () { + return this.waitFor({ + id : "refreshUsersButton", + viewName : view, + actions : new Press(), + errorMessage : "Could not find the 'Refresh' button" + }); + }, + iPressOnSort : function () { return this.waitFor({ id : "sortUsersButton", @@ -229,7 +238,22 @@ sap.ui.define([ success : function () { Opa5.assert.ok(true, "The table is sorted correctly"); }, - errorMessage : "Table not found or it is not sorted correctl." + errorMessage : "Table not found or it is not sorted correctly." + }); + }, + + theTableShouldEndWith : function (sLastName) { + return this.waitFor({ + id : sTableId, + viewName : view, + matchers : function (oTable) { + return getListBinding(oTable).getCurrentContexts().pop() + ?.getProperty("LastName") === sLastName; + }, + success : function () { + Opa5.assert.ok(true, "The table ends with " + sLastName); + }, + errorMessage : "Table not found or it is not sorted correctly." }); }, diff --git a/packages/odatav4/steps/11/webapp/view/App.view.xml b/packages/odatav4/steps/11/webapp/view/App.view.xml index 08e36fad0..1e2df56fe 100644 --- a/packages/odatav4/steps/11/webapp/view/App.view.xml +++ b/packages/odatav4/steps/11/webapp/view/App.view.xml @@ -16,7 +16,7 @@ id="resetChangesButton" text="{i18n>resetChangesButtonText}" enabled="{= !${appView>/hasUIChanges}}" - press="onResetDataSource" + press=".onResetDataSource" type="Emphasized">