Skip to content

Commit

Permalink
Merge branch 'main' into suppurt-unset-text-resource
Browse files Browse the repository at this point in the history
  • Loading branch information
standeren authored Jan 27, 2025
2 parents 07fbbef + 165fcf1 commit bfc2852
Show file tree
Hide file tree
Showing 27 changed files with 223 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('Navigation', () => {
});

getFilteredMenuListForOverviewPage().forEach((link) => {
expect(screen.getByRole('link', { name: getLinkName(link) })).toBeInTheDocument();
expect(screen.getByRole('link', { name: textMock(link.key) })).toBeInTheDocument();
});
});

Expand All @@ -32,9 +32,9 @@ describe('Navigation', () => {

getFilteredMenuListForOverviewPage().forEach((link) => {
if (link.featureFlagName) {
expect(screen.queryByRole('link', { name: getLinkName(link) })).not.toBeInTheDocument();
expect(screen.queryByRole('link', { name: textMock(link.key) })).not.toBeInTheDocument();
} else {
expect(screen.getByRole('link', { name: getLinkName(link) })).toBeInTheDocument();
expect(screen.getByRole('link', { name: textMock(link.key) })).toBeInTheDocument();
}
});
});
Expand All @@ -47,11 +47,11 @@ describe('Navigation', () => {
});

getFilteredMenuListForOverviewPage().forEach((link) => {
expect(screen.getByRole('link', { name: getLinkName(link) })).toBeInTheDocument();
expect(screen.getByRole('link', { name: textMock(link.key) })).toBeInTheDocument();
});
});

it('renders "beta" tag for menu items that are tagges as beta', () => {
it('renders menu items that are tagged as beta, with isBeta class', () => {
const betaItems = topBarMenuItem.filter((item) => !!item.isBeta);

// ensure any feature flags are toggled on
Expand All @@ -62,18 +62,25 @@ describe('Navigation', () => {
});

betaItems.forEach((link) => {
expect(screen.getByRole('link', { name: `${textMock(link.key)} Beta` })).toBeInTheDocument();
expect(screen.getByRole('link', { name: textMock(link.key) })).toHaveClass('isBeta');
});
});
});

const getLinkName = (linkItem: HeaderMenuItem): string => {
let name = textMock(linkItem.key);
if (linkItem.isBeta) {
name = `${name} Beta`;
}
return name;
};
it('renders menu items that are not tagged as beta, without isBeta class', () => {
const menuItemsNotBeta = getFilteredMenuListForOverviewPage().filter((item) => !item.isBeta);

// ensure any feature flags are toggled on
typedLocalStorage.setItem('featureFlags', getFeatureFlags(menuItemsNotBeta));

renderWithProviders(<Navigation />, {
startUrl: `${APP_DEVELOPMENT_BASENAME}/my-org/my-app`,
});

menuItemsNotBeta.forEach((link) => {
expect(screen.getByRole('link', { name: textMock(link.key) })).not.toHaveClass('isBeta');
});
});
});

const getFeatureFlags = (menuItems: HeaderMenuItem[]) => {
return menuItems.filter((item) => !!item.featureFlagName).map((item) => item.featureFlagName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { useTranslation } from 'react-i18next';
import { Heading } from '@digdir/designsystemet-react';
import { getFilteredMenuListForOverviewPage } from 'app-development/utils/headerMenu/headerMenuUtils';
import { Link } from 'react-router-dom';
import { StudioBetaTag } from '@studio/components';
import cn from 'classnames';
import { studioBetaTagClasses } from '@studio/components';

export const Navigation = () => {
const { t } = useTranslation();
Expand All @@ -19,10 +20,13 @@ export const Navigation = () => {
<div className={classes.links}>
{menuItems.map((menuItem) => {
return (
<Link key={menuItem.key} to={`../${menuItem.link}`} className={classes.link}>
<Link
key={menuItem.key}
to={`../${menuItem.link}`}
className={cn(classes.link, menuItem.isBeta && studioBetaTagClasses.isBeta)}
>
<menuItem.icon className={classes.icon} />
<span>{t(menuItem.key)}</span>
{menuItem.isBeta && <StudioBetaTag />}
</Link>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,3 @@
.active {
border-bottom: 2px solid var(--fds-semantic-surface-neutral-default);
}

.betaTag {
min-height: min-content;
padding: 0 var(--fds-spacing-1);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('LargeNavigationMenu', () => {
renderLargeNavigationMenu();

menuItems.forEach((item) => {
expect(screen.getByText(item.name)).toBeInTheDocument();
expect(screen.getByRole('link', { name: item.name })).toBeInTheDocument();
});
});

Expand All @@ -30,26 +30,20 @@ describe('LargeNavigationMenu', () => {
routerInitialEntries: [menuItems[0].link],
});

const activeItem = screen.getByText(menuItems[0].name);
const activeItem = screen.getByRole('link', { name: menuItems[0].name });
expect(activeItem).toHaveClass('active');
});

it('should display the beta tag for items marked as beta', () => {
it('should set "isBeta" className for menu item that is beta', () => {
renderLargeNavigationMenu();

const betaTags = screen.getAllByText('Beta');

expect(betaTags.length).toEqual(menuItems.filter((item) => item.isBeta).length);
const menuItem = screen.getByRole('link', { name: menuItems[0].name });
expect(menuItem).toHaveClass('isBeta');
});

it('should not display the beta tag for items not marked as beta', () => {
it('should not set "isBeta" className for menu item that is not beta', () => {
renderLargeNavigationMenu();

const betaTags = screen.getAllByText('Beta');

expect(menuItems.length - betaTags.length).toEqual(
menuItems.filter((item) => !item.isBeta).length,
);
const menuItem = screen.getByRole('link', { name: menuItems[1].name });
expect(menuItem).not.toHaveClass('isBeta');
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { type ReactElement } from 'react';
import classes from './LargeNavigationMenu.module.css';
import cn from 'classnames';
import { NavLink, useLocation } from 'react-router-dom';
import { StudioBetaTag, StudioPageHeader } from '@studio/components';
import { StudioPageHeader } from '@studio/components';
import { extractLastRouterParam } from 'app-development/utils/headerMenu/headerMenuUtils';
import { type NavigationMenuItem } from 'app-development/types/HeaderMenu/NavigationMenuItem';
import { usePageHeaderContext } from 'app-development/contexts/PageHeaderContext';
Expand Down Expand Up @@ -36,6 +36,7 @@ const HeaderButtonListItem = ({ menuItem }: HeaderButtonListItemProps): ReactEle
<StudioPageHeader.HeaderLink
color='dark'
variant={variant}
isBeta={menuItem.isBeta}
renderLink={(props) => (
<NavLink to={menuItem.link} {...props}>
<span
Expand All @@ -45,7 +46,6 @@ const HeaderButtonListItem = ({ menuItem }: HeaderButtonListItemProps): ReactEle
>
{menuItem.name}
</span>
{menuItem.isBeta && <StudioBetaTag className={classes.betaTag} />}
</NavLink>
)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const mockMenuItem: NavigationMenuSmallItem = {
href: menuItemLink,
openInNewTab: false,
},
isBeta: true,
isBeta: false,
};

const mockOnClick = jest.fn();
Expand All @@ -32,7 +32,7 @@ describe('SmallHeaderMenuItem', () => {
renderSmallHeaderMenuItem();

const linkElement = screen.getByRole('menuitem', {
name: `${textMock(menuItemName)} Beta`,
name: textMock(menuItemName),
});
expect(linkElement).toBeInTheDocument();
expect(linkElement).toHaveAttribute('href', menuItemLink);
Expand All @@ -44,17 +44,37 @@ describe('SmallHeaderMenuItem', () => {
});

const linkElement = screen.getByRole('menuitem', {
name: `${textMock(menuItemName)} Beta`,
name: textMock(menuItemName),
});
expect(linkElement).toHaveClass('active');
});

it('should add "isBeta" class when menuItem is beta', () => {
renderSmallHeaderMenuItem({
componentProps: { menuItem: { ...mockMenuItem, isBeta: true } },
});

const linkElement = screen.getByRole('menuitem', {
name: textMock(menuItemName),
});
expect(linkElement).toHaveClass('isBeta');
});

it('should not add "isBeta" class by default', () => {
renderSmallHeaderMenuItem();

const linkElement = screen.getByRole('menuitem', {
name: textMock(menuItemName),
});
expect(linkElement).not.toHaveClass('isBeta');
});

it('should call onClick when the NavLink is clicked', async () => {
const user = userEvent.setup();
renderSmallHeaderMenuItem();

const linkElement = screen.getByRole('menuitem', {
name: `${textMock(menuItemName)} Beta`,
name: textMock(menuItemName),
});
await user.click(linkElement);

Expand All @@ -76,7 +96,7 @@ describe('SmallHeaderMenuItem', () => {
});

const linkElement = screen.getByRole('menuitem', {
name: `${textMock('testMenuItem')} Beta`,
name: textMock('testMenuItem'),
});
expect(linkElement).toHaveAttribute('target', '_blank');
expect(linkElement).toHaveAttribute('rel', 'noopener noreferrer');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
import { DropdownMenu } from '@digdir/designsystemet-react';
import { extractLastRouterParam } from 'app-development/utils/headerMenu/headerMenuUtils';
import { type NavigationMenuSmallItem } from 'app-development/types/HeaderMenu/NavigationMenuSmallItem';
import { StudioBetaTag } from '@studio/components';
import { studioBetaTagClasses } from '@studio/components';

export type SmallHeaderMenuItemProps = {
menuItem: NavigationMenuSmallItem;
Expand Down Expand Up @@ -35,13 +35,13 @@ export const SmallHeaderMenuItem = ({
return (
<DropdownMenu.Item key={menuItem.name} asChild className={linkItemClassName}>
<NavLink
className={menuItem.isBeta && studioBetaTagClasses.isBeta}
to={menuItem.action.href}
onClick={onClick}
target={menuItem.action.openInNewTab ? '_blank' : ''}
rel={menuItem.action.openInNewTab ? 'noopener noreferrer' : ''}
>
{t(menuItem.name)}
{menuItem.isBeta && <StudioBetaTag />}
</NavLink>
</DropdownMenu.Item>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import { QueryKey } from 'app-shared/types/QueryKey';
import { PackagesRouter } from 'app-shared/navigation/PackagesRouter';
import { app, org } from '@studio/testing/testids';
import { useUserOrgPermissionQuery } from '../../hooks/queries/useUserOrgPermissionsQuery';

jest.mock('../../hooks/queries/useUserOrgPermissionsQuery');

(useUserOrgPermissionQuery as jest.Mock).mockReturnValue({
data: { canCreateOrgRepo: true },
});

const mockServiceFullName: string = `${org}/${app}`;
const mockUser: User = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { screen } from '@testing-library/react';
import {
NewApplicationForm,
type NewApplicationFormProps,
Expand All @@ -8,7 +8,11 @@ import {
import { type User } from 'app-shared/types/Repository';
import { type Organization } from 'app-shared/types/Organization';
import userEvent from '@testing-library/user-event';
import { textMock } from '../../../testing/mocks/i18nMock';
import { textMock } from '@studio/testing/mocks/i18nMock';
import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext';
import { renderWithProviders } from '../../testing/mocks';
import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import { useUserOrgPermissionQuery } from '../../hooks/queries/useUserOrgPermissionsQuery';

const mockOnSubmit = jest.fn();

Expand Down Expand Up @@ -51,12 +55,18 @@ const defaultProps: NewApplicationFormProps = {
actionableElement: mockCancelComponentButton,
};

jest.mock('../../hooks/queries/useUserOrgPermissionsQuery');

(useUserOrgPermissionQuery as jest.Mock).mockReturnValue({
data: { canCreateOrgRepo: true },
});

describe('NewApplicationForm', () => {
afterEach(jest.clearAllMocks);

it('calls onSubmit when form is submitted with valid data', async () => {
const user = userEvent.setup();
render(<NewApplicationForm {...defaultProps} />);
renderNewApplicationForm();

const select = screen.getByLabelText(textMock('general.service_owner'));
await user.click(select);
Expand All @@ -81,7 +91,7 @@ describe('NewApplicationForm', () => {

it('does not call onSubmit when form is submitted with invalid data', async () => {
const user = userEvent.setup();
render(<NewApplicationForm {...defaultProps} />);
renderNewApplicationForm();

const select = screen.getByLabelText(textMock('general.service_owner'));
await user.click(select);
Expand All @@ -96,4 +106,47 @@ describe('NewApplicationForm', () => {

expect(mockOnSubmit).toHaveBeenCalledTimes(0);
});

it('should notify the user if they lack permission to create a new application for the organization and disable the "Create" button', async () => {
const user = userEvent.setup();
(useUserOrgPermissionQuery as jest.Mock).mockReturnValue({
data: { canCreateOrgRepo: false },
});
renderNewApplicationForm(defaultProps);

const serviceOwnerSelect = screen.getByLabelText(textMock('general.service_owner'));
await user.selectOptions(serviceOwnerSelect, mockOrg.username);
expect(
await screen.findByText(textMock('dashboard.missing_service_owner_rights_error_message')),
).toBeInTheDocument();
expect(screen.getByRole('button', { name: mockSubmitbuttonText })).toBeDisabled();
});

it('should enable the "Create" button and not display an error if the user has permission to create an organization', async () => {
const user = userEvent.setup();
(useUserOrgPermissionQuery as jest.Mock).mockReturnValue({
data: { canCreateOrgRepo: true },
});
renderNewApplicationForm(defaultProps);

const serviceOwnerSelect = screen.getByLabelText(textMock('general.service_owner'));
await user.selectOptions(serviceOwnerSelect, mockOrg.username);
expect(
screen.queryByText(textMock('dashboard.missing_service_owner_rights_error_message')),
).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: mockSubmitbuttonText })).toBeEnabled();
});
});

function renderNewApplicationForm(
newApplicationFormProps?: Partial<NewApplicationFormProps>,
services?: Partial<ServicesContextProps>,
) {
return renderWithProviders(
<NewApplicationForm {...defaultProps} {...newApplicationFormProps} />,
{
queries: services,
queryClient: createQueryClientMock(),
},
);
}
Loading

0 comments on commit bfc2852

Please sign in to comment.