-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RNMobile] Refactor react-native-editor
initialization test
#37955
Changes from 4 commits
e8d809f
ae0aa9f
b1964b5
4e08331
78f61bb
493e054
c89ed9d
d24cd67
f43de21
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,23 +2,24 @@ | |
* External dependencies | ||
*/ | ||
import { AppRegistry } from 'react-native'; | ||
import { render, waitFor } from 'test/helpers'; | ||
import { act, render } from 'test/helpers'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; | ||
import * as wpHooks from '@wordpress/hooks'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { registerGutenberg } from '..'; | ||
import { registerGutenberg, initialHtmlGutenberg } from '..'; | ||
import setupLocale from '../setup-locale'; | ||
|
||
jest.mock( 'react-native/Libraries/ReactNative/AppRegistry' ); | ||
jest.mock( '../setup-locale' ); | ||
|
||
const initGutenberg = ( registerParams ) => { | ||
const initGutenberg = ( registerParams, editorProps ) => { | ||
let EditorComponent; | ||
AppRegistry.registerComponent.mockImplementation( | ||
( name, componentProvider ) => { | ||
|
@@ -27,158 +28,218 @@ const initGutenberg = ( registerParams ) => { | |
); | ||
registerGutenberg( registerParams ); | ||
|
||
return render( <EditorComponent /> ); | ||
let screen; | ||
// This guarantees that setup module is imported on every test, as it's imported upon Editor component rendering. | ||
jest.isolateModules( () => { | ||
screen = render( <EditorComponent { ...editorProps } /> ); | ||
} ); | ||
|
||
return screen; | ||
}; | ||
|
||
describe( 'Register Gutenberg', () => { | ||
beforeEach( () => { | ||
// We need to reset modules to guarantee that setup module is imported on every test. | ||
jest.resetModules(); | ||
} ); | ||
Comment on lines
-34
to
-37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is no longer necessary as the setup module import is guaranteed by using the |
||
|
||
it( 'registers Gutenberg editor component', () => { | ||
registerGutenberg(); | ||
expect( AppRegistry.registerComponent ).toHaveBeenCalled(); | ||
} ); | ||
|
||
it( 'sets up locale before editor is initialized', () => { | ||
const mockOnModuleImported = jest.fn(); | ||
jest.mock( '../setup', () => { | ||
// To determine if the setup module is imported, we create a mock function that is called when the module is mocked. | ||
mockOnModuleImported(); | ||
|
||
return { | ||
__esModule: true, | ||
default: jest.fn().mockReturnValue( <></> ), | ||
}; | ||
describe( 'check calling order of hooks and callbacks', () => { | ||
afterAll( () => { | ||
// Revert setup mock to assure that rest of tests use original implementation. | ||
jest.unmock( '../setup' ); | ||
} ); | ||
|
||
initGutenberg(); | ||
|
||
// "invocationCallOrder" can be used to compare call orders between different mocks. | ||
// Reference: https://git.io/JyBk0 | ||
const setupLocaleCallOrder = setupLocale.mock.invocationCallOrder[ 0 ]; | ||
const onSetupImportedCallOrder = | ||
mockOnModuleImported.mock.invocationCallOrder[ 0 ]; | ||
it( 'sets up locale before editor is initialized', () => { | ||
const mockOnModuleImported = jest.fn(); | ||
jest.mock( '../setup', () => { | ||
// To determine if the setup module is imported, we create a mock function that is called when the module is mocked. | ||
mockOnModuleImported(); | ||
|
||
return { | ||
__esModule: true, | ||
default: jest.fn().mockReturnValue( <></> ), | ||
}; | ||
} ); | ||
|
||
initGutenberg(); | ||
|
||
// "invocationCallOrder" can be used to compare call orders between different mocks. | ||
// Reference: https://git.io/JyBk0 | ||
const setupLocaleCallOrder = | ||
setupLocale.mock.invocationCallOrder[ 0 ]; | ||
const onSetupImportedCallOrder = | ||
mockOnModuleImported.mock.invocationCallOrder[ 0 ]; | ||
|
||
expect( setupLocaleCallOrder ).toBeLessThan( | ||
onSetupImportedCallOrder | ||
); | ||
} ); | ||
|
||
expect( setupLocaleCallOrder ).toBeLessThan( onSetupImportedCallOrder ); | ||
} ); | ||
it( 'beforeInit callback is invoked before the editor is initialized', () => { | ||
const beforeInitCallback = jest.fn(); | ||
const mockOnModuleImported = jest.fn(); | ||
jest.mock( '../setup', () => { | ||
// To determine if the setup module is imported, we create a mock function that is called when the module is mocked. | ||
mockOnModuleImported(); | ||
|
||
return { | ||
__esModule: true, | ||
default: jest.fn().mockReturnValue( <></> ), | ||
}; | ||
} ); | ||
|
||
initGutenberg( { beforeInitCallback } ); | ||
|
||
// "invocationCallOrder" can be used to compare call orders between different mocks. | ||
// Reference: https://git.io/JyBk0 | ||
const beforeInitCallOrder = | ||
beforeInitCallback.mock.invocationCallOrder[ 0 ]; | ||
const onSetupImportedCallOrder = | ||
mockOnModuleImported.mock.invocationCallOrder[ 0 ]; | ||
|
||
expect( beforeInitCallOrder ).toBeLessThan( | ||
onSetupImportedCallOrder | ||
); | ||
} ); | ||
|
||
it( 'beforeInit callback is invoked before the editor is initialized', () => { | ||
const beforeInitCallback = jest.fn(); | ||
const mockOnModuleImported = jest.fn(); | ||
jest.mock( '../setup', () => { | ||
// To determine if the setup module is imported, we create a mock function that is called when the module is mocked. | ||
mockOnModuleImported(); | ||
it( 'dispatches "native.pre-render" hook before the editor is rendered', () => { | ||
const doAction = jest.spyOn( wpHooks, 'doAction' ); | ||
|
||
return { | ||
__esModule: true, | ||
default: jest.fn().mockReturnValue( <></> ), | ||
// An empty component is provided in order to listen for render calls of the editor component. | ||
const onRenderEditor = jest.fn(); | ||
const EditorComponent = () => { | ||
onRenderEditor(); | ||
return null; | ||
}; | ||
jest.mock( '../setup', () => ( { | ||
__esModule: true, | ||
default: jest.fn().mockReturnValue( <EditorComponent /> ), | ||
} ) ); | ||
|
||
initGutenberg(); | ||
|
||
const hookCallIndex = 0; | ||
// "invocationCallOrder" can be used to compare call orders between different mocks. | ||
// Reference: https://git.io/JyBk0 | ||
const hookCallOrder = | ||
doAction.mock.invocationCallOrder[ hookCallIndex ]; | ||
const onRenderEditorCallOrder = | ||
onRenderEditor.mock.invocationCallOrder[ 0 ]; | ||
const hookName = doAction.mock.calls[ hookCallIndex ][ 0 ]; | ||
|
||
expect( hookName ).toBe( 'native.pre-render' ); | ||
expect( hookCallOrder ).toBeLessThan( onRenderEditorCallOrder ); | ||
} ); | ||
|
||
initGutenberg( { beforeInitCallback } ); | ||
it( 'dispatches "native.block_editor_props" hook before the editor is rendered', () => { | ||
const applyFilters = jest.spyOn( wpHooks, 'applyFilters' ); | ||
|
||
// "invocationCallOrder" can be used to compare call orders between different mocks. | ||
// Reference: https://git.io/JyBk0 | ||
const beforeInitCallOrder = | ||
beforeInitCallback.mock.invocationCallOrder[ 0 ]; | ||
const onSetupImportedCallOrder = | ||
mockOnModuleImported.mock.invocationCallOrder[ 0 ]; | ||
// An empty component is provided in order to listen for render calls of the editor component. | ||
const onRenderEditor = jest.fn(); | ||
const EditorComponent = () => { | ||
onRenderEditor(); | ||
return null; | ||
}; | ||
jest.mock( '../setup', () => ( { | ||
__esModule: true, | ||
default: jest.fn().mockReturnValue( <EditorComponent /> ), | ||
} ) ); | ||
|
||
initGutenberg(); | ||
|
||
const hookCallIndex = 0; | ||
// "invocationCallOrder" can be used to compare call orders between different mocks. | ||
// Reference: https://git.io/JyBk0 | ||
const hookCallOrder = | ||
applyFilters.mock.invocationCallOrder[ hookCallIndex ]; | ||
const onRenderEditorCallOrder = | ||
onRenderEditor.mock.invocationCallOrder[ 0 ]; | ||
const hookName = applyFilters.mock.calls[ hookCallIndex ][ 0 ]; | ||
|
||
expect( hookName ).toBe( 'native.block_editor_props' ); | ||
expect( hookCallOrder ).toBeLessThan( onRenderEditorCallOrder ); | ||
} ); | ||
|
||
expect( beforeInitCallOrder ).toBeLessThan( onSetupImportedCallOrder ); | ||
} ); | ||
it( 'dispatches "native.render" hook after the editor is rendered', () => { | ||
const doAction = jest.spyOn( wpHooks, 'doAction' ); | ||
|
||
it( 'dispatches "native.pre-render" hook before the editor is rendered', () => { | ||
const doAction = jest.spyOn( wpHooks, 'doAction' ); | ||
|
||
// An empty component is provided in order to listen for render calls of the editor component. | ||
const onRenderEditor = jest.fn(); | ||
const EditorComponent = () => { | ||
onRenderEditor(); | ||
return null; | ||
}; | ||
jest.mock( '../setup', () => ( { | ||
__esModule: true, | ||
default: jest.fn().mockReturnValue( <EditorComponent /> ), | ||
} ) ); | ||
|
||
initGutenberg(); | ||
|
||
const hookCallIndex = 0; | ||
// "invocationCallOrder" can be used to compare call orders between different mocks. | ||
// Reference: https://git.io/JyBk0 | ||
const hookCallOrder = | ||
doAction.mock.invocationCallOrder[ hookCallIndex ]; | ||
const onRenderEditorCallOrder = | ||
onRenderEditor.mock.invocationCallOrder[ 0 ]; | ||
const hookName = doAction.mock.calls[ hookCallIndex ][ 0 ]; | ||
|
||
expect( hookName ).toBe( 'native.pre-render' ); | ||
expect( hookCallOrder ).toBeLessThan( onRenderEditorCallOrder ); | ||
// An empty component is provided in order to listen for render calls of the editor component. | ||
const onRenderEditor = jest.fn(); | ||
const EditorComponent = () => { | ||
onRenderEditor(); | ||
return null; | ||
}; | ||
jest.mock( '../setup', () => ( { | ||
__esModule: true, | ||
default: jest.fn().mockReturnValue( <EditorComponent /> ), | ||
} ) ); | ||
|
||
initGutenberg(); | ||
|
||
const hookCallIndex = 1; | ||
// "invocationCallOrder" can be used to compare call orders between different mocks. | ||
// Reference: https://git.io/JyBk0 | ||
const hookCallOrder = | ||
doAction.mock.invocationCallOrder[ hookCallIndex ]; | ||
const onRenderEditorCallOrder = | ||
onRenderEditor.mock.invocationCallOrder[ 0 ]; | ||
const hookName = doAction.mock.calls[ hookCallIndex ][ 0 ]; | ||
|
||
expect( hookName ).toBe( 'native.render' ); | ||
expect( hookCallOrder ).toBeGreaterThan( onRenderEditorCallOrder ); | ||
} ); | ||
} ); | ||
|
||
it( 'dispatches "native.block_editor_props" hook before the editor is rendered', () => { | ||
const applyFilters = jest.spyOn( wpHooks, 'applyFilters' ); | ||
|
||
// An empty component is provided in order to listen for render calls of the editor component. | ||
const onRenderEditor = jest.fn(); | ||
const EditorComponent = () => { | ||
onRenderEditor(); | ||
return null; | ||
}; | ||
jest.mock( '../setup', () => ( { | ||
__esModule: true, | ||
default: jest.fn().mockReturnValue( <EditorComponent /> ), | ||
} ) ); | ||
|
||
initGutenberg(); | ||
|
||
const hookCallIndex = 0; | ||
// "invocationCallOrder" can be used to compare call orders between different mocks. | ||
// Reference: https://git.io/JyBk0 | ||
const hookCallOrder = | ||
applyFilters.mock.invocationCallOrder[ hookCallIndex ]; | ||
const onRenderEditorCallOrder = | ||
onRenderEditor.mock.invocationCallOrder[ 0 ]; | ||
const hookName = applyFilters.mock.calls[ hookCallIndex ][ 0 ]; | ||
|
||
expect( hookName ).toBe( 'native.block_editor_props' ); | ||
expect( hookCallOrder ).toBeLessThan( onRenderEditorCallOrder ); | ||
} ); | ||
describe( 'editor initialization', () => { | ||
beforeEach( () => { | ||
// Setup already registers blocks so we need assure that no blocks are registered before the test. | ||
getBlockTypes().forEach( ( block ) => { | ||
unregisterBlockType( block.name ); | ||
} ); | ||
} ); | ||
|
||
it( 'dispatches "native.render" hook after the editor is rendered', () => { | ||
const doAction = jest.spyOn( wpHooks, 'doAction' ); | ||
|
||
// An empty component is provided in order to listen for render calls of the editor component. | ||
const onRenderEditor = jest.fn(); | ||
const EditorComponent = () => { | ||
onRenderEditor(); | ||
return null; | ||
}; | ||
jest.mock( '../setup', () => ( { | ||
__esModule: true, | ||
default: jest.fn().mockReturnValue( <EditorComponent /> ), | ||
} ) ); | ||
|
||
initGutenberg(); | ||
|
||
const hookCallIndex = 1; | ||
// "invocationCallOrder" can be used to compare call orders between different mocks. | ||
// Reference: https://git.io/JyBk0 | ||
const hookCallOrder = | ||
doAction.mock.invocationCallOrder[ hookCallIndex ]; | ||
const onRenderEditorCallOrder = | ||
onRenderEditor.mock.invocationCallOrder[ 0 ]; | ||
const hookName = doAction.mock.calls[ hookCallIndex ][ 0 ]; | ||
|
||
expect( hookName ).toBe( 'native.render' ); | ||
expect( hookCallOrder ).toBeGreaterThan( onRenderEditorCallOrder ); | ||
} ); | ||
it( 'initializes the editor with empty HTML', async () => { | ||
const consoleLog = jest | ||
.spyOn( console, 'log' ) | ||
.mockImplementation( jest.fn() ); | ||
|
||
const { getByTestId } = initGutenberg( {}, { initialData: '' } ); | ||
// Some of the store updates that happen upon editor initialization are executed at the end of the current | ||
// Javascript block execution and after the test is finished. In order to prevent "act" warnings due to | ||
// this behavior, we wait for the execution block to be finished before acting on the test. | ||
await act( | ||
() => new Promise( ( resolve ) => setImmediate( resolve ) ) | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we don't wait for the JS block to be finished, we get a bunch of
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leaving a note that we are actively investigating this issue and approach further in #38052. We should likely retroactively apply whatever global solution we find there to this test. |
||
const blockList = getByTestId( 'block-list-wrapper' ); | ||
|
||
expect( blockList ).toHaveProperty( 'type', 'View' ); | ||
expect( consoleLog ).toHaveBeenCalledWith( 'Hermes is: true' ); | ||
} ); | ||
|
||
it( 'initializes the editor', () => { | ||
const { getByTestId } = initGutenberg(); | ||
const blockList = waitFor( () => getByTestId( 'block-list-wrapper' ) ); | ||
expect( blockList ).toBeDefined(); | ||
it( 'initializes the editor with initial HTML', async () => { | ||
const consoleLog = jest | ||
.spyOn( console, 'log' ) | ||
.mockImplementation( jest.fn() ); | ||
const consoleInfo = jest | ||
.spyOn( console, 'info' ) | ||
.mockImplementation( jest.fn() ); | ||
|
||
const { getByTestId } = initGutenberg( | ||
{}, | ||
{ initialData: initialHtmlGutenberg } | ||
); | ||
// Some of the store updates that happen upon editor initialization are executed at the end of the current | ||
// Javascript block execution and after the test is finished. In order to prevent "act" warnings due to | ||
// this behavior, we wait for the execution block to be finished before acting on the test. | ||
await act( | ||
() => new Promise( ( resolve ) => setImmediate( resolve ) ) | ||
); | ||
const blockList = getByTestId( 'block-list-wrapper' ); | ||
|
||
expect( blockList ).toHaveProperty( 'type', 'View' ); | ||
fluiddot marked this conversation as resolved.
Show resolved
Hide resolved
|
||
expect( consoleLog ).toHaveBeenCalledWith( 'Hermes is: true' ); | ||
// It's expected that some blocks are upgraded and inform about it (example: "Updated Block: core/cover") | ||
expect( consoleInfo ).toHaveBeenCalled(); | ||
} ); | ||
} ); | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this approach, we no longer need to reset modules for testing the call order of hooks and callbacks. Besides, I found out that resetting modules was breaking the test cases that render the editor.