| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942 |
- import CodeMirrorEditor from '../../../../frontend/js/features/source-editor/components/codemirror-editor'
- import {
- EditorProviders,
- makeProjectProvider,
- USER_EMAIL,
- 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 { mockProject } from '../source-editor/helpers/mock-project'
- import { UserId } from '@ol-types/user'
- const userData = {
- avatar_text: 'User',
- email: USER_EMAIL,
- hue: 180,
- id: USER_ID,
- isSelf: true,
- first_name: 'Test',
- last_name: 'User',
- }
- const resolvedThreadId = 'resolved-thread-id'
- const unresolvedThreadId = 'unresolved-thread-id'
- describe('<ReviewPanel />', function () {
- beforeEach(function () {
- window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
- cy.interceptEvents()
- cy.intercept('GET', '/project/*/changes/users', [
- {
- id: USER_ID,
- email: USER_EMAIL,
- first_name: 'Test',
- last_name: 'User',
- },
- ])
- cy.intercept('GET', '/project/*/threads', {
- // Resolved comment thread
- [resolvedThreadId]: {
- messages: [
- {
- content: 'comment text',
- id: `${resolvedThreadId}-1`,
- timestamp: new Date('2025-01-01T00:00:00.000Z'),
- user: userData,
- user_id: USER_ID,
- },
- ],
- resolved: true,
- resolved_at: new Date('2025-01-02T00:00:00.000Z').toISOString(),
- resolved_by_user_id: USER_ID,
- resolved_by_user: userData,
- },
- // Unresolved comment thread
- [unresolvedThreadId]: {
- messages: [
- {
- content: 'unresolved comment text',
- id: `${unresolvedThreadId}-1`,
- timestamp: new Date('2025-01-01T00:00:00.000Z'),
- user: userData,
- user_id: USER_ID,
- },
- {
- content: 'reply to thread',
- id: `${unresolvedThreadId}-2`,
- timestamp: new Date('2025-01-01T01:00:00.000Z'),
- user: userData,
- user_id: USER_ID,
- },
- ],
- },
- })
- const commentOps = [
- {
- id: 'resolved-op-id',
- op: { p: 161, c: 'Your introduction', t: resolvedThreadId },
- },
- {
- id: 'unresolved-op-id',
- op: { p: 210, c: 'Your results', t: unresolvedThreadId },
- },
- ]
- const changesOps = [
- {
- 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 ' },
- },
- ]
- cy.intercept('GET', '/project/*/ranges', [
- {
- id: docId,
- ranges: {
- changes: changesOps,
- comments: commentOps,
- docId,
- },
- },
- ])
- cy.intercept(
- 'POST',
- `/project/*/doc/${docId}/thread/${resolvedThreadId}/reopen`,
- {}
- ).as('reopenThread')
- cy.intercept(
- 'POST',
- `/project/*/doc/${docId}/thread/${unresolvedThreadId}/resolve`,
- {}
- ).as('resolveThreadId')
- cy.intercept(
- 'POST',
- `/project/*/thread/${unresolvedThreadId}/messages/${unresolvedThreadId}-1/edit`,
- {}
- ).as('editComment')
- cy.intercept(
- 'POST',
- `/project/*/thread/${unresolvedThreadId}/messages`,
- {}
- ).as('addReply')
- cy.intercept(
- 'POST',
- /\/project\/.*\/thread\/[a-z0-9]{24}\/messages/,
- {}
- ).as('addNewComment')
- cy.intercept(
- 'DELETE',
- `/project/*/doc/${docId}/thread/${resolvedThreadId}`,
- {}
- ).as('deleteResolvedThread')
- cy.intercept(
- 'DELETE',
- `/project/*/thread/${unresolvedThreadId}/messages/${unresolvedThreadId}-2`,
- {}
- ).as('deleteComment')
- cy.intercept(
- 'DELETE',
- `/project/*/doc/${docId}/thread/${unresolvedThreadId}`,
- {}
- ).as('deleteThread')
- cy.intercept('POST', `/project/*/doc/${docId}/changes/accept`, {}).as(
- 'acceptChange'
- )
- cy.intercept('POST', `/project/*/doc/${docId}/metadata`, {})
- const getChanges = cy.stub().as('getChanges').returns([])
- const removeChangeIds = cy.stub().as('removeChangeIds')
- const scope = mockScope(undefined, {
- docOptions: {
- rangesOptions: {
- comments: commentOps,
- changes: changesOps,
- getChanges,
- removeChangeIds,
- },
- },
- })
- const project = mockProject({
- projectOwner: {
- _id: USER_ID,
- },
- projectFeatures: { trackChanges: false, trackChangesVisible: true },
- })
- cy.wrap(scope).as('scope')
- cy.mount(
- <TestContainer className="rp-size-expanded">
- <EditorProviders
- scope={scope}
- providers={{ ProjectProvider: makeProjectProvider(project) }}
- >
- <CodeMirrorEditor />
- </EditorProviders>
- </TestContainer>
- )
- // Wait for the editor to be ready before interacting with it
- cy.get('.cm-content').should('have.css', 'opacity', '1')
- // Open the review panel with keyboard shortcut
- cy.findByText('contentLine 0').type('{command}j', { scrollBehavior: false })
- cy.findByText('contentLine 1').type('{ctrl}j', { scrollBehavior: false })
- cy.findByTestId('review-panel').as('review-panel')
- })
- describe('toolbar', function () {
- describe('resolved comments dropdown', function () {
- it('renders a dropdown of resolved comments', function () {
- // The dropdown button should be visible
- cy.findByLabelText('Resolved comments').click()
- // It should open the dropdown
- cy.findByRole('tooltip')
- .should('exist')
- .within(() => {
- // TODO: Fix selector
- cy.get(
- '.review-panel-resolved-comments-header .badge-content'
- ).should('contain.text', '1')
- // Should name the document with the comment
- cy.findByText('test.tex').should('exist')
- // Should show the comment text
- cy.findByText('comment text').should('exist')
- // Should show the author name
- // TODO: Fix selector
- cy.get('.review-panel-entry-user').should(
- 'contain.text',
- 'Test User'
- )
- })
- })
- it('reopens resolved comment', function () {
- cy.findByLabelText('Resolved comments').click()
- cy.findByRole('tooltip').within(() => {
- // Find the re-open icon button using the hidden label
- cy.findByText('Re-open').click({ force: true })
- // verify the reopen thread API call
- cy.wait('@reopenThread')
- // TODO: Figure out a way to plumb the websocket response back through
- // to the test so we can verify the comment is no longer resolved
- // cy.get(
- // '.review-panel-resolved-comments-header .badge-content'
- // ).should('contain.text', '0')
- })
- })
- it('deletes resolved comment', function () {
- cy.findByLabelText('Resolved comments').click()
- cy.findByRole('tooltip').within(() => {
- // Find the Delete icon button using the hidden label
- cy.findByText('Delete').click({ force: true })
- // verify the delete thread API call
- cy.wait('@deleteResolvedThread')
- // TODO: Figure out a way to plumb the websocket response back through
- // to the test so we can verify the comment is no longer there
- // cy.get(
- // '.review-panel-resolved-comments-header .badge-content'
- // ).should('contain.text', '0')
- })
- })
- })
- })
- describe('toggler', function () {
- it('should close panel when pressing close button', function () {
- cy.findByLabelText('Close').click({ scrollBehavior: false })
- // We should collapse to the mini state
- cy.get('.review-panel-mini').should('exist')
- })
- })
- describe('navigation', function () {
- it('renders navigation', function () {
- cy.get('@review-panel').within(() => {
- cy.findByRole('tab', { name: /current file/i })
- cy.findByRole('tab', { name: /overview/i })
- })
- })
- it('selects the active tab', function () {
- cy.get('@review-panel').within(() => {
- cy.findByRole('tab', { name: /current file/i }).should(
- 'have.attr',
- 'aria-selected',
- 'true'
- )
- cy.findByRole('tab', { name: /overview/i }).should(
- 'have.attr',
- 'aria-selected',
- 'false'
- )
- cy.findByRole('tab', { name: /overview/i }).click()
- cy.findByRole('tab', { name: /current file/i }).should(
- 'have.attr',
- 'aria-selected',
- 'false'
- )
- cy.findByRole('tab', { name: /overview/i }).should(
- 'have.attr',
- 'aria-selected',
- 'true'
- )
- })
- })
- })
- describe('comment entries', function () {
- it('shows threads and comments', function () {
- cy.get('@review-panel').within(() => {
- cy.findByText('unresolved comment text').should('exist')
- cy.findByText('reply to thread').should('exist')
- })
- })
- it('edits comment', function () {
- cy.get('@review-panel').within(() => {
- // TODO: Fix selector
- cy.get('.review-panel-comment-wrapper')
- .first()
- .within(() => {
- // Find the options icon button using the hidden label
- cy.findByText('More options')
- .first()
- .click({ force: true, scrollBehavior: false })
- cy.findByRole('menu').within(() => {
- cy.findByText('Edit').click({ scrollBehavior: false })
- })
- cy.findByRole('textbox').type(
- '{selectAll}edited comment text{enter}',
- { scrollBehavior: false }
- )
- cy.wait('@editComment')
- // TODO: Figure out a way to plumb the websocket response back through
- // to the test so we can verify the comment is resolved
- // cy.findByText('edited comment text').should('exist')
- })
- })
- })
- it('deletes thread', function () {
- cy.get('@review-panel').within(() => {
- // TODO: Fix selector
- cy.get('.review-panel-comment-wrapper')
- .first()
- .within(() => {
- // Find the options icon button using the hidden label
- cy.findByText('More options')
- .first()
- .click({ force: true, scrollBehavior: false })
- cy.findByRole('menu').within(() => {
- cy.findByText('Delete').click({ scrollBehavior: false })
- })
- })
- })
- cy.findByRole('dialog').within(() => {
- cy.findByRole('button', { name: 'Delete' }).click()
- })
- cy.wait('@deleteThread')
- // TODO: Figure out a way to plumb the websocket response back through
- // to the test so we can verify the thread is deleted
- // cy.findByText('unresolved comment text').should('not.exist')
- })
- it('deletes reply', function () {
- cy.get('@review-panel').within(() => {
- // TODO: Fix selector
- cy.get('.review-panel-comment-wrapper')
- .eq(1)
- .within(() => {
- // Find the options icon button using the hidden label
- cy.findByText('More options')
- .first()
- .click({ force: true, scrollBehavior: false })
- cy.findByRole('menu').within(() => {
- cy.findByText('Delete').click({ scrollBehavior: false })
- })
- })
- })
- cy.findByRole('dialog').within(() => {
- cy.findByRole('button', { name: 'Delete' }).click()
- })
- cy.wait('@deleteComment')
- // TODO: Figure out a way to plumb the websocket response back through
- // to the test so we can verify the reply is deleted
- // cy.findByText('reply to thread').should('not.exist')
- })
- it('cancels comment deletion', function () {
- cy.get('@review-panel').within(() => {
- // TODO: Fix selector
- cy.get('.review-panel-comment-wrapper')
- .eq(1)
- .within(() => {
- // Find the options icon button using the hidden label
- cy.findByText('More options')
- .first()
- .click({ force: true, scrollBehavior: false })
- cy.findByRole('menu').within(() => {
- cy.findByText('Delete').click({ scrollBehavior: false })
- })
- })
- })
- cy.findByRole('dialog').within(() => {
- cy.findByRole('button', { name: 'Cancel' }).click()
- })
- cy.findByText('unresolved comment text').should('exist')
- })
- it('adds new comment (replies) to a thread', function () {
- cy.get('@review-panel').within(() => {
- cy.findByRole('textbox').type('a new reply{enter}', {
- scrollBehavior: false,
- })
- })
- cy.wait('@addReply')
- })
- it('resolves comment', function () {
- cy.get('@review-panel').within(() => {
- // Find the resolve icon button using the hidden label
- cy.findByText('Resolve comment').click({ force: true })
- cy.wait('@resolveThreadId')
- // TODO: Figure out a way to plumb the websocket response back through
- // to the test so we can verify the comment is resolved
- // cy.findByText('unresolved comment text').should('not.exist')
- })
- })
- })
- describe('change entries', function () {
- it('renders inserted entries in current file mode', function () {
- cy.get('@review-panel').within(() => {
- cy.findByText('Added:').should('exist')
- cy.findByText('introduction').should('exist')
- })
- })
- it('renders deleted entries in current file mode', function () {
- cy.get('@review-panel').within(() => {
- cy.findByText('Deleted:').should('exist')
- cy.findByText('beautiful').should('exist')
- })
- })
- it('accepts change', function () {
- cy.get('@review-panel').within(() => {
- // TODO: Fix selector
- cy.get('.review-panel-entry-insert').within(() => {
- // Find the accept icon button using the hidden label
- cy.findByText('Accept change').click({ force: true })
- cy.wait('@acceptChange')
- })
- // TODO: Fix selector
- cy.get('.review-panel-entry-delete').within(() => {
- // Find the accept icon button using the hidden label
- cy.findByText('Accept change').click({ force: true })
- cy.wait('@acceptChange')
- })
- })
- })
- it('rejects change', function () {
- cy.get('@review-panel').within(() => {
- // TODO: Fix selector
- cy.get('.review-panel-entry-insert').within(() => {
- // Find the reject icon button using the hidden label
- cy.findByText('Reject change').click({ force: true })
- cy.get('@getChanges').should('be.calledOnce')
- })
- // TODO: Fix selector
- cy.get('.review-panel-entry-delete').within(() => {
- // Find the reject icon button using the hidden label
- cy.findByText('Reject change').click({ force: true })
- cy.get('@getChanges').should('be.calledTwice')
- })
- })
- })
- })
- describe('aggregate change entries', function () {
- // eslint-disable-next-line mocha/no-skipped-tests
- it.skip('renders changed entries in current file mode', function () {})
- // eslint-disable-next-line mocha/no-skipped-tests
- it.skip('renders changed entries in overview mode', function () {})
- })
- describe('add comment entry', function () {
- beforeEach(function () {
- cy.findByText('contentLine 12').type(
- '{home}{shift}' + '{rightArrow}'.repeat(6),
- { scrollBehavior: false }
- )
- // TODO: Fix selector
- cy.get('.review-tooltip-add-comment-button').as('add-comment-button')
- })
- it('renders floating `add comment button`', function () {
- cy.get('@add-comment-button').should('exist')
- })
- it('can add comment', function () {
- cy.get('@add-comment-button').click({ scrollBehavior: false })
- cy.get('@review-panel').within(() => {
- // TODO: Fix selector
- cy.get('.review-panel-add-comment-textarea').type(
- 'a new comment{enter}',
- {
- scrollBehavior: false,
- }
- )
- })
- cy.wait('@addNewComment')
- // TODO : Figure out a way to plumb the websocket response back through
- // to the test so we can verify the comment is added
- // cy.findByText('a new comment').should('exist')
- })
- it('cancels adding comment', function () {
- cy.get('@add-comment-button').click({ scrollBehavior: false })
- cy.get('@review-panel').within(() => {
- cy.findByRole('button', { name: 'Cancel' }).click({
- scrollBehavior: false,
- })
- })
- })
- })
- describe('add comment tooltip visibility', function () {
- beforeEach(function () {
- cy.findByText('contentLine 12').type(
- '{home}{shift}' + '{rightArrow}'.repeat(6),
- { scrollBehavior: false }
- )
- cy.get('.review-tooltip-menu').should('exist')
- })
- it('hides the tooltip when clicking a toolbar button', function () {
- cy.findByRole('button', { name: 'Undo' }).click({ scrollBehavior: false })
- cy.get('.review-tooltip-menu').should('not.exist')
- })
- it('hides the tooltip when clicking outside the editor', function () {
- cy.get('@review-panel').click({ scrollBehavior: false })
- cy.get('.review-tooltip-menu').should('not.exist')
- })
- it('keeps the add comment button functional when clicked', function () {
- cy.get('.review-tooltip-add-comment-button').click({
- scrollBehavior: false,
- })
- cy.get('@review-panel').within(() => {
- cy.get('.review-panel-add-comment-textarea').should('exist')
- })
- })
- })
- describe('bulk actions entry', function () {
- beforeEach(function () {
- // Select a deletion and an insertion
- cy.findByText('\\maketitle').type(
- '{home}{shift}' + '{downArrow}'.repeat(10),
- { scrollBehavior: false }
- )
- cy.findByLabelText('Accept selected changes').as(
- 'accept-selected-changes'
- )
- cy.findByLabelText('Reject selected changes').as(
- 'reject-selected-changes'
- )
- })
- it('renders the reject and accept all buttons`', function () {
- cy.get('@accept-selected-changes').should('exist')
- cy.get('@reject-selected-changes').should('exist')
- })
- it('accepts all 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 all 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 tooltip visible when cancelling the accept 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('.review-tooltip-menu').should('exist')
- })
- it('keeps the tooltip visible when cancelling the reject confirmation modal', function () {
- cy.get('@reject-selected-changes').click({ scrollBehavior: false })
- cy.findByRole('dialog').within(() => {
- cy.findByRole('button', { name: 'Cancel' }).click({
- scrollBehavior: false,
- })
- })
- cy.get('.review-tooltip-menu').should('exist')
- })
- })
- describe('overview mode', function () {
- beforeEach(function () {
- cy.findByRole('tab', { name: /overview/i }).click()
- })
- it('shows list of files changed', function () {
- // TODO: Fix selector
- cy.get('.collapsible-file-header').should('contain.text', 'test.tex')
- })
- it('renders comments', function () {
- cy.get('@review-panel').within(() => {
- cy.findByText('unresolved comment text').should('exist')
- cy.findByText('reply to thread').should('exist')
- })
- })
- it('renders changes', function () {
- cy.get('@review-panel').within(() => {
- cy.findByText('Added:').should('exist')
- cy.findByText('introduction').should('exist')
- cy.findByText('Deleted:').should('exist')
- cy.findByText('beautiful').should('exist')
- })
- })
- it('collapses the file entries when clicked', function () {
- cy.findByText('test.tex').click()
- cy.get('@review-panel').within(() => {
- // TODO: Fix selector
- cy.get('.review-panel-entry').should('not.exist')
- })
- cy.findByText('test.tex').click()
- cy.get('@review-panel').within(() => {
- // TODO: Fix selector
- cy.get('.review-panel-entry').should('exist')
- })
- })
- })
- })
- describe('<ReviewPanel /> in mini mode', function () {
- function render({ comments = [], changes = [], threads = {} }: any) {
- window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
- cy.interceptEvents()
- cy.intercept('GET', '/project/*/changes/users', [
- {
- id: USER_ID,
- email: USER_EMAIL,
- first_name: 'Test',
- last_name: 'User',
- },
- ])
- const getChanges = cy.stub().as('getChanges').returns([])
- const removeChangeIds = cy.stub().as('removeChangeIds')
- const scope = mockScope(undefined, {
- docOptions: {
- rangesOptions: {
- comments,
- changes,
- getChanges,
- removeChangeIds,
- },
- },
- projectFeatures: { trackChangesVisible: true },
- })
- const project = mockProject({
- projectFeatures: { trackChangesVisible: true },
- })
- cy.intercept('GET', '/project/*/ranges', [
- {
- id: docId,
- ranges: {
- changes,
- comments,
- docId,
- },
- },
- ])
- cy.intercept('GET', '/project/*/threads', threads).as('loadThreads')
- cy.intercept('POST', `/project/*/doc/${docId}/metadata`, {})
- cy.wrap(scope).as('scope')
- cy.mount(
- <TestContainer>
- <EditorProviders
- scope={scope}
- providers={{ ProjectProvider: makeProjectProvider(project) }}
- >
- <CodeMirrorEditor />
- </EditorProviders>
- </TestContainer>
- )
- // Wait for editor
- cy.get('.cm-content').should('have.css', 'opacity', '1')
- // Wait for the threads to load, since mini mode renders conditionally on
- // the threads/ranges data being present
- cy.wait('@loadThreads')
- // Toggle the review panel twice to ensure data is loaded
- cy.findByText('contentLine 0').type('{command}jj', {
- scrollBehavior: false,
- })
- cy.findByText('contentLine 1').type('{ctrl}jj', { scrollBehavior: false })
- }
- it("doesn't render mini when no comments or changes are present in project", function () {
- render({
- comments: [],
- changes: [],
- threads: {},
- })
- cy.get('.review-panel-mini').should('not.exist')
- })
- it("doesn't render mini when no comments or changes are present in document", function () {
- render({
- comments: [],
- changes: [],
- threads: {
- 'random-unrelated-thread': {
- messages: [
- {
- content: 'a comment',
- id: 'random-unrelated-thread-1',
- timestamp: new Date('2025-01-01T01:00:00.000Z'),
- user: userData,
- user_id: USER_ID,
- },
- ],
- },
- },
- })
- cy.get('.review-panel-mini').should('not.exist')
- })
- it("doesn't render mini when a resolved comment is present in document", function () {
- render({
- comments: [
- {
- id: resolvedThreadId,
- op: { p: 161, c: 'Your introduction', t: resolvedThreadId },
- },
- ],
- changes: [],
- threads: {
- [resolvedThreadId]: {
- resolved: true,
- resolved_at: new Date('2025-01-02T00:00:00.000Z').toISOString(),
- resolved_by_user_id: USER_ID,
- resolved_by_user: userData,
- messages: [
- {
- content: 'a comment',
- id: `${resolvedThreadId}-1`,
- timestamp: new Date('2025-01-01T01:00:00.000Z'),
- user: userData,
- user_id: USER_ID,
- },
- ],
- },
- },
- })
- cy.get('.review-panel-mini').should('not.exist')
- })
- it('renders mini when an unresolved comment is present in document', function () {
- render({
- comments: [
- {
- id: unresolvedThreadId,
- op: { p: 161, c: 'Your introduction', t: unresolvedThreadId },
- },
- ],
- changes: [],
- threads: {
- [unresolvedThreadId]: {
- messages: [
- {
- content: 'a comment',
- id: `${unresolvedThreadId}-1`,
- timestamp: new Date('2025-01-01T01:00:00.000Z'),
- user: userData,
- user_id: USER_ID,
- },
- ],
- },
- },
- })
- cy.get('.review-panel-mini').should('exist')
- })
- it('renders mini when a tracked change is present in document', function () {
- render({
- comments: [],
- 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' },
- },
- ],
- threads: {},
- })
- cy.get('.review-panel-mini').should('exist')
- })
- })
- describe('<ReviewPanel /> for free users', function () {
- function mountEditor(ownerId = USER_ID) {
- const scope = mockScope(undefined, {
- permissions: { write: true, trackedWrite: false, comment: true },
- })
- const project = mockProject({
- projectFeatures: { trackChanges: false, trackChangesVisible: true },
- projectOwner: {
- _id: ownerId,
- },
- })
- cy.wrap(scope).as('scope')
- cy.mount(
- <TestContainer className="rp-size-expanded">
- <EditorProviders
- scope={scope}
- providers={{ ProjectProvider: makeProjectProvider(project) }}
- >
- <CodeMirrorEditor />
- </EditorProviders>
- </TestContainer>
- )
- // Wait for the editor to be ready before interacting with it
- cy.get('.cm-content').should('have.css', 'opacity', '1')
- cy.findByLabelText('Editing').click()
- cy.findByRole('menu').within(() => {
- cy.findByText(/Reviewing/).click()
- })
- }
- beforeEach(function () {
- window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
- cy.interceptEvents()
- cy.intercept('GET', '/project/*/changes/users', [])
- cy.intercept('GET', '/project/*/threads', {})
- })
- it('renders modal', function () {
- mountEditor()
- cy.findByRole('dialog').within(() => {
- cy.findByText('Upgrade to review').should('exist')
- })
- })
- it('closes modal', function () {
- mountEditor()
- cy.findByRole('dialog').within(() => {
- cy.findByText('Close').click()
- })
- cy.findByRole('dialog').should('not.exist')
- })
- it('opens subscription page after clicking on `upgrade`', function () {
- mountEditor()
- cy.findByRole('dialog').within(() => {
- // Verify the button exists. Clicking it will open a new window
- cy.findByText('Upgrade').should('exist')
- })
- })
- // eslint-disable-next-line mocha/no-skipped-tests
- it.skip('opens subscription page after clicking on `try it for free`', function () {})
- it('shows `ask project owner to upgrade` message', function () {
- mountEditor('other-user-id' as UserId)
- cy.findByRole('dialog').within(() => {
- cy.findByText(
- 'Please ask the project owner to upgrade to use track changes'
- ).should('exist')
- })
- })
- })
|