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>2021-07-23 15:09:05 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-07-23 15:09:05 +0300
commitab8eecd62cc11a31568b25304f5fd31c8b7f437f (patch)
treeb73b765c3cea414112840fd8041c62f886d8ce53 /spec/frontend/packages_and_registries
parent00a889ea7a115ebbda95a071dd630f93b79261e3 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/packages_and_registries')
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap36
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/dependency_row_spec.js.snap34
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/file_sha_spec.js.snap30
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap112
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/npm_installation_spec.js.snap36
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/nuget_installation_spec.js.snap36
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap45
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js118
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js132
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js72
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/dependency_row_spec.js62
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/file_sha_spec.js33
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/installation_title_spec.js58
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js60
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js184
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js123
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js79
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js258
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js116
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js72
20 files changed, 1696 insertions, 0 deletions
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap
new file mode 100644
index 00000000000..a3423e3f4d7
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/conan_installation_spec.js.snap
@@ -0,0 +1,36 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ConanInstallation renders all the messages 1`] = `
+<div>
+ <installation-title-stub
+ options="[object Object]"
+ packagetype="conan"
+ />
+
+ <code-instruction-stub
+ copytext="Copy Conan Command"
+ instruction="foo/command"
+ label="Conan Command"
+ trackingaction="copy_conan_command"
+ trackinglabel="code_instruction"
+ />
+
+ <h3
+ class="gl-font-lg"
+ >
+ Registry setup
+ </h3>
+
+ <code-instruction-stub
+ copytext="Copy Conan Setup Command"
+ instruction="foo/setup"
+ label="Add Conan Remote"
+ trackingaction="copy_conan_setup_command"
+ trackinglabel="code_instruction"
+ />
+
+ <gl-sprintf-stub
+ message="For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}."
+ />
+</div>
+`;
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/dependency_row_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/dependency_row_spec.js.snap
new file mode 100644
index 00000000000..39469bf4fd0
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/dependency_row_spec.js.snap
@@ -0,0 +1,34 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`DependencyRow renders full dependency 1`] = `
+<div
+ class="gl-responsive-table-row"
+>
+ <div
+ class="table-section section-50"
+ >
+ <strong
+ class="gl-text-body"
+ >
+ Test.Dependency
+ </strong>
+
+ <span
+ data-testid="target-framework"
+ >
+ (.NETStandard2.0)
+ </span>
+ </div>
+
+ <div
+ class="table-section section-50 gl-display-flex gl-md-justify-content-end"
+ data-testid="version-pattern"
+ >
+ <span
+ class="gl-text-body"
+ >
+ 2.3.7
+ </span>
+ </div>
+</div>
+`;
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/file_sha_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/file_sha_spec.js.snap
new file mode 100644
index 00000000000..881d441e116
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/file_sha_spec.js.snap
@@ -0,0 +1,30 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FileSha renders 1`] = `
+<div
+ class="gl-display-flex gl-align-items-center gl-font-monospace gl-font-sm gl-word-break-all gl-py-2 gl-border-b-solid gl-border-gray-100 gl-border-b-1"
+>
+ <!---->
+
+ <span>
+ <div
+ class="gl-px-4"
+ >
+
+ bar:
+ foo
+
+ <gl-button-stub
+ aria-label="Copy this value"
+ buttontextclasses=""
+ category="tertiary"
+ data-clipboard-text="foo"
+ icon="copy-to-clipboard"
+ size="small"
+ title="Copy SHA"
+ variant="default"
+ />
+ </div>
+ </span>
+</div>
+`;
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap
new file mode 100644
index 00000000000..8a2793c0010
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap
@@ -0,0 +1,112 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`MavenInstallation groovy renders all the messages 1`] = `
+<div>
+ <installation-title-stub
+ options="[object Object],[object Object],[object Object]"
+ packagetype="maven"
+ />
+
+ <code-instruction-stub
+ class="gl-mb-5"
+ copytext="Copy Gradle Groovy DSL install command"
+ instruction="foo/gradle/groovy/install"
+ label="Gradle Groovy DSL install command"
+ trackingaction="copy_gradle_install_command"
+ trackinglabel="code_instruction"
+ />
+
+ <code-instruction-stub
+ copytext="Copy add Gradle Groovy DSL repository command"
+ instruction="foo/gradle/groovy/add/source"
+ label="Add Gradle Groovy DSL repository command"
+ multiline="true"
+ trackingaction="copy_gradle_add_to_source_command"
+ trackinglabel="code_instruction"
+ />
+</div>
+`;
+
+exports[`MavenInstallation kotlin renders all the messages 1`] = `
+<div>
+ <installation-title-stub
+ options="[object Object],[object Object],[object Object]"
+ packagetype="maven"
+ />
+
+ <code-instruction-stub
+ class="gl-mb-5"
+ copytext="Copy Gradle Kotlin DSL install command"
+ instruction="foo/gradle/kotlin/install"
+ label="Gradle Kotlin DSL install command"
+ trackingaction="copy_kotlin_install_command"
+ trackinglabel="code_instruction"
+ />
+
+ <code-instruction-stub
+ copytext="Copy add Gradle Kotlin DSL repository command"
+ instruction="foo/gradle/kotlin/add/source"
+ label="Add Gradle Kotlin DSL repository command"
+ multiline="true"
+ trackingaction="copy_kotlin_add_to_source_command"
+ trackinglabel="code_instruction"
+ />
+</div>
+`;
+
+exports[`MavenInstallation maven renders all the messages 1`] = `
+<div>
+ <installation-title-stub
+ options="[object Object],[object Object],[object Object]"
+ packagetype="maven"
+ />
+
+ <p>
+ <gl-sprintf-stub
+ message="Copy and paste this inside your %{codeStart}pom.xml%{codeEnd} %{codeStart}dependencies%{codeEnd} block."
+ />
+ </p>
+
+ <code-instruction-stub
+ copytext="Copy Maven XML"
+ instruction="foo/xml"
+ label=""
+ multiline="true"
+ trackingaction="copy_maven_xml"
+ trackinglabel="code_instruction"
+ />
+
+ <code-instruction-stub
+ copytext="Copy Maven command"
+ instruction="foo/command"
+ label="Maven Command"
+ trackingaction="copy_maven_command"
+ trackinglabel="code_instruction"
+ />
+
+ <h3
+ class="gl-font-lg"
+ >
+ Registry setup
+ </h3>
+
+ <p>
+ <gl-sprintf-stub
+ message="If you haven't already done so, you will need to add the below to your %{codeStart}pom.xml%{codeEnd} file."
+ />
+ </p>
+
+ <code-instruction-stub
+ copytext="Copy Maven registry XML"
+ instruction="foo/setup"
+ label=""
+ multiline="true"
+ trackingaction="copy_maven_setup_xml"
+ trackinglabel="code_instruction"
+ />
+
+ <gl-sprintf-stub
+ message="For more information on the Maven registry, %{linkStart}see the documentation%{linkEnd}."
+ />
+</div>
+`;
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/npm_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/npm_installation_spec.js.snap
new file mode 100644
index 00000000000..015c7b94dde
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/npm_installation_spec.js.snap
@@ -0,0 +1,36 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`NpmInstallation renders all the messages 1`] = `
+<div>
+ <installation-title-stub
+ options="[object Object],[object Object]"
+ packagetype="npm"
+ />
+
+ <code-instruction-stub
+ copytext="Copy npm command"
+ instruction="npm i @Test/package"
+ label=""
+ trackingaction="copy_npm_install_command"
+ trackinglabel="code_instruction"
+ />
+
+ <h3
+ class="gl-font-lg"
+ >
+ Registry setup
+ </h3>
+
+ <code-instruction-stub
+ copytext="Copy npm setup command"
+ instruction="echo @Test:registry=undefined/ >> .npmrc"
+ label=""
+ trackingaction="copy_npm_setup_command"
+ trackinglabel="code_instruction"
+ />
+
+ <gl-sprintf-stub
+ message="You may also need to setup authentication using an auth token. %{linkStart}See the documentation%{linkEnd} to find out more."
+ />
+</div>
+`;
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/nuget_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/nuget_installation_spec.js.snap
new file mode 100644
index 00000000000..04532743952
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/nuget_installation_spec.js.snap
@@ -0,0 +1,36 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`NugetInstallation renders all the messages 1`] = `
+<div>
+ <installation-title-stub
+ options="[object Object]"
+ packagetype="nuget"
+ />
+
+ <code-instruction-stub
+ copytext="Copy NuGet Command"
+ instruction="foo/command"
+ label="NuGet Command"
+ trackingaction="copy_nuget_install_command"
+ trackinglabel="code_instruction"
+ />
+
+ <h3
+ class="gl-font-lg"
+ >
+ Registry setup
+ </h3>
+
+ <code-instruction-stub
+ copytext="Copy NuGet Setup Command"
+ instruction="foo/setup"
+ label="Add NuGet Source"
+ trackingaction="copy_nuget_setup_command"
+ trackinglabel="code_instruction"
+ />
+
+ <gl-sprintf-stub
+ message="For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}."
+ />
+</div>
+`;
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap
new file mode 100644
index 00000000000..d5bb825d8d1
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap
@@ -0,0 +1,45 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PypiInstallation renders all the messages 1`] = `
+<div>
+ <installation-title-stub
+ options="[object Object]"
+ packagetype="pypi"
+ />
+
+ <code-instruction-stub
+ copytext="Copy Pip command"
+ data-testid="pip-command"
+ instruction="pip install"
+ label="Pip Command"
+ trackingaction="copy_pip_install_command"
+ trackinglabel="code_instruction"
+ />
+
+ <h3
+ class="gl-font-lg"
+ >
+ Registry setup
+ </h3>
+
+ <p>
+ <gl-sprintf-stub
+ message="If you haven't already done so, you will need to add the below to your %{codeStart}.pypirc%{codeEnd} file."
+ />
+ </p>
+
+ <code-instruction-stub
+ copytext="Copy .pypirc content"
+ data-testid="pypi-setup-content"
+ instruction="python setup"
+ label=""
+ multiline="true"
+ trackingaction="copy_pypi_setup_command"
+ trackinglabel="code_instruction"
+ />
+
+ <gl-sprintf-stub
+ message="For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}."
+ />
+</div>
+`;
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js
new file mode 100644
index 00000000000..2b3acbf99f3
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js
@@ -0,0 +1,118 @@
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { mavenPackage, conanPackage, nugetPackage, npmPackage } from 'jest/packages/mock_data';
+import component from '~/packages_and_registries/package_registry/components/details/additional_metadata.vue';
+import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
+
+describe('Package Additional Metadata', () => {
+ let wrapper;
+ const defaultProps = {
+ packageEntity: { ...mavenPackage },
+ };
+
+ const mountComponent = (props) => {
+ wrapper = shallowMount(component, {
+ propsData: { ...defaultProps, ...props },
+ stubs: {
+ DetailsRow,
+ GlSprintf,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findTitle = () => wrapper.find('[data-testid="title"]');
+ const findMainArea = () => wrapper.find('[data-testid="main"]');
+ const findNugetSource = () => wrapper.find('[data-testid="nuget-source"]');
+ const findNugetLicense = () => wrapper.find('[data-testid="nuget-license"]');
+ const findConanRecipe = () => wrapper.find('[data-testid="conan-recipe"]');
+ const findMavenApp = () => wrapper.find('[data-testid="maven-app"]');
+ const findMavenGroup = () => wrapper.find('[data-testid="maven-group"]');
+ const findElementLink = (container) => container.find(GlLink);
+
+ it('has the correct title', () => {
+ mountComponent();
+
+ const title = findTitle();
+
+ expect(title.exists()).toBe(true);
+ expect(title.text()).toBe('Additional Metadata');
+ });
+
+ describe.each`
+ packageEntity | visible | metadata
+ ${mavenPackage} | ${true} | ${'maven_metadatum'}
+ ${conanPackage} | ${true} | ${'conan_metadatum'}
+ ${nugetPackage} | ${true} | ${'nuget_metadatum'}
+ ${npmPackage} | ${false} | ${null}
+ `('Component visibility', ({ packageEntity, visible, metadata }) => {
+ it(`Is ${visible} that the component markup is visible when the package is ${packageEntity.package_type}`, () => {
+ mountComponent({ packageEntity });
+
+ expect(findTitle().exists()).toBe(visible);
+ expect(findMainArea().exists()).toBe(visible);
+ });
+
+ it(`The component is hidden if ${metadata} is missing`, () => {
+ mountComponent({ packageEntity: { ...packageEntity, [metadata]: null } });
+
+ expect(findTitle().exists()).toBe(false);
+ expect(findMainArea().exists()).toBe(false);
+ });
+ });
+
+ describe('nuget metadata', () => {
+ beforeEach(() => {
+ mountComponent({ packageEntity: nugetPackage });
+ });
+
+ it.each`
+ name | finderFunction | text | link | icon
+ ${'source'} | ${findNugetSource} | ${'Source project located at project-foo-url'} | ${'project_url'} | ${'project'}
+ ${'license'} | ${findNugetLicense} | ${'License information located at license-foo-url'} | ${'license_url'} | ${'license'}
+ `('$name element', ({ finderFunction, text, link, icon }) => {
+ const element = finderFunction();
+ expect(element.exists()).toBe(true);
+ expect(element.text()).toBe(text);
+ expect(element.props('icon')).toBe(icon);
+ expect(findElementLink(element).attributes('href')).toBe(nugetPackage.nuget_metadatum[link]);
+ });
+ });
+
+ describe('conan metadata', () => {
+ beforeEach(() => {
+ mountComponent({ packageEntity: conanPackage });
+ });
+
+ it.each`
+ name | finderFunction | text | icon
+ ${'recipe'} | ${findConanRecipe} | ${'Recipe: conan-package/1.0.0@conan+conan-package/stable'} | ${'information-o'}
+ `('$name element', ({ finderFunction, text, icon }) => {
+ const element = finderFunction();
+ expect(element.exists()).toBe(true);
+ expect(element.text()).toBe(text);
+ expect(element.props('icon')).toBe(icon);
+ });
+ });
+
+ describe('maven metadata', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it.each`
+ name | finderFunction | text | icon
+ ${'app'} | ${findMavenApp} | ${'App name: test-app'} | ${'information-o'}
+ ${'group'} | ${findMavenGroup} | ${'App group: com.test.app'} | ${'information-o'}
+ `('$name element', ({ finderFunction, text, icon }) => {
+ const element = finderFunction();
+ expect(element.exists()).toBe(true);
+ expect(element.text()).toBe(text);
+ expect(element.props('icon')).toBe(icon);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js
new file mode 100644
index 00000000000..0afe69dd467
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js
@@ -0,0 +1,132 @@
+import { GlSprintf, GlLink } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import { registryUrl as composerHelpPath } from 'jest/packages/details/mock_data';
+import { composerPackage as packageEntity } from 'jest/packages/mock_data';
+import { TrackingActions } from '~/packages/details/constants';
+import ComposerInstallation from '~/packages_and_registries/package_registry/components/details/composer_installation.vue';
+import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('ComposerInstallation', () => {
+ let wrapper;
+ let store;
+
+ const composerRegistryIncludeStr = 'foo/registry';
+ const composerPackageIncludeStr = 'foo/package';
+
+ const createStore = (groupExists = true) => {
+ store = new Vuex.Store({
+ state: { packageEntity, composerHelpPath },
+ getters: {
+ composerRegistryInclude: () => composerRegistryIncludeStr,
+ composerPackageInclude: () => composerPackageIncludeStr,
+ groupExists: () => groupExists,
+ },
+ });
+ };
+
+ const findRootNode = () => wrapper.find('[data-testid="root-node"]');
+ const findRegistryInclude = () => wrapper.find('[data-testid="registry-include"]');
+ const findPackageInclude = () => wrapper.find('[data-testid="package-include"]');
+ const findHelpText = () => wrapper.find('[data-testid="help-text"]');
+ const findHelpLink = () => wrapper.find(GlLink);
+ const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
+
+ function createComponent() {
+ wrapper = shallowMount(ComposerInstallation, {
+ localVue,
+ store,
+ stubs: {
+ GlSprintf,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('install command switch', () => {
+ it('has the installation title component', () => {
+ createStore();
+ createComponent();
+
+ expect(findInstallationTitle().exists()).toBe(true);
+ expect(findInstallationTitle().props()).toMatchObject({
+ packageType: 'composer',
+ options: [{ value: 'composer', label: 'Show Composer commands' }],
+ });
+ });
+ });
+
+ describe('registry include command', () => {
+ beforeEach(() => {
+ createStore();
+ createComponent();
+ });
+
+ it('uses code_instructions', () => {
+ const registryIncludeCommand = findRegistryInclude();
+ expect(registryIncludeCommand.exists()).toBe(true);
+ expect(registryIncludeCommand.props()).toMatchObject({
+ instruction: composerRegistryIncludeStr,
+ copyText: 'Copy registry include',
+ trackingAction: TrackingActions.COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND,
+ });
+ });
+
+ it('has the correct title', () => {
+ expect(findRegistryInclude().props('label')).toBe('Add composer registry');
+ });
+ });
+
+ describe('package include command', () => {
+ beforeEach(() => {
+ createStore();
+ createComponent();
+ });
+
+ it('uses code_instructions', () => {
+ const registryIncludeCommand = findPackageInclude();
+ expect(registryIncludeCommand.exists()).toBe(true);
+ expect(registryIncludeCommand.props()).toMatchObject({
+ instruction: composerPackageIncludeStr,
+ copyText: 'Copy require package include',
+ trackingAction: TrackingActions.COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND,
+ });
+ });
+
+ it('has the correct title', () => {
+ expect(findPackageInclude().props('label')).toBe('Install package version');
+ });
+
+ it('has the correct help text', () => {
+ expect(findHelpText().text()).toBe(
+ 'For more information on Composer packages in GitLab, see the documentation.',
+ );
+ expect(findHelpLink().attributes()).toMatchObject({
+ href: composerHelpPath,
+ target: '_blank',
+ });
+ });
+ });
+
+ describe('root node', () => {
+ it('is normally rendered', () => {
+ createStore();
+ createComponent();
+
+ expect(findRootNode().exists()).toBe(true);
+ });
+
+ it('is not rendered when the group does not exist', () => {
+ createStore(false);
+ createComponent();
+
+ expect(findRootNode().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js
new file mode 100644
index 00000000000..08dd21c4496
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js
@@ -0,0 +1,72 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import { registryUrl as conanPath } from 'jest/packages/details/mock_data';
+import { conanPackage as packageEntity } from 'jest/packages/mock_data';
+import ConanInstallation from '~/packages_and_registries/package_registry/components/details/conan_installation.vue';
+import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
+import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('ConanInstallation', () => {
+ let wrapper;
+
+ const conanInstallationCommandStr = 'foo/command';
+ const conanSetupCommandStr = 'foo/setup';
+
+ const store = new Vuex.Store({
+ state: {
+ packageEntity,
+ conanPath,
+ },
+ getters: {
+ conanInstallationCommand: () => conanInstallationCommandStr,
+ conanSetupCommand: () => conanSetupCommandStr,
+ },
+ });
+
+ const findCodeInstructions = () => wrapper.findAll(CodeInstructions);
+ const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
+
+ function createComponent() {
+ wrapper = shallowMount(ConanInstallation, {
+ localVue,
+ store,
+ });
+ }
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders all the messages', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('install command switch', () => {
+ it('has the installation title component', () => {
+ expect(findInstallationTitle().exists()).toBe(true);
+ expect(findInstallationTitle().props()).toMatchObject({
+ packageType: 'conan',
+ options: [{ value: 'conan', label: 'Show Conan commands' }],
+ });
+ });
+ });
+
+ describe('installation commands', () => {
+ it('renders the correct command', () => {
+ expect(findCodeInstructions().at(0).props('instruction')).toBe(conanInstallationCommandStr);
+ });
+ });
+
+ describe('setup commands', () => {
+ it('renders the correct command', () => {
+ expect(findCodeInstructions().at(1).props('instruction')).toBe(conanSetupCommandStr);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/dependency_row_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/dependency_row_spec.js
new file mode 100644
index 00000000000..de561bdf8c9
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/dependency_row_spec.js
@@ -0,0 +1,62 @@
+import { shallowMount } from '@vue/test-utils';
+import { dependencyLinks } from 'jest/packages/mock_data';
+import DependencyRow from '~/packages_and_registries/package_registry/components/details/dependency_row.vue';
+
+describe('DependencyRow', () => {
+ let wrapper;
+
+ const { withoutFramework, withoutVersion, fullLink } = dependencyLinks;
+
+ function createComponent({ dependencyLink = fullLink } = {}) {
+ wrapper = shallowMount(DependencyRow, {
+ propsData: {
+ dependency: dependencyLink,
+ },
+ });
+ }
+
+ const dependencyVersion = () => wrapper.find('[data-testid="version-pattern"]');
+ const dependencyFramework = () => wrapper.find('[data-testid="target-framework"]');
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('renders', () => {
+ it('full dependency', () => {
+ createComponent();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+ });
+
+ describe('version', () => {
+ it('does not render any version information when not supplied', () => {
+ createComponent({ dependencyLink: withoutVersion });
+
+ expect(dependencyVersion().exists()).toBe(false);
+ });
+
+ it('does render version info when it exists', () => {
+ createComponent();
+
+ expect(dependencyVersion().exists()).toBe(true);
+ expect(dependencyVersion().text()).toBe(fullLink.version_pattern);
+ });
+ });
+
+ describe('target framework', () => {
+ it('does not render any framework information when not supplied', () => {
+ createComponent({ dependencyLink: withoutFramework });
+
+ expect(dependencyFramework().exists()).toBe(false);
+ });
+
+ it('does render framework info when it exists', () => {
+ createComponent();
+
+ expect(dependencyFramework().exists()).toBe(true);
+ expect(dependencyFramework().text()).toBe(`(${fullLink.target_framework})`);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/file_sha_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/file_sha_spec.js
new file mode 100644
index 00000000000..ebfbbe5b864
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/file_sha_spec.js
@@ -0,0 +1,33 @@
+import { shallowMount } from '@vue/test-utils';
+
+import FileSha from '~/packages_and_registries/package_registry/components/details/file_sha.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
+
+describe('FileSha', () => {
+ let wrapper;
+
+ const defaultProps = { sha: 'foo', title: 'bar' };
+
+ function createComponent() {
+ wrapper = shallowMount(FileSha, {
+ propsData: {
+ ...defaultProps,
+ },
+ stubs: {
+ ClipboardButton,
+ DetailsRow,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders', () => {
+ createComponent();
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/installation_title_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/installation_title_spec.js
new file mode 100644
index 00000000000..5fe795f768e
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/installation_title_spec.js
@@ -0,0 +1,58 @@
+import { shallowMount } from '@vue/test-utils';
+
+import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
+import PersistedDropdownSelection from '~/vue_shared/components/registry/persisted_dropdown_selection.vue';
+
+describe('InstallationTitle', () => {
+ let wrapper;
+
+ const defaultProps = { packageType: 'foo', options: [{ value: 'foo', label: 'bar' }] };
+
+ const findPersistedDropdownSelection = () => wrapper.findComponent(PersistedDropdownSelection);
+ const findTitle = () => wrapper.find('h3');
+
+ function createComponent({ props = {} } = {}) {
+ wrapper = shallowMount(InstallationTitle, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('has a title', () => {
+ createComponent();
+
+ expect(findTitle().exists()).toBe(true);
+ expect(findTitle().text()).toBe('Installation');
+ });
+
+ describe('persisted dropdown selection', () => {
+ it('exists', () => {
+ createComponent();
+
+ expect(findPersistedDropdownSelection().exists()).toBe(true);
+ });
+
+ it('has the correct props', () => {
+ createComponent();
+
+ expect(findPersistedDropdownSelection().props()).toMatchObject({
+ storageKey: 'package_foo_installation_instructions',
+ options: defaultProps.options,
+ });
+ });
+
+ it('on change event emits a change event', () => {
+ createComponent();
+
+ findPersistedDropdownSelection().vm.$emit('change', 'baz');
+
+ expect(wrapper.emitted('change')).toEqual([['baz']]);
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js
new file mode 100644
index 00000000000..b5b795ccc5a
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js
@@ -0,0 +1,60 @@
+import { shallowMount } from '@vue/test-utils';
+import {
+ conanPackage,
+ mavenPackage,
+ npmPackage,
+ nugetPackage,
+ pypiPackage,
+ composerPackage,
+ terraformModule,
+} from 'jest/packages/mock_data';
+import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/components/terraform_installation.vue';
+import ComposerInstallation from '~/packages_and_registries/package_registry/components/details/composer_installation.vue';
+import ConanInstallation from '~/packages_and_registries/package_registry/components/details/conan_installation.vue';
+import InstallationCommands from '~/packages_and_registries/package_registry/components/details/installation_commands.vue';
+
+import MavenInstallation from '~/packages_and_registries/package_registry/components/details/maven_installation.vue';
+import NpmInstallation from '~/packages_and_registries/package_registry/components/details/npm_installation.vue';
+import NugetInstallation from '~/packages_and_registries/package_registry/components/details/nuget_installation.vue';
+import PypiInstallation from '~/packages_and_registries/package_registry/components/details/pypi_installation.vue';
+
+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);
+ const composerInstallation = () => wrapper.find(ComposerInstallation);
+ const terraformInstallation = () => wrapper.findComponent(TerraformInstallation);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('installation instructions', () => {
+ describe.each`
+ packageEntity | selector
+ ${conanPackage} | ${conanInstallation}
+ ${mavenPackage} | ${mavenInstallation}
+ ${npmPackage} | ${npmInstallation}
+ ${nugetPackage} | ${nugetInstallation}
+ ${pypiPackage} | ${pypiInstallation}
+ ${composerPackage} | ${composerInstallation}
+ ${terraformModule} | ${terraformInstallation}
+ `('renders', ({ packageEntity, selector }) => {
+ it(`${packageEntity.package_type} instructions exist`, () => {
+ createComponent({ packageEntity });
+
+ expect(selector()).toExist();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js
new file mode 100644
index 00000000000..d2ee0ea8bad
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js
@@ -0,0 +1,184 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import Vuex from 'vuex';
+import { registryUrl as mavenPath } from 'jest/packages/details/mock_data';
+import { mavenPackage as packageEntity } from 'jest/packages/mock_data';
+import { TrackingActions } from '~/packages/details/constants';
+import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
+import MavenInstallation from '~/packages_and_registries/package_registry/components/details/maven_installation.vue';
+import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('MavenInstallation', () => {
+ let wrapper;
+
+ const xmlCodeBlock = 'foo/xml';
+ const mavenCommandStr = 'foo/command';
+ const mavenSetupXml = 'foo/setup';
+ const gradleGroovyInstallCommandText = 'foo/gradle/groovy/install';
+ const gradleGroovyAddSourceCommandText = 'foo/gradle/groovy/add/source';
+ const gradleKotlinInstallCommandText = 'foo/gradle/kotlin/install';
+ const gradleKotlinAddSourceCommandText = 'foo/gradle/kotlin/add/source';
+
+ const store = new Vuex.Store({
+ state: {
+ packageEntity,
+ mavenPath,
+ },
+ getters: {
+ mavenInstallationXml: () => xmlCodeBlock,
+ mavenInstallationCommand: () => mavenCommandStr,
+ mavenSetupXml: () => mavenSetupXml,
+ gradleGroovyInstalCommand: () => gradleGroovyInstallCommandText,
+ gradleGroovyAddSourceCommand: () => gradleGroovyAddSourceCommandText,
+ gradleKotlinInstalCommand: () => gradleKotlinInstallCommandText,
+ gradleKotlinAddSourceCommand: () => gradleKotlinAddSourceCommandText,
+ },
+ });
+
+ const findCodeInstructions = () => wrapper.findAll(CodeInstructions);
+ const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
+
+ function createComponent({ data = {} } = {}) {
+ wrapper = shallowMount(MavenInstallation, {
+ localVue,
+ store,
+ data() {
+ return data;
+ },
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('install command switch', () => {
+ it('has the installation title component', () => {
+ createComponent();
+
+ expect(findInstallationTitle().exists()).toBe(true);
+ expect(findInstallationTitle().props()).toMatchObject({
+ packageType: 'maven',
+ options: [
+ { value: 'maven', label: 'Maven XML' },
+ { value: 'groovy', label: 'Gradle Groovy DSL' },
+ { value: 'kotlin', label: 'Gradle Kotlin DSL' },
+ ],
+ });
+ });
+
+ it('on change event updates the instructions to show', async () => {
+ createComponent();
+
+ expect(findCodeInstructions().at(0).props('instruction')).toBe(xmlCodeBlock);
+ findInstallationTitle().vm.$emit('change', 'groovy');
+
+ await nextTick();
+
+ expect(findCodeInstructions().at(0).props('instruction')).toBe(
+ gradleGroovyInstallCommandText,
+ );
+ });
+ });
+
+ describe('maven', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders all the messages', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('installation commands', () => {
+ it('renders the correct xml block', () => {
+ expect(findCodeInstructions().at(0).props()).toMatchObject({
+ instruction: xmlCodeBlock,
+ multiline: true,
+ trackingAction: TrackingActions.COPY_MAVEN_XML,
+ });
+ });
+
+ it('renders the correct maven command', () => {
+ expect(findCodeInstructions().at(1).props()).toMatchObject({
+ instruction: mavenCommandStr,
+ multiline: false,
+ trackingAction: TrackingActions.COPY_MAVEN_COMMAND,
+ });
+ });
+ });
+
+ describe('setup commands', () => {
+ it('renders the correct xml block', () => {
+ expect(findCodeInstructions().at(2).props()).toMatchObject({
+ instruction: mavenSetupXml,
+ multiline: true,
+ trackingAction: TrackingActions.COPY_MAVEN_SETUP,
+ });
+ });
+ });
+ });
+
+ describe('groovy', () => {
+ beforeEach(() => {
+ createComponent({ data: { instructionType: 'groovy' } });
+ });
+
+ it('renders all the messages', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('installation commands', () => {
+ it('renders the gradle install command', () => {
+ expect(findCodeInstructions().at(0).props()).toMatchObject({
+ instruction: gradleGroovyInstallCommandText,
+ multiline: false,
+ trackingAction: TrackingActions.COPY_GRADLE_INSTALL_COMMAND,
+ });
+ });
+ });
+
+ describe('setup commands', () => {
+ it('renders the correct gradle command', () => {
+ expect(findCodeInstructions().at(1).props()).toMatchObject({
+ instruction: gradleGroovyAddSourceCommandText,
+ multiline: true,
+ trackingAction: TrackingActions.COPY_GRADLE_ADD_TO_SOURCE_COMMAND,
+ });
+ });
+ });
+ });
+
+ describe('kotlin', () => {
+ beforeEach(() => {
+ createComponent({ data: { instructionType: 'kotlin' } });
+ });
+
+ it('renders all the messages', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('installation commands', () => {
+ it('renders the gradle install command', () => {
+ expect(findCodeInstructions().at(0).props()).toMatchObject({
+ instruction: gradleKotlinInstallCommandText,
+ multiline: false,
+ trackingAction: TrackingActions.COPY_KOTLIN_INSTALL_COMMAND,
+ });
+ });
+ });
+
+ describe('setup commands', () => {
+ it('renders the correct gradle command', () => {
+ expect(findCodeInstructions().at(1).props()).toMatchObject({
+ instruction: gradleKotlinAddSourceCommandText,
+ multiline: true,
+ trackingAction: TrackingActions.COPY_KOTLIN_ADD_TO_SOURCE_COMMAND,
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js
new file mode 100644
index 00000000000..e4b41133fd5
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js
@@ -0,0 +1,123 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import Vuex from 'vuex';
+import { registryUrl as nugetPath } from 'jest/packages/details/mock_data';
+import { npmPackage as packageEntity } from 'jest/packages/mock_data';
+import { TrackingActions } from '~/packages/details/constants';
+import { npmInstallationCommand, npmSetupCommand } from '~/packages/details/store/getters';
+import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
+import NpmInstallation from '~/packages_and_registries/package_registry/components/details/npm_installation.vue';
+import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('NpmInstallation', () => {
+ let wrapper;
+
+ const npmInstallationCommandLabel = 'npm i @Test/package';
+ const yarnInstallationCommandLabel = 'yarn add @Test/package';
+
+ const findCodeInstructions = () => wrapper.findAll(CodeInstructions);
+ const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
+
+ function createComponent({ data = {} } = {}) {
+ const store = new Vuex.Store({
+ state: {
+ packageEntity,
+ nugetPath,
+ },
+ getters: {
+ npmInstallationCommand,
+ npmSetupCommand,
+ },
+ });
+
+ wrapper = shallowMount(NpmInstallation, {
+ localVue,
+ store,
+ data() {
+ return data;
+ },
+ });
+ }
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders all the messages', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('install command switch', () => {
+ it('has the installation title component', () => {
+ expect(findInstallationTitle().exists()).toBe(true);
+ expect(findInstallationTitle().props()).toMatchObject({
+ packageType: 'npm',
+ options: [
+ { value: 'npm', label: 'Show NPM commands' },
+ { value: 'yarn', label: 'Show Yarn commands' },
+ ],
+ });
+ });
+
+ it('on change event updates the instructions to show', async () => {
+ createComponent();
+
+ expect(findCodeInstructions().at(0).props('instruction')).toBe(npmInstallationCommandLabel);
+ findInstallationTitle().vm.$emit('change', 'yarn');
+
+ await nextTick();
+
+ expect(findCodeInstructions().at(0).props('instruction')).toBe(yarnInstallationCommandLabel);
+ });
+ });
+
+ describe('npm', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+ it('renders the correct installation command', () => {
+ expect(findCodeInstructions().at(0).props()).toMatchObject({
+ instruction: npmInstallationCommandLabel,
+ multiline: false,
+ trackingAction: TrackingActions.COPY_NPM_INSTALL_COMMAND,
+ });
+ });
+
+ it('renders the correct setup command', () => {
+ expect(findCodeInstructions().at(1).props()).toMatchObject({
+ instruction: 'echo @Test:registry=undefined/ >> .npmrc',
+ multiline: false,
+ trackingAction: TrackingActions.COPY_NPM_SETUP_COMMAND,
+ });
+ });
+ });
+
+ describe('yarn', () => {
+ beforeEach(() => {
+ createComponent({ data: { instructionType: 'yarn' } });
+ });
+
+ it('renders the correct setup command', () => {
+ expect(findCodeInstructions().at(0).props()).toMatchObject({
+ instruction: yarnInstallationCommandLabel,
+ multiline: false,
+ trackingAction: TrackingActions.COPY_YARN_INSTALL_COMMAND,
+ });
+ });
+
+ it('renders the correct registry command', () => {
+ expect(findCodeInstructions().at(1).props()).toMatchObject({
+ instruction: 'echo \\"@Test:registry\\" \\"undefined/\\" >> .yarnrc',
+ multiline: false,
+ trackingAction: TrackingActions.COPY_YARN_SETUP_COMMAND,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js
new file mode 100644
index 00000000000..c642714f7cd
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js
@@ -0,0 +1,79 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import { registryUrl as nugetPath } from 'jest/packages/details/mock_data';
+import { nugetPackage as packageEntity } from 'jest/packages/mock_data';
+import { TrackingActions } from '~/packages/details/constants';
+import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
+import NugetInstallation from '~/packages_and_registries/package_registry/components/details/nuget_installation.vue';
+import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('NugetInstallation', () => {
+ let wrapper;
+
+ const nugetInstallationCommandStr = 'foo/command';
+ const nugetSetupCommandStr = 'foo/setup';
+
+ const store = new Vuex.Store({
+ state: {
+ packageEntity,
+ nugetPath,
+ },
+ getters: {
+ nugetInstallationCommand: () => nugetInstallationCommandStr,
+ nugetSetupCommand: () => nugetSetupCommandStr,
+ },
+ });
+
+ const findCodeInstructions = () => wrapper.findAll(CodeInstructions);
+ const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
+
+ function createComponent() {
+ wrapper = shallowMount(NugetInstallation, {
+ localVue,
+ store,
+ });
+ }
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders all the messages', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('install command switch', () => {
+ it('has the installation title component', () => {
+ expect(findInstallationTitle().exists()).toBe(true);
+ expect(findInstallationTitle().props()).toMatchObject({
+ packageType: 'nuget',
+ options: [{ value: 'nuget', label: 'Show Nuget commands' }],
+ });
+ });
+ });
+
+ describe('installation commands', () => {
+ it('renders the correct command', () => {
+ expect(findCodeInstructions().at(0).props()).toMatchObject({
+ instruction: nugetInstallationCommandStr,
+ trackingAction: TrackingActions.COPY_NUGET_INSTALL_COMMAND,
+ });
+ });
+ });
+
+ describe('setup commands', () => {
+ it('renders the correct command', () => {
+ expect(findCodeInstructions().at(1).props()).toMatchObject({
+ instruction: nugetSetupCommandStr,
+ trackingAction: TrackingActions.COPY_NUGET_SETUP_COMMAND,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js
new file mode 100644
index 00000000000..75fd5b163fc
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js
@@ -0,0 +1,258 @@
+import { GlDropdown, GlButton } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import stubChildren from 'helpers/stub_children';
+import { npmFiles, mavenFiles } from 'jest/packages/mock_data';
+import component from '~/packages_and_registries/package_registry/components/details/package_files.vue';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
+describe('Package Files', () => {
+ let wrapper;
+
+ const findAllRows = () => wrapper.findAll('[data-testid="file-row"');
+ const findFirstRow = () => findAllRows().at(0);
+ const findSecondRow = () => findAllRows().at(1);
+ const findFirstRowDownloadLink = () => findFirstRow().find('[data-testid="download-link"]');
+ const findFirstRowCommitLink = () => findFirstRow().find('[data-testid="commit-link"]');
+ const findSecondRowCommitLink = () => findSecondRow().find('[data-testid="commit-link"]');
+ const findFirstRowFileIcon = () => findFirstRow().find(FileIcon);
+ const findFirstRowCreatedAt = () => findFirstRow().find(TimeAgoTooltip);
+ const findFirstActionMenu = () => findFirstRow().findComponent(GlDropdown);
+ const findActionMenuDelete = () => findFirstActionMenu().find('[data-testid="delete-file"]');
+ const findFirstToggleDetailsButton = () => findFirstRow().findComponent(GlButton);
+ const findFirstRowShaComponent = (id) => wrapper.find(`[data-testid="${id}"]`);
+
+ const createComponent = ({ packageFiles = npmFiles, canDelete = true } = {}) => {
+ wrapper = mount(component, {
+ propsData: {
+ packageFiles,
+ canDelete,
+ },
+ stubs: {
+ ...stubChildren(component),
+ GlTable: false,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('rows', () => {
+ it('renders a single file for an npm package', () => {
+ createComponent();
+
+ expect(findAllRows()).toHaveLength(1);
+ });
+
+ it('renders multiple files for a package that contains more than one file', () => {
+ createComponent({ packageFiles: mavenFiles });
+
+ expect(findAllRows()).toHaveLength(2);
+ });
+ });
+
+ describe('link', () => {
+ it('exists', () => {
+ createComponent();
+
+ expect(findFirstRowDownloadLink().exists()).toBe(true);
+ });
+
+ it('has the correct attrs bound', () => {
+ createComponent();
+
+ expect(findFirstRowDownloadLink().attributes('href')).toBe(npmFiles[0].download_path);
+ });
+
+ it('emits "download-file" event on click', () => {
+ createComponent();
+
+ findFirstRowDownloadLink().vm.$emit('click');
+
+ expect(wrapper.emitted('download-file')).toEqual([[]]);
+ });
+ });
+
+ describe('file-icon', () => {
+ it('exists', () => {
+ createComponent();
+
+ expect(findFirstRowFileIcon().exists()).toBe(true);
+ });
+
+ it('has the correct props bound', () => {
+ createComponent();
+
+ expect(findFirstRowFileIcon().props('fileName')).toBe(npmFiles[0].file_name);
+ });
+ });
+
+ describe('time-ago tooltip', () => {
+ it('exists', () => {
+ createComponent();
+
+ expect(findFirstRowCreatedAt().exists()).toBe(true);
+ });
+
+ it('has the correct props bound', () => {
+ createComponent();
+
+ expect(findFirstRowCreatedAt().props('time')).toBe(npmFiles[0].created_at);
+ });
+ });
+
+ describe('commit', () => {
+ describe('when package file has a pipeline associated', () => {
+ it('exists', () => {
+ createComponent();
+
+ expect(findFirstRowCommitLink().exists()).toBe(true);
+ });
+
+ it('the link points to the commit url', () => {
+ createComponent();
+
+ expect(findFirstRowCommitLink().attributes('href')).toBe(
+ npmFiles[0].pipelines[0].project.commit_url,
+ );
+ });
+
+ it('the text is git_commit_message', () => {
+ createComponent();
+
+ expect(findFirstRowCommitLink().text()).toBe(npmFiles[0].pipelines[0].git_commit_message);
+ });
+ });
+ describe('when package file has no pipeline associated', () => {
+ it('does not exist', () => {
+ createComponent({ packageFiles: mavenFiles });
+
+ expect(findFirstRowCommitLink().exists()).toBe(false);
+ });
+ });
+
+ describe('when only one file lacks an associated pipeline', () => {
+ it('renders the commit when it exists and not otherwise', () => {
+ createComponent({ packageFiles: [npmFiles[0], mavenFiles[0]] });
+
+ expect(findFirstRowCommitLink().exists()).toBe(true);
+ expect(findSecondRowCommitLink().exists()).toBe(false);
+ });
+ });
+
+ describe('action menu', () => {
+ describe('when the user can delete', () => {
+ it('exists', () => {
+ createComponent();
+
+ expect(findFirstActionMenu().exists()).toBe(true);
+ });
+
+ describe('menu items', () => {
+ describe('delete file', () => {
+ it('exists', () => {
+ createComponent();
+
+ expect(findActionMenuDelete().exists()).toBe(true);
+ });
+
+ it('emits a delete event when clicked', () => {
+ createComponent();
+
+ findActionMenuDelete().vm.$emit('click');
+
+ const [[{ id }]] = wrapper.emitted('delete-file');
+ expect(id).toBe(npmFiles[0].id);
+ });
+ });
+ });
+ });
+
+ describe('when the user can not delete', () => {
+ const canDelete = false;
+
+ it('does not exist', () => {
+ createComponent({ canDelete });
+
+ expect(findFirstActionMenu().exists()).toBe(false);
+ });
+ });
+ });
+ });
+
+ describe('additional details', () => {
+ describe('details toggle button', () => {
+ it('exists', () => {
+ createComponent();
+
+ expect(findFirstToggleDetailsButton().exists()).toBe(true);
+ });
+
+ it('is hidden when no details is present', () => {
+ const [{ ...noShaFile }] = npmFiles;
+ noShaFile.file_sha256 = null;
+ noShaFile.file_md5 = null;
+ noShaFile.file_sha1 = null;
+ createComponent({ packageFiles: [noShaFile] });
+
+ expect(findFirstToggleDetailsButton().exists()).toBe(false);
+ });
+
+ it('toggles the details row', async () => {
+ createComponent();
+
+ expect(findFirstToggleDetailsButton().props('icon')).toBe('angle-down');
+
+ findFirstToggleDetailsButton().vm.$emit('click');
+ await nextTick();
+
+ expect(findFirstRowShaComponent('sha-256').exists()).toBe(true);
+ expect(findFirstToggleDetailsButton().props('icon')).toBe('angle-up');
+
+ findFirstToggleDetailsButton().vm.$emit('click');
+ await nextTick();
+
+ expect(findFirstRowShaComponent('sha-256').exists()).toBe(false);
+ expect(findFirstToggleDetailsButton().props('icon')).toBe('angle-down');
+ });
+ });
+
+ describe('file shas', () => {
+ const showShaFiles = () => {
+ findFirstToggleDetailsButton().vm.$emit('click');
+ return nextTick();
+ };
+
+ it.each`
+ selector | title | sha
+ ${'sha-256'} | ${'SHA-256'} | ${'file_sha256'}
+ ${'md5'} | ${'MD5'} | ${'file_md5'}
+ ${'sha-1'} | ${'SHA-1'} | ${'file_sha1'}
+ `('has a $title row', async ({ selector, title, sha }) => {
+ createComponent();
+
+ await showShaFiles();
+
+ expect(findFirstRowShaComponent(selector).props()).toMatchObject({
+ title,
+ sha,
+ });
+ });
+
+ it('does not display a row when the data is missing', async () => {
+ const [{ ...missingMd5 }] = npmFiles;
+ missingMd5.file_md5 = null;
+
+ createComponent({ packageFiles: [missingMd5] });
+
+ await showShaFiles();
+
+ expect(findFirstRowShaComponent('md5').exists()).toBe(false);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js
new file mode 100644
index 00000000000..b0b6224916c
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js
@@ -0,0 +1,116 @@
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { stubComponent } from 'helpers/stub_component';
+import { mavenPackage, mockPipelineInfo } from 'jest/packages/mock_data';
+import { HISTORY_PIPELINES_LIMIT } from '~/packages/details/constants';
+import component from '~/packages_and_registries/package_registry/components/details/package_history.vue';
+import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
+describe('Package History', () => {
+ let wrapper;
+ const defaultProps = {
+ projectName: 'baz project',
+ packageEntity: { ...mavenPackage },
+ };
+
+ const createPipelines = (amount) =>
+ [...Array(amount)].map((x, index) => ({ ...mockPipelineInfo, id: index + 1 }));
+
+ const mountComponent = (props) => {
+ wrapper = shallowMount(component, {
+ propsData: { ...defaultProps, ...props },
+ stubs: {
+ HistoryItem: stubComponent(HistoryItem, {
+ template: '<div data-testid="history-element"><slot></slot></div>',
+ }),
+ GlSprintf,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findHistoryElement = (testId) => wrapper.find(`[data-testid="${testId}"]`);
+ const findElementLink = (container) => container.find(GlLink);
+ const findElementTimeAgo = (container) => container.find(TimeAgoTooltip);
+ const findTitle = () => wrapper.find('[data-testid="title"]');
+ const findTimeline = () => wrapper.find('[data-testid="timeline"]');
+
+ it('has the correct title', () => {
+ mountComponent();
+
+ const title = findTitle();
+
+ expect(title.exists()).toBe(true);
+ expect(title.text()).toBe('History');
+ });
+
+ it('has a timeline container', () => {
+ mountComponent();
+
+ const title = findTimeline();
+
+ expect(title.exists()).toBe(true);
+ expect(title.classes()).toEqual(
+ expect.arrayContaining(['timeline', 'main-notes-list', 'notes']),
+ );
+ });
+ describe.each`
+ name | amount | icon | text | timeAgoTooltip | link
+ ${'created-on'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'clock'} | ${'Test package version 1.0.0 was first created'} | ${mavenPackage.created_at} | ${null}
+ ${'first-pipeline-commit'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'commit'} | ${'Created by commit #sha-baz on branch branch-name'} | ${null} | ${mockPipelineInfo.project.commit_url}
+ ${'first-pipeline-pipeline'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pipeline'} | ${'Built by pipeline #1 triggered by foo'} | ${mockPipelineInfo.created_at} | ${mockPipelineInfo.project.pipeline_url}
+ ${'published'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'package'} | ${'Published to the baz project Package Registry'} | ${mavenPackage.created_at} | ${null}
+ ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'history'} | ${'Package has 1 archived update'} | ${null} | ${null}
+ ${'archived'} | ${HISTORY_PIPELINES_LIMIT + 3} | ${'history'} | ${'Package has 2 archived updates'} | ${null} | ${null}
+ ${'pipeline-entry'} | ${HISTORY_PIPELINES_LIMIT + 2} | ${'pencil'} | ${'Package updated by commit #sha-baz on branch branch-name, built by pipeline #3, and published to the registry'} | ${mavenPackage.created_at} | ${mockPipelineInfo.project.commit_url}
+ `(
+ 'with $amount pipelines history element $name',
+ ({ name, icon, text, timeAgoTooltip, link, amount }) => {
+ let element;
+
+ beforeEach(() => {
+ mountComponent({
+ packageEntity: { ...mavenPackage, pipelines: createPipelines(amount) },
+ });
+ element = findHistoryElement(name);
+ });
+
+ it('exists', () => {
+ expect(element.exists()).toBe(true);
+ });
+
+ it('has the correct icon', () => {
+ expect(element.props('icon')).toBe(icon);
+ });
+
+ it('has the correct text', () => {
+ expect(element.text()).toBe(text);
+ });
+
+ it('time-ago tooltip', () => {
+ const timeAgo = findElementTimeAgo(element);
+ const exist = Boolean(timeAgoTooltip);
+
+ expect(timeAgo.exists()).toBe(exist);
+ if (exist) {
+ expect(timeAgo.props('time')).toBe(timeAgoTooltip);
+ }
+ });
+
+ it('link', () => {
+ const linkElement = findElementLink(element);
+ const exist = Boolean(link);
+
+ expect(linkElement.exists()).toBe(exist);
+ if (exist) {
+ expect(linkElement.attributes('href')).toBe(link);
+ }
+ });
+ },
+ );
+});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js
new file mode 100644
index 00000000000..43a09b53be5
--- /dev/null
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js
@@ -0,0 +1,72 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import { pypiPackage as packageEntity } from 'jest/packages/mock_data';
+import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
+import PypiInstallation from '~/packages_and_registries/package_registry/components/details/pypi_installation.vue';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('PypiInstallation', () => {
+ let wrapper;
+
+ const pipCommandStr = 'pip install';
+ const pypiSetupStr = 'python setup';
+
+ const store = new Vuex.Store({
+ state: {
+ packageEntity,
+ pypiHelpPath: 'foo',
+ },
+ getters: {
+ pypiPipCommand: () => pipCommandStr,
+ pypiSetupCommand: () => pypiSetupStr,
+ },
+ });
+
+ const pipCommand = () => wrapper.find('[data-testid="pip-command"]');
+ const setupInstruction = () => wrapper.find('[data-testid="pypi-setup-content"]');
+
+ const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
+
+ function createComponent() {
+ wrapper = shallowMount(PypiInstallation, {
+ localVue,
+ store,
+ });
+ }
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('install command switch', () => {
+ it('has the installation title component', () => {
+ expect(findInstallationTitle().exists()).toBe(true);
+ expect(findInstallationTitle().props()).toMatchObject({
+ packageType: 'pypi',
+ options: [{ value: 'pypi', label: 'Show PyPi commands' }],
+ });
+ });
+ });
+
+ it('renders all the messages', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('installation commands', () => {
+ it('renders the correct pip command', () => {
+ expect(pipCommand().props('instruction')).toBe(pipCommandStr);
+ });
+ });
+
+ describe('setup commands', () => {
+ it('renders the correct setup block', () => {
+ expect(setupInstruction().props('instruction')).toBe(pypiSetupStr);
+ });
+ });
+});