Compare two Mob.Test.view_tree/1 snapshots and report the first divergence.
Mob's product claim is one design, both platforms. A comparator over the semantic trees each platform returns is what makes that claim testable — the first thing that differs is the framework failing its own promise.
This module is deliberately pure: it takes two normalised view-trees and
answers :ok or {:divergence, %{...}}. Rendering the same fixture on both
platforms and driving the round trip belongs to mob_dev, not here — the
comparator is easy to test without a device, and hard to test with one.
The rules
Each rule guards a class of platform bug and skips the classes that would produce false alarms until the framework can report them faithfully.
Structure. Different
typeat a matched position, or a different number of children at any level, is always a divergence. This is the class MOB-147 B-1 falls into — one platform renders a node type the other does not.Label / value. A
textprop that only reaches the tree on one side (a rendering path that drops it), orvalueon an input that differs, is a divergence.Frame. Compared only when both sides carry a frame. Android's
MobBridge.uiViewTree()reports a frame only for nodes withprops["id"](only those are tracked byModifier.onGloballyPositioned), so any node without an id hasframe: nilon Android and a real frame on iOS. Reporting that as a divergence would flag every non-id'd node in every fixture — the fixture author chooses which nodes need geometry compared by giving them ids. Numbers are compared with a tolerance, default 1.0 dp; small differences are ordinary rounding across the platform layout engines.Root frame is not compared. The synthetic root's frame is the screen, and comparing screen sizes across an iPad and a Motorola G says nothing about the framework.
Class, bg_color, text_color are not compared yet.
bg_colorandtext_colorarenullon Android today (paint resolves through the theme after the tree is built), and comparing them would treat every colour as a divergence. When Android surfaces those fields the rules can be added without a shape change here.
Report only the first divergence walked in child order. Depth-first, so a parent's own divergence is reported before its children's; among children, the leftmost wins. That keeps the reader looking at cause rather than cascade.
Summary
Types
Path to a divergent node, as a list of child indices from the root.
[] is the root itself; [0, 2] is the third child of the first child.
Functions
Compare two normalised view trees.
One-line human summary of a divergence, for a log line or a defect report.
Types
@type path() :: [non_neg_integer()]
Path to a divergent node, as a list of child indices from the root.
[] is the root itself; [0, 2] is the third child of the first child.
@type result() :: :ok | {:divergence, divergence()}
Functions
Compare two normalised view trees.
Options:
:frame_tolerance_dp— max absolute difference per frame coordinate before geometry is reported as divergent. Default1.0.
Either side may be the atom :no_window, nil or {:error, reason} — the
shapes Mob.Test.view_tree/1 can return when a device is not ready. The
comparator refuses to report divergence in that case ({:error, :not_ready}),
because "one side did not answer" is not a bug in the tree, it is a bug in
the harness.
@spec describe(divergence()) :: String.t()
One-line human summary of a divergence, for a log line or a defect report.