| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712 |
- import {
- render,
- screen,
- fireEvent,
- waitForElementToBeRemoved,
- } from '@testing-library/react'
- import userEvent from '@testing-library/user-event'
- import EmailsSection from '../../../../../../frontend/js/features/settings/components/emails-section'
- import { expect } from 'chai'
- import fetchMock from 'fetch-mock'
- import { UserEmailData } from '../../../../../../types/user-email'
- import { Affiliation } from '../../../../../../types/affiliation'
- import withMarkup from '../../../../helpers/with-markup'
- import getMeta from '@/utils/meta'
- const userEmailData: UserEmailData & { affiliation: Affiliation } = {
- affiliation: {
- cachedConfirmedAt: null,
- cachedEntitlement: null,
- cachedLastDayToReconfirm: null,
- cachedPastReconfirmDate: false,
- cachedReconfirmedAt: null,
- department: 'Art History',
- institution: {
- commonsAccount: false,
- writefullCommonsAccount: false,
- confirmed: true,
- id: 1,
- isUniversity: true,
- maxConfirmationMonths: null,
- name: 'Overleaf',
- ssoEnabled: false,
- ssoBeta: false,
- },
- inReconfirmNotificationPeriod: false,
- inferred: false,
- licence: 'pro_plus',
- pastReconfirmDate: false,
- portal: { slug: '', templates_count: 1 },
- role: 'Reader',
- },
- email: 'baz@overleaf.com',
- default: false,
- }
- const institutionDomainData = [
- {
- university: {
- id: 1234,
- ssoEnabled: true,
- name: 'Auto Complete University',
- },
- hostname: 'autocomplete.edu',
- confirmed: true,
- },
- ] as const
- function resetFetchMock() {
- fetchMock.removeRoutes().clearHistory()
- fetchMock.get('express:/institutions/domains', [])
- }
- async function confirmCodeForEmail(email: string) {
- await screen.findByText(
- `Enter the 6-digit confirmation code sent to ${email}.`
- )
- const inputCode = screen.getByLabelText(/6-digit confirmation code/i)
- fireEvent.change(inputCode, { target: { value: '123456' } })
- const submitCodeBtn = screen.getByRole<HTMLButtonElement>('button', {
- name: 'Confirm',
- })
- fireEvent.click(submitCodeBtn)
- await waitForElementToBeRemoved(() =>
- screen.getByRole('button', { name: /confirming/i })
- )
- }
- describe('<EmailsSection />', function () {
- beforeEach(function () {
- Object.assign(getMeta('ol-ExposedSettings'), {
- hasAffiliationsFeature: true,
- hasSamlFeature: true,
- samlInitPath: 'saml/init',
- })
- fetchMock.removeRoutes().clearHistory()
- })
- afterEach(function () {
- resetFetchMock()
- })
- it('renders "add another email" button', async function () {
- fetchMock.get('/user/emails?ensureAffiliation=true', [])
- render(<EmailsSection />)
- await screen.findByRole('button', { name: /add another email/i })
- })
- it('renders input', async function () {
- fetchMock.get('/user/emails?ensureAffiliation=true', [])
- render(<EmailsSection />)
- await fetchMock.callHistory.flush(true)
- const button = await screen.findByRole<HTMLButtonElement>('button', {
- name: /add another email/i,
- })
- fireEvent.click(button)
- await screen.findByLabelText(/email/i, { selector: 'input' })
- })
- it('renders "Start adding your address" until a valid email is typed', async function () {
- fetchMock.get('/user/emails?ensureAffiliation=true', [])
- fetchMock.get(`/institutions/domains?hostname=email.com&limit=1`, 200)
- fetchMock.get(`/institutions/domains?hostname=email&limit=1`, 200)
- render(<EmailsSection />)
- await fetchMock.callHistory.flush(true)
- const button = await screen.findByRole<HTMLButtonElement>('button', {
- name: /add another email/i,
- })
- fireEvent.click(button)
- const input = screen.getByLabelText(/email/i, { selector: 'input' })
- // initially the text is displayed and the "add email" button disabled
- screen.getByText('Start by adding your email address.')
- expect(
- screen.getByRole<HTMLButtonElement>('button', {
- name: /add new email/i,
- }).disabled
- ).to.be.true
- // no changes while writing the email address
- fireEvent.change(input, {
- target: { value: 'partial@email' },
- })
- screen.getByText('Start by adding your email address.')
- expect(
- screen.getByRole<HTMLButtonElement>('button', {
- name: /add new email/i,
- }).disabled
- ).to.be.true
- // the text is removed when the complete email address is typed, and the "add button" is reenabled
- fireEvent.change(input, {
- target: { value: 'valid@email.com' },
- })
- expect(screen.queryByText('Start by adding your email address.')).to.be.null
- expect(
- screen.getByRole<HTMLButtonElement>('button', {
- name: /add new email/i,
- }).disabled
- ).to.be.false
- })
- it('renders "add new email" button', async function () {
- fetchMock.get('/user/emails?ensureAffiliation=true', [])
- render(<EmailsSection />)
- const button = await screen.findByRole<HTMLButtonElement>('button', {
- name: /add another email/i,
- })
- fireEvent.click(button)
- screen.getByRole('button', { name: /add new email/i })
- })
- it('prevent users from adding new emails when the limit is reached', async function () {
- const emails = []
- for (let i = 0; i < 10; i++) {
- emails.push({ email: `bar${i}@overleaf.com` })
- }
- fetchMock.get('/user/emails?ensureAffiliation=true', emails)
- render(<EmailsSection />)
- const findByTextWithMarkup = withMarkup(screen.findByText)
- await findByTextWithMarkup(
- 'You can have a maximum of 10 email addresses on this account. To add another email address, please delete an existing one.'
- )
- expect(screen.queryByRole('button', { name: /add another email/i })).to.not
- .exist
- })
- it('adds new email address', async function () {
- fetchMock.get('/user/emails?ensureAffiliation=true', [])
- render(<EmailsSection />)
- const addAnotherEmailBtn = await screen.findByRole<HTMLButtonElement>(
- 'button',
- { name: /add another email/i }
- )
- await fetchMock.callHistory.flush(true)
- resetFetchMock()
- fetchMock
- .get('/user/emails?ensureAffiliation=true', [userEmailData])
- .post('/user/emails/secondary', 200)
- .post('/user/emails/confirm-secondary', 200)
- fireEvent.click(addAnotherEmailBtn)
- const input = screen.getByLabelText(/email/i, { selector: 'input' })
- fireEvent.change(input, {
- target: { value: userEmailData.email },
- })
- const submitBtn = screen.getByRole<HTMLButtonElement>('button', {
- name: /add new email/i,
- })
- expect(submitBtn.disabled).to.be.false
- fireEvent.click(submitBtn)
- expect(submitBtn.disabled).to.be.true
- await waitForElementToBeRemoved(() =>
- screen.getByRole('button', {
- name: /Loading/i,
- })
- )
- await confirmCodeForEmail(userEmailData.email)
- await screen.findByText(userEmailData.email)
- })
- it('fails to add add new email address', async function () {
- fetchMock.get('/user/emails?ensureAffiliation=true', [])
- render(<EmailsSection />)
- const addAnotherEmailBtn = await screen.findByRole<HTMLButtonElement>(
- 'button',
- { name: /add another email/i }
- )
- await fetchMock.callHistory.flush(true)
- resetFetchMock()
- fetchMock
- .get('/user/emails?ensureAffiliation=true', [])
- .post('/user/emails/secondary', 400)
- fireEvent.click(addAnotherEmailBtn)
- const input = screen.getByLabelText(/email/i, { selector: 'input' })
- fireEvent.change(input, {
- target: { value: userEmailData.email },
- })
- const submitBtn = screen.getByRole<HTMLButtonElement>('button', {
- name: /add new email/i,
- })
- expect(submitBtn.disabled).to.be.false
- fireEvent.click(submitBtn)
- expect(submitBtn.disabled).to.be.true
- await screen.findByText(
- /Invalid Request. Please correct the data and try again./i
- )
- expect(submitBtn).to.not.be.null
- expect(submitBtn.disabled).to.be.false
- })
- it('can link email address to an existing SSO institution', async function () {
- fetchMock.get('/user/emails?ensureAffiliation=true', [])
- render(<EmailsSection />)
- const button = await screen.findByRole<HTMLButtonElement>('button', {
- name: /add another email/i,
- })
- await fetchMock.callHistory.flush(true)
- fetchMock.removeRoutes().clearHistory()
- fetchMock.get('express:/institutions/domains', institutionDomainData)
- await userEvent.click(button)
- const input = screen.getByLabelText(/email/i, { selector: 'input' })
- fireEvent.change(input, {
- target: { value: 'user@autocomplete.edu' },
- })
- await screen.findByRole('button', { name: 'Link accounts and add email' })
- })
- it('adds new email address with existing institution and custom departments', async function () {
- const country = 'Germany'
- const customDepartment = 'Custom department'
- fetchMock.get('/user/emails?ensureAffiliation=true', [])
- render(<EmailsSection />)
- const button = await screen.findByRole<HTMLButtonElement>('button', {
- name: /add another email/i,
- })
- await fetchMock.callHistory.flush(true)
- resetFetchMock()
- await userEvent.click(button)
- await userEvent.type(
- screen.getByLabelText(/email/i, { selector: 'input' }),
- userEmailData.email
- )
- await userEvent.click(screen.getByRole('button', { name: /let us know/i }))
- const universityInput = screen.getByRole<HTMLInputElement>('combobox', {
- name: /university/i,
- })
- expect(universityInput.disabled).to.be.true
- fetchMock.get('/institutions/list?country_code=de', [
- {
- id: userEmailData.affiliation.institution.id,
- name: userEmailData.affiliation.institution.name,
- country_code: 'de',
- departments: [customDepartment],
- },
- ])
- // Select the country from dropdown
- await userEvent.type(
- screen.getByRole('combobox', {
- name: /country/i,
- }),
- country
- )
- await userEvent.click(screen.getByText(country))
- expect(universityInput.disabled).to.be.false
- await fetchMock.callHistory.flush(true)
- resetFetchMock()
- // Select the university from dropdown
- await userEvent.click(universityInput)
- await userEvent.click(
- await screen.findByText(userEmailData.affiliation.institution.name)
- )
- const roleInput = screen.getByRole('combobox', { name: /role/i })
- await userEvent.type(roleInput, userEmailData.affiliation.role!)
- const departmentInput = screen.getByRole('combobox', {
- name: /department/i,
- })
- await userEvent.click(departmentInput)
- await userEvent.click(screen.getByText(customDepartment))
- const userEmailDataCopy = {
- ...userEmailData,
- affiliation: {
- ...userEmailData.affiliation,
- department: customDepartment,
- },
- }
- fetchMock
- .get('/user/emails?ensureAffiliation=true', [userEmailDataCopy])
- .post('/user/emails/secondary', 200)
- .post('/user/emails/confirm-secondary', 200)
- await userEvent.click(
- screen.getByRole('button', {
- name: /add new email/i,
- })
- )
- const request = fetchMock.callHistory.calls(/\/user\/emails/).at(0)
- expect(
- JSON.parse(request?.options.body?.toString() || '{}')
- ).to.deep.include({
- email: userEmailData.email,
- university: {
- id: userEmailData.affiliation?.institution.id,
- },
- role: userEmailData.affiliation?.role,
- department: customDepartment,
- })
- await screen.findByText(
- `Enter the 6-digit confirmation code sent to ${userEmailData.email}.`
- )
- await confirmCodeForEmail(userEmailData.email)
- await screen.findByText(userEmailData.affiliation.role!, { exact: false })
- await screen.findByText(customDepartment, { exact: false })
- })
- it('autocompletes institution name', async function () {
- fetchMock.get('/user/emails?ensureAffiliation=true', [])
- render(<EmailsSection />)
- const button = await screen.findByRole<HTMLButtonElement>('button', {
- name: /add another email/i,
- })
- await fetchMock.callHistory.flush(true)
- resetFetchMock()
- fetchMock.get('/institutions/list?country_code=de', [
- {
- id: 1,
- name: 'University of Bonn',
- },
- {
- id: 2,
- name: 'Bochum institute of Science',
- },
- ])
- // open "add new email" section and click "let us know" to open the Country/University form
- await userEvent.click(button)
- await userEvent.type(
- screen.getByLabelText(/email/i, { selector: 'input' }),
- userEmailData.email
- )
- await userEvent.click(screen.getByRole('button', { name: /let us know/i }))
- // select a country
- const countryInput = screen.getByRole<HTMLInputElement>('combobox', {
- name: /country/i,
- })
- await userEvent.click(countryInput)
- await userEvent.type(countryInput, 'Germ')
- await userEvent.click(await screen.findByText('Germany'))
- // match several universities on initial typing
- const universityInput = screen.getByRole<HTMLInputElement>('combobox', {
- name: /university/i,
- })
- await userEvent.click(universityInput)
- await userEvent.type(universityInput, 'bo')
- await screen.findByText('University of Bonn')
- await screen.findByText('Bochum institute of Science')
- // match a single university when typing to refine the search
- await userEvent.type(universityInput, 'nn')
- await screen.findByText('University of Bonn')
- expect(screen.queryByText('Bochum institute of Science')).to.be.null
- })
- it('adds new email address without existing institution', async function () {
- const country = 'Germany'
- const countryCode = 'de'
- const newUniversity = 'Abcdef'
- fetchMock.get('/user/emails?ensureAffiliation=true', [])
- render(<EmailsSection />)
- const button = await screen.findByRole<HTMLButtonElement>('button', {
- name: /add another email/i,
- })
- await fetchMock.callHistory.flush(true)
- resetFetchMock()
- await userEvent.click(button)
- await userEvent.type(
- screen.getByLabelText(/email/i, { selector: 'input' }),
- userEmailData.email
- )
- await userEvent.click(screen.getByRole('button', { name: /let us know/i }))
- const universityInput = screen.getByRole<HTMLInputElement>('combobox', {
- name: /university/i,
- })
- expect(universityInput.disabled).to.be.true
- fetchMock.get(/\/institutions\/list/, [
- {
- id: userEmailData.affiliation.institution.id,
- name: userEmailData.affiliation.institution.name,
- country_code: 'de',
- departments: [],
- },
- ])
- // Select the country from dropdown
- await userEvent.type(
- screen.getByRole('combobox', {
- name: /country/i,
- }),
- country
- )
- await userEvent.click(screen.getByText(country))
- expect(universityInput.disabled).to.be.false
- await fetchMock.callHistory.flush(true)
- resetFetchMock()
- // Enter the university manually
- await userEvent.type(universityInput, newUniversity)
- const roleInput = screen.getByRole('combobox', { name: /role/i })
- await userEvent.type(roleInput, userEmailData.affiliation.role!)
- const departmentInput = screen.getByRole('combobox', {
- name: /department/i,
- })
- await userEvent.type(departmentInput, userEmailData.affiliation.department!)
- const userEmailDataCopy = {
- ...userEmailData,
- affiliation: {
- ...userEmailData.affiliation,
- institution: {
- ...userEmailData.affiliation.institution,
- name: newUniversity,
- },
- },
- }
- fetchMock
- .get('/user/emails?ensureAffiliation=true', [userEmailDataCopy])
- .post('/user/emails/secondary', 200)
- .post('/user/emails/confirm-secondary', 200)
- await userEvent.click(
- screen.getByRole('button', {
- name: /add new email/i,
- })
- )
- await confirmCodeForEmail(userEmailData.email)
- const request = fetchMock.callHistory.calls(/\/user\/emails/).at(0)
- expect(
- JSON.parse(request?.options.body?.toString() || '{}')
- ).to.deep.include({
- email: userEmailData.email,
- university: {
- name: newUniversity,
- country_code: countryCode,
- },
- role: userEmailData.affiliation?.role,
- department: userEmailData.affiliation?.department,
- })
- await screen.findByText(userEmailData.email)
- await screen.findByText(newUniversity)
- await screen.findByText(userEmailData.affiliation.role!, { exact: false })
- await screen.findByText(userEmailData.affiliation.department!, {
- exact: false,
- })
- })
- it('shows country, university, role and department fields based on whether `change` was clicked or not', async function () {
- const institutionDomainDataCopy = [
- {
- ...institutionDomainData[0],
- university: {
- ...institutionDomainData[0].university,
- ssoEnabled: false,
- },
- },
- ]
- const hostnameFirstChar = institutionDomainDataCopy[0].hostname.charAt(0)
- fetchMock.get('/user/emails?ensureAffiliation=true', [])
- render(<EmailsSection />)
- const button = await screen.findByRole<HTMLButtonElement>('button', {
- name: /add another email/i,
- })
- await fetchMock.callHistory.flush(true)
- fetchMock.removeRoutes().clearHistory()
- fetchMock.get(
- `/institutions/domains?hostname=${hostnameFirstChar}&limit=1`,
- institutionDomainDataCopy
- )
- await userEvent.click(button)
- await userEvent.type(
- screen.getByLabelText(/email/i, { selector: 'input' }),
- `user@${hostnameFirstChar}`
- )
- await userEvent.keyboard('{Tab}')
- await fetchMock.callHistory.flush(true)
- fetchMock.removeRoutes().clearHistory()
- expect(
- screen.queryByRole('combobox', {
- name: /country/i,
- })
- ).to.be.null
- expect(
- screen.queryByRole('combobox', {
- name: /university/i,
- })
- ).to.be.null
- screen.getByRole('combobox', {
- name: /role/i,
- })
- screen.getByRole('combobox', {
- name: /department/i,
- })
- await userEvent.click(screen.getByRole('button', { name: /change/i }))
- screen.getByRole('combobox', {
- name: /country/i,
- })
- screen.getByRole('combobox', {
- name: /university/i,
- })
- expect(
- screen.queryByRole('combobox', {
- name: /role/i,
- })
- ).to.be.null
- expect(
- screen.queryByRole('combobox', {
- name: /department/i,
- })
- ).to.be.null
- })
- it('displays institution name with change button when autocompleted and adds new record', async function () {
- const institutionDomainDataCopy = [
- {
- ...institutionDomainData[0],
- university: {
- ...institutionDomainData[0].university,
- ssoEnabled: false,
- },
- },
- ]
- const hostnameFirstChar = institutionDomainDataCopy[0].hostname.charAt(0)
- fetchMock.get('/user/emails?ensureAffiliation=true', [])
- render(<EmailsSection />)
- const button = await screen.findByRole<HTMLButtonElement>('button', {
- name: /add another email/i,
- })
- await fetchMock.callHistory.flush(true)
- fetchMock.removeRoutes().clearHistory()
- fetchMock.get(
- `/institutions/domains?hostname=${hostnameFirstChar}&limit=1`,
- institutionDomainDataCopy
- )
- await userEvent.click(button)
- await userEvent.type(
- screen.getByLabelText(/email/i, { selector: 'input' }),
- `user@${hostnameFirstChar}`
- )
- await userEvent.keyboard('{Tab}')
- await fetchMock.callHistory.flush(true)
- fetchMock.removeRoutes().clearHistory()
- screen.getByText(institutionDomainDataCopy[0].university.name)
- const userEmailDataCopy = {
- ...userEmailData,
- affiliation: {
- ...userEmailData.affiliation,
- institution: {
- ...userEmailData.affiliation.institution,
- name: institutionDomainDataCopy[0].university.name,
- },
- },
- }
- fetchMock
- .get('/user/emails?ensureAffiliation=true', [userEmailDataCopy])
- .post('/user/emails/secondary', 200)
- .post('/user/emails/confirm-secondary', 200)
- await userEvent.type(
- screen.getByRole('combobox', { name: /role/i }),
- userEmailData.affiliation.role!
- )
- await userEvent.type(
- screen.getByRole('combobox', { name: /department/i }),
- userEmailData.affiliation.department!
- )
- await userEvent.click(
- screen.getByRole('button', {
- name: /add new email/i,
- })
- )
- await confirmCodeForEmail('user@autocomplete.edu')
- await fetchMock.callHistory.flush(true)
- fetchMock.removeRoutes().clearHistory()
- screen.getByText(userEmailDataCopy.affiliation.institution.name, {
- exact: false,
- })
- screen.getByText(userEmailDataCopy.affiliation.role!, { exact: false })
- screen.getByText(userEmailDataCopy.affiliation.department!, {
- exact: false,
- })
- })
- })
|