diff --git a/services/web/test/frontend/features/settings/components/emails/emails-row.test.tsx b/services/web/test/frontend/features/settings/components/emails/emails-row.test.tsx
new file mode 100644
index 0000000000..ce1b1d51f7
--- /dev/null
+++ b/services/web/test/frontend/features/settings/components/emails/emails-row.test.tsx
@@ -0,0 +1,113 @@
+import { render, screen } from '@testing-library/react'
+import { expect } from 'chai'
+import fetchMock from 'fetch-mock'
+import { cloneDeep } from 'lodash'
+import EmailsRow from '../../../../../../frontend/js/features/settings/components/emails/row'
+import {
+ professionalUserData,
+ unconfirmedUserData,
+} from '../../fixtures/test-user-email-data'
+import { UserEmailData } from '../../../../../../types/user-email'
+import { UserEmailsProvider } from '../../../../../../frontend/js/features/settings/context/user-email-context'
+
+function renderEmailsRow(data: UserEmailData) {
+ return render(
+
+
+
+ )
+}
+
+function getByTextContent(text: string) {
+ return screen.getAllByText(
+ (content, node) =>
+ content === text || node.children[0]?.textContent === text
+ )
+}
+
+describe('', function () {
+ beforeEach(function () {
+ window.metaAttributesCache = window.metaAttributesCache || new Map()
+ window.metaAttributesCache.set('ol-ExposedSettings', {
+ samlInitPath: '/saml',
+ hasSamlBeta: true,
+ })
+ })
+
+ describe('with unaffiliated email data', function () {
+ it('renders email info', function () {
+ renderEmailsRow(unconfirmedUserData)
+ screen.getByText('baz@overleaf.com')
+ })
+
+ it('renders actions', function () {
+ renderEmailsRow(unconfirmedUserData)
+ screen.getByRole('button', { name: 'Make Primary' })
+ })
+ })
+
+ describe('with affiliated email data', function () {
+ it('renders email info', function () {
+ renderEmailsRow(professionalUserData)
+ screen.getByText('foo@overleaf.com (primary)')
+ })
+
+ it('renders actions', function () {
+ renderEmailsRow(professionalUserData)
+ screen.getByRole('button', { name: 'Remove' })
+ })
+
+ it('renders institution info', function () {
+ renderEmailsRow(professionalUserData)
+ screen.getByText('Overleaf')
+ screen.getByText('Reader, Art History')
+ })
+ })
+
+ describe('with email data affiliated to an institution with SSO available', function () {
+ let affiliatedEmail: UserEmailData
+
+ beforeEach(function () {
+ window.metaAttributesCache.get('ol-ExposedSettings').hasSamlFeature = true
+
+ // make sure the institution has SSO available
+ affiliatedEmail = cloneDeep(professionalUserData)
+ affiliatedEmail.affiliation.institution.confirmed = true
+ affiliatedEmail.affiliation.institution.isUniversity = true
+ affiliatedEmail.affiliation.institution.ssoEnabled = true
+ })
+
+ describe('when the email is not yet linked to the institution', function () {
+ beforeEach(async function () {
+ fetchMock.reset()
+ fetchMock.get(/\/user\/emails/, [affiliatedEmail, unconfirmedUserData])
+ await fetchMock.flush(true)
+ })
+
+ it('prompts the user to link to their institutional account', function () {
+ renderEmailsRow(affiliatedEmail)
+ getByTextContent(
+ 'You can now link your Overleaf account to your Overleaf institutional account.'
+ )
+ screen.getByRole('link', { name: 'Link Accounts' })
+ })
+ })
+
+ describe('when the email is already linked to the institution', function () {
+ beforeEach(async function () {
+ affiliatedEmail.samlProviderId = '1'
+ fetchMock.reset()
+ fetchMock.get(/\/user\/emails/, [affiliatedEmail, unconfirmedUserData])
+ await fetchMock.flush(true)
+ })
+
+ it('prompts the user to login using their institutional account', function () {
+ renderEmailsRow(affiliatedEmail)
+ getByTextContent(
+ 'You can log in to Overleaf through your Overleaf institutional login.'
+ )
+ expect(screen.queryByRole('link', { name: 'Link Accounts' })).to.be.null
+ })
+ })
+ })
+})