Procházet zdrojové kódy

Upgrade ESLint plugins (#13153)

GitOrigin-RevId: 9cd0b4429a9c2b3df9c18957aef21ef021e5fdfd
Alf Eaton před 3 roky
rodič
revize
510e686b7b
23 změnil soubory, kde provedl 1014 přidání a 588 odebrání
  1. 520 149
      package-lock.json
  2. 4 1
      services/web/.eslintrc
  3. 2 0
      services/web/frontend/js/features/file-tree/components/file-tree-doc.js
  4. 2 0
      services/web/frontend/js/features/file-tree/components/file-tree-folder.js
  5. 0 1
      services/web/frontend/js/features/file-view/components/file-view-image.js
  6. 2 0
      services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder.tsx
  7. 2 0
      services/web/frontend/js/features/outline/components/outline-item.js
  8. 7 7
      services/web/package.json
  9. 13 15
      services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx
  10. 12 13
      services/web/test/frontend/components/shared/start-free-trial-button.spec.tsx
  11. 48 25
      services/web/test/frontend/features/source-editor/components/codemirror-editor-autocomplete.spec.tsx
  12. 78 67
      services/web/test/frontend/features/source-editor/components/codemirror-editor-close-brackets.spec.tsx
  13. 19 14
      services/web/test/frontend/features/source-editor/components/codemirror-editor-cursor.spec.tsx
  14. 22 22
      services/web/test/frontend/features/source-editor/components/codemirror-editor-fundamentals.spec.tsx
  15. 61 65
      services/web/test/frontend/features/source-editor/components/codemirror-editor-shortcuts.spec.tsx
  16. 3 5
      services/web/test/frontend/features/source-editor/components/codemirror-editor-spellchecker.spec.tsx
  17. 38 42
      services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-list.spec.tsx
  18. 5 3
      services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-toolbar.spec.tsx
  19. 99 102
      services/web/test/frontend/features/source-editor/components/codemirror-editor-visual.spec.tsx
  20. 59 38
      services/web/test/frontend/features/source-editor/components/codemirror-editor.spec.tsx
  21. 6 9
      services/web/test/frontend/features/subscription/components/new/checkout.spec.tsx
  22. 6 2
      services/web/test/frontend/shared/hooks/use-abort-controller.test.tsx
  23. 6 8
      services/web/test/frontend/shared/hooks/use-resize.spec.tsx

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 520 - 149
package-lock.json


+ 4 - 1
services/web/.eslintrc

@@ -180,12 +180,15 @@
         //   "ignoreRefs": true
         // },],
         "react/no-did-update-set-state": "error",
-        "react/no-unknown-property": "error",
         "react/no-unused-prop-types": "error",
         "react/prop-types": "error",
         // "react/react-in-jsx-scope": "error",
         // END: inline standard-react rules
 
+        "react/no-unknown-property": ["error", {
+          "ignore": ["dnd-container", "dropdown-toggle"]
+        }],
+
         "react/jsx-no-target-blank": ["error", {
           "allowReferrer": true
         }],

+ 2 - 0
services/web/frontend/js/features/file-tree/components/file-tree-doc.js

@@ -16,7 +16,9 @@ function FileTreeDoc({ name, id, isFile, isLinkedFile }) {
 
   return (
     <li
+      // eslint-disable-next-line jsx-a11y/role-has-required-aria-props
       role="treeitem"
+      // aria-selected is provided in selectableEntityProps
       {...selectableEntityProps}
       aria-label={name}
       tabIndex="0"

+ 2 - 0
services/web/frontend/js/features/file-tree/components/file-tree-folder.js

@@ -62,7 +62,9 @@ function FileTreeFolder({ name, id, folders, docs, files }) {
   return (
     <>
       <li
+        // eslint-disable-next-line jsx-a11y/role-has-required-aria-props
         role="treeitem"
+        // aria-selected is provided in selectableEntityProps
         {...selectableEntityProps}
         aria-expanded={expanded}
         aria-label={name}

+ 0 - 1
services/web/frontend/js/features/file-view/components/file-view-image.js

@@ -11,7 +11,6 @@ export default function FileViewImage({ fileName, fileId, onLoad, onError }) {
       src={`/project/${projectId}/file/${fileId}`}
       onLoad={onLoad}
       onError={onError}
-      onAbort={onError}
       alt={fileName}
     />
   )

+ 2 - 0
services/web/frontend/js/features/history/components/file-tree/history-file-tree-folder.tsx

@@ -46,6 +46,8 @@ function HistoryFileTreeFolder({
   return (
     <>
       <li
+        // FIXME
+        // eslint-disable-next-line jsx-a11y/role-has-required-aria-props
         role="treeitem"
         aria-expanded={expanded}
         aria-label={name}

+ 2 - 0
services/web/frontend/js/features/outline/components/outline-item.js

@@ -58,6 +58,8 @@ const OutlineItem = memo(function OutlineItem({
     <li
       className={mainItemClasses}
       aria-expanded={ariaExpandedValue}
+      // FIXME
+      // eslint-disable-next-line jsx-a11y/role-has-required-aria-props
       role="treeitem"
       aria-current={isHighlighted}
       aria-label={outlineItem.title}

+ 7 - 7
services/web/package.json

@@ -276,8 +276,8 @@
     "@types/recurly__recurly-js": "^4.22.0",
     "@types/sinon-chai": "^3.2.8",
     "@types/uuid": "^8.3.4",
-    "@typescript-eslint/eslint-plugin": "^5.12.1",
-    "@typescript-eslint/parser": "^5.3.1",
+    "@typescript-eslint/eslint-plugin": "^5.59.6",
+    "@typescript-eslint/parser": "^5.59.6",
     "acorn": "^7.1.1",
     "acorn-walk": "^7.1.1",
     "angular-mocks": "~1.8.0",
@@ -297,11 +297,11 @@
     "cypress-plugin-tab": "^1.0.5",
     "es6-promise": "^4.2.8",
     "escodegen": "^2.0.0",
-    "eslint-config-standard-jsx": "^11.0.0-0",
-    "eslint-plugin-cypress": "^2.12.1",
-    "eslint-plugin-jsx-a11y": "^6.5.1",
-    "eslint-plugin-react": "^7.27.0",
-    "eslint-plugin-react-hooks": "^4.3.0",
+    "eslint-config-standard-jsx": "^11.0.0",
+    "eslint-plugin-cypress": "^2.13.3",
+    "eslint-plugin-jsx-a11y": "^6.7.1",
+    "eslint-plugin-react": "^7.32.2",
+    "eslint-plugin-react-hooks": "^4.6.0",
     "expose-loader": "^4.1.0",
     "fetch-mock": "^9.10.2",
     "glob": "^7.1.6",

+ 13 - 15
services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx

@@ -110,25 +110,23 @@ describe('<PdfPreview/>', function () {
         )
 
         // start compiling
-        cy.findByRole('button', { name: 'Recompile' })
-          .click()
-          .then(() => {
-            cy.findByRole('button', { name: 'Compiling…' })
+        cy.findByRole('button', { name: 'Recompile' }).click()
 
-            // trigger a recompile
-            cy.window().then(win => {
-              win.dispatchEvent(new CustomEvent('pdf:recompile'))
-            })
+        cy.findByRole('button', { name: 'Compiling…' }).then(() => {
+          // trigger a recompile
+          cy.window().then(win => {
+            win.dispatchEvent(new CustomEvent('pdf:recompile'))
+          })
 
-            // finish the original compile
-            resolveDeferredCompile()
+          // finish the original compile
+          resolveDeferredCompile()
 
-            // wait for the original compile to finish
-            cy.waitForCompile({ pdf: true })
+          // wait for the original compile to finish
+          cy.waitForCompile({ pdf: true })
 
-            // NOTE: difficult to assert that a second request won't be sent, at some point
-            expect(counter).to.equal(1)
-          })
+          // NOTE: difficult to assert that a second request won't be sent, at some point
+          expect(counter).to.equal(1)
+        })
       }
     )
   })

+ 12 - 13
services/web/test/frontend/components/shared/start-free-trial-button.spec.tsx

@@ -73,18 +73,17 @@ describe('start free trial button', function () {
     cy.window().then(win => {
       cy.stub(win, 'open').as('Open')
     })
-    cy.get('button.btn')
-      .contains('Start Free Trial!')
-      .click()
-      .then(() => {
-        cy.wait('@event-paywall-click')
-          .its('request.body.paywall-type')
-          .should('eq', 'cypress-test')
-        cy.get('@Open').should(
-          'have.been.calledOnceWithExactly',
-          '/user/subscription/choose-your-plan?itm_campaign=cypress-test'
-        )
-        expect(onClickStub).to.be.called
-      })
+    cy.get('button.btn').contains('Start Free Trial!').click()
+
+    cy.wrap(null).then(() => {
+      cy.wait('@event-paywall-click')
+        .its('request.body.paywall-type')
+        .should('eq', 'cypress-test')
+      cy.get('@Open').should(
+        'have.been.calledOnceWithExactly',
+        '/user/subscription/choose-your-plan?itm_campaign=cypress-test'
+      )
+      expect(onClickStub).to.be.called
+    })
   })
 })

+ 48 - 25
services/web/test/frontend/features/source-editor/components/codemirror-editor-autocomplete.spec.tsx

@@ -111,7 +111,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     cy.findAllByRole('listbox').should('have.length', 0)
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     // single backslash
     cy.get('@line').type('\\')
@@ -161,7 +162,7 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     cy.get('@line')
       // .type('{tab}') // Tab not supported in Cypress
       .type('{rightArrow}{rightArrow}')
-      .type('fr')
+    cy.get('@line').type('fr')
 
     // select option from autocomplete
     // disabled as selector not working (Cypress bug?)
@@ -282,7 +283,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     cy.contains('\\section{Results}')
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     // ---- Basic autocomplete of environments
     cy.get('@line').type('\\begin{itemi')
@@ -318,13 +320,14 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     cy.get('.cm-line').eq(26).contains('\\end{align}')
 
     // ---- Start typing a begin command
-    cy.get('.cm-line').eq(28).click().as('line')
+    cy.get('.cm-line').eq(28).as('line')
+    cy.get('@line').click()
     cy.get('@line').type('\\begin{{}ab')
     cy.findAllByRole('option').as('options')
     cy.get('@options').should('have.length', 5)
 
     // ---- The environment being typed should appear in the list
-    cy.get('@options').contains('\\begin{ab}').should('exist')
+    cy.get('@options').contains('\\begin{ab}')
 
     // ---- A new environment used elsewhere in the doc should appear next
     cy.get('@options')
@@ -399,7 +402,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     cy.findAllByRole('listbox').should('have.length', 0)
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     // a usepackage command
     cy.get('@line').type('\\usepackage')
@@ -473,7 +477,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     cy.findAllByRole('listbox').should('have.length', 0)
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     // a cite command with no opening brace
     cy.get('@line').type('\\cite')
@@ -544,7 +549,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     cy.findAllByRole('listbox').should('have.length', 0)
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     // single backslash
     cy.get('@line').type('\\')
@@ -574,7 +580,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     )
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     cy.get('@line').type('\\frac')
 
@@ -589,9 +596,11 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     cy.get('@line').should('contain.text', '\\frac{\\textbf{}}{}')
 
     // go to new line
-    cy.get('@line').click().type('{enter}')
+    cy.get('@line').click()
+    cy.get('@line').type('{enter}')
 
-    cy.get('.cm-line').eq(17).click().as('line')
+    cy.get('.cm-line').eq(17).as('line')
+    cy.get('@line').click()
     cy.get('@line').type('\\frac')
 
     // select completion
@@ -617,7 +626,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     )
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     // type some commands
     // note: '{{}' is a single opening brace
@@ -651,7 +661,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     )
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     // type some commands
     // note: '{{}' is a single opening brace
@@ -687,7 +698,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     cy.get('.cm-editor').as('editor')
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     // a new command, then the start of the command on a new blank line
     cy.get('@line').type('\\foo[bar]{{}baz}{{}zap}')
@@ -696,7 +708,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     cy.get('@editor').trigger('keydown', { key: 'Enter' })
 
     // put the cursor on the new line to type in
-    cy.get('.cm-line').eq(17).click().as('line')
+    cy.get('.cm-line').eq(17).as('line')
+    cy.get('@line').click()
 
     // the start of the command
     cy.get('@line').type('\\foo')
@@ -724,7 +737,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     cy.get('.cm-editor').as('editor')
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     // a new command, then the start of the command on a new blank line
     cy.get('@line').type('$\\somemathcommand$')
@@ -733,7 +747,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     cy.get('@editor').trigger('keydown', { key: 'Enter' })
 
     // put the cursor on the new line to type in
-    cy.get('.cm-line').eq(17).click().as('line')
+    cy.get('.cm-line').eq(17).as('line')
+    cy.get('@line').click()
 
     // the start of the command
     cy.get('@line').type('hello \\somema')
@@ -758,7 +773,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     cy.get('.cm-editor').as('editor')
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     // a new command, then the start of the command on a new blank line
     cy.get('@line').type('\\newcommand{{}\\foo}[1]{{}#1}')
@@ -767,7 +783,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     cy.get('@editor').trigger('keydown', { key: 'Enter' })
 
     // put the cursor on the new line to type in
-    cy.get('.cm-line').eq(17).click().as('line')
+    cy.get('.cm-line').eq(17).as('line')
+    cy.get('@line').click()
 
     // the start of the command
     cy.get('@line').type('\\fo')
@@ -829,7 +846,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     cy.get('.cm-editor').as('editor')
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     // start typing a command
     cy.get('@line').type('\\label')
@@ -855,7 +873,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
         </Container>
       )
       // put the cursor on a blank line to type in
-      cy.get('.cm-line').eq(16).click().as('line')
+      cy.get('.cm-line').eq(16).as('line')
+      cy.get('@line').click()
 
       // type the name of a symbol
       cy.get('@line').type(' \\alpha')
@@ -893,7 +912,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     )
 
     // Put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     // Type \begin{itemize}.
     // Note: '{{}' is a single opening brace
@@ -916,7 +936,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     )
 
     // Put the cursor on a blank line above target line
-    cy.get('.cm-line').eq(20).click().as('line')
+    cy.get('.cm-line').eq(20).as('line')
+    cy.get('@line').click()
 
     // Move to the position between the braces then type 'itemize'
     cy.get('@line').type(`{downArrow}${'{rightArrow}'.repeat(7)}itemize`, {
@@ -938,7 +959,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     )
 
     // Put the cursor on a blank line above target line
-    cy.get('.cm-line').eq(20).click().as('line')
+    cy.get('.cm-line').eq(20).as('line')
+    cy.get('@line').click()
 
     // Move to the position after the opening brace then type 'itemize}'
     cy.get('@line').type(`{downArrow}${'{rightArrow}'.repeat(7)}itemize}`, {
@@ -999,7 +1021,8 @@ describe('autocomplete', { scrollBehavior: false }, function () {
     )
 
     // Put the cursor on a blank line and type
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
     cy.get('@line').type('\\include{e', { delay: 100 })
     cy.findAllByRole('option').contains('example.tex').click()
     activeEditorLine().contains('\\include{example')

+ 78 - 67
services/web/test/frontend/features/source-editor/components/codemirror-editor-close-brackets.spec.tsx

@@ -23,7 +23,8 @@ describe('close brackets', { scrollBehavior: false }, function () {
       </Container>
     )
 
-    cy.get('.cm-line').eq(20).click().as('active-line')
+    cy.get('.cm-line').eq(20).as('active-line')
+    cy.get('@active-line').click()
   })
 
   afterEach(function () {
@@ -32,161 +33,171 @@ describe('close brackets', { scrollBehavior: false }, function () {
 
   describe('unprefixed characters', function () {
     it('auto-closes a curly bracket', function () {
-      cy.get('@active-line')
-        .type('{{}')
-        .should('have.text', '{}')
-        .type('{backspace}')
-        .should('have.text', '')
+      cy.get('@active-line').type('{{}')
+      cy.get('@active-line').should('have.text', '{}')
+      cy.get('@active-line').type('{backspace}')
+      cy.get('@active-line').should('have.text', '')
     })
 
     it('auto-closes a square bracket', function () {
-      cy.get('@active-line')
-        .type('[')
-        .should('have.text', '[]')
-        .type('{backspace}')
-        .should('have.text', '')
+      cy.get('@active-line').type('[')
+      cy.get('@active-line').should('have.text', '[]')
+      cy.get('@active-line').type('{backspace}')
+      cy.get('@active-line').should('have.text', '')
     })
 
     it('does not auto-close a round bracket', function () {
-      cy.get('@active-line').type('(').should('have.text', '(')
+      cy.get('@active-line').type('(')
+      cy.get('@active-line').should('have.text', '(')
     })
 
     it('auto-closes a dollar sign', function () {
-      cy.get('@active-line')
-        .type('$')
-        .should('have.text', '$$')
-        .type('{backspace}')
-        .should('have.text', '')
+      cy.get('@active-line').type('$')
+      cy.get('@active-line').should('have.text', '$$')
+      cy.get('@active-line').type('{backspace}')
+      cy.get('@active-line').should('have.text', '')
     })
 
     it('auto-closes another dollar sign', function () {
-      cy.get('@active-line')
-        .type('$$')
-        .should('have.text', '$$$$')
-        .type('{backspace}{backspace}')
-        .should('have.text', '')
+      cy.get('@active-line').type('$$')
+      cy.get('@active-line').should('have.text', '$$$$')
+      cy.get('@active-line').type('{backspace}{backspace}')
+      cy.get('@active-line').should('have.text', '')
     })
 
     it('avoids creating an odd number of adjacent dollar signs', function () {
-      cy.get('@active-line')
-        .type('$2')
-        .should('have.text', '$2$')
-        .type('{leftArrow}$')
-        .should('have.text', '$$2$')
+      cy.get('@active-line').type('$2')
+      cy.get('@active-line').should('have.text', '$2$')
+      cy.get('@active-line').type('{leftArrow}$')
+      cy.get('@active-line').should('have.text', '$$2$')
     })
   })
 
   describe('prefixed characters', function () {
     it('auto-closes a backslash-prefixed round bracket', function () {
-      cy.get('@active-line').type('\\(').should('have.text', '\\(\\)')
+      cy.get('@active-line').type('\\(')
+      cy.get('@active-line').should('have.text', '\\(\\)')
     })
 
     it('auto-closes a backslash-prefixed square bracket', function () {
-      cy.get('@active-line').type('\\[').should('have.text', '\\[\\]')
+      cy.get('@active-line').type('\\[')
+      cy.get('@active-line').should('have.text', '\\[\\]')
     })
 
     it('does not auto-close a backslash-prefixed curly bracket', function () {
-      cy.get('@active-line').type('\\{{}').should('have.text', '\\{')
+      cy.get('@active-line').type('\\{{}')
+      cy.get('@active-line').should('have.text', '\\{')
     })
 
     it('does not auto-close a backslash-prefixed dollar sign', function () {
-      cy.get('@active-line').type('\\$').should('have.text', '\\$')
+      cy.get('@active-line').type('\\$')
+      cy.get('@active-line').should('have.text', '\\$')
     })
   })
 
   describe('double-prefixed characters', function () {
     it('auto-closes a double-backslash-prefixed square bracket with a square bracket', function () {
-      cy.get('@active-line').type('\\\\[').should('have.text', '\\\\[]')
+      cy.get('@active-line').type('\\\\[')
+      cy.get('@active-line').should('have.text', '\\\\[]')
     })
 
     it('auto-closes a double-backslash-prefixed curly bracket with a curly bracket', function () {
-      cy.get('@active-line').type('\\\\{').should('have.text', '\\\\{}')
+      cy.get('@active-line').type('\\\\{')
+      cy.get('@active-line').should('have.text', '\\\\{}')
     })
 
     it('auto-closes a double-backslash-prefixed dollar sign with a dollar sign', function () {
-      cy.get('@active-line').type('\\\\$').should('have.text', '\\\\$$')
+      cy.get('@active-line').type('\\\\$')
+      cy.get('@active-line').should('have.text', '\\\\$$')
     })
 
     it('does not auto-close a double-backslash-prefixed round bracket', function () {
-      cy.get('@active-line').type('\\\\(').should('have.text', '\\\\(')
+      cy.get('@active-line').type('\\\\(')
+      cy.get('@active-line').should('have.text', '\\\\(')
     })
   })
 
   describe('adjacent characters', function () {
     it('does auto-close a dollar sign before punctuation', function () {
-      cy.get('@active-line')
-        .type(':2')
-        .type('{leftArrow}{leftArrow}$')
-        .should('have.text', '$$:2')
+      cy.get('@active-line').type(':2')
+      cy.get('@active-line').type('{leftArrow}{leftArrow}$')
+      cy.get('@active-line').should('have.text', '$$:2')
     })
 
     it('does auto-close a dollar sign after punctuation', function () {
-      cy.get('@active-line').type('2:').type('$').should('have.text', '2:$$')
+      cy.get('@active-line').type('2:')
+      cy.get('@active-line').type('$')
+      cy.get('@active-line').should('have.text', '2:$$')
     })
 
     it('does not auto-close a dollar sign before text', function () {
-      cy.get('@active-line')
-        .type('2')
-        .type('{leftArrow}$')
-        .should('have.text', '$2')
+      cy.get('@active-line').type('2')
+      cy.get('@active-line').type('{leftArrow}$')
+      cy.get('@active-line').should('have.text', '$2')
     })
 
     it('does not auto-close a dollar sign after text', function () {
-      cy.get('@active-line').type('2').type('$').should('have.text', '2$')
+      cy.get('@active-line').type('2')
+      cy.get('@active-line').type('$')
+      cy.get('@active-line').should('have.text', '2$')
     })
 
     it('does auto-close a curly bracket before punctuation', function () {
-      cy.get('@active-line')
-        .type(':2')
-        .type('{leftArrow}{leftArrow}{{}')
-        .should('have.text', '{}:2')
+      cy.get('@active-line').type(':2')
+      cy.get('@active-line').type('{leftArrow}{leftArrow}{{}')
+      cy.get('@active-line').should('have.text', '{}:2')
     })
 
     it('does auto-close a curly bracket after punctuation', function () {
-      cy.get('@active-line').type('2:').type('{{}').should('have.text', '2:{}')
+      cy.get('@active-line').type('2:')
+      cy.get('@active-line').type('{{}')
+      cy.get('@active-line').should('have.text', '2:{}')
     })
 
     it('does not auto-close a curly bracket before text', function () {
-      cy.get('@active-line')
-        .type('2')
-        .type('{leftArrow}{{}')
-        .should('have.text', '{2')
+      cy.get('@active-line').type('2')
+      cy.get('@active-line').type('{leftArrow}{{}')
+      cy.get('@active-line').should('have.text', '{2')
     })
 
     it('does auto-close a curly bracket after text', function () {
-      cy.get('@active-line').type('2').type('{{}').should('have.text', '2{}')
+      cy.get('@active-line').type('2')
+      cy.get('@active-line').type('{{}')
+      cy.get('@active-line').should('have.text', '2{}')
     })
 
     it('does auto-close $$ before punctuation', function () {
-      cy.get('@active-line')
-        .type(':2')
-        .type('{leftArrow}{leftArrow}$$')
-        .should('have.text', '$$$$:2')
+      cy.get('@active-line').type(':2')
+      cy.get('@active-line').type('{leftArrow}{leftArrow}$$')
+      cy.get('@active-line').should('have.text', '$$$$:2')
     })
 
     it('does not auto-close $$ before text', function () {
-      cy.get('@active-line')
-        .type('2')
-        .type('{leftArrow}$$')
-        .should('have.text', '$$2')
+      cy.get('@active-line').type('2')
+      cy.get('@active-line').type('{leftArrow}$$')
+      cy.get('@active-line').should('have.text', '$$2')
     })
   })
 
   describe('closed brackets', function () {
     it('does type over a closing dollar sign', function () {
-      cy.get('@active-line').type('$2$').should('have.text', '$2$')
+      cy.get('@active-line').type('$2$')
+      cy.get('@active-line').should('have.text', '$2$')
     })
 
     it('does type over two closing dollar signs', function () {
-      cy.get('@active-line').type('$$2$$').should('have.text', '$$2$$')
+      cy.get('@active-line').type('$$2$$')
+      cy.get('@active-line').should('have.text', '$$2$$')
     })
 
     it('does type over a closing curly bracket', function () {
-      cy.get('@active-line').type('{{}2}').should('have.text', '{2}')
+      cy.get('@active-line').type('{{}2}')
+      cy.get('@active-line').should('have.text', '{2}')
     })
 
     it('does type over a closing square bracket', function () {
-      cy.get('@active-line').type('[2]').should('have.text', '[2]')
+      cy.get('@active-line').type('[2]')
+      cy.get('@active-line').should('have.text', '[2]')
     })
   })
 })

+ 19 - 14
services/web/test/frontend/features/source-editor/components/codemirror-editor-cursor.spec.tsx

@@ -40,35 +40,39 @@ ${'long line '.repeat(200)}`
 
   it('has cursor', function () {
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(1).click().as('line')
+    cy.get('.cm-line').eq(1).as('line')
+    cy.get('@line').click()
 
-    cy.get('.cm-cursor').as('cursor').should('exist')
+    cy.get('.cm-cursor').as('cursor')
 
     cy.get('.cm-cursor').then(assertIsFullLineHeight)
   })
 
   it('has cursor on empty line whose height is the same as the line', function () {
     // Put the cursor on a blank line
-    cy.get('.cm-line').eq(1).click().as('line')
+    cy.get('.cm-line').eq(1).as('line')
+    cy.get('@line').click()
 
-    cy.get('.cm-cursor').as('cursor').should('exist')
+    cy.get('.cm-cursor').as('cursor')
 
     cy.get('@cursor').then(assertIsFullLineHeight)
   })
 
   it('has cursor on non-empty line whose height is the same as the line', function () {
     // Put the cursor on a blank line
-    cy.get('.cm-line').eq(1).click().as('line')
+    cy.get('.cm-line').eq(1).as('line')
+    cy.get('@line').click()
     cy.get('@line').type('wombat')
 
-    cy.get('.cm-cursor').as('cursor').should('exist')
+    cy.get('.cm-cursor').as('cursor')
 
     cy.get('@cursor').then(assertIsFullLineHeight)
   })
 
   it('puts cursor in the correct place inside brackets', function () {
     // Put the cursor on a blank line
-    cy.get('.cm-line').eq(1).click().as('line')
+    cy.get('.cm-line').eq(1).as('line')
+    cy.get('@line').click()
     cy.get('@line').type('[{Enter}')
 
     // Get the line inside the bracket
@@ -87,9 +91,10 @@ ${'long line '.repeat(200)}`
 
   it('has active line highlight line decoration of same height as line when there is no selection and line does not wrap', function () {
     // Put the cursor on a blank line
-    cy.get('.cm-line').eq(1).click().as('line')
+    cy.get('.cm-line').eq(1).as('line')
+    cy.get('@line').click()
 
-    cy.get('.cm-content .cm-activeLine').as('highlight').should('exist')
+    cy.get('.cm-content .cm-activeLine').as('highlight')
     cy.get('.ol-cm-activeLineLayer .cm-activeLine').should('not.exist')
 
     cy.get('@highlight').then(assertIsFullLineHeight)
@@ -97,11 +102,10 @@ ${'long line '.repeat(200)}`
 
   it('has active line highlight layer decoration of same height as non-wrapped line when there is no selection and line wraps', function () {
     // Put the cursor on a blank line
-    cy.get('.cm-line').eq(2).click().as('line')
+    cy.get('.cm-line').eq(2).as('line')
+    cy.get('@line').click()
 
-    cy.get('.ol-cm-activeLineLayer .cm-activeLine')
-      .as('highlight')
-      .should('exist')
+    cy.get('.ol-cm-activeLineLayer .cm-activeLine').as('highlight')
     cy.get('.cm-content .cm-activeLine').should('not.exist')
 
     cy.get('.cm-line').eq(1).as('line')
@@ -111,7 +115,8 @@ ${'long line '.repeat(200)}`
 
   it('has no active line highlight when there is a selection', function () {
     // Put the cursor on a blank line
-    cy.get('.cm-line').eq(1).click().as('line')
+    cy.get('.cm-line').eq(1).as('line')
+    cy.get('@line').click()
     cy.get('@line').type(isMac ? '{cmd}A' : '{ctrl}A')
 
     cy.get('.cm-activeLine').should('not.exist')

+ 22 - 22
services/web/test/frontend/features/source-editor/components/codemirror-editor-fundamentals.spec.tsx

@@ -26,48 +26,48 @@ test
       </Container>
     )
 
-    cy.get('.cm-line').eq(0).click().as('first-line')
-    cy.get('.cm-line').eq(1).click().as('line')
-    cy.get('.cm-line').eq(2).click().as('empty-line')
+    cy.get('.cm-line').eq(0).as('first-line')
+    cy.get('.cm-line').eq(1).as('line')
+    cy.get('.cm-line').eq(2).as('empty-line')
+
+    cy.get('@line').click()
   })
 
   it('deletes with backspace', function () {
-    cy.get('@line').type('{backspace}').should('have.text', 'tes')
+    cy.get('@line').type('{backspace}')
+    cy.get('@line').should('have.text', 'tes')
   })
 
   it('moves with arrow keys', function () {
-    cy.get('@line')
-      .type('{leftArrow}1')
-      .should('have.text', 'tes1t')
-      .type('{rightArrow}2')
-      .should('have.text', 'tes1t2')
-      .type('{downArrow}3')
-      .type('{upArrow}{upArrow}4')
+    cy.get('@line').type('{leftArrow}1')
+    cy.get('@line').should('have.text', 'tes1t')
+    cy.get('@line').type('{rightArrow}2')
+    cy.get('@line').should('have.text', 'tes1t2')
+    cy.get('@line').type('{downArrow}3')
+    cy.get('@line').type('{upArrow}{upArrow}4')
     cy.get('@empty-line').should('have.text', '3')
     cy.get('@first-line').should('have.text', '4')
   })
 
   it('deletes with delete', function () {
-    cy.get('@line').type('{leftArrow}{del}').should('have.text', 'tes')
+    cy.get('@line').type('{leftArrow}{del}')
+    cy.get('@line').should('have.text', 'tes')
   })
 
   it('types characters', function () {
-    cy.get('@empty-line')
-      .type('hello codemirror!')
-      .should('have.text', 'hello codemirror!')
+    cy.get('@empty-line').type('hello codemirror!')
+    cy.get('@empty-line').should('have.text', 'hello codemirror!')
   })
 
   it('replaces selections', function () {
-    cy.get('@line')
-      .type('{shift}{leftArrow}{leftArrow}{leftArrow}')
-      .type('abby cat')
-      .should('have.text', 'tabby cat')
+    cy.get('@line').type('{shift}{leftArrow}{leftArrow}{leftArrow}')
+    cy.get('@line').type('abby cat')
+    cy.get('@line').should('have.text', 'tabby cat')
   })
 
   it('inserts LaTeX commands', function () {
-    cy.get('@empty-line')
-      .type('\\cmd[opt]{{}arg}')
-      .should('have.text', '\\cmd[opt]{arg}')
+    cy.get('@empty-line').type('\\cmd[opt]{{}arg}')
+    cy.get('@empty-line').should('have.text', '\\cmd[opt]{arg}')
   })
 
   it('allows line-breaks', function () {

+ 61 - 65
services/web/test/frontend/features/source-editor/components/codemirror-editor-shortcuts.spec.tsx

@@ -28,7 +28,8 @@ describe('keyboard shortcuts', { scrollBehavior: false }, function () {
       </Container>
     )
 
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
     cy.get('.cm-editor').as('editor')
   })
 
@@ -37,12 +38,12 @@ describe('keyboard shortcuts', { scrollBehavior: false }, function () {
   })
 
   it('comment line with {meta+/}', function () {
-    cy.get('@line')
-      .type('text')
-      .type(`{${metaKey}+/}`)
-      .should('have.text', '% text')
+    cy.get('@line').type('text')
+    cy.get('@line').type(`{${metaKey}+/}`)
+    cy.get('@line').should('have.text', '% text')
 
-    cy.get('@line').type(`{${metaKey}+/}`).should('have.text', 'text')
+    cy.get('@line').type(`{${metaKey}+/}`)
+    cy.get('@line').should('have.text', 'text')
   })
 
   it('comment line with {ctrl+#}', function () {
@@ -55,23 +56,23 @@ describe('keyboard shortcuts', { scrollBehavior: false }, function () {
   })
 
   it('undo line with {meta+z}', function () {
-    cy.get('@line').type('text').type(`{${metaKey}+z}`).should('have.text', '')
+    cy.get('@line').type('text')
+    cy.get('@line').type(`{${metaKey}+z}`)
+    cy.get('@line').should('have.text', '')
   })
 
   it('redo line with {meta+shift+z}', function () {
-    cy.get('@line')
-      .type('text')
-      .type(`{${metaKey}+z}`) // undo
-      .type(`{${metaKey}+shift+z}`) // redo
-      .should('have.text', 'text')
+    cy.get('@line').type('text')
+    cy.get('@line').type(`{${metaKey}+z}`) // undo
+    cy.get('@line').type(`{${metaKey}+shift+z}`) // redo
+    cy.get('@line').should('have.text', 'text')
   })
 
   it('redo line with {meta+y}', function () {
-    cy.get('@line')
-      .type('text')
-      .type(`{${metaKey}+z}`) // undo
-      .type(`{${metaKey}+y}`) // redo
-      .should('have.text', 'text')
+    cy.get('@line').type('text')
+    cy.get('@line').type(`{${metaKey}+z}`) // undo
+    cy.get('@line').type(`{${metaKey}+y}`) // redo
+    cy.get('@line').should('have.text', 'text')
   })
 
   it('delete line with {meta+d}', function () {
@@ -83,24 +84,21 @@ describe('keyboard shortcuts', { scrollBehavior: false }, function () {
   })
 
   it('indent line with {tab}', function () {
-    cy.get('@line')
-      .trigger('keydown', { key: 'Tab' })
-      .should('have.text', '    ')
+    cy.get('@line').trigger('keydown', { key: 'Tab' })
+    cy.get('@line').should('have.text', '    ')
   })
 
   it('unindent line with {shift+tab}', function () {
-    cy.get('@line')
-      .trigger('keydown', { key: 'Tab' }) // indent
-      .trigger('keydown', { key: 'Tab', shiftKey: true }) // unindent
-      .should('have.text', '')
+    cy.get('@line').trigger('keydown', { key: 'Tab' }) // indent
+    cy.get('@line').trigger('keydown', { key: 'Tab', shiftKey: true }) // unindent
+    cy.get('@line').should('have.text', '')
   })
 
   it('uppercase selection with {ctrl+u}', function () {
-    cy.get('@line')
-      .type('a')
-      .type('{shift+leftArrow}') // select text
-      .type('{ctrl+u}')
-      .should('have.text', 'A')
+    cy.get('@line').type('a')
+    cy.get('@line').type('{shift+leftArrow}') // select text
+    cy.get('@line').type('{ctrl+u}')
+    cy.get('@line').should('have.text', 'A')
   })
 
   it('lowercase selection with {ctrl+shift+u}', function () {
@@ -110,27 +108,24 @@ describe('keyboard shortcuts', { scrollBehavior: false }, function () {
       this.skip()
     }
 
-    cy.get('@line')
-      .type('A')
-      .type('{shift+leftArrow}') // select text
-      .type('{ctrl+shift+u}') // TODO: ctrl+shift+u is a system shortcut so this fails in CI
-      .should('have.text', 'a')
+    cy.get('@line').type('A')
+    cy.get('@line').type('{shift+leftArrow}') // select text
+    cy.get('@line').type('{ctrl+shift+u}') // TODO: ctrl+shift+u is a system shortcut so this fails in CI
+    cy.get('@line').should('have.text', 'a')
   })
 
   it('wrap selection with "\\textbf{}" by using {meta+b}', function () {
-    cy.get('@line')
-      .type('a')
-      .type('{shift+leftArrow}') // select text
-      .type(`{${metaKey}+b}`)
-      .should('have.text', '\\textbf{a}')
+    cy.get('@line').type('a')
+    cy.get('@line').type('{shift+leftArrow}') // select text
+    cy.get('@line').type(`{${metaKey}+b}`)
+    cy.get('@line').should('have.text', '\\textbf{a}')
   })
 
   it('wrap selection with "\\textit{}" by using {meta+i}', function () {
-    cy.get('@line')
-      .type('a')
-      .type('{shift+leftArrow}') // select text
-      .type(`{${metaKey}+i}`)
-      .should('have.text', '\\textit{a}')
+    cy.get('@line').type('a')
+    cy.get('@line').type('{shift+leftArrow}') // select text
+    cy.get('@line').type(`{${metaKey}+i}`)
+    cy.get('@line').should('have.text', '\\textit{a}')
   })
 })
 
@@ -158,7 +153,10 @@ contentLine3
         </EditorProviders>
       </Container>
     )
-    cy.get('.cm-line').eq(1).scrollIntoView().click().as('line')
+    cy.get('.cm-line').eq(1).as('line')
+    cy.get('@line').scrollIntoView()
+    cy.get('@line').click()
+
     cy.get('.cm-editor').as('editor')
   })
 
@@ -215,10 +213,9 @@ contentLine3
   })
 
   it('toggle comments with M-;', function () {
-    cy.get('@line')
-      .should('have.text', '\\documentclass{article}')
-      .type('{alt};')
-      .should('have.text', '% \\documentclass{article}')
+    cy.get('@line').should('have.text', '\\documentclass{article}')
+    cy.get('@line').type('{alt};')
+    cy.get('@line').should('have.text', '% \\documentclass{article}')
   })
 
   it('should jump between start and end with M-S-, and M-S-.', function () {
@@ -230,12 +227,11 @@ contentLine3
   })
 
   it('can enter characters', function () {
-    cy.get('.cm-line')
-      .eq(0)
-      .scrollIntoView()
-      .click()
-      .type(CHARACTERS)
-      .should('have.text', CHARACTERS)
+    cy.get('.cm-line').eq(0).as('line')
+    cy.get('@line').scrollIntoView()
+    cy.get('@line').click()
+    cy.get('@line').type(CHARACTERS)
+    cy.get('@line').should('have.text', CHARACTERS)
   })
 })
 
@@ -266,7 +262,9 @@ contentLine3
         </EditorProviders>
       </Container>
     )
-    cy.get('.cm-line').eq(1).scrollIntoView().click().as('line')
+    cy.get('.cm-line').eq(1).as('line')
+    cy.get('@line').scrollIntoView()
+    cy.get('@line').click()
     cy.get('.cm-editor').as('editor')
   })
 
@@ -275,12 +273,11 @@ contentLine3
   })
 
   it('can enter characters', function () {
-    cy.get('.cm-line')
-      .eq(0)
-      .scrollIntoView()
-      .click()
-      .type(`i${CHARACTERS}{esc}`)
-      .should('have.text', CHARACTERS)
+    cy.get('.cm-line').eq(0).as('line')
+    cy.get('@line').scrollIntoView()
+    cy.get('@line').click()
+    cy.get('@line').type(`i${CHARACTERS}{esc}`)
+    cy.get('@line').should('have.text', CHARACTERS)
   })
 
   it('can move around in normal mode', function () {
@@ -293,8 +290,7 @@ contentLine3
     activeEditorLine().should('have.text', '\\begin{document}')
 
     // Move the cursor left, insert 1, move it right, insert a 2
-    cy.get('@line')
-      .type('hi1{esc}la2{esc}')
-      .should('have.text', '\\documentclass{article1}2')
+    cy.get('@line').type('hi1{esc}la2{esc}')
+    cy.get('@line').should('have.text', '\\documentclass{article1}2')
   })
 })

+ 3 - 5
services/web/test/frontend/features/source-editor/components/codemirror-editor-spellchecker.spec.tsx

@@ -38,7 +38,8 @@ describe('Spellchecker', function () {
       </Container>
     )
 
-    cy.get('.cm-line').eq(13).click().as('line')
+    cy.get('.cm-line').eq(13).as('line')
+    cy.get('@line').click()
   })
 
   afterEach(function () {
@@ -87,9 +88,6 @@ describe('Spellchecker', function () {
 
     cy.get('@line').type('notawombat')
     cy.wait('@spellCheckRequest')
-    cy.get('@line')
-      .get('.ol-cm-spelling-error')
-      .should('exist')
-      .contains('notawombat')
+    cy.get('@line').get('.ol-cm-spelling-error').contains('notawombat')
   })
 })

+ 38 - 42
services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-list.spec.tsx

@@ -96,13 +96,12 @@ describe('<CodeMirrorEditor/> lists in Rich Text mode', function () {
     mountEditor(content)
 
     // move to the start of the item and press Tab
-    cy.get('.cm-line')
-      .eq(2)
-      .click()
-      .type('{leftArrow}'.repeat(4))
-      .trigger('keydown', {
-        key: 'Tab',
-      })
+    cy.get('.cm-line').eq(2).as('line')
+    cy.get('@line').click()
+    cy.get('@line').type('{leftArrow}'.repeat(4))
+    cy.get('@line').trigger('keydown', {
+      key: 'Tab',
+    })
 
     cy.get('.cm-content').should(
       'have.text',
@@ -127,7 +126,9 @@ describe('<CodeMirrorEditor/> lists in Rich Text mode', function () {
     mountEditor(content)
 
     // focus a line (at the end of a list item) and press Tab
-    cy.get('.cm-line').eq(2).click().trigger('keydown', {
+    cy.get('.cm-line').eq(2).as('line')
+    cy.get('@line').click()
+    cy.get('@line').trigger('keydown', {
       key: 'Tab',
     })
 
@@ -153,13 +154,12 @@ describe('<CodeMirrorEditor/> lists in Rich Text mode', function () {
     mountEditor(content)
 
     // move to the start of the list item and press Tab
-    cy.get('.cm-line')
-      .eq(2)
-      .click()
-      .type('{leftArrow}'.repeat(4))
-      .trigger('keydown', {
-        key: 'Tab',
-      })
+    cy.get('.cm-line').eq(2).as('line')
+    cy.get('@line').click()
+    cy.get('@line').type('{leftArrow}'.repeat(4))
+    cy.get('@line').trigger('keydown', {
+      key: 'Tab',
+    })
 
     cy.get('.cm-content').should(
       'have.text',
@@ -227,11 +227,10 @@ describe('<CodeMirrorEditor/> lists in Rich Text mode', function () {
     ].join('\n')
     mountEditor(content)
 
-    cy.get('.cm-line')
-      .eq(2)
-      .click()
-      .type('{leftArrow}'.repeat(3)) // to the start of the item
-      .type('{upArrow}{Shift}{rightArrow}{rightArrow}{rightArrow}') // up and extend to the end of the item
+    cy.get('.cm-line').eq(2).as('line')
+    cy.get('@line').click()
+    cy.get('@line').type('{leftArrow}'.repeat(3)) // to the start of the item
+    cy.get('@line').type('{upArrow}{Shift}{rightArrow}{rightArrow}{rightArrow}') // up and extend to the end of the item
 
     cy.window().should(win => {
       expect(win.getSelection()?.toString()).to.equal('One')
@@ -247,11 +246,10 @@ describe('<CodeMirrorEditor/> lists in Rich Text mode', function () {
     ].join('\n')
     mountEditor(content)
 
-    cy.get('.cm-line')
-      .eq(2)
-      .click()
-      .type('{leftArrow}'.repeat(3)) // to the start of the item
-      .type('{upArrow}{Shift}{rightArrow}{rightArrow}{rightArrow}') // up and extend to the end of the item
+    cy.get('.cm-line').eq(2).as('line')
+    cy.get('@line').click()
+    cy.get('@line').type('{leftArrow}'.repeat(3)) // to the start of the item
+    cy.get('@line').type('{upArrow}{Shift}{rightArrow}{rightArrow}{rightArrow}') // up and extend to the end of the item
 
     cy.window().should(win => {
       expect(win.getSelection()?.toString()).to.equal('One')
@@ -267,17 +265,16 @@ describe('<CodeMirrorEditor/> lists in Rich Text mode', function () {
     ].join('\n')
     mountEditor(content)
 
-    cy.get('.cm-line')
-      .eq(0)
-      .click('left')
-      .type(
-        '{downArrow}'.repeat(4) + // down to the end line
-          '{rightArrow}'.repeat(3) + // along a few characters
-          '{upArrow}'.repeat(2) + // up to the first list item
-          '{rightArrow}'.repeat(4) + // along to the start of the second list item
-          '{shift}' + // start extending the selection
-          '{rightArrow}'.repeat(3) // cover the word
-      )
+    cy.get('.cm-line').eq(0).as('line')
+    cy.get('@line').click('left')
+    cy.get('@line').type(
+      '{downArrow}'.repeat(4) + // down to the end line
+        '{rightArrow}'.repeat(3) + // along a few characters
+        '{upArrow}'.repeat(2) + // up to the first list item
+        '{rightArrow}'.repeat(4) + // along to the start of the second list item
+        '{shift}' + // start extending the selection
+        '{rightArrow}'.repeat(3) // cover the word
+    )
 
     cy.window().should(win => {
       expect(win.getSelection()?.toString()).to.equal('Two')
@@ -293,12 +290,11 @@ describe('<CodeMirrorEditor/> lists in Rich Text mode', function () {
     ].join('\n')
     mountEditor(content)
 
-    cy.get('.cm-line')
-      .eq(2)
-      .click()
-      .type('\\ite')
-      .type('{enter}')
-      .type('second')
+    cy.get('.cm-line').eq(2).as('line')
+    cy.get('@line').click()
+    cy.get('@line').type('\\ite')
+    cy.get('@line').type('{enter}')
+    cy.get('@line').type('second')
 
     cy.get('.cm-content').should(
       'have.text',

+ 5 - 3
services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-toolbar.spec.tsx

@@ -116,9 +116,9 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
     clickToolbarButton('Insert Link')
     cy.get('.cm-content').should('have.text', '\\href{}{test}')
 
+    cy.get('.cm-line').eq(0).type('http://example.com')
     cy.get('.cm-line')
       .eq(0)
-      .type('http://example.com')
       .should('have.text', '\\href{http://example.com}{test}')
   })
 
@@ -138,7 +138,8 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
       ].join('')
     )
 
-    cy.get('.cm-line').eq(1).type('ing').should('have.text', ' testing')
+    cy.get('.cm-line').eq(1).type('ing')
+    cy.get('.cm-line').eq(1).should('have.text', ' testing')
   })
 
   it('should insert a numbered list', function () {
@@ -157,6 +158,7 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
       ].join('')
     )
 
-    cy.get('.cm-line').eq(1).type('ing').should('have.text', ' testing')
+    cy.get('.cm-line').eq(1).type('ing')
+    cy.get('.cm-line').eq(1).should('have.text', ' testing')
   })
 })

+ 99 - 102
services/web/test/frontend/features/source-editor/components/codemirror-editor-visual.spec.tsx

@@ -37,23 +37,25 @@ describe('<CodeMirrorEditor/> in Rich Text mode', function () {
     // wait for the content to be parsed and revealed
     cy.get('.cm-content').should('have.css', 'opacity', '1')
 
-    cy.get('.cm-line').eq(0).click().as('first-line')
+    cy.get('.cm-line').eq(0).as('first-line')
     cy.get('.cm-line').eq(1).as('second-line')
     cy.get('.cm-line').eq(2).as('third-line')
     cy.get('.cm-line').eq(3).as('fourth-line')
     cy.get('.ol-cm-toolbar [aria-label="Format Bold"]').as('toolbar-bold')
+
+    cy.get('@first-line').click()
   })
 
   forEach(['LaTeX', 'TeX']).it('renders the %s logo', function (logo) {
-    cy.get('@first-line').type(`\\${logo}{{}}{Enter}`).should('have.text', logo)
+    cy.get('@first-line').type(`\\${logo}{{}}{Enter}`)
+    cy.get('@first-line').should('have.text', logo)
   })
 
   it('renders \\dots', function () {
-    cy.get('@first-line')
-      .type('\\dots{Esc}')
-      .should('have.text', '\\dots')
-      .type('{Enter}')
-      .should('have.text', '…')
+    cy.get('@first-line').type('\\dots{Esc}')
+    cy.get('@first-line').should('have.text', '\\dots')
+    cy.get('@first-line').type('{Enter}')
+    cy.get('@first-line').should('have.text', '…')
   })
 
   it('creates a new list item on Enter', function () {
@@ -124,16 +126,15 @@ describe('<CodeMirrorEditor/> in Rich Text mode', function () {
   forEach(['textbf', 'textit', 'underline']).it(
     'handles \\%s text',
     function (command) {
-      cy.get('@first-line')
-        .type(`\\${command}{`)
-        .should('have.text', `{}`)
-        .type('{rightArrow} ')
-        .should('have.text', '{} ')
-        .type('{Backspace}{leftArrow}test text')
-        .should('have.text', '{test text}')
-        .type('{rightArrow} foo')
-        .should('have.text', 'test text foo') // no braces
-        .find(`.ol-cm-command-${command}`)
+      cy.get('@first-line').type(`\\${command}{`)
+      cy.get('@first-line').should('have.text', `{}`)
+      cy.get('@first-line').type('{rightArrow} ')
+      cy.get('@first-line').should('have.text', '{} ')
+      cy.get('@first-line').type('{Backspace}{leftArrow}test text')
+      cy.get('@first-line').should('have.text', '{test text}')
+      cy.get('@first-line').type('{rightArrow} foo')
+      cy.get('@first-line').should('have.text', 'test text foo') // no braces
+      cy.get('@first-line').find(`.ol-cm-command-${command}`)
     }
   )
 
@@ -146,43 +147,37 @@ describe('<CodeMirrorEditor/> in Rich Text mode', function () {
     'paragraph',
     'subparagraph',
   ]).it('handles \\%s sectioning command', function (command) {
-    cy.get('@first-line')
-      .type(`\\${command}{`)
-      .should('have.text', `\\${command}{}`)
-      .type('{rightArrow} ')
-      .should('have.text', `\\${command}{} `)
-      // Press enter before closing brace
-      .type('{Backspace}{leftArrow}title{leftArrow}{Enter}')
-      .should('have.text', 'title')
-      .find(`.ol-cm-heading.ol-cm-command-${command}`)
-      .should('exist')
+    cy.get('@first-line').type(`\\${command}{`)
+    cy.get('@first-line').should('have.text', `\\${command}{}`)
+    cy.get('@first-line').type('{rightArrow} ')
+    cy.get('@first-line').should('have.text', `\\${command}{} `)
+    // Press enter before closing brace
+    cy.get('@first-line').type('{Backspace}{leftArrow}title{leftArrow}{Enter}')
+    cy.get('@first-line').should('have.text', 'title')
+    cy.get('@first-line').find(`.ol-cm-heading.ol-cm-command-${command}`)
   })
 
   forEach(['textsc', 'texttt', 'sout', 'emph', 'url', 'caption']).it(
     'handles \\%s text',
     function (command) {
-      cy.get('@first-line')
-        .type(`\\${command}{`)
-        .should('have.text', `\\${command}{}`)
-        .type('{rightArrow} ')
-        .should('have.text', `\\${command}{} `)
-        .type('{Backspace}{leftArrow}test text{rightArrow} ')
-        .should('have.text', 'test text ')
-        .find(`.ol-cm-command-${command}`)
-        .should('exist')
+      cy.get('@first-line').type(`\\${command}{`)
+      cy.get('@first-line').should('have.text', `\\${command}{}`)
+      cy.get('@first-line').type('{rightArrow} ')
+      cy.get('@first-line').should('have.text', `\\${command}{} `)
+      cy.get('@first-line').type('{Backspace}{leftArrow}test text{rightArrow} ')
+      cy.get('@first-line').should('have.text', 'test text ')
+      cy.get('@first-line').find(`.ol-cm-command-${command}`)
     }
   )
 
   it('handles \\verb text', function () {
-    cy.get('@first-line')
-      .type(`\\verb|`)
-      .should('have.text', `\\verb|`)
-      .type('| ')
-      .should('have.text', `\\verb|| `)
-      .type('{Backspace}{leftArrow}test text{rightArrow} ')
-      .should('have.text', 'test text ')
-      .find(`.ol-cm-command-verb`)
-      .should('exist')
+    cy.get('@first-line').type(`\\verb|`)
+    cy.get('@first-line').should('have.text', `\\verb|`)
+    cy.get('@first-line').type('| ')
+    cy.get('@first-line').should('have.text', `\\verb|| `)
+    cy.get('@first-line').type('{Backspace}{leftArrow}test text{rightArrow} ')
+    cy.get('@first-line').should('have.text', 'test text ')
+    cy.get('@first-line').find(`.ol-cm-command-verb`)
   })
 
   forEach([
@@ -191,47 +186,56 @@ describe('<CodeMirrorEditor/> in Rich Text mode', function () {
     ['cite', '📚'],
     ['include', '🔗'],
   ]).it('handles \\%s commands', function (command, icon) {
-    cy.get('@first-line')
-      .type(`\\${command}{} `)
-      .should('have.text', `\\${command}{} `)
-      .type('{Backspace}{leftArrow}key')
-      .should('have.text', `\\${command}{key}`)
-      .type('{rightArrow}')
-      .should('have.text', `\\${command}{key}`)
-      .type(' ')
-      .should('have.text', `${icon}key `)
+    cy.get('@first-line').type(`\\${command}{} `)
+    cy.get('@first-line').should('have.text', `\\${command}{} `)
+    cy.get('@first-line').type('{Backspace}{leftArrow}key')
+    cy.get('@first-line').should('have.text', `\\${command}{key}`)
+    cy.get('@first-line').type('{rightArrow}')
+    cy.get('@first-line').should('have.text', `\\${command}{key}`)
+    cy.get('@first-line').type(' ')
+    cy.get('@first-line').should('have.text', `${icon}key `)
   })
 
   it('handles \\href command', function () {
-    cy.get('@first-line')
-      .type('\\href{{}https://overleaf.com} ')
-      .should('have.text', '\\href{https://overleaf.com} ')
-      .type('{Backspace}{{}{Del}Overleaf ')
-      .should('have.text', '\\href{https://overleaf.com}{Overleaf ')
-      .type('{Backspace}} ')
-      .should('have.text', 'Overleaf ')
-      .find('.ol-cm-link-text')
-      .should('exist')
+    cy.get('@first-line').type('\\href{{}https://overleaf.com} ')
+    cy.get('@first-line').should('have.text', '\\href{https://overleaf.com} ')
+    cy.get('@first-line').type('{Backspace}{{}{Del}Overleaf ')
+    cy.get('@first-line').should(
+      'have.text',
+      '\\href{https://overleaf.com}{Overleaf '
+    )
+    cy.get('@first-line').type('{Backspace}} ')
+    cy.get('@first-line').should('have.text', 'Overleaf ')
+    cy.get('@first-line').find('.ol-cm-link-text')
   })
 
   it('displays unknown commands unchanged', function () {
-    cy.get('@first-line')
-      .type('\\foo[bar]{{}baz} ')
-      .should('have.text', '\\foo[bar]{baz} ')
+    cy.get('@first-line').type('\\foo[bar]{{}baz} ')
+    cy.get('@first-line').should('have.text', '\\foo[bar]{baz} ')
   })
 
   describe('Figure environments', function () {
     beforeEach(function () {
-      cy.get('@first-line').type('\\begin{{}figure').type('{Enter}') // end with cursor in file path
+      cy.get('@first-line').type('\\begin{{}figure')
+      cy.get('@first-line').type('{Enter}') // end with cursor in file path
     })
 
     it('loads figures', function () {
       cy.get('@third-line').type('path/to/image')
 
-      cy.get('@third-line')
-        .should('have.text', '    \\includegraphics{path/to/image}')
-        .type('{DownArrow}{DownArrow}{DownArrow}{DownArrow}')
-        .should('not.exist') // Should be removed from dom when line is hidden
+      cy.get('@third-line').should(
+        'have.text',
+        '    \\includegraphics{path/to/image}'
+      )
+
+      // move the cursor out of the figure
+      cy.get('@third-line').type('{DownArrow}{DownArrow}{DownArrow}{DownArrow}')
+
+      // Should be removed from dom when line is hidden
+      cy.get('.cm-content').should(
+        'not.contain',
+        '\\includegraphics{path/to/image}'
+      )
 
       cy.get('img.ol-cm-graphics').should('have.attr', 'src', 'path/to/image')
     })
@@ -256,7 +260,8 @@ describe('<CodeMirrorEditor/> in Rich Text mode', function () {
       cy.get('@second-line')
         .should('have.text', '    \\centering')
         .should('have.class', 'ol-cm-environment-centered')
-        .type('{Backspace}')
+      cy.get('@second-line').type('{Backspace}')
+      cy.get('@second-line')
         .should('have.text', '    \\centerin')
         .should('not.have.class', 'ol-cm-environment-centered')
     })
@@ -280,34 +285,32 @@ describe('<CodeMirrorEditor/> in Rich Text mode', function () {
       })
 
       it('handles range selections inside bold', function () {
-        cy.get('@first-line')
-          .type('\\textbf{{}test}')
-          .type('{LeftArrow}'.repeat(4))
-          .type('{Shift}{RightArrow}{RightArrow}')
+        cy.get('@first-line').type('\\textbf{{}test}')
+        cy.get('@first-line').type('{LeftArrow}'.repeat(4))
+        cy.get('@first-line').type('{Shift}{RightArrow}{RightArrow}')
         cy.get('@toolbar-bold').should('have.class', 'active')
       })
 
       it('handles range selections spanning bold', function () {
-        cy.get('@first-line')
-          .type('\\textbf{{}test} outside')
-          .type('{LeftArrow}'.repeat(10))
-          .type('{Shift}' + '{RightArrow}'.repeat(5))
+        cy.get('@first-line').type('\\textbf{{}test} outside')
+        cy.get('@first-line').type('{LeftArrow}'.repeat(10))
+        cy.get('@first-line').type('{Shift}' + '{RightArrow}'.repeat(5))
         cy.get('@toolbar-bold').should('not.have.class', 'active')
       })
 
       it('does not highlight bold when commands at selection ends are different', function () {
-        cy.get('@first-line')
-          .type('\\textbf{{}first} \\textbf{{}second}')
-          .type('{LeftArrow}'.repeat(12))
-          .type('{Shift}' + '{RightArrow}'.repeat(7))
+        cy.get('@first-line').type('\\textbf{{}first} \\textbf{{}second}')
+        cy.get('@first-line').type('{LeftArrow}'.repeat(12))
+        cy.get('@first-line').type('{Shift}' + '{RightArrow}'.repeat(7))
         cy.get('@toolbar-bold').should('not.have.class', 'active')
       })
 
       it('highlight when ends share common formatting ancestor', function () {
-        cy.get('@first-line')
-          .type('\\textbf{{}\\textit{{}first} \\textit{{}second}}')
-          .type('{LeftArrow}'.repeat(13))
-          .type('{Shift}' + '{RightArrow}'.repeat(7))
+        cy.get('@first-line').type(
+          '\\textbf{{}\\textit{{}first} \\textit{{}second}}'
+        )
+        cy.get('@first-line').type('{LeftArrow}'.repeat(13))
+        cy.get('@first-line').type('{Shift}' + '{RightArrow}'.repeat(7))
         cy.get('@toolbar-bold').should('have.class', 'active')
       })
     })
@@ -318,16 +321,14 @@ describe('<CodeMirrorEditor/> in Rich Text mode', function () {
       cy.get('@first-line').type(
         '\\begin{{}frame}{{}Slide\\\\title}{Enter}\\end{{}frame}{Enter}'
       )
-      cy.get('.ol-cm-divider').should('exist')
-      cy.get('.ol-cm-frame-title').should('exist')
+      cy.get('.ol-cm-divider')
+      cy.get('.ol-cm-frame-title')
     })
     it('typesets title', function () {
       cy.get('@first-line').type(
         '\\begin{{}frame}{{}Slide\\\\title}{Enter}\\end{{}frame}{Enter}'
       )
-      cy.get('.ol-cm-frame-title')
-        .should('exist')
-        .should('have.html', 'Slide<br>title')
+      cy.get('.ol-cm-frame-title').should('have.html', 'Slide<br>title')
     })
 
     // eslint-disable-next-line mocha/no-skipped-tests
@@ -337,16 +338,14 @@ describe('<CodeMirrorEditor/> in Rich Text mode', function () {
       )
 
       // allow plenty of time for MathJax to load
-      cy.get('.MathJax', { timeout: 10000 }).should('exist')
+      cy.get('.MathJax', { timeout: 10000 })
     })
 
     it('typesets subtitle', function () {
       cy.get('@first-line').type(
         '\\begin{{}frame}{{}Slide title}{{}Slide subtitle}{Enter}\\end{{}frame}{Enter}'
       )
-      cy.get('.ol-cm-frame-subtitle')
-        .should('exist')
-        .should('have.html', 'Slide subtitle')
+      cy.get('.ol-cm-frame-subtitle').should('have.html', 'Slide subtitle')
     })
   })
 
@@ -364,12 +363,10 @@ describe('<CodeMirrorEditor/> in Rich Text mode', function () {
 
     // allow plenty of time for MathJax to load
     // TODO: re-enable this assertion when stable
-    // cy.get('.MathJax', { timeout: 10000 }).should('exist')
+    // cy.get('.MathJax', { timeout: 10000 })
 
-    cy.get('.ol-cm-maketitle').should('exist')
-    cy.get('.ol-cm-title')
-      .should('exist')
-      .should('contain.html', 'Document title<br>with')
+    cy.get('.ol-cm-maketitle')
+    cy.get('.ol-cm-title').should('contain.html', 'Document title<br>with')
     cy.get('.ol-cm-author').should('have.text', 'Author')
   })
 

+ 59 - 38
services/web/test/frontend/features/source-editor/components/codemirror-editor.spec.tsx

@@ -33,14 +33,14 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
     )
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
-
-    cy.get('@line')
-      .type('this is some text')
-      .should('have.text', 'this is some text')
-      .type('{shift}{leftArrow}{leftArrow}{leftArrow}{leftArrow}')
-      .type('{backspace}')
-      .should('have.text', 'this is some ')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
+
+    cy.get('@line').type('this is some text')
+    cy.get('@line').should('have.text', 'this is some text')
+    cy.get('@line').type('{shift}{leftArrow}{leftArrow}{leftArrow}{leftArrow}')
+    cy.get('@line').type('{backspace}')
+    cy.get('@line').should('have.text', 'this is some ')
   })
 
   it('renders client-side lint annotations in the gutter', function () {
@@ -137,7 +137,9 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
     )
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().type('foo{enter}')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
+    cy.get('@line').type('foo{enter}')
 
     activeEditorLine().should('have.text', '')
   })
@@ -154,7 +156,8 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
     )
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     cy.get('@line').type('\\begin{{}itemiz')
     cy.findAllByRole('listbox').contains('\\begin{itemize}').click()
@@ -174,10 +177,12 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
     )
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     // Single indentation
-    cy.get('@line').trigger('keydown', { key: 'Tab' }).type('{enter}')
+    cy.get('@line').trigger('keydown', { key: 'Tab' })
+    cy.get('@line').type('{enter}')
 
     activeEditorLine().should('have.text', '    ')
 
@@ -242,7 +247,8 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
       throw error
     })
 
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     cy.get('@line').type('text')
     cy.get('@line').should('not.contain.text', 'text')
@@ -266,7 +272,7 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
 
     pairs.forEach(pair => {
       activeEditorLine().type(pair).as('line')
-      cy.get('@line').find('.cm-matchingBracket').should('exist')
+      cy.get('@line').find('.cm-matchingBracket')
       cy.get('@line').type('{enter}')
     })
   })
@@ -283,7 +289,8 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
     )
 
     // select foldable line
-    cy.get('.cm-line').eq(9).click().as('line')
+    cy.get('.cm-line').eq(9).as('line')
+    cy.get('@line').click()
 
     const testUnfoldedState = () => {
       cy.get('.cm-gutterElement').eq(11).should('have.text', '11')
@@ -330,7 +337,8 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
     cy.interceptCompile()
 
     // put the cursor on a blank line to type in
-    cy.get('.cm-line').eq(16).click().as('line')
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
 
     cy.get('.cm-vim-panel').should('have.length', 0)
 
@@ -338,7 +346,8 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
 
     cy.get('.cm-vim-panel').should('have.length', 1)
 
-    cy.get('.cm-vim-panel input').type('w').type('{enter}')
+    cy.get('.cm-vim-panel input').type('w')
+    cy.get('.cm-vim-panel input').type('{enter}')
 
     // Compile after save
     cy.waitForCompile()
@@ -355,15 +364,16 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
       </Container>
     )
 
-    cy.get('.cm-line')
-      .eq(16)
-      .click()
-      .type(
-        '{enter}text_to_find{enter}abcde 1{enter}abcde 2{enter}abcde 3{enter}ABCDE 4{enter}'
-      )
+    cy.get('.cm-line').eq(16).as('line')
+
+    cy.get('@line').click()
+    cy.get('@line').type(
+      '{enter}text_to_find{enter}abcde 1{enter}abcde 2{enter}abcde 3{enter}ABCDE 4{enter}'
+    )
 
     // select text `text_to_find`
-    cy.get('.cm-line').eq(17).dblclick().as('lineToFind')
+    cy.get('.cm-line').eq(17).as('lineToFind')
+    cy.get('@lineToFind').dblclick()
 
     // search panel is not displayed
     cy.findByRole('search').should('have.length', 0)
@@ -382,7 +392,8 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
       // search input's value should be set to the selected text
       .should('have.value', 'text_to_find')
 
-    cy.get('@search-input').clear().type('abcde')
+    cy.get('@search-input').clear()
+    cy.get('@search-input').type('abcde')
 
     cy.findByRole('button', { name: 'next' }).as('next-btn')
     cy.findByRole('button', { name: 'previous' }).as('previous-btn')
@@ -411,7 +422,8 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
 
     // matches case
     cy.contains('Aa').click()
-    cy.get('@search-input').clear().type('ABCDE')
+    cy.get('@search-input').clear()
+    cy.get('@search-input').type('ABCDE')
     cy.get('.cm-searchMatch-selected').should('contain.text', 'ABCDE')
     cy.get('@search-input').clear()
     cy.contains('Aa').click()
@@ -434,24 +446,29 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
       .should('not.contain.text', 'abcde')
 
     // replace all
-    cy.get('@search-input').clear().type('abcde')
-    cy.get('@replace-input').clear().type('test')
+    cy.get('@search-input').clear()
+    cy.get('@search-input').type('abcde')
+    cy.get('@replace-input').clear()
+    cy.get('@replace-input').type('test')
     cy.findByRole('button', { name: /replace all/i }).click()
     cy.get('@search-input').clear()
     cy.get('@replace-input').clear()
     cy.should('not.contain.text', 'abcde')
 
     // replace all within selection
-    cy.get('@search-input').clear().type('contentLine')
+    cy.get('@search-input').clear()
+    cy.get('@search-input').type('contentLine')
     cy.get('.ol-cm-search-form-position').should('have.text', '1 of 100')
-    cy.get('.cm-line')
-      .eq(27)
-      .should('contain.text', 'contentLine 0')
-      .click()
-      .type('{shift}{downArrow}{downArrow}{downArrow}')
+
+    cy.get('.cm-line').eq(27).as('contentLine')
+    cy.get('@contentLine').should('contain.text', 'contentLine 0')
+    cy.get('@contentLine').click()
+    cy.get('@contentLine').type('{shift}{downArrow}{downArrow}{downArrow}')
+
     cy.findByLabelText('Within selection').click()
     cy.get('.ol-cm-search-form-position').should('have.text', '1 of 3')
-    cy.get('@replace-input').clear().type('contentedLine')
+    cy.get('@replace-input').clear()
+    cy.get('@replace-input').type('contentedLine')
     cy.findByRole('button', { name: /replace all/i }).click()
     cy.get('.cm-line:contains("contentedLine")').should('have.length', 3)
     cy.findByLabelText('Within selection').click()
@@ -475,7 +492,9 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
     )
 
     // Open the search panel
-    cy.get('.cm-line').eq(16).click().type(`{${metaKey}+f}`)
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
+    cy.get('@line').type(`{${metaKey}+f}`)
 
     cy.findByRole('search').within(() => {
       cy.findByLabelText('Find').as('find-input')
@@ -526,11 +545,13 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
         '@within-selection-label',
       ]) {
         // Toggle when clicked, then focus the search input
-        cy.get(option).click().should('have.class', 'checked')
+        cy.get(option).click()
+        cy.get(option).should('have.class', 'checked')
         cy.get('@find-input').should('be.focused')
 
         // Toggle when clicked again, then focus the search input
-        cy.get(option).click().should('not.have.class', 'checked')
+        cy.get(option).click()
+        cy.get(option).should('not.have.class', 'checked')
         cy.get('@find-input').should('be.focused')
       }
     })

+ 6 - 9
services/web/test/frontend/features/subscription/components/new/checkout.spec.tsx

@@ -207,9 +207,8 @@ describe('checkout panel', function () {
     cy.mount(<CheckoutPanelWithPaymentProvider />)
     cy.get('@coupon').should('have.been.calledOnce')
     cy.findByTestId('checkout-form').within(() => {
-      cy.findByLabelText(/coupon code/i)
-        .type(couponCode, { delay: 0 })
-        .blur()
+      cy.findByLabelText(/coupon code/i).type(couponCode, { delay: 0 })
+      cy.findByLabelText(/coupon code/i).blur()
     })
     cy.get('@coupon')
       .should('have.been.calledTwice')
@@ -239,9 +238,8 @@ describe('checkout panel', function () {
     })
     cy.mount(<CheckoutPanelWithPaymentProvider />)
     cy.findByTestId('checkout-form').within(() => {
-      cy.findByLabelText(/coupon code/i)
-        .type('promo_code', { delay: 0 })
-        .blur()
+      cy.findByLabelText(/coupon code/i).type('promo_code', { delay: 0 })
+      cy.findByLabelText(/coupon code/i).blur()
     })
     cy.findByRole('alert').within(() => {
       cy.contains(/coupon code is not valid for selected plan/i)
@@ -273,9 +271,8 @@ describe('checkout panel', function () {
     cy.mount(<CheckoutPanelWithPaymentProvider />)
     cy.get('@catch').should('have.been.calledOnce')
     cy.findByTestId('checkout-form').within(() => {
-      cy.findByLabelText(/coupon code/i)
-        .type('promo_code', { delay: 0 })
-        .blur()
+      cy.findByLabelText(/coupon code/i).type('promo_code', { delay: 0 })
+      cy.findByLabelText(/coupon code/i).blur()
     })
     cy.get('@catch').should('have.been.calledTwice')
     cy.findByRole('alert').within(() => {

+ 6 - 2
services/web/test/frontend/shared/hooks/use-abort-controller.test.js → services/web/test/frontend/shared/hooks/use-abort-controller.test.tsx

@@ -6,7 +6,11 @@ import useAbortController from '../../../../frontend/js/shared/hooks/use-abort-c
 import { getJSON } from '../../../../frontend/js/infrastructure/fetch-json'
 
 describe('useAbortController', function () {
-  let status
+  let status: {
+    loading: boolean
+    success: boolean | null
+    error: any | null
+  }
 
   beforeEach(function () {
     fetchMock.restore()
@@ -22,7 +26,7 @@ describe('useAbortController', function () {
     fetchMock.restore()
   })
 
-  function AbortableRequest({ url }) {
+  function AbortableRequest({ url }: { url: string }) {
     const { signal } = useAbortController()
 
     React.useEffect(() => {

+ 6 - 8
services/web/test/frontend/shared/hooks/use-resize.spec.tsx

@@ -88,10 +88,9 @@ describe('useResize', function () {
     const xPos = 400
     cy.mount(<ResizeTest />)
 
-    cy.get('#handle')
-      .trigger('mousedown', { button: 0 })
-      .trigger('mousemove', { clientX: xPos })
-      .trigger('mouseup')
+    cy.get('#handle').trigger('mousedown', { button: 0 })
+    cy.get('#handle').trigger('mousemove', { clientX: xPos })
+    cy.get('#handle').trigger('mouseup')
 
     cy.get('#target').should('have.css', 'width', `${xPos}px`)
   })
@@ -100,10 +99,9 @@ describe('useResize', function () {
     const xPos = 400
     cy.mount(<PersistedResizeTest />)
 
-    cy.get('#handle')
-      .trigger('mousedown', { button: 0 })
-      .trigger('mousemove', { clientX: xPos })
-      .trigger('mouseup')
+    cy.get('#handle').trigger('mousedown', { button: 0 })
+    cy.get('#handle').trigger('mousemove', { clientX: xPos })
+    cy.get('#handle').trigger('mouseup')
 
     cy.window()
       .its('localStorage.resizeable-test')

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů