test: Adds tests to TableLoader component (#13299)

diff --git a/superset-frontend/spec/fixtures/mockStore.js b/superset-frontend/spec/fixtures/mockStore.js
index f4aad15..2b34ff3 100644
--- a/superset-frontend/spec/fixtures/mockStore.js
+++ b/superset-frontend/spec/fixtures/mockStore.js
@@ -30,6 +30,9 @@
 import { dashboardFilters } from './mockDashboardFilters';
 import { nativeFilters } from './mockNativeFilters';
 
+export const storeWithState = state =>
+  createStore(rootReducer, state, compose(applyMiddleware(thunk)));
+
 export const getMockStore = overrideState =>
   createStore(
     rootReducer,
diff --git a/superset-frontend/src/components/TableLoader/TableLoader.test.tsx b/superset-frontend/src/components/TableLoader/TableLoader.test.tsx
new file mode 100644
index 0000000..c0c4687
--- /dev/null
+++ b/superset-frontend/src/components/TableLoader/TableLoader.test.tsx
@@ -0,0 +1,94 @@
+/**
+ * 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 React from 'react';
+import { render, screen } from 'spec/helpers/testing-library';
+import { Provider } from 'react-redux';
+import fetchMock from 'fetch-mock';
+import { storeWithState } from 'spec/fixtures/mockStore';
+import ToastPresenter from 'src/messageToasts/containers/ToastPresenter';
+import TableLoader, { TableLoaderProps } from '.';
+
+fetchMock.get('glob:*/api/v1/mock', [
+  { id: 1, name: 'John Doe' },
+  { id: 2, name: 'Jane Doe' },
+]);
+
+const defaultProps: TableLoaderProps = {
+  dataEndpoint: '/api/v1/mock',
+  addDangerToast: jest.fn(),
+};
+
+function renderWithProps(props: TableLoaderProps = defaultProps) {
+  return render(
+    <Provider store={storeWithState({})}>
+      <TableLoader {...props} />
+      <ToastPresenter />
+    </Provider>,
+  );
+}
+
+test('renders loading and table', async () => {
+  renderWithProps();
+
+  expect(screen.getByRole('status')).toBeInTheDocument();
+  expect(await screen.findByRole('table')).toBeInTheDocument();
+});
+
+test('renders with column names', async () => {
+  renderWithProps({
+    ...defaultProps,
+    columns: ['id_modified', 'name_modified'],
+  });
+
+  const columnHeaders = await screen.findAllByRole('columnheader');
+
+  expect(columnHeaders[0]).toHaveTextContent('id_modified');
+  expect(columnHeaders[1]).toHaveTextContent('name_modified');
+});
+
+test('renders without mutator', async () => {
+  renderWithProps();
+
+  expect(await screen.findAllByRole('row')).toHaveLength(3);
+  expect(await screen.findAllByRole('columnheader')).toHaveLength(2);
+  expect(await screen.findAllByRole('cell')).toHaveLength(4);
+});
+
+test('renders with mutator', async () => {
+  const mutator = function (data: { id: number; name: string }[]) {
+    return data.map(row => ({
+      id: row.id,
+      name: <h4>{row.name}</h4>,
+    }));
+  };
+
+  renderWithProps({ ...defaultProps, mutator });
+
+  expect(await screen.findAllByRole('heading', { level: 4 })).toHaveLength(2);
+});
+
+test('renders error message', async () => {
+  fetchMock.mock('glob:*/api/v1/mock', 500, {
+    overwriteRoutes: true,
+  });
+
+  renderWithProps();
+
+  expect(await screen.findByRole('alert')).toBeInTheDocument();
+});
diff --git a/superset-frontend/src/components/TableLoader.tsx b/superset-frontend/src/components/TableLoader/index.tsx
similarity index 77%
rename from superset-frontend/src/components/TableLoader.tsx
rename to superset-frontend/src/components/TableLoader/index.tsx
index aa46a25..c622449 100644
--- a/superset-frontend/src/components/TableLoader.tsx
+++ b/superset-frontend/src/components/TableLoader/index.tsx
@@ -17,25 +17,13 @@
  * under the License.
  */
 import React, { useState, useEffect, useMemo } from 'react';
-import PropTypes from 'prop-types';
 import { t, SupersetClient, JsonObject } from '@superset-ui/core';
-import TableView from 'src/components/TableView';
-import withToasts from '../messageToasts/enhancers/withToasts';
-import Loading from './Loading';
-import '../../stylesheets/reactable-pagination.less';
-import { EmptyWrapperType } from './TableView/TableView';
+import TableView, { EmptyWrapperType } from 'src/components/TableView';
+import withToasts from 'src/messageToasts/enhancers/withToasts';
+import Loading from 'src/components/Loading';
+import 'stylesheets/reactable-pagination.less';
 
-const propTypes = {
-  dataEndpoint: PropTypes.string.isRequired,
-  mutator: PropTypes.func,
-  columns: PropTypes.arrayOf(PropTypes.string),
-  addDangerToast: PropTypes.func.isRequired,
-  addInfoToast: PropTypes.func.isRequired,
-  addSuccessToast: PropTypes.func.isRequired,
-  addWarningToast: PropTypes.func.isRequired,
-};
-
-interface TableLoaderProps {
+export interface TableLoaderProps {
   dataEndpoint?: string;
   mutator?: (data: JsonObject) => any[];
   columns?: string[];
@@ -96,6 +84,4 @@
   );
 };
 
-TableLoader.propTypes = propTypes;
-
 export default withToasts(TableLoader);
diff --git a/superset-frontend/src/profile/App.tsx b/superset-frontend/src/profile/App.tsx
index 8774d63..d3c5e43 100644
--- a/superset-frontend/src/profile/App.tsx
+++ b/superset-frontend/src/profile/App.tsx
@@ -22,12 +22,13 @@
 import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
 import { Provider } from 'react-redux';
 import { ThemeProvider } from '@superset-ui/core';
-import App from './components/App';
-import messageToastReducer from '../messageToasts/reducers';
-import { initEnhancer } from '../reduxUtils';
-import setupApp from '../setup/setupApp';
+import App from 'src/profile/components/App';
+import messageToastReducer from 'src/messageToasts/reducers';
+import { initEnhancer } from 'src/reduxUtils';
+import setupApp from 'src/setup/setupApp';
 import './main.less';
-import { theme } from '../preamble';
+import { theme } from 'src/preamble';
+import ToastPresenter from 'src/messageToasts/containers/ToastPresenter';
 
 setupApp();
 
@@ -48,6 +49,7 @@
   <Provider store={store}>
     <ThemeProvider theme={theme}>
       <App user={bootstrap.user} />
+      <ToastPresenter />
     </ThemeProvider>
   </Provider>
 );