Skip to content

Implement "Dependencies" tab #3346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 1, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Implement "Dependencies" page
  • Loading branch information
Turbo87 committed Feb 27, 2021
commit 0f16504f4c8e93faaa19c82ff5f8a107f929d629
11 changes: 11 additions & 0 deletions app/components/crate-header.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@
{{@crate.versions.length}} Versions
</nav.Tab>

<nav.Tab
@link={{if
@versionNum
(link "crate.version-dependencies" @crate @versionNum)
(link "crate.dependencies" @crate)
}}
data-test-deps-tab
>
Dependencies
</nav.Tab>

<nav.Tab @link={{link "crate.reverse-dependencies" @crate}} data-test-rev-deps-tab>
Dependents
</nav.Tab>
Expand Down
31 changes: 31 additions & 0 deletions app/components/dependency-list/row.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<div
local-class="
row
{{if @dependency.optional "optional"}}
{{if this.focused "focused"}}
"
...attributes
>
<span local-class="range" data-test-range>
{{format-req @dependency.req}}
</span>

<span>
<LinkTo
@route="crate"
@model={{@dependency.crate_id}}
local-class="link"
{{on "focusin" (fn this.setFocused true)}}
{{on "focusout" (fn this.setFocused false)}}
data-test-release-track-link
>
{{@dependency.crate_id}}
</LinkTo>

<span local-class="metadata">
{{#if @dependency.optional}}
optional
{{/if}}
</span>
</span>
</div>
11 changes: 11 additions & 0 deletions app/components/dependency-list/row.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { action } from '@ember/object';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

export default class VersionRow extends Component {
@tracked focused = false;

@action setFocused(value) {
this.focused = value;
}
}
100 changes: 100 additions & 0 deletions app/components/dependency-list/row.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
.row {
--bg-color: var(--grey200);
--hover-bg-color: hsl(217, 37%, 98%);
--range-color: var(--grey900);
--crate-color: var(--grey700);
--shadow: 0 1px 3px hsla(51, 90%, 42%, .35);

display: flex;
align-items: center;
position: relative;
font-size: 18px;
padding: 15px 25px;
background-color: white;
border-radius: 7px;
box-shadow: var(--shadow);
transition: all 300ms;

&:hover, &.focused {
background-color: var(--hover-bg-color);
transition: all 0ms;
}

&.focused {
box-shadow: 0 0 0 3px var(--yellow500), var(--shadow);
}

&.optional {
--range-color: var(--grey600);
--crate-color: var(--grey600);
}

[title], :global(.ember-tooltip-target) {
position: relative;
z-index: 1;
cursor: help;
}

:global(.ember-tooltip) {
word-break: break-all;
}

@media only screen and (max-width: 550px) {
display: block
}
}

.range {
margin-right: 15px;
min-width: 100px;
color: var(--range-color);
font-variant: tabular-nums;
}

.link {
color: var(--crate-color);
font-weight: 500;
margin-right: 15px;
outline: none;

&:hover {
color: var(--crate-color);
}

&::after {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
}

.metadata {
color: var(--grey600);
text-transform: uppercase;
letter-spacing: .7px;
font-size: 13px;

a {
position: relative;
color: var(--grey600);

&:hover {
color: var(--grey900);
}
}

svg {
height: 1em;
width: auto;
margin-right: 2px;
margin-bottom: -.1em;
}

:global(.ember-tooltip) {
text-transform: none;
letter-spacing: normal;
}
}
2 changes: 2 additions & 0 deletions app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ Router.map(function () {
this.route('crates');
this.route('crate', { path: '/crates/:crate_id' }, function () {
this.route('versions');
this.route('dependencies');
this.route('version', { path: '/:version_num' });
this.route('version-dependencies', { path: '/:version_num/dependencies' });

this.route('reverse-dependencies', { path: 'reverse_dependencies' });

Expand Down
13 changes: 13 additions & 0 deletions app/routes/crate/dependencies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Route from '@ember/routing/route';

export default class VersionRoute extends Route {
async model() {
let crate = this.modelFor('crate');
let versions = await crate.get('versions');

let { defaultVersion } = crate;
let version = versions.find(version => version.num === defaultVersion) ?? versions.lastObject;

this.replaceWith('crate.version-dependencies', crate, version.num);
}
}
34 changes: 34 additions & 0 deletions app/routes/crate/version-dependencies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default class VersionRoute extends Route {
@service notifications;

async model(params) {
let crate = this.modelFor('crate');
let versions = await crate.get('versions');

let requestedVersion = params.version_num;
let version = versions.find(version => version.num === requestedVersion);
if (!version) {
this.notifications.error(`Version '${requestedVersion}' of crate '${crate.name}' does not exist`);
this.replaceWith('crate.index');
}

try {
await version.loadDepsTask.perform();
} catch {
this.notifications.error(
`Failed to load the list of dependencies for the '${crate.name}' crate. Please try again later!`,
);
this.replaceWith('crate.index');
}

return version;
}

setupController(controller, model) {
controller.set('version', model);
controller.set('crate', this.modelFor('crate'));
}
}
11 changes: 11 additions & 0 deletions app/styles/crate/version-dependencies.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.list {
list-style: none;
margin: 0;
padding: 0;

li {
&:not(:first-child) {
margin-top: 10px;
}
}
}
38 changes: 38 additions & 0 deletions app/templates/crate/version-dependencies.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{{page-title this.crate.name}}

<CrateHeader
@crate={{this.crate}}
@version={{this.version}}
@versionNum={{this.version.num}}
/>

<h3 local-class="heading">Dependencies</h3>
{{#if this.version.normalDependencies}}
<ul local-class="list" data-test-dependencies>
{{#each this.version.normalDependencies as |dependency|}}
<li><DependencyList::Row @dependency={{dependency}} /></li>
{{/each}}
</ul>
{{else}}
<div local-class="no-deps" data-test-no-dependencies>
This version of the "{{this.crate.name}}" crate has no dependencies
</div>
{{/if}}

{{#if this.version.buildDependencies}}
<h3 local-class="heading">Build-Dependencies</h3>
<ul local-class="list" data-test-build-dependencies>
{{#each this.version.buildDependencies as |dependency|}}
<li><DependencyList::Row @dependency={{dependency}} /></li>
{{/each}}
</ul>
{{/if}}

{{#if this.version.devDependencies}}
<h3 local-class="heading">Dev-Dependencies</h3>
<ul local-class="list" data-test-dev-dependencies>
{{#each this.version.devDependencies as |dependency|}}
<li><DependencyList::Row @dependency={{dependency}} /></li>
{{/each}}
</ul>
{{/if}}
54 changes: 54 additions & 0 deletions tests/acceptance/crate-dependencies-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { currentURL, visit } from '@ember/test-helpers';
import { module, test } from 'qunit';

import percySnapshot from '@percy/ember';
import a11yAudit from 'ember-a11y-testing/test-support/audit';
import { getPageTitle } from 'ember-page-title/test-support';

import { setupApplicationTest } from 'cargo/tests/helpers';

import axeConfig from '../axe-config';

module('Acceptance | crate dependencies page', function (hooks) {
setupApplicationTest(hooks);

test('shows the lists of dependencies', async function (assert) {
this.server.loadFixtures();

await visit('/crates/nanomsg/dependencies');
assert.equal(currentURL(), '/crates/nanomsg/0.6.1/dependencies');
assert.equal(getPageTitle(), 'nanomsg - crates.io: Rust Package Registry');

assert.dom('[data-test-dependencies] li').exists({ count: 2 });
assert.dom('[data-test-build-dependencies] li').exists({ count: 1 });
assert.dom('[data-test-dev-dependencies] li').exists({ count: 1 });

await percySnapshot(assert);
await a11yAudit(axeConfig);
});

test('empty list case', async function (assert) {
this.server.create('crate', { name: 'nanomsg' });
this.server.create('version', { crateId: 'nanomsg', num: '0.6.1' });

await visit('/crates/nanomsg/dependencies');

assert.dom('[data-test-no-dependencies]').exists();
assert.dom('[data-test-dependencies] li').doesNotExist();
assert.dom('[data-test-build-dependencies] li').doesNotExist();
assert.dom('[data-test-dev-dependencies] li').doesNotExist();
});

test('shows error message if loading of dependencies fails', async function (assert) {
this.server.loadFixtures();

this.server.get('/api/v1/crates/:crate_name/:version_num/dependencies', {}, 500);

await visit('/crates/nanomsg/dependencies');
assert.equal(currentURL(), '/crates/nanomsg');

assert
.dom('[data-test-notification-message="error"]')
.hasText("Failed to load the list of dependencies for the 'nanomsg' crate. Please try again later!");
});
});
16 changes: 16 additions & 0 deletions tests/acceptance/crate-navtabs-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { setupApplicationTest } from 'cargo/tests/helpers';

const TAB_README = '[data-test-readme-tab] a';
const TAB_VERSIONS = '[data-test-versions-tab] a';
const TAB_DEPS = '[data-test-deps-tab] a';
const TAB_REV_DEPS = '[data-test-rev-deps-tab] a';
const TAB_SETTINGS = '[data-test-settings-tab] a';

Expand All @@ -20,6 +21,7 @@ module('Acceptance | crate navigation tabs', function (hooks) {

assert.dom(TAB_README).hasAttribute('href', '/crates/nanomsg').hasAttribute('data-test-active');
assert.dom(TAB_VERSIONS).hasAttribute('href', '/crates/nanomsg/versions').hasNoAttribute('data-test-active');
assert.dom(TAB_DEPS).hasAttribute('href', '/crates/nanomsg/dependencies').hasNoAttribute('data-test-active');
assert
.dom(TAB_REV_DEPS)
.hasAttribute('href', '/crates/nanomsg/reverse_dependencies')
Expand All @@ -31,6 +33,19 @@ module('Acceptance | crate navigation tabs', function (hooks) {

assert.dom(TAB_README).hasAttribute('href', '/crates/nanomsg').hasNoAttribute('data-test-active');
assert.dom(TAB_VERSIONS).hasAttribute('href', '/crates/nanomsg/versions').hasAttribute('data-test-active');
assert.dom(TAB_DEPS).hasAttribute('href', '/crates/nanomsg/dependencies').hasNoAttribute('data-test-active');
assert
.dom(TAB_REV_DEPS)
.hasAttribute('href', '/crates/nanomsg/reverse_dependencies')
.hasNoAttribute('data-test-active');
assert.dom(TAB_SETTINGS).doesNotExist();

await click(TAB_DEPS);
assert.equal(currentURL(), '/crates/nanomsg/0.6.1/dependencies');

assert.dom(TAB_README).hasAttribute('href', '/crates/nanomsg/0.6.1').hasNoAttribute('data-test-active');
assert.dom(TAB_VERSIONS).hasAttribute('href', '/crates/nanomsg/versions').hasNoAttribute('data-test-active');
assert.dom(TAB_DEPS).hasAttribute('href', '/crates/nanomsg/0.6.1/dependencies').hasAttribute('data-test-active');
assert
.dom(TAB_REV_DEPS)
.hasAttribute('href', '/crates/nanomsg/reverse_dependencies')
Expand All @@ -42,6 +57,7 @@ module('Acceptance | crate navigation tabs', function (hooks) {

assert.dom(TAB_README).hasAttribute('href', '/crates/nanomsg').hasNoAttribute('data-test-active');
assert.dom(TAB_VERSIONS).hasAttribute('href', '/crates/nanomsg/versions').hasNoAttribute('data-test-active');
assert.dom(TAB_DEPS).hasAttribute('href', '/crates/nanomsg/dependencies').hasNoAttribute('data-test-active');
assert
.dom(TAB_REV_DEPS)
.hasAttribute('href', '/crates/nanomsg/reverse_dependencies')
Expand Down