mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-21 13:47:39 +02:00
test: move import test from cypress to RTL to make it less flaky (#6982)
This commit is contained in:
parent
d698eccb4a
commit
d01100fbf0
1
.github/workflows/e2e.frontend.yaml
vendored
1
.github/workflows/e2e.frontend.yaml
vendored
@ -12,7 +12,6 @@ jobs:
|
|||||||
- projects/access.spec.ts
|
- projects/access.spec.ts
|
||||||
- projects/overview.spec.ts
|
- projects/overview.spec.ts
|
||||||
- segments/segments.spec.ts
|
- segments/segments.spec.ts
|
||||||
- import/import.spec.ts
|
|
||||||
steps:
|
steps:
|
||||||
- name: Dump GitHub context
|
- name: Dump GitHub context
|
||||||
env:
|
env:
|
||||||
|
@ -1,136 +0,0 @@
|
|||||||
///<reference path="../../global.d.ts" />
|
|
||||||
|
|
||||||
describe('imports', () => {
|
|
||||||
const baseUrl = Cypress.config().baseUrl;
|
|
||||||
const randomSeed = String(Math.random()).split('.')[1];
|
|
||||||
const randomFeatureName = `cypress-features${randomSeed}`;
|
|
||||||
const userIds: any[] = [];
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
cy.runBefore();
|
|
||||||
cy.login_UI();
|
|
||||||
for (let i = 1; i <= 2; i++) {
|
|
||||||
cy.request('POST', `${baseUrl}/api/admin/user-admin`, {
|
|
||||||
name: `unleash-e2e-user${i}-${randomFeatureName}`,
|
|
||||||
email: `unleash-e2e-user${i}-${randomFeatureName}@test.com`,
|
|
||||||
sendEmail: false,
|
|
||||||
rootRole: 3,
|
|
||||||
}).then((response) => userIds.push(response.body.id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
userIds.forEach((id) =>
|
|
||||||
cy.request('DELETE', `${baseUrl}/api/admin/user-admin/${id}`),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.login_UI();
|
|
||||||
if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
|
|
||||||
cy.get("[data-testid='CLOSE_SPLASH']").click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can import data', () => {
|
|
||||||
cy.visit('/projects/default');
|
|
||||||
cy.get("[data-testid='IMPORT_BUTTON']").click({ force: true });
|
|
||||||
|
|
||||||
const exportText = {
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
name: randomFeatureName,
|
|
||||||
description: '',
|
|
||||||
type: 'release',
|
|
||||||
project: 'default',
|
|
||||||
stale: false,
|
|
||||||
impressionData: false,
|
|
||||||
archived: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
featureStrategies: [
|
|
||||||
{
|
|
||||||
name: 'flexibleRollout',
|
|
||||||
id: '14a0d9dd-2b5d-4a21-98fd-ede72bda0328',
|
|
||||||
featureName: randomFeatureName,
|
|
||||||
parameters: {
|
|
||||||
groupId: randomFeatureName,
|
|
||||||
rollout: '50',
|
|
||||||
stickiness: 'default',
|
|
||||||
},
|
|
||||||
constraints: [],
|
|
||||||
segments: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
featureEnvironments: [
|
|
||||||
{
|
|
||||||
enabled: true,
|
|
||||||
featureName: randomFeatureName,
|
|
||||||
environment: 'test',
|
|
||||||
variants: [],
|
|
||||||
name: randomFeatureName,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
contextFields: [],
|
|
||||||
featureTags: [
|
|
||||||
{
|
|
||||||
featureName: randomFeatureName,
|
|
||||||
tagType: 'simple',
|
|
||||||
tagValue: 'best-tag',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
featureName: randomFeatureName,
|
|
||||||
tagType: 'simple',
|
|
||||||
tagValue: 'rserw',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
featureName: randomFeatureName,
|
|
||||||
tagType: 'simple',
|
|
||||||
tagValue: 'FARO',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
segments: [],
|
|
||||||
tagTypes: [
|
|
||||||
{
|
|
||||||
name: 'simple',
|
|
||||||
description: 'Used to simplify filtering of features',
|
|
||||||
icon: '#',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
cy.get("[data-testid='VALIDATE_BUTTON']").should('be.disabled');
|
|
||||||
|
|
||||||
cy.intercept('POST', '/api/admin/features-batch/import').as(
|
|
||||||
'featureImported',
|
|
||||||
);
|
|
||||||
|
|
||||||
// cypress can only work with input@file that is visible
|
|
||||||
cy.get('input[type=file]')
|
|
||||||
.invoke('attr', 'style', 'display: block')
|
|
||||||
.selectFile({
|
|
||||||
contents: Cypress.Buffer.from(JSON.stringify(exportText)),
|
|
||||||
fileName: 'upload.json',
|
|
||||||
lastModified: Date.now(),
|
|
||||||
});
|
|
||||||
cy.get("[data-testid='VALIDATE_BUTTON']").click();
|
|
||||||
cy.get("[data-testid='IMPORT_CONFIGURATION_BUTTON']").click();
|
|
||||||
|
|
||||||
cy.wait('@featureImported');
|
|
||||||
cy.contains('Import completed');
|
|
||||||
|
|
||||||
cy.request({
|
|
||||||
url: `/api/admin/projects/default/features/${randomFeatureName}`,
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
}).then((response) => {
|
|
||||||
expect(response.body.name).to.equal(randomFeatureName);
|
|
||||||
const devEnv = response.body.environments.find(
|
|
||||||
(env: any) => env.name === 'development',
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(devEnv.name).to.equal('development');
|
|
||||||
expect(devEnv.strategies[0].parameters.rollout).to.equal('50');
|
|
||||||
expect(devEnv.enabled).to.equal(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
151
frontend/src/component/project/Project/Import/Import.test.tsx
Normal file
151
frontend/src/component/project/Project/Import/Import.test.tsx
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import { render } from 'utils/testRenderer';
|
||||||
|
import { screen, waitFor } from '@testing-library/react';
|
||||||
|
import { ImportModal } from './ImportModal';
|
||||||
|
import { testServerRoute, testServerSetup } from 'utils/testServer';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||||
|
|
||||||
|
const server = testServerSetup();
|
||||||
|
|
||||||
|
const setupApi = () => {
|
||||||
|
testServerRoute(server, '/api/admin/ui-config', {
|
||||||
|
versionInfo: {
|
||||||
|
current: { enterprise: 'present' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
testServerRoute(server, '/api/admin/projects/default', {
|
||||||
|
environments: [
|
||||||
|
{ environment: 'development' },
|
||||||
|
{ environment: 'production' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
testServerRoute(
|
||||||
|
server,
|
||||||
|
'/api/admin/features-batch/validate',
|
||||||
|
{ errors: [], permissions: [], warnings: [] },
|
||||||
|
'post',
|
||||||
|
);
|
||||||
|
testServerRoute(server, '/api/admin/features-batch/import', {}, 'post');
|
||||||
|
};
|
||||||
|
|
||||||
|
const importFile = async (content: string) => {
|
||||||
|
const selectFileInput = screen.getByTestId('import-file');
|
||||||
|
const importFile = new File([content], 'import.json', {
|
||||||
|
type: 'application/json',
|
||||||
|
});
|
||||||
|
userEvent.upload(selectFileInput, importFile);
|
||||||
|
};
|
||||||
|
|
||||||
|
test('Import happy path', async () => {
|
||||||
|
setupApi();
|
||||||
|
let closed = false;
|
||||||
|
const setOpen = (open: boolean) => {
|
||||||
|
closed = !open;
|
||||||
|
};
|
||||||
|
render(<ImportModal open={true} setOpen={setOpen} project='default' />, {
|
||||||
|
permissions: [{ permission: CREATE_FEATURE }],
|
||||||
|
});
|
||||||
|
|
||||||
|
// configure stage
|
||||||
|
screen.getByText('Import options');
|
||||||
|
screen.getByText('Drop your file here');
|
||||||
|
const validateButton = screen.getByText('Validate');
|
||||||
|
expect(validateButton).toBeDisabled();
|
||||||
|
|
||||||
|
await importFile('{}');
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Validate')).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
const codeEditorLabel = screen.getByText('Code editor');
|
||||||
|
codeEditorLabel.click();
|
||||||
|
const editor = screen.getByLabelText('Exported toggles');
|
||||||
|
expect(editor.textContent).toBe('{}');
|
||||||
|
|
||||||
|
screen.getByText('Validate').click();
|
||||||
|
|
||||||
|
// validate stage
|
||||||
|
screen.getByText('You are importing this configuration in:');
|
||||||
|
screen.getByText('development');
|
||||||
|
screen.getByText('default');
|
||||||
|
const importButton = screen.getByText('Import configuration');
|
||||||
|
expect(importButton).toBeEnabled();
|
||||||
|
importButton.click();
|
||||||
|
|
||||||
|
// import stage
|
||||||
|
await screen.findByText('Importing...');
|
||||||
|
await screen.findByText('Import completed');
|
||||||
|
|
||||||
|
expect(closed).toBe(false);
|
||||||
|
const closeButton = screen.getByText('Close');
|
||||||
|
closeButton.click();
|
||||||
|
expect(closed).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Block when importing non json content', async () => {
|
||||||
|
setupApi();
|
||||||
|
const setOpen = () => {};
|
||||||
|
render(<ImportModal open={true} setOpen={setOpen} project='default' />, {
|
||||||
|
permissions: [{ permission: CREATE_FEATURE }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const codeEditorLabel = screen.getByText('Code editor');
|
||||||
|
codeEditorLabel.click();
|
||||||
|
const editor = screen.getByLabelText('Exported toggles');
|
||||||
|
userEvent.type(editor, 'invalid non json');
|
||||||
|
|
||||||
|
const validateButton = screen.getByText('Validate');
|
||||||
|
expect(validateButton).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Show validation errors', async () => {
|
||||||
|
setupApi();
|
||||||
|
testServerRoute(
|
||||||
|
server,
|
||||||
|
'/api/admin/features-batch/validate',
|
||||||
|
{
|
||||||
|
errors: [
|
||||||
|
{ message: 'error message', affectedItems: ['itemC', 'itemD'] },
|
||||||
|
],
|
||||||
|
permissions: [
|
||||||
|
{
|
||||||
|
message: 'permission message',
|
||||||
|
affectedItems: ['itemE', 'itemF'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
warnings: [
|
||||||
|
{
|
||||||
|
message: 'warning message',
|
||||||
|
affectedItems: ['itemA', 'itemB'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'post',
|
||||||
|
);
|
||||||
|
const setOpen = () => {};
|
||||||
|
render(<ImportModal open={true} setOpen={setOpen} project='default' />, {
|
||||||
|
permissions: [{ permission: CREATE_FEATURE }],
|
||||||
|
});
|
||||||
|
|
||||||
|
await importFile('{}');
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Validate')).toBeEnabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
screen.getByText('Validate').click();
|
||||||
|
|
||||||
|
await screen.findByText('warning message');
|
||||||
|
await screen.findByText('itemA');
|
||||||
|
await screen.findByText('itemB');
|
||||||
|
|
||||||
|
await screen.findByText('error message');
|
||||||
|
await screen.findByText('itemC');
|
||||||
|
await screen.findByText('itemD');
|
||||||
|
|
||||||
|
await screen.findByText('permission message');
|
||||||
|
await screen.findByText('itemE');
|
||||||
|
await screen.findByText('itemF');
|
||||||
|
|
||||||
|
const importButton = screen.getByText('Import configuration');
|
||||||
|
expect(importButton).toBeDisabled();
|
||||||
|
});
|
@ -52,7 +52,7 @@ export const FileDropZone: FC<IFileDropZoneProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box {...getRootProps()} {...props}>
|
<Box {...getRootProps()} {...props}>
|
||||||
<input {...getInputProps()} />
|
<input data-testid='import-file' {...getInputProps()} />
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -85,7 +85,12 @@
|
|||||||
"^.+\\.tsx?$": ["@swc/jest"]
|
"^.+\\.tsx?$": ["@swc/jest"]
|
||||||
},
|
},
|
||||||
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||||
"testPathIgnorePatterns": ["/dist/", "/node_modules/", "/frontend/"],
|
"testPathIgnorePatterns": [
|
||||||
|
"/dist/",
|
||||||
|
"/node_modules/",
|
||||||
|
"/frontend/",
|
||||||
|
"/website/"
|
||||||
|
],
|
||||||
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json"],
|
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json"],
|
||||||
"coveragePathIgnorePatterns": [
|
"coveragePathIgnorePatterns": [
|
||||||
"/node_modules/",
|
"/node_modules/",
|
||||||
|
Loading…
Reference in New Issue
Block a user