Skip to content

Commit b0d9ed1

Browse files
committed
⚡️(frontend) add skeleton on content loading
Content is longer to load than other parts of the editor because of the connection with websocket to the collaboration server. To improve the user experience, we add a skeleton on the content part of the editor while the others parts are displayed.
1 parent d41e44d commit b0d9ed1

14 files changed

Lines changed: 274 additions & 154 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ and this project adheres to
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- ⚡️(frontend) add skeleton on content loading #2254
12+
913
### Fixed
1014

1115
- 🐛(frontend) sanitize pasted and dropped content in document title #2210

src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -688,25 +688,23 @@ test.describe('Doc Editor', () => {
688688
test('it checks interlink feature', async ({ page, browserName }) => {
689689
const [randomDoc] = await createDoc(page, 'doc-interlink', browserName, 1);
690690

691-
await verifyDocName(page, randomDoc);
692-
693691
const { name: docChild1 } = await createRootSubPage(
694692
page,
695693
browserName,
696694
'doc-interlink-child-1',
697695
);
698696

699-
await verifyDocName(page, docChild1);
700-
701697
const { name: docChild2 } = await createRootSubPage(
702698
page,
703699
browserName,
704700
'doc-interlink-child-2',
705701
);
706702

707-
await verifyDocName(page, docChild2);
708-
709703
const treeRow = await getTreeRow(page, docChild2);
704+
705+
// To let the time for the emoji-picker to load
706+
await page.waitForTimeout(500);
707+
710708
await treeRow.locator('.--docs--doc-icon').click();
711709
await page.getByRole('button', { name: '😀' }).first().click();
712710

src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ test.describe('Doc Header', () => {
104104
browserName,
105105
1,
106106
);
107+
108+
await writeInEditor({ page, text: 'Hello Content' });
109+
107110
await page.getByRole('button', { name: 'Share' }).click();
108111
await updateShareLink(page, 'Public', 'Editing');
109112

@@ -116,17 +119,18 @@ test.describe('Doc Header', () => {
116119
docTitle,
117120
});
118121

119-
// Wait for other page to sync
122+
await expect(otherPage.getByText('Hello Content')).toBeVisible();
123+
124+
// Wait for other page to broadcast sync
120125
await page.waitForTimeout(1000);
121126

122127
await page.keyboard.press('Escape');
123128
const elTitle = page.getByRole('textbox', { name: 'Document title' });
124129
await expect(elTitle).toBeVisible();
125130
await elTitle.fill('Hello World');
126131
await elTitle.blur();
127-
await verifyDocName(page, 'Hello World');
128132

129-
// Wait for other page to sync
133+
// Wait for other page to broadcast sync
130134
await page.waitForTimeout(1000);
131135

132136
// Check other user page
@@ -531,7 +535,7 @@ test.describe('Doc Header', () => {
531535
browserName === 'webkit',
532536
'navigator.clipboard is not working with webkit and playwright',
533537
);
534-
const uuid = await mockedDocument(page, {
538+
await mockedDocument(page, {
535539
abilities: {
536540
destroy: false, // Means owner
537541
link_configuration: true,
@@ -552,7 +556,6 @@ test.describe('Doc Header', () => {
552556
name: 'Share',
553557
exact: true,
554558
});
555-
await expect(shareButton).toBeVisible();
556559

557560
await shareButton.click();
558561
await page.getByRole('button', { name: 'Copy link' }).click();
@@ -563,8 +566,8 @@ test.describe('Doc Header', () => {
563566
);
564567
const clipboardContent = await handle.jsonValue();
565568

566-
const origin = await page.evaluate(() => window.location.origin);
567-
expect(clipboardContent.trim()).toMatch(`${origin}/docs/${uuid}/`);
569+
const url = page.url();
570+
expect(clipboardContent.trim()).toMatch(url);
568571
});
569572

570573
test('it pins a document', async ({ page, browserName }) => {

src/frontend/apps/e2e/__tests__/app-impress/doc-inherited-share.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ test.describe('Inherited share accesses', () => {
3131
.getByRole('link')
3232
.click();
3333

34+
await page.getByRole('button', { name: 'close' }).first().click();
35+
3436
await verifyDocName(page, parentTitle);
3537
});
3638

src/frontend/apps/e2e/__tests__/app-impress/doc-version.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -185,23 +185,23 @@ test.describe('Doc Version', () => {
185185

186186
await page.getByLabel('Restore', { exact: true }).click();
187187

188-
await page.waitForTimeout(500);
188+
const mainEditor = page.getByLabel('Document editor');
189189

190-
await expect(editor.getByText('Hello')).toBeVisible();
191-
await expect(editor.getByText('World')).toBeHidden();
190+
await expect(mainEditor.getByText('Hello')).toBeVisible();
191+
await expect(mainEditor.getByText('World')).toBeHidden();
192192

193193
// The old comment is not restored
194-
await expect(editor.getByText('Hello')).toHaveCSS(
194+
await expect(mainEditor.getByText('Hello')).toHaveCSS(
195195
'background-color',
196196
'rgba(0, 0, 0, 0)',
197197
);
198198

199199
// We can add a new comment
200-
await editor.getByText('Hello').selectText();
200+
await mainEditor.getByText('Hello').selectText();
201201
await page.getByRole('button', { name: 'Add comment' }).click();
202202

203203
await thread.getByRole('paragraph').first().fill('This is a comment');
204204
await thread.locator('[data-test="save"]').click();
205-
await expect(editor.getByText('Hello')).toHaveClass('bn-thread-mark');
205+
await expect(mainEditor.getByText('Hello')).toHaveClass('bn-thread-mark');
206206
});
207207
});

src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -131,42 +131,64 @@ export const createDoc = async (
131131
await openHeaderMenu(page);
132132
}
133133

134+
const responsePromiseCreateDoc = page.waitForResponse(
135+
(response) =>
136+
response.url().includes('/api/v1.0/documents/') &&
137+
response.status() === 201 &&
138+
response.request().method() === 'POST',
139+
);
140+
134141
await page
135142
.getByRole('button', {
136143
name: 'New doc',
137144
})
138145
.click();
139146

147+
await page.waitForURL('**/docs/**', {
148+
timeout: 10000,
149+
waitUntil: 'networkidle',
150+
});
151+
152+
const responseCreateDoc = await responsePromiseCreateDoc;
153+
expect(responseCreateDoc.ok()).toBeTruthy();
154+
const { id: docId } = (await responseCreateDoc.json()) as { id: string };
155+
156+
const responsePromiseUpdateDoc = page.waitForResponse(
157+
(response) =>
158+
response.url().includes(`/api/v1.0/documents/${docId}`) &&
159+
response.status() === 200 &&
160+
response.request().method() === 'PATCH',
161+
);
162+
140163
const input = page.getByLabel('Document title');
141164
await expect(input).toBeVisible({
142165
timeout: 10000,
143166
});
144-
await expect(input).toHaveText('');
167+
await expect(input).toHaveText('', {
168+
timeout: 10000,
169+
});
145170

146171
await input.fill(randomDocs[i]);
147-
await input.blur();
172+
void input.blur();
173+
174+
const responseUpdateDoc = await responsePromiseUpdateDoc;
175+
expect(responseUpdateDoc.ok()).toBeTruthy();
148176
}
149177

150178
return randomDocs;
151179
};
152180

153181
export const verifyDocName = async (page: Page, docName: string) => {
154-
await expect(
155-
page.getByLabel('It is the card information about the document.'),
156-
).toBeVisible({
182+
const card = page.getByLabel(
183+
'It is the card information about the document.',
184+
);
185+
await expect(card).toBeVisible({
157186
timeout: 10000,
158187
});
159188

160-
/*replace toHaveText with toContainText to handle cases where emojis or other characters might be added*/
161-
try {
162-
await expect(
163-
page.getByRole('textbox', { name: 'Document title' }),
164-
).toContainText(docName, {
165-
timeout: 3000,
166-
});
167-
} catch {
168-
await expect(page.getByRole('heading', { name: docName })).toBeVisible();
169-
}
189+
await expect(card).toHaveText(new RegExp(docName), {
190+
timeout: 10000,
191+
});
170192
};
171193

172194
export const getGridRow = async (page: Page, title: string) => {
@@ -228,11 +250,9 @@ export const updateDocTitle = async (page: Page, title: string) => {
228250
const input = page.getByRole('textbox', { name: 'Document title' });
229251
await expect(input).toHaveText('');
230252
await expect(input).toBeVisible();
231-
await input.click();
232253
await input.fill(title, {
233254
force: true,
234255
});
235-
await input.click();
236256
await input.blur();
237257
await verifyDocName(page, title);
238258
};
@@ -248,10 +268,11 @@ export const waitForResponseCreateDoc = (page: Page) => {
248268

249269
export const mockedDocument = async (page: Page, data: object) => {
250270
// document/[ID]/ or document/[ID]/tree/ routes
251-
const uuid = crypto.randomUUID();
271+
let uuid: string | undefined;
252272
await page.route(/.*\/documents\/[^/]+\/(?:$|tree\/.*)/, async (route) => {
253273
const request = route.request();
254274
if (request.method().includes('GET') && !request.url().includes('page=')) {
275+
uuid = request.url().match(/\/documents\/([^/]+)\//)?.[1];
255276
const { abilities, ...doc } = data as unknown as {
256277
abilities?: Record<string, unknown>;
257278
};

src/frontend/apps/impress/src/features/docs/doc-editor/__tests__/DocEditor.spec.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ vi.mock('@/stores', () => ({
1111
useResponsiveStore: () => ({ isDesktop: false }),
1212
}));
1313

14-
vi.mock('@/features/skeletons', () => ({
15-
useSkeletonStore: () => ({
16-
setIsSkeletonVisible: vi.fn(),
17-
}),
18-
}));
14+
vi.mock('@/features/skeletons', async () => {
15+
const actual = await vi.importActual<any>('../../../skeletons');
16+
return {
17+
...actual,
18+
useSkeletonStore: () => ({
19+
setIsSkeletonVisible: vi.fn(),
20+
}),
21+
};
22+
});
1923

2024
vi.mock('../../doc-management', async () => {
2125
const actual = await vi.importActual<any>('../../doc-management');

0 commit comments

Comments
 (0)