mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-07 09:51:37 +00:00
test: define large-library browse strategy contract
This commit is contained in:
parent
da490605b7
commit
1533d4146a
4 changed files with 241 additions and 0 deletions
21
server/utils/queries/libraryBrowseCount.js
Normal file
21
server/utils/queries/libraryBrowseCount.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
async function loadBrowseCount({ mode, exactCountLoader } = {}) {
|
||||
if (mode === 'skip') {
|
||||
return {
|
||||
total: null,
|
||||
isExact: false,
|
||||
isDeferred: true
|
||||
}
|
||||
}
|
||||
|
||||
const total = await exactCountLoader()
|
||||
|
||||
return {
|
||||
total,
|
||||
isExact: true,
|
||||
isDeferred: mode === 'deferred-exact'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadBrowseCount
|
||||
}
|
||||
63
server/utils/queries/libraryBrowseStrategy.js
Normal file
63
server/utils/queries/libraryBrowseStrategy.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
function getCursorKeys(sortBy) {
|
||||
if (sortBy === 'media.metadata.authorName') {
|
||||
return ['authorNamesFirstLast', 'titleIgnorePrefix', 'id']
|
||||
}
|
||||
|
||||
if (sortBy === 'media.metadata.authorNameLF') {
|
||||
return ['authorNamesLastFirst', 'titleIgnorePrefix', 'id']
|
||||
}
|
||||
|
||||
if (sortBy === 'progress') {
|
||||
return ['mediaProgress.updatedAt', 'id']
|
||||
}
|
||||
|
||||
if (sortBy === 'updatedAt') {
|
||||
return ['updatedAt', 'id']
|
||||
}
|
||||
|
||||
if (sortBy === 'addedAt') {
|
||||
return ['createdAt', 'id']
|
||||
}
|
||||
|
||||
return ['titleIgnorePrefix', 'id']
|
||||
}
|
||||
|
||||
function getFamily({ mediaType, sortBy, filterGroup, collapseseries }) {
|
||||
if (sortBy === 'random') return 'random-browse'
|
||||
if (mediaType === 'podcast') return 'podcast-browse'
|
||||
if (collapseseries && filterGroup === 'series') return 'collapsed-series-browse'
|
||||
if (filterGroup === 'series') return 'series-browse'
|
||||
if (filterGroup === 'progress') return 'progress-browse'
|
||||
if (filterGroup === 'recent' || sortBy === 'addedAt') return 'recent-browse'
|
||||
if (sortBy === 'media.metadata.authorName' || sortBy === 'media.metadata.authorNameLF') return 'author-browse'
|
||||
if (filterGroup) return 'filtered-browse'
|
||||
return 'plain-browse'
|
||||
}
|
||||
|
||||
function getLibraryBrowseStrategy({ mediaType, sortBy, filterGroup, pageMode, collapseseries } = {}) {
|
||||
const normalizedSort = sortBy || 'media.metadata.title'
|
||||
|
||||
if (normalizedSort === 'random') {
|
||||
return {
|
||||
family: 'random-browse',
|
||||
paginationMode: 'offset',
|
||||
countMode: 'exact-on-initial-page',
|
||||
deepScrollAllowed: false,
|
||||
tieBreaker: 'id',
|
||||
cursorKeys: []
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
family: getFamily({ mediaType, sortBy: normalizedSort, filterGroup, collapseseries }),
|
||||
paginationMode: pageMode === 'endless' ? 'keyset' : 'offset',
|
||||
countMode: 'deferred-exact',
|
||||
deepScrollAllowed: true,
|
||||
tieBreaker: 'id',
|
||||
cursorKeys: getCursorKeys(normalizedSort)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getLibraryBrowseStrategy
|
||||
}
|
||||
29
test/server/utils/libraryBrowseCount.test.js
Normal file
29
test/server/utils/libraryBrowseCount.test.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
const chai = require('chai')
|
||||
const expect = chai.expect
|
||||
|
||||
const { loadBrowseCount } = require('../../../server/utils/queries/libraryBrowseCount')
|
||||
|
||||
describe('libraryBrowseCount', () => {
|
||||
it('returns deferred exact count metadata on first chunk', async () => {
|
||||
const payload = await loadBrowseCount({
|
||||
mode: 'deferred-exact',
|
||||
exactCountLoader: async () => 123
|
||||
})
|
||||
|
||||
expect(payload).to.deep.equal({ total: 123, isExact: true, isDeferred: true })
|
||||
})
|
||||
|
||||
it('skips exact count work on follow-up endless-scroll chunks', async () => {
|
||||
let called = false
|
||||
const payload = await loadBrowseCount({
|
||||
mode: 'skip',
|
||||
exactCountLoader: async () => {
|
||||
called = true
|
||||
return 123
|
||||
}
|
||||
})
|
||||
|
||||
expect(payload).to.deep.equal({ total: null, isExact: false, isDeferred: true })
|
||||
expect(called).to.equal(false)
|
||||
})
|
||||
})
|
||||
128
test/server/utils/libraryBrowseStrategy.test.js
Normal file
128
test/server/utils/libraryBrowseStrategy.test.js
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
const chai = require('chai')
|
||||
const expect = chai.expect
|
||||
|
||||
const { getLibraryBrowseStrategy } = require('../../../server/utils/queries/libraryBrowseStrategy')
|
||||
|
||||
describe('libraryBrowseStrategy', () => {
|
||||
it('uses keyset pagination for title browse', () => {
|
||||
const strategy = getLibraryBrowseStrategy({
|
||||
mediaType: 'book',
|
||||
sortBy: 'media.metadata.title',
|
||||
filterGroup: null,
|
||||
pageMode: 'endless'
|
||||
})
|
||||
|
||||
expect(strategy.paginationMode).to.equal('keyset')
|
||||
expect(strategy.countMode).to.equal('deferred-exact')
|
||||
expect(strategy.deepScrollAllowed).to.equal(true)
|
||||
expect(strategy.tieBreaker).to.equal('id')
|
||||
expect(strategy.cursorKeys).to.deep.equal(['titleIgnorePrefix', 'id'])
|
||||
})
|
||||
|
||||
it('falls back to offset for random sort', () => {
|
||||
const strategy = getLibraryBrowseStrategy({
|
||||
mediaType: 'book',
|
||||
sortBy: 'random',
|
||||
filterGroup: null,
|
||||
pageMode: 'endless'
|
||||
})
|
||||
|
||||
expect(strategy.paginationMode).to.equal('offset')
|
||||
expect(strategy.deepScrollAllowed).to.equal(false)
|
||||
})
|
||||
|
||||
it('uses keyset pagination for recently added browse', () => {
|
||||
const strategy = getLibraryBrowseStrategy({
|
||||
mediaType: 'book',
|
||||
sortBy: 'addedAt',
|
||||
filterGroup: 'recent',
|
||||
pageMode: 'endless'
|
||||
})
|
||||
|
||||
expect(strategy.family).to.equal('recent-browse')
|
||||
expect(strategy.paginationMode).to.equal('keyset')
|
||||
})
|
||||
|
||||
it('uses keyset pagination for progress browse', () => {
|
||||
const strategy = getLibraryBrowseStrategy({
|
||||
mediaType: 'book',
|
||||
sortBy: 'progress',
|
||||
filterGroup: 'progress',
|
||||
pageMode: 'endless'
|
||||
})
|
||||
|
||||
expect(strategy.family).to.equal('progress-browse')
|
||||
expect(strategy.paginationMode).to.equal('keyset')
|
||||
})
|
||||
|
||||
it('uses keyset pagination for updated browse', () => {
|
||||
const strategy = getLibraryBrowseStrategy({
|
||||
mediaType: 'book',
|
||||
sortBy: 'updatedAt',
|
||||
filterGroup: null,
|
||||
pageMode: 'endless'
|
||||
})
|
||||
|
||||
expect(strategy.family).to.equal('plain-browse')
|
||||
expect(strategy.paginationMode).to.equal('keyset')
|
||||
expect(strategy.cursorKeys).to.deep.equal(['updatedAt', 'id'])
|
||||
})
|
||||
|
||||
it('uses keyset pagination for deterministic author browse', () => {
|
||||
const strategy = getLibraryBrowseStrategy({
|
||||
mediaType: 'book',
|
||||
sortBy: 'media.metadata.authorName',
|
||||
filterGroup: null,
|
||||
pageMode: 'endless'
|
||||
})
|
||||
|
||||
expect(strategy.family).to.equal('author-browse')
|
||||
expect(strategy.paginationMode).to.equal('keyset')
|
||||
expect(strategy.cursorKeys).to.deep.equal(['authorNamesFirstLast', 'titleIgnorePrefix', 'id'])
|
||||
})
|
||||
|
||||
it('defines a filtered browse family for generic library filters', () => {
|
||||
const strategy = getLibraryBrowseStrategy({
|
||||
mediaType: 'book',
|
||||
sortBy: 'media.metadata.title',
|
||||
filterGroup: 'tags',
|
||||
pageMode: 'endless'
|
||||
})
|
||||
|
||||
expect(strategy.family).to.equal('filtered-browse')
|
||||
})
|
||||
|
||||
it('defines a series browse family', () => {
|
||||
const strategy = getLibraryBrowseStrategy({
|
||||
mediaType: 'book',
|
||||
sortBy: 'sequence',
|
||||
filterGroup: 'series',
|
||||
pageMode: 'endless'
|
||||
})
|
||||
|
||||
expect(strategy.family).to.equal('series-browse')
|
||||
})
|
||||
|
||||
it('defines a collapsed-series browse family', () => {
|
||||
const strategy = getLibraryBrowseStrategy({
|
||||
mediaType: 'book',
|
||||
sortBy: 'sequence',
|
||||
filterGroup: 'series',
|
||||
pageMode: 'endless',
|
||||
collapseseries: true
|
||||
})
|
||||
|
||||
expect(strategy.family).to.equal('collapsed-series-browse')
|
||||
})
|
||||
|
||||
it('defines a podcast browse family', () => {
|
||||
const strategy = getLibraryBrowseStrategy({
|
||||
mediaType: 'podcast',
|
||||
sortBy: 'media.metadata.title',
|
||||
filterGroup: null,
|
||||
pageMode: 'endless'
|
||||
})
|
||||
|
||||
expect(strategy.family).to.equal('podcast-browse')
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue