fix(feed): resolve home feed back-button scroll-to-top trap
- Separated obstacle detection from feed marker validation - Prevented blind BACK button presses when markers are missing mid-scroll - Added TDD verification for feed navigation stability - Cleaned up debug artifacts and temporary test output
This commit is contained in:
@@ -14,115 +14,30 @@ class MockConfigs:
|
||||
def __init__(self, args):
|
||||
self.args = args
|
||||
|
||||
class MockDeviceV2:
|
||||
def __init__(self):
|
||||
self.clicks = []
|
||||
self.shells = []
|
||||
self.double_clicks = []
|
||||
self.presses = []
|
||||
self.xml_dump = "<hierarchy><node resource-id='com.instagram.android:id/row_comment_imageview' bounds='[10,10][20,20]' content-desc='Story' text='following' /><node resource-id='com.instagram.android:id/button_like' bounds='[50,50][60,60]' /><node resource-id='com.instagram.android:id/reel_viewer' /></hierarchy>"
|
||||
|
||||
def click(self, x, y):
|
||||
self.clicks.append((x, y))
|
||||
self.xml_dump += f"<node new='true' x='{x}' y='{y}' />"
|
||||
|
||||
def shell(self, cmd):
|
||||
self.shells.append(cmd)
|
||||
|
||||
def double_click(self, x, y, duration=0):
|
||||
self.double_clicks.append((x, y))
|
||||
|
||||
def press(self, key):
|
||||
self.presses.append(key)
|
||||
|
||||
def dump_hierarchy(self):
|
||||
return self.xml_dump
|
||||
from unittest.mock import create_autospec, MagicMock
|
||||
from GramAddict.core.device_facade import DeviceFacade
|
||||
from GramAddict.core.telepathic_engine import TelepathicEngine
|
||||
|
||||
def create_mock_device():
|
||||
mock = create_autospec(DeviceFacade, instance=True)
|
||||
mock.app_id = "com.instagram.android"
|
||||
mock.device_id = "test_device"
|
||||
|
||||
mock.info = {"displayWidth": 1080, "displayHeight": 2400}
|
||||
mock.get_info.return_value = {"displayWidth": 1080, "displayHeight": 2400}
|
||||
mock.cm_to_pixels.side_effect = lambda cm: int(cm * 10)
|
||||
import uuid
|
||||
mock.dump_hierarchy.side_effect = lambda: f"<hierarchy><node resource-id=\"com.instagram.android:id/row_comment_imageview\" bounds=\"[10,10][20,20]\" content-desc=\"Story\" text=\"following\" /><node resource-id=\"com.instagram.android:id/button_like\" bounds=\"[50,50][60,60]\" /><node resource-id=\"com.instagram.android:id/reel_viewer\" /><node sid=\"{uuid.uuid4()}\" /></hierarchy>"
|
||||
|
||||
return mock
|
||||
|
||||
class MockDevice:
|
||||
def __init__(self):
|
||||
self.deviceV2 = MockDeviceV2()
|
||||
self.app_id = "com.instagram.android"
|
||||
|
||||
def _get_current_app(self):
|
||||
return "com.instagram.android"
|
||||
|
||||
def get_info(self):
|
||||
return {"displayWidth": 1080, "displayHeight": 2400}
|
||||
|
||||
def cm_to_pixels(self, cm):
|
||||
return cm * 10
|
||||
|
||||
def dump_hierarchy(self):
|
||||
return self.deviceV2.dump_hierarchy()
|
||||
|
||||
def click(self, x=None, y=None, obj=None):
|
||||
if obj:
|
||||
x, y = obj.get("x", 0), obj.get("y", 0)
|
||||
self.deviceV2.click(x, y)
|
||||
|
||||
class MockTelepathicEngine:
|
||||
def find_best_node(self, xml, intent_description, device=None, **kwargs):
|
||||
description = intent_description.lower()
|
||||
if "story ring" in description:
|
||||
return {"x": 100, "y": 100, "description": "Story Ring", "score": 1.0}
|
||||
if "follow" in description or "folgen" in description:
|
||||
return {"x": 200, "y": 200, "text": "Follow", "description": "Follow Button", "score": 1.0}
|
||||
if "first image" in description or "profile grid" in description:
|
||||
return {"x": 300, "y": 300, "description": "Grid Image", "score": 1.0}
|
||||
return None
|
||||
|
||||
def _extract_semantic_nodes(self, xml, intent=None, threshold=0.0):
|
||||
return [{"x": 10, "y": 10, "semantic_string": "mock node", "area": 100}]
|
||||
|
||||
def _keyword_match_score(self, intent, nodes):
|
||||
if nodes:
|
||||
return {"semantic": nodes[0].get("semantic_string"), "score": 0.9, "node": nodes[0]}
|
||||
return None
|
||||
|
||||
def _cosine_similarity(self, v1, v2):
|
||||
return 0.9
|
||||
|
||||
def verify_success(self, intent_description, post_click_xml, previous_state_xml=None):
|
||||
return True
|
||||
|
||||
def confirm_click(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def reject_click(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def classify_screen_content(self, xml, target_class):
|
||||
# Default mock behavior: assume it matches if it's not obviously trash
|
||||
return "organic"
|
||||
|
||||
def get_active_engagement(self):
|
||||
return {"type": "like", "confidence": 0.8}
|
||||
|
||||
def audit_stack_integrity(self):
|
||||
return True
|
||||
|
||||
def visual_vibe_check(self, images_b64):
|
||||
return True, "High quality aesthetic"
|
||||
|
||||
def evaluate_profile_vibe(self, device, persona_interests: list[str]):
|
||||
return {"quality_score": 8, "matches_niche": True, "reason": "Mocked positive vibe"}
|
||||
|
||||
def evaluate_grid_visuals(self, device, grid_nodes):
|
||||
return [0.9] * len(grid_nodes)
|
||||
|
||||
def _load_json(self, path):
|
||||
return {}
|
||||
|
||||
def _save_json(self, path, data):
|
||||
pass
|
||||
|
||||
def _vision_cortex_fallback(self, xml, intent):
|
||||
return {"x": 500, "y": 500, "confidence": 0.7}
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls):
|
||||
return cls()
|
||||
def create_mock_telepathic_engine():
|
||||
mock = create_autospec(TelepathicEngine, instance=True)
|
||||
mock.find_best_node.return_value = {"x": 500, "y": 500, "confidence": 0.9}
|
||||
mock.evaluate_profile_vibe.return_value = {"quality_score": 8, "matches_niche": True, "reason": "Mocked positive vibe"}
|
||||
mock.evaluate_grid_visuals.return_value = [0.9] * 6
|
||||
mock._extract_semantic_nodes.return_value = [{"x": 500, "y": 500, "semantic_string": "dummy node"}]
|
||||
return mock
|
||||
|
||||
@pytest.fixture
|
||||
def mock_logger():
|
||||
@@ -130,7 +45,7 @@ def mock_logger():
|
||||
|
||||
@pytest.fixture
|
||||
def device():
|
||||
return MockDevice()
|
||||
return create_mock_device()
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_singletons():
|
||||
@@ -155,7 +70,7 @@ def reset_singletons():
|
||||
@pytest.fixture(autouse=True)
|
||||
def telepathic_mock(monkeypatch):
|
||||
import GramAddict.core.telepathic_engine
|
||||
engine = MockTelepathicEngine()
|
||||
engine = create_mock_telepathic_engine()
|
||||
monkeypatch.setattr(GramAddict.core.telepathic_engine.TelepathicEngine, "get_instance", lambda: engine)
|
||||
return engine
|
||||
|
||||
@@ -172,7 +87,7 @@ def mock_cognitive_stack():
|
||||
"nav_graph": MagicMock(),
|
||||
"zero_engine": MagicMock(),
|
||||
"crm": MagicMock(),
|
||||
"telepathic": MockTelepathicEngine()
|
||||
"telepathic": create_mock_telepathic_engine()
|
||||
}
|
||||
stack["radome"].sanitize_xml.side_effect = lambda x: x
|
||||
return stack
|
||||
|
||||
Reference in New Issue
Block a user