Full document, spreadsheet, slideshow, and diagram tooling
1import { test, expect } from '@playwright/test';
2import { createNewDoc, createNewSheet, waitForSaved } from './helpers';
3
4test.describe('Version History', () => {
5 test.describe('Docs', () => {
6 test.beforeEach(async ({ page }) => {
7 await createNewDoc(page);
8 });
9
10 test('version history panel opens via button click', async ({ page }) => {
11 const editor = page.locator('.tiptap');
12 await editor.click();
13 await page.keyboard.type('Version history test content');
14 await waitForSaved(page);
15
16 await page.click('#btn-history');
17
18 // The version panel should become visible
19 const panel = page.locator('.version-panel');
20 await expect(panel).toHaveClass(/open/, { timeout: 5000 });
21 await expect(panel).toBeVisible();
22 });
23
24 test('version history panel opens with Cmd+Shift+H', async ({ page }) => {
25 const editor = page.locator('.tiptap');
26 await editor.click();
27 await page.keyboard.type('Keyboard shortcut test');
28 await waitForSaved(page);
29
30 await page.keyboard.press('Meta+Shift+h');
31
32 const panel = page.locator('.version-panel');
33 await expect(panel).toHaveClass(/open/, { timeout: 5000 });
34 });
35
36 test('version history panel closes with close button', async ({ page }) => {
37 const editor = page.locator('.tiptap');
38 await editor.click();
39 await page.keyboard.type('Close test');
40 await waitForSaved(page);
41
42 await page.click('#btn-history');
43 const panel = page.locator('.version-panel');
44 await expect(panel).toHaveClass(/open/, { timeout: 5000 });
45
46 // Click the close button inside the panel
47 await page.click('.version-panel-close');
48 await expect(panel).not.toHaveClass(/open/);
49 });
50
51 test('version history panel closes with Escape', async ({ page }) => {
52 const editor = page.locator('.tiptap');
53 await editor.click();
54 await page.keyboard.type('Escape test');
55 await waitForSaved(page);
56
57 await page.click('#btn-history');
58 const panel = page.locator('.version-panel');
59 await expect(panel).toHaveClass(/open/, { timeout: 5000 });
60
61 await page.keyboard.press('Escape');
62 await expect(panel).not.toHaveClass(/open/);
63 });
64
65 test('version history panel shows version list after content is saved', async ({ page }) => {
66 const editor = page.locator('.tiptap');
67 await editor.click();
68 await page.keyboard.type('Content for versioning');
69 await waitForSaved(page);
70
71 // Wait a moment for the version to be created server-side
72 await page.waitForTimeout(1000);
73
74 await page.click('#btn-history');
75 const panel = page.locator('.version-panel');
76 await expect(panel).toHaveClass(/open/, { timeout: 5000 });
77
78 // The version list should contain items or an empty state
79 const list = panel.locator('.version-panel-list');
80 await expect(list).toBeVisible();
81
82 // There should be at least one version item or the "No versions yet" message
83 const items = panel.locator('.version-panel-item');
84 const emptyMsg = panel.locator('.version-empty');
85 const hasItems = await items.count() > 0;
86 const hasEmpty = await emptyMsg.count() > 0;
87 expect(hasItems || hasEmpty).toBeTruthy();
88 });
89
90 test('version panel shows author and timestamp for each version', async ({ page }) => {
91 const editor = page.locator('.tiptap');
92 await editor.click();
93 await page.keyboard.type('Author attribution test');
94 await waitForSaved(page);
95 await page.waitForTimeout(1000);
96
97 await page.click('#btn-history');
98 const panel = page.locator('.version-panel');
99 await expect(panel).toHaveClass(/open/, { timeout: 5000 });
100
101 const items = panel.locator('.version-panel-item');
102 if (await items.count() > 0) {
103 // Each version item should have time and author
104 const firstItem = items.first();
105 await expect(firstItem.locator('.version-panel-time')).toBeVisible();
106 await expect(firstItem.locator('.version-panel-author')).toBeVisible();
107 }
108 });
109
110 test('clicking a version item shows preview', async ({ page }) => {
111 const editor = page.locator('.tiptap');
112 await editor.click();
113 await page.keyboard.type('Preview test content');
114 await waitForSaved(page);
115 await page.waitForTimeout(1000);
116
117 await page.click('#btn-history');
118 const panel = page.locator('.version-panel');
119 await expect(panel).toHaveClass(/open/, { timeout: 5000 });
120
121 const items = panel.locator('.version-panel-item');
122 if (await items.count() > 0) {
123 await items.first().click();
124
125 // Preview section should become visible
126 const preview = panel.locator('.version-panel-preview');
127 await expect(preview).toBeVisible({ timeout: 5000 });
128
129 // Back button should be visible
130 await expect(panel.locator('.version-panel-back')).toBeVisible();
131
132 // Restore button should be visible
133 await expect(panel.locator('.version-panel-restore')).toBeVisible();
134 }
135 });
136
137 test('back button in preview returns to version list', async ({ page }) => {
138 const editor = page.locator('.tiptap');
139 await editor.click();
140 await page.keyboard.type('Back button test');
141 await waitForSaved(page);
142 await page.waitForTimeout(1000);
143
144 await page.click('#btn-history');
145 const panel = page.locator('.version-panel');
146 await expect(panel).toHaveClass(/open/, { timeout: 5000 });
147
148 const items = panel.locator('.version-panel-item');
149 if (await items.count() > 0) {
150 await items.first().click();
151 await expect(panel.locator('.version-panel-preview')).toBeVisible({ timeout: 5000 });
152
153 // Click back
154 await page.click('.version-panel-back');
155 await expect(panel.locator('.version-panel-preview')).not.toBeVisible();
156 }
157 });
158
159 test('clicking restore button restores the version after confirmation', async ({ page }) => {
160 const editor = page.locator('.tiptap');
161 await editor.click();
162 await page.keyboard.type('Original content');
163 await waitForSaved(page);
164 await page.waitForTimeout(1000);
165
166 // Overwrite with different content
167 await editor.click();
168 await page.keyboard.press('Meta+a');
169 await page.keyboard.type('Replaced content');
170 await waitForSaved(page);
171 await page.waitForTimeout(1000);
172
173 // Open version panel and click the first (oldest) version
174 await page.click('#btn-history');
175 const panel = page.locator('.version-panel');
176 await expect(panel).toHaveClass(/open/, { timeout: 5000 });
177
178 const items = panel.locator('.version-panel-item');
179 if (await items.count() > 1) {
180 // Click the oldest version (last in the list — newest first)
181 await items.last().click();
182 await expect(panel.locator('.version-panel-preview')).toBeVisible({ timeout: 5000 });
183
184 // Accept the confirm() dialog when restore is clicked
185 page.on('dialog', dialog => dialog.accept());
186 await page.click('.version-panel-restore');
187
188 // Panel should close after restore
189 await expect(panel).not.toHaveClass(/open/, { timeout: 5000 });
190 }
191 });
192
193 test('clicking restore cancel does not restore', async ({ page }) => {
194 const editor = page.locator('.tiptap');
195 await editor.click();
196 await page.keyboard.type('Keep this content');
197 await waitForSaved(page);
198 await page.waitForTimeout(1000);
199
200 await page.click('#btn-history');
201 const panel = page.locator('.version-panel');
202 await expect(panel).toHaveClass(/open/, { timeout: 5000 });
203
204 const items = panel.locator('.version-panel-item');
205 if (await items.count() > 0) {
206 await items.first().click();
207 await expect(panel.locator('.version-panel-preview')).toBeVisible({ timeout: 5000 });
208
209 // Dismiss the confirm() dialog
210 page.on('dialog', dialog => dialog.dismiss());
211 await page.click('.version-panel-restore');
212
213 // Panel should remain open (restore was cancelled)
214 await expect(panel).toHaveClass(/open/);
215 await expect(panel.locator('.version-panel-preview')).toBeVisible();
216 }
217 });
218
219 test('name version button is present on version items', async ({ page }) => {
220 const editor = page.locator('.tiptap');
221 await editor.click();
222 await page.keyboard.type('Name version test');
223 await waitForSaved(page);
224 await page.waitForTimeout(1000);
225
226 await page.click('#btn-history');
227 const panel = page.locator('.version-panel');
228 await expect(panel).toHaveClass(/open/, { timeout: 5000 });
229
230 const items = panel.locator('.version-panel-item');
231 if (await items.count() > 0) {
232 // Each item should have a name button
233 await expect(items.first().locator('.version-panel-name-btn')).toBeVisible();
234 }
235 });
236 });
237
238 test.describe('Sheets', () => {
239 test('version history panel opens for sheets via button', async ({ page }) => {
240 await createNewSheet(page);
241
242 // Add some data
243 await page.locator('td[data-id="A1"]').click();
244 await page.keyboard.type('Sheet data');
245 await page.keyboard.press('Enter');
246 await waitForSaved(page);
247
248 await page.click('#btn-history');
249
250 // Version panel should appear
251 const panel = page.locator('.version-panel');
252 await expect(panel).toHaveClass(/open/, { timeout: 5000 });
253 });
254 });
255});