| /** |
| * 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 { |
| render, |
| screen, |
| userEvent, |
| waitFor, |
| } from 'spec/helpers/testing-library'; |
| import { isFeatureEnabled, getExtensionsRegistry } from '@superset-ui/core'; |
| import Welcome from 'src/pages/Home'; |
| import setupCodeOverrides from 'src/setup/setupCodeOverrides'; |
| |
| const chartsEndpoint = 'glob:*/api/v1/chart/?*'; |
| const chartInfoEndpoint = 'glob:*/api/v1/chart/_info?*'; |
| const chartFavoriteStatusEndpoint = 'glob:*/api/v1/chart/favorite_status?*'; |
| const dashboardsEndpoint = 'glob:*/api/v1/dashboard/?*'; |
| const dashboardInfoEndpoint = 'glob:*/api/v1/dashboard/_info?*'; |
| const dashboardFavoriteStatusEndpoint = |
| 'glob:*/api/v1/dashboard/favorite_status/?*'; |
| const savedQueryEndpoint = 'glob:*/api/v1/saved_query/?*'; |
| const savedQueryInfoEndpoint = 'glob:*/api/v1/saved_query/_info?*'; |
| const recentActivityEndpoint = 'glob:*/api/v1/log/recent_activity/*'; |
| |
| fetchMock.get(chartsEndpoint, { |
| result: [ |
| { |
| slice_name: 'ChartyChart', |
| changed_on_utc: '24 Feb 2014 10:13:14', |
| url: '/fakeUrl/explore', |
| id: '4', |
| table: {}, |
| }, |
| ], |
| }); |
| |
| fetchMock.get(dashboardsEndpoint, { |
| result: [ |
| { |
| dashboard_title: 'Dashboard_Test', |
| changed_on_utc: '24 Feb 2014 10:13:14', |
| url: '/fakeUrl/dashboard', |
| id: '3', |
| }, |
| ], |
| }); |
| |
| fetchMock.get(savedQueryEndpoint, { |
| result: [], |
| }); |
| |
| const mockRecentActivityResult = [ |
| { |
| action: 'dashboard', |
| item_title: "World Bank's Data", |
| item_type: 'dashboard', |
| item_url: '/superset/dashboard/world_health/', |
| time: 1741644942130.566, |
| time_delta_humanized: 'a day ago', |
| }, |
| { |
| action: 'dashboard', |
| item_title: '[ untitled dashboard ]', |
| item_type: 'dashboard', |
| item_url: '/superset/dashboard/19/', |
| time: 1741644881695.7869, |
| time_delta_humanized: 'a day ago', |
| }, |
| { |
| action: 'dashboard', |
| item_title: '[ untitled dashboard ]', |
| item_type: 'dashboard', |
| item_url: '/superset/dashboard/19/', |
| time: 1741644381695.7869, |
| time_delta_humanized: 'two day ago', |
| }, |
| ]; |
| |
| fetchMock.get(recentActivityEndpoint, { |
| result: mockRecentActivityResult, |
| }); |
| |
| fetchMock.get(chartInfoEndpoint, { |
| permissions: [], |
| }); |
| |
| fetchMock.get(chartFavoriteStatusEndpoint, { |
| result: [], |
| }); |
| |
| fetchMock.get(dashboardInfoEndpoint, { |
| permissions: [], |
| }); |
| |
| fetchMock.get(dashboardFavoriteStatusEndpoint, { |
| result: [], |
| }); |
| |
| fetchMock.get(savedQueryInfoEndpoint, { |
| permissions: [], |
| }); |
| |
| const mockedProps = { |
| user: { |
| username: 'alpha', |
| firstName: 'alpha', |
| lastName: 'alpha', |
| createdOn: '2016-11-11T12:34:17', |
| userId: 5, |
| email: 'alpha@alpha.com', |
| isActive: true, |
| isAnonymous: false, |
| permissions: {}, |
| roles: { |
| sql_lab: [['can_read', 'SavedQuery']], |
| }, |
| }, |
| }; |
| |
| const mockedPropsWithoutSqlRole = { |
| ...{ |
| ...mockedProps, |
| user: { |
| ...mockedProps.user, |
| roles: {}, |
| }, |
| }, |
| }; |
| |
| jest.mock('@superset-ui/core', () => ({ |
| ...jest.requireActual('@superset-ui/core'), |
| isFeatureEnabled: jest.fn(), |
| })); |
| |
| const mockedIsFeatureEnabled = isFeatureEnabled as jest.Mock; |
| |
| const renderWelcome = (props = mockedProps) => |
| waitFor(() => { |
| render(<Welcome {...props} />, { |
| useRedux: true, |
| useRouter: true, |
| }); |
| }); |
| |
| afterEach(() => { |
| fetchMock.resetHistory(); |
| }); |
| |
| test('With sql role - renders', async () => { |
| await renderWelcome(); |
| expect(await screen.findByText('Dashboards')).toBeInTheDocument(); |
| }); |
| |
| test('With sql role - renders all panels on the page on page load', async () => { |
| await renderWelcome(); |
| const panels = await screen.findAllByText( |
| /Dashboards|Charts|Recents|Saved queries/, |
| ); |
| expect(panels).toHaveLength(4); |
| }); |
| |
| test('With sql role - renders distinct recent activities', async () => { |
| await renderWelcome(); |
| const recentPanel = screen.getByRole('button', { name: 'collapsed Recents' }); |
| userEvent.click(recentPanel); |
| await waitFor(() => |
| expect( |
| screen.queryAllByText(mockRecentActivityResult[0].item_title), |
| ).toHaveLength(1), |
| ); |
| expect( |
| screen.queryAllByText(mockRecentActivityResult[1].item_title), |
| ).toHaveLength(1); |
| }); |
| |
| test('With sql role - calls api methods in parallel on page load', async () => { |
| await renderWelcome(); |
| expect(fetchMock.calls(chartsEndpoint)).toHaveLength(2); |
| expect(fetchMock.calls(recentActivityEndpoint)).toHaveLength(1); |
| expect(fetchMock.calls(savedQueryEndpoint)).toHaveLength(1); |
| expect(fetchMock.calls(dashboardsEndpoint)).toHaveLength(2); |
| }); |
| |
| test('Without sql role - renders', async () => { |
| /* |
| We ignore the ts error here because the type does not recognize the absence of a role entry |
| */ |
| // @ts-ignore-next-line |
| await renderWelcome(mockedPropsWithoutSqlRole); |
| expect(await screen.findByText('Dashboards')).toBeInTheDocument(); |
| }); |
| |
| test('Without sql role - renders all panels on the page on page load', async () => { |
| // @ts-ignore-next-line |
| await renderWelcome(mockedPropsWithoutSqlRole); |
| const panels = await screen.findAllByText(/Dashboards|Charts|Recents/); |
| expect(panels).toHaveLength(3); |
| }); |
| |
| test('Without sql role - calls api methods in parallel on page load', async () => { |
| // @ts-ignore-next-line |
| await renderWelcome(mockedPropsWithoutSqlRole); |
| expect(fetchMock.calls(chartsEndpoint)).toHaveLength(2); |
| expect(fetchMock.calls(recentActivityEndpoint)).toHaveLength(1); |
| expect(fetchMock.calls(savedQueryEndpoint)).toHaveLength(0); |
| expect(fetchMock.calls(dashboardsEndpoint)).toHaveLength(2); |
| }); |
| |
| // Mock specific to the tests related to the toggle switch |
| fetchMock.get('glob:*/api/v1/dashboard/*', { |
| result: { |
| dashboard_title: 'Dashboard 4', |
| changed_on_utc: '24 Feb 2014 10:13:14', |
| url: '/fakeUrl/dashboard/4', |
| id: '4', |
| }, |
| }); |
| |
| test('With toggle switch - shows a toggle button when feature flag is turned on', async () => { |
| mockedIsFeatureEnabled.mockReturnValue(true); |
| |
| await renderWelcome(); |
| expect(screen.getByRole('switch')).toBeInTheDocument(); |
| }); |
| |
| test('With toggle switch - does not show thumbnails when switch is off', async () => { |
| mockedIsFeatureEnabled.mockReturnValue(true); |
| |
| await renderWelcome(); |
| const toggle = await screen.findByRole('switch', {}, { timeout: 10000 }); |
| |
| await waitFor( |
| () => { |
| userEvent.click(toggle); |
| expect(screen.queryByAltText('Thumbnails')).not.toBeInTheDocument(); |
| }, |
| { timeout: 10000 }, |
| ); |
| }); |
| |
| test('Should render an extension component if one is supplied', async () => { |
| const extensionsRegistry = getExtensionsRegistry(); |
| |
| extensionsRegistry.set('welcome.banner', () => ( |
| <>welcome.banner extension component</> |
| )); |
| |
| setupCodeOverrides(); |
| |
| await renderWelcome(); |
| |
| expect( |
| screen.getByText('welcome.banner extension component'), |
| ).toBeInTheDocument(); |
| }); |
| |
| test('Should render a submenu extension component if one is supplied', async () => { |
| const extensionsRegistry = getExtensionsRegistry(); |
| |
| extensionsRegistry.set('home.submenu', () => <>submenu extension</>); |
| |
| setupCodeOverrides(); |
| |
| await renderWelcome(); |
| |
| expect(screen.getByText('submenu extension')).toBeInTheDocument(); |
| }); |
| |
| test('Should not make data fetch calls if `welcome.main.replacement` is defined', async () => { |
| const extensionsRegistry = getExtensionsRegistry(); |
| |
| // Clean up |
| extensionsRegistry.set('welcome.banner', () => null); |
| |
| // Set up |
| extensionsRegistry.set('welcome.main.replacement', () => ( |
| <>welcome.main.replacement extension component</> |
| )); |
| |
| setupCodeOverrides(); |
| |
| await renderWelcome(); |
| |
| expect( |
| screen.getByText('welcome.main.replacement extension component'), |
| ).toBeInTheDocument(); |
| |
| expect(fetchMock.calls(chartsEndpoint)).toHaveLength(0); |
| expect(fetchMock.calls(dashboardsEndpoint)).toHaveLength(0); |
| expect(fetchMock.calls(recentActivityEndpoint)).toHaveLength(0); |
| expect(fetchMock.calls(savedQueryEndpoint)).toHaveLength(0); |
| }); |