mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-27 01:19:00 +02:00
Follow-up to: https://github.com/Unleash/unleash/pull/8642 Introduces a reusable `Highlight` component that leverages the Context API pattern, enabling highlight effects to be triggered from anywhere in the application. This update refactors the existing highlight effect in the event timeline to use the new Highlight component and extends the functionality to include the Unleash AI experiment, triggered by its entry in the "New in Unleash" section.
383 lines
12 KiB
TypeScript
383 lines
12 KiB
TypeScript
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
import { MemoryRouter, Routes, Route } from 'react-router-dom';
|
|
import { FeatureView } from '../feature/FeatureView/FeatureView';
|
|
import { ThemeProvider } from 'themes/ThemeProvider';
|
|
import { AccessProvider } from '../providers/AccessProvider/AccessProvider';
|
|
import { AnnouncerProvider } from '../common/Announcer/AnnouncerProvider/AnnouncerProvider';
|
|
import { testServerRoute, testServerSetup } from '../../utils/testServer';
|
|
import { UIProviderContainer } from '../providers/UIProvider/UIProviderContainer';
|
|
import type React from 'react';
|
|
import type { FC } from 'react';
|
|
import type { IPermission } from '../../interfaces/user';
|
|
import { SWRConfig } from 'swr';
|
|
import type { ProjectMode } from '../project/Project/hooks/useProjectEnterpriseSettingsForm';
|
|
import { StickyProvider } from 'component/common/Sticky/StickyProvider';
|
|
import { HighlightProvider } from 'component/common/Highlight/HighlightProvider';
|
|
|
|
const server = testServerSetup();
|
|
|
|
const projectWithCollaborationMode = (mode: ProjectMode) =>
|
|
testServerRoute(server, '/api/admin/projects/default/overview', { mode });
|
|
|
|
const changeRequestsEnabledIn = (
|
|
env: 'development' | 'production' | 'custom',
|
|
) =>
|
|
testServerRoute(
|
|
server,
|
|
'/api/admin/projects/default/change-requests/config',
|
|
[
|
|
{
|
|
environment: 'development',
|
|
type: 'development',
|
|
requiredApprovals: null,
|
|
changeRequestEnabled: env === 'development',
|
|
},
|
|
{
|
|
environment: 'production',
|
|
type: 'production',
|
|
requiredApprovals: 1,
|
|
changeRequestEnabled: env === 'production',
|
|
},
|
|
{
|
|
environment: 'custom',
|
|
type: 'production',
|
|
requiredApprovals: null,
|
|
changeRequestEnabled: env === 'custom',
|
|
},
|
|
],
|
|
);
|
|
|
|
const uiConfigForEnterprise = () =>
|
|
testServerRoute(server, '/api/admin/ui-config', {
|
|
environment: 'Open Source',
|
|
flags: {
|
|
changeRequests: true,
|
|
},
|
|
versionInfo: {
|
|
current: { oss: '4.18.0-beta.5', enterprise: '4.17.0-beta.1' },
|
|
},
|
|
disablePasswordAuth: false,
|
|
});
|
|
|
|
const setupOtherRoutes = (feature: string) => {
|
|
testServerRoute(
|
|
server,
|
|
'api/admin/projects/default/change-requests/pending',
|
|
[],
|
|
);
|
|
testServerRoute(server, `api/admin/client-metrics/features/${feature}`, {
|
|
version: 1,
|
|
maturity: 'stable',
|
|
featureName: feature,
|
|
lastHourUsage: [],
|
|
seenApplications: [],
|
|
});
|
|
testServerRoute(server, `api/admin/features/${feature}/tags`, {
|
|
version: 1,
|
|
tags: [],
|
|
});
|
|
testServerRoute(server, `api/admin/tag-types`, {
|
|
tagTypes: [],
|
|
version: 1,
|
|
});
|
|
testServerRoute(server, `api/admin/tags/simple`, {
|
|
version: 1,
|
|
tags: [],
|
|
});
|
|
|
|
testServerRoute(server, `api/admin/strategies`, {
|
|
version: 1,
|
|
strategies: [
|
|
{
|
|
displayName: 'Standard',
|
|
name: 'default',
|
|
editable: false,
|
|
description:
|
|
'The standard strategy is strictly on / off for your entire userbase.',
|
|
parameters: [],
|
|
deprecated: false,
|
|
},
|
|
{
|
|
displayName: 'UserIDs',
|
|
name: 'userWithId',
|
|
editable: false,
|
|
description:
|
|
'Enable the feature for a specific set of userIds.',
|
|
parameters: [
|
|
{
|
|
name: 'userIds',
|
|
type: 'list',
|
|
description: '',
|
|
required: false,
|
|
},
|
|
],
|
|
deprecated: false,
|
|
},
|
|
],
|
|
});
|
|
};
|
|
|
|
const userHasPermissions = (permissions: Array<IPermission>) => {
|
|
testServerRoute(server, 'api/admin/user', {
|
|
user: {
|
|
isAPI: false,
|
|
id: 2,
|
|
name: 'Test',
|
|
email: 'test@getunleash.ai',
|
|
imageUrl:
|
|
'https://gravatar.com/avatar/e55646b526ff342ff8b43721f0cbdd8e?size=42&default=retro',
|
|
seenAt: '2022-11-29T08:21:52.581Z',
|
|
loginAttempts: 0,
|
|
createdAt: '2022-11-21T10:10:33.074Z',
|
|
},
|
|
permissions,
|
|
feedback: [],
|
|
splash: {},
|
|
});
|
|
};
|
|
const userIsMemberOfProjects = (projects: string[]) => {
|
|
userHasPermissions(
|
|
projects.map((project) => ({
|
|
project,
|
|
environment: 'irrelevant',
|
|
permission: 'irrelevant',
|
|
})),
|
|
);
|
|
};
|
|
|
|
const featureEnvironments = (
|
|
feature: string,
|
|
environments: Array<{ name: string; strategies: Array<string> }>,
|
|
) => {
|
|
testServerRoute(server, `/api/admin/projects/default/features/${feature}`, {
|
|
environments: environments.map((env) => ({
|
|
name: env.name,
|
|
enabled: false,
|
|
type: 'production',
|
|
sortOrder: 1,
|
|
strategies: env.strategies.map((strategy) => ({
|
|
name: strategy,
|
|
id: Math.random(),
|
|
constraints: [],
|
|
parameters: [],
|
|
sortOrder: 1,
|
|
})),
|
|
})),
|
|
name: feature,
|
|
impressionData: false,
|
|
description: '',
|
|
project: 'default',
|
|
stale: false,
|
|
variants: [],
|
|
createdAt: '2022-11-14T08:16:33.338Z',
|
|
lastSeenAt: null,
|
|
type: 'release',
|
|
archived: false,
|
|
children: [],
|
|
dependencies: [],
|
|
});
|
|
};
|
|
|
|
const UnleashUiSetup: FC<{
|
|
path: string;
|
|
pathTemplate: string;
|
|
children?: React.ReactNode;
|
|
}> = ({ children, path, pathTemplate }) => (
|
|
<SWRConfig
|
|
value={{
|
|
provider: () => new Map(),
|
|
isVisible() {
|
|
return true;
|
|
},
|
|
}}
|
|
>
|
|
<UIProviderContainer>
|
|
<AccessProvider>
|
|
<MemoryRouter initialEntries={[path]}>
|
|
<ThemeProvider>
|
|
<AnnouncerProvider>
|
|
<StickyProvider>
|
|
<HighlightProvider>
|
|
<Routes>
|
|
<Route
|
|
path={pathTemplate}
|
|
element={children}
|
|
/>
|
|
</Routes>
|
|
</HighlightProvider>
|
|
</StickyProvider>
|
|
</AnnouncerProvider>
|
|
</ThemeProvider>
|
|
</MemoryRouter>
|
|
</AccessProvider>
|
|
</UIProviderContainer>
|
|
</SWRConfig>
|
|
);
|
|
|
|
const strategiesAreDisplayed = async (
|
|
firstStrategy: string,
|
|
secondStrategy: string,
|
|
) => {
|
|
await screen.findByText(firstStrategy);
|
|
await screen.findByText(secondStrategy);
|
|
};
|
|
|
|
const getDeleteButtons = async () => {
|
|
const removeMenus = screen.getAllByTestId(`STRATEGY_REMOVE_MENU_BTN`);
|
|
const deleteButtons: HTMLElement[] = [];
|
|
|
|
await Promise.all(
|
|
removeMenus.map(async (menu) => {
|
|
fireEvent.click(menu);
|
|
const removeButton = await screen.findAllByTestId(
|
|
'STRATEGY_FORM_REMOVE_ID',
|
|
);
|
|
deleteButtons.push(...removeButton);
|
|
}),
|
|
);
|
|
return deleteButtons;
|
|
};
|
|
|
|
const deleteButtonsActiveInChangeRequestEnv = async () => {
|
|
const deleteButtons = await getDeleteButtons();
|
|
expect(deleteButtons.length).toBe(3);
|
|
|
|
// wait for change request config to be loaded
|
|
await waitFor(() => {
|
|
// production
|
|
const productionStrategyDeleteButton = deleteButtons[1];
|
|
expect(productionStrategyDeleteButton).not.toHaveClass('Mui-disabled');
|
|
});
|
|
await waitFor(() => {
|
|
// custom env
|
|
const customEnvStrategyDeleteButton = deleteButtons[2];
|
|
expect(customEnvStrategyDeleteButton).toHaveClass('Mui-disabled');
|
|
});
|
|
};
|
|
|
|
const deleteButtonsInactiveInChangeRequestEnv = async () => {
|
|
const deleteButtons = await getDeleteButtons();
|
|
expect(deleteButtons.length).toBe(3);
|
|
|
|
// wait for change request config to be loaded
|
|
await waitFor(() => {
|
|
// production
|
|
const productionStrategyDeleteButton = deleteButtons[1];
|
|
expect(productionStrategyDeleteButton).toHaveClass('Mui-disabled');
|
|
});
|
|
await waitFor(() => {
|
|
// custom env
|
|
const customEnvStrategyDeleteButton = deleteButtons[2];
|
|
expect(customEnvStrategyDeleteButton).toHaveClass('Mui-disabled');
|
|
});
|
|
};
|
|
|
|
const copyButtonsActiveInOtherEnv = async () => {
|
|
const copyButtons = await screen.findAllByTestId('STRATEGY_FORM_COPY_ID');
|
|
expect(copyButtons.length).toBe(2);
|
|
|
|
// production
|
|
const productionStrategyCopyButton = copyButtons[0];
|
|
expect(productionStrategyCopyButton).toBeDisabled();
|
|
|
|
// custom env
|
|
const customEnvStrategyCopyButton = copyButtons[1];
|
|
expect(customEnvStrategyCopyButton).not.toBeDisabled();
|
|
};
|
|
|
|
const openEnvironments = async (envNames: string[]) => {
|
|
for (const env of envNames) {
|
|
(await screen.findAllByText(env))[1].click();
|
|
}
|
|
};
|
|
|
|
test('open mode + non-project member can perform basic change request actions', async () => {
|
|
const project = 'default';
|
|
const featureName = 'test';
|
|
featureEnvironments(featureName, [
|
|
{ name: 'development', strategies: [] },
|
|
{ name: 'production', strategies: ['userWithId'] },
|
|
{ name: 'custom', strategies: ['default'] },
|
|
]);
|
|
userIsMemberOfProjects([]);
|
|
changeRequestsEnabledIn('production');
|
|
projectWithCollaborationMode('open');
|
|
uiConfigForEnterprise();
|
|
setupOtherRoutes(featureName);
|
|
|
|
render(
|
|
<UnleashUiSetup
|
|
pathTemplate='/projects/:projectId/features/:featureId/*'
|
|
path={`/projects/${project}/features/${featureName}`}
|
|
>
|
|
<FeatureView />
|
|
</UnleashUiSetup>,
|
|
);
|
|
|
|
await openEnvironments(['development', 'production', 'custom']);
|
|
|
|
await strategiesAreDisplayed('UserIDs', 'Standard');
|
|
await deleteButtonsActiveInChangeRequestEnv();
|
|
await copyButtonsActiveInOtherEnv();
|
|
});
|
|
|
|
test('protected mode + project member can perform basic change request actions', async () => {
|
|
const project = 'default';
|
|
const featureName = 'test';
|
|
featureEnvironments(featureName, [
|
|
{ name: 'development', strategies: [] },
|
|
{ name: 'production', strategies: ['userWithId'] },
|
|
{ name: 'custom', strategies: ['default'] },
|
|
]);
|
|
userIsMemberOfProjects([project]);
|
|
changeRequestsEnabledIn('production');
|
|
projectWithCollaborationMode('protected');
|
|
uiConfigForEnterprise();
|
|
setupOtherRoutes(featureName);
|
|
|
|
render(
|
|
<UnleashUiSetup
|
|
pathTemplate='/projects/:projectId/features/:featureId/*'
|
|
path={`/projects/${project}/features/${featureName}`}
|
|
>
|
|
<FeatureView />
|
|
</UnleashUiSetup>,
|
|
);
|
|
|
|
await openEnvironments(['development', 'production', 'custom']);
|
|
|
|
await strategiesAreDisplayed('UserIDs', 'Standard');
|
|
await deleteButtonsActiveInChangeRequestEnv();
|
|
await copyButtonsActiveInOtherEnv();
|
|
});
|
|
|
|
test.skip('protected mode + non-project member cannot perform basic change request actions', async () => {
|
|
const project = 'default';
|
|
const featureName = 'test';
|
|
featureEnvironments(featureName, [
|
|
{ name: 'development', strategies: [] },
|
|
{ name: 'production', strategies: ['userWithId'] },
|
|
{ name: 'custom', strategies: ['default'] },
|
|
]);
|
|
userIsMemberOfProjects([]);
|
|
changeRequestsEnabledIn('production');
|
|
projectWithCollaborationMode('protected');
|
|
uiConfigForEnterprise();
|
|
setupOtherRoutes(featureName);
|
|
|
|
render(
|
|
<UnleashUiSetup
|
|
pathTemplate='/projects/:projectId/features/:featureId/*'
|
|
path={`/projects/${project}/features/${featureName}`}
|
|
>
|
|
<FeatureView />
|
|
</UnleashUiSetup>,
|
|
);
|
|
|
|
await openEnvironments(['development', 'production', 'custom']);
|
|
|
|
await strategiesAreDisplayed('UserIDs', 'Standard');
|
|
await deleteButtonsInactiveInChangeRequestEnv();
|
|
await copyButtonsActiveInOtherEnv();
|
|
});
|