diff --git a/packages/frontend/src/scripts/scroll.ts b/packages/frontend/src/scripts/scroll.ts index b2b2c41dcf2d..7338de62b6b4 100644 --- a/packages/frontend/src/scripts/scroll.ts +++ b/packages/frontend/src/scripts/scroll.ts @@ -30,7 +30,7 @@ export function getScrollPosition(el: HTMLElement | null): number { export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false) { // とりあえず評価してみる - if (isTopVisible(el)) { + if (el.isConnected && isTopVisible(el)) { cb(); if (once) return null; } @@ -54,7 +54,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1 const container = getScrollContainer(el); // とりあえず評価してみる - if (isBottomVisible(el, tolerance, container)) { + if (el.isConnected && isBottomVisible(el, tolerance, container)) { cb(); if (once) return null; } diff --git a/packages/frontend/test/scroll.test.ts b/packages/frontend/test/scroll.test.ts new file mode 100644 index 000000000000..3fa740b11c08 --- /dev/null +++ b/packages/frontend/test/scroll.test.ts @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { describe, test, assert, afterEach } from 'vitest'; +import { Window } from 'happy-dom'; +import { onScrollBottom, onScrollTop } from '@/scripts/scroll'; + +describe('Scroll', () => { + describe('onScrollTop', () => { + test('Initial onScrollTop callback for connected elements', () => { + const { document } = new Window(); + const div = document.createElement('div'); + assert.strictEqual(div.scrollTop, 0); + + document.body.append(div); + + let called = false; + onScrollTop(div as any as HTMLElement, () => called = true); + + assert.ok(called); + }); + + test('No onScrollTop callback for disconnected elements', () => { + const { document } = new Window(); + const div = document.createElement('div'); + assert.strictEqual(div.scrollTop, 0); + + let called = false; + onScrollTop(div as any as HTMLElement, () => called = true); + + assert.ok(!called); + }); + }); + + describe('onScrollBottom', () => { + test('Initial onScrollBottom callback for connected elements', () => { + const { document } = new Window(); + const div = document.createElement('div'); + assert.strictEqual(div.scrollTop, 0); + (div as any).scrollHeight = 100; // happy-dom has no scrollHeight + + document.body.append(div); + + let called = false; + onScrollBottom(div as any as HTMLElement, () => called = true); + + assert.ok(called); + }); + + test('No onScrollBottom callback for disconnected elements', () => { + const { document } = new Window(); + const div = document.createElement('div'); + assert.strictEqual(div.scrollTop, 0); + (div as any).scrollHeight = 100; // happy-dom has no scrollHeight + + let called = false; + onScrollBottom(div as any as HTMLElement, () => called = true); + + assert.ok(!called); + }); + }); +});