File size: 6,663 Bytes
97ee7cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import { devices, expect, test } from '@playwright/test';

const MOBILE_VIEWPORT = devices['iPhone 14 Pro Max'];

test.describe('Mobile map native experience', () => {
  const { defaultBrowserType: _bt, ...mobileContext } = MOBILE_VIEWPORT;

  test.describe('timezone-based startup region', () => {
    test('America/New_York → america view', async ({ browser }) => {
      const context = await browser.newContext({
        ...mobileContext,
        timezoneId: 'America/New_York',
        locale: 'en-US',
      });
      const page = await context.newPage();
      await page.addInitScript(() => {
        (window as any).__testResolvedLocation = true;
      });
      await page.goto('/');
      await page.waitForTimeout(3000);
      const region = await page.evaluate(() => {
        const select = document.getElementById('regionSelect') as HTMLSelectElement | null;
        return select?.value ?? null;
      });
      expect(region).toBe('america');
      await context.close();
    });

    test('Europe/London → eu view', async ({ browser }) => {
      const context = await browser.newContext({
        ...mobileContext,
        timezoneId: 'Europe/London',
        locale: 'en-GB',
      });
      const page = await context.newPage();
      await page.goto('/');
      await page.waitForTimeout(3000);
      const region = await page.evaluate(() => {
        const select = document.getElementById('regionSelect') as HTMLSelectElement | null;
        return select?.value ?? null;
      });
      expect(region).toBe('eu');
      await context.close();
    });

    test('Asia/Tokyo → asia view', async ({ browser }) => {
      const context = await browser.newContext({
        ...mobileContext,
        timezoneId: 'Asia/Tokyo',
        locale: 'ja-JP',
      });
      const page = await context.newPage();
      await page.goto('/');
      await page.waitForTimeout(3000);
      const region = await page.evaluate(() => {
        const select = document.getElementById('regionSelect') as HTMLSelectElement | null;
        return select?.value ?? null;
      });
      expect(region).toBe('asia');
      await context.close();
    });
  });

  test.describe('URL restore', () => {
    test.use(mobileContext);

    test('lat/lon override view center', async ({ page }) => {
      await page.goto('/?view=eu&lat=48.86&lon=2.35&zoom=5');
      await page.waitForTimeout(3000);
      const url = page.url();
      const params = new URL(url).searchParams;
      const lat = params.get('lat');
      const lon = params.get('lon');
      if (lat && lon) {
        expect(parseFloat(lat)).toBeCloseTo(48.86, 0);
        expect(parseFloat(lon)).toBeCloseTo(2.35, 0);
      } else {
        const region = await page.evaluate(() => {
          const select = document.getElementById('regionSelect') as HTMLSelectElement | null;
          return select?.value ?? null;
        });
        expect(region).not.toBe('eu');
      }
    });

    test('zero-degree coordinates center at equator/prime meridian', async ({ page }) => {
      await page.goto('/?lat=0&lon=0&zoom=4');
      await page.waitForTimeout(3000);
      const url = page.url();
      const params = new URL(url).searchParams;
      const lat = params.get('lat');
      const lon = params.get('lon');
      expect(lat).not.toBeNull();
      expect(lon).not.toBeNull();
      if (lat && lon) {
        expect(Math.abs(parseFloat(lat))).toBeLessThan(5);
        expect(Math.abs(parseFloat(lon))).toBeLessThan(5);
      }
    });
  });

  test.describe('touch interactions', () => {
    test.use(mobileContext);

    test('single-finger pan does not scroll page', async ({ page }) => {
      await page.goto('/');
      await page.waitForTimeout(3000);
      const mapEl = page.locator('#mapContainer');
      await expect(mapEl).toBeVisible({ timeout: 10000 });

      const scrollBefore = await page.evaluate(() => window.scrollY);

      const box = await mapEl.boundingBox();
      if (box) {
        const startX = box.x + box.width / 2;
        const startY = box.y + box.height / 2;
        await page.touchscreen.tap(startX, startY);
        await page.mouse.move(startX, startY);
        await page.touchscreen.tap(startX, startY + 50);
      }

      const scrollAfter = await page.evaluate(() => window.scrollY);
      expect(scrollAfter).toBe(scrollBefore);
    });
  });

  test.describe('geolocation startup centering', () => {
    test('centers map on granted geolocation coords', async ({ browser }) => {
      const context = await browser.newContext({
        ...mobileContext,
        geolocation: { latitude: 48.8566, longitude: 2.3522 },
        permissions: ['geolocation'],
      });
      const page = await context.newPage();
      await page.goto('/');
      await page.waitForFunction(
        () => {
          const select = document.getElementById('regionSelect') as HTMLSelectElement | null;
          return select?.value === 'eu';
        },
        { timeout: 10000 },
      );
      await context.close();
    });
  });

  test.describe('mobile map viewport', () => {
    test('map starts expanded and occupies most of viewport', async ({ browser }) => {
      const context = await browser.newContext({
        ...mobileContext,
        locale: 'en-US',
      });
      const page = await context.newPage();
      await page.goto('/');
      const mapSection = page.locator('#mapSection');
      await expect(mapSection).toBeVisible({ timeout: 10000 });
      await expect(mapSection).not.toHaveClass(/collapsed/);

      const ratio = await page.evaluate(() => {
        const el = document.getElementById('mapSection');
        return (el?.getBoundingClientRect().height ?? 0) / window.innerHeight;
      });
      expect(ratio).toBeGreaterThanOrEqual(0.7);
      await context.close();
    });
  });

  test.describe('breakpoint consistency at 768px', () => {
    test('JS and CSS agree at exactly 768px', async ({ browser }) => {
      const context = await browser.newContext({
        viewport: { width: 768, height: 1024 },
        locale: 'en-US',
      });
      const page = await context.newPage();
      await page.goto('/');
      await page.waitForTimeout(2000);

      const result = await page.evaluate(() => {
        const jsMobile = window.innerWidth <= 768;
        const el = document.createElement('div');
        el.style.display = 'none';
        document.body.appendChild(el);
        const cssMobile = window.matchMedia('(max-width: 768px)').matches;
        el.remove();
        return { jsMobile, cssMobile };
      });

      expect(result.jsMobile).toBe(result.cssMobile);
      await context.close();
    });
  });
});