Skip to content

Instantly share code, notes, and snippets.

@gynekolog
Last active March 26, 2024 16:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gynekolog/9807f68819fd9732a129912509d58c73 to your computer and use it in GitHub Desktop.
Save gynekolog/9807f68819fd9732a129912509d58c73 to your computer and use it in GitHub Desktop.
Pagination generator
// The code is based on https://gist.github.com/kottenator/9d936eb3e4e3c3e02598#gistcomment-3238804
type PageItem = number | "...";
export const getRange = (start: number, end: number): PageItem[] => {
if (end < start) throw Error(`End number must be higher then start number: start ${start}, end ${start}`);
const rangeLength = end - start + 1;
return Array(rangeLength)
.fill(0)
.map((_, i) => i + start);
};
const clamp = (value: number, lower: number, upper: number) => Math.min(Math.max(value, lower), upper);
export const calculatePages = (currentPage: number, pageCount: number, size: number): PageItem[] => {
if (pageCount < 1) {
console.warn("Page count has negative value. Returning empty array.");
return [];
}
if (currentPage < 1) {
console.warn("Current page has negative value. Current page will be set to 1");
currentPage = 1;
}
if (currentPage > pageCount) {
console.warn("Current page is higher than page count. Current page will be set to page count:", pageCount);
currentPage = pageCount;
}
if (size % 2 === 0) {
console.warn("The size must be odd. The size will be increased by 1");
size += 1;
}
if (size < 5) {
console.warn("The minimum size is 5. The size will be increased to 5");
size = 5;
}
const offset = (size - 1) / 2;
const shouldAddDots = pageCount > size;
const rangeConfig = {
start: clamp(currentPage - offset, 1, shouldAddDots ? pageCount - size + 1 : 1),
end: clamp(currentPage + offset, size, pageCount),
};
const pages = getRange(rangeConfig.start, rangeConfig.end);
if (shouldAddDots && pages[0] !== 1) {
pages[0] = 1;
pages[1] = "...";
}
if (shouldAddDots && pages[pages.length - 1] !== pageCount) {
pages[pages.length - 1] = pageCount;
pages[pages.length - 2] = "...";
}
return pages;
};
@gynekolog
Copy link
Author

  • test:
describe("get range", () => {
  test("when end parameter is higher than start", () => {
    expect(getRange(-4, 2)).toStrictEqual([-4, -3, -2, -1, 0, 1, 2]);
    expect(getRange(-1, 1)).toStrictEqual([-1, 0, 1]);
    expect(getRange(0, 1)).toStrictEqual([0, 1]);
    expect(getRange(1, 3)).toStrictEqual([1, 2, 3]);
    expect(getRange(100, 104)).toStrictEqual([100, 101, 102, 103, 104]);
    expect(getRange(1, 1)).toStrictEqual([1]);
  });

  test("when paramters are same", () => {
    expect(getRange(-10, -10)).toStrictEqual([-10]);
    expect(getRange(-1, -1)).toStrictEqual([-1]);
    expect(getRange(0, 0)).toStrictEqual([0]);
    expect(getRange(10, 10)).toStrictEqual([10]);
  });

  test("when end parameter is lower than start", () => {
    const expected = /End number must be higher then start number/;
    expect(() => getRange(1, -1)).toThrowError(expected);
    expect(() => getRange(0, -100)).toThrowError(expected);
    expect(() => getRange(200, 100)).toThrowError(expected);
  });
});

describe("calculate pages", () => {
  test("current page is same as page count", () => {
    expect(calculatePages(0, 0, 7)).toStrictEqual([]);
    expect(calculatePages(1, 1, 7)).toStrictEqual([1]);
  });

  test("current page is lower than 1", () => {
    expect(calculatePages(-20, 0, 7)).toStrictEqual([]);
    expect(calculatePages(-20, 1, 7)).toStrictEqual([1]);
    expect(calculatePages(-20, 2, 7)).toStrictEqual([1, 2]);
  });

  test("current page is higher than page count", () => {
    expect(calculatePages(-2, 1, 7)).toStrictEqual([1]);
    expect(calculatePages(2, 1, 7)).toStrictEqual([1]);
    expect(calculatePages(20, 5, 7)).toStrictEqual([1, 2, 3, 4, 5]);
    expect(calculatePages(20, 4, 5)).toStrictEqual([1, 2, 3, 4]);
  });

  test("page count is negative value", () => {
    expect(calculatePages(-2, -5, 7)).toStrictEqual([]);
    expect(calculatePages(-20, -5, 7)).toStrictEqual([]);
    expect(calculatePages(20, -5, 7)).toStrictEqual([]);
  });

  test("size is lower then 5", () => {
    expect(calculatePages(1, 1, 4)).toStrictEqual([1]);
    expect(calculatePages(1, 2, 0)).toStrictEqual([1, 2]);
    expect(calculatePages(1, 3, -4)).toStrictEqual([1, 2, 3]);
    expect(calculatePages(1, 7, -10)).toStrictEqual([1, 2, 3, "...", 7]);
  });

  test("size is not even", () => {
    expect(calculatePages(1, 7, 6)).toStrictEqual([1, 2, 3, 4, 5, 6, 7]);
    expect(calculatePages(1, 7, 8)).toStrictEqual([1, 2, 3, 4, 5, 6, 7]);
    expect(calculatePages(1, 10, 8)).toStrictEqual([1, 2, 3, 4, 5, 6, 7, "...", 10]);
  });

  test("page count is lower than step", () => {
    expect(calculatePages(1, 1, 7)).toStrictEqual([1]);
    expect(calculatePages(1, 2, 7)).toStrictEqual([1, 2]);
    expect(calculatePages(1, 3, 7)).toStrictEqual([1, 2, 3]);
    expect(calculatePages(1, 4, 7)).toStrictEqual([1, 2, 3, 4]);
    expect(calculatePages(1, 5, 7)).toStrictEqual([1, 2, 3, 4, 5]);
    expect(calculatePages(1, 6, 7)).toStrictEqual([1, 2, 3, 4, 5, 6]);
    expect(calculatePages(1, 7, 7)).toStrictEqual([1, 2, 3, 4, 5, 6, 7]);
    expect(calculatePages(1, 7, 5)).toStrictEqual([1, 2, 3, "...", 7]);
    expect(calculatePages(1, 5, 5)).toStrictEqual([1, 2, 3, 4, 5]);
  });

  describe("page count is higher than step (max size 5)", () => {
    const runner = test.each([
      [1, [1, 2, 3, "...", 20]],
      [2, [1, 2, 3, "...", 20]],
      [3, [1, 2, 3, "...", 20]],
      [4, [1, "...", 4, "...", 20]],
      [5, [1, "...", 5, "...", 20]],
      [6, [1, "...", 6, "...", 20]],
      [7, [1, "...", 7, "...", 20]],
      [8, [1, "...", 8, "...", 20]],
      [9, [1, "...", 9, "...", 20]],
      [10, [1, "...", 10, "...", 20]],
      [11, [1, "...", 11, "...", 20]],
      [12, [1, "...", 12, "...", 20]],
      [13, [1, "...", 13, "...", 20]],
      [14, [1, "...", 14, "...", 20]],
      [15, [1, "...", 15, "...", 20]],
      [16, [1, "...", 16, "...", 20]],
      [17, [1, "...", 17, "...", 20]],
      [18, [1, "...", 18, 19, 20]],
      [19, [1, "...", 18, 19, 20]],
      [20, [1, "...", 18, 19, 20]],
    ]);

    runner("pagination(%i, 20)", (index, expected) => {
      expect(calculatePages(index, 20, 5)).toStrictEqual(expected);
    });
  });

  describe("page count is higher than step (max size 7)", () => {
    const runner = test.each([
      [1, [1, 2, 3, 4, 5, "...", 20]],
      [2, [1, 2, 3, 4, 5, "...", 20]],
      [3, [1, 2, 3, 4, 5, "...", 20]],
      [4, [1, 2, 3, 4, 5, "...", 20]],
      [5, [1, "...", 4, 5, 6, "...", 20]],
      [6, [1, "...", 5, 6, 7, "...", 20]],
      [7, [1, "...", 6, 7, 8, "...", 20]],
      [8, [1, "...", 7, 8, 9, "...", 20]],
      [9, [1, "...", 8, 9, 10, "...", 20]],
      [10, [1, "...", 9, 10, 11, "...", 20]],
      [11, [1, "...", 10, 11, 12, "...", 20]],
      [12, [1, "...", 11, 12, 13, "...", 20]],
      [13, [1, "...", 12, 13, 14, "...", 20]],
      [14, [1, "...", 13, 14, 15, "...", 20]],
      [15, [1, "...", 14, 15, 16, "...", 20]],
      [16, [1, "...", 15, 16, 17, "...", 20]],
      [17, [1, "...", 16, 17, 18, 19, 20]],
      [18, [1, "...", 16, 17, 18, 19, 20]],
      [19, [1, "...", 16, 17, 18, 19, 20]],
      [20, [1, "...", 16, 17, 18, 19, 20]],
    ]);

    runner("pagination(%i, 20)", (index, expected) => {
      expect(calculatePages(index, 20, 7)).toStrictEqual(expected);
    });
  });

  describe("page count is higher than step (max size 9)", () => {
    const runner = test.each([
      [1, [1, 2, 3, 4, 5, 6, 7, "...", 20]],
      [2, [1, 2, 3, 4, 5, 6, 7, "...", 20]],
      [3, [1, 2, 3, 4, 5, 6, 7, "...", 20]],
      [4, [1, 2, 3, 4, 5, 6, 7, "...", 20]],
      [5, [1, 2, 3, 4, 5, 6, 7, "...", 20]],
      [6, [1, "...", 4, 5, 6, 7, 8, "...", 20]],
      [7, [1, "...", 5, 6, 7, 8, 9, "...", 20]],
      [8, [1, "...", 6, 7, 8, 9, 10, "...", 20]],
      [9, [1, "...", 7, 8, 9, 10, 11, "...", 20]],
      [10, [1, "...", 8, 9, 10, 11, 12, "...", 20]],
      [11, [1, "...", 9, 10, 11, 12, 13, "...", 20]],
      [12, [1, "...", 10, 11, 12, 13, 14, "...", 20]],
      [13, [1, "...", 11, 12, 13, 14, 15, "...", 20]],
      [14, [1, "...", 12, 13, 14, 15, 16, "...", 20]],
      [15, [1, "...", 13, 14, 15, 16, 17, "...", 20]],
      [16, [1, "...", 14, 15, 16, 17, 18, 19, 20]],
      [17, [1, "...", 14, 15, 16, 17, 18, 19, 20]],
      [18, [1, "...", 14, 15, 16, 17, 18, 19, 20]],
      [19, [1, "...", 14, 15, 16, 17, 18, 19, 20]],
      [20, [1, "...", 14, 15, 16, 17, 18, 19, 20]],
    ]);

    runner("pagination(%i, 20)", (index, expected) => {
      expect(calculatePages(index, 20, 9)).toStrictEqual(expected);
    });
  });

  test("maintains performance", () => {
    const t0 = performance.now();
    calculatePages(1, 99999999999, 7);
    const t1 = performance.now();

    expect(t1 - t0).toBeLessThan(1);
  });
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment