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
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ border-radius: 128px;
- The Admin Tagging Status table shows what is being looked up right now.
- Sorting by a tag column outside the table view (cover cards, OPDS feeds)
no longer errors.
- Reading Bottom to Top jumped to the wrong page. The page list runs
backwards in that direction, so opening a comic, changing pages from the
toolbar or following a link landed on the mirrored page — page one showed
the last page. Top to Bottom was never affected.

## v2.2.11

Expand Down
8 changes: 7 additions & 1 deletion frontend/src/components/reader/pager/pager-vertical.vue
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,13 @@ export default {
this.programmaticScroll = true;
const vs = this.$refs.verticalScroll;
if (vs) {
vs.scrollToIndex(page);
/*
* ``items`` runs backwards for bottom-to-top reading, so a page
* number is not its own index there: page 0 is the last item.
* Look the page up in ``items`` rather than repeating the
* reversal, so this keeps following however that list is built.
*/
vs.scrollToIndex(this.items.indexOf(page));
} else {
console.debug("Can't find verticalScroll component.");
}
Expand Down
99 changes: 99 additions & 0 deletions frontend/tests/unit/pager-vertical.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Tests for ``pager-vertical.vue`` page-to-index mapping.
*
* The vertical pager feeds ``v-virtual-scroll`` a list of page numbers, and
* reverses that list for bottom-to-top reading. ``scrollToIndex`` takes an
* index into that list, so a page number is only its own index while
* reading top-to-bottom. Passing the page number straight through sent
* bottom-to-top readers to the mirrored page — opening at page 0 landed on
* the last page.
*/
import { createTestingPinia } from "@pinia/testing";
import { mount } from "@vue/test-utils";
import { afterEach, describe, expect, test, vi } from "vitest";

import PagerVertical from "@/components/reader/pager/pager-vertical.vue";
import vuetify from "@/plugins/vuetify";

const PK = 7;
const MAX_PAGE = 9;

let wrappers = [];

function mountPager(readingDirection) {
const scrollToIndex = vi.fn();
const pinia = createTestingPinia({
// getBookSettings has to really run to return the seeded settings.
stubActions: false,
initialState: {
reader: {
page: 0,
// Seeded settings are returned as-is by getBookSettings.
bookSettings: {
[PK]: {
readingDirection,
isVertical: true,
isReadInReverse: readingDirection === "btt",
},
},
},
},
});
const wrapper = mount(PagerVertical, {
props: { book: { pk: PK, maxPage: MAX_PAGE } },
global: {
plugins: [pinia, vuetify],
stubs: {
ScaleForScroll: { template: "<div><slot /></div>" },
// The pager reaches the scroller through the ref, so the spy has
// to live on the stub the ref resolves to.
VVirtualScroll: { template: "<div />", methods: { scrollToIndex } },
},
},
});
wrappers.push(wrapper);
return { wrapper, scrollToIndex };
}

afterEach(() => {
for (const wrapper of wrappers) {
wrapper.unmount();
}
wrappers = [];
});

describe("PagerVertical — scrolling to a page", () => {
test("top to bottom scrolls to the page's own index", () => {
const { wrapper, scrollToIndex } = mountPager("ttb");

wrapper.vm.scrollToPage(3);

expect(wrapper.vm.items[3]).toBe(3);
expect(scrollToIndex).toHaveBeenCalledWith(3);
});

test("bottom to top scrolls to the page's mirrored index", () => {
const { wrapper, scrollToIndex } = mountPager("btt");

wrapper.vm.scrollToPage(3);

expect(wrapper.vm.items[6]).toBe(3);
expect(scrollToIndex).toHaveBeenCalledWith(6);
});

test("bottom to top opens page 0 at the end of the list", () => {
const { wrapper, scrollToIndex } = mountPager("btt");

wrapper.vm.scrollToPage(0);

expect(scrollToIndex).toHaveBeenCalledWith(MAX_PAGE);
});

test("bottom to top reaches the last page at the top of the list", () => {
const { wrapper, scrollToIndex } = mountPager("btt");

wrapper.vm.scrollToPage(MAX_PAGE);

expect(scrollToIndex).toHaveBeenCalledWith(0);
});
});