OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / repos-page-pagination.test.js

118 lines · javascript ref:
// The /repos landing page ships a bounded first page, not the whole fleet.
//
// It rendered EVERY repo: 131 cards, 207KB of HTML, each with an inline SVG
// sparkline. The list is the forge's front door and the cost grows with every
// repo hosted, so the first visit — the one that has nothing cached — pays for
// the full index before showing anything.
//
// So: render the 25 most recently pushed, and let the rest be pulled in on
// demand at a size the reader picks. Ordering is unchanged (most recent commit
// first), so the first page is the part anyone actually wanted.
//
// These tests cover the pure slicing/rendering contract. The paging state
// (offset arithmetic, exhaustion) is what a lazy "view more" gets wrong, so it
// is asserted directly rather than through the DOM.

import test from 'brittle'

import {
  PAGE_SIZE,
  sortReposByRecency,
  renderRepoCards,
  __test
} from '../lib/web/pages/repos.js'

function fakeRepos (n) {
  const out = []
  for (let i = 0; i < n; i++) {
    out.push({
      name: 'repo-' + String(i).padStart(3, '0'),
      serving: true,
      branches: 1,
      commits: i,
      // Descending timestamps: repo-000 is the most recent.
      lastCommit: { timestamp: 2000000000 - i * 86400, hash: 'h' + i },
      activity: new Array(365).fill(0)
    })
  }
  return out
}

test('the first page is bounded and holds the most recent repos', async (t) => {
  const repos = fakeRepos(131)
  const sorted = sortReposByRecency(repos)

  t.is(PAGE_SIZE, 25, 'first page is 25 repos')
  t.is(sorted.length, 131, 'sorting does not drop anything')
  t.is(sorted[0].name, 'repo-000', 'most recent commit first')
  t.is(sorted[130].name, 'repo-130', 'quietest last')

  const firstPage = sorted.slice(0, PAGE_SIZE)
  const html = renderRepoCards(firstPage, '')
  const cards = html.split('class="repo-card"').length - 1
  t.is(cards, 25, 'exactly one page of cards rendered')
  t.ok(html.includes('repo-000'), 'includes the most recent repo')
  t.absent(html.includes('repo-025'), 'does not include repo 26 onward')
})

test('repos with no commits sink below repos that have them', async (t) => {
  const repos = [
    { name: 'quiet', lastCommit: null },
    { name: 'busy', lastCommit: { timestamp: 1000 } },
    { name: 'empty' }
  ]
  const sorted = sortReposByRecency(repos)
  t.is(sorted[0].name, 'busy', 'a repo with commits ranks above ones without')
  t.is(sorted.length, 3, 'nothing is dropped')
})

test('page slicing walks the whole list without gaps or repeats', async (t) => {
  const sorted = sortReposByRecency(fakeRepos(131))

  const seen = []
  let offset = 0
  // Mirrors what the client does: first page server-rendered, then the
  // reader picks a size for each subsequent pull.
  for (const size of [PAGE_SIZE, 25, 50, 100]) {
    const page = __test.pageOf(sorted, offset, size)
    seen.push(...page.repos.map((r) => r.name))
    offset = page.nextOffset
    if (page.remaining === 0) break
  }

  t.is(seen.length, 131, 'every repo was served exactly once')
  t.alike(seen, sorted.map((r) => r.name), 'in the same order as the full list')
  t.is(new Set(seen).size, 131, 'no duplicates across pages')
})

test('a page past the end is empty rather than an error', async (t) => {
  const sorted = sortReposByRecency(fakeRepos(10))

  const past = __test.pageOf(sorted, 999, 25)
  t.is(past.repos.length, 0, 'no repos')
  t.is(past.remaining, 0, 'nothing remaining')
  t.is(past.nextOffset, 999, 'offset does not run away')

  // An exhausted page is what tells the client to hide the control.
  const exact = __test.pageOf(sorted, 0, 25)
  t.is(exact.repos.length, 10, 'a short final page returns what exists')
  t.is(exact.remaining, 0, 'and reports itself exhausted')
})

test('"all" pulls everything that is left in one page', async (t) => {
  const sorted = sortReposByRecency(fakeRepos(131))
  const rest = __test.pageOf(sorted, PAGE_SIZE, Infinity)

  t.is(rest.repos.length, 131 - PAGE_SIZE, 'the entire remainder')
  t.is(rest.remaining, 0, 'exhausted')
  t.is(rest.nextOffset, 131, 'offset lands at the end')
})

test('a card renders the same whether it is on page 1 or a later page', async (t) => {
  const sorted = sortReposByRecency(fakeRepos(60))
  const inFirst = renderRepoCards([sorted[0]], '/git')
  const inLater = renderRepoCards([sorted[0]], '/git')
  t.is(inFirst, inLater, 'card markup is a pure function of the repo')
  t.ok(inFirst.includes('/git/repos/repo-000'), 'prefix applied to the link')
})