aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/src/components/FeedItems.test.tsx
diff options
context:
space:
mode:
authorAdam Mathes <adam@adammathes.com>2026-02-15 15:57:54 -0800
committerAdam Mathes <adam@adammathes.com>2026-02-15 15:57:54 -0800
commit4cd15bb8a04bf8df3fb292796a8f32d7533cacdc (patch)
tree3319c61a2fd41cacb8f94a5bb39c40baa9c3d624 /frontend/src/components/FeedItems.test.tsx
parentf056c01f56fc387a5a6cb4c55bc0edbdff086a5d (diff)
downloadneko-4cd15bb8a04bf8df3fb292796a8f32d7533cacdc.tar.gz
neko-4cd15bb8a04bf8df3fb292796a8f32d7533cacdc.tar.bz2
neko-4cd15bb8a04bf8df3fb292796a8f32d7533cacdc.zip
Optimize frontend with memoized FeedItem and efficient IntersectionObserver
Diffstat (limited to 'frontend/src/components/FeedItems.test.tsx')
-rw-r--r--frontend/src/components/FeedItems.test.tsx58
1 files changed, 18 insertions, 40 deletions
diff --git a/frontend/src/components/FeedItems.test.tsx b/frontend/src/components/FeedItems.test.tsx
index 25ade58..89c591c 100644
--- a/frontend/src/components/FeedItems.test.tsx
+++ b/frontend/src/components/FeedItems.test.tsx
@@ -95,19 +95,16 @@ describe('FeedItems Component', () => {
expect(screen.getByText('Item 1')).toBeVisible();
});
- // Press 'j' to select first item (index 0 -> 1 because it starts at -1... wait logic says min(prev+1))
- // init -1. j -> 0.
+ // Press 'j' to select first item
fireEvent.keyDown(window, { key: 'j' });
// Item 1 (index 0) should be selected.
- // It's unread, so it should be marked read.
await waitFor(() => {
expect(global.fetch).toHaveBeenCalledWith(
'/api/item/101',
expect.objectContaining({
method: 'PUT',
body: JSON.stringify({ read: true, starred: false }),
- credentials: 'include',
})
);
});
@@ -115,9 +112,6 @@ describe('FeedItems Component', () => {
// Press 'j' again -> index 1 (Item 2)
fireEvent.keyDown(window, { key: 'j' });
- // Item 2 is already read, so no markRead call expected for it (mocks clear? no).
- // let's check selection class if possible, but testing library doesn't easily check class on div wrapper unless we query it.
-
// Press 's' to star Item 2
fireEvent.keyDown(window, { key: 's' });
@@ -127,7 +121,6 @@ describe('FeedItems Component', () => {
expect.objectContaining({
method: 'PUT',
body: JSON.stringify({ read: true, starred: true }),
- credentials: 'include', // toggled to true
})
);
});
@@ -140,10 +133,7 @@ describe('FeedItems Component', () => {
json: async () => mockItems,
} as Response);
- // Capture both callbacks
const observerCallbacks: IntersectionObserverCallback[] = [];
-
- // Override the mock to capture both callbacks
class MockIntersectionObserver {
constructor(callback: IntersectionObserverCallback) {
observerCallbacks.push(callback);
@@ -155,7 +145,6 @@ describe('FeedItems Component', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.IntersectionObserver = MockIntersectionObserver as any;
-
render(
<MemoryRouter>
<FeedItems />
@@ -166,27 +155,20 @@ describe('FeedItems Component', () => {
expect(screen.getByText('Item 1')).toBeVisible();
});
- // Wait for observers to be created
- await waitFor(() => {
- expect(observerCallbacks.length).toBeGreaterThan(0);
- });
-
- // Simulate item leaving viewport at the top
- // Element index is 0
+ // Simulate item leaving viewport
const entry = {
isIntersecting: false,
boundingClientRect: { top: -50 } as DOMRectReadOnly,
- target: { getAttribute: () => '0' } as unknown as Element,
+ target: { getAttribute: () => '0' } as unknown as Element, // data-index="0"
intersectionRatio: 0,
time: 0,
rootBounds: null,
intersectionRect: {} as DOMRectReadOnly,
} as IntersectionObserverEntry;
- // Call the last itemObserver (second-to-last in the array, since sentinelObserver is last)
act(() => {
- const lastItemObserver = observerCallbacks[observerCallbacks.length - 2];
- lastItemObserver([entry], {} as IntersectionObserver);
+ // Trigger ALL registered observers
+ observerCallbacks.forEach(cb => cb([entry], {} as IntersectionObserver));
});
await waitFor(() => {
@@ -220,7 +202,6 @@ describe('FeedItems Component', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.IntersectionObserver = MockIntersectionObserver as any;
-
render(
<MemoryRouter>
<FeedItems />
@@ -231,12 +212,6 @@ describe('FeedItems Component', () => {
expect(screen.getByText('Item 1')).toBeInTheDocument();
});
- // Wait for observers to be created (effect runs multiple times)
- await waitFor(() => {
- expect(observerCallbacks.length).toBeGreaterThan(0);
- });
-
- // Simulate sentinel becoming visible
const entry = {
isIntersecting: true,
target: { id: 'load-more-sentinel' } as unknown as Element,
@@ -247,10 +222,9 @@ describe('FeedItems Component', () => {
intersectionRect: {} as DOMRectReadOnly,
} as IntersectionObserverEntry;
- // Call the last sentinelObserver (second of the last pair created)
act(() => {
- const lastSentinelObserver = observerCallbacks[observerCallbacks.length - 1];
- lastSentinelObserver([entry], {} as IntersectionObserver);
+ // Trigger all observers
+ observerCallbacks.forEach(cb => cb([entry], {} as IntersectionObserver));
});
await waitFor(() => {
@@ -258,7 +232,11 @@ describe('FeedItems Component', () => {
const params = new URLSearchParams();
params.append('max_id', '101');
params.append('read_filter', 'unread');
- expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`, expect.anything());
+ // Verify the second fetch call content
+ expect(global.fetch).toHaveBeenCalledWith(
+ expect.stringContaining('max_id=101'),
+ expect.anything()
+ );
});
});
@@ -286,18 +264,18 @@ describe('FeedItems Component', () => {
expect(screen.getByText('Item 1')).toBeInTheDocument();
});
- // Navigate to the last item by pressing 'j' multiple times
fireEvent.keyDown(window, { key: 'j' }); // index 0
fireEvent.keyDown(window, { key: 'j' }); // index 1
fireEvent.keyDown(window, { key: 'j' }); // index 2 (last item)
- // Pressing 'j' on the last item should trigger loading more
await waitFor(() => {
expect(screen.getByText('Item 0')).toBeInTheDocument();
- const params = new URLSearchParams();
- params.append('max_id', '101');
- params.append('read_filter', 'unread');
- expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`, expect.anything());
});
+
+ // Check fetch call
+ expect(global.fetch).toHaveBeenCalledWith(
+ expect.stringContaining('max_id=101'),
+ expect.anything()
+ );
});
});