clone of repo on github
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

382 lines
12 KiB

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { writable, get } from 'svelte/store';
import { displayLimits } from '$lib/stores/displayLimits';
import { visualizationConfig } from '$lib/stores/visualizationConfig';
import type { NDKEvent } from '@nostr-dev-kit/ndk';
// Mock NDK Event for testing
function createMockEvent(kind: number, id: string): NDKEvent {
return {
id,
kind,
pubkey: 'mock-pubkey',
created_at: Date.now() / 1000,
content: `Mock content for ${id}`,
tags: []
} as NDKEvent;
}
describe('Display Limits Integration', () => {
beforeEach(() => {
// Reset stores to default values
displayLimits.set({
max30040: -1,
max30041: -1,
fetchIfNotFound: false
});
visualizationConfig.setMaxPublicationIndices(-1);
visualizationConfig.setMaxEventsPerIndex(-1);
});
describe('Event Filtering with Limits', () => {
it('should filter events when limits are set', () => {
const events = [
createMockEvent(30040, 'index1'),
createMockEvent(30040, 'index2'),
createMockEvent(30040, 'index3'),
createMockEvent(30041, 'content1'),
createMockEvent(30041, 'content2'),
createMockEvent(30041, 'content3'),
createMockEvent(30041, 'content4')
];
// Apply display limits
const limits = get(displayLimits);
limits.max30040 = 2;
limits.max30041 = 3;
// Filter function
const filterByLimits = (events: NDKEvent[], limits: any) => {
const kindCounts = new Map<number, number>();
return events.filter(event => {
const count = kindCounts.get(event.kind) || 0;
if (event.kind === 30040 && limits.max30040 !== -1 && count >= limits.max30040) {
return false;
}
if (event.kind === 30041 && limits.max30041 !== -1 && count >= limits.max30041) {
return false;
}
kindCounts.set(event.kind, count + 1);
return true;
});
};
const filtered = filterByLimits(events, limits);
// Should have 2 index events and 3 content events
expect(filtered.filter(e => e.kind === 30040)).toHaveLength(2);
expect(filtered.filter(e => e.kind === 30041)).toHaveLength(3);
expect(filtered).toHaveLength(5);
});
it('should respect unlimited (-1) values', () => {
const events = Array.from({ length: 100 }, (_, i) =>
createMockEvent(i % 2 === 0 ? 30040 : 30041, `event${i}`)
);
// Set one limit, leave other unlimited
displayLimits.update(limits => ({
...limits,
max30040: 10,
max30041: -1
}));
const limits = get(displayLimits);
const filtered = events.filter((event, index) => {
if (event.kind === 30040) {
const count = events.slice(0, index).filter(e => e.kind === 30040).length;
return limits.max30040 === -1 || count < limits.max30040;
}
return true; // No limit on 30041
});
// Should have exactly 10 kind 30040 events
expect(filtered.filter(e => e.kind === 30040)).toHaveLength(10);
// Should have all 50 kind 30041 events
expect(filtered.filter(e => e.kind === 30041)).toHaveLength(50);
});
});
describe('Publication Index Limits', () => {
it('should limit publication indices separately from content', () => {
const config = get(visualizationConfig);
// Create publication structure
const publications = [
{
index: createMockEvent(30040, 'pub1'),
content: [
createMockEvent(30041, 'pub1-content1'),
createMockEvent(30041, 'pub1-content2'),
createMockEvent(30041, 'pub1-content3')
]
},
{
index: createMockEvent(30040, 'pub2'),
content: [
createMockEvent(30041, 'pub2-content1'),
createMockEvent(30041, 'pub2-content2')
]
},
{
index: createMockEvent(30040, 'pub3'),
content: [
createMockEvent(30041, 'pub3-content1')
]
}
];
// Set limits
visualizationConfig.setMaxPublicationIndices(2);
visualizationConfig.setMaxEventsPerIndex(2);
// Apply limits
const limitedPubs = publications
.slice(0, get(visualizationConfig).maxPublicationIndices === -1
? publications.length
: get(visualizationConfig).maxPublicationIndices)
.map(pub => ({
index: pub.index,
content: pub.content.slice(0, get(visualizationConfig).maxEventsPerIndex === -1
? pub.content.length
: get(visualizationConfig).maxEventsPerIndex)
}));
// Should have 2 publications
expect(limitedPubs).toHaveLength(2);
// First pub should have 2 content events
expect(limitedPubs[0].content).toHaveLength(2);
// Second pub should have 2 content events
expect(limitedPubs[1].content).toHaveLength(2);
});
it('should handle per-index limits correctly', () => {
visualizationConfig.setMaxEventsPerIndex(3);
const maxPerIndex = get(visualizationConfig).maxEventsPerIndex;
const indexEvents = new Map<string, NDKEvent[]>();
// Simulate grouping events by index
const events = [
{ indexId: 'idx1', event: createMockEvent(30041, 'c1') },
{ indexId: 'idx1', event: createMockEvent(30041, 'c2') },
{ indexId: 'idx1', event: createMockEvent(30041, 'c3') },
{ indexId: 'idx1', event: createMockEvent(30041, 'c4') }, // Should be filtered
{ indexId: 'idx2', event: createMockEvent(30041, 'c5') },
{ indexId: 'idx2', event: createMockEvent(30041, 'c6') }
];
events.forEach(({ indexId, event }) => {
const current = indexEvents.get(indexId) || [];
if (maxPerIndex === -1 || current.length < maxPerIndex) {
indexEvents.set(indexId, [...current, event]);
}
});
// idx1 should have 3 events
expect(indexEvents.get('idx1')).toHaveLength(3);
// idx2 should have 2 events
expect(indexEvents.get('idx2')).toHaveLength(2);
});
});
describe('Fetch If Not Found Feature', () => {
it('should identify missing referenced events', () => {
const availableEvents = new Set(['event1', 'event2', 'event3']);
const referencedEvents = ['event1', 'event2', 'event4', 'event5'];
displayLimits.update(limits => ({
...limits,
fetchIfNotFound: true
}));
const limits = get(displayLimits);
const missingEvents = limits.fetchIfNotFound
? referencedEvents.filter(id => !availableEvents.has(id))
: [];
expect(missingEvents).toEqual(['event4', 'event5']);
});
it('should not fetch when fetchIfNotFound is false', () => {
const availableEvents = new Set(['event1']);
const referencedEvents = ['event1', 'event2', 'event3'];
displayLimits.update(limits => ({
...limits,
fetchIfNotFound: false
}));
const limits = get(displayLimits);
const shouldFetch = limits.fetchIfNotFound &&
referencedEvents.some(id => !availableEvents.has(id));
expect(shouldFetch).toBe(false);
});
it('should batch fetch requests for missing events', () => {
const fetchQueue: string[] = [];
const addToFetchQueue = (ids: string[]) => {
fetchQueue.push(...ids);
};
// Simulate finding missing events
const missingEvents = ['event10', 'event11', 'event12'];
displayLimits.update(limits => ({
...limits,
fetchIfNotFound: true
}));
if (get(displayLimits).fetchIfNotFound) {
addToFetchQueue(missingEvents);
}
expect(fetchQueue).toEqual(missingEvents);
expect(fetchQueue).toHaveLength(3);
});
});
describe('Integration with Visualization Updates', () => {
it('should trigger appropriate updates when limits change', () => {
const updateTypes: string[] = [];
const mockUpdate = (type: string) => updateTypes.push(type);
// Change publication index limit
const oldConfig = get(visualizationConfig);
visualizationConfig.setMaxPublicationIndices(5);
if (get(visualizationConfig).maxPublicationIndices !== oldConfig.maxPublicationIndices) {
mockUpdate('filter-indices');
}
// Change events per index limit
visualizationConfig.setMaxEventsPerIndex(10);
mockUpdate('filter-content');
// Toggle fetchIfNotFound
displayLimits.update(limits => ({
...limits,
fetchIfNotFound: true
}));
mockUpdate('check-missing');
expect(updateTypes).toContain('filter-indices');
expect(updateTypes).toContain('filter-content');
expect(updateTypes).toContain('check-missing');
});
it('should preserve existing graph structure when applying limits', () => {
const graph = {
nodes: [
{ id: 'idx1', type: 'index' },
{ id: 'c1', type: 'content' },
{ id: 'c2', type: 'content' },
{ id: 'c3', type: 'content' }
],
links: [
{ source: 'idx1', target: 'c1' },
{ source: 'idx1', target: 'c2' },
{ source: 'idx1', target: 'c3' }
]
};
// Apply content limit
visualizationConfig.setMaxEventsPerIndex(2);
const limit = get(visualizationConfig).maxEventsPerIndex;
// Filter nodes and links based on limit
const contentNodes = graph.nodes.filter(n => n.type === 'content');
const limitedContentIds = contentNodes.slice(0, limit).map(n => n.id);
const filteredGraph = {
nodes: graph.nodes.filter(n =>
n.type !== 'content' || limitedContentIds.includes(n.id)
),
links: graph.links.filter(l =>
limitedContentIds.includes(l.target)
)
};
expect(filteredGraph.nodes).toHaveLength(3); // 1 index + 2 content
expect(filteredGraph.links).toHaveLength(2);
});
});
describe('Performance Considerations', () => {
it('should handle large event sets efficiently', () => {
const largeEventSet = Array.from({ length: 10000 }, (_, i) =>
createMockEvent(i % 2 === 0 ? 30040 : 30041, `event${i}`)
);
const startTime = performance.now();
// Apply strict limits
displayLimits.update(limits => ({
...limits,
max30040: 50,
max30041: 100
}));
const limits = get(displayLimits);
const kindCounts = new Map<number, number>();
const filtered = largeEventSet.filter(event => {
const count = kindCounts.get(event.kind) || 0;
if (event.kind === 30040 && limits.max30040 !== -1 && count >= limits.max30040) {
return false;
}
if (event.kind === 30041 && limits.max30041 !== -1 && count >= limits.max30041) {
return false;
}
kindCounts.set(event.kind, count + 1);
return true;
});
const endTime = performance.now();
const filterTime = endTime - startTime;
// Should complete quickly even with large sets
expect(filterTime).toBeLessThan(100); // 100ms threshold
expect(filtered).toHaveLength(150); // 50 + 100
});
it('should cache limit calculations when possible', () => {
let calculationCount = 0;
const getCachedLimits = (() => {
let cache: any = null;
let cacheKey: string = '';
return (limits: any) => {
const key = JSON.stringify(limits);
if (key !== cacheKey) {
calculationCount++;
cache = { ...limits, calculated: true };
cacheKey = key;
}
return cache;
};
})();
// First call - should calculate
getCachedLimits(get(displayLimits));
expect(calculationCount).toBe(1);
// Same limits - should use cache
getCachedLimits(get(displayLimits));
expect(calculationCount).toBe(1);
// Change limits - should recalculate
displayLimits.update(limits => ({ ...limits, max30040: 10 }));
getCachedLimits(get(displayLimits));
expect(calculationCount).toBe(2);
});
});
});