From fca6c952f8a2449bbfcbb5085cf4552ebaff4900 Mon Sep 17 00:00:00 2001 From: roo hutton Date: Mon, 22 Jul 2024 14:13:04 +0100 Subject: [PATCH] Merge pull request #19391 from overleaf/rh-readd-collaborator [web] Re-add collaborator email after removed from invite input GitOrigin-RevId: 629ac28292978d24323ff2ba53ae1c9987bce9a2 --- .../select-collaborators.jsx | 10 ++ .../components/select-collaborators.jsx | 10 ++ .../components/share-project-modal.test.jsx | 92 +++++++++++++++++++ 3 files changed, 112 insertions(+) diff --git a/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/select-collaborators.jsx b/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/select-collaborators.jsx index fc55778a19..5339237813 100644 --- a/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/select-collaborators.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/select-collaborators.jsx @@ -77,6 +77,15 @@ export default function SelectCollaborators({ return true }, [inputValue, selectedItems]) + function stateReducer(state, actionAndChanges) { + const { type, changes } = actionAndChanges + // force selected item to be null so that adding, removing, then re-adding the same collaborator is recognised as a selection change + if (type === useCombobox.stateChangeTypes.InputChange) { + return { ...changes, selectedItem: null } + } + return changes + } + const { isOpen, getLabelProps, @@ -91,6 +100,7 @@ export default function SelectCollaborators({ defaultHighlightedIndex: 0, items: filteredOptions, itemToString: item => item && item.name, + stateReducer, onStateChange: ({ inputValue, type, selectedItem }) => { switch (type) { // add a selected item on Enter (keypress), click or blur diff --git a/services/web/frontend/js/features/share-project-modal/components/select-collaborators.jsx b/services/web/frontend/js/features/share-project-modal/components/select-collaborators.jsx index bf948ea8c1..9156650e06 100644 --- a/services/web/frontend/js/features/share-project-modal/components/select-collaborators.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/select-collaborators.jsx @@ -77,6 +77,15 @@ export default function SelectCollaborators({ return true }, [inputValue, selectedItems]) + function stateReducer(state, actionAndChanges) { + const { type, changes } = actionAndChanges + // force selected item to be null so that adding, removing, then re-adding the same collaborator is recognised as a selection change + if (type === useCombobox.stateChangeTypes.InputChange) { + return { ...changes, selectedItem: null } + } + return changes + } + const { isOpen, getLabelProps, @@ -91,6 +100,7 @@ export default function SelectCollaborators({ defaultHighlightedIndex: 0, items: filteredOptions, itemToString: item => item && item.name, + stateReducer, onStateChange: ({ inputValue, type, selectedItem }) => { switch (type) { // add a selected item on Enter (keypress), click or blur diff --git a/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx b/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx index c6e2f56664..f2b1ef9e53 100644 --- a/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx +++ b/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx @@ -888,4 +888,96 @@ describe('', function () { ) }) }) + + it('selects contact by typing a partial email and selecting the suggestion', async function () { + renderWithEditorContext(, { + scope: { project }, + }) + + const [inputElement] = await screen.findAllByLabelText( + 'Share with your collaborators' + ) + + // Wait for contacts to load + await waitFor(() => { + expect(fetchMock.called('express:/user/contacts')).to.be.true + }) + + // Enter a prefix that matches a contact + await userEvent.type(inputElement, 'pto') + + // The matching contact should now be present and selected + await userEvent.click( + screen.getByRole('option', { + name: `Claudius Ptolemy `, + selected: true, + }) + ) + + // Click anywhere on the form to blur the input + await userEvent.click(screen.getByRole('dialog')) + + // The contact should be added + await waitFor(() => { + expect(screen.getAllByRole('button', { name: 'Remove' })).to.have.length( + 1 + ) + }) + }) + + it('allows an email address to be selected, removed, then re-added', async function () { + renderWithEditorContext(, { + scope: { project }, + }) + + const [inputElement] = await screen.findAllByLabelText( + 'Share with your collaborators' + ) + + // Wait for contacts to load + await waitFor(() => { + expect(fetchMock.called('express:/user/contacts')).to.be.true + }) + + // Enter a prefix that matches a contact + await userEvent.type(inputElement, 'pto') + + // Select the suggested contact + await userEvent.click( + screen.getByRole('option', { + name: `Claudius Ptolemy `, + selected: true, + }) + ) + + // Click anywhere on the form to blur the input + await userEvent.click(screen.getByRole('dialog')) + + // Remove the just-added collaborator + await userEvent.click(screen.getByRole('button', { name: 'Remove' })) + + // Remove button should now be gone + expect(screen.queryByRole('button', { name: 'Remove' })).to.be.null + + // Add the same collaborator again + await userEvent.type(inputElement, 'pto') + + // Click the suggested contact again + await userEvent.click( + screen.getByRole('option', { + name: `Claudius Ptolemy `, + selected: true, + }) + ) + + // Click anywhere on the form to blur the input + await userEvent.click(screen.getByRole('dialog')) + + // The contact should be added + await waitFor(() => { + expect(screen.getAllByRole('button', { name: 'Remove' })).to.have.length( + 1 + ) + }) + }) })