From b200c15978cfd83667ee5ee2c5d39ae4ab76a3b3 Mon Sep 17 00:00:00 2001 From: Oliwer Christ Date: Wed, 8 Apr 2026 08:40:24 +0200 Subject: [PATCH 1/2] feat(dts-generator): add bindProperty declarations and tests to TypedJSONModel --- .../src/resources/typed-json-model.d.ts | 17 ++++++ test-packages/typed-json-model/package.json | 2 +- test-packages/typed-json-model/tsconfig.json | 3 +- .../typed-json-model/webapp/model/model.ts | 20 ++++++ .../webapp/model/test/cases/generalCases.ts | 61 +++++++++++++++++++ 5 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 test-packages/typed-json-model/webapp/model/test/cases/generalCases.ts diff --git a/packages/dts-generator/src/resources/typed-json-model.d.ts b/packages/dts-generator/src/resources/typed-json-model.d.ts index 5d33d33..a3bbe08 100644 --- a/packages/dts-generator/src/resources/typed-json-model.d.ts +++ b/packages/dts-generator/src/resources/typed-json-model.d.ts @@ -2,6 +2,7 @@ declare module "sap/ui/model/json/TypedJSONModel" { import JSONModel from "sap/ui/model/json/JSONModel"; import TypedJSONContext from "sap/ui/model/json/TypedJSONContext"; import Context from "sap/ui/model/Context"; + import PropertyBinding from "sap/ui/model/PropertyBinding"; /** * TypedJSONModel is a subclass of JSONModel that provides type-safe access to the model data. It is only available when using UI5 with TypeScript. @@ -30,6 +31,22 @@ declare module "sap/ui/model/json/TypedJSONModel" { oContext: TypedJSONContext, ): PropertyByRelativeBindingPath; + // Overload for absolute paths + bindProperty>( + sPath: Path, + oContext?: undefined, + mParameters?: object, + ): PropertyBinding; + // Overload for relative paths + bindProperty< + Path extends RelativeBindingPath, + Root extends AbsoluteBindingPath, + >( + sPath: Path, + oContext: TypedJSONContext, + mParameters?: object, + ): PropertyBinding; + setData(oData: Data, bMerge?: boolean): void; // setProperty with AbsoluteBindingPath (context === undefined), diff --git a/test-packages/typed-json-model/package.json b/test-packages/typed-json-model/package.json index 290cf06..e712066 100644 --- a/test-packages/typed-json-model/package.json +++ b/test-packages/typed-json-model/package.json @@ -16,7 +16,7 @@ "ci": "npm run lint && npm run ui5lint && npm run ts-typecheck && npm run test" }, "devDependencies": { - "@types/openui5": "1.136.0", + "@openui5/types": "1.146.0", "@ui5/cli": "^4.0.30", "@ui5/linter": "^1.20.2", "eslint": "^9.37.0", diff --git a/test-packages/typed-json-model/tsconfig.json b/test-packages/typed-json-model/tsconfig.json index eca45e6..182cca5 100644 --- a/test-packages/typed-json-model/tsconfig.json +++ b/test-packages/typed-json-model/tsconfig.json @@ -11,7 +11,8 @@ "baseUrl": "./", "paths": {}, "composite": true, - "outDir": "./dist" + "outDir": "./dist", + "types": ["@openui5/types"] }, "include": ["./webapp/**/*"], "exclude": ["./**/*.mjs", "./webapp/**/test/**"] diff --git a/test-packages/typed-json-model/webapp/model/model.ts b/test-packages/typed-json-model/webapp/model/model.ts index a3b70ad..9e641cf 100644 --- a/test-packages/typed-json-model/webapp/model/model.ts +++ b/test-packages/typed-json-model/webapp/model/model.ts @@ -1,4 +1,5 @@ import Context from "sap/ui/model/Context"; +import PropertyBinding from "sap/ui/model/PropertyBinding"; import JSONModel from "sap/ui/model/json/JSONModel"; import { AbsoluteBindingPath, @@ -57,6 +58,25 @@ export class TypedJSONModel extends JSONModel { | PropertyByRelativeBindingPath; } + // Overload for absolute paths + bindProperty>( + sPath: Path, + oContext?: undefined, + mParameters?: object, + ): PropertyBinding; + // Overload for relative paths + bindProperty, Root extends AbsoluteBindingPath>( + sPath: Path, + oContext: TypedJSONContext, + mParameters?: object, + ): PropertyBinding; + bindProperty< + Path extends AbsoluteBindingPath | RelativeBindingPath, + Root extends AbsoluteBindingPath, + >(sPath: Path, oContext?: TypedJSONContext, mParameters?: object): PropertyBinding { + return super.bindProperty(sPath, oContext, mParameters); + } + setData(oData: Data, bMerge?: boolean): void { super.setData(oData, bMerge); } diff --git a/test-packages/typed-json-model/webapp/model/test/cases/generalCases.ts b/test-packages/typed-json-model/webapp/model/test/cases/generalCases.ts new file mode 100644 index 0000000..d045e0e --- /dev/null +++ b/test-packages/typed-json-model/webapp/model/test/cases/generalCases.ts @@ -0,0 +1,61 @@ +/** + * @file Various general test cases to test the TypedJSONModel for APIs which always return the same type, + * regardless of the provided path (e.g. getObject, getPath, etc.) + */ + +import { TypedJSONModel } from "../../model"; +import PropertyBinding from "sap/ui/model/PropertyBinding"; + +interface Person { + name: string; + age: number; + address: { + city: string; + zip: string; + }; + orders: Array<{ + id: number; + product: string; + }>; +} + +const data: Person = { + name: "John", + age: 30, + address: { city: "Walldorf", zip: "69190" }, + orders: [{ id: 1, product: "UI5" }], +}; + +/*********************************************************************************************************************** + * bindProperty - Absolute cases + **********************************************************************************************************************/ + +const model0 = new TypedJSONModel(data); + +/** @expect ok */ let propertyBindingAbsolute: PropertyBinding = model0.bindProperty("/name"); +/** @expect ok */ propertyBindingAbsolute = model0.bindProperty("/age"); +/** @expect ok */ propertyBindingAbsolute = model0.bindProperty("/address/city"); +/** @expect ok */ propertyBindingAbsolute = model0.bindProperty("/address/zip"); +/** @expect ok */ propertyBindingAbsolute = model0.bindProperty("/orders/0/id"); +/** @expect ok */ propertyBindingAbsolute = model0.bindProperty("/orders/0/product"); + +/** @expect ts2345 */ propertyBindingAbsolute = model0.bindProperty("/phone"); +/** @expect ts2345 */ propertyBindingAbsolute = model0.bindProperty("/address/country"); +/** @expect ts2345 */ propertyBindingAbsolute = model0.bindProperty("/orders/0/price"); +/** @expect ts2345 */ propertyBindingAbsolute = model0.bindProperty("/doesNotExist"); +/** @expect ok */ propertyBindingAbsolute = model0.bindProperty("/name", undefined); +/** @expect ok */ propertyBindingAbsolute = model0.bindProperty("/age", undefined); + +/*********************************************************************************************************************** + * bindProperty - Relative cases + **********************************************************************************************************************/ + +const context = model0.createBindingContext("/address"); + +/** @expect ok */ let propertyBindingRelative: PropertyBinding = model0.bindProperty("city", context); +/** @expect ok */ propertyBindingRelative = model0.bindProperty("zip", context); + +/** @expect ts2769 */ propertyBindingRelative = model0.bindProperty("country", context); +/** @expect ts2769 */ propertyBindingRelative = model0.bindProperty("phone", context); + +/** @expect ts2769 */ propertyBindingAbsolute = model0.bindProperty("/name", "not-a-context"); From 8016d061f8df24734a43201da5a1f4f44c65c471 Mon Sep 17 00:00:00 2001 From: Oliwer Christ Date: Wed, 8 Apr 2026 15:25:17 +0200 Subject: [PATCH 2/2] chore: rename generalCases.ts to general.ts --- .../webapp/model/test/cases/{generalCases.ts => general.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test-packages/typed-json-model/webapp/model/test/cases/{generalCases.ts => general.ts} (100%) diff --git a/test-packages/typed-json-model/webapp/model/test/cases/generalCases.ts b/test-packages/typed-json-model/webapp/model/test/cases/general.ts similarity index 100% rename from test-packages/typed-json-model/webapp/model/test/cases/generalCases.ts rename to test-packages/typed-json-model/webapp/model/test/cases/general.ts