aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--frontend-vanilla/src/main.test.ts2
-rw-r--r--frontend-vanilla/src/main.ts4
-rw-r--r--frontend-vanilla/src/regression.test.ts59
-rw-r--r--frontend/src/components/FeedItems.test.tsx6
-rw-r--r--frontend/src/components/FeedItems.tsx4
5 files changed, 54 insertions, 21 deletions
diff --git a/frontend-vanilla/src/main.test.ts b/frontend-vanilla/src/main.test.ts
index da2c41c..eb9e615 100644
--- a/frontend-vanilla/src/main.test.ts
+++ b/frontend-vanilla/src/main.test.ts
@@ -287,7 +287,7 @@ describe('main application logic', () => {
if (itemEl) {
itemEl.getBoundingClientRect = vi.fn(() => ({
- top: -50, bottom: 50, height: 100, left: 0, right: 0, width: 0, x: 0, y: 0, toJSON: () => { }
+ top: -150, bottom: -50, height: 100, left: 0, right: 0, width: 0, x: 0, y: 0, toJSON: () => { }
}));
}
diff --git a/frontend-vanilla/src/main.ts b/frontend-vanilla/src/main.ts
index a8606e3..02e4188 100644
--- a/frontend-vanilla/src/main.ts
+++ b/frontend-vanilla/src/main.ts
@@ -291,8 +291,8 @@ export function renderItems() {
const el = document.querySelector(`.feed-item[data-id="${item._id}"]`);
if (el) {
const rect = el.getBoundingClientRect();
- // Mark as read if the top of the item is above the top of the container
- if (rect.top < containerRect.top) {
+ // Mark as read if the bottom of the item is above the top of the container
+ if (rect.bottom < containerRect.top) {
updateItem(item._id, { read: true });
}
}
diff --git a/frontend-vanilla/src/regression.test.ts b/frontend-vanilla/src/regression.test.ts
index 94eb51e..a0b13d5 100644
--- a/frontend-vanilla/src/regression.test.ts
+++ b/frontend-vanilla/src/regression.test.ts
@@ -39,7 +39,7 @@ describe('Scroll-to-Read Regression Tests', () => {
vi.useRealTimers();
});
- it('should mark item as read when existing in store and scrolled past', async () => {
+ it('should mark item as read when existing in store and fully scrolled past (bottom < container top)', async () => {
vi.useRealTimers();
const mockItem = {
_id: 999,
@@ -51,16 +51,9 @@ describe('Scroll-to-Read Regression Tests', () => {
store.setItems([mockItem]);
- // Manually trigger the render logic that sets up the listener (implied by renderItems/init in real app)
- // Here we can't easily trigger the exact closure in main.ts without fully initializing it.
- // However, we can simulate the "check logic" if we extract it or replicate the test conditions
- // that the previous main.test.ts used.
-
- // Since we are mocking the scroll behavior on the DOM element which main.ts attaches to:
+ // Manual setup
const mainContent = document.getElementById('main-content');
expect(mainContent).not.toBeNull();
-
- // We need to invoke renderItems to attach the listener
renderItems();
// Mock getBoundingClientRect for container
@@ -70,18 +63,18 @@ describe('Scroll-to-Read Regression Tests', () => {
}));
}
- // Mock getBoundingClientRect for item (scrolled past top: -50)
- // renderItems creates the DOM elements based on store
+ // Mock getBoundingClientRect for item (fully scrolled past: bottom < 0)
+ // Item top: -150, Item bottom: -50. Container Top: 0.
+ // -50 < 0, so should mark as read.
const itemEl = document.querySelector(`.feed-item[data-id="999"]`);
expect(itemEl).not.toBeNull();
if (itemEl) {
itemEl.getBoundingClientRect = vi.fn(() => ({
- top: -50, bottom: 50, height: 100, left: 0, right: 0, width: 0, x: 0, y: 0, toJSON: () => { }
+ top: -150, bottom: -50, height: 100, left: 0, right: 0, width: 0, x: 0, y: 0, toJSON: () => { }
}));
}
- // Prepare API mock
vi.mocked(apiFetch).mockResolvedValue({ ok: true } as Response);
// Trigger scroll event
@@ -97,7 +90,45 @@ describe('Scroll-to-Read Regression Tests', () => {
}));
});
- it('should NOT mark item as read if NOT scrolled past', async () => {
+ it('should NOT mark item as read if only partially scrolled past (top < container top but bottom > container top)', async () => {
+ vi.useRealTimers();
+ const mockItem = {
+ _id: 777,
+ title: 'Partial Test Item',
+ read: false,
+ url: 'http://example.com/partial',
+ publish_date: '2023-01-01'
+ } as any;
+
+ store.setItems([mockItem]);
+ renderItems();
+
+ const mainContent = document.getElementById('main-content');
+ if (mainContent) {
+ mainContent.getBoundingClientRect = vi.fn(() => ({
+ top: 0, bottom: 800, height: 800, left: 0, right: 0, width: 0, x: 0, y: 0, toJSON: () => { }
+ }));
+ }
+
+ // Mock getBoundingClientRect for item (partially scrolled past: top < 0, bottom > 0)
+ // Item top: -50, Item bottom: 50. Container Top: 0.
+ // 50 is NOT < 0, so should NOT mark as read.
+ const itemEl = document.querySelector(`.feed-item[data-id="777"]`);
+ if (itemEl) {
+ itemEl.getBoundingClientRect = vi.fn(() => ({
+ top: -50, bottom: 50, height: 100, left: 0, right: 0, width: 0, x: 0, y: 0, toJSON: () => { }
+ }));
+ }
+
+ vi.mocked(apiFetch).mockResolvedValue({ ok: true } as Response);
+
+ mainContent?.dispatchEvent(new Event('scroll'));
+ await new Promise(resolve => setTimeout(resolve, 300));
+
+ expect(apiFetch).not.toHaveBeenCalledWith(expect.stringContaining('/api/item/777'), expect.anything());
+ });
+
+ it('should NOT mark item as read if NOT scrolled past (item below container top)', async () => {
vi.useRealTimers();
const mockItem = {
_id: 888,
diff --git a/frontend/src/components/FeedItems.test.tsx b/frontend/src/components/FeedItems.test.tsx
index ad8bf4f..a1ec761 100644
--- a/frontend/src/components/FeedItems.test.tsx
+++ b/frontend/src/components/FeedItems.test.tsx
@@ -96,7 +96,9 @@ describe('FeedItems Component', () => {
});
// Press 'j' to select first item
- fireEvent.keyDown(window, { key: 'j' });
+ await act(async () => {
+ fireEvent.keyDown(window, { key: 'j' });
+ });
// Item 1 (index 0) should be selected.
await waitFor(() => {
@@ -150,7 +152,7 @@ describe('FeedItems Component', () => {
if (this.id && this.id.startsWith('item-')) {
// Item top is -50 (above container top 0)
return {
- top: -50, bottom: 50, height: 100, left: 0, right: 1000, width: 1000, x: 0, y: 0,
+ top: -150, bottom: -50, height: 100, left: 0, right: 1000, width: 1000, x: 0, y: 0,
toJSON: () => { }
} as DOMRect;
}
diff --git a/frontend/src/components/FeedItems.tsx b/frontend/src/components/FeedItems.tsx
index e2df011..e38850a 100644
--- a/frontend/src/components/FeedItems.tsx
+++ b/frontend/src/components/FeedItems.tsx
@@ -216,8 +216,8 @@ export default function FeedItems() {
const rect = el.getBoundingClientRect();
- // Mark as read if the top of the item is above the top of the container
- if (rect.top < containerRect.top) {
+ // Mark as read if the bottom of the item is above the top of the container
+ if (rect.bottom < containerRect.top) {
markAsRead(item);
}
});