emails-section-add-new-email.test.tsx 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. import {
  2. render,
  3. screen,
  4. fireEvent,
  5. waitForElementToBeRemoved,
  6. within,
  7. } from '@testing-library/react'
  8. import userEvent from '@testing-library/user-event'
  9. import EmailsSection from '../../../../../../frontend/js/features/settings/components/emails-section'
  10. import { expect } from 'chai'
  11. import fetchMock from 'fetch-mock'
  12. import { UserEmailData } from '../../../../../../types/user-email'
  13. import { Affiliation } from '../../../../../../types/affiliation'
  14. import withMarkup from '../../../../helpers/with-markup'
  15. import getMeta from '@/utils/meta'
  16. import { clearDomainCache } from '../../../../../../frontend/js/features/settings/components/emails/add-email/input'
  17. const userEmailData: UserEmailData & { affiliation: Affiliation } = {
  18. affiliation: {
  19. cachedConfirmedAt: null,
  20. cachedEntitlement: null,
  21. cachedLastDayToReconfirm: null,
  22. cachedPastReconfirmDate: false,
  23. cachedReconfirmedAt: null,
  24. department: 'Art History',
  25. institution: {
  26. commonsAccount: false,
  27. writefullCommonsAccount: false,
  28. confirmed: true,
  29. id: 1,
  30. isUniversity: true,
  31. maxConfirmationMonths: null,
  32. name: 'Overleaf',
  33. ssoEnabled: false,
  34. ssoBeta: false,
  35. },
  36. inReconfirmNotificationPeriod: false,
  37. inferred: false,
  38. licence: 'pro_plus',
  39. pastReconfirmDate: false,
  40. portal: { slug: '', templates_count: 1 },
  41. role: 'Reader',
  42. },
  43. email: 'baz@overleaf.com',
  44. default: false,
  45. }
  46. const institutionDomainData = [
  47. {
  48. university: {
  49. id: 1234,
  50. ssoEnabled: true,
  51. name: 'Auto Complete University',
  52. },
  53. hostname: 'autocomplete.edu',
  54. confirmed: true,
  55. },
  56. ] as const
  57. function resetFetchMock() {
  58. fetchMock.removeRoutes().clearHistory()
  59. fetchMock.get('express:/institutions/domains', [])
  60. }
  61. async function confirmCodeForEmail(email: string) {
  62. const inputCode = await screen.findByRole('textbox', {
  63. name: /Enter the 6-digit confirmation code sent to /,
  64. })
  65. screen.getByText(email, { exact: false })
  66. fireEvent.change(inputCode, { target: { value: '123456' } })
  67. const submitCodeBtn = screen.getByRole<HTMLButtonElement>('button', {
  68. name: 'Confirm',
  69. })
  70. fireEvent.click(submitCodeBtn)
  71. await waitForElementToBeRemoved(() =>
  72. screen.getByRole('button', { name: 'Confirming' })
  73. )
  74. }
  75. describe('<EmailsSection />', function () {
  76. beforeEach(function () {
  77. Object.assign(getMeta('ol-ExposedSettings'), {
  78. hasAffiliationsFeature: true,
  79. hasSamlFeature: true,
  80. samlInitPath: 'saml/init',
  81. })
  82. fetchMock.removeRoutes().clearHistory()
  83. })
  84. afterEach(function () {
  85. resetFetchMock()
  86. clearDomainCache()
  87. })
  88. it('renders "add another email" button', async function () {
  89. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  90. render(<EmailsSection />)
  91. await screen.findByRole('button', { name: 'Add another email' })
  92. })
  93. it('renders input', async function () {
  94. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  95. render(<EmailsSection />)
  96. await fetchMock.callHistory.flush(true)
  97. const button = await screen.findByRole<HTMLButtonElement>('button', {
  98. name: 'Add another email',
  99. })
  100. fireEvent.click(button)
  101. await screen.findByRole('textbox', { name: 'Email' })
  102. })
  103. it('renders "Start adding your address" until a valid email is typed', async function () {
  104. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  105. fetchMock.get(`/institutions/domains?hostname=email.com&limit=1`, 200)
  106. fetchMock.get(`/institutions/domains?hostname=email&limit=1`, 200)
  107. render(<EmailsSection />)
  108. await fetchMock.callHistory.flush(true)
  109. const button = await screen.findByRole<HTMLButtonElement>('button', {
  110. name: 'Add another email',
  111. })
  112. fireEvent.click(button)
  113. const input = screen.getByRole('textbox', { name: 'Email' })
  114. // initially the text is displayed and the "add email" button disabled
  115. screen.getByText('Start by adding your email address.')
  116. expect(
  117. screen.getByRole<HTMLButtonElement>('button', {
  118. name: 'Add new email',
  119. }).disabled
  120. ).to.be.true
  121. // no changes while writing the email address
  122. fireEvent.change(input, {
  123. target: { value: 'partial@email' },
  124. })
  125. screen.getByText('Start by adding your email address.')
  126. expect(
  127. screen.getByRole<HTMLButtonElement>('button', {
  128. name: 'Add new email',
  129. }).disabled
  130. ).to.be.true
  131. // the text is removed when the complete email address is typed, and the "add button" is reenabled
  132. fireEvent.change(input, {
  133. target: { value: 'valid@email.com' },
  134. })
  135. expect(screen.queryByText('Start by adding your email address.')).to.be.null
  136. expect(
  137. screen.getByRole<HTMLButtonElement>('button', {
  138. name: 'Add new email',
  139. }).disabled
  140. ).to.be.false
  141. })
  142. it('renders "add new email" button', async function () {
  143. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  144. render(<EmailsSection />)
  145. const button = await screen.findByRole<HTMLButtonElement>('button', {
  146. name: 'Add another email',
  147. })
  148. fireEvent.click(button)
  149. screen.getByRole('button', { name: 'Add new email' })
  150. })
  151. it('prevent users from adding new emails when the limit is reached', async function () {
  152. const emails = []
  153. for (let i = 0; i < 10; i++) {
  154. emails.push({ email: `bar${i}@overleaf.com` })
  155. }
  156. fetchMock.get('/user/emails?ensureAffiliation=true', emails)
  157. render(<EmailsSection />)
  158. const findByTextWithMarkup = withMarkup(screen.findByText)
  159. await findByTextWithMarkup(
  160. 'You can have a maximum of 10 email addresses on this account. To add another email address, please delete an existing one.'
  161. )
  162. expect(screen.queryByRole('button', { name: 'Add another email' })).to.not
  163. .exist
  164. })
  165. it('adds new email address', async function () {
  166. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  167. render(<EmailsSection />)
  168. const addAnotherEmailBtn = await screen.findByRole<HTMLButtonElement>(
  169. 'button',
  170. { name: 'Add another email' }
  171. )
  172. await fetchMock.callHistory.flush(true)
  173. resetFetchMock()
  174. fetchMock
  175. .get('/user/emails?ensureAffiliation=true', [userEmailData])
  176. .post('/user/emails/secondary', 200)
  177. .post('/user/emails/confirm-secondary', 200)
  178. fireEvent.click(addAnotherEmailBtn)
  179. const input = screen.getByRole('textbox', { name: 'Email' })
  180. fireEvent.change(input, {
  181. target: { value: userEmailData.email },
  182. })
  183. const submitBtn = screen.getByRole<HTMLButtonElement>('button', {
  184. name: 'Add new email',
  185. })
  186. expect(submitBtn.disabled).to.be.false
  187. fireEvent.click(submitBtn)
  188. expect(submitBtn.disabled).to.be.true
  189. await waitForElementToBeRemoved(() =>
  190. screen.getByRole('button', {
  191. name: /adding/i,
  192. })
  193. )
  194. await confirmCodeForEmail(userEmailData.email)
  195. const elements = await screen.findAllByText(userEmailData.email)
  196. expect(elements.length).to.equal(2)
  197. })
  198. it('fails to add add new email address', async function () {
  199. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  200. render(<EmailsSection />)
  201. const addAnotherEmailBtn = await screen.findByRole<HTMLButtonElement>(
  202. 'button',
  203. { name: 'Add another email' }
  204. )
  205. await fetchMock.callHistory.flush(true)
  206. resetFetchMock()
  207. fetchMock
  208. .get('/user/emails?ensureAffiliation=true', [])
  209. .post('/user/emails/secondary', 400)
  210. fireEvent.click(addAnotherEmailBtn)
  211. const input = screen.getByRole('textbox', { name: 'Email' })
  212. fireEvent.change(input, {
  213. target: { value: userEmailData.email },
  214. })
  215. const submitBtn = screen.getByRole<HTMLButtonElement>('button', {
  216. name: 'Add new email',
  217. })
  218. expect(submitBtn.disabled).to.be.false
  219. fireEvent.click(submitBtn)
  220. expect(submitBtn.disabled).to.be.true
  221. await screen.findByText(
  222. 'Invalid Request. Please correct the data and try again.'
  223. )
  224. expect(submitBtn).to.not.be.null
  225. expect(submitBtn.disabled).to.be.false
  226. })
  227. it('can link email address to an existing SSO institution', async function () {
  228. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  229. render(<EmailsSection />)
  230. const button = await screen.findByRole<HTMLButtonElement>('button', {
  231. name: 'Add another email',
  232. })
  233. await fetchMock.callHistory.flush(true)
  234. fetchMock.removeRoutes().clearHistory()
  235. fetchMock.get('express:/institutions/domains', institutionDomainData)
  236. await userEvent.click(button)
  237. const input = screen.getByRole('textbox', { name: 'Email' })
  238. fireEvent.change(input, {
  239. target: { value: 'user@autocomplete.edu' },
  240. })
  241. await screen.findByRole('button', { name: 'Link accounts and add email' })
  242. })
  243. it('prevents user from linking to same SSO institution twice', async function () {
  244. fetchMock.get('/user/emails?ensureAffiliation=true', [
  245. { email: 'bar@autocomplete.edu', samlProviderId: '1234' },
  246. ])
  247. render(<EmailsSection />)
  248. const button = await screen.findByRole<HTMLButtonElement>('button', {
  249. name: 'Add another email',
  250. })
  251. await fetchMock.callHistory.flush(true)
  252. fetchMock.removeRoutes().clearHistory()
  253. fetchMock.get('express:/institutions/domains', institutionDomainData)
  254. await userEvent.click(button)
  255. const input = screen.getByRole('textbox', { name: 'Email' })
  256. fireEvent.change(input, {
  257. target: { value: 'baz@autocomplete.edu' },
  258. })
  259. await screen.findByText(
  260. 'This institution is already linked with your account via another email address.'
  261. )
  262. })
  263. it('adds new email address with existing institution and custom departments', async function () {
  264. const country = 'Germany'
  265. const customDepartment = 'Custom department'
  266. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  267. render(<EmailsSection />)
  268. const button = await screen.findByRole<HTMLButtonElement>('button', {
  269. name: 'Add another email',
  270. })
  271. await fetchMock.callHistory.flush(true)
  272. resetFetchMock()
  273. await userEvent.click(button)
  274. await userEvent.type(
  275. screen.getByRole('textbox', { name: 'Email' }),
  276. userEmailData.email
  277. )
  278. await userEvent.click(screen.getByRole('button', { name: 'Let us know' }))
  279. const universityInput = screen.getByRole<HTMLInputElement>('combobox', {
  280. name: 'University',
  281. })
  282. expect(universityInput.disabled).to.be.true
  283. fetchMock.get('/institutions/list?country_code=de', [
  284. {
  285. id: userEmailData.affiliation.institution.id,
  286. name: userEmailData.affiliation.institution.name,
  287. country_code: 'de',
  288. departments: [customDepartment],
  289. },
  290. ])
  291. // Select the country from dropdown
  292. await userEvent.type(
  293. screen.getByRole('combobox', {
  294. name: 'Country',
  295. }),
  296. country
  297. )
  298. await userEvent.click(screen.getByText(country))
  299. expect(universityInput.disabled).to.be.false
  300. await fetchMock.callHistory.flush(true)
  301. resetFetchMock()
  302. // Select the university from dropdown
  303. await userEvent.click(universityInput)
  304. await userEvent.click(
  305. await screen.findByText(userEmailData.affiliation.institution.name)
  306. )
  307. const roleInput = screen.getByRole('combobox', { name: 'Role' })
  308. await userEvent.type(roleInput, userEmailData.affiliation.role!)
  309. const departmentInput = screen.getByRole('combobox', {
  310. name: 'Department',
  311. })
  312. await userEvent.click(departmentInput)
  313. await userEvent.click(screen.getByText(customDepartment))
  314. const userEmailDataCopy = {
  315. ...userEmailData,
  316. affiliation: {
  317. ...userEmailData.affiliation,
  318. department: customDepartment,
  319. },
  320. }
  321. fetchMock
  322. .get('/user/emails?ensureAffiliation=true', [userEmailDataCopy])
  323. .post('/user/emails/secondary', 200)
  324. .post('/user/emails/confirm-secondary', 200)
  325. await userEvent.click(
  326. screen.getByRole('button', {
  327. name: 'Add new email',
  328. })
  329. )
  330. const request = fetchMock.callHistory.calls(/\/user\/emails/).at(0)
  331. expect(
  332. JSON.parse(request?.options.body?.toString() || '{}')
  333. ).to.deep.include({
  334. email: userEmailData.email,
  335. university: {
  336. id: userEmailData.affiliation?.institution.id,
  337. },
  338. role: userEmailData.affiliation?.role,
  339. department: customDepartment,
  340. })
  341. await screen.findByText(/Enter the 6-digit confirmation code sent to /)
  342. await confirmCodeForEmail(userEmailData.email)
  343. await screen.findByText(userEmailData.affiliation.role!, { exact: false })
  344. await screen.findByText(customDepartment, { exact: false })
  345. })
  346. it('autocompletes institution name', async function () {
  347. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  348. render(<EmailsSection />)
  349. const button = await screen.findByRole<HTMLButtonElement>('button', {
  350. name: 'Add another email',
  351. })
  352. await fetchMock.callHistory.flush(true)
  353. resetFetchMock()
  354. fetchMock.get('/institutions/list?country_code=de', [
  355. {
  356. id: 1,
  357. name: 'University of Bonn',
  358. },
  359. {
  360. id: 2,
  361. name: 'Bochum institute of Science',
  362. },
  363. ])
  364. // open "add new email" section and click "let us know" to open the Country/University form
  365. await userEvent.click(button)
  366. await userEvent.type(
  367. screen.getByRole('textbox', { name: 'Email' }),
  368. userEmailData.email
  369. )
  370. await userEvent.click(screen.getByRole('button', { name: 'Let us know' }))
  371. // select a country
  372. const countryInput = screen.getByRole<HTMLInputElement>('combobox', {
  373. name: 'Country',
  374. })
  375. await userEvent.click(countryInput)
  376. await userEvent.type(countryInput, 'Germ')
  377. await userEvent.click(await screen.findByText('Germany'))
  378. // match several universities on initial typing
  379. const universityInput = screen.getByRole<HTMLInputElement>('combobox', {
  380. name: 'University',
  381. })
  382. await userEvent.click(universityInput)
  383. await userEvent.type(universityInput, 'bo')
  384. await screen.findByText('University of Bonn')
  385. await screen.findByText('Bochum institute of Science')
  386. // match a single university when typing to refine the search
  387. await userEvent.type(universityInput, 'nn')
  388. await screen.findByText('University of Bonn')
  389. expect(screen.queryByText('Bochum institute of Science')).to.be.null
  390. })
  391. it('adds new email address without existing institution', async function () {
  392. const country = 'Germany'
  393. const countryCode = 'de'
  394. const newUniversity = 'Abcdef'
  395. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  396. render(<EmailsSection />)
  397. const button = await screen.findByRole<HTMLButtonElement>('button', {
  398. name: 'Add another email',
  399. })
  400. await fetchMock.callHistory.flush(true)
  401. resetFetchMock()
  402. await userEvent.click(button)
  403. await userEvent.type(
  404. screen.getByRole('textbox', { name: 'Email' }),
  405. userEmailData.email
  406. )
  407. await userEvent.click(screen.getByRole('button', { name: 'Let us know' }))
  408. const universityInput = screen.getByRole<HTMLInputElement>('combobox', {
  409. name: 'University',
  410. })
  411. expect(universityInput.disabled).to.be.true
  412. fetchMock.get(/\/institutions\/list/, [
  413. {
  414. id: userEmailData.affiliation.institution.id,
  415. name: userEmailData.affiliation.institution.name,
  416. country_code: 'de',
  417. departments: [],
  418. },
  419. ])
  420. // Select the country from dropdown
  421. await userEvent.type(
  422. screen.getByRole('combobox', {
  423. name: 'Country',
  424. }),
  425. country
  426. )
  427. await userEvent.click(screen.getByText(country))
  428. expect(universityInput.disabled).to.be.false
  429. await fetchMock.callHistory.flush(true)
  430. resetFetchMock()
  431. // Enter the university manually
  432. await userEvent.type(universityInput, newUniversity)
  433. const roleInput = screen.getByRole('combobox', { name: 'Role' })
  434. await userEvent.type(roleInput, userEmailData.affiliation.role!)
  435. const departmentInput = screen.getByRole('combobox', {
  436. name: 'Department',
  437. })
  438. await userEvent.type(departmentInput, userEmailData.affiliation.department!)
  439. const userEmailDataCopy = {
  440. ...userEmailData,
  441. affiliation: {
  442. ...userEmailData.affiliation,
  443. institution: {
  444. ...userEmailData.affiliation.institution,
  445. name: newUniversity,
  446. },
  447. },
  448. }
  449. fetchMock
  450. .get('/user/emails?ensureAffiliation=true', [userEmailDataCopy])
  451. .post('/user/emails/secondary', 200)
  452. .post('/user/emails/confirm-secondary', 200)
  453. await userEvent.click(
  454. screen.getByRole('button', {
  455. name: 'Add new email',
  456. })
  457. )
  458. await confirmCodeForEmail(userEmailData.email)
  459. const request = fetchMock.callHistory.calls(/\/user\/emails/).at(0)
  460. expect(
  461. JSON.parse(request?.options.body?.toString() || '{}')
  462. ).to.deep.include({
  463. email: userEmailData.email,
  464. university: {
  465. name: newUniversity,
  466. country_code: countryCode,
  467. },
  468. role: userEmailData.affiliation?.role,
  469. department: userEmailData.affiliation?.department,
  470. })
  471. const elements = await screen.findAllByText(userEmailData.email)
  472. expect(elements.length).to.equal(2)
  473. await screen.findByText(newUniversity)
  474. await screen.findByText(userEmailData.affiliation.role!, { exact: false })
  475. await screen.findByText(userEmailData.affiliation.department!, {
  476. exact: false,
  477. })
  478. })
  479. it('shows country, university, role and department fields based on whether `change` was clicked or not', async function () {
  480. const institutionDomainDataCopy = [
  481. {
  482. ...institutionDomainData[0],
  483. university: {
  484. ...institutionDomainData[0].university,
  485. ssoEnabled: false,
  486. },
  487. },
  488. ]
  489. const hostnameFirstChar = institutionDomainDataCopy[0].hostname.charAt(0)
  490. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  491. render(<EmailsSection />)
  492. const button = await screen.findByRole<HTMLButtonElement>('button', {
  493. name: 'Add another email',
  494. })
  495. await fetchMock.callHistory.flush(true)
  496. fetchMock.removeRoutes().clearHistory()
  497. fetchMock.get(
  498. `/institutions/domains?hostname=${hostnameFirstChar}&limit=1`,
  499. institutionDomainDataCopy
  500. )
  501. await userEvent.click(button)
  502. await userEvent.type(
  503. screen.getByRole('textbox', { name: 'Email' }),
  504. `user@${hostnameFirstChar}`
  505. )
  506. await userEvent.keyboard('{Tab}')
  507. await fetchMock.callHistory.flush(true)
  508. fetchMock.removeRoutes().clearHistory()
  509. expect(
  510. screen.queryByRole('combobox', {
  511. name: 'Country',
  512. })
  513. ).to.be.null
  514. expect(
  515. screen.queryByRole('combobox', {
  516. name: 'University',
  517. })
  518. ).to.be.null
  519. screen.getByRole('combobox', {
  520. name: 'Role',
  521. })
  522. screen.getByRole('combobox', {
  523. name: 'Department',
  524. })
  525. await userEvent.click(screen.getByRole('button', { name: 'Change' }))
  526. screen.getByRole('combobox', {
  527. name: 'Country',
  528. })
  529. screen.getByRole('combobox', {
  530. name: 'University',
  531. })
  532. expect(
  533. screen.queryByRole('combobox', {
  534. name: 'Role',
  535. })
  536. ).to.be.null
  537. expect(
  538. screen.queryByRole('combobox', {
  539. name: 'Department',
  540. })
  541. ).to.be.null
  542. })
  543. it('displays institution name with change button when autocompleted and adds new record', async function () {
  544. const institutionDomainDataCopy = [
  545. {
  546. ...institutionDomainData[0],
  547. university: {
  548. ...institutionDomainData[0].university,
  549. ssoEnabled: false,
  550. },
  551. },
  552. ]
  553. const hostnameFirstChar = institutionDomainDataCopy[0].hostname.charAt(0)
  554. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  555. render(<EmailsSection />)
  556. const button = await screen.findByRole<HTMLButtonElement>('button', {
  557. name: 'Add another email',
  558. })
  559. await fetchMock.callHistory.flush(true)
  560. fetchMock.removeRoutes().clearHistory()
  561. fetchMock.get(
  562. `/institutions/domains?hostname=${hostnameFirstChar}&limit=1`,
  563. institutionDomainDataCopy
  564. )
  565. await userEvent.click(button)
  566. await userEvent.type(
  567. screen.getByRole('textbox', { name: 'Email' }),
  568. `user@${hostnameFirstChar}`
  569. )
  570. await userEvent.keyboard('{Tab}')
  571. await fetchMock.callHistory.flush(true)
  572. fetchMock.removeRoutes().clearHistory()
  573. screen.getByText(institutionDomainDataCopy[0].university.name)
  574. const userEmailDataCopy = {
  575. ...userEmailData,
  576. affiliation: {
  577. ...userEmailData.affiliation,
  578. institution: {
  579. ...userEmailData.affiliation.institution,
  580. name: institutionDomainDataCopy[0].university.name,
  581. },
  582. },
  583. }
  584. fetchMock
  585. .get('/user/emails?ensureAffiliation=true', [userEmailDataCopy])
  586. .post('/user/emails/secondary', 200)
  587. .post('/user/emails/confirm-secondary', 200)
  588. await userEvent.type(
  589. screen.getByRole('combobox', { name: 'Role' }),
  590. userEmailData.affiliation.role!
  591. )
  592. await userEvent.type(
  593. screen.getByRole('combobox', { name: 'Department' }),
  594. userEmailData.affiliation.department!
  595. )
  596. await userEvent.click(
  597. screen.getByRole('button', {
  598. name: 'Add new email',
  599. })
  600. )
  601. await confirmCodeForEmail('user@autocomplete.edu')
  602. await fetchMock.callHistory.flush(true)
  603. fetchMock.removeRoutes().clearHistory()
  604. screen.getByText(userEmailDataCopy.affiliation.institution.name, {
  605. exact: false,
  606. })
  607. screen.getByText(userEmailDataCopy.affiliation.role!, { exact: false })
  608. screen.getByText(userEmailDataCopy.affiliation.department!, {
  609. exact: false,
  610. })
  611. })
  612. describe('when domain is captured by a group', function () {
  613. describe('and managed users is not enabled', function () {
  614. beforeEach(async function () {
  615. await fetchMock.callHistory.flush(true)
  616. fetchMock.removeRoutes().clearHistory()
  617. const institution = {
  618. university: {
  619. id: 1234,
  620. ssoEnabled: false,
  621. name: 'Auto Complete University',
  622. },
  623. hostname: 'autocomplete.edu',
  624. confirmed: true,
  625. group: {
  626. domainCaptureEnabled: true,
  627. ssoConfig: {
  628. enabled: true,
  629. },
  630. },
  631. }
  632. fetchMock.get('express:/institutions/domains', [institution])
  633. })
  634. it('can add email address via SSO', async function () {
  635. // note: this UI is a WIP
  636. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  637. render(<EmailsSection />)
  638. const button = await screen.findByRole<HTMLButtonElement>('button', {
  639. name: 'Add another email',
  640. })
  641. await userEvent.click(button)
  642. const input = screen.getByRole('textbox', { name: 'Email' })
  643. fireEvent.change(input, {
  644. target: { value: 'user@autocomplete.edu' },
  645. })
  646. await screen.findByText('This feature is currently unavailable.')
  647. })
  648. })
  649. describe('and managed users is enabled', function () {
  650. beforeEach(async function () {
  651. await fetchMock.callHistory.flush(true)
  652. fetchMock.removeRoutes().clearHistory()
  653. const institution = {
  654. university: {
  655. id: 1234,
  656. ssoEnabled: false,
  657. name: 'Auto Complete University',
  658. },
  659. hostname: 'autocomplete.edu',
  660. confirmed: true,
  661. group: {
  662. domainCaptureEnabled: true,
  663. managedUsersEnabled: true,
  664. ssoConfig: {
  665. enabled: true,
  666. },
  667. },
  668. }
  669. fetchMock.get('express:/institutions/domains', [institution])
  670. })
  671. it('renders error', async function () {
  672. // note: this UI is a WIP
  673. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  674. render(<EmailsSection />)
  675. const button = await screen.findByRole<HTMLButtonElement>('button', {
  676. name: 'Add another email',
  677. })
  678. await userEvent.click(button)
  679. const input = screen.getByRole('textbox', { name: 'Email' })
  680. fireEvent.change(input, {
  681. target: { value: 'user@autocomplete.edu' },
  682. })
  683. const notification = await screen.findByRole('alert')
  684. within(notification).getByText(
  685. 'Your company email address has been registered under a verified domain, and cannot be added as a secondary email.',
  686. { exact: false }
  687. )
  688. })
  689. })
  690. describe('if Commons SSO then enabled, that takes priority over group UI', function () {
  691. // we shouldn't have SSO config in v1 and in v2 but adding test to ensure Commons takes priority
  692. beforeEach(async function () {
  693. await fetchMock.callHistory.flush(true)
  694. fetchMock.removeRoutes().clearHistory()
  695. const institution = {
  696. university: {
  697. id: 1234,
  698. ssoEnabled: true,
  699. name: 'Auto Complete University',
  700. },
  701. hostname: 'autocomplete.edu',
  702. confirmed: true,
  703. group: {
  704. domainCaptureEnabled: true,
  705. ssoConfig: {
  706. enabled: true,
  707. },
  708. },
  709. }
  710. fetchMock.get('express:/institutions/domains', [institution])
  711. })
  712. it('renders Commons UI', async function () {
  713. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  714. render(<EmailsSection />)
  715. const button = await screen.findByRole<HTMLButtonElement>('button', {
  716. name: 'Add another email',
  717. })
  718. await userEvent.click(button)
  719. const input = screen.getByRole('textbox', { name: 'Email' })
  720. fireEvent.change(input, {
  721. target: { value: 'user@autocomplete.edu' },
  722. })
  723. await screen.findByRole('button', {
  724. name: 'Link accounts and add email',
  725. })
  726. })
  727. })
  728. })
  729. })