diff --git a/services/web/frontend/js/features/editor-floating-menu/components/tracked-changes-actions.tsx b/services/web/frontend/js/features/editor-floating-menu/components/tracked-changes-actions.tsx
index ae38eeb019..0b2172757a 100644
--- a/services/web/frontend/js/features/editor-floating-menu/components/tracked-changes-actions.tsx
+++ b/services/web/frontend/js/features/editor-floating-menu/components/tracked-changes-actions.tsx
@@ -11,7 +11,7 @@ const TrackedChangesActions: FC = () => {
const { changesInSelection, acceptChangesHandler, rejectChangesHandler } =
useTrackedChangesActions()
- if (!permissions.comment || changesInSelection.length === 0) {
+ if (!permissions.write || changesInSelection.length === 0) {
return null
}
diff --git a/services/web/frontend/js/features/review-panel/components/review-tooltip-menu.tsx b/services/web/frontend/js/features/review-panel/components/review-tooltip-menu.tsx
index 475c3e09d9..cd070d46dc 100644
--- a/services/web/frontend/js/features/review-panel/components/review-tooltip-menu.tsx
+++ b/services/web/frontend/js/features/review-panel/components/review-tooltip-menu.tsx
@@ -138,6 +138,7 @@ const ReviewTooltipMenuContent = memo<{ onAddComment: () => void }>(
const view = useCodeMirrorViewContext()
const state = useCodeMirrorStateContext()
const { reviewPanelOpen } = useLayoutContext()
+ const permissions = usePermissionsContext()
const ranges = useRangesContext()
const { acceptChanges, rejectChanges } = useRangesActionsContext()
const { showGenericConfirmModal } = useModalsContext()
@@ -208,7 +209,8 @@ const ReviewTooltipMenuContent = memo<{ onAddComment: () => void }>(
changesInSelection,
])
- const showChangesButtons = changesInSelection.length > 0
+ const showChangesButtons =
+ permissions.write && changesInSelection.length > 0
useEffect(() => {
view.requestMeasure({
diff --git a/services/web/frontend/js/features/source-editor/hooks/use-context-menu-items.tsx b/services/web/frontend/js/features/source-editor/hooks/use-context-menu-items.tsx
index 61a1dbed83..c1a3bf56d5 100644
--- a/services/web/frontend/js/features/source-editor/hooks/use-context-menu-items.tsx
+++ b/services/web/frontend/js/features/source-editor/hooks/use-context-menu-items.tsx
@@ -281,14 +281,14 @@ export const useContextMenuItems = () => {
label: t('accept_selected_changes'),
handler: handleAcceptChanges,
disabled: false,
- show: permissions.comment && changesInSelection.length > 0,
+ show: permissions.write && changesInSelection.length > 0,
shortcut: undefined,
},
{
label: t('reject_selected_changes'),
handler: handleRejectChanges,
disabled: false,
- show: permissions.comment && changesInSelection.length > 0,
+ show: permissions.write && changesInSelection.length > 0,
shortcut: undefined,
},
].filter(item => item.show),
diff --git a/services/web/test/frontend/features/editor-floating-menu/editor-floating-menu.spec.tsx b/services/web/test/frontend/features/editor-floating-menu/editor-floating-menu.spec.tsx
index 58afd47870..5182c4474f 100644
--- a/services/web/test/frontend/features/editor-floating-menu/editor-floating-menu.spec.tsx
+++ b/services/web/test/frontend/features/editor-floating-menu/editor-floating-menu.spec.tsx
@@ -4,6 +4,7 @@ import { mockScope } from '../source-editor/helpers/mock-scope'
import { TestContainer } from '../source-editor/helpers/test-container'
import { docId } from '../source-editor/helpers/mock-doc'
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
+import type { PermissionsLevel } from '@/features/ide-react/types/permissions'
function FloatingMenuToggle() {
const { setUserSettings } = useUserSettingsContext()
@@ -64,7 +65,9 @@ describe('', function () {
)
}
- function mountEditorWithChanges() {
+ function mountEditorWithChanges({
+ permissionsLevel,
+ }: { permissionsLevel?: PermissionsLevel } = {}) {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
window.metaAttributesCache.set('ol-splitTestVariants', {
'writefull-toolbar-migration': 'enabled',
@@ -100,6 +103,7 @@ describe('', function () {
docOptions: {
rangesOptions: { changes, getChanges, removeChangeIds },
},
+ permissionsLevel,
})
cy.mount(
@@ -269,4 +273,16 @@ describe('', function () {
cy.get('.editor-floating-menu').should('exist')
})
})
+
+ describe('bulk tracked-change actions without write permission', function () {
+ it('hides the accept and reject controls for a reviewer', function () {
+ mountEditorWithChanges({ permissionsLevel: 'review' })
+
+ cy.get('.editor-floating-menu').within(() => {
+ cy.findByLabelText('Add comment').should('exist')
+ })
+ cy.findByLabelText('Accept selected changes').should('not.exist')
+ cy.findByLabelText('Reject selected changes').should('not.exist')
+ })
+ })
})
diff --git a/services/web/test/frontend/features/review-panel/review-panel.spec.tsx b/services/web/test/frontend/features/review-panel/review-panel.spec.tsx
index f6eafbf0bf..4aecc39e89 100644
--- a/services/web/test/frontend/features/review-panel/review-panel.spec.tsx
+++ b/services/web/test/frontend/features/review-panel/review-panel.spec.tsx
@@ -684,6 +684,69 @@ describe('', function () {
})
})
+describe(' review tooltip without write permission', function () {
+ beforeEach(function () {
+ window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
+ cy.interceptEvents()
+ cy.intercept('GET', '/project/*/changes/users', [])
+ cy.intercept('GET', '/project/*/threads', {})
+
+ const changes = [
+ {
+ metadata: {
+ user_id: USER_ID,
+ ts: new Date('2025-01-01T00:00:00.000Z'),
+ },
+ id: 'inserted-op-id',
+ op: { p: 166, t: 'inserted-op-id', i: 'introduction' },
+ },
+ {
+ metadata: {
+ user_id: USER_ID,
+ ts: new Date('2025-01-01T01:00:00.000Z'),
+ },
+ id: 'deleted-op-id',
+ op: { p: 110, t: 'deleted-op-id', d: 'beautiful ' },
+ },
+ ]
+
+ const scope = mockScope(undefined, {
+ docOptions: { rangesOptions: { changes } },
+ permissionsLevel: 'review',
+ })
+ const project = mockProject({
+ projectOwner: { _id: USER_ID },
+ projectFeatures: { trackChanges: true, trackChangesVisible: true },
+ })
+
+ cy.mount(
+
+
+
+
+
+ )
+
+ cy.get('.cm-content').should('have.css', 'opacity', '1')
+
+ // Select a deletion and an insertion so the tooltip's bulk actions would appear
+ cy.findByText('\\maketitle').type(
+ '{home}{shift}' + '{downArrow}'.repeat(10),
+ { scrollBehavior: false }
+ )
+ })
+
+ it('shows the comment action but hides accept and reject for a reviewer', function () {
+ cy.get('.review-tooltip-menu').should('exist')
+ cy.get('.review-tooltip-add-comment-button').should('exist')
+ cy.findByLabelText('Accept selected changes').should('not.exist')
+ cy.findByLabelText('Reject selected changes').should('not.exist')
+ })
+})
+
describe(' in mini mode', function () {
function render({ comments = [], changes = [], threads = {} }: any) {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
diff --git a/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts b/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts
index fd19ce4f85..a5811a7332 100644
--- a/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts
+++ b/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts
@@ -4,7 +4,7 @@ import { sleep } from '../../../helpers/sleep'
export const rootFolderId = '012345678901234567890123'
export const mockScope = (
content?: string,
- { docOptions = {}, permissions = {} }: any = {}
+ { docOptions = {}, permissions = {}, permissionsLevel = 'owner' }: any = {}
) => {
return {
editor: {
@@ -26,6 +26,6 @@ export const mockScope = (
toggleTrackChangesForEveryone: cy.stub(),
refreshResolvedCommentsDropdown: cy.stub(() => sleep(1000)),
onlineUserCursorHighlights: {},
- permissionsLevel: 'owner',
+ permissionsLevel,
}
}