Improve stability and speed of E2E tests (#1319)

* Change cypress settings in CI job
* Catch error from highlight js chunk loading in auto completion
* Refactor code
* Show notification if highlightjs loading failed

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2021-06-13 23:02:39 +02:00 committed by GitHub
parent a4d6de9555
commit 93722f4161
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 132 additions and 50 deletions

View file

@ -18,16 +18,16 @@ jobs:
- name: Check out repo
uses: actions/checkout@v2
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache build
uses: actions/cache@v2.1.6
with:
path: build
key: build
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache node_modules
uses: actions/cache@v2
id: yarn-cache
@ -57,6 +57,9 @@ jobs:
name: Perform E2E Test in ${{ matrix.browser }}
needs: build-frontend
runs-on: ubuntu-latest
container:
image: cypress/browsers:node14.16.0-chrome90-ff88
options: --user 1001 --shm-size=2g
strategy:
matrix:
browser: [ 'chrome', 'firefox' ]

View file

@ -461,6 +461,7 @@
"successfullyCopied": "Copied!",
"copyError": "Error while copying!",
"errorOccurred": "An error occurred",
"errorWhileLoadingLibrary": "An unexpected error occurred while loading '{{name}}'.\nCheck the browser console for more information.\nReport this error only if it comes up again.",
"readForMoreInfo": "Read here for more information"
},
"login": {

View file

@ -5,45 +5,111 @@
*/
import { Editor, Hint, Hints, Pos } from 'codemirror'
import { findWordAtCursor, Hinter, search } from './index'
import { findWordAtCursor, generateHintListByPrefix, Hinter } from './index'
import { DEFAULT_DURATION_IN_SECONDS, dispatchUiNotification } from '../../../../redux/ui-notifications/methods'
import i18n from 'i18next'
type highlightJsImport = typeof import('../../../common/hljs/hljs')
const wordRegExp = /^```((\w|-|_|\+)*)$/
let allSupportedLanguages: string[] = []
const codeBlockHint = (editor: Editor): Promise<Hints | null> => {
return import(/* webpackChunkName: "highlight.js" */ '../../../common/hljs/hljs').then(
(hljs) =>
new Promise((resolve) => {
/**
* Fetches the highlight js chunk.
* @return the retrieved highlight js api
*/
const loadHighlightJs = async (): Promise<highlightJsImport | null> => {
try {
return await import('../../../common/hljs/hljs')
} catch (error) {
dispatchUiNotification(
i18n.t('common.errorOccurred'),
i18n.t('common.errorWhileLoadingLibrary', { name: 'highlight.js' }),
DEFAULT_DURATION_IN_SECONDS,
'exclamation-circle'
)
console.error("can't load highlight js", error)
return null
}
}
/**
* Extracts the language from the current line in the editor.
*
* @param editor The editor that contains the search time
* @return null if no search term could be found or the found word and the cursor position.
*/
const extractSearchTerm = (
editor: Editor
): null | {
searchTerm: string
startIndex: number
endIndex: number
} => {
const searchTerm = findWordAtCursor(editor)
const searchResult = wordRegExp.exec(searchTerm.text)
if (searchResult === null) {
resolve(null)
return
return null
}
const term = searchResult[1]
return {
searchTerm: searchResult[1],
startIndex: searchTerm.start,
endIndex: searchTerm.end
}
}
/**
* Builds the list of languages that are supported by highlight js or custom embeddings.
* @return An array of language names
*/
const buildLanguageList = async (): Promise<string[]> => {
const highlightJs = await loadHighlightJs()
if (highlightJs === null) {
return []
}
if (allSupportedLanguages.length === 0) {
allSupportedLanguages = hljs.default
allSupportedLanguages = highlightJs.default
.listLanguages()
.concat('csv', 'flow', 'html', 'js', 'markmap', 'abc', 'graphviz', 'mermaid', 'vega-lite')
}
const suggestions = search(term, allSupportedLanguages)
const cursor = editor.getCursor()
return allSupportedLanguages
}
/**
* Creates a codemirror autocompletion hint with supported highlight js languages.
*
* @param editor The codemirror editor that requested the autocompletion
* @return The generated {@link Hints} or null if no hints exist.
*/
const codeBlockHint = async (editor: Editor): Promise<Hints | null> => {
const searchResult = extractSearchTerm(editor)
if (!searchResult) {
return null
}
const languages = await buildLanguageList()
if (languages.length === 0) {
return null
}
const suggestions = generateHintListByPrefix(searchResult.searchTerm, languages)
if (!suggestions) {
resolve(null)
} else {
resolve({
return null
}
const lineIndex = editor.getCursor().line
return {
list: suggestions.map(
(suggestion: string): Hint => ({
text: '```' + suggestion + '\n\n```\n',
displayText: suggestion
})
),
from: Pos(cursor.line, searchTerm.start),
to: Pos(cursor.line, searchTerm.end)
})
from: Pos(lineIndex, searchResult.startIndex),
to: Pos(lineIndex, searchResult.endIndex)
}
})
)
}
export const CodeBlockHinter: Hinter = {

View file

@ -5,7 +5,7 @@
*/
import { Editor, Hint, Hints, Pos } from 'codemirror'
import { findWordAtCursor, Hinter, search } from './index'
import { findWordAtCursor, generateHintListByPrefix, Hinter } from './index'
const wordRegExp = /^(\s{0,3})(#{1,6})$/
const allSupportedHeaders = ['# h1', '## h2', '### h3', '#### h4', '##### h5', '###### h6', '###### tags: `example`']
@ -24,7 +24,7 @@ const headerHint = (editor: Editor): Promise<Hints | null> => {
resolve(null)
return
}
const suggestions = search(term, allSupportedHeaders)
const suggestions = generateHintListByPrefix(term, allSupportedHeaders)
const cursor = editor.getCursor()
if (!suggestions) {
resolve(null)

View file

@ -46,14 +46,15 @@ export const findWordAtCursor = (editor: Editor): findWordAtCursorResponse => {
}
}
export const search = (term: string, list: string[]): string[] => {
const suggestions: string[] = []
list.forEach((item) => {
if (item.toLowerCase().startsWith(term.toLowerCase())) {
suggestions.push(item)
}
})
return suggestions.slice(0, 7)
/**
* Generates a list (with max 8 entries) of hints for the autocompletion.
*
* @param prefix This is the case insensitive prefix that every hint must have
* @param hintCandidates The list of hint candidates
*/
export const generateHintListByPrefix = (prefix: string, hintCandidates: string[]): string[] => {
const searchTerm = prefix.toLowerCase()
return hintCandidates.filter((item) => item.toLowerCase().startsWith(searchTerm)).slice(0, 7)
}
export const allHinters: Hinter[] = [

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import React, { Fragment, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { Button, ProgressBar, Toast } from 'react-bootstrap'
import { UiNotification } from '../../redux/ui-notifications/types'
import { ForkAwesomeIcon } from '../common/fork-awesome/fork-awesome-icon'
@ -88,6 +88,17 @@ export const UiNotificationToast: React.FC<UiNotificationProps> = ({
[buttons, dismissThisNotification]
)
const contentDom = useMemo(() => {
return content.split('\n').map((value) => {
return (
<Fragment>
{value}
<br />
</Fragment>
)
})
}, [content])
return (
<Toast show={!dismissed && eta !== undefined} onClose={dismissThisNotification}>
<Toast.Header>
@ -99,7 +110,7 @@ export const UiNotificationToast: React.FC<UiNotificationProps> = ({
</strong>
<small>{date.toRelative({ style: 'short' })}</small>
</Toast.Header>
<Toast.Body>{content}</Toast.Body>
<Toast.Body>{contentDom}</Toast.Body>
<ProgressBar variant={'info'} now={eta} max={durationInSecond * STEPS_PER_SECOND} min={0} />
<div>{buttonsDom}</div>
</Toast>