diff options
Diffstat (limited to 'spec/frontend/ml/experiment_tracking/components/ml_experiment_spec.js')
-rw-r--r-- | spec/frontend/ml/experiment_tracking/components/ml_experiment_spec.js | 359 |
1 files changed, 267 insertions, 92 deletions
diff --git a/spec/frontend/ml/experiment_tracking/components/ml_experiment_spec.js b/spec/frontend/ml/experiment_tracking/components/ml_experiment_spec.js index abcaf17303f..f307d2c5a58 100644 --- a/spec/frontend/ml/experiment_tracking/components/ml_experiment_spec.js +++ b/spec/frontend/ml/experiment_tracking/components/ml_experiment_spec.js @@ -1,140 +1,315 @@ -import { GlAlert, GlPagination } from '@gitlab/ui'; +import { GlAlert, GlTable, GlLink } from '@gitlab/ui'; +import { nextTick } from 'vue'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import MlExperiment from '~/ml/experiment_tracking/components/ml_experiment.vue'; +import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue'; +import Pagination from '~/vue_shared/components/incubation/pagination.vue'; +import setWindowLocation from 'helpers/set_window_location_helper'; +import * as urlHelpers from '~/lib/utils/url_utility'; describe('MlExperiment', () => { let wrapper; + const startCursor = 'eyJpZCI6IjE2In0'; + const defaultPageInfo = { + startCursor, + endCursor: 'eyJpZCI6IjIifQ', + hasNextPage: true, + hasPreviousPage: true, + }; + const createWrapper = ( candidates = [], metricNames = [], paramNames = [], - pagination = { page: 1, isLastPage: false, per_page: 2, totalItems: 0 }, + pageInfo = defaultPageInfo, ) => { - return mountExtended(MlExperiment, { - provide: { candidates, metricNames, paramNames, pagination }, + wrapper = mountExtended(MlExperiment, { + provide: { candidates, metricNames, paramNames, pageInfo }, }); }; - const findAlert = () => wrapper.findComponent(GlAlert); + const candidates = [ + { + rmse: 1, + l1_ratio: 0.4, + details: 'link_to_candidate1', + artifact: 'link_to_artifact', + name: 'aCandidate', + created_at: '2023-01-05T14:07:02.975Z', + user: { username: 'root', path: '/root' }, + }, + { + auc: 0.3, + l1_ratio: 0.5, + details: 'link_to_candidate2', + created_at: '2023-01-05T14:07:02.975Z', + name: null, + user: null, + }, + { + auc: 0.3, + l1_ratio: 0.5, + details: 'link_to_candidate3', + created_at: '2023-01-05T14:07:02.975Z', + name: null, + user: null, + }, + { + auc: 0.3, + l1_ratio: 0.5, + details: 'link_to_candidate4', + created_at: '2023-01-05T14:07:02.975Z', + name: null, + user: null, + }, + { + auc: 0.3, + l1_ratio: 0.5, + details: 'link_to_candidate5', + created_at: '2023-01-05T14:07:02.975Z', + name: null, + user: null, + }, + ]; + + const createWrapperWithCandidates = (pageInfo = defaultPageInfo) => { + createWrapper(candidates, ['rmse', 'auc', 'mae'], ['l1_ratio'], pageInfo); + }; - const findEmptyState = () => wrapper.findByText('This experiment has no logged candidates'); + const findAlert = () => wrapper.findComponent(GlAlert); + const findPagination = () => wrapper.findComponent(Pagination); + const findEmptyState = () => wrapper.findByText('No candidates to display'); + const findRegistrySearch = () => wrapper.findComponent(RegistrySearch); + const findTable = () => wrapper.findComponent(GlTable); + const findTableHeaders = () => findTable().findAll('th'); + const findTableRows = () => findTable().findAll('tbody > tr'); + const findNthTableRow = (idx) => findTableRows().at(idx); + const findColumnInRow = (row, col) => findNthTableRow(row).findAll('td').at(col); + const hrefInRowAndColumn = (row, col) => + findColumnInRow(row, col).findComponent(GlLink).attributes().href; it('shows incubation warning', () => { - wrapper = createWrapper(); + createWrapper(); expect(findAlert().exists()).toBe(true); }); - describe('no candidates', () => { - it('shows empty state', () => { - wrapper = createWrapper(); + describe('default inputs', () => { + beforeEach(async () => { + createWrapper(); + await nextTick(); + }); + + it('shows empty state', () => { expect(findEmptyState().exists()).toBe(true); }); it('does not show pagination', () => { - wrapper = createWrapper(); + expect(findPagination().exists()).toBe(false); + }); - expect(wrapper.findComponent(GlPagination).exists()).toBe(false); + it('there are no columns', () => { + expect(findTable().findAll('th')).toHaveLength(0); + }); + + it('initializes sorting correctly', () => { + expect(findRegistrySearch().props('sorting')).toMatchObject({ + orderBy: 'created_at', + sort: 'desc', + }); + }); + + it('initializes filters correctly', () => { + expect(findRegistrySearch().props('filters')).toMatchObject([{ value: { data: '' } }]); }); }); - describe('with candidates', () => { - const defaultPagination = { page: 1, isLastPage: false, per_page: 2, totalItems: 5 }; - - const createWrapperWithCandidates = (pagination = defaultPagination) => { - return createWrapper( - [ - { - rmse: 1, - l1_ratio: 0.4, - details: 'link_to_candidate1', - artifact: 'link_to_artifact', - name: 'aCandidate', - created_at: '2023-01-05T14:07:02.975Z', - user: { username: 'root', path: '/root' }, - }, - { - auc: 0.3, - l1_ratio: 0.5, - details: 'link_to_candidate2', - created_at: '2023-01-05T14:07:02.975Z', - name: null, - user: null, - }, - { - auc: 0.3, - l1_ratio: 0.5, - details: 'link_to_candidate3', - created_at: '2023-01-05T14:07:02.975Z', - name: null, - user: null, - }, - { - auc: 0.3, - l1_ratio: 0.5, - details: 'link_to_candidate4', - created_at: '2023-01-05T14:07:02.975Z', - name: null, - user: null, - }, - { - auc: 0.3, - l1_ratio: 0.5, - details: 'link_to_candidate5', - created_at: '2023-01-05T14:07:02.975Z', - name: null, - user: null, - }, - ], - ['rmse', 'auc', 'mae'], - ['l1_ratio'], - pagination, - ); - }; - - it('renders correctly', () => { - wrapper = createWrapperWithCandidates(); - - expect(wrapper.element).toMatchSnapshot(); - }); - - describe('Pagination behaviour', () => { - it('should show', () => { - wrapper = createWrapperWithCandidates(); - - expect(wrapper.findComponent(GlPagination).exists()).toBe(true); + describe('Search', () => { + it('shows search box', () => { + createWrapper(); + + expect(findRegistrySearch().exists()).toBe(true); + }); + + it('metrics are added as options for sorting', () => { + createWrapper([], ['bar']); + + const labels = findRegistrySearch() + .props('sortableFields') + .map((e) => e.orderBy); + expect(labels).toContain('metric.bar'); + }); + + it('sets the component filters based on the querystring', () => { + setWindowLocation('https://blah?name=A&orderBy=B&sort=C'); + + createWrapper(); + + expect(findRegistrySearch().props('filters')).toMatchObject([{ value: { data: 'A' } }]); + }); + + it('sets the component sort based on the querystring', () => { + setWindowLocation('https://blah?name=A&orderBy=B&sort=C'); + + createWrapper(); + + expect(findRegistrySearch().props('sorting')).toMatchObject({ orderBy: 'B', sort: 'c' }); + }); + + it('sets the component sort based on the querystring, when order by is a metric', () => { + setWindowLocation('https://blah?name=A&orderBy=B&sort=C&orderByType=metric'); + + createWrapper(); + + expect(findRegistrySearch().props('sorting')).toMatchObject({ + orderBy: 'metric.B', + sort: 'c', + }); + }); + + describe('Search submit', () => { + beforeEach(() => { + setWindowLocation('https://blah.com/?name=query&orderBy=name&orderByType=column&sort=asc'); + jest.spyOn(urlHelpers, 'visitUrl').mockImplementation(() => {}); + + createWrapper(); + }); + + it('On submit, resets the cursor and reloads to correct page', () => { + findRegistrySearch().vm.$emit('filter:submit'); + + expect(urlHelpers.visitUrl).toHaveBeenCalledTimes(1); + expect(urlHelpers.visitUrl).toHaveBeenCalledWith( + 'https://blah.com/?name=query&orderBy=name&orderByType=column&sort=asc', + ); }); - it('should get the page number from the URL', () => { - wrapper = createWrapperWithCandidates({ ...defaultPagination, page: 2 }); + it('On sorting changed, resets cursor and reloads to correct page', () => { + findRegistrySearch().vm.$emit('sorting:changed', { orderBy: 'created_at' }); - expect(wrapper.findComponent(GlPagination).props().value).toBe(2); + expect(urlHelpers.visitUrl).toHaveBeenCalledTimes(1); + expect(urlHelpers.visitUrl).toHaveBeenCalledWith( + 'https://blah.com/?name=query&orderBy=created_at&orderByType=column&sort=asc', + ); }); - it('should not have a prevPage if the page is 1', () => { - wrapper = createWrapperWithCandidates(); + it('On sorting changed and is metric, resets cursor and reloads to correct page', () => { + findRegistrySearch().vm.$emit('sorting:changed', { orderBy: 'metric.auc' }); - expect(wrapper.findComponent(GlPagination).props().prevPage).toBe(null); + expect(urlHelpers.visitUrl).toHaveBeenCalledTimes(1); + expect(urlHelpers.visitUrl).toHaveBeenCalledWith( + 'https://blah.com/?name=query&orderBy=auc&orderByType=metric&sort=asc', + ); }); - it('should set the prevPage to 1 if the page is 2', () => { - wrapper = createWrapperWithCandidates({ ...defaultPagination, page: 2 }); + it('On direction changed, reloads to correct page', () => { + findRegistrySearch().vm.$emit('sorting:changed', { sort: 'desc' }); - expect(wrapper.findComponent(GlPagination).props().prevPage).toBe(1); + expect(urlHelpers.visitUrl).toHaveBeenCalledTimes(1); + expect(urlHelpers.visitUrl).toHaveBeenCalledWith( + 'https://blah.com/?name=query&orderBy=name&orderByType=column&sort=desc', + ); }); + }); + }); + + describe('Pagination behaviour', () => { + beforeEach(() => { + createWrapperWithCandidates(); + }); + + it('should show', () => { + expect(findPagination().exists()).toBe(true); + }); - it('should not have a nextPage if isLastPage is true', async () => { - wrapper = createWrapperWithCandidates({ ...defaultPagination, isLastPage: true }); + it('Passes pagination to pagination component', () => { + createWrapperWithCandidates(); + + expect(findPagination().props('startCursor')).toBe(startCursor); + }); + }); + + describe('Candidate table', () => { + const firstCandidateIndex = 0; + const secondCandidateIndex = 1; + const firstCandidate = candidates[firstCandidateIndex]; + + beforeEach(() => { + createWrapperWithCandidates(); + }); + + it('renders all rows', () => { + expect(findTableRows()).toHaveLength(candidates.length); + }); + + it('sets the correct columns in the table', () => { + const expectedColumnNames = [ + 'Name', + 'Created at', + 'User', + 'L1 Ratio', + 'Rmse', + 'Auc', + 'Mae', + '', + '', + ]; + + expect(findTableHeaders().wrappers.map((h) => h.text())).toEqual(expectedColumnNames); + }); - expect(wrapper.findComponent(GlPagination).props().nextPage).toBe(null); + describe('Artifact column', () => { + const artifactColumnIndex = -1; + + it('shows the a link to the artifact', () => { + expect(hrefInRowAndColumn(firstCandidateIndex, artifactColumnIndex)).toBe( + firstCandidate.artifact, + ); + }); + + it('shows empty state when no artifact', () => { + expect(findColumnInRow(secondCandidateIndex, artifactColumnIndex).text()).toBe('-'); + }); + }); + + describe('User column', () => { + const userColumn = 2; + + it('creates a link to the user', () => { + const column = findColumnInRow(firstCandidateIndex, userColumn).findComponent(GlLink); + + expect(column.attributes().href).toBe(firstCandidate.user.path); + expect(column.text()).toBe(`@${firstCandidate.user.username}`); + }); + + it('when there is no user shows empty state', () => { + createWrapperWithCandidates(); + + expect(findColumnInRow(secondCandidateIndex, userColumn).text()).toBe('-'); }); + }); + + describe('Candidate name column', () => { + const nameColumnIndex = 0; + + it('Sets the name', () => { + expect(findColumnInRow(firstCandidateIndex, nameColumnIndex).text()).toBe( + firstCandidate.name, + ); + }); + + it('when there is no user shows nothing', () => { + expect(findColumnInRow(secondCandidateIndex, nameColumnIndex).text()).toBe(''); + }); + }); - it('should set the nextPage to 2 if the page is 1', () => { - wrapper = createWrapperWithCandidates(); + describe('Detail column', () => { + const detailColumn = -2; - expect(wrapper.findComponent(GlPagination).props().nextPage).toBe(2); + it('is a link to details', () => { + expect(hrefInRowAndColumn(firstCandidateIndex, detailColumn)).toBe(firstCandidate.details); }); }); }); |