Browse Source

Merge pull request #13117 from overleaf/ii-history-react-changes-list-tests

[web] Add changes list tests

GitOrigin-RevId: 5a4e1ea647d737dc761e4258002e6507c76e95a9
ilkin-overleaf 3 years ago
parent
commit
454f8f80c2

+ 18 - 4
services/web/frontend/js/features/history/components/change-list/changes.tsx

@@ -14,20 +14,34 @@ function Changes({ pathnames, projectOps }: ChangesProps) {
     <ol className="history-version-changes">
       {pathnames.map(pathname => (
         <li key={pathname}>
-          <div className="history-version-change-action">
+          <div
+            className="history-version-change-action"
+            data-testid="history-version-change-action"
+          >
             {t('file_action_edited')}
           </div>
-          <div className="history-version-change-doc">{pathname}</div>
+          <div
+            className="history-version-change-doc"
+            data-testid="history-version-change-doc"
+          >
+            {pathname}
+          </div>
         </li>
       ))}
       {projectOps.map((op, index) => (
         <li key={index}>
-          <div className="history-version-change-action">
+          <div
+            className="history-version-change-action"
+            data-testid="history-version-change-action"
+          >
             {op.rename && t('file_action_renamed')}
             {op.add && t('file_action_created')}
             {op.remove && t('file_action_deleted')}
           </div>
-          <div className="history-version-change-doc">
+          <div
+            className="history-version-change-doc"
+            data-testid="history-version-change-doc"
+          >
             {getProjectOpDoc(op)}
           </div>
         </li>

+ 3 - 11
services/web/frontend/js/features/history/components/change-list/dropdown/menu-item/download.tsx

@@ -1,7 +1,6 @@
 import { useTranslation } from 'react-i18next'
-import { MenuItem, MenuItemProps } from 'react-bootstrap'
+import { MenuItem } from 'react-bootstrap'
 import Icon from '../../../../../../shared/components/icon'
-import * as location from '../../../../../../shared/components/location'
 
 type DownloadProps = {
   projectId: string
@@ -17,19 +16,12 @@ function Download({
 }: DownloadProps) {
   const { t } = useTranslation()
 
-  const handleDownloadVersion = (e: React.MouseEvent<MenuItemProps>) => {
-    e.preventDefault()
-    closeDropdown()
-    const event = e as typeof e & { target: HTMLAnchorElement }
-    location.assign(event.target.href)
-  }
-
   return (
     <MenuItem
       href={`/project/${projectId}/version/${version}/zip`}
-      target="_blank"
+      download={`${projectId}_v${version}.zip`}
       rel="noreferrer"
-      onClick={handleDownloadVersion}
+      onClick={closeDropdown}
       {...props}
     >
       <Icon type="cloud-download" fw /> {t('history_download_this_version')}

+ 1 - 0
services/web/frontend/js/features/history/components/change-list/history-version-details.tsx

@@ -38,6 +38,7 @@ function HistoryVersionDetails({
         'history-version-selectable': selectable,
       })}
       data-testid="history-version-details"
+      data-selected={selected}
       onClick={selectable ? handleSelect : undefined}
     >
       {children}

+ 4 - 1
services/web/frontend/js/features/history/components/change-list/history-version.tsx

@@ -66,7 +66,10 @@ function HistoryVersion({
           selectable={selectable}
         >
           <div className="history-version-main-details">
-            <time className="history-version-metadata-time">
+            <time
+              className="history-version-metadata-time"
+              data-testid="history-version-metadata-time"
+            >
               <b>{formatTime(update.meta.end_ts, 'Do MMMM, h:mm a')}</b>
             </time>
             {orderedLabels.map(label => (

+ 4 - 1
services/web/frontend/js/features/history/components/change-list/label-list-item.tsx

@@ -76,7 +76,10 @@ function LabelListItem({
               currentUserId={currentUserId}
               label={label}
             />
-            <time className="history-version-metadata-time">
+            <time
+              className="history-version-metadata-time"
+              data-testid="history-version-metadata-time"
+            >
               {formatTime(label.created_at, 'Do MMMM, h:mm a')}
             </time>
             {!isPseudoLabel(label) && (

+ 4 - 1
services/web/frontend/js/features/history/components/change-list/metadata-users-list.tsx

@@ -15,7 +15,10 @@ function MetadataUsersList({
   const { t } = useTranslation()
 
   return (
-    <ol className="history-version-metadata-users">
+    <ol
+      className="history-version-metadata-users"
+      data-testid="history-version-metadata-users"
+    >
       {users.map((user, index) => (
         <li key={index}>
           <UserNameWithColoredBadge user={user} currentUserId={currentUserId} />

+ 254 - 22
services/web/test/frontend/features/history/components/change-list.spec.tsx

@@ -9,6 +9,10 @@ import {
 import { HistoryProvider } from '../../../../../frontend/js/features/history/context/history-context'
 import { updates } from '../fixtures/updates'
 import { labels } from '../fixtures/labels'
+import {
+  formatTime,
+  relativeDate,
+} from '../../../../../frontend/js/features/utils/format-date'
 
 const mountWithEditorProviders = (
   component: React.ReactNode,
@@ -27,6 +31,28 @@ const mountWithEditorProviders = (
 }
 
 describe('change list', function () {
+  const scope = {
+    ui: { view: 'history', pdfLayout: 'sideBySide', chatOpen: true },
+  }
+
+  const waitForData = () => {
+    cy.wait('@updates')
+    cy.wait('@labels')
+    cy.wait('@diff')
+  }
+
+  beforeEach(function () {
+    cy.intercept('GET', '/project/*/updates*', {
+      body: updates,
+    }).as('updates')
+    cy.intercept('GET', '/project/*/labels', {
+      body: labels,
+    }).as('labels')
+    cy.intercept('GET', '/project/*/filetree/diff*', {
+      body: { diff: [{ pathname: 'main.tex' }, { pathname: 'name.tex' }] },
+    }).as('diff')
+  })
+
   describe('toggle switch', function () {
     it('renders switch buttons', function () {
       mountWithEditorProviders(
@@ -61,28 +87,6 @@ describe('change list', function () {
   })
 
   describe('tags', function () {
-    const scope = {
-      ui: { view: 'history', pdfLayout: 'sideBySide', chatOpen: true },
-    }
-
-    const waitForData = () => {
-      cy.wait('@updates')
-      cy.wait('@labels')
-      cy.wait('@diff')
-    }
-
-    beforeEach(function () {
-      cy.intercept('GET', '/project/*/updates*', {
-        body: updates,
-      }).as('updates')
-      cy.intercept('GET', '/project/*/labels', {
-        body: labels,
-      }).as('labels')
-      cy.intercept('GET', '/project/*/filetree/diff*', {
-        body: { diff: [{ pathname: 'main.tex' }, { pathname: 'name.tex' }] },
-      }).as('diff')
-    })
-
     it('renders tags', function () {
       mountWithEditorProviders(<ChangeList />, scope, {
         user: {
@@ -215,6 +219,234 @@ describe('change list', function () {
       cy.wait('@delete')
       cy.findByText(labelToDelete).should('not.exist')
     })
+
+    it('verifies that selecting the same list item will not trigger a new diff', function () {
+      mountWithEditorProviders(<ChangeList />, scope, {
+        user: {
+          id: USER_ID,
+          email: USER_EMAIL,
+          isAdmin: true,
+        },
+      })
+      waitForData()
+
+      const stub = cy.stub().as('diffStub')
+      cy.intercept('GET', '/project/*/filetree/diff*', stub).as('diff')
+
+      cy.findAllByTestId('history-version-details').eq(1).as('details')
+      cy.get('@details').click() // 1st click
+      cy.wait('@diff')
+      cy.get('@details').click() // 2nd click
+      cy.get('@diffStub').should('have.been.calledOnce')
+    })
+  })
+
+  describe('all history', function () {
+    beforeEach(function () {
+      mountWithEditorProviders(<ChangeList />, scope, {
+        user: {
+          id: USER_ID,
+          email: USER_EMAIL,
+          isAdmin: true,
+        },
+      })
+      waitForData()
+    })
+
+    it('shows grouped versions date', function () {
+      cy.findByText(relativeDate(updates.updates[0].meta.end_ts))
+      cy.findByText(relativeDate(updates.updates[1].meta.end_ts))
+    })
+
+    it('shows the date of the version', function () {
+      cy.findAllByTestId('history-version-details')
+        .eq(0)
+        .within(() => {
+          cy.findByTestId('history-version-metadata-time').should(
+            'have.text',
+            formatTime(updates.updates[0].meta.end_ts, 'Do MMMM, h:mm a')
+          )
+        })
+    })
+
+    it('shows change action', function () {
+      cy.findAllByTestId('history-version-details')
+        .eq(0)
+        .within(() => {
+          cy.findByTestId('history-version-change-action').should(
+            'have.text',
+            'Created'
+          )
+        })
+    })
+
+    it('shows changed document name', function () {
+      cy.findAllByTestId('history-version-details')
+        .eq(1)
+        .within(() => {
+          cy.findByTestId('history-version-change-doc').should(
+            'have.text',
+            updates.updates[1].pathnames[0]
+          )
+        })
+    })
+
+    it('shows users', function () {
+      cy.findAllByTestId('history-version-details')
+        .eq(0)
+        .within(() => {
+          cy.findByTestId('history-version-metadata-users')
+            .should('contain.text', 'You')
+            .and('contain.text', updates.updates[0].meta.users[1].first_name)
+        })
+    })
+  })
+
+  describe('labels only', function () {
+    beforeEach(function () {
+      mountWithEditorProviders(<ChangeList />, scope, {
+        user: {
+          id: USER_ID,
+          email: USER_EMAIL,
+          isAdmin: true,
+        },
+      })
+      waitForData()
+      cy.findByLabelText(/labels/i).click({ force: true })
+    })
+
+    it('does not show the dropdown menu item for adding new labels', function () {
+      cy.findAllByTestId('history-version-details')
+        .eq(1)
+        .within(() => {
+          cy.findByRole('button', { name: /more actions/i }).click()
+          cy.findByRole('menu').within(() => {
+            cy.findByRole('menuitem', {
+              name: /label this version/i,
+            }).should('not.exist')
+          })
+        })
+    })
+
+    it('resets from compare to view mode when switching tabs', function () {
+      cy.findAllByTestId('history-version-details')
+        .eq(1)
+        .within(() => {
+          cy.findByRole('button', { name: /more actions/i }).click()
+          cy.findByRole('menu').within(() => {
+            cy.findByRole('menuitem', {
+              name: /compare to selected version/i,
+            }).click()
+          })
+        })
+      cy.wait('@diff')
+      cy.findByLabelText(/all history/i).click({ force: true })
+      cy.findAllByTestId('history-version-details').should($versions => {
+        const [first, ...rest] = Array.from($versions)
+        expect(first.dataset.selected === 'true').to.be.true
+        expect(rest.every(version => version.dataset.selected === 'false')).to
+          .be.true
+      })
+    })
+  })
+
+  describe('dropdown', function () {
+    beforeEach(function () {
+      mountWithEditorProviders(<ChangeList />, scope, {
+        user: {
+          id: USER_ID,
+          email: USER_EMAIL,
+          isAdmin: true,
+        },
+      })
+      waitForData()
+    })
+
+    it('adds badge/label', function () {
+      cy.findAllByTestId('history-version-details').eq(1).as('version')
+      cy.get('@version').within(() => {
+        cy.findByRole('button', { name: /more actions/i }).click()
+        cy.findByRole('menu').within(() => {
+          cy.findByRole('menuitem', {
+            name: /label this version/i,
+          }).click()
+        })
+      })
+      cy.findByRole('dialog').as('modal')
+      cy.intercept('POST', '/project/*/labels', req => {
+        req.reply(200, {
+          id: '64633ee158e9ef7da614c000',
+          comment: req.body.comment,
+          version: req.body.version,
+          user_id: USER_ID,
+          created_at: '2023-05-16T08:29:21.250Z',
+          user_display_name: 'john.doe',
+        })
+      }).as('addLabel')
+      const newLabel = 'my new label'
+      cy.get('@modal').within(() => {
+        cy.findByRole('heading', { name: /add label/i })
+        cy.findByRole('button', { name: /cancel/i })
+        cy.findByRole('button', { name: /add label/i }).should('be.disabled')
+        cy.findByPlaceholderText(/new label name/i).as('input')
+        cy.get('@input').type(newLabel)
+        cy.findByRole('button', { name: /add label/i }).should('be.enabled')
+        cy.get('@input').type('{enter}')
+      })
+      cy.wait('@addLabel')
+      cy.get('@version').within(() => {
+        cy.findAllByTestId('history-version-badge').should($badges => {
+          const includes = Array.from($badges).some(badge =>
+            badge.textContent?.includes(newLabel)
+          )
+          expect(includes).to.be.true
+        })
+      })
+    })
+
+    it('downloads version', function () {
+      cy.intercept('GET', '/project/*/version/*/zip', { statusCode: 200 }).as(
+        'download'
+      )
+      cy.findAllByTestId('history-version-details')
+        .eq(0)
+        .within(() => {
+          cy.findByRole('button', { name: /more actions/i }).click()
+          cy.findByRole('menu').within(() => {
+            cy.findByRole('menuitem', {
+              name: /download this version/i,
+            }).click()
+          })
+        })
+      cy.wait('@download')
+    })
+
+    it('compares versions', function () {
+      cy.findAllByTestId('history-version-details').should($versions => {
+        const [first, ...rest] = Array.from($versions)
+        expect(first).to.have.attr('data-selected', 'true')
+        rest.forEach(version =>
+          expect(version).to.have.attr('data-selected', 'false')
+        )
+      })
+
+      cy.intercept('GET', '/project/*/filetree/diff*', { statusCode: 200 }).as(
+        'compareDiff'
+      )
+
+      cy.findAllByTestId('history-version-details')
+        .last()
+        .within(() => {
+          cy.findByRole('button', { name: /more actions/i }).click()
+          cy.findByRole('menu').within(() => {
+            cy.findByRole('menuitem', {
+              name: /compare to selected version/i,
+            }).click()
+          })
+        })
+
+      cy.wait('@compareDiff')
+    })
   })
 
   describe('paywall', function () {

+ 7 - 1
services/web/test/frontend/features/history/fixtures/updates.ts

@@ -1,4 +1,4 @@
-import { USER_ID } from '../../../helpers/editor-providers'
+import { USER_ID, USER_EMAIL } from '../../../helpers/editor-providers'
 
 export const updates = {
   updates: [
@@ -7,6 +7,12 @@ export const updates = {
       toV: 4,
       meta: {
         users: [
+          {
+            first_name: 'testuser',
+            last_name: '',
+            email: USER_EMAIL,
+            id: USER_ID,
+          },
           {
             first_name: 'john.doe',
             last_name: '',