Skip to content

Commit

Permalink
Merge pull request #3276 from serlo/tests/fill-in-the-gaps
Browse files Browse the repository at this point in the history
E2E tests for fill in the gaps
  • Loading branch information
hejtful authored Jan 22, 2024
2 parents 6268c26 + 7151517 commit 82283a8
Show file tree
Hide file tree
Showing 19 changed files with 241 additions and 32 deletions.
Binary file not shown.
Binary file not shown.
11 changes: 4 additions & 7 deletions e2e-tests/codecept.config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
const useLocalAPI = process.env.FRONTEND_API == 'local'
import config from './config'

const { isCI, browser, frontendUrl } = config

const useLocalAPI = process.env.FRONTEND_API == 'local'
export const adminUser = useLocalAPI ? 'admin' : 'Kulla'
const isCI = Boolean(process.env.CI)
const browser = process.env.BROWSER ?? 'chromium'
const frontendUrl =
process.env.FRONTEND_URL ?? useLocalAPI
? 'http://localhost:3000'
: 'https://de.serlo-staging.dev'

const isChromium = browser === 'chromium'

Expand Down
1 change: 0 additions & 1 deletion e2e-tests/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ function createConfig() {
const useLocalAPI = process.env.FRONTEND_API == 'local'

return {
adminUser: useLocalAPI ? 'admin' : 'Kulla',
isCI: Boolean(process.env.CI),
browser: process.env.BROWSER ?? 'chromium',
frontendUrl:
Expand Down
193 changes: 193 additions & 0 deletions e2e-tests/tests/450-blank-exercise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import assert from 'assert'
import { popupWarningFix } from './helpers/popup-warning-fix'

Feature('Serlo Editor - Blank exercise')

Before(popupWarningFix)

const FillInTheBlanksExerciseButton = '$add-exercise-blanksExercise'

const initialTextPluginCount = 1

Scenario('Create and remove fill in the gap exercise', async ({ I }) => {
I.amOnPage('/entity/create/Exercise/23869')

I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount)
I.click(FillInTheBlanksExerciseButton)
I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount + 1)

I.click(locate('$plugin-text-editor').last())

I.type('This is a test')

I.click('$additional-toolbar-controls')
I.click('$remove-plugin-button')
I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount)
})

Scenario(
'Create and remove fill in the gap exercise via undo',
async ({ I }) => {
I.amOnPage('/entity/create/Exercise/23869')

I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount)
I.click(FillInTheBlanksExerciseButton)
I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount + 1)

I.pressKey(['CommandOrControl', 'Z'])

I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount)
}
)

Scenario('Create and remove gaps through toolbar', async ({ I }) => {
I.amOnPage('/entity/create/Exercise/23869')

I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount)
I.click(FillInTheBlanksExerciseButton)
I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount + 1)

I.click(locate('$plugin-text-editor').last())
I.type('This is a test with one gap')

I.say('Select last word with keyboard and create gap')
I.pressKey(['CommandOrControl', 'Shift', 'ArrowLeft'])

I.seeElement('$plugin-toolbar-button-lücke-erstellen')
I.dontSeeElement('$plugin-toolbar-button-lücke-entfernen')
I.click('$plugin-toolbar-button-lücke-erstellen')
I.seeElement('$plugin-toolbar-button-lücke-entfernen')
I.dontSeeElement('$plugin-toolbar-button-lücke-erstellen')

I.seeNumberOfElements('$blank-input', 1)
I.click('$plugin-toolbar-button-lücke-entfernen')
I.dontSeeElement('$plugin-toolbar-button-lücke-entfernen')
I.dontSeeElement('$blank-input')
})

Scenario('Create a blank gap and type in it', async ({ I }) => {
I.amOnPage('/entity/create/Exercise/23869')

I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount)
I.click(FillInTheBlanksExerciseButton)
I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount + 1)

I.click(locate('$plugin-text-editor').last())
I.say('Create an empty gap then type in it')

I.type('No gap here ')
I.click('$plugin-toolbar-button-lücke-erstellen')
I.seeNumberOfElements('$blank-input', 1)
const GapContent = 'gap content'
I.type(GapContent)
I.seeInField('$blank-input', GapContent)
})

Scenario('Create and delete gaps with backspace/del', async ({ I }) => {
I.amOnPage('/entity/create/Exercise/23869')

I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount)
I.click(FillInTheBlanksExerciseButton)
I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount + 1)

I.click(locate('$plugin-text-editor').last())
I.type('No gap here ')

I.say('Create a gap, then delete it with backspace')
I.click('$plugin-toolbar-button-lücke-erstellen')
I.seeNumberOfElements('$blank-input', 1)
I.pressKey('Backspace')
I.dontSeeElement('$blank-input')

I.say('Create a gap, then delete it with del')
I.click('$plugin-toolbar-button-lücke-erstellen')
I.seeNumberOfElements('$blank-input', 1)
I.pressKey('Delete')
I.dontSeeElement('$blank-input')
})

Scenario.todo(
'Ensure an added gap before any text gets focused',
async ({ I }) => {
I.amOnPage('/entity/create/Exercise/23869')

I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount)
I.click(FillInTheBlanksExerciseButton)
I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount + 1)

I.click(locate('$plugin-text-editor').last())

I.click('$plugin-toolbar-button-lücke-erstellen')
I.seeNumberOfElements('$blank-input', 1)
const isBlankInputFocused = await I.executeScript(() => {
const blankInput = document.querySelector("[data-qa='blank-input']")
return document.activeElement === blankInput
})

assert.strictEqual(
isBlankInputFocused,
true,
'The blank input element is not focused'
)
}
)

Scenario(
'Create a few gaps, go to preview mode and solve them!',
async ({ I }) => {
I.amOnPage('/entity/create/Exercise/23869')

I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount)
I.click(FillInTheBlanksExerciseButton)
I.seeNumberOfElements('$plugin-text-editor', initialTextPluginCount + 1)

I.click(locate('$plugin-text-editor').last())
I.type('No gap here ')

I.say('Create two gaps')
I.click('$plugin-toolbar-button-lücke-erstellen')
I.seeNumberOfElements('$blank-input', 1)
I.type('first')

// unfocus gap
I.pressKey('ArrowRight')
// add normal text with surrounding space
I.type(' and ')

I.click('$plugin-toolbar-button-lücke-erstellen')
I.type('second')
I.seeNumberOfElements('$blank-input', 2)

I.say('Change mode to preview and solve them incorrectly')
I.click('$plugin-blanks-exercise-preview-button')
I.seeNumberOfElements('$blank-input', 2)
I.click(locate('$blank-input').first())
// Adding the second gap solution to the first gap
I.type('second')

// The button to check answers should only be visible once all gaps have
// inputs
I.dontSeeElement('$plugin-exercise-check-answer-button')

I.click(locate('$blank-input').last())
// Adding the first gap solution to the second gap
I.type('first')
I.seeElement('$plugin-exercise-check-answer-button')
I.click('$plugin-exercise-check-answer-button')
I.seeElement('$plugin-exercise-feedback-incorrect')

I.say('We now edit the gaps and solve them correctly')
// Double click to highlight and overwrite the existing input
I.doubleClick(locate('$blank-input').first())
I.type('first')

// Double click to highlight and overwrite the existing input
I.doubleClick(locate('$blank-input').last())
I.type('second')

I.click('$plugin-exercise-check-answer-button')
I.seeElement('$plugin-exercise-feedback-correct')
}
)

Scenario.todo('Tests for drag & drop mode')
3 changes: 3 additions & 0 deletions packages/editor/src/editor-ui/exercises/add-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ interface AddButtonProps {
children: string
title?: string
secondary?: boolean
dataQa?: string
}

export function AddButton({
title,
onClick,
children,
secondary,
dataQa,
}: AddButtonProps) {
return (
<button
Expand All @@ -25,6 +27,7 @@ export function AddButton({
: 'serlo-button-editor-primary',
'mr-2'
)}
data-qa={dataQa}
>
<FaIcon icon={faPlus} /> {children}
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ export function ExerciseFeedback(props: SolutionFeedbackProps) {

return (
<div className="ml-3 mt-1 flex text-lg animate-in fade-in">
<span className="-mt-1 mr-0.5 text-2xl motion-safe:animate-in motion-safe:zoom-in">
<span
className="-mt-1 mr-0.5 text-2xl motion-safe:animate-in motion-safe:zoom-in"
data-qa={`plugin-exercise-feedback-${
correct ? 'correct' : 'incorrect'
}`}
>
{correct ? '🎉' : '✋'}
</span>{' '}
<div className="serlo-p mb-0 ml-1">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function PluginToolMenu({ pluginControls }: PluginToolMenuProps) {
<Root>
<List>
<Item>
<Trigger>
<Trigger data-qa="additional-toolbar-controls">
<FaIcon className="mx-2 px-2" icon={faEllipsis} />
</Trigger>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { Fragment, useState } from 'react'
import { Editor as SlateEditor } from 'slate'

import { PluginToolbarTextControlButton } from './plugin-toolbar-text-control-button'
import type { NestedControlButton, ControlButton } from './types'
import {
type NestedControlButton,
type ControlButton,
TextEditorFormattingOption,
} from './types'
import { FaIcon } from '@/components/fa-icon'

export interface PluginToolbarTextControlsProps {
Expand All @@ -24,13 +28,16 @@ export function PluginToolbarTextControls({
const [subMenu, setSubMenu] = useState<number>()

const isMath = (control: ControlButton) =>
Object.hasOwn(control, 'name') && control.name === 'math'
Object.hasOwn(control, 'name') &&
control.name === TextEditorFormattingOption.math

const isBlank = (control: ControlButton) =>
Object.hasOwn(control, 'name') && control.name === 'textBlank'
Object.hasOwn(control, 'name') &&
control.name === TextEditorFormattingOption.textBlank

const mathActive = controls.find(isMath)?.isActive(editor)
const blankActive = controls.find(isBlank)?.isActive(editor)
const hasFillInTheBlanks = controls.find(isBlank)
const blankActive = hasFillInTheBlanks?.isActive(editor)
const isSpecialMode = mathActive || blankActive

if (typeof subMenu !== 'number') {
Expand Down
1 change: 1 addition & 0 deletions packages/editor/src/plugins/exercise/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export function ExerciseEditor(props: ExerciseProps) {
key={type}
onClick={() => interactive.create({ plugin: type })}
secondary
dataQa={`add-exercise-${type}`}
>
{exTemplateStrings[type]}
</AddButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { removeBlanks } from '@editor/editor-ui/plugin-toolbar/text-controls/uti
import {
ChangeEvent,
KeyboardEvent as ReactKeyboardEvent,
createRef,
useRef,
useContext,
useEffect,
} from 'react'
Expand All @@ -23,17 +23,11 @@ export function BlankRenderer({ element }: BlankRendererProps) {
const focused = useFocused()

// Autofocus when adding and removing a blank
const inputRef = createRef<HTMLInputElement>()
const inputRef = useRef<HTMLInputElement | null>(null)
useEffect(() => {
// Focus input when the blank is added
setTimeout(() => inputRef.current?.focus())

// Editor gets refocused when the blank is removed from within
// text-controls/utils/blank.ts as it leads to slate errors on unmount.

// Only run on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
}, [inputRef])

// Focus input when the blank is selected using arrow keys
// + set cursor at the start if entering using right arrow key
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { ExerciseFeedback } from '@editor/editor-ui/exercises/exercise-feedback'
import { useInstanceData } from '@serlo/frontend/src/contexts/instance-context'

import { cn } from '@/helper/cn'

interface BlankCheckButtonProps {
isVisible: boolean
feedback: Map<string, { isCorrect?: boolean | undefined }>
Expand All @@ -15,16 +13,17 @@ export function BlankCheckButton(props: BlankCheckButtonProps) {

const exercisesStrings = useInstanceData().strings.content.exercises

const className = cn(
'serlo-button-blue mr-3 h-8',
isVisible ? '' : 'pointer-events-none opacity-0'
)
if (!isVisible) return null

const isCorrect = [...feedback].every((entry) => entry[1].isCorrect)

return (
<div className="mt-2 flex">
<button className={className} onClick={onClick}>
<button
className="serlo-button-blue mr-3 h-8"
onClick={onClick}
data-qa="plugin-exercise-check-answer-button"
>
{exercisesStrings.check}
</button>
{isFeedbackVisible ? <ExerciseFeedback correct={isCorrect} /> : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export function BlankDraggableArea(props: BlankDraggableAreaProps) {
'mt-5 flex min-h-8 w-full items-stretch rounded-full bg-slate-100',
isOver ? 'bg-slate-200' : ''
)}
data-qa="blank-solution-area"
ref={dropRef}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const BlankRendererInput = forwardRef<
isAnswerCorrect && 'border-green-500',
isAnswerCorrect === false && 'border-red-500'
)}
data-qa="blank-input"
size={4}
spellCheck={false}
autoCorrect="off"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const FillInTheBlanksToolbar = ({
<button
onClick={() => setPreviewActive(!previewActive)}
className="serlo-tooltip-trigger mr-2 rounded-md border border-gray-500 px-1 text-sm transition-all hover:bg-editor-primary-200 focus-visible:bg-editor-primary-200"
data-qa="plugin-blanks-exercise-preview-button"
>
<EditorTooltip
text={
Expand All @@ -45,6 +46,7 @@ export const FillInTheBlanksToolbar = ({
<ToolbarSelect
tooltipText={blanksExerciseStrings.chooseType}
value={state.mode.value}
dataQa="plugin-blanks-mode-switch"
changeValue={(value) => state.mode.set(value)}
options={[
{ value: 'typing', text: blanksExerciseStrings.modes.typing },
Expand Down
Loading

0 comments on commit 82283a8

Please sign in to comment.