diff --git a/report-viewer/tests/unit/components/comparisonTable/ComparisonTable.test.ts b/report-viewer/tests/unit/components/comparisonTable/ComparisonTable.test.ts new file mode 100644 index 000000000..2866e79a4 --- /dev/null +++ b/report-viewer/tests/unit/components/comparisonTable/ComparisonTable.test.ts @@ -0,0 +1,371 @@ +import ComparisonTable from '@/components/ComparisonsTable.vue' +import { flushPromises, mount } from '@vue/test-utils' +import { describe, it, vi, expect } from 'vitest' +import { createTestingPinia } from '@pinia/testing' +import { store } from '@/stores/store' +import { MetricType } from '@/model/MetricType.ts' +import { router } from '@/router' +import OptionsSelector from '@/components/optionsSelectors/OptionsSelectorComponent.vue' +import OptionComponent from '@/components/optionsSelectors/OptionComponent.vue' + +describe('ComparisonTable', async () => { + it('Test search string filtering', async () => { + const wrapper = mount(ComparisonTable, { + props: { + topComparisons: [ + { + sortingPlace: 0, + id: 1, + firstSubmissionId: 'A', + secondSubmissionId: 'B', + similarities: { + [MetricType.AVERAGE]: 1, + [MetricType.MAXIMUM]: 0.5 + }, + clusterIndex: -1 + }, + { + sortingPlace: 1, + id: 2, + firstSubmissionId: 'C', + secondSubmissionId: 'D', + similarities: { + [MetricType.AVERAGE]: 0.5, + [MetricType.MAXIMUM]: 1 + }, + clusterIndex: -1 + } + ], + clusters: [] + }, + global: { + plugins: [getStore(), router] + } + }) + + // check that filtering works with one name + wrapper.find('input').setValue('A') + await flushPromises() + const displayedComparisonsSingleName = wrapper.vm.displayedComparisons + expect(displayedComparisonsSingleName.length).toBe(1) + expect(displayedComparisonsSingleName[0].firstSubmissionId).toBe('A') + expect(displayedComparisonsSingleName[0].secondSubmissionId).toBe('B') + + // check that filtering works with two names + wrapper.find('input').setValue('A D') + await flushPromises() + const displayedComparisonsTwoNames = wrapper.vm.displayedComparisons + expect(displayedComparisonsTwoNames.length).toBe(2) + }) + + it('Test search bar filtering by index', async () => { + const wrapper = mount(ComparisonTable, { + props: { + topComparisons: [ + { + sortingPlace: 0, + id: 1, + firstSubmissionId: 'A', + secondSubmissionId: 'B', + similarities: { + [MetricType.AVERAGE]: 0.3, + [MetricType.MAXIMUM]: 0.5 + }, + clusterIndex: -1 + }, + { + sortingPlace: 1, + id: 2, + firstSubmissionId: 'C', + secondSubmissionId: 'D', + similarities: { + [MetricType.AVERAGE]: 0.5, + [MetricType.MAXIMUM]: 1 + }, + clusterIndex: -1 + }, + { + sortingPlace: 1, + id: 2, + firstSubmissionId: 'E', + secondSubmissionId: 'F', + similarities: { + [MetricType.AVERAGE]: 0.3, + [MetricType.MAXIMUM]: 0.1 + }, + clusterIndex: -1 + }, + { + sortingPlace: 1, + id: 2, + firstSubmissionId: 'H', + secondSubmissionId: 'G', + similarities: { + [MetricType.AVERAGE]: 0.9, + [MetricType.MAXIMUM]: 0.2 + }, + clusterIndex: -1 + } + ], + clusters: [] + }, + global: { + plugins: [getStore(), router] + } + }) + + wrapper.find('input').setValue('2') + await flushPromises() + const displayedComparisonsIndex1 = wrapper.vm.displayedComparisons + expect(displayedComparisonsIndex1.length).toBe(1) + expect(displayedComparisonsIndex1[0].firstSubmissionId).toBe('C') + + wrapper.find('input').setValue('2 3') + await flushPromises() + const displayedComparisonsIndex2 = wrapper.vm.displayedComparisons + expect(displayedComparisonsIndex2.length).toBe(2) + expect(displayedComparisonsIndex2[0].firstSubmissionId).toBe('C') + expect(displayedComparisonsIndex2[1].firstSubmissionId).toBe('A') + + wrapper.find('input').setValue('index:1') + await flushPromises() + const displayedComparisonsIndex3 = wrapper.vm.displayedComparisons + expect(displayedComparisonsIndex3.length).toBe(1) + expect(displayedComparisonsIndex3[0].firstSubmissionId).toBe('H') + + const metricOptions = wrapper.getComponent(OptionsSelector).findAllComponents(OptionComponent) + await metricOptions[1].trigger('click') + wrapper.find('input').setValue('index:2') + await flushPromises() + const displayedComparisonsIndex4 = wrapper.vm.displayedComparisons + expect(displayedComparisonsIndex4.length).toBe(1) + expect(displayedComparisonsIndex4[0].firstSubmissionId).toBe('A') + }) + + it('Test search bar filtering by metric', async () => { + const wrapper = mount(ComparisonTable, { + props: { + topComparisons: [ + { + sortingPlace: 0, + id: 1, + firstSubmissionId: 'A', + secondSubmissionId: 'B', + similarities: { + [MetricType.AVERAGE]: 0.3, + [MetricType.MAXIMUM]: 0.5 + }, + clusterIndex: -1 + }, + { + sortingPlace: 1, + id: 2, + firstSubmissionId: 'C', + secondSubmissionId: 'D', + similarities: { + [MetricType.AVERAGE]: 0.4, + [MetricType.MAXIMUM]: 1 + }, + clusterIndex: -1 + }, + { + sortingPlace: 1, + id: 2, + firstSubmissionId: 'E', + secondSubmissionId: 'F', + similarities: { + [MetricType.AVERAGE]: 0.3, + [MetricType.MAXIMUM]: 0.1 + }, + clusterIndex: -1 + }, + { + sortingPlace: 1, + id: 2, + firstSubmissionId: 'H', + secondSubmissionId: 'G', + similarities: { + [MetricType.AVERAGE]: 0.9, + [MetricType.MAXIMUM]: 0.2 + }, + clusterIndex: -1 + } + ], + clusters: [] + }, + global: { + plugins: [getStore(), router] + } + }) + + // check that filtering works over all metrics when no metric is specified + wrapper.find('input').setValue('>45') + await flushPromises() + const displayedComparisonsMetricNoPercentage = wrapper.vm.displayedComparisons + expect(displayedComparisonsMetricNoPercentage.length).toBe(3) + + // check that filtering works with and without percentage + wrapper.find('input').setValue('>45%') + await flushPromises() + const displayedComparisonsMetricWithPercentage = wrapper.vm.displayedComparisons + expect(displayedComparisonsMetricWithPercentage.length).toBe(3) + expect(displayedComparisonsMetricWithPercentage).toEqual(displayedComparisonsMetricNoPercentage) + + // check that filtering works on max metric percentage + wrapper.find('input').setValue('max:>45') + await flushPromises() + expect(wrapper.vm.displayedComparisons.length).toBe(2) + + // check that filtering works on average metric percentage + wrapper.find('input').setValue('avg:>45') + await flushPromises() + expect(wrapper.vm.displayedComparisons.length).toBe(1) + + // check that filtering works correctly on greater, greater or equal, less and less or equal + wrapper.find('input').setValue('max:>50') + await flushPromises() + expect(wrapper.vm.displayedComparisons.length).toBe(1) + + wrapper.find('input').setValue('max:>=50') + await flushPromises() + expect(wrapper.vm.displayedComparisons.length).toBe(2) + + wrapper.find('input').setValue('max:<50%') + await flushPromises() + expect(wrapper.vm.displayedComparisons.length).toBe(2) + + wrapper.find('input').setValue('max:<=50') + await flushPromises() + expect(wrapper.vm.displayedComparisons.length).toBe(3) + }) + + it('Test sorting working', async () => { + const wrapper = mount(ComparisonTable, { + props: { + topComparisons: [ + { + sortingPlace: 0, + id: 1, + firstSubmissionId: 'A', + secondSubmissionId: 'B', + similarities: { + [MetricType.AVERAGE]: 0.3, + [MetricType.MAXIMUM]: 0.5 + }, + clusterIndex: 0 + }, + { + sortingPlace: 1, + id: 2, + firstSubmissionId: 'C', + secondSubmissionId: 'D', + similarities: { + [MetricType.AVERAGE]: 0.5, + [MetricType.MAXIMUM]: 1 + }, + clusterIndex: 1 + }, + { + sortingPlace: 1, + id: 2, + firstSubmissionId: 'E', + secondSubmissionId: 'F', + similarities: { + [MetricType.AVERAGE]: 0.3, + [MetricType.MAXIMUM]: 0.1 + }, + clusterIndex: 2 + }, + { + sortingPlace: 1, + id: 2, + firstSubmissionId: 'H', + secondSubmissionId: 'G', + similarities: { + [MetricType.AVERAGE]: 0.9, + [MetricType.MAXIMUM]: 0.2 + }, + clusterIndex: -1 + } + ], + clusters: [ + { + averageSimilarity: 0.5, + strength: 0.5, + members: ['A', 'B'] + }, + { + averageSimilarity: 0.6, + strength: 0.5, + members: ['C', 'D'] + }, + { + averageSimilarity: 0.9, + strength: 0.5, + members: ['E', 'F'] + } + ] + }, + global: { + plugins: [getStore(), router] + } + }) + + // Test sorting by average + const displayedComparisonsAverageSorted = wrapper.vm.displayedComparisons + expect(displayedComparisonsAverageSorted[0].firstSubmissionId).toBe('H') + expect(displayedComparisonsAverageSorted[1].firstSubmissionId).toBe('C') + expect(displayedComparisonsAverageSorted[2].firstSubmissionId).toBe('A') + expect(displayedComparisonsAverageSorted[3].firstSubmissionId).toBe('E') + + const metricOptions = wrapper.getComponent(OptionsSelector).findAllComponents(OptionComponent) + await metricOptions[1].trigger('click') + await flushPromises() + + // Test sorting by max + const displayedComparisonsMaxSorted = wrapper.vm.displayedComparisons + expect(displayedComparisonsMaxSorted[0].firstSubmissionId).toBe('C') + expect(displayedComparisonsMaxSorted[1].firstSubmissionId).toBe('A') + expect(displayedComparisonsMaxSorted[2].firstSubmissionId).toBe('H') + expect(displayedComparisonsMaxSorted[3].firstSubmissionId).toBe('E') + + await metricOptions[2].trigger('click') + await flushPromises() + + // Test sorting by cluster + const displayedComparisonsClusterSorted = wrapper.vm.displayedComparisons + expect(displayedComparisonsClusterSorted[0].firstSubmissionId).toBe('E') + expect(displayedComparisonsClusterSorted[1].firstSubmissionId).toBe('C') + expect(displayedComparisonsClusterSorted[2].firstSubmissionId).toBe('A') + }) + + it('Test header prop', async () => { + const headerText = 'Custom Header' + + const wrapper = mount(ComparisonTable, { + props: { + topComparisons: [], + clusters: [], + header: headerText + }, + global: { + plugins: [createTestingPinia({ createSpy: vi.fn }), router] + } + }) + + expect(wrapper.text()).toContain(headerText) + }) +}) + +function getStore() { + const testStore = createTestingPinia({ createSpy: vi.fn }) + store().state.submissionIdsToComparisonFileName.set('A', new Map([['B', 'file1']])) + store().state.submissionIdsToComparisonFileName.set('B', new Map([['A', 'file1']])) + store().state.submissionIdsToComparisonFileName.set('C', new Map([['D', 'file2']])) + store().state.submissionIdsToComparisonFileName.set('D', new Map([['C', 'file2']])) + store().state.submissionIdsToComparisonFileName.set('E', new Map([['F', 'file3']])) + store().state.submissionIdsToComparisonFileName.set('F', new Map([['E', 'file3']])) + store().state.submissionIdsToComparisonFileName.set('G', new Map([['H', 'file4']])) + store().state.submissionIdsToComparisonFileName.set('H', new Map([['G', 'file4']])) + return testStore +} diff --git a/report-viewer/tests/unit/components/comparisonTable/ComparisonTableFilter.test.ts b/report-viewer/tests/unit/components/comparisonTable/ComparisonTableFilter.test.ts new file mode 100644 index 000000000..2b11ea6a5 --- /dev/null +++ b/report-viewer/tests/unit/components/comparisonTable/ComparisonTableFilter.test.ts @@ -0,0 +1,189 @@ +import ComparisonTableFilter from '@/components/ComparisonTableFilter.vue' +import { flushPromises, mount } from '@vue/test-utils' +import { describe, it, vi, expect } from 'vitest' +import { createTestingPinia } from '@pinia/testing' +import { store } from '@/stores/store' +import { MetricType } from '@/model/MetricType.ts' +import ButtonComponent from '@/components/ButtonComponent.vue' +import OptionsSelector from '@/components/optionsSelectors/OptionsSelectorComponent.vue' +import OptionComponent from '@/components/optionsSelectors/OptionComponent.vue' + +describe('ComparisonTableFilter', async () => { + it('Test search string updating', async () => { + const wrapper = mount(ComparisonTableFilter, { + props: { + searchString: '', + 'onUpdate:searchString': (e) => wrapper.setProps({ searchString: e }) + }, + global: { + plugins: [createTestingPinia({ createSpy: vi.fn })] + } + }) + setUpStore() + + const searchValue = 'JPlag' + + wrapper.find('input').setValue(searchValue) + await flushPromises() + expect(wrapper.props('searchString')).toBe(searchValue) + }) + + it('Test metric changes', async () => { + const wrapper = mount(ComparisonTableFilter, { + global: { + plugins: [createTestingPinia({ createSpy: vi.fn })] + } + }) + setUpStore() + + expect(wrapper.text()).toContain('Average') + expect(wrapper.text()).toContain('Maximum') + expect(wrapper.text()).toContain('Cluster') + + const options = wrapper.getComponent(OptionsSelector).findAllComponents(OptionComponent) + + expectHighlighting(0) + + await options[1].trigger('click') + expect(store().uiState.comparisonTableSortingMetric).toBe(MetricType.MAXIMUM) + expect(store().uiState.comparisonTableClusterSorting).toBeFalsy() + expectHighlighting(1) + + await options[2].trigger('click') + expect(store().uiState.comparisonTableSortingMetric).toBe(MetricType.AVERAGE) + expect(store().uiState.comparisonTableClusterSorting).toBeTruthy() + expectHighlighting(2) + + await options[0].trigger('click') + expect(store().uiState.comparisonTableSortingMetric).toBe(MetricType.AVERAGE) + expect(store().uiState.comparisonTableClusterSorting).toBeFalsy() + expectHighlighting(0) + + function expectHighlighting(index: number) { + for (let i = 0; i < options.length; i++) { + if (i == index) { + expect(options[i].classes()).toContain('!bg-accent') + } else { + expect(options[i].classes()).not.toContain('!bg-accent') + } + } + } + }) + + it('Test anonymous button', async () => { + const wrapper = mount(ComparisonTableFilter, { + global: { + plugins: [createTestingPinia({ createSpy: vi.fn })] + } + }) + setUpStore() + + await wrapper.vm.$nextTick() + expect(wrapper.text()).toContain('Hide All') + + await wrapper.getComponent(ButtonComponent).trigger('click') + + expect(store().state.anonymous.size).toBe(store().getSubmissionIds.length) + for (const id of store().getSubmissionIds) { + expect(store().state.anonymous).toContain(id) + } + + // Vue does not actually rerender the component, so this is commented out + await wrapper.vm.$nextTick() + expect(wrapper.text()).toContain('Show All') + expect(wrapper.text()).not.toContain('Hide All') + + await wrapper.getComponent(ButtonComponent).trigger('click') + expect(store().state.anonymous.size).toBe(0) + }) + + it('Test deanoymization', async () => { + const wrapper = mount(ComparisonTableFilter, { + props: { + searchString: '', + 'onUpdate:searchString': (e) => wrapper.setProps({ searchString: e }) + }, + global: { + plugins: [createTestingPinia({ createSpy: vi.fn })] + } + }) + setUpStore(true) + + wrapper.find('input').setValue('C') + expect(store().state.anonymous.size).toBe(store().getSubmissionIds.length - 1) + expect(store().state.anonymous).not.toContain('C') + }) + + it('Test deanoymization - case insensitive', async () => { + const wrapper = mount(ComparisonTableFilter, { + props: { + searchString: '', + 'onUpdate:searchString': (e) => wrapper.setProps({ searchString: e }) + }, + global: { + plugins: [createTestingPinia({ createSpy: vi.fn })] + } + }) + setUpStore(true) + + wrapper.find('input').setValue('c') + expect(store().state.anonymous.size).toBe(store().getSubmissionIds.length - 1) + expect(store().state.anonymous).not.toContain('C') + }) + + it('Test deanoymization - multiple', async () => { + const wrapper = mount(ComparisonTableFilter, { + props: { + searchString: '', + 'onUpdate:searchString': (e) => wrapper.setProps({ searchString: e }) + }, + global: { + plugins: [createTestingPinia({ createSpy: vi.fn })] + } + }) + setUpStore(true) + + wrapper.find('input').setValue('c A') + expect(store().state.anonymous.size).toBe(store().getSubmissionIds.length - 2) + expect(store().state.anonymous).not.toContain('C') + expect(store().state.anonymous).not.toContain('A') + }) + + it('Test deanoymization - name with spaces', async () => { + const wrapper = mount(ComparisonTableFilter, { + props: { + searchString: '', + 'onUpdate:searchString': (e) => wrapper.setProps({ searchString: e }) + }, + global: { + plugins: [createTestingPinia({ createSpy: vi.fn })] + } + }) + setUpStore(true) + + wrapper.find('input').setValue('test') + expect(store().state.anonymous.size).toBe(store().getSubmissionIds.length) + expect(store().state.anonymous).toContain('test_User') + + wrapper.find('input').setValue('User') + expect(store().state.anonymous.size).toBe(store().getSubmissionIds.length) + expect(store().state.anonymous).toContain('test_User') + + wrapper.find('input').setValue('test User') + expect(store().state.anonymous.size).toBe(store().getSubmissionIds.length - 1) + expect(store().state.anonymous).not.toContain('test_User') + }) +}) + +function setUpStore(fillAnonymous = false) { + const submissionsToDisplayNames = new Map() + submissionsToDisplayNames.set('A', 'A') + submissionsToDisplayNames.set('B', 'B') + submissionsToDisplayNames.set('C', 'C') + submissionsToDisplayNames.set('test_User', 'test User') + store().state.fileIdToDisplayName = submissionsToDisplayNames + store().state.anonymous.clear() + if (fillAnonymous) { + store().state.anonymous = new Set(submissionsToDisplayNames.keys()) + } +}