Editor: Update the store to use Core Data entities.#16932
Conversation
youknowriad
left a comment
There was a problem hiding this comment.
Quickly looking at the code, it looks great. I love how this simplifies a lot of things. The different responsibilities become way clearer.
Do you know why the tests are failing? I feel like tests failing in big refactorings can have a lot of impact so I'd prefer to wait before doing a deeper review.
No idea, but it looks pretty scary. I'll see what's going on and ping you when I know more. |
49c7aa8 to
56d0e9e
Compare
fefcb29 to
bfee37a
Compare
There was a problem hiding this comment.
It's great to see this refactoring happening. We will benefit from it immensely in the long run.
I have some concerns with regards to unit tests. They are essential for this part of the application in the context of maintenance. This is a very complex code and only a good set of unit and e2e tests can help us ensure it's easier to update for a wider group of contributors. In some places, I'm not 100% we that removed tests shouldn't be replaced with their corresponding version translated to the new conditions. However, with the huge number of lines updated in this PR it's hard to tell whether I make correct assumptions.
/cc @hypest and @koke - it feels like this PR should be validated against mobile app to ensure it doesn't break there.
packages/editor/src/store/actions.js
Outdated
| * | ||
| * @return {string} The blocks serialization. | ||
| */ | ||
| export const serializeBlocks = memoize( |
There was a problem hiding this comment.
Should this helper be exposed as part of the public API?
There are no corresponding unit tests so it doesn't seem like it's used outside of this file.
There was a problem hiding this comment.
It's used in tests and a selector as a fallback. It gets tested indirectly in a lot of places that match the post content.
There was a problem hiding this comment.
Anything exported in this file gets exported as a store action. It could be moved to a utils file if needed or something like that.
|
|
||
| const selectors = { ..._selectors }; | ||
| const selectorNames = Object.keys( selectors ); | ||
| selectorNames.forEach( ( name ) => { |
There was a problem hiding this comment.
Looking at this custom logic which replaces the production code, I'm wondering whether all the tests in the current form are still valuable. I don't think that having 100-ish lines of complex logic is something we want to maintain in the long run.
Have you considered any alternatives? Why did you decide to take this approach?
There was a problem hiding this comment.
It's the same registry selector mocking that was being done for isEditedPostAutosaveable, but in a general way without having to inline it into every test case.
| saving: { | ||
| requesting: true, | ||
| }, | ||
| getCurrentUser() {}, |
There was a problem hiding this comment.
Why those functions are part of the state?
There was a problem hiding this comment.
It's part of the registry selector mocking done above. These parts don't have a legacy editor store counterpart like saving does. So, I just inlined a mock getter instead of making up a piece of state which could be confusing. This sort of "remembers" that difference.
| title: 'sassel', | ||
| }, | ||
| saving: {}, | ||
| getCurrentUser() {}, |
There was a problem hiding this comment.
It looks like those functions are added here only to satisfy custom implementation of selectors. I think we should explore some alternatives to make those tests easier to read.
There was a problem hiding this comment.
I agree it makes the tests harder to reason about at first glance. But, I think it's a fine trade-off against rewriting all of them in this PR without really gaining extra coverage or a better harness.
youknowriad
left a comment
There was a problem hiding this comment.
nice work here. Nice to see all this removed code.
We'd have to run some performance tests on this PR
| blocks.length && | ||
| ! isUnmodifiedDefaultBlock( blocks[ blocks.length - 1 ] ) | ||
| ) { | ||
| __unstableMarkLastChangeAsPersistent(); |
There was a problem hiding this comment.
Any reason for this change? I wouldn't have expected this refactoring to touch the components directly?
There was a problem hiding this comment.
We need those replacements to be persistent so that undo works as expected in the E2E tests.
There was a problem hiding this comment.
I mean what's changed? Why do we need this to make them persistent? How was it before?
There was a problem hiding this comment.
To be honest, I'm surprised it ever worked for this test case specifically:
it( 'can undo asterisk transform', async () => {
await clickBlockAppender();
await page.keyboard.type( '1. ' );
await pressKeyWithModifier( 'primary', 'z' );
expect( await getEditedPostContent() ).toMatchSnapshot();
} );
If you do it at a normal pace, it works as expected and undos back to "1. ". But, if you do it super fast, the block editor never marks the last change before the transform as persistent and it goes back to "1". Could something be making this faster so now we can't rely on the time out trigerred __unstableMarkLastChangeAsPersistent?
|
|
||
| it( 'Should prompt if content added without save', async () => { | ||
| await clickBlockAppender(); | ||
| await page.keyboard.type( 'Paragraph' ); |
There was a problem hiding this comment.
This is changing the meaning of the test. The test was saying that if you just click the appender, you don't have to type to consider the post dirty.
There was a problem hiding this comment.
Yeah, but an empty paragraph block does not make the post dirty anymore. Other blocks do though.
There was a problem hiding this comment.
Why? why it was changed?
There was a problem hiding this comment.
Because:
// A single unmodified default block is assumed to
// be equivalent to an empty post.
if (
blocksForSerialization.length === 1 &&
isUnmodifiedDefaultBlock( blocksForSerialization[ 0 ] )
) {
blocksForSerialization = [];
}
There was a problem hiding this comment.
Still, why it wasn't working this way before? Why are we making this User Experience change in this refactoring PR?
There was a problem hiding this comment.
This isn't what is forcing us to serialize blocks. We serialize blocks because .content expects a string, not an array of blocks.
Serialization should be even faster than rendering the block list, because save components are generally simpler than edit components, and we are not manipulating a DOM.
There is always the possibility of tagging blocks in core in a way that tells it to do something with it when saving, that something being serializing it and setting content to the result. But, it feels very hacky and we would also need to do it when comparing edits with persisted values.
There was a problem hiding this comment.
It was an early decision to not serialize blocks on each change because it's too expensive. (You can probably verify quickly by running the performance tests on this branch).
edit is not expensive because React doesn't rerender everything on each change and with our asyncmode we only rerender synchronously the edited block.
There was a problem hiding this comment.
(It only happens on every persistent/undo-level-creating change.)
There was a problem hiding this comment.
This branch
Average time to type character: 78.965ms
Slowest time to type character: 170ms
Fastest time to type character: 69ms
master
Average time to type character: 52.535ms
Slowest time to type character: 122ms
Fastest time to type character: 43ms
packages/editor/src/store/actions.js
Outdated
| * | ||
| * @return {string} The blocks serialization. | ||
| */ | ||
| export const serializeBlocks = memoize( |
There was a problem hiding this comment.
Anything exported in this file gets exported as a store action. It could be moved to a utils file if needed or something like that.
Definitely.
Yeah good idea, I'll do that now. |
0ca7b0f to
450b7f2
Compare
|
@youknowriad I implemented everything we talked about and made Travis happy ✅ again. Let me know if I missed anything. |
* Editor: Update the store to use Core Data entities. * Editor: Fix selector test suites. * Editor: Fix some legacy selectors and behaviors. * Editor: Fix action tests. * Editor: Fix remaining broken unit tests. * Editor: Fix more tests. * Editor: Fix more e2e test behaviors. * Editor: Fix preview functionality. * Core Data: Fix autosaves filtering. * Editor: Don't make entity dirty with initial edits. * Editor: Don't save if the post is not saveable. * Core Data: Fix merged edits logic. * Core Data: Fix undo to fit e2e expected behaviors. * Core Data: Handle more change detection and saving flows. * Block Editor: Fix undo level logic. * Core Data: Clean up undo reducer comment. * Editor: Make `serializeBlocks` a util. * Core Data: Clarify raw attribute usage. * Core Data: Memoize . * Core Data: Use new raw entity record selector instead of modifying the existing one. * Core Data: Make save notices the caller's responsibility. * Editor: Use the store key constant in actions instead of a string literal. * Editor: Defer serialization of blocks until save. * Editor: Fix raw content access in set up. * Editor: Revert broken test change. * Editor: Make initial edits a dirtying operation. * Editor: Add comment clarifying why we set content to a new function on edits. * Demo: Fix tests to consider the initial edits dirtying. * Core Data: Set auto-drafts to drafts when autosaving them. * Core Data: Handle receiving autosaves correctly when editing non-autosave-persisting-properties.
* Editor: Update the store to use Core Data entities. * Editor: Fix selector test suites. * Editor: Fix some legacy selectors and behaviors. * Editor: Fix action tests. * Editor: Fix remaining broken unit tests. * Editor: Fix more tests. * Editor: Fix more e2e test behaviors. * Editor: Fix preview functionality. * Core Data: Fix autosaves filtering. * Editor: Don't make entity dirty with initial edits. * Editor: Don't save if the post is not saveable. * Core Data: Fix merged edits logic. * Core Data: Fix undo to fit e2e expected behaviors. * Core Data: Handle more change detection and saving flows. * Block Editor: Fix undo level logic. * Core Data: Clean up undo reducer comment. * Editor: Make `serializeBlocks` a util. * Core Data: Clarify raw attribute usage. * Core Data: Memoize . * Core Data: Use new raw entity record selector instead of modifying the existing one. * Core Data: Make save notices the caller's responsibility. * Editor: Use the store key constant in actions instead of a string literal. * Editor: Defer serialization of blocks until save. * Editor: Fix raw content access in set up. * Editor: Revert broken test change. * Editor: Make initial edits a dirtying operation. * Editor: Add comment clarifying why we set content to a new function on edits. * Demo: Fix tests to consider the initial edits dirtying. * Core Data: Set auto-drafts to drafts when autosaving them. * Core Data: Handle receiving autosaves correctly when editing non-autosave-persisting-properties.
|
I see some regressions after this PR, after a quick round of testing. Not sure if it's all because of this PR.
If feel like we should have added more undo e2e tests before rewriting. |
* Editor: Update the store to use Core Data entities. * Editor: Fix selector test suites. * Editor: Fix some legacy selectors and behaviors. * Editor: Fix action tests. * Editor: Fix remaining broken unit tests. * Editor: Fix more tests. * Editor: Fix more e2e test behaviors. * Editor: Fix preview functionality. * Core Data: Fix autosaves filtering. * Editor: Don't make entity dirty with initial edits. * Editor: Don't save if the post is not saveable. * Core Data: Fix merged edits logic. * Core Data: Fix undo to fit e2e expected behaviors. * Core Data: Handle more change detection and saving flows. * Block Editor: Fix undo level logic. * Core Data: Clean up undo reducer comment. * Editor: Make `serializeBlocks` a util. * Core Data: Clarify raw attribute usage. * Core Data: Memoize . * Core Data: Use new raw entity record selector instead of modifying the existing one. * Core Data: Make save notices the caller's responsibility. * Editor: Use the store key constant in actions instead of a string literal. * Editor: Defer serialization of blocks until save. * Editor: Fix raw content access in set up. * Editor: Revert broken test change. * Editor: Make initial edits a dirtying operation. * Editor: Add comment clarifying why we set content to a new function on edits. * Demo: Fix tests to consider the initial edits dirtying. * Core Data: Set auto-drafts to drafts when autosaving them. * Core Data: Handle receiving autosaves correctly when editing non-autosave-persisting-properties.
|
Undo levels get created whenever the block editor calls |
| const { select } = window.wp.data; | ||
| return select( 'core/editor' ).isEditedPostDirty(); | ||
| } ); | ||
| expect( isDirty ).toBeFalsy(); |
There was a problem hiding this comment.
@epiqueras @youknowriad @mcsf Why has this been changed???
There was a problem hiding this comment.
Initial edits should be considered a dirtying operation.
See #16932 (comment)
| } ); | ||
|
|
||
| await assertIsDirty( false ); | ||
| await assertIsDirty( true ); |
There was a problem hiding this comment.
Initial edits should be considered a dirtying operation.
See #16932 (comment)
| await pressKeyWithModifier( 'primary', 'S' ); | ||
|
|
||
| await page.type( '.editor-post-title__input', '!' ); | ||
| await page.waitForSelector( '.editor-post-save-draft' ); |
There was a problem hiding this comment.
What has changed so that this became necessary?
There was a problem hiding this comment.
It wasn't deterministically waiting enough time. I think it might have relied on a race condition.
| }; | ||
|
|
||
| expect( isEditedPostEmpty( state ) ).toBe( false ); | ||
| expect( isEditedPostEmpty( state ) ).toBe( true ); |
There was a problem hiding this comment.
Why did this change? Why are tests changed without explanation?
There was a problem hiding this comment.
Initial edits should be considered a dirtying operation.
See #16932 (comment)
| const record = getEntityRecord( state, kind, name, key ); | ||
| return ( | ||
| record && | ||
| Object.keys( record ).reduce( ( acc, _key ) => { |
There was a problem hiding this comment.
Tabbing is a little wild here. Not sure why ESLint didn't catch this.
Description
This PR picks up from where #16903 left off in the planned entities refactor. It almost completely rewrites the editor store to act as a thin routing interface between the Block Editor and Core Data entities, in preparation for multi-entity editor setups for full site editing.
How has this been tested?
The existing tests were refactored to work with the new code.
Checklist: