diff --git a/.github/workflows/compatibility-matrix.json b/.github/workflows/compatibility-matrix.json index e9fba377..7f872773 100644 --- a/.github/workflows/compatibility-matrix.json +++ b/.github/workflows/compatibility-matrix.json @@ -22,6 +22,13 @@ "java": "17", "kotlin": "1.9.20" }, + { + "name": "AGP 8.9 - Latest 8.x", + "agp": "8.9.1", + "gradle": "9.3.0", + "java": "17", + "kotlin": "1.9.20" + }, { "name": "AGP 9.0 - Stable", "agp": "9.0.0", diff --git a/agent-core/src/main/java/com/newrelic/agent/android/util/Util.java b/agent-core/src/main/java/com/newrelic/agent/android/util/Util.java index 375c8a11..606497ea 100644 --- a/agent-core/src/main/java/com/newrelic/agent/android/util/Util.java +++ b/agent-core/src/main/java/com/newrelic/agent/android/util/Util.java @@ -7,10 +7,11 @@ import java.net.MalformedURLException; import java.net.URL; +import java.security.SecureRandom; import java.util.Random; public class Util { - private static final Random random = new Random(); + private static final Random random = new SecureRandom(); public static String sanitizeUrl(String urlString) { if (urlString == null) { diff --git a/agent/src/main/java/com/newrelic/agent/android/AndroidAgentImpl.java b/agent/src/main/java/com/newrelic/agent/android/AndroidAgentImpl.java index 0b0db6d1..fa91f8be 100644 --- a/agent/src/main/java/com/newrelic/agent/android/AndroidAgentImpl.java +++ b/agent/src/main/java/com/newrelic/agent/android/AndroidAgentImpl.java @@ -91,7 +91,13 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -332,11 +338,31 @@ public EnvironmentInformation getEnvironmentInformation() { } // Safely sample memory usage, handling potential null returns - Sample memorySample = Sampler.sampleMemory(activityManager); - if (memorySample != null && memorySample.getSampleValue() != null) { - envInfo.setMemoryUsage(memorySample.getSampleValue().asLong()); - } else { + final ExecutorService executor = Executors.newSingleThreadExecutor(); + final Future future = executor.submit(new Callable() { + @Override + public Sample call() throws Exception { + return Sampler.sampleMemory(activityManager); + } + }); + + try { + Sample memorySample = future.get(100, TimeUnit.MILLISECONDS); + if (memorySample != null && memorySample.getSampleValue() != null) { + envInfo.setMemoryUsage(memorySample.getSampleValue().asLong()); + } else { + envInfo.setMemoryUsage(0L); + } + } catch (TimeoutException e) { + log.warn("Timed out while sampling memory, returning 0."); + future.cancel(true); envInfo.setMemoryUsage(0L); + } catch (Exception e) { + log.error("Exception while sampling memory", e); + AgentHealth.noticeException(e); + envInfo.setMemoryUsage(0L); + } finally { + executor.shutdownNow(); } envInfo.setOrientation(context.getResources().getConfiguration().orientation); diff --git a/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SemanticsNodeTouchHandlerTest.java b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SemanticsNodeTouchHandlerTest.java new file mode 100644 index 00000000..8ac19e74 --- /dev/null +++ b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SemanticsNodeTouchHandlerTest.java @@ -0,0 +1,567 @@ +///* +// * Copyright (c) 2024. New Relic Corporation. All rights reserved. +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package com.newrelic.agent.android.sessionReplay; +// +//import android.content.Context; +//import android.view.View; +// +//import androidx.compose.ui.geometry.Rect; +//import androidx.compose.ui.platform.AndroidComposeView; +//import androidx.compose.ui.semantics.SemanticsNode; +//import androidx.compose.ui.semantics.SemanticsOwner; +//import androidx.test.core.app.ApplicationProvider; +// +//import com.newrelic.agent.android.AgentConfiguration; +// +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.robolectric.RobolectricTestRunner; +// +//import java.util.ArrayList; +//import java.util.List; +// +//import static org.mockito.Mockito.mock; +//import static org.mockito.Mockito.when; +// +//@RunWith(RobolectricTestRunner.class) +//public class SemanticsNodeTouchHandlerTest { +// +// private Context context; +// private SessionReplayConfiguration configuration; +// private SemanticsNodeTouchHandler handler; +// +// @Before +// public void setUp() { +// context = ApplicationProvider.getApplicationContext(); +// +// AgentConfiguration agentConfiguration = AgentConfiguration.getInstance(); +// configuration = new SessionReplayConfiguration(); +// configuration.setEnabled(true); +// configuration.setMaskAllUserTouches(false); +// configuration.processCustomMaskingRules(); +// agentConfiguration.setSessionReplayConfiguration(configuration); +// +// handler = new SemanticsNodeTouchHandler(configuration); +// } +// +// // ==================== CONSTRUCTOR TESTS ==================== +// +// @Test +// public void testConstructor_WithValidConfiguration() { +// SemanticsNodeTouchHandler newHandler = new SemanticsNodeTouchHandler(configuration); +// Assert.assertNotNull(newHandler); +// } +// +// @Test +// public void testConstructor_WithNullConfiguration() { +// SemanticsNodeTouchHandler newHandler = new SemanticsNodeTouchHandler(null); +// Assert.assertNotNull(newHandler); +// } +// +// // ==================== GET COMPOSE SEMANTICS NODE - NULL TESTS ==================== +// +// @Test +// public void testGetComposeSemanticsNode_NullView_ReturnsNull() { +// Object result = handler.getComposeSemanticsNode(null, 100, 100); +// Assert.assertNull(result); +// } +// +// @Test +// public void testGetComposeSemanticsNode_ViewWithNullParent_ReturnsNull() { +// View view = new View(context); +// // View has no parent +// +// Object result = handler.getComposeSemanticsNode(view, 100, 100); +// Assert.assertNull(result); +// } +// +// @Test +// public void testGetComposeSemanticsNode_RegularView_ReturnsNull() { +// View view = new View(context); +// // Not an AndroidComposeView +// +// Object result = handler.getComposeSemanticsNode(view, 100, 100); +// Assert.assertNull(result); +// } +// +// @Test +// public void testGetComposeSemanticsNode_WithAndroidComposeView() { +// // Mock AndroidComposeView +// AndroidComposeView composeView = mock(AndroidComposeView.class); +// SemanticsOwner semanticsOwner = mock(SemanticsOwner.class); +// SemanticsNode rootNode = mock(SemanticsNode.class); +// Rect bounds = new Rect(0f, 0f, 200f, 200f); +// +// when(composeView.getSemanticsOwner()).thenReturn(semanticsOwner); +// when(semanticsOwner.getUnmergedRootSemanticsNode()).thenReturn(rootNode); +// when(rootNode.getBoundsInRoot()).thenReturn(bounds); +// when(rootNode.getChildren()).thenReturn(new ArrayList<>()); +// +// // Touch within bounds +// Object result = handler.getComposeSemanticsNode(composeView, 100, 100); +// +// // Should return the root node +// Assert.assertNotNull(result); +// Assert.assertEquals(rootNode, result); +// } +// +// @Test +// public void testGetComposeSemanticsNode_WithException_ReturnsNull() { +// // Mock view that throws exception +// View view = mock(View.class); +// when(view.getParent()).thenThrow(new RuntimeException("Test exception")); +// +// Object result = handler.getComposeSemanticsNode(view, 100, 100); +// +// // Should catch exception and return null +// Assert.assertNull(result); +// } +// +// // ==================== FIND NODE AT POSITION - NULL TESTS ==================== +// +// @Test +// public void testFindNodeAtPosition_NullNode_ReturnsNull() { +// Object result = handler.findNodeAtPosition(null, 100, 100); +// Assert.assertNull(result); +// } +// +// @Test +// public void testFindNodeAtPosition_NodeWithNullBounds_ReturnsNull() { +// SemanticsNode node = mock(SemanticsNode.class); +// when(node.getBoundsInRoot()).thenReturn(null); +// +// Object result = handler.findNodeAtPosition(node, 100, 100); +// +// Assert.assertNull(result); +// } +// +// // ==================== FIND NODE AT POSITION - BOUNDS TESTS ==================== +// +// @Test +// public void testFindNodeAtPosition_TouchWithinBounds_ReturnsNode() { +// SemanticsNode node = mock(SemanticsNode.class); +// Rect bounds = new Rect(0f, 0f, 200f, 200f); +// +// when(node.getBoundsInRoot()).thenReturn(bounds); +// when(node.getChildren()).thenReturn(new ArrayList<>()); +// +// // Touch at (100, 100) is within (0, 0, 200, 200) +// Object result = handler.findNodeAtPosition(node, 100, 100); +// +// Assert.assertNotNull(result); +// Assert.assertEquals(node, result); +// } +// +// @Test +// public void testFindNodeAtPosition_TouchOutsideBounds_ReturnsNull() { +// SemanticsNode node = mock(SemanticsNode.class); +// Rect bounds = new Rect(0f, 0f, 100f, 100f); +// +// when(node.getBoundsInRoot()).thenReturn(bounds); +// when(node.getChildren()).thenReturn(new ArrayList<>()); +// +// // Touch at (200, 200) is outside (0, 0, 100, 100) +// Object result = handler.findNodeAtPosition(node, 200, 200); +// +// Assert.assertNull(result); +// } +// +// @Test +// public void testFindNodeAtPosition_TouchAtTopLeftCorner() { +// SemanticsNode node = mock(SemanticsNode.class); +// Rect bounds = new Rect(0f, 0f, 100f, 100f); +// +// when(node.getBoundsInRoot()).thenReturn(bounds); +// when(node.getChildren()).thenReturn(new ArrayList<>()); +// +// Object result = handler.findNodeAtPosition(node, 0, 0); +// +// Assert.assertNotNull(result); +// Assert.assertEquals(node, result); +// } +// +// @Test +// public void testFindNodeAtPosition_TouchAtBottomRightCorner() { +// SemanticsNode node = mock(SemanticsNode.class); +// Rect bounds = new Rect(0f, 0f, 100f, 100f); +// +// when(node.getBoundsInRoot()).thenReturn(bounds); +// when(node.getChildren()).thenReturn(new ArrayList<>()); +// +// Object result = handler.findNodeAtPosition(node, 100, 100); +// +// Assert.assertNotNull(result); +// Assert.assertEquals(node, result); +// } +// +// @Test +// public void testFindNodeAtPosition_TouchJustOutsideBounds() { +// SemanticsNode node = mock(SemanticsNode.class); +// Rect bounds = new Rect(0f, 0f, 100f, 100f); +// +// when(node.getBoundsInRoot()).thenReturn(bounds); +// when(node.getChildren()).thenReturn(new ArrayList<>()); +// +// Object result = handler.findNodeAtPosition(node, 101, 101); +// +// Assert.assertNull(result); +// } +// +// // ==================== FIND NODE AT POSITION - HIERARCHY TESTS ==================== +// +// @Test +// public void testFindNodeAtPosition_NoChildren_ReturnsParent() { +// SemanticsNode parentNode = mock(SemanticsNode.class); +// Rect bounds = new Rect(0f, 0f, 200f, 200f); +// +// when(parentNode.getBoundsInRoot()).thenReturn(bounds); +// when(parentNode.getChildren()).thenReturn(new ArrayList<>()); +// +// Object result = handler.findNodeAtPosition(parentNode, 100, 100); +// +// Assert.assertEquals(parentNode, result); +// } +// +// @Test +// public void testFindNodeAtPosition_WithChildren_TouchesChild() { +// SemanticsNode parentNode = mock(SemanticsNode.class); +// SemanticsNode childNode = mock(SemanticsNode.class); +// +// Rect parentBounds = new Rect(0f, 0f, 200f, 200f); +// Rect childBounds = new Rect(50f, 50f, 150f, 150f); +// +// List children = new ArrayList<>(); +// children.add(childNode); +// +// when(parentNode.getBoundsInRoot()).thenReturn(parentBounds); +// when(parentNode.getChildren()).thenReturn(children); +// when(childNode.getBoundsInRoot()).thenReturn(childBounds); +// when(childNode.getChildren()).thenReturn(new ArrayList<>()); +// +// // Touch at (100, 100) is within child bounds +// Object result = handler.findNodeAtPosition(parentNode, 100, 100); +// +// Assert.assertEquals(childNode, result); +// } +// +// @Test +// public void testFindNodeAtPosition_WithChildren_TouchesNoChild_ReturnsParent() { +// SemanticsNode parentNode = mock(SemanticsNode.class); +// SemanticsNode childNode = mock(SemanticsNode.class); +// +// Rect parentBounds = new Rect(0f, 0f, 200f, 200f); +// Rect childBounds = new Rect(100f, 100f, 150f, 150f); +// +// List children = new ArrayList<>(); +// children.add(childNode); +// +// when(parentNode.getBoundsInRoot()).thenReturn(parentBounds); +// when(parentNode.getChildren()).thenReturn(children); +// when(childNode.getBoundsInRoot()).thenReturn(childBounds); +// when(childNode.getChildren()).thenReturn(new ArrayList<>()); +// +// // Touch at (50, 50) is within parent but not child +// Object result = handler.findNodeAtPosition(parentNode, 50, 50); +// +// Assert.assertEquals(parentNode, result); +// } +// +// @Test +// public void testFindNodeAtPosition_NestedHierarchy() { +// SemanticsNode rootNode = mock(SemanticsNode.class); +// SemanticsNode middleNode = mock(SemanticsNode.class); +// SemanticsNode leafNode = mock(SemanticsNode.class); +// +// Rect rootBounds = new Rect(0f, 0f, 300f, 300f); +// Rect middleBounds = new Rect(50f, 50f, 250f, 250f); +// Rect leafBounds = new Rect(100f, 100f, 200f, 200f); +// +// List rootChildren = new ArrayList<>(); +// rootChildren.add(middleNode); +// List middleChildren = new ArrayList<>(); +// middleChildren.add(leafNode); +// +// when(rootNode.getBoundsInRoot()).thenReturn(rootBounds); +// when(rootNode.getChildren()).thenReturn(rootChildren); +// when(middleNode.getBoundsInRoot()).thenReturn(middleBounds); +// when(middleNode.getChildren()).thenReturn(middleChildren); +// when(leafNode.getBoundsInRoot()).thenReturn(leafBounds); +// when(leafNode.getChildren()).thenReturn(new ArrayList<>()); +// +// // Touch at (150, 150) should find leaf node +// Object result = handler.findNodeAtPosition(rootNode, 150, 150); +// +// Assert.assertEquals(leafNode, result); +// } +// +// @Test +// public void testFindNodeAtPosition_MultipleChildren_FindsCorrectOne() { +// SemanticsNode parentNode = mock(SemanticsNode.class); +// SemanticsNode child1 = mock(SemanticsNode.class); +// SemanticsNode child2 = mock(SemanticsNode.class); +// SemanticsNode child3 = mock(SemanticsNode.class); +// +// Rect parentBounds = new Rect(0f, 0f, 300f, 300f); +// Rect bounds1 = new Rect(0f, 0f, 100f, 100f); +// Rect bounds2 = new Rect(100f, 100f, 200f, 200f); +// Rect bounds3 = new Rect(200f, 200f, 300f, 300f); +// +// List children = new ArrayList<>(); +// children.add(child1); +// children.add(child2); +// children.add(child3); +// +// when(parentNode.getBoundsInRoot()).thenReturn(parentBounds); +// when(parentNode.getChildren()).thenReturn(children); +// when(child1.getBoundsInRoot()).thenReturn(bounds1); +// when(child1.getChildren()).thenReturn(new ArrayList<>()); +// when(child2.getBoundsInRoot()).thenReturn(bounds2); +// when(child2.getChildren()).thenReturn(new ArrayList<>()); +// when(child3.getBoundsInRoot()).thenReturn(bounds3); +// when(child3.getChildren()).thenReturn(new ArrayList<>()); +// +// // Touch at (150, 150) should find child2 +// Object result = handler.findNodeAtPosition(parentNode, 150, 150); +// +// Assert.assertEquals(child2, result); +// } +// +// // ==================== GET SEMANTICS NODE STABLE ID - NULL TESTS ==================== +// +// @Test +// public void testGetSemanticsNodeStableId_NullNode_ReturnsMinusOne() { +// int id = handler.getSemanticsNodeStableId(null); +// Assert.assertEquals(-1, id); +// } +// +// // ==================== GET SEMANTICS NODE STABLE ID - BASIC TESTS ==================== +// +// @Test +// public void testGetSemanticsNodeStableId_ValidNode_ReturnsNodeId() { +// SemanticsNode node = mock(SemanticsNode.class); +// when(node.getId()).thenReturn(12345); +// +// int id = handler.getSemanticsNodeStableId(node); +// +// Assert.assertEquals(12345, id); +// } +// +// @Test +// public void testGetSemanticsNodeStableId_DifferentNodes_DifferentIds() { +// SemanticsNode node1 = mock(SemanticsNode.class); +// SemanticsNode node2 = mock(SemanticsNode.class); +// +// when(node1.getId()).thenReturn(100); +// when(node2.getId()).thenReturn(200); +// +// int id1 = handler.getSemanticsNodeStableId(node1); +// int id2 = handler.getSemanticsNodeStableId(node2); +// +// Assert.assertEquals(100, id1); +// Assert.assertEquals(200, id2); +// Assert.assertNotEquals(id1, id2); +// } +// +// // ==================== GET SEMANTICS NODE STABLE ID - MASKING TESTS ==================== +// +// @Test +// public void testGetSemanticsNodeStableId_MaskAllUserTouches_ReturnsMaskedId() { +// configuration.setMaskAllUserTouches(true); +// +// SemanticsNode node = mock(SemanticsNode.class); +// when(node.getId()).thenReturn(12345); +// +// int id = handler.getSemanticsNodeStableId(node); +// +// // Should return MASKED_TOUCH_ID (0) instead of actual ID +// Assert.assertEquals(0, id); +// } +// +// @Test +// public void testGetSemanticsNodeStableId_NoMasking_ReturnsActualId() { +// configuration.setMaskAllUserTouches(false); +// +// SemanticsNode node = mock(SemanticsNode.class); +// when(node.getId()).thenReturn(12345); +// +// int id = handler.getSemanticsNodeStableId(node); +// +// // Should return actual ID +// Assert.assertEquals(12345, id); +// } +// +// @Test +// public void testGetSemanticsNodeStableId_MultipleCalls_SameNode_SameId() { +// SemanticsNode node = mock(SemanticsNode.class); +// when(node.getId()).thenReturn(12345); +// +// int id1 = handler.getSemanticsNodeStableId(node); +// int id2 = handler.getSemanticsNodeStableId(node); +// int id3 = handler.getSemanticsNodeStableId(node); +// +// Assert.assertEquals(id1, id2); +// Assert.assertEquals(id2, id3); +// } +// +// // ==================== EDGE CASE TESTS ==================== +// +// @Test +// public void testFindNodeAtPosition_NegativeCoordinates() { +// SemanticsNode node = mock(SemanticsNode.class); +// Rect bounds = new Rect(0f, 0f, 100f, 100f); +// +// when(node.getBoundsInRoot()).thenReturn(bounds); +// when(node.getChildren()).thenReturn(new ArrayList<>()); +// +// Object result = handler.findNodeAtPosition(node, -10, -10); +// +// Assert.assertNull(result); +// } +// +// @Test +// public void testFindNodeAtPosition_VeryLargeCoordinates() { +// SemanticsNode node = mock(SemanticsNode.class); +// Rect bounds = new Rect(0f, 0f, 100f, 100f); +// +// when(node.getBoundsInRoot()).thenReturn(bounds); +// when(node.getChildren()).thenReturn(new ArrayList<>()); +// +// Object result = handler.findNodeAtPosition(node, Integer.MAX_VALUE, Integer.MAX_VALUE); +// +// Assert.assertNull(result); +// } +// +// @Test +// public void testFindNodeAtPosition_ZeroSizedBounds() { +// SemanticsNode node = mock(SemanticsNode.class); +// Rect bounds = new Rect(50f, 50f, 50f, 50f); // Zero width/height +// +// when(node.getBoundsInRoot()).thenReturn(bounds); +// when(node.getChildren()).thenReturn(new ArrayList<>()); +// +// Object result = handler.findNodeAtPosition(node, 50, 50); +// +// // Touch exactly at the point should be included (<=, >= checks) +// Assert.assertNotNull(result); +// } +// +// @Test +// public void testGetSemanticsNodeStableId_ZeroId() { +// SemanticsNode node = mock(SemanticsNode.class); +// when(node.getId()).thenReturn(0); +// +// int id = handler.getSemanticsNodeStableId(node); +// +// Assert.assertEquals(0, id); +// } +// +// @Test +// public void testGetSemanticsNodeStableId_NegativeId() { +// SemanticsNode node = mock(SemanticsNode.class); +// when(node.getId()).thenReturn(-1); +// +// int id = handler.getSemanticsNodeStableId(node); +// +// Assert.assertEquals(-1, id); +// } +// +// // ==================== CONFIGURATION TESTS ==================== +// +// @Test +// public void testConfiguration_MaskAllUserTouches_ToggleOn() { +// SemanticsNode node = mock(SemanticsNode.class); +// when(node.getId()).thenReturn(12345); +// +// configuration.setMaskAllUserTouches(false); +// int idBefore = handler.getSemanticsNodeStableId(node); +// +// configuration.setMaskAllUserTouches(true); +// int idAfter = handler.getSemanticsNodeStableId(node); +// +// Assert.assertEquals(12345, idBefore); +// Assert.assertEquals(0, idAfter); // Masked +// } +// +// @Test +// public void testConfiguration_WithDifferentConfigurations() { +// SessionReplayConfiguration config1 = new SessionReplayConfiguration(); +// config1.setMaskAllUserTouches(true); +// +// SessionReplayConfiguration config2 = new SessionReplayConfiguration(); +// config2.setMaskAllUserTouches(false); +// +// SemanticsNodeTouchHandler handler1 = new SemanticsNodeTouchHandler(config1); +// SemanticsNodeTouchHandler handler2 = new SemanticsNodeTouchHandler(config2); +// +// SemanticsNode node = mock(SemanticsNode.class); +// when(node.getId()).thenReturn(12345); +// +// int id1 = handler1.getSemanticsNodeStableId(node); +// int id2 = handler2.getSemanticsNodeStableId(node); +// +// Assert.assertEquals(0, id1); // Masked +// Assert.assertEquals(12345, id2); // Not masked +// } +// +// // ==================== INTEGRATION TESTS ==================== +// +// @Test +// public void testIntegration_FindAndGetId() { +// SemanticsNode node = mock(SemanticsNode.class); +// Rect bounds = new Rect(0f, 0f, 200f, 200f); +// +// when(node.getBoundsInRoot()).thenReturn(bounds); +// when(node.getChildren()).thenReturn(new ArrayList<>()); +// when(node.getId()).thenReturn(12345); +// +// // Find node at position +// Object foundNode = handler.findNodeAtPosition(node, 100, 100); +// Assert.assertNotNull(foundNode); +// +// // Get stable ID for found node +// int id = handler.getSemanticsNodeStableId((SemanticsNode) foundNode); +// Assert.assertEquals(12345, id); +// } +// +// @Test +// public void testIntegration_HierarchyTraversal() { +// // Create a 3-level hierarchy +// SemanticsNode root = mock(SemanticsNode.class); +// SemanticsNode middle = mock(SemanticsNode.class); +// SemanticsNode leaf = mock(SemanticsNode.class); +// +// Rect rootBounds = new Rect(0f, 0f, 300f, 300f); +// Rect middleBounds = new Rect(50f, 50f, 250f, 250f); +// Rect leafBounds = new Rect(100f, 100f, 200f, 200f); +// +// List rootChildren = new ArrayList<>(); +// rootChildren.add(middle); +// List middleChildren = new ArrayList<>(); +// middleChildren.add(leaf); +// +// when(root.getBoundsInRoot()).thenReturn(rootBounds); +// when(root.getChildren()).thenReturn(rootChildren); +// when(root.getId()).thenReturn(1); +// +// when(middle.getBoundsInRoot()).thenReturn(middleBounds); +// when(middle.getChildren()).thenReturn(middleChildren); +// when(middle.getId()).thenReturn(2); +// +// when(leaf.getBoundsInRoot()).thenReturn(leafBounds); +// when(leaf.getChildren()).thenReturn(new ArrayList<>()); +// when(leaf.getId()).thenReturn(3); +// +// // Find leaf node +// Object found = handler.findNodeAtPosition(root, 150, 150); +// Assert.assertEquals(leaf, found); +// +// // Get its ID +// int id = handler.getSemanticsNodeStableId((SemanticsNode) found); +// Assert.assertEquals(3, id); +// } +//} \ No newline at end of file diff --git a/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayActivityLifecycleCallbacksTest.java b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayActivityLifecycleCallbacksTest.java new file mode 100644 index 00000000..6789b65d --- /dev/null +++ b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayActivityLifecycleCallbacksTest.java @@ -0,0 +1,554 @@ +///* +// * Copyright (c) 2024. New Relic Corporation. All rights reserved. +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package com.newrelic.agent.android.sessionReplay; +// +//import android.app.Activity; +//import android.app.Application; +//import android.os.Bundle; +//import android.view.MotionEvent; +//import android.view.View; +// +//import androidx.test.core.app.ApplicationProvider; +// +//import com.newrelic.agent.android.AgentConfiguration; +// +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.robolectric.Robolectric; +//import org.robolectric.RobolectricTestRunner; +//import org.robolectric.android.controller.ActivityController; +// +//import java.util.concurrent.atomic.AtomicInteger; +//import java.util.concurrent.atomic.AtomicReference; +// +//@RunWith(RobolectricTestRunner.class) +//public class SessionReplayActivityLifecycleCallbacksTest { +// +// private Application application; +// private AgentConfiguration agentConfiguration; +// private SessionReplayConfiguration sessionReplayConfiguration; +// private OnTouchRecordedListener mockTouchListener; +// private SessionReplayModeManager modeManager; +// private SessionReplayActivityLifecycleCallbacks callbacks; +// private AtomicInteger touchRecordedCount; +// private AtomicReference lastTouchTracker; +// +// @Before +// public void setUp() { +// application = ApplicationProvider.getApplicationContext(); +// agentConfiguration = AgentConfiguration.getInstance(); +// agentConfiguration.setSessionID("test-session-" + System.currentTimeMillis()); +// +// sessionReplayConfiguration = new SessionReplayConfiguration(); +// sessionReplayConfiguration.setEnabled(true); +// sessionReplayConfiguration.setMaskAllUserTouches(false); +// sessionReplayConfiguration.processCustomMaskingRules(); +// agentConfiguration.setSessionReplayConfiguration(sessionReplayConfiguration); +// +// modeManager = new SessionReplayModeManager(sessionReplayConfiguration); +// +// touchRecordedCount = new AtomicInteger(0); +// lastTouchTracker = new AtomicReference<>(null); +// +// mockTouchListener = new OnTouchRecordedListener() { +// @Override +// public void onTouchRecorded(TouchTracker touchTracker) { +// touchRecordedCount.incrementAndGet(); +// lastTouchTracker.set(touchTracker); +// } +// }; +// +// callbacks = new SessionReplayActivityLifecycleCallbacks(mockTouchListener, application, modeManager); +// } +// +// // ==================== CONSTRUCTOR TESTS ==================== +// +// @Test +// public void testConstructor_WithValidParameters() { +// SessionReplayActivityLifecycleCallbacks newCallbacks = new SessionReplayActivityLifecycleCallbacks( +// mockTouchListener, +// application, +// modeManager +// ); +// Assert.assertNotNull(newCallbacks); +// Assert.assertNotNull(newCallbacks.viewTouchHandler); +// Assert.assertNotNull(newCallbacks.semanticsNodeTouchHandler); +// Assert.assertNotNull(newCallbacks.sessionReplayConfiguration); +// } +// +// @Test +// public void testConstructor_InitializesViewTouchHandler() { +// Assert.assertNotNull(callbacks.viewTouchHandler); +// Assert.assertTrue(callbacks.viewTouchHandler instanceof ViewTouchHandler); +// } +// +// @Test +// public void testConstructor_InitializesSemanticsNodeTouchHandler() { +// Assert.assertNotNull(callbacks.semanticsNodeTouchHandler); +// Assert.assertTrue(callbacks.semanticsNodeTouchHandler instanceof SemanticsNodeTouchHandler); +// } +// +// @Test +// public void testConstructor_InitializesSessionReplayConfiguration() { +// Assert.assertNotNull(callbacks.sessionReplayConfiguration); +// Assert.assertTrue(callbacks.sessionReplayConfiguration instanceof SessionReplayConfiguration); +// } +// +// @Test +// public void testConstructor_WithNullListener() { +// SessionReplayActivityLifecycleCallbacks newCallbacks = new SessionReplayActivityLifecycleCallbacks( +// null, +// application, +// modeManager +// ); +// Assert.assertNotNull(newCallbacks); +// } +// +// @Test +// public void testConstructor_WithNullModeManager() { +// SessionReplayActivityLifecycleCallbacks newCallbacks = new SessionReplayActivityLifecycleCallbacks( +// mockTouchListener, +// application, +// null +// ); +// Assert.assertNotNull(newCallbacks); +// } +// +// // ==================== LIFECYCLE METHOD TESTS ==================== +// +// @Test +// public void testOnActivityCreated_DoesNotThrowException() { +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.create().get(); +// +// // Should not throw exception +// callbacks.onActivityCreated(activity, null); +// } +// +// @Test +// public void testOnActivityCreated_WithBundle() { +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.create().get(); +// Bundle bundle = new Bundle(); +// +// callbacks.onActivityCreated(activity, bundle); +// // Test passes if no exception thrown +// } +// +// @Test +// public void testOnActivityStarted_DoesNotThrowException() { +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.create().start().get(); +// +// callbacks.onActivityStarted(activity); +// } +// +// @Test +// public void testOnActivityResumed_DoesNotThrowException() { +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.create().start().resume().get(); +// +// // May interact with Curtains library +// // Should not throw exception even if Curtains not fully initialized in test +// try { +// callbacks.onActivityResumed(activity); +// } catch (Exception e) { +// // May fail in test environment due to Curtains dependencies +// // Test structure is still valid +// } +// } +// +// @Test +// public void testOnActivityPrePaused_DoesNotThrowException() { +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.create().start().resume().get(); +// +// callbacks.onActivityPrePaused(activity); +// } +// +// @Test +// public void testOnActivityPaused_DoesNotThrowException() { +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.create().start().resume().pause().get(); +// +// callbacks.onActivityPaused(activity); +// } +// +// @Test +// public void testOnActivityStopped_DoesNotThrowException() { +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.create().start().resume().pause().stop().get(); +// +// callbacks.onActivityStopped(activity); +// } +// +// @Test +// public void testOnActivitySaveInstanceState_DoesNotThrowException() { +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.create().get(); +// Bundle bundle = new Bundle(); +// +// callbacks.onActivitySaveInstanceState(activity, bundle); +// } +// +// @Test +// public void testOnActivityDestroyed_DoesNotThrowException() { +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.create().start().resume().pause().stop().destroy().get(); +// +// callbacks.onActivityDestroyed(activity); +// } +// +// // ==================== FULL LIFECYCLE TESTS ==================== +// +// @Test +// public void testFullLifecycle_CreateToDestroy() { +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.get(); +// +// // Full lifecycle +// callbacks.onActivityCreated(activity, null); +// callbacks.onActivityStarted(activity); +// +// try { +// callbacks.onActivityResumed(activity); +// } catch (Exception e) { +// // May fail due to Curtains +// } +// +// callbacks.onActivityPaused(activity); +// callbacks.onActivityStopped(activity); +// callbacks.onActivityDestroyed(activity); +// +// // Test passes if no critical exceptions +// } +// +// @Test +// public void testLifecycle_MultipleResumePause() { +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.get(); +// +// callbacks.onActivityCreated(activity, null); +// callbacks.onActivityStarted(activity); +// +// // Multiple resume-pause cycles +// try { +// callbacks.onActivityResumed(activity); +// } catch (Exception e) { +// // Ignore Curtains errors +// } +// +// callbacks.onActivityPaused(activity); +// +// try { +// callbacks.onActivityResumed(activity); +// } catch (Exception e) { +// // Ignore Curtains errors +// } +// +// callbacks.onActivityPaused(activity); +// +// // Test passes if no exception +// } +// +// // ==================== SETUP TOUCH INTERCEPTOR TESTS ==================== +// +// @Test +// public void testSetupTouchInterceptorForWindow_WithNullView() { +// // Should handle null view gracefully +// try { +// callbacks.setupTouchInterceptorForWindow(null); +// // May throw NullPointerException which is acceptable +// } catch (NullPointerException e) { +// // Expected for null view +// } +// } +// +// @Test +// public void testSetupTouchInterceptorForWindow_WithValidView() { +// View view = new View(application); +// +// // May fail due to Windows.getPhoneWindowForView returning null in test +// // But should not crash +// try { +// callbacks.setupTouchInterceptorForWindow(view); +// } catch (Exception e) { +// // May fail in test environment, structure is valid +// } +// } +// +// @Test +// public void testSetupTouchInterceptorForWindow_WithPopupWindow() { +// View view = new View(application); +// +// // If view is detected as POPUP_WINDOW, should return early +// // Hard to test without mocking Windows.getWindowType() +// try { +// callbacks.setupTouchInterceptorForWindow(view); +// } catch (Exception e) { +// // Test structure is valid +// } +// } +// +// // ==================== TOUCH TRACKING TESTS ==================== +// +// @Test +// public void testTouchTracking_InitialState() { +// // Initial state should have no touch tracker +// Assert.assertEquals("Should have no touches recorded initially", 0, touchRecordedCount.get()); +// Assert.assertNull("Last touch tracker should be null initially", lastTouchTracker.get()); +// } +// +// @Test +// public void testTouchTracking_SingleDownUpSequence() { +// // This is hard to test without full Activity and Window setup +// // The test structure validates the callback interface +// Assert.assertNotNull(mockTouchListener); +// Assert.assertEquals(0, touchRecordedCount.get()); +// } +// +// @Test +// public void testTouchTracking_MultipleDownUpSequences() { +// // Validates that touch tracking state is maintained across multiple touches +// // Actual touch simulation requires full Android framework +// Assert.assertEquals(0, touchRecordedCount.get()); +// } +// +// // ==================== GET PIXEL TESTS ==================== +// +// // getPixel is private, but we can test its behavior indirectly +// // The method divides pixel by density +// +// @Test +// public void testGetPixel_ConversionLogic() { +// // Cannot test private method directly +// // But we verify callbacks was constructed with correct density +// Assert.assertNotNull(callbacks); +// } +// +// // ==================== CONFIGURATION TESTS ==================== +// +// @Test +// public void testConfiguration_MaskAllUserTouches_True() { +// sessionReplayConfiguration.setMaskAllUserTouches(true); +// +// SessionReplayActivityLifecycleCallbacks newCallbacks = new SessionReplayActivityLifecycleCallbacks( +// mockTouchListener, +// application, +// modeManager +// ); +// +// Assert.assertTrue(newCallbacks.sessionReplayConfiguration.isMaskAllUserTouches()); +// } +// +// @Test +// public void testConfiguration_MaskAllUserTouches_False() { +// sessionReplayConfiguration.setMaskAllUserTouches(false); +// +// SessionReplayActivityLifecycleCallbacks newCallbacks = new SessionReplayActivityLifecycleCallbacks( +// mockTouchListener, +// application, +// modeManager +// ); +// +// Assert.assertFalse(newCallbacks.sessionReplayConfiguration.isMaskAllUserTouches()); +// } +// +// // ==================== MODE MANAGER TESTS ==================== +// +// @Test +// public void testModeManager_OffMode() { +// modeManager.setCurrentMode(SessionReplayMode.OFF); +// +// SessionReplayActivityLifecycleCallbacks newCallbacks = new SessionReplayActivityLifecycleCallbacks( +// mockTouchListener, +// application, +// modeManager +// ); +// +// Assert.assertNotNull(newCallbacks); +// // In OFF mode, touches should not be recorded +// } +// +// @Test +// public void testModeManager_DefaultMode() { +// modeManager.setCurrentMode(SessionReplayMode.DEFAULT); +// +// SessionReplayActivityLifecycleCallbacks newCallbacks = new SessionReplayActivityLifecycleCallbacks( +// mockTouchListener, +// application, +// modeManager +// ); +// +// Assert.assertNotNull(newCallbacks); +// } +// +// @Test +// public void testModeManager_ErrorMode() { +// modeManager.setCurrentMode(SessionReplayMode.ERROR); +// +// SessionReplayActivityLifecycleCallbacks newCallbacks = new SessionReplayActivityLifecycleCallbacks( +// mockTouchListener, +// application, +// modeManager +// ); +// +// Assert.assertNotNull(newCallbacks); +// } +// +// // ==================== EDGE CASE TESTS ==================== +// +// @Test +// public void testMultipleActivities() { +// ActivityController controller1 = Robolectric.buildActivity(Activity.class); +// ActivityController controller2 = Robolectric.buildActivity(Activity.class); +// +// Activity activity1 = controller1.create().get(); +// Activity activity2 = controller2.create().get(); +// +// callbacks.onActivityCreated(activity1, null); +// callbacks.onActivityStarted(activity1); +// +// callbacks.onActivityCreated(activity2, null); +// callbacks.onActivityStarted(activity2); +// +// // Should handle multiple activities +// } +// +// @Test +// public void testActivityRecreation_WithSavedState() { +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.get(); +// Bundle savedState = new Bundle(); +// savedState.putString("test_key", "test_value"); +// +// callbacks.onActivityCreated(activity, savedState); +// +// // Should handle activity recreation with saved state +// } +// +// @Test +// public void testRapidLifecycleChanges() { +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.get(); +// +// // Rapid lifecycle changes +// for (int i = 0; i < 10; i++) { +// callbacks.onActivityCreated(activity, null); +// callbacks.onActivityStarted(activity); +// try { +// callbacks.onActivityResumed(activity); +// } catch (Exception e) { +// // Ignore +// } +// callbacks.onActivityPaused(activity); +// callbacks.onActivityStopped(activity); +// } +// +// // Should handle rapid changes without memory leaks or crashes +// } +// +// // ==================== INTEGRATION TESTS ==================== +// +// @Test +// public void testIntegration_WithRealActivityLifecycle() { +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// +// // Use controller to go through real lifecycle +// Activity activity = controller.create().start().resume().get(); +// +// // Register callbacks +// try { +// callbacks.onActivityResumed(activity); +// } catch (Exception e) { +// // May fail due to Curtains, but structure is valid +// } +// +// // Pause +// callbacks.onActivityPaused(activity); +// +// // Test passes if lifecycle is handled correctly +// } +// +// @Test +// public void testIntegration_MultipleCallbackInstances() { +// OnTouchRecordedListener listener1 = touchTracker -> {}; +// OnTouchRecordedListener listener2 = touchTracker -> {}; +// +// SessionReplayActivityLifecycleCallbacks callbacks1 = new SessionReplayActivityLifecycleCallbacks( +// listener1, +// application, +// modeManager +// ); +// +// SessionReplayActivityLifecycleCallbacks callbacks2 = new SessionReplayActivityLifecycleCallbacks( +// listener2, +// application, +// modeManager +// ); +// +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.create().start().resume().get(); +// +// // Both callbacks on same activity +// callbacks1.onActivityCreated(activity, null); +// callbacks2.onActivityCreated(activity, null); +// +// // Should work with multiple callback instances +// } +// +// // ==================== ERROR HANDLING TESTS ==================== +// +// @Test +// public void testErrorHandling_NullActivity() { +// // Some methods may accept null activity +// try { +// callbacks.onActivityCreated(null, null); +// // May throw NullPointerException +// } catch (NullPointerException e) { +// // Expected for null activity +// } +// } +// +// @Test +// public void testErrorHandling_DetachedActivity() { +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.create().start().resume().pause().stop().destroy().get(); +// +// // Callbacks on destroyed activity +// try { +// callbacks.onActivityResumed(activity); +// } catch (Exception e) { +// // May fail, but should not crash app +// } +// } +// +// // ==================== LISTENER TESTS ==================== +// +// @Test +// public void testListener_IsNotNull() { +// Assert.assertNotNull(mockTouchListener); +// } +// +// @Test +// public void testListener_InitialCount() { +// Assert.assertEquals(0, touchRecordedCount.get()); +// } +// +// @Test +// public void testListener_WithNullModeManager_DoesNotCrash() { +// SessionReplayActivityLifecycleCallbacks callbacksWithNullModeManager = +// new SessionReplayActivityLifecycleCallbacks(mockTouchListener, application, null); +// +// ActivityController controller = Robolectric.buildActivity(Activity.class); +// Activity activity = controller.create().get(); +// +// // Should not crash even with null mode manager +// callbacksWithNullModeManager.onActivityCreated(activity, null); +// } +//} \ No newline at end of file diff --git a/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayCaptureTest.java b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayCaptureTest.java new file mode 100644 index 00000000..dbf67930 --- /dev/null +++ b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayCaptureTest.java @@ -0,0 +1,558 @@ +///* +// * Copyright (c) 2024. New Relic Corporation. All rights reserved. +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package com.newrelic.agent.android.sessionReplay; +// +//import android.content.Context; +//import android.view.View; +//import android.widget.FrameLayout; +//import android.widget.ImageView; +//import android.widget.LinearLayout; +//import android.widget.TextView; +// +//import androidx.test.core.app.ApplicationProvider; +// +//import com.newrelic.agent.android.AgentConfiguration; +//import com.newrelic.agent.android.R; +// +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.robolectric.RobolectricTestRunner; +// +//@RunWith(RobolectricTestRunner.class) +//public class SessionReplayCaptureTest { +// +// private Context context; +// private AgentConfiguration agentConfiguration; +// private SessionReplayConfiguration sessionReplayConfiguration; +// private SessionReplayLocalConfiguration sessionReplayLocalConfiguration; +// +// @Before +// public void setUp() { +// context = ApplicationProvider.getApplicationContext(); +// +// agentConfiguration = new AgentConfiguration(); +// +// sessionReplayConfiguration = new SessionReplayConfiguration(); +// sessionReplayConfiguration.setEnabled(true); +// sessionReplayConfiguration.setMode("full"); +// sessionReplayConfiguration.setMaskUserInputText(false); +// sessionReplayConfiguration.setMaskApplicationText(false); +// sessionReplayConfiguration.setMaskAllImages(false); +// +// sessionReplayLocalConfiguration = new SessionReplayLocalConfiguration(); +// +// agentConfiguration.setSessionReplayConfiguration(sessionReplayConfiguration); +// agentConfiguration.setSessionReplayLocalConfiguration(sessionReplayLocalConfiguration); +// } +// +// // ==================== CONSTRUCTOR TESTS ==================== +// +// @Test +// public void testConstructor() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// Assert.assertNotNull(capture); +// } +// +// @Test +// public void testConstructorWithNullConfig() { +// try { +// SessionReplayCapture capture = new SessionReplayCapture(null); +// // May or may not throw - depends on implementation +// } catch (NullPointerException e) { +// // Expected if null config is not allowed +// } +// } +// +// // ==================== SINGLE VIEW CAPTURE TESTS ==================== +// +// @Test +// public void testCapture_SingleView() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// View view = new View(context); +// view.layout(0, 0, 100, 100); +// +// SessionReplayViewThingyInterface thingy = capture.capture(view, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertNotNull(thingy.getViewDetails()); +// } +// +// @Test +// public void testCapture_TextView() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// TextView textView = new TextView(context); +// textView.setText("Test"); +// textView.layout(0, 0, 100, 100); +// +// SessionReplayViewThingyInterface thingy = capture.capture(textView, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayTextViewThingy); +// } +// +// @Test +// public void testCapture_ImageView() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// ImageView imageView = new ImageView(context); +// imageView.layout(0, 0, 100, 100); +// +// SessionReplayViewThingyInterface thingy = capture.capture(imageView, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayImageViewThingy); +// } +// +// // ==================== VIEW GROUP CAPTURE TESTS ==================== +// +// @Test +// public void testCapture_EmptyViewGroup() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// FrameLayout frameLayout = new FrameLayout(context); +// frameLayout.layout(0, 0, 200, 200); +// +// SessionReplayViewThingyInterface thingy = capture.capture(frameLayout, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertNotNull(thingy.getSubviews()); +// Assert.assertTrue(thingy.getSubviews().isEmpty()); +// } +// +// @Test +// public void testCapture_ViewGroupWithOneChild() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// FrameLayout parent = new FrameLayout(context); +// parent.layout(0, 0, 200, 200); +// +// View child = new View(context); +// child.layout(0, 0, 100, 100); +// parent.addView(child); +// +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertNotNull(thingy.getSubviews()); +// Assert.assertEquals(1, thingy.getSubviews().size()); +// } +// +// @Test +// public void testCapture_ViewGroupWithMultipleChildren() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// LinearLayout parent = new LinearLayout(context); +// parent.layout(0, 0, 300, 300); +// +// for (int i = 0; i < 5; i++) { +// View child = new View(context); +// child.layout(0, i * 50, 100, (i + 1) * 50); +// parent.addView(child); +// } +// +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertEquals(5, thingy.getSubviews().size()); +// } +// +// @Test +// public void testCapture_NestedViewGroups() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// +// FrameLayout level1 = new FrameLayout(context); +// level1.layout(0, 0, 300, 300); +// +// LinearLayout level2 = new LinearLayout(context); +// level2.layout(0, 0, 200, 200); +// level1.addView(level2); +// +// View level3 = new View(context); +// level3.layout(0, 0, 100, 100); +// level2.addView(level3); +// +// SessionReplayViewThingyInterface thingy = capture.capture(level1, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertEquals(1, thingy.getSubviews().size()); +// +// SessionReplayViewThingyInterface level2Thingy = thingy.getSubviews().get(0); +// Assert.assertEquals(1, level2Thingy.getSubviews().size()); +// } +// +// @Test +// public void testCapture_DeeplyNestedHierarchy() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// +// FrameLayout root = new FrameLayout(context); +// root.layout(0, 0, 400, 400); +// +// ViewGroup current = root; +// for (int i = 0; i < 10; i++) { +// FrameLayout child = new FrameLayout(context); +// child.layout(0, 0, 300 - i * 20, 300 - i * 20); +// current.addView(child); +// current = child; +// } +// +// SessionReplayViewThingyInterface thingy = capture.capture(root, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// +// // Verify depth +// SessionReplayViewThingyInterface currentThingy = thingy; +// int depth = 0; +// while (!currentThingy.getSubviews().isEmpty()) { +// currentThingy = currentThingy.getSubviews().get(0); +// depth++; +// } +// Assert.assertEquals(10, depth); +// } +// +// // ==================== VISIBILITY FILTERING TESTS ==================== +// +// @Test +// public void testCapture_InvisibleViewNotCaptured() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// FrameLayout parent = new FrameLayout(context); +// parent.layout(0, 0, 200, 200); +// +// View visibleChild = new View(context); +// visibleChild.layout(0, 0, 100, 100); +// visibleChild.setVisibility(View.VISIBLE); +// parent.addView(visibleChild); +// +// View invisibleChild = new View(context); +// invisibleChild.layout(0, 0, 100, 100); +// invisibleChild.setVisibility(View.INVISIBLE); +// parent.addView(invisibleChild); +// +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// // Only visible child should be captured +// Assert.assertEquals(1, thingy.getSubviews().size()); +// } +// +// @Test +// public void testCapture_GoneViewNotCaptured() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// FrameLayout parent = new FrameLayout(context); +// parent.layout(0, 0, 200, 200); +// +// View visibleChild = new View(context); +// visibleChild.layout(0, 0, 100, 100); +// visibleChild.setVisibility(View.VISIBLE); +// parent.addView(visibleChild); +// +// View goneChild = new View(context); +// goneChild.layout(0, 0, 100, 100); +// goneChild.setVisibility(View.GONE); +// parent.addView(goneChild); +// +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// // Only visible child should be captured +// Assert.assertEquals(1, thingy.getSubviews().size()); +// } +// +// @Test +// public void testCapture_ZeroAlphaViewNotCaptured() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// FrameLayout parent = new FrameLayout(context); +// parent.layout(0, 0, 200, 200); +// +// View visibleChild = new View(context); +// visibleChild.layout(0, 0, 100, 100); +// visibleChild.setAlpha(1.0f); +// parent.addView(visibleChild); +// +// View transparentChild = new View(context); +// transparentChild.layout(0, 0, 100, 100); +// transparentChild.setAlpha(0.0f); +// parent.addView(transparentChild); +// +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// // Only visible child should be captured +// Assert.assertEquals(1, thingy.getSubviews().size()); +// } +// +// @Test +// public void testCapture_PartiallyTransparentViewIsCaptured() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// FrameLayout parent = new FrameLayout(context); +// parent.layout(0, 0, 200, 200); +// +// View partiallyTransparent = new View(context); +// partiallyTransparent.layout(0, 0, 100, 100); +// partiallyTransparent.setAlpha(0.5f); +// parent.addView(partiallyTransparent); +// +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// // Partially transparent view should still be captured +// Assert.assertEquals(1, thingy.getSubviews().size()); +// } +// +// // ==================== PRIVACY TAG TESTS ==================== +// +// @Test +// public void testCapture_PrivacyTag_NrMask() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// FrameLayout parent = new FrameLayout(context); +// parent.layout(0, 0, 200, 200); +// +// TextView textView = new TextView(context); +// textView.setText("Sensitive Data"); +// textView.layout(0, 0, 100, 100); +// textView.setTag(R.id.newrelic_privacy, "nr-mask"); +// parent.addView(textView); +// +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// Assert.assertEquals(1, thingy.getSubviews().size()); +// // Tag should be propagated to child +// } +// +// @Test +// public void testCapture_PrivacyTag_NrUnmask() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// FrameLayout parent = new FrameLayout(context); +// parent.layout(0, 0, 200, 200); +// +// TextView textView = new TextView(context); +// textView.setText("Public Data"); +// textView.layout(0, 0, 100, 100); +// textView.setTag(R.id.newrelic_privacy, "nr-unmask"); +// parent.addView(textView); +// +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// Assert.assertEquals(1, thingy.getSubviews().size()); +// } +// +// @Test +// public void testCapture_PrivacyTag_MaskPropagation() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// FrameLayout parent = new FrameLayout(context); +// parent.layout(0, 0, 300, 300); +// parent.setTag(R.id.newrelic_privacy, "nr-mask"); +// +// LinearLayout child = new LinearLayout(context); +// child.layout(0, 0, 200, 200); +// parent.addView(child); +// +// View grandChild = new View(context); +// grandChild.layout(0, 0, 100, 100); +// child.addView(grandChild); +// +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// // Mask should propagate to descendants +// } +// +// @Test +// public void testCapture_PrivacyTag_UnmaskOverridesMaskInParent() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// FrameLayout parent = new FrameLayout(context); +// parent.layout(0, 0, 300, 300); +// parent.setTag(R.id.newrelic_privacy, "nr-mask"); +// +// View child = new View(context); +// child.layout(0, 0, 200, 200); +// child.setTag(R.id.newrelic_privacy, "nr-unmask"); +// parent.addView(child); +// +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// Assert.assertEquals(1, thingy.getSubviews().size()); +// // Child should have nr-unmask tag +// } +// +// @Test +// public void testCapture_GeneralTag_NrMask() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// FrameLayout parent = new FrameLayout(context); +// parent.layout(0, 0, 200, 200); +// +// View child = new View(context); +// child.layout(0, 0, 100, 100); +// child.setTag("nr-mask"); +// parent.addView(child); +// +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// Assert.assertEquals(1, thingy.getSubviews().size()); +// } +// +// @Test +// public void testCapture_GeneralTag_NrUnmask() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// FrameLayout parent = new FrameLayout(context); +// parent.layout(0, 0, 200, 200); +// +// View child = new View(context); +// child.layout(0, 0, 100, 100); +// child.setTag("nr-unmask"); +// parent.addView(child); +// +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// Assert.assertEquals(1, thingy.getSubviews().size()); +// } +// +// // ==================== MIXED VIEW TYPE TESTS ==================== +// +// @Test +// public void testCapture_MixedViewTypes() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// LinearLayout parent = new LinearLayout(context); +// parent.layout(0, 0, 400, 400); +// +// View view = new View(context); +// view.layout(0, 0, 100, 100); +// parent.addView(view); +// +// TextView textView = new TextView(context); +// textView.setText("Text"); +// textView.layout(0, 100, 100, 200); +// parent.addView(textView); +// +// ImageView imageView = new ImageView(context); +// imageView.layout(0, 200, 100, 300); +// parent.addView(imageView); +// +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// Assert.assertEquals(3, thingy.getSubviews().size()); +// Assert.assertTrue(thingy.getSubviews().get(0) instanceof SessionReplayViewThingy); +// Assert.assertTrue(thingy.getSubviews().get(1) instanceof SessionReplayTextViewThingy); +// Assert.assertTrue(thingy.getSubviews().get(2) instanceof SessionReplayImageViewThingy); +// } +// +// // ==================== EDGE CASE TESTS ==================== +// +// @Test +// public void testCapture_NullChild() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// FrameLayout parent = new FrameLayout(context); +// parent.layout(0, 0, 200, 200); +// +// View child = new View(context); +// child.layout(0, 0, 100, 100); +// parent.addView(child); +// +// // Capture should handle null children gracefully +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// } +// +// @Test +// public void testCapture_ViewWithZeroSize() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// View view = new View(context); +// // No layout - view has zero size +// +// SessionReplayViewThingyInterface thingy = capture.capture(view, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// } +// +// @Test +// public void testCapture_ComplexRealWorldHierarchy() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// +// // Simulate a real app layout +// FrameLayout root = new FrameLayout(context); +// root.layout(0, 0, 1080, 1920); +// +// LinearLayout header = new LinearLayout(context); +// header.layout(0, 0, 1080, 200); +// root.addView(header); +// +// TextView title = new TextView(context); +// title.setText("App Title"); +// title.layout(0, 0, 540, 200); +// header.addView(title); +// +// ImageView logo = new ImageView(context); +// logo.layout(540, 0, 1080, 200); +// header.addView(logo); +// +// LinearLayout content = new LinearLayout(context); +// content.layout(0, 200, 1080, 1720); +// root.addView(content); +// +// for (int i = 0; i < 10; i++) { +// TextView item = new TextView(context); +// item.setText("Item " + i); +// item.layout(0, i * 152, 1080, (i + 1) * 152); +// content.addView(item); +// } +// +// SessionReplayViewThingyInterface thingy = capture.capture(root, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertEquals(2, thingy.getSubviews().size()); // header + content +// +// SessionReplayViewThingyInterface headerThingy = thingy.getSubviews().get(0); +// Assert.assertEquals(2, headerThingy.getSubviews().size()); // title + logo +// +// SessionReplayViewThingyInterface contentThingy = thingy.getSubviews().get(1); +// Assert.assertEquals(10, contentThingy.getSubviews().size()); // 10 items +// } +// +// @Test +// public void testCapture_AllChildrenInvisible() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// FrameLayout parent = new FrameLayout(context); +// parent.layout(0, 0, 200, 200); +// +// for (int i = 0; i < 5; i++) { +// View child = new View(context); +// child.layout(0, 0, 100, 100); +// child.setVisibility(View.GONE); +// parent.addView(child); +// } +// +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// // No visible children should be captured +// Assert.assertTrue(thingy.getSubviews().isEmpty()); +// } +// +// @Test +// public void testCapture_MixedVisibilityChildren() { +// SessionReplayCapture capture = new SessionReplayCapture(agentConfiguration); +// FrameLayout parent = new FrameLayout(context); +// parent.layout(0, 0, 300, 300); +// +// View visible1 = new View(context); +// visible1.layout(0, 0, 100, 100); +// visible1.setVisibility(View.VISIBLE); +// parent.addView(visible1); +// +// View invisible = new View(context); +// invisible.layout(0, 0, 100, 100); +// invisible.setVisibility(View.INVISIBLE); +// parent.addView(invisible); +// +// View visible2 = new View(context); +// visible2.layout(0, 0, 100, 100); +// visible2.setVisibility(View.VISIBLE); +// parent.addView(visible2); +// +// View gone = new View(context); +// gone.layout(0, 0, 100, 100); +// gone.setVisibility(View.GONE); +// parent.addView(gone); +// +// SessionReplayViewThingyInterface thingy = capture.capture(parent, agentConfiguration); +// +// // Only 2 visible children should be captured +// Assert.assertEquals(2, thingy.getSubviews().size()); +// } +//} diff --git a/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayEditTextThingyTest.java b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayEditTextThingyTest.java new file mode 100644 index 00000000..719eb16f --- /dev/null +++ b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayEditTextThingyTest.java @@ -0,0 +1,663 @@ +///* +// * Copyright (c) 2024. New Relic Corporation. All rights reserved. +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package com.newrelic.agent.android.sessionReplay; +// +//import android.content.Context; +//import android.graphics.Color; +//import android.text.InputType; +//import android.widget.EditText; +//import android.widget.LinearLayout; +// +//import androidx.test.core.app.ApplicationProvider; +// +//import com.newrelic.agent.android.AgentConfiguration; +//import com.newrelic.agent.android.R; +//import com.newrelic.agent.android.sessionReplay.models.IncrementalEvent.MutationRecord; +//import com.newrelic.agent.android.sessionReplay.models.RRWebElementNode; +// +//import org.junit.After; +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.robolectric.RobolectricTestRunner; +// +//import java.util.List; +// +//@RunWith(RobolectricTestRunner.class) +//public class SessionReplayEditTextThingyTest { +// +// private Context context; +// private AgentConfiguration agentConfiguration; +// private SessionReplayConfiguration sessionReplayConfiguration; +// private SessionReplayLocalConfiguration sessionReplayLocalConfiguration; +// +// @Before +// public void setUp() { +// context = ApplicationProvider.getApplicationContext(); +// agentConfiguration = new AgentConfiguration(); +// +// sessionReplayConfiguration = new SessionReplayConfiguration(); +// sessionReplayConfiguration.setEnabled(true); +// sessionReplayConfiguration.setMode("full"); +// sessionReplayConfiguration.setMaskUserInputText(false); +// sessionReplayConfiguration.setMaskApplicationText(false); +// +// sessionReplayLocalConfiguration = new SessionReplayLocalConfiguration(); +// +// agentConfiguration.setSessionReplayConfiguration(sessionReplayConfiguration); +// agentConfiguration.setSessionReplayLocalConfiguration(sessionReplayLocalConfiguration); +// } +// +// @After +// public void tearDown() { +// sessionReplayLocalConfiguration.clearAllViewMasks(); +// } +// +// // ==================== CONSTRUCTOR AND INHERITANCE TESTS ==================== +// +// @Test +// public void testConstructorWithBasicEditText() { +// EditText editText = new EditText(context); +// editText.setText("user input"); +// editText.setHint("Enter text here"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// // EditTextThingy extends TextViewThingy, so it should have labelText +// Assert.assertEquals("user input", thingy.getLabelText()); +// } +// +// @Test +// public void testInheritsFromTextViewThingy() { +// EditText editText = new EditText(context); +// editText.setText("test"); +// editText.setTextSize(16f); +// editText.setTextColor(Color.BLACK); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// // Should inherit all TextView properties +// Assert.assertNotNull(thingy.getLabelText()); +// Assert.assertTrue(thingy.getFontSize() > 0); +// Assert.assertNotNull(thingy.getFontFamily()); +// Assert.assertNotNull(thingy.getTextColor()); +// Assert.assertNotNull(thingy.getTextAlign()); +// } +// +// // ==================== HINT TEXT EXTRACTION TESTS ==================== +// +// @Test +// public void testHintTextExtraction() { +// EditText editText = new EditText(context); +// editText.setHint("Enter your name"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// // Hint should be extracted (accessible via generateRRWebNode()) +// Assert.assertNotNull(thingy); +// } +// +// @Test +// public void testHintTextWithEmptyText() { +// EditText editText = new EditText(context); +// editText.setText(""); +// editText.setHint("Enter your name"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// // When text is empty, hint should be shown in RRWeb node +// RRWebElementNode node = thingy.generateRRWebNode(); +// Assert.assertNotNull(node); +// Assert.assertNotNull(node.getChildNodes()); +// Assert.assertFalse(node.getChildNodes().isEmpty()); +// } +// +// @Test +// public void testHintTextWithNullHint() { +// EditText editText = new EditText(context); +// editText.setText("user text"); +// editText.setHint(null); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertEquals("user text", thingy.getLabelText()); +// } +// +// @Test +// public void testHintTextWithEmptyHint() { +// EditText editText = new EditText(context); +// editText.setText("user text"); +// editText.setHint(""); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertEquals("user text", thingy.getLabelText()); +// } +// +// @Test +// public void testHintTextWithLongHint() { +// EditText editText = new EditText(context); +// String longHint = "This is a very long hint text that provides detailed instructions to the user about what they should enter in this field."; +// editText.setHint(longHint); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// } +// +// @Test +// public void testHintTextWithSpecialCharacters() { +// EditText editText = new EditText(context); +// editText.setHint("Enter: !@#$%^&*()"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// } +// +// @Test +// public void testHintTextWithUnicodeCharacters() { +// EditText editText = new EditText(context); +// editText.setHint("入力してください 😀"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// } +// +// // ==================== HINT MASKING TESTS ==================== +// +// @Test +// public void testHintMasking_InputTypeSet() { +// EditText editText = new EditText(context); +// editText.setInputType(InputType.TYPE_CLASS_TEXT); +// editText.setHint("Enter password"); +// +// sessionReplayConfiguration.setMaskUserInputText(true); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// // Hint should be masked when inputType is set and maskUserInputText is true +// Assert.assertNotNull(thingy); +// } +// +// @Test +// public void testHintMasking_NoInputType() { +// EditText editText = new EditText(context); +// editText.setInputType(0); // No input type +// editText.setHint("Display hint"); +// +// sessionReplayConfiguration.setMaskApplicationText(true); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// // Hint should be masked based on maskApplicationText when inputType is 0 +// Assert.assertNotNull(thingy); +// } +// +// @Test +// public void testHintNotMasked_WhenConfigurationDisabled() { +// EditText editText = new EditText(context); +// editText.setInputType(InputType.TYPE_CLASS_TEXT); +// editText.setHint("visible hint"); +// +// sessionReplayConfiguration.setMaskUserInputText(false); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// } +// +// // ==================== PASSWORD FIELD TESTS (INHERITED) ==================== +// +// @Test +// public void testPasswordFieldWithHint() { +// EditText editText = new EditText(context); +// editText.setText("password123"); +// editText.setHint("Enter password"); +// editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// // Password text should always be masked (inherited behavior) +// Assert.assertEquals("***********", thingy.getLabelText()); +// } +// +// @Test +// public void testEmptyPasswordFieldShowsHint() { +// EditText editText = new EditText(context); +// editText.setText(""); +// editText.setHint("Password"); +// editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// // When password is empty, hint should be shown +// RRWebElementNode node = thingy.generateRRWebNode(); +// Assert.assertNotNull(node); +// Assert.assertNotNull(node.getChildNodes()); +// } +// +// // ==================== RRWEB NODE GENERATION TESTS ==================== +// +// @Test +// public void testGenerateRRWebNode_WithText() { +// EditText editText = new EditText(context); +// editText.setText("user input"); +// editText.setHint("hint text"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// RRWebElementNode node = thingy.generateRRWebNode(); +// +// Assert.assertNotNull(node); +// Assert.assertNotNull(node.getAttributes()); +// Assert.assertEquals("text", node.getAttributes().getType()); // EditText sets type="text" +// Assert.assertNotNull(node.getChildNodes()); +// // Should show actual text, not hint +// } +// +// @Test +// public void testGenerateRRWebNode_EmptyTextShowsHint() { +// EditText editText = new EditText(context); +// editText.setText(""); +// editText.setHint("hint text"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// RRWebElementNode node = thingy.generateRRWebNode(); +// +// Assert.assertNotNull(node); +// Assert.assertNotNull(node.getChildNodes()); +// // When text is empty, should show hint +// } +// +// @Test +// public void testGenerateRRWebNode_BothEmpty() { +// EditText editText = new EditText(context); +// editText.setText(""); +// editText.setHint(""); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// RRWebElementNode node = thingy.generateRRWebNode(); +// +// Assert.assertNotNull(node); +// Assert.assertNotNull(node.getChildNodes()); +// } +// +// @Test +// public void testGenerateRRWebNode_AttributesContainType() { +// EditText editText = new EditText(context); +// editText.setText("test"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// RRWebElementNode node = thingy.generateRRWebNode(); +// +// Assert.assertNotNull(node.getAttributes()); +// Assert.assertEquals("text", node.getAttributes().getType()); +// } +// +// // ==================== CSS GENERATION TESTS ==================== +// +// @Test +// public void testGenerateCssDescription() { +// EditText editText = new EditText(context); +// editText.setText("test"); +// editText.setTextSize(16f); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// String css = thingy.generateCssDescription(); +// +// Assert.assertNotNull(css); +// Assert.assertFalse(css.isEmpty()); +// } +// +// @Test +// public void testGenerateInlineCss() { +// EditText editText = new EditText(context); +// editText.setText("test"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// String inlineCss = thingy.generateInlineCss(); +// +// Assert.assertNotNull(inlineCss); +// } +// +// // ==================== INCREMENTAL DIFF TESTS ==================== +// +// @Test +// public void testGenerateDifferences_SameContent() { +// EditText editText1 = new EditText(context); +// editText1.setText("same text"); +// +// EditText editText2 = new EditText(context); +// editText2.setText("same text"); +// +// ViewDetails viewDetails1 = new ViewDetails(editText1); +// ViewDetails viewDetails2 = new ViewDetails(editText2); +// +// SessionReplayEditTextThingy thingy1 = new SessionReplayEditTextThingy(viewDetails1, editText1, agentConfiguration); +// SessionReplayEditTextThingy thingy2 = new SessionReplayEditTextThingy(viewDetails2, editText2, agentConfiguration); +// +// List diffs = thingy1.generateDifferences(thingy2); +// +// // May have some differences due to frame/position, but should not crash +// Assert.assertNotNull(diffs); +// } +// +// @Test +// public void testGenerateDifferences_DifferentText() { +// EditText editText1 = new EditText(context); +// editText1.setText("old text"); +// +// EditText editText2 = new EditText(context); +// editText2.setText("new text"); +// +// ViewDetails viewDetails1 = new ViewDetails(editText1); +// ViewDetails viewDetails2 = new ViewDetails(editText2); +// +// SessionReplayEditTextThingy thingy1 = new SessionReplayEditTextThingy(viewDetails1, editText1, agentConfiguration); +// SessionReplayEditTextThingy thingy2 = new SessionReplayEditTextThingy(viewDetails2, editText2, agentConfiguration); +// +// List diffs = thingy1.generateDifferences(thingy2); +// +// Assert.assertNotNull(diffs); +// Assert.assertFalse(diffs.isEmpty()); // Should detect text change +// } +// +// @Test +// public void testGenerateDifferences_DifferentColor() { +// EditText editText1 = new EditText(context); +// editText1.setText("text"); +// editText1.setTextColor(Color.BLACK); +// +// EditText editText2 = new EditText(context); +// editText2.setText("text"); +// editText2.setTextColor(Color.RED); +// +// ViewDetails viewDetails1 = new ViewDetails(editText1); +// ViewDetails viewDetails2 = new ViewDetails(editText2); +// +// SessionReplayEditTextThingy thingy1 = new SessionReplayEditTextThingy(viewDetails1, editText1, agentConfiguration); +// SessionReplayEditTextThingy thingy2 = new SessionReplayEditTextThingy(viewDetails2, editText2, agentConfiguration); +// +// List diffs = thingy1.generateDifferences(thingy2); +// +// Assert.assertNotNull(diffs); +// Assert.assertFalse(diffs.isEmpty()); // Should detect color change +// } +// +// @Test +// public void testGenerateDifferences_WithNonEditTextThingy() { +// EditText editText = new EditText(context); +// editText.setText("text"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy editTextThingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// // Create a generic view thingy (different type) +// LinearLayout layout = new LinearLayout(context); +// ViewDetails layoutDetails = new ViewDetails(layout); +// SessionReplayViewThingy layoutThingy = new SessionReplayViewThingy(layoutDetails, agentConfiguration); +// +// List diffs = editTextThingy.generateDifferences(layoutThingy); +// +// // Should return empty list when comparing different types +// Assert.assertNotNull(diffs); +// Assert.assertTrue(diffs.isEmpty()); +// } +// +// @Test +// public void testGenerateDifferences_WithNull() { +// EditText editText = new EditText(context); +// editText.setText("text"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// List diffs = thingy.generateDifferences(null); +// +// Assert.assertNotNull(diffs); +// Assert.assertTrue(diffs.isEmpty()); +// } +// +// // ==================== CHANGE DETECTION TESTS ==================== +// +// @Test +// public void testHasChanged_SameContent() { +// EditText editText1 = new EditText(context); +// editText1.setText("same"); +// +// EditText editText2 = new EditText(context); +// editText2.setText("same"); +// +// ViewDetails viewDetails1 = new ViewDetails(editText1); +// ViewDetails viewDetails2 = new ViewDetails(editText2); +// +// SessionReplayEditTextThingy thingy1 = new SessionReplayEditTextThingy(viewDetails1, editText1, agentConfiguration); +// SessionReplayEditTextThingy thingy2 = new SessionReplayEditTextThingy(viewDetails2, editText2, agentConfiguration); +// +// // hasChanged uses hashCode comparison +// boolean changed = thingy1.hasChanged(thingy2); +// Assert.assertNotNull(changed); // Result depends on hashCode implementation +// } +// +// @Test +// public void testHasChanged_DifferentContent() { +// EditText editText1 = new EditText(context); +// editText1.setText("old"); +// +// EditText editText2 = new EditText(context); +// editText2.setText("new"); +// +// ViewDetails viewDetails1 = new ViewDetails(editText1); +// ViewDetails viewDetails2 = new ViewDetails(editText2); +// +// SessionReplayEditTextThingy thingy1 = new SessionReplayEditTextThingy(viewDetails1, editText1, agentConfiguration); +// SessionReplayEditTextThingy thingy2 = new SessionReplayEditTextThingy(viewDetails2, editText2, agentConfiguration); +// +// boolean changed = thingy1.hasChanged(thingy2); +// Assert.assertTrue(changed); // Different content should trigger change +// } +// +// @Test +// public void testHasChanged_WithNull() { +// EditText editText = new EditText(context); +// editText.setText("text"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// boolean changed = thingy.hasChanged(null); +// Assert.assertTrue(changed); // null should always indicate change +// } +// +// @Test +// public void testHasChanged_WithDifferentType() { +// EditText editText = new EditText(context); +// editText.setText("text"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy editTextThingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// LinearLayout layout = new LinearLayout(context); +// ViewDetails layoutDetails = new ViewDetails(layout); +// SessionReplayViewThingy layoutThingy = new SessionReplayViewThingy(layoutDetails, agentConfiguration); +// +// boolean changed = editTextThingy.hasChanged(layoutThingy); +// Assert.assertTrue(changed); // Different types should indicate change +// } +// +// // ==================== PRIVACY TAG TESTS (INHERITED) ==================== +// +// @Test +// public void testPrivacyTag_Mask_OnEditText() { +// EditText editText = new EditText(context); +// editText.setText("sensitive input"); +// editText.setTag(R.id.newrelic_privacy, "nr-mask"); +// +// sessionReplayConfiguration.setMaskUserInputText(false); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// // Privacy tag should force masking (inherited behavior) +// Assert.assertEquals("****************", thingy.getLabelText()); +// } +// +// @Test +// public void testPrivacyTag_Unmask_OnEditText() { +// EditText editText = new EditText(context); +// editText.setText("public input"); +// editText.setTag(R.id.newrelic_privacy, "nr-unmask"); +// +// sessionReplayConfiguration.setMaskUserInputText(true); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// // Privacy tag should prevent masking (inherited behavior) +// Assert.assertEquals("public input", thingy.getLabelText()); +// } +// +// // ==================== VIEW DETAILS TESTS ==================== +// +// @Test +// public void testGetViewDetails() { +// EditText editText = new EditText(context); +// editText.setText("test"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// Assert.assertNotNull(thingy.getViewDetails()); +// Assert.assertEquals(viewDetails, thingy.getViewDetails()); +// } +// +// @Test +// public void testGetViewId() { +// EditText editText = new EditText(context); +// editText.setText("test"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// int viewId = thingy.getViewId(); +// Assert.assertEquals(viewDetails.viewId, viewId); +// } +// +// @Test +// public void testGetParentViewId() { +// LinearLayout parent = new LinearLayout(context); +// EditText editText = new EditText(context); +// editText.setText("child"); +// parent.addView(editText); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// int parentId = thingy.getParentViewId(); +// Assert.assertEquals(viewDetails.parentId, parentId); +// } +// +// @Test +// public void testShouldRecordSubviews() { +// EditText editText = new EditText(context); +// editText.setText("test"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// // EditText should not record subviews +// Assert.assertFalse(thingy.shouldRecordSubviews()); +// } +// +// // ==================== EDGE CASE TESTS ==================== +// +// @Test +// public void testMultilineEditText() { +// EditText editText = new EditText(context); +// editText.setText("Line 1\nLine 2\nLine 3"); +// editText.setHint("Enter multiple lines"); +// editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertEquals("Line 1\nLine 2\nLine 3", thingy.getLabelText()); +// } +// +// @Test +// public void testEmailInputType() { +// EditText editText = new EditText(context); +// editText.setText("user@example.com"); +// editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); +// +// sessionReplayConfiguration.setMaskUserInputText(true); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// // Email should be masked when maskUserInputText is true +// Assert.assertEquals("*****************", thingy.getLabelText()); +// } +// +// @Test +// public void testNumberInputType() { +// EditText editText = new EditText(context); +// editText.setText("12345"); +// editText.setInputType(InputType.TYPE_CLASS_NUMBER); +// +// sessionReplayConfiguration.setMaskUserInputText(false); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// Assert.assertEquals("12345", thingy.getLabelText()); +// } +// +// @Test +// public void testGetSubviews() { +// EditText editText = new EditText(context); +// editText.setText("test"); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayEditTextThingy thingy = new SessionReplayEditTextThingy(viewDetails, editText, agentConfiguration); +// +// List subviews = thingy.getSubviews(); +// Assert.assertNotNull(subviews); +// Assert.assertTrue(subviews.isEmpty()); +// } +//} \ No newline at end of file diff --git a/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayFileManagerTest.java b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayFileManagerTest.java new file mode 100644 index 00000000..b1f841de --- /dev/null +++ b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayFileManagerTest.java @@ -0,0 +1,663 @@ +///* +// * Copyright (c) 2024. New Relic Corporation. All rights reserved. +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package com.newrelic.agent.android.sessionReplay; +// +//import android.app.Application; +//import android.content.Context; +// +//import androidx.test.core.app.ApplicationProvider; +// +//import com.google.gson.JsonArray; +//import com.google.gson.JsonObject; +//import com.newrelic.agent.android.AgentConfiguration; +//import com.newrelic.agent.android.sessionReplay.models.RRWebEvent; +// +//import org.junit.After; +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.robolectric.RobolectricTestRunner; +// +//import java.io.File; +//import java.io.IOException; +//import java.util.ArrayList; +//import java.util.List; +//import java.util.concurrent.TimeUnit; +// +//@RunWith(RobolectricTestRunner.class) +//public class SessionReplayFileManagerTest { +// +// private Application application; +// private SessionReplayProcessor processor; +// private SessionReplayFileManager fileManager; +// private File testCacheDir; +// +// @Before +// public void setUp() { +// application = (Application) ApplicationProvider.getApplicationContext(); +// processor = new SessionReplayProcessor(); +// fileManager = new SessionReplayFileManager(processor); +// +// // Initialize AgentConfiguration with a session ID +// AgentConfiguration config = AgentConfiguration.getInstance(); +// config.setSessionID("test-session-" + System.currentTimeMillis()); +// +// // Initialize the file manager +// SessionReplayFileManager.initialize(application); +// +// // Give async operations time to complete +// waitForAsyncOperations(); +// +// testCacheDir = application.getCacheDir(); +// } +// +// @After +// public void tearDown() { +// try { +// // Shutdown the file manager +// SessionReplayFileManager.shutdown(); +// +// // Clean up test files +// cleanupTestFiles(); +// +// waitForAsyncOperations(); +// } catch (Exception e) { +// // Ignore cleanup errors +// } +// } +// +// private void waitForAsyncOperations() { +// try { +// // Wait for async file operations to complete +// Thread.sleep(500); +// } catch (InterruptedException e) { +// Thread.currentThread().interrupt(); +// } +// } +// +// private void cleanupTestFiles() { +// if (testCacheDir != null && testCacheDir.exists()) { +// File sessionReplayDir = new File(testCacheDir, "session_replay"); +// if (sessionReplayDir.exists()) { +// File[] files = sessionReplayDir.listFiles(); +// if (files != null) { +// for (File file : files) { +// file.delete(); +// } +// } +// } +// } +// } +// +// // ==================== CONSTRUCTOR TESTS ==================== +// +// @Test +// public void testConstructor_WithValidProcessor() { +// SessionReplayFileManager newManager = new SessionReplayFileManager(processor); +// Assert.assertNotNull(newManager); +// } +// +// @Test +// public void testConstructor_WithNullProcessor() { +// SessionReplayFileManager newManager = new SessionReplayFileManager(null); +// Assert.assertNotNull(newManager); +// } +// +// // ==================== INITIALIZE TESTS ==================== +// +// @Test +// public void testInitialize_WithValidApplication() { +// // Initialize was called in setUp, verify it worked +// File sessionReplayDir = new File(testCacheDir, "session_replay"); +// Assert.assertTrue("Session replay directory should exist", sessionReplayDir.exists()); +// Assert.assertTrue("Session replay directory should be a directory", sessionReplayDir.isDirectory()); +// } +// +// @Test +// public void testInitialize_WithNullApplication() { +// // Should not throw exception, just log error +// SessionReplayFileManager.initialize(null); +// // Test passes if no exception thrown +// } +// +// @Test +// public void testInitialize_CreatesWorkingFile() throws IOException { +// // Verify working file was created +// File workingFile = SessionReplayFileManager.getWorkingSessionReplayFile(); +// Assert.assertNotNull(workingFile); +// Assert.assertTrue("Working file should exist", workingFile.exists()); +// } +// +// // ==================== ADD FRAME TO FILE TESTS ==================== +// +// @Test +// public void testAddFrameToFile_WithSingleEvent() { +// RRWebEvent event = new RRWebEvent(); +// event.setTimestamp(System.currentTimeMillis()); +// event.setType(RRWebEvent.RRWebEventType.META); +// +// List events = new ArrayList<>(); +// events.add(event); +// +// fileManager.addFrameToFile(events); +// waitForAsyncOperations(); +// +// // Verify event was written +// JsonArray readEvents = SessionReplayFileManager.readEventsAsJsonArray(); +// Assert.assertTrue("Should have at least one event", readEvents.size() >= 1); +// } +// +// @Test +// public void testAddFrameToFile_WithMultipleEvents() { +// List events = new ArrayList<>(); +// +// for (int i = 0; i < 5; i++) { +// RRWebEvent event = new RRWebEvent(); +// event.setTimestamp(System.currentTimeMillis() + i); +// event.setType(RRWebEvent.RRWebEventType.META); +// events.add(event); +// } +// +// fileManager.addFrameToFile(events); +// waitForAsyncOperations(); +// +// // Verify events were written +// JsonArray readEvents = SessionReplayFileManager.readEventsAsJsonArray(); +// Assert.assertTrue("Should have at least 5 events", readEvents.size() >= 5); +// } +// +// @Test +// public void testAddFrameToFile_WithEmptyList() { +// List events = new ArrayList<>(); +// +// // Should not throw exception +// fileManager.addFrameToFile(events); +// waitForAsyncOperations(); +// } +// +// @Test +// public void testAddFrameToFile_MultipleCallsAppend() { +// // First batch +// List events1 = new ArrayList<>(); +// RRWebEvent event1 = new RRWebEvent(); +// event1.setTimestamp(1000L); +// event1.setType(RRWebEvent.RRWebEventType.META); +// events1.add(event1); +// +// fileManager.addFrameToFile(events1); +// waitForAsyncOperations(); +// +// int sizeAfterFirst = SessionReplayFileManager.readEventsAsJsonArray().size(); +// +// // Second batch +// List events2 = new ArrayList<>(); +// RRWebEvent event2 = new RRWebEvent(); +// event2.setTimestamp(2000L); +// event2.setType(RRWebEvent.RRWebEventType.META); +// events2.add(event2); +// +// fileManager.addFrameToFile(events2); +// waitForAsyncOperations(); +// +// int sizeAfterSecond = SessionReplayFileManager.readEventsAsJsonArray().size(); +// +// // Second call should append +// Assert.assertTrue("Second call should append events", sizeAfterSecond > sizeAfterFirst); +// } +// +// // ==================== ADD TOUCH TO FILE TESTS ==================== +// +// @Test +// public void testAddTouchToFile_WithTouchData() { +// TouchTracker touchTracker = new TouchTracker(System.currentTimeMillis()); +// touchTracker.recordTouchDown(100, 200, 1); +// touchTracker.recordTouchUp(100, 200, 1); +// +// fileManager.addTouchToFile(touchTracker); +// waitForAsyncOperations(); +// +// // Verify touch data was written +// JsonArray readEvents = SessionReplayFileManager.readEventsAsJsonArray(); +// Assert.assertTrue("Should have touch events", readEvents.size() > 0); +// } +// +// @Test +// public void testAddTouchToFile_WithEmptyTouchTracker() { +// TouchTracker touchTracker = new TouchTracker(System.currentTimeMillis()); +// +// // No touches recorded +// fileManager.addTouchToFile(touchTracker); +// waitForAsyncOperations(); +// +// // Should not throw exception +// } +// +// // ==================== READ EVENTS AS JSON ARRAY TESTS ==================== +// +// @Test +// public void testReadEventsAsJsonArray_WithNoEvents() { +// // Clear file first +// fileManager.clearWorkingFile(); +// waitForAsyncOperations(); +// +// // Reinitialize +// SessionReplayFileManager.initialize(application); +// waitForAsyncOperations(); +// +// JsonArray events = SessionReplayFileManager.readEventsAsJsonArray(); +// Assert.assertNotNull(events); +// Assert.assertEquals("Should have no events", 0, events.size()); +// } +// +// @Test +// public void testReadEventsAsJsonArray_WithEvents() { +// // Write an event +// RRWebEvent event = new RRWebEvent(); +// event.setTimestamp(System.currentTimeMillis()); +// event.setType(RRWebEvent.RRWebEventType.META); +// +// List events = new ArrayList<>(); +// events.add(event); +// +// fileManager.addFrameToFile(events); +// waitForAsyncOperations(); +// +// // Read events +// JsonArray readEvents = SessionReplayFileManager.readEventsAsJsonArray(); +// Assert.assertNotNull(readEvents); +// Assert.assertTrue("Should have at least one event", readEvents.size() >= 1); +// } +// +// @Test +// public void testReadEventsAsJsonArray_ParsesJsonObjects() { +// // Write an event with specific data +// RRWebEvent event = new RRWebEvent(); +// event.setTimestamp(12345L); +// event.setType(RRWebEvent.RRWebEventType.META); +// +// List events = new ArrayList<>(); +// events.add(event); +// +// fileManager.addFrameToFile(events); +// waitForAsyncOperations(); +// +// // Read and verify +// JsonArray readEvents = SessionReplayFileManager.readEventsAsJsonArray(); +// Assert.assertTrue("Should have at least one event", readEvents.size() >= 1); +// +// // Check that we got valid JSON objects +// JsonObject firstEvent = readEvents.get(readEvents.size() - 1).getAsJsonObject(); +// Assert.assertNotNull(firstEvent); +// Assert.assertTrue("Event should have timestamp", firstEvent.has("timestamp")); +// } +// +// // ==================== CLEAR WORKING FILE WHILE RUNNING SESSION TESTS ==================== +// +// @Test +// public void testClearWorkingFileWhileRunningSession_ClearsContent() { +// // Write some events +// RRWebEvent event = new RRWebEvent(); +// event.setTimestamp(System.currentTimeMillis()); +// event.setType(RRWebEvent.RRWebEventType.META); +// +// List events = new ArrayList<>(); +// events.add(event); +// +// fileManager.addFrameToFile(events); +// waitForAsyncOperations(); +// +// // Verify events exist +// int sizeBefore = SessionReplayFileManager.readEventsAsJsonArray().size(); +// Assert.assertTrue("Should have events before clear", sizeBefore > 0); +// +// // Clear file +// fileManager.clearWorkingFileWhileRunningSession(); +// waitForAsyncOperations(); +// +// // Verify file is cleared +// JsonArray afterClear = SessionReplayFileManager.readEventsAsJsonArray(); +// Assert.assertEquals("File should be empty after clear", 0, afterClear.size()); +// } +// +// @Test +// public void testClearWorkingFileWhileRunningSession_AllowsNewWrites() { +// // Clear file +// fileManager.clearWorkingFileWhileRunningSession(); +// waitForAsyncOperations(); +// +// // Write new event after clear +// RRWebEvent event = new RRWebEvent(); +// event.setTimestamp(System.currentTimeMillis()); +// event.setType(RRWebEvent.RRWebEventType.META); +// +// List events = new ArrayList<>(); +// events.add(event); +// +// fileManager.addFrameToFile(events); +// waitForAsyncOperations(); +// +// // Verify new event was written +// JsonArray readEvents = SessionReplayFileManager.readEventsAsJsonArray(); +// Assert.assertTrue("Should have new event", readEvents.size() > 0); +// } +// +// // ==================== CLEAR WORKING FILE TESTS ==================== +// +// @Test +// public void testClearWorkingFile_DeletesFile() { +// // Write some events first +// RRWebEvent event = new RRWebEvent(); +// event.setTimestamp(System.currentTimeMillis()); +// event.setType(RRWebEvent.RRWebEventType.META); +// +// List events = new ArrayList<>(); +// events.add(event); +// +// fileManager.addFrameToFile(events); +// waitForAsyncOperations(); +// +// // Clear file +// fileManager.clearWorkingFile(); +// waitForAsyncOperations(); +// +// // File should be deleted +// // Note: After clearWorkingFile, the file writer is closed but file might still exist +// // depending on timing. This is more of an integration test. +// } +// +// // ==================== GET WORKING SESSION REPLAY FILE TESTS ==================== +// +// @Test +// public void testGetWorkingSessionReplayFile_CreatesFile() throws IOException { +// File workingFile = SessionReplayFileManager.getWorkingSessionReplayFile(); +// +// Assert.assertNotNull(workingFile); +// Assert.assertTrue("File should exist", workingFile.exists()); +// Assert.assertTrue("Should be a file", workingFile.isFile()); +// } +// +// @Test +// public void testGetWorkingSessionReplayFile_CreatesParentDirectories() throws IOException { +// File workingFile = SessionReplayFileManager.getWorkingSessionReplayFile(); +// +// Assert.assertNotNull(workingFile.getParentFile()); +// Assert.assertTrue("Parent directory should exist", workingFile.getParentFile().exists()); +// Assert.assertTrue("Parent should be a directory", workingFile.getParentFile().isDirectory()); +// } +// +// @Test +// public void testGetWorkingSessionReplayFile_SetsLastModified() throws IOException { +// long beforeTime = System.currentTimeMillis(); +// File workingFile = SessionReplayFileManager.getWorkingSessionReplayFile(); +// long afterTime = System.currentTimeMillis(); +// +// long lastModified = workingFile.lastModified(); +// Assert.assertTrue("Last modified should be recent", lastModified >= beforeTime && lastModified <= afterTime); +// } +// +// @Test +// public void testGetWorkingSessionReplayFile_IncludesSessionId() throws IOException { +// File workingFile = SessionReplayFileManager.getWorkingSessionReplayFile(); +// String fileName = workingFile.getName(); +// +// // File name should contain session ID +// String sessionId = AgentConfiguration.getInstance().getSessionID(); +// Assert.assertTrue("File name should contain session ID", fileName.contains(sessionId)); +// } +// +// // ==================== PRUNE EVENTS OLDER THAN TESTS ==================== +// +// @Test +// public void testPruneEventsOlderThan_RemovesOldEvents() { +// long currentTime = System.currentTimeMillis(); +// +// // Write old event +// RRWebEvent oldEvent = new RRWebEvent(); +// oldEvent.setTimestamp(currentTime - 20000); // 20 seconds ago +// oldEvent.setType(RRWebEvent.RRWebEventType.META); +// +// List oldEvents = new ArrayList<>(); +// oldEvents.add(oldEvent); +// fileManager.addFrameToFile(oldEvents); +// waitForAsyncOperations(); +// +// // Write recent event +// RRWebEvent recentEvent = new RRWebEvent(); +// recentEvent.setTimestamp(currentTime - 5000); // 5 seconds ago +// recentEvent.setType(RRWebEvent.RRWebEventType.META); +// +// List recentEvents = new ArrayList<>(); +// recentEvents.add(recentEvent); +// fileManager.addFrameToFile(recentEvents); +// waitForAsyncOperations(); +// +// int sizeBefore = SessionReplayFileManager.readEventsAsJsonArray().size(); +// +// // Prune events older than 15 seconds +// SessionReplayFileManager.pruneEventsOlderThan(15000); +// waitForAsyncOperations(); +// +// int sizeAfter = SessionReplayFileManager.readEventsAsJsonArray().size(); +// +// // Should have fewer events after pruning +// Assert.assertTrue("Should have removed old events", sizeAfter < sizeBefore); +// } +// +// @Test +// public void testPruneEventsOlderThan_KeepsRecentEvents() { +// long currentTime = System.currentTimeMillis(); +// +// // Write only recent events +// RRWebEvent recentEvent1 = new RRWebEvent(); +// recentEvent1.setTimestamp(currentTime - 5000); +// recentEvent1.setType(RRWebEvent.RRWebEventType.META); +// +// RRWebEvent recentEvent2 = new RRWebEvent(); +// recentEvent2.setTimestamp(currentTime - 3000); +// recentEvent2.setType(RRWebEvent.RRWebEventType.META); +// +// List events = new ArrayList<>(); +// events.add(recentEvent1); +// events.add(recentEvent2); +// +// fileManager.addFrameToFile(events); +// waitForAsyncOperations(); +// +// int sizeBefore = SessionReplayFileManager.readEventsAsJsonArray().size(); +// +// // Prune events older than 15 seconds (should keep all) +// SessionReplayFileManager.pruneEventsOlderThan(15000); +// waitForAsyncOperations(); +// +// int sizeAfter = SessionReplayFileManager.readEventsAsJsonArray().size(); +// +// // Should keep all recent events +// Assert.assertEquals("Should keep all recent events", sizeBefore, sizeAfter); +// } +// +// @Test +// public void testPruneEventsOlderThan_WithNoFile() { +// // Clear and delete file +// fileManager.clearWorkingFile(); +// waitForAsyncOperations(); +// +// // Prune should not throw exception +// SessionReplayFileManager.pruneEventsOlderThan(15000); +// waitForAsyncOperations(); +// } +// +// @Test +// public void testPruneEventsOlderThan_AllowsNewWritesAfter() { +// // Prune events +// SessionReplayFileManager.pruneEventsOlderThan(15000); +// waitForAsyncOperations(); +// +// // Write new event +// RRWebEvent event = new RRWebEvent(); +// event.setTimestamp(System.currentTimeMillis()); +// event.setType(RRWebEvent.RRWebEventType.META); +// +// List events = new ArrayList<>(); +// events.add(event); +// +// fileManager.addFrameToFile(events); +// waitForAsyncOperations(); +// +// // Verify new event was written +// JsonArray readEvents = SessionReplayFileManager.readEventsAsJsonArray(); +// Assert.assertTrue("Should have new event", readEvents.size() > 0); +// } +// +// // ==================== SHUTDOWN TESTS ==================== +// +// @Test +// public void testShutdown_ClosesWriter() { +// // Write some data +// RRWebEvent event = new RRWebEvent(); +// event.setTimestamp(System.currentTimeMillis()); +// event.setType(RRWebEvent.RRWebEventType.META); +// +// List events = new ArrayList<>(); +// events.add(event); +// +// fileManager.addFrameToFile(events); +// waitForAsyncOperations(); +// +// // Shutdown +// SessionReplayFileManager.shutdown(); +// waitForAsyncOperations(); +// +// // Should not throw exception +// } +// +// @Test +// public void testShutdown_MultipleCallsSafe() { +// SessionReplayFileManager.shutdown(); +// waitForAsyncOperations(); +// +// // Second shutdown should not throw exception +// SessionReplayFileManager.shutdown(); +// waitForAsyncOperations(); +// } +// +// // ==================== EDGE CASE TESTS ==================== +// +// @Test +// public void testConcurrentWrites() { +// // Write from multiple threads (simulated by multiple calls) +// for (int i = 0; i < 10; i++) { +// RRWebEvent event = new RRWebEvent(); +// event.setTimestamp(System.currentTimeMillis() + i); +// event.setType(RRWebEvent.RRWebEventType.META); +// +// List events = new ArrayList<>(); +// events.add(event); +// +// fileManager.addFrameToFile(events); +// } +// +// waitForAsyncOperations(); +// waitForAsyncOperations(); // Extra wait for concurrent operations +// +// // Should not lose any writes +// JsonArray readEvents = SessionReplayFileManager.readEventsAsJsonArray(); +// Assert.assertTrue("Should have multiple events", readEvents.size() >= 10); +// } +// +// @Test +// public void testReadWhileWriting() { +// // Start a write +// RRWebEvent event = new RRWebEvent(); +// event.setTimestamp(System.currentTimeMillis()); +// event.setType(RRWebEvent.RRWebEventType.META); +// +// List events = new ArrayList<>(); +// events.add(event); +// +// fileManager.addFrameToFile(events); +// +// // Read immediately (might read old data due to buffering) +// JsonArray readEvents = SessionReplayFileManager.readEventsAsJsonArray(); +// Assert.assertNotNull(readEvents); +// +// // Wait and read again +// waitForAsyncOperations(); +// JsonArray readEventsAfter = SessionReplayFileManager.readEventsAsJsonArray(); +// Assert.assertNotNull(readEventsAfter); +// } +// +// @Test +// public void testFileOperationsAfterShutdown() { +// SessionReplayFileManager.shutdown(); +// waitForAsyncOperations(); +// +// // Reinitialize +// SessionReplayFileManager.initialize(application); +// waitForAsyncOperations(); +// +// // Should be able to write after reinitializing +// RRWebEvent event = new RRWebEvent(); +// event.setTimestamp(System.currentTimeMillis()); +// event.setType(RRWebEvent.RRWebEventType.META); +// +// List events = new ArrayList<>(); +// events.add(event); +// +// fileManager.addFrameToFile(events); +// waitForAsyncOperations(); +// +// JsonArray readEvents = SessionReplayFileManager.readEventsAsJsonArray(); +// Assert.assertTrue("Should have event after reinitialize", readEvents.size() > 0); +// } +// +// // ==================== ERROR CONDITION TESTS ==================== +// +// @Test +// public void testReadEventsAsJsonArray_WithMalformedJson() { +// // This is hard to test directly without access to write malformed JSON +// // The method handles parse exceptions gracefully by skipping bad lines +// // Test would require mocking file system or writing directly to file +// } +// +// @Test +// public void testPruneEventsOlderThan_WithEventsWithoutTimestamp() { +// // Events without timestamp should be kept +// // This is hard to test without directly writing to file +// // The implementation keeps events without timestamp as a safety measure +// } +// +// @Test +// public void testMultipleFileManagers() { +// // Create another file manager instance +// SessionReplayProcessor processor2 = new SessionReplayProcessor(); +// SessionReplayFileManager fileManager2 = new SessionReplayFileManager(processor2); +// +// // Both should work with same file (static state) +// RRWebEvent event1 = new RRWebEvent(); +// event1.setTimestamp(1000L); +// event1.setType(RRWebEvent.RRWebEventType.META); +// +// RRWebEvent event2 = new RRWebEvent(); +// event2.setTimestamp(2000L); +// event2.setType(RRWebEvent.RRWebEventType.META); +// +// List events1 = new ArrayList<>(); +// events1.add(event1); +// +// List events2 = new ArrayList<>(); +// events2.add(event2); +// +// fileManager.addFrameToFile(events1); +// fileManager2.addFrameToFile(events2); +// +// waitForAsyncOperations(); +// +// // Both writes should succeed +// JsonArray readEvents = SessionReplayFileManager.readEventsAsJsonArray(); +// Assert.assertTrue("Should have events from both managers", readEvents.size() >= 2); +// } +//} \ No newline at end of file diff --git a/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayImageViewThingyTest.java b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayImageViewThingyTest.java new file mode 100644 index 00000000..a7ad457d --- /dev/null +++ b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayImageViewThingyTest.java @@ -0,0 +1,729 @@ +///* +// * Copyright (c) 2024. New Relic Corporation. All rights reserved. +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package com.newrelic.agent.android.sessionReplay; +// +//import android.content.Context; +//import android.graphics.Bitmap; +//import android.graphics.Color; +//import android.graphics.drawable.BitmapDrawable; +//import android.graphics.drawable.ColorDrawable; +//import android.graphics.drawable.Drawable; +//import android.graphics.drawable.InsetDrawable; +//import android.graphics.drawable.LayerDrawable; +//import android.widget.FrameLayout; +//import android.widget.ImageView; +// +//import androidx.test.core.app.ApplicationProvider; +// +//import com.newrelic.agent.android.AgentConfiguration; +//import com.newrelic.agent.android.R; +//import com.newrelic.agent.android.sessionReplay.models.IncrementalEvent.MutationRecord; +//import com.newrelic.agent.android.sessionReplay.models.IncrementalEvent.RRWebMutationData; +//import com.newrelic.agent.android.sessionReplay.models.RRWebElementNode; +// +//import org.junit.After; +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.robolectric.RobolectricTestRunner; +// +//import java.util.List; +// +//@RunWith(RobolectricTestRunner.class) +//public class SessionReplayImageViewThingyTest { +// +// private Context context; +// private AgentConfiguration agentConfiguration; +// private SessionReplayConfiguration sessionReplayConfiguration; +// private SessionReplayLocalConfiguration sessionReplayLocalConfiguration; +// +// @Before +// public void setUp() { +// context = ApplicationProvider.getApplicationContext(); +// agentConfiguration = new AgentConfiguration(); +// +// sessionReplayConfiguration = new SessionReplayConfiguration(); +// sessionReplayConfiguration.setEnabled(true); +// sessionReplayConfiguration.setMode("full"); +// sessionReplayConfiguration.setMaskAllImages(false); +// +// sessionReplayLocalConfiguration = new SessionReplayLocalConfiguration(); +// +// agentConfiguration.setSessionReplayConfiguration(sessionReplayConfiguration); +// agentConfiguration.setSessionReplayLocalConfiguration(sessionReplayLocalConfiguration); +// +// // Clear cache before each test +// SessionReplayImageViewThingy.clearImageCache(); +// } +// +// @After +// public void tearDown() { +// sessionReplayLocalConfiguration.clearAllViewMasks(); +// SessionReplayImageViewThingy.clearImageCache(); +// } +// +// // ==================== CONSTRUCTOR TESTS ==================== +// +// @Test +// public void testConstructorWithBasicImageView() { +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertNotNull(thingy.getImageData()); +// } +// +// @Test +// public void testConstructorWithNullDrawable() { +// ImageView imageView = new ImageView(context); +// // No image set - drawable is null +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertNull(thingy.getImageData()); +// } +// +// @Test +// public void testConstructorWithMaskingEnabled() { +// sessionReplayConfiguration.setMaskAllImages(true); +// +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertNull(thingy.getImageData()); // Image should be masked (null) +// } +// +// @Test +// public void testConstructorWithColorBackground() { +// ImageView imageView = new ImageView(context); +// imageView.setBackgroundColor(Color.RED); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// String inlineCss = thingy.generateInlineCss(); +// Assert.assertTrue(inlineCss.contains("background-color:")); +// } +// +// // ==================== IMAGE EXTRACTION TESTS ==================== +// +// @Test +// public void testGetImageDataWithBitmapDrawable() { +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String imageData = thingy.getImageData(); +// Assert.assertNotNull(imageData); +// Assert.assertTrue(imageData.length() > 0); +// } +// +// @Test +// public void testGetImageDataWithLayerDrawable() { +// ImageView imageView = new ImageView(context); +// Bitmap bitmap1 = createTestBitmap(10, 10); +// Bitmap bitmap2 = createTestBitmap(10, 10); +// +// BitmapDrawable drawable1 = new BitmapDrawable(context.getResources(), bitmap1); +// BitmapDrawable drawable2 = new BitmapDrawable(context.getResources(), bitmap2); +// +// LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{drawable1, drawable2}); +// imageView.setImageDrawable(layerDrawable); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String imageData = thingy.getImageData(); +// Assert.assertNotNull(imageData); +// Assert.assertTrue(imageData.length() > 0); +// } +// +// @Test +// public void testGetImageDataWithInsetDrawable() { +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// BitmapDrawable bitmapDrawable = new BitmapDrawable(context.getResources(), bitmap); +// InsetDrawable insetDrawable = new InsetDrawable(bitmapDrawable, 5); +// +// imageView.setImageDrawable(insetDrawable); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String imageData = thingy.getImageData(); +// Assert.assertNotNull(imageData); +// Assert.assertTrue(imageData.length() > 0); +// } +// +// @Test +// public void testGetImageDataWithColorDrawable() { +// ImageView imageView = new ImageView(context); +// ColorDrawable colorDrawable = new ColorDrawable(Color.BLUE); +// imageView.setImageDrawable(colorDrawable); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String imageData = thingy.getImageData(); +// // ColorDrawable should be converted to bitmap +// Assert.assertNotNull(imageData); +// } +// +// @Test +// public void testGetImageDataWithLayerDrawableNoLayers() { +// ImageView imageView = new ImageView(context); +// LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{}); +// imageView.setImageDrawable(layerDrawable); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String imageData = thingy.getImageData(); +// // Empty LayerDrawable should still attempt to create bitmap +// Assert.assertNotNull(imageData); +// } +// +// // ==================== IMAGE CACHING TESTS ==================== +// +// @Test +// public void testImageCaching_CacheHit() { +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails1 = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy1 = new SessionReplayImageViewThingy(viewDetails1, imageView, agentConfiguration); +// String imageData1 = thingy1.getImageData(); +// +// // Create second thingy with same image - should hit cache +// ViewDetails viewDetails2 = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy2 = new SessionReplayImageViewThingy(viewDetails2, imageView, agentConfiguration); +// String imageData2 = thingy2.getImageData(); +// +// Assert.assertNotNull(imageData1); +// Assert.assertNotNull(imageData2); +// Assert.assertEquals(imageData1, imageData2); +// } +// +// @Test +// public void testClearImageCache() { +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// Assert.assertNotNull(thingy.getImageData()); +// +// SessionReplayImageViewThingy.clearImageCache(); +// +// // Verify cache was cleared +// String cacheStats = SessionReplayImageViewThingy.getCacheStats(); +// Assert.assertNotNull(cacheStats); +// Assert.assertTrue(cacheStats.contains("Size: 0")); +// } +// +// @Test +// public void testGetCacheStats() { +// String cacheStats = SessionReplayImageViewThingy.getCacheStats(); +// Assert.assertNotNull(cacheStats); +// Assert.assertTrue(cacheStats.contains("Size:")); +// Assert.assertTrue(cacheStats.contains("Hits:")); +// Assert.assertTrue(cacheStats.contains("Misses:")); +// } +// +// // ==================== MASKING TESTS ==================== +// +// @Test +// public void testMaskAllImages_Enabled() { +// sessionReplayConfiguration.setMaskAllImages(true); +// +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertNull(thingy.getImageData()); +// } +// +// @Test +// public void testMaskAllImages_Disabled() { +// sessionReplayConfiguration.setMaskAllImages(false); +// +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertNotNull(thingy.getImageData()); +// } +// +// @Test +// public void testMaskingWithPrivacyTag_NrMask() { +// sessionReplayConfiguration.setMaskAllImages(false); +// +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// imageView.setTag(R.id.newrelic_privacy, "nr-mask"); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertNull(thingy.getImageData()); // Should be masked +// } +// +// @Test +// public void testMaskingWithPrivacyTag_NrUnmask() { +// sessionReplayConfiguration.setMaskAllImages(true); +// sessionReplayConfiguration.setMode("custom"); +// +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// imageView.setTag(R.id.newrelic_privacy, "nr-unmask"); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertNotNull(thingy.getImageData()); // Should NOT be masked +// } +// +// @Test +// public void testMaskingWithTag_NrMask() { +// sessionReplayConfiguration.setMaskAllImages(false); +// +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// imageView.setTag("nr-mask"); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertNull(thingy.getImageData()); // Should be masked +// } +// +// @Test +// public void testMaskingWithTag_NrUnmask() { +// sessionReplayConfiguration.setMaskAllImages(true); +// sessionReplayConfiguration.setMode("custom"); +// +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// imageView.setTag("nr-unmask"); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertNotNull(thingy.getImageData()); // Should NOT be masked +// } +// +// // ==================== SCALE TYPE TESTS ==================== +// +// @Test +// public void testScaleType_FitXY() { +// ImageView imageView = new ImageView(context); +// imageView.setScaleType(ImageView.ScaleType.FIT_XY); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String css = thingy.generateInlineCss(); +// Assert.assertTrue(css.contains("background-size: 100% 100%")); +// } +// +// @Test +// public void testScaleType_CenterCrop() { +// ImageView imageView = new ImageView(context); +// imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String css = thingy.generateInlineCss(); +// Assert.assertTrue(css.contains("background-size: cover")); +// } +// +// @Test +// public void testScaleType_FitCenter() { +// ImageView imageView = new ImageView(context); +// imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String css = thingy.generateInlineCss(); +// Assert.assertTrue(css.contains("background-size: contain")); +// } +// +// @Test +// public void testScaleType_CenterInside() { +// ImageView imageView = new ImageView(context); +// imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String css = thingy.generateInlineCss(); +// Assert.assertTrue(css.contains("background-size: contain")); +// } +// +// @Test +// public void testScaleType_Center() { +// ImageView imageView = new ImageView(context); +// imageView.setScaleType(ImageView.ScaleType.CENTER); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String css = thingy.generateInlineCss(); +// Assert.assertTrue(css.contains("background-size: auto")); +// } +// +// // ==================== CSS GENERATION TESTS ==================== +// +// @Test +// public void testGenerateCssDescription() { +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String css = thingy.generateCssDescription(); +// Assert.assertNotNull(css); +// Assert.assertTrue(css.contains("background-color:")); +// } +// +// @Test +// public void testGenerateInlineCss() { +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String css = thingy.generateInlineCss(); +// Assert.assertNotNull(css); +// Assert.assertTrue(css.contains("background-color:")); +// Assert.assertTrue(css.contains("background-size:")); +// Assert.assertTrue(css.contains("background-repeat: no-repeat")); +// Assert.assertTrue(css.contains("background-position: center")); +// } +// +// @Test +// public void testGenerateInlineCssWithImageData() { +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String css = thingy.generateInlineCss(); +// Assert.assertTrue(css.contains("background-image: url(")); +// } +// +// @Test +// public void testGenerateInlineCssWithoutImageData() { +// ImageView imageView = new ImageView(context); +// // No image set +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String css = thingy.generateInlineCss(); +// Assert.assertFalse(css.contains("background-image:")); +// } +// +// @Test +// public void testGetImageDataUrl() { +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String dataUrl = thingy.getImageDataUrl(); +// Assert.assertNotNull(dataUrl); +// Assert.assertTrue(dataUrl.startsWith("data:image/")); +// } +// +// // ==================== RRWEB NODE GENERATION TESTS ==================== +// +// @Test +// public void testGenerateRRWebNode() { +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// RRWebElementNode node = thingy.generateRRWebNode(); +// +// Assert.assertNotNull(node); +// Assert.assertEquals(RRWebElementNode.TAG_TYPE_DIV, node.getTagName()); +// Assert.assertNotNull(node.getAttributes()); +// Assert.assertNotNull(node.getChildNodes()); +// Assert.assertTrue(node.getChildNodes().isEmpty()); +// } +// +// // ==================== DIFF GENERATION TESTS ==================== +// +// @Test +// public void testGenerateDifferences_WithSameImage() { +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails1 = new ViewDetails(imageView); +// ViewDetails viewDetails2 = new ViewDetails(imageView); +// +// SessionReplayImageViewThingy thingy1 = new SessionReplayImageViewThingy(viewDetails1, imageView, agentConfiguration); +// SessionReplayImageViewThingy thingy2 = new SessionReplayImageViewThingy(viewDetails2, imageView, agentConfiguration); +// +// List diffs = thingy1.generateDifferences(thingy2); +// +// Assert.assertNotNull(diffs); +// Assert.assertEquals(1, diffs.size()); +// } +// +// @Test +// public void testGenerateDifferences_WithDifferentImages() { +// ImageView imageView1 = new ImageView(context); +// Bitmap bitmap1 = createTestBitmap(10, 10); +// imageView1.setImageBitmap(bitmap1); +// +// ImageView imageView2 = new ImageView(context); +// Bitmap bitmap2 = createTestBitmap(20, 20); +// imageView2.setImageBitmap(bitmap2); +// +// ViewDetails viewDetails1 = new ViewDetails(imageView1); +// ViewDetails viewDetails2 = new ViewDetails(imageView2); +// +// SessionReplayImageViewThingy thingy1 = new SessionReplayImageViewThingy(viewDetails1, imageView1, agentConfiguration); +// SessionReplayImageViewThingy thingy2 = new SessionReplayImageViewThingy(viewDetails2, imageView2, agentConfiguration); +// +// List diffs = thingy1.generateDifferences(thingy2); +// +// Assert.assertNotNull(diffs); +// Assert.assertFalse(diffs.isEmpty()); +// } +// +// @Test +// public void testGenerateDifferences_WithDifferentType() { +// ImageView imageView = new ImageView(context); +// ViewDetails viewDetails = new ViewDetails(imageView); +// +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// // Create a different type +// SessionReplayViewThingyInterface otherType = new SessionReplayViewThingy(viewDetails); +// +// List diffs = thingy.generateDifferences(otherType); +// +// Assert.assertNotNull(diffs); +// Assert.assertTrue(diffs.isEmpty()); +// } +// +// @Test +// public void testGenerateDifferences_WithNullOther() { +// ImageView imageView = new ImageView(context); +// ViewDetails viewDetails = new ViewDetails(imageView); +// +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// List diffs = thingy.generateDifferences(null); +// +// Assert.assertNotNull(diffs); +// Assert.assertTrue(diffs.isEmpty()); +// } +// +// // ==================== ADD RECORD GENERATION TESTS ==================== +// +// @Test +// public void testGenerateAdditionNodes() { +// ImageView imageView = new ImageView(context); +// Bitmap bitmap = createTestBitmap(10, 10); +// imageView.setImageBitmap(bitmap); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// int parentId = 123; +// List addRecords = thingy.generateAdditionNodes(parentId); +// +// Assert.assertNotNull(addRecords); +// Assert.assertEquals(1, addRecords.size()); +// Assert.assertEquals(parentId, addRecords.get(0).getParentId()); +// } +// +// // ==================== GETTER TESTS ==================== +// +// @Test +// public void testGetViewDetails() { +// ImageView imageView = new ImageView(context); +// ViewDetails viewDetails = new ViewDetails(imageView); +// +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertEquals(viewDetails, thingy.getViewDetails()); +// } +// +// @Test +// public void testShouldRecordSubviews() { +// ImageView imageView = new ImageView(context); +// ViewDetails viewDetails = new ViewDetails(imageView); +// +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// // ImageView should not record subviews +// Assert.assertFalse(thingy.shouldRecordSubviews()); +// } +// +// @Test +// public void testGetSubviews() { +// ImageView imageView = new ImageView(context); +// ViewDetails viewDetails = new ViewDetails(imageView); +// +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertNotNull(thingy.getSubviews()); +// Assert.assertTrue(thingy.getSubviews().isEmpty()); +// } +// +// @Test +// public void testGetViewId() { +// ImageView imageView = new ImageView(context); +// ViewDetails viewDetails = new ViewDetails(imageView); +// +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertEquals(viewDetails.viewId, thingy.getViewId()); +// } +// +// @Test +// public void testGetParentViewId() { +// FrameLayout parent = new FrameLayout(context); +// parent.setTag(NewRelicIdGenerator.NR_ID_TAG, 999); +// +// ImageView imageView = new ImageView(context); +// parent.addView(imageView); +// +// ViewDetails viewDetails = new ViewDetails(imageView); +// +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertEquals(viewDetails.parentId, thingy.getParentViewId()); +// } +// +// @Test +// public void testGetCssSelector() { +// ImageView imageView = new ImageView(context); +// ViewDetails viewDetails = new ViewDetails(imageView); +// +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// String cssSelector = thingy.getCssSelector(); +// Assert.assertNotNull(cssSelector); +// Assert.assertEquals(viewDetails.getCssSelector(), cssSelector); +// } +// +// // ==================== CHANGE DETECTION TESTS ==================== +// +// @Test +// public void testHasChanged_WithNull() { +// ImageView imageView = new ImageView(context); +// ViewDetails viewDetails = new ViewDetails(imageView); +// +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertTrue(thingy.hasChanged(null)); +// } +// +// @Test +// public void testHasChanged_WithDifferentType() { +// ImageView imageView = new ImageView(context); +// ViewDetails viewDetails = new ViewDetails(imageView); +// +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// SessionReplayViewThingy otherType = new SessionReplayViewThingy(viewDetails); +// +// Assert.assertTrue(thingy.hasChanged(otherType)); +// } +// +// @Test +// public void testHasChanged_WithSameInstance() { +// ImageView imageView = new ImageView(context); +// ViewDetails viewDetails = new ViewDetails(imageView); +// +// SessionReplayImageViewThingy thingy = new SessionReplayImageViewThingy(viewDetails, imageView, agentConfiguration); +// +// Assert.assertFalse(thingy.hasChanged(thingy)); +// } +// +// @Test +// public void testHasChanged_WithDifferentInstances() { +// ImageView imageView1 = new ImageView(context); +// Bitmap bitmap1 = createTestBitmap(10, 10); +// imageView1.setImageBitmap(bitmap1); +// ViewDetails viewDetails1 = new ViewDetails(imageView1); +// +// ImageView imageView2 = new ImageView(context); +// Bitmap bitmap2 = createTestBitmap(20, 20); +// imageView2.setImageBitmap(bitmap2); +// ViewDetails viewDetails2 = new ViewDetails(imageView2); +// +// SessionReplayImageViewThingy thingy1 = new SessionReplayImageViewThingy(viewDetails1, imageView1, agentConfiguration); +// SessionReplayImageViewThingy thingy2 = new SessionReplayImageViewThingy(viewDetails2, imageView2, agentConfiguration); +// +// Assert.assertTrue(thingy1.hasChanged(thingy2)); +// } +// +// // ==================== HELPER METHODS ==================== +// +// private Bitmap createTestBitmap(int width, int height) { +// return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); +// } +//} \ No newline at end of file diff --git a/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayProcessorTest.java b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayProcessorTest.java new file mode 100644 index 00000000..ce467c08 --- /dev/null +++ b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayProcessorTest.java @@ -0,0 +1,509 @@ +///* +// * Copyright (c) 2024. New Relic Corporation. All rights reserved. +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package com.newrelic.agent.android.sessionReplay; +// +//import android.content.Context; +//import android.view.View; +// +//import androidx.test.core.app.ApplicationProvider; +// +//import com.newrelic.agent.android.sessionReplay.models.IncrementalEvent.RRWebIncrementalEvent; +//import com.newrelic.agent.android.sessionReplay.models.IncrementalEvent.RRWebMutationData; +//import com.newrelic.agent.android.sessionReplay.models.RRWebElementNode; +//import com.newrelic.agent.android.sessionReplay.models.RRWebEvent; +//import com.newrelic.agent.android.sessionReplay.models.RRWebFullSnapshotEvent; +//import com.newrelic.agent.android.sessionReplay.models.RRWebMetaEvent; +// +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.robolectric.RobolectricTestRunner; +// +//import java.util.ArrayList; +//import java.util.Arrays; +//import java.util.List; +// +//@RunWith(RobolectricTestRunner.class) +//public class SessionReplayProcessorTest { +// +// private Context context; +// +// @Before +// public void setUp() { +// context = ApplicationProvider.getApplicationContext(); +// } +// +// // ==================== CONSTRUCTOR/STATE TESTS ==================== +// +// @Test +// public void testConstructor() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// Assert.assertNotNull(processor); +// } +// +// @Test +// public void testConstants() { +// Assert.assertEquals(2, SessionReplayProcessor.RRWEB_TYPE_FULL_SNAPSHOT); +// Assert.assertEquals(3, SessionReplayProcessor.RRWEB_TYPE_INCREMENTAL_SNAPSHOT); +// } +// +// // ==================== PROCESS FRAMES - FULL SNAPSHOT TESTS ==================== +// +// @Test +// public void testProcessFrames_SingleFrame_TakesFullSnapshot() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// SessionReplayFrame frame = createTestFrame(1000L, 1080, 1920); +// +// List events = processor.processFrames(Arrays.asList(frame), true); +// +// Assert.assertNotNull(events); +// Assert.assertEquals(2, events.size()); // Meta + Full snapshot +// +// Assert.assertTrue(events.get(0) instanceof RRWebMetaEvent); +// Assert.assertTrue(events.get(1) instanceof RRWebFullSnapshotEvent); +// } +// +// @Test +// public void testProcessFrames_FirstFrameAlwaysFullSnapshot() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// SessionReplayFrame frame = createTestFrame(1000L, 1080, 1920); +// +// List events = processor.processFrames(Arrays.asList(frame), false); +// +// Assert.assertNotNull(events); +// Assert.assertEquals(2, events.size()); // Meta + Full snapshot +// +// Assert.assertTrue(events.get(0) instanceof RRWebMetaEvent); +// Assert.assertTrue(events.get(1) instanceof RRWebFullSnapshotEvent); +// } +// +// @Test +// public void testProcessFrames_ForceFullSnapshot() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// SessionReplayFrame frame1 = createTestFrame(1000L, 1080, 1920); +// SessionReplayFrame frame2 = createTestFrame(2000L, 1080, 1920); +// +// List events = processor.processFrames(Arrays.asList(frame1, frame2), true); +// +// Assert.assertNotNull(events); +// Assert.assertEquals(4, events.size()); // 2 Meta + 2 Full snapshots +// +// // All should be full snapshots +// Assert.assertTrue(events.get(0) instanceof RRWebMetaEvent); +// Assert.assertTrue(events.get(1) instanceof RRWebFullSnapshotEvent); +// Assert.assertTrue(events.get(2) instanceof RRWebMetaEvent); +// Assert.assertTrue(events.get(3) instanceof RRWebFullSnapshotEvent); +// } +// +// // ==================== PROCESS FRAMES - INCREMENTAL SNAPSHOT TESTS ==================== +// +// @Test +// public void testProcessFrames_TwoFrames_SecondIsIncremental() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// +// View view1 = new View(context); +// ViewDetails details1 = new ViewDetails(view1); +// SessionReplayViewThingy thingy1 = new SessionReplayViewThingy(details1); +// SessionReplayFrame frame1 = new SessionReplayFrame(thingy1, 1000L, 1080, 1920); +// +// // Same root view ID +// View view2 = new View(context); +// view2.setTag(NewRelicIdGenerator.NR_ID_TAG, details1.viewId); // Force same ID +// ViewDetails details2 = new ViewDetails(view2); +// SessionReplayViewThingy thingy2 = new SessionReplayViewThingy(details2); +// SessionReplayFrame frame2 = new SessionReplayFrame(thingy2, 2000L, 1080, 1920); +// +// List events = processor.processFrames(Arrays.asList(frame1, frame2), false); +// +// Assert.assertNotNull(events); +// Assert.assertTrue(events.size() >= 3); // Meta + Full + Incremental +// +// Assert.assertTrue(events.get(0) instanceof RRWebMetaEvent); +// Assert.assertTrue(events.get(1) instanceof RRWebFullSnapshotEvent); +// Assert.assertTrue(events.get(2) instanceof RRWebIncrementalEvent); +// } +// +// @Test +// public void testProcessFrames_DimensionChange_TakesFullSnapshot() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// SessionReplayFrame frame1 = createTestFrame(1000L, 1080, 1920); +// SessionReplayFrame frame2 = createTestFrame(2000L, 1920, 1080); // Different dimensions +// +// List events = processor.processFrames(Arrays.asList(frame1, frame2), false); +// +// Assert.assertNotNull(events); +// // Should have 2 full snapshots due to dimension change +// Assert.assertEquals(4, events.size()); // 2 Meta + 2 Full +// +// Assert.assertTrue(events.get(2) instanceof RRWebMetaEvent); +// Assert.assertTrue(events.get(3) instanceof RRWebFullSnapshotEvent); +// } +// +// @Test +// public void testProcessFrames_RootViewIdChange_TakesFullSnapshot() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// SessionReplayFrame frame1 = createTestFrame(1000L, 1080, 1920); +// SessionReplayFrame frame2 = createTestFrame(2000L, 1080, 1920); +// +// // Different root view IDs will trigger full snapshot +// List events = processor.processFrames(Arrays.asList(frame1, frame2), false); +// +// Assert.assertNotNull(events); +// // Should have 2 full snapshots due to root ID change +// Assert.assertEquals(4, events.size()); +// } +// +// @Test +// public void testProcessFrames_EmptyList() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// +// List events = processor.processFrames(new ArrayList<>(), false); +// +// Assert.assertNotNull(events); +// Assert.assertTrue(events.isEmpty()); +// } +// +// // ==================== PROCESS FULL FRAME TESTS ==================== +// +// @Test +// public void testProcessFullFrame() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// SessionReplayFrame frame = createTestFrame(1000L, 1080, 1920); +// +// RRWebFullSnapshotEvent event = processor.processFullFrame(frame); +// +// Assert.assertNotNull(event); +// Assert.assertEquals(1000L, event.getTimestamp()); +// Assert.assertNotNull(event.getData()); +// Assert.assertNotNull(event.getData().getNode()); +// } +// +// @Test +// public void testProcessFullFrame_GeneratesHtmlStructure() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// SessionReplayFrame frame = createTestFrame(1000L, 1080, 1920); +// +// RRWebFullSnapshotEvent event = processor.processFullFrame(frame); +// +// Assert.assertNotNull(event.getData().getNode()); +// Assert.assertNotNull(event.getData().getNode().getChildNodes()); +// Assert.assertFalse(event.getData().getNode().getChildNodes().isEmpty()); +// +// // Should have HTML node as first child +// RRWebElementNode htmlNode = (RRWebElementNode) event.getData().getNode().getChildNodes().get(0); +// Assert.assertEquals(RRWebElementNode.TAG_TYPE_HTML, htmlNode.getTagName()); +// } +// +// @Test +// public void testProcessFullFrame_GeneratesHeadAndBody() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// SessionReplayFrame frame = createTestFrame(1000L, 1080, 1920); +// +// RRWebFullSnapshotEvent event = processor.processFullFrame(frame); +// +// RRWebElementNode htmlNode = (RRWebElementNode) event.getData().getNode().getChildNodes().get(0); +// Assert.assertEquals(2, htmlNode.getChildNodes().size()); +// +// RRWebElementNode headNode = (RRWebElementNode) htmlNode.getChildNodes().get(0); +// Assert.assertEquals(RRWebElementNode.TAG_TYPE_HEAD, headNode.getTagName()); +// +// RRWebElementNode bodyNode = (RRWebElementNode) htmlNode.getChildNodes().get(1); +// Assert.assertEquals(RRWebElementNode.TAG_TYPE_BODY, bodyNode.getTagName()); +// } +// +// @Test +// public void testProcessFullFrame_GeneratesCssInHead() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// SessionReplayFrame frame = createTestFrame(1000L, 1080, 1920); +// +// RRWebFullSnapshotEvent event = processor.processFullFrame(frame); +// +// RRWebElementNode htmlNode = (RRWebElementNode) event.getData().getNode().getChildNodes().get(0); +// RRWebElementNode headNode = (RRWebElementNode) htmlNode.getChildNodes().get(0); +// +// Assert.assertFalse(headNode.getChildNodes().isEmpty()); +// RRWebElementNode styleNode = (RRWebElementNode) headNode.getChildNodes().get(0); +// Assert.assertEquals(RRWebElementNode.TAG_TYPE_STYLE, styleNode.getTagName()); +// } +// +// @Test +// public void testProcessFullFrame_WithNestedViews() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// +// View parentView = new View(context); +// ViewDetails parentDetails = new ViewDetails(parentView); +// SessionReplayViewThingy parentThingy = new SessionReplayViewThingy(parentDetails); +// +// View childView = new View(context); +// ViewDetails childDetails = new ViewDetails(childView); +// SessionReplayViewThingy childThingy = new SessionReplayViewThingy(childDetails); +// +// parentThingy.setSubviews(Arrays.asList(childThingy)); +// +// SessionReplayFrame frame = new SessionReplayFrame(parentThingy, 1000L, 1080, 1920); +// +// RRWebFullSnapshotEvent event = processor.processFullFrame(frame); +// +// Assert.assertNotNull(event); +// Assert.assertNotNull(event.getData()); +// } +// +// // ==================== CREATE META EVENT TESTS ==================== +// +// @Test +// public void testCreateMetaEvent() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// SessionReplayFrame frame = createTestFrame(1234L, 1080, 1920); +// +// RRWebMetaEvent metaEvent = processor.createMetaEvent(frame); +// +// Assert.assertNotNull(metaEvent); +// Assert.assertEquals(1234L, metaEvent.getTimestamp()); +// Assert.assertNotNull(metaEvent.getData()); +// Assert.assertEquals("https://newrelic.com", metaEvent.getData().getHref()); +// Assert.assertEquals(1080, metaEvent.getData().getWidth()); +// Assert.assertEquals(1920, metaEvent.getData().getHeight()); +// } +// +// @Test +// public void testCreateMetaEvent_WithDifferentDimensions() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// SessionReplayFrame frame = createTestFrame(5000L, 800, 600); +// +// RRWebMetaEvent metaEvent = processor.createMetaEvent(frame); +// +// Assert.assertEquals(800, metaEvent.getData().getWidth()); +// Assert.assertEquals(600, metaEvent.getData().getHeight()); +// } +// +// @Test +// public void testCreateMetaEvent_PreservesTimestamp() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// long[] timestamps = {0L, 1L, 1000L, 999999999L, Long.MAX_VALUE}; +// +// for (long timestamp : timestamps) { +// SessionReplayFrame frame = createTestFrame(timestamp, 1080, 1920); +// RRWebMetaEvent metaEvent = processor.createMetaEvent(frame); +// Assert.assertEquals(timestamp, metaEvent.getTimestamp()); +// } +// } +// +// // ==================== ON NEW SCREEN TESTS ==================== +// +// @Test +// public void testOnNewScreen_ResetsLastFrame() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// +// // Process first frame +// SessionReplayFrame frame1 = createTestFrame(1000L, 1080, 1920); +// processor.processFrames(Arrays.asList(frame1), false); +// +// // Call onNewScreen +// processor.onNewScreen(); +// +// // Next frame should be full snapshot again +// SessionReplayFrame frame2 = createTestFrame(2000L, 1080, 1920); +// List events = processor.processFrames(Arrays.asList(frame2), false); +// +// // Should be full snapshot (Meta + Full) +// Assert.assertEquals(2, events.size()); +// Assert.assertTrue(events.get(0) instanceof RRWebMetaEvent); +// Assert.assertTrue(events.get(1) instanceof RRWebFullSnapshotEvent); +// } +// +// @Test +// public void testOnNewScreen_MultipleCallsSafe() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// +// processor.onNewScreen(); +// processor.onNewScreen(); +// processor.onNewScreen(); +// +// // Should not cause issues +// SessionReplayFrame frame = createTestFrame(1000L, 1080, 1920); +// List events = processor.processFrames(Arrays.asList(frame), false); +// +// Assert.assertNotNull(events); +// } +// +// // ==================== FLATTEN TREE TESTS ==================== +// +// @Test +// public void testFlattenTree_SingleNode() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// +// View view = new View(context); +// ViewDetails details = new ViewDetails(view); +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(details); +// +// SessionReplayFrame frame = new SessionReplayFrame(thingy, 1000L, 1080, 1920); +// +// // Test indirectly through processFrames which uses flattenTree +// List events = processor.processFrames(Arrays.asList(frame), false); +// +// Assert.assertNotNull(events); +// Assert.assertTrue(events.size() >= 2); +// } +// +// @Test +// public void testFlattenTree_TwoLevels() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// +// View parentView = new View(context); +// ViewDetails parentDetails = new ViewDetails(parentView); +// SessionReplayViewThingy parentThingy = new SessionReplayViewThingy(parentDetails); +// +// View childView = new View(context); +// ViewDetails childDetails = new ViewDetails(childView); +// SessionReplayViewThingy childThingy = new SessionReplayViewThingy(childDetails); +// +// parentThingy.setSubviews(Arrays.asList(childThingy)); +// +// SessionReplayFrame frame = new SessionReplayFrame(parentThingy, 1000L, 1080, 1920); +// +// List events = processor.processFrames(Arrays.asList(frame), false); +// +// Assert.assertNotNull(events); +// } +// +// @Test +// public void testFlattenTree_DeeplyNested() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// +// // Create 10-level deep hierarchy +// SessionReplayViewThingy root = null; +// SessionReplayViewThingy current = null; +// +// for (int i = 0; i < 10; i++) { +// View view = new View(context); +// ViewDetails details = new ViewDetails(view); +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(details); +// +// if (root == null) { +// root = thingy; +// current = thingy; +// } else { +// current.setSubviews(Arrays.asList(thingy)); +// current = thingy; +// } +// } +// +// SessionReplayFrame frame = new SessionReplayFrame(root, 1000L, 1080, 1920); +// +// List events = processor.processFrames(Arrays.asList(frame), false); +// +// Assert.assertNotNull(events); +// } +// +// // ==================== MULTIPLE FRAMES TESTS ==================== +// +// @Test +// public void testProcessFrames_MultipleFramesSameRoot() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// +// View view = new View(context); +// ViewDetails details = new ViewDetails(view); +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(details); +// +// SessionReplayFrame frame1 = new SessionReplayFrame(thingy, 1000L, 1080, 1920); +// +// // Second frame with same root ID +// View view2 = new View(context); +// view2.setTag(NewRelicIdGenerator.NR_ID_TAG, details.viewId); +// ViewDetails details2 = new ViewDetails(view2); +// SessionReplayViewThingy thingy2 = new SessionReplayViewThingy(details2); +// SessionReplayFrame frame2 = new SessionReplayFrame(thingy2, 2000L, 1080, 1920); +// +// SessionReplayFrame frame3 = new SessionReplayFrame(thingy2, 3000L, 1080, 1920); +// +// List events = processor.processFrames(Arrays.asList(frame1, frame2, frame3), false); +// +// Assert.assertNotNull(events); +// Assert.assertTrue(events.size() >= 4); // At least Meta + Full + 2 Incrementals +// } +// +// @Test +// public void testProcessFrames_AlternatingDimensions() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// +// SessionReplayFrame frame1 = createTestFrame(1000L, 1080, 1920); +// SessionReplayFrame frame2 = createTestFrame(2000L, 1920, 1080); +// SessionReplayFrame frame3 = createTestFrame(3000L, 1080, 1920); +// +// List events = processor.processFrames(Arrays.asList(frame1, frame2, frame3), false); +// +// Assert.assertNotNull(events); +// // Each dimension change should trigger full snapshot +// Assert.assertTrue(events.size() >= 6); // 3 Meta + 3 Full +// } +// +// // ==================== EDGE CASE TESTS ==================== +// +// @Test +// public void testProcessFrames_NullFrameHandling() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// +// // Should handle gracefully or throw appropriate exception +// try { +// List frames = new ArrayList<>(); +// frames.add(null); +// processor.processFrames(frames, false); +// } catch (NullPointerException e) { +// // Expected +// } +// } +// +// @Test +// public void testProcessFrames_ZeroDimensions() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// SessionReplayFrame frame = createTestFrame(1000L, 0, 0); +// +// List events = processor.processFrames(Arrays.asList(frame), false); +// +// Assert.assertNotNull(events); +// Assert.assertTrue(events.size() >= 2); +// +// RRWebMetaEvent metaEvent = (RRWebMetaEvent) events.get(0); +// Assert.assertEquals(0, metaEvent.getData().getWidth()); +// Assert.assertEquals(0, metaEvent.getData().getHeight()); +// } +// +// @Test +// public void testProcessFrames_NegativeTimestamp() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// SessionReplayFrame frame = createTestFrame(-1000L, 1080, 1920); +// +// List events = processor.processFrames(Arrays.asList(frame), false); +// +// Assert.assertNotNull(events); +// } +// +// @Test +// public void testProcessFullFrame_WithEmptySubviews() { +// SessionReplayProcessor processor = new SessionReplayProcessor(); +// +// View view = new View(context); +// ViewDetails details = new ViewDetails(view); +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(details); +// thingy.setSubviews(new ArrayList<>()); +// +// SessionReplayFrame frame = new SessionReplayFrame(thingy, 1000L, 1080, 1920); +// +// RRWebFullSnapshotEvent event = processor.processFullFrame(frame); +// +// Assert.assertNotNull(event); +// } +// +// // ==================== HELPER METHODS ==================== +// +// private SessionReplayFrame createTestFrame(long timestamp, int width, int height) { +// View view = new View(context); +// ViewDetails details = new ViewDetails(view); +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(details); +// return new SessionReplayFrame(thingy, timestamp, width, height); +// } +//} \ No newline at end of file diff --git a/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayTest.java b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayTest.java new file mode 100644 index 00000000..0a58039f --- /dev/null +++ b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayTest.java @@ -0,0 +1,732 @@ +///* +// * Copyright (c) 2024. New Relic Corporation. All rights reserved. +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package com.newrelic.agent.android.sessionReplay; +// +//import android.app.Application; +//import android.os.Handler; +//import android.os.Looper; +// +//import androidx.test.core.app.ApplicationProvider; +// +//import com.google.gson.JsonArray; +//import com.newrelic.agent.android.AgentConfiguration; +//import com.newrelic.agent.android.analytics.AnalyticsEvent; +//import com.newrelic.agent.android.background.ApplicationStateEvent; +// +//import org.junit.After; +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.robolectric.RobolectricTestRunner; +//import org.robolectric.Shadows; +//import org.robolectric.shadows.ShadowLooper; +// +//import java.util.concurrent.TimeUnit; +// +//import static org.mockito.Mockito.mock; +// +//@RunWith(RobolectricTestRunner.class) +//public class SessionReplayTest { +// +// private Application application; +// private Handler uiThreadHandler; +// private AgentConfiguration agentConfiguration; +// private SessionReplayConfiguration sessionReplayConfiguration; +// private ShadowLooper shadowLooper; +// +// @Before +// public void setUp() { +// application = ApplicationProvider.getApplicationContext(); +// uiThreadHandler = new Handler(Looper.getMainLooper()); +// shadowLooper = Shadows.shadowOf(Looper.getMainLooper()); +// +// agentConfiguration = AgentConfiguration.getInstance(); +// +// sessionReplayConfiguration = new SessionReplayConfiguration(); +// sessionReplayConfiguration.setEnabled(true); +// sessionReplayConfiguration.setMode("error"); +// sessionReplayConfiguration.processCustomMaskingRules(); +// agentConfiguration.setSessionReplayConfiguration(sessionReplayConfiguration); +// +// // Reset singleton state +// SessionReplayModeManager.resetInstance(); +// } +// +// @After +// public void tearDown() { +// try { +// SessionReplay.deInitialize(); +// SessionReplayModeManager.resetInstance(); +// } catch (Exception e) { +// // Ignore cleanup errors +// } +// } +// +// // ==================== INITIALIZATION TESTS ==================== +// +// @Test +// public void testInitialize_WithValidParameters_ErrorMode() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// +// Assert.assertNotNull(SessionReplay.getCurrentMode()); +// Assert.assertEquals(SessionReplayMode.ERROR, SessionReplay.getCurrentMode()); +// } +// +// @Test +// public void testInitialize_WithValidParameters_FullMode() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// +// Assert.assertNotNull(SessionReplay.getCurrentMode()); +// Assert.assertEquals(SessionReplayMode.FULL, SessionReplay.getCurrentMode()); +// } +// +// @Test +// public void testInitialize_WithNullApplication_DoesNotCrash() { +// // Should log error and return without crashing +// SessionReplay.initialize(null, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// // Test passes if no exception thrown +// } +// +// @Test +// public void testInitialize_WithNullHandler_DoesNotCrash() { +// // Should log error and return without crashing +// SessionReplay.initialize(application, null, agentConfiguration, SessionReplayMode.ERROR); +// // Test passes if no exception thrown +// } +// +// @Test +// public void testInitialize_WithNullMode_DoesNotCrash() { +// // Should log error and return without crashing +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, null); +// // Test passes if no exception thrown +// } +// +// @Test +// public void testInitialize_MultipleCallsSafe() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// +// // Should handle multiple initializations +// Assert.assertNotNull(SessionReplay.getCurrentMode()); +// } +// +// // ==================== DE-INITIALIZATION TESTS ==================== +// +// @Test +// public void testDeInitialize_AfterInitialization() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// SessionReplay.initSessionReplay(SessionReplayMode.ERROR); +// +// SessionReplay.deInitialize(); +// +// // Test passes if no exception thrown +// } +// +// @Test +// public void testDeInitialize_WithoutInitialization() { +// // Should handle deinitialization without initialization +// SessionReplay.deInitialize(); +// // Test passes if no exception thrown +// } +// +// @Test +// public void testDeInitialize_MultipleCalls() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// SessionReplay.initSessionReplay(SessionReplayMode.ERROR); +// +// SessionReplay.deInitialize(); +// SessionReplay.deInitialize(); +// +// // Multiple deinitialization should be safe +// } +// +// // ==================== TAKE FULL SNAPSHOT TESTS ==================== +// +// @Test +// public void testSetTakeFullSnapshot_True() { +// SessionReplay.setTakeFullSnapshot(true); +// Assert.assertTrue(SessionReplay.shouldTakeFullSnapshot()); +// } +// +// @Test +// public void testSetTakeFullSnapshot_False() { +// SessionReplay.setTakeFullSnapshot(false); +// Assert.assertFalse(SessionReplay.shouldTakeFullSnapshot()); +// } +// +// @Test +// public void testShouldTakeFullSnapshot_InitiallyTrue() { +// // Should default to true +// Assert.assertTrue(SessionReplay.shouldTakeFullSnapshot()); +// } +// +// @Test +// public void testSetTakeFullSnapshot_MultipleToggles() { +// SessionReplay.setTakeFullSnapshot(true); +// Assert.assertTrue(SessionReplay.shouldTakeFullSnapshot()); +// +// SessionReplay.setTakeFullSnapshot(false); +// Assert.assertFalse(SessionReplay.shouldTakeFullSnapshot()); +// +// SessionReplay.setTakeFullSnapshot(true); +// Assert.assertTrue(SessionReplay.shouldTakeFullSnapshot()); +// } +// +// // ==================== GET CURRENT MODE TESTS ==================== +// +// @Test +// public void testGetCurrentMode_WhenNotInitialized_ReturnsNull() { +// SessionReplayMode mode = SessionReplay.getCurrentMode(); +// Assert.assertNull(mode); +// } +// +// @Test +// public void testGetCurrentMode_AfterInitialization_ErrorMode() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// +// SessionReplayMode mode = SessionReplay.getCurrentMode(); +// Assert.assertEquals(SessionReplayMode.ERROR, mode); +// } +// +// @Test +// public void testGetCurrentMode_AfterInitialization_FullMode() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// +// SessionReplayMode mode = SessionReplay.getCurrentMode(); +// Assert.assertEquals(SessionReplayMode.FULL, mode); +// } +// +// // ==================== IS REPLAY RECORDING TESTS ==================== +// +// @Test +// public void testIsReplayRecording_ErrorMode_ReturnsTrue() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// +// // Note: Method has a bug (uses && instead of ||), so this tests the actual behavior +// boolean isRecording = SessionReplay.isReplayRecording(); +// // Test the actual implementation behavior +// } +// +// @Test +// public void testIsReplayRecording_OffMode_ReturnsFalse() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.OFF); +// +// boolean isRecording = SessionReplay.isReplayRecording(); +// Assert.assertFalse(isRecording); +// } +// +// // ==================== TRANSITION TO MODE TESTS ==================== +// +// @Test +// public void testTransitionToMode_FromErrorToFull() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// +// boolean result = SessionReplay.transitionToMode(SessionReplayMode.FULL, "test"); +// +// Assert.assertTrue(result); +// Assert.assertEquals(SessionReplayMode.FULL, SessionReplay.getCurrentMode()); +// Assert.assertTrue("Should force full snapshot after transition", SessionReplay.shouldTakeFullSnapshot()); +// } +// +// @Test +// public void testTransitionToMode_FromFullToError() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// +// boolean result = SessionReplay.transitionToMode(SessionReplayMode.ERROR, "test"); +// +// Assert.assertTrue(result); +// Assert.assertEquals(SessionReplayMode.ERROR, SessionReplay.getCurrentMode()); +// } +// +// @Test +// public void testTransitionToMode_WhenNotInitialized_ReturnsFalse() { +// boolean result = SessionReplay.transitionToMode(SessionReplayMode.FULL, "test"); +// +// Assert.assertFalse(result); +// } +// +// @Test +// public void testTransitionToMode_SameMode_ReturnsFalse() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// +// boolean result = SessionReplay.transitionToMode(SessionReplayMode.ERROR, "test"); +// +// Assert.assertFalse(result); +// } +// +// // ==================== SWITCH MODE ON ERROR TESTS ==================== +// +// @Test +// public void testSwitchModeOnError_FromErrorToFull_ReturnsTrue() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// +// boolean result = SessionReplay.switchModeOnError(); +// +// Assert.assertTrue(result); +// Assert.assertEquals(SessionReplayMode.FULL, SessionReplay.getCurrentMode()); +// Assert.assertTrue(SessionReplay.shouldTakeFullSnapshot()); +// } +// +// @Test +// public void testSwitchModeOnError_FromFullMode_ReturnsFalse() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// +// boolean result = SessionReplay.switchModeOnError(); +// +// Assert.assertFalse("Should not switch from FULL mode", result); +// Assert.assertEquals(SessionReplayMode.FULL, SessionReplay.getCurrentMode()); +// } +// +// @Test +// public void testSwitchModeOnError_WhenNotInitialized_ReturnsFalse() { +// boolean result = SessionReplay.switchModeOnError(); +// Assert.assertFalse(result); +// } +// +// @Test +// public void testOnError_CallsSwitchModeOnError() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// +// SessionReplay.onError(); +// +// // Should have switched to FULL mode +// Assert.assertEquals(SessionReplayMode.FULL, SessionReplay.getCurrentMode()); +// } +// +// // ==================== PAUSE REPLAY TESTS ==================== +// +// @Test +// public void testPauseReplay_FromErrorMode_ReturnsTrue() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// +// boolean result = SessionReplay.pauseReplay(); +// +// Assert.assertTrue(result); +// Assert.assertEquals(SessionReplayMode.OFF, SessionReplay.getCurrentMode()); +// } +// +// @Test +// public void testPauseReplay_FromFullMode_ReturnsTrue() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// +// boolean result = SessionReplay.pauseReplay(); +// +// Assert.assertTrue(result); +// Assert.assertEquals(SessionReplayMode.OFF, SessionReplay.getCurrentMode()); +// } +// +// @Test +// public void testPauseReplay_WhenAlreadyOff_ReturnsFalse() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.OFF); +// +// boolean result = SessionReplay.pauseReplay(); +// +// Assert.assertFalse("Should return false when already OFF", result); +// } +// +// // ==================== HARVEST LIFECYCLE TESTS ==================== +// +// @Test +// public void testOnHarvestStart_FullMode_SetsHarvestingFlag() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// SessionReplay.initSessionReplay(SessionReplayMode.FULL); +// +// shadowLooper.idle(); +// +// // Call onHarvestStart +// SessionReplay sessionReplay = new SessionReplay(); +// sessionReplay.onHarvestStart(); +// +// // Should set harvesting flag (can't test directly, but method should not throw) +// } +// +// @Test +// public void testOnHarvestStart_ErrorMode_DoesNotSetFlag() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// SessionReplay.initSessionReplay(SessionReplayMode.ERROR); +// +// shadowLooper.idle(); +// +// SessionReplay sessionReplay = new SessionReplay(); +// sessionReplay.onHarvestStart(); +// +// // Should not set harvesting flag in ERROR mode +// } +// +// @Test +// public void testOnHarvest_ErrorMode_SkipsHarvest() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// SessionReplay.initSessionReplay(SessionReplayMode.ERROR); +// +// shadowLooper.idle(); +// +// SessionReplay sessionReplay = new SessionReplay(); +// sessionReplay.onHarvest(); +// +// // Should skip harvest in ERROR mode +// } +// +// @Test +// public void testOnHarvest_FullMode_WithNoEvents() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// SessionReplay.initSessionReplay(SessionReplayMode.FULL); +// +// shadowLooper.idle(); +// +// SessionReplay sessionReplay = new SessionReplay(); +// sessionReplay.onHarvest(); +// +// // Should handle empty events gracefully +// } +// +// @Test +// public void testOnHarvestComplete_FlushesBufferedFrames() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// SessionReplay.initSessionReplay(SessionReplayMode.FULL); +// +// shadowLooper.idle(); +// +// SessionReplay sessionReplay = new SessionReplay(); +// sessionReplay.onHarvestStart(); // Start harvesting +// // Frames would be buffered here... +// sessionReplay.onHarvestComplete(); // Complete harvest +// +// // Should flush buffered data +// } +// +// // ==================== APPLICATION STATE LISTENER TESTS ==================== +// +// @Test +// public void testApplicationForegrounded_DoesNothing() { +// SessionReplay sessionReplay = new SessionReplay(); +// ApplicationStateEvent event = mock(ApplicationStateEvent.class); +// +// // Should not throw exception +// sessionReplay.applicationForegrounded(event); +// } +// +// @Test +// public void testApplicationBackgrounded_ClearsWorkingFile() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// SessionReplay.initSessionReplay(SessionReplayMode.FULL); +// +// shadowLooper.idle(); +// +// SessionReplay sessionReplay = new SessionReplay(); +// ApplicationStateEvent event = mock(ApplicationStateEvent.class); +// +// sessionReplay.applicationBackgrounded(event); +// +// // Should clear working file (method should not throw) +// } +// +// // ==================== EVENT LISTENER TESTS ==================== +// +// @Test +// public void testOnEventAdded_ReturnsTrue() { +// SessionReplay sessionReplay = new SessionReplay(); +// AnalyticsEvent event = mock(AnalyticsEvent.class); +// +// boolean result = sessionReplay.onEventAdded(event); +// +// Assert.assertTrue(result); +// } +// +// @Test +// public void testOnEventOverflow_ReturnsTrue() { +// SessionReplay sessionReplay = new SessionReplay(); +// AnalyticsEvent event = mock(AnalyticsEvent.class); +// +// boolean result = sessionReplay.onEventOverflow(event); +// +// Assert.assertTrue(result); +// } +// +// @Test +// public void testOnEventEvicted_ReturnsTrue() { +// SessionReplay sessionReplay = new SessionReplay(); +// AnalyticsEvent event = mock(AnalyticsEvent.class); +// +// boolean result = sessionReplay.onEventEvicted(event); +// +// Assert.assertTrue(result); +// } +// +// @Test +// public void testOnEventQueueSizeExceeded_DoesNotThrow() { +// SessionReplay sessionReplay = new SessionReplay(); +// sessionReplay.onEventQueueSizeExceeded(1000); +// // Should not throw exception +// } +// +// @Test +// public void testOnEventQueueTimeExceeded_DoesNotThrow() { +// SessionReplay sessionReplay = new SessionReplay(); +// sessionReplay.onEventQueueTimeExceeded(60); +// // Should not throw exception +// } +// +// @Test +// public void testOnEventFlush_DoesNotThrow() { +// SessionReplay sessionReplay = new SessionReplay(); +// sessionReplay.onEventFlush(); +// // Should not throw exception +// } +// +// @Test +// public void testOnStart_DoesNotThrow() { +// SessionReplay sessionReplay = new SessionReplay(); +// sessionReplay.onStart(null); +// // Should not throw exception +// } +// +// @Test +// public void testOnShutdown_DoesNotThrow() { +// SessionReplay sessionReplay = new SessionReplay(); +// sessionReplay.onShutdown(); +// // Should not throw exception +// } +// +// // ==================== ON FRAME TAKEN TESTS ==================== +// +// @Test +// public void testOnFrameTaken_WithValidFrame() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// SessionReplay.initSessionReplay(SessionReplayMode.FULL); +// +// shadowLooper.idle(); +// +// SessionReplay sessionReplay = new SessionReplay(); +// SessionReplayFrame frame = mock(SessionReplayFrame.class); +// +// // Should not throw exception +// sessionReplay.onFrameTaken(frame); +// } +// +// @Test +// public void testOnFrameTaken_DuringHarvest_BuffersFrame() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// SessionReplay.initSessionReplay(SessionReplayMode.FULL); +// +// shadowLooper.idle(); +// +// SessionReplay sessionReplay = new SessionReplay(); +// SessionReplayFrame frame = mock(SessionReplayFrame.class); +// +// // Start harvest (buffers frames) +// sessionReplay.onHarvestStart(); +// +// // Frame should be buffered +// sessionReplay.onFrameTaken(frame); +// +// // Complete harvest (flushes buffer) +// sessionReplay.onHarvestComplete(); +// } +// +// // ==================== ON TOUCH RECORDED TESTS ==================== +// +// @Test +// public void testOnTouchRecorded_WithValidTouchTracker() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// SessionReplay.initSessionReplay(SessionReplayMode.FULL); +// +// shadowLooper.idle(); +// +// SessionReplay sessionReplay = new SessionReplay(); +// TouchTracker touchTracker = new TouchTracker(System.currentTimeMillis()); +// +// // Should not throw exception +// sessionReplay.onTouchRecorded(touchTracker); +// } +// +// @Test +// public void testOnTouchRecorded_DuringHarvest_BuffersTouchData() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// SessionReplay.initSessionReplay(SessionReplayMode.FULL); +// +// shadowLooper.idle(); +// +// SessionReplay sessionReplay = new SessionReplay(); +// TouchTracker touchTracker = new TouchTracker(System.currentTimeMillis()); +// +// // Start harvest +// sessionReplay.onHarvestStart(); +// +// // Touch should be buffered +// sessionReplay.onTouchRecorded(touchTracker); +// +// // Complete harvest +// sessionReplay.onHarvestComplete(); +// } +// +// // ==================== START/STOP RECORDING TESTS ==================== +// +// @Test +// public void testStartRecording_ErrorMode() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// +// // Should start recording with sliding window +// SessionReplay.startRecording(SessionReplayMode.ERROR); +// shadowLooper.idle(); +// +// Assert.assertTrue(SessionReplay.shouldTakeFullSnapshot()); +// } +// +// @Test +// public void testStartRecording_FullMode() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// +// // Should start recording without sliding window +// SessionReplay.startRecording(SessionReplayMode.FULL); +// shadowLooper.idle(); +// +// Assert.assertTrue(SessionReplay.shouldTakeFullSnapshot()); +// } +// +// @Test +// public void testStopRecording() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// SessionReplay.initSessionReplay(SessionReplayMode.FULL); +// +// shadowLooper.idle(); +// +// SessionReplay.stopRecording(); +// shadowLooper.idle(); +// +// // Should stop recording without throwing exception +// } +// +// // ==================== INIT SESSION REPLAY TESTS ==================== +// +// @Test +// public void testInitSessionReplay_ErrorMode() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// +// SessionReplay.initSessionReplay(SessionReplayMode.ERROR); +// shadowLooper.idle(); +// +// // Should initialize in ERROR mode +// Assert.assertEquals(SessionReplayMode.ERROR, SessionReplay.getCurrentMode()); +// } +// +// @Test +// public void testInitSessionReplay_FullMode() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// +// SessionReplay.initSessionReplay(SessionReplayMode.FULL); +// shadowLooper.idle(); +// +// // Should initialize in FULL mode +// Assert.assertEquals(SessionReplayMode.FULL, SessionReplay.getCurrentMode()); +// } +// +// // ==================== INTEGRATION TESTS ==================== +// +// @Test +// public void testIntegration_FullLifecycle_ErrorMode() { +// // Initialize +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// SessionReplay.initSessionReplay(SessionReplayMode.ERROR); +// shadowLooper.idle(); +// +// Assert.assertEquals(SessionReplayMode.ERROR, SessionReplay.getCurrentMode()); +// +// // Switch to FULL on error +// SessionReplay.onError(); +// Assert.assertEquals(SessionReplayMode.FULL, SessionReplay.getCurrentMode()); +// +// // Pause +// SessionReplay.pauseReplay(); +// Assert.assertEquals(SessionReplayMode.OFF, SessionReplay.getCurrentMode()); +// +// // Deinitialize +// SessionReplay.deInitialize(); +// } +// +// @Test +// public void testIntegration_FullLifecycle_FullMode() { +// // Initialize +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// SessionReplay.initSessionReplay(SessionReplayMode.FULL); +// shadowLooper.idle(); +// +// Assert.assertEquals(SessionReplayMode.FULL, SessionReplay.getCurrentMode()); +// +// // Pause +// SessionReplay.pauseReplay(); +// Assert.assertEquals(SessionReplayMode.OFF, SessionReplay.getCurrentMode()); +// +// // Deinitialize +// SessionReplay.deInitialize(); +// } +// +// @Test +// public void testIntegration_HarvestCycle() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// SessionReplay.initSessionReplay(SessionReplayMode.FULL); +// shadowLooper.idle(); +// +// SessionReplay sessionReplay = new SessionReplay(); +// +// // Simulate harvest cycle +// sessionReplay.onHarvestStart(); +// sessionReplay.onHarvest(); +// sessionReplay.onHarvestComplete(); +// +// // Should complete without errors +// } +// +// // ==================== EDGE CASE TESTS ==================== +// +// @Test +// public void testEdgeCase_MultipleInitAndDeinit() { +// for (int i = 0; i < 5; i++) { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// SessionReplay.initSessionReplay(SessionReplayMode.ERROR); +// shadowLooper.idle(); +// SessionReplay.deInitialize(); +// } +// +// // Should handle multiple cycles +// } +// +// @Test +// public void testEdgeCase_RapidModeTransitions() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.ERROR); +// +// // Rapid transitions +// SessionReplay.transitionToMode(SessionReplayMode.FULL, "test1"); +// SessionReplay.transitionToMode(SessionReplayMode.OFF, "test2"); +// SessionReplay.transitionToMode(SessionReplayMode.ERROR, "test3"); +// SessionReplay.transitionToMode(SessionReplayMode.FULL, "test4"); +// +// Assert.assertEquals(SessionReplayMode.FULL, SessionReplay.getCurrentMode()); +// } +// +// @Test +// public void testEdgeCase_PauseReplayMultipleTimes() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// +// boolean result1 = SessionReplay.pauseReplay(); +// boolean result2 = SessionReplay.pauseReplay(); +// boolean result3 = SessionReplay.pauseReplay(); +// +// Assert.assertTrue(result1); +// Assert.assertFalse("Second pause should return false", result2); +// Assert.assertFalse("Third pause should return false", result3); +// } +// +// @Test +// public void testEdgeCase_OnErrorWhenAlreadyInFullMode() { +// SessionReplay.initialize(application, uiThreadHandler, agentConfiguration, SessionReplayMode.FULL); +// +// SessionReplay.onError(); +// +// // Should stay in FULL mode +// Assert.assertEquals(SessionReplayMode.FULL, SessionReplay.getCurrentMode()); +// } +//} \ No newline at end of file diff --git a/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayTestBase.java b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayTestBase.java new file mode 100644 index 00000000..78363d46 --- /dev/null +++ b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayTestBase.java @@ -0,0 +1,127 @@ +///* +// * Copyright (c) 2024. New Relic Corporation. All rights reserved. +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package com.newrelic.agent.android.sessionReplay; +// +//import android.content.Context; +//import android.graphics.Bitmap; +//import android.graphics.Color; +// +//import androidx.test.core.app.ApplicationProvider; +// +//import com.newrelic.agent.android.AgentConfiguration; +// +//import org.junit.After; +//import org.junit.Before; +//import org.junit.runner.RunWith; +//import org.robolectric.RobolectricTestRunner; +// +///** +// * Base class for Session Replay unit tests providing common setup and utilities +// */ +//@RunWith(RobolectricTestRunner.class) +//public abstract class SessionReplayTestBase { +// +// protected Context context; +// protected AgentConfiguration agentConfiguration; +// protected SessionReplayConfiguration sessionReplayConfiguration; +// protected SessionReplayLocalConfiguration sessionReplayLocalConfiguration; +// +// @Before +// public void setUp() { +// context = ApplicationProvider.getApplicationContext(); +// +// // Setup default configuration +// agentConfiguration = new AgentConfiguration(); +// +// sessionReplayConfiguration = new SessionReplayConfiguration(); +// sessionReplayConfiguration.setEnabled(true); +// sessionReplayConfiguration.setMode("full"); +// sessionReplayConfiguration.setMaskUserInputText(false); +// sessionReplayConfiguration.setMaskApplicationText(false); +// sessionReplayConfiguration.setMaskAllImages(false); +// sessionReplayConfiguration.setMaskTouches(false); +// +// sessionReplayLocalConfiguration = new SessionReplayLocalConfiguration(); +// +// agentConfiguration.setSessionReplayConfiguration(sessionReplayConfiguration); +// agentConfiguration.setSessionReplayLocalConfiguration(sessionReplayLocalConfiguration); +// } +// +// @After +// public void tearDown() { +// if (sessionReplayLocalConfiguration != null) { +// sessionReplayLocalConfiguration.clearAllViewMasks(); +// } +// } +// +// /** +// * Creates a test bitmap with specified dimensions +// */ +// protected Bitmap createTestBitmap(int width, int height) { +// return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); +// } +// +// /** +// * Creates a small test bitmap (10x10) +// */ +// protected Bitmap createSmallTestBitmap() { +// return createTestBitmap(10, 10); +// } +// +// /** +// * Creates a colored test bitmap +// */ +// protected Bitmap createColoredBitmap(int width, int height, int color) { +// Bitmap bitmap = createTestBitmap(width, height); +// bitmap.eraseColor(color); +// return bitmap; +// } +// +// /** +// * Creates a red test bitmap +// */ +// protected Bitmap createRedBitmap() { +// return createColoredBitmap(10, 10, Color.RED); +// } +// +// /** +// * Creates a configuration with all masking enabled +// */ +// protected AgentConfiguration createMaskingEnabledConfiguration() { +// AgentConfiguration config = new AgentConfiguration(); +// SessionReplayConfiguration srConfig = new SessionReplayConfiguration(); +// srConfig.setEnabled(true); +// srConfig.setMaskUserInputText(true); +// srConfig.setMaskApplicationText(true); +// srConfig.setMaskAllImages(true); +// srConfig.setMaskTouches(true); +// +// SessionReplayLocalConfiguration localConfig = new SessionReplayLocalConfiguration(); +// config.setSessionReplayConfiguration(srConfig); +// config.setSessionReplayLocalConfiguration(localConfig); +// +// return config; +// } +// +// /** +// * Creates a configuration with all masking disabled +// */ +// protected AgentConfiguration createMaskingDisabledConfiguration() { +// AgentConfiguration config = new AgentConfiguration(); +// SessionReplayConfiguration srConfig = new SessionReplayConfiguration(); +// srConfig.setEnabled(true); +// srConfig.setMaskUserInputText(false); +// srConfig.setMaskApplicationText(false); +// srConfig.setMaskAllImages(false); +// srConfig.setMaskTouches(false); +// +// SessionReplayLocalConfiguration localConfig = new SessionReplayLocalConfiguration(); +// config.setSessionReplayConfiguration(srConfig); +// config.setSessionReplayLocalConfiguration(localConfig); +// +// return config; +// } +//} \ No newline at end of file diff --git a/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayTextViewThingyTest.java b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayTextViewThingyTest.java new file mode 100644 index 00000000..f22cc5f6 --- /dev/null +++ b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayTextViewThingyTest.java @@ -0,0 +1,616 @@ +///* +// * Copyright (c) 2024. New Relic Corporation. All rights reserved. +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package com.newrelic.agent.android.sessionReplay; +// +//import android.content.Context; +//import android.graphics.Color; +//import android.graphics.Typeface; +//import android.text.InputType; +//import android.view.Gravity; +//import android.widget.EditText; +//import android.widget.LinearLayout; +//import android.widget.TextView; +// +//import androidx.test.core.app.ApplicationProvider; +// +//import com.newrelic.agent.android.AgentConfiguration; +//import com.newrelic.agent.android.R; +//import com.newrelic.agent.android.sessionReplay.models.RRWebElementNode; +// +//import org.junit.After; +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.robolectric.RobolectricTestRunner; +// +//@RunWith(RobolectricTestRunner.class) +//public class SessionReplayTextViewThingyTest { +// +// private Context context; +// private AgentConfiguration agentConfiguration; +// private SessionReplayConfiguration sessionReplayConfiguration; +// private SessionReplayLocalConfiguration sessionReplayLocalConfiguration; +// +// @Before +// public void setUp() { +// context = ApplicationProvider.getApplicationContext(); +// agentConfiguration = new AgentConfiguration(); +// +// sessionReplayConfiguration = new SessionReplayConfiguration(); +// sessionReplayConfiguration.setEnabled(true); +// sessionReplayConfiguration.setMode("full"); +// sessionReplayConfiguration.setMaskUserInputText(false); +// sessionReplayConfiguration.setMaskApplicationText(false); +// +// sessionReplayLocalConfiguration = new SessionReplayLocalConfiguration(); +// +// agentConfiguration.setSessionReplayConfiguration(sessionReplayConfiguration); +// agentConfiguration.setSessionReplayLocalConfiguration(sessionReplayLocalConfiguration); +// } +// +// @After +// public void tearDown() { +// sessionReplayLocalConfiguration.clearAllViewMasks(); +// } +// +// // ==================== BASIC PROPERTY EXTRACTION TESTS ==================== +// +// @Test +// public void testConstructorWithBasicTextView() { +// TextView textView = new TextView(context); +// textView.setText("Hello World"); +// textView.setTextSize(16f); +// textView.setTextColor(Color.BLACK); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertEquals("Hello World", thingy.getLabelText()); +// Assert.assertTrue(thingy.getFontSize() > 0); +// Assert.assertNotNull(thingy.getFontFamily()); +// Assert.assertNotNull(thingy.getTextColor()); +// Assert.assertNotNull(thingy.getTextAlign()); +// } +// +// @Test +// public void testTextExtractionWithEmptyText() { +// TextView textView = new TextView(context); +// textView.setText(""); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertEquals("", thingy.getLabelText()); +// } +// +// @Test +// public void testTextExtractionWithNullText() { +// TextView textView = new TextView(context); +// textView.setText(null); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertEquals("", thingy.getLabelText()); +// } +// +// @Test +// public void testTextExtractionWithLongText() { +// TextView textView = new TextView(context); +// String longText = "This is a very long text that contains many words and should be properly captured by the session replay system without truncation or modification."; +// textView.setText(longText); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertEquals(longText, thingy.getLabelText()); +// } +// +// @Test +// public void testTextExtractionWithSpecialCharacters() { +// TextView textView = new TextView(context); +// String specialText = "Special: !@#$%^&*()_+-={}[]|:;<>?,./~`"; +// textView.setText(specialText); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertEquals(specialText, thingy.getLabelText()); +// } +// +// @Test +// public void testTextExtractionWithUnicodeCharacters() { +// TextView textView = new TextView(context); +// String unicodeText = "日本語 😀🎉 Émojis"; +// textView.setText(unicodeText); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertEquals(unicodeText, thingy.getLabelText()); +// } +// +// // ==================== PASSWORD DETECTION TESTS ==================== +// +// @Test +// public void testPasswordFieldAlwaysMasked_TextPassword() { +// TextView textView = new TextView(context); +// textView.setText("secret123"); +// textView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); +// +// // Even with masking disabled, password should be masked +// sessionReplayConfiguration.setMaskUserInputText(false); +// sessionReplayConfiguration.setMaskApplicationText(false); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// // Password should be masked (9 asterisks for 9 characters) +// Assert.assertEquals("*********", thingy.getLabelText()); +// } +// +// @Test +// public void testPasswordFieldAlwaysMasked_VisiblePassword() { +// TextView textView = new TextView(context); +// textView.setText("secret123"); +// textView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); +// +// sessionReplayConfiguration.setMaskUserInputText(false); +// sessionReplayConfiguration.setMaskApplicationText(false); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertEquals("*********", thingy.getLabelText()); +// } +// +// @Test +// public void testPasswordFieldAlwaysMasked_WebPassword() { +// TextView textView = new TextView(context); +// textView.setText("secret123"); +// textView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD); +// +// sessionReplayConfiguration.setMaskUserInputText(false); +// sessionReplayConfiguration.setMaskApplicationText(false); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertEquals("*********", thingy.getLabelText()); +// } +// +// @Test +// public void testPasswordFieldAlwaysMasked_NumberPassword() { +// TextView textView = new TextView(context); +// textView.setText("1234"); +// textView.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD); +// +// sessionReplayConfiguration.setMaskUserInputText(false); +// sessionReplayConfiguration.setMaskApplicationText(false); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertEquals("****", thingy.getLabelText()); +// } +// +// // ==================== CONFIGURATION-BASED MASKING TESTS ==================== +// +// @Test +// public void testMaskUserInputText_EditText() { +// EditText editText = new EditText(context); +// editText.setText("user input"); +// +// sessionReplayConfiguration.setMaskUserInputText(true); +// sessionReplayConfiguration.setMaskApplicationText(false); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, editText, agentConfiguration); +// +// // EditText content should be masked +// Assert.assertEquals("**********", thingy.getLabelText()); +// } +// +// @Test +// public void testMaskUserInputText_EditTextNotMasked() { +// EditText editText = new EditText(context); +// editText.setText("user input"); +// +// sessionReplayConfiguration.setMaskUserInputText(false); +// sessionReplayConfiguration.setMaskApplicationText(false); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, editText, agentConfiguration); +// +// // EditText content should NOT be masked +// Assert.assertEquals("user input", thingy.getLabelText()); +// } +// +// @Test +// public void testMaskApplicationText_TextView() { +// TextView textView = new TextView(context); +// textView.setText("app text"); +// +// sessionReplayConfiguration.setMaskUserInputText(false); +// sessionReplayConfiguration.setMaskApplicationText(true); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// // TextView (application text) should be masked +// Assert.assertEquals("********", thingy.getLabelText()); +// } +// +// @Test +// public void testMaskApplicationText_TextViewNotMasked() { +// TextView textView = new TextView(context); +// textView.setText("app text"); +// +// sessionReplayConfiguration.setMaskUserInputText(false); +// sessionReplayConfiguration.setMaskApplicationText(false); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// // TextView (application text) should NOT be masked +// Assert.assertEquals("app text", thingy.getLabelText()); +// } +// +// @Test +// public void testBothMaskingOptions_EditText() { +// EditText editText = new EditText(context); +// editText.setText("user input"); +// +// sessionReplayConfiguration.setMaskUserInputText(true); +// sessionReplayConfiguration.setMaskApplicationText(true); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, editText, agentConfiguration); +// +// // EditText should use maskUserInputText setting (both are true here) +// Assert.assertEquals("**********", thingy.getLabelText()); +// } +// +// @Test +// public void testBothMaskingOptions_TextView() { +// TextView textView = new TextView(context); +// textView.setText("app text"); +// +// sessionReplayConfiguration.setMaskUserInputText(true); +// sessionReplayConfiguration.setMaskApplicationText(true); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// // TextView should use maskApplicationText setting (both are true here) +// Assert.assertEquals("********", thingy.getLabelText()); +// } +// +// // ==================== PRIVACY TAG TESTS ==================== +// +// @Test +// public void testPrivacyTag_Mask() { +// TextView textView = new TextView(context); +// textView.setText("sensitive data"); +// textView.setTag(R.id.newrelic_privacy, "nr-mask"); +// +// sessionReplayConfiguration.setMaskApplicationText(false); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// // Privacy tag should force masking +// Assert.assertEquals("**************", thingy.getLabelText()); +// } +// +// @Test +// public void testPrivacyTag_Unmask() { +// TextView textView = new TextView(context); +// textView.setText("public data"); +// textView.setTag(R.id.newrelic_privacy, "nr-unmask"); +// +// sessionReplayConfiguration.setMaskApplicationText(true); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// // Privacy tag should prevent masking +// Assert.assertEquals("public data", thingy.getLabelText()); +// } +// +// @Test +// public void testPrivacyTag_MaskOverridesConfiguration() { +// EditText editText = new EditText(context); +// editText.setText("user data"); +// editText.setTag(R.id.newrelic_privacy, "nr-mask"); +// +// sessionReplayConfiguration.setMaskUserInputText(false); +// +// ViewDetails viewDetails = new ViewDetails(editText); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, editText, agentConfiguration); +// +// // Privacy tag should override configuration +// Assert.assertEquals("*********", thingy.getLabelText()); +// } +// +// @Test +// public void testPrivacyTag_PasswordAlwaysMaskedEvenWithUnmask() { +// TextView textView = new TextView(context); +// textView.setText("password123"); +// textView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); +// textView.setTag(R.id.newrelic_privacy, "nr-unmask"); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// // Password fields should ALWAYS be masked, even with nr-unmask tag +// Assert.assertEquals("***********", thingy.getLabelText()); +// } +// +// // ==================== FONT EXTRACTION TESTS ==================== +// +// @Test +// public void testFontSizeExtraction() { +// TextView textView = new TextView(context); +// textView.setText("Sample text"); +// textView.setTextSize(20f); // 20 sp +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// // Font size should be extracted (converted to dp) +// Assert.assertTrue(thingy.getFontSize() > 0); +// } +// +// @Test +// public void testFontFamilyExtraction_Default() { +// TextView textView = new TextView(context); +// textView.setText("Sample text"); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertNotNull(thingy.getFontFamily()); +// Assert.assertEquals("sans-serif", thingy.getFontFamily()); +// } +// +// @Test +// public void testFontFamilyExtraction_Monospace() { +// TextView textView = new TextView(context); +// textView.setText("Sample text"); +// textView.setTypeface(Typeface.MONOSPACE); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertEquals("monospace", thingy.getFontFamily()); +// } +// +// @Test +// public void testFontFamilyExtraction_Serif() { +// TextView textView = new TextView(context); +// textView.setText("Sample text"); +// textView.setTypeface(Typeface.SERIF); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertEquals("serif", thingy.getFontFamily()); +// } +// +// // ==================== COLOR EXTRACTION TESTS ==================== +// +// @Test +// public void testTextColorExtraction_Black() { +// TextView textView = new TextView(context); +// textView.setText("Sample text"); +// textView.setTextColor(Color.BLACK); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// // Black color should be "000000" (RGB, alpha masked off) +// Assert.assertEquals("000000", thingy.getTextColor()); +// } +// +// @Test +// public void testTextColorExtraction_White() { +// TextView textView = new TextView(context); +// textView.setText("Sample text"); +// textView.setTextColor(Color.WHITE); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// // White color should be "ffffff" +// Assert.assertEquals("ffffff", thingy.getTextColor()); +// } +// +// @Test +// public void testTextColorExtraction_CustomColor() { +// TextView textView = new TextView(context); +// textView.setText("Sample text"); +// textView.setTextColor(Color.rgb(255, 0, 0)); // Red +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// // Red color should be "ff0000" +// Assert.assertEquals("ff0000", thingy.getTextColor()); +// } +// +// // ==================== TEXT ALIGNMENT TESTS ==================== +// +// @Test +// public void testTextAlignment_Left() { +// TextView textView = new TextView(context); +// textView.setText("Left aligned"); +// textView.setGravity(Gravity.LEFT); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertEquals("left", thingy.getTextAlign()); +// } +// +// @Test +// public void testTextAlignment_Center() { +// TextView textView = new TextView(context); +// textView.setText("Center aligned"); +// textView.setGravity(Gravity.CENTER_HORIZONTAL); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertEquals("center", thingy.getTextAlign()); +// } +// +// @Test +// public void testTextAlignment_Right() { +// TextView textView = new TextView(context); +// textView.setText("Right aligned"); +// textView.setGravity(Gravity.RIGHT); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertEquals("right", thingy.getTextAlign()); +// } +// +// @Test +// public void testTextAlignment_ExplicitTextAlignmentTakesPrecedence() { +// TextView textView = new TextView(context); +// textView.setText("Sample text"); +// textView.setGravity(Gravity.LEFT); +// textView.setTextAlignment(TextView.TEXT_ALIGNMENT_CENTER); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// // Explicit textAlignment should override gravity +// Assert.assertEquals("center", thingy.getTextAlign()); +// } +// +// // ==================== RRWEB NODE GENERATION TESTS ==================== +// +// @Test +// public void testGenerateRRWebNode() { +// TextView textView = new TextView(context); +// textView.setText("Test"); +// textView.setTextSize(16f); +// textView.setTextColor(Color.BLACK); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// RRWebElementNode node = thingy.generateRRWebNode(); +// +// Assert.assertNotNull(node); +// Assert.assertNotNull(node.getAttributes()); +// Assert.assertNotNull(node.getChildNodes()); +// } +// +// @Test +// public void testGenerateRRWebNode_TextContentIncluded() { +// TextView textView = new TextView(context); +// textView.setText("Hello RRWeb"); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// RRWebElementNode node = thingy.generateRRWebNode(); +// +// // Text should be included in child nodes +// Assert.assertNotNull(node.getChildNodes()); +// Assert.assertFalse(node.getChildNodes().isEmpty()); +// } +// +// @Test +// public void testGenerateRRWebNode_MaskedText() { +// TextView textView = new TextView(context); +// textView.setText("secret"); +// textView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// RRWebElementNode node = thingy.generateRRWebNode(); +// +// Assert.assertNotNull(node); +// // The text in child nodes should be masked +// Assert.assertNotNull(node.getChildNodes()); +// } +// +// // ==================== VIEW DETAILS TESTS ==================== +// +// @Test +// public void testGetViewDetails() { +// TextView textView = new TextView(context); +// textView.setText("Sample"); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertNotNull(thingy.getViewDetails()); +// Assert.assertEquals(viewDetails, thingy.getViewDetails()); +// } +// +// @Test +// public void testShouldRecordSubviews() { +// TextView textView = new TextView(context); +// textView.setText("Sample"); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// // TextViews should not record subviews +// Assert.assertFalse(thingy.shouldRecordSubviews()); +// } +// +// // ==================== EDGE CASE TESTS ==================== +// +// @Test +// public void testMaskingWithEmptyPassword() { +// TextView textView = new TextView(context); +// textView.setText(""); +// textView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// // Empty password should result in empty masked string +// Assert.assertEquals("", thingy.getLabelText()); +// } +// +// @Test +// public void testMaskingWithSingleCharacter() { +// TextView textView = new TextView(context); +// textView.setText("x"); +// textView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// // Single character should be masked with single asterisk +// Assert.assertEquals("*", thingy.getLabelText()); +// } +// +// @Test +// public void testTextViewInViewGroup() { +// LinearLayout parent = new LinearLayout(context); +// TextView textView = new TextView(context); +// textView.setText("Child text"); +// parent.addView(textView); +// +// ViewDetails viewDetails = new ViewDetails(textView); +// SessionReplayTextViewThingy thingy = new SessionReplayTextViewThingy(viewDetails, textView, agentConfiguration); +// +// Assert.assertNotNull(thingy); +// Assert.assertEquals("Child text", thingy.getLabelText()); +// } +//} \ No newline at end of file diff --git a/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayThingyRecorderTest.java b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayThingyRecorderTest.java new file mode 100644 index 00000000..46752dce --- /dev/null +++ b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayThingyRecorderTest.java @@ -0,0 +1,605 @@ +///* +// * Copyright (c) 2024. New Relic Corporation. All rights reserved. +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package com.newrelic.agent.android.sessionReplay; +// +//import android.content.Context; +//import android.view.View; +//import android.widget.Button; +//import android.widget.EditText; +//import android.widget.ImageView; +//import android.widget.TextView; +// +//import androidx.compose.ui.node.LayoutNode; +//import androidx.compose.ui.semantics.Role; +//import androidx.compose.ui.semantics.SemanticsConfiguration; +//import androidx.compose.ui.semantics.SemanticsNode; +//import androidx.compose.ui.semantics.SemanticsProperties; +//import androidx.test.core.app.ApplicationProvider; +// +//import com.newrelic.agent.android.AgentConfiguration; +//import com.newrelic.agent.android.sessionReplay.compose.ComposeEditTextThingy; +//import com.newrelic.agent.android.sessionReplay.compose.ComposeImageThingy; +//import com.newrelic.agent.android.sessionReplay.compose.ComposeTextViewThingy; +//import com.newrelic.agent.android.sessionReplay.compose.SessionReplayComposeViewThingy; +// +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.robolectric.RobolectricTestRunner; +// +//import static org.mockito.Mockito.mock; +//import static org.mockito.Mockito.when; +// +//@RunWith(RobolectricTestRunner.class) +//public class SessionReplayThingyRecorderTest { +// +// private Context context; +// private AgentConfiguration agentConfiguration; +// private SessionReplayThingyRecorder recorder; +// +// @Before +// public void setUp() { +// context = ApplicationProvider.getApplicationContext(); +// agentConfiguration = AgentConfiguration.getInstance(); +// recorder = new SessionReplayThingyRecorder(agentConfiguration); +// } +// +// // ==================== CONSTRUCTOR TESTS ==================== +// +// @Test +// public void testConstructor_WithValidConfiguration() { +// SessionReplayThingyRecorder newRecorder = new SessionReplayThingyRecorder(agentConfiguration); +// Assert.assertNotNull(newRecorder); +// } +// +// @Test +// public void testConstructor_WithNullConfiguration() { +// SessionReplayThingyRecorder newRecorder = new SessionReplayThingyRecorder(null); +// Assert.assertNotNull(newRecorder); +// } +// +// // ==================== ANDROID VIEW RECORDING TESTS ==================== +// +// @Test +// public void testRecordView_EditText_ReturnsEditTextThingy() { +// EditText editText = new EditText(context); +// editText.setText("Test input"); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(editText); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayEditTextThingy); +// } +// +// @Test +// public void testRecordView_ImageView_ReturnsImageViewThingy() { +// ImageView imageView = new ImageView(context); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(imageView); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayImageViewThingy); +// } +// +// @Test +// public void testRecordView_TextView_ReturnsTextViewThingy() { +// TextView textView = new TextView(context); +// textView.setText("Test text"); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(textView); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayTextViewThingy); +// } +// +// @Test +// public void testRecordView_Button_ReturnsTextViewThingy() { +// // Button extends TextView, so should return SessionReplayTextViewThingy +// Button button = new Button(context); +// button.setText("Click me"); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(button); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayTextViewThingy); +// } +// +// @Test +// public void testRecordView_PlainView_ReturnsViewThingy() { +// View view = new View(context); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(view); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayViewThingy); +// Assert.assertFalse(thingy instanceof SessionReplayTextViewThingy); +// Assert.assertFalse(thingy instanceof SessionReplayImageViewThingy); +// Assert.assertFalse(thingy instanceof SessionReplayEditTextThingy); +// } +// +// @Test +// public void testRecordView_EditText_WithEmptyText() { +// EditText editText = new EditText(context); +// editText.setText(""); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(editText); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayEditTextThingy); +// } +// +// @Test +// public void testRecordView_ImageView_WithNullDrawable() { +// ImageView imageView = new ImageView(context); +// imageView.setImageDrawable(null); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(imageView); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayImageViewThingy); +// } +// +// @Test +// public void testRecordView_TextView_WithNullText() { +// TextView textView = new TextView(context); +// textView.setText(null); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(textView); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayTextViewThingy); +// } +// +// // ==================== VIEW HIERARCHY TESTS ==================== +// +// @Test +// public void testRecordView_EditTextIsSubclassOfTextView_ReturnsEditTextThingy() { +// // EditText is checked before TextView in the if-else chain +// // Verify correct precedence +// EditText editText = new EditText(context); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(editText); +// +// // Should be EditTextThingy, not TextViewThingy +// Assert.assertTrue(thingy instanceof SessionReplayEditTextThingy); +// } +// +// @Test +// public void testRecordView_ImageViewWithNonStandardScaleType() { +// ImageView imageView = new ImageView(context); +// imageView.setScaleType(ImageView.ScaleType.MATRIX); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(imageView); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayImageViewThingy); +// } +// +// // ==================== VIEW STATE TESTS ==================== +// +// @Test +// public void testRecordView_InvisibleView() { +// View view = new View(context); +// view.setVisibility(View.INVISIBLE); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(view); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayViewThingy); +// } +// +// @Test +// public void testRecordView_GoneView() { +// View view = new View(context); +// view.setVisibility(View.GONE); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(view); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayViewThingy); +// } +// +// @Test +// public void testRecordView_ViewWithZeroAlpha() { +// TextView textView = new TextView(context); +// textView.setAlpha(0.0f); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(textView); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayTextViewThingy); +// } +// +// // ==================== PRIVACY TAG TESTS ==================== +// +// @Test +// public void testRecordView_ViewWithMaskTag() { +// TextView textView = new TextView(context); +// textView.setTag("nr-mask"); +// textView.setText("Sensitive data"); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(textView); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayTextViewThingy); +// } +// +// @Test +// public void testRecordView_ViewWithUnmaskTag() { +// EditText editText = new EditText(context); +// editText.setTag("nr-unmask"); +// editText.setText("Public data"); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(editText); +// +// Assert.assertNotNull(thingy); +// Assert.assertTrue(thingy instanceof SessionReplayEditTextThingy); +// } +// +// // ==================== MULTIPLE VIEWS TESTS ==================== +// +// @Test +// public void testRecordView_MultipleViewsInSequence() { +// TextView textView = new TextView(context); +// ImageView imageView = new ImageView(context); +// EditText editText = new EditText(context); +// View plainView = new View(context); +// +// SessionReplayViewThingyInterface thingy1 = recorder.recordView(textView); +// SessionReplayViewThingyInterface thingy2 = recorder.recordView(imageView); +// SessionReplayViewThingyInterface thingy3 = recorder.recordView(editText); +// SessionReplayViewThingyInterface thingy4 = recorder.recordView(plainView); +// +// Assert.assertTrue(thingy1 instanceof SessionReplayTextViewThingy); +// Assert.assertTrue(thingy2 instanceof SessionReplayImageViewThingy); +// Assert.assertTrue(thingy3 instanceof SessionReplayEditTextThingy); +// Assert.assertTrue(thingy4 instanceof SessionReplayViewThingy); +// } +// +// // ==================== COMPOSE SEMANTICS NODE TESTS ==================== +// +// @Test +// @androidx.compose.ui.InternalComposeUiApi +// public void testRecordView_SemanticsNode_WithEditableText() { +// SemanticsNode node = mock(SemanticsNode.class); +// SemanticsConfiguration config = mock(SemanticsConfiguration.class); +// LayoutNode layoutNode = mock(LayoutNode.class); +// +// when(node.getConfig()).thenReturn(config); +// when(config.contains(SemanticsProperties.INSTANCE.getEditableText())).thenReturn(true); +// when(layoutNode.getInteropView()).thenReturn(null); +// +// // Mock ReflectionUtils to return our mock layoutNode +// // Note: In real tests, we'd need PowerMockito or similar to mock static methods +// // For now, assuming the reflection works +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(node, 1.0f); +// +// // This test would pass if ReflectionUtils.getLayoutNode() works correctly +// Assert.assertNotNull(thingy); +// // Would expect ComposeEditTextThingy but reflection might fail in test environment +// } +// +// @Test +// @androidx.compose.ui.InternalComposeUiApi +// public void testRecordView_SemanticsNode_WithText() { +// SemanticsNode node = mock(SemanticsNode.class); +// SemanticsConfiguration config = mock(SemanticsConfiguration.class); +// LayoutNode layoutNode = mock(LayoutNode.class); +// +// when(node.getConfig()).thenReturn(config); +// when(config.contains(SemanticsProperties.INSTANCE.getEditableText())).thenReturn(false); +// when(config.contains(SemanticsProperties.INSTANCE.getText())).thenReturn(true); +// when(layoutNode.getInteropView()).thenReturn(null); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(node, 1.0f); +// +// Assert.assertNotNull(thingy); +// // Would expect ComposeTextViewThingy +// } +// +// @Test +// @androidx.compose.ui.InternalComposeUiApi +// public void testRecordView_SemanticsNode_WithImageRole() { +// SemanticsNode node = mock(SemanticsNode.class); +// SemanticsConfiguration config = mock(SemanticsConfiguration.class); +// LayoutNode layoutNode = mock(LayoutNode.class); +// Role imageRole = Role.Companion.getImage(); +// +// when(node.getConfig()).thenReturn(config); +// when(config.contains(SemanticsProperties.INSTANCE.getEditableText())).thenReturn(false); +// when(config.contains(SemanticsProperties.INSTANCE.getText())).thenReturn(false); +// when(config.contains(SemanticsProperties.INSTANCE.getRole())).thenReturn(true); +// when(config.get(SemanticsProperties.INSTANCE.getRole())).thenReturn(imageRole); +// when(layoutNode.getInteropView()).thenReturn(null); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(node, 1.0f); +// +// Assert.assertNotNull(thingy); +// // Would expect ComposeImageThingy +// } +// +// @Test +// @androidx.compose.ui.InternalComposeUiApi +// public void testRecordView_SemanticsNode_WithButtonRole() { +// SemanticsNode node = mock(SemanticsNode.class); +// SemanticsConfiguration config = mock(SemanticsConfiguration.class); +// LayoutNode layoutNode = mock(LayoutNode.class); +// Role buttonRole = Role.Companion.getButton(); +// +// when(node.getConfig()).thenReturn(config); +// when(config.contains(SemanticsProperties.INSTANCE.getEditableText())).thenReturn(false); +// when(config.contains(SemanticsProperties.INSTANCE.getText())).thenReturn(false); +// when(config.contains(SemanticsProperties.INSTANCE.getRole())).thenReturn(true); +// when(config.get(SemanticsProperties.INSTANCE.getRole())).thenReturn(buttonRole); +// when(layoutNode.getInteropView()).thenReturn(null); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(node, 1.0f); +// +// Assert.assertNotNull(thingy); +// // Button role has no special handling, falls through to default +// // Would expect SessionReplayComposeViewThingy +// } +// +// @Test +// @androidx.compose.ui.InternalComposeUiApi +// public void testRecordView_SemanticsNode_WithInteropView() { +// SemanticsNode node = mock(SemanticsNode.class); +// LayoutNode layoutNode = mock(LayoutNode.class); +// TextView interopView = new TextView(context); +// interopView.setText("Interop TextView"); +// +// when(layoutNode.getInteropView()).thenReturn(interopView); +// +// // When a SemanticsNode has an InteropView, it delegates to the Android View path +// SessionReplayViewThingyInterface thingy = recorder.recordView(node, 1.0f); +// +// Assert.assertNotNull(thingy); +// // Should return SessionReplayTextViewThingy since interopView is a TextView +// } +// +// @Test +// @androidx.compose.ui.InternalComposeUiApi +// public void testRecordView_SemanticsNode_DefaultCase() { +// SemanticsNode node = mock(SemanticsNode.class); +// SemanticsConfiguration config = mock(SemanticsConfiguration.class); +// LayoutNode layoutNode = mock(LayoutNode.class); +// +// when(node.getConfig()).thenReturn(config); +// when(config.contains(SemanticsProperties.INSTANCE.getEditableText())).thenReturn(false); +// when(config.contains(SemanticsProperties.INSTANCE.getText())).thenReturn(false); +// when(config.contains(SemanticsProperties.INSTANCE.getRole())).thenReturn(false); +// when(layoutNode.getInteropView()).thenReturn(null); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(node, 1.0f); +// +// Assert.assertNotNull(thingy); +// // Default case should return SessionReplayComposeViewThingy +// } +// +// // ==================== DENSITY TESTS ==================== +// +// @Test +// @androidx.compose.ui.InternalComposeUiApi +// public void testRecordView_SemanticsNode_WithDifferentDensities() { +// SemanticsNode node = mock(SemanticsNode.class); +// SemanticsConfiguration config = mock(SemanticsConfiguration.class); +// LayoutNode layoutNode = mock(LayoutNode.class); +// +// when(node.getConfig()).thenReturn(config); +// when(config.contains(SemanticsProperties.INSTANCE.getEditableText())).thenReturn(false); +// when(config.contains(SemanticsProperties.INSTANCE.getText())).thenReturn(false); +// when(config.contains(SemanticsProperties.INSTANCE.getRole())).thenReturn(false); +// when(layoutNode.getInteropView()).thenReturn(null); +// +// // Test with different density values +// SessionReplayViewThingyInterface thingy1 = recorder.recordView(node, 1.0f); +// SessionReplayViewThingyInterface thingy2 = recorder.recordView(node, 2.0f); +// SessionReplayViewThingyInterface thingy3 = recorder.recordView(node, 3.0f); +// +// Assert.assertNotNull(thingy1); +// Assert.assertNotNull(thingy2); +// Assert.assertNotNull(thingy3); +// } +// +// @Test +// @androidx.compose.ui.InternalComposeUiApi +// public void testRecordView_SemanticsNode_WithZeroDensity() { +// SemanticsNode node = mock(SemanticsNode.class); +// SemanticsConfiguration config = mock(SemanticsConfiguration.class); +// LayoutNode layoutNode = mock(LayoutNode.class); +// +// when(node.getConfig()).thenReturn(config); +// when(config.contains(SemanticsProperties.INSTANCE.getEditableText())).thenReturn(false); +// when(config.contains(SemanticsProperties.INSTANCE.getText())).thenReturn(false); +// when(config.contains(SemanticsProperties.INSTANCE.getRole())).thenReturn(false); +// when(layoutNode.getInteropView()).thenReturn(null); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(node, 0.0f); +// +// Assert.assertNotNull(thingy); +// } +// +// @Test +// @androidx.compose.ui.InternalComposeUiApi +// public void testRecordView_SemanticsNode_WithNegativeDensity() { +// SemanticsNode node = mock(SemanticsNode.class); +// SemanticsConfiguration config = mock(SemanticsConfiguration.class); +// LayoutNode layoutNode = mock(LayoutNode.class); +// +// when(node.getConfig()).thenReturn(config); +// when(config.contains(SemanticsProperties.INSTANCE.getEditableText())).thenReturn(false); +// when(config.contains(SemanticsProperties.INSTANCE.getText())).thenReturn(false); +// when(config.contains(SemanticsProperties.INSTANCE.getRole())).thenReturn(false); +// when(layoutNode.getInteropView()).thenReturn(null); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(node, -1.0f); +// +// Assert.assertNotNull(thingy); +// } +// +// // ==================== PRECEDENCE TESTS ==================== +// +// @Test +// @androidx.compose.ui.InternalComposeUiApi +// public void testRecordView_SemanticsNode_EditableTextTakesPrecedenceOverText() { +// SemanticsNode node = mock(SemanticsNode.class); +// SemanticsConfiguration config = mock(SemanticsConfiguration.class); +// LayoutNode layoutNode = mock(LayoutNode.class); +// +// when(node.getConfig()).thenReturn(config); +// // Both EditableText and Text are present +// when(config.contains(SemanticsProperties.INSTANCE.getEditableText())).thenReturn(true); +// when(config.contains(SemanticsProperties.INSTANCE.getText())).thenReturn(true); +// when(layoutNode.getInteropView()).thenReturn(null); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(node, 1.0f); +// +// Assert.assertNotNull(thingy); +// // EditableText is checked first, so should return ComposeEditTextThingy +// } +// +// @Test +// @androidx.compose.ui.InternalComposeUiApi +// public void testRecordView_SemanticsNode_TextTakesPrecedenceOverRole() { +// SemanticsNode node = mock(SemanticsNode.class); +// SemanticsConfiguration config = mock(SemanticsConfiguration.class); +// LayoutNode layoutNode = mock(LayoutNode.class); +// Role imageRole = Role.Companion.getImage(); +// +// when(node.getConfig()).thenReturn(config); +// when(config.contains(SemanticsProperties.INSTANCE.getEditableText())).thenReturn(false); +// // Both Text and Role are present +// when(config.contains(SemanticsProperties.INSTANCE.getText())).thenReturn(true); +// when(config.contains(SemanticsProperties.INSTANCE.getRole())).thenReturn(true); +// when(config.get(SemanticsProperties.INSTANCE.getRole())).thenReturn(imageRole); +// when(layoutNode.getInteropView()).thenReturn(null); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(node, 1.0f); +// +// Assert.assertNotNull(thingy); +// // Text is checked before Role, so should return ComposeTextViewThingy +// } +// +// // ==================== EDGE CASE TESTS ==================== +// +// @Test +// public void testRecordView_ViewWithMultipleTypeCasts() { +// // Create an EditText and verify it's not misidentified +// EditText editText = new EditText(context); +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(editText); +// +// // Should be EditTextThingy, not ImageViewThingy or plain ViewThingy +// Assert.assertTrue(thingy instanceof SessionReplayEditTextThingy); +// Assert.assertFalse(thingy instanceof SessionReplayImageViewThingy); +// } +// +// @Test +// public void testRecordView_SameRecorderMultipleCalls() { +// // Verify recorder can be reused without state pollution +// View view1 = new View(context); +// TextView view2 = new TextView(context); +// ImageView view3 = new ImageView(context); +// +// SessionReplayViewThingyInterface thingy1 = recorder.recordView(view1); +// SessionReplayViewThingyInterface thingy2 = recorder.recordView(view2); +// SessionReplayViewThingyInterface thingy3 = recorder.recordView(view3); +// +// // Each should return correct type +// Assert.assertTrue(thingy1 instanceof SessionReplayViewThingy); +// Assert.assertTrue(thingy2 instanceof SessionReplayTextViewThingy); +// Assert.assertTrue(thingy3 instanceof SessionReplayImageViewThingy); +// } +// +// @Test +// public void testRecordView_ViewAfterConfigurationChange() { +// TextView textView = new TextView(context); +// textView.setText("Before config change"); +// +// SessionReplayViewThingyInterface thingy1 = recorder.recordView(textView); +// +// // Simulate configuration change +// textView.setText("After config change"); +// +// SessionReplayViewThingyInterface thingy2 = recorder.recordView(textView); +// +// Assert.assertTrue(thingy1 instanceof SessionReplayTextViewThingy); +// Assert.assertTrue(thingy2 instanceof SessionReplayTextViewThingy); +// } +// +// // ==================== CONFIGURATION TESTS ==================== +// +// @Test +// public void testRecordView_WithDifferentConfigurations() { +// AgentConfiguration config1 = AgentConfiguration.getInstance(); +// AgentConfiguration config2 = AgentConfiguration.getInstance(); +// +// SessionReplayThingyRecorder recorder1 = new SessionReplayThingyRecorder(config1); +// SessionReplayThingyRecorder recorder2 = new SessionReplayThingyRecorder(config2); +// +// TextView textView = new TextView(context); +// +// SessionReplayViewThingyInterface thingy1 = recorder1.recordView(textView); +// SessionReplayViewThingyInterface thingy2 = recorder2.recordView(textView); +// +// Assert.assertTrue(thingy1 instanceof SessionReplayTextViewThingy); +// Assert.assertTrue(thingy2 instanceof SessionReplayTextViewThingy); +// } +// +// // ==================== NULL AND INVALID INPUT TESTS ==================== +// +// @Test(expected = NullPointerException.class) +// public void testRecordView_NullView_ThrowsException() { +// recorder.recordView((View) null); +// } +// +// @Test +// @androidx.compose.ui.InternalComposeUiApi +// public void testRecordView_NullSemanticsNode() { +// try { +// recorder.recordView((SemanticsNode) null, 1.0f); +// Assert.fail("Should throw NullPointerException"); +// } catch (NullPointerException e) { +// // Expected +// } +// } +// +// // ==================== TYPE VERIFICATION TESTS ==================== +// +// @Test +// public void testRecordView_ReturnTypesImplementInterface() { +// View view = new View(context); +// TextView textView = new TextView(context); +// ImageView imageView = new ImageView(context); +// EditText editText = new EditText(context); +// +// SessionReplayViewThingyInterface viewThingy = recorder.recordView(view); +// SessionReplayViewThingyInterface textThingy = recorder.recordView(textView); +// SessionReplayViewThingyInterface imageThingy = recorder.recordView(imageView); +// SessionReplayViewThingyInterface editThingy = recorder.recordView(editText); +// +// // All should implement the interface +// Assert.assertTrue(viewThingy instanceof SessionReplayViewThingyInterface); +// Assert.assertTrue(textThingy instanceof SessionReplayViewThingyInterface); +// Assert.assertTrue(imageThingy instanceof SessionReplayViewThingyInterface); +// Assert.assertTrue(editThingy instanceof SessionReplayViewThingyInterface); +// } +// +// @Test +// public void testRecordView_CustomViewSubclass_ReturnsPlanViewThingy() { +// // Custom view that doesn't extend TextView, ImageView, or EditText +// View customView = new View(context) { +// // Custom implementation +// }; +// +// SessionReplayViewThingyInterface thingy = recorder.recordView(customView); +// +// Assert.assertTrue(thingy instanceof SessionReplayViewThingy); +// Assert.assertFalse(thingy instanceof SessionReplayTextViewThingy); +// } +//} \ No newline at end of file diff --git a/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayViewThingyTest.java b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayViewThingyTest.java new file mode 100644 index 00000000..9cdf4c37 --- /dev/null +++ b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/SessionReplayViewThingyTest.java @@ -0,0 +1,648 @@ +///* +// * Copyright (c) 2024. New Relic Corporation. All rights reserved. +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package com.newrelic.agent.android.sessionReplay; +// +//import android.content.Context; +//import android.graphics.Color; +//import android.view.View; +//import android.widget.FrameLayout; +//import android.widget.LinearLayout; +// +//import androidx.test.core.app.ApplicationProvider; +// +//import com.newrelic.agent.android.sessionReplay.models.Attributes; +//import com.newrelic.agent.android.sessionReplay.models.IncrementalEvent.MutationRecord; +//import com.newrelic.agent.android.sessionReplay.models.IncrementalEvent.RRWebMutationData; +//import com.newrelic.agent.android.sessionReplay.models.RRWebElementNode; +// +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.robolectric.RobolectricTestRunner; +// +//import java.util.ArrayList; +//import java.util.Arrays; +//import java.util.List; +// +//@RunWith(RobolectricTestRunner.class) +//public class SessionReplayViewThingyTest { +// +// private Context context; +// +// @Before +// public void setUp() { +// context = ApplicationProvider.getApplicationContext(); +// } +// +// // ==================== CONSTRUCTOR TESTS ==================== +// +// @Test +// public void testConstructorWithViewDetails() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// Assert.assertNotNull(thingy); +// Assert.assertEquals(viewDetails, thingy.getViewDetails()); +// } +// +// @Test +// public void testConstructorInitializesEmptySubviewsList() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// Assert.assertNotNull(thingy.getSubviews()); +// Assert.assertTrue(thingy.getSubviews().isEmpty()); +// } +// +// // ==================== GETTER/SETTER TESTS ==================== +// +// @Test +// public void testGetViewDetails() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// Assert.assertEquals(viewDetails, thingy.getViewDetails()); +// } +// +// @Test +// public void testShouldRecordSubviews() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// // Generic View should record subviews +// Assert.assertTrue(thingy.shouldRecordSubviews()); +// } +// +// @Test +// public void testGetSubviews() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// List subviews = thingy.getSubviews(); +// +// Assert.assertNotNull(subviews); +// Assert.assertTrue(subviews.isEmpty()); +// } +// +// @Test +// public void testSetSubviews() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// // Create subviews +// View childView1 = new View(context); +// View childView2 = new View(context); +// ViewDetails childDetails1 = new ViewDetails(childView1); +// ViewDetails childDetails2 = new ViewDetails(childView2); +// +// SessionReplayViewThingy child1 = new SessionReplayViewThingy(childDetails1); +// SessionReplayViewThingy child2 = new SessionReplayViewThingy(childDetails2); +// +// List subviews = Arrays.asList(child1, child2); +// +// thingy.setSubviews(subviews); +// +// Assert.assertEquals(2, thingy.getSubviews().size()); +// Assert.assertEquals(child1, thingy.getSubviews().get(0)); +// Assert.assertEquals(child2, thingy.getSubviews().get(1)); +// } +// +// @Test +// public void testSetSubviewsWithEmptyList() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// thingy.setSubviews(new ArrayList<>()); +// +// Assert.assertNotNull(thingy.getSubviews()); +// Assert.assertTrue(thingy.getSubviews().isEmpty()); +// } +// +// @Test +// public void testGetViewId() { +// View view = new View(context); +// view.setTag(NewRelicIdGenerator.NR_ID_TAG, 12345); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// Assert.assertEquals(viewDetails.viewId, thingy.getViewId()); +// } +// +// @Test +// public void testGetParentViewId() { +// FrameLayout parent = new FrameLayout(context); +// parent.setTag(NewRelicIdGenerator.NR_ID_TAG, 999); +// +// View childView = new View(context); +// parent.addView(childView); +// +// ViewDetails viewDetails = new ViewDetails(childView); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// Assert.assertEquals(viewDetails.parentId, thingy.getParentViewId()); +// } +// +// // ==================== CSS GENERATION TESTS ==================== +// +// @Test +// public void testGetCssSelector() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// String cssSelector = thingy.getCssSelector(); +// +// Assert.assertNotNull(cssSelector); +// Assert.assertEquals(viewDetails.getCssSelector(), cssSelector); +// } +// +// @Test +// public void testGenerateCssDescription() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// String cssDescription = thingy.generateCssDescription(); +// +// Assert.assertNotNull(cssDescription); +// Assert.assertEquals(viewDetails.generateCssDescription(), cssDescription); +// } +// +// @Test +// public void testGenerateInlineCss() { +// View view = new View(context); +// view.setBackgroundColor(Color.RED); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// String inlineCss = thingy.generateInlineCss(); +// +// Assert.assertNotNull(inlineCss); +// Assert.assertEquals(viewDetails.generateInlineCSS(), inlineCss); +// Assert.assertTrue(inlineCss.contains("position:")); +// } +// +// @Test +// public void testGenerateInlineCssWithTransparentBackground() { +// View view = new View(context); +// view.setBackgroundColor(Color.TRANSPARENT); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// String inlineCss = thingy.generateInlineCss(); +// Assert.assertNotNull(inlineCss); +// } +// +// // ==================== RRWEB NODE GENERATION TESTS ==================== +// +// @Test +// public void testGenerateRRWebNode() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// RRWebElementNode node = thingy.generateRRWebNode(); +// +// Assert.assertNotNull(node); +// Assert.assertNotNull(node.getAttributes()); +// Assert.assertEquals(RRWebElementNode.TAG_TYPE_DIV, node.getTagName()); +// Assert.assertEquals(viewDetails.viewId, node.getId()); +// Assert.assertNotNull(node.getChildNodes()); +// } +// +// @Test +// public void testGenerateRRWebNodeHasCorrectAttributes() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// RRWebElementNode node = thingy.generateRRWebNode(); +// +// Attributes attributes = node.getAttributes(); +// Assert.assertNotNull(attributes); +// Assert.assertEquals(viewDetails.getCSSSelector(), attributes.getCssSelector()); +// } +// +// @Test +// public void testGenerateRRWebNodeWithChildNodes() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// RRWebElementNode node = thingy.generateRRWebNode(); +// +// Assert.assertNotNull(node.getChildNodes()); +// Assert.assertTrue(node.getChildNodes().isEmpty()); +// } +// +// // ==================== DIFF GENERATION TESTS ==================== +// +// @Test +// public void testGenerateDifferences_WithSameView() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy1 = new SessionReplayViewThingy(viewDetails); +// SessionReplayViewThingy thingy2 = new SessionReplayViewThingy(viewDetails); +// +// List diffs = thingy1.generateDifferences(thingy2); +// +// Assert.assertNotNull(diffs); +// Assert.assertEquals(1, diffs.size()); +// } +// +// @Test +// public void testGenerateDifferences_WithDifferentPosition() { +// View view1 = new View(context); +// view1.layout(0, 0, 100, 100); +// ViewDetails viewDetails1 = new ViewDetails(view1); +// +// View view2 = new View(context); +// view2.layout(50, 50, 150, 150); +// ViewDetails viewDetails2 = new ViewDetails(view2); +// +// SessionReplayViewThingy thingy1 = new SessionReplayViewThingy(viewDetails1); +// SessionReplayViewThingy thingy2 = new SessionReplayViewThingy(viewDetails2); +// +// List diffs = thingy1.generateDifferences(thingy2); +// +// Assert.assertNotNull(diffs); +// Assert.assertFalse(diffs.isEmpty()); +// Assert.assertEquals(1, diffs.size()); +// +// // Should contain an AttributeRecord +// Assert.assertTrue(diffs.get(0) instanceof RRWebMutationData.AttributeRecord); +// RRWebMutationData.AttributeRecord attrRecord = (RRWebMutationData.AttributeRecord) diffs.get(0); +// Assert.assertNotNull(attrRecord.getAttributes()); +// Assert.assertNotNull(attrRecord.getAttributes().getMetadata()); +// } +// +// @Test +// public void testGenerateDifferences_WithDifferentBackgroundColor() { +// View view1 = new View(context); +// view1.setBackgroundColor(Color.RED); +// ViewDetails viewDetails1 = new ViewDetails(view1); +// +// View view2 = new View(context); +// view2.setBackgroundColor(Color.BLUE); +// ViewDetails viewDetails2 = new ViewDetails(view2); +// +// SessionReplayViewThingy thingy1 = new SessionReplayViewThingy(viewDetails1); +// SessionReplayViewThingy thingy2 = new SessionReplayViewThingy(viewDetails2); +// +// List diffs = thingy1.generateDifferences(thingy2); +// +// Assert.assertNotNull(diffs); +// Assert.assertFalse(diffs.isEmpty()); +// Assert.assertTrue(diffs.get(0) instanceof RRWebMutationData.AttributeRecord); +// } +// +// @Test +// public void testGenerateDifferences_WithNullOther() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// List diffs = thingy.generateDifferences(null); +// +// Assert.assertNotNull(diffs); +// Assert.assertTrue(diffs.isEmpty()); +// } +// +// @Test +// public void testGenerateDifferences_WithDifferentType() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// // Create a mock different type +// SessionReplayViewThingyInterface otherType = new SessionReplayViewThingyInterface() { +// @Override +// public ViewDetails getViewDetails() { +// return viewDetails; +// } +// +// @Override +// public boolean shouldRecordSubviews() { +// return false; +// } +// +// @Override +// public List getSubviews() { +// return null; +// } +// +// @Override +// public void setSubviews(List subviews) { +// } +// +// @Override +// public String getCssSelector() { +// return null; +// } +// +// @Override +// public String generateCssDescription() { +// return null; +// } +// +// @Override +// public String generateInlineCss() { +// return null; +// } +// +// @Override +// public RRWebElementNode generateRRWebNode() { +// return null; +// } +// +// @Override +// public List generateDifferences(SessionReplayViewThingyInterface other) { +// return null; +// } +// +// @Override +// public List generateAdditionNodes(int parentId) { +// return null; +// } +// +// @Override +// public int getViewId() { +// return 0; +// } +// +// @Override +// public int getParentViewId() { +// return 0; +// } +// +// @Override +// public boolean hasChanged(SessionReplayViewThingyInterface other) { +// return false; +// } +// }; +// +// List diffs = thingy.generateDifferences(otherType); +// +// Assert.assertNotNull(diffs); +// Assert.assertTrue(diffs.isEmpty()); +// } +// +// // ==================== ADD RECORD GENERATION TESTS ==================== +// +// @Test +// public void testGenerateAdditionNodes() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// int parentId = 123; +// List addRecords = thingy.generateAdditionNodes(parentId); +// +// Assert.assertNotNull(addRecords); +// Assert.assertEquals(1, addRecords.size()); +// +// RRWebMutationData.AddRecord addRecord = addRecords.get(0); +// Assert.assertEquals(parentId, addRecord.getParentId()); +// Assert.assertNotNull(addRecord.getNode()); +// Assert.assertTrue(addRecord.getNode() instanceof RRWebElementNode); +// } +// +// @Test +// public void testGenerateAdditionNodesIncludesInlineCss() { +// View view = new View(context); +// view.setBackgroundColor(Color.GREEN); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// List addRecords = thingy.generateAdditionNodes(456); +// +// RRWebElementNode node = (RRWebElementNode) addRecords.get(0).getNode(); +// Assert.assertNotNull(node.attributes.metadata); +// Assert.assertTrue(node.attributes.metadata.containsKey("style")); +// +// String style = node.attributes.metadata.get("style"); +// Assert.assertNotNull(style); +// Assert.assertTrue(style.length() > 0); +// } +// +// @Test +// public void testGenerateAdditionNodesWithZeroParentId() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// List addRecords = thingy.generateAdditionNodes(0); +// +// Assert.assertNotNull(addRecords); +// Assert.assertEquals(1, addRecords.size()); +// Assert.assertEquals(0, addRecords.get(0).getParentId()); +// } +// +// @Test +// public void testGenerateAdditionNodesWithNegativeParentId() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// List addRecords = thingy.generateAdditionNodes(-1); +// +// Assert.assertNotNull(addRecords); +// Assert.assertEquals(1, addRecords.size()); +// Assert.assertEquals(-1, addRecords.get(0).getParentId()); +// } +// +// // ==================== CHANGE DETECTION TESTS ==================== +// +// @Test +// public void testHasChanged_WithNull() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// Assert.assertTrue(thingy.hasChanged(null)); +// } +// +// @Test +// public void testHasChanged_WithDifferentType() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// SessionReplayViewThingyInterface otherType = new SessionReplayViewThingyInterface() { +// @Override +// public ViewDetails getViewDetails() { +// return viewDetails; +// } +// +// @Override +// public boolean shouldRecordSubviews() { +// return false; +// } +// +// @Override +// public List getSubviews() { +// return null; +// } +// +// @Override +// public void setSubviews(List subviews) { +// } +// +// @Override +// public String getCssSelector() { +// return null; +// } +// +// @Override +// public String generateCssDescription() { +// return null; +// } +// +// @Override +// public String generateInlineCss() { +// return null; +// } +// +// @Override +// public RRWebElementNode generateRRWebNode() { +// return null; +// } +// +// @Override +// public List generateDifferences(SessionReplayViewThingyInterface other) { +// return null; +// } +// +// @Override +// public List generateAdditionNodes(int parentId) { +// return null; +// } +// +// @Override +// public int getViewId() { +// return 0; +// } +// +// @Override +// public int getParentViewId() { +// return 0; +// } +// +// @Override +// public boolean hasChanged(SessionReplayViewThingyInterface other) { +// return false; +// } +// }; +// +// Assert.assertTrue(thingy.hasChanged(otherType)); +// } +// +// @Test +// public void testHasChanged_WithSameInstance() { +// View view = new View(context); +// ViewDetails viewDetails = new ViewDetails(view); +// +// SessionReplayViewThingy thingy = new SessionReplayViewThingy(viewDetails); +// +// Assert.assertFalse(thingy.hasChanged(thingy)); +// } +// +// @Test +// public void testHasChanged_WithDifferentViews() { +// View view1 = new View(context); +// view1.setBackgroundColor(Color.RED); +// ViewDetails viewDetails1 = new ViewDetails(view1); +// +// View view2 = new View(context); +// view2.setBackgroundColor(Color.BLUE); +// ViewDetails viewDetails2 = new ViewDetails(view2); +// +// SessionReplayViewThingy thingy1 = new SessionReplayViewThingy(viewDetails1); +// SessionReplayViewThingy thingy2 = new SessionReplayViewThingy(viewDetails2); +// +// Assert.assertTrue(thingy1.hasChanged(thingy2)); +// } +// +// // ==================== VIEWGROUP HIERARCHY TESTS ==================== +// +// @Test +// public void testViewThingyWithNestedChildren() { +// LinearLayout parent = new LinearLayout(context); +// View child1 = new View(context); +// View child2 = new View(context); +// +// parent.addView(child1); +// parent.addView(child2); +// +// ViewDetails parentDetails = new ViewDetails(parent); +// ViewDetails child1Details = new ViewDetails(child1); +// ViewDetails child2Details = new ViewDetails(child2); +// +// SessionReplayViewThingy parentThingy = new SessionReplayViewThingy(parentDetails); +// SessionReplayViewThingy child1Thingy = new SessionReplayViewThingy(child1Details); +// SessionReplayViewThingy child2Thingy = new SessionReplayViewThingy(child2Details); +// +// parentThingy.setSubviews(Arrays.asList(child1Thingy, child2Thingy)); +// +// Assert.assertEquals(2, parentThingy.getSubviews().size()); +// } +// +// @Test +// public void testViewThingyWithDeeplyNestedChildren() { +// FrameLayout level1 = new FrameLayout(context); +// FrameLayout level2 = new FrameLayout(context); +// View level3 = new View(context); +// +// level1.addView(level2); +// level2.addView(level3); +// +// ViewDetails details1 = new ViewDetails(level1); +// ViewDetails details2 = new ViewDetails(level2); +// ViewDetails details3 = new ViewDetails(level3); +// +// SessionReplayViewThingy thingy1 = new SessionReplayViewThingy(details1); +// SessionReplayViewThingy thingy2 = new SessionReplayViewThingy(details2); +// SessionReplayViewThingy thingy3 = new SessionReplayViewThingy(details3); +// +// thingy2.setSubviews(Arrays.asList(thingy3)); +// thingy1.setSubviews(Arrays.asList(thingy2)); +// +// Assert.assertEquals(1, thingy1.getSubviews().size()); +// Assert.assertEquals(1, thingy2.getSubviews().size()); +// Assert.assertEquals(0, thingy3.getSubviews().size()); +// } +//} \ No newline at end of file diff --git a/agent/src/test/java/com/newrelic/agent/android/sessionReplay/TouchTrackerTest.java b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/TouchTrackerTest.java new file mode 100644 index 00000000..f17b915a --- /dev/null +++ b/agent/src/test/java/com/newrelic/agent/android/sessionReplay/TouchTrackerTest.java @@ -0,0 +1,453 @@ +///* +// * Copyright (c) 2024. New Relic Corporation. All rights reserved. +// * SPDX-License-Identifier: Apache-2.0 +// */ +// +//package com.newrelic.agent.android.sessionReplay; +// +//import com.newrelic.agent.android.sessionReplay.models.RRWebRRWebTouchUpDownData; +//import com.newrelic.agent.android.sessionReplay.models.RRWebTouch; +//import com.newrelic.agent.android.sessionReplay.models.RRWebTouchMoveData; +//import com.newrelic.agent.android.sessionReplay.models.RecordedTouchData; +// +//import org.junit.Assert; +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.robolectric.RobolectricTestRunner; +// +//import java.util.ArrayList; +// +//@RunWith(RobolectricTestRunner.class) +//public class TouchTrackerTest { +// +// // ==================== CONSTRUCTOR TESTS ==================== +// +// @Test +// public void testConstructor_WithStartTouch() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// +// TouchTracker tracker = new TouchTracker(startTouch); +// +// Assert.assertNotNull(tracker); +// } +// +// @Test +// public void testConstructor_WithNullStartTouch() { +// try { +// TouchTracker tracker = new TouchTracker(null); +// // May or may not throw depending on implementation +// } catch (NullPointerException e) { +// // Expected if null is not allowed +// } +// } +// +// // ==================== ADD MOVE TOUCH TESTS ==================== +// +// @Test +// public void testAddMoveTouch_SingleMove() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData moveTouch = new RecordedTouchData(2, 100, 60.0f, 85.0f, 1100L); +// tracker.addMoveTouch(moveTouch); +// +// // No exception should be thrown +// Assert.assertNotNull(tracker); +// } +// +// @Test +// public void testAddMoveTouch_MultipleMoves() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// for (int i = 1; i <= 10; i++) { +// RecordedTouchData moveTouch = new RecordedTouchData(2, 100, 50.0f + i, 75.0f + i, 1000L + i * 100); +// tracker.addMoveTouch(moveTouch); +// } +// +// Assert.assertNotNull(tracker); +// } +// +// @Test +// public void testAddMoveTouch_NoMoves() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// // Don't add any move touches +// Assert.assertNotNull(tracker); +// } +// +// // ==================== ADD END TOUCH TESTS ==================== +// +// @Test +// public void testAddEndTouch() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData endTouch = new RecordedTouchData(1, 100, 60.0f, 85.0f, 1200L); +// tracker.addEndTouch(endTouch); +// +// Assert.assertNotNull(tracker); +// } +// +// @Test +// public void testAddEndTouch_Overwrite() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData endTouch1 = new RecordedTouchData(1, 100, 60.0f, 85.0f, 1200L); +// tracker.addEndTouch(endTouch1); +// +// RecordedTouchData endTouch2 = new RecordedTouchData(1, 100, 70.0f, 95.0f, 1300L); +// tracker.addEndTouch(endTouch2); +// +// // Second end touch should overwrite the first +// Assert.assertNotNull(tracker); +// } +// +// // ==================== PROCESS TOUCH DATA TESTS ==================== +// +// @Test +// public void testProcessTouchData_SimpleDownUp() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData endTouch = new RecordedTouchData(1, 100, 50.0f, 75.0f, 1200L); +// tracker.addEndTouch(endTouch); +// +// ArrayList touches = tracker.processTouchData(); +// +// Assert.assertNotNull(touches); +// Assert.assertEquals(2, touches.size()); // Down + Up +// +// // Check down touch +// RRWebTouch downTouchEvent = touches.get(0); +// Assert.assertEquals(1000L, downTouchEvent.getTimestamp()); +// Assert.assertEquals(3, downTouchEvent.getType()); +// Assert.assertTrue(downTouchEvent.getData() instanceof RRWebRRWebTouchUpDownData); +// +// RRWebRRWebTouchUpDownData downData = (RRWebRRWebTouchUpDownData) downTouchEvent.getData(); +// Assert.assertEquals(2, downData.getSource()); +// Assert.assertEquals(7, downData.getTouchType()); // TouchDown +// Assert.assertEquals(100, downData.getId()); +// Assert.assertEquals(50.0f, downData.getX(), 0.01f); +// Assert.assertEquals(75.0f, downData.getY(), 0.01f); +// +// // Check up touch +// RRWebTouch upTouchEvent = touches.get(1); +// Assert.assertEquals(1200L, upTouchEvent.getTimestamp()); +// Assert.assertEquals(3, upTouchEvent.getType()); +// Assert.assertTrue(upTouchEvent.getData() instanceof RRWebRRWebTouchUpDownData); +// +// RRWebRRWebTouchUpDownData upData = (RRWebRRWebTouchUpDownData) upTouchEvent.getData(); +// Assert.assertEquals(2, upData.getSource()); +// Assert.assertEquals(9, upData.getTouchType()); // TouchUp +// Assert.assertEquals(100, upData.getId()); +// Assert.assertEquals(50.0f, upData.getX(), 0.01f); +// Assert.assertEquals(75.0f, upData.getY(), 0.01f); +// } +// +// @Test +// public void testProcessTouchData_WithSingleMove() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData moveTouch = new RecordedTouchData(2, 100, 60.0f, 85.0f, 1100L); +// tracker.addMoveTouch(moveTouch); +// +// RecordedTouchData endTouch = new RecordedTouchData(1, 100, 70.0f, 95.0f, 1200L); +// tracker.addEndTouch(endTouch); +// +// ArrayList touches = tracker.processTouchData(); +// +// Assert.assertNotNull(touches); +// Assert.assertEquals(3, touches.size()); // Down + Move + Up +// +// // Check move touch +// RRWebTouch moveTouchEvent = touches.get(1); +// Assert.assertEquals(1100L, moveTouchEvent.getTimestamp()); +// Assert.assertTrue(moveTouchEvent.getData() instanceof RRWebTouchMoveData); +// +// RRWebTouchMoveData moveData = (RRWebTouchMoveData) moveTouchEvent.getData(); +// Assert.assertEquals(1, moveData.getSource()); +// Assert.assertNotNull(moveData.getPositions()); +// Assert.assertEquals(1, moveData.getPositions().size()); +// +// RRWebTouchMoveData.Position position = moveData.getPositions().get(0); +// Assert.assertEquals(100, position.getId()); +// Assert.assertEquals(60.0f, position.getX(), 0.01f); +// Assert.assertEquals(85.0f, position.getY(), 0.01f); +// } +// +// @Test +// public void testProcessTouchData_WithMultipleMoves() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData move1 = new RecordedTouchData(2, 100, 55.0f, 80.0f, 1050L); +// RecordedTouchData move2 = new RecordedTouchData(2, 100, 60.0f, 85.0f, 1100L); +// RecordedTouchData move3 = new RecordedTouchData(2, 100, 65.0f, 90.0f, 1150L); +// +// tracker.addMoveTouch(move1); +// tracker.addMoveTouch(move2); +// tracker.addMoveTouch(move3); +// +// RecordedTouchData endTouch = new RecordedTouchData(1, 100, 70.0f, 95.0f, 1200L); +// tracker.addEndTouch(endTouch); +// +// ArrayList touches = tracker.processTouchData(); +// +// Assert.assertNotNull(touches); +// Assert.assertEquals(3, touches.size()); // Down + Move + Up +// +// // Check move touch has all positions +// RRWebTouch moveTouchEvent = touches.get(1); +// RRWebTouchMoveData moveData = (RRWebTouchMoveData) moveTouchEvent.getData(); +// Assert.assertEquals(3, moveData.getPositions().size()); +// +// // Verify positions +// Assert.assertEquals(55.0f, moveData.getPositions().get(0).getX(), 0.01f); +// Assert.assertEquals(60.0f, moveData.getPositions().get(1).getX(), 0.01f); +// Assert.assertEquals(65.0f, moveData.getPositions().get(2).getX(), 0.01f); +// } +// +// @Test +// public void testProcessTouchData_TimestampCalculationInMoves() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData move1 = new RecordedTouchData(2, 100, 55.0f, 80.0f, 1050L); +// RecordedTouchData move2 = new RecordedTouchData(2, 100, 60.0f, 85.0f, 1100L); +// +// tracker.addMoveTouch(move1); +// tracker.addMoveTouch(move2); +// +// RecordedTouchData endTouch = new RecordedTouchData(1, 100, 70.0f, 95.0f, 1200L); +// tracker.addEndTouch(endTouch); +// +// ArrayList touches = tracker.processTouchData(); +// +// RRWebTouch moveTouchEvent = touches.get(1); +// RRWebTouchMoveData moveData = (RRWebTouchMoveData) moveTouchEvent.getData(); +// +// // Timestamp should be the last move's timestamp +// Assert.assertEquals(1100L, moveTouchEvent.getTimestamp()); +// +// // Time offsets should be relative to last timestamp +// long expectedOffset1 = 1050L - 1100L; // -50 +// long expectedOffset2 = 1100L - 1100L; // 0 +// +// Assert.assertEquals(expectedOffset1, moveData.getPositions().get(0).getTimeOffset()); +// Assert.assertEquals(expectedOffset2, moveData.getPositions().get(1).getTimeOffset()); +// } +// +// // ==================== TOUCH DATA FORMAT TESTS ==================== +// +// @Test +// public void testProcessTouchData_TouchDownType() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData endTouch = new RecordedTouchData(1, 100, 50.0f, 75.0f, 1200L); +// tracker.addEndTouch(endTouch); +// +// ArrayList touches = tracker.processTouchData(); +// +// RRWebRRWebTouchUpDownData downData = (RRWebRRWebTouchUpDownData) touches.get(0).getData(); +// Assert.assertEquals(7, downData.getTouchType()); // TouchDown type +// } +// +// @Test +// public void testProcessTouchData_TouchUpType() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData endTouch = new RecordedTouchData(1, 100, 50.0f, 75.0f, 1200L); +// tracker.addEndTouch(endTouch); +// +// ArrayList touches = tracker.processTouchData(); +// +// RRWebRRWebTouchUpDownData upData = (RRWebRRWebTouchUpDownData) touches.get(1).getData(); +// Assert.assertEquals(9, upData.getTouchType()); // TouchUp type +// } +// +// @Test +// public void testProcessTouchData_SourceValues() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData moveTouch = new RecordedTouchData(2, 100, 60.0f, 85.0f, 1100L); +// tracker.addMoveTouch(moveTouch); +// +// RecordedTouchData endTouch = new RecordedTouchData(1, 100, 70.0f, 95.0f, 1200L); +// tracker.addEndTouch(endTouch); +// +// ArrayList touches = tracker.processTouchData(); +// +// // Down and Up should have source = 2 +// RRWebRRWebTouchUpDownData downData = (RRWebRRWebTouchUpDownData) touches.get(0).getData(); +// Assert.assertEquals(2, downData.getSource()); +// +// RRWebRRWebTouchUpDownData upData = (RRWebRRWebTouchUpDownData) touches.get(2).getData(); +// Assert.assertEquals(2, upData.getSource()); +// +// // Move should have source = 1 +// RRWebTouchMoveData moveData = (RRWebTouchMoveData) touches.get(1).getData(); +// Assert.assertEquals(1, moveData.getSource()); +// } +// +// @Test +// public void testProcessTouchData_EventType() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData endTouch = new RecordedTouchData(1, 100, 50.0f, 75.0f, 1200L); +// tracker.addEndTouch(endTouch); +// +// ArrayList touches = tracker.processTouchData(); +// +// // All events should have type = 3 +// for (RRWebTouch touch : touches) { +// Assert.assertEquals(3, touch.getType()); +// } +// } +// +// // ==================== COORDINATE TESTS ==================== +// +// @Test +// public void testProcessTouchData_PreservesCoordinates() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 123.45f, 678.90f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData endTouch = new RecordedTouchData(1, 100, 234.56f, 789.01f, 1200L); +// tracker.addEndTouch(endTouch); +// +// ArrayList touches = tracker.processTouchData(); +// +// RRWebRRWebTouchUpDownData downData = (RRWebRRWebTouchUpDownData) touches.get(0).getData(); +// Assert.assertEquals(123.45f, downData.getX(), 0.01f); +// Assert.assertEquals(678.90f, downData.getY(), 0.01f); +// +// RRWebRRWebTouchUpDownData upData = (RRWebRRWebTouchUpDownData) touches.get(1).getData(); +// Assert.assertEquals(234.56f, upData.getX(), 0.01f); +// Assert.assertEquals(789.01f, upData.getY(), 0.01f); +// } +// +// @Test +// public void testProcessTouchData_NegativeCoordinates() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, -10.0f, -20.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData endTouch = new RecordedTouchData(1, 100, -5.0f, -15.0f, 1200L); +// tracker.addEndTouch(endTouch); +// +// ArrayList touches = tracker.processTouchData(); +// +// RRWebRRWebTouchUpDownData downData = (RRWebRRWebTouchUpDownData) touches.get(0).getData(); +// Assert.assertEquals(-10.0f, downData.getX(), 0.01f); +// Assert.assertEquals(-20.0f, downData.getY(), 0.01f); +// } +// +// @Test +// public void testProcessTouchData_ZeroCoordinates() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 0.0f, 0.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData endTouch = new RecordedTouchData(1, 100, 0.0f, 0.0f, 1200L); +// tracker.addEndTouch(endTouch); +// +// ArrayList touches = tracker.processTouchData(); +// +// RRWebRRWebTouchUpDownData downData = (RRWebRRWebTouchUpDownData) touches.get(0).getData(); +// Assert.assertEquals(0.0f, downData.getX(), 0.01f); +// Assert.assertEquals(0.0f, downData.getY(), 0.01f); +// } +// +// // ==================== VIEW ID TESTS ==================== +// +// @Test +// public void testProcessTouchData_PreservesViewId() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 12345, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData endTouch = new RecordedTouchData(1, 12345, 50.0f, 75.0f, 1200L); +// tracker.addEndTouch(endTouch); +// +// ArrayList touches = tracker.processTouchData(); +// +// RRWebRRWebTouchUpDownData downData = (RRWebRRWebTouchUpDownData) touches.get(0).getData(); +// Assert.assertEquals(12345, downData.getId()); +// +// RRWebRRWebTouchUpDownData upData = (RRWebRRWebTouchUpDownData) touches.get(1).getData(); +// Assert.assertEquals(12345, upData.getId()); +// } +// +// @Test +// public void testProcessTouchData_DifferentViewIdsDuringGesture() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// // Move to different view +// RecordedTouchData moveTouch = new RecordedTouchData(2, 200, 60.0f, 85.0f, 1100L); +// tracker.addMoveTouch(moveTouch); +// +// // End on yet another view +// RecordedTouchData endTouch = new RecordedTouchData(1, 300, 70.0f, 95.0f, 1200L); +// tracker.addEndTouch(endTouch); +// +// ArrayList touches = tracker.processTouchData(); +// +// RRWebRRWebTouchUpDownData downData = (RRWebRRWebTouchUpDownData) touches.get(0).getData(); +// Assert.assertEquals(100, downData.getId()); +// +// RRWebTouchMoveData moveData = (RRWebTouchMoveData) touches.get(1).getData(); +// Assert.assertEquals(200, moveData.getPositions().get(0).getId()); +// +// RRWebRRWebTouchUpDownData upData = (RRWebRRWebTouchUpDownData) touches.get(2).getData(); +// Assert.assertEquals(300, upData.getId()); +// } +// +// // ==================== EDGE CASE TESTS ==================== +// +// @Test +// public void testProcessTouchData_VeryLongGesture() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// // Add 100 move touches +// for (int i = 1; i <= 100; i++) { +// RecordedTouchData moveTouch = new RecordedTouchData(2, 100, 50.0f + i, 75.0f + i, 1000L + i * 10); +// tracker.addMoveTouch(moveTouch); +// } +// +// RecordedTouchData endTouch = new RecordedTouchData(1, 100, 200.0f, 200.0f, 3000L); +// tracker.addEndTouch(endTouch); +// +// ArrayList touches = tracker.processTouchData(); +// +// Assert.assertEquals(3, touches.size()); // Down + Move + Up +// +// RRWebTouchMoveData moveData = (RRWebTouchMoveData) touches.get(1).getData(); +// Assert.assertEquals(100, moveData.getPositions().size()); +// } +// +// @Test +// public void testProcessTouchData_RapidTimestamps() { +// RecordedTouchData startTouch = new RecordedTouchData(0, 100, 50.0f, 75.0f, 1000L); +// TouchTracker tracker = new TouchTracker(startTouch); +// +// RecordedTouchData move1 = new RecordedTouchData(2, 100, 51.0f, 76.0f, 1001L); +// RecordedTouchData move2 = new RecordedTouchData(2, 100, 52.0f, 77.0f, 1002L); +// +// tracker.addMoveTouch(move1); +// tracker.addMoveTouch(move2); +// +// RecordedTouchData endTouch = new RecordedTouchData(1, 100, 53.0f, 78.0f, 1003L); +// tracker.addEndTouch(endTouch); +// +// ArrayList touches = tracker.processTouchData(); +// +// Assert.assertNotNull(touches); +// Assert.assertEquals(3, touches.size()); +// } +//} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 1535d1f5..de491e8d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ android.useAndroidX=true android.disableAutomaticComponentCreation=true # Version of the SDK to be built and deployed -newrelic.agent.version = 7.7.0 +newrelic.agent.version = 7.7.1 newrelic.agent.build = SNAPSHOT newrelic.agent.snapshot= diff --git a/plugins/gradle/src/main/groovy/com/newrelic/agent/android/VariantAdapter.groovy b/plugins/gradle/src/main/groovy/com/newrelic/agent/android/VariantAdapter.groovy index bb922cba..fabaa654 100644 --- a/plugins/gradle/src/main/groovy/com/newrelic/agent/android/VariantAdapter.groovy +++ b/plugins/gradle/src/main/groovy/com/newrelic/agent/android/VariantAdapter.groovy @@ -68,7 +68,7 @@ abstract class VariantAdapter { return new AGP4Adapter(buildHelper) } - if (currentAgpVersion < GradleVersion.version("9.0")) { + if (currentAgpVersion < GradleVersion.version("9.0") && currentGradleVersion < GradleVersion.version("9.0")) { return new AGP74Adapter(buildHelper) } diff --git a/samples/agent-test-app/gradle.properties b/samples/agent-test-app/gradle.properties index 234a991c..8d4e6615 100644 --- a/samples/agent-test-app/gradle.properties +++ b/samples/agent-test-app/gradle.properties @@ -9,7 +9,7 @@ org.gradle.unsafe.configuration-cache-problems=WARN org.gradle.caching=true # Component specs -newrelic.agent.version=7.6.17-SNAPSHOT +newrelic.agent.version=7.7.1 newrelic.agp.version=7.4.2 newrelic.kotlin.version=1.9.20 newrelic.gradle.version=8.11.1