aboutsummaryrefslogtreecommitdiffstats
path: root/frontend-vanilla/src/perf/store.perf.test.ts
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-02-16 18:15:47 +0000
committerClaude <noreply@anthropic.com>2026-02-16 18:15:47 +0000
commitd4caf45b2b9ea6a3276de792cf6f73085e66b1ae (patch)
tree3a8b7b1d9277ba0db4c946fea43043d65220fd8d /frontend-vanilla/src/perf/store.perf.test.ts
parentc9c5def76c3a3340373143f846454b795d296c82 (diff)
downloadneko-d4caf45b2b9ea6a3276de792cf6f73085e66b1ae.tar.gz
neko-d4caf45b2b9ea6a3276de792cf6f73085e66b1ae.tar.bz2
neko-d4caf45b2b9ea6a3276de792cf6f73085e66b1ae.zip
Add performance benchmarks, stress tests, and frontend perf tests
Go benchmarks cover item CRUD/filter/sanitization, API endpoints (stream, item update, feed list), middleware stack (gzip, security headers, CSRF), and crawler pipeline (feed parsing, mocked crawl). Stress tests verify concurrent reads/writes and large dataset handling. Frontend perf tests measure template generation, DOM insertion, and store event throughput. New Makefile targets: bench, bench-short, stress, test-perf. https://claude.ai/code/session_01ChDVWFDrQoFjMYHpaLGr9s
Diffstat (limited to 'frontend-vanilla/src/perf/store.perf.test.ts')
-rw-r--r--frontend-vanilla/src/perf/store.perf.test.ts124
1 files changed, 124 insertions, 0 deletions
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);
+ });
+});