mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-08-31 01:14:08 +08:00
test: move Microsoft user link coverage to client (#68367)
Co-authored-by: sembauke <sembauke@users.noreply.github.com>
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user