Skip to content

Content Guidelines: Add frontend test coverage#76676

Open
im3dabasia wants to merge 5 commits intoWordPress:trunkfrom
im3dabasia:try/add-tests-76384
Open

Content Guidelines: Add frontend test coverage#76676
im3dabasia wants to merge 5 commits intoWordPress:trunkfrom
im3dabasia:try/add-tests-76384

Conversation

@im3dabasia
Copy link
Copy Markdown
Contributor

@im3dabasia im3dabasia commented Mar 19, 2026

Work in Progress

What?

Closes #76390
Parent issue: #76384

Adds the tests mentioned in the issue.

Use of AI Tools

Claude

@im3dabasia im3dabasia self-assigned this Mar 19, 2026
@im3dabasia im3dabasia added [Type] Enhancement A suggestion for improvement. [Feature] Guidelines An experimental feature for adding site-wide editorial rules. labels Mar 19, 2026
@dhruvikpatel18
Copy link
Copy Markdown
Contributor

@im3dabasia, Can I work on Revision history interactions tests as it's not seem included in this PR. If you're open to that, please invite me to the collaboration for your branch.

@im3dabasia im3dabasia marked this pull request as ready for review April 13, 2026 12:06
@im3dabasia im3dabasia requested a review from Copilot April 13, 2026 12:06
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 13, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: im3dabasia <[email protected]>
Co-authored-by: dhruvikpatel18 <[email protected]>
Co-authored-by: aswasif007 <[email protected]>
Co-authored-by: aagam-shah <[email protected]>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@im3dabasia
Copy link
Copy Markdown
Contributor Author

@andrewserong, @aagam-shah would appreciate your review on this PR when you get a chance

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Jest unit/integration-style tests to improve frontend coverage for the Content Guidelines feature, targeting store behavior and import/export and revision-history flows (per #76390 / tracking #76384).

Changes:

  • Added store unit tests covering selectors, action creators, and basic reducer immutability checks.
  • Added CRUD-oriented store tests validating category + block guideline updates and hydration from REST responses.
  • Added API import/export tests covering valid imports, partial imports, invalid input handling, rollback on save failures, and export JSON structure.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
routes/content-guidelines/tests/store.test.ts Unit tests for store selectors/actions/reducer edge cases.
routes/content-guidelines/tests/guidelines-crud.test.ts Store CRUD-style tests validating read/write/update “flows” via reducer+selectors.
routes/content-guidelines/tests/api-import-export.test.ts Tests for importContentGuidelines / exportContentGuidelines behavior and rollback/export structure.
routes/content-guidelines/tests/revision-history.test.ts Intended revision history coverage, but currently mocks the API module and doesn’t exercise the component logic.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +17 to +21
// Mock dependencies
jest.mock( '@wordpress/data' );
jest.mock( '@wordpress/notices' );
jest.mock( '../api' );

Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jest.mock( '../api' ) turns the API functions into mocks, but the tests then call fetchContentGuidelinesRevisions / restoreContentGuidelinesRevision directly. This makes the suite assert only that the mocked functions were called with the same args the test passed in, rather than exercising the real revision-history flow (the RevisionHistory component’s useEffect/handleRestore logic). Consider rendering components/revision-history.tsx with React Testing Library (mocking useSelect/useDispatch and the API module), or alternatively remove the API mock and unit-test the real api.ts functions by mocking @wordpress/api-fetch.

Copilot uses AI. Check for mistakes.
Comment on lines +148 to +170
test( 'should handle fetch error and dispatch error notice', async () => {
mockSelect.getId.mockReturnValue( 123 );

mockFetchContentGuidelinesRevisions.mockRejectedValue(
new Error( 'API Error' )
);

const noticesDispatch = ( dispatch as jest.Mock )( noticesStore );

await fetchContentGuidelinesRevisions( {
guidelinesId: 123,
perPage: 100,
} ).catch( () => {
noticesDispatch.createErrorNotice(
'Could not load revision history. Please try again.',
{ type: 'snackbar' }
);
} );

expect( mockDispatch.createErrorNotice ).toHaveBeenCalledWith(
'Could not load revision history. Please try again.',
{ type: 'snackbar' }
);
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several tests manually call noticesDispatch.createErrorNotice(...) / createSuccessNotice(...) inside the test itself (e.g. in .catch blocks) instead of asserting that production code dispatched these notices. This can pass even if the app never shows notices. Adjust the tests to trigger the actual code paths (render the RevisionHistory component and interact with it, or call a real implementation function) and assert the notice dispatches happen as a result.

Suggested change
test( 'should handle fetch error and dispatch error notice', async () => {
mockSelect.getId.mockReturnValue( 123 );
mockFetchContentGuidelinesRevisions.mockRejectedValue(
new Error( 'API Error' )
);
const noticesDispatch = ( dispatch as jest.Mock )( noticesStore );
await fetchContentGuidelinesRevisions( {
guidelinesId: 123,
perPage: 100,
} ).catch( () => {
noticesDispatch.createErrorNotice(
'Could not load revision history. Please try again.',
{ type: 'snackbar' }
);
} );
expect( mockDispatch.createErrorNotice ).toHaveBeenCalledWith(
'Could not load revision history. Please try again.',
{ type: 'snackbar' }
);
test( 'should reject when fetching revisions fails', async () => {
mockSelect.getId.mockReturnValue( 123 );
mockFetchContentGuidelinesRevisions.mockRejectedValue(
new Error( 'API Error' )
);
await expect(
fetchContentGuidelinesRevisions( {
guidelinesId: 123,
perPage: 100,
} )
).rejects.toThrow( 'API Error' );

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Comment on lines +328 to +334
let state = createInitialState();
state.categories = {
site: 'Site',
copy: 'Copy',
images: 'Images',
additional: 'Additional',
blocks: {},
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createInitialState() returns the store's DEFAULT_STATE object by reference (via reducer(undefined, ...)). This test then mutates state.categories directly, which can leak state across tests within this file and also hides potential immutability issues. Prefer constructing a new state object (e.g. shallow-clone createInitialState() and override categories) instead of mutating the returned state in-place.

Suggested change
let state = createInitialState();
state.categories = {
site: 'Site',
copy: 'Copy',
images: 'Images',
additional: 'Additional',
blocks: {},
let state = {
...createInitialState(),
categories: {
site: 'Site',
copy: 'Copy',
images: 'Images',
additional: 'Additional',
blocks: {},
},

Copilot uses AI. Check for mistakes.
@aswasif007 aswasif007 self-requested a review April 14, 2026 07:57
Copy link
Copy Markdown
Contributor

@aswasif007 aswasif007 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this!

Mostly good, but I've requested some improvements.

Comment on lines +199 to +212
await expect( importContentGuidelines( file ) ).rejects.toThrow(
'Save failed'
);

// Verify rollback was called
expect( mockDispatch.setGuideline ).toHaveBeenCalledWith(
'site',
'Original site'
);
expect( mockDispatch.setGuideline ).toHaveBeenCalledWith(
'copy',
'Original copy'
);
} );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rollback tests can pass even if rollback is incomplete or out of order.
That's because The rollback assertions here only check that original values were called at least once, not that they are the final state after failure. If code regresses (e.g., writes original values and then overwrites again), these tests could still pass.

Comment on lines +252 to +261
// Verify rollback for block guidelines
expect( mockDispatch.setBlockGuideline ).toHaveBeenCalledWith(
'core/paragraph',
'Original paragraph'
);
expect( mockDispatch.setBlockGuideline ).toHaveBeenCalledWith(
'core/heading',
'Original heading'
);
} );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation explicitly clears current blocks before restoring previous ones; this is important when import introduces new block keys. The test does not assert that clear step happened, so a regression in that path would be undetected

Comment on lines +89 to +96
expect( mockDispatch.setGuideline ).toHaveBeenCalledWith(
'site',
'Site guidelines'
);
expect( mockDispatch.setGuideline ).toHaveBeenCalledWith(
'copy',
'Copy guidelines'
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also assert the presence of the images and additional guidelines.

Comment on lines +91 to +101
const result = await fetchContentGuidelinesRevisions( {
guidelinesId,
perPage: 100,
} );

expect( mockFetchContentGuidelinesRevisions ).toHaveBeenCalledWith(
{
guidelinesId: 123,
perPage: 100,
}
);
Copy link
Copy Markdown
Contributor

@aswasif007 aswasif007 Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are calling the mocked function then asserting the test's own invocation.

Comment on lines +148 to +170
test( 'should handle fetch error and dispatch error notice', async () => {
mockSelect.getId.mockReturnValue( 123 );

mockFetchContentGuidelinesRevisions.mockRejectedValue(
new Error( 'API Error' )
);

const noticesDispatch = ( dispatch as jest.Mock )( noticesStore );

await fetchContentGuidelinesRevisions( {
guidelinesId: 123,
perPage: 100,
} ).catch( () => {
noticesDispatch.createErrorNotice(
'Could not load revision history. Please try again.',
{ type: 'snackbar' }
);
} );

expect( mockDispatch.createErrorNotice ).toHaveBeenCalledWith(
'Could not load revision history. Please try again.',
{ type: 'snackbar' }
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Comment on lines +367 to +375
await restoreContentGuidelinesRevision(
guidelinesId,
initial.revisions[ 0 ].id
);

expect( mockRestoreContentGuidelinesRevision ).toHaveBeenCalledWith(
123,
1
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above - we are calling a mocked function, then asserting that invocation.

Comment on lines +267 to +282
test( 'creates action with response payload', () => {
const response: RestGuidelinesResponse = {
id: 5,
status: 'draft',
guideline_categories: {
site: { guidelines: 'Site' },
copy: { guidelines: 'Copy' },
images: { guidelines: 'Images' },
additional: { guidelines: 'Additional' },
},
};

const action = actions.setFromResponse( response );

expect( action.type ).toBe( 'SET_FROM_RESPONSE' );
expect( action.response ).toBe( response );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file validates the action shape, but not reducer output after parsing API responses, which is where most complexity lives (blocks handling, fallback defaults, nullable fields). A bug in response parsing could pass this suite.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Guidelines An experimental feature for adding site-wide editorial rules. [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Content Guidelines: Add frontend test coverage

4 participants