import CodeMirrorEditor from '../../../../frontend/js/features/source-editor/components/codemirror-editor'
import { EditorProviders, USER_ID } from '../../helpers/editor-providers'
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()
return (
)
}
describe('', function () {
function mountEditor({
migrationEnabled,
trackChangesVisible = true,
floatingMenu = true,
withSettingsToggle = false,
}: {
migrationEnabled: boolean
trackChangesVisible?: boolean
floatingMenu?: boolean
withSettingsToggle?: boolean
}) {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
window.metaAttributesCache.set('ol-splitTestVariants', {
'writefull-toolbar-migration': migrationEnabled ? 'enabled' : 'default',
})
cy.interceptEvents()
const scope = mockScope()
cy.mount(
{withSettingsToggle && }
)
// Create a non-empty selection so the selection tooltip is shown.
cy.findByText('contentLine 12').type(
'{home}{shift}' + '{rightArrow}'.repeat(6),
{
scrollBehavior: false,
}
)
}
function mountEditorWithChanges({
permissionsLevel,
}: { permissionsLevel?: PermissionsLevel } = {}) {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
window.metaAttributesCache.set('ol-splitTestVariants', {
'writefull-toolbar-migration': 'enabled',
})
cy.interceptEvents()
cy.intercept('POST', `/project/*/doc/${docId}/changes/accept`, {}).as(
'acceptChange'
)
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 getChanges = cy.stub().as('getChanges').returns([])
const removeChangeIds = cy.stub().as('removeChangeIds')
const scope = mockScope(undefined, {
docOptions: {
rangesOptions: { changes, getChanges, removeChangeIds },
},
permissionsLevel,
})
cy.mount(
)
// Select a deletion and an insertion so the bulk-action controls appear.
cy.findByText('\\maketitle').type(
'{home}{shift}' + '{downArrow}'.repeat(10),
{ scrollBehavior: false }
)
}
describe('when the migration split test is enabled', function () {
it('shows the unified floating menu with Add comment, not the legacy tooltip', function () {
mountEditor({ migrationEnabled: true })
cy.get('.editor-floating-menu').within(() => {
cy.findByLabelText('Add comment').should('exist')
})
// Legacy tooltip is replaced.
cy.get('.review-tooltip-menu').should('not.exist')
})
it('clicking Add comment dispatches the add-new-review-comment event', function () {
mountEditor({ migrationEnabled: true })
cy.window().then(win => {
win.addEventListener(
'add-new-review-comment',
cy.stub().as('addComment')
)
})
cy.get('.editor-floating-menu').within(() => {
cy.findByLabelText('Add comment').click({ scrollBehavior: false })
})
cy.get('@addComment').should('have.been.called')
})
})
describe('when the migration split test is disabled (control)', function () {
it('keeps the legacy review tooltip and does not render the unified menu', function () {
mountEditor({ migrationEnabled: false })
cy.get('.review-tooltip-menu').should('exist')
cy.get('.editor-floating-menu').should('not.exist')
})
})
describe('floating menu setting', function () {
it('does not show the unified floating menu when the migration split test is enabled', function () {
mountEditor({ migrationEnabled: true, floatingMenu: false })
cy.get('.editor-floating-menu').should('not.exist')
})
it('does not show the legacy review tooltip when the migration split test is disabled', function () {
mountEditor({ migrationEnabled: false, floatingMenu: false })
cy.get('.review-tooltip-menu').should('not.exist')
})
it('removes the menu and its tooltip when the setting is turned off', function () {
mountEditor({
migrationEnabled: true,
floatingMenu: true,
withSettingsToggle: true,
})
cy.get('.review-tooltip-menu-container').should('exist')
cy.get('.editor-floating-menu').should('exist')
// force: the visible menu can overlap this test-only toggle button
cy.findByRole('button', { name: 'Toggle floating menu' }).click({
scrollBehavior: false,
force: true,
})
cy.get('.editor-floating-menu').should('not.exist')
cy.get('.review-tooltip-menu-container').should('not.exist')
})
it('restores the menu when the setting is turned back on', function () {
mountEditor({
migrationEnabled: true,
floatingMenu: false,
withSettingsToggle: true,
})
cy.get('.editor-floating-menu').should('not.exist')
cy.findByRole('button', { name: 'Toggle floating menu' }).click({
scrollBehavior: false,
force: true,
})
// delay: the extension reconfigures on a deferred dispatch, so spaced-out
// keystrokes ensure a selection event lands after it to rebuild the menu
cy.findByText('contentLine 12').type(
'{home}{shift}' + '{rightArrow}'.repeat(6),
{ scrollBehavior: false, delay: 20 }
)
cy.get('.editor-floating-menu').should('exist')
})
})
describe('bulk tracked-change actions', function () {
beforeEach(function () {
mountEditorWithChanges()
cy.findByLabelText('Accept selected changes').as(
'accept-selected-changes'
)
cy.findByLabelText('Reject selected changes').as(
'reject-selected-changes'
)
})
it('renders the accept and reject controls in the unified menu', function () {
cy.get('.editor-floating-menu').should('exist')
cy.get('@accept-selected-changes').should('exist')
cy.get('@reject-selected-changes').should('exist')
})
it('accepts the selected changes', function () {
cy.get('@accept-selected-changes').click({ scrollBehavior: false })
cy.findByRole('dialog').within(() => {
cy.findByText(
'Are you sure you want to accept the selected 2 changes?'
).should('exist')
cy.findByRole('button', { name: 'OK' }).click({ scrollBehavior: false })
})
cy.wait('@acceptChange')
cy.get('@removeChangeIds').should('have.been.calledWith', [
'inserted-op-id',
'deleted-op-id',
])
})
it('rejects the selected changes', function () {
cy.get('@reject-selected-changes').click({ scrollBehavior: false })
cy.findByRole('dialog').within(() => {
cy.findByText(
'Are you sure you want to reject the selected 2 changes?'
).should('exist')
cy.findByRole('button', { name: 'OK' }).click({ scrollBehavior: false })
})
cy.get('@getChanges').should('have.been.calledWith', [
'inserted-op-id',
'deleted-op-id',
])
})
it('keeps the menu visible when cancelling the confirmation modal', function () {
cy.get('@accept-selected-changes').click({ scrollBehavior: false })
cy.findByRole('dialog').within(() => {
cy.findByRole('button', { name: 'Cancel' }).click({
scrollBehavior: false,
})
})
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')
})
})
})