Skip to content

Commit 976f0b2

Browse files
committed
Implement /crates/:crate_id/range/:range route
... that redirects the user to the highest version that satisfies the specified semver range
1 parent 6fddb34 commit 976f0b2

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

app/router.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Router.map(function () {
1515
this.route('dependencies');
1616
this.route('version', { path: '/:version_num' });
1717
this.route('version-dependencies', { path: '/:version_num/dependencies' });
18+
this.route('range', { path: '/range/:range' });
1819

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

app/routes/crate/range.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import Route from '@ember/routing/route';
2+
import { inject as service } from '@ember/service';
3+
4+
import maxSatisfying from 'semver/ranges/max-satisfying';
5+
6+
export default class VersionRoute extends Route {
7+
@service notifications;
8+
@service router;
9+
10+
async model({ range }) {
11+
let crate = this.modelFor('crate');
12+
13+
let versions = await crate.get('versions');
14+
let allVersionNums = versions.map(it => it.num);
15+
let unyankedVersionNums = versions.filter(it => !it.yanked).map(it => it.num);
16+
17+
// find a version that matches the specified range
18+
let versionNum = maxSatisfying(unyankedVersionNums, range) ?? maxSatisfying(allVersionNums, range);
19+
if (!versionNum) {
20+
this.notifications.error(`No matching version of crate '${crate.name}' found for: ${range}`);
21+
this.replaceWith('crate.index');
22+
}
23+
24+
this.router.replaceWith('crate.version', versionNum);
25+
}
26+
}

tests/routes/crate/range-test.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { currentURL, visit } from '@ember/test-helpers';
2+
import { module, test } from 'qunit';
3+
4+
import { setupApplicationTest } from 'cargo/tests/helpers';
5+
6+
module('Route | crate.range', function (hooks) {
7+
setupApplicationTest(hooks);
8+
9+
test('happy path', async function (assert) {
10+
let crate = this.server.create('crate', { name: 'foo' });
11+
this.server.create('version', { crate, num: '1.0.0' });
12+
this.server.create('version', { crate, num: '1.1.0' });
13+
this.server.create('version', { crate, num: '1.2.0' });
14+
this.server.create('version', { crate, num: '1.2.3' });
15+
16+
await visit('/crates/foo/range/^1.1.0');
17+
assert.equal(currentURL(), `/crates/foo/1.2.3`);
18+
assert.dom('[data-test-crate-name]').hasText('foo');
19+
assert.dom('[data-test-crate-version]').hasText('1.2.3');
20+
assert.dom('[data-test-notification-message]').doesNotExist();
21+
});
22+
23+
test('happy path with tilde range', async function (assert) {
24+
let crate = this.server.create('crate', { name: 'foo' });
25+
this.server.create('version', { crate, num: '1.0.0' });
26+
this.server.create('version', { crate, num: '1.1.0' });
27+
this.server.create('version', { crate, num: '1.1.1' });
28+
this.server.create('version', { crate, num: '1.2.0' });
29+
30+
await visit('/crates/foo/range/~1.1.0');
31+
assert.equal(currentURL(), `/crates/foo/1.1.1`);
32+
assert.dom('[data-test-crate-name]').hasText('foo');
33+
assert.dom('[data-test-crate-version]').hasText('1.1.1');
34+
assert.dom('[data-test-notification-message]').doesNotExist();
35+
});
36+
37+
test('ignores yanked versions if possible', async function (assert) {
38+
let crate = this.server.create('crate', { name: 'foo' });
39+
this.server.create('version', { crate, num: '1.0.0' });
40+
this.server.create('version', { crate, num: '1.1.0' });
41+
this.server.create('version', { crate, num: '1.1.1' });
42+
this.server.create('version', { crate, num: '1.2.0', yanked: true });
43+
44+
await visit('/crates/foo/range/^1.0.0');
45+
assert.equal(currentURL(), `/crates/foo/1.1.1`);
46+
assert.dom('[data-test-crate-name]').hasText('foo');
47+
assert.dom('[data-test-crate-version]').hasText('1.1.1');
48+
assert.dom('[data-test-notification-message]').doesNotExist();
49+
});
50+
51+
test('falls back to yanked version if necessary', async function (assert) {
52+
let crate = this.server.create('crate', { name: 'foo' });
53+
this.server.create('version', { crate, num: '1.0.0', yanked: true });
54+
this.server.create('version', { crate, num: '1.1.0', yanked: true });
55+
this.server.create('version', { crate, num: '1.1.1', yanked: true });
56+
this.server.create('version', { crate, num: '2.0.0' });
57+
58+
await visit('/crates/foo/range/^1.0.0');
59+
assert.equal(currentURL(), `/crates/foo/1.1.1`);
60+
assert.dom('[data-test-crate-name]').hasText('foo');
61+
assert.dom('[data-test-crate-version]').hasText('1.1.1');
62+
assert.dom('[data-test-notification-message]').doesNotExist();
63+
});
64+
65+
test('redirects to main crate page if no match found', async function (assert) {
66+
let crate = this.server.create('crate', { name: 'foo' });
67+
this.server.create('version', { crate, num: '1.0.0' });
68+
this.server.create('version', { crate, num: '1.1.0' });
69+
this.server.create('version', { crate, num: '1.1.1' });
70+
this.server.create('version', { crate, num: '2.0.0' });
71+
72+
await visit('/crates/foo/range/^3');
73+
assert.equal(currentURL(), `/crates/foo`);
74+
assert.dom('[data-test-crate-name]').hasText('foo');
75+
assert.dom('[data-test-crate-version]').hasText('2.0.0');
76+
assert.dom('[data-test-notification-message="error"]').hasText("No matching version of crate 'foo' found for: ^3");
77+
});
78+
});

0 commit comments

Comments
 (0)