mirror of
https://github.com/laurent22/joplin.git
synced 2025-04-14 11:18:47 +02:00
Chore: Reduce mobile note screen test flakiness (#11145)
This commit is contained in:
parent
916b3f6f69
commit
5fceb5a3c9
@ -139,6 +139,7 @@ const MenuComponent: React.FC<Props> = props => {
|
|||||||
style={styles.menuContentScroller}
|
style={styles.menuContentScroller}
|
||||||
aria-modal={true}
|
aria-modal={true}
|
||||||
accessibilityViewIsModal={true}
|
accessibilityViewIsModal={true}
|
||||||
|
testID={`menu-content-${refocusCounter ? 'refocusing' : ''}`}
|
||||||
>{menuOptionComponents}</ScrollView>
|
>{menuOptionComponents}</ScrollView>
|
||||||
</MenuOptions>
|
</MenuOptions>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { describe, it, beforeEach } from '@jest/globals';
|
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 '@testing-library/jest-native/extend-expect';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import NoteScreen from './Note';
|
import NoteScreen from './Note';
|
||||||
import { MenuProvider } from 'react-native-popup-menu';
|
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 Note from '@joplin/lib/models/Note';
|
||||||
import { AppState } from '../../utils/types';
|
import { AppState } from '../../utils/types';
|
||||||
import { Store } from 'redux';
|
import { Store } from 'redux';
|
||||||
@ -61,7 +62,7 @@ const getNoteEditorControl = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const waitForNoteToMatch = async (noteId: string, note: Partial<NoteEntity>) => {
|
const waitForNoteToMatch = async (noteId: string, note: Partial<NoteEntity>) => {
|
||||||
await act(() => waitFor(async () => {
|
await act(() => waitForWithRealTimers(async () => {
|
||||||
const loadedNote = await Note.load(noteId);
|
const loadedNote = await Note.load(noteId);
|
||||||
expect(loadedNote).toMatchObject(note);
|
expect(loadedNote).toMatchObject(note);
|
||||||
}));
|
}));
|
||||||
@ -72,7 +73,6 @@ const openNewNote = async (noteProperties: NoteEntity) => {
|
|||||||
parent_id: (await Folder.defaultFolder()).id,
|
parent_id: (await Folder.defaultFolder()).id,
|
||||||
...noteProperties,
|
...noteProperties,
|
||||||
});
|
});
|
||||||
|
|
||||||
const displayParentId = getDisplayParentId(note, await Folder.load(note.parent_id));
|
const displayParentId = getDisplayParentId(note, await Folder.load(note.parent_id));
|
||||||
|
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
@ -106,20 +106,31 @@ const openNoteActionsMenu = async () => {
|
|||||||
cursor = cursor.parent;
|
cursor = cursor.parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
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 openEditor = async () => {
|
||||||
const editButton = await screen.findByLabelText('Edit');
|
const editButton = await screen.findByLabelText('Edit');
|
||||||
await userEvent.press(editButton);
|
|
||||||
|
fireEvent.press(editButton);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByLabelText('Edit')).toBeNull();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('screens/Note', () => {
|
describe('screens/Note', () => {
|
||||||
beforeAll(() => {
|
|
||||||
// advanceTimers: Needed by internal note save logic
|
|
||||||
jest.useFakeTimers({ advanceTimers: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await setupDatabaseAndSynchronizer(0);
|
await setupDatabaseAndSynchronizer(0);
|
||||||
await switchClient(0);
|
await switchClient(0);
|
||||||
@ -160,6 +171,8 @@ describe('screens/Note', () => {
|
|||||||
|
|
||||||
await waitForNoteToMatch(noteId, { title: 'New title', body: 'Unchanged body' });
|
await waitForNoteToMatch(noteId, { title: 'New title', body: 'Unchanged body' });
|
||||||
|
|
||||||
|
// Use fake timers to allow advancing timers without pausing the test
|
||||||
|
await runWithFakeTimers(async () => {
|
||||||
let expectedTitle = 'New title';
|
let expectedTitle = 'New title';
|
||||||
for (let i = 0; i <= 10; i++) {
|
for (let i = 0; i <= 10; i++) {
|
||||||
for (const chunk of ['!', ' test', '!!!', ' Testing']) {
|
for (const chunk of ['!', ' test', '!!!', ' Testing']) {
|
||||||
@ -175,13 +188,14 @@ describe('screens/Note', () => {
|
|||||||
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 () => {
|
it('changing the note body in the editor should update the note\'s body', async () => {
|
||||||
const defaultBody = 'Change me!';
|
const defaultBody = 'Change me!';
|
||||||
const noteId = await openNewNote({ title: 'Unchanged title', body: defaultBody });
|
const noteId = await openNewNote({ title: 'Unchanged title', body: defaultBody });
|
||||||
|
|
||||||
const noteScreen = render(<WrappedNoteScreen />);
|
const noteScreen = render(<WrappedNoteScreen />);
|
||||||
|
await act(async () => await runWithFakeTimers(async () => {
|
||||||
await openEditor();
|
await openEditor();
|
||||||
const editor = await getNoteEditorControl();
|
const editor = await getNoteEditorControl();
|
||||||
editor.select(defaultBody.length, defaultBody.length);
|
editor.select(defaultBody.length, defaultBody.length);
|
||||||
@ -197,11 +211,11 @@ describe('screens/Note', () => {
|
|||||||
|
|
||||||
// TODO: Decreasing this below 100 causes the test to fail.
|
// TODO: Decreasing this below 100 causes the test to fail.
|
||||||
// See issue #11125.
|
// See issue #11125.
|
||||||
await jest.advanceTimersByTimeAsync(150);
|
await jest.advanceTimersByTimeAsync(450);
|
||||||
|
|
||||||
noteScreen.unmount();
|
noteScreen.unmount();
|
||||||
await waitForNoteToMatch(noteId, { body: 'Change me! Testing!!! This is a test. Test!' });
|
await waitForNoteToMatch(noteId, { body: 'Change me! Testing!!! This is a test. Test!' });
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pressing "delete" should move the note to the trash', async () => {
|
it('pressing "delete" should move the note to the trash', async () => {
|
||||||
@ -212,9 +226,9 @@ describe('screens/Note', () => {
|
|||||||
const deleteButton = await screen.findByText('Delete');
|
const deleteButton = await screen.findByText('Delete');
|
||||||
fireEvent.press(deleteButton);
|
fireEvent.press(deleteButton);
|
||||||
|
|
||||||
await act(() => waitFor(async () => {
|
await waitFor(async () => {
|
||||||
expect((await Note.load(noteId)).deleted_time).toBeGreaterThan(0);
|
expect((await Note.load(noteId)).deleted_time).toBeGreaterThan(0);
|
||||||
}));
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pressing "delete permanently" should permanently delete a note', async () => {
|
it('pressing "delete permanently" should permanently delete a note', async () => {
|
||||||
@ -229,9 +243,9 @@ describe('screens/Note', () => {
|
|||||||
const deleteButton = await screen.findByText('Permanently delete note');
|
const deleteButton = await screen.findByText('Permanently delete note');
|
||||||
fireEvent.press(deleteButton);
|
fireEvent.press(deleteButton);
|
||||||
|
|
||||||
await act(() => waitFor(async () => {
|
await waitFor(async () => {
|
||||||
expect(await Note.load(noteId)).toBeUndefined();
|
expect(await Note.load(noteId)).toBeUndefined();
|
||||||
}));
|
});
|
||||||
expect(shim.showMessageBox).toHaveBeenCalled();
|
expect(shim.showMessageBox).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1125,7 +1125,8 @@ export const runWithFakeTimers = async (callback: ()=> Promise<void>) => {
|
|||||||
throw new Error('Fake timers are only supported in jest.');
|
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
|
// The shim.setTimeout and similar functions need to be changed to
|
||||||
// use fake timers.
|
// use fake timers.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user