@wordpress/e2e-tests 7.19.0 → 7.20.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 (50) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/LICENSE.md +1 -1
  3. package/jest.config.js +0 -1
  4. package/package.json +8 -9
  5. package/plugins/iframed-block/block.json +1 -0
  6. package/plugins/iframed-inline-styles/block.json +1 -0
  7. package/plugins/iframed-masonry-block/block.json +1 -0
  8. package/plugins/iframed-multiple-stylesheets/block.json +1 -0
  9. package/plugins/interactive-blocks/directive-bind/block.json +1 -0
  10. package/plugins/interactive-blocks/directive-body/block.json +1 -0
  11. package/plugins/interactive-blocks/directive-class/block.json +1 -0
  12. package/plugins/interactive-blocks/directive-context/block.json +1 -0
  13. package/plugins/interactive-blocks/directive-init/block.json +1 -0
  14. package/plugins/interactive-blocks/directive-key/block.json +1 -0
  15. package/plugins/interactive-blocks/directive-on/block.json +1 -0
  16. package/plugins/interactive-blocks/directive-priorities/block.json +1 -0
  17. package/plugins/interactive-blocks/directive-slots/block.json +1 -0
  18. package/plugins/interactive-blocks/directive-style/block.json +1 -0
  19. package/plugins/interactive-blocks/directive-text/block.json +1 -0
  20. package/plugins/interactive-blocks/directive-watch/block.json +1 -0
  21. package/plugins/interactive-blocks/negation-operator/block.json +1 -0
  22. package/plugins/interactive-blocks/router-navigate/block.json +1 -0
  23. package/plugins/interactive-blocks/router-regions/block.json +1 -0
  24. package/plugins/interactive-blocks/store-tag/block.json +1 -0
  25. package/plugins/interactive-blocks/tovdom/block.json +1 -0
  26. package/plugins/interactive-blocks/tovdom-islands/block.json +1 -0
  27. package/plugins/interactive-blocks/tovdom-islands/render.php +12 -0
  28. package/plugins/pattern-recursion.php +22 -0
  29. package/specs/editor/various/inserting-blocks.test.js +3 -5
  30. package/specs/editor/various/__snapshots__/block-editor-keyboard-shortcuts.test.js.snap +0 -153
  31. package/specs/editor/various/allowed-patterns.test.js +0 -74
  32. package/specs/editor/various/block-editor-keyboard-shortcuts.test.js +0 -110
  33. package/specs/editor/various/block-switcher.test.js +0 -130
  34. package/specs/editor/various/core-settings.test.js +0 -42
  35. package/specs/editor/various/datepicker.test.js +0 -148
  36. package/specs/editor/various/dropdown-menu.test.js +0 -143
  37. package/specs/editor/various/editor-modes.test.js +0 -163
  38. package/specs/editor/various/invalid-block.test.js +0 -100
  39. package/specs/editor/various/nux.test.js +0 -158
  40. package/specs/editor/various/preferences.test.js +0 -62
  41. package/specs/editor/various/publish-panel.test.js +0 -82
  42. package/specs/editor/various/publishing.test.js +0 -176
  43. package/specs/editor/various/scheduling.test.js +0 -65
  44. package/specs/editor/various/sidebar.test.js +0 -171
  45. package/specs/editor/various/taxonomies.test.js +0 -251
  46. package/specs/experiments/blocks/post-comments-form.test.js +0 -53
  47. package/specs/experiments/experimental-features.js +0 -39
  48. package/specs/experiments/fixtures/menu-items-request-fixture.json +0 -84
  49. package/specs/site-editor/settings-sidebar.test.js +0 -122
  50. package/specs/widgets/editing-widgets.test.js +0 -962
@@ -1,171 +0,0 @@
1
- /**
2
- * WordPress dependencies
3
- */
4
- import {
5
- clearLocalStorage,
6
- createNewPost,
7
- findSidebarPanelWithTitle,
8
- enableFocusLossObservation,
9
- disableFocusLossObservation,
10
- openDocumentSettingsSidebar,
11
- pressKeyWithModifier,
12
- setBrowserViewport,
13
- } from '@wordpress/e2e-test-utils';
14
-
15
- const SIDEBAR_SELECTOR = '.edit-post-sidebar';
16
- const ACTIVE_SIDEBAR_TAB_SELECTOR =
17
- 'div[aria-label="Editor settings"] [role="tab"][aria-selected="true"]';
18
- const ACTIVE_SIDEBAR_BUTTON_TEXT = 'Post';
19
-
20
- describe( 'Sidebar', () => {
21
- afterEach( () => {
22
- disableFocusLossObservation();
23
- } );
24
-
25
- it( 'should have sidebar visible at the start with document sidebar active on desktop', async () => {
26
- await setBrowserViewport( 'large' );
27
- await clearLocalStorage();
28
- await createNewPost();
29
- await enableFocusLossObservation();
30
- const { nodesCount, content, height, width } = await page.$$eval(
31
- ACTIVE_SIDEBAR_TAB_SELECTOR,
32
- ( nodes ) => {
33
- const firstNode = nodes[ 0 ];
34
- return {
35
- nodesCount: nodes.length,
36
- content: firstNode.innerText,
37
- height: firstNode.offsetHeight,
38
- width: firstNode.offsetWidth,
39
- };
40
- }
41
- );
42
-
43
- // should have only one active sidebar tab.
44
- expect( nodesCount ).toBe( 1 );
45
-
46
- // the active sidebar tab should be document.
47
- expect( content ).toBe( ACTIVE_SIDEBAR_BUTTON_TEXT );
48
-
49
- // the active sidebar tab should be visible
50
- expect( width ).toBeGreaterThan( 10 );
51
- expect( height ).toBeGreaterThan( 10 );
52
- } );
53
-
54
- it( 'should have the sidebar closed by default on mobile', async () => {
55
- await setBrowserViewport( 'small' );
56
- await clearLocalStorage();
57
- await createNewPost();
58
- await enableFocusLossObservation();
59
- const sidebar = await page.$( SIDEBAR_SELECTOR );
60
- expect( sidebar ).toBeNull();
61
- } );
62
-
63
- it( 'should close the sidebar when resizing from desktop to mobile', async () => {
64
- await setBrowserViewport( 'large' );
65
- await clearLocalStorage();
66
- await createNewPost();
67
- await enableFocusLossObservation();
68
-
69
- const sidebars = await page.$$( SIDEBAR_SELECTOR );
70
- expect( sidebars ).toHaveLength( 1 );
71
-
72
- await setBrowserViewport( 'small' );
73
-
74
- const sidebarsMobile = await page.$$( SIDEBAR_SELECTOR );
75
- // sidebar should be closed when resizing to mobile.
76
- expect( sidebarsMobile ).toHaveLength( 0 );
77
- } );
78
-
79
- it( 'should reopen sidebar the sidebar when resizing from mobile to desktop if the sidebar was closed automatically', async () => {
80
- await setBrowserViewport( 'large' );
81
- await clearLocalStorage();
82
- await createNewPost();
83
- await enableFocusLossObservation();
84
- await setBrowserViewport( 'small' );
85
-
86
- const sidebarsMobile = await page.$$( SIDEBAR_SELECTOR );
87
- expect( sidebarsMobile ).toHaveLength( 0 );
88
-
89
- await setBrowserViewport( 'large' );
90
-
91
- await page.waitForSelector( SIDEBAR_SELECTOR );
92
- } );
93
-
94
- it( 'should preserve tab order while changing active tab', async () => {
95
- await createNewPost();
96
- await enableFocusLossObservation();
97
-
98
- // Region navigate to Sidebar.
99
- await pressKeyWithModifier( 'ctrl', '`' );
100
-
101
- // Tab lands at first (presumed selected) option "Post".
102
- await page.keyboard.press( 'Tab' );
103
-
104
- // The Post tab should be focused and selected.
105
- const [ documentInspectorTab ] = await page.$x(
106
- '//button[@role="tab"][@aria-selected="true"][contains(text(), "Post")]'
107
- );
108
- expect( documentInspectorTab ).toBeDefined();
109
- expect( documentInspectorTab ).toHaveFocus();
110
-
111
- // Arrow key into and activate "Block".
112
- await page.keyboard.press( 'ArrowRight' );
113
- await page.keyboard.press( 'Space' );
114
-
115
- // The Block tab should be focused and selected.
116
- const [ blockInspectorTab ] = await page.$x(
117
- '//button[@role="tab"][@aria-selected="true"][contains(text(), "Block")]'
118
- );
119
- expect( blockInspectorTab ).toBeDefined();
120
- expect( blockInspectorTab ).toHaveFocus();
121
- } );
122
-
123
- it( 'should be possible to programmatically remove Document Settings panels', async () => {
124
- await createNewPost();
125
- await enableFocusLossObservation();
126
- await openDocumentSettingsSidebar();
127
-
128
- expect( await findSidebarPanelWithTitle( 'Categories' ) ).toBeDefined();
129
- expect( await findSidebarPanelWithTitle( 'Tags' ) ).toBeDefined();
130
- expect(
131
- await findSidebarPanelWithTitle( 'Featured image' )
132
- ).toBeDefined();
133
- expect( await findSidebarPanelWithTitle( 'Excerpt' ) ).toBeDefined();
134
- expect( await findSidebarPanelWithTitle( 'Discussion' ) ).toBeDefined();
135
- expect( await findSidebarPanelWithTitle( 'Summary' ) ).toBeDefined();
136
-
137
- await page.evaluate( () => {
138
- const { removeEditorPanel } = wp.data.dispatch( 'core/edit-post' );
139
-
140
- removeEditorPanel( 'taxonomy-panel-category' );
141
- removeEditorPanel( 'taxonomy-panel-post_tag' );
142
- removeEditorPanel( 'featured-image' );
143
- removeEditorPanel( 'post-excerpt' );
144
- removeEditorPanel( 'discussion-panel' );
145
- removeEditorPanel( 'post-status' );
146
- } );
147
-
148
- const getPanelToggleSelector = ( panelTitle ) => {
149
- return `//div[contains(@class, "edit-post-sidebar")]//button[contains(@class, "components-panel__body-toggle") and contains(text(),"${ panelTitle }")]`;
150
- };
151
-
152
- expect(
153
- await page.$x( getPanelToggleSelector( 'Categories' ) )
154
- ).toEqual( [] );
155
- expect( await page.$x( getPanelToggleSelector( 'Tags' ) ) ).toEqual(
156
- []
157
- );
158
- expect(
159
- await page.$x( getPanelToggleSelector( 'Featured image' ) )
160
- ).toEqual( [] );
161
- expect( await page.$x( getPanelToggleSelector( 'Excerpt' ) ) ).toEqual(
162
- []
163
- );
164
- expect(
165
- await page.$x( getPanelToggleSelector( 'Discussion' ) )
166
- ).toEqual( [] );
167
- expect( await page.$x( getPanelToggleSelector( 'Summary' ) ) ).toEqual(
168
- []
169
- );
170
- } );
171
- } );
@@ -1,251 +0,0 @@
1
- /**
2
- * WordPress dependencies
3
- */
4
- import {
5
- createNewPost,
6
- findSidebarPanelWithTitle,
7
- openDocumentSettingsSidebar,
8
- publishPost,
9
- canvas,
10
- } from '@wordpress/e2e-test-utils';
11
-
12
- /**
13
- * Module constants
14
- */
15
- const TAG_TOKEN_SELECTOR =
16
- '.components-form-token-field__token-text span:not(.components-visually-hidden)';
17
-
18
- function generateRandomNumber() {
19
- // Using `Math.random()` directly is fine in this testing context.
20
- // eslint-disable-next-line no-restricted-syntax
21
- return Math.round( 1 + Math.random() * ( Number.MAX_SAFE_INTEGER - 1 ) );
22
- }
23
-
24
- describe( 'Taxonomies', () => {
25
- const canCreatTermInTaxonomy = ( taxonomy ) => {
26
- return page.evaluate( ( _taxonomy ) => {
27
- const post = wp.data.select( 'core/editor' ).getCurrentPost();
28
- if ( ! post._links ) {
29
- return false;
30
- }
31
- return !! post._links[ `wp:action-create-${ _taxonomy }` ];
32
- }, taxonomy );
33
- };
34
-
35
- const getSelectCategories = () => {
36
- return page.evaluate( () => {
37
- return Array.from(
38
- document.querySelectorAll(
39
- '.editor-post-taxonomies__hierarchical-terms-choice .components-checkbox-control__input:checked'
40
- )
41
- ).map( ( node ) => {
42
- return node.parentElement.nextSibling.innerText;
43
- } );
44
- } );
45
- };
46
-
47
- const getCurrentTags = async () => {
48
- const tagsPanel = await findSidebarPanelWithTitle( 'Tags' );
49
- return page.evaluate(
50
- ( node, selector ) => {
51
- return Array.from( node.querySelectorAll( selector ) ).map(
52
- ( field ) => {
53
- return field.innerText;
54
- }
55
- );
56
- },
57
- tagsPanel,
58
- TAG_TOKEN_SELECTOR
59
- );
60
- };
61
-
62
- const openSidebarPanelWithTitle = async ( title ) => {
63
- const panel = await page.waitForXPath(
64
- `//div[contains(@class,"edit-post-sidebar")]//button[@class="components-button components-panel__body-toggle"][contains(text(),"${ title }")]`
65
- );
66
- await panel.click();
67
- };
68
-
69
- it( 'should be able to open the categories panel and create a new main category if the user has the right capabilities', async () => {
70
- await createNewPost();
71
-
72
- await openDocumentSettingsSidebar();
73
-
74
- await openSidebarPanelWithTitle( 'Categories' );
75
-
76
- await page.waitForSelector(
77
- '.editor-post-taxonomies__hierarchical-terms-list'
78
- );
79
-
80
- // If the user has no permission to add a new category finish the test.
81
- if ( ! ( await canCreatTermInTaxonomy( 'categories' ) ) ) {
82
- return;
83
- }
84
-
85
- await page.waitForSelector(
86
- 'button.editor-post-taxonomies__hierarchical-terms-add'
87
- );
88
-
89
- // Click add new category button.
90
- await page.click(
91
- 'button.editor-post-taxonomies__hierarchical-terms-add'
92
- );
93
-
94
- // Type the category name in the field.
95
- await page.type(
96
- '.editor-post-taxonomies__hierarchical-terms-input input[type=text]',
97
- 'z rand category 1'
98
- );
99
-
100
- // Click the submit button.
101
- await page.click(
102
- '.editor-post-taxonomies__hierarchical-terms-submit'
103
- );
104
-
105
- // Wait for the categories to load.
106
- await page.waitForSelector(
107
- '.editor-post-taxonomies__hierarchical-terms-choice .components-checkbox-control__input:checked'
108
- );
109
-
110
- let selectedCategories = await getSelectCategories();
111
-
112
- // The new category is selected.
113
- expect( selectedCategories ).toHaveLength( 1 );
114
- expect( selectedCategories[ 0 ] ).toEqual( 'z rand category 1' );
115
-
116
- // Type something in the title so we can publish the post.
117
- await canvas().type( '.editor-post-title__input', 'Hello World' );
118
-
119
- // Publish the post.
120
- await publishPost();
121
-
122
- // Reload the editor.
123
- await page.reload();
124
-
125
- // Wait for the categories to load.
126
- await page.waitForSelector(
127
- '.editor-post-taxonomies__hierarchical-terms-choice .components-checkbox-control__input:checked'
128
- );
129
-
130
- selectedCategories = await getSelectCategories();
131
-
132
- // The category selection was persisted after the publish process.
133
- expect( selectedCategories ).toHaveLength( 1 );
134
- expect( selectedCategories[ 0 ] ).toEqual( 'z rand category 1' );
135
- } );
136
-
137
- it( "should be able to create a new tag with ' on the name", async () => {
138
- await createNewPost();
139
-
140
- await openDocumentSettingsSidebar();
141
-
142
- await openSidebarPanelWithTitle( 'Tags' );
143
-
144
- // If the user has no permission to add a new tag finish the test.
145
- if ( ! ( await canCreatTermInTaxonomy( 'tags' ) ) ) {
146
- return;
147
- }
148
-
149
- const tagsPanel = await findSidebarPanelWithTitle( 'Tags' );
150
- const tagInput = await tagsPanel.$(
151
- '.components-form-token-field__input'
152
- );
153
-
154
- // Click the tag input field.
155
- await tagInput.click();
156
-
157
- const tagName = "tag'-" + generateRandomNumber();
158
-
159
- // Type the category name in the field.
160
- await tagInput.type( tagName );
161
-
162
- // Press enter to create a new tag.
163
- await tagInput.press( 'Enter' );
164
-
165
- await page.waitForSelector( TAG_TOKEN_SELECTOR );
166
-
167
- // Get an array with the tags of the post.
168
- let tags = await getCurrentTags();
169
-
170
- // The post should only contain the tag we added.
171
- expect( tags ).toHaveLength( 1 );
172
- expect( tags[ 0 ] ).toEqual( tagName );
173
-
174
- // Type something in the title so we can publish the post.
175
- await canvas().type( '.editor-post-title__input', 'Hello World' );
176
-
177
- // Publish the post.
178
- await publishPost();
179
-
180
- // Reload the editor.
181
- await page.reload();
182
-
183
- // Wait for the tags to load.
184
- await page.waitForSelector( '.components-form-token-field__token' );
185
-
186
- tags = await getCurrentTags();
187
-
188
- // The tag selection was persisted after the publish process.
189
- expect( tags ).toHaveLength( 1 );
190
- expect( tags[ 0 ] ).toEqual( tagName );
191
- } );
192
-
193
- it( 'should be able to open the tags panel and create a new tag if the user has the right capabilities', async () => {
194
- await createNewPost();
195
-
196
- await openDocumentSettingsSidebar();
197
-
198
- await openSidebarPanelWithTitle( 'Tags' );
199
-
200
- // If the user has no permission to add a new tag finish the test.
201
- if ( ! ( await canCreatTermInTaxonomy( 'tags' ) ) ) {
202
- return;
203
- }
204
-
205
- // At the start there are no tag tokens.
206
- expect( await page.$$( TAG_TOKEN_SELECTOR ) ).toHaveLength( 0 );
207
-
208
- const tagsPanel = await findSidebarPanelWithTitle( 'Tags' );
209
- const tagInput = await tagsPanel.$(
210
- '.components-form-token-field__input'
211
- );
212
-
213
- // Click the tag input field.
214
- await tagInput.click();
215
-
216
- const tagName = 'tag-' + generateRandomNumber();
217
-
218
- // Type the category name in the field.
219
- await tagInput.type( tagName );
220
-
221
- // Press enter to create a new tag.
222
- await tagInput.press( 'Enter' );
223
-
224
- await page.waitForSelector( TAG_TOKEN_SELECTOR );
225
-
226
- // Get an array with the tags of the post.
227
- let tags = await getCurrentTags();
228
-
229
- // The post should only contain the tag we added.
230
- expect( tags ).toHaveLength( 1 );
231
- expect( tags[ 0 ] ).toEqual( tagName );
232
-
233
- // Type something in the title so we can publish the post.
234
- await canvas().type( '.editor-post-title__input', 'Hello World' );
235
-
236
- // Publish the post.
237
- await publishPost();
238
-
239
- // Reload the editor.
240
- await page.reload();
241
-
242
- // Wait for the tags to load.
243
- await page.waitForSelector( '.components-form-token-field__token' );
244
-
245
- tags = await getCurrentTags();
246
-
247
- // The tag selection was persisted after the publish process.
248
- expect( tags ).toHaveLength( 1 );
249
- expect( tags[ 0 ] ).toEqual( tagName );
250
- } );
251
- } );
@@ -1,53 +0,0 @@
1
- /**
2
- * WordPress dependencies
3
- */
4
- import {
5
- insertBlock,
6
- activateTheme,
7
- setOption,
8
- visitSiteEditor,
9
- enterEditMode,
10
- deleteAllTemplates,
11
- canvas,
12
- } from '@wordpress/e2e-test-utils';
13
-
14
- describe( 'Comments Form', () => {
15
- let previousCommentStatus;
16
-
17
- beforeAll( async () => {
18
- await activateTheme( 'emptytheme' );
19
- await deleteAllTemplates( 'wp_template' );
20
- previousCommentStatus = await setOption(
21
- 'default_comment_status',
22
- 'closed'
23
- );
24
- } );
25
-
26
- afterAll( async () => {
27
- await setOption( 'default_comment_status', previousCommentStatus );
28
- } );
29
-
30
- describe( 'placeholder', () => {
31
- it( 'displays in site editor even when comments are closed by default', async () => {
32
- // Navigate to "Singular" post template
33
- await visitSiteEditor();
34
- await expect( page ).toClick(
35
- '.edit-site-sidebar-navigation-item',
36
- { text: /templates/i }
37
- );
38
- await expect( page ).toClick(
39
- '.edit-site-sidebar-navigation-item',
40
- { text: /single entries/i }
41
- );
42
- await enterEditMode();
43
-
44
- // Insert post comments form
45
- await insertBlock( 'Comments Form' );
46
-
47
- // Ensure the placeholder is there
48
- await expect( canvas() ).toMatchElement(
49
- '.wp-block-post-comments-form .comment-form'
50
- );
51
- } );
52
- } );
53
- } );
@@ -1,39 +0,0 @@
1
- /**
2
- * WordPress dependencies
3
- */
4
- import { addQueryArgs } from '@wordpress/url';
5
- import { visitAdminPage } from '@wordpress/e2e-test-utils';
6
-
7
- async function setExperimentalFeaturesState( features, enable ) {
8
- const query = addQueryArgs( '', {
9
- page: 'gutenberg-experiments',
10
- } );
11
- await visitAdminPage( '/admin.php', query );
12
-
13
- await Promise.all(
14
- features.map( async ( feature ) => {
15
- await page.waitForSelector( feature );
16
- const checkedSelector = `${ feature }[checked=checked]`;
17
- const isChecked = !! ( await page.$( checkedSelector ) );
18
- if ( ( ! isChecked && enable ) || ( isChecked && ! enable ) ) {
19
- await page.click( feature );
20
- }
21
- } )
22
- );
23
- await Promise.all( [
24
- page.waitForNavigation( { waitUntil: 'networkidle0' } ),
25
- page.click( '#submit' ),
26
- ] );
27
- }
28
-
29
- /**
30
- * Establishes test lifecycle to enable experimental feature for the duration of
31
- * the grouped test block.
32
- *
33
- * @param {Array} features Array of {string} selectors of settings to enable.
34
- * Assumes they can be enabled with one click.
35
- */
36
- export function useExperimentalFeatures( features ) {
37
- beforeAll( () => setExperimentalFeaturesState( features, true ) );
38
- afterAll( () => setExperimentalFeaturesState( features, false ) );
39
- }
@@ -1,84 +0,0 @@
1
- [
2
- {
3
- "title": "Home",
4
- "url": "http://localhost:8889/",
5
- "menu_order": 1
6
- },
7
- {
8
- "title": "About",
9
- "type": "post_type",
10
- "object": "page",
11
- "menu_order": 2
12
- },
13
- {
14
- "title": "Our team",
15
- "type": "post_type",
16
- "object": "page",
17
- "menu_order": 3,
18
- "parent": 1
19
- },
20
- {
21
- "title": "Shop",
22
- "type": "post_type",
23
- "object": "page",
24
- "menu_order": 4
25
- },
26
- {
27
- "title": "Winter apparel",
28
- "type": "post_type",
29
- "object": "page",
30
- "menu_order": 5,
31
- "parent": 3
32
- },
33
- {
34
- "title": "Chunky socks",
35
- "type": "post_type",
36
- "object": "page",
37
- "menu_order": 6,
38
- "parent": 4
39
- },
40
- {
41
- "title": "Hideous hats",
42
- "type": "post_type",
43
- "object": "page",
44
- "menu_order": 7,
45
- "parent": 4
46
- },
47
- {
48
- "title": "Glorious gloves",
49
- "type": "post_type",
50
- "object": "page",
51
- "menu_order": 8,
52
- "parent": 4
53
- },
54
- {
55
- "title": "Jazzy Jumpers",
56
- "type": "post_type",
57
- "object": "page",
58
- "menu_order": 9,
59
- "parent": 4
60
- },
61
- {
62
- "title": "Shipping",
63
- "type": "post_type",
64
- "object": "page",
65
- "menu_order": 10
66
- },
67
- {
68
- "title": "Contact Us",
69
- "type": "post_type",
70
- "object": "page",
71
- "menu_order": 11
72
- },
73
- {
74
- "title": "WordPress.org",
75
- "url": "https://wordpress.org",
76
- "menu_order": 12
77
- },
78
- {
79
- "title": "Google",
80
- "url": "https://google.com",
81
- "menu_order": 13,
82
- "parent": 11
83
- }
84
- ]