test: move Microsoft user link coverage to client (#68367)

Co-authored-by: sembauke <sembauke@users.noreply.github.com>
This commit is contained in:
Sem Bauke
2026-08-21 17:09:20 +02:00
committed by GitHub
parent 5993070e9e
commit c28ced9c2d
3 changed files with 178 additions and 111 deletions
@@ -1,44 +1,191 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { describe, expect, test, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { legacy_createStore as createStore } from 'redux';
import { describe, expect, it, vi } from 'vitest';
import i18nTestConfig from '../../../../i18n/config-for-tests';
import translations from '../../../../i18n/locales/english/translations.json';
import { linkMsUsername, unlinkMsUsername } from '../../../redux/actions';
import LinkMsUser from './link-ms-user';
vi.mock('react-redux', () => ({
connect: () => (Component: React.ComponentType<Record<string, unknown>>) =>
Component
}));
vi.unmock('react-i18next');
vi.mock('../../../components/Header/components/login', () => ({
default: () => null
}));
i18nTestConfig.addResourceBundle(
'en',
'translations',
translations,
true,
true
);
const LinkedMicrosoftUser = LinkMsUser as React.ComponentType<
Record<string, unknown>
>;
interface TestUser {
msUsername?: string | null;
}
function createTestStore({
isProcessing = false,
sessionUser
}: {
isProcessing?: boolean;
sessionUser: TestUser | null;
}) {
return createStore(() => ({
app: {
isProcessing,
user: {
sessionUser
}
}
}));
}
function renderWithUser({
isProcessing,
sessionUser
}: {
isProcessing?: boolean;
sessionUser: TestUser | null;
}) {
const reduxStore = createTestStore({ isProcessing, sessionUser });
const dispatch = vi.spyOn(reduxStore, 'dispatch');
render(
<Provider store={reduxStore}>
<I18nextProvider i18n={i18nTestConfig}>
<LinkMsUser />
</I18nextProvider>
</Provider>
);
return { dispatch };
}
describe('LinkMsUser', () => {
test('renders an unlink account button for linked Microsoft users', () => {
const unlinkMsUsername = vi.fn();
it('renders sign-in guidance for signed-out users', () => {
renderWithUser({ sessionUser: null });
render(
<LinkedMicrosoftUser
isProcessing={false}
isSignedIn={true}
linkMsUsername={vi.fn()}
msUsername='dotnetcamper'
setIsProcessing={vi.fn()}
unlinkMsUsername={unlinkMsUsername}
/>
);
expect(
screen.getByRole('heading', {
level: 2,
name: translations.learn.ms['link-header']
})
).toBeInTheDocument();
expect(
screen.getByText(translations.learn.ms['link-signin'])
).toBeInTheDocument();
expect(
screen.getByRole('link', { name: translations.buttons['sign-in'] })
).toHaveAttribute('href', expect.stringMatching(/\/signin$/));
});
it('renders linked Microsoft account state', async () => {
const user = userEvent.setup();
const { dispatch } = renderWithUser({
sessionUser: { msUsername: 'dotnetcamper' }
});
expect(
screen.getByText(
i18nTestConfig.t('learn.ms.linked', {
msUsername: 'dotnetcamper'
})
)
).toBeInTheDocument();
const unlinkButton = screen.getByRole('button', {
name: 'buttons.unlink-account'
name: translations.buttons['unlink-account']
});
expect(unlinkButton).toBeInTheDocument();
expect(unlinkButton).not.toHaveAttribute('aria-disabled', 'true');
fireEvent.click(unlinkButton);
await user.click(unlinkButton);
expect(unlinkMsUsername).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith(unlinkMsUsername());
});
it('disables the unlink button while processing', () => {
renderWithUser({
isProcessing: true,
sessionUser: { msUsername: 'dotnetcamper' }
});
expect(
screen.getByRole('button', {
name: translations.buttons['unlink-account']
})
).toHaveAttribute('aria-disabled', 'true');
});
it('renders unlinked Microsoft account guidance and validates transcript links', async () => {
const user = userEvent.setup();
const { dispatch } = renderWithUser({ sessionUser: { msUsername: null } });
expect(
screen.getByRole('heading', {
level: 2,
name: translations.learn.ms['link-header']
})
).toBeInTheDocument();
expect(
screen.getByText(translations.learn.ms.unlinked)
).toBeInTheDocument();
expect(
screen.getByRole('link', {
name: 'https://learn.microsoft.com/users/me/transcript'
})
).toHaveAttribute(
'href',
'https://learn.microsoft.com/users/me/transcript'
);
expect(
screen.getByText(translations.learn.ms['link-li-2'])
).toBeInTheDocument();
expect(
screen.getByText(translations.learn.ms['link-li-3'])
).toBeInTheDocument();
expect(
screen.getByText(translations.learn.ms['link-li-4'])
).toBeInTheDocument();
expect(
screen.getByText(translations.learn.ms['link-li-6'])
).toBeInTheDocument();
const transcriptInput = screen.getByLabelText(
translations.learn.ms['transcript-label']
);
expect(transcriptInput).toHaveAttribute(
'placeholder',
'https://learn.microsoft.com/en-us/users/username/transcript/transcriptId'
);
const linkButton = screen.getByRole('button', {
name: translations.buttons['link-account']
});
expect(linkButton).toHaveAttribute('aria-disabled', 'true');
await user.type(transcriptInput, 'https://learn.microsoft.com');
expect(linkButton).toHaveAttribute('aria-disabled', 'true');
expect(
screen.getByText(
/Your transcript link is not correct, it should have the following form:/
)
).toBeInTheDocument();
await user.clear(transcriptInput);
const validTranscriptUrl =
'https://learn.microsoft.com/en-us/users/dotnetcamper/transcript/abc123';
await user.type(transcriptInput, validTranscriptUrl);
expect(linkButton).not.toHaveAttribute('aria-disabled', 'true');
await user.click(linkButton);
expect(dispatch).toHaveBeenCalledWith(
linkMsUsername({ msTranscriptUrl: validTranscriptUrl })
);
});
});
@@ -100,7 +100,7 @@ function LinkMsUser({
block={true}
variant='primary'
disabled={isProcessing}
onClick={unlinkMsUsername}
onClick={() => unlinkMsUsername()}
>
{t('buttons.unlink-account')}
</Button>
+2 -82
View File
@@ -10,56 +10,12 @@ test.beforeEach(async ({ page }) => {
);
});
test.describe('Link MS user component (signed-out user)', () => {
test.use({ storageState: { cookies: [], origins: [] } });
test('should display the page content with a signin CTA', async ({
page
}) => {
await expect(
page.getByRole('heading', {
name: 'Trophy - Write Your First Code Using C#',
level: 1
})
).toBeVisible();
await expect(
page.getByRole('heading', {
name: translations.learn.ms['link-header'],
level: 2
})
).toBeVisible();
await expect(
page.getByText(translations.learn.ms['link-signin'])
).toBeVisible();
// There are 2 sign in button on the page: one in the navbar, and one in the page content
const signInButtons = await page
.getByRole('link', { name: translations.buttons['sign-in'] })
.all();
expect(signInButtons).toHaveLength(2);
});
});
test.describe('Link MS user component (signed-in user)', () => {
test.describe('Link MS user component unlink flow', () => {
test.afterEach(() => {
execSync('node ../tools/scripts/seed/seed-ms-username');
});
test("should recognize the user's MS account", async ({ page }) => {
await expect(
page.getByRole('heading', {
name: 'Trophy - Write Your First Code Using C#',
level: 1
})
).toBeVisible();
await expect(page.locator('main')).toHaveText(
/The Microsoft account with username "certifieduser" is currently linked to your freeCodeCamp account\. If this is not your Microsoft username, remove the link\./
);
});
test('should allow the user to unlink their MS account and display a form for re-link', async ({
test('should allow the user to unlink their MS account and display a form for relinking', async ({
page
}) => {
const unlinkButton = page.getByRole('button', {
@@ -77,46 +33,10 @@ test.describe('Link MS user component (signed-in user)', () => {
})
).toBeVisible();
await expect(page.getByText(translations.learn.ms.unlinked)).toBeVisible();
await expect(
page.getByRole('listitem').filter({
hasText:
'Using a browser where you are logged into your Microsoft account, go to https://learn.microsoft.com/users/me/transcript'
})
).toBeVisible();
await expect(
page
.getByRole('listitem')
.filter({ hasText: translations.learn.ms['link-li-2'] })
).toBeVisible();
await expect(
page
.getByRole('listitem')
.filter({ hasText: translations.learn.ms['link-li-3'] })
).toBeVisible();
await expect(
page
.getByRole('listitem')
.filter({ hasText: translations.learn.ms['link-li-4'] })
).toBeVisible();
await expect(
page.getByRole('listitem').filter({
hasText:
'Paste the URL into the input below, it should look similar to this: https://learn.microsoft.com/LOCALE/users/USERNAME/transcript/ID'
})
).toBeVisible();
await expect(
page
.getByRole('listitem')
.filter({ hasText: translations.learn.ms['link-li-6'] })
).toBeVisible();
const transcriptLinkInput = page.getByLabel(
translations.learn.ms['transcript-label']
);
await expect(transcriptLinkInput).toBeVisible();
await expect(transcriptLinkInput).toHaveAttribute(
'placeholder',
'https://learn.microsoft.com/en-us/users/username/transcript/transcriptId'
);
});
});