Преглед на файлове

[CE/SP] Add e2e tests for project list (#18475)

* [CE/SP] Add e2e tests for project list

GitOrigin-RevId: 0368d74b47c79f74b3520177d8bb697013d851d4
Miguel Serrano преди 2 години
родител
ревизия
0cfc64786d

+ 4 - 2
server-ce/test/admin.spec.ts

@@ -14,8 +14,10 @@ describe('admin panel', function () {
     const deletedProjectName = `deleted-project-${uuid()}`
     let projectToDeleteId = ''
 
-    const findProjectRow = (projectName: string) =>
-      cy.findByText(projectName).parent().parent()
+    const findProjectRow = (projectName: string) => {
+      cy.log('find project row')
+      return cy.findByText(projectName).parent().parent()
+    }
 
     startWith({
       pro: true,

+ 5 - 1
server-ce/test/cypress.config.js

@@ -1,4 +1,5 @@
 const { defineConfig } = require('cypress')
+const { readPdf, readFileInZip } = require('./helpers/read-file')
 
 const specPattern = process.env.SPEC_PATTERN || './**/*.spec.{js,ts,tsx}'
 
@@ -14,7 +15,10 @@ module.exports = defineConfig({
   e2e: {
     baseUrl: 'http://localhost',
     setupNodeEvents(on, config) {
-      // implement node event listeners here
+      on('task', {
+        readPdf,
+        readFileInZip,
+      })
     },
     specPattern,
   },

+ 52 - 0
server-ce/test/helpers/read-file.ts

@@ -0,0 +1,52 @@
+import fs from 'fs'
+import path from 'path'
+import pdf from 'pdf-parse'
+import AdmZip from 'adm-zip'
+import { promisify } from 'util'
+
+const sleep = promisify(setTimeout)
+
+const MAX_ATTEMPTS = 15
+const POLL_INTERVAL = 500
+
+type ReadFileInZipArgs = {
+  pathToZip: string
+  fileToRead: string
+}
+
+export async function readFileInZip({
+  pathToZip,
+  fileToRead,
+}: ReadFileInZipArgs) {
+  let attempt = 0
+  while (attempt < MAX_ATTEMPTS) {
+    if (fs.existsSync(pathToZip)) {
+      const zip = new AdmZip(path.resolve(pathToZip))
+      const entry = zip
+        .getEntries()
+        .find(entry => entry.entryName == fileToRead)
+      if (entry) {
+        return entry.getData().toString('utf8')
+      } else {
+        throw new Error(`${fileToRead} not found in ${pathToZip}`)
+      }
+    }
+    await sleep(POLL_INTERVAL)
+    attempt++
+  }
+  throw new Error(`${pathToZip} not found`)
+}
+
+export async function readPdf(file: string) {
+  let attempt = 0
+  while (attempt < MAX_ATTEMPTS) {
+    if (fs.existsSync(file)) {
+      const dataBuffer = fs.readFileSync(path.resolve(file))
+      const { text } = await pdf(dataBuffer)
+      return text
+    }
+    await sleep(POLL_INTERVAL)
+    attempt++
+  }
+  throw new Error(`${file} not found`)
+}

+ 51 - 3
server-ce/test/package-lock.json

@@ -8,13 +8,17 @@
       "dependencies": {
         "@isomorphic-git/lightning-fs": "^4.6.0",
         "@testing-library/cypress": "^10.0.1",
+        "@types/adm-zip": "^0.5.5",
+        "@types/pdf-parse": "^1.1.4",
         "@types/uuid": "^9.0.8",
+        "adm-zip": "^0.5.12",
         "body-parser": "^1.20.2",
         "celebrate": "^15.0.3",
         "cypress": "13.6.6",
         "express": "^4.19.2",
         "isomorphic-git": "^1.25.10",
         "js-yaml": "^4.1.0",
+        "pdf-parse": "^1.1.1",
         "typescript": "^5.0.4",
         "uuid": "^9.0.1"
       }
@@ -321,6 +325,14 @@
         "node": ">=14"
       }
     },
+    "node_modules/@types/adm-zip": {
+      "version": "0.5.5",
+      "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.5.tgz",
+      "integrity": "sha512-YCGstVMjc4LTY5uK9/obvxBya93axZOVOyf2GSUulADzmLhYE45u2nAssCs/fWBs1Ifq5Vat75JTPwd5XZoPJw==",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/aria-query": {
       "version": "5.0.3",
       "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.3.tgz",
@@ -330,11 +342,15 @@
       "version": "18.18.8",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.8.tgz",
       "integrity": "sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ==",
-      "optional": true,
       "dependencies": {
         "undici-types": "~5.26.4"
       }
     },
+    "node_modules/@types/pdf-parse": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/@types/pdf-parse/-/pdf-parse-1.1.4.tgz",
+      "integrity": "sha512-+gbBHbNCVGGYw1S9lAIIvrHW47UYOhMIFUsJcMkMrzy1Jf0vulBN3XQIjPgnoOXveMuHnF3b57fXROnY/Or7eg=="
+    },
     "node_modules/@types/sinonjs__fake-timers": {
       "version": "8.1.1",
       "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz",
@@ -371,6 +387,14 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/adm-zip": {
+      "version": "0.5.12",
+      "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.12.tgz",
+      "integrity": "sha512-6TVU49mK6KZb4qG6xWaaM4C7sA/sgUMLy/JYMOzkcp3BvVLpW0fXDFQiIzAuxFCt/2+xD7fNIiPFAoLZPhVNLQ==",
+      "engines": {
+        "node": ">=6.0"
+      }
+    },
     "node_modules/aggregate-error": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
@@ -2427,6 +2451,11 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/node-ensure": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz",
+      "integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw=="
+    },
     "node_modules/npm-run-path": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@@ -2572,6 +2601,26 @@
       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
       "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
     },
+    "node_modules/pdf-parse": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.1.tgz",
+      "integrity": "sha512-v6ZJ/efsBpGrGGknjtq9J/oC8tZWq0KWL5vQrk2GlzLEQPUDB1ex+13Rmidl1neNN358Jn9EHZw5y07FFtaC7A==",
+      "dependencies": {
+        "debug": "^3.1.0",
+        "node-ensure": "^0.0.0"
+      },
+      "engines": {
+        "node": ">=6.8.1"
+      }
+    },
+    "node_modules/pdf-parse/node_modules/debug": {
+      "version": "3.2.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+      "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+      "dependencies": {
+        "ms": "^2.1.1"
+      }
+    },
     "node_modules/pend": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
@@ -3238,8 +3287,7 @@
     "node_modules/undici-types": {
       "version": "5.26.5",
       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
-      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
-      "optional": true
+      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
     },
     "node_modules/universalify": {
       "version": "2.0.0",

+ 4 - 0
server-ce/test/package.json

@@ -11,13 +11,17 @@
   "dependencies": {
     "@isomorphic-git/lightning-fs": "^4.6.0",
     "@testing-library/cypress": "^10.0.1",
+    "@types/adm-zip": "^0.5.5",
+    "@types/pdf-parse": "^1.1.4",
     "@types/uuid": "^9.0.8",
+    "adm-zip": "^0.5.12",
     "body-parser": "^1.20.2",
     "celebrate": "^15.0.3",
     "cypress": "13.6.6",
     "express": "^4.19.2",
     "isomorphic-git": "^1.25.10",
     "js-yaml": "^4.1.0",
+    "pdf-parse": "^1.1.1",
     "typescript": "^5.0.4",
     "uuid": "^9.0.1"
   }

+ 116 - 0
server-ce/test/project-list.spec.ts

@@ -0,0 +1,116 @@
+import { ensureUserExists, login } from './helpers/login'
+import { createProject } from './helpers/project'
+import { startWith } from './helpers/config'
+import { v4 as uuid } from 'uuid'
+
+const WITHOUT_PROJECTS_USER = 'user-without-projects@example.com'
+const REGULAR_USER = 'user@example.com'
+
+describe('Project List', () => {
+  startWith({ pro: true })
+
+  const findProjectRow = (projectName: string) => {
+    cy.log('find project row')
+    return cy.findByText(projectName).parent().parent()
+  }
+
+  describe('user with no projects', () => {
+    ensureUserExists({ email: WITHOUT_PROJECTS_USER })
+
+    it("'Import from Github' is not displayed in the welcome page", () => {
+      login(WITHOUT_PROJECTS_USER)
+      cy.visit('/project')
+      cy.findByText('Create a new project').click()
+      cy.findByText(/Import from Github/i).should('not.exist')
+    })
+  })
+
+  describe('user with projects', () => {
+    const projectName = `test-project-${uuid()}`
+    let projectId: string | undefined
+    ensureUserExists({ email: REGULAR_USER })
+
+    before(() => {
+      login(REGULAR_USER)
+      cy.visit('/project')
+      createProject(projectName, { type: 'Example Project' }).then(
+        id => (projectId = id)
+      )
+    })
+
+    it('Can download project sources', () => {
+      login(REGULAR_USER)
+      cy.visit('/project')
+
+      findProjectRow(projectName).within(() =>
+        cy.get(`[aria-label="Download .zip file"]`).click()
+      )
+
+      cy.task('readFileInZip', {
+        pathToZip: `cypress/downloads/${projectName}.zip`,
+        fileToRead: 'main.tex',
+      }).should('contain', 'Your introduction goes here')
+    })
+
+    it('Can download project PDF', () => {
+      login(REGULAR_USER)
+      cy.visit('/project')
+
+      findProjectRow(projectName).within(() =>
+        cy.get(`[aria-label="Download PDF"]`).click()
+      )
+
+      const pdfName = projectName.replaceAll('-', '_')
+      cy.task('readPdf', `cypress/downloads/${pdfName}.pdf`).should(
+        'contain',
+        'Your introduction goes here'
+      )
+    })
+
+    it('can assign and remove tags to projects', () => {
+      const tagName = uuid().slice(0, 7) // long tag names are truncated in the UI, which affects selectors
+      login(REGULAR_USER)
+      cy.visit('/project')
+
+      cy.log('select project')
+      cy.get(`[id="select-project-${projectId}"`).click()
+
+      cy.log('add tag to project')
+      cy.get('button[aria-label="Tags"]').click()
+      cy.findByText('Create new tag').click()
+      cy.get('input[name="new-tag-form-name"]').type(`${tagName}{enter}`)
+      cy.get(`button[aria-label="Select tag ${tagName}"]`) // tag label in project row
+
+      cy.log('remove tag')
+      cy.get(`button[aria-label="Remove tag ${tagName}"]`)
+        .first()
+        .click({ force: true })
+      cy.get(`button[aria-label="Select tag ${tagName}"]`).should('not.exist')
+    })
+
+    it('can filter by tag', () => {
+      cy.log('create a separate project to filter')
+      const nonTaggedProjectName = `project-${uuid()}`
+      login(REGULAR_USER)
+      cy.visit('/project')
+      createProject(nonTaggedProjectName)
+      cy.visit('/project')
+
+      cy.log('select project')
+      cy.get(`[id="select-project-${projectId}"`).click()
+
+      cy.log('add tag to project')
+      const tagName = uuid().slice(0, 7) // long tag names are truncated in the UI, which affects selectors
+      cy.get('button[aria-label="Tags"]').click()
+      cy.findByText('Create new tag').click()
+      cy.get('input[name="new-tag-form-name"]').type(`${tagName}{enter}`)
+
+      cy.log(
+        'check the non-tagged project is filtered out after clicking the tag'
+      )
+      cy.findByText(nonTaggedProjectName).should('exist')
+      cy.get('button').contains(tagName).click({ force: true })
+      cy.findByText(nonTaggedProjectName).should('not.exist')
+    })
+  })
+})