From 6ca33597976d9836ec726eaf5b14776a8d9a717d Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Mon, 27 Jan 2025 12:37:29 +0200 Subject: [PATCH 1/5] fix: bind i18n models with namespace --- .../src/cpe/changes/flex-change.ts | 2 +- .../src/cpe/changes/validator.ts | 30 ++++++++++--------- .../src/messagebundle.properties | 5 ++++ .../test/unit/cpe/changes/validator.spec.ts | 26 ++++++++++++---- 4 files changed, 42 insertions(+), 21 deletions(-) diff --git a/packages/preview-middleware-client/src/cpe/changes/flex-change.ts b/packages/preview-middleware-client/src/cpe/changes/flex-change.ts index 34acbc9bd3..55a6069e57 100644 --- a/packages/preview-middleware-client/src/cpe/changes/flex-change.ts +++ b/packages/preview-middleware-client/src/cpe/changes/flex-change.ts @@ -35,7 +35,7 @@ export async function applyChange(options: UI5AdaptationOptions, change: Propert const changeType = isBindingString ? 'BindProperty' : 'Property'; if (isBindingModel) { - validateBindingModel(modifiedControl, change.value as string); + await validateBindingModel(modifiedControl, change.value as string); } const property = isBindingString ? 'newBinding' : 'newValue'; diff --git a/packages/preview-middleware-client/src/cpe/changes/validator.ts b/packages/preview-middleware-client/src/cpe/changes/validator.ts index 381dbb1985..137e8b55e3 100644 --- a/packages/preview-middleware-client/src/cpe/changes/validator.ts +++ b/packages/preview-middleware-client/src/cpe/changes/validator.ts @@ -1,6 +1,7 @@ import ResourceBundle from 'sap/base/i18n/ResourceBundle'; import ResourceModel from 'sap/ui/model/resource/ResourceModel'; import type UI5Element from 'sap/ui/core/Element'; +import { getTextBundle } from '../../i18n'; /** * Function to validate if a given value is a valid binding model. @@ -8,26 +9,27 @@ import type UI5Element from 'sap/ui/core/Element'; * @param modifiedControl control to be modified. * @param value value to be checked. */ -export function validateBindingModel(modifiedControl: UI5Element, value: string): void { +export async function validateBindingModel(modifiedControl: UI5Element, value: string): Promise { + const textBundle = await getTextBundle(); const bindingValue = value.replace(/[{}]/gi, '').trim(); const bindingParts = bindingValue.split('>').filter((el) => el !== ''); if (!bindingParts.length) { - throw new SyntaxError('Invalid binding string.'); + throw new SyntaxError(textBundle.getText('INVALID_BINDING_STRING')); } - if (bindingParts[0].trim() === 'i18n') { - if (bindingParts.length === 2) { - const resourceKey = bindingParts[1].trim(); - const resourceBundle = (modifiedControl.getModel('i18n') as ResourceModel).getResourceBundle() as ResourceBundle; - if (!resourceBundle.getText(resourceKey, undefined, true)) { - throw new SyntaxError( - 'Invalid key in the binding string. Supported value pattern is {i18n>YOUR_KEY}. Check if the key already exists in i18n.properties.' + - 'If not, add the key in the i18n.properties file and reload the editor for the new key to take effect.' - ); - } - } else { - throw new SyntaxError('Invalid binding string. Supported value pattern is {i18n>YOUR_KEY}'); + if (bindingParts.length === 2) { + const bindingModel = bindingParts[0]; + const resourceKey = bindingParts[1].trim(); + const resourceModel = (modifiedControl.getModel(bindingModel) as ResourceModel); + if(!resourceModel) { + throw new SyntaxError(textBundle.getText('INVALID_BINDING_MODEL')); } + const resourceBundle = resourceModel.getResourceBundle() as ResourceBundle; + if (!resourceBundle.getText(resourceKey, undefined, true)) { + throw new SyntaxError(textBundle.getText('INVALID_BINDING_MODEL_KEY')); + } + } else { + throw new SyntaxError(textBundle.getText('INVALID_BINDING_STRING_FORMAT')); } } diff --git a/packages/preview-middleware-client/src/messagebundle.properties b/packages/preview-middleware-client/src/messagebundle.properties index 879675988d..2142c7d408 100644 --- a/packages/preview-middleware-client/src/messagebundle.properties +++ b/packages/preview-middleware-client/src/messagebundle.properties @@ -56,3 +56,8 @@ EXTEND_WITH_ANNOTATION=Extend With Annotation ANNOTATION_FILE_HAS_BEEN_FOUND=An annotation file has been found. SHOW_FILE_IN_VSCODE=Show File in VSCode +INVALID_BINDING_STRING=Invalid binding string. +INVALID_BINDING_STRING_FORMAT=Invalid binding string. Supported value pattern is {i18n>YOUR_KEY} +INVALID_BINDING_MODEL=Invalid binding model. +INVALID_BINDING_MODEL_KEY=Invalid key in the binding string. Supported value pattern is {i18n>YOUR_KEY}. Check if the key already exists in i18n.properties.If not, add the key in the i18n.properties file and reload the editor for the new key to take effect. + diff --git a/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts b/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts index 33ec1d513a..f4b112364c 100644 --- a/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts +++ b/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts @@ -11,27 +11,27 @@ describe('vaildateBindingModel', () => { ) }) }; - test('should throw when invalid binding model string is provided', () => { + test('should throw when invalid binding model string is provided', async () => { try { - validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{}'); + await validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{}'); } catch (error) { expect(error).toBeInstanceOf(SyntaxError); expect(error.message).toBe('Invalid binding string.'); } }); - test('should throw when invalid binding string for i18n model is provided', () => { + test('should throw when invalid binding string for i18n model is provided', async () => { try { - validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{ i18n }'); + await validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{ i18n }'); } catch (error) { expect(error).toBeInstanceOf(SyntaxError); expect(error.message).toBe('Invalid binding string. Supported value pattern is {i18n>YOUR_KEY}'); } }); - test('should throw when the provided key does not exist in i18n.properties', () => { + test('should throw when the provided key does not exist in i18n.properties', async () => { try { - validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{ i18n>test }'); + await validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{ i18n>test }'); } catch (error) { expect(error).toBeInstanceOf(SyntaxError); expect(error.message).toBe( @@ -39,4 +39,18 @@ describe('vaildateBindingModel', () => { ); } }); + + test('should throw error when invalid binding model is provided', async () => { + const control = { + getModel: jest.fn().mockReturnValue(undefined) + }; + try { + await validateBindingModel(control as unknown as UI5Element, '{ i18n|namespace>test }'); + } catch (error) { + expect(error).toBeInstanceOf(SyntaxError); + expect(error.message).toBe( + 'Invalid binding model.' + ); + } + }); }); From b9bc48cb4fe1144ecf40713256d2ed37858f04e8 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Mon, 27 Jan 2025 12:41:03 +0200 Subject: [PATCH 2/5] chore: add changeset --- .changeset/four-suits-run.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/four-suits-run.md diff --git a/.changeset/four-suits-run.md b/.changeset/four-suits-run.md new file mode 100644 index 0000000000..ecb4ceb260 --- /dev/null +++ b/.changeset/four-suits-run.md @@ -0,0 +1,6 @@ +--- +'@sap-ux-private/preview-middleware-client': patch +'@sap-ux/preview-middleware': patch +--- + +Bind i18n models with namespace From 5a9e40ce4e86dc03085498a616793ed688885268 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Fri, 31 Jan 2025 11:02:31 +0200 Subject: [PATCH 3/5] test: enhance tests --- .../test/unit/cpe/changes/validator.spec.ts | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts b/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts index f4b112364c..82604acd16 100644 --- a/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts +++ b/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts @@ -1,5 +1,8 @@ import type UI5Element from 'sap/ui/core/Element'; import { validateBindingModel } from '../../../../src/cpe/changes/validator'; +import { getTextBundle } from '../../../../src/i18n'; + +jest.mock('../../../../src/i18n'); describe('vaildateBindingModel', () => { const mockModifiedcontrol = { @@ -11,46 +14,42 @@ describe('vaildateBindingModel', () => { ) }) }; + + let getTextMock: jest.Mock; + + beforeEach(() => { + getTextMock = jest.fn(); + + (getTextBundle as jest.Mock).mockResolvedValue({ + hasText: jest.fn().mockReturnValue(true), + getText: getTextMock + }); + }); + + afterEach(() => { + getTextMock.mockClear(); + }) + test('should throw when invalid binding model string is provided', async () => { - try { - await validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{}'); - } catch (error) { - expect(error).toBeInstanceOf(SyntaxError); - expect(error.message).toBe('Invalid binding string.'); - } + getTextMock.mockReturnValue('Invalid binding string.'); + await expect(async () => await validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{}')).rejects.toThrow('Invalid binding string.'); }); test('should throw when invalid binding string for i18n model is provided', async () => { - try { - await validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{ i18n }'); - } catch (error) { - expect(error).toBeInstanceOf(SyntaxError); - expect(error.message).toBe('Invalid binding string. Supported value pattern is {i18n>YOUR_KEY}'); - } + getTextMock.mockReturnValue('Invalid binding string. Supported value pattern is {i18n>YOUR_KEY}'); + await expect(async () => await validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{ i18n }')).rejects.toThrow('Invalid binding string. Supported value pattern is {i18n>YOUR_KEY}'); }); test('should throw when the provided key does not exist in i18n.properties', async () => { - try { - await validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{ i18n>test }'); - } catch (error) { - expect(error).toBeInstanceOf(SyntaxError); - expect(error.message).toBe( - 'Invalid key in the binding string. Supported value pattern is {i18n>YOUR_KEY}. Check if the key already exists in i18n.properties.If not, add the key in the i18n.properties file and reload the editor for the new key to take effect.' - ); - } + getTextMock.mockReturnValue('Invalid key in the binding string. Supported value pattern is {i18n>YOUR_KEY}. Check if the key already exists in i18n.properties.If not, add the key in the i18n.properties file and reload the editor for the new key to take effect.'); + await expect(async () => await validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{ i18n>test }')).rejects.toThrow('Invalid key in the binding string. Supported value pattern is {i18n>YOUR_KEY}. Check if the key already exists in i18n.properties.If not, add the key in the i18n.properties file and reload the editor for the new key to take effect.'); }); test('should throw error when invalid binding model is provided', async () => { + getTextMock.mockReturnValue('Invalid binding model.'); const control = { getModel: jest.fn().mockReturnValue(undefined) }; - try { - await validateBindingModel(control as unknown as UI5Element, '{ i18n|namespace>test }'); - } catch (error) { - expect(error).toBeInstanceOf(SyntaxError); - expect(error.message).toBe( - 'Invalid binding model.' - ); - } + await expect(async () => await validateBindingModel(control as unknown as UI5Element, '{ i18n>test }')).rejects.toThrow('Invalid binding model.'); }); }); From 01e1b3c6181c58f823972d0704ffb89afc9eecd2 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Fri, 31 Jan 2025 11:22:08 +0200 Subject: [PATCH 4/5] refactor: remove redundant await --- .../test/unit/cpe/changes/validator.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts b/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts index 82604acd16..c958c7064e 100644 --- a/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts +++ b/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts @@ -32,17 +32,17 @@ describe('vaildateBindingModel', () => { test('should throw when invalid binding model string is provided', async () => { getTextMock.mockReturnValue('Invalid binding string.'); - await expect(async () => await validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{}')).rejects.toThrow('Invalid binding string.'); + await expect(() => validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{}')).rejects.toThrow('Invalid binding string.'); }); test('should throw when invalid binding string for i18n model is provided', async () => { getTextMock.mockReturnValue('Invalid binding string. Supported value pattern is {i18n>YOUR_KEY}'); - await expect(async () => await validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{ i18n }')).rejects.toThrow('Invalid binding string. Supported value pattern is {i18n>YOUR_KEY}'); + await expect(() => validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{ i18n }')).rejects.toThrow('Invalid binding string. Supported value pattern is {i18n>YOUR_KEY}'); }); test('should throw when the provided key does not exist in i18n.properties', async () => { getTextMock.mockReturnValue('Invalid key in the binding string. Supported value pattern is {i18n>YOUR_KEY}. Check if the key already exists in i18n.properties.If not, add the key in the i18n.properties file and reload the editor for the new key to take effect.'); - await expect(async () => await validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{ i18n>test }')).rejects.toThrow('Invalid key in the binding string. Supported value pattern is {i18n>YOUR_KEY}. Check if the key already exists in i18n.properties.If not, add the key in the i18n.properties file and reload the editor for the new key to take effect.'); + await expect(() => validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{ i18n>test }')).rejects.toThrow('Invalid key in the binding string. Supported value pattern is {i18n>YOUR_KEY}. Check if the key already exists in i18n.properties.If not, add the key in the i18n.properties file and reload the editor for the new key to take effect.'); }); test('should throw error when invalid binding model is provided', async () => { @@ -50,6 +50,6 @@ describe('vaildateBindingModel', () => { const control = { getModel: jest.fn().mockReturnValue(undefined) }; - await expect(async () => await validateBindingModel(control as unknown as UI5Element, '{ i18n>test }')).rejects.toThrow('Invalid binding model.'); + await expect(() => validateBindingModel(control as unknown as UI5Element, '{ i18n>test }')).rejects.toThrow('Invalid binding model.'); }); }); From b189be929499bdc5d2a8e6e628bc58462efbce5d Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Fri, 31 Jan 2025 15:07:50 +0200 Subject: [PATCH 5/5] refactor: remove redundant mocks --- .../test/unit/cpe/changes/validator.spec.ts | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts b/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts index c958c7064e..55c89a67bd 100644 --- a/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts +++ b/packages/preview-middleware-client/test/unit/cpe/changes/validator.spec.ts @@ -1,8 +1,5 @@ import type UI5Element from 'sap/ui/core/Element'; import { validateBindingModel } from '../../../../src/cpe/changes/validator'; -import { getTextBundle } from '../../../../src/i18n'; - -jest.mock('../../../../src/i18n'); describe('vaildateBindingModel', () => { const mockModifiedcontrol = { @@ -15,38 +12,19 @@ describe('vaildateBindingModel', () => { }) }; - let getTextMock: jest.Mock; - - beforeEach(() => { - getTextMock = jest.fn(); - - (getTextBundle as jest.Mock).mockResolvedValue({ - hasText: jest.fn().mockReturnValue(true), - getText: getTextMock - }); - }); - - afterEach(() => { - getTextMock.mockClear(); - }) - test('should throw when invalid binding model string is provided', async () => { - getTextMock.mockReturnValue('Invalid binding string.'); await expect(() => validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{}')).rejects.toThrow('Invalid binding string.'); }); test('should throw when invalid binding string for i18n model is provided', async () => { - getTextMock.mockReturnValue('Invalid binding string. Supported value pattern is {i18n>YOUR_KEY}'); await expect(() => validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{ i18n }')).rejects.toThrow('Invalid binding string. Supported value pattern is {i18n>YOUR_KEY}'); }); test('should throw when the provided key does not exist in i18n.properties', async () => { - getTextMock.mockReturnValue('Invalid key in the binding string. Supported value pattern is {i18n>YOUR_KEY}. Check if the key already exists in i18n.properties.If not, add the key in the i18n.properties file and reload the editor for the new key to take effect.'); await expect(() => validateBindingModel(mockModifiedcontrol as unknown as UI5Element, '{ i18n>test }')).rejects.toThrow('Invalid key in the binding string. Supported value pattern is {i18n>YOUR_KEY}. Check if the key already exists in i18n.properties.If not, add the key in the i18n.properties file and reload the editor for the new key to take effect.'); }); test('should throw error when invalid binding model is provided', async () => { - getTextMock.mockReturnValue('Invalid binding model.'); const control = { getModel: jest.fn().mockReturnValue(undefined) };