Files
grafana/public/app/features/plugins/admin/pages/Browse.test.tsx

412 lines
19 KiB
TypeScript
Raw Normal View History

import { render, RenderResult, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { TestProvider } from 'test/helpers/TestProvider';
import { PluginType, escapeStringForRegex } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
import { RouteDescriptor } from 'app/core/navigation/types';
import { configureStore } from 'app/store/configureStore';
import { getCatalogPluginMock, getPluginsStateMock } from '../__mocks__';
import { fetchRemotePlugins } from '../state/actions';
import { PluginAdminRoutes, CatalogPlugin, ReducerState, RequestStatus } from '../types';
import BrowsePage from './Browse';
Plugins Catalog: migrate state handling to Redux (#38876) * feat(Plugins/Catalog): start adding necessary apis * feat(PLugins/Catalog): add extra helpers for merging local & remote plugins * feat(Plugins/Catalog): add plugin details as an optional field of CatalogPlugin * feat(PLugins/Catalog): add scaffolding for the new redux model * feat(PLugins/Catalog): export reducers based on a feature-flag * refactor(Plugins/Admin): rename api methods * feat(Plugin/Catalog): add an api method for fetching a single plugin * feat(Plugins/Admin): try cleaning stuff around plugin fetching * ffeat(Plugins/Catalog): return the catalog reducer when the feature flag is set * refactor(Plugins/Admin): fix typings * feat(Plugins/Admin): use the new reducer for the browse page * feat(catalog): introduce selectors to search and filter plugins list * refactor(Plugins/Details): rename page prop type * refactor(Plugins/Admin): add a const for a state prefix * refactor(Plugins/Admin): use the state prefix in the actions * feat(Plugins/Admin): add types for the requests * refactor(Plugins/Admin): add request info to the reducer * refactor(Plugins/Admin): add request handling to the hooks & selectors * refactor(Plugins/Details): start using the data stored in Redux * refactor(Plugins/Admin): rename selector to start with "select" * fix(Plugins/Admin): only fetch plugins once * refactor(Plugins/Admin): make the tab selection work in details * refactor(catalog): put back loading and error states in plugin list * refactor(Plugins/Admin): use CatalogPlugin for <PluginDetailsSignature /> * feat(Plugins/Admin): add an api method for fetching plugin details * refactor(Plugins/Admin): add action for updating the details * irefactor(Plugins/Admin): show basic plugin details info * refactor(Plugin Details): migrate the plugin details header * refactor(Plugins/Admin): make the config and dashboards tabs work * refactor(Plugins/Admin): add old reducer state to the new one * feat(catalog): introduce actions, reducers and hooks for install & uninstall * refactor(catalog): wire up InstallControls component to redux * refactor(catalog): move parentUrl inside PluginDetailsHeader and uncomment InstallControls * feat(catalog): introduce code for plugin updates to install action * refactor(Plugins/Admin): add backward compatible actions * test(catalog): update PluginDetails and Browse tests to work with catalog store * refactor(Plugins/Admin): make the dashboards and panels work again * refactor(Plugins/Admin): fix linter and typescript errors * fix(Plugins/Admin): put the local-only plugins to the beginning of the list * fix(Plugins/Admin): fix the mocks in the tests for PluginDetails * refactor(Plugins/Admin): remove unecessary hook usePluginsByFilter() * refactor(Plugins/Admin): extract the useTabs() hook to its own file * refactor(Plugins/Admin): remove unused helpers and types * fix(Plugins/Admin): show the first tab when uninstalling an app plugin This can cause the user to find themselves on a dissappeared tab, as the config and dashboards tabs are removed. * fix(catalog): correct logic for checking if activeTabIndex is greater than total tabs * fix(Plugins/Admin): fix race-condition between fetching plugin details and all plugins * fix(Plugins): fix strict type errors * chore(catalog): remove todos * feat(catalog): render an alert in PluginDetails when a plugin cannot be found * feat(catalog): use the proper store state * refactor(Plugins/Admin): fetch local and remote plugins in parallell Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com> * style(catalog): fix prettier error in api * fix(catalog): prevent throwing error if InstallControlsButton is unmounted during install * refactor(Plugins/Admin): add a separate hook for filtering & sorting plugins Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com> Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>
2021-09-09 12:20:35 +02:00
jest.mock('@grafana/runtime', () => {
const original = jest.requireActual('@grafana/runtime');
const mockedRuntime = { ...original };
mockedRuntime.config.bootData.user.isGrafanaAdmin = true;
mockedRuntime.config.buildInfo.version = 'v8.1.0';
return mockedRuntime;
Plugins Catalog: migrate state handling to Redux (#38876) * feat(Plugins/Catalog): start adding necessary apis * feat(PLugins/Catalog): add extra helpers for merging local & remote plugins * feat(Plugins/Catalog): add plugin details as an optional field of CatalogPlugin * feat(PLugins/Catalog): add scaffolding for the new redux model * feat(PLugins/Catalog): export reducers based on a feature-flag * refactor(Plugins/Admin): rename api methods * feat(Plugin/Catalog): add an api method for fetching a single plugin * feat(Plugins/Admin): try cleaning stuff around plugin fetching * ffeat(Plugins/Catalog): return the catalog reducer when the feature flag is set * refactor(Plugins/Admin): fix typings * feat(Plugins/Admin): use the new reducer for the browse page * feat(catalog): introduce selectors to search and filter plugins list * refactor(Plugins/Details): rename page prop type * refactor(Plugins/Admin): add a const for a state prefix * refactor(Plugins/Admin): use the state prefix in the actions * feat(Plugins/Admin): add types for the requests * refactor(Plugins/Admin): add request info to the reducer * refactor(Plugins/Admin): add request handling to the hooks & selectors * refactor(Plugins/Details): start using the data stored in Redux * refactor(Plugins/Admin): rename selector to start with "select" * fix(Plugins/Admin): only fetch plugins once * refactor(Plugins/Admin): make the tab selection work in details * refactor(catalog): put back loading and error states in plugin list * refactor(Plugins/Admin): use CatalogPlugin for <PluginDetailsSignature /> * feat(Plugins/Admin): add an api method for fetching plugin details * refactor(Plugins/Admin): add action for updating the details * irefactor(Plugins/Admin): show basic plugin details info * refactor(Plugin Details): migrate the plugin details header * refactor(Plugins/Admin): make the config and dashboards tabs work * refactor(Plugins/Admin): add old reducer state to the new one * feat(catalog): introduce actions, reducers and hooks for install & uninstall * refactor(catalog): wire up InstallControls component to redux * refactor(catalog): move parentUrl inside PluginDetailsHeader and uncomment InstallControls * feat(catalog): introduce code for plugin updates to install action * refactor(Plugins/Admin): add backward compatible actions * test(catalog): update PluginDetails and Browse tests to work with catalog store * refactor(Plugins/Admin): make the dashboards and panels work again * refactor(Plugins/Admin): fix linter and typescript errors * fix(Plugins/Admin): put the local-only plugins to the beginning of the list * fix(Plugins/Admin): fix the mocks in the tests for PluginDetails * refactor(Plugins/Admin): remove unecessary hook usePluginsByFilter() * refactor(Plugins/Admin): extract the useTabs() hook to its own file * refactor(Plugins/Admin): remove unused helpers and types * fix(Plugins/Admin): show the first tab when uninstalling an app plugin This can cause the user to find themselves on a dissappeared tab, as the config and dashboards tabs are removed. * fix(catalog): correct logic for checking if activeTabIndex is greater than total tabs * fix(Plugins/Admin): fix race-condition between fetching plugin details and all plugins * fix(Plugins): fix strict type errors * chore(catalog): remove todos * feat(catalog): render an alert in PluginDetails when a plugin cannot be found * feat(catalog): use the proper store state * refactor(Plugins/Admin): fetch local and remote plugins in parallell Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com> * style(catalog): fix prettier error in api * fix(catalog): prevent throwing error if InstallControlsButton is unmounted during install * refactor(Plugins/Admin): add a separate hook for filtering & sorting plugins Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com> Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>
2021-09-09 12:20:35 +02:00
});
const renderBrowse = (
path = '/plugins',
plugins: CatalogPlugin[] = [],
pluginsStateOverride?: ReducerState
): RenderResult => {
const store = configureStore({ plugins: pluginsStateOverride || getPluginsStateMock(plugins) });
locationService.push(path);
const props = getRouteComponentProps({
route: { routeName: PluginAdminRoutes.Home } as RouteDescriptor,
});
return render(
<TestProvider store={store}>
<BrowsePage {...props} />
</TestProvider>
);
};
describe('Browse list of plugins', () => {
describe('when filtering', () => {
it('should list installed plugins by default', async () => {
const { queryByText } = renderBrowse('/plugins', [
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', isInstalled: true }),
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', isInstalled: true }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', isInstalled: true }),
getCatalogPluginMock({ id: 'plugin-4', name: 'Plugin 4', isInstalled: false }),
]);
await waitFor(() => expect(queryByText('Plugin 1')).toBeInTheDocument());
expect(queryByText('Plugin 1')).toBeInTheDocument();
expect(queryByText('Plugin 2')).toBeInTheDocument();
expect(queryByText('Plugin 3')).toBeInTheDocument();
expect(queryByText('Plugin 4')).toBeNull();
});
it('should list all plugins (except core plugins) when filtering by all', async () => {
const { queryByText } = renderBrowse('/plugins?filterBy=all&filterByType=all', [
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', isInstalled: true }),
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', isInstalled: false }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', isInstalled: true }),
getCatalogPluginMock({ id: 'plugin-4', name: 'Plugin 4', isInstalled: true, isCore: true }),
]);
await waitFor(() => expect(queryByText('Plugin 1')).toBeInTheDocument());
expect(queryByText('Plugin 2')).toBeInTheDocument();
expect(queryByText('Plugin 3')).toBeInTheDocument();
// Core plugins should not be listed
expect(queryByText('Plugin 4')).not.toBeInTheDocument();
});
it('should list installed plugins (including core plugins) when filtering by installed', async () => {
const { queryByText } = renderBrowse('/plugins?filterBy=installed', [
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', isInstalled: true }),
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', isInstalled: false }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', isInstalled: true }),
getCatalogPluginMock({ id: 'plugin-4', name: 'Plugin 4', isInstalled: true, isCore: true }),
]);
await waitFor(() => expect(queryByText('Plugin 1')).toBeInTheDocument());
expect(queryByText('Plugin 3')).toBeInTheDocument();
expect(queryByText('Plugin 4')).toBeInTheDocument();
// Not showing not installed plugins
expect(queryByText('Plugin 2')).not.toBeInTheDocument();
});
it('should list all plugins (including disabled plugins) when filtering by all', async () => {
const { queryByText } = renderBrowse('/plugins?filterBy=all&filterByType=all', [
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', isInstalled: true }),
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', isInstalled: false }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', isInstalled: true }),
getCatalogPluginMock({ id: 'plugin-4', name: 'Plugin 4', isInstalled: true, isDisabled: true }),
]);
await waitFor(() => expect(queryByText('Plugin 1')).toBeInTheDocument());
expect(queryByText('Plugin 2')).toBeInTheDocument();
expect(queryByText('Plugin 3')).toBeInTheDocument();
expect(queryByText('Plugin 4')).toBeInTheDocument();
});
it('should list installed plugins (including disabled plugins) when filtering by installed', async () => {
const { queryByText } = renderBrowse('/plugins?filterBy=installed', [
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', isInstalled: true }),
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', isInstalled: false }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', isInstalled: true }),
getCatalogPluginMock({ id: 'plugin-4', name: 'Plugin 4', isInstalled: true, isDisabled: true }),
]);
await waitFor(() => expect(queryByText('Plugin 1')).toBeInTheDocument());
expect(queryByText('Plugin 3')).toBeInTheDocument();
expect(queryByText('Plugin 4')).toBeInTheDocument();
// Not showing not installed plugins
expect(queryByText('Plugin 2')).not.toBeInTheDocument();
});
it('should list enterprise plugins when querying for them', async () => {
const { queryByText } = renderBrowse('/plugins?filterBy=all&q=wavefront', [
getCatalogPluginMock({ id: 'wavefront', name: 'Wavefront', isInstalled: true, isEnterprise: true }),
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', isInstalled: true, isCore: true }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', isInstalled: true }),
]);
await waitFor(() => expect(queryByText('Wavefront')).toBeInTheDocument());
// Should not show plugins that don't match the query
expect(queryByText('Plugin 2')).not.toBeInTheDocument();
expect(queryByText('Plugin 3')).not.toBeInTheDocument();
});
it('should list only datasource plugins when filtering by datasource', async () => {
const { queryByText } = renderBrowse('/plugins?filterBy=all&filterByType=datasource', [
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', type: PluginType.app }),
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', type: PluginType.datasource }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', type: PluginType.panel }),
]);
await waitFor(() => expect(queryByText('Plugin 2')).toBeInTheDocument());
// Other plugin types shouldn't be shown
expect(queryByText('Plugin 1')).not.toBeInTheDocument();
expect(queryByText('Plugin 3')).not.toBeInTheDocument();
});
it('should list only panel plugins when filtering by panel', async () => {
const { queryByText } = renderBrowse('/plugins?filterBy=all&filterByType=panel', [
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', type: PluginType.app }),
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', type: PluginType.datasource }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', type: PluginType.panel }),
]);
await waitFor(() => expect(queryByText('Plugin 3')).toBeInTheDocument());
// Other plugin types shouldn't be shown
expect(queryByText('Plugin 1')).not.toBeInTheDocument();
expect(queryByText('Plugin 2')).not.toBeInTheDocument();
});
it('should list only app plugins when filtering by app', async () => {
const { queryByText } = renderBrowse('/plugins?filterBy=all&filterByType=app', [
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', type: PluginType.app }),
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', type: PluginType.datasource }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', type: PluginType.panel }),
]);
await waitFor(() => expect(queryByText('Plugin 1')).toBeInTheDocument());
// Other plugin types shouldn't be shown
expect(queryByText('Plugin 2')).not.toBeInTheDocument();
expect(queryByText('Plugin 3')).not.toBeInTheDocument();
});
});
describe('when searching', () => {
it('should only list plugins matching search', async () => {
const { queryByText } = renderBrowse('/plugins?filterBy=all&q=zabbix', [
getCatalogPluginMock({ id: 'zabbix', name: 'Zabbix' }),
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2' }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3' }),
]);
await waitFor(() => expect(queryByText('Zabbix')).toBeInTheDocument());
// Other plugin types shouldn't be shown
expect(queryByText('Plugin 2')).not.toBeInTheDocument();
expect(queryByText('Plugin 3')).not.toBeInTheDocument();
});
it('should handle escaped regex characters in the search query (e.g. "(" )', async () => {
const { queryByText } = renderBrowse('/plugins?filterBy=all&q=' + escapeStringForRegex('graph (old)'), [
getCatalogPluginMock({ id: 'graph', name: 'Graph (old)' }),
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2' }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3' }),
]);
await waitFor(() => expect(queryByText('Graph (old)')).toBeInTheDocument());
// Other plugin types shouldn't be shown
expect(queryByText('Plugin 2')).not.toBeInTheDocument();
expect(queryByText('Plugin 3')).not.toBeInTheDocument();
});
it('should be possible to filter plugins by type', async () => {
const { queryByText } = renderBrowse('/plugins?filterByType=datasource&filterBy=all', [
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', type: PluginType.app }),
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', type: PluginType.app }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', type: PluginType.datasource }),
]);
await waitFor(() => expect(queryByText('Plugin 3')).toBeInTheDocument());
// Other plugin types shouldn't be shown
expect(queryByText('Plugin 1')).not.toBeInTheDocument();
expect(queryByText('Plugin 2')).not.toBeInTheDocument();
});
it('should be possible to filter plugins both by type and a keyword', async () => {
const { queryByText } = renderBrowse('/plugins?filterByType=datasource&filterBy=all&q=Foo', [
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', type: PluginType.app }),
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', type: PluginType.datasource }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Foo plugin', type: PluginType.datasource }),
]);
await waitFor(() => expect(queryByText('Foo plugin')).toBeInTheDocument());
// Other plugin types shouldn't be shown
expect(queryByText('Plugin 1')).not.toBeInTheDocument();
expect(queryByText('Plugin 2')).not.toBeInTheDocument();
});
it('should list all available plugins if the keyword is empty', async () => {
const { queryByText } = renderBrowse('/plugins?filterBy=all&q=', [
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', type: PluginType.app }),
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2', type: PluginType.panel }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3', type: PluginType.datasource }),
]);
// We did not filter for any specific plugin type, so all plugins should be shown
await waitFor(() => expect(queryByText('Plugin 1')).toBeInTheDocument());
expect(queryByText('Plugin 2')).toBeInTheDocument();
expect(queryByText('Plugin 3')).toBeInTheDocument();
});
});
describe('when sorting', () => {
it('should sort plugins by name in ascending alphabetical order', async () => {
const { findByTestId } = renderBrowse('/plugins?filterBy=all', [
getCatalogPluginMock({ id: 'wavefront', name: 'Wavefront' }),
getCatalogPluginMock({ id: 'redis-application', name: 'Redis Application' }),
getCatalogPluginMock({ id: 'zabbix', name: 'Zabbix' }),
getCatalogPluginMock({ id: 'diagram', name: 'Diagram' }),
getCatalogPluginMock({ id: 'acesvg', name: 'ACE.SVG' }),
]);
const pluginList = await findByTestId('plugin-list');
const pluginHeadings = within(pluginList).queryAllByRole('heading');
expect(pluginHeadings.map((heading) => heading.innerHTML)).toStrictEqual([
'ACE.SVG',
'Diagram',
'Redis Application',
'Wavefront',
'Zabbix',
]);
});
it('should sort plugins by name in descending alphabetical order', async () => {
const { findByTestId } = renderBrowse('/plugins?filterBy=all&sortBy=nameDesc', [
getCatalogPluginMock({ id: 'wavefront', name: 'Wavefront' }),
getCatalogPluginMock({ id: 'redis-application', name: 'Redis Application' }),
getCatalogPluginMock({ id: 'zabbix', name: 'Zabbix' }),
getCatalogPluginMock({ id: 'diagram', name: 'Diagram' }),
getCatalogPluginMock({ id: 'acesvg', name: 'ACE.SVG' }),
]);
const pluginList = await findByTestId('plugin-list');
const pluginHeadings = within(pluginList).queryAllByRole('heading');
expect(pluginHeadings.map((heading) => heading.innerHTML)).toStrictEqual([
'Zabbix',
'Wavefront',
'Redis Application',
'Diagram',
'ACE.SVG',
]);
});
it('should sort plugins by date in ascending updated order', async () => {
const { findByTestId } = renderBrowse('/plugins?filterBy=all&sortBy=updated', [
getCatalogPluginMock({ id: '1', name: 'Wavefront', updatedAt: '2021-04-01T00:00:00.000Z' }),
getCatalogPluginMock({ id: '2', name: 'Redis Application', updatedAt: '2021-02-01T00:00:00.000Z' }),
getCatalogPluginMock({ id: '3', name: 'Zabbix', updatedAt: '2021-01-01T00:00:00.000Z' }),
getCatalogPluginMock({ id: '4', name: 'Diagram', updatedAt: '2021-05-01T00:00:00.000Z' }),
getCatalogPluginMock({ id: '5', name: 'ACE.SVG', updatedAt: '2021-02-01T00:00:00.000Z' }),
]);
const pluginList = await findByTestId('plugin-list');
const pluginHeadings = within(pluginList).queryAllByRole('heading');
expect(pluginHeadings.map((heading) => heading.innerHTML)).toStrictEqual([
'Diagram',
'Wavefront',
'Redis Application',
'ACE.SVG',
'Zabbix',
]);
});
it('should sort plugins by date in ascending published order', async () => {
const { findByTestId } = renderBrowse('/plugins?filterBy=all&sortBy=published', [
getCatalogPluginMock({ id: '1', name: 'Wavefront', publishedAt: '2021-04-01T00:00:00.000Z' }),
getCatalogPluginMock({ id: '2', name: 'Redis Application', publishedAt: '2021-02-01T00:00:00.000Z' }),
getCatalogPluginMock({ id: '3', name: 'Zabbix', publishedAt: '2021-01-01T00:00:00.000Z' }),
getCatalogPluginMock({ id: '4', name: 'Diagram', publishedAt: '2021-05-01T00:00:00.000Z' }),
getCatalogPluginMock({ id: '5', name: 'ACE.SVG', publishedAt: '2021-02-01T00:00:00.000Z' }),
]);
const pluginList = await findByTestId('plugin-list');
const pluginHeadings = within(pluginList).queryAllByRole('heading');
expect(pluginHeadings.map((heading) => heading.innerHTML)).toStrictEqual([
'Diagram',
'Wavefront',
'Redis Application',
'ACE.SVG',
'Zabbix',
]);
});
it('should sort plugins by number of downloads in ascending order', async () => {
const { findByTestId } = renderBrowse('/plugins?filterBy=all&sortBy=downloads', [
getCatalogPluginMock({ id: '1', name: 'Wavefront', downloads: 30 }),
getCatalogPluginMock({ id: '2', name: 'Redis Application', downloads: 10 }),
getCatalogPluginMock({ id: '3', name: 'Zabbix', downloads: 50 }),
getCatalogPluginMock({ id: '4', name: 'Diagram', downloads: 20 }),
getCatalogPluginMock({ id: '5', name: 'ACE.SVG', downloads: 40 }),
]);
const pluginList = await findByTestId('plugin-list');
const pluginHeadings = within(pluginList).queryAllByRole('heading');
expect(pluginHeadings.map((heading) => heading.innerHTML)).toStrictEqual([
'Zabbix',
'ACE.SVG',
'Wavefront',
'Diagram',
'Redis Application',
]);
});
});
describe('when GCOM api is not available', () => {
it('should disable the All / Installed filter', async () => {
const plugins = [
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1', isInstalled: true }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 2', isInstalled: true }),
getCatalogPluginMock({ id: 'plugin-4', name: 'Plugin 3', isInstalled: true }),
];
const state = getPluginsStateMock(plugins);
// Mock the store like if the remote plugins request was rejected
const stateOverride = {
...state,
requests: {
...state.requests,
[fetchRemotePlugins.typePrefix]: {
status: RequestStatus.Rejected,
},
},
};
// The radio input for the filters should be disabled
const { getByRole } = renderBrowse('/plugins', [], stateOverride);
await waitFor(() => expect(getByRole('radio', { name: 'Installed' })).toBeDisabled());
});
});
it('should be possible to switch between display modes', async () => {
const { findByTestId, getByRole, getByTitle, queryByText } = renderBrowse('/plugins?filterBy=all', [
getCatalogPluginMock({ id: 'plugin-1', name: 'Plugin 1' }),
getCatalogPluginMock({ id: 'plugin-2', name: 'Plugin 2' }),
getCatalogPluginMock({ id: 'plugin-3', name: 'Plugin 3' }),
]);
await findByTestId('plugin-list');
const listOptionTitle = 'Display plugins in list';
const gridOptionTitle = 'Display plugins in a grid layout';
const listOption = getByRole('radio', { name: listOptionTitle });
const listOptionLabel = getByTitle(listOptionTitle);
const gridOption = getByRole('radio', { name: gridOptionTitle });
const gridOptionLabel = getByTitle(gridOptionTitle);
// All options should be visible
expect(listOptionLabel).toBeVisible();
expect(gridOptionLabel).toBeVisible();
// The default display mode should be "grid"
expect(gridOption).toBeChecked();
expect(listOption).not.toBeChecked();
// Switch to "list" view
await userEvent.click(listOption);
expect(gridOption).not.toBeChecked();
expect(listOption).toBeChecked();
// All plugins are still visible
expect(queryByText('Plugin 1')).toBeInTheDocument();
expect(queryByText('Plugin 2')).toBeInTheDocument();
expect(queryByText('Plugin 3')).toBeInTheDocument();
});
});