Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,31 @@ import { beforeAll, describe, expect, it, jest } from '@jest/globals';
*/
import { useDispatch, useSelect } from '@wordpress/data';

jest.mock('@wordpress/edit-post', () => ({
PluginSidebar: ({ children }) => children,
PluginSidebarMoreMenuItem: ({ children }) => children,
}));

jest.mock('@wordpress/block-editor', () => ({
BlockIcon: ({ icon }) => icon,
}));

/**
* Internal dependencies
*/
import AMPDocumentStatusNotification from '../index';
import { useAMPDocumentToggle } from '../../../hooks/use-amp-document-toggle';
import { useErrorsFetchingStateChanges } from '../../../hooks/use-errors-fetching-state-changes';

jest.mock('@wordpress/data/build/components/use-select', () => jest.fn());
jest.mock('@wordpress/data/build/components/use-dispatch/use-dispatch', () =>
jest.fn()
);
jest.mock('@wordpress/data', () => ({
useSelect: jest.fn(),
useDispatch: jest.fn(),
combineReducers: jest.fn(),
createSelector: jest.fn(),
createReduxStore: jest.fn(),
register: jest.fn(),
}));

jest.mock('../../../hooks/use-amp-document-toggle', () => ({
useAMPDocumentToggle: jest.fn(),
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ import { useDispatch, useSelect } from '@wordpress/data';
import AMPRevalidateNotification from '../revalidate-notification';
import { useErrorsFetchingStateChanges } from '../../../hooks/use-errors-fetching-state-changes';

jest.mock('@wordpress/data/build/components/use-select', () => jest.fn());
jest.mock('@wordpress/data/build/components/use-dispatch/use-dispatch', () =>
jest.fn()
);
jest.mock('@wordpress/data', () => ({
useSelect: jest.fn(),
useDispatch: jest.fn(),
combineReducers: jest.fn(),
createSelector: jest.fn(),
createReduxStore: jest.fn(),
register: jest.fn(),
}));

jest.mock('../../../hooks/use-errors-fetching-state-changes', () => ({
useErrorsFetchingStateChanges: jest.fn(),
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ import { useDispatch, useSelect } from '@wordpress/data';
*/
import AMPValidationStatusNotification from '../status-notification';

jest.mock('@wordpress/data/build/components/use-select', () => jest.fn());
jest.mock('@wordpress/data/build/components/use-dispatch/use-dispatch', () =>
jest.fn()
);
jest.mock('@wordpress/data', () => ({
useSelect: jest.fn(),
useDispatch: jest.fn(),
combineReducers: jest.fn(),
createSelector: jest.fn(),
createReduxStore: jest.fn(),
register: jest.fn(),
}));

describe('AMPValidationStatusNotification', () => {
const autosave = jest.fn();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import { useDispatch, useSelect } from '@wordpress/data';
*/
import { useAMPDocumentToggle } from '../use-amp-document-toggle';

jest.mock('@wordpress/data/build/components/use-select', () => jest.fn());
jest.mock('@wordpress/data/build/components/use-dispatch/use-dispatch', () =>
jest.fn()
);
jest.mock('@wordpress/data', () => ({
useSelect: jest.fn(),
useDispatch: jest.fn(),
}));

describe('useAMPDocumentToggle', () => {
const editPost = jest.fn();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import { useSelect } from '@wordpress/data';
*/
import { useErrorsFetchingStateChanges } from '../use-errors-fetching-state-changes';

jest.mock('@wordpress/data/build/components/use-select', () => jest.fn());
jest.mock('@wordpress/data', () => ({
useSelect: jest.fn(),
createReduxStore: jest.fn(),
register: jest.fn(),
}));

describe('useErrorsFetchingStateChanges', () => {
function ComponentContainingHook() {
Expand Down
144 changes: 139 additions & 5 deletions assets/src/block-validation/hooks/test/use-post-dirty-state-updates.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
* External dependencies
*/
import { render, act } from '@testing-library/react';
import { beforeAll, describe, expect, it, jest } from '@jest/globals';
import {
beforeAll,
beforeEach,
describe,
expect,
it,
jest,
} from '@jest/globals';

/**
* WordPress dependencies
Expand All @@ -21,11 +28,42 @@ import {
import { usePostDirtyStateChanges } from '../use-post-dirty-state-changes';
import { store as blockValidationStore } from '../../store';

jest.mock('@wordpress/data/build/components/use-select', () => jest.fn());
jest.mock('@wordpress/compose/build/hooks/use-debounce', () => (fn) => fn);
const mockState = { isPostDirty: false };

jest.mock('@wordpress/data', () => ({
useSelect: jest.fn(),
useDispatch: jest.fn(() => ({})),
createReduxStore: jest.fn((key, options) => ({ key, ...options })),
register: jest.fn(),
select: jest.fn((storeName) => {
if (storeName?.key === 'amp/block-validation') {
return { getIsPostDirty: jest.fn(() => mockState.isPostDirty) };
}
return { getIsPostDirty: jest.fn(() => false) };
}),
dispatch: jest.fn((storeName) => {
if (storeName === 'test/use-post-dirty-state-updates') {
return {
change: jest.fn(() => {
mockState.isPostDirty = true;
}),
};
}
return { change: jest.fn() };
}),
subscribe: jest.fn(() => jest.fn()),
}));

const { useDispatch } = jest.requireMock('@wordpress/data');
jest.mock('@wordpress/compose', () => ({
...jest.requireActual('@wordpress/compose'),
useDebounce: (fn) => fn,
}));

describe('usePostDirtyStateChanges', () => {
const getEditedPostContent = jest.fn();
const setIsPostDirty = jest.fn();
const setMaybeIsPostDirty = jest.fn();

function ComponentContainingHook() {
usePostDirtyStateChanges();
Expand All @@ -38,12 +76,26 @@ describe('usePostDirtyStateChanges', () => {
}

function setupUseSelect(overrides) {
useSelect.mockImplementation(() => ({
const settings = {
getEditedPostContent,
isSavingOrPreviewingPost: false,
isPostDirty: select(blockValidationStore).getIsPostDirty(),
...overrides,
}));
};

// If saving/previewing, clear dirty state
if (settings.isSavingOrPreviewingPost) {
mockState.isPostDirty = false;
}

useSelect.mockImplementation(() => settings);
}

function setupUseDispatch() {
useDispatch.mockReturnValue({
setIsPostDirty,
setMaybeIsPostDirty,
});
}

beforeAll(() => {
Expand All @@ -57,6 +109,11 @@ describe('usePostDirtyStateChanges', () => {
);
});

beforeEach(() => {
mockState.isPostDirty = false;
setupUseDispatch();
});

it('sets dirty state when content changes and clears it after save', () => {
// Initial render.
getEditedPostContent.mockReturnValue('initial');
Expand Down Expand Up @@ -98,4 +155,81 @@ describe('usePostDirtyStateChanges', () => {

expect(select(blockValidationStore).getIsPostDirty()).toBe(true);
});

it('calls setIsPostDirty(true) when updated content differs from initial content', () => {
// Mock the subscribe function to capture the listener
let subscribedListener;
const mockSubscribe = jest.fn((listener) => {
subscribedListener = listener;
return jest.fn(); // return unsubscribe function
});

const { subscribe } = jest.requireMock('@wordpress/data');
subscribe.mockImplementation(mockSubscribe);

// Initial render with initial content
getEditedPostContent.mockReturnValue('initial content');
setupUseSelect({
isPostDirty: false,
isSavingOrPreviewingPost: false,
});

const { rerender } = render(<ComponentContainingHook />);

// Verify setIsPostDirty was not called initially
expect(setIsPostDirty).not.toHaveBeenCalledWith(true);

// Change the content returned by getEditedPostContent
getEditedPostContent.mockReturnValue('modified content');

// Trigger the listener to update updatedContent state
act(() => {
subscribedListener();
});

// Re-render to trigger the useEffect that checks content !== updatedContent
act(() => {
rerender(<ComponentContainingHook />);
});

// Verify setIsPostDirty(true) was called when content changed
expect(setIsPostDirty).toHaveBeenCalledWith(true);
});

it('calls setUpdatedContent when listener is triggered', () => {
// Mock the subscribe function to capture the listener
let subscribedListener;
const mockSubscribe = jest.fn((listener) => {
subscribedListener = listener;
return jest.fn(); // return unsubscribe function
});

// Import and mock the subscribe function
const { subscribe } = jest.requireMock('@wordpress/data');
subscribe.mockImplementation(mockSubscribe);

// Initial render
getEditedPostContent.mockReturnValue('initial');
setupUseSelect({
isPostDirty: false, // Ensure post is not dirty so subscription happens
isSavingOrPreviewingPost: false,
});

render(<ComponentContainingHook />);

// Verify subscribe was called
expect(subscribe).toHaveBeenCalledWith(expect.any(Function));
expect(subscribedListener).toBeDefined();

// Change the content that getEditedPostContent returns
getEditedPostContent.mockReturnValue('updated content from listener');

// Trigger the listener (simulating store change)
act(() => {
subscribedListener();
});

// Verify getEditedPostContent was called during listener execution
expect(getEditedPostContent).toHaveBeenCalledWith();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External dependencies
*/
import { render, waitFor } from '@testing-library/react';
import { describe, expect, it, jest } from '@jest/globals';
import { beforeEach, describe, expect, it, jest } from '@jest/globals';

/**
* WordPress dependencies
Expand All @@ -19,22 +19,57 @@ import {
import { store as blockValidationStore } from '../../store';

// This allows us to tweak the returned value on each test
jest.mock('@wordpress/data/build/components/use-select', () => jest.fn());
const mockValidationState = { errors: [] };

jest.mock('@wordpress/data', () => ({
useSelect: jest.fn(),
useDispatch: jest.fn(() => ({
setIsFetchingErrors: jest.fn(),
setFetchingErrorsRequestErrorMessage: jest.fn(),
setReviewLink: jest.fn(),
setSupportLink: jest.fn(),
setValidationErrors: jest.fn((errors) => {
mockValidationState.errors = errors || [];
}),
})),
createReduxStore: jest.fn((key, options) => ({ key, ...options })),
register: jest.fn(),
select: jest.fn((storeName) => {
if (storeName?.key === 'amp/block-validation') {
return {
getIsPostDirty: jest.fn(() => false),
getValidationErrors: jest.fn(() => mockValidationState.errors),
};
}
return {
getIsPostDirty: jest.fn(() => false),
getValidationErrors: jest.fn(() => []),
};
}),
dispatch: jest.fn(() => ({ change: jest.fn() })),
}));

jest.mock(
'@wordpress/api-fetch',
() => () =>
new Promise((resolve) => {
resolve({
review_link: 'http://site.test/wp-admin',
results:
require('../../store/test/__data__/raw-validation-errors')
.rawValidationErrors,
results: Array(8)
.fill()
.map((_, i) => ({
code: `mock_error_${i}`,
message: `Mock validation error ${i}`,
})),
});
})
);

describe('useValidationErrorStateUpdates', () => {
beforeEach(() => {
mockValidationState.errors = [];
});

function ComponentContainingHook() {
useValidationErrorStateUpdates();

Expand Down
18 changes: 18 additions & 0 deletions assets/src/block-validation/store/test/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ describe('Block validation data store', () => {
'http://example.com'
);

dispatch(blockValidationStore).setSupportLink(
'http://support.example.com'
);

expect(select(blockValidationStore).getSupportLink()).toBe(
'http://support.example.com'
);

expect(select(blockValidationStore).getAMPCompatibilityBroken()).toBe(
false
);
Expand Down Expand Up @@ -83,5 +91,15 @@ describe('Block validation data store', () => {
dispatch(blockValidationStore).setIsFetchingErrors(false);

expect(select(blockValidationStore).getIsFetchingErrors()).toBe(false);

expect(select(blockValidationStore).getIsPostDirty()).toBe(false);

dispatch(blockValidationStore).setIsPostDirty(true);

expect(select(blockValidationStore).getIsPostDirty()).toBe(true);

dispatch(blockValidationStore).setIsPostDirty(false);

expect(select(blockValidationStore).getIsPostDirty()).toBe(false);
});
});
Loading
Loading