mirror of
https://github.com/apache/superset.git
synced 2026-04-28 12:34:23 +00:00
Compare commits
2 Commits
docs/testi
...
remove-fav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2355c7162 | ||
|
|
320cc6831c |
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* 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 { screen, waitFor } from 'spec/helpers/testing-library';
|
||||
import {
|
||||
API_ENDPOINTS,
|
||||
renderChartList,
|
||||
setupMocks,
|
||||
} from './ChartList.testHelpers';
|
||||
|
||||
jest.setTimeout(30000);
|
||||
|
||||
const mockUser = {
|
||||
userId: 1,
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
roles: {
|
||||
Admin: [
|
||||
['can_read', 'Chart'],
|
||||
['can_write', 'Chart'],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
describe('ChartList - Favorite Column Visibility', () => {
|
||||
beforeEach(() => {
|
||||
setupMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.resetHistory();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
test('hides favorite column when no charts are favorited', async () => {
|
||||
// Mock favorite status API to return all false
|
||||
fetchMock.get(
|
||||
API_ENDPOINTS.CHART_FAVORITE_STATUS,
|
||||
{
|
||||
result: [
|
||||
{ id: 0, value: false },
|
||||
{ id: 1, value: false },
|
||||
{ id: 2, value: false },
|
||||
{ id: 3, value: false },
|
||||
],
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
renderChartList(mockUser);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('chart-list-view')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Wait for data to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Chart 0')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Favorite column should be hidden - check that favorite stars are not present
|
||||
const favoriteStars = screen.queryAllByTestId('fave-unfave-icon');
|
||||
expect(favoriteStars).toHaveLength(0);
|
||||
|
||||
// Verify that other columns are still present (check table headers)
|
||||
expect(
|
||||
screen.getByRole('columnheader', { name: 'Name' }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('columnheader', { name: 'Type' }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('shows favorite column when at least one chart is favorited', async () => {
|
||||
// Mock favorite status API to return mixed favorites
|
||||
fetchMock.get(
|
||||
API_ENDPOINTS.CHART_FAVORITE_STATUS,
|
||||
{
|
||||
result: [
|
||||
{ id: 0, value: true }, // This chart is favorited
|
||||
{ id: 1, value: false },
|
||||
{ id: 2, value: false },
|
||||
{ id: 3, value: false },
|
||||
],
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
renderChartList(mockUser);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('chart-list-view')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Wait for data to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Chart 0')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Favorite column should be visible - wait for stars to appear
|
||||
await waitFor(
|
||||
() => {
|
||||
const favoriteStars = screen.getAllByTestId('fave-unfave-icon');
|
||||
expect(favoriteStars.length).toBeGreaterThan(0);
|
||||
},
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
});
|
||||
|
||||
test('shows favorite column when all charts are favorited', async () => {
|
||||
// Mock favorite status API to return all true
|
||||
fetchMock.get(
|
||||
API_ENDPOINTS.CHART_FAVORITE_STATUS,
|
||||
{
|
||||
result: [
|
||||
{ id: 0, value: true },
|
||||
{ id: 1, value: true },
|
||||
{ id: 2, value: true },
|
||||
{ id: 3, value: true },
|
||||
],
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
renderChartList(mockUser);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('chart-list-view')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Wait for data to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Chart 0')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Favorite column should be visible
|
||||
await waitFor(
|
||||
() => {
|
||||
const favoriteStars = screen.getAllByTestId('fave-unfave-icon');
|
||||
expect(favoriteStars.length).toBeGreaterThan(0);
|
||||
},
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
});
|
||||
|
||||
test('hides favorite column when user is not logged in', async () => {
|
||||
// Mock favorite status API
|
||||
fetchMock.get(
|
||||
API_ENDPOINTS.CHART_FAVORITE_STATUS,
|
||||
{
|
||||
result: [
|
||||
{ id: 0, value: true },
|
||||
{ id: 1, value: false },
|
||||
],
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
// Render without userId (user not logged in)
|
||||
const noUser = {
|
||||
userId: null,
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
roles: {},
|
||||
};
|
||||
|
||||
renderChartList(noUser);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('chart-list-view')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Wait for data to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Chart 0')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Favorite column should be hidden when user is not logged in
|
||||
const favoriteStars = screen.queryAllByTestId('fave-unfave-icon');
|
||||
expect(favoriteStars).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('hides favorite column when chart list is empty', async () => {
|
||||
// Mock empty charts response
|
||||
fetchMock.get(
|
||||
API_ENDPOINTS.CHARTS,
|
||||
{
|
||||
result: [],
|
||||
chart_count: 0,
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
// Mock empty favorite status
|
||||
fetchMock.get(
|
||||
API_ENDPOINTS.CHART_FAVORITE_STATUS,
|
||||
{
|
||||
result: [],
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
renderChartList(mockUser);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('chart-list-view')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// No favorite stars should be present when there are no charts
|
||||
const favoriteStars = screen.queryAllByTestId('fave-unfave-icon');
|
||||
expect(favoriteStars).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('handles partial favorite status loading gracefully', async () => {
|
||||
// Mock partial favorite status (fewer items than charts)
|
||||
fetchMock.get(
|
||||
API_ENDPOINTS.CHART_FAVORITE_STATUS,
|
||||
{
|
||||
result: [
|
||||
{ id: 0, value: false },
|
||||
{ id: 1, value: false },
|
||||
// Missing status for charts 2 and 3
|
||||
],
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
renderChartList(mockUser);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('chart-list-view')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Wait for data to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Chart 0')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Should hide column when favorite status is incomplete
|
||||
const favoriteStars = screen.queryAllByTestId('fave-unfave-icon');
|
||||
expect(favoriteStars).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@@ -315,6 +315,14 @@ function ChartList(props: ChartListProps) {
|
||||
};
|
||||
};
|
||||
|
||||
const hasFavoritesOnPage = useMemo(
|
||||
() =>
|
||||
charts.length > 0 &&
|
||||
Object.keys(favoriteStatus).length === charts.length &&
|
||||
Object.values(favoriteStatus).some(status => status === true),
|
||||
[charts.length, favoriteStatus],
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -334,7 +342,7 @@ function ChartList(props: ChartListProps) {
|
||||
id: 'id',
|
||||
disableSortBy: true,
|
||||
size: 'xs',
|
||||
hidden: !userId,
|
||||
hidden: !userId || !hasFavoritesOnPage,
|
||||
},
|
||||
{
|
||||
Cell: ({
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* 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 { MemoryRouter } from 'react-router-dom';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { isFeatureEnabled } from '@superset-ui/core';
|
||||
import { render, screen, waitFor } from 'spec/helpers/testing-library';
|
||||
import { QueryParamProvider } from 'use-query-params';
|
||||
|
||||
import DashboardList from 'src/pages/DashboardList';
|
||||
|
||||
jest.mock('@superset-ui/core', () => ({
|
||||
...jest.requireActual('@superset-ui/core'),
|
||||
isFeatureEnabled: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.setTimeout(30000);
|
||||
|
||||
const dashboardsInfoEndpoint = 'glob:*/api/v1/dashboard/_info*';
|
||||
const dashboardOwnersEndpoint = 'glob:*/api/v1/dashboard/related/owners*';
|
||||
const dashboardCreatedByEndpoint =
|
||||
'glob:*/api/v1/dashboard/related/created_by*';
|
||||
const dashboardFavoriteStatusEndpoint =
|
||||
'glob:*/api/v1/dashboard/favorite_status*';
|
||||
const dashboardsEndpoint = 'glob:*/api/v1/dashboard/?*';
|
||||
|
||||
const mockDashboards = [...new Array(3)].map((_, i) => ({
|
||||
id: i,
|
||||
url: `url-${i}`,
|
||||
dashboard_title: `Dashboard ${i}`,
|
||||
changed_by_name: 'user',
|
||||
changed_by_fk: 1,
|
||||
published: true,
|
||||
changed_on_utc: new Date().toISOString(),
|
||||
changed_on_delta_humanized: '5 minutes ago',
|
||||
owners: [{ id: 1, first_name: 'admin', last_name: 'admin_user' }],
|
||||
roles: [{ id: 1, name: 'adminUser' }],
|
||||
thumbnail_url: '/thumbnail',
|
||||
}));
|
||||
|
||||
const mockUser = {
|
||||
userId: 1,
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
};
|
||||
|
||||
const setupBasicMocks = () => {
|
||||
fetchMock.reset();
|
||||
|
||||
fetchMock.get(dashboardsInfoEndpoint, {
|
||||
permissions: ['can_read', 'can_write'],
|
||||
});
|
||||
|
||||
fetchMock.get(dashboardOwnersEndpoint, {
|
||||
result: [],
|
||||
});
|
||||
|
||||
fetchMock.get(dashboardCreatedByEndpoint, {
|
||||
result: [],
|
||||
});
|
||||
|
||||
fetchMock.get(dashboardsEndpoint, {
|
||||
result: mockDashboards,
|
||||
dashboard_count: 3,
|
||||
});
|
||||
|
||||
global.URL.createObjectURL = jest.fn();
|
||||
fetchMock.get('/thumbnail', { body: new Blob(), sendAsJson: false });
|
||||
};
|
||||
|
||||
describe('DashboardList - Favorite Column Visibility', () => {
|
||||
const renderDashboardList = (props = {}, userProp = mockUser) =>
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<QueryParamProvider>
|
||||
<DashboardList {...props} user={userProp} />
|
||||
</QueryParamProvider>
|
||||
</MemoryRouter>,
|
||||
{ useRedux: true },
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
setupBasicMocks();
|
||||
(
|
||||
isFeatureEnabled as jest.MockedFunction<typeof isFeatureEnabled>
|
||||
).mockImplementation(feature => feature === 'LISTVIEWS_DEFAULT_CARD_VIEW');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fetchMock.resetHistory();
|
||||
fetchMock.restore();
|
||||
(
|
||||
isFeatureEnabled as jest.MockedFunction<typeof isFeatureEnabled>
|
||||
).mockReset();
|
||||
});
|
||||
|
||||
test('hides favorite column when no dashboards are favorited', async () => {
|
||||
// Mock favorite status API to return all false
|
||||
fetchMock.get(
|
||||
dashboardFavoriteStatusEndpoint,
|
||||
{
|
||||
result: [
|
||||
{ id: 0, value: false },
|
||||
{ id: 1, value: false },
|
||||
{ id: 2, value: false },
|
||||
],
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
renderDashboardList();
|
||||
|
||||
// Wait for component to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Dashboards')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Wait for data to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Dashboard 0')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Favorite column should be hidden - check that favorite stars are not present
|
||||
const favoriteStars = screen.queryAllByTestId('fave-unfave-icon');
|
||||
expect(favoriteStars).toHaveLength(0);
|
||||
|
||||
// Verify that other columns are still present
|
||||
expect(screen.getByText('Title')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('shows favorite column when at least one dashboard is favorited', async () => {
|
||||
// Mock favorite status API to return mixed favorites
|
||||
fetchMock.get(
|
||||
dashboardFavoriteStatusEndpoint,
|
||||
{
|
||||
result: [
|
||||
{ id: 0, value: true }, // This dashboard is favorited
|
||||
{ id: 1, value: false },
|
||||
{ id: 2, value: false },
|
||||
],
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
renderDashboardList();
|
||||
|
||||
// Wait for component to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Dashboards')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Wait for data to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Dashboard 0')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Favorite column should be visible - check that favorite stars are present
|
||||
await waitFor(
|
||||
() => {
|
||||
const favoriteStars = screen.getAllByTestId('fave-unfave-icon');
|
||||
expect(favoriteStars.length).toBeGreaterThan(0);
|
||||
},
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
});
|
||||
|
||||
test('shows favorite column when all dashboards are favorited', async () => {
|
||||
// Mock favorite status API to return all true
|
||||
fetchMock.get(
|
||||
dashboardFavoriteStatusEndpoint,
|
||||
{
|
||||
result: [
|
||||
{ id: 0, value: true },
|
||||
{ id: 1, value: true },
|
||||
{ id: 2, value: true },
|
||||
],
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
renderDashboardList();
|
||||
|
||||
// Wait for component to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Dashboards')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Wait for data to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Dashboard 0')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Favorite column should be visible
|
||||
await waitFor(
|
||||
() => {
|
||||
const favoriteStars = screen.getAllByTestId('fave-unfave-icon');
|
||||
expect(favoriteStars.length).toBeGreaterThan(0);
|
||||
},
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
});
|
||||
|
||||
test('hides favorite column when user is not logged in', async () => {
|
||||
// Mock favorite status API
|
||||
fetchMock.get(
|
||||
dashboardFavoriteStatusEndpoint,
|
||||
{
|
||||
result: [
|
||||
{ id: 0, value: true },
|
||||
{ id: 1, value: false },
|
||||
],
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
// Render without userId (user not logged in)
|
||||
const noUser = {
|
||||
userId: 0, // Use 0 instead of null to satisfy type requirements
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
};
|
||||
|
||||
renderDashboardList({}, noUser);
|
||||
|
||||
// Wait for component to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Dashboards')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Wait for data to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Dashboard 0')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Favorite column should be hidden when user is not logged in
|
||||
const favoriteStars = screen.queryAllByTestId('fave-unfave-icon');
|
||||
expect(favoriteStars).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('hides favorite column when dashboard list is empty', async () => {
|
||||
// Mock empty dashboards response
|
||||
fetchMock.get(
|
||||
dashboardsEndpoint,
|
||||
{
|
||||
result: [],
|
||||
dashboard_count: 0,
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
// Mock empty favorite status
|
||||
fetchMock.get(
|
||||
dashboardFavoriteStatusEndpoint,
|
||||
{
|
||||
result: [],
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
renderDashboardList();
|
||||
|
||||
// Wait for component to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Dashboards')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// No favorite stars should be present when there are no dashboards
|
||||
const favoriteStars = screen.queryAllByTestId('fave-unfave-icon');
|
||||
expect(favoriteStars).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('handles partial favorite status loading gracefully', async () => {
|
||||
// Mock partial favorite status (fewer items than dashboards)
|
||||
fetchMock.get(
|
||||
dashboardFavoriteStatusEndpoint,
|
||||
{
|
||||
result: [
|
||||
{ id: 0, value: false },
|
||||
// Missing status for dashboards 1 and 2
|
||||
],
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
renderDashboardList();
|
||||
|
||||
// Wait for component to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Dashboards')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Wait for data to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Dashboard 0')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Should hide column when favorite status is incomplete
|
||||
const favoriteStars = screen.queryAllByTestId('fave-unfave-icon');
|
||||
expect(favoriteStars).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@@ -300,6 +300,14 @@ function DashboardList(props: DashboardListProps) {
|
||||
);
|
||||
}
|
||||
|
||||
const hasFavoritesOnPage = useMemo(
|
||||
() =>
|
||||
dashboards.length > 0 &&
|
||||
Object.keys(favoriteStatus).length === dashboards.length &&
|
||||
Object.values(favoriteStatus).some(status => status === true),
|
||||
[dashboards.length, favoriteStatus],
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -319,7 +327,7 @@ function DashboardList(props: DashboardListProps) {
|
||||
id: 'id',
|
||||
disableSortBy: true,
|
||||
size: 'xs',
|
||||
hidden: !user?.userId,
|
||||
hidden: !user?.userId || !hasFavoritesOnPage,
|
||||
},
|
||||
{
|
||||
Cell: ({
|
||||
|
||||
Reference in New Issue
Block a user