Skip to content
Merged
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
14 changes: 10 additions & 4 deletions src/apisearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,24 @@ const field = (item, spec) =>
export function resultsToHTML(def, query, data, apiURL) {
const host = new URL(apiURL).host;
const fields = def.fields ?? {};
const list = pick(data, def.results ?? 'results');
const items = (Array.isArray(list) ? list : []).slice(0, MAX_RESULTS).map((item) => {
const picked = pick(data, def.results ?? 'results');
const list = Array.isArray(picked) ? picked : [];
const items = list.slice(0, MAX_RESULTS).map((item) => {
const href = new URL(String(field(item, fields.url ?? 'url') ?? ''), apiURL).href;
const title = String(field(item, fields.title ?? 'title') || href);
const text = fields.text ? String(field(item, fields.text) ?? '').trim() : '';
return `<li><a href="${escapeHTML(href)}">${escapeHTML(title)}</a>`
+ `${text ? ` ${escapeHTML(text)}` : ''}</li>`;
});
const total = Number(def.total ? pick(data, def.total) : NaN);
const count = Number.isFinite(total) && total >= items.length ? total : items.length;
// An answer with no total says only how many results it carried, and one
// cut to fit is a floor: RubyGems answers 30 a page, so "rails" read as 20
// pages matching and nothing saying the list went on.
const known = Number.isFinite(total) && total >= items.length;
const count = known ? total : list.length;
const floor = !known && count > items.length ? 'at least ' : '';
const summary = items.length
? `${count} page${count === 1 ? '' : 's'} match, ranked by the site's own search`
? `${floor}${count} page${count === 1 ? '' : 's'} match, ranked by the site's own search`
+ `${count > items.length ? `, top ${items.length} shown` : ''}:`
: `nothing in the site's own search matches; try fewer or different words`;
return `<html><head><title>${escapeHTML(host)} search: ${escapeHTML(query)}</title></head><body><main>`
Expand Down
11 changes: 11 additions & 0 deletions tests/apisearch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ test('the site total is reported, and result count is what the page shows', () =
assert.match(resultsToHTML(DEF, 'map', DATA, API), /2696 pages match, ranked by the site's own search, top 2 shown:/);
});

test('with no total, a list cut to fit says it went on', () => {
// RubyGems answers a search with a bare array of 30 and no total. Cut to
// 20, the page said 20 pages match and nothing about the other ten.
const gems = Array.from({ length: 30 }, (_, i) => ({ name: `gem${i}`, url: `https://rubygems.org/gems/gem${i}` }));
const def = { results: '', fields: { title: 'name', url: 'url' } };
const api = 'https://rubygems.org/api/v1/search.json?query=rails';
assert.match(resultsToHTML(def, 'rails', gems, api), /at least 30 pages match, ranked by the site's own search, top 20 shown:/);
// A list that fits is still counted as it is.
assert.match(resultsToHTML(def, 'rails', gems.slice(0, 5), api), /<p>5 pages match, ranked by the site's own search:<\/p>/);
});

test('a title is response data, never markup on the results page', () => {
const html = resultsToHTML(DEF, 'map', DATA, API);
assert.doesNotMatch(html, /<script/);
Expand Down
Loading