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:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-12-13 03:15:40 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-12-13 03:15:40 +0300
commit95e5fa3fb3882addb4672f3f123bc122e84fb52c (patch)
tree7f4024b168571308f6c7c9f3cc6e92ed6ae4d7c2 /spec/frontend
parent42a4fe5b394e010b3f512a5a138359618295193b (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/ml/model_registry/apps/show_ml_model_spec.js19
-rw-r--r--spec/frontend/ml/model_registry/components/candidate_list_row_spec.js39
-rw-r--r--spec/frontend/ml/model_registry/components/candidate_list_spec.js182
-rw-r--r--spec/frontend/ml/model_registry/components/model_version_list_spec.js7
-rw-r--r--spec/frontend/ml/model_registry/components/model_version_row_spec.js2
-rw-r--r--spec/frontend/ml/model_registry/graphql_mock_data.js116
-rw-r--r--spec/frontend/ml/model_registry/mock_data.js61
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js8
8 files changed, 368 insertions, 66 deletions
diff --git a/spec/frontend/ml/model_registry/apps/show_ml_model_spec.js b/spec/frontend/ml/model_registry/apps/show_ml_model_spec.js
index d58fc70af64..cf282f81cbc 100644
--- a/spec/frontend/ml/model_registry/apps/show_ml_model_spec.js
+++ b/spec/frontend/ml/model_registry/apps/show_ml_model_spec.js
@@ -1,16 +1,28 @@
import { GlBadge, GlTab } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
import { ShowMlModel } from '~/ml/model_registry/apps';
import ModelVersionList from '~/ml/model_registry/components/model_version_list.vue';
+import CandidateList from '~/ml/model_registry/components/candidate_list.vue';
import ModelVersionDetail from '~/ml/model_registry/components/model_version_detail.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
import { NO_VERSIONS_LABEL } from '~/ml/model_registry/translations';
+import createMockApollo from 'helpers/mock_apollo_helper';
import { MODEL, makeModel } from '../mock_data';
+const apolloProvider = createMockApollo([]);
let wrapper;
+
+Vue.use(VueApollo);
+
const createWrapper = (model = MODEL) => {
- wrapper = shallowMount(ShowMlModel, { propsData: { model }, stubs: { GlTab } });
+ wrapper = shallowMount(ShowMlModel, {
+ apolloProvider,
+ propsData: { model },
+ stubs: { GlTab },
+ });
};
const findDetailTab = () => wrapper.findAllComponents(GlTab).at(0);
@@ -19,6 +31,7 @@ const findVersionsCountBadge = () => findVersionsTab().findComponent(GlBadge);
const findModelVersionList = () => findVersionsTab().findComponent(ModelVersionList);
const findModelVersionDetail = () => findDetailTab().findComponent(ModelVersionDetail);
const findCandidateTab = () => wrapper.findAllComponents(GlTab).at(2);
+const findCandidateList = () => findCandidateTab().findComponent(CandidateList);
const findCandidatesCountBadge = () => findCandidateTab().findComponent(GlBadge);
const findTitleArea = () => wrapper.findComponent(TitleArea);
const findVersionCountMetadataItem = () => findTitleArea().findComponent(MetadataItem);
@@ -90,5 +103,9 @@ describe('ShowMlModel', () => {
it('shows the number of candidates in the tab', () => {
expect(findCandidatesCountBadge().text()).toBe(MODEL.candidateCount.toString());
});
+
+ it('shows a list of candidates', () => {
+ expect(findCandidateList().props('modelId')).toBe(MODEL.id);
+ });
});
});
diff --git a/spec/frontend/ml/model_registry/components/candidate_list_row_spec.js b/spec/frontend/ml/model_registry/components/candidate_list_row_spec.js
new file mode 100644
index 00000000000..5ac8d07ff01
--- /dev/null
+++ b/spec/frontend/ml/model_registry/components/candidate_list_row_spec.js
@@ -0,0 +1,39 @@
+import { GlLink, GlSprintf, GlTruncate } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import ListItem from '~/vue_shared/components/registry/list_item.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import CandidateListRow from '~/ml/model_registry/components/candidate_list_row.vue';
+import { graphqlCandidates } from '../graphql_mock_data';
+
+const CANDIDATE = graphqlCandidates[0];
+
+let wrapper;
+const createWrapper = (candidate = CANDIDATE) => {
+ wrapper = shallowMount(CandidateListRow, {
+ propsData: { candidate },
+ stubs: {
+ GlSprintf,
+ GlTruncate,
+ },
+ });
+};
+
+const findListItem = () => wrapper.findComponent(ListItem);
+const findLink = () => findListItem().findComponent(GlLink);
+const findTruncated = () => findLink().findComponent(GlTruncate);
+const findTooltip = () => findListItem().findComponent(TimeAgoTooltip);
+
+describe('ml/model_registry/components/candidate_list_row.vue', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('Has a link to the candidate', () => {
+ expect(findTruncated().props('text')).toBe(CANDIDATE.name);
+ expect(findLink().attributes('href')).toBe(CANDIDATE._links.showPath);
+ });
+
+ it('Shows created at', () => {
+ expect(findTooltip().props('time')).toBe(CANDIDATE.createdAt);
+ });
+});
diff --git a/spec/frontend/ml/model_registry/components/candidate_list_spec.js b/spec/frontend/ml/model_registry/components/candidate_list_spec.js
new file mode 100644
index 00000000000..c10222a99fd
--- /dev/null
+++ b/spec/frontend/ml/model_registry/components/candidate_list_spec.js
@@ -0,0 +1,182 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlAlert } from '@gitlab/ui';
+import { shallowMount } 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 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';
+import {
+ emptyCandidateQuery,
+ modelCandidatesQuery,
+ graphqlCandidates,
+ graphqlPageInfo,
+} from '../graphql_mock_data';
+
+Vue.use(VueApollo);
+
+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 findAllRows = () => wrapper.findAllComponents(CandidateListRow);
+
+ const mountComponent = ({
+ props = {},
+ resolver = jest.fn().mockResolvedValue(modelCandidatesQuery()),
+ } = {}) => {
+ const requestHandlers = [[getModelCandidatesQuery, resolver]];
+ apolloProvider = createMockApollo(requestHandlers);
+
+ wrapper = shallowMount(CandidateList, {
+ apolloProvider,
+ propsData: {
+ modelId: 2,
+ ...props,
+ },
+ stubs: {
+ RegistryList,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ jest.spyOn(Sentry, 'captureException').mockImplementation();
+ });
+
+ describe('when list is loaded and has no data', () => {
+ const resolver = jest.fn().mockResolvedValue(emptyCandidateQuery);
+ beforeEach(async () => {
+ mountComponent({ resolver });
+ await waitForPromises();
+ });
+
+ it('displays empty slot message', () => {
+ 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', () => {
+ beforeEach(async () => {
+ const error = new Error('Failure!');
+ mountComponent({ resolver: jest.fn().mockRejectedValue(error) });
+
+ await waitForPromises();
+ });
+
+ 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');
+ });
+
+ it('error is logged in sentry', () => {
+ expect(Sentry.captureException).toHaveBeenCalled();
+ });
+ });
+
+ describe('when list is loaded with data', () => {
+ beforeEach(async () => {
+ mountComponent();
+ await waitForPromises();
+ });
+
+ it('displays package registry list', () => {
+ expect(findRegistryList().exists()).toEqual(true);
+ });
+
+ 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);
+ expect(findAllRows()).toHaveLength(graphqlCandidates.length);
+ });
+
+ it('binds the correct props', () => {
+ expect(findAllRows().at(0).props()).toMatchObject({
+ candidate: expect.objectContaining(graphqlCandidates[0]),
+ });
+
+ expect(findAllRows().at(1).props()).toMatchObject({
+ 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', () => {
+ const resolver = jest.fn().mockResolvedValue(modelCandidatesQuery());
+
+ beforeEach(async () => {
+ mountComponent({ resolver });
+ 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 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 }),
+ );
+ });
+ });
+});
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 52010820b80..1d57baa050b 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
@@ -17,7 +17,7 @@ import {
modelVersionsQuery,
graphqlModelVersions,
graphqlPageInfo,
-} from '../mock_data';
+} from '../graphql_mock_data';
Vue.use(VueApollo);
@@ -85,7 +85,8 @@ describe('ModelVersionList', () => {
describe('if load fails, alert', () => {
beforeEach(async () => {
- mountComponent({ resolver: jest.fn().mockRejectedValue() });
+ const error = new Error('Failure!');
+ mountComponent({ resolver: jest.fn().mockRejectedValue(error) });
await waitForPromises();
});
@@ -95,7 +96,7 @@ describe('ModelVersionList', () => {
});
it('shows error message', () => {
- expect(findAlert().text()).toMatchInterpolatedText('Failed to load model versions');
+ expect(findAlert().text()).toContain('Failed to load model versions with error: Failure!');
});
it('is not dismissible', () => {
diff --git a/spec/frontend/ml/model_registry/components/model_version_row_spec.js b/spec/frontend/ml/model_registry/components/model_version_row_spec.js
index bcdc8faa61f..9f709f2e072 100644
--- a/spec/frontend/ml/model_registry/components/model_version_row_spec.js
+++ b/spec/frontend/ml/model_registry/components/model_version_row_spec.js
@@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import ModelVersionRow from '~/ml/model_registry/components/model_version_row.vue';
-import { graphqlModelVersions } from '../mock_data';
+import { graphqlModelVersions } from '../graphql_mock_data';
let wrapper;
const createWrapper = (modelVersion = graphqlModelVersions[0]) => {
diff --git a/spec/frontend/ml/model_registry/graphql_mock_data.js b/spec/frontend/ml/model_registry/graphql_mock_data.js
new file mode 100644
index 00000000000..1c31ee4627f
--- /dev/null
+++ b/spec/frontend/ml/model_registry/graphql_mock_data.js
@@ -0,0 +1,116 @@
+import { defaultPageInfo } from './mock_data';
+
+export const graphqlPageInfo = {
+ ...defaultPageInfo,
+ __typename: 'PageInfo',
+};
+
+export const graphqlModelVersions = [
+ {
+ createdAt: '2021-08-10T09:33:54Z',
+ id: 'gid://gitlab/Ml::ModelVersion/243',
+ version: '1.0.1',
+ _links: {
+ showPath: '/path/to/modelversion/243',
+ },
+ __typename: 'MlModelVersion',
+ },
+ {
+ createdAt: '2021-08-10T09:33:54Z',
+ id: 'gid://gitlab/Ml::ModelVersion/244',
+ version: '1.0.2',
+ _links: {
+ showPath: '/path/to/modelversion/244',
+ },
+ __typename: 'MlModelVersion',
+ },
+];
+
+export const modelVersionsQuery = (versions = graphqlModelVersions) => ({
+ data: {
+ mlModel: {
+ id: 'gid://gitlab/Ml::Model/2',
+ versions: {
+ count: versions.length,
+ nodes: versions,
+ pageInfo: graphqlPageInfo,
+ __typename: 'MlModelConnection',
+ },
+ __typename: 'MlModelType',
+ },
+ },
+});
+
+export const graphqlCandidates = [
+ {
+ id: 'gid://gitlab/Ml::Candidate/1',
+ name: 'narwhal-aardvark-heron-6953',
+ createdAt: '2023-12-06T12:41:48Z',
+ _links: {
+ showPath: '/path/to/candidate/1',
+ },
+ },
+ {
+ id: 'gid://gitlab/Ml::Candidate/2',
+ name: 'anteater-chimpanzee-snake-1254',
+ createdAt: '2023-12-06T12:41:48Z',
+ _links: {
+ showPath: '/path/to/candidate/2',
+ },
+ },
+];
+
+export const modelCandidatesQuery = (candidates = graphqlCandidates) => ({
+ data: {
+ mlModel: {
+ id: 'gid://gitlab/Ml::Model/2',
+ candidates: {
+ count: candidates.length,
+ nodes: candidates,
+ pageInfo: graphqlPageInfo,
+ __typename: 'MlCandidateConnection',
+ },
+ __typename: 'MlModelType',
+ },
+ },
+});
+
+export const emptyModelVersionsQuery = {
+ data: {
+ mlModel: {
+ id: 'gid://gitlab/Ml::Model/2',
+ versions: {
+ count: 0,
+ nodes: [],
+ pageInfo: {
+ hasNextPage: false,
+ hasPreviousPage: false,
+ endCursor: 'endCursor',
+ startCursor: 'startCursor',
+ },
+ __typename: 'MlModelConnection',
+ },
+ __typename: 'MlModelType',
+ },
+ },
+};
+
+export const emptyCandidateQuery = {
+ data: {
+ mlModel: {
+ id: 'gid://gitlab/Ml::Model/2',
+ candidates: {
+ count: 0,
+ nodes: [],
+ pageInfo: {
+ hasNextPage: false,
+ hasPreviousPage: false,
+ endCursor: 'endCursor',
+ startCursor: 'startCursor',
+ },
+ __typename: 'MlCandidateConnection',
+ },
+ __typename: 'MlModelType',
+ },
+ },
+};
diff --git a/spec/frontend/ml/model_registry/mock_data.js b/spec/frontend/ml/model_registry/mock_data.js
index 78e22eda7b9..4399df38990 100644
--- a/spec/frontend/ml/model_registry/mock_data.js
+++ b/spec/frontend/ml/model_registry/mock_data.js
@@ -102,64 +102,3 @@ export const defaultPageInfo = Object.freeze({
hasNextPage: true,
hasPreviousPage: true,
});
-
-export const graphqlPageInfo = {
- ...defaultPageInfo,
- __typename: 'PageInfo',
-};
-
-export const graphqlModelVersions = [
- {
- createdAt: '2021-08-10T09:33:54Z',
- id: 'gid://gitlab/Ml::ModelVersion/243',
- version: '1.0.1',
- _links: {
- showPath: '/path/to/modelversion/243',
- },
- __typename: 'MlModelVersion',
- },
- {
- createdAt: '2021-08-10T09:33:54Z',
- id: 'gid://gitlab/Ml::ModelVersion/244',
- version: '1.0.2',
- _links: {
- showPath: '/path/to/modelversion/244',
- },
- __typename: 'MlModelVersion',
- },
-];
-
-export const modelVersionsQuery = (versions = graphqlModelVersions) => ({
- data: {
- mlModel: {
- id: 'gid://gitlab/Ml::Model/2',
- versions: {
- count: versions.length,
- nodes: versions,
- pageInfo: graphqlPageInfo,
- __typename: 'MlModelConnection',
- },
- __typename: 'MlModelType',
- },
- },
-});
-
-export const emptyModelVersionsQuery = {
- data: {
- mlModel: {
- id: 'gid://gitlab/Ml::Model/2',
- versions: {
- count: 0,
- nodes: [],
- pageInfo: {
- hasNextPage: false,
- hasPreviousPage: false,
- endCursor: 'endCursor',
- startCursor: 'startCursor',
- },
- __typename: 'MlModelConnection',
- },
- __typename: 'MlModelType',
- },
- },
-};
diff --git a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
index 8b672ff3f32..207ce8c1ffa 100644
--- a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
+++ b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
@@ -137,6 +137,7 @@ describe('Settings Panel', () => {
const findMonitorSettings = () => wrapper.findComponent({ ref: 'monitor-settings' });
const findModelExperimentsSettings = () =>
wrapper.findComponent({ ref: 'model-experiments-settings' });
+ const findModelRegistrySettings = () => wrapper.findComponent({ ref: 'model-registry-settings' });
describe('Project Visibility', () => {
it('should set the project visibility help path', () => {
@@ -758,4 +759,11 @@ describe('Settings Panel', () => {
expect(findModelExperimentsSettings().exists()).toBe(true);
});
});
+ describe('Model registry', () => {
+ it('shows model registry toggle', () => {
+ wrapper = mountComponent({});
+
+ expect(findModelRegistrySettings().exists()).toBe(true);
+ });
+ });
});