-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Read annotation service from manifest to enable CRUD operations in markers display Co-authored-by: Chris Colvard <cjcolvar@iu.edu> Co-authored-by: Mason Ballengee <masaball@iu.edu> Co-authored-by: Jon Cameron <joncamer@iu.edu> * Clean up error handling in API requests * Work in progress prototype of add marker functionality Co-authored-by: Mason Ballengee <masaball@iu.edu> * WIP Successfully create marker, parse response, and update state/context Co-authored-by: Dananji Withana <dwithana@iu.edu> Co-authored-by: Mason Ballengee <masaball@iu.edu> * Add styling to new marker form, code re-organize * Fixed broken tests, add new tests for new components * Fix markers state management after edit/delete operations Co-authored-by: Mason Ballengee <masaball@iu.edu> Co-authored-by: Chris Colvard <chris.colvard@gmail.com> * Fix failing tests --------- Co-authored-by: dananji <dwithana@iu.edu> Co-authored-by: Mason Ballengee <masaball@iu.edu> Co-authored-by: Jon Cameron <joncamer@iu.edu>
- Loading branch information
1 parent
ea17212
commit 1f44ea6
Showing
21 changed files
with
1,175 additions
and
476 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
159 changes: 159 additions & 0 deletions
159
src/components/MarkersDisplay/MarkerUtils/CreateMarker.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { createNewAnnotation, parseMarkerAnnotation } from '@Services/playlist-parser'; | ||
import { validateTimeInput, timeToS, timeToHHmmss } from '@Services/utility-helpers'; | ||
import { SaveIcon, CancelIcon } from './SVGIcons'; | ||
|
||
const CreateMarker = ({ newMarkerEndpoint, canvasId, handleCreate, getCurrentTime }) => { | ||
const [isOpen, setIsOpen] = React.useState(false); | ||
const [isValid, setIsValid] = React.useState(false); | ||
const [saveError, setSaveError] = React.useState(false); | ||
const [errorMessage, setErrorMessage] = React.useState(''); | ||
const [markerTime, setMarkerTime] = React.useState(); | ||
|
||
const handleAddMarker = () => { | ||
const currentTime = timeToHHmmss(getCurrentTime(), true, true); | ||
validateTime(currentTime); | ||
setIsOpen(true); | ||
}; | ||
|
||
const handleCreateSubmit = (e) => { | ||
e.preventDefault(); | ||
const form = e.target; | ||
const formData = new FormData(form); | ||
const { label, time } = Object.fromEntries(formData.entries()); | ||
const annotation = { | ||
type: "Annotation", | ||
motivation: "highlighting", | ||
body: { | ||
type: "TextualBody", | ||
format: "text/html", | ||
value: label, | ||
}, | ||
target: `${canvasId}#t=${timeToS(time)}` | ||
}; | ||
|
||
const requestOptions = { | ||
method: 'POST', | ||
/** NOTE: In avalon try this option */ | ||
headers: { | ||
'Accept': 'application/json', | ||
'Avalon-Api-Key': '', | ||
}, | ||
body: JSON.stringify(annotation) | ||
}; | ||
fetch(newMarkerEndpoint, requestOptions) | ||
.then((response) => { | ||
if (response.status != 201) { | ||
throw new Error(); | ||
} else { | ||
return response.json(); | ||
} | ||
}).then((json) => { | ||
const anno = createNewAnnotation(json); | ||
const newMarker = parseMarkerAnnotation(anno); | ||
if (newMarker) { | ||
handleCreate(newMarker); | ||
} | ||
setIsOpen(false); | ||
}) | ||
.catch((e) => { | ||
console.error('Failed to create annotation; ', e); | ||
setSaveError(true); | ||
setErrorMessage('Marker creation failed.'); | ||
}); | ||
}; | ||
|
||
const handleCreateCancel = () => { | ||
setIsOpen(false); | ||
setIsValid(false); | ||
setErrorMessage(''); | ||
setSaveError(false); | ||
}; | ||
|
||
const validateTime = (value) => { | ||
setMarkerTime(value); | ||
let isValid = validateTimeInput(value); | ||
setIsValid(isValid); | ||
}; | ||
|
||
return ( | ||
<div className="ramp-markers-display__new-marker"> | ||
<button | ||
type="submit" | ||
onClick={handleAddMarker} | ||
className="ramp--markers-display__edit-button" | ||
data-testid="create-new-marker-button" | ||
>Add New Marker</button> | ||
{isOpen && | ||
(<form | ||
className="ramp--markers-display__new-marker-form" | ||
method="post" | ||
onSubmit={handleCreateSubmit} | ||
data-testid="create-new-marker-form" | ||
> | ||
<table className="create-marker-form-table"> | ||
<tbody> | ||
<tr> | ||
<td> | ||
<label htmlFor="new-marker-title">Title:</label> | ||
<input | ||
id="new-marker-title" | ||
data-testid="create-marker-title" | ||
type="text" | ||
className="ramp--markers-display__create-marker" | ||
name="label" /> | ||
</td> | ||
<td> | ||
<label htmlFor="new-marker-time">Time:</label> | ||
<input | ||
id="new-marker-time" | ||
data-testid="create-marker-timestamp" | ||
type="text" | ||
className={`ramp--markers-display__create-marker ${isValid ? 'time-valid' : 'time-invalid'}`} | ||
name="time" | ||
value={markerTime} | ||
onChange={(e) => validateTime(e.target.value)} /> | ||
</td> | ||
<td> | ||
<div className="marker-actions"> | ||
{ | ||
saveError && | ||
<p className="ramp--markers-display__error-message"> | ||
{errorMessage} | ||
</p> | ||
} | ||
<button | ||
type="submit" | ||
className="ramp--markers-display__edit-button" | ||
data-testid="edit-save-button" | ||
disabled={!isValid} | ||
> | ||
<SaveIcon /> Save | ||
</button> | ||
<button | ||
className="ramp--markers-display__edit-button-danger" | ||
data-testid="edit-cancel-button" | ||
onClick={handleCreateCancel} | ||
> | ||
<CancelIcon /> Cancel | ||
</button> | ||
</div> | ||
</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
</form>) | ||
} | ||
</div> | ||
); | ||
}; | ||
|
||
CreateMarker.propTypes = { | ||
newMarkerEndpoint: PropTypes.string.isRequired, | ||
canvasId: PropTypes.string, | ||
handleCreate: PropTypes.func.isRequired, | ||
getCurrentTime: PropTypes.func.isRequired, | ||
}; | ||
|
||
export default CreateMarker; |
91 changes: 91 additions & 0 deletions
91
src/components/MarkersDisplay/MarkerUtils/CreateMarker.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import React from 'react'; | ||
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'; | ||
import CreateMarker from './CreateMarker'; | ||
|
||
describe('CreateMarker component', () => { | ||
const handleCreateMock = jest.fn(); | ||
const getCurrentTimeMock = jest.fn(() => { return 44.3; }); | ||
beforeEach(() => { | ||
render(<CreateMarker | ||
newMarkerEndpoint={'http://example.com/marker'} | ||
canvasId={'http://example.com/manifest/canvas/1'} | ||
handleCreate={handleCreateMock} | ||
getCurrentTime={getCurrentTimeMock} />); | ||
}); | ||
|
||
test('renders successfully', () => { | ||
expect(screen.queryByTestId('create-new-marker-button')).toBeInTheDocument(); | ||
expect(screen.queryByTestId('create-new-marker-form')).not.toBeInTheDocument(); | ||
}); | ||
|
||
test('add new marker button click opens form', () => { | ||
fireEvent.click(screen.getByTestId('create-new-marker-button')); | ||
expect(screen.queryByTestId('create-new-marker-form')).toBeInTheDocument(); | ||
expect(screen.getByTestId('create-marker-title')).toBeInTheDocument(); | ||
expect(screen.getByTestId('create-marker-timestamp')).toBeInTheDocument(); | ||
}); | ||
|
||
test('form opens with empty title and current time of playhead', () => { | ||
fireEvent.click(screen.getByTestId('create-new-marker-button')); | ||
waitFor(() => { | ||
expect(getCurrentTimeMock).toHaveBeenCalledTimes(1); | ||
expect(screen.getByTestId('create-marker-title')).toHaveTextContent(''); | ||
expect(screen.getByTestId('create-marker-timestamp')).toHaveTextContent('00:00:44.300'); | ||
expect(screen.getByTestId('create-marker-timestamp')).toHaveClass('time-valid'); | ||
}); | ||
}); | ||
|
||
test('validates time input and enable/disable save button', () => { | ||
fireEvent.click(screen.getByTestId('create-new-marker-button')); | ||
fireEvent.change(screen.getByTestId('create-marker-timestamp'), { target: { value: '00' } }); | ||
expect(screen.getByTestId('create-marker-timestamp')).toHaveClass('time-invalid'); | ||
expect(screen.getByTestId('edit-save-button')).toBeDisabled(); | ||
|
||
fireEvent.change(screen.getByTestId('create-marker-timestamp'), { target: { value: '00:00' } }); | ||
expect(screen.getByTestId('create-marker-timestamp')).toHaveClass('time-valid'); | ||
expect(screen.getByTestId('edit-save-button')).not.toBeDisabled(); | ||
|
||
fireEvent.change(screen.getByTestId('create-marker-timestamp'), { target: { value: '00:00:' } }); | ||
expect(screen.getByTestId('create-marker-timestamp')).toHaveClass('time-invalid'); | ||
expect(screen.getByTestId('edit-save-button')).toBeDisabled(); | ||
|
||
fireEvent.change(screen.getByTestId('create-marker-timestamp'), { target: { value: '00:00:32.' } }); | ||
expect(screen.getByTestId('create-marker-timestamp')).toHaveClass('time-invalid'); | ||
expect(screen.getByTestId('edit-save-button')).toBeDisabled(); | ||
|
||
fireEvent.change(screen.getByTestId('create-marker-timestamp'), { target: { value: '00:00:32.543' } }); | ||
expect(screen.getByTestId('create-marker-timestamp')).toHaveClass('time-valid'); | ||
expect(screen.getByTestId('edit-save-button')).not.toBeDisabled(); | ||
}); | ||
|
||
test('saves marker on save button click', async () => { | ||
const fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValueOnce({ | ||
status: 201, | ||
json: jest.fn(() => { | ||
return { | ||
"@context": "http://www.w3.org/ns/anno.jsonld", | ||
"id": "http://example.com/marker/1", | ||
"type": "Annotation", | ||
"motivation": "highlighting", | ||
"body": { | ||
"type": "TextualBody", | ||
"value": "Test Marker" | ||
}, | ||
"target": "http://example.com/manifest/canvas/1#t=44.3" | ||
}; | ||
}) | ||
}); | ||
|
||
fireEvent.click(screen.getByTestId('create-new-marker-button')); | ||
fireEvent.change(screen.getByTestId('create-marker-title'), { target: { value: 'Test Marker' } }); | ||
|
||
expect(screen.getByTestId('create-marker-timestamp')).toHaveClass('time-valid'); | ||
expect(screen.getByTestId('edit-save-button')).not.toBeDisabled(); | ||
|
||
fireEvent.click(screen.getByTestId('edit-save-button')); | ||
await waitFor(() => { | ||
expect(fetchSpy).toHaveBeenCalledTimes(1); | ||
expect(handleCreateMock).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.