From 2081be222a8efde880f02b39d3a22a370e24ccc8 Mon Sep 17 00:00:00 2001 From: Raj-bytecommandor Date: Tue, 21 Oct 2025 16:02:12 +0530 Subject: [PATCH 1/7] (fix) Implementer tools: Prevent crash when hovering over tree-level descriptions --- .../config-subtree.component.tsx | 43 +++--- .../translations/en.json | 76 +++++----- .../apps/esm-login-app/translations/en.json | 82 +++++------ .../translations/en.json | 114 +++++++-------- .../translations/en.json | 28 ++-- .../esm-translations/translations/en.json | 132 +++++++++--------- 6 files changed, 239 insertions(+), 236 deletions(-) diff --git a/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx b/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx index 574029031..46f4cea52 100644 --- a/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx +++ b/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx @@ -12,12 +12,13 @@ export interface ConfigSubtreeProps { export function ConfigSubtree({ config, path = [] }: ConfigSubtreeProps) { function setActiveItemDescriptionOnMouseEnter(thisPath, key, value) { if (!implementerToolsStore.getState().configPathBeingEdited) { + const isLeaf = value.hasOwnProperty('_value') || value.hasOwnProperty('_type'); implementerToolsStore.setState({ activeItemDescription: { path: thisPath, source: value._source, description: value._description, - value: JSON.stringify(value._value), + value: isLeaf ? JSON.stringify(value._value) : undefined, }, }); } @@ -32,25 +33,27 @@ export function ConfigSubtree({ config, path = [] }: ConfigSubtreeProps) { return ( <> - {Object.entries(config).map(([key, value], i) => { - const thisPath = path.concat([key]); - const isLeaf = value.hasOwnProperty('_value') || value.hasOwnProperty('_type'); - return ( - setActiveItemDescriptionOnMouseEnter(thisPath, key, value)} - onMouseLeave={() => removeActiveItemDescriptionOnMouseLeave(thisPath)} - key={`subtree-${thisPath.join('.')}`} - > - {isLeaf ? ( - - ) : ( - - )} - - ); - })} + {Object.entries(config) + .filter(([key]) => !key.startsWith('_')) + .map(([key, value], i) => { + const thisPath = path.concat([key]); + const isLeaf = value.hasOwnProperty('_value') || value.hasOwnProperty('_type'); + return ( + setActiveItemDescriptionOnMouseEnter(thisPath, key, value)} + onMouseLeave={() => removeActiveItemDescriptionOnMouseLeave(thisPath)} + key={`subtree-${thisPath.join('.')}`} + > + {isLeaf ? ( + + ) : ( + + )} + + ); + })} ); } diff --git a/packages/apps/esm-implementer-tools-app/translations/en.json b/packages/apps/esm-implementer-tools-app/translations/en.json index 36d97aa5c..fd4b12676 100644 --- a/packages/apps/esm-implementer-tools-app/translations/en.json +++ b/packages/apps/esm-implementer-tools-app/translations/en.json @@ -1,38 +1,38 @@ -{ - "activeItemSourceText": "The current value comes from ", - "arrayValidationMessage": "Value must be an array", - "backendModules": "Backend modules", - "booleanValidationMessage": "Value must be a boolean", - "checkImplementerToolsMessage": "Check the Backend Modules tab in the Implementer Tools for more details", - "clearConfig": "Clear local config", - "close": "Close", - "configuration": "Configuration", - "description": "Description", - "downloadConfig": "Download config", - "edit": "Edit", - "editValueButtonText": "Edit", - "enabled": "Enabled", - "extensions": "Extensions", - "featureFlag": "Feature flag", - "featureFlags": "Feature flags", - "frontendModules": "Frontend modules", - "implementerTools": "Implementer Tools", - "installedVersion": "Installed version", - "itemDescriptionSourceDefaultText": "The current value is the default.", - "jsonEditor": "JSON editor", - "missing": "Missing", - "moduleName": "Module name", - "modulesWithMissingDependenciesWarning": "Some modules have unresolved backend dependencies", - "numberValidationMessage": "Value must be a number", - "objectValidationMessage": "Value must be an object", - "requiredVersion": "Required Version", - "resetToDefaultValueButtonText": "Reset to default", - "stringValidationMessage": "Value must be a string", - "toggleImplementerTools": "Toggle Implementer Tools", - "uiEditor": "UI editor", - "unknownVersion": "unknown", - "updateConfig": "Update config", - "uuidValidationMessage": "Value must be a valid UUID string", - "value": "Value", - "viewModules": "View modules" -} +{ + "activeItemSourceText": "The current value comes from ", + "arrayValidationMessage": "Value must be an array", + "backendModules": "Backend modules", + "booleanValidationMessage": "Value must be a boolean", + "checkImplementerToolsMessage": "Check the Backend Modules tab in the Implementer Tools for more details", + "clearConfig": "Clear local config", + "close": "Close", + "configuration": "Configuration", + "description": "Description", + "downloadConfig": "Download config", + "edit": "Edit", + "editValueButtonText": "Edit", + "enabled": "Enabled", + "extensions": "Extensions", + "featureFlag": "Feature flag", + "featureFlags": "Feature flags", + "frontendModules": "Frontend modules", + "implementerTools": "Implementer Tools", + "installedVersion": "Installed version", + "itemDescriptionSourceDefaultText": "The current value is the default.", + "jsonEditor": "JSON editor", + "missing": "Missing", + "moduleName": "Module name", + "modulesWithMissingDependenciesWarning": "Some modules have unresolved backend dependencies", + "numberValidationMessage": "Value must be a number", + "objectValidationMessage": "Value must be an object", + "requiredVersion": "Required Version", + "resetToDefaultValueButtonText": "Reset to default", + "stringValidationMessage": "Value must be a string", + "toggleImplementerTools": "Toggle Implementer Tools", + "uiEditor": "UI editor", + "unknownVersion": "unknown", + "updateConfig": "Update config", + "uuidValidationMessage": "Value must be a valid UUID string", + "value": "Value", + "viewModules": "View modules" +} diff --git a/packages/apps/esm-login-app/translations/en.json b/packages/apps/esm-login-app/translations/en.json index 4675c40c4..62391ba8b 100644 --- a/packages/apps/esm-login-app/translations/en.json +++ b/packages/apps/esm-login-app/translations/en.json @@ -1,41 +1,41 @@ -{ - "builtWith": "Built with", - "cancel": "Cancel", - "change": "Change", - "changeLocation": "Change location", - "changePassword": "Change password", - "changingPassword": "Changing password", - "confirmPassword": "Confirm new password", - "continue": "Continue", - "errorChangingPassword": "Error changing password", - "footerlogo": "Footer Logo", - "invalidCredentials": "Invalid username or password", - "learnMore": "Learn more", - "locationPreferenceRemoved": "Login location preference removed", - "locationPreferenceRemovedMessage": "You will need to select a location on each login", - "locationSaved": "Location saved", - "locationSaveMessage": "Your preferred location has been saved for future logins", - "locationUpdated": "Location updated", - "locationUpdateMessage": "Your preferred login location has been updated", - "loggingIn": "Logging in", - "login": "Log in", - "loginButtonIconDescription": "Log in button", - "Logout": "Logout", - "newPassword": "New password", - "newPasswordRequired": "New password is required", - "oldPassword": "Old password", - "oldPasswordRequired": "Old password is required", - "openmrsLogo": "OpenMRS logo", - "password": "Password", - "passwordChangedSuccessfully": "Password changed successfully", - "passwordConfirmationRequired": "Password confirmation is required", - "passwordsDoNotMatch": "Passwords do not match", - "poweredBySubtext": "An open-source medical record system and global community", - "rememberLocationForFutureLogins": "Remember my location for future logins", - "selectYourLocation": "Select your location from the list below. Use the search bar to find your location.", - "showPassword": "Show password", - "submitting": "Submitting", - "username": "Username", - "validValueRequired": "A valid value is required", - "welcome": "Welcome" -} +{ + "builtWith": "Built with", + "cancel": "Cancel", + "change": "Change", + "changeLocation": "Change location", + "changePassword": "Change password", + "changingPassword": "Changing password", + "confirmPassword": "Confirm new password", + "continue": "Continue", + "errorChangingPassword": "Error changing password", + "footerlogo": "Footer Logo", + "invalidCredentials": "Invalid username or password", + "learnMore": "Learn more", + "locationPreferenceRemoved": "Login location preference removed", + "locationPreferenceRemovedMessage": "You will need to select a location on each login", + "locationSaved": "Location saved", + "locationSaveMessage": "Your preferred location has been saved for future logins", + "locationUpdated": "Location updated", + "locationUpdateMessage": "Your preferred login location has been updated", + "loggingIn": "Logging in", + "login": "Log in", + "loginButtonIconDescription": "Log in button", + "Logout": "Logout", + "newPassword": "New password", + "newPasswordRequired": "New password is required", + "oldPassword": "Old password", + "oldPasswordRequired": "Old password is required", + "openmrsLogo": "OpenMRS logo", + "password": "Password", + "passwordChangedSuccessfully": "Password changed successfully", + "passwordConfirmationRequired": "Password confirmation is required", + "passwordsDoNotMatch": "Passwords do not match", + "poweredBySubtext": "An open-source medical record system and global community", + "rememberLocationForFutureLogins": "Remember my location for future logins", + "selectYourLocation": "Select your location from the list below. Use the search bar to find your location.", + "showPassword": "Show password", + "submitting": "Submitting", + "username": "Username", + "validValueRequired": "A valid value is required", + "welcome": "Welcome" +} diff --git a/packages/apps/esm-offline-tools-app/translations/en.json b/packages/apps/esm-offline-tools-app/translations/en.json index 304df0615..e3d8fec24 100644 --- a/packages/apps/esm-offline-tools-app/translations/en.json +++ b/packages/apps/esm-offline-tools-app/translations/en.json @@ -1,57 +1,57 @@ -{ - "emptyStateText": "There are no {{displayText}} to display", - "home": "Home", - "homeHeader": "Offline home", - "homeOverviewCardOfflineActionsFailedToUpload": "Failed to upload", - "homeOverviewCardOfflineActionsHeader": "Offline Actions", - "homeOverviewCardOfflineActionsPendingUpload": "Pending upload", - "homeOverviewCardPatientsDownloaded": "Downloaded", - "homeOverviewCardPatientsHeader": "Patients", - "homeOverviewCardPatientsNewlyRegistered": "Newly registered", - "homeOverviewCardView": "View", - "offlineActions": "Offline Actions", - "offlineActionsDeleteConfirmationModalCancel": "Cancel", - "offlineActionsDeleteConfirmationModalConfirm": "Delete forever", - "offlineActionsDeleteConfirmationModalContent": "Are you sure that you want to delete all selected offline actions? This cannot be undone!", - "offlineActionsDeleteConfirmationModalTitle": "Delete offline actions", - "offlineActionsHeader": "Offline Actions", - "offlineActionsNoActionsEmptyStateContent": "All offline actions have been uploaded successfully,\nand merged with the online patient records.", - "offlineActionsNoActionsEmptyStateImageAlt": "No Pending Actions Image", - "offlineActionsNoActionsEmptyStateTitle": "No actions pending upload", - "offlineActionsTableAction": "Action", - "offlineActionsTableCreatedOn": "Date & Time", - "offlineActionsTableDeleteActions_one": "Delete action", - "offlineActionsTableDeleteActions_other": "Delete {{count}} actions", - "offlineActionsTableError": "Error", - "offlineActionsTablePatient": "Patient", - "offlineActionsUpdateOfflinePatients": "Update offline patients", - "offlinePatients": "Offline patients", - "offlinePatients_lower": "offline patients", - "offlinePatientsHeader": "Offline patients", - "offlinePatientsTableDeleteConfirmationModalCancel": "Cancel", - "offlinePatientsTableDeleteConfirmationModalConfirm": "Remove patients", - "offlinePatientsTableDeleteConfirmationModalContent": "Are you sure that you want to remove all selected patients from the offline list? Their charts will no longer be available in offline mode and any newly registered patient will be permanently deleted.", - "offlinePatientsTableDeleteConfirmationModalTitle": "Remove offline patients", - "offlinePatientsTableHeaderAge": "Age", - "offlinePatientsTableHeaderGender": "Gender", - "offlinePatientsTableHeaderLastUpdated": "Last updated", - "offlinePatientsTableHeaderName": "Name", - "offlinePatientsTableLastUpdatedDownloading": "Downloading...", - "offlinePatientsTableLastUpdatedError": "error", - "offlinePatientsTableLastUpdatedErrors": "errors", - "offlinePatientsTableLastUpdatedNotYetSynchronized": "Not synchronized", - "offlinePatientsTableLastUpdatedOutdatedData": "Outdated data", - "offlinePatientsTableNameNewlyRegistered": "New", - "offlinePatientsTableRemoveFromOfflineList": "Remove from list", - "offlinePatientsTableSearchLabel": "Search this list", - "offlinePatientsTableSearchPlaceholder": "Search this list", - "offlinePatientsTableTitle": "Offline patients", - "offlinePatientsTableUpdatePatient": "Update patient", - "offlinePatientsTableUpdatePatients": "Update patients", - "offlinePatientSyncDetailsDownloadedHeader": "Downloaded to this device", - "offlinePatientSyncDetailsFailedHeader": "There was an error downloading the following items", - "offlinePatientSyncDetailsFallbackErrorMessage": "Unknown error.", - "offlinePatientSyncDetailsHeader": "Offline patient details", - "offlineReady": "Offline Ready", - "offlineToolsAppMenuLink": "Offline tools" -} +{ + "emptyStateText": "There are no {{displayText}} to display", + "home": "Home", + "homeHeader": "Offline home", + "homeOverviewCardOfflineActionsFailedToUpload": "Failed to upload", + "homeOverviewCardOfflineActionsHeader": "Offline Actions", + "homeOverviewCardOfflineActionsPendingUpload": "Pending upload", + "homeOverviewCardPatientsDownloaded": "Downloaded", + "homeOverviewCardPatientsHeader": "Patients", + "homeOverviewCardPatientsNewlyRegistered": "Newly registered", + "homeOverviewCardView": "View", + "offlineActions": "Offline Actions", + "offlineActionsDeleteConfirmationModalCancel": "Cancel", + "offlineActionsDeleteConfirmationModalConfirm": "Delete forever", + "offlineActionsDeleteConfirmationModalContent": "Are you sure that you want to delete all selected offline actions? This cannot be undone!", + "offlineActionsDeleteConfirmationModalTitle": "Delete offline actions", + "offlineActionsHeader": "Offline Actions", + "offlineActionsNoActionsEmptyStateContent": "All offline actions have been uploaded successfully,\nand merged with the online patient records.", + "offlineActionsNoActionsEmptyStateImageAlt": "No Pending Actions Image", + "offlineActionsNoActionsEmptyStateTitle": "No actions pending upload", + "offlineActionsTableAction": "Action", + "offlineActionsTableCreatedOn": "Date & Time", + "offlineActionsTableDeleteActions_one": "Delete action", + "offlineActionsTableDeleteActions_other": "Delete {{count}} actions", + "offlineActionsTableError": "Error", + "offlineActionsTablePatient": "Patient", + "offlineActionsUpdateOfflinePatients": "Update offline patients", + "offlinePatients": "Offline patients", + "offlinePatients_lower": "offline patients", + "offlinePatientsHeader": "Offline patients", + "offlinePatientsTableDeleteConfirmationModalCancel": "Cancel", + "offlinePatientsTableDeleteConfirmationModalConfirm": "Remove patients", + "offlinePatientsTableDeleteConfirmationModalContent": "Are you sure that you want to remove all selected patients from the offline list? Their charts will no longer be available in offline mode and any newly registered patient will be permanently deleted.", + "offlinePatientsTableDeleteConfirmationModalTitle": "Remove offline patients", + "offlinePatientsTableHeaderAge": "Age", + "offlinePatientsTableHeaderGender": "Gender", + "offlinePatientsTableHeaderLastUpdated": "Last updated", + "offlinePatientsTableHeaderName": "Name", + "offlinePatientsTableLastUpdatedDownloading": "Downloading...", + "offlinePatientsTableLastUpdatedError": "error", + "offlinePatientsTableLastUpdatedErrors": "errors", + "offlinePatientsTableLastUpdatedNotYetSynchronized": "Not synchronized", + "offlinePatientsTableLastUpdatedOutdatedData": "Outdated data", + "offlinePatientsTableNameNewlyRegistered": "New", + "offlinePatientsTableRemoveFromOfflineList": "Remove from list", + "offlinePatientsTableSearchLabel": "Search this list", + "offlinePatientsTableSearchPlaceholder": "Search this list", + "offlinePatientsTableTitle": "Offline patients", + "offlinePatientsTableUpdatePatient": "Update patient", + "offlinePatientsTableUpdatePatients": "Update patients", + "offlinePatientSyncDetailsDownloadedHeader": "Downloaded to this device", + "offlinePatientSyncDetailsFailedHeader": "There was an error downloading the following items", + "offlinePatientSyncDetailsFallbackErrorMessage": "Unknown error.", + "offlinePatientSyncDetailsHeader": "Offline patient details", + "offlineReady": "Offline Ready", + "offlineToolsAppMenuLink": "Offline tools" +} diff --git a/packages/apps/esm-primary-navigation-app/translations/en.json b/packages/apps/esm-primary-navigation-app/translations/en.json index fdfafb6e8..d310a03d0 100644 --- a/packages/apps/esm-primary-navigation-app/translations/en.json +++ b/packages/apps/esm-primary-navigation-app/translations/en.json @@ -1,14 +1,14 @@ -{ - "AppMenuTooltip": "App Menu", - "cancel": "Cancel", - "change": "Change", - "changeDefaultLocale": "Update your default locale", - "changeDefaultLocaleExplanation": "Leave this unchecked to change language for this session only", - "changeLanguage": "Change language", - "changingLanguage": "Changing language", - "noPathInDashboardExtension": "Cannot render the dashboard extension without the property \"path\" being set in the configuration schema", - "notifications": "Notifications", - "userMenu": "User menu", - "userMenuOptions": "User menu options", - "userMenuTooltip": "My Account" -} +{ + "AppMenuTooltip": "App Menu", + "cancel": "Cancel", + "change": "Change", + "changeDefaultLocale": "Update your default locale", + "changeDefaultLocaleExplanation": "Leave this unchecked to change language for this session only", + "changeLanguage": "Change language", + "changingLanguage": "Changing language", + "noPathInDashboardExtension": "Cannot render the dashboard extension without the property \"path\" being set in the configuration schema", + "notifications": "Notifications", + "userMenu": "User menu", + "userMenuOptions": "User menu options", + "userMenuTooltip": "My Account" +} diff --git a/packages/framework/esm-translations/translations/en.json b/packages/framework/esm-translations/translations/en.json index 08af9ef00..ca4371ae3 100644 --- a/packages/framework/esm-translations/translations/en.json +++ b/packages/framework/esm-translations/translations/en.json @@ -1,66 +1,66 @@ -{ - "actions": "Actions", - "address": "Address", - "address1": "Address line 1", - "address2": "Address line 2", - "address3": "Address line 3", - "address4": "Address line 4", - "address5": "Address line 5", - "address6": "Address line 6", - "age": "Age", - "cancel": "Cancel", - "change": "Change", - "city": "City", - "cityVillage": "City", - "Clinic": "Clinic", - "close": "Close", - "closeAllOpenedWorkspaces": "Discard changes in {{count}} workspaces", - "closeWorkspaces2PromptBody": "You are about to close the following workspace(s), which might have unsaved changes:", - "closeWorkspaces2PromptTitle": "Close workspace(s)", - "closingAllWorkspacesPromptBody": "There may be unsaved changes in the following workspaces. Do you want to discard changes in the following workspaces? {{workspaceNames}}", - "closingAllWorkspacesPromptTitle": "You have unsaved changes", - "confirm": "Confirm", - "contactAdministratorIfIssuePersists": "Contact your system administrator if the problem persists.", - "contactDetails": "Contact Details", - "country": "Country", - "countyDistrict": "District", - "delete": "Delete", - "discard": "Discard", - "district": "District", - "edit": "Edit", - "error": "Error", - "errorCopy": "Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above.", - "female": "Female", - "hide": "Hide", - "loading": "Loading", - "male": "Male", - "maximize": "Maximize", - "minimize": "Minimize", - "openAnyway": "Open Anyway", - "other": "Other", - "patientIdentifierSticker": "Patient identifier sticker", - "patientLists": "Patient Lists", - "postalCode": "Postal code", - "print": "Print", - "printError": "Print error", - "printErrorExplainer": "An error occurred in {{errorLocation}}", - "printIdentifierSticker": "Print identifier sticker", - "printing": "Printing", - "relationships": "Relationships", - "resetOverrides": "Reset overrides", - "save": "Save", - "scriptLoadingError": "Failed to load overridden script from {{url}}. Please check that the bundled script is available at the expected URL. Click the button below to reset all import map overrides.", - "scriptLoadingFailed": "Error: Script failed to load", - "seeMoreLists": "See {{count}} more lists", - "sex": "Sex", - "showLess": "Show less", - "showMore": "Show more", - "state": "State", - "stateProvince": "State", - "toggleDevTools": "Toggle dev tools", - "unknown": "Unknown", - "unsavedChangesInOpenedWorkspace": "You may have unsaved changes in the opened workspace. Do you want to discard these changes?", - "unsavedChangesInWorkspace": "There may be unsaved changes in \"{{workspaceName}}\". Please save them before opening another workspace.", - "unsavedChangesTitleText": "Unsaved changes", - "workspaceHeader": "Workspace header" -} +{ + "actions": "Actions", + "address": "Address", + "address1": "Address line 1", + "address2": "Address line 2", + "address3": "Address line 3", + "address4": "Address line 4", + "address5": "Address line 5", + "address6": "Address line 6", + "age": "Age", + "cancel": "Cancel", + "change": "Change", + "city": "City", + "cityVillage": "City", + "Clinic": "Clinic", + "close": "Close", + "closeAllOpenedWorkspaces": "Discard changes in {{count}} workspaces", + "closeWorkspaces2PromptBody": "You are about to close the following workspace(s), which might have unsaved changes:", + "closeWorkspaces2PromptTitle": "Close workspace(s)", + "closingAllWorkspacesPromptBody": "There may be unsaved changes in the following workspaces. Do you want to discard changes in the following workspaces? {{workspaceNames}}", + "closingAllWorkspacesPromptTitle": "You have unsaved changes", + "confirm": "Confirm", + "contactAdministratorIfIssuePersists": "Contact your system administrator if the problem persists.", + "contactDetails": "Contact Details", + "country": "Country", + "countyDistrict": "District", + "delete": "Delete", + "discard": "Discard", + "district": "District", + "edit": "Edit", + "error": "Error", + "errorCopy": "Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above.", + "female": "Female", + "hide": "Hide", + "loading": "Loading", + "male": "Male", + "maximize": "Maximize", + "minimize": "Minimize", + "openAnyway": "Open Anyway", + "other": "Other", + "patientIdentifierSticker": "Patient identifier sticker", + "patientLists": "Patient Lists", + "postalCode": "Postal code", + "print": "Print", + "printError": "Print error", + "printErrorExplainer": "An error occurred in {{errorLocation}}", + "printIdentifierSticker": "Print identifier sticker", + "printing": "Printing", + "relationships": "Relationships", + "resetOverrides": "Reset overrides", + "save": "Save", + "scriptLoadingError": "Failed to load overridden script from {{url}}. Please check that the bundled script is available at the expected URL. Click the button below to reset all import map overrides.", + "scriptLoadingFailed": "Error: Script failed to load", + "seeMoreLists": "See {{count}} more lists", + "sex": "Sex", + "showLess": "Show less", + "showMore": "Show more", + "state": "State", + "stateProvince": "State", + "toggleDevTools": "Toggle dev tools", + "unknown": "Unknown", + "unsavedChangesInOpenedWorkspace": "You may have unsaved changes in the opened workspace. Do you want to discard these changes?", + "unsavedChangesInWorkspace": "There may be unsaved changes in \"{{workspaceName}}\". Please save them before opening another workspace.", + "unsavedChangesTitleText": "Unsaved changes", + "workspaceHeader": "Workspace header" +} From 9148da5e5e8ae21fb212ebea604390368f017ed3 Mon Sep 17 00:00:00 2001 From: Raj-bytecommandor Date: Wed, 22 Oct 2025 14:32:23 +0530 Subject: [PATCH 2/7] fix: use Object.hasOwn() and revert en.json changes --- .../config-subtree.component.tsx | 4 +- .../translations/en.json | 76 +++++----- .../apps/esm-login-app/translations/en.json | 82 +++++------ .../translations/en.json | 114 +++++++-------- .../translations/en.json | 28 ++-- .../esm-translations/translations/en.json | 132 +++++++++--------- 6 files changed, 218 insertions(+), 218 deletions(-) diff --git a/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx b/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx index 46f4cea52..56747ec3b 100644 --- a/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx +++ b/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx @@ -12,7 +12,7 @@ export interface ConfigSubtreeProps { export function ConfigSubtree({ config, path = [] }: ConfigSubtreeProps) { function setActiveItemDescriptionOnMouseEnter(thisPath, key, value) { if (!implementerToolsStore.getState().configPathBeingEdited) { - const isLeaf = value.hasOwnProperty('_value') || value.hasOwnProperty('_type'); + const isLeaf = Object.hasOwn(value, '_value'); implementerToolsStore.setState({ activeItemDescription: { path: thisPath, @@ -37,7 +37,7 @@ export function ConfigSubtree({ config, path = [] }: ConfigSubtreeProps) { .filter(([key]) => !key.startsWith('_')) .map(([key, value], i) => { const thisPath = path.concat([key]); - const isLeaf = value.hasOwnProperty('_value') || value.hasOwnProperty('_type'); + const isLeaf = Object.hasOwn(value, '_value'); return ( Date: Thu, 23 Oct 2025 17:42:58 +0530 Subject: [PATCH 3/7] fix: add type guards and fix lodash.unset usage in implementer tools - Add type checks before Object.hasOwn() to prevent TypeError on null/undefined values - Fix improper use of lodash.unset by cloning state before mutation - Follows immutable state update patterns to prevent store corruption --- .../config-subtree.component.tsx | 4 +- .../editable-value.component.tsx | 4 +- .../translations/en.json | 76 +++++----- .../apps/esm-login-app/translations/en.json | 82 +++++------ .../translations/en.json | 114 +++++++-------- .../translations/en.json | 28 ++-- .../esm-translations/translations/en.json | 132 +++++++++--------- 7 files changed, 221 insertions(+), 219 deletions(-) diff --git a/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx b/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx index 56747ec3b..0f0f5ff8a 100644 --- a/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx +++ b/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx @@ -12,7 +12,7 @@ export interface ConfigSubtreeProps { export function ConfigSubtree({ config, path = [] }: ConfigSubtreeProps) { function setActiveItemDescriptionOnMouseEnter(thisPath, key, value) { if (!implementerToolsStore.getState().configPathBeingEdited) { - const isLeaf = Object.hasOwn(value, '_value'); + const isLeaf = value && typeof value === 'object' && Object.hasOwn(value, '_value'); implementerToolsStore.setState({ activeItemDescription: { path: thisPath, @@ -37,7 +37,7 @@ export function ConfigSubtree({ config, path = [] }: ConfigSubtreeProps) { .filter(([key]) => !key.startsWith('_')) .map(([key, value], i) => { const thisPath = path.concat([key]); - const isLeaf = Object.hasOwn(value, '_value'); + const isLeaf = value && typeof value === 'object' && Object.hasOwn(value, '_value'); return ( { clearConfigErrors(path.join('.')); - temporaryConfigStore.setState(unset(temporaryConfigStore.getState(), ['config', ...path]) as any); + const state = cloneDeep(temporaryConfigStore.getState()); + unset(state, ['config', ...path]); + temporaryConfigStore.setState(state); }} /> ) : null} diff --git a/packages/apps/esm-implementer-tools-app/translations/en.json b/packages/apps/esm-implementer-tools-app/translations/en.json index 36d97aa5c..fd4b12676 100644 --- a/packages/apps/esm-implementer-tools-app/translations/en.json +++ b/packages/apps/esm-implementer-tools-app/translations/en.json @@ -1,38 +1,38 @@ -{ - "activeItemSourceText": "The current value comes from ", - "arrayValidationMessage": "Value must be an array", - "backendModules": "Backend modules", - "booleanValidationMessage": "Value must be a boolean", - "checkImplementerToolsMessage": "Check the Backend Modules tab in the Implementer Tools for more details", - "clearConfig": "Clear local config", - "close": "Close", - "configuration": "Configuration", - "description": "Description", - "downloadConfig": "Download config", - "edit": "Edit", - "editValueButtonText": "Edit", - "enabled": "Enabled", - "extensions": "Extensions", - "featureFlag": "Feature flag", - "featureFlags": "Feature flags", - "frontendModules": "Frontend modules", - "implementerTools": "Implementer Tools", - "installedVersion": "Installed version", - "itemDescriptionSourceDefaultText": "The current value is the default.", - "jsonEditor": "JSON editor", - "missing": "Missing", - "moduleName": "Module name", - "modulesWithMissingDependenciesWarning": "Some modules have unresolved backend dependencies", - "numberValidationMessage": "Value must be a number", - "objectValidationMessage": "Value must be an object", - "requiredVersion": "Required Version", - "resetToDefaultValueButtonText": "Reset to default", - "stringValidationMessage": "Value must be a string", - "toggleImplementerTools": "Toggle Implementer Tools", - "uiEditor": "UI editor", - "unknownVersion": "unknown", - "updateConfig": "Update config", - "uuidValidationMessage": "Value must be a valid UUID string", - "value": "Value", - "viewModules": "View modules" -} +{ + "activeItemSourceText": "The current value comes from ", + "arrayValidationMessage": "Value must be an array", + "backendModules": "Backend modules", + "booleanValidationMessage": "Value must be a boolean", + "checkImplementerToolsMessage": "Check the Backend Modules tab in the Implementer Tools for more details", + "clearConfig": "Clear local config", + "close": "Close", + "configuration": "Configuration", + "description": "Description", + "downloadConfig": "Download config", + "edit": "Edit", + "editValueButtonText": "Edit", + "enabled": "Enabled", + "extensions": "Extensions", + "featureFlag": "Feature flag", + "featureFlags": "Feature flags", + "frontendModules": "Frontend modules", + "implementerTools": "Implementer Tools", + "installedVersion": "Installed version", + "itemDescriptionSourceDefaultText": "The current value is the default.", + "jsonEditor": "JSON editor", + "missing": "Missing", + "moduleName": "Module name", + "modulesWithMissingDependenciesWarning": "Some modules have unresolved backend dependencies", + "numberValidationMessage": "Value must be a number", + "objectValidationMessage": "Value must be an object", + "requiredVersion": "Required Version", + "resetToDefaultValueButtonText": "Reset to default", + "stringValidationMessage": "Value must be a string", + "toggleImplementerTools": "Toggle Implementer Tools", + "uiEditor": "UI editor", + "unknownVersion": "unknown", + "updateConfig": "Update config", + "uuidValidationMessage": "Value must be a valid UUID string", + "value": "Value", + "viewModules": "View modules" +} diff --git a/packages/apps/esm-login-app/translations/en.json b/packages/apps/esm-login-app/translations/en.json index 4675c40c4..62391ba8b 100644 --- a/packages/apps/esm-login-app/translations/en.json +++ b/packages/apps/esm-login-app/translations/en.json @@ -1,41 +1,41 @@ -{ - "builtWith": "Built with", - "cancel": "Cancel", - "change": "Change", - "changeLocation": "Change location", - "changePassword": "Change password", - "changingPassword": "Changing password", - "confirmPassword": "Confirm new password", - "continue": "Continue", - "errorChangingPassword": "Error changing password", - "footerlogo": "Footer Logo", - "invalidCredentials": "Invalid username or password", - "learnMore": "Learn more", - "locationPreferenceRemoved": "Login location preference removed", - "locationPreferenceRemovedMessage": "You will need to select a location on each login", - "locationSaved": "Location saved", - "locationSaveMessage": "Your preferred location has been saved for future logins", - "locationUpdated": "Location updated", - "locationUpdateMessage": "Your preferred login location has been updated", - "loggingIn": "Logging in", - "login": "Log in", - "loginButtonIconDescription": "Log in button", - "Logout": "Logout", - "newPassword": "New password", - "newPasswordRequired": "New password is required", - "oldPassword": "Old password", - "oldPasswordRequired": "Old password is required", - "openmrsLogo": "OpenMRS logo", - "password": "Password", - "passwordChangedSuccessfully": "Password changed successfully", - "passwordConfirmationRequired": "Password confirmation is required", - "passwordsDoNotMatch": "Passwords do not match", - "poweredBySubtext": "An open-source medical record system and global community", - "rememberLocationForFutureLogins": "Remember my location for future logins", - "selectYourLocation": "Select your location from the list below. Use the search bar to find your location.", - "showPassword": "Show password", - "submitting": "Submitting", - "username": "Username", - "validValueRequired": "A valid value is required", - "welcome": "Welcome" -} +{ + "builtWith": "Built with", + "cancel": "Cancel", + "change": "Change", + "changeLocation": "Change location", + "changePassword": "Change password", + "changingPassword": "Changing password", + "confirmPassword": "Confirm new password", + "continue": "Continue", + "errorChangingPassword": "Error changing password", + "footerlogo": "Footer Logo", + "invalidCredentials": "Invalid username or password", + "learnMore": "Learn more", + "locationPreferenceRemoved": "Login location preference removed", + "locationPreferenceRemovedMessage": "You will need to select a location on each login", + "locationSaved": "Location saved", + "locationSaveMessage": "Your preferred location has been saved for future logins", + "locationUpdated": "Location updated", + "locationUpdateMessage": "Your preferred login location has been updated", + "loggingIn": "Logging in", + "login": "Log in", + "loginButtonIconDescription": "Log in button", + "Logout": "Logout", + "newPassword": "New password", + "newPasswordRequired": "New password is required", + "oldPassword": "Old password", + "oldPasswordRequired": "Old password is required", + "openmrsLogo": "OpenMRS logo", + "password": "Password", + "passwordChangedSuccessfully": "Password changed successfully", + "passwordConfirmationRequired": "Password confirmation is required", + "passwordsDoNotMatch": "Passwords do not match", + "poweredBySubtext": "An open-source medical record system and global community", + "rememberLocationForFutureLogins": "Remember my location for future logins", + "selectYourLocation": "Select your location from the list below. Use the search bar to find your location.", + "showPassword": "Show password", + "submitting": "Submitting", + "username": "Username", + "validValueRequired": "A valid value is required", + "welcome": "Welcome" +} diff --git a/packages/apps/esm-offline-tools-app/translations/en.json b/packages/apps/esm-offline-tools-app/translations/en.json index 304df0615..e3d8fec24 100644 --- a/packages/apps/esm-offline-tools-app/translations/en.json +++ b/packages/apps/esm-offline-tools-app/translations/en.json @@ -1,57 +1,57 @@ -{ - "emptyStateText": "There are no {{displayText}} to display", - "home": "Home", - "homeHeader": "Offline home", - "homeOverviewCardOfflineActionsFailedToUpload": "Failed to upload", - "homeOverviewCardOfflineActionsHeader": "Offline Actions", - "homeOverviewCardOfflineActionsPendingUpload": "Pending upload", - "homeOverviewCardPatientsDownloaded": "Downloaded", - "homeOverviewCardPatientsHeader": "Patients", - "homeOverviewCardPatientsNewlyRegistered": "Newly registered", - "homeOverviewCardView": "View", - "offlineActions": "Offline Actions", - "offlineActionsDeleteConfirmationModalCancel": "Cancel", - "offlineActionsDeleteConfirmationModalConfirm": "Delete forever", - "offlineActionsDeleteConfirmationModalContent": "Are you sure that you want to delete all selected offline actions? This cannot be undone!", - "offlineActionsDeleteConfirmationModalTitle": "Delete offline actions", - "offlineActionsHeader": "Offline Actions", - "offlineActionsNoActionsEmptyStateContent": "All offline actions have been uploaded successfully,\nand merged with the online patient records.", - "offlineActionsNoActionsEmptyStateImageAlt": "No Pending Actions Image", - "offlineActionsNoActionsEmptyStateTitle": "No actions pending upload", - "offlineActionsTableAction": "Action", - "offlineActionsTableCreatedOn": "Date & Time", - "offlineActionsTableDeleteActions_one": "Delete action", - "offlineActionsTableDeleteActions_other": "Delete {{count}} actions", - "offlineActionsTableError": "Error", - "offlineActionsTablePatient": "Patient", - "offlineActionsUpdateOfflinePatients": "Update offline patients", - "offlinePatients": "Offline patients", - "offlinePatients_lower": "offline patients", - "offlinePatientsHeader": "Offline patients", - "offlinePatientsTableDeleteConfirmationModalCancel": "Cancel", - "offlinePatientsTableDeleteConfirmationModalConfirm": "Remove patients", - "offlinePatientsTableDeleteConfirmationModalContent": "Are you sure that you want to remove all selected patients from the offline list? Their charts will no longer be available in offline mode and any newly registered patient will be permanently deleted.", - "offlinePatientsTableDeleteConfirmationModalTitle": "Remove offline patients", - "offlinePatientsTableHeaderAge": "Age", - "offlinePatientsTableHeaderGender": "Gender", - "offlinePatientsTableHeaderLastUpdated": "Last updated", - "offlinePatientsTableHeaderName": "Name", - "offlinePatientsTableLastUpdatedDownloading": "Downloading...", - "offlinePatientsTableLastUpdatedError": "error", - "offlinePatientsTableLastUpdatedErrors": "errors", - "offlinePatientsTableLastUpdatedNotYetSynchronized": "Not synchronized", - "offlinePatientsTableLastUpdatedOutdatedData": "Outdated data", - "offlinePatientsTableNameNewlyRegistered": "New", - "offlinePatientsTableRemoveFromOfflineList": "Remove from list", - "offlinePatientsTableSearchLabel": "Search this list", - "offlinePatientsTableSearchPlaceholder": "Search this list", - "offlinePatientsTableTitle": "Offline patients", - "offlinePatientsTableUpdatePatient": "Update patient", - "offlinePatientsTableUpdatePatients": "Update patients", - "offlinePatientSyncDetailsDownloadedHeader": "Downloaded to this device", - "offlinePatientSyncDetailsFailedHeader": "There was an error downloading the following items", - "offlinePatientSyncDetailsFallbackErrorMessage": "Unknown error.", - "offlinePatientSyncDetailsHeader": "Offline patient details", - "offlineReady": "Offline Ready", - "offlineToolsAppMenuLink": "Offline tools" -} +{ + "emptyStateText": "There are no {{displayText}} to display", + "home": "Home", + "homeHeader": "Offline home", + "homeOverviewCardOfflineActionsFailedToUpload": "Failed to upload", + "homeOverviewCardOfflineActionsHeader": "Offline Actions", + "homeOverviewCardOfflineActionsPendingUpload": "Pending upload", + "homeOverviewCardPatientsDownloaded": "Downloaded", + "homeOverviewCardPatientsHeader": "Patients", + "homeOverviewCardPatientsNewlyRegistered": "Newly registered", + "homeOverviewCardView": "View", + "offlineActions": "Offline Actions", + "offlineActionsDeleteConfirmationModalCancel": "Cancel", + "offlineActionsDeleteConfirmationModalConfirm": "Delete forever", + "offlineActionsDeleteConfirmationModalContent": "Are you sure that you want to delete all selected offline actions? This cannot be undone!", + "offlineActionsDeleteConfirmationModalTitle": "Delete offline actions", + "offlineActionsHeader": "Offline Actions", + "offlineActionsNoActionsEmptyStateContent": "All offline actions have been uploaded successfully,\nand merged with the online patient records.", + "offlineActionsNoActionsEmptyStateImageAlt": "No Pending Actions Image", + "offlineActionsNoActionsEmptyStateTitle": "No actions pending upload", + "offlineActionsTableAction": "Action", + "offlineActionsTableCreatedOn": "Date & Time", + "offlineActionsTableDeleteActions_one": "Delete action", + "offlineActionsTableDeleteActions_other": "Delete {{count}} actions", + "offlineActionsTableError": "Error", + "offlineActionsTablePatient": "Patient", + "offlineActionsUpdateOfflinePatients": "Update offline patients", + "offlinePatients": "Offline patients", + "offlinePatients_lower": "offline patients", + "offlinePatientsHeader": "Offline patients", + "offlinePatientsTableDeleteConfirmationModalCancel": "Cancel", + "offlinePatientsTableDeleteConfirmationModalConfirm": "Remove patients", + "offlinePatientsTableDeleteConfirmationModalContent": "Are you sure that you want to remove all selected patients from the offline list? Their charts will no longer be available in offline mode and any newly registered patient will be permanently deleted.", + "offlinePatientsTableDeleteConfirmationModalTitle": "Remove offline patients", + "offlinePatientsTableHeaderAge": "Age", + "offlinePatientsTableHeaderGender": "Gender", + "offlinePatientsTableHeaderLastUpdated": "Last updated", + "offlinePatientsTableHeaderName": "Name", + "offlinePatientsTableLastUpdatedDownloading": "Downloading...", + "offlinePatientsTableLastUpdatedError": "error", + "offlinePatientsTableLastUpdatedErrors": "errors", + "offlinePatientsTableLastUpdatedNotYetSynchronized": "Not synchronized", + "offlinePatientsTableLastUpdatedOutdatedData": "Outdated data", + "offlinePatientsTableNameNewlyRegistered": "New", + "offlinePatientsTableRemoveFromOfflineList": "Remove from list", + "offlinePatientsTableSearchLabel": "Search this list", + "offlinePatientsTableSearchPlaceholder": "Search this list", + "offlinePatientsTableTitle": "Offline patients", + "offlinePatientsTableUpdatePatient": "Update patient", + "offlinePatientsTableUpdatePatients": "Update patients", + "offlinePatientSyncDetailsDownloadedHeader": "Downloaded to this device", + "offlinePatientSyncDetailsFailedHeader": "There was an error downloading the following items", + "offlinePatientSyncDetailsFallbackErrorMessage": "Unknown error.", + "offlinePatientSyncDetailsHeader": "Offline patient details", + "offlineReady": "Offline Ready", + "offlineToolsAppMenuLink": "Offline tools" +} diff --git a/packages/apps/esm-primary-navigation-app/translations/en.json b/packages/apps/esm-primary-navigation-app/translations/en.json index fdfafb6e8..d310a03d0 100644 --- a/packages/apps/esm-primary-navigation-app/translations/en.json +++ b/packages/apps/esm-primary-navigation-app/translations/en.json @@ -1,14 +1,14 @@ -{ - "AppMenuTooltip": "App Menu", - "cancel": "Cancel", - "change": "Change", - "changeDefaultLocale": "Update your default locale", - "changeDefaultLocaleExplanation": "Leave this unchecked to change language for this session only", - "changeLanguage": "Change language", - "changingLanguage": "Changing language", - "noPathInDashboardExtension": "Cannot render the dashboard extension without the property \"path\" being set in the configuration schema", - "notifications": "Notifications", - "userMenu": "User menu", - "userMenuOptions": "User menu options", - "userMenuTooltip": "My Account" -} +{ + "AppMenuTooltip": "App Menu", + "cancel": "Cancel", + "change": "Change", + "changeDefaultLocale": "Update your default locale", + "changeDefaultLocaleExplanation": "Leave this unchecked to change language for this session only", + "changeLanguage": "Change language", + "changingLanguage": "Changing language", + "noPathInDashboardExtension": "Cannot render the dashboard extension without the property \"path\" being set in the configuration schema", + "notifications": "Notifications", + "userMenu": "User menu", + "userMenuOptions": "User menu options", + "userMenuTooltip": "My Account" +} diff --git a/packages/framework/esm-translations/translations/en.json b/packages/framework/esm-translations/translations/en.json index 08af9ef00..ca4371ae3 100644 --- a/packages/framework/esm-translations/translations/en.json +++ b/packages/framework/esm-translations/translations/en.json @@ -1,66 +1,66 @@ -{ - "actions": "Actions", - "address": "Address", - "address1": "Address line 1", - "address2": "Address line 2", - "address3": "Address line 3", - "address4": "Address line 4", - "address5": "Address line 5", - "address6": "Address line 6", - "age": "Age", - "cancel": "Cancel", - "change": "Change", - "city": "City", - "cityVillage": "City", - "Clinic": "Clinic", - "close": "Close", - "closeAllOpenedWorkspaces": "Discard changes in {{count}} workspaces", - "closeWorkspaces2PromptBody": "You are about to close the following workspace(s), which might have unsaved changes:", - "closeWorkspaces2PromptTitle": "Close workspace(s)", - "closingAllWorkspacesPromptBody": "There may be unsaved changes in the following workspaces. Do you want to discard changes in the following workspaces? {{workspaceNames}}", - "closingAllWorkspacesPromptTitle": "You have unsaved changes", - "confirm": "Confirm", - "contactAdministratorIfIssuePersists": "Contact your system administrator if the problem persists.", - "contactDetails": "Contact Details", - "country": "Country", - "countyDistrict": "District", - "delete": "Delete", - "discard": "Discard", - "district": "District", - "edit": "Edit", - "error": "Error", - "errorCopy": "Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above.", - "female": "Female", - "hide": "Hide", - "loading": "Loading", - "male": "Male", - "maximize": "Maximize", - "minimize": "Minimize", - "openAnyway": "Open Anyway", - "other": "Other", - "patientIdentifierSticker": "Patient identifier sticker", - "patientLists": "Patient Lists", - "postalCode": "Postal code", - "print": "Print", - "printError": "Print error", - "printErrorExplainer": "An error occurred in {{errorLocation}}", - "printIdentifierSticker": "Print identifier sticker", - "printing": "Printing", - "relationships": "Relationships", - "resetOverrides": "Reset overrides", - "save": "Save", - "scriptLoadingError": "Failed to load overridden script from {{url}}. Please check that the bundled script is available at the expected URL. Click the button below to reset all import map overrides.", - "scriptLoadingFailed": "Error: Script failed to load", - "seeMoreLists": "See {{count}} more lists", - "sex": "Sex", - "showLess": "Show less", - "showMore": "Show more", - "state": "State", - "stateProvince": "State", - "toggleDevTools": "Toggle dev tools", - "unknown": "Unknown", - "unsavedChangesInOpenedWorkspace": "You may have unsaved changes in the opened workspace. Do you want to discard these changes?", - "unsavedChangesInWorkspace": "There may be unsaved changes in \"{{workspaceName}}\". Please save them before opening another workspace.", - "unsavedChangesTitleText": "Unsaved changes", - "workspaceHeader": "Workspace header" -} +{ + "actions": "Actions", + "address": "Address", + "address1": "Address line 1", + "address2": "Address line 2", + "address3": "Address line 3", + "address4": "Address line 4", + "address5": "Address line 5", + "address6": "Address line 6", + "age": "Age", + "cancel": "Cancel", + "change": "Change", + "city": "City", + "cityVillage": "City", + "Clinic": "Clinic", + "close": "Close", + "closeAllOpenedWorkspaces": "Discard changes in {{count}} workspaces", + "closeWorkspaces2PromptBody": "You are about to close the following workspace(s), which might have unsaved changes:", + "closeWorkspaces2PromptTitle": "Close workspace(s)", + "closingAllWorkspacesPromptBody": "There may be unsaved changes in the following workspaces. Do you want to discard changes in the following workspaces? {{workspaceNames}}", + "closingAllWorkspacesPromptTitle": "You have unsaved changes", + "confirm": "Confirm", + "contactAdministratorIfIssuePersists": "Contact your system administrator if the problem persists.", + "contactDetails": "Contact Details", + "country": "Country", + "countyDistrict": "District", + "delete": "Delete", + "discard": "Discard", + "district": "District", + "edit": "Edit", + "error": "Error", + "errorCopy": "Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above.", + "female": "Female", + "hide": "Hide", + "loading": "Loading", + "male": "Male", + "maximize": "Maximize", + "minimize": "Minimize", + "openAnyway": "Open Anyway", + "other": "Other", + "patientIdentifierSticker": "Patient identifier sticker", + "patientLists": "Patient Lists", + "postalCode": "Postal code", + "print": "Print", + "printError": "Print error", + "printErrorExplainer": "An error occurred in {{errorLocation}}", + "printIdentifierSticker": "Print identifier sticker", + "printing": "Printing", + "relationships": "Relationships", + "resetOverrides": "Reset overrides", + "save": "Save", + "scriptLoadingError": "Failed to load overridden script from {{url}}. Please check that the bundled script is available at the expected URL. Click the button below to reset all import map overrides.", + "scriptLoadingFailed": "Error: Script failed to load", + "seeMoreLists": "See {{count}} more lists", + "sex": "Sex", + "showLess": "Show less", + "showMore": "Show more", + "state": "State", + "stateProvince": "State", + "toggleDevTools": "Toggle dev tools", + "unknown": "Unknown", + "unsavedChangesInOpenedWorkspace": "You may have unsaved changes in the opened workspace. Do you want to discard these changes?", + "unsavedChangesInWorkspace": "There may be unsaved changes in \"{{workspaceName}}\". Please save them before opening another workspace.", + "unsavedChangesTitleText": "Unsaved changes", + "workspaceHeader": "Workspace header" +} From 715dd51a42f4eca05f628846f513e4ecee32cd4d Mon Sep 17 00:00:00 2001 From: Raj-bytecommandor Date: Thu, 23 Oct 2025 17:52:18 +0530 Subject: [PATCH 4/7] test: add unit tests for type guards and lodash.unset fix - Add tests to verify type guards prevent TypeError on null/undefined values - Add tests to verify immutable state update pattern with lodash.unset - Ensures changes follow best practices and prevent store corruption --- .../config-subtree-type-check.test.tsx | 41 ++++++++++++ .../__tests__/editable-value-unset.test.tsx | 66 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/config-subtree-type-check.test.tsx create mode 100644 packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/editable-value-unset.test.tsx diff --git a/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/config-subtree-type-check.test.tsx b/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/config-subtree-type-check.test.tsx new file mode 100644 index 000000000..b0c146f17 --- /dev/null +++ b/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/config-subtree-type-check.test.tsx @@ -0,0 +1,41 @@ +import { describe, it, expect } from '@jest/globals'; + +describe('config-subtree type guards', () => { + it('should handle null value without throwing TypeError', () => { + const value = null; + const isLeaf = value && typeof value === 'object' && Object.hasOwn(value, '_value'); + expect(isLeaf).toBe(false); + }); + + it('should handle undefined value without throwing TypeError', () => { + const value = undefined; + const isLeaf = value && typeof value === 'object' && Object.hasOwn(value, '_value'); + expect(isLeaf).toBe(false); + }); + + it('should return true for object with _value property', () => { + const value = { _value: 'test', _source: 'test' }; + const isLeaf = value && typeof value === 'object' && Object.hasOwn(value, '_value'); + expect(isLeaf).toBe(true); + }); + + it('should return false for object without _value property', () => { + const value = { nested: { _value: 'test' } }; + const isLeaf = value && typeof value === 'object' && Object.hasOwn(value, '_value'); + expect(isLeaf).toBe(false); + }); + + it('should return false for primitive values', () => { + const stringValue = 'test'; + const numberValue = 123; + const boolValue = true; + + const isLeafString = stringValue && typeof stringValue === 'object' && Object.hasOwn(stringValue as any, '_value'); + const isLeafNumber = numberValue && typeof numberValue === 'object' && Object.hasOwn(numberValue as any, '_value'); + const isLeafBool = boolValue && typeof boolValue === 'object' && Object.hasOwn(boolValue as any, '_value'); + + expect(isLeafString).toBe(false); + expect(isLeafNumber).toBe(false); + expect(isLeafBool).toBe(false); + }); +}); diff --git a/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/editable-value-unset.test.tsx b/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/editable-value-unset.test.tsx new file mode 100644 index 000000000..b64df0f56 --- /dev/null +++ b/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/editable-value-unset.test.tsx @@ -0,0 +1,66 @@ +import { describe, it, expect } from '@jest/globals'; +import { cloneDeep, unset } from 'lodash-es'; + +describe('lodash.unset immutable pattern', () => { + it('should properly use unset with immutable state updates', () => { + const initialState = { + config: { + '@openmrs/mario': { + hasHat: true, + numberFingers: 8, + }, + }, + }; + + // Clone first, mutate the clone, then use the clone + const state = cloneDeep(initialState); + const result = unset(state, ['config', '@openmrs/mario', 'hasHat']); + + // unset returns true if the property existed + expect(result).toBe(true); + + // The cloned state should be mutated + expect(state.config['@openmrs/mario'].hasHat).toBeUndefined(); + + // The original state should be unchanged + expect(initialState.config['@openmrs/mario'].hasHat).toBe(true); + + // The mutated state should still have other properties + expect(state.config['@openmrs/mario'].numberFingers).toBe(8); + }); + + it('should return false when unsetting non-existent property', () => { + const initialState = { + config: { + '@openmrs/mario': { + hasHat: true, + }, + }, + }; + + const state = cloneDeep(initialState); + const result = unset(state, ['config', '@openmrs/mario', 'nonExistent']); + + // unset returns false if the property did not exist + expect(result).toBe(false); + }); + + it('should handle nested property removal correctly', () => { + const initialState = { + config: { + '@openmrs/mario': { + weapons: { + gloves: 2, + parasol: 1, + }, + }, + }, + }; + + const state = cloneDeep(initialState); + unset(state, ['config', '@openmrs/mario', 'weapons', 'gloves']); + + expect(state.config['@openmrs/mario'].weapons.gloves).toBeUndefined(); + expect(state.config['@openmrs/mario'].weapons.parasol).toBe(1); + }); +}); From 16ea8c8ce4f592353de228341d7aceeb689223c4 Mon Sep 17 00:00:00 2001 From: Dennis Kigen Date: Tue, 28 Oct 2025 00:35:21 +0300 Subject: [PATCH 5/7] Fixup: Complete type guards and improve type safety in config-subtree component Adds type guards for _source and _description properties to prevent runtime crashes when accessing metadata on non-leaf config nodes. Also removes unused parameters and adds explicit TypeScript type annotations for better type safety. --- .../interactive-editor/config-subtree.component.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx b/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx index 0f0f5ff8a..09afa7251 100644 --- a/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx +++ b/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/config-subtree.component.tsx @@ -10,21 +10,21 @@ export interface ConfigSubtreeProps { } export function ConfigSubtree({ config, path = [] }: ConfigSubtreeProps) { - function setActiveItemDescriptionOnMouseEnter(thisPath, key, value) { + function setActiveItemDescriptionOnMouseEnter(thisPath: Array, value: any) { if (!implementerToolsStore.getState().configPathBeingEdited) { const isLeaf = value && typeof value === 'object' && Object.hasOwn(value, '_value'); implementerToolsStore.setState({ activeItemDescription: { path: thisPath, - source: value._source, - description: value._description, + source: isLeaf ? value._source : undefined, + description: isLeaf ? value._description : undefined, value: isLeaf ? JSON.stringify(value._value) : undefined, }, }); } } - function removeActiveItemDescriptionOnMouseLeave(thisPath) { + function removeActiveItemDescriptionOnMouseLeave(thisPath: Array) { const state = implementerToolsStore.getState(); if (isEqual(state.activeItemDescription?.path, thisPath) && !isEqual(state.configPathBeingEdited, thisPath)) { implementerToolsStore.setState({ activeItemDescription: undefined }); @@ -35,14 +35,14 @@ export function ConfigSubtree({ config, path = [] }: ConfigSubtreeProps) { <> {Object.entries(config) .filter(([key]) => !key.startsWith('_')) - .map(([key, value], i) => { + .map(([key, value]) => { const thisPath = path.concat([key]); const isLeaf = value && typeof value === 'object' && Object.hasOwn(value, '_value'); return ( setActiveItemDescriptionOnMouseEnter(thisPath, key, value)} + onMouseEnter={() => setActiveItemDescriptionOnMouseEnter(thisPath, value)} onMouseLeave={() => removeActiveItemDescriptionOnMouseLeave(thisPath)} key={`subtree-${thisPath.join('.')}`} > From 014df6b081773d846880f4df9192eba8883a4b90 Mon Sep 17 00:00:00 2001 From: Dennis Kigen Date: Tue, 28 Oct 2025 00:41:09 +0300 Subject: [PATCH 6/7] Normalize line endings in translation files to LF Converts CRLF (Windows) line endings to LF (Unix) to match repository convention. --- .../translations/en.json | 76 +++++----- .../apps/esm-login-app/translations/en.json | 82 +++++------ .../translations/en.json | 114 +++++++-------- .../translations/en.json | 28 ++-- .../esm-translations/translations/en.json | 132 +++++++++--------- 5 files changed, 216 insertions(+), 216 deletions(-) diff --git a/packages/apps/esm-implementer-tools-app/translations/en.json b/packages/apps/esm-implementer-tools-app/translations/en.json index fd4b12676..36d97aa5c 100644 --- a/packages/apps/esm-implementer-tools-app/translations/en.json +++ b/packages/apps/esm-implementer-tools-app/translations/en.json @@ -1,38 +1,38 @@ -{ - "activeItemSourceText": "The current value comes from ", - "arrayValidationMessage": "Value must be an array", - "backendModules": "Backend modules", - "booleanValidationMessage": "Value must be a boolean", - "checkImplementerToolsMessage": "Check the Backend Modules tab in the Implementer Tools for more details", - "clearConfig": "Clear local config", - "close": "Close", - "configuration": "Configuration", - "description": "Description", - "downloadConfig": "Download config", - "edit": "Edit", - "editValueButtonText": "Edit", - "enabled": "Enabled", - "extensions": "Extensions", - "featureFlag": "Feature flag", - "featureFlags": "Feature flags", - "frontendModules": "Frontend modules", - "implementerTools": "Implementer Tools", - "installedVersion": "Installed version", - "itemDescriptionSourceDefaultText": "The current value is the default.", - "jsonEditor": "JSON editor", - "missing": "Missing", - "moduleName": "Module name", - "modulesWithMissingDependenciesWarning": "Some modules have unresolved backend dependencies", - "numberValidationMessage": "Value must be a number", - "objectValidationMessage": "Value must be an object", - "requiredVersion": "Required Version", - "resetToDefaultValueButtonText": "Reset to default", - "stringValidationMessage": "Value must be a string", - "toggleImplementerTools": "Toggle Implementer Tools", - "uiEditor": "UI editor", - "unknownVersion": "unknown", - "updateConfig": "Update config", - "uuidValidationMessage": "Value must be a valid UUID string", - "value": "Value", - "viewModules": "View modules" -} +{ + "activeItemSourceText": "The current value comes from ", + "arrayValidationMessage": "Value must be an array", + "backendModules": "Backend modules", + "booleanValidationMessage": "Value must be a boolean", + "checkImplementerToolsMessage": "Check the Backend Modules tab in the Implementer Tools for more details", + "clearConfig": "Clear local config", + "close": "Close", + "configuration": "Configuration", + "description": "Description", + "downloadConfig": "Download config", + "edit": "Edit", + "editValueButtonText": "Edit", + "enabled": "Enabled", + "extensions": "Extensions", + "featureFlag": "Feature flag", + "featureFlags": "Feature flags", + "frontendModules": "Frontend modules", + "implementerTools": "Implementer Tools", + "installedVersion": "Installed version", + "itemDescriptionSourceDefaultText": "The current value is the default.", + "jsonEditor": "JSON editor", + "missing": "Missing", + "moduleName": "Module name", + "modulesWithMissingDependenciesWarning": "Some modules have unresolved backend dependencies", + "numberValidationMessage": "Value must be a number", + "objectValidationMessage": "Value must be an object", + "requiredVersion": "Required Version", + "resetToDefaultValueButtonText": "Reset to default", + "stringValidationMessage": "Value must be a string", + "toggleImplementerTools": "Toggle Implementer Tools", + "uiEditor": "UI editor", + "unknownVersion": "unknown", + "updateConfig": "Update config", + "uuidValidationMessage": "Value must be a valid UUID string", + "value": "Value", + "viewModules": "View modules" +} diff --git a/packages/apps/esm-login-app/translations/en.json b/packages/apps/esm-login-app/translations/en.json index 62391ba8b..4675c40c4 100644 --- a/packages/apps/esm-login-app/translations/en.json +++ b/packages/apps/esm-login-app/translations/en.json @@ -1,41 +1,41 @@ -{ - "builtWith": "Built with", - "cancel": "Cancel", - "change": "Change", - "changeLocation": "Change location", - "changePassword": "Change password", - "changingPassword": "Changing password", - "confirmPassword": "Confirm new password", - "continue": "Continue", - "errorChangingPassword": "Error changing password", - "footerlogo": "Footer Logo", - "invalidCredentials": "Invalid username or password", - "learnMore": "Learn more", - "locationPreferenceRemoved": "Login location preference removed", - "locationPreferenceRemovedMessage": "You will need to select a location on each login", - "locationSaved": "Location saved", - "locationSaveMessage": "Your preferred location has been saved for future logins", - "locationUpdated": "Location updated", - "locationUpdateMessage": "Your preferred login location has been updated", - "loggingIn": "Logging in", - "login": "Log in", - "loginButtonIconDescription": "Log in button", - "Logout": "Logout", - "newPassword": "New password", - "newPasswordRequired": "New password is required", - "oldPassword": "Old password", - "oldPasswordRequired": "Old password is required", - "openmrsLogo": "OpenMRS logo", - "password": "Password", - "passwordChangedSuccessfully": "Password changed successfully", - "passwordConfirmationRequired": "Password confirmation is required", - "passwordsDoNotMatch": "Passwords do not match", - "poweredBySubtext": "An open-source medical record system and global community", - "rememberLocationForFutureLogins": "Remember my location for future logins", - "selectYourLocation": "Select your location from the list below. Use the search bar to find your location.", - "showPassword": "Show password", - "submitting": "Submitting", - "username": "Username", - "validValueRequired": "A valid value is required", - "welcome": "Welcome" -} +{ + "builtWith": "Built with", + "cancel": "Cancel", + "change": "Change", + "changeLocation": "Change location", + "changePassword": "Change password", + "changingPassword": "Changing password", + "confirmPassword": "Confirm new password", + "continue": "Continue", + "errorChangingPassword": "Error changing password", + "footerlogo": "Footer Logo", + "invalidCredentials": "Invalid username or password", + "learnMore": "Learn more", + "locationPreferenceRemoved": "Login location preference removed", + "locationPreferenceRemovedMessage": "You will need to select a location on each login", + "locationSaved": "Location saved", + "locationSaveMessage": "Your preferred location has been saved for future logins", + "locationUpdated": "Location updated", + "locationUpdateMessage": "Your preferred login location has been updated", + "loggingIn": "Logging in", + "login": "Log in", + "loginButtonIconDescription": "Log in button", + "Logout": "Logout", + "newPassword": "New password", + "newPasswordRequired": "New password is required", + "oldPassword": "Old password", + "oldPasswordRequired": "Old password is required", + "openmrsLogo": "OpenMRS logo", + "password": "Password", + "passwordChangedSuccessfully": "Password changed successfully", + "passwordConfirmationRequired": "Password confirmation is required", + "passwordsDoNotMatch": "Passwords do not match", + "poweredBySubtext": "An open-source medical record system and global community", + "rememberLocationForFutureLogins": "Remember my location for future logins", + "selectYourLocation": "Select your location from the list below. Use the search bar to find your location.", + "showPassword": "Show password", + "submitting": "Submitting", + "username": "Username", + "validValueRequired": "A valid value is required", + "welcome": "Welcome" +} diff --git a/packages/apps/esm-offline-tools-app/translations/en.json b/packages/apps/esm-offline-tools-app/translations/en.json index e3d8fec24..304df0615 100644 --- a/packages/apps/esm-offline-tools-app/translations/en.json +++ b/packages/apps/esm-offline-tools-app/translations/en.json @@ -1,57 +1,57 @@ -{ - "emptyStateText": "There are no {{displayText}} to display", - "home": "Home", - "homeHeader": "Offline home", - "homeOverviewCardOfflineActionsFailedToUpload": "Failed to upload", - "homeOverviewCardOfflineActionsHeader": "Offline Actions", - "homeOverviewCardOfflineActionsPendingUpload": "Pending upload", - "homeOverviewCardPatientsDownloaded": "Downloaded", - "homeOverviewCardPatientsHeader": "Patients", - "homeOverviewCardPatientsNewlyRegistered": "Newly registered", - "homeOverviewCardView": "View", - "offlineActions": "Offline Actions", - "offlineActionsDeleteConfirmationModalCancel": "Cancel", - "offlineActionsDeleteConfirmationModalConfirm": "Delete forever", - "offlineActionsDeleteConfirmationModalContent": "Are you sure that you want to delete all selected offline actions? This cannot be undone!", - "offlineActionsDeleteConfirmationModalTitle": "Delete offline actions", - "offlineActionsHeader": "Offline Actions", - "offlineActionsNoActionsEmptyStateContent": "All offline actions have been uploaded successfully,\nand merged with the online patient records.", - "offlineActionsNoActionsEmptyStateImageAlt": "No Pending Actions Image", - "offlineActionsNoActionsEmptyStateTitle": "No actions pending upload", - "offlineActionsTableAction": "Action", - "offlineActionsTableCreatedOn": "Date & Time", - "offlineActionsTableDeleteActions_one": "Delete action", - "offlineActionsTableDeleteActions_other": "Delete {{count}} actions", - "offlineActionsTableError": "Error", - "offlineActionsTablePatient": "Patient", - "offlineActionsUpdateOfflinePatients": "Update offline patients", - "offlinePatients": "Offline patients", - "offlinePatients_lower": "offline patients", - "offlinePatientsHeader": "Offline patients", - "offlinePatientsTableDeleteConfirmationModalCancel": "Cancel", - "offlinePatientsTableDeleteConfirmationModalConfirm": "Remove patients", - "offlinePatientsTableDeleteConfirmationModalContent": "Are you sure that you want to remove all selected patients from the offline list? Their charts will no longer be available in offline mode and any newly registered patient will be permanently deleted.", - "offlinePatientsTableDeleteConfirmationModalTitle": "Remove offline patients", - "offlinePatientsTableHeaderAge": "Age", - "offlinePatientsTableHeaderGender": "Gender", - "offlinePatientsTableHeaderLastUpdated": "Last updated", - "offlinePatientsTableHeaderName": "Name", - "offlinePatientsTableLastUpdatedDownloading": "Downloading...", - "offlinePatientsTableLastUpdatedError": "error", - "offlinePatientsTableLastUpdatedErrors": "errors", - "offlinePatientsTableLastUpdatedNotYetSynchronized": "Not synchronized", - "offlinePatientsTableLastUpdatedOutdatedData": "Outdated data", - "offlinePatientsTableNameNewlyRegistered": "New", - "offlinePatientsTableRemoveFromOfflineList": "Remove from list", - "offlinePatientsTableSearchLabel": "Search this list", - "offlinePatientsTableSearchPlaceholder": "Search this list", - "offlinePatientsTableTitle": "Offline patients", - "offlinePatientsTableUpdatePatient": "Update patient", - "offlinePatientsTableUpdatePatients": "Update patients", - "offlinePatientSyncDetailsDownloadedHeader": "Downloaded to this device", - "offlinePatientSyncDetailsFailedHeader": "There was an error downloading the following items", - "offlinePatientSyncDetailsFallbackErrorMessage": "Unknown error.", - "offlinePatientSyncDetailsHeader": "Offline patient details", - "offlineReady": "Offline Ready", - "offlineToolsAppMenuLink": "Offline tools" -} +{ + "emptyStateText": "There are no {{displayText}} to display", + "home": "Home", + "homeHeader": "Offline home", + "homeOverviewCardOfflineActionsFailedToUpload": "Failed to upload", + "homeOverviewCardOfflineActionsHeader": "Offline Actions", + "homeOverviewCardOfflineActionsPendingUpload": "Pending upload", + "homeOverviewCardPatientsDownloaded": "Downloaded", + "homeOverviewCardPatientsHeader": "Patients", + "homeOverviewCardPatientsNewlyRegistered": "Newly registered", + "homeOverviewCardView": "View", + "offlineActions": "Offline Actions", + "offlineActionsDeleteConfirmationModalCancel": "Cancel", + "offlineActionsDeleteConfirmationModalConfirm": "Delete forever", + "offlineActionsDeleteConfirmationModalContent": "Are you sure that you want to delete all selected offline actions? This cannot be undone!", + "offlineActionsDeleteConfirmationModalTitle": "Delete offline actions", + "offlineActionsHeader": "Offline Actions", + "offlineActionsNoActionsEmptyStateContent": "All offline actions have been uploaded successfully,\nand merged with the online patient records.", + "offlineActionsNoActionsEmptyStateImageAlt": "No Pending Actions Image", + "offlineActionsNoActionsEmptyStateTitle": "No actions pending upload", + "offlineActionsTableAction": "Action", + "offlineActionsTableCreatedOn": "Date & Time", + "offlineActionsTableDeleteActions_one": "Delete action", + "offlineActionsTableDeleteActions_other": "Delete {{count}} actions", + "offlineActionsTableError": "Error", + "offlineActionsTablePatient": "Patient", + "offlineActionsUpdateOfflinePatients": "Update offline patients", + "offlinePatients": "Offline patients", + "offlinePatients_lower": "offline patients", + "offlinePatientsHeader": "Offline patients", + "offlinePatientsTableDeleteConfirmationModalCancel": "Cancel", + "offlinePatientsTableDeleteConfirmationModalConfirm": "Remove patients", + "offlinePatientsTableDeleteConfirmationModalContent": "Are you sure that you want to remove all selected patients from the offline list? Their charts will no longer be available in offline mode and any newly registered patient will be permanently deleted.", + "offlinePatientsTableDeleteConfirmationModalTitle": "Remove offline patients", + "offlinePatientsTableHeaderAge": "Age", + "offlinePatientsTableHeaderGender": "Gender", + "offlinePatientsTableHeaderLastUpdated": "Last updated", + "offlinePatientsTableHeaderName": "Name", + "offlinePatientsTableLastUpdatedDownloading": "Downloading...", + "offlinePatientsTableLastUpdatedError": "error", + "offlinePatientsTableLastUpdatedErrors": "errors", + "offlinePatientsTableLastUpdatedNotYetSynchronized": "Not synchronized", + "offlinePatientsTableLastUpdatedOutdatedData": "Outdated data", + "offlinePatientsTableNameNewlyRegistered": "New", + "offlinePatientsTableRemoveFromOfflineList": "Remove from list", + "offlinePatientsTableSearchLabel": "Search this list", + "offlinePatientsTableSearchPlaceholder": "Search this list", + "offlinePatientsTableTitle": "Offline patients", + "offlinePatientsTableUpdatePatient": "Update patient", + "offlinePatientsTableUpdatePatients": "Update patients", + "offlinePatientSyncDetailsDownloadedHeader": "Downloaded to this device", + "offlinePatientSyncDetailsFailedHeader": "There was an error downloading the following items", + "offlinePatientSyncDetailsFallbackErrorMessage": "Unknown error.", + "offlinePatientSyncDetailsHeader": "Offline patient details", + "offlineReady": "Offline Ready", + "offlineToolsAppMenuLink": "Offline tools" +} diff --git a/packages/apps/esm-primary-navigation-app/translations/en.json b/packages/apps/esm-primary-navigation-app/translations/en.json index d310a03d0..fdfafb6e8 100644 --- a/packages/apps/esm-primary-navigation-app/translations/en.json +++ b/packages/apps/esm-primary-navigation-app/translations/en.json @@ -1,14 +1,14 @@ -{ - "AppMenuTooltip": "App Menu", - "cancel": "Cancel", - "change": "Change", - "changeDefaultLocale": "Update your default locale", - "changeDefaultLocaleExplanation": "Leave this unchecked to change language for this session only", - "changeLanguage": "Change language", - "changingLanguage": "Changing language", - "noPathInDashboardExtension": "Cannot render the dashboard extension without the property \"path\" being set in the configuration schema", - "notifications": "Notifications", - "userMenu": "User menu", - "userMenuOptions": "User menu options", - "userMenuTooltip": "My Account" -} +{ + "AppMenuTooltip": "App Menu", + "cancel": "Cancel", + "change": "Change", + "changeDefaultLocale": "Update your default locale", + "changeDefaultLocaleExplanation": "Leave this unchecked to change language for this session only", + "changeLanguage": "Change language", + "changingLanguage": "Changing language", + "noPathInDashboardExtension": "Cannot render the dashboard extension without the property \"path\" being set in the configuration schema", + "notifications": "Notifications", + "userMenu": "User menu", + "userMenuOptions": "User menu options", + "userMenuTooltip": "My Account" +} diff --git a/packages/framework/esm-translations/translations/en.json b/packages/framework/esm-translations/translations/en.json index ca4371ae3..08af9ef00 100644 --- a/packages/framework/esm-translations/translations/en.json +++ b/packages/framework/esm-translations/translations/en.json @@ -1,66 +1,66 @@ -{ - "actions": "Actions", - "address": "Address", - "address1": "Address line 1", - "address2": "Address line 2", - "address3": "Address line 3", - "address4": "Address line 4", - "address5": "Address line 5", - "address6": "Address line 6", - "age": "Age", - "cancel": "Cancel", - "change": "Change", - "city": "City", - "cityVillage": "City", - "Clinic": "Clinic", - "close": "Close", - "closeAllOpenedWorkspaces": "Discard changes in {{count}} workspaces", - "closeWorkspaces2PromptBody": "You are about to close the following workspace(s), which might have unsaved changes:", - "closeWorkspaces2PromptTitle": "Close workspace(s)", - "closingAllWorkspacesPromptBody": "There may be unsaved changes in the following workspaces. Do you want to discard changes in the following workspaces? {{workspaceNames}}", - "closingAllWorkspacesPromptTitle": "You have unsaved changes", - "confirm": "Confirm", - "contactAdministratorIfIssuePersists": "Contact your system administrator if the problem persists.", - "contactDetails": "Contact Details", - "country": "Country", - "countyDistrict": "District", - "delete": "Delete", - "discard": "Discard", - "district": "District", - "edit": "Edit", - "error": "Error", - "errorCopy": "Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above.", - "female": "Female", - "hide": "Hide", - "loading": "Loading", - "male": "Male", - "maximize": "Maximize", - "minimize": "Minimize", - "openAnyway": "Open Anyway", - "other": "Other", - "patientIdentifierSticker": "Patient identifier sticker", - "patientLists": "Patient Lists", - "postalCode": "Postal code", - "print": "Print", - "printError": "Print error", - "printErrorExplainer": "An error occurred in {{errorLocation}}", - "printIdentifierSticker": "Print identifier sticker", - "printing": "Printing", - "relationships": "Relationships", - "resetOverrides": "Reset overrides", - "save": "Save", - "scriptLoadingError": "Failed to load overridden script from {{url}}. Please check that the bundled script is available at the expected URL. Click the button below to reset all import map overrides.", - "scriptLoadingFailed": "Error: Script failed to load", - "seeMoreLists": "See {{count}} more lists", - "sex": "Sex", - "showLess": "Show less", - "showMore": "Show more", - "state": "State", - "stateProvince": "State", - "toggleDevTools": "Toggle dev tools", - "unknown": "Unknown", - "unsavedChangesInOpenedWorkspace": "You may have unsaved changes in the opened workspace. Do you want to discard these changes?", - "unsavedChangesInWorkspace": "There may be unsaved changes in \"{{workspaceName}}\". Please save them before opening another workspace.", - "unsavedChangesTitleText": "Unsaved changes", - "workspaceHeader": "Workspace header" -} +{ + "actions": "Actions", + "address": "Address", + "address1": "Address line 1", + "address2": "Address line 2", + "address3": "Address line 3", + "address4": "Address line 4", + "address5": "Address line 5", + "address6": "Address line 6", + "age": "Age", + "cancel": "Cancel", + "change": "Change", + "city": "City", + "cityVillage": "City", + "Clinic": "Clinic", + "close": "Close", + "closeAllOpenedWorkspaces": "Discard changes in {{count}} workspaces", + "closeWorkspaces2PromptBody": "You are about to close the following workspace(s), which might have unsaved changes:", + "closeWorkspaces2PromptTitle": "Close workspace(s)", + "closingAllWorkspacesPromptBody": "There may be unsaved changes in the following workspaces. Do you want to discard changes in the following workspaces? {{workspaceNames}}", + "closingAllWorkspacesPromptTitle": "You have unsaved changes", + "confirm": "Confirm", + "contactAdministratorIfIssuePersists": "Contact your system administrator if the problem persists.", + "contactDetails": "Contact Details", + "country": "Country", + "countyDistrict": "District", + "delete": "Delete", + "discard": "Discard", + "district": "District", + "edit": "Edit", + "error": "Error", + "errorCopy": "Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above.", + "female": "Female", + "hide": "Hide", + "loading": "Loading", + "male": "Male", + "maximize": "Maximize", + "minimize": "Minimize", + "openAnyway": "Open Anyway", + "other": "Other", + "patientIdentifierSticker": "Patient identifier sticker", + "patientLists": "Patient Lists", + "postalCode": "Postal code", + "print": "Print", + "printError": "Print error", + "printErrorExplainer": "An error occurred in {{errorLocation}}", + "printIdentifierSticker": "Print identifier sticker", + "printing": "Printing", + "relationships": "Relationships", + "resetOverrides": "Reset overrides", + "save": "Save", + "scriptLoadingError": "Failed to load overridden script from {{url}}. Please check that the bundled script is available at the expected URL. Click the button below to reset all import map overrides.", + "scriptLoadingFailed": "Error: Script failed to load", + "seeMoreLists": "See {{count}} more lists", + "sex": "Sex", + "showLess": "Show less", + "showMore": "Show more", + "state": "State", + "stateProvince": "State", + "toggleDevTools": "Toggle dev tools", + "unknown": "Unknown", + "unsavedChangesInOpenedWorkspace": "You may have unsaved changes in the opened workspace. Do you want to discard these changes?", + "unsavedChangesInWorkspace": "There may be unsaved changes in \"{{workspaceName}}\". Please save them before opening another workspace.", + "unsavedChangesTitleText": "Unsaved changes", + "workspaceHeader": "Workspace header" +} From d903639c94a6e30593e221b7eb648d4f386f700c Mon Sep 17 00:00:00 2001 From: Dennis Kigen Date: Tue, 28 Oct 2025 00:48:09 +0300 Subject: [PATCH 7/7] Replace non-canonical tests with canonical component-level test Previous tests were not canonical and did not test the component-level behavior. Rather, they test implementation details. They also used non-canonical imports instead of Jest globals and are not colocated properly to the code they were testing. --- .../src/configuration/configuration.test.tsx | 34 ++++++++++ .../config-subtree-type-check.test.tsx | 41 ------------ .../__tests__/editable-value-unset.test.tsx | 66 ------------------- 3 files changed, 34 insertions(+), 107 deletions(-) delete mode 100644 packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/config-subtree-type-check.test.tsx delete mode 100644 packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/editable-value-unset.test.tsx diff --git a/packages/apps/esm-implementer-tools-app/src/configuration/configuration.test.tsx b/packages/apps/esm-implementer-tools-app/src/configuration/configuration.test.tsx index f5e4bf179..ad8f2720f 100644 --- a/packages/apps/esm-implementer-tools-app/src/configuration/configuration.test.tsx +++ b/packages/apps/esm-implementer-tools-app/src/configuration/configuration.test.tsx @@ -361,4 +361,38 @@ describe('Configuration', () => { // expect(mockSetTemporaryConfigValue).toHaveBeenCalledWith(["@openmrs/luigi", "favoriteNumbers"], [5, 11, 13]); } }); + + it('handles hovering over config tree items without crashing', async () => { + const user = userEvent.setup(); + + implementerToolsConfigStore.setState({ + config: { + '@openmrs/mario': { + hasHat: mockImplToolsConfig['@openmrs/mario'].hasHat, + weapons: { + gloves: { + _type: Type.Number, + _default: 0, + _value: 2, + _source: 'provided', + }, + }, + }, + }, + }); + + renderConfiguration(); + + // Find and hover over a leaf node (hasHat) + const hasHatElement = await screen.findByText('hasHat'); + await user.hover(hasHatElement); + + // Find and hover over a branch node (weapons) - this should not crash + const weaponsElement = await screen.findByText('weapons'); + await user.hover(weaponsElement); + + // Both elements should still be in the document (no crash occurred) + expect(hasHatElement).toBeInTheDocument(); + expect(weaponsElement).toBeInTheDocument(); + }); }); diff --git a/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/config-subtree-type-check.test.tsx b/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/config-subtree-type-check.test.tsx deleted file mode 100644 index b0c146f17..000000000 --- a/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/config-subtree-type-check.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { describe, it, expect } from '@jest/globals'; - -describe('config-subtree type guards', () => { - it('should handle null value without throwing TypeError', () => { - const value = null; - const isLeaf = value && typeof value === 'object' && Object.hasOwn(value, '_value'); - expect(isLeaf).toBe(false); - }); - - it('should handle undefined value without throwing TypeError', () => { - const value = undefined; - const isLeaf = value && typeof value === 'object' && Object.hasOwn(value, '_value'); - expect(isLeaf).toBe(false); - }); - - it('should return true for object with _value property', () => { - const value = { _value: 'test', _source: 'test' }; - const isLeaf = value && typeof value === 'object' && Object.hasOwn(value, '_value'); - expect(isLeaf).toBe(true); - }); - - it('should return false for object without _value property', () => { - const value = { nested: { _value: 'test' } }; - const isLeaf = value && typeof value === 'object' && Object.hasOwn(value, '_value'); - expect(isLeaf).toBe(false); - }); - - it('should return false for primitive values', () => { - const stringValue = 'test'; - const numberValue = 123; - const boolValue = true; - - const isLeafString = stringValue && typeof stringValue === 'object' && Object.hasOwn(stringValue as any, '_value'); - const isLeafNumber = numberValue && typeof numberValue === 'object' && Object.hasOwn(numberValue as any, '_value'); - const isLeafBool = boolValue && typeof boolValue === 'object' && Object.hasOwn(boolValue as any, '_value'); - - expect(isLeafString).toBe(false); - expect(isLeafNumber).toBe(false); - expect(isLeafBool).toBe(false); - }); -}); diff --git a/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/editable-value-unset.test.tsx b/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/editable-value-unset.test.tsx deleted file mode 100644 index b64df0f56..000000000 --- a/packages/apps/esm-implementer-tools-app/src/configuration/interactive-editor/__tests__/editable-value-unset.test.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { describe, it, expect } from '@jest/globals'; -import { cloneDeep, unset } from 'lodash-es'; - -describe('lodash.unset immutable pattern', () => { - it('should properly use unset with immutable state updates', () => { - const initialState = { - config: { - '@openmrs/mario': { - hasHat: true, - numberFingers: 8, - }, - }, - }; - - // Clone first, mutate the clone, then use the clone - const state = cloneDeep(initialState); - const result = unset(state, ['config', '@openmrs/mario', 'hasHat']); - - // unset returns true if the property existed - expect(result).toBe(true); - - // The cloned state should be mutated - expect(state.config['@openmrs/mario'].hasHat).toBeUndefined(); - - // The original state should be unchanged - expect(initialState.config['@openmrs/mario'].hasHat).toBe(true); - - // The mutated state should still have other properties - expect(state.config['@openmrs/mario'].numberFingers).toBe(8); - }); - - it('should return false when unsetting non-existent property', () => { - const initialState = { - config: { - '@openmrs/mario': { - hasHat: true, - }, - }, - }; - - const state = cloneDeep(initialState); - const result = unset(state, ['config', '@openmrs/mario', 'nonExistent']); - - // unset returns false if the property did not exist - expect(result).toBe(false); - }); - - it('should handle nested property removal correctly', () => { - const initialState = { - config: { - '@openmrs/mario': { - weapons: { - gloves: 2, - parasol: 1, - }, - }, - }, - }; - - const state = cloneDeep(initialState); - unset(state, ['config', '@openmrs/mario', 'weapons', 'gloves']); - - expect(state.config['@openmrs/mario'].weapons.gloves).toBeUndefined(); - expect(state.config['@openmrs/mario'].weapons.parasol).toBe(1); - }); -});