airbnb/caravel

View on GitHub
superset-frontend/src/features/datasets/AddDataset/EditDataset/UsageTab/UsageTab.test.tsx

Summary

Maintainability
B
4 hrs
Test Coverage
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import fetchMock from 'fetch-mock';
import userEvent from '@testing-library/user-event';
import { render, screen, waitFor } from 'spec/helpers/testing-library';
import { ChartListChart, getMockChart } from 'spec/fixtures/mockCharts';
import ToastContainer from 'src/components/MessageToasts/ToastContainer';
import DatasetUsage from '.';

const DEFAULT_DATASET_ID = '1';
const DEFAULT_ORDER_COLUMN = 'last_saved_at';
const DEFAULT_ORDER_DIRECTION = 'desc';
const DEFAULT_PAGE = 0;
const DEFAULT_PAGE_SIZE = 25;

const getChartResponse = (result: ChartListChart[]) => ({
  count: result.length,
  result,
});

const CHARTS_ENDPOINT = 'glob:*/api/v1/chart/?*';
const mockChartsFetch = (response: fetchMock.MockResponse) => {
  fetchMock.reset();
  fetchMock.get('glob:*/api/v1/chart/_info?*', {
    permissions: ['can_export', 'can_read', 'can_write'],
  });

  fetchMock.get(CHARTS_ENDPOINT, response);
};

const renderDatasetUsage = () =>
  render(
    <>
      <DatasetUsage datasetId={DEFAULT_DATASET_ID} />
      <ToastContainer />
    </>,
    { useRedux: true, useRouter: true },
  );

const expectLastChartRequest = (params?: {
  datasetId?: string;
  orderColumn?: string;
  orderDirection?: 'desc' | 'asc';
  page?: number;
  pageSize?: number;
}) => {
  const { datasetId, orderColumn, orderDirection, page, pageSize } = {
    datasetId: DEFAULT_DATASET_ID,
    orderColumn: DEFAULT_ORDER_COLUMN,
    orderDirection: DEFAULT_ORDER_DIRECTION,
    page: DEFAULT_PAGE,
    pageSize: DEFAULT_PAGE_SIZE,
    ...(params || {}),
  };

  const calls = fetchMock.calls(CHARTS_ENDPOINT);
  expect(calls.length).toBeGreaterThan(0);
  const lastChartRequestUrl = calls[calls.length - 1][0];
  expect(lastChartRequestUrl).toMatch(
    new RegExp(`col:datasource_id,opr:eq,value:%27${datasetId}%27`),
  );

  expect(lastChartRequestUrl).toMatch(
    new RegExp(`order_column:${orderColumn}`),
  );

  expect(lastChartRequestUrl).toMatch(
    new RegExp(`order_direction:${orderDirection}`),
  );

  expect(lastChartRequestUrl).toMatch(new RegExp(`page:${page}`));
  expect(lastChartRequestUrl).toMatch(new RegExp(`page_size:${pageSize}`));
};

test('shows loading state', async () => {
  mockChartsFetch(
    new Promise(resolve =>
      setTimeout(() => resolve(getChartResponse([])), 250),
    ),
  );

  renderDatasetUsage();

  const loadingIndicator = await screen.findByRole('status', {
    name: /loading/i,
  });

  expect(loadingIndicator).toBeVisible();
});

test('shows error state', async () => {
  mockChartsFetch(500);
  renderDatasetUsage();

  const errorMessage = await screen.findByText(
    /an error occurred while fetching charts/i,
  );

  expect(errorMessage).toBeInTheDocument();
});

test('shows empty state', async () => {
  mockChartsFetch(getChartResponse([]));
  renderDatasetUsage();

  const noChartsTitle = await screen.findByText(/no charts/i);
  const noChartsDescription = screen.getByText(
    /this dataset is not used to power any charts\./i,
  );

  expect(noChartsTitle).toBeVisible();
  expect(noChartsDescription).toBeVisible();
  expect(fetchMock.calls(CHARTS_ENDPOINT)).toHaveLength(1);
  expectLastChartRequest();
});

test('show and sort by chart title', async () => {
  mockChartsFetch(
    getChartResponse([
      getMockChart({ id: 1, slice_name: 'Sample A' }),
      getMockChart({ id: 2, slice_name: 'Sample C' }),
      getMockChart({ id: 3, slice_name: 'Sample B' }),
    ]),
  );

  renderDatasetUsage();

  const chartNameColumnHeader = screen.getByText('Chart');
  const chartNameLinks = await screen.findAllByRole('link', {
    name: /sample/i,
  });

  // Default sort
  expect(chartNameLinks).toHaveLength(3);
  expect(chartNameLinks[0]).toHaveTextContent('Sample A');
  expect(chartNameLinks[0]).toHaveAttribute('href', '/explore/?slice_id=1');
  expect(chartNameLinks[1]).toHaveTextContent('Sample C');
  expect(chartNameLinks[1]).toHaveAttribute('href', '/explore/?slice_id=2');
  expect(chartNameLinks[2]).toHaveTextContent('Sample B');
  expect(chartNameLinks[2]).toHaveAttribute('href', '/explore/?slice_id=3');
  expectLastChartRequest();

  // Sort by name ascending
  userEvent.click(chartNameColumnHeader);
  waitFor(() => {
    expectLastChartRequest({
      orderColumn: 'slice_name',
      orderDirection: 'asc',
    });
  });

  // Sort by name descending
  userEvent.click(chartNameColumnHeader);
  waitFor(() => {
    expectLastChartRequest({
      orderColumn: 'slice_name',
      orderDirection: 'desc',
    });
  });
});

test('show chart owners', async () => {
  mockChartsFetch(
    getChartResponse([
      getMockChart({
        id: 1,
        owners: [
          { id: 1, first_name: 'John', last_name: 'Doe', username: 'j1' },
          { id: 2, first_name: 'Jane', last_name: 'Doe', username: 'j2' },
        ],
      }),
      getMockChart({ id: 2 }),
      getMockChart({
        id: 3,
        owners: [
          { id: 3, first_name: 'John', last_name: 'Doe', username: 'j1' },
        ],
      }),
    ]),
  );

  renderDatasetUsage();

  const chartOwners = await screen.findAllByText(/doe/i);

  expect(chartOwners).toHaveLength(3);
  expect(chartOwners[0]).toHaveTextContent('John Doe');
  expect(chartOwners[1]).toHaveTextContent('Jane Doe');
  expect(chartOwners[0].parentNode).toBe(chartOwners[1].parentNode);
  expect(chartOwners[2]).toHaveTextContent('John Doe');
  expect(chartOwners[2].parentNode).not.toBe(chartOwners[0].parentNode);
  expect(chartOwners[2].parentNode).not.toBe(chartOwners[1].parentNode);
  expectLastChartRequest();
});

const getDate = (msAgo: number) => {
  const date = new Date();
  date.setMilliseconds(date.getMilliseconds() - msAgo);
  return date;
};

test('show and sort by chart last modified', async () => {
  mockChartsFetch(
    getChartResponse([
      getMockChart({ id: 2, last_saved_at: getDate(10000).toISOString() }),
      getMockChart({ id: 1, last_saved_at: getDate(1000000).toISOString() }),
      getMockChart({ id: 3, last_saved_at: getDate(100000000).toISOString() }),
    ]),
  );

  renderDatasetUsage();

  const chartLastModifiedColumnHeader = screen.getByText('Chart last modified');
  const chartLastModifiedValues = await screen.findAllByText(
    /a few seconds ago|17 minutes ago|a day ago/i,
  );

  // Default sort
  expect(chartLastModifiedValues).toHaveLength(3);
  expect(chartLastModifiedValues[0]).toHaveTextContent('a few seconds ago');
  expect(chartLastModifiedValues[1]).toHaveTextContent('17 minutes ago');
  expect(chartLastModifiedValues[2]).toHaveTextContent('a day ago');
  expectLastChartRequest();

  // Sort by last modified ascending
  userEvent.click(chartLastModifiedColumnHeader);
  waitFor(() => {
    expectLastChartRequest({ orderDirection: 'asc' });
  });

  // Sort by last modified descending
  userEvent.click(chartLastModifiedColumnHeader);
  waitFor(() => {
    expectLastChartRequest({ orderDirection: 'desc' });
  });
});

test('show and sort by chart last modified by', async () => {
  mockChartsFetch(
    getChartResponse([
      getMockChart({
        id: 2,
        last_saved_by: { id: 1, first_name: 'John', last_name: 'Doe' },
      }),
      getMockChart({
        id: 1,
        last_saved_by: null,
      }),
      getMockChart({
        id: 3,
        last_saved_by: { id: 2, first_name: 'Jane', last_name: 'Doe' },
      }),
    ]),
  );

  renderDatasetUsage();

  const chartLastModifiedByColumnHeader = screen.getByText(
    'Chart last modified by',
  );

  const chartLastModifiedByValues = await screen.findAllByText(/doe/i);

  // Default sort
  expect(chartLastModifiedByValues).toHaveLength(2);
  expect(chartLastModifiedByValues[0]).toHaveTextContent('John Doe');
  expect(chartLastModifiedByValues[1]).toHaveTextContent('Jane Doe');
  expectLastChartRequest();

  // Sort by last modified ascending
  userEvent.click(chartLastModifiedByColumnHeader);
  waitFor(() => {
    expectLastChartRequest({ orderDirection: 'asc' });
  });

  // Sort by last modified descending
  userEvent.click(chartLastModifiedByColumnHeader);
  waitFor(() => {
    expectLastChartRequest({ orderDirection: 'desc' });
  });
});

test('show chart dashboards', async () => {
  mockChartsFetch(
    getChartResponse([
      getMockChart({
        id: 1,
        dashboards: [
          { id: 1, dashboard_title: 'Sample dashboard A' },
          { id: 2, dashboard_title: 'Sample dashboard B' },
        ],
      }),
      getMockChart({ id: 2 }),
      getMockChart({
        id: 3,
        dashboards: [{ id: 3, dashboard_title: 'Sample dashboard C' }],
      }),
    ]),
  );

  renderDatasetUsage();

  const chartDashboards = await screen.findAllByRole('link', {
    name: /sample dashboard/i,
  });

  expect(chartDashboards).toHaveLength(3);
  expect(chartDashboards[0]).toHaveTextContent('Sample dashboard A');
  expect(chartDashboards[0]).toHaveAttribute('href', '/superset/dashboard/1');
  expect(chartDashboards[1]).toHaveTextContent('Sample dashboard B');
  expect(chartDashboards[1]).toHaveAttribute('href', '/superset/dashboard/2');
  expect(chartDashboards[0].closest('.ant-table-cell')).toBe(
    chartDashboards[1].closest('.ant-table-cell'),
  );

  expect(chartDashboards[2]).toHaveTextContent('Sample dashboard C');
  expect(chartDashboards[2]).toHaveAttribute('href', '/superset/dashboard/3');
  expect(chartDashboards[2].closest('.ant-table-cell')).not.toBe(
    chartDashboards[0].closest('.ant-table-cell'),
  );

  expect(chartDashboards[2].closest('.ant-table-cell')).not.toBe(
    chartDashboards[1].closest('.ant-table-cell'),
  );

  expectLastChartRequest();

  expect(
    screen.queryByRole('button', {
      name: /right/i,
    }),
  ).not.toBeInTheDocument();
});

test('paginates', async () => {
  const charts = [];
  for (let i = 0; i < 65; i += 1) {
    charts.push(
      getMockChart({
        id: i + 1,
        slice_name: `Sample chart ${i + 1}`,
      }),
    );
  }

  mockChartsFetch(getChartResponse(charts));
  renderDatasetUsage();

  // First page
  let chartNameValues = await screen.findAllByRole('cell', {
    name: /sample chart/i,
  });

  expect(chartNameValues).toHaveLength(25);
  expect(chartNameValues[0]).toHaveTextContent('Sample chart 1');
  expect(chartNameValues[24]).toHaveTextContent('Sample chart 25');

  // Second page
  userEvent.click(
    screen.getByRole('button', {
      name: /right/i,
    }),
  );

  chartNameValues = await screen.findAllByRole('cell', {
    name: /sample chart/i,
  });

  expect(chartNameValues).toHaveLength(25);
  expect(chartNameValues[0]).toHaveTextContent('Sample chart 26');
  expect(chartNameValues[24]).toHaveTextContent('Sample chart 50');

  // Third page
  userEvent.click(
    screen.getByRole('button', {
      name: /right/i,
    }),
  );

  chartNameValues = await screen.findAllByRole('cell', {
    name: /sample chart/i,
  });

  expect(chartNameValues).toHaveLength(15);
  expect(chartNameValues[0]).toHaveTextContent('Sample chart 51');
  expect(chartNameValues[14]).toHaveTextContent('Sample chart 65');
});