Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/ml/model_registry/components')
-rw-r--r--spec/frontend/ml/model_registry/components/actions_dropdown_spec.js39
-rw-r--r--spec/frontend/ml/model_registry/components/candidate_list_spec.js94
-rw-r--r--spec/frontend/ml/model_registry/components/model_version_list_spec.js90
-rw-r--r--spec/frontend/ml/model_registry/components/searchable_list_spec.js170
4 files changed, 245 insertions, 148 deletions
diff --git a/spec/frontend/ml/model_registry/components/actions_dropdown_spec.js b/spec/frontend/ml/model_registry/components/actions_dropdown_spec.js
new file mode 100644
index 00000000000..6285d7360c7
--- /dev/null
+++ b/spec/frontend/ml/model_registry/components/actions_dropdown_spec.js
@@ -0,0 +1,39 @@
+import { mount } from '@vue/test-utils';
+import { GlDisclosureDropdownItem } from '@gitlab/ui';
+import ActionsDropdown from '~/ml/model_registry/components/actions_dropdown.vue';
+
+describe('ml/model_registry/components/actions_dropdown', () => {
+ let wrapper;
+
+ const showToast = jest.fn();
+
+ const createWrapper = () => {
+ wrapper = mount(ActionsDropdown, {
+ mocks: {
+ $toast: {
+ show: showToast,
+ },
+ },
+ provide: {
+ mlflowTrackingUrl: 'path/to/mlflow',
+ },
+ });
+ };
+
+ const findCopyLinkDropdownItem = () => wrapper.findComponent(GlDisclosureDropdownItem);
+
+ it('has data-clipboard-text set to the correct url', () => {
+ createWrapper();
+
+ expect(findCopyLinkDropdownItem().text()).toBe('Copy MLflow tracking URL');
+ expect(findCopyLinkDropdownItem().attributes()['data-clipboard-text']).toBe('path/to/mlflow');
+ });
+
+ it('shows a success toast after copying the url to the clipboard', () => {
+ createWrapper();
+
+ findCopyLinkDropdownItem().find('button').trigger('click');
+
+ expect(showToast).toHaveBeenCalledWith('Copied MLflow tracking URL to clipboard');
+ });
+});
diff --git a/spec/frontend/ml/model_registry/components/candidate_list_spec.js b/spec/frontend/ml/model_registry/components/candidate_list_spec.js
index c10222a99fd..8491c7be16f 100644
--- a/spec/frontend/ml/model_registry/components/candidate_list_spec.js
+++ b/spec/frontend/ml/model_registry/components/candidate_list_spec.js
@@ -1,13 +1,11 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import { GlAlert } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import CandidateList from '~/ml/model_registry/components/candidate_list.vue';
-import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
-import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
+import SearchableList from '~/ml/model_registry/components/searchable_list.vue';
import CandidateListRow from '~/ml/model_registry/components/candidate_list_row.vue';
import getModelCandidatesQuery from '~/ml/model_registry/graphql/queries/get_model_candidates.query.graphql';
import { GRAPHQL_PAGE_SIZE } from '~/ml/model_registry/constants';
@@ -24,10 +22,7 @@ describe('ml/model_registry/components/candidate_list.vue', () => {
let wrapper;
let apolloProvider;
- const findAlert = () => wrapper.findComponent(GlAlert);
- const findLoader = () => wrapper.findComponent(PackagesListLoader);
- const findRegistryList = () => wrapper.findComponent(RegistryList);
- const findListRow = () => wrapper.findComponent(CandidateListRow);
+ const findSearchableList = () => wrapper.findComponent(SearchableList);
const findAllRows = () => wrapper.findAllComponents(CandidateListRow);
const mountComponent = ({
@@ -37,15 +32,12 @@ describe('ml/model_registry/components/candidate_list.vue', () => {
const requestHandlers = [[getModelCandidatesQuery, resolver]];
apolloProvider = createMockApollo(requestHandlers);
- wrapper = shallowMount(CandidateList, {
+ wrapper = mount(CandidateList, {
apolloProvider,
propsData: {
modelId: 2,
...props,
},
- stubs: {
- RegistryList,
- },
});
};
@@ -60,25 +52,9 @@ describe('ml/model_registry/components/candidate_list.vue', () => {
await waitForPromises();
});
- it('displays empty slot message', () => {
+ it('shows empty state', () => {
expect(wrapper.text()).toContain('This model has no candidates');
});
-
- it('does not display loader', () => {
- expect(findLoader().exists()).toBe(false);
- });
-
- it('does not display rows', () => {
- expect(findListRow().exists()).toBe(false);
- });
-
- it('does not display registry list', () => {
- expect(findRegistryList().exists()).toBe(false);
- });
-
- it('does not display alert', () => {
- expect(findAlert().exists()).toBe(false);
- });
});
describe('if load fails, alert', () => {
@@ -90,19 +66,9 @@ describe('ml/model_registry/components/candidate_list.vue', () => {
});
it('is displayed', () => {
- expect(findAlert().exists()).toBe(true);
- });
-
- it('shows error message', () => {
- expect(findAlert().text()).toContain('Failed to load model candidates with error: Failure!');
- });
-
- it('is not dismissible', () => {
- expect(findAlert().props('dismissible')).toBe(false);
- });
-
- it('is of variant danger', () => {
- expect(findAlert().attributes('variant')).toBe('danger');
+ expect(findSearchableList().props('errorMessage')).toBe(
+ 'Failed to load model candidates with error: Failure!',
+ );
});
it('error is logged in sentry', () => {
@@ -116,21 +82,11 @@ describe('ml/model_registry/components/candidate_list.vue', () => {
await waitForPromises();
});
- it('displays package registry list', () => {
- expect(findRegistryList().exists()).toEqual(true);
+ it('Passes items to list', () => {
+ expect(findSearchableList().props('items')).toEqual(graphqlCandidates);
});
- it('binds the right props', () => {
- expect(findRegistryList().props()).toMatchObject({
- items: graphqlCandidates,
- pagination: {},
- isLoading: false,
- hiddenDelete: true,
- });
- });
-
- it('displays candidate rows', () => {
- expect(findAllRows().exists()).toEqual(true);
+ it('displays package version rows', () => {
expect(findAllRows()).toHaveLength(graphqlCandidates.length);
});
@@ -143,17 +99,9 @@ describe('ml/model_registry/components/candidate_list.vue', () => {
candidate: expect.objectContaining(graphqlCandidates[1]),
});
});
-
- it('does not display loader', () => {
- expect(findLoader().exists()).toBe(false);
- });
-
- it('does not display empty message', () => {
- expect(findAlert().exists()).toBe(false);
- });
});
- describe('when user interacts with pagination', () => {
+ describe('when list requests update', () => {
const resolver = jest.fn().mockResolvedValue(modelCandidatesQuery());
beforeEach(async () => {
@@ -161,21 +109,17 @@ describe('ml/model_registry/components/candidate_list.vue', () => {
await waitForPromises();
});
- it('when list emits next-page fetches the next set of records', async () => {
- findRegistryList().vm.$emit('next-page');
- await waitForPromises();
-
- expect(resolver).toHaveBeenLastCalledWith(
- expect.objectContaining({ after: graphqlPageInfo.endCursor, first: GRAPHQL_PAGE_SIZE }),
- );
- });
+ it('when list emits fetch-page fetches the next set of records', async () => {
+ findSearchableList().vm.$emit('fetch-page', {
+ after: 'eyJpZCI6IjIifQ',
+ first: 30,
+ id: 'gid://gitlab/Ml::Model/2',
+ });
- it('when list emits prev-page fetches the prev set of records', async () => {
- findRegistryList().vm.$emit('prev-page');
await waitForPromises();
expect(resolver).toHaveBeenLastCalledWith(
- expect.objectContaining({ before: graphqlPageInfo.startCursor, last: GRAPHQL_PAGE_SIZE }),
+ expect.objectContaining({ after: graphqlPageInfo.endCursor, first: GRAPHQL_PAGE_SIZE }),
);
});
});
diff --git a/spec/frontend/ml/model_registry/components/model_version_list_spec.js b/spec/frontend/ml/model_registry/components/model_version_list_spec.js
index 41f7e71c543..f5d6acf3bae 100644
--- a/spec/frontend/ml/model_registry/components/model_version_list_spec.js
+++ b/spec/frontend/ml/model_registry/components/model_version_list_spec.js
@@ -1,13 +1,11 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import { GlAlert } from '@gitlab/ui';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import ModelVersionList from '~/ml/model_registry/components/model_version_list.vue';
-import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
-import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
+import SearchableList from '~/ml/model_registry/components/searchable_list.vue';
import ModelVersionRow from '~/ml/model_registry/components/model_version_row.vue';
import getModelVersionsQuery from '~/ml/model_registry/graphql/queries/get_model_versions.query.graphql';
import EmptyState from '~/ml/model_registry/components/empty_state.vue';
@@ -25,11 +23,8 @@ describe('ModelVersionList', () => {
let wrapper;
let apolloProvider;
- const findAlert = () => wrapper.findComponent(GlAlert);
- const findLoader = () => wrapper.findComponent(PackagesListLoader);
- const findRegistryList = () => wrapper.findComponent(RegistryList);
+ const findSearchableList = () => wrapper.findComponent(SearchableList);
const findEmptyState = () => wrapper.findComponent(EmptyState);
- const findListRow = () => wrapper.findComponent(ModelVersionRow);
const findAllRows = () => wrapper.findAllComponents(ModelVersionRow);
const mountComponent = ({
@@ -39,15 +34,12 @@ describe('ModelVersionList', () => {
const requestHandlers = [[getModelVersionsQuery, resolver]];
apolloProvider = createMockApollo(requestHandlers);
- wrapper = shallowMountExtended(ModelVersionList, {
+ wrapper = mountExtended(ModelVersionList, {
apolloProvider,
propsData: {
modelId: 2,
...props,
},
- stubs: {
- RegistryList,
- },
});
};
@@ -65,22 +57,6 @@ describe('ModelVersionList', () => {
it('shows empty state', () => {
expect(findEmptyState().props('entityType')).toBe(MODEL_ENTITIES.modelVersion);
});
-
- it('does not display loader', () => {
- expect(findLoader().exists()).toBe(false);
- });
-
- it('does not display rows', () => {
- expect(findListRow().exists()).toBe(false);
- });
-
- it('does not display registry list', () => {
- expect(findRegistryList().exists()).toBe(false);
- });
-
- it('does not display alert', () => {
- expect(findAlert().exists()).toBe(false);
- });
});
describe('if load fails, alert', () => {
@@ -92,19 +68,9 @@ describe('ModelVersionList', () => {
});
it('is displayed', () => {
- expect(findAlert().exists()).toBe(true);
- });
-
- it('shows error message', () => {
- expect(findAlert().text()).toContain('Failed to load model versions with error: Failure!');
- });
-
- it('is not dismissible', () => {
- expect(findAlert().props('dismissible')).toBe(false);
- });
-
- it('is of variant danger', () => {
- expect(findAlert().attributes('variant')).toBe('danger');
+ expect(findSearchableList().props('errorMessage')).toBe(
+ 'Failed to load model versions with error: Failure!',
+ );
});
it('error is logged in sentry', () => {
@@ -118,21 +84,11 @@ describe('ModelVersionList', () => {
await waitForPromises();
});
- it('displays package registry list', () => {
- expect(findRegistryList().exists()).toEqual(true);
- });
-
- it('binds the right props', () => {
- expect(findRegistryList().props()).toMatchObject({
- items: graphqlModelVersions,
- pagination: {},
- isLoading: false,
- hiddenDelete: true,
- });
+ it('Passes items to list', () => {
+ expect(findSearchableList().props('items')).toEqual(graphqlModelVersions);
});
it('displays package version rows', () => {
- expect(findAllRows().exists()).toEqual(true);
expect(findAllRows()).toHaveLength(graphqlModelVersions.length);
});
@@ -145,17 +101,9 @@ describe('ModelVersionList', () => {
modelVersion: expect.objectContaining(graphqlModelVersions[1]),
});
});
-
- it('does not display loader', () => {
- expect(findLoader().exists()).toBe(false);
- });
-
- it('does not display empty state', () => {
- expect(findEmptyState().exists()).toBe(false);
- });
});
- describe('when user interacts with pagination', () => {
+ describe('when list requests update', () => {
const resolver = jest.fn().mockResolvedValue(modelVersionsQuery());
beforeEach(async () => {
@@ -163,21 +111,17 @@ describe('ModelVersionList', () => {
await waitForPromises();
});
- it('when list emits next-page fetches the next set of records', async () => {
- findRegistryList().vm.$emit('next-page');
- await waitForPromises();
-
- expect(resolver).toHaveBeenLastCalledWith(
- expect.objectContaining({ after: graphqlPageInfo.endCursor, first: GRAPHQL_PAGE_SIZE }),
- );
- });
+ it('when list emits fetch-page fetches the next set of records', async () => {
+ findSearchableList().vm.$emit('fetch-page', {
+ after: 'eyJpZCI6IjIifQ',
+ first: 30,
+ id: 'gid://gitlab/Ml::Model/2',
+ });
- it('when list emits prev-page fetches the prev set of records', async () => {
- findRegistryList().vm.$emit('prev-page');
await waitForPromises();
expect(resolver).toHaveBeenLastCalledWith(
- expect.objectContaining({ before: graphqlPageInfo.startCursor, last: GRAPHQL_PAGE_SIZE }),
+ expect.objectContaining({ after: graphqlPageInfo.endCursor, first: GRAPHQL_PAGE_SIZE }),
);
});
});
diff --git a/spec/frontend/ml/model_registry/components/searchable_list_spec.js b/spec/frontend/ml/model_registry/components/searchable_list_spec.js
new file mode 100644
index 00000000000..ea58a9a830a
--- /dev/null
+++ b/spec/frontend/ml/model_registry/components/searchable_list_spec.js
@@ -0,0 +1,170 @@
+import { GlAlert } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import SearchableList from '~/ml/model_registry/components/searchable_list.vue';
+import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
+import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
+import { defaultPageInfo } from '../mock_data';
+
+describe('ml/model_registry/components/searchable_list.vue', () => {
+ let wrapper;
+
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findLoader = () => wrapper.findComponent(PackagesListLoader);
+ const findRegistryList = () => wrapper.findComponent(RegistryList);
+ const findEmptyState = () => wrapper.findByTestId('empty-state-slot');
+ const findFirstRow = () => wrapper.findByTestId('element');
+ const findRows = () => wrapper.findAllByTestId('element');
+
+ const defaultProps = {
+ items: ['a', 'b', 'c'],
+ pageInfo: defaultPageInfo,
+ isLoading: false,
+ errorMessage: '',
+ };
+
+ const mountComponent = (props = {}) => {
+ wrapper = shallowMountExtended(SearchableList, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ stubs: {
+ RegistryList,
+ },
+ slots: {
+ 'empty-state': '<div data-testid="empty-state-slot">This is empty</div>',
+ item: '<div data-testid="element"></div>',
+ },
+ });
+ };
+
+ describe('when list is loaded and has no data', () => {
+ beforeEach(() => mountComponent({ items: [] }));
+
+ it('shows empty state', () => {
+ expect(findEmptyState().text()).toBe('This is empty');
+ });
+
+ it('does not display loader', () => {
+ expect(findLoader().exists()).toBe(false);
+ });
+
+ it('does not display rows', () => {
+ expect(findFirstRow().exists()).toBe(false);
+ });
+
+ it('does not display registry list', () => {
+ expect(findRegistryList().exists()).toBe(false);
+ });
+
+ it('does not display alert', () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+ });
+
+ describe('if errorMessage', () => {
+ beforeEach(() => mountComponent({ errorMessage: 'Failure!' }));
+
+ it('shows error message', () => {
+ expect(findAlert().text()).toContain('Failure!');
+ });
+
+ it('is not dismissible', () => {
+ expect(findAlert().props('dismissible')).toBe(false);
+ });
+
+ it('is of variant danger', () => {
+ expect(findAlert().attributes('variant')).toBe('danger');
+ });
+
+ it('hides loader', () => {
+ expect(findLoader().exists()).toBe(false);
+ });
+
+ it('hides registry list', () => {
+ expect(findRegistryList().exists()).toBe(false);
+ });
+
+ it('hides empty state', () => {
+ expect(findEmptyState().exists()).toBe(false);
+ });
+ });
+
+ describe('if loading', () => {
+ beforeEach(() => mountComponent({ isLoading: true }));
+
+ it('shows loader', () => {
+ expect(findLoader().exists()).toBe(true);
+ });
+
+ it('hides error message', () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+
+ it('hides registry list', () => {
+ expect(findRegistryList().exists()).toBe(false);
+ });
+
+ it('hides empty state', () => {
+ expect(findEmptyState().exists()).toBe(false);
+ });
+ });
+
+ describe('when list is loaded with data', () => {
+ beforeEach(() => mountComponent());
+
+ it('displays package registry list', () => {
+ expect(findRegistryList().exists()).toEqual(true);
+ });
+
+ it('binds the right props', () => {
+ expect(findRegistryList().props()).toMatchObject({
+ items: ['a', 'b', 'c'],
+ isLoading: false,
+ pagination: defaultPageInfo,
+ hiddenDelete: true,
+ });
+ });
+
+ it('displays package version rows', () => {
+ expect(findRows().exists()).toEqual(true);
+ expect(findRows()).toHaveLength(3);
+ });
+
+ it('does not display loader', () => {
+ expect(findLoader().exists()).toBe(false);
+ });
+
+ it('does not display empty state', () => {
+ expect(findEmptyState().exists()).toBe(false);
+ });
+ });
+
+ describe('when user interacts with pagination', () => {
+ beforeEach(() => mountComponent());
+
+ it('when list emits next-page emits fetchPage with correct pageInfo', () => {
+ findRegistryList().vm.$emit('next-page');
+
+ const expectedNewPageInfo = {
+ after: 'eyJpZCI6IjIifQ',
+ first: 30,
+ last: null,
+ };
+
+ expect(wrapper.emitted('fetch-page')).toEqual([[expectedNewPageInfo]]);
+ });
+
+ it('when list emits prev-page emits fetchPage with correct pageInfo', () => {
+ findRegistryList().vm.$emit('prev-page');
+
+ const expectedNewPageInfo = {
+ before: 'eyJpZCI6IjE2In0',
+ first: null,
+ last: 30,
+ };
+
+ expect(wrapper.emitted('fetch-page')).toEqual([[expectedNewPageInfo]]);
+ });
+ });
+});