diff options
Diffstat (limited to 'frontend-vanilla/src')
| -rw-r--r-- | frontend-vanilla/src/perf/renderItems.perf.test.ts | 90 | ||||
| -rw-r--r-- | frontend-vanilla/src/perf/store.perf.test.ts | 124 |
2 files changed, 214 insertions, 0 deletions
diff --git a/frontend-vanilla/src/perf/renderItems.perf.test.ts b/frontend-vanilla/src/perf/renderItems.perf.test.ts new file mode 100644 index 0000000..3dd2ed7 --- /dev/null +++ b/frontend-vanilla/src/perf/renderItems.perf.test.ts @@ -0,0 +1,90 @@ +import { describe, it, expect } from 'vitest'; +import { createFeedItem } from '../components/FeedItem'; +import type { Item } from '../types'; + +function makeItem(id: number): Item { + return { + _id: id, + feed_id: 1, + title: `Test Item ${id}`, + url: `https://example.com/item/${id}`, + description: `<p>Description for item ${id} with <b>bold</b> and <a href="https://example.com">link</a></p>`, + publish_date: '2024-01-01T00:00:00Z', + read: id % 3 === 0, + starred: id % 5 === 0, + feed_title: 'Test Feed', + }; +} + +describe('renderItems performance', () => { + it('createFeedItem renders 100 items under 50ms', () => { + const items = Array.from({ length: 100 }, (_, i) => makeItem(i)); + + const start = performance.now(); + const html = items.map(item => createFeedItem(item)).join(''); + const elapsed = performance.now() - start; + + expect(html).toBeTruthy(); + expect(html).toContain('feed-item'); + expect(elapsed).toBeLessThan(50); + }); + + it('createFeedItem renders 500 items under 200ms', () => { + const items = Array.from({ length: 500 }, (_, i) => makeItem(i)); + + const start = performance.now(); + const html = items.map(item => createFeedItem(item)).join(''); + const elapsed = performance.now() - start; + + expect(html).toBeTruthy(); + expect(elapsed).toBeLessThan(200); + }); + + it('createFeedItem renders 1000 items under 100ms', () => { + const items = Array.from({ length: 1000 }, (_, i) => makeItem(i)); + + const start = performance.now(); + const results: string[] = []; + for (const item of items) { + results.push(createFeedItem(item)); + } + const elapsed = performance.now() - start; + + expect(results.length).toBe(1000); + expect(elapsed).toBeLessThan(100); + }); + + it('DOM insertion of 100 items under 200ms', () => { + const items = Array.from({ length: 100 }, (_, i) => makeItem(i)); + const html = items.map(item => createFeedItem(item)).join(''); + + const container = document.createElement('ul'); + document.body.appendChild(container); + + const start = performance.now(); + container.innerHTML = html; + const elapsed = performance.now() - start; + + expect(container.children.length).toBe(100); + expect(elapsed).toBeLessThan(200); + + document.body.removeChild(container); + }); + + it('DOM insertion of 500 items under 500ms', () => { + const items = Array.from({ length: 500 }, (_, i) => makeItem(i)); + const html = items.map(item => createFeedItem(item)).join(''); + + const container = document.createElement('ul'); + document.body.appendChild(container); + + const start = performance.now(); + container.innerHTML = html; + const elapsed = performance.now() - start; + + expect(container.children.length).toBe(500); + expect(elapsed).toBeLessThan(500); + + document.body.removeChild(container); + }); +}); diff --git a/frontend-vanilla/src/perf/store.perf.test.ts b/frontend-vanilla/src/perf/store.perf.test.ts new file mode 100644 index 0000000..734e132 --- /dev/null +++ b/frontend-vanilla/src/perf/store.perf.test.ts @@ -0,0 +1,124 @@ +import { describe, it, expect } from 'vitest'; +import { Store } from '../store'; +import type { Item, Feed, Category } from '../types'; + +function makeItem(id: number): Item { + return { + _id: id, + feed_id: 1, + title: `Test Item ${id}`, + url: `https://example.com/item/${id}`, + description: `Description for item ${id}`, + publish_date: '2024-01-01T00:00:00Z', + read: false, + starred: false, + feed_title: 'Test Feed', + }; +} + +function makeFeed(id: number): Feed { + return { + _id: id, + url: `https://example.com/feed/${id}`, + web_url: `https://example.com/${id}`, + title: `Feed ${id}`, + category: `cat-${id % 5}`, + }; +} + +describe('store performance', () => { + it('setItems with 500 items + event dispatch under 10ms', () => { + const store = new Store(); + const items = Array.from({ length: 500 }, (_, i) => makeItem(i)); + + let eventFired = false; + store.on('items-updated', () => { eventFired = true; }); + + const start = performance.now(); + store.setItems(items); + const elapsed = performance.now() - start; + + expect(store.items.length).toBe(500); + expect(eventFired).toBe(true); + expect(elapsed).toBeLessThan(10); + }); + + it('setItems append 500 items to existing 500 under 10ms', () => { + const store = new Store(); + const initial = Array.from({ length: 500 }, (_, i) => makeItem(i)); + const more = Array.from({ length: 500 }, (_, i) => makeItem(i + 500)); + + store.setItems(initial); + + const start = performance.now(); + store.setItems(more, true); + const elapsed = performance.now() - start; + + expect(store.items.length).toBe(1000); + expect(elapsed).toBeLessThan(10); + }); + + it('setFeeds with 200 feeds under 5ms', () => { + const store = new Store(); + const feeds = Array.from({ length: 200 }, (_, i) => makeFeed(i)); + + let eventFired = false; + store.on('feeds-updated', () => { eventFired = true; }); + + const start = performance.now(); + store.setFeeds(feeds); + const elapsed = performance.now() - start; + + expect(store.feeds.length).toBe(200); + expect(eventFired).toBe(true); + expect(elapsed).toBeLessThan(5); + }); + + it('rapid filter changes (100 toggles) under 50ms', () => { + const store = new Store(); + const filters: Array<'unread' | 'all' | 'starred'> = ['unread', 'all', 'starred']; + let eventCount = 0; + store.on('filter-updated', () => { eventCount++; }); + + const start = performance.now(); + for (let i = 0; i < 100; i++) { + store.setFilter(filters[i % 3]); + } + const elapsed = performance.now() - start; + + expect(eventCount).toBeGreaterThan(0); + expect(elapsed).toBeLessThan(50); + }); + + it('rapid search query changes (100 updates) under 50ms', () => { + const store = new Store(); + let eventCount = 0; + store.on('search-updated', () => { eventCount++; }); + + const start = performance.now(); + for (let i = 0; i < 100; i++) { + store.setSearchQuery(`query-${i}`); + } + const elapsed = performance.now() - start; + + expect(eventCount).toBe(100); + expect(elapsed).toBeLessThan(50); + }); + + it('multiple listeners (50) on items-updated under 10ms', () => { + const store = new Store(); + const items = Array.from({ length: 100 }, (_, i) => makeItem(i)); + let totalCalls = 0; + + for (let i = 0; i < 50; i++) { + store.on('items-updated', () => { totalCalls++; }); + } + + const start = performance.now(); + store.setItems(items); + const elapsed = performance.now() - start; + + expect(totalCalls).toBe(50); + expect(elapsed).toBeLessThan(10); + }); +}); |
