Full document, spreadsheet, slideshow, and diagram tooling
1import { test, expect } from '@playwright/test';
2import { createNewDoc } from './helpers';
3
4test.describe('Mobile — Docs Editor', () => {
5 test.beforeEach(async ({ page }) => {
6 await createNewDoc(page);
7 });
8
9 test('editor is visible and fills viewport width', async ({ page }) => {
10 const editor = page.locator('.tiptap');
11 await expect(editor).toBeVisible();
12
13 const viewport = page.viewportSize()!;
14 const box = await editor.boundingBox();
15 // Editor should use most of the viewport width
16 expect(box!.width).toBeGreaterThan(viewport.width * 0.8);
17 });
18
19 test('toolbar buttons meet minimum touch target size', async ({ page }) => {
20 const viewport = page.viewportSize()!;
21 if (viewport.width > 768) test.skip();
22
23 const buttons = page.locator('.tb-btn:visible');
24 const count = await buttons.count();
25 expect(count).toBeGreaterThan(0);
26
27 for (let i = 0; i < Math.min(count, 5); i++) {
28 const box = await buttons.nth(i).boundingBox();
29 if (box) {
30 expect(box.width).toBeGreaterThanOrEqual(42); // 44 target, 2px tolerance
31 expect(box.height).toBeGreaterThanOrEqual(42);
32 }
33 }
34 });
35
36 test('non-essential toolbar items hidden on mobile', async ({ page }) => {
37 const viewport = page.viewportSize()!;
38 if (viewport.width > 768) test.skip();
39
40 const hiddenItems = page.locator('.toolbar-mobile-hide');
41 const count = await hiddenItems.count();
42 for (let i = 0; i < count; i++) {
43 await expect(hiddenItems.nth(i)).not.toBeVisible();
44 }
45 });
46
47 test('can type and see text in editor', async ({ page }) => {
48 const editor = page.locator('.tiptap');
49 await editor.click();
50 await page.keyboard.type('Mobile typing test');
51 await expect(editor).toContainText('Mobile typing test');
52 });
53
54 test('topbar does not overflow horizontally', async ({ page }) => {
55 const viewport = page.viewportSize()!;
56 const topbar = page.locator('.app-topbar');
57 const box = await topbar.boundingBox();
58 if (box) {
59 expect(box.width).toBeLessThanOrEqual(viewport.width + 1);
60 }
61 });
62
63 test('document title input is accessible', async ({ page }) => {
64 const titleInput = page.locator('.doc-title-input');
65 await expect(titleInput).toBeVisible();
66 await titleInput.fill('My Mobile Doc');
67 await expect(titleInput).toHaveValue('My Mobile Doc');
68 });
69
70 test('word count visible in footer', async ({ page }) => {
71 const editor = page.locator('.tiptap');
72 await editor.click();
73 await page.keyboard.type('one two three');
74
75 await expect(page.locator('#word-count')).toContainText('3 words', { timeout: 5000 });
76 });
77
78 test('command palette opens and is usable', async ({ page }) => {
79 const viewport = page.viewportSize()!;
80
81 await page.keyboard.press('Meta+k');
82 const palette = page.locator('.cmd-palette');
83 await expect(palette).toBeVisible({ timeout: 3000 });
84
85 // Palette should fit within viewport
86 const box = await palette.boundingBox();
87 if (box) {
88 expect(box.right).toBeLessThanOrEqual(viewport.width + 5);
89 expect(box.bottom).toBeLessThanOrEqual(viewport.height + 5);
90 }
91
92 // Dismiss
93 await page.keyboard.press('Escape');
94 await expect(palette).not.toBeVisible();
95 });
96
97 test('modals are full-screen on phones', async ({ page }) => {
98 const viewport = page.viewportSize()!;
99 if (viewport.width > 480) test.skip();
100
101 // Open share dialog as a test modal
102 await page.click('#btn-share');
103 const dialog = page.locator('#share-dialog');
104 await expect(dialog).toBeVisible({ timeout: 3000 });
105
106 const box = await dialog.boundingBox();
107 if (box) {
108 // Should be approximately full-width on phones
109 expect(box.width).toBeGreaterThan(viewport.width * 0.9);
110 }
111
112 await page.click('#share-dialog-close');
113 });
114});