@wordpress/editor 12.10.0 → 12.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/index.js +12 -1
  3. package/build/components/index.js.map +1 -1
  4. package/build/components/local-autosave-monitor/index.js +24 -19
  5. package/build/components/local-autosave-monitor/index.js.map +1 -1
  6. package/build/components/page-attributes/parent.js +1 -1
  7. package/build/components/page-attributes/parent.js.map +1 -1
  8. package/build/components/post-publish-panel/maybe-category-panel.js +5 -5
  9. package/build/components/post-publish-panel/maybe-category-panel.js.map +1 -1
  10. package/build/components/post-schedule/label.js +93 -13
  11. package/build/components/post-schedule/label.js.map +1 -1
  12. package/build/components/post-taxonomies/flat-term-selector.js +7 -4
  13. package/build/components/post-taxonomies/flat-term-selector.js.map +1 -1
  14. package/build/components/post-visibility/index.js +3 -1
  15. package/build/components/post-visibility/index.js.map +1 -1
  16. package/build/components/provider/index.native.js +0 -1
  17. package/build/components/provider/index.native.js.map +1 -1
  18. package/build/store/actions.js +5 -1
  19. package/build/store/actions.js.map +1 -1
  20. package/build/store/reducer.js +1 -1
  21. package/build/store/reducer.js.map +1 -1
  22. package/build-module/components/index.js +1 -1
  23. package/build-module/components/index.js.map +1 -1
  24. package/build-module/components/local-autosave-monitor/index.js +24 -18
  25. package/build-module/components/local-autosave-monitor/index.js.map +1 -1
  26. package/build-module/components/page-attributes/parent.js +2 -2
  27. package/build-module/components/page-attributes/parent.js.map +1 -1
  28. package/build-module/components/post-publish-panel/maybe-category-panel.js +5 -5
  29. package/build-module/components/post-publish-panel/maybe-category-panel.js.map +1 -1
  30. package/build-module/components/post-schedule/label.js +90 -13
  31. package/build-module/components/post-schedule/label.js.map +1 -1
  32. package/build-module/components/post-taxonomies/flat-term-selector.js +7 -4
  33. package/build-module/components/post-taxonomies/flat-term-selector.js.map +1 -1
  34. package/build-module/components/post-visibility/index.js +4 -2
  35. package/build-module/components/post-visibility/index.js.map +1 -1
  36. package/build-module/components/provider/index.native.js +0 -1
  37. package/build-module/components/provider/index.native.js.map +1 -1
  38. package/build-module/store/actions.js +5 -1
  39. package/build-module/store/actions.js.map +1 -1
  40. package/build-module/store/reducer.js +2 -2
  41. package/build-module/store/reducer.js.map +1 -1
  42. package/build-style/style-rtl.css +1 -1
  43. package/build-style/style.css +1 -1
  44. package/package.json +28 -28
  45. package/src/components/index.js +4 -1
  46. package/src/components/local-autosave-monitor/index.js +24 -18
  47. package/src/components/page-attributes/parent.js +1 -2
  48. package/src/components/post-publish-panel/maybe-category-panel.js +5 -2
  49. package/src/components/post-publish-panel/test/__snapshots__/index.js.snap +1 -1
  50. package/src/components/post-schedule/label.js +111 -17
  51. package/src/components/post-schedule/test/label.js +127 -15
  52. package/src/components/post-taxonomies/flat-term-selector.js +5 -4
  53. package/src/components/post-title/style.scss +1 -1
  54. package/src/components/post-visibility/index.js +2 -2
  55. package/src/components/provider/index.native.js +1 -7
  56. package/src/store/actions.js +3 -1
  57. package/src/store/reducer.js +2 -2
  58. package/src/store/test/selectors.js +8 -10
@@ -1,8 +1,3 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { once, uniqueId, omit } from 'lodash';
5
-
6
1
  /**
7
2
  * WordPress dependencies
8
3
  */
@@ -27,23 +22,30 @@ const requestIdleCallback = window.requestIdleCallback
27
22
  ? window.requestIdleCallback
28
23
  : window.requestAnimationFrame;
29
24
 
25
+ let hasStorageSupport;
26
+ let uniqueId = 0;
27
+
30
28
  /**
31
29
  * Function which returns true if the current environment supports browser
32
30
  * sessionStorage, or false otherwise. The result of this function is cached and
33
31
  * reused in subsequent invocations.
34
32
  */
35
- const hasSessionStorageSupport = once( () => {
36
- try {
37
- // Private Browsing in Safari 10 and earlier will throw an error when
38
- // attempting to set into sessionStorage. The test here is intentional in
39
- // causing a thrown error as condition bailing from local autosave.
40
- window.sessionStorage.setItem( '__wpEditorTestSessionStorage', '' );
41
- window.sessionStorage.removeItem( '__wpEditorTestSessionStorage' );
42
- return true;
43
- } catch ( error ) {
44
- return false;
33
+ const hasSessionStorageSupport = () => {
34
+ if ( typeof hasStorageSupport === 'undefined' ) {
35
+ try {
36
+ // Private Browsing in Safari 10 and earlier will throw an error when
37
+ // attempting to set into sessionStorage. The test here is intentional in
38
+ // causing a thrown error as condition bailing from local autosave.
39
+ window.sessionStorage.setItem( '__wpEditorTestSessionStorage', '' );
40
+ window.sessionStorage.removeItem( '__wpEditorTestSessionStorage' );
41
+ hasStorageSupport = true;
42
+ } catch ( error ) {
43
+ hasStorageSupport = false;
44
+ }
45
45
  }
46
- } );
46
+
47
+ return hasStorageSupport;
48
+ };
47
49
 
48
50
  /**
49
51
  * Custom hook which manages the creation of a notice prompting the user to
@@ -98,7 +100,7 @@ function useAutosaveNotice() {
98
100
  return;
99
101
  }
100
102
 
101
- const noticeId = uniqueId( 'wpEditorAutosaveRestore' );
103
+ const noticeId = `wpEditorAutosaveRestore${ ++uniqueId }`;
102
104
  createWarningNotice(
103
105
  __(
104
106
  'The backup of this post in your browser is different from the version below.'
@@ -109,7 +111,11 @@ function useAutosaveNotice() {
109
111
  {
110
112
  label: __( 'Restore the backup' ),
111
113
  onClick() {
112
- editPost( omit( edits, [ 'content' ] ) );
114
+ const {
115
+ content: editsContent,
116
+ ...editsWithoutContent
117
+ } = edits;
118
+ editPost( editsWithoutContent );
113
119
  resetEditorBlocks( parse( edits.content ) );
114
120
  removeNotice( noticeId );
115
121
  },
@@ -7,7 +7,6 @@ import {
7
7
  debounce,
8
8
  repeat,
9
9
  find,
10
- flatten,
11
10
  deburr,
12
11
  } from 'lodash';
13
12
 
@@ -111,7 +110,7 @@ export function PageAttributesParent() {
111
110
  return priorityA >= priorityB ? 1 : -1;
112
111
  } );
113
112
 
114
- return flatten( sortedNodes );
113
+ return sortedNodes.flat();
115
114
  };
116
115
 
117
116
  let tree = pageItems.map( ( item ) => ( {
@@ -23,12 +23,15 @@ function MaybeCategoryPanel() {
23
23
  const postType = select( editorStore ).getCurrentPostType();
24
24
  const categoriesTaxonomy =
25
25
  select( coreStore ).getTaxonomy( 'category' );
26
- const defaultCategorySlug = 'uncategorized';
26
+ const defaultCategoryId = select( coreStore ).getEntityRecord(
27
+ 'root',
28
+ 'site'
29
+ )?.default_category;
27
30
  const defaultCategory = select( coreStore ).getEntityRecords(
28
31
  'taxonomy',
29
32
  'category',
30
33
  {
31
- slug: defaultCategorySlug,
34
+ id: defaultCategoryId,
32
35
  }
33
36
  )?.[ 0 ];
34
37
  const postTypeSupportsCategories =
@@ -184,7 +184,7 @@ exports[`PostPublishPanel should render the spinner if the post is being saved 1
184
184
  <div
185
185
  className="editor-post-publish-panel__content"
186
186
  >
187
- <Spinner />
187
+ <ForwardRef(UnforwardedSpinner) />
188
188
  </div>
189
189
  <div
190
190
  className="editor-post-publish-panel__footer"
@@ -1,28 +1,122 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { __ } from '@wordpress/i18n';
5
- import { format, __experimentalGetSettings } from '@wordpress/date';
6
- import { withSelect } from '@wordpress/data';
4
+ import { __, _x, sprintf, isRTL } from '@wordpress/i18n';
5
+ import { __experimentalGetSettings, getDate, dateI18n } from '@wordpress/date';
6
+ import { useSelect } from '@wordpress/data';
7
7
 
8
8
  /**
9
9
  * Internal dependencies
10
10
  */
11
11
  import { store as editorStore } from '../../store';
12
12
 
13
- export function PostScheduleLabel( { date, isFloating } ) {
14
- const settings = __experimentalGetSettings();
15
- return date && ! isFloating
16
- ? format(
17
- `${ settings.formats.date } ${ settings.formats.time }`,
18
- date
19
- )
20
- : __( 'Immediately' );
13
+ export default function PostScheduleLabel( props ) {
14
+ return usePostScheduleLabel( props );
21
15
  }
22
16
 
23
- export default withSelect( ( select ) => {
24
- return {
25
- date: select( editorStore ).getEditedPostAttribute( 'date' ),
26
- isFloating: select( editorStore ).isEditedPostDateFloating(),
27
- };
28
- } )( PostScheduleLabel );
17
+ export function usePostScheduleLabel( { full } ) {
18
+ const { date, isFloating } = useSelect(
19
+ ( select ) => ( {
20
+ date: select( editorStore ).getEditedPostAttribute( 'date' ),
21
+ isFloating: select( editorStore ).isEditedPostDateFloating(),
22
+ } ),
23
+ []
24
+ );
25
+
26
+ return full
27
+ ? getFullPostScheduleLabel( date )
28
+ : getPostScheduleLabel( date, { isFloating } );
29
+ }
30
+
31
+ export function getFullPostScheduleLabel( dateAttribute ) {
32
+ const date = getDate( dateAttribute );
33
+
34
+ const timezoneAbbreviation = getTimezoneAbbreviation();
35
+ const formattedDate = dateI18n(
36
+ // translators: If using a space between 'g:i' and 'a', use a non-breaking sapce.
37
+ _x( 'F j, Y g:i\xa0a', 'post schedule full date format' ),
38
+ date
39
+ );
40
+ return isRTL()
41
+ ? `${ timezoneAbbreviation } ${ formattedDate }`
42
+ : `${ formattedDate } ${ timezoneAbbreviation }`;
43
+ }
44
+
45
+ export function getPostScheduleLabel(
46
+ dateAttribute,
47
+ { isFloating = false, now = new Date() } = {}
48
+ ) {
49
+ if ( ! dateAttribute || isFloating ) {
50
+ return __( 'Immediately' );
51
+ }
52
+
53
+ // If the user timezone does not equal the site timezone then using words
54
+ // like 'tomorrow' is confusing, so show the full date.
55
+ if ( ! isTimezoneSameAsSiteTimezone( now ) ) {
56
+ return getFullPostScheduleLabel( dateAttribute );
57
+ }
58
+
59
+ const date = getDate( dateAttribute );
60
+
61
+ if ( isSameDay( date, now ) ) {
62
+ return sprintf(
63
+ // translators: %s: Time of day the post is scheduled for.
64
+ __( 'Today at %s' ),
65
+ // translators: If using a space between 'g:i' and 'a', use a non-breaking sapce.
66
+ dateI18n( _x( 'g:i\xa0a', 'post schedule time format' ), date )
67
+ );
68
+ }
69
+
70
+ const tomorrow = new Date( now );
71
+ tomorrow.setDate( tomorrow.getDate() + 1 );
72
+
73
+ if ( isSameDay( date, tomorrow ) ) {
74
+ return sprintf(
75
+ // translators: %s: Time of day the post is scheduled for.
76
+ __( 'Tomorrow at %s' ),
77
+ // translators: If using a space between 'g:i' and 'a', use a non-breaking sapce.
78
+ dateI18n( _x( 'g:i\xa0a', 'post schedule time format' ), date )
79
+ );
80
+ }
81
+
82
+ if ( date.getFullYear() === now.getFullYear() ) {
83
+ return dateI18n(
84
+ // translators: If using a space between 'g:i' and 'a', use a non-breaking sapce.
85
+ _x( 'F j g:i\xa0a', 'post schedule date format without year' ),
86
+ date
87
+ );
88
+ }
89
+
90
+ return dateI18n(
91
+ // translators: Use a non-breaking space between 'g:i' and 'a' if appropriate.
92
+ _x( 'F j, Y g:i\xa0a', 'post schedule full date format' ),
93
+ date
94
+ );
95
+ }
96
+
97
+ function getTimezoneAbbreviation() {
98
+ const { timezone } = __experimentalGetSettings();
99
+
100
+ if ( timezone.abbr && isNaN( Number( timezone.abbr ) ) ) {
101
+ return timezone.abbr;
102
+ }
103
+
104
+ const symbol = timezone.offset < 0 ? '' : '+';
105
+ return `UTC${ symbol }${ timezone.offset }`;
106
+ }
107
+
108
+ function isTimezoneSameAsSiteTimezone( date ) {
109
+ const { timezone } = __experimentalGetSettings();
110
+
111
+ const siteOffset = Number( timezone.offset );
112
+ const dateOffset = -1 * ( date.getTimezoneOffset() / 60 );
113
+ return siteOffset === dateOffset;
114
+ }
115
+
116
+ function isSameDay( left, right ) {
117
+ return (
118
+ left.getDate() === right.getDate() &&
119
+ left.getMonth() === right.getMonth() &&
120
+ left.getFullYear() === right.getFullYear()
121
+ );
122
+ }
@@ -1,32 +1,144 @@
1
1
  /**
2
- * External dependencies
2
+ * WordPress dependencies
3
3
  */
4
- import { shallow } from 'enzyme';
4
+ import { __experimentalGetSettings, setSettings } from '@wordpress/date';
5
5
 
6
6
  /**
7
7
  * Internal dependencies
8
8
  */
9
- import { PostScheduleLabel } from '../label';
9
+ import { getFullPostScheduleLabel, getPostScheduleLabel } from '../label';
10
10
 
11
- describe( 'PostScheduleLabel', () => {
11
+ describe( 'getFullPostScheduleLabel', () => {
12
+ it( 'should show a date', () => {
13
+ const label = getFullPostScheduleLabel( '2022-04-28T15:30:00' );
14
+ expect( label ).toBe( 'April 28, 2022 3:30\xa0pm UTC+0' ); // Unused, for backwards compatibility.
15
+ } );
16
+
17
+ it( "should show site's timezone abbr", () => {
18
+ const settings = __experimentalGetSettings();
19
+
20
+ setSettings( {
21
+ ...settings,
22
+ timezone: { offset: 10, string: 'Australia/Sydney', abbr: 'AEST' },
23
+ } );
24
+
25
+ const label = getFullPostScheduleLabel( '2022-04-28T15:30:00' );
26
+ expect( label ).toBe( 'April 28, 2022 3:30\xa0pm AEST' );
27
+
28
+ setSettings( settings );
29
+ } );
30
+
31
+ it( "should show site's timezone offset", () => {
32
+ const settings = __experimentalGetSettings();
33
+
34
+ setSettings( {
35
+ ...settings,
36
+ timezone: { offset: 10 },
37
+ } );
38
+
39
+ const label = getFullPostScheduleLabel( '2022-04-28T15:30:00' );
40
+ expect( label ).toBe( 'April 28, 2022 3:30\xa0pm UTC+10' );
41
+
42
+ setSettings( settings );
43
+ } );
44
+ } );
45
+
46
+ describe( 'getPostScheduleLabel', () => {
12
47
  it( 'should show the post will be published immediately if no publish date is set', () => {
13
- const wrapper = shallow( <PostScheduleLabel date={ undefined } /> );
14
- expect( wrapper.text() ).toBe( 'Immediately' );
48
+ const label = getPostScheduleLabel( undefined );
49
+ expect( label ).toBe( 'Immediately' );
15
50
  } );
16
51
 
17
52
  it( 'should show the post will be published immediately if it has a floating date', () => {
18
- const date = '2018-09-17T01:23:45.678Z';
19
- const wrapper = shallow(
20
- <PostScheduleLabel date={ date } isFloating={ true } />
53
+ const label = getPostScheduleLabel( '2022-04-28T15:30:00', {
54
+ isFloating: true,
55
+ } );
56
+ expect( label ).toBe( 'Immediately' );
57
+ } );
58
+
59
+ it( "should show full date if user timezone does not equal site's timezone", () => {
60
+ const now = new Date( '2022-04-28T13:00:00.000Z' );
61
+ jest.spyOn( now, 'getTimezoneOffset' ).mockImplementationOnce(
62
+ () => 10 * -60 // UTC+10
21
63
  );
22
- expect( wrapper.text() ).toBe( 'Immediately' );
64
+
65
+ const label = getPostScheduleLabel( '2022-04-28T15:30:00', { now } );
66
+ expect( label ).toBe( 'April 28, 2022 3:30\xa0pm UTC+0' );
23
67
  } );
24
68
 
25
- it( 'should show the scheduled publish date if a date has been set', () => {
26
- const date = '2018-09-17T01:23:45.678Z';
27
- const wrapper = shallow(
28
- <PostScheduleLabel date={ date } isFloating={ false } />
69
+ it( "should show today if date is same day as now and user timezone equals site's timezone", () => {
70
+ const settings = __experimentalGetSettings();
71
+
72
+ setSettings( {
73
+ ...settings,
74
+ timezone: { offset: 10 },
75
+ } );
76
+
77
+ const now = new Date( '2022-04-28T03:00:00.000Z' );
78
+ jest.spyOn( now, 'getTimezoneOffset' ).mockImplementationOnce(
79
+ () => 10 * -60 // UTC+10
29
80
  );
30
- expect( wrapper.text() ).not.toBe( 'Immediately' );
81
+
82
+ const label = getPostScheduleLabel( '2022-04-28T15:30:00', { now } );
83
+ expect( label ).toBe( 'Today at 3:30\xa0pm' );
84
+
85
+ setSettings( settings );
86
+ } );
87
+
88
+ it( "should show tomorrow if date is same day as now + 1 day and user timezone equals site's timezone", () => {
89
+ const settings = __experimentalGetSettings();
90
+
91
+ setSettings( {
92
+ ...settings,
93
+ timezone: { offset: 10 },
94
+ } );
95
+
96
+ const now = new Date( '2022-04-28T03:00:00.000Z' );
97
+ jest.spyOn( now, 'getTimezoneOffset' ).mockImplementationOnce(
98
+ () => 10 * -60 // UTC+10
99
+ );
100
+
101
+ const label = getPostScheduleLabel( '2022-04-29T15:30:00', { now } );
102
+ expect( label ).toBe( 'Tomorrow at 3:30\xa0pm' );
103
+
104
+ setSettings( settings );
105
+ } );
106
+
107
+ it( "should hide year if date is same year as now and user timezone equals site's timezone", () => {
108
+ const settings = __experimentalGetSettings();
109
+
110
+ setSettings( {
111
+ ...settings,
112
+ timezone: { offset: 10 },
113
+ } );
114
+
115
+ const now = new Date( '2022-04-28T03:00:00.000Z' );
116
+ jest.spyOn( now, 'getTimezoneOffset' ).mockImplementationOnce(
117
+ () => 10 * -60 // UTC+10
118
+ );
119
+
120
+ const label = getPostScheduleLabel( '2022-12-25T15:30:00', { now } );
121
+ expect( label ).toBe( 'December 25 3:30\xa0pm' );
122
+
123
+ setSettings( settings );
124
+ } );
125
+
126
+ it( "should show year if date is not same year as now and user timezone equals site's timezone", () => {
127
+ const settings = __experimentalGetSettings();
128
+
129
+ setSettings( {
130
+ ...settings,
131
+ timezone: { offset: 10 },
132
+ } );
133
+
134
+ const now = new Date( '2022-04-28T03:00:00.000Z' );
135
+ jest.spyOn( now, 'getTimezoneOffset' ).mockImplementationOnce(
136
+ () => 10 * -60 // UTC+10
137
+ );
138
+
139
+ const label = getPostScheduleLabel( '2023-04-28T15:30:00', { now } );
140
+ expect( label ).toBe( 'April 28, 2023 3:30\xa0pm' );
141
+
142
+ setSettings( settings );
31
143
  } );
32
144
  } );
@@ -55,11 +55,11 @@ const termNamesToIds = ( names, terms ) => {
55
55
  };
56
56
 
57
57
  // Tries to create a term or fetch it if it already exists.
58
- function findOrCreateTerm( termName, restBase ) {
58
+ function findOrCreateTerm( termName, restBase, namespace ) {
59
59
  const escapedTermName = escapeString( termName );
60
60
 
61
61
  return apiFetch( {
62
- path: `/wp/v2/${ restBase }`,
62
+ path: `/${ namespace }/${ restBase }`,
63
63
  method: 'POST',
64
64
  data: { name: escapedTermName },
65
65
  } )
@@ -68,7 +68,7 @@ function findOrCreateTerm( termName, restBase ) {
68
68
  if ( errorCode === 'term_exists' ) {
69
69
  // If the terms exist, fetch it instead of creating a new one.
70
70
  const addRequest = apiFetch( {
71
- path: addQueryArgs( `/wp/v2/${ restBase }`, {
71
+ path: addQueryArgs( `/${ namespace }/${ restBase }`, {
72
72
  ...DEFAULT_QUERY,
73
73
  search: escapedTermName,
74
74
  } ),
@@ -224,9 +224,10 @@ function FlatTermSelector( { slug } ) {
224
224
  return;
225
225
  }
226
226
 
227
+ const namespace = taxonomy?.rest_namespace ?? 'wp/v2';
227
228
  Promise.all(
228
229
  newTermNames.map( ( termName ) =>
229
- findOrCreateTerm( termName, taxonomy.rest_base )
230
+ findOrCreateTerm( termName, taxonomy.rest_base, namespace )
230
231
  )
231
232
  ).then( ( newTerms ) => {
232
233
  const newAvailableTerms = availableTerms.concat( newTerms );
@@ -2,7 +2,7 @@
2
2
  position: relative;
3
3
 
4
4
  &.is-focus-mode {
5
- opacity: 0.5;
5
+ opacity: 0.2;
6
6
  transition: opacity 0.1s linear;
7
7
  @include reduce-motion("transition");
8
8
 
@@ -68,7 +68,7 @@ export default function PostVisibility( { onClose } ) {
68
68
  };
69
69
 
70
70
  return (
71
- <>
71
+ <div className="editor-post-visibility">
72
72
  <InspectorPopoverHeader
73
73
  title={ __( 'Visibility' ) }
74
74
  help={ __( 'Control how this post is viewed.' ) }
@@ -128,7 +128,7 @@ export default function PostVisibility( { onClose } ) {
128
128
  >
129
129
  { __( 'Would you like to privately publish this post now?' ) }
130
130
  </ConfirmDialog>
131
- </>
131
+ </div>
132
132
  );
133
133
  }
134
134
 
@@ -319,13 +319,7 @@ class NativeEditorProvider extends Component {
319
319
  }
320
320
 
321
321
  render() {
322
- const {
323
- children,
324
- post, // eslint-disable-line no-unused-vars
325
- capabilities,
326
- settings,
327
- ...props
328
- } = this.props;
322
+ const { children, post, capabilities, settings, ...props } = this.props;
329
323
  const editorSettings = this.getEditorSettings( settings, capabilities );
330
324
 
331
325
  return (
@@ -250,10 +250,12 @@ export const trashPost =
250
250
  .resolveSelect( coreStore )
251
251
  .getPostType( postTypeSlug );
252
252
  registry.dispatch( noticesStore ).removeNotice( TRASH_POST_NOTICE_ID );
253
+ const { rest_base: restBase, rest_namespace: restNamespace = 'wp/v2' } =
254
+ postType;
253
255
  try {
254
256
  const post = select.getCurrentPost();
255
257
  await apiFetch( {
256
- path: `/wp/v2/${ postType.rest_base }/${ post.id }`,
258
+ path: `/${ restNamespace }/${ restBase }/${ post.id }`,
257
259
  method: 'DELETE',
258
260
  } );
259
261
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { omit, keys, isEqual } from 'lodash';
4
+ import { omit, isEqual } from 'lodash';
5
5
 
6
6
  /**
7
7
  * WordPress dependencies
@@ -39,7 +39,7 @@ export function getPostRawValue( value ) {
39
39
  * @return {boolean} Whether the two objects have the same keys.
40
40
  */
41
41
  export function hasSameKeys( a, b ) {
42
- return isEqual( keys( a ), keys( b ) );
42
+ return isEqual( Object.keys( a ), Object.keys( b ) );
43
43
  }
44
44
 
45
45
  /**
@@ -1,8 +1,3 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { filter, without } from 'lodash';
5
-
6
1
  /**
7
2
  * WordPress dependencies
8
3
  */
@@ -227,7 +222,9 @@ describe( 'selectors', () => {
227
222
  let cachedSelectors;
228
223
 
229
224
  beforeAll( () => {
230
- cachedSelectors = filter( selectors, ( selector ) => selector.clear );
225
+ cachedSelectors = Object.entries( selectors )
226
+ .filter( ( [ , selector ] ) => selector.clear )
227
+ .map( ( [ , selector ] ) => selector );
231
228
  } );
232
229
 
233
230
  beforeEach( () => {
@@ -1598,10 +1595,11 @@ describe( 'selectors', () => {
1598
1595
  } );
1599
1596
 
1600
1597
  it( 'should return true if title or excerpt have changed', () => {
1601
- for ( const variantField of [ 'title', 'excerpt' ] ) {
1602
- for ( const constantField of without(
1603
- [ 'title', 'excerpt' ],
1604
- variantField
1598
+ const fields = [ 'title', 'excerpt' ];
1599
+
1600
+ for ( const variantField of fields ) {
1601
+ for ( const constantField of fields.filter(
1602
+ ( f ) => ! f === variantField
1605
1603
  ) ) {
1606
1604
  const state = {
1607
1605
  editor: {