import { describe, it, expect, vi, beforeEach } from 'vitest'; import { readFileSync } from 'fs'; import { resolve } from 'path'; import { store } from './store'; import { renderLayout, renderItems } from './main'; import { apiFetch } from './api'; // Mock api vi.mock('./api', () => ({ apiFetch: vi.fn() })); // Mock IntersectionObserver class MockIntersectionObserver { observe = vi.fn(); unobserve = vi.fn(); disconnect = vi.fn(); } vi.stubGlobal('IntersectionObserver', MockIntersectionObserver); // Read the main stylesheet once for CSS rule assertions const cssContent = readFileSync(resolve(__dirname, 'style.css'), 'utf-8'); describe('Mobile horizontal overflow prevention', () => { beforeEach(() => { document.body.innerHTML = '
'; vi.stubGlobal('location', { href: 'http://localhost/v3/', pathname: '/v3/', search: '', assign: vi.fn(), replace: vi.fn() }); vi.stubGlobal('history', { pushState: vi.fn() }); Element.prototype.scrollIntoView = vi.fn(); vi.clearAllMocks(); store.setFeeds([]); store.setTags([]); store.setItems([]); vi.mocked(apiFetch).mockResolvedValue({ ok: true, status: 200, json: async () => [] } as Response); }); describe('CSS containment rules', () => { it('.item-description should have overflow-x hidden to contain wide RSS content', () => { // .item-description must prevent wide child elements (tables, iframes) // from causing horizontal viewport overflow const itemDescBlock = cssContent.match( /\.item-description\s*\{[^}]*\}/g ); expect(itemDescBlock).not.toBeNull(); const mainBlock = itemDescBlock!.find( block => !block.includes('img') && !block.includes('video') && !block.includes('pre') && !block.includes(' a') ); expect(mainBlock).toBeDefined(); expect(mainBlock).toMatch(/overflow-x:\s*hidden/); }); it('.item-description should constrain tables with max-width', () => { // RSS feeds commonly contain| Very wide table from RSS |