diff --git a/server/utils/queries/libraryItemsBookFilters.js b/server/utils/queries/libraryItemsBookFilters.js index 22e99c228..b8a4f89ed 100644 --- a/server/utils/queries/libraryItemsBookFilters.js +++ b/server/utils/queries/libraryItemsBookFilters.js @@ -504,6 +504,7 @@ module.exports = { if (filterGroup === 'series' && collapseseries) { collapseseries = false } + sortBy = sortBy || 'media.metadata.title' if (filterGroup !== 'series' && sortBy === 'sequence') { sortBy = 'media.metadata.title' } diff --git a/test/server/controllers/LibraryController.largeLibraryBrowse.test.js b/test/server/controllers/LibraryController.largeLibraryBrowse.test.js index 337db5e2d..a49f9e2c6 100644 --- a/test/server/controllers/LibraryController.largeLibraryBrowse.test.js +++ b/test/server/controllers/LibraryController.largeLibraryBrowse.test.js @@ -290,6 +290,40 @@ describe('LibraryController large-library browse contract', () => { expect(findAndCountAllStub.called).to.equal(false) }) + it('defaults omitted sort to deterministic title ordering for endless browse requests', async () => { + global.ServerSettings = { sortingIgnorePrefix: true } + + const countStub = sinon.stub(Database.bookModel, 'count').resolves(10) + const findAndCountAllStub = sinon.stub(Database.bookModel, 'findAndCountAll').resolves({ rows: [], count: 10 }) + const findAllStub = sinon.stub(Database.bookModel, 'findAll').resolves([ + { id: 'book-1', title: 'Alpha', libraryItem: { id: 'item-1', titleIgnorePrefix: 'Alpha', dataValues: { titleIgnorePrefix: 'Alpha' } } } + ]) + + const result = await libraryItemsBookFilters.getFilteredLibraryItems( + 'lib_1', + { id: 'user_1', canAccessExplicitContent: true, accessAllTags: true }, + null, + null, + null, + false, + false, + [], + 20, + 0, + false, + { pageMode: 'endless' } + ) + + expect(result.paginationMode).to.equal('keyset') + expect(findAllStub.calledOnce).to.equal(true) + expect(findAndCountAllStub.called).to.equal(false) + expect(countStub.calledOnce).to.equal(true) + expect(findAllStub.firstCall.args[0].order).to.have.length(2) + expect(findAllStub.firstCall.args[0].order[0][0].val || findAllStub.firstCall.args[0].order[0][0]).to.include('titleIgnorePrefix') + expect(findAllStub.firstCall.args[0].order[1][0].val || findAllStub.firstCall.args[0].order[1][0]).to.include('libraryItem') + expect(findAllStub.firstCall.args[0].order[1][0].val || findAllStub.firstCall.args[0].order[1][0]).to.include('id') + }) + it('uses offset fallback for progress browse requests with null-valued sort fields', async () => { const countStub = sinon.stub(Database.bookModel, 'count').resolves(123) const findAllStub = sinon.stub(Database.bookModel, 'findAll').resolves([])