Commit a99f9e44 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch...

Merge branch '227582-package-detail-ui-update-the-package-detail-main-body-to-include-a-history-install-commands' into 'master'

New Package details UI

See merge request gitlab-org/gitlab!38680
parents 46695c58 552afbe4
<script>
import { GlAvatar, GlTooltipDirective, GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
import { mapGetters, mapState } from 'vuex';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { __, s__ } from '~/locale';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { formatDate } from '~/lib/utils/datetime_utility';
export default {
name: 'PackageActivity',
components: {
ClipboardButton,
GlAvatar,
GlIcon,
GlLink,
GlSprintf,
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [timeagoMixin],
data() {
return {
showDescription: false,
};
},
computed: {
...mapState(['packageEntity']),
...mapGetters(['packagePipeline']),
publishedDate() {
return formatDate(this.packageEntity.created_at, 'HH:MM yyyy-mm-dd');
},
},
methods: {
toggleShowDescription() {
this.showDescription = !this.showDescription;
},
},
i18n: {
showCommit: __('Show commit description'),
pipelineText: s__(
'PackageRegistry|Pipeline %{linkStart}%{linkEnd} triggered %{timestamp} by %{author}',
),
publishText: s__('PackageRegistry|Published to the repository at %{timestamp}'),
},
};
</script>
<template>
<div class="mb-3">
<h3 class="gl-font-lg">{{ __('Activity') }}</h3>
<div ref="commit-info" class="info-well">
<div v-if="packagePipeline" class="well-segment">
<div class="d-flex align-items-center">
<gl-icon name="commit" class="d-none d-sm-block" />
<button
v-if="packagePipeline.git_commit_message"
ref="commit-message-toggle"
v-gl-tooltip
:title="$options.i18n.showCommit"
:aria-label="$options.i18n.showCommit"
class="text-expander mr-2 d-none d-sm-flex"
type="button"
@click="toggleShowDescription"
>
<gl-icon name="ellipsis_h" :size="12" />
</button>
<gl-link :href="`../../commit/${packagePipeline.sha}`">{{ packagePipeline.sha }}</gl-link>
<clipboard-button
:text="packagePipeline.sha"
:title="__('Copy commit SHA')"
css-class="border-0 text-secondary py-0"
/>
</div>
<div v-if="showDescription" ref="commit-message" class="mt-2 d-none d-sm-block">
<pre class="commit-row-description mb-0 pl-2">{{
packagePipeline.git_commit_message
}}</pre>
</div>
</div>
<div v-if="packagePipeline" ref="pipeline-info" class="well-segment">
<div class="d-flex align-items-center">
<gl-icon name="pipeline" class="mr-2 d-none d-sm-block" />
<gl-sprintf :message="$options.i18n.pipelineText">
<template #link>
&nbsp;
<gl-link :href="`../../pipelines/${packagePipeline.id}`"
>#{{ packagePipeline.id }}</gl-link
>
&nbsp;
</template>
<template #timestamp>
<span v-gl-tooltip :title="tooltipTitle(packagePipeline.created_at)">
&nbsp;{{ timeFormatted(packagePipeline.created_at) }}&nbsp;
</span>
</template>
<template #author
>{{ packagePipeline.user.name }}
<gl-avatar
class="ml-2 d-none d-sm-block"
:src="packagePipeline.user.avatar_url"
:size="24"
/></template>
</gl-sprintf>
</div>
</div>
<div class="well-segment d-flex align-items-center">
<gl-icon name="clock" class="mr-2 d-none d-sm-block" />
<gl-sprintf :message="$options.i18n.publishText">
<template #timestamp>
{{ publishedDate }}
</template>
</gl-sprintf>
</div>
</div>
</div>
</template>
......@@ -14,23 +14,16 @@ import {
} from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import Tracking from '~/tracking';
import PackageActivity from './activity.vue';
import PackageHistory from './package_history.vue';
import PackageInformation from './information.vue';
import PackageTitle from './package_title.vue';
import ConanInstallation from './conan_installation.vue';
import MavenInstallation from './maven_installation.vue';
import NpmInstallation from './npm_installation.vue';
import NugetInstallation from './nuget_installation.vue';
import PypiInstallation from './pypi_installation.vue';
import PackagesListLoader from '../../shared/components/packages_list_loader.vue';
import PackageListRow from '../../shared/components/package_list_row.vue';
import DependencyRow from './dependency_row.vue';
import AdditionalMetadata from './additional_metadata.vue';
import InstallationCommands from './installation_commands.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import { generatePackageInfo } from '../utils';
import { __, s__ } from '~/locale';
import { PackageType, TrackingActions } from '../../shared/constants';
import { packageTypeToTrackCategory } from '../../shared/utils';
......@@ -48,19 +41,13 @@ export default {
GlTable,
FileIcon,
GlSprintf,
PackageActivity,
PackageInformation,
PackageTitle,
ConanInstallation,
MavenInstallation,
NpmInstallation,
NugetInstallation,
PypiInstallation,
PackagesListLoader,
PackageListRow,
DependencyRow,
PackageHistory,
AdditionalMetadata,
InstallationCommands,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -79,62 +66,13 @@ export default {
'svgPath',
'npmPath',
'npmHelpPath',
'oneColumnView',
]),
installationComponent() {
switch (this.packageEntity.package_type) {
case PackageType.CONAN:
return ConanInstallation;
case PackageType.MAVEN:
return MavenInstallation;
case PackageType.NPM:
return NpmInstallation;
case PackageType.NUGET:
return NugetInstallation;
case PackageType.PYPI:
return PypiInstallation;
default:
return null;
}
},
isValidPackage() {
return Boolean(this.packageEntity.name);
},
canDeletePackage() {
return this.canDelete && this.destroyPath;
},
packageInformation() {
return generatePackageInfo(this.packageEntity);
},
packageMetadataTitle() {
switch (this.packageEntity.package_type) {
case PackageType.MAVEN:
return s__('Maven Metadata');
default:
return s__('Package information');
}
},
packageMetadata() {
switch (this.packageEntity.package_type) {
case PackageType.MAVEN:
return [
{
label: s__('Group ID'),
value: this.packageEntity.maven_metadatum.app_group,
},
{
label: s__('Artifact ID'),
value: this.packageEntity.maven_metadatum.app_name,
},
{
label: s__('Version'),
value: this.packageEntity.maven_metadatum.app_version,
},
];
default:
return null;
}
},
filesTableRows() {
return this.packageFiles.map(x => ({
name: x.file_name,
......@@ -157,6 +95,9 @@ export default {
showDependencies() {
return this.packageEntity.package_type === PackageType.NUGET;
},
showFiles() {
return this.packageEntity?.package_type !== PackageType.COMPOSER;
},
},
methods: {
...mapActions(['fetchPackageVersions']),
......@@ -225,68 +166,47 @@ export default {
<gl-tabs>
<gl-tab :title="__('Detail')">
<template v-if="!oneColumnView">
<div
class="row"
data-qa-selector="package_information_content"
data-testid="old-package-info"
>
<div class="col-sm-6">
<package-information :information="packageInformation" />
<package-information
v-if="packageMetadata"
:heading="packageMetadataTitle"
:information="packageMetadata"
:show-copy="true"
/>
</div>
<div data-qa-selector="package_information_content">
<package-history :package-entity="packageEntity" :project-name="projectName" />
<div class="col-sm-6">
<component
:is="installationComponent"
v-if="installationComponent"
:name="packageEntity.name"
:registry-url="npmPath"
:help-url="npmHelpPath"
<installation-commands
:package-entity="packageEntity"
:npm-path="npmPath"
:npm-help-path="npmHelpPath"
/>
</div>
</div>
<package-activity />
</template>
<template v-else>
<package-history :package-entity="packageEntity" :project-name="projectName" />
<additional-metadata :package-entity="packageEntity" />
</template>
</div>
<template v-if="showFiles">
<h3 class="gl-font-lg gl-mt-5">{{ __('Files') }}</h3>
<gl-table
:fields="$options.filesTableHeaderFields"
:items="filesTableRows"
tbody-tr-class="js-file-row"
>
<template #cell(name)="items">
<template #cell(name)="{ item }">
<gl-link
:href="items.item.downloadPath"
:href="item.downloadPath"
class="js-file-download gl-relative"
@click="track($options.trackingActions.PULL_PACKAGE)"
>
<file-icon
:file-name="items.item.name"
:file-name="item.name"
css-classes="gl-relative file-icon"
class="gl-mr-1 gl-relative"
/>
<span class="gl-relative">{{ items.item.name }}</span>
<span class="gl-relative">{{ item.name }}</span>
</gl-link>
</template>
<template #cell(created)="items">
<span v-gl-tooltip :title="tooltipTitle(items.item.created)">{{
timeFormatted(items.item.created)
<template #cell(created)="{ item }">
<span v-gl-tooltip :title="tooltipTitle(item.created)">{{
timeFormatted(item.created)
}}</span>
</template>
</gl-table>
</template>
</gl-tab>
<gl-tab v-if="showDependencies" title-item-class="js-dependencies-tab">
......
<script>
import { s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { GlLink } from '@gitlab/ui';
import { InformationType } from '../constants';
export default {
name: 'PackageInformation',
components: {
ClipboardButton,
GlLink,
},
props: {
heading: {
type: String,
default: s__('Package information'),
required: false,
},
information: {
type: Array,
default: () => [],
required: true,
},
showCopy: {
type: Boolean,
required: false,
default: false,
},
},
informationType: InformationType,
};
</script>
<template>
<div class="card">
<div class="card-header">
<strong>{{ heading }}</strong>
</div>
<ul class="content-list">
<li v-for="(item, index) in information" :key="index">
<span class="text-secondary">{{ item.label }}</span>
<div class="float-right w-75 gl-text-right">
<gl-link
v-if="item.type === $options.informationType.LINK"
:href="item.value"
target="_blank"
>
{{ item.value }}
</gl-link>
<span v-else>{{ item.value }}</span>
<clipboard-button
v-if="showCopy"
:text="item.value"
:title="sprintf(__('Copy %{field}'), { field: item.label })"
css-class="border-0 text-secondary py-0"
/>
</div>
</li>
</ul>
</div>
</template>
<script>
import ConanInstallation from './conan_installation.vue';
import MavenInstallation from './maven_installation.vue';
import NpmInstallation from './npm_installation.vue';
import NugetInstallation from './nuget_installation.vue';
import PypiInstallation from './pypi_installation.vue';
import { PackageType } from '../../shared/constants';
export default {
name: 'InstallationCommands',
components: {
[PackageType.CONAN]: ConanInstallation,
[PackageType.MAVEN]: MavenInstallation,
[PackageType.NPM]: NpmInstallation,
[PackageType.NUGET]: NugetInstallation,
[PackageType.PYPI]: PypiInstallation,
},
props: {
packageEntity: {
type: Object,
required: true,
},
npmPath: {
type: String,
required: false,
default: '',
},
npmHelpPath: {
type: String,
required: false,
default: '',
},
},
computed: {
installationComponent() {
return this.$options.components[this.packageEntity.package_type];
},
},
};
</script>
<template>
<div v-if="installationComponent">
<h3 class="gl-font-lg gl-mt-5" data-testid="title">{{ __('Installation Commands') }}</h3>
<component
:is="installationComponent"
:name="packageEntity.name"
:registry-url="npmPath"
:help-url="npmHelpPath"
/>
</div>
</template>
......@@ -41,7 +41,3 @@ export const NpmManager = {
export const FETCH_PACKAGE_VERSIONS_ERROR = s__(
'PackageRegistry|Unable to fetch package version information.',
);
export const InformationType = {
LINK: 'link',
};
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import PackagesApp from './components/app.vue';
import Translate from '~/vue_shared/translate';
import createStore from './store';
......@@ -8,7 +7,7 @@ Vue.use(Translate);
export default () => {
const el = document.querySelector('#js-vue-packages-detail');
const { package: packageJson, canDelete: canDeleteStr, oneColumnView, ...rest } = el.dataset;
const { package: packageJson, canDelete: canDeleteStr, ...rest } = el.dataset;
const packageEntity = JSON.parse(packageJson);
const canDelete = canDeleteStr === 'true';
......@@ -16,7 +15,6 @@ export default () => {
packageEntity,
packageFiles: packageEntity.package_files,
canDelete,
oneColumnView: parseBoolean(oneColumnView),
...rest,
});
......
import { __ } from '~/locale';
import { formatDate } from '~/lib/utils/datetime_utility';
import { TrackingActions, InformationType } from './constants';
import { PackageType } from '../shared/constants';
import { orderBy } from 'lodash';
import { TrackingActions } from './constants';
export const trackInstallationTabChange = {
methods: {
......@@ -25,67 +21,3 @@ export function generateConanRecipe(packageEntity = {}) {
return `${name}/${version}@${packageUsername}/${packageChannel}`;
}
export function generatePackageInfo(packageEntity = {}) {
const information = [];
if (packageEntity.package_type === PackageType.CONAN) {
information.push({
order: 1,
label: __('Recipe'),
value: generateConanRecipe(packageEntity),
});
} else {
information.push({
order: 1,
label: __('Name'),
value: packageEntity.name || '',
});
}
if (packageEntity.package_type === PackageType.NUGET) {
const {
nuget_metadatum: { project_url: projectUrl, license_url: licenseUrl } = {},
} = packageEntity;
if (projectUrl) {
information.push({
order: 3,
label: __('Project URL'),
value: projectUrl,
type: InformationType.LINK,
});
}
if (licenseUrl) {
information.push({
order: 4,
label: __('License URL'),
value: licenseUrl,
type: InformationType.LINK,
});
}
}
return orderBy(
[
...information,
{
order: 2,
label: __('Version'),
value: packageEntity.version || '',
},
{
order: 5,
label: __('Created on'),
value: formatDate(packageEntity.created_at),
},
{
order: 6,
label: __('Updated at'),
value: formatDate(packageEntity.updated_at),
},
],
['order'],
);
}
......@@ -20,5 +20,4 @@
pypi_path: pypi_registry_url(@project.id),
pypi_setup_path: package_registry_project_url(@project.id, :pypi),
pypi_help_path: help_page_path('user/packages/pypi_repository/index'),
project_name: @project.name,
one_column_view: Feature.enabled?(:packages_details_one_column, @project).to_s } }
project_name: @project.name} }
---
title: Enable New Package details UI, remove feature flag and remove all old code
merge_request: 38680
author:
type: changed
......@@ -3235,9 +3235,6 @@ msgstr ""
msgid "Artifact"
msgstr ""
msgid "Artifact ID"
msgstr ""
msgid "Artifact could not be deleted."
msgstr ""
......@@ -6763,9 +6760,6 @@ msgstr ""
msgid "Copy"
msgstr ""
msgid "Copy %{field}"
msgstr ""
msgid "Copy %{http_label} clone URL"
msgstr ""
......@@ -13025,6 +13019,9 @@ msgstr ""
msgid "Install on clusters"
msgstr ""
msgid "Installation Commands"
msgstr ""
msgid "Installed"
msgstr ""
......@@ -14091,9 +14088,6 @@ msgstr ""
msgid "License ID:"
msgstr ""
msgid "License URL"
msgstr ""
msgid "License-Check"
msgstr ""
......@@ -14693,9 +14687,6 @@ msgstr ""
msgid "MattermostService|This service allows users to perform common operations on this project by entering slash commands in Mattermost."
msgstr ""
msgid "Maven Metadata"
msgstr ""
msgid "Max Group Export Download requests per minute per user"
msgstr ""
......@@ -16963,9 +16954,6 @@ msgstr ""
msgid "Package deleted successfully"
msgstr ""
msgid "Package information"
msgstr ""
msgid "Package recipe already exists"
msgstr ""
......@@ -17122,18 +17110,12 @@ msgstr ""
msgid "PackageRegistry|Pip Command"
msgstr ""
msgid "PackageRegistry|Pipeline %{linkStart}%{linkEnd} triggered %{timestamp} by %{author}"
msgstr ""
msgid "PackageRegistry|Pipeline %{link} triggered %{datetime} by %{author}"
msgstr ""
msgid "PackageRegistry|Published to the %{project} Package Registry %{datetime}"
msgstr ""
msgid "PackageRegistry|Published to the repository at %{timestamp}"
msgstr ""
msgid "PackageRegistry|PyPi"
msgstr ""
......@@ -19858,9 +19840,6 @@ msgstr ""
msgid "Recent searches"
msgstr ""
msgid "Recipe"
msgstr ""
msgid "Reconfigure"
msgstr ""
......@@ -26133,9 +26112,6 @@ msgstr ""
msgid "Updated %{updated_at} by %{updated_by}"
msgstr ""
msgid "Updated at"
msgstr ""
msgid "Updated to %{linkStart}chart v%{linkEnd}"
msgstr ""
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PackageActivity render to match the default snapshot when no pipeline 1`] = `
<div
class="mb-3"
>
<h3
class="gl-font-lg"
>
Activity
</h3>
<div
class="info-well"
>
<!---->
<!---->
<div
class="well-segment d-flex align-items-center"
>
<gl-icon-stub
class="mr-2 d-none d-sm-block"
name="clock"
size="16"
/>
<gl-sprintf-stub
message="Published to the repository at %{timestamp}"
/>
</div>
</div>
</div>
`;
exports[`PackageActivity render to match the default snapshot when there is a pipeline 1`] = `
<div
class="mb-3"
>
<h3
class="gl-font-lg"
>
Activity
</h3>
<div
class="info-well"
>
<div
class="well-segment"
>
<div
class="d-flex align-items-center"
>
<gl-icon-stub
class="d-none d-sm-block"
name="commit"
size="16"
/>
<!---->
<gl-link-stub
href="../../commit/sha-baz"
>
sha-baz
</gl-link-stub>
<clipboard-button-stub
cssclass="border-0 text-secondary py-0"
text="sha-baz"
title="Copy commit SHA"
tooltipplacement="top"
/>
</div>
<!---->
</div>
<div
class="well-segment"
>
<div
class="d-flex align-items-center"
>
<gl-icon-stub
class="mr-2 d-none d-sm-block"
name="pipeline"
size="16"
/>
<gl-sprintf-stub
message="Pipeline %{linkStart}%{linkEnd} triggered %{timestamp} by %{author}"
/>
</div>
</div>
<div
class="well-segment d-flex align-items-center"
>
<gl-icon-stub
class="mr-2 d-none d-sm-block"
name="clock"
size="16"
/>
<gl-sprintf-stub
message="Published to the repository at %{timestamp}"
/>
</div>
</div>
</div>
`;
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import PackageActivity from '~/packages/details/components/activity.vue';
import {
npmPackage,
mavenPackage as packageWithoutBuildInfo,
mockPipelineInfo,
} from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('PackageActivity', () => {
let wrapper;
let store;
function createComponent(packageEntity = packageWithoutBuildInfo, pipelineInfo = null) {
store = new Vuex.Store({
state: {
packageEntity,
},
getters: {
packagePipeline: () => pipelineInfo,
},
});
wrapper = shallowMount(PackageActivity, {
localVue,
store,
});
}
const commitMessageToggle = () => wrapper.find({ ref: 'commit-message-toggle' });
const commitMessage = () => wrapper.find({ ref: 'commit-message' });
const commitInfo = () => wrapper.find({ ref: 'commit-info' });
const pipelineInfo = () => wrapper.find({ ref: 'pipeline-info' });
afterEach(() => {
if (wrapper) wrapper.destroy();
wrapper = null;
});
describe('render', () => {
it('to match the default snapshot when no pipeline', () => {
createComponent();
expect(wrapper.element).toMatchSnapshot();
});
it('to match the default snapshot when there is a pipeline', () => {
createComponent(npmPackage, mockPipelineInfo);
expect(wrapper.element).toMatchSnapshot();
});
});
describe('commit message toggle', () => {
it("does not display the commit message button when there isn't one", () => {
createComponent(npmPackage, mockPipelineInfo);
expect(commitMessageToggle().exists()).toBe(false);
expect(commitMessage().exists()).toBe(false);
});
it('displays the commit message on toggle', () => {
const commitMessageStr = 'a message';
createComponent(npmPackage, {
...mockPipelineInfo,
git_commit_message: commitMessageStr,
});
commitMessageToggle().trigger('click');
return wrapper.vm.$nextTick(() => expect(commitMessage().text()).toBe(commitMessageStr));
});
});
describe('pipeline information', () => {
it('does not display pipeline information when no build info is available', () => {
createComponent();
expect(pipelineInfo().exists()).toBe(false);
});
it('displays the pipeline information if found', () => {
createComponent(npmPackage, mockPipelineInfo);
expect(commitInfo().exists()).toBe(true);
expect(pipelineInfo().exists()).toBe(true);
});
});
});
......@@ -5,28 +5,25 @@ import Tracking from '~/tracking';
import * as getters from '~/packages/details/store/getters';
import PackagesApp from '~/packages/details/components/app.vue';
import PackageTitle from '~/packages/details/components/package_title.vue';
import PackageInformation from '~/packages/details/components/information.vue';
import NpmInstallation from '~/packages/details/components/npm_installation.vue';
import MavenInstallation from '~/packages/details/components/maven_installation.vue';
import * as SharedUtils from '~/packages/shared/utils';
import { TrackingActions } from '~/packages/shared/constants';
import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
import PackageListRow from '~/packages/shared/components/package_list_row.vue';
import ConanInstallation from '~/packages/details/components/conan_installation.vue';
import NugetInstallation from '~/packages/details/components/nuget_installation.vue';
import PypiInstallation from '~/packages/details/components/pypi_installation.vue';
import DependencyRow from '~/packages/details/components/dependency_row.vue';
import PackageHistory from '~/packages/details/components/package_history.vue';
import AdditionalMetadata from '~/packages/details/components/additional_metadata.vue';
import PackageActivity from '~/packages/details/components/activity.vue';
import InstallationCommands from '~/packages/details/components/installation_commands.vue';
import {
composerPackage,
conanPackage,
mavenPackage,
mavenFiles,
npmPackage,
npmFiles,
nugetPackage,
pypiPackage,
} from '../../mock_data';
import stubChildren from 'helpers/stub_children';
......@@ -79,13 +76,6 @@ describe('PackagesApp', () => {
const packageTitle = () => wrapper.find(PackageTitle);
const emptyState = () => wrapper.find(GlEmptyState);
const allPackageInformation = () => wrapper.findAll(PackageInformation);
const packageInformation = index => allPackageInformation().at(index);
const npmInstallation = () => wrapper.find(NpmInstallation);
const mavenInstallation = () => wrapper.find(MavenInstallation);
const conanInstallation = () => wrapper.find(ConanInstallation);
const nugetInstallation = () => wrapper.find(NugetInstallation);
const pypiInstallation = () => wrapper.find(PypiInstallation);
const allFileRows = () => wrapper.findAll('.js-file-row');
const firstFileDownloadLink = () => wrapper.find('.js-file-download');
const deleteButton = () => wrapper.find('.js-delete-button');
......@@ -101,8 +91,7 @@ describe('PackagesApp', () => {
const dependencyRows = () => wrapper.findAll(DependencyRow);
const findPackageHistory = () => wrapper.find(PackageHistory);
const findAdditionalMetadata = () => wrapper.find(AdditionalMetadata);
const findPackageActivity = () => wrapper.find(PackageActivity);
const findOldPackageInfo = () => wrapper.find('[data-testid="old-package-info"]');
const findInstallationCommands = () => wrapper.find(InstallationCommands);
afterEach(() => {
wrapper.destroy();
......@@ -122,35 +111,28 @@ describe('PackagesApp', () => {
expect(emptyState()).toExist();
});
it('renders package information and metadata for packages containing both information and metadata', () => {
it('package history has the right props', () => {
createComponent();
expect(packageInformation(0)).toExist();
expect(packageInformation(1)).toExist();
expect(findPackageHistory().exists()).toBe(true);
expect(findPackageHistory().props('packageEntity')).toEqual(wrapper.vm.packageEntity);
expect(findPackageHistory().props('projectName')).toEqual(wrapper.vm.projectName);
});
it('does not render package metadata for npm as npm packages do not contain metadata', () => {
createComponent({ packageEntity: npmPackage, packageFiles: npmFiles });
expect(packageInformation(0)).toExist();
expect(allPackageInformation()).toHaveLength(1);
it('additional metadata has the right props', () => {
createComponent();
expect(findAdditionalMetadata().exists()).toBe(true);
expect(findAdditionalMetadata().props('packageEntity')).toEqual(wrapper.vm.packageEntity);
});
describe('installation instructions', () => {
describe.each`
packageEntity | selector
${conanPackage} | ${conanInstallation}
${mavenPackage} | ${mavenInstallation}
${npmPackage} | ${npmInstallation}
${nugetPackage} | ${nugetInstallation}
${pypiPackage} | ${pypiInstallation}
`('renders', ({ packageEntity, selector }) => {
it(`${packageEntity.package_type} instructions`, () => {
createComponent({ packageEntity });
expect(selector()).toExist();
});
it('installation commands has the right props', () => {
createComponent();
expect(findInstallationCommands().exists()).toBe(true);
expect(findInstallationCommands().props('packageEntity')).toEqual(wrapper.vm.packageEntity);
});
it('hides the files table if package type is COMPOSER', () => {
createComponent({ packageEntity: composerPackage });
expect(allFileRows().exists()).toBe(false);
});
it('renders a single file for an npm package as they only contain one file', () => {
......@@ -296,39 +278,4 @@ describe('PackagesApp', () => {
);
});
});
it('package history has the right props', () => {
createComponent({ oneColumnView: true });
expect(findPackageHistory().props('packageEntity')).toEqual(wrapper.vm.packageEntity);
expect(findPackageHistory().props('projectName')).toEqual(wrapper.vm.projectName);
});
it('additional metadata has the right props', () => {
createComponent({ oneColumnView: true });
expect(findAdditionalMetadata().props('packageEntity')).toEqual(wrapper.vm.packageEntity);
});
describe('one column layout feature flag', () => {
describe.each([true, false])('with oneColumnView set to %s', oneColumnView => {
beforeEach(() => {
createComponent({ oneColumnView });
});
it(`is ${oneColumnView} that package history is visible`, () => {
expect(findPackageHistory().exists()).toBe(oneColumnView);
});
it(`is ${oneColumnView} that additional metadata is visible`, () => {
expect(findAdditionalMetadata().exists()).toBe(oneColumnView);
});
it(`is ${!oneColumnView} that old info block is visible`, () => {
expect(findOldPackageInfo().exists()).toBe(!oneColumnView);
});
it(`is ${!oneColumnView} that package activity is visible`, () => {
expect(findPackageActivity().exists()).toBe(!oneColumnView);
});
});
});
});
import { shallowMount } from '@vue/test-utils';
import PackageInformation from '~/packages/details/components/information.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { GlLink } from '@gitlab/ui';
describe('PackageInformation', () => {
let wrapper;
const gitlabLink = 'https://gitlab.com';
const testInformation = [
{
label: 'Information one',
value: 'Information value one',
},
{
label: 'Information two',
value: 'Information value two',
},
{
label: 'Information three',
value: 'Information value three',
},
];
function createComponent(props = {}) {
const propsData = {
information: testInformation,
...props,
};
wrapper = shallowMount(PackageInformation, {
propsData,
});
}
const headingSelector = () => wrapper.find('.card-header > strong');
const copyButton = () => wrapper.findAll(ClipboardButton);
const informationSelector = () => wrapper.findAll('ul.content-list li');
const informationRowText = index =>
informationSelector()
.at(index)
.text();
const informationLink = () => wrapper.find(GlLink);
afterEach(() => {
if (wrapper) wrapper.destroy();
});
it('renders the information block with default heading', () => {
createComponent();
expect(headingSelector()).toExist();
expect(headingSelector().text()).toBe('Package information');
});
it('renders a custom supplied heading', () => {
const heading = 'A custom heading';
createComponent({
heading,
});
expect(headingSelector()).toExist();
expect(headingSelector().text()).toBe(heading);
});
it('renders the supplied information', () => {
createComponent();
expect(informationSelector()).toHaveLength(testInformation.length);
expect(informationRowText(0)).toContain(testInformation[0].value);
expect(informationRowText(1)).toContain(testInformation[1].value);
expect(informationRowText(2)).toContain(testInformation[2].value);
});
it('renders a link when the information is of type link', () => {
createComponent({
information: [
{
label: 'Information link',
value: gitlabLink,
type: 'link',
},
],
});
const link = informationLink();
expect(link.exists()).toBe(true);
expect(link.text()).toBe(gitlabLink);
expect(link.attributes('href')).toBe(gitlabLink);
});
describe('copy button', () => {
it('does not render by default', () => {
createComponent();
expect(copyButton().exists()).toBe(false);
});
it('does render when the prop is set and has correct text set', () => {
createComponent({ showCopy: true });
expect(copyButton()).toHaveLength(testInformation.length);
expect(copyButton().at(0).vm.text).toBe(testInformation[0].value);
expect(copyButton().at(1).vm.text).toBe(testInformation[1].value);
expect(copyButton().at(2).vm.text).toBe(testInformation[2].value);
});
});
});
import { shallowMount } from '@vue/test-utils';
import InstallationCommands from '~/packages/details/components/installation_commands.vue';
import NpmInstallation from '~/packages/details/components/npm_installation.vue';
import MavenInstallation from '~/packages/details/components/maven_installation.vue';
import ConanInstallation from '~/packages/details/components/conan_installation.vue';
import NugetInstallation from '~/packages/details/components/nuget_installation.vue';
import PypiInstallation from '~/packages/details/components/pypi_installation.vue';
import { conanPackage, mavenPackage, npmPackage, nugetPackage, pypiPackage } from '../../mock_data';
describe('InstallationCommands', () => {
let wrapper;
function createComponent(propsData) {
wrapper = shallowMount(InstallationCommands, {
propsData,
});
}
const npmInstallation = () => wrapper.find(NpmInstallation);
const mavenInstallation = () => wrapper.find(MavenInstallation);
const conanInstallation = () => wrapper.find(ConanInstallation);
const nugetInstallation = () => wrapper.find(NugetInstallation);
const pypiInstallation = () => wrapper.find(PypiInstallation);
afterEach(() => {
wrapper.destroy();
});
describe('installation instructions', () => {
describe.each`
packageEntity | selector
${conanPackage} | ${conanInstallation}
${mavenPackage} | ${mavenInstallation}
${npmPackage} | ${npmInstallation}
${nugetPackage} | ${nugetInstallation}
${pypiPackage} | ${pypiInstallation}
`('renders', ({ packageEntity, selector }) => {
it(`${packageEntity.package_type} instructions exist`, () => {
createComponent({ packageEntity });
expect(selector()).toExist();
});
});
});
});
import { formatDate } from '~/lib/utils/datetime_utility';
import { orderBy } from 'lodash';
export const registryUrl = 'foo/registry';
export const mavenMetadata = {
......@@ -44,67 +41,6 @@ export const generateMavenSetupXml = () => `<repositories>
</snapshotRepository>
</distributionManagement>`;
const generateCommonPackageInformation = packageEntity => [
{
label: 'Version',
value: packageEntity.version,
order: 2,
},
{
label: 'Created on',
value: formatDate(packageEntity.created_at),
order: 5,
},
{
label: 'Updated at',
value: formatDate(packageEntity.updated_at),
order: 6,
},
];
export const generateStandardPackageInformation = packageEntity => [
{
label: 'Name',
value: packageEntity.name,
order: 1,
},
...generateCommonPackageInformation(packageEntity),
];
export const generateConanInformation = conanPackage => [
{
label: 'Recipe',
value: conanPackage.recipe,
order: 1,
},
...generateCommonPackageInformation(conanPackage),
];
export const generateNugetInformation = nugetPackage =>
orderBy(
[
...generateCommonPackageInformation(nugetPackage),
{
label: 'Name',
value: nugetPackage.name,
order: 1,
},
{
label: 'Project URL',
value: nugetPackage.nuget_metadatum.project_url,
order: 3,
type: 'link',
},
{
label: 'License URL',
value: nugetPackage.nuget_metadatum.license_url,
order: 4,
type: 'link',
},
],
['order'],
);
export const pypiSetupCommandStr = `[gitlab]
repository = foo
username = __token__
......
import { generateConanRecipe, generatePackageInfo } from '~/packages/details/utils';
import { conanPackage, mavenPackage, npmPackage, nugetPackage } from '../mock_data';
import {
generateConanInformation,
generateStandardPackageInformation,
generateNugetInformation,
} from './mock_data';
import { generateConanRecipe } from '~/packages/details/utils';
import { conanPackage } from '../mock_data';
describe('Package detail utils', () => {
describe('generating information', () => {
describe('conan packages', () => {
const conanInformation = generateConanInformation(conanPackage);
it('correctly generates the conan information', () => {
const info = generatePackageInfo(conanPackage);
expect(info).toEqual(conanInformation);
});
describe('generating recipe', () => {
describe('generateConanRecipe', () => {
it('correctly generates the conan recipe', () => {
const recipe = generateConanRecipe(conanPackage);
......@@ -33,39 +18,7 @@ describe('Package detail utils', () => {
it('recipe returns empty strings for missing metadata', () => {
const recipe = generateConanRecipe({ name: 'foo', version: '0.0.1' });
expect(recipe).toEqual('foo/0.0.1@/');
});
});
});
describe('npm packages', () => {
const npmInformation = generateStandardPackageInformation(npmPackage);
it('correctly generates the npm information', () => {
const info = generatePackageInfo(npmPackage);
expect(info).toEqual(npmInformation);
});
});
describe('maven packages', () => {
const mavenInformation = generateStandardPackageInformation(mavenPackage);
it('correctly generates the maven information', () => {
const info = generatePackageInfo(mavenPackage);
expect(info).toEqual(mavenInformation);
});
});
describe('nuget packages', () => {
const nugetInformation = generateNugetInformation(nugetPackage);
it('correctly generates the nuget information', () => {
const info = generatePackageInfo(nugetPackage);
expect(info).toEqual(nugetInformation);
});
expect(recipe).toBe('foo/0.0.1@/');
});
});
});
......@@ -140,6 +140,18 @@ export const pypiPackage = {
version: '1.0.0',
};
export const composerPackage = {
created_at: '2015-12-10',
id: 5,
name: 'ComposerPackage',
package_files: [],
package_type: 'composer',
project_id: 1,
tags: [],
updated_at: '2015-12-10',
version: '1.0.0',
};
export const mockTags = [
{
name: 'foo-1',
......
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