Commit e41151b7 authored by Illya Klymov's avatar Illya Klymov

Merge branch '212867-clean-up-dependency_list_ui-for-table' into 'master'

Remove feature flag from dependencies table

See merge request gitlab-org/gitlab!31079
parents 7dcbe647 5b76787c
......@@ -2,8 +2,6 @@
import { cloneDeep } from 'lodash';
import { GlBadge, GlIcon, GlLink, GlButton, GlSkeletonLoading, GlTable } from '@gitlab/ui';
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import DependenciesTableRow from './dependencies_table_row.vue';
import DependencyLicenseLinks from './dependency_license_links.vue';
import DependencyVulnerabilities from './dependency_vulnerabilities.vue';
......@@ -26,7 +24,6 @@ const tdClass = (value, key, item) => {
export default {
name: 'DependenciesTable',
components: {
DependenciesTableRow,
DependencyLicenseLinks,
DependencyVulnerabilities,
GlBadge,
......@@ -36,7 +33,6 @@ export default {
GlSkeletonLoading,
GlTable,
},
mixins: [glFeatureFlagsMixin()],
props: {
dependencies: {
type: Array,
......@@ -48,18 +44,8 @@ export default {
},
},
data() {
const tableSections = [
{ className: 'section-20', label: s__('Dependencies|Status') },
{ className: 'section-20', label: s__('Dependencies|Component') },
{ className: 'section-10', label: s__('Dependencies|Version') },
{ className: 'section-20', label: s__('Dependencies|Packager') },
{ className: 'section-15', label: s__('Dependencies|Location') },
{ className: 'section-15', label: s__('Dependencies|License') },
];
return {
localDependencies: this.transformDependenciesForUI(this.dependencies),
tableSections,
};
},
computed: {
......@@ -96,7 +82,6 @@ export default {
<!-- tbody- and thead-class props can be removed when
https://gitlab.com/gitlab-org/gitlab/-/issues/213324 is fixed -->
<gl-table
v-if="glFeatures.dependencyListUi"
:fields="$options.fields"
:items="localDependencies"
:busy="isLoading"
......@@ -168,25 +153,4 @@ export default {
</div>
</template>
</gl-table>
<div v-else>
<div class="gl-responsive-table-row table-row-header text-2 bg-secondary-50 px-2" role="row">
<div
v-for="(section, index) in tableSections"
:key="index"
class="table-section"
:class="section.className"
role="rowheader"
>
{{ section.label }}
</div>
</div>
<dependencies-table-row
v-for="(dependency, index) in dependencies"
:key="index"
:dependency="dependency"
:is-loading="isLoading"
/>
</div>
</template>
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DependenciesTable component given a list of dependencies (loaded) matches the snapshot 1`] = `
<div>
<div
class="gl-responsive-table-row table-row-header text-2 bg-secondary-50 px-2"
role="row"
>
<div
class="table-section section-20"
role="rowheader"
>
Status
</div>
<div
class="table-section section-20"
role="rowheader"
>
Component
</div>
<div
class="table-section section-10"
role="rowheader"
>
Version
</div>
<div
class="table-section section-20"
role="rowheader"
>
Packager
</div>
<div
class="table-section section-15"
role="rowheader"
>
Location
</div>
<div
class="table-section section-15"
role="rowheader"
>
License
</div>
</div>
<dependencies-table-row-stub
dependency="[object Object]"
/>
<dependencies-table-row-stub
dependency="[object Object]"
/>
</div>
`;
exports[`DependenciesTable component given a list of dependencies (loading) matches the snapshot 1`] = `
<div>
<div
class="gl-responsive-table-row table-row-header text-2 bg-secondary-50 px-2"
role="row"
>
<div
class="table-section section-20"
role="rowheader"
>
Status
</div>
<div
class="table-section section-20"
role="rowheader"
>
Component
</div>
<div
class="table-section section-10"
role="rowheader"
>
Version
</div>
<div
class="table-section section-20"
role="rowheader"
>
Packager
</div>
<div
class="table-section section-15"
role="rowheader"
>
Location
</div>
<div
class="table-section section-15"
role="rowheader"
>
License
</div>
</div>
<dependencies-table-row-stub
dependency="[object Object]"
isloading="true"
/>
<dependencies-table-row-stub
dependency="[object Object]"
isloading="true"
/>
</div>
`;
exports[`DependenciesTable component given an empty list of dependencies matches the snapshot 1`] = `
<div>
<div
class="gl-responsive-table-row table-row-header text-2 bg-secondary-50 px-2"
role="row"
>
<div
class="table-section section-20"
role="rowheader"
>
Status
</div>
<div
class="table-section section-20"
role="rowheader"
>
Component
</div>
<div
class="table-section section-10"
role="rowheader"
>
Version
</div>
<div
class="table-section section-20"
role="rowheader"
>
Packager
</div>
<div
class="table-section section-15"
role="rowheader"
>
Location
</div>
<div
class="table-section section-15"
role="rowheader"
>
License
</div>
</div>
</div>
`;
import { nextTick } from 'vue';
import { mount, shallowMount } from '@vue/test-utils';
import { mount } from '@vue/test-utils';
import { GlBadge, GlButton, GlLink, GlSkeletonLoading } from '@gitlab/ui';
import stubChildren from 'helpers/stub_children';
import DependenciesTable from 'ee/dependencies/components/dependencies_table.vue';
import DependenciesTableRow from 'ee/dependencies/components/dependencies_table_row.vue';
import DependencyLicenseLinks from 'ee/dependencies/components/dependency_license_links.vue';
import DependencyVulnerabilities from 'ee/dependencies/components/dependency_vulnerabilities.vue';
import { makeDependency } from './utils';
......@@ -10,10 +10,11 @@ import { makeDependency } from './utils';
describe('DependenciesTable component', () => {
let wrapper;
const factory = ({ propsData, ...options } = {}) => {
wrapper = shallowMount(DependenciesTable, {
const createComponent = ({ propsData, ...options } = {}) => {
wrapper = mount(DependenciesTable, {
...options,
propsData: { ...propsData },
stubs: { ...stubChildren(DependenciesTable), GlTable: false },
});
};
......@@ -21,255 +22,192 @@ describe('DependenciesTable component', () => {
wrapper.destroy();
});
describe('given an empty list of dependencies', () => {
const findTableRows = () => wrapper.findAll('tbody > tr');
const findRowToggleButtons = () => wrapper.findAll(GlButton);
const findDependencyVulnerabilities = () => wrapper.find(DependencyVulnerabilities);
const normalizeWhitespace = string => string.replace(/\s+/g, ' ');
const expectDependencyRow = (rowWrapper, dependency) => {
const [
componentCell,
packagerCell,
locationCell,
licenseCell,
isVulnerableCell,
] = rowWrapper.findAll('td').wrappers;
expect(normalizeWhitespace(componentCell.text())).toBe(
`${dependency.name} ${dependency.version}`,
);
expect(packagerCell.text()).toBe(dependency.packager);
const locationLink = locationCell.find(GlLink);
expect(locationLink.attributes().href).toBe(dependency.location.blob_path);
expect(locationLink.text()).toBe(dependency.location.path);
const licenseLinks = licenseCell.find(DependencyLicenseLinks);
expect(licenseLinks.exists()).toBe(true);
expect(licenseLinks.props()).toEqual({
licenses: dependency.licenses,
title: dependency.name,
});
const isVulnerableCellText = normalizeWhitespace(isVulnerableCell.text());
if (dependency.vulnerabilities.length) {
expect(isVulnerableCellText).toContain(`${dependency.vulnerabilities.length} vuln`);
} else {
expect(isVulnerableCellText).toBe('');
}
};
describe('given the table is loading', () => {
let dependencies;
beforeEach(() => {
factory({
dependencies = [makeDependency()];
createComponent({
propsData: {
dependencies: [],
isLoading: false,
dependencies,
isLoading: true,
},
});
});
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
it('renders the loading skeleton', () => {
expect(wrapper.contains(GlSkeletonLoading)).toBe(true);
});
it('does not render any dependencies', () => {
expect(wrapper.text()).not.toContain(dependencies[0].name);
});
});
[true, false].forEach(isLoading => {
describe(`given a list of dependencies (${isLoading ? 'loading' : 'loaded'})`, () => {
let dependencies;
beforeEach(() => {
dependencies = [makeDependency(), makeDependency({ name: 'foo' })];
factory({
propsData: {
dependencies,
isLoading,
},
});
describe('given an empty list of dependencies', () => {
beforeEach(() => {
createComponent({
propsData: {
dependencies: [],
isLoading: false,
},
});
});
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('renders the table header', () => {
const expectedLabels = DependenciesTable.fields.map(({ label }) => label);
const headerCells = wrapper.findAll('thead th').wrappers;
it('passes the correct props to the table rows', () => {
const rows = wrapper.findAll(DependenciesTableRow).wrappers;
rows.forEach((row, index) => {
expect(row.props()).toEqual(
expect.objectContaining({
dependency: dependencies[index],
isLoading,
}),
);
});
});
expect(headerCells.map(cell => cell.text())).toEqual(expectedLabels);
});
});
describe('given the dependencyListUi feature flag is enabled', () => {
const createComponent = ({ propsData, ...options } = {}) => {
const stubs = Object.keys(DependenciesTable.components).filter(
component => component !== 'GlTable',
);
wrapper = mount(DependenciesTable, {
...options,
propsData: { ...propsData },
provide: { glFeatures: { dependencyListUi: true } },
stubs,
});
};
const findTableRows = () => wrapper.findAll('tbody > tr');
const findRowToggleButtons = () => wrapper.findAll(GlButton);
const findDependencyVulnerabilities = () => wrapper.find(DependencyVulnerabilities);
const normalizeWhitespace = string => string.replace(/\s+/g, ' ');
const expectDependencyRow = (rowWrapper, dependency) => {
const [
componentCell,
packagerCell,
locationCell,
licenseCell,
isVulnerableCell,
] = rowWrapper.findAll('td').wrappers;
expect(normalizeWhitespace(componentCell.text())).toBe(
`${dependency.name} ${dependency.version}`,
);
expect(packagerCell.text()).toBe(dependency.packager);
const locationLink = locationCell.find(GlLink);
expect(locationLink.attributes().href).toBe(dependency.location.blob_path);
expect(locationLink.text()).toBe(dependency.location.path);
const licenseLinks = licenseCell.find(DependencyLicenseLinks);
expect(licenseLinks.exists()).toBe(true);
expect(licenseLinks.props()).toEqual({
licenses: dependency.licenses,
title: dependency.name,
});
it('does not render any rows', () => {
expect(findTableRows()).toHaveLength(0);
});
});
const isVulnerableCellText = normalizeWhitespace(isVulnerableCell.text());
if (dependency.vulnerabilities.length) {
expect(isVulnerableCellText).toContain(`${dependency.vulnerabilities.length} vuln`);
} else {
expect(isVulnerableCellText).toBe('');
}
};
describe('given dependencies with no vulnerabilities', () => {
let dependencies;
describe('given the table is loading', () => {
let dependencies;
beforeEach(() => {
dependencies = [
makeDependency({ vulnerabilities: [] }),
makeDependency({ name: 'foo', vulnerabilities: [] }),
];
beforeEach(() => {
dependencies = [makeDependency()];
createComponent({
propsData: {
dependencies,
isLoading: true,
},
});
createComponent({
propsData: {
dependencies,
isLoading: false,
},
});
});
it('renders the loading skeleton', () => {
expect(wrapper.contains(GlSkeletonLoading)).toBe(true);
});
it('renders a row for each dependency', () => {
const rows = findTableRows();
it('does not render any dependencies', () => {
expect(wrapper.text()).not.toContain(dependencies[0].name);
dependencies.forEach((dependency, i) => {
expectDependencyRow(rows.at(i), dependency);
});
});
describe('given an empty list of dependencies', () => {
beforeEach(() => {
createComponent({
propsData: {
dependencies: [],
isLoading: false,
},
});
});
it('does not render any row toggle buttons', () => {
expect(findRowToggleButtons()).toHaveLength(0);
});
it('renders the table header', () => {
const expectedLabels = DependenciesTable.fields.map(({ label }) => label);
const headerCells = wrapper.findAll('thead th').wrappers;
it('does not render vulnerability details', () => {
expect(findDependencyVulnerabilities().exists()).toBe(false);
});
});
expect(headerCells.map(cell => cell.text())).toEqual(expectedLabels);
});
describe('given some dependencies with vulnerabilities', () => {
let dependencies;
it('does not render any rows', () => {
expect(findTableRows()).toHaveLength(0);
beforeEach(() => {
dependencies = [
makeDependency({ name: 'qux', vulnerabilities: ['bar', 'baz'] }),
makeDependency({ vulnerabilities: [] }),
// Guarantee that the component doesn't mutate these, but still
// maintains its row-toggling behaviour (i.e., via _showDetails)
].map(Object.freeze);
createComponent({
propsData: {
dependencies,
isLoading: false,
},
});
});
describe('given dependencies with no vulnerabilities', () => {
let dependencies;
it('renders a row for each dependency', () => {
const rows = findTableRows();
beforeEach(() => {
dependencies = [
makeDependency({ vulnerabilities: [] }),
makeDependency({ name: 'foo', vulnerabilities: [] }),
];
createComponent({
propsData: {
dependencies,
isLoading: false,
},
});
dependencies.forEach((dependency, i) => {
expectDependencyRow(rows.at(i), dependency);
});
});
it('renders a row for each dependency', () => {
const rows = findTableRows();
it('render the toggle button for each row', () => {
const toggleButtons = findRowToggleButtons();
dependencies.forEach((dependency, i) => {
expectDependencyRow(rows.at(i), dependency);
});
});
dependencies.forEach((dependency, i) => {
const button = toggleButtons.at(i);
it('does not render any row toggle buttons', () => {
expect(findRowToggleButtons()).toHaveLength(0);
expect(button.exists()).toBe(true);
expect(button.classes('invisible')).toBe(dependency.vulnerabilities.length === 0);
});
});
it('does not render vulnerability details', () => {
expect(findDependencyVulnerabilities().exists()).toBe(false);
});
it('does not render vulnerability details', () => {
expect(findDependencyVulnerabilities().exists()).toBe(false);
});
describe('given some dependencies with vulnerabilities', () => {
let dependencies;
describe('the dependency vulnerabilities', () => {
let rowIndexWithVulnerabilities;
beforeEach(() => {
dependencies = [
makeDependency({ name: 'qux', vulnerabilities: ['bar', 'baz'] }),
makeDependency({ vulnerabilities: [] }),
// Guarantee that the component doesn't mutate these, but still
// maintains its row-toggling behaviour (i.e., via _showDetails)
].map(Object.freeze);
createComponent({
propsData: {
dependencies,
isLoading: false,
},
});
rowIndexWithVulnerabilities = dependencies.findIndex(dep => dep.vulnerabilities.length > 0);
});
it('renders a row for each dependency', () => {
const rows = findTableRows();
dependencies.forEach((dependency, i) => {
expectDependencyRow(rows.at(i), dependency);
});
});
it('can be displayed by clicking on the toggle button', () => {
const toggleButton = findRowToggleButtons().at(rowIndexWithVulnerabilities);
toggleButton.vm.$emit('click');
it('render the toggle button for each row', () => {
const toggleButtons = findRowToggleButtons();
dependencies.forEach((dependency, i) => {
const button = toggleButtons.at(i);
expect(button.exists()).toBe(true);
expect(button.classes('invisible')).toBe(dependency.vulnerabilities.length === 0);
});
});
it('does not render vulnerability details', () => {
expect(findDependencyVulnerabilities().exists()).toBe(false);
});
describe('the dependency vulnerabilities', () => {
let rowIndexWithVulnerabilities;
beforeEach(() => {
rowIndexWithVulnerabilities = dependencies.findIndex(
dep => dep.vulnerabilities.length > 0,
);
});
it('can be displayed by clicking on the toggle button', () => {
const toggleButton = findRowToggleButtons().at(rowIndexWithVulnerabilities);
toggleButton.vm.$emit('click');
return nextTick().then(() => {
expect(findDependencyVulnerabilities().props()).toEqual({
vulnerabilities: dependencies[rowIndexWithVulnerabilities].vulnerabilities,
});
return nextTick().then(() => {
expect(findDependencyVulnerabilities().props()).toEqual({
vulnerabilities: dependencies[rowIndexWithVulnerabilities].vulnerabilities,
});
});
});
it('can be displayed by clicking on the vulnerabilities badge', () => {
const badge = findTableRows()
.at(rowIndexWithVulnerabilities)
.find(GlBadge);
badge.trigger('click');
it('can be displayed by clicking on the vulnerabilities badge', () => {
const badge = findTableRows()
.at(rowIndexWithVulnerabilities)
.find(GlBadge);
badge.trigger('click');
return nextTick().then(() => {
expect(findDependencyVulnerabilities().props()).toEqual({
vulnerabilities: dependencies[rowIndexWithVulnerabilities].vulnerabilities,
});
return nextTick().then(() => {
expect(findDependencyVulnerabilities().props()).toEqual({
vulnerabilities: dependencies[rowIndexWithVulnerabilities].vulnerabilities,
});
});
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment