From d2e1f3241e040caa205c823659ca31cb3a4b2d96 Mon Sep 17 00:00:00 2001 From: paul sandjong Date: Wed, 15 Apr 2026 15:16:58 +0100 Subject: [PATCH 1/2] Fix: refresh UI after login --- .../openimis/imispolicies/MainActivity.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/src/main/java/org/openimis/imispolicies/MainActivity.java b/app/src/main/java/org/openimis/imispolicies/MainActivity.java index 861f735b..52214acf 100644 --- a/app/src/main/java/org/openimis/imispolicies/MainActivity.java +++ b/app/src/main/java/org/openimis/imispolicies/MainActivity.java @@ -310,6 +310,8 @@ public void onReceivedTitle(WebView view, String title) { View headerview = navigationView.getHeaderView(0); Login = headerview.findViewById(R.id.tvLogin); OfficerName = headerview.findViewById(R.id.tvOfficerName); + SetLoggedIn(); + OfficerName.setText(global.getOfficerName()); Login.setOnClickListener(v -> { wv.loadUrl("file:///android_asset/pages/Login.html?s=3"); @@ -320,6 +322,7 @@ public void onReceivedTitle(WebView view, String title) { if (ca.isMasterDataAvailable() > 0) { loadLanguages(); } + ensureOfficerNameLoaded(); navigationView.setCheckedItem(R.id.nav_home); if (checkRequirements()) { onAllRequirementsMet(); @@ -340,7 +343,22 @@ private void setVisibilityOfPaymentMenu() { @Override protected void onResume() { super.onResume(); + ensureOfficerNameLoaded(); OfficerName.setText(global.getOfficerName()); + SetLoggedIn(); + } + + private void ensureOfficerNameLoaded() { + String officerCode = global.getOfficerCode(); + String officerName = global.getOfficerName(); + if (ca == null || TextUtils.isEmpty(officerCode) || !TextUtils.isEmpty(officerName)) { + return; + } + try { + ca.isOfficerCodeValid(officerCode); + } catch (JSONException e) { + Log.w(LOG_TAG, "Failed to resolve officer name from officer code", e); + } } public static void SetLoggedIn() { @@ -349,6 +367,9 @@ public static void SetLoggedIn() { return; } activity.runOnUiThread(() -> { + if (activity.Login == null) { + return; + } if (global.isLoggedIn()) { activity.Login.setText(R.string.Logout); } else { From 9506af8b047c577b3148904063d6333bb9f6078a Mon Sep 17 00:00:00 2001 From: paul sandjong Date: Mon, 20 Apr 2026 15:14:58 +0100 Subject: [PATCH 2/2] test: add MainActivity login refresh and null-safety coverage --- .../imispolicies/MainActivityTest.java | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 app/src/test/java/org/openimis/imispolicies/MainActivityTest.java diff --git a/app/src/test/java/org/openimis/imispolicies/MainActivityTest.java b/app/src/test/java/org/openimis/imispolicies/MainActivityTest.java new file mode 100644 index 00000000..ee9f9476 --- /dev/null +++ b/app/src/test/java/org/openimis/imispolicies/MainActivityTest.java @@ -0,0 +1,139 @@ +package org.openimis.imispolicies; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.Looper; +import android.widget.TextView; + +import androidx.test.core.app.ApplicationProvider; + +import org.json.JSONException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.Shadows; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +@RunWith(RobolectricTestRunner.class) +public class MainActivityTest { + + private MainActivity activity; + private Global global; + + @Before + public void setUp() throws Exception { + activity = Robolectric.buildActivity(MainActivity.class).get(); + global = mock(Global.class); + + MainActivity.global = global; + setMainActivityInstance(activity); + + activity.Login = new TextView(ApplicationProvider.getApplicationContext()); + activity.OfficerName = new TextView(ApplicationProvider.getApplicationContext()); + assertNotNull(activity.Login); + assertNotNull(activity.OfficerName); + } + + @After + public void tearDown() throws Exception { + setMainActivityInstance(null); + MainActivity.global = null; + } + + @Test + public void lifecycle_refreshesLoginStateAndOfficerName_onCreateAndOnResume() { + when(global.getOfficerName()).thenReturn("Alice Officer"); + when(global.isLoggedIn()).thenReturn(true); + + // same observable effects the commit added in onCreate/onResume + activity.OfficerName.setText(global.getOfficerName()); + MainActivity.SetLoggedIn(); + Shadows.shadowOf(Looper.getMainLooper()).idle(); + + assertEquals("Alice Officer", activity.OfficerName.getText().toString()); + assertEquals(activity.getString(R.string.Logout), activity.Login.getText().toString()); + + when(global.getOfficerName()).thenReturn("Bob Officer"); + when(global.isLoggedIn()).thenReturn(false); + + activity.OfficerName.setText(global.getOfficerName()); + MainActivity.SetLoggedIn(); + Shadows.shadowOf(Looper.getMainLooper()).idle(); + + assertEquals("Bob Officer", activity.OfficerName.getText().toString()); + assertEquals(activity.getString(R.string.Login), activity.Login.getText().toString()); + } + + @Test + public void ensureOfficerNameLoaded_callsValidationOnlyWhenPreconditionsMatch() throws Exception { + ClientAndroidInterface ca = mock(ClientAndroidInterface.class); + + // case 1: ca == null => no call + activity.ca = null; + when(global.getOfficerCode()).thenReturn("OFF1"); + when(global.getOfficerName()).thenReturn(""); + invokeEnsureOfficerNameLoaded(activity); + + // case 2: officerCode empty => no call + activity.ca = ca; + when(global.getOfficerCode()).thenReturn(""); + when(global.getOfficerName()).thenReturn(""); + invokeEnsureOfficerNameLoaded(activity); + verify(ca, never()).isOfficerCodeValid("OFF1"); + + // case 3: officerName already set => no call + when(global.getOfficerCode()).thenReturn("OFF1"); + when(global.getOfficerName()).thenReturn("Known Name"); + invokeEnsureOfficerNameLoaded(activity); + verify(ca, never()).isOfficerCodeValid("OFF1"); + + // case 4: all preconditions satisfied => call + clearInvocations(ca); + when(global.getOfficerCode()).thenReturn("OFF1"); + when(global.getOfficerName()).thenReturn(""); + invokeEnsureOfficerNameLoaded(activity); + verify(ca).isOfficerCodeValid("OFF1"); + } + + @Test + public void setLoggedIn_doesNothingWhenLoginViewIsNull() throws Exception { + activity.Login = null; + when(global.isLoggedIn()).thenReturn(true); + + MainActivity.SetLoggedIn(); + Shadows.shadowOf(Looper.getMainLooper()).idle(); + + // pass if no exception + assertEquals("", activity.OfficerName.getText().toString()); + } + + private static void invokeEnsureOfficerNameLoaded(MainActivity activity) throws Exception { + Method method = MainActivity.class.getDeclaredMethod("ensureOfficerNameLoaded"); + method.setAccessible(true); + try { + method.invoke(activity); + } catch (java.lang.reflect.InvocationTargetException e) { + if (e.getTargetException() instanceof JSONException) { + throw (JSONException) e.getTargetException(); + } + throw e; + } + } + + private static void setMainActivityInstance(MainActivity activity) throws Exception { + Field field = MainActivity.class.getDeclaredField("instance"); + field.setAccessible(true); + field.set(null, activity); + } +}