| 1 |
// The hover bar's translated-content guard. On a non-default-language render, |
| 2 |
// clicking the Quick Edit pill on a text block must (a) show the error right |
| 3 |
// under the bar — not a top-right pill the Agent sidebar would hide — and |
| 4 |
// (b) never commit a selection (which renders nothing and, worse, the |
| 5 |
// unsubSelected cancel-on-clear logic would tear down a co-staged Ask AI |
| 6 |
// block). The bar (and its Ask AI pill) stays put. |
| 7 |
|
| 8 |
const mockSetSelected = jest.fn(); |
| 9 |
const mockSetCommittedSelection = jest.fn(); |
| 10 |
let mockStoreState; |
| 11 |
|
| 12 |
jest.mock('@quick-edit/state/edit-mode', () => ({ |
| 13 |
useEditModeStore: { |
| 14 |
getState: () => ({ on: true }), |
| 15 |
subscribe: () => () => {}, |
| 16 |
}, |
| 17 |
})); |
| 18 |
jest.mock('@quick-edit/state/store', () => ({ |
| 19 |
useQuickEditStore: { |
| 20 |
getState: () => mockStoreState, |
| 21 |
subscribe: () => () => {}, |
| 22 |
setState: () => {}, |
| 23 |
}, |
| 24 |
})); |
| 25 |
jest.mock('@quick-edit/lib/dom', () => ({ |
| 26 |
resolveTarget: (el) => ({ |
| 27 |
el, |
| 28 |
blockType: el?.dataset?.blockType || 'core/heading', |
| 29 |
blockId: 1, |
| 30 |
source: { kind: 'post', id: 1 }, |
| 31 |
}), |
| 32 |
})); |
| 33 |
jest.mock('@quick-edit/lib/quick-edit-handlers', () => ({ |
| 34 |
hasQuickEditModalFor: () => true, |
| 35 |
})); |
| 36 |
jest.mock('@quick-edit/lib/ask-ai', () => ({ |
| 37 |
isAgentAvailable: () => false, |
| 38 |
isAgentSidebarOpen: () => false, |
| 39 |
hasAgentBlockSelected: () => false, |
| 40 |
askAiAboutElement: jest.fn(), |
| 41 |
stageAgentBlock: jest.fn(), |
| 42 |
subscribeToAgentBlock: () => () => {}, |
| 43 |
})); |
| 44 |
jest.mock('@quick-edit/lib/agent-gate', () => ({ |
| 45 |
isAgentEligibleForTarget: () => false, |
| 46 |
})); |
| 47 |
jest.mock('@quick-edit/lib/block-source-cache', () => ({ |
| 48 |
prefetchBlockSource: jest.fn(), |
| 49 |
})); |
| 50 |
jest.mock('@quick-edit/lib/click-rule', () => ({ |
| 51 |
decideClickAction: () => ({ action: 'ignore' }), |
| 52 |
})); |
| 53 |
jest.mock('@quick-edit/lib/insights', () => ({ track: jest.fn() })); |
| 54 |
jest.mock('@quick-edit/lib/save-bridge', () => ({ |
| 55 |
hasSaver: () => false, |
| 56 |
saveSelected: jest.fn(), |
| 57 |
})); |
| 58 |
|
| 59 |
const importBar = () => require('@quick-edit/lib/hover-bar'); |
| 60 |
|
| 61 |
const setTranslated = (isTranslated) => { |
| 62 |
window.extQuickEditData = { |
| 63 |
quickEditEnabled: true, |
| 64 |
translatedContext: isTranslated |
| 65 |
? { isTranslated: true, plugin: 'translatepress' } |
| 66 |
: { isTranslated: false, plugin: null }, |
| 67 |
}; |
| 68 |
}; |
| 69 |
|
| 70 |
const heading = () => { |
| 71 |
const el = document.createElement('h2'); |
| 72 |
el.setAttribute('data-extendify-agent-block-id', '1'); |
| 73 |
el.getBoundingClientRect = () => ({ |
| 74 |
top: 100, |
| 75 |
bottom: 140, |
| 76 |
left: 50, |
| 77 |
right: 250, |
| 78 |
width: 200, |
| 79 |
height: 40, |
| 80 |
}); |
| 81 |
document.body.appendChild(el); |
| 82 |
return el; |
| 83 |
}; |
| 84 |
|
| 85 |
const qerPill = () => |
| 86 |
document.querySelector( |
| 87 |
'.extendify-quick-edit-bar [data-extendify-quick-edit-pill]', |
| 88 |
); |
| 89 |
|
| 90 |
beforeEach(() => { |
| 91 |
jest.useFakeTimers(); |
| 92 |
jest.resetModules(); |
| 93 |
jest.clearAllMocks(); |
| 94 |
document.body.innerHTML = ''; |
| 95 |
mockStoreState = { |
| 96 |
selected: null, |
| 97 |
committedSelection: null, |
| 98 |
agentBlock: null, |
| 99 |
setSelected: mockSetSelected, |
| 100 |
setCommittedSelection: mockSetCommittedSelection, |
| 101 |
clearSelected: jest.fn(), |
| 102 |
}; |
| 103 |
}); |
| 104 |
|
| 105 |
afterEach(() => { |
| 106 |
jest.runOnlyPendingTimers(); |
| 107 |
jest.useRealTimers(); |
| 108 |
delete window.extQuickEditData; |
| 109 |
}); |
| 110 |
|
| 111 |
describe('hover-bar — translated text guard', () => { |
| 112 |
it('Quick Edit pill on a translated text block shows the error under the bar and commits no selection', () => { |
| 113 |
setTranslated(true); |
| 114 |
const { showBar } = importBar(); |
| 115 |
showBar(heading()); |
| 116 |
|
| 117 |
const pill = qerPill(); |
| 118 |
expect(pill).not.toBeNull(); |
| 119 |
expect(pill.textContent).toContain('Quick Edit'); |
| 120 |
|
| 121 |
pill.click(); |
| 122 |
|
| 123 |
const alert = document.querySelector('[role="alert"]'); |
| 124 |
expect(alert).not.toBeNull(); |
| 125 |
expect(alert.textContent).toContain('TranslatePress'); |
| 126 |
expect(mockSetSelected).not.toHaveBeenCalled(); |
| 127 |
// The bar (and its Ask AI pill) stays mounted. |
| 128 |
expect(document.querySelector('.extendify-quick-edit-bar')).not.toBeNull(); |
| 129 |
}); |
| 130 |
|
| 131 |
it('editTarget (block-click / keyboard) commits no selection for translated text', () => { |
| 132 |
setTranslated(true); |
| 133 |
const { editTarget } = importBar(); |
| 134 |
editTarget({ |
| 135 |
el: heading(), |
| 136 |
blockType: 'core/heading', |
| 137 |
blockId: 1, |
| 138 |
source: { kind: 'post', id: 1 }, |
| 139 |
}); |
| 140 |
expect(mockSetSelected).not.toHaveBeenCalled(); |
| 141 |
}); |
| 142 |
|
| 143 |
it('on the default language the Quick Edit pill still commits a selection (no error)', () => { |
| 144 |
setTranslated(false); |
| 145 |
const { showBar } = importBar(); |
| 146 |
showBar(heading()); |
| 147 |
|
| 148 |
qerPill().click(); |
| 149 |
|
| 150 |
expect(mockSetSelected).toHaveBeenCalledTimes(1); |
| 151 |
expect(document.querySelector('[role="alert"]')).toBeNull(); |
| 152 |
}); |
| 153 |
}); |
| 154 |
|