airbnb/caravel

View on GitHub
superset-frontend/src/SqlLab/components/AceEditorWrapper/useKeywords.test.ts

Summary

Maintainability
C
1 day
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 { act, renderHook } from '@testing-library/react-hooks';
import { getExtensionsRegistry } from '@superset-ui/core';
import {
  createWrapper,
  defaultStore as store,
  createStore,
} from 'spec/helpers/testing-library';
import { api } from 'src/hooks/apiResources/queryApi';
import { schemaApiUtil } from 'src/hooks/apiResources/schemas';
import { tableApiUtil } from 'src/hooks/apiResources/tables';
import { addTable } from 'src/SqlLab/actions/sqlLab';
import { initialState } from 'src/SqlLab/fixtures';
import reducers from 'spec/helpers/reducerIndex';
import {
  SCHEMA_AUTOCOMPLETE_SCORE,
  TABLE_AUTOCOMPLETE_SCORE,
  COLUMN_AUTOCOMPLETE_SCORE,
  SQL_FUNCTIONS_AUTOCOMPLETE_SCORE,
} from 'src/SqlLab/constants';
import { useKeywords } from './useKeywords';

const fakeSchemaApiResult = ['schema1', 'schema2'];
const fakeTableApiResult = {
  count: 2,
  result: [
    {
      id: 1,
      value: 'fake api result1',
      label: 'fake api label1',
      type: 'table',
    },
    {
      id: 2,
      value: 'fake api result2',
      label: 'fake api label2',
      type: 'table',
    },
  ],
};
const fakeFunctionNamesApiResult = {
  function_names: ['abs', 'avg', 'sum'],
};

const expectDbId = 1;
const expectCatalog = null;
const expectSchema = 'schema1';

beforeEach(() => {
  act(() => {
    store.dispatch(
      schemaApiUtil.upsertQueryData(
        'schemas',
        {
          dbId: expectDbId,
          forceRefresh: false,
        },
        fakeSchemaApiResult.map(value => ({
          value,
          label: value,
          title: value,
        })),
      ),
    );
    store.dispatch(
      tableApiUtil.upsertQueryData(
        'tables',
        { dbId: expectDbId, schema: expectSchema },
        {
          options: fakeTableApiResult.result,
          hasMore: false,
        },
      ),
    );
  });
});

afterEach(() => {
  fetchMock.reset();
  act(() => {
    store.dispatch(api.util.resetApiState());
  });
});

test('returns keywords including fetched function_names data', async () => {
  const dbFunctionNamesApiRoute = `glob:*/api/v1/database/${expectDbId}/function_names/`;
  fetchMock.get(dbFunctionNamesApiRoute, fakeFunctionNamesApiResult);

  const { result, waitFor } = renderHook(
    () =>
      useKeywords({
        queryEditorId: 'testqueryid',
        dbId: expectDbId,
        schema: expectSchema,
      }),
    {
      wrapper: createWrapper({
        useRedux: true,
        store,
      }),
    },
  );

  await waitFor(() =>
    expect(fetchMock.calls(dbFunctionNamesApiRoute).length).toBe(1),
  );
  fakeSchemaApiResult.forEach(schema => {
    expect(result.current).toContainEqual(
      expect.objectContaining({
        name: schema,
        score: SCHEMA_AUTOCOMPLETE_SCORE,
        meta: 'schema',
      }),
    );
  });
  fakeTableApiResult.result.forEach(({ value }) => {
    expect(result.current).toContainEqual(
      expect.objectContaining({
        value,
        score: TABLE_AUTOCOMPLETE_SCORE,
        meta: 'table',
      }),
    );
  });
  fakeFunctionNamesApiResult.function_names.forEach(func => {
    expect(result.current).toContainEqual(
      expect.objectContaining({
        name: func,
        value: func,
        meta: 'function',
        score: SQL_FUNCTIONS_AUTOCOMPLETE_SCORE,
      }),
    );
  });
});

test('skip fetching if autocomplete skipped', () => {
  const { result } = renderHook(
    () =>
      useKeywords(
        {
          queryEditorId: 'testqueryid',
          dbId: expectDbId,
          schema: expectSchema,
        },
        true,
      ),
    {
      wrapper: createWrapper({
        useRedux: true,
        store,
      }),
    },
  );
  expect(result.current).toEqual([]);
  expect(fetchMock.calls()).toEqual([]);
});

test('returns column keywords among selected tables', async () => {
  const expectTable = 'table1';
  const expectColumn = 'column1';
  const expectQueryEditorId = 'testqueryid';

  const unexpectedColumn = 'column2';
  const unexpectedTable = 'table2';

  const dbFunctionNamesApiRoute = `glob:*/api/v1/database/${expectDbId}/function_names/`;
  const storeWithSqlLab = createStore(initialState, reducers);
  fetchMock.get(dbFunctionNamesApiRoute, fakeFunctionNamesApiResult);

  act(() => {
    storeWithSqlLab.dispatch(
      tableApiUtil.upsertQueryData(
        'tableMetadata',
        {
          dbId: expectDbId,
          catalog: null,
          schema: expectSchema,
          table: expectTable,
        },
        {
          name: expectTable,
          columns: [
            {
              name: expectColumn,
              type: 'VARCHAR',
            },
          ],
        },
      ),
    );

    storeWithSqlLab.dispatch(
      tableApiUtil.upsertQueryData(
        'tableMetadata',
        {
          dbId: expectDbId,
          catalog: null,
          schema: expectSchema,
          table: unexpectedTable,
        },
        {
          name: unexpectedTable,
          columns: [
            {
              name: unexpectedColumn,
              type: 'VARCHAR',
            },
          ],
        },
      ),
    );
    storeWithSqlLab.dispatch(
      addTable(
        { id: expectQueryEditorId },
        expectTable,
        expectCatalog,
        expectSchema,
      ),
    );
  });

  const { result, waitFor } = renderHook(
    () =>
      useKeywords({
        queryEditorId: expectQueryEditorId,
        dbId: expectDbId,
        catalog: null,
        schema: expectSchema,
      }),
    {
      wrapper: createWrapper({
        useRedux: true,
        store: storeWithSqlLab,
      }),
    },
  );

  await waitFor(() =>
    expect(result.current).toContainEqual(
      expect.objectContaining({
        name: expectColumn,
        value: expectColumn,
        score: COLUMN_AUTOCOMPLETE_SCORE,
        meta: 'column',
      }),
    ),
  );

  expect(result.current).not.toContainEqual(
    expect.objectContaining({
      name: unexpectedColumn,
    }),
  );

  act(() => {
    storeWithSqlLab.dispatch(
      addTable(
        { id: expectQueryEditorId },
        unexpectedTable,
        expectCatalog,
        expectSchema,
      ),
    );
  });

  await waitFor(() =>
    expect(result.current).toContainEqual(
      expect.objectContaining({
        name: unexpectedColumn,
      }),
    ),
  );
});

test('returns long keywords with docText', async () => {
  const expectLongKeywordDbId = 2;
  const longKeyword = 'veryveryveryveryverylongtablename';
  const dbFunctionNamesApiRoute = `glob:*/api/v1/database/${expectLongKeywordDbId}/function_names/`;
  fetchMock.get(dbFunctionNamesApiRoute, { function_names: [] });

  act(() => {
    store.dispatch(
      schemaApiUtil.upsertQueryData(
        'schemas',
        {
          dbId: expectLongKeywordDbId,
          forceRefresh: false,
        },
        ['short', longKeyword].map(value => ({
          value,
          label: value,
          title: value,
        })),
      ),
    );
  });
  const { result, waitFor } = renderHook(
    () =>
      useKeywords({
        queryEditorId: 'testqueryid',
        dbId: expectLongKeywordDbId,
      }),
    {
      wrapper: createWrapper({
        useRedux: true,
        store,
      }),
    },
  );
  await waitFor(() =>
    expect(result.current).toContainEqual(
      expect.objectContaining({
        name: longKeyword,
        value: longKeyword,
        docText: longKeyword,
      }),
    ),
  );
});

test('Add custom keywords for autocomplete', () => {
  const expected = [
    {
      name: 'Custom keyword 1',
      label: 'Custom keyword 1',
      meta: 'Custom',
      value: 'custom1',
      score: 100,
    },
    {
      name: 'Custom keyword 2',
      label: 'Custom keyword 2',
      meta: 'Custom',
      value: 'custom2',
      score: 50,
    },
  ];
  const extensionsRegistry = getExtensionsRegistry();
  extensionsRegistry.set(
    'sqleditor.extension.customAutocomplete',
    () => expected,
  );
  const { result } = renderHook(
    () =>
      useKeywords({
        queryEditorId: 'testqueryid',
        dbId: expectDbId,
        schema: expectSchema,
      }),
    {
      wrapper: createWrapper({
        useRedux: true,
        store,
      }),
    },
  );
  expect(result.current).toContainEqual(expect.objectContaining(expected[0]));
  expect(result.current).toContainEqual(expect.objectContaining(expected[1]));
});