From 9e3e33bce7307425928351152f187086f48c1cfd Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Tue, 25 Feb 2025 20:12:56 -0500 Subject: [PATCH 1/2] use selectioncard, clean up, and fix tests --- .../SelectionCard/SelectionCard.tsx | 17 ++- .../TPAProviders.styles.ts | 88 -------------- .../TPAProviders.test.tsx | 86 ++++++------- .../AuthenticationSettings/TPAProviders.tsx | 114 ++++++------------ 4 files changed, 98 insertions(+), 207 deletions(-) delete mode 100644 packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.styles.ts diff --git a/packages/manager/src/components/SelectionCard/SelectionCard.tsx b/packages/manager/src/components/SelectionCard/SelectionCard.tsx index 8e91d4a54ee..c85f9a0af8e 100644 --- a/packages/manager/src/components/SelectionCard/SelectionCard.tsx +++ b/packages/manager/src/components/SelectionCard/SelectionCard.tsx @@ -1,6 +1,6 @@ import { Tooltip } from '@linode/ui'; import { styled } from '@mui/material/styles'; -import Grid from '@mui/material/Grid2'; +import Grid, { Grid2Props } from '@mui/material/Grid2'; import * as React from 'react'; import { CardBase } from './CardBase'; @@ -18,11 +18,21 @@ export interface SelectionCardProps { * Additional CSS classes to apply to the root element. */ className?: string; + /** + * An optional custom data-testid + * @default selection-card + */ + 'data-testid'?: string; /** * If true, the card will be disabled and will be displayed in a disabled state. * @default false */ disabled?: boolean; + /** + * Optionally override the grid item's size + * @default { lg: 4, sm: 6, xl: 3, xs: 12 } + */ + gridSize?: Grid2Props['size']; /** * The heading of the card. * @example Linode 1GB @@ -106,6 +116,7 @@ export const SelectionCard = React.memo((props: SelectionCardProps) => { checked, className, disabled, + gridSize, heading, headingDecoration, id, @@ -156,12 +167,12 @@ export const SelectionCard = React.memo((props: SelectionCardProps) => { className={className} data-qa-selection-card data-qa-selection-card-checked={checked} - data-testid="selection-card" + data-testid={props['data-testid'] ?? 'selection-card'} disabled={disabled} id={id} onClick={handleClick} onKeyPress={handleKeyPress} - size={{ lg: 4, sm: 6, xl: 3, xs: 12 }} + size={gridSize ?? { lg: 4, sm: 6, xl: 3, xs: 12 }} sx={sxGrid} tabIndex={0} > diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.styles.ts b/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.styles.ts deleted file mode 100644 index 68c52926131..00000000000 --- a/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.styles.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Button, Notice, Paper, Typography } from '@linode/ui'; -import { styled } from '@mui/material/styles'; -import Grid from '@mui/material/Grid2'; - -export const StyledRootContainer = styled(Paper, { - label: 'StyledRootContainer', -})(({ theme }) => ({ - marginBottom: theme.spacing(3), - padding: theme.spacing(3), - paddingTop: 17, -})); - -export const StyledCopy = styled(Typography, { - label: 'StyledCopy', -})(({ theme }) => ({ - lineHeight: '1.25rem', - marginBottom: theme.spacing(2), - marginTop: theme.spacing(), - maxWidth: 960, -})); - -export const StyledProvidersListGrid = styled(Grid, { - label: 'StyledProvidersListGrid', -})(({ theme }) => ({ - '& .MuiGrid-item': { - [theme.breakpoints.down('sm')]: { - flexBasis: '100%', - maxWidth: '100%', - }, - [theme.breakpoints.down(1100)]: { - flexBasis: '50%', - maxWidth: '50%', - }, - }, - marginBottom: 0, - [theme.breakpoints.down('sm')]: { - marginTop: theme.spacing(), - }, - width: 'calc(100% + 24px)', -})); - -export const StyledButton = styled(Button, { - label: 'StyledButton', - shouldForwardProp: (propName) => propName !== 'isButtonEnabled', -})<{ isButtonEnabled: boolean }>(({ isButtonEnabled, theme }) => ({ - '& > span': { - color: theme.color.headline, - display: 'inline-block', - width: '100%', - }, - '&:hover': { - backgroundColor: theme.color.grey6, - }, - backgroundColor: - theme.name === 'light' - ? theme.tokens.color.Ultramarine[5] - : theme.tokens.color.Ultramarine[80], - borderRadius: 1, - marginTop: theme.spacing(), - minHeight: 70, - paddingLeft: `calc(${theme.spacing(3)} - 4px)`, - paddingRight: `calc(${theme.spacing(3)} - 4px)`, - [theme.breakpoints.down('md')]: { - marginLeft: 0, - }, - [theme.breakpoints.down('sm')]: { - marginLeft: 0, - marginTop: 0, - }, - width: 'calc(100% - 8px)', - ...(isButtonEnabled && { - border: `1px solid ${theme.palette.primary.main} !important`, - }), -})); - -export const StyledEnabledText = styled('span', { - label: 'StyledEnabledText', -})(({ theme }) => ({ - font: theme.font.normal, - marginLeft: 4, -})); - -export const StyledNotice = styled(Notice, { - label: 'StyledNotice', -})(({ theme }) => ({ - font: theme.font.bold, - fontSize: '0.875rem', -})); diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.test.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.test.tsx index d954fb07540..f4e969b4b57 100644 --- a/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.test.tsx +++ b/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.test.tsx @@ -1,4 +1,4 @@ -import { screen } from '@testing-library/react'; +import { screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; @@ -6,64 +6,68 @@ import { renderWithTheme } from 'src/utilities/testHelpers'; import { TPAProviders } from './TPAProviders'; -vi.mock('src/hooks/useFlags', () => ({ - __esModule: true, - useFlags: vi.fn().mockReturnValue({ - tpaProviders: [ - { - displayName: 'Google', - href: 'https://google.com', - icon: 'GoogleIcon', - name: 'google', - }, - { - displayName: 'GitHub', - href: 'https://github.com', - icon: 'GitHubIcon', - name: 'github', - }, - ], - }), -})); +import type { Provider } from 'src/featureFlags'; + +const providers: Provider[] = [ + { + displayName: 'Google', + href: 'https://google.com', + icon: 'GoogleIcon', + name: 'google', + }, + { + displayName: 'GitHub', + href: 'https://github.com', + icon: 'GitHubIcon', + name: 'github', + }, +]; + +const flags = { tpaProviders: providers }; describe('TPAProviders component', () => { it('Should render login method with Linode button', () => { - const authType = 'password'; - renderWithTheme(); + renderWithTheme(, { flags }); + const linodeButton = screen.getByTestId('Button-Cloud Manager'); - const LinodeButtonEnabled = screen.getByTestId('Enabled-Cloud Manager'); expect(linodeButton).toBeInTheDocument(); - expect(linodeButton).toHaveAttribute('aria-disabled', 'true'); - expect(LinodeButtonEnabled).toBeInTheDocument(); + expect(linodeButton).toBeDisabled(); + + const enabledText = within(linodeButton).getByText('Enabled'); + expect(enabledText).toBeVisible(); }); it('Should render login method with Google button', () => { - const authType = 'google'; - renderWithTheme(); + renderWithTheme(, { flags }); + const googleButton = screen.getByTestId('Button-Google'); - const googleButtonEnabled = screen.getByTestId('Enabled-Google'); - const noticeElement = screen.getByTestId('Notice-Google'); expect(googleButton).toBeInTheDocument(); - expect(googleButton).toHaveAttribute('aria-disabled', 'true'); - expect(googleButtonEnabled).toBeInTheDocument(); + expect(googleButton).toBeDisabled(); + + const enabledText = within(googleButton).getByText('Enabled'); + expect(enabledText).toBeVisible(); + + const noticeElement = screen.getByTestId('Notice-Google'); expect(noticeElement).toBeInTheDocument(); }); it('Should render login method with GitHub button', () => { - const authType = 'github'; - renderWithTheme(); + renderWithTheme(, { flags }); + const githubButton = screen.getByTestId('Button-GitHub'); - const githubButtonEnabled = screen.getByTestId('Enabled-GitHub'); - const noticeElement = screen.getByTestId('Notice-GitHub'); expect(githubButton).toBeInTheDocument(); - expect(githubButton).toHaveAttribute('aria-disabled', 'true'); - expect(githubButtonEnabled).toBeInTheDocument(); + expect(githubButton).toBeDisabled(); + + const enabledText = within(githubButton).getByText('Enabled'); + expect(enabledText).toBeInTheDocument(); + + const noticeElement = screen.getByTestId('Notice-GitHub'); expect(noticeElement).toBeInTheDocument(); }); test('Should open the dialog when the button is clicked', async () => { - const authType = 'password'; - const { getByTestId } = renderWithTheme( - - ); + const { + getByTestId, + } = renderWithTheme(, { flags }); + const button = getByTestId('Button-Google'); await userEvent.click(button); const dialog = getByTestId('drawer'); diff --git a/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.tsx b/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.tsx index 5a7348837d3..8413c886654 100644 --- a/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.tsx +++ b/packages/manager/src/features/Profile/AuthenticationSettings/TPAProviders.tsx @@ -1,5 +1,4 @@ -import { Box, Divider, Typography } from '@linode/ui'; -import { useTheme } from '@mui/material/styles'; +import { Box, Divider, Notice, Paper, Stack, Typography } from '@linode/ui'; import Grid from '@mui/material/Grid2'; import * as React from 'react'; @@ -8,17 +7,10 @@ import AkamaiWaveOnlyIcon from 'src/assets/icons/providers/akamai-logo-rgb-waveO import GitHubIcon from 'src/assets/icons/providers/github-logo.svg'; import GoogleIcon from 'src/assets/icons/providers/google-logo.svg'; import { Link } from 'src/components/Link'; +import { SelectionCard } from 'src/components/SelectionCard/SelectionCard'; import { useFlags } from 'src/hooks/useFlags'; import { TPADialog } from './TPADialog'; -import { - StyledButton, - StyledCopy, - StyledEnabledText, - StyledNotice, - StyledProvidersListGrid, - StyledRootContainer, -} from './TPAProviders.styles'; import type { TPAProvider } from '@linode/api-v4/lib/profile'; @@ -40,7 +32,6 @@ const linode = { }; export const TPAProviders = (props: Props) => { - const theme = useTheme(); const flags = useFlags(); // Get list of providers from LaunchDarkly @@ -61,10 +52,10 @@ export const TPAProviders = (props: Props) => { }; return ( - <> - + + Login Method - + You can use your Cloud Manager credentials or another provider such as Google or GitHub to log in to your Cloud Manager account. More information is available in{' '} @@ -72,99 +63,72 @@ export const TPAProviders = (props: Props) => { How to Enable Third Party Authentication on Your User Account . We strongly recommend setting up Two-Factor Authentication (2FA). - - + + {providersIncludingLinode.map((thisProvider) => { const ProviderIcon = icons[thisProvider.name]; const isProviderEnabled = props.authType === thisProvider.name; return ( - - { - handleProviderChange(thisProvider.name); - }} - data-testid={`Button-${thisProvider.displayName}`} - disabled={isProviderEnabled} - isButtonEnabled={isProviderEnabled} - > - - - - {thisProvider.displayName} - {isProviderEnabled ? ( - - (Enabled) - - ) : null} - - {isProviderEnabled ? : null} - - - + renderVariant={ + isProviderEnabled ? () => : undefined + } + tooltip={ + isProviderEnabled + ? `${thisProvider.displayName} is your current authentication provider.` + : undefined + } + data-testid={`Button-${thisProvider.displayName}`} + disabled={isProviderEnabled} + heading={thisProvider.displayName} + key={thisProvider.displayName} + onClick={() => handleProviderChange(thisProvider.name)} + renderIcon={() => } + subheadings={isProviderEnabled ? ['Enabled'] : []} + tooltipPlacement="bottom" + /> ); })} - - {isThirdPartyAuthEnabled ? ( -
+ + {isThirdPartyAuthEnabled && ( + {currentProvider.displayName} Authentication - + Your login credentials are currently managed via{' '} {currentProvider.displayName}. - - + + If you need to reset your password or set up Two-Factor Authentication (2FA), please visit the{' '} {`${currentProvider.displayName}` + ` website`} . - - + + To disable {currentProvider.displayName} authentication and log in using your Cloud Manager credentials, click the Cloud Manager button above. We’ll send you an e-mail with instructions on how to reset your password. - -
- ) : null} -
+ + + )} + setDialogOpen(false)} open={isDialogOpen} /> - + ); }; From 2cd6647267526269f5fa22674699502961057a78 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Tue, 25 Feb 2025 20:29:57 -0500 Subject: [PATCH 2/2] changeset --- packages/manager/.changeset/pr-11732-fixed-1740533390539.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/manager/.changeset/pr-11732-fixed-1740533390539.md diff --git a/packages/manager/.changeset/pr-11732-fixed-1740533390539.md b/packages/manager/.changeset/pr-11732-fixed-1740533390539.md new file mode 100644 index 00000000000..c4f2289f3c7 --- /dev/null +++ b/packages/manager/.changeset/pr-11732-fixed-1740533390539.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Fixed +--- + +Authentication Provider Selection Card UI regression ([#11732](https://github.com/linode/manager/pull/11732))