import { vi } from 'vitest';
import { screen, waitFor } from '@testing-library/react';
import { render } from 'utils/testRenderer';
import { testServerRoute, testServerSetup } from 'utils/testServer';
import { FeatureArchiveDialog } from './FeatureArchiveDialog.tsx';
const server = testServerSetup();
const setupHappyPathForChangeRequest = () => {
testServerRoute(
server,
'/api/admin/projects/projectId/environments/development/change-requests',
{},
'post',
);
testServerRoute(
server,
'/api/admin/projects/projectId/change-requests/config',
[
{
environment: 'development',
type: 'development',
requiredApprovals: 1,
changeRequestEnabled: true,
},
],
);
};
const setupArchiveValidation = (orphanParents: string[]) => {
testServerRoute(server, '/api/admin/ui-config', {
versionInfo: {
current: { oss: 'version', enterprise: 'version' },
},
});
testServerRoute(
server,
'/api/admin/projects/projectId/archive/validate',
{
hasDeletedDependencies: true,
parentsWithChildFeatures: orphanParents,
},
'post',
);
};
const setupFlagScheduleConflicts = (
scheduledCRs: { id: number; title?: string }[],
) => {
testServerRoute(
server,
'/api/admin/projects/projectId/change-requests/scheduled',
scheduledCRs,
);
};
test('Add single archive feature change to change request', async () => {
const onClose = vi.fn();
const onConfirm = vi.fn();
setupHappyPathForChangeRequest();
setupArchiveValidation([]);
render(
,
);
expect(screen.getByText('Archive feature flag')).toBeInTheDocument();
await screen.findByText(
'Archiving flags with dependencies will also remove those dependencies.',
);
const button = await screen.findByText('Add change to draft');
button.click();
await waitFor(() => {
expect(onConfirm).toBeCalledTimes(1);
});
expect(onClose).toBeCalledTimes(1);
});
test('Add multiple archive feature changes to change request', async () => {
const onClose = vi.fn();
const onConfirm = vi.fn();
setupHappyPathForChangeRequest();
setupArchiveValidation([]);
render(
,
);
await screen.findByText('Archive feature flags');
await screen.findByText(
'Archiving flags with dependencies will also remove those dependencies.',
);
const button = await screen.findByText('Add to change request');
button.click();
await waitFor(() => {
expect(onConfirm).toBeCalledTimes(1);
});
expect(onClose).toBeCalledTimes(1);
});
test('Skip change request does not affect archive', async () => {
const onClose = vi.fn();
const onConfirm = vi.fn();
setupHappyPathForChangeRequest();
setupArchiveValidation([]);
render(
,
{ permissions: [{ permission: 'SKIP_CHANGE_REQUEST' }] },
);
await screen.findByText('Archive feature flag');
const button = await screen.findByText('Add change to draft');
await waitFor(() => expect(button).toBeEnabled());
button.click();
await waitFor(() => {
expect(onClose).toBeCalledTimes(1);
});
expect(onConfirm).toBeCalledTimes(1);
});
test('Show error message when multiple parents of orphaned children are archived', async () => {
const onClose = vi.fn();
const onConfirm = vi.fn();
setupArchiveValidation(['parentA', 'parentB']);
render(
,
);
await screen.findByText('2 feature flags');
await screen.findByText(
'have child features that depend on them and are not part of the archive operation. These parent features can not be archived:',
);
expect(
screen.queryByText(
'Archiving flags with dependencies will also remove those dependencies.',
),
).not.toBeInTheDocument();
});
test('Show error message when 1 parent of orphaned children is archived', async () => {
const onClose = vi.fn();
const onConfirm = vi.fn();
setupArchiveValidation(['parent']);
render(
,
);
await screen.findByText('parent');
await screen.findByText(
'has child features that depend on it and are not part of the archive operation.',
);
expect(
screen.queryByText(
'Archiving flags with dependencies will also remove those dependencies.',
),
).not.toBeInTheDocument();
});
describe('schedule conflicts', () => {
test.each([1, 2, 5, 10])(
'Shows a warning when archiving %s flag(s) with change request schedule conflicts',
async (numberOfFlags) => {
setupArchiveValidation([]);
const featureIds = new Array(numberOfFlags)
.fill(0)
.map((_, i) => `feature-flag-${i + 1}`);
const conflicts = [{ id: 5, title: 'crTitle' }, { id: 6 }];
setupFlagScheduleConflicts(conflicts);
render(
,
);
const links = await screen.findAllByRole('link');
expect(links).toHaveLength(2);
expect(links[0]).toHaveTextContent('#5 (crTitle)');
expect(links[0]).toHaveAccessibleDescription('Change request 5');
expect(links[1]).toHaveTextContent('Change request #6');
expect(links[1]).toHaveAccessibleDescription('Change request 6');
const alerts = await screen.findAllByRole('alert');
expect(alerts).toHaveLength(2);
expect(alerts[1]).toHaveTextContent(
'This archive operation would conflict with 2 scheduled change request(s).',
);
},
);
});