aboutsummaryrefslogtreecommitdiff
path: root/app/src/test/java/invalid/lena/scrcpy/TouchMapTest.java
blob: becacc1b46d081c2b8ae91b59a3fbd83085f6b88 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package invalid.lena.scrcpy;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

// The arithmetic that decides where a tap lands. Until this existed the
// mapping was verified by nothing: the only coverage was an emulator
// self-mirror, where the target and the view are the same size, so the
// transform is the identity and every scaling or offset bug passes.
public class TouchMapTest {

    // A 1080x2400 phone showing a 1080x1920 target: fitted to 864x1920,
    // leaving 108px bars left and right.
    private static final int VIEW_X = 108, VIEW_W = 864, TARGET_W = 1080;

    @Test
    public void leftEdgeOfTheVideoIsTheLeftEdgeOfTheTarget() {
        assertEquals(0, TouchMap.map(VIEW_X, VIEW_X, VIEW_W, TARGET_W));
    }

    // The view has fewer pixels than the target, so the last column the
    // mapping can produce is floor((span-1) * target / span) - 1078 here,
    // not 1079. That is granularity, not an off-by-one: a 864px-wide view
    // cannot address all 1080 target columns. What matters is that the
    // right edge lands on the last addressable column and stays inside.
    @Test
    public void rightEdgeIsTheLastAddressableColumn() {
        int got = TouchMap.map(VIEW_X + VIEW_W - 1, VIEW_X, VIEW_W, TARGET_W);
        assertEquals((VIEW_W - 1) * TARGET_W / VIEW_W, got);
        assertTrue("must stay inside the target", got < TARGET_W);
        assertTrue("must be within one step of the far edge",
                got >= TARGET_W - (TARGET_W / VIEW_W) - 1);
    }

    @Test
    public void centreMapsToCentre() {
        assertEquals(TARGET_W / 2, TouchMap.map(VIEW_X + VIEW_W / 2, VIEW_X, VIEW_W, TARGET_W));
    }

    @Test
    public void theLetterboxBarIsClampedNotWrapped() {
        // Touches on the bars must land on the near edge. Subtracting the
        // origin without clamping would make the left bar negative and the
        // right bar overflow past the target's width.
        assertEquals(0, TouchMap.map(0, VIEW_X, VIEW_W, TARGET_W));
        assertEquals(0, TouchMap.map(VIEW_X - 50, VIEW_X, VIEW_W, TARGET_W));
        int far = (VIEW_W - 1) * TARGET_W / VIEW_W;   // last addressable column
        assertEquals(far, TouchMap.map(VIEW_X + VIEW_W + 50, VIEW_X, VIEW_W, TARGET_W));
        assertEquals(far, TouchMap.map(2000, VIEW_X, VIEW_W, TARGET_W));
    }

    @Test
    public void neverEscapesTheTargetForAnyCoordinate() {
        for (int c = -500; c < 3000; c++) {
            int t = TouchMap.map(c, VIEW_X, VIEW_W, TARGET_W);
            if (t < 0 || t >= TARGET_W) {
                throw new AssertionError("coord " + c + " mapped outside the target: " + t);
            }
        }
    }

    @Test
    public void noOverflowAtFourK() {
        // 3840 * 2160 exceeds int when multiplied in 32 bits; the mapping
        // has to widen before it scales.
        assertEquals(2159, TouchMap.map(3839, 0, 3840, 2160));
        assertEquals(0,    TouchMap.map(0,    0, 3840, 2160));
    }

    @Test
    public void identityWhenTheViewMatchesTheTarget() {
        // The self-mirror case the e2e exercises: nothing should move.
        for (int c : new int[]{0, 1, 539, 1079}) {
            assertEquals(c, TouchMap.map(c, 0, 1080, 1080));
        }
    }

    @Test
    public void degenerateGeometryIsRefusedRatherThanDividingByZero() {
        assertEquals(0, TouchMap.map(100, 0, 0, 1080));
        assertEquals(0, TouchMap.map(100, 0, 1080, 0));
    }
}