diff --git a/android/app/src/main/java/com/hyzq/boss/ProjectChatUiState.java b/android/app/src/main/java/com/hyzq/boss/ProjectChatUiState.java index 53bacfe..449fdbf 100644 --- a/android/app/src/main/java/com/hyzq/boss/ProjectChatUiState.java +++ b/android/app/src/main/java/com/hyzq/boss/ProjectChatUiState.java @@ -2,6 +2,7 @@ package com.hyzq.boss; import androidx.annotation.Nullable; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; @@ -12,9 +13,10 @@ public final class ProjectChatUiState { public final boolean multiSelecting; public final Set selectedMessageIds; - public SelectionState(boolean multiSelecting, Set selectedMessageIds) { - this.multiSelecting = multiSelecting; - this.selectedMessageIds = new LinkedHashSet<>(selectedMessageIds); + private SelectionState(Set selectedMessageIds) { + LinkedHashSet normalizedIds = new LinkedHashSet<>(selectedMessageIds); + this.multiSelecting = !normalizedIds.isEmpty(); + this.selectedMessageIds = Collections.unmodifiableSet(normalizedIds); } } @@ -27,21 +29,24 @@ public final class ProjectChatUiState { } public static SelectionState emptySelection() { - return new SelectionState(false, new LinkedHashSet<>()); + return new SelectionState(new LinkedHashSet<>()); } public static SelectionState toggleSelection(@Nullable SelectionState current, String messageId) { + if (messageId == null || messageId.trim().isEmpty()) { + throw new IllegalArgumentException("messageId must not be blank"); + } SelectionState state = current == null ? emptySelection() : current; LinkedHashSet selectedMessageIds = new LinkedHashSet<>(state.selectedMessageIds); if (selectedMessageIds.contains(messageId)) { selectedMessageIds.remove(messageId); - return new SelectionState(!selectedMessageIds.isEmpty(), selectedMessageIds); + return new SelectionState(selectedMessageIds); } selectedMessageIds.add(messageId); - return new SelectionState(true, selectedMessageIds); + return new SelectionState(selectedMessageIds); } public static boolean canForwardSelection(@Nullable SelectionState state) { - return state != null && state.selectedMessageIds.size() >= 2; + return state != null && state.multiSelecting && state.selectedMessageIds.size() >= 2; } } diff --git a/android/app/src/test/java/com/hyzq/boss/ProjectChatUiStateTest.java b/android/app/src/test/java/com/hyzq/boss/ProjectChatUiStateTest.java index e1e89ee..534754f 100644 --- a/android/app/src/test/java/com/hyzq/boss/ProjectChatUiStateTest.java +++ b/android/app/src/test/java/com/hyzq/boss/ProjectChatUiStateTest.java @@ -1,8 +1,12 @@ package com.hyzq.boss; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; import org.junit.Test; @@ -32,8 +36,7 @@ public class ProjectChatUiStateTest { @Test public void deselectingLastMessageExitsMultiSelectMode() { - ProjectChatUiState.SelectionState state = - new ProjectChatUiState.SelectionState(true, java.util.Set.of("m1")); + ProjectChatUiState.SelectionState state = ProjectChatUiState.toggleSelection(null, "m1"); ProjectChatUiState.SelectionState next = ProjectChatUiState.toggleSelection(state, "m1"); assertFalse(next.multiSelecting); assertTrue(next.selectedMessageIds.isEmpty()); @@ -41,8 +44,32 @@ public class ProjectChatUiStateTest { @Test public void bundleForwardRequiresAtLeastTwoMessages() { - ProjectChatUiState.SelectionState state = - new ProjectChatUiState.SelectionState(true, java.util.Set.of("m1")); + ProjectChatUiState.SelectionState state = ProjectChatUiState.toggleSelection(null, "m1"); assertFalse(ProjectChatUiState.canForwardSelection(state)); + + ProjectChatUiState.SelectionState next = ProjectChatUiState.toggleSelection(state, "m2"); + assertTrue(ProjectChatUiState.canForwardSelection(next)); + } + + @Test + public void selectionPreservesInsertionOrder() { + ProjectChatUiState.SelectionState state = ProjectChatUiState.toggleSelection(null, "m2"); + state = ProjectChatUiState.toggleSelection(state, "m1"); + state = ProjectChatUiState.toggleSelection(state, "m3"); + + assertArrayEquals( + new String[] {"m2", "m1", "m3"}, + new ArrayList<>(state.selectedMessageIds).toArray(new String[0]) + ); + } + + @Test + public void toggleSelectionRejectsBlankMessageIds() { + try { + ProjectChatUiState.toggleSelection(null, " "); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertEquals("messageId must not be blank", expected.getMessage()); + } } }