mirror of
https://github.com/hanzo-apps/world.git
synced 2026-10-30 14:53:09 +00:00
The fork still wore upstream's brand throughout: "World Monitor" in 754 places of prose and metadata, WORLD MONITOR in the blog logo and footer, and a byline reading "by Someone.ceo" — another company's name signing our pages. Blog assets, OG images and post directories were named for it too, and the OpenAPI schema types carried it in every identifier. Files are renamed, references follow them, and the localStorage and slug names match. Deleted .claudedocs/sentry-triage, an AI-written summary file of the kind that belongs in LLM.md if anywhere. NOTICE and LICENSE are untouched by design. NOTICE names the upstream project, the fork point and the original author; LICENSE keeps his copyright line, which MIT requires. That is where credit belongs — not in our product's chrome.
215 lines
8.6 KiB
TypeScript
215 lines
8.6 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
|
|
test.describe('keyword spike modal/badge flow', () => {
|
|
test('injects synthetic headlines and renders keyword_spike end-to-end', async ({ page }) => {
|
|
await page.goto('/tests/runtime-harness.html');
|
|
|
|
const setup = await page.evaluate(async () => {
|
|
const { SignalModal } = await import('/src/components/SignalModal.ts');
|
|
const { IntelligenceGapBadge } = await import('/src/components/IntelligenceGapBadge.ts');
|
|
const trending = await import('/src/services/trending-keywords.ts');
|
|
const correlation = await import('/src/services/correlation.ts');
|
|
|
|
// The badge/modal render through i18n (t()); the app bootstrap initializes it
|
|
// before any component mounts. This isolated harness must do the same, else
|
|
// t() returns undefined and getSignalContext().actionableInsight.split() throws.
|
|
const { initI18n } = await import('/src/services/i18n.ts');
|
|
await initI18n();
|
|
|
|
const previousConfig = trending.getTrendingConfig();
|
|
const headerRight = document.createElement('div');
|
|
headerRight.className = 'header-right';
|
|
document.body.appendChild(headerRight);
|
|
|
|
// The findings badge is opt-in (default OFF): its constructor only mounts and
|
|
// renders when the user has enabled it. Simulate an opted-in session so the
|
|
// badge mounts into .header-right and its count/dropdown render.
|
|
localStorage.setItem('world-intel-findings', 'shown');
|
|
|
|
const modal = new SignalModal();
|
|
const badge = new IntelligenceGapBadge();
|
|
badge.setOnSignalClick((signal) => modal.showSignal(signal));
|
|
|
|
trending.updateTrendingConfig({
|
|
blockedTerms: [],
|
|
minSpikeCount: 5,
|
|
spikeMultiplier: 3,
|
|
autoSummarize: false,
|
|
});
|
|
|
|
const now = new Date();
|
|
// Keep the trending proper noun ("Iran") mid-sentence and capitalized: the
|
|
// significance filter (isLikelyProperNoun) only counts capitalization for
|
|
// tokens that appear past position 0, so a sentence-leading term reads as an
|
|
// ordinary word and gets suppressed. Mid-sentence, it registers as a genuine
|
|
// entity spiking across sources — no ML worker required.
|
|
const headlines = [
|
|
{ source: 'Reuters', title: 'Talks on Iran sanctions stall in Washington', link: 'https://example.com/reuters/1' },
|
|
{ source: 'AP', title: 'New Iran sanctions debate intensifies among allies', link: 'https://example.com/ap/1' },
|
|
{ source: 'BBC', title: 'Markets watch Iran sanctions with fresh concern', link: 'https://example.com/bbc/1' },
|
|
{ source: 'Reuters', title: 'Regional powers weigh Iran sanctions package', link: 'https://example.com/reuters/2' },
|
|
{ source: 'AP', title: 'Momentum grows for Iran sanctions proposal', link: 'https://example.com/ap/2' },
|
|
{ source: 'BBC', title: 'Analysts see Iran sanctions timeline shortening', link: 'https://example.com/bbc/2' },
|
|
].map(item => ({
|
|
...item,
|
|
pubDate: now,
|
|
}));
|
|
|
|
trending.ingestHeadlines(headlines);
|
|
|
|
let spikes = trending.drainTrendingSignals();
|
|
for (let i = 0; i < 20 && spikes.length === 0; i += 1) {
|
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
spikes = trending.drainTrendingSignals();
|
|
}
|
|
|
|
if (spikes.length === 0) {
|
|
badge.destroy();
|
|
modal.getElement().remove();
|
|
trending.updateTrendingConfig(previousConfig);
|
|
return { ok: false, reason: 'No keyword spikes emitted from synthetic data' };
|
|
}
|
|
|
|
correlation.addToSignalHistory(spikes);
|
|
badge.update();
|
|
|
|
// Keep refs alive for user interactions in the test.
|
|
(window as unknown as Record<string, unknown>).__keywordSpikeTest = {
|
|
badge,
|
|
modal,
|
|
previousConfig,
|
|
};
|
|
|
|
return {
|
|
ok: true,
|
|
spikeType: spikes[0]?.type,
|
|
title: spikes[0]?.title ?? '',
|
|
badgeCount: (document.querySelector('.findings-count') as HTMLElement | null)?.textContent ?? '0',
|
|
};
|
|
});
|
|
|
|
expect(setup.ok).toBe(true);
|
|
expect(setup.spikeType).toBe('keyword_spike');
|
|
expect(Number(setup.badgeCount)).toBeGreaterThan(0);
|
|
|
|
await page.click('.intel-findings-badge');
|
|
const finding = page.locator('.finding-item').first();
|
|
await expect(finding).toBeVisible();
|
|
await expect(finding).toContainText('Trending');
|
|
|
|
await finding.click();
|
|
await expect(page.locator('.signal-modal-overlay.active')).toBeVisible();
|
|
await expect(page.locator('.signal-item .signal-type').first()).toContainText('Keyword Spike');
|
|
await expect(page.locator('.suppress-keyword-btn').first()).toBeVisible();
|
|
|
|
await page.evaluate(async () => {
|
|
const trending = await import('/src/services/trending-keywords.ts');
|
|
const store = (window as unknown as Record<string, unknown>).__keywordSpikeTest as
|
|
| {
|
|
badge?: { destroy?: () => void };
|
|
modal?: { getElement?: () => HTMLElement };
|
|
previousConfig?: Parameters<typeof trending.updateTrendingConfig>[0];
|
|
}
|
|
| undefined;
|
|
|
|
store?.badge?.destroy?.();
|
|
store?.modal?.getElement?.()?.remove();
|
|
if (store?.previousConfig) {
|
|
trending.updateTrendingConfig(store.previousConfig);
|
|
}
|
|
localStorage.removeItem('world-intel-findings');
|
|
delete (window as unknown as Record<string, unknown>).__keywordSpikeTest;
|
|
});
|
|
});
|
|
|
|
test('does not emit spikes from source-attribution suffixes', async ({ page }) => {
|
|
await page.goto('/tests/runtime-harness.html');
|
|
|
|
const result = await page.evaluate(async () => {
|
|
const trending = await import('/src/services/trending-keywords.ts');
|
|
const previousConfig = trending.getTrendingConfig();
|
|
|
|
try {
|
|
trending.updateTrendingConfig({
|
|
blockedTerms: [],
|
|
minSpikeCount: 4,
|
|
spikeMultiplier: 3,
|
|
autoSummarize: false,
|
|
});
|
|
|
|
const now = new Date();
|
|
const headlines = [
|
|
{ source: 'Reuters', title: 'Qzxalpha ventures stabilize - WireDesk' },
|
|
{ source: 'AP', title: 'Bravotango liquidity trims - WireDesk' },
|
|
{ source: 'BBC', title: 'Cindelta refinery expands - WireDesk' },
|
|
{ source: 'Bloomberg', title: 'Dorion transit reroutes - WireDesk' },
|
|
{ source: 'WSJ', title: 'Epsiluna lending reprices - WireDesk' },
|
|
].map((item) => ({ ...item, pubDate: now }));
|
|
|
|
trending.ingestHeadlines(headlines);
|
|
|
|
let spikes = trending.drainTrendingSignals();
|
|
for (let i = 0; i < 20 && spikes.length === 0; i += 1) {
|
|
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
spikes = trending.drainTrendingSignals();
|
|
}
|
|
|
|
return {
|
|
emittedTitles: spikes.map((signal) => signal.title),
|
|
hasWireDeskSpike: spikes.some((signal) => /wiredesk/i.test(signal.title)),
|
|
};
|
|
} finally {
|
|
trending.updateTrendingConfig(previousConfig);
|
|
}
|
|
});
|
|
|
|
expect(result.hasWireDeskSpike).toBe(false);
|
|
expect(result.emittedTitles.length).toBe(0);
|
|
});
|
|
|
|
test('suppresses month-name token spikes', async ({ page }) => {
|
|
await page.goto('/tests/runtime-harness.html');
|
|
|
|
const result = await page.evaluate(async () => {
|
|
const trending = await import('/src/services/trending-keywords.ts');
|
|
const previousConfig = trending.getTrendingConfig();
|
|
|
|
try {
|
|
trending.updateTrendingConfig({
|
|
blockedTerms: [],
|
|
minSpikeCount: 4,
|
|
spikeMultiplier: 3,
|
|
autoSummarize: false,
|
|
});
|
|
|
|
const now = new Date();
|
|
const headlines = [
|
|
{ source: 'Reuters', title: 'January qxavon ledger shift' },
|
|
{ source: 'AP', title: 'January brivon routing update' },
|
|
{ source: 'BBC', title: 'January caldren supply note' },
|
|
{ source: 'Bloomberg', title: 'January dernix cargo brief' },
|
|
{ source: 'WSJ', title: 'January eptara policy digest' },
|
|
].map((item) => ({ ...item, pubDate: now }));
|
|
|
|
trending.ingestHeadlines(headlines);
|
|
|
|
let spikes = trending.drainTrendingSignals();
|
|
for (let i = 0; i < 20 && spikes.length === 0; i += 1) {
|
|
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
spikes = trending.drainTrendingSignals();
|
|
}
|
|
|
|
return {
|
|
emittedTitles: spikes.map((signal) => signal.title),
|
|
hasJanuarySpike: spikes.some((signal) => /january/i.test(signal.title)),
|
|
};
|
|
} finally {
|
|
trending.updateTrendingConfig(previousConfig);
|
|
}
|
|
});
|
|
|
|
expect(result.hasJanuarySpike).toBe(false);
|
|
expect(result.emittedTitles.length).toBe(0);
|
|
});
|
|
});
|