diff --git a/cypress/e2e/notes-100/comment-edit.cy.js b/cypress/e2e/notes-100/comment-edit.cy.js new file mode 100644 index 000000000..94c5954c3 --- /dev/null +++ b/cypress/e2e/notes-100/comment-edit.cy.js @@ -0,0 +1,41 @@ +import '@percy/cypress' +import { + homepageSetup, + returningUserVisitsHomepageWaitForModel, + auth0Login, +} from '../../support/utils' + +/** {@link https://github.com/bldrs-ai/Share/issues/1186} */ +describe('Notes 100: Comment edit', () => { + beforeEach(homepageSetup) + context('Returning user visits homepage', () => { + beforeEach(returningUserVisitsHomepageWaitForModel) + context('Open Notes > First note --', () => { + beforeEach(() => { + cy.get('[data-testid="control-button-notes"]').click() + cy.get('[data-testid="list-notes"] :nth-child(1) > [data-testid="note-body"]').first().click() + auth0Login() + }) + it('Edit button should be visible', () => { + cy.get(`[data-testid="list-notes"] > :nth-child(3) + > [data-testid="note-card"] > .MuiCardActions-root > .MuiBox-root > [data-testid="editComment"]`) + cy.percySnapshot() + }) + it('Comment switches to edit mode', () => { + cy.get(`[data-testid="list-notes"] > :nth-child(3) + > [data-testid="note-card"] > .MuiCardActions-root > .MuiBox-root > [data-testid="editComment"]`).click() + cy.get('[data-testid="Save"]') + cy.percySnapshot() + }) + it('Comment displays updated body', () => { + cy.get(`[data-testid="list-notes"] > :nth-child(3) + > [data-testid="note-card"] > .MuiCardActions-root > .MuiBox-root > [data-testid="editComment"]`).click() + cy.get('[placeholder="Note body"]').click().type('updated body') + cy.get('[data-testid="Save"]').click() + // eslint-disable-next-line cypress/no-unnecessary-waiting, no-magic-numbers + cy.wait(1000) + cy.percySnapshot() + }) + }) + }) +}) diff --git a/src/Components/Notes/NoteBodyEdit.jsx b/src/Components/Notes/NoteBodyEdit.jsx index df019d669..78e64b8a5 100644 --- a/src/Components/Notes/NoteBodyEdit.jsx +++ b/src/Components/Notes/NoteBodyEdit.jsx @@ -21,6 +21,7 @@ export default function EditCardBody({handleTextUpdate, value = ''}) { alignItems="flex-end" > state.setSnackMessage) const [showCreateComment, setShowCreateComment] = useState(false) - const [editMode, setEditMode] = useState(false) const [editBody, setEditBody] = useState(body) @@ -96,14 +96,12 @@ export default function NoteCard({ setEditBody(body) }, [selectedNoteId, body]) - useEffect(() => { if (selected && firstCamera) { setCameraFromParams(firstCamera, cameraControls) } }, [selected, firstCamera, cameraControls]) - /** Selecting a card move the notes to the replies/comments thread. */ function selectCard() { let selectedNote = null @@ -119,7 +117,6 @@ export default function NoteCard({ setHashParams(window.location, HASH_PREFIX_NOTES, {id: id}) } - /** Moves the camera to the position specified in the url attached to the issue/comment */ function showCameraView() { setCameraFromParams(firstCamera, cameraControls) @@ -135,7 +132,6 @@ export default function NoteCard({ setSnackMessage({text: 'The url path is copied to the clipboard', autoDismiss: true}) } - /** * Closes the issue. TODO(pablo): this isn't a delete * @@ -150,7 +146,6 @@ export default function NoteCard({ return res } - /** * Delete comment from repo and remove from UI * @@ -169,9 +164,8 @@ export default function NoteCard({ setComments(newComments) } */ - /** Update issue on GH, set read-only */ - async function submitUpdate() { + async function updateIssueGithub() { const res = await updateIssue(repository, noteNumber, title, editBody, accessToken) const editedNote = notes.find((note) => note.id === id) editedNote.body = res.data.body @@ -179,6 +173,16 @@ export default function NoteCard({ setEditMode(false) } + /** + * Update comment in github + * + * @param {number} commentId + */ + async function updateCommentGithub(commentId) { + await updateComment(repository, commentId, editBody, accessToken) + setEditMode(false) + } + return ( @@ -203,7 +207,7 @@ export default function NoteCard({ {isNote && !editMode && !selected && } {selected && !editMode && } - {!isNote && } + {!isNote && !editMode && } {editMode && setEditBody(event.target.value)} @@ -226,8 +230,9 @@ export default function NoteCard({ onClickShare={shareIssue} selectCard={selectCard} selected={selected} - submitUpdate={submitUpdate} - synched={synched} + setEditMode={setEditMode} + submitNoteUpdate={updateIssueGithub} + submitCommentUpdate={updateCommentGithub} username={username} /> diff --git a/src/Components/Notes/NoteCardCreate.jsx b/src/Components/Notes/NoteCardCreate.jsx index 52c6140fb..226f96a14 100644 --- a/src/Components/Notes/NoteCardCreate.jsx +++ b/src/Components/Notes/NoteCardCreate.jsx @@ -160,9 +160,9 @@ export default function NoteCardCreate({ justifyContent='flex-end' alignContent='flex-end' direction='row' - sx={{width: '100%'}} + sx={{width: '100%', padding: '0 0.5em'}} > - {isNote ? + {isNote ? } noteNumber Array of expressIDs * @return {ReactElement} @@ -33,145 +33,160 @@ export default function NoteFooter({ onClickShare, selectCard, selected, - setShowCreateComment, showCreateComment, - submitUpdate, - synched, + submitNoteUpdate, username, + submitCommentUpdate, + setEditMode, }) { const existPlaceMarkInFeature = useExistInFeature('placemark') const isScreenshotEnabled = useExistInFeature('screenshot') - const viewer = useStore((state) => state.viewer) const repository = useStore((state) => state.repository) const placeMarkId = useStore((state) => state.placeMarkId) const placeMarkActivated = useStore((state) => state.placeMarkActivated) - const [shareIssue, setShareIssue] = useState(false) const [screenshotUri, setScreenshotUri] = useState(null) - const {user} = useAuth0() const theme = useTheme() const {togglePlaceMarkActive} = usePlaceMark() - const hasCameras = embeddedCameras.length > 0 /** Navigate to github issue */ function openGithubIssue() { window.open( `https://github.com/${repository.orgName}/${repository.name}/issues/${noteNumber}`, - '_blank') + '_blank', + ) } return ( - {isNote && - } - aboutInfo={false} - /> - } - - {hasCameras && - } - aboutInfo={false} - />} - - {selected && - { - onClickShare() - setShareIssue(!shareIssue) - }} - icon={} - /> - } - - {isNote && selected && synched && existPlaceMarkInFeature && - user && user.nickname === username && - - { - togglePlaceMarkActive(id) - }} - icon={} - /> - - } - - {isScreenshotEnabled && screenshotUri && - screenshot - } - - {isScreenshotEnabled && - { - setScreenshotUri(viewer.takeScreenshot()) - }} - icon={} - /> - } - - {editMode && - } - onClick={() => submitUpdate(repository, accessToken, id)} - /> - } - - {isNote && !selected && - } - /> - } + {isNote && ( + } + /> + )} + + {hasCameras && ( + } + /> + )} + + {selected && ( + { + onClickShare() + setShareIssue(!shareIssue) + }} + icon={} + /> + )} + + {isNote && selected && existPlaceMarkInFeature && user && user.nickname === username && ( + + { + togglePlaceMarkActive(id) + }} + icon={} + /> + + )} + + {isScreenshotEnabled && screenshotUri && screenshot} + + {isScreenshotEnabled && ( + { + setScreenshotUri(viewer.takeScreenshot()) + }} + icon={} + /> + )} + + {isNote && !selected && ( + } + /> + )} + + + {editMode && isNote && ( + } + onClick={() => submitNoteUpdate(id)} + /> + )} + + {!editMode && isNote && !selected && numberOfComments > 0 && ( + <> + } + /> + {numberOfComments} + + )} + + {!editMode && !isNote && user && username === user.nickname && ( + } + onClick={() => setEditMode(true)} + /> + )} - {numberOfComments > 0 && !editMode && - - {!selected && + {editMode && !isNote && ( } + title="Save" + placement="top" + icon={} + onClick={() => submitCommentUpdate(id)} /> - } - {!selected && numberOfComments} - - } + )} + ) } diff --git a/src/__mocks__/api-handlers.js b/src/__mocks__/api-handlers.js index e232c709a..7a61f2e3c 100644 --- a/src/__mocks__/api-handlers.js +++ b/src/__mocks__/api-handlers.js @@ -244,6 +244,12 @@ function githubHandlers(githubStore) { ) }), + rest.patch(`${GH_BASE}/repos/:org/:repo/issues/comments/:commentId`, (req, res, ctx) => { + return res( + ctx.status(httpOk), + ) + }), + rest.get(`${GH_BASE}/user/orgs`, (req, res, ctx) => { const authHeader = req.headers.get('authorization') diff --git a/src/net/github/Comments.fixture.js b/src/net/github/Comments.fixture.js index 5efaa99c4..f8e15c142 100644 --- a/src/net/github/Comments.fixture.js +++ b/src/net/github/Comments.fixture.js @@ -11,7 +11,7 @@ export const MOCK_COMMENTS = { node_id: 'IC_kwDOFwgxOc5EPlQ3', number: 1, user: { - login: 'OlegMoshkovich', + login: 'cypresstester', id: 3433607, node_id: 'MDQ6VXNlcjM0MzM2MDY=', avatar_url: 'https://avatars.githubusercontent.com/u/3433606?v=4', @@ -56,7 +56,7 @@ export const MOCK_COMMENTS = { number: 2, node_id: 'IC_kwDOFwgxOc5EPlQ3', user: { - login: 'OlegMoshkovich', + login: 'cypresstester', id: 3433606, node_id: 'MDQ6VXNlcjM0MzM2MDY=', avatar_url: 'https://avatars.githubusercontent.com/u/3433606?v=4', diff --git a/src/net/github/Comments.js b/src/net/github/Comments.js index d7b1a6646..712808340 100644 --- a/src/net/github/Comments.js +++ b/src/net/github/Comments.js @@ -1,5 +1,5 @@ import {assertDefined} from '../../utils/assert' -import {getGitHub, postGitHub, deleteGitHub} from './Http' +import {getGitHub, patchGitHub, postGitHub, deleteGitHub} from './Http' /** @@ -46,6 +46,23 @@ export async function getComments(repository, accessToken = '') { return res.data } +/** + * @param {object} repository - Repository object containing orgName and name. + * @param {number} commentId - ID of the comment to be updated. + * @param {string} body - New body of the comment. + * @param {string} accessToken - GitHub access token. + * @return {object} response from GitHub. + */ +export async function updateComment(repository, commentId, body, accessToken) { + assertDefined(...arguments) + + const args = { + body, + } + + return await patchGitHub(repository, `issues/comments/${commentId}`, args, accessToken) +} + /** * @param {object} repository diff --git a/src/theme/Components.js b/src/theme/Components.js index 2d33ee669..545dc52eb 100644 --- a/src/theme/Components.js +++ b/src/theme/Components.js @@ -73,6 +73,18 @@ export function getComponentOverrides(palette, typography) { root: StandardButton, // Same as MuiToggleButton }, }, + MuiInputBase: { + variants: [ + { + props: {variant: 'edit'}, + style: ({theme}) => ({ + border: `1px solid ${theme.palette.primary.main}`, + padding: '.5em', + borderRadius: '10px', + }), + }, + ], + }, MuiCard: { styleOverrides: { root: {