File size: 4,838 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
import { devices, expect, test } from '@playwright/test';

// #5159 / PR #5205 review P2: the source-pattern guard in
// tests/skeleton-app-footprint-parity.test.mjs proves the creation template
// SEEDS .collapsed, but only a behavioral check proves the section is never
// OBSERVABLE expanded. Pre-fix, #mapSection painted expanded (~796px) for
// ~150ms (9+ frames) before setupMobileMapToggle collapsed it, shoving
// #panelsGrid up 698px (CLS 0.617, 3/3 repro). This spec samples every rAF
// frame from document start: for the seeded collapsed cohort, NO frame may
// show an attached #mapSection without .collapsed.
//
const { defaultBrowserType, ...mobileDevice } = devices['iPhone 14 Pro Max'];
void defaultBrowserType;

async function expectDashboardBooted(page: import('@playwright/test').Page): Promise<void> {
  await expect(page.locator('html')).toHaveAttribute('data-wm-event-handlers-ready', 'true', { timeout: 45_000 });
  await expect(page.locator('#mapSection')).toBeVisible({ timeout: 45_000 });
  await expect(page.locator('#panelsGrid')).toBeVisible({ timeout: 45_000 });
  await expect(page.locator('#panelsGrid > .panel[data-panel]:not(.hidden)').first()).toBeVisible({ timeout: 45_000 });
  await expect(page.locator('#mapSection')).not.toHaveClass(/collapsed/);
}

declare global {
  interface Window {
    __wmMapFrames?: Array<{ t: number; collapsed: boolean; className: string }>;
  }
}

test.describe('collapsed-map cohort first paint (#5159)', () => {
  test.use({ ...mobileDevice });

  test('#mapSection is never observable without .collapsed when the pref is set', async ({ page }) => {
    await page.addInitScript(() => {
      try {
        localStorage.setItem('mobile-map-collapsed', 'true');
      } catch {
        /* storage unavailable — the seeded-cohort contract is then vacuous */
      }
      window.__wmMapFrames = [];
      const sample = (): void => {
        const sec = document.getElementById('mapSection');
        if (sec) {
          const frames = window.__wmMapFrames;
          const collapsed = sec.classList.contains('collapsed');
          const last = frames[frames.length - 1];
          if (!last || last.collapsed !== collapsed) {
            frames.push({ t: Math.round(performance.now()), collapsed, className: sec.className.slice(0, 80) });
          }
        }
        requestAnimationFrame(sample);
      };
      requestAnimationFrame(sample);
    });

    await page.goto('/dashboard', { waitUntil: 'domcontentloaded' });
    await page.waitForSelector('#mapSection', { timeout: 45_000 });
    // Let several render passes land (deferred panels, map hydration) so a
    // late uncollapse would be caught too.
    await page.waitForTimeout(4_000);

    const frames = await page.evaluate(() => window.__wmMapFrames ?? []);
    expect(frames.length, 'the rAF sampler must have observed #mapSection').toBeGreaterThan(0);
    const uncollapsed = frames.filter((f) => !f.collapsed);
    expect(
      uncollapsed,
      `#mapSection was observable WITHOUT .collapsed for the seeded cohort (pre-#5205 bug: expanded-then-snap, CLS 0.617): ${JSON.stringify(uncollapsed)}`,
    ).toEqual([]);
  });

  test('dashboard boots with defaults when browser storage access is blocked (#5209)', async ({ page }) => {
    const pageErrors: string[] = [];
    page.on('pageerror', (error) => pageErrors.push(`${error.name}: ${error.message}`));

    await page.addInitScript(() => {
      for (const storageName of ['localStorage', 'sessionStorage'] as const) {
        Object.defineProperty(window, storageName, {
          configurable: true,
          get() {
            throw new DOMException('storage blocked', 'SecurityError');
          },
        });
      }
    });

    await page.goto('/dashboard', { waitUntil: 'domcontentloaded' });

    await expectDashboardBooted(page);
    expect(pageErrors).toEqual([]);
  });

  test('dashboard boots with defaults when browser storage is read-only (#5209)', async ({ page }) => {
    const pageErrors: string[] = [];
    page.on('pageerror', (error) => pageErrors.push(`${error.name}: ${error.message}`));

    await page.addInitScript(() => {
      for (const storage of [window.localStorage, window.sessionStorage]) {
        Object.defineProperties(storage, {
          setItem: {
            configurable: true,
            value() {
              throw new DOMException('storage is read-only', 'QuotaExceededError');
            },
          },
          removeItem: {
            configurable: true,
            value() {
              throw new DOMException('storage is read-only', 'QuotaExceededError');
            },
          },
        });
      }
    });

    await page.goto('/dashboard', { waitUntil: 'domcontentloaded' });

    await expectDashboardBooted(page);
    expect(pageErrors).toEqual([]);
  });
});