diff --git a/src/fragmentarium/ui/edition/TransliterationForm.test.tsx b/src/fragmentarium/ui/edition/TransliterationForm.test.tsx
index 82a1f5f13..b781a2edd 100644
--- a/src/fragmentarium/ui/edition/TransliterationForm.test.tsx
+++ b/src/fragmentarium/ui/edition/TransliterationForm.test.tsx
@@ -1,31 +1,52 @@
import React from 'react'
-import { render, screen } from '@testing-library/react'
-import { changeValueByLabel, submitFormByTestId } from 'test-support/utils'
+import { fireEvent, render, screen } from '@testing-library/react'
+import { submitFormByTestId } from 'test-support/utils'
import { Promise } from 'bluebird'
import TransliterationForm from './TransliterationForm'
+import { act } from 'react-dom/test-utils'
+import userEvent from '@testing-library/user-event'
const transliteration = 'line1\nline2'
const notes = 'notes'
const introduction = 'introduction'
+let addEventListenerSpy
let updateEdition
-beforeEach(() => {
+beforeEach(async () => {
+ jest.restoreAllMocks()
+ addEventListenerSpy = jest.spyOn(window, 'addEventListener')
updateEdition = jest.fn()
updateEdition.mockReturnValue(Promise.resolve())
- render(
-
- )
+ act(() => {
+ render(
+
+ )
+ })
})
-test('Submitting the form calls updateEdition', () => {
+it('Updates transliteration on change', async () => {
+ const newTransliteration = 'line1\nline2\nnew line'
+ const transliterationEditor = screen.getAllByRole('textbox')[0]
+ fireEvent.click(transliterationEditor)
+ await userEvent.click(transliterationEditor)
+ await userEvent.paste(transliterationEditor, newTransliteration)
+ act(() => {
+ fireEvent.change(transliterationEditor, {
+ target: { value: newTransliteration },
+ })
+ })
+ expect(transliterationEditor).toHaveValue(newTransliteration)
+})
+
+it('Submitting the form calls updateEdition', () => {
submitFormByTestId(screen, 'transliteration-form')
expect(updateEdition).toHaveBeenCalledWith(
transliteration,
@@ -34,18 +55,31 @@ test('Submitting the form calls updateEdition', () => {
)
})
-xit('Updates transliteration on change', () => {
+it('Displays warning before closing when unsaved', async () => {
const newTransliteration = 'line1\nline2\nnew line'
- changeValueByLabel(screen, 'Transliteration', newTransliteration)
-
- expect(screen.getByLabelText('Transliteration')).toHaveValue(
- newTransliteration
+ window.confirm = jest.fn(() => true)
+ const beforeUnloadEvent = new Event('beforeunload', { cancelable: true })
+ const transliterationEditor = screen.getAllByRole('textbox')[0]
+ fireEvent.click(transliterationEditor)
+ await userEvent.click(transliterationEditor)
+ await userEvent.paste(transliterationEditor, newTransliteration)
+ act(() => {
+ fireEvent.change(transliterationEditor, {
+ target: { value: newTransliteration },
+ })
+ })
+ expect(transliterationEditor).toHaveValue(newTransliteration)
+ window.dispatchEvent(beforeUnloadEvent)
+ expect(addEventListenerSpy).toHaveBeenCalledWith(
+ 'beforeunload',
+ expect.any(Function)
+ )
+ const mockEvent = { returnValue: '' }
+ const beforeUnloadHandler = addEventListenerSpy.mock.calls.find(
+ (call) => call[0] === 'beforeunload'
+ )[1]
+ beforeUnloadHandler(mockEvent)
+ expect(mockEvent.returnValue).toBe(
+ 'You have unsaved changes. Are you sure you want to leave?'
)
-})
-
-xit('Updates introduction on change', () => {
- const newIntroduction = 'Introduction\n\nintroduction continued'
- changeValueByLabel(screen, 'Introduction', newIntroduction)
-
- expect(screen.getByLabelText('Introduction')).toHaveValue(newIntroduction)
})
diff --git a/src/fragmentarium/ui/edition/TransliterationForm.tsx b/src/fragmentarium/ui/edition/TransliterationForm.tsx
index cc80c68c1..441c480d4 100644
--- a/src/fragmentarium/ui/edition/TransliterationForm.tsx
+++ b/src/fragmentarium/ui/edition/TransliterationForm.tsx
@@ -1,4 +1,4 @@
-import React, { Component, FormEvent } from 'react'
+import React, { useState, useEffect, FormEvent, useCallback } from 'react'
import {
FormGroup,
FormLabel,
@@ -25,159 +25,221 @@ type Props = {
notes: string,
introduction: string
) => Promise
- disabled: boolean
+ disabled?: boolean
}
-type State = {
+
+type FormData = {
transliteration: string
notes: string
introduction: string
error: Error | null
- disabled: boolean
+ disabled?: boolean
}
-class TransliterationForm extends Component {
- static readonly defaultProps = {
- disabled: false,
- }
- private readonly formId: string
- private updatePromise: Promise
-
- constructor(props: Props) {
- super(props)
- this.formId = _.uniqueId('TransliterationForm-')
- this.state = {
- transliteration: this.props.transliteration,
- notes: this.props.notes,
- introduction: this.props.introduction,
- error: null,
- disabled: false,
- }
- this.updatePromise = Promise.resolve()
+const handleBeforeUnload = (
+ event: BeforeUnloadEvent,
+ hasChanges: () => boolean
+): string | void => {
+ if (hasChanges()) {
+ const confirmationMessage =
+ 'You have unsaved changes. Are you sure you want to leave?'
+ event.returnValue = confirmationMessage
+ return confirmationMessage
}
+}
- componentWillUnmount(): void {
- this.updatePromise.cancel()
+const runBeforeUnloadEvent = ({
+ hasChanges,
+ updatePromise,
+}: {
+ hasChanges: () => boolean
+ updatePromise: Promise
+}) => {
+ const _handleBeforeEvent = (event) => handleBeforeUnload(event, hasChanges)
+ if (hasChanges()) {
+ window.addEventListener('beforeunload', _handleBeforeEvent)
+ } else {
+ window.removeEventListener('beforeunload', _handleBeforeEvent)
}
-
- get hasChanges(): boolean {
- const transliterationChanged =
- this.state.transliteration !== this.props.transliteration
- const notesChanged = this.state.notes !== this.props.notes
- const introductionChanged =
- this.state.introduction !== this.props.introduction
- return transliterationChanged || notesChanged || introductionChanged
+ return () => {
+ window.removeEventListener('beforeunload', _handleBeforeEvent)
+ updatePromise.cancel()
}
+}
- update = (property: string) => (value: string): void => {
- this.setState({
- ...this.state,
+const SubmitButton = ({
+ propsDisabled,
+ hasChanges,
+ formId,
+}: {
+ propsDisabled?: boolean
+ hasChanges: boolean
+ formId: string
+}) => (
+
+)
+
+const getFormGroup = ({
+ name,
+ key,
+ value,
+ formId,
+ propsDisabled,
+ update,
+ formData,
+}: {
+ name: 'transliteration' | 'notes' | 'introduction'
+ key: number
+ value: string
+ formId: string
+ propsDisabled?: boolean
+ update: (property: keyof FormData) => (value: string) => void
+ formData: FormData
+}): JSX.Element => {
+ return (
+
+ {_.capitalize(name)}{' '}
+ {name === 'transliteration' && }
+
+
+ )
+}
+
+const fields: Array<'transliteration' | 'notes' | 'introduction'> = [
+ 'transliteration',
+ 'notes',
+ 'introduction',
+]
+
+const TransliterationForm: React.FC = ({
+ transliteration,
+ notes,
+ introduction,
+ updateEdition,
+ disabled: propsDisabled,
+}): JSX.Element => {
+ const formId = _.uniqueId('TransliterationForm-')
+ const [formData, setFormData] = useState({
+ transliteration,
+ notes,
+ introduction,
+ error: null,
+ disabled: false,
+ })
+ const [updatePromise, setUpdatePromise] = useState(Promise.resolve())
+ const update = (property: keyof FormData) => (value: string) => {
+ setFormData({
+ ...formData,
[property]: value,
})
}
- onTemplate = (template: string): void => {
- this.setState({
- ...this.state,
+ const onTemplate = (template: string) => {
+ setFormData({
+ ...formData,
transliteration: template,
})
}
- submit = (event: FormEvent): void => {
+ const submit = (event: FormEvent) => {
event.preventDefault()
- this.setState({
- ...this.state,
- error: null,
- })
- this.updatePromise = this.props
- .updateEdition(
- this.state.transliteration,
- this.state.notes,
- this.state.introduction
- )
+ setFormData({ ...formData, error: null })
+ const promise = updateEdition(
+ formData.transliteration,
+ formData.notes,
+ formData.introduction
+ )
.then((fragment) => {
- this.setState({
- ...this.state,
+ setFormData({
+ ...formData,
transliteration: fragment.atf,
notes: fragment.notes.text,
introduction: fragment.introduction.text,
})
})
- .catch((error) =>
- this.setState({
- ...this.state,
- error: error,
- })
- )
+ .catch((error) => {
+ setFormData({ ...formData, error })
+ })
+ setUpdatePromise(promise)
}
- SubmitButton = (): JSX.Element => (
-
+ const hasChanges = useCallback(
+ (): boolean =>
+ formData.transliteration !== transliteration ||
+ formData.notes !== notes ||
+ formData.introduction !== introduction,
+ [formData, transliteration, notes, introduction]
)
- render(): JSX.Element {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
- }
+ useEffect(() => {
+ return runBeforeUnloadEvent({ hasChanges, updatePromise })
+ }, [
+ formData,
+ transliteration,
+ notes,
+ introduction,
+ updatePromise,
+ hasChanges,
+ ])
+
+ const formGroups = fields.map(
+ (name, key: number): JSX.Element =>
+ getFormGroup({
+ name,
+ key,
+ value: formData[name],
+ formId,
+ propsDisabled,
+ update,
+ formData,
+ })
+ )
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
}
export default TransliterationForm