From 9ec829f4492575c2c2c3372738d7c45f1b37c329 Mon Sep 17 00:00:00 2001 From: michaldev0 <125133223+michaldev0@users.noreply.github.com> Date: Fri, 29 May 2026 07:42:06 +0200 Subject: [PATCH] OLMIS-8128: Keep patients validation state session-only Validation highlights on the patients tab no longer survive a page refresh. --- CHANGELOG.md | 1 + ...equisition-patients-view-tab.controller.js | 19 ++- ...ition-patients-view-tab.controller.spec.js | 132 ++++++++++++++++++ 3 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 src/requisition-view-tab/requisition-patients-view-tab.controller.spec.js diff --git a/CHANGELOG.md b/CHANGELOG.md index b5d0dae3..7014ee91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Upcoming Version (WIP) ================== Bug fixes: * [OLMIS-8154](https://openlmis.atlassian.net/browse/OLMIS-8154): Explanation no longer required when requested equals calculated. +* [OLMIS-8128](https://openlmis.atlassian.net/browse/OLMIS-8128): TB Monthly patients tab no longer shows validation highlights after a page refresh 7.0.16 / 2026-02-05 ================= diff --git a/src/requisition-view-tab/requisition-patients-view-tab.controller.js b/src/requisition-view-tab/requisition-patients-view-tab.controller.js index 43ad3061..fc65c737 100644 --- a/src/requisition-view-tab/requisition-patients-view-tab.controller.js +++ b/src/requisition-view-tab/requisition-patients-view-tab.controller.js @@ -130,14 +130,29 @@ return array; } + // Validation state (isInvalid) is session-only and must never be persisted, + // otherwise red highlights survive a page refresh. Returns a copy with isInvalid + // cleared, leaving the in-memory rows (and the in-session highlighting) intact. + function withoutInvalidState(array) { + return array.map(function(row) { + return angular.extend({}, row, { + data: row.data.map(function(field) { + return angular.extend({}, field, { + isInvalid: false + }); + }) + }); + }); + } + // Saves data from the table in local storage vm.handleSaveInLocalStorage = function(partArrayTitle, rowsValues) { if (partArrayTitle === leprosyTitle) { localStorageFactory(LEPROSY_STORAGE).clearAll(); - localStorageFactory(LEPROSY_STORAGE).put(rowsValues); + localStorageFactory(LEPROSY_STORAGE).put(withoutInvalidState(rowsValues)); } else if (partArrayTitle === TBTitle) { localStorageFactory(TB_STORAGE).clearAll(); - localStorageFactory(TB_STORAGE).put(rowsValues); + localStorageFactory(TB_STORAGE).put(withoutInvalidState(rowsValues)); } var TBArray = getFromLocalStorage(TB_STORAGE); diff --git a/src/requisition-view-tab/requisition-patients-view-tab.controller.spec.js b/src/requisition-view-tab/requisition-patients-view-tab.controller.spec.js new file mode 100644 index 00000000..494f33dd --- /dev/null +++ b/src/requisition-view-tab/requisition-patients-view-tab.controller.spec.js @@ -0,0 +1,132 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + *   + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.  + */ + +describe('PatientsViewTabController', function() { + + beforeEach(function() { + module('requisition-view-tab', function($provide) { + $provide.value('featureFlagService', { + set: function() {}, + get: function() {} + }); + }); + + inject(function($injector) { + this.$controller = $injector.get('$controller'); + this.$rootScope = $injector.get('$rootScope'); + this.localStorageFactory = $injector.get('localStorageFactory'); + this.TB_STORAGE = $injector.get('TB_STORAGE'); + this.LEPROSY_STORAGE = $injector.get('LEPROSY_STORAGE'); + }); + + this.localStorageFactory(this.TB_STORAGE).clearAll(); + this.localStorageFactory(this.LEPROSY_STORAGE).clearAll(); + + this.vm = this.$controller('PatientsViewTabController', { + canSubmit: true, + canAuthorize: true, + requisition: {} + }); + + // Helper: rows for a single field carrying the given value + isInvalid flag. + this.rowsWith = function(value, isInvalid) { + return [{ + key: 'someKey', + title: 'someTitle', + isSkipped: false, + isSkipDisabled: false, + data: [{ + value: value, + disabled: false, + isInvalid: isInvalid, + isFocused: false + }] + }]; + }; + + // Helper: true if any field in the stored array is flagged invalid. + this.anyStoredInvalid = function(storageName) { + var stored = this.localStorageFactory(storageName).getAll()[0] || []; + return stored.some(function(row) { + return row.data.some(function(field) { + return field.isInvalid === true; + }); + }); + }; + }); + + afterEach(function() { + this.localStorageFactory(this.TB_STORAGE).clearAll(); + this.localStorageFactory(this.LEPROSY_STORAGE).clearAll(); + }); + + // OLMIS-8128: validation state (isInvalid) is session-only and must never be + // persisted, otherwise red highlights survive a page refresh. + describe('handleSaveInLocalStorage', function() { + + it('persists field values but never the isInvalid flag', function() { + this.vm.handleSaveInLocalStorage(this.vm.TBTitle, this.rowsWith('5', true)); + + var stored = this.localStorageFactory(this.TB_STORAGE).getAll()[0]; + + expect(stored[0].data[0].value).toBe('5'); + expect(stored[0].data[0].isInvalid).toBe(false); + }); + + it('does not mutate the in-memory rows so in-session highlighting is preserved', function() { + var rows = this.rowsWith('', true); + + this.vm.handleSaveInLocalStorage(this.vm.TBTitle, rows); + + expect(rows[0].data[0].isInvalid).toBe(true); + }); + + it('strips the isInvalid flag for the Leprosy table as well', function() { + this.vm.handleSaveInLocalStorage(this.vm.LeprosyTitle, this.rowsWith('', true)); + + expect(this.anyStoredInvalid(this.LEPROSY_STORAGE)).toBe(false); + }); + }); + + describe('isSubmitInProgress handler', function() { + + it('marks empty fields red in memory but stores them clean, so a refresh shows no red', function() { + this.vm.$onInit(); + + this.$rootScope.$broadcast('isSubmitInProgress', true); + + var anyMemoryInvalid = this.vm.TBRowsNew.some(function(row) { + return row.data.some(function(field) { + return field.isInvalid === true; + }); + }); + + expect(anyMemoryInvalid).toBe(true); + expect(this.anyStoredInvalid(this.TB_STORAGE)).toBe(false); + }); + }); + + describe('$onInit', function() { + + it('does not flag the form invalid when restoring clean data after a refresh', function() { + this.localStorageFactory(this.TB_STORAGE).clearAll(); + this.localStorageFactory(this.TB_STORAGE).put(this.rowsWith('', false)); + + this.vm.$onInit(); + + expect(this.vm.isFormInvalid).toBe(false); + }); + }); +});