Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions mirage/route-handlers/-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,5 @@ export function releaseTracks(versions) {
}
return tracks;
}

export { default as compareSemvers } from 'semver/functions/compare-loose';
34 changes: 30 additions & 4 deletions mirage/route-handlers/crates.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Response } from 'miragejs';

import { getSession } from '../utils/session';
import { compareIsoDates, compareStrings, notFound, pageParams, releaseTracks } from './-utils';
import { compareIsoDates, compareSemvers, compareStrings, notFound, pageParams, releaseTracks } from './-utils';

function toCanonicalName(name) {
return name.toLowerCase().replace(/-/g, '_');
Expand Down Expand Up @@ -159,17 +159,43 @@ export function register(server) {
if (!crate) return notFound();

let versions = crate.versions;
let { nums } = request.queryParams;
let { nums, sort, per_page, seek } = request.queryParams;
if (nums) {
versions = versions.filter(version => nums.includes(version.num));
}
versions = versions.sort((a, b) => compareIsoDates(b.created_at, a.created_at));
versions =
sort == 'date'
? versions.sort((a, b) => compareIsoDates(b.created_at, a.created_at))
: versions.sort((a, b) => compareSemvers(b.num, a.num));
let total = versions.length;
let include = request.queryParams?.include ?? '';
let release_tracks = include.split(',').includes('release_tracks') && releaseTracks(crate.versions);

// seek pagination
// A simplified seek encoding is applied here for testing purposes only. It should be opaque
// in real-world scenarios.
let next_seek = null;
if (per_page != null) {
if (seek != null) {
let idx = versions.models.findIndex(it => it.num === seek);
versions = versions.slice(idx + 1);
}
versions = versions.slice(0, per_page);

if (versions.length >= per_page) {
let last = versions.models.at(-1);
next_seek = last.num;
}
}
let next_page = null;
if (next_seek) {
next_page = new URLSearchParams({ ...request.queryParams, seek: next_seek });
next_page = `?${next_page}`;
}

let resp = {
...this.serialize(versions),
meta: { total, next_page: null },
meta: { total, next_page },
};
if (release_tracks && Object.keys(release_tracks).length !== 0) {
resp.meta.release_tracks = release_tracks;
Expand Down
10 changes: 5 additions & 5 deletions tests/components/version-list-row-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,17 @@ module('Component | VersionList::Row', function (hooks) {
let crateRecord = await store.findRecord('crate', crate.name);
let versions = (await crateRecord.loadVersionsTask.perform()).slice();
await crateRecord.loadOwnerUserTask.perform();
this.firstVersion = versions[0];
this.secondVersion = versions[1];
this.thirdVersion = versions[2];
this.firstVersion = versions.find(it => it.num == '0.3.0');
this.secondVersion = versions.find(it => it.num == '0.2.0');
this.thirdVersion = versions.find(it => it.num == '0.1.0');

await render(hbs`<VersionList::Row @version={{this.firstVersion}} />`);
assert.dom('[data-test-feature-list]').doesNotExist();
assert.dom('[data-test-feature-list]').hasText('2 Features');

await render(hbs`<VersionList::Row @version={{this.secondVersion}} />`);
assert.dom('[data-test-feature-list]').hasText('1 Feature');

await render(hbs`<VersionList::Row @version={{this.thirdVersion}} />`);
assert.dom('[data-test-feature-list]').hasText('2 Features');
assert.dom('[data-test-feature-list]').doesNotExist();
});
});
160 changes: 139 additions & 21 deletions tests/mirage/crates/versions/list-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,21 @@ module('Mirage | GET /api/v1/crates/:name/versions', function (hooks) {
assert.deepEqual(await response.json(), {
versions: [
{
id: '1',
id: '3',
crate: 'rand',
crate_size: 0,
crate_size: 325_926,
created_at: '2010-06-16T21:30:45Z',
dl_path: '/api/v1/crates/rand/1.0.0/download',
downloads: 0,
license: 'MIT/Apache-2.0',
dl_path: '/api/v1/crates/rand/1.2.0/download',
downloads: 7404,
license: 'Apache-2.0',
links: {
dependencies: '/api/v1/crates/rand/1.0.0/dependencies',
version_downloads: '/api/v1/crates/rand/1.0.0/downloads',
dependencies: '/api/v1/crates/rand/1.2.0/dependencies',
version_downloads: '/api/v1/crates/rand/1.2.0/downloads',
},
num: '1.0.0',
num: '1.2.0',
published_by: null,
readme_path: '/api/v1/crates/rand/1.0.0/readme',
rust_version: null,
readme_path: '/api/v1/crates/rand/1.2.0/readme',
rust_version: '1.69',
updated_at: '2017-02-24T12:34:56Z',
yanked: false,
yank_message: null,
Expand Down Expand Up @@ -84,21 +84,21 @@ module('Mirage | GET /api/v1/crates/:name/versions', function (hooks) {
yank_message: null,
},
{
id: '3',
id: '1',
crate: 'rand',
crate_size: 325_926,
crate_size: 0,
created_at: '2010-06-16T21:30:45Z',
dl_path: '/api/v1/crates/rand/1.2.0/download',
downloads: 7404,
license: 'Apache-2.0',
dl_path: '/api/v1/crates/rand/1.0.0/download',
downloads: 0,
license: 'MIT/Apache-2.0',
links: {
dependencies: '/api/v1/crates/rand/1.2.0/dependencies',
version_downloads: '/api/v1/crates/rand/1.2.0/downloads',
dependencies: '/api/v1/crates/rand/1.0.0/dependencies',
version_downloads: '/api/v1/crates/rand/1.0.0/downloads',
},
num: '1.2.0',
num: '1.0.0',
published_by: null,
readme_path: '/api/v1/crates/rand/1.2.0/readme',
rust_version: '1.69',
readme_path: '/api/v1/crates/rand/1.0.0/readme',
rust_version: null,
updated_at: '2017-02-24T12:34:56Z',
yanked: false,
yank_message: null,
Expand All @@ -108,6 +108,124 @@ module('Mirage | GET /api/v1/crates/:name/versions', function (hooks) {
});
});

test('supports `sort` parameters', async function (assert) {
let user = this.server.create('user');
let crate = this.server.create('crate', { name: 'rand' });
this.server.create('version', { crate, num: '1.0.0', created_at: '2010-06-16T21:30:45Z' });
this.server.create('version', { crate, num: '1.2.0', rust_version: '1.69', created_at: '2010-06-16T21:30:46Z' });
this.server.create('version', { crate, num: '1.1.0', publishedBy: user, created_at: '2010-06-16T21:30:47Z' });

// sort by `semver` by default
{
let response = await fetch('/api/v1/crates/rand/versions');
assert.strictEqual(response.status, 200);
let json = await response.json();
assert.deepEqual(
json.versions.map(it => it.num),
['1.2.0', '1.1.0', '1.0.0'],
);
}

{
let response = await fetch('/api/v1/crates/rand/versions?sort=semver');
assert.strictEqual(response.status, 200);
let json = await response.json();
assert.deepEqual(
json.versions.map(it => it.num),
['1.2.0', '1.1.0', '1.0.0'],
);
}

{
let response = await fetch('/api/v1/crates/rand/versions?sort=date');
assert.strictEqual(response.status, 200);
let json = await response.json();
assert.deepEqual(
json.versions.map(it => it.num),
['1.1.0', '1.2.0', '1.0.0'],
);
}
});

test('supports seek pagination', async function (assert) {
let user = this.server.create('user');
let crate = this.server.create('crate', { name: 'rand' });
this.server.create('version', { crate, num: '1.0.0', created_at: '2010-06-16T21:30:45Z' });
this.server.create('version', { crate, num: '1.2.0', rust_version: '1.69', created_at: '2010-06-16T21:30:46Z' });
this.server.create('version', { crate, num: '1.1.0', publishedBy: user, created_at: '2010-06-16T21:30:47Z' });

async function seek_forwards(queryParams) {
let calls = 0;
let next_page;
let responses = [];
let base_url = '/api/v1/crates/rand/versions';
let params = new URLSearchParams(queryParams);
let url = `${base_url}?${params}`;
while ((calls == 0 || next_page) && calls < 10) {
if (next_page) {
url = `${base_url}${next_page}`;
}
let response = await fetch(url);
calls += 1;
assert.strictEqual(response.status, 200);
let json = await response.json();
responses.push(json);
next_page = json.meta.next_page;
if (next_page == null) {
break;
}
}
return responses;
}

// sort by `semver` by default
{
let responses = await seek_forwards({ per_page: 1 });
assert.deepEqual(
responses.map(it => it.versions.map(v => v.num)),
[['1.2.0'], ['1.1.0'], ['1.0.0'], []],
);
assert.deepEqual(
responses.map(it => it.meta.next_page),
['?per_page=1&seek=1.2.0', '?per_page=1&seek=1.1.0', '?per_page=1&seek=1.0.0', null],
);
}

{
let responses = await seek_forwards({ per_page: 1, sort: 'semver' });
assert.deepEqual(
responses.map(it => it.versions.map(v => v.num)),
[['1.2.0'], ['1.1.0'], ['1.0.0'], []],
);
assert.deepEqual(
responses.map(it => it.meta.next_page),
[
'?per_page=1&sort=semver&seek=1.2.0',
'?per_page=1&sort=semver&seek=1.1.0',
'?per_page=1&sort=semver&seek=1.0.0',
null,
],
);
}

{
let responses = await seek_forwards({ per_page: 1, sort: 'date' });
assert.deepEqual(
responses.map(it => it.versions.map(v => v.num)),
[['1.1.0'], ['1.2.0'], ['1.0.0'], []],
);
assert.deepEqual(
responses.map(it => it.meta.next_page),
[
'?per_page=1&sort=date&seek=1.1.0',
'?per_page=1&sort=date&seek=1.2.0',
'?per_page=1&sort=date&seek=1.0.0',
null,
],
);
}
});

test('supports multiple `ids[]` parameters', async function (assert) {
let user = this.server.create('user');
let crate = this.server.create('crate', { name: 'rand' });
Expand All @@ -119,7 +237,7 @@ module('Mirage | GET /api/v1/crates/:name/versions', function (hooks) {
let json = await response.json();
assert.deepEqual(
json.versions.map(v => v.num),
['1.0.0', '1.2.0'],
['1.2.0', '1.0.0'],
);
});

Expand Down
16 changes: 8 additions & 8 deletions tests/models/version-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,8 @@ module('Model | Version', function (hooks) {
'0.3.3',
'0.3.2',
'0.3.1',
'0.3.0-alpha.01',
'0.3.0',
'0.3.0-alpha.01',
'0.2.1',
'0.2.0',
'0.1.2',
Expand All @@ -181,8 +181,8 @@ module('Model | Version', function (hooks) {
{ num: '0.3.3', isHighestOfReleaseTrack: false },
{ num: '0.3.2', isHighestOfReleaseTrack: false },
{ num: '0.3.1', isHighestOfReleaseTrack: false },
{ num: '0.3.0-alpha.01', isHighestOfReleaseTrack: false },
{ num: '0.3.0', isHighestOfReleaseTrack: false },
{ num: '0.3.0-alpha.01', isHighestOfReleaseTrack: false },
{ num: '0.2.1', isHighestOfReleaseTrack: true },
{ num: '0.2.0', isHighestOfReleaseTrack: false },
{ num: '0.1.2', isHighestOfReleaseTrack: true },
Expand All @@ -203,9 +203,9 @@ module('Model | Version', function (hooks) {
assert.deepEqual(
versions.map(it => ({ num: it.num, isHighestOfReleaseTrack: it.isHighestOfReleaseTrack })),
[
{ num: '0.4.0', isHighestOfReleaseTrack: false },
{ num: '0.4.1', isHighestOfReleaseTrack: true },
{ num: '0.4.2', isHighestOfReleaseTrack: false },
{ num: '0.4.1', isHighestOfReleaseTrack: true },
{ num: '0.4.0', isHighestOfReleaseTrack: false },
],
);
});
Expand All @@ -221,8 +221,8 @@ module('Model | Version', function (hooks) {
assert.deepEqual(
versions.map(it => ({ num: it.num, isHighestOfReleaseTrack: it.isHighestOfReleaseTrack })),
[
{ num: '0.4.0', isHighestOfReleaseTrack: false },
{ num: '0.4.1', isHighestOfReleaseTrack: true },
{ num: '0.4.0', isHighestOfReleaseTrack: false },
],
);

Expand All @@ -234,10 +234,10 @@ module('Model | Version', function (hooks) {
assert.deepEqual(
versions.map(it => ({ num: it.num, isHighestOfReleaseTrack: it.isHighestOfReleaseTrack })),
[
{ num: '0.4.0', isHighestOfReleaseTrack: false },
{ num: '0.4.1', isHighestOfReleaseTrack: false },
{ num: '0.4.2', isHighestOfReleaseTrack: true },
{ num: '0.4.3', isHighestOfReleaseTrack: false },
{ num: '0.4.2', isHighestOfReleaseTrack: true },
{ num: '0.4.1', isHighestOfReleaseTrack: false },
{ num: '0.4.0', isHighestOfReleaseTrack: false },
],
);
});
Expand Down
Loading