From afa87af01c79a9baa539f2992d32154d2a4739bd Mon Sep 17 00:00:00 2001 From: Adam Mathes Date: Sat, 14 Feb 2026 14:46:37 -0800 Subject: task: delete vanilla js prototype\n\n- Removed vanilla/ directory and web/dist/vanilla directory\n- Updated Makefile, Dockerfile, and CI workflow to remove vanilla references\n- Cleaned up web/web.go to remove vanilla embed and routes\n- Verified build and tests pass\n\nCloses NK-2tcnmq --- vanilla/app.test.js | 291 ---------------------------------------------------- 1 file changed, 291 deletions(-) delete mode 100644 vanilla/app.test.js (limited to 'vanilla/app.test.js') diff --git a/vanilla/app.test.js b/vanilla/app.test.js deleted file mode 100644 index 32d09de..0000000 --- a/vanilla/app.test.js +++ /dev/null @@ -1,291 +0,0 @@ -// @vitest-environment jsdom -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { fetchFeeds, fetchItems, renderFeeds, renderItems, toggleStar, toggleRead } from './app.js'; - -// Mock fetch -const fetchMock = vi.fn(); -global.fetch = fetchMock; - -describe('Vanilla JS App', () => { - beforeEach(() => { - document.body.innerHTML = ` -
- -
-
-

All Items

-
-
-
-
- `; - fetchMock.mockReset(); - }); - - describe('fetchFeeds', () => { - it('should fetch feeds and render them', async () => { - const mockFeeds = [{ id: 1, title: 'Test Feed', url: 'http://example.com' }]; - fetchMock.mockResolvedValue({ - ok: true, - json: async () => mockFeeds, - }); - - await fetchFeeds(); - - expect(fetchMock).toHaveBeenCalledWith('/api/feed/'); - const feedItems = document.querySelectorAll('.feed-item'); - // "All Items", "Unread Items", "Starred Items", plus 1 feed = 4 items - expect(feedItems.length).toBe(4); - expect(feedItems[3].textContent).toBe('Test Feed'); - }); - - it('should handle errors gracefully', async () => { - fetchMock.mockRejectedValue(new Error('Network error')); - await expect(fetchFeeds()).rejects.toThrow('Network error'); - expect(document.getElementById('feeds-nav').innerHTML).toContain('Error loading feeds'); - }); - }); - - describe('fetchItems', () => { - it('should fetch items and render them', async () => { - const mockItems = [{ - id: 101, - title: 'Item 1', - url: 'http://example.com/1', - feed: { title: 'Feed A' }, - starred: false, - read: false - }]; - fetchMock.mockResolvedValue({ - ok: true, - json: async () => mockItems, - }); - - await fetchItems(); - - expect(fetchMock).toHaveBeenCalledWith('/api/stream/'); - const entries = document.querySelectorAll('.entry'); - expect(entries.length).toBe(1); - expect(entries[0].querySelector('.entry-title').textContent).toBe('Item 1'); - }); - - it('should handle filters', async () => { - fetchMock.mockResolvedValue({ ok: true, json: async () => [] }); - - await fetchItems(123, 'unread', 'query'); - - const expectedUrl = '/api/stream/?feed_id=123&read_filter=unread&q=query'; - expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('feed_id=123')); - expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('read_filter=unread')); - expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('q=query')); - }); - }); - - describe('renderFeeds', () => { - it('should render system feeds and user feeds', async () => { - const feeds = [ - { id: 1, title: 'Feed 1', url: 'u1' }, - { id: 2, title: 'Feed 2', url: 'u2' } - ]; - // Mock fetch for the click handler - fetchMock.mockResolvedValue({ ok: true, json: async () => [] }); - - renderFeeds(feeds); - - const items = document.querySelectorAll('.feed-item'); - expect(items.length).toBe(5); // All, Unread, Starred, Feed 1, Feed 2 - - // Click handler test: All Items - items[0].click(); - expect(items[0].classList.contains('active')).toBe(true); - expect(document.getElementById('feed-title').textContent).toBe('All Items'); - expect(fetchMock).toHaveBeenCalledWith('/api/stream/'); - - // Click handler test: Unread Items - items[1].click(); - expect(items[1].classList.contains('active')).toBe(true); - expect(document.getElementById('feed-title').textContent).toBe('Unread Items'); - expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('read_filter=unread')); - - // Click handler test: Starred Items - items[2].click(); - expect(items[2].classList.contains('active')).toBe(true); - expect(document.getElementById('feed-title').textContent).toBe('Starred Items'); - expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('starred=true')); - - // Click handler test: Specific Feed - items[3].click(); - expect(items[3].classList.contains('active')).toBe(true); - expect(document.getElementById('feed-title').textContent).toBe('Feed 1'); - expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('feed_id=1')); - - // Wait for async operations to complete to avoid unhandled rejections - await new Promise(resolve => setTimeout(resolve, 0)); - }); - }); - - describe('renderItems', () => { - it('should render "No items found" if empty', () => { - renderItems([]); - expect(document.getElementById('entries-list').innerHTML).toContain('No items found'); - }); - - it('should render items with correct controls', () => { - const items = [{ - id: 1, - title: 'Test', - url: 'http://test.com', - starred: true, - read: false, - feed: { title: 'Feed' }, - created_at: new Date().toISOString() - }]; - renderItems(items); - - const starBtn = document.querySelector('.btn-star'); - expect(starBtn.textContent).toBe('★'); - expect(starBtn.classList.contains('active')).toBe(true); - - const readBtn = document.querySelector('.btn-read'); - expect(readBtn.textContent).toBe('Mark Read'); - expect(readBtn.classList.contains('unread')).toBe(true); - }); - }); - - describe('Interaction Toggles', () => { - let btn; - beforeEach(() => { - btn = document.createElement('button'); - document.body.appendChild(btn); - }); - afterEach(() => { - if (btn) btn.remove(); - }); - - it('should toggle star status', async () => { - fetchMock.mockResolvedValue({ ok: true }); - - const newStatus = await toggleStar(1, false, btn); - - expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/item/1'), expect.objectContaining({ - method: 'PUT', - body: JSON.stringify({ id: 1, starred: true }) - })); - expect(newStatus).toBe(true); - expect(btn.textContent).toBe('★'); - }); - - it('should toggle read status', async () => { - fetchMock.mockResolvedValue({ ok: true }); - - // Setup DOM for title dimming - const header = document.createElement('div'); - header.className = 'entry-header'; - const title = document.createElement('a'); - title.className = 'entry-title'; - header.appendChild(btn); // btn inside header - header.appendChild(title); - document.body.appendChild(header); - - const newStatus = await toggleRead(1, false, btn); - - expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('/api/item/1'), expect.objectContaining({ - method: 'PUT', - body: JSON.stringify({ id: 1, read: true }) - })); - expect(newStatus).toBe(true); - expect(title.classList.contains('read')).toBe(true); - }); - - }); - - describe('Error Handling', () => { - it('fetchItems should handle existing list element error', async () => { - fetchMock.mockRejectedValue(new Error('Fetch failed')); - await expect(fetchItems()).rejects.toThrow('Fetch failed'); - expect(document.getElementById('entries-list').innerHTML).toContain('Error loading items'); - }); - }); - - describe('init', () => { - it('should initialize app if elements exist', async () => { - // Mock fetch for the init calls - fetchMock.mockResolvedValue({ ok: true, json: async () => [] }); - - const addEventListenerSpy = vi.spyOn(document.getElementById('search-input'), 'addEventListener'); - // init is already imported - const { init } = await import('./app.js'); - // Reset mocks - fetchMock.mockClear(); - - init(); - - expect(fetchMock).toHaveBeenCalledTimes(2); // fetchFeeds + fetchItems - expect(addEventListenerSpy).toHaveBeenCalledWith('keypress', expect.any(Function)); - - // Wait for async operations to complete - await new Promise(resolve => setTimeout(resolve, 0)); - }); - - it('should do nothing if feeds-nav missing', async () => { - document.body.innerHTML = ''; // Clear DOM - fetchMock.mockClear(); - const { init } = await import('./app.js'); - - init(); - - expect(fetchMock).not.toHaveBeenCalled(); - }); - }); - - describe('Search Interaction', () => { - it('should trigger search on Enter', async () => { - // Mock fetch for the init calls & search - fetchMock.mockResolvedValue({ ok: true, json: async () => [] }); - - // Re-setup DOM and Init - const { init } = await import('./app.js'); - init(); - fetchMock.mockClear(); - - const searchInput = document.getElementById('search-input'); - searchInput.value = 'test query'; - - // Create Enter keypress event - const event = new KeyboardEvent('keypress', { key: 'Enter' }); - searchInput.dispatchEvent(event); - - expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('q=test+query')); - expect(document.getElementById('feed-title').textContent).toBe('Search: test query'); - - // Wait for async operations to complete - await new Promise(resolve => setTimeout(resolve, 0)); - }); - - it('should ignore empty search', async () => { - // Mock fetch for the init calls - fetchMock.mockResolvedValue({ ok: true, json: async () => [] }); - - const { init } = await import('./app.js'); - init(); - fetchMock.mockClear(); - - const searchInput = document.getElementById('search-input'); - searchInput.value = ' '; - - const event = new KeyboardEvent('keypress', { key: 'Enter' }); - searchInput.dispatchEvent(event); - - expect(fetchMock).not.toHaveBeenCalled(); - - // Wait for async operations to complete - await new Promise(resolve => setTimeout(resolve, 0)); - }); - }); -}); -- cgit v1.2.3