From 1e27fe4c17a8794462f1c2ac0a98f743387fa244 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Fri, 27 Sep 2024 16:29:25 -0700 Subject: [PATCH 1/7] Mobile: Fixes #11134: Fix automatic resource download mode --- .../components/screens/Note.test.tsx | 78 ++++++++++++++++--- .../app-mobile/components/screens/Note.tsx | 18 +++-- packages/lib/testing/test-utils.ts | 4 +- 3 files changed, 80 insertions(+), 20 deletions(-) diff --git a/packages/app-mobile/components/screens/Note.test.tsx b/packages/app-mobile/components/screens/Note.test.tsx index 1997a945951..5edabc9eba7 100644 --- a/packages/app-mobile/components/screens/Note.test.tsx +++ b/packages/app-mobile/components/screens/Note.test.tsx @@ -7,7 +7,7 @@ import { Provider } from 'react-redux'; import NoteScreen from './Note'; import { MenuProvider } from 'react-native-popup-menu'; -import { setupDatabaseAndSynchronizer, switchClient, simulateReadOnlyShareEnv, waitFor } from '@joplin/lib/testing/test-utils'; +import { setupDatabaseAndSynchronizer, switchClient, simulateReadOnlyShareEnv, waitFor, supportDir, synchronizerStart, resourceFetcher, runWithFakeTimers } from '@joplin/lib/testing/test-utils'; import Note from '@joplin/lib/models/Note'; import { AppState } from '../../utils/types'; import { Store } from 'redux'; @@ -26,6 +26,8 @@ import { LayoutChangeEvent } from 'react-native'; import shim from '@joplin/lib/shim'; import getWebViewWindowById from '../../utils/testing/getWebViewWindowById'; import CodeMirrorControl from '@joplin/editor/CodeMirror/CodeMirrorControl'; +import Setting from '@joplin/lib/models/Setting'; +import Resource from '@joplin/lib/models/Resource'; interface WrapperProps { } @@ -67,12 +69,8 @@ const waitForNoteToMatch = async (noteId: string, note: Partial) => })); }; -const openNewNote = async (noteProperties: NoteEntity) => { - const note = await Note.save({ - parent_id: (await Folder.defaultFolder()).id, - ...noteProperties, - }); - +const openExistingNote = async (noteId: string) => { + const note = await Note.load(noteId); const displayParentId = getDisplayParentId(note, await Folder.load(note.parent_id)); store.dispatch({ @@ -85,7 +83,15 @@ const openNewNote = async (noteProperties: NoteEntity) => { id: note.id, folderId: displayParentId, }); +}; + +const openNewNote = async (noteProperties: NoteEntity) => { + const note = await Note.save({ + parent_id: (await Folder.defaultFolder()).id, + ...noteProperties, + }); + await openExistingNote(note.id); await waitForNoteToMatch(note.id, { parent_id: note.parent_id, title: note.title, body: note.body }); return note.id; @@ -106,21 +112,20 @@ const openNoteActionsMenu = async () => { cursor = cursor.parent; } - await userEvent.press(actionMenuButton); + await runWithFakeTimers(() => userEvent.press(actionMenuButton)); }; const openEditor = async () => { const editButton = await screen.findByLabelText('Edit'); - await userEvent.press(editButton); + fireEvent.press(editButton); }; describe('screens/Note', () => { - beforeAll(() => { + beforeEach(async () => { // advanceTimers: Needed by internal note save logic jest.useFakeTimers({ advanceTimers: true }); - }); - beforeEach(async () => { + await setupDatabaseAndSynchronizer(1); await setupDatabaseAndSynchronizer(0); await switchClient(0); @@ -265,4 +270,53 @@ describe('screens/Note', () => { cleanup(); }); + + it.each([ + 'auto', + 'manual', + ])('should correctly auto-download or not auto-download resources in %j mode', async (downloadMode) => { + // This test interacts with the synchronizer, which seems to need real timers. + jest.useRealTimers(); + + let note = await Note.save({ title: 'Note 1', parent_id: (await Folder.defaultFolder()).id }); + note = await shim.attachFileToNote(note, `${supportDir}/photo.jpg`); + + await synchronizerStart(); + await switchClient(1); + Setting.setValue('sync.resourceDownloadMode', downloadMode); + await synchronizerStart(); + + // Before opening the note, the resource should not be marked for download + const allResources = await Resource.all(); + expect(allResources.length).toBe(1); + const resource = allResources[0]; + expect(await Resource.localState(resource)).toMatchObject({ fetch_status: Resource.FETCH_STATUS_IDLE }); + + await openExistingNote(note.id); + + render(); + + // Note should render + const titleInput = await screen.findByDisplayValue('Note 1'); + expect(titleInput).toBeVisible(); + + // Wrap in act() -- the component may update in the background during this. + await act(async () => { + await resourceFetcher().waitForAllFinished(); + + // After opening the note, the resource should be marked for download only in automatic mode + if (downloadMode === 'auto') { + await waitFor(async () => { + expect(await Resource.localState(resource.id)).toMatchObject({ fetch_status: Resource.FETCH_STATUS_DONE }); + }); + } else if (downloadMode === 'manual') { + // In manual mode, should not mark for download + expect(await Resource.localState(resource)).toMatchObject({ fetch_status: Resource.FETCH_STATUS_IDLE }); + } else { + throw new Error(`Should not be testing downloadMode: ${downloadMode}.`); + } + }); + + screen.unmount(); + }); }); diff --git a/packages/app-mobile/components/screens/Note.tsx b/packages/app-mobile/components/screens/Note.tsx index 04800651e60..7dc1079275a 100644 --- a/packages/app-mobile/components/screens/Note.tsx +++ b/packages/app-mobile/components/screens/Note.tsx @@ -493,11 +493,6 @@ class NoteScreenComponent extends BaseScreenComponent implements B this.undoRedoService_ = new UndoRedoService(); this.undoRedoService_.on('stackChange', this.undoRedoService_stackChange); - if (this.state.note && this.state.note.body && Setting.value('sync.resourceDownloadMode') === 'auto') { - const resourceIds = await Note.linkedResourceIds(this.state.note.body); - await ResourceFetcher.instance().markForDownload(resourceIds); - } - // Although it is async, we don't wait for the answer so that if permission // has already been granted, it doesn't slow down opening the note. If it hasn't // been granted, the popup will open anyway. @@ -509,8 +504,12 @@ class NoteScreenComponent extends BaseScreenComponent implements B void ResourceFetcher.instance().markForDownload(event.resourceId); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - public componentDidUpdate(prevProps: any, prevState: any) { + public async markAllAttachedResourcesForDownload() { + const resourceIds = await Note.linkedResourceIds(this.state.note.body); + await ResourceFetcher.instance().markForDownload(resourceIds); + } + + public componentDidUpdate(prevProps: Props, prevState: State) { if (this.doFocusUpdate_) { this.doFocusUpdate_ = false; this.scheduleFocusUpdate(); @@ -528,6 +527,11 @@ class NoteScreenComponent extends BaseScreenComponent implements B void promptRestoreAutosave((drawingData: string) => { void this.attachNewDrawing(drawingData); }); + + // Handle automatic resource downloading + if (this.state.note.body && Setting.value('sync.resourceDownloadMode') === 'auto') { + void this.markAllAttachedResourcesForDownload(); + } } // Disable opening/closing the side menu with touch gestures diff --git a/packages/lib/testing/test-utils.ts b/packages/lib/testing/test-utils.ts index cc5f558ce5d..df2cbaca18b 100644 --- a/packages/lib/testing/test-utils.ts +++ b/packages/lib/testing/test-utils.ts @@ -286,6 +286,7 @@ async function switchClient(id: number, options: any = null) { BaseItem.encryptionService_ = encryptionServices_[id]; Resource.encryptionService_ = encryptionServices_[id]; BaseItem.revisionService_ = revisionServices_[id]; + ResourceFetcher.instance_ = resourceFetchers_[id]; await Setting.reset(); Setting.settingFilename = settingFilename(id); @@ -1125,7 +1126,8 @@ export const runWithFakeTimers = async (callback: ()=> Promise) => { throw new Error('Fake timers are only supported in jest.'); } - jest.useFakeTimers(); + // advanceTimers: true is needed for certain Joplin APIs to work. + jest.useFakeTimers({ advanceTimers: true }); // The shim.setTimeout and similar functions need to be changed to // use fake timers. From 6ffed45deaabd84ca759b0e5a812d3caa43f31b6 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Fri, 27 Sep 2024 16:32:51 -0700 Subject: [PATCH 2/7] Simplify test changes --- packages/app-mobile/components/screens/Note.test.tsx | 6 +++--- packages/lib/testing/test-utils.ts | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/app-mobile/components/screens/Note.test.tsx b/packages/app-mobile/components/screens/Note.test.tsx index 5edabc9eba7..897cb36caf7 100644 --- a/packages/app-mobile/components/screens/Note.test.tsx +++ b/packages/app-mobile/components/screens/Note.test.tsx @@ -7,7 +7,7 @@ import { Provider } from 'react-redux'; import NoteScreen from './Note'; import { MenuProvider } from 'react-native-popup-menu'; -import { setupDatabaseAndSynchronizer, switchClient, simulateReadOnlyShareEnv, waitFor, supportDir, synchronizerStart, resourceFetcher, runWithFakeTimers } from '@joplin/lib/testing/test-utils'; +import { setupDatabaseAndSynchronizer, switchClient, simulateReadOnlyShareEnv, waitFor, supportDir, synchronizerStart, resourceFetcher } from '@joplin/lib/testing/test-utils'; import Note from '@joplin/lib/models/Note'; import { AppState } from '../../utils/types'; import { Store } from 'redux'; @@ -112,12 +112,12 @@ const openNoteActionsMenu = async () => { cursor = cursor.parent; } - await runWithFakeTimers(() => userEvent.press(actionMenuButton)); + await userEvent.press(actionMenuButton); }; const openEditor = async () => { const editButton = await screen.findByLabelText('Edit'); - fireEvent.press(editButton); + await userEvent.press(editButton); }; describe('screens/Note', () => { diff --git a/packages/lib/testing/test-utils.ts b/packages/lib/testing/test-utils.ts index df2cbaca18b..1357b1f7381 100644 --- a/packages/lib/testing/test-utils.ts +++ b/packages/lib/testing/test-utils.ts @@ -1126,8 +1126,7 @@ export const runWithFakeTimers = async (callback: ()=> Promise) => { throw new Error('Fake timers are only supported in jest.'); } - // advanceTimers: true is needed for certain Joplin APIs to work. - jest.useFakeTimers({ advanceTimers: true }); + jest.useFakeTimers(); // The shim.setTimeout and similar functions need to be changed to // use fake timers. From 5c6cbdbd7395c8eb0dffd32352aafa925c1418dc Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Fri, 27 Sep 2024 16:43:12 -0700 Subject: [PATCH 3/7] Add null check for consistency with previous logic --- packages/app-mobile/components/screens/Note.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-mobile/components/screens/Note.tsx b/packages/app-mobile/components/screens/Note.tsx index 7dc1079275a..a5f4253d8d4 100644 --- a/packages/app-mobile/components/screens/Note.tsx +++ b/packages/app-mobile/components/screens/Note.tsx @@ -529,7 +529,7 @@ class NoteScreenComponent extends BaseScreenComponent implements B }); // Handle automatic resource downloading - if (this.state.note.body && Setting.value('sync.resourceDownloadMode') === 'auto') { + if (this.state.note?.body && Setting.value('sync.resourceDownloadMode') === 'auto') { void this.markAllAttachedResourcesForDownload(); } } From c6f75f650ee881b25ac5cea0902f79135ce83e44 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Fri, 27 Sep 2024 16:29:25 -0700 Subject: [PATCH 4/7] Chore: Attempting to fix mobile test CI failures --- .../components/ScreenHeader/Menu.tsx | 1 + .../components/screens/Note.test.tsx | 115 ++++++++++-------- packages/lib/testing/test-utils.ts | 3 +- 3 files changed, 70 insertions(+), 49 deletions(-) diff --git a/packages/app-mobile/components/ScreenHeader/Menu.tsx b/packages/app-mobile/components/ScreenHeader/Menu.tsx index c5f41ed0b15..c51acae3cd6 100644 --- a/packages/app-mobile/components/ScreenHeader/Menu.tsx +++ b/packages/app-mobile/components/ScreenHeader/Menu.tsx @@ -139,6 +139,7 @@ const MenuComponent: React.FC = props => { style={styles.menuContentScroller} aria-modal={true} accessibilityViewIsModal={true} + testID={`menu-content-${refocusCounter ? 'refocusing' : ''}`} >{menuOptionComponents} diff --git a/packages/app-mobile/components/screens/Note.test.tsx b/packages/app-mobile/components/screens/Note.test.tsx index 1997a945951..a91097c4277 100644 --- a/packages/app-mobile/components/screens/Note.test.tsx +++ b/packages/app-mobile/components/screens/Note.test.tsx @@ -1,13 +1,14 @@ import * as React from 'react'; import { describe, it, beforeEach } from '@jest/globals'; -import { act, fireEvent, render, screen, userEvent } from '@testing-library/react-native'; +import { act, fireEvent, render, screen, userEvent, waitFor } from '@testing-library/react-native'; import '@testing-library/jest-native/extend-expect'; import { Provider } from 'react-redux'; import NoteScreen from './Note'; import { MenuProvider } from 'react-native-popup-menu'; -import { setupDatabaseAndSynchronizer, switchClient, simulateReadOnlyShareEnv, waitFor } from '@joplin/lib/testing/test-utils'; +import { setupDatabaseAndSynchronizer, switchClient, simulateReadOnlyShareEnv, runWithFakeTimers } from '@joplin/lib/testing/test-utils'; +import { waitFor as waitForWithRealTimers } from '@joplin/lib/testing/test-utils'; import Note from '@joplin/lib/models/Note'; import { AppState } from '../../utils/types'; import { Store } from 'redux'; @@ -61,18 +62,14 @@ const getNoteEditorControl = async () => { }; const waitForNoteToMatch = async (noteId: string, note: Partial) => { - await act(() => waitFor(async () => { + await act(() => waitForWithRealTimers(async () => { const loadedNote = await Note.load(noteId); expect(loadedNote).toMatchObject(note); })); }; -const openNewNote = async (noteProperties: NoteEntity) => { - const note = await Note.save({ - parent_id: (await Folder.defaultFolder()).id, - ...noteProperties, - }); - +const openExistingNote = async (noteId: string) => { + const note = await Note.load(noteId); const displayParentId = getDisplayParentId(note, await Folder.load(note.parent_id)); store.dispatch({ @@ -85,7 +82,15 @@ const openNewNote = async (noteProperties: NoteEntity) => { id: note.id, folderId: displayParentId, }); +}; + +const openNewNote = async (noteProperties: NoteEntity) => { + const note = await Note.save({ + parent_id: (await Folder.defaultFolder()).id, + ...noteProperties, + }); + await openExistingNote(note.id); await waitForNoteToMatch(note.id, { parent_id: note.parent_id, title: note.title, body: note.body }); return note.id; @@ -106,20 +111,31 @@ const openNoteActionsMenu = async () => { cursor = cursor.parent; } - await userEvent.press(actionMenuButton); + // Wrap in act(...) -- this tells the test library that component state is intended to update (prevents + // warnings). + await act(async () => { + await runWithFakeTimers(async () => { + await userEvent.press(actionMenuButton); + }); + + // State can update until the menu content is marked as in the process of refocusing (part of the + // menu transition). + await waitFor(async () => { + expect(await screen.findByTestId('menu-content-refocusing')).toBeVisible(); + }); + }); }; const openEditor = async () => { const editButton = await screen.findByLabelText('Edit'); - await userEvent.press(editButton); -}; -describe('screens/Note', () => { - beforeAll(() => { - // advanceTimers: Needed by internal note save logic - jest.useFakeTimers({ advanceTimers: true }); + fireEvent.press(editButton); + await waitFor(() => { + expect(screen.queryByLabelText('Edit')).toBeNull(); }); +}; +describe('screens/Note', () => { beforeEach(async () => { await setupDatabaseAndSynchronizer(0); await switchClient(0); @@ -160,20 +176,23 @@ describe('screens/Note', () => { await waitForNoteToMatch(noteId, { title: 'New title', body: 'Unchanged body' }); - let expectedTitle = 'New title'; - for (let i = 0; i <= 10; i++) { - for (const chunk of ['!', ' test', '!!!', ' Testing']) { - jest.advanceTimersByTime(i % 5); - await user.type(titleInput, chunk); - expectedTitle += chunk; - - // Don't verify after each input event -- this allows the save action queue to fill. - if (i % 4 === 0) { - await waitForNoteToMatch(noteId, { title: expectedTitle }); + // Use fake timers to allow advancing timers without pausing the test + await runWithFakeTimers(async () => { + let expectedTitle = 'New title'; + for (let i = 0; i <= 10; i++) { + for (const chunk of ['!', ' test', '!!!', ' Testing']) { + jest.advanceTimersByTime(i % 5); + await user.type(titleInput, chunk); + expectedTitle += chunk; + + // Don't verify after each input event -- this allows the save action queue to fill. + if (i % 4 === 0) { + await waitForNoteToMatch(noteId, { title: expectedTitle }); + } } + await waitForNoteToMatch(noteId, { title: expectedTitle }); } - await waitForNoteToMatch(noteId, { title: expectedTitle }); - } + }); }); it('changing the note body in the editor should update the note\'s body', async () => { @@ -181,27 +200,27 @@ describe('screens/Note', () => { const noteId = await openNewNote({ title: 'Unchanged title', body: defaultBody }); const noteScreen = render(); + await act(async () => await runWithFakeTimers(async () => { + await openEditor(); + const editor = await getNoteEditorControl(); + editor.select(defaultBody.length, defaultBody.length); - await openEditor(); - const editor = await getNoteEditorControl(); - editor.select(defaultBody.length, defaultBody.length); + editor.insertText(' Testing!!!'); + await waitForNoteToMatch(noteId, { body: 'Change me! Testing!!!' }); - editor.insertText(' Testing!!!'); - await waitForNoteToMatch(noteId, { body: 'Change me! Testing!!!' }); + editor.insertText(' This is a test.'); + await waitForNoteToMatch(noteId, { body: 'Change me! Testing!!! This is a test.' }); - editor.insertText(' This is a test.'); - await waitForNoteToMatch(noteId, { body: 'Change me! Testing!!! This is a test.' }); + // should also save changes made shortly before unmounting + editor.insertText(' Test!'); - // should also save changes made shortly before unmounting - editor.insertText(' Test!'); - - // TODO: Decreasing this below 100 causes the test to fail. - // See issue #11125. - await jest.advanceTimersByTimeAsync(150); - - noteScreen.unmount(); - await waitForNoteToMatch(noteId, { body: 'Change me! Testing!!! This is a test. Test!' }); + // TODO: Decreasing this below 100 causes the test to fail. + // See issue #11125. + await jest.advanceTimersByTimeAsync(450); + noteScreen.unmount(); + await waitForNoteToMatch(noteId, { body: 'Change me! Testing!!! This is a test. Test!' }); + })); }); it('pressing "delete" should move the note to the trash', async () => { @@ -212,9 +231,9 @@ describe('screens/Note', () => { const deleteButton = await screen.findByText('Delete'); fireEvent.press(deleteButton); - await act(() => waitFor(async () => { + await waitFor(async () => { expect((await Note.load(noteId)).deleted_time).toBeGreaterThan(0); - })); + }); }); it('pressing "delete permanently" should permanently delete a note', async () => { @@ -229,9 +248,9 @@ describe('screens/Note', () => { const deleteButton = await screen.findByText('Permanently delete note'); fireEvent.press(deleteButton); - await act(() => waitFor(async () => { + await waitFor(async () => { expect(await Note.load(noteId)).toBeUndefined(); - })); + }); expect(shim.showMessageBox).toHaveBeenCalled(); }); diff --git a/packages/lib/testing/test-utils.ts b/packages/lib/testing/test-utils.ts index cc5f558ce5d..da0bdef06a8 100644 --- a/packages/lib/testing/test-utils.ts +++ b/packages/lib/testing/test-utils.ts @@ -1125,7 +1125,8 @@ export const runWithFakeTimers = async (callback: ()=> Promise) => { throw new Error('Fake timers are only supported in jest.'); } - jest.useFakeTimers(); + // advanceTimers: Needed by Joplin's database driver + jest.useFakeTimers({ advanceTimers: true }); // The shim.setTimeout and similar functions need to be changed to // use fake timers. From f0956d8cac6b812907265bfd70fe59596ec312c0 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Fri, 27 Sep 2024 20:42:52 -0700 Subject: [PATCH 5/7] Revert: Break openNewNote into two functions --- .../app-mobile/components/screens/Note.test.tsx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/app-mobile/components/screens/Note.test.tsx b/packages/app-mobile/components/screens/Note.test.tsx index a91097c4277..a903803c513 100644 --- a/packages/app-mobile/components/screens/Note.test.tsx +++ b/packages/app-mobile/components/screens/Note.test.tsx @@ -68,8 +68,11 @@ const waitForNoteToMatch = async (noteId: string, note: Partial) => })); }; -const openExistingNote = async (noteId: string) => { - const note = await Note.load(noteId); +const openNewNote = async (noteProperties: NoteEntity) => { + const note = await Note.save({ + parent_id: (await Folder.defaultFolder()).id, + ...noteProperties, + }); const displayParentId = getDisplayParentId(note, await Folder.load(note.parent_id)); store.dispatch({ @@ -82,15 +85,7 @@ const openExistingNote = async (noteId: string) => { id: note.id, folderId: displayParentId, }); -}; - -const openNewNote = async (noteProperties: NoteEntity) => { - const note = await Note.save({ - parent_id: (await Folder.defaultFolder()).id, - ...noteProperties, - }); - await openExistingNote(note.id); await waitForNoteToMatch(note.id, { parent_id: note.parent_id, title: note.title, body: note.body }); return note.id; From d598b097d212dfb2c824dd097e6349d980d16c24 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Fri, 27 Sep 2024 20:56:16 -0700 Subject: [PATCH 6/7] Clean up post-merge --- packages/app-mobile/components/screens/Note.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-mobile/components/screens/Note.test.tsx b/packages/app-mobile/components/screens/Note.test.tsx index 5961fc4e80a..3016d8bf839 100644 --- a/packages/app-mobile/components/screens/Note.test.tsx +++ b/packages/app-mobile/components/screens/Note.test.tsx @@ -7,7 +7,7 @@ import { Provider } from 'react-redux'; import NoteScreen from './Note'; import { MenuProvider } from 'react-native-popup-menu'; -import { setupDatabaseAndSynchronizer, switchClient, simulateReadOnlyShareEnv, supportDir, synchronizerStart, resourceFetcher } from '@joplin/lib/testing/test-utils'; +import { setupDatabaseAndSynchronizer, switchClient, simulateReadOnlyShareEnv, supportDir, synchronizerStart, resourceFetcher, runWithFakeTimers } from '@joplin/lib/testing/test-utils'; import { waitFor as waitForWithRealTimers } from '@joplin/lib/testing/test-utils'; import Note from '@joplin/lib/models/Note'; import { AppState } from '../../utils/types'; From 27747e95d96591f270ab01b178f7b65538bd261d Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Sat, 28 Sep 2024 09:20:07 -0700 Subject: [PATCH 7/7] Remove test logic that should be unnecessary post-merge --- packages/app-mobile/components/screens/Note.test.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/app-mobile/components/screens/Note.test.tsx b/packages/app-mobile/components/screens/Note.test.tsx index 3016d8bf839..1de9266c000 100644 --- a/packages/app-mobile/components/screens/Note.test.tsx +++ b/packages/app-mobile/components/screens/Note.test.tsx @@ -292,9 +292,6 @@ describe('screens/Note', () => { 'auto', 'manual', ])('should correctly auto-download or not auto-download resources in %j mode', async (downloadMode) => { - // This test interacts with the synchronizer, which seems to need real timers. - jest.useRealTimers(); - let note = await Note.save({ title: 'Note 1', parent_id: (await Folder.defaultFolder()).id }); note = await shim.attachFileToNote(note, `${supportDir}/photo.jpg`); @@ -333,7 +330,5 @@ describe('screens/Note', () => { throw new Error(`Should not be testing downloadMode: ${downloadMode}.`); } }); - - screen.unmount(); }); });