123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440 |
- import {browserHistory} from 'react-router';
- import {enforceActOnUseLegacyStoreHook, mountWithTheme} from 'sentry-test/enzyme';
- import {initializeOrg} from 'sentry-test/initializeOrg';
- import {act} from 'sentry-test/reactTestingLibrary';
- import ProjectsStore from 'sentry/stores/projectsStore';
- import {OrganizationContext} from 'sentry/views/organizationContext';
- import TransactionVitals from 'sentry/views/performance/transactionSummary/transactionVitals';
- import {
- VITAL_GROUPS,
- ZOOM_KEYS,
- } from 'sentry/views/performance/transactionSummary/transactionVitals/constants';
- function initialize({project, features, transaction, query} = {}) {
- features = features || ['performance-view'];
- project = project || TestStubs.Project();
- query = query || {};
- const data = initializeOrg({
- organization: TestStubs.Organization({
- features,
- projects: [project],
- }),
- router: {
- location: {
- query: {
- transaction: transaction || '/',
- project: project.id,
- ...query,
- },
- },
- },
- });
- act(() => ProjectsStore.loadInitialData(data.organization.projects));
- return data;
- }
- const WrappedComponent = ({organization, ...props}) => {
- return (
- <OrganizationContext.Provider value={organization}>
- <TransactionVitals organization={organization} {...props} />
- </OrganizationContext.Provider>
- );
- };
- /**
- * These values are what we expect to see on the page based on the
- * mocked api responses below.
- */
- const vitals = [
- {
- slug: 'fp',
- heading: 'First Paint (FP)',
- baseline: '4.57s',
- },
- {
- slug: 'fcp',
- heading: 'First Contentful Paint (FCP)',
- baseline: '1.46s',
- },
- {
- slug: 'lcp',
- heading: 'Largest Contentful Paint (LCP)',
- baseline: '1.34s',
- },
- {
- slug: 'fid',
- heading: 'First Input Delay (FID)',
- baseline: '987.00ms',
- },
- {
- slug: 'cls',
- heading: 'Cumulative Layout Shift (CLS)',
- baseline: '0.02',
- },
- ];
- describe('Performance > Web Vitals', function () {
- enforceActOnUseLegacyStoreHook();
- beforeEach(function () {
- // @ts-ignore no-console
- // eslint-disable-next-line no-console
- jest.spyOn(console, 'error').mockImplementation(jest.fn());
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/projects/',
- body: [],
- });
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/project-transaction-threshold-override/',
- method: 'GET',
- body: {
- threshold: '800',
- metric: 'lcp',
- },
- });
- // Mock baseline measurements
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events-vitals/',
- body: {
- 'measurements.fp': {poor: 1, meh: 2, good: 3, total: 6, p75: 4567},
- 'measurements.fcp': {poor: 1, meh: 2, good: 3, total: 6, p75: 1456},
- 'measurements.lcp': {poor: 1, meh: 2, good: 3, total: 6, p75: 1342},
- 'measurements.fid': {poor: 1, meh: 2, good: 3, total: 6, p75: 987},
- 'measurements.cls': {poor: 1, meh: 2, good: 3, total: 6, p75: 0.02},
- },
- });
- const histogramData = {};
- const webVitals = VITAL_GROUPS.reduce((vs, group) => vs.concat(group.vitals), []);
- for (const measurement of webVitals) {
- const data = [];
- for (let i = 0; i < 100; i++) {
- data.push({
- histogram: i,
- count: i,
- });
- }
- histogramData[`measurements.${measurement}`] = data;
- }
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events-histogram/',
- body: histogramData,
- });
- MockApiClient.addMockResponse({
- method: 'GET',
- url: `/organizations/org-slug/key-transactions-list/`,
- body: [],
- });
- MockApiClient.addMockResponse({
- url: '/prompts-activity/',
- body: {},
- });
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/sdk-updates/',
- body: [],
- });
- });
- afterEach(() => {
- // @ts-ignore no-console
- // eslint-disable-next-line no-console
- console.error.mockRestore();
- });
- it('render no access without feature', async function () {
- const {organization, router} = initialize({
- features: [],
- });
- const wrapper = mountWithTheme(
- <WrappedComponent organization={organization} location={router.location} />
- );
- await tick();
- wrapper.update();
- expect(wrapper.text()).toEqual("You don't have access to this feature");
- });
- it('renders the basic UI components', async function () {
- const {organization, router, routerContext} = initialize();
- const wrapper = mountWithTheme(
- <WrappedComponent
- organization={organization}
- location={router.location}
- router={router}
- />,
- routerContext
- );
- await tick();
- wrapper.update();
- expect(wrapper.find('TransactionHeader')).toHaveLength(1);
- expect(wrapper.find('SearchBar')).toHaveLength(1);
- expect(wrapper.find('TransactionVitals')).toHaveLength(1);
- });
- it('renders the correct bread crumbs', async function () {
- const {organization, router, routerContext} = initialize();
- const wrapper = mountWithTheme(
- <WrappedComponent
- organization={organization}
- location={router.location}
- router={router}
- />,
- routerContext
- );
- await tick();
- wrapper.update();
- expect(wrapper.find('Breadcrumb').text()).toEqual(
- expect.stringContaining('Web Vitals')
- );
- });
- it('renders all vitals cards correctly', async function () {
- const {organization, router, routerContext} = initialize();
- const wrapper = mountWithTheme(
- <WrappedComponent
- organization={organization}
- location={router.location}
- router={router}
- />,
- routerContext
- );
- await tick();
- wrapper.update();
- const vitalCards = wrapper.find('VitalCard');
- expect(vitalCards).toHaveLength(5);
- vitalCards.forEach((vitalCard, i) => {
- expect(vitalCard.find('CardSectionHeading').text()).toEqual(
- expect.stringContaining(vitals[i].heading)
- );
- expect(vitalCard.find('StatNumber').text()).toEqual(vitals[i].baseline);
- });
- expect(vitalCards.find('BarChart')).toHaveLength(5);
- });
- describe('reset view', function () {
- it('disables button on default view', async function () {
- const {organization, router, routerContext} = initialize();
- const wrapper = mountWithTheme(
- <WrappedComponent
- organization={organization}
- location={router.location}
- router={router}
- />,
- routerContext
- );
- await tick();
- wrapper.update();
- expect(
- wrapper.find('Button[data-test-id="reset-view"]').prop('disabled')
- ).toBeTruthy();
- });
- it('enables button on left zoom', async function () {
- const {organization, router, routerContext} = initialize({
- query: {
- lcpStart: '20',
- },
- });
- const wrapper = mountWithTheme(
- <WrappedComponent
- organization={organization}
- location={router.location}
- router={router}
- />,
- routerContext
- );
- await tick();
- wrapper.update();
- expect(
- wrapper.find('Button[data-test-id="reset-view"]').prop('disabled')
- ).toBeFalsy();
- });
- it('enables button on right zoom', async function () {
- const {organization, router, routerContext} = initialize({
- query: {
- fpEnd: '20',
- },
- });
- const wrapper = mountWithTheme(
- <WrappedComponent
- organization={organization}
- location={router.location}
- router={router}
- />,
- routerContext
- );
- await tick();
- wrapper.update();
- expect(
- wrapper.find('Button[data-test-id="reset-view"]').prop('disabled')
- ).toBeFalsy();
- });
- it('enables button on left and right zoom', async function () {
- const {organization, router, routerContext} = initialize({
- query: {
- fcpStart: '20',
- fcpEnd: '20',
- },
- });
- const wrapper = mountWithTheme(
- <WrappedComponent
- organization={organization}
- location={router.location}
- router={router}
- />,
- routerContext
- );
- await tick();
- wrapper.update();
- expect(
- wrapper.find('Button[data-test-id="reset-view"]').prop('disabled')
- ).toBeFalsy();
- });
- it('resets view properly', async function () {
- const {organization, router, routerContext} = initialize({
- query: {
- fidStart: '20',
- lcpEnd: '20',
- },
- });
- const wrapper = mountWithTheme(
- <WrappedComponent
- organization={organization}
- location={router.location}
- router={router}
- />,
- routerContext
- );
- await tick();
- wrapper.update();
- wrapper.find('Button[data-test-id="reset-view"]').simulate('click');
- expect(browserHistory.push).toHaveBeenCalledWith({
- query: expect.not.objectContaining(
- ZOOM_KEYS.reduce((obj, key) => {
- obj[key] = expect.anything();
- return obj;
- }, {})
- ),
- });
- });
- it('renders an info alert when missing web vitals data', async function () {
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events-vitals/',
- body: {
- 'measurements.fp': {poor: 1, meh: 2, good: 3, total: 6, p75: 4567},
- 'measurements.fcp': {poor: 1, meh: 2, good: 3, total: 6, p75: 1456},
- },
- });
- const {organization, router, routerContext} = initialize({
- query: {
- lcpStart: '20',
- },
- });
- const wrapper = mountWithTheme(
- <WrappedComponent
- organization={organization}
- location={router.location}
- router={router}
- />,
- routerContext
- );
- await tick();
- wrapper.update();
- expect(wrapper.find('Alert')).toHaveLength(1);
- });
- it('does not render an info alert when data from all web vitals is present', async function () {
- const {organization, router, routerContext} = initialize({
- query: {
- lcpStart: '20',
- },
- });
- const wrapper = mountWithTheme(
- <WrappedComponent
- organization={organization}
- location={router.location}
- router={router}
- />,
- routerContext
- );
- await tick();
- wrapper.update();
- expect(wrapper.find('Alert')).toHaveLength(0);
- });
- });
- it('renders an info alert when some web vitals measurements has no data available', async function () {
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events-vitals/',
- body: {
- 'measurements.cls': {poor: 1, meh: 2, good: 3, total: 6, p75: 4567},
- 'measurements.fcp': {poor: 1, meh: 2, good: 3, total: 6, p75: 4567},
- 'measurements.fid': {poor: 1, meh: 2, good: 3, total: 6, p75: 4567},
- 'measurements.fp': {poor: 1, meh: 2, good: 3, total: 6, p75: 1456},
- 'measurements.lcp': {poor: 0, meh: 0, good: 0, total: 0, p75: null},
- },
- });
- const {organization, router, routerContext} = initialize({
- query: {
- lcpStart: '20',
- },
- });
- const wrapper = mountWithTheme(
- <WrappedComponent
- organization={organization}
- location={router.location}
- router={router}
- />,
- routerContext
- );
- await tick();
- wrapper.update();
- expect(wrapper.find('Alert')).toHaveLength(1);
- });
- });
|