Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [M3-6736] - VPC detail summary #9549

Merged
merged 16 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

VPC detail summary ([#9549](https://github.com/linode/manager/pull/9549))
118 changes: 118 additions & 0 deletions packages/manager/cypress/e2e/core/vpc/vpc-details-page.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {
mockGetVPC,
mockGetVPCs,
mockDeleteVPC,
mockUpdateVPC,
} from 'support/intercepts/vpc';
import {
mockAppendFeatureFlags,
mockGetFeatureFlagClientstream,
} from 'support/intercepts/feature-flags';
import { vpcFactory } from '@src/factories';
import { randomLabel, randomNumber, randomPhrase } from 'support/util/random';
import { makeFeatureFlagData } from 'support/util/feature-flags';
import type { VPC } from '@linode/api-v4';
import { getRegionById } from 'support/util/regions';
import { ui } from 'support/ui';

describe('VPC details page', () => {
/**
* - Confirms that VPC details pages can be visited.
* - Confirms that VPC details pages show VPC information.
* - Confirms UI flow when editing a VPC from details page.
* - Confirms UI flow when deleting a VPC from details page.
*/
it('can edit and delete a VPC from the VPC details page', () => {
const mockVPC: VPC = vpcFactory.build({
id: randomNumber(),
label: randomLabel(),
});

const mockVPCUpdated = {
...mockVPC,
label: randomLabel(),
description: randomPhrase(),
};

const vpcRegion = getRegionById(mockVPC.region);

mockAppendFeatureFlags({
vpc: makeFeatureFlagData(true),
}).as('getFeatureFlags');
mockGetFeatureFlagClientstream().as('getClientStream');
mockGetVPC(mockVPC).as('getVPC');
mockUpdateVPC(mockVPC.id, mockVPCUpdated).as('updateVPC');
mockDeleteVPC(mockVPC.id).as('deleteVPC');

cy.visitWithLogin(`/vpc/${mockVPC.id}`);
cy.wait(['@getFeatureFlags', '@getClientStream', '@getVPC']);

// Confirm that VPC details are displayed.
cy.findByText(mockVPC.label).should('be.visible');
cy.findByText(vpcRegion.label).should('be.visible');

// Confirm that VPC can be updated and that page reflects changes.
ui.button
.findByTitle('Edit')
.should('be.visible')
.should('be.enabled')
.click();

ui.drawer
.findByTitle('Edit VPC')
.should('be.visible')
.within(() => {
cy.findByLabelText('Label')
.should('be.visible')
.click()
.clear()
.type(mockVPCUpdated.label);

cy.findByLabelText('Description')
.should('be.visible')
.click()
.clear()
.type(mockVPCUpdated.description);

ui.button
.findByTitle('Save')
.should('be.visible')
.should('be.enabled')
.click();
});

cy.wait('@updateVPC');
cy.findByText(mockVPCUpdated.label).should('be.visible');
cy.findByText(mockVPCUpdated.description).should('be.visible');

// Confirm that VPC can be deleted and user is redirected to landing page.
ui.button
.findByTitle('Delete')
.should('be.visible')
.should('be.enabled')
.click();

ui.dialog
.findByTitle(`Delete VPC ${mockVPCUpdated.label}`)
.should('be.visible')
.within(() => {
cy.findByLabelText('VPC Label')
.should('be.visible')
.click()
.type(mockVPCUpdated.label);

ui.button
.findByTitle('Delete')
.should('be.visible')
.should('be.enabled')
.click();
});

mockGetVPCs([]).as('getVPCs');
cy.wait(['@deleteVPC', '@getVPCs']);

// Confirm that user is redirected to VPC landing page.
cy.url().should('endWith', '/vpc');
cy.findByText('Create a private and isolated network.');
});
});
12 changes: 12 additions & 0 deletions packages/manager/cypress/support/intercepts/vpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ import { apiMatcher } from 'support/util/intercepts';
import { paginateResponse } from 'support/util/paginate';

import type { VPC } from '@linode/api-v4';
import { makeResponse } from 'support/util/response';

/**
* Intercepts GET request to fetch a VPC and mocks response.
*
* @param vpc - VPC with which to mock response.
*
* @returns Cypress chainable.
*/
export const mockGetVPC = (vpc: VPC): Cypress.Chainable<null> => {
return cy.intercept('GET', apiMatcher(`vpcs/${vpc.id}`), makeResponse(vpc));
};

/**
* Intercepts GET request to fetch VPCs and mocks response.
Expand Down
67 changes: 67 additions & 0 deletions packages/manager/src/features/VPC/VPCDetail/VPCDetail.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Typography } from '@mui/material';
import { styled } from '@mui/material/styles';

import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
import { Paper } from 'src/components/Paper';

export const StyledActionButton = styled(Button, {
label: 'StyledActionButton',
})(({ theme }) => ({
'&:hover': {
backgroundColor: theme.color.blueDTwhite,
color: theme.color.white,
},
color: theme.textColors.linkActiveLight,
fontFamily: theme.font.normal,
fontSize: '0.875rem',
height: theme.spacing(5),
minWidth: 'auto',
}));

export const StyledDescriptionBox = styled(Box, {
label: 'StyledDescriptionBox',
})(({ theme }) => ({
[theme.breakpoints.down('lg')]: {
display: 'flex',
flexDirection: 'column',
paddingTop: theme.spacing(3),
},
[theme.breakpoints.down('sm')]: {
paddingTop: theme.spacing(1),
},
}));

export const StyledSummaryBox = styled(Box, {
label: 'StyledSummaryBox',
})(({ theme }) => ({
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
},
}));

export const StyledSummaryTextTypography = styled(Typography, {
label: 'StyledSummaryTextTypography',
})(({ theme }) => ({
'& strong': {
paddingRight: theme.spacing(1),
},
'&:first-of-type': {
paddingBottom: theme.spacing(2),
},
[theme.breakpoints.down('sm')]: {
paddingBottom: theme.spacing(2),
},
whiteSpace: 'nowrap',
}));

export const StyledPaper = styled(Paper, {
label: 'StyledPaper',
})(({ theme }) => ({
borderTop: `1px solid ${theme.borderColors.borderTable}`,
display: 'flex',
padding: theme.spacing(2),
[theme.breakpoints.down('lg')]: {
flexDirection: 'column',
},
}));
91 changes: 91 additions & 0 deletions packages/manager/src/features/VPC/VPCDetail/VPCDetail.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { waitForElementToBeRemoved } from '@testing-library/react';
import * as React from 'react';
import { QueryClient } from 'react-query';

import { vpcFactory } from 'src/factories/vpcs';
import { rest, server } from 'src/mocks/testServer';
import { mockMatchMedia, renderWithTheme } from 'src/utilities/testHelpers';

import VPCDetail from './VPCDetail';

const queryClient = new QueryClient();

beforeAll(() => mockMatchMedia());
afterEach(() => {
queryClient.clear();
});

const loadingTestId = 'circle-progress';

describe('VPC Detail Summary section', () => {
it('should display number of subnets and linodes, region, id, creation and update dates', async () => {
const vpcFactory1 = vpcFactory.build({ id: 100 });
server.use(
rest.get('*/vpcs/:vpcId', (req, res, ctx) => {
return res(ctx.json(vpcFactory1));
})
);

const { getAllByText, getByTestId } = renderWithTheme(<VPCDetail />, {
queryClient,
});

// Loading state should render
expect(getByTestId(loadingTestId)).toBeInTheDocument();

await waitForElementToBeRemoved(getByTestId(loadingTestId));

getAllByText('Subnets');
getAllByText('Linodes');
getAllByText('0');

getAllByText('Region');
getAllByText('Newark, NJ');

getAllByText('VPC ID');
getAllByText(vpcFactory1.id);

getAllByText('Created');
getAllByText(vpcFactory1.created);

getAllByText('Updated');
getAllByText(vpcFactory1.updated);
});

it('should display description if one is provided', async () => {
const vpcFactory1 = vpcFactory.build({
description: `VPC for webserver and database. VPC for webserver and database. VPC for webserver and database. VPC for webserver and database. VPC for webserver...`,
id: 101,
});
server.use(
rest.get('*/vpcs/:vpcId', (req, res, ctx) => {
return res(ctx.json(vpcFactory1));
})
);

const { getByTestId, getByText } = renderWithTheme(<VPCDetail />, {
queryClient,
});

await waitForElementToBeRemoved(getByTestId(loadingTestId));

getByText('Description');
getByText(vpcFactory1.description);
});

it('should hide description if none is provided', async () => {
server.use(
rest.get('*/vpcs/:vpcId', (req, res, ctx) => {
return res(ctx.json(vpcFactory.build()));
})
);

const { getByTestId, queryByText } = renderWithTheme(<VPCDetail />, {
queryClient,
});

await waitForElementToBeRemoved(getByTestId(loadingTestId));

expect(queryByText('Description')).not.toBeInTheDocument();
});
});
Loading