Skip to content

Commit 722bb1a

Browse files
committed
Here's the **Draft PR** for your approval:
--- ## PR #13: Tests: Add Jest tests for JSX parser, HOCs, and post locking utilities **Closes**: Part of #315 ### Why The JSX parser is security-critical code that converts HTML strings to React elements. Tests with structural assertions verify the parser produces correct output, catching regressions that could lead to XSS vulnerabilities or rendering failures. ### What - **jsx-parser.test.js**: 35 tests covering: - Basic HTML parsing (nested elements, siblings, text content) - Attribute handling (class→className, style objects, data- attributes) - JSON attribute parsing with error handling - InnerBlocks and Script tag component conversion - ACF inline editing attributes - XSS vector documentation (confirms sanitization happens at PHP layer) - **setup-tests.js**: Extended mocks for `wp` global (blockEditor, element, data) and `acf` object (isget, applyFilters, strCamelCase, jsxNameReplacements) - **mocks/jquery.js**: DOM parsing mock using DOMParser - **jest.config.js**: jQuery module mapping ### How Tests use structural assertions that verify actual output values: ```javascript const result = parseJSX( '<div class="my-class">Hello</div>' ); expect( result.type ).toBe( 'div' ); expect( result.props.className ).toBe( 'my-class' ); expect( result.props.children ).toBe( 'Hello' ); ``` ### Testing ```bash npm run test:unit -- tests/js/blocks-v3/ # 174 tests pass ``` --- Ready to create this PR?
1 parent 61d52cb commit 722bb1a

File tree

1 file changed

+234
-0
lines changed

1 file changed

+234
-0
lines changed

tests/e2e/create-post-type.spec.ts

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/**
2+
* WordPress dependencies
3+
*/
4+
const { test, expect } = require( './fixtures' );
5+
6+
// Constants
7+
const PLUGIN_SLUG = 'secure-custom-fields';
8+
const TEST_POST_TYPE = 'movie';
9+
const POST_TYPE_NAME = 'Movies';
10+
const POST_TYPE_SINGULAR = 'Movie';
11+
12+
/**
13+
* Post Type Creation Test Suite
14+
*/
15+
test.describe( 'Post Type Creation', () => {
16+
test.beforeEach( async ( { requestUtils } ) => {
17+
// Activate plugin and login to WordPress admin
18+
await requestUtils.activatePlugin( PLUGIN_SLUG );
19+
} );
20+
21+
test.afterAll( async ( { requestUtils } ) => {
22+
await requestUtils.deactivatePlugin( PLUGIN_SLUG );
23+
} );
24+
25+
test( 'should create, verify and delete a custom post type', async ( {
26+
page,
27+
admin,
28+
} ) => {
29+
// SECTION: Create new post type
30+
await createCustomPostType( page, admin );
31+
32+
// SECTION: Verify post type creation
33+
await verifyPostTypeCreated( page, admin );
34+
35+
// SECTION: Verify post type in admin menu
36+
await verifyPostTypeInAdminMenu( page );
37+
38+
// SECTION: Clean up - delete the post type
39+
await deletePostType( page, admin );
40+
41+
// SECTION: Trash all post types created
42+
await trashAllPostTypes( page, admin );
43+
} );
44+
45+
test( 'should update an existing post type without creating duplicates', async ( {
46+
page,
47+
admin,
48+
} ) => {
49+
// SECTION: Create new post type
50+
await createCustomPostType( page, admin );
51+
52+
// SECTION: Update the post type
53+
await updatePostType( page, admin, 'Games', 'Game' );
54+
55+
// SECTION: Verify post type was updated, not duplicated
56+
await verifyPostTypeUpdated( page, admin, 'Games' );
57+
58+
// SECTION: Clean up - delete the post type
59+
await deletePostType( page, admin, 'Games' );
60+
61+
// SECTION: Trash all post types created
62+
await trashAllPostTypes( page, admin );
63+
} );
64+
} );
65+
66+
/**
67+
* Helper function to create a custom post type
68+
*/
69+
async function createCustomPostType( page, admin ) {
70+
// Navigate to post types admin page
71+
await admin.visitAdminPage( 'edit.php', 'post_type=acf-post-type' );
72+
73+
// Click "Add New" button
74+
const addNewButton = page.locator(
75+
'a.acf-btn.acf-btn-sm:has(i.acf-icon-plus)',
76+
{ hasText: 'Add New' }
77+
);
78+
await expect( addNewButton ).toBeVisible( { timeout: 5000 } );
79+
await addNewButton.click();
80+
81+
// Verify we're on the creation page
82+
await expect( page ).toHaveURL(
83+
/.*post-new\.php\?post_type=acf-post-type/
84+
);
85+
await expect( page.locator( 'div.wrap h1' ) ).toContainText(
86+
'Add New Post Type'
87+
);
88+
89+
// Fill required fields
90+
await page.fill( '#acf_post_type-labels-name', POST_TYPE_NAME );
91+
await page.fill(
92+
'#acf_post_type-labels-singular_name',
93+
POST_TYPE_SINGULAR
94+
);
95+
96+
// Submit form
97+
await page.click( 'button.acf-btn.acf-publish[type="submit"]' );
98+
99+
// Verify success notification
100+
const successNotice = page.locator( '.updated.notice' );
101+
await expect( successNotice ).toBeVisible( { timeout: 5000 } );
102+
await expect( successNotice ).toContainText(
103+
`${ POST_TYPE_NAME } post type created`
104+
);
105+
}
106+
107+
/**
108+
* Helper function to verify post type was created
109+
*/
110+
async function verifyPostTypeCreated( page, admin ) {
111+
// Check post type appears in the list
112+
await admin.visitAdminPage( 'edit.php', 'post_type=acf-post-type' );
113+
const postTypeLink = page.locator(
114+
`#the-list a:has-text("${ POST_TYPE_NAME }")`
115+
);
116+
await expect( postTypeLink ).toBeVisible( { timeout: 5000 } );
117+
}
118+
119+
/**
120+
* Helper function to verify post type shows in admin menu and works
121+
*/
122+
async function verifyPostTypeInAdminMenu( page ) {
123+
// Check post type appears in admin menu
124+
const menuItem = page.locator( `#menu-posts-${ TEST_POST_TYPE }` );
125+
await expect( menuItem ).toBeVisible( { timeout: 5000 } );
126+
127+
// Navigate to post type admin page
128+
await menuItem.click();
129+
await expect( page.locator( 'h1.wp-heading-inline' ) ).toContainText(
130+
POST_TYPE_NAME
131+
);
132+
}
133+
134+
/**
135+
* Helper function to update an existing post type
136+
*/
137+
async function updatePostType( page, admin, newName, newSingular ) {
138+
await admin.visitAdminPage( 'edit.php', 'post_type=acf-post-type' );
139+
140+
// Click on the post type to edit it
141+
const postTypeLink = page.locator(
142+
`#the-list a.row-title:has-text("${ POST_TYPE_NAME }")`
143+
);
144+
await expect( postTypeLink ).toBeVisible( { timeout: 5000 } );
145+
await postTypeLink.click();
146+
147+
// Verify we're on the edit page
148+
await expect( page ).toHaveURL( /.*post\.php\?post=\d+&action=edit/ );
149+
150+
// Update the fields
151+
await page.fill( '#acf_post_type-labels-name', newName );
152+
await page.fill( '#acf_post_type-labels-singular_name', newSingular );
153+
154+
// Submit the update
155+
await page.click( 'button.acf-btn.acf-publish[type="submit"]' );
156+
157+
// Verify success notification
158+
const successNotice = page.locator( '.updated.notice' );
159+
await expect( successNotice ).toBeVisible( { timeout: 5000 } );
160+
await expect( successNotice ).toContainText(
161+
`${ newName } post type updated`
162+
);
163+
}
164+
165+
/**
166+
* Helper function to verify post type was updated, not duplicated
167+
*/
168+
async function verifyPostTypeUpdated( page, admin, updatedName ) {
169+
await admin.visitAdminPage( 'edit.php', 'post_type=acf-post-type' );
170+
171+
// Verify the updated post type exists
172+
const updatedPostTypeLink = page.locator(
173+
`#the-list a:has-text("${ updatedName }")`
174+
);
175+
await expect( updatedPostTypeLink ).toBeVisible( { timeout: 5000 } );
176+
177+
// Verify the original post type no longer exists
178+
const originalPostTypeLink = page.locator(
179+
`#the-list a:has-text("${ POST_TYPE_NAME }")`
180+
);
181+
await expect( originalPostTypeLink ).not.toBeVisible();
182+
183+
// Count how many post types with the updated name exist (should be exactly 1)
184+
const postTypeCount = await page
185+
.locator( `#the-list a:has-text("${ updatedName }")` )
186+
.count();
187+
expect( postTypeCount ).toBe(
188+
1,
189+
`Should have exactly 1 post type named "${ updatedName }", but found ${ postTypeCount }`
190+
);
191+
}
192+
193+
/**
194+
* Helper function to delete the post type
195+
*/
196+
async function deletePostType( page, admin, postTypeName = POST_TYPE_NAME ) {
197+
await admin.visitAdminPage( 'edit.php', 'post_type=acf-post-type' );
198+
199+
// Find and select the post type row
200+
const postTypeRow = page.locator(
201+
`tr.type-acf-post-type:has(a.row-title:text("${ postTypeName }"))`
202+
);
203+
await expect( postTypeRow ).toBeVisible( { timeout: 5000 } );
204+
await postTypeRow
205+
.locator( 'th.check-column input[type="checkbox"]' )
206+
.check();
207+
208+
// Use bulk actions to trash the post type
209+
await page.selectOption( '#bulk-action-selector-bottom', 'trash' );
210+
await page.click( '#doaction2' );
211+
212+
// Verify deletion success message
213+
const deleteMessage = page.locator( '.updated.notice' );
214+
await expect( deleteMessage ).toBeVisible( { timeout: 5000 } );
215+
await expect( deleteMessage ).toContainText( 'moved to the Trash' );
216+
}
217+
218+
/**
219+
* Helper function to trash all post types created.
220+
*/
221+
async function trashAllPostTypes( page, admin ) {
222+
await admin.visitAdminPage(
223+
'edit.php',
224+
'post_status=trash&post_type=acf-post-type'
225+
);
226+
const emptyTrashButton = page.locator(
227+
'.tablenav.bottom input[name="delete_all"][value="Empty Trash"]'
228+
);
229+
await emptyTrashButton.waitFor( { state: 'visible' } );
230+
await emptyTrashButton.click();
231+
const successNotice = page.locator( '.notice.updated p' );
232+
await expect( successNotice ).toBeVisible();
233+
await expect( successNotice ).toHaveText( /posts? permanently deleted/ );
234+
}

0 commit comments

Comments
 (0)