PromoLens

Open-source Chrome extension that estimates, on request, how likely a Reddit post is to be promotional. Not affiliated with Reddit.

View the Project on GitHub PhiDesign/promolens

Testing PromoLens

Automated tests

npm test            # run everything once
npm run test:watch  # re-run on change
npm run check       # typecheck + tests + build

Framework: vitest. DOM tests run in jsdom and opt in with a // @vitest-environment jsdom comment.

File Covers
packages/shared/test/scoring.test.ts clamping 0-100, category caps, style-only cap, correlation discount, counter-signals, clear/missing/unclear/unknown disclosure, reach separate from promotion, high upvotes alone never raise the score, comment accusations (+2 max, independent concerns, coordinated -> no points + lower confidence, unverified linked post discounted, author admission), confidence rules, three reasons max
packages/shared/test/detectors.test.ts each local detector: calls to buy/register/download, waitlist, coupon/referral, affiliate URL params, redirect domains, DM requests, “comment interested”, product links vs Reddit/general hosts, repeated naming, story arc, workflow insertion, marketing/testimonial language, clear and unclear disclosure, Brand Affiliate label, limitations/alternatives, useful-without-links, availability notes
packages/shared/test/schemas.test.ts request/response schema validation (example request accepted, missing title, oversized body, out-of-range score, unknown enum values, >3 reasons)
packages/shared/test/hash.test.ts deterministic hashing; content changes rehash; vote changes do not
apps/api/test/server.test.ts health, valid analyze -> schema-valid, cache hit on repeat, 400/413/415/404, CORS allow/deny + preflight, 429 rate limit
apps/api/test/cache.test.ts memory cache expiry/eviction/clear, rate limiter window
apps/extension/test/adapter.test.ts extraction from sanitized fixtures, links/domains, ring mounting with the title slot, stable keys, detail URL detection, end-to-end fixture scores (organic, transparent founder, undisclosed workflow, high-upvote organic)
apps/extension/test/adapter-detail.test.ts comment extraction on a detail page, OP flagging, unsupported accusation stays minimal
apps/extension/test/scanner.test.ts one ring per post, no duplicates on repeated scans, dynamically inserted post detected via MutationObserver, removed posts cleaned up, stop removes everything, IntersectionObserver gating
apps/extension/test/cache.test.ts chrome.storage-style cache: 24 h expiry, TTL change at read time, pruning, clear keeps settings
apps/extension/test/queue.test.ts concurrency limit, duplicate keys, cancellation of pending/running tasks, priority, error isolation
apps/extension/test/apiClient.test.ts validated results, malformed/non-JSON/HTTP errors, unreachable server, max two concurrent, timeout

Fixtures

apps/extension/test/fixtures/ contains sanitized, synthetic HTML modelled on www.reddit.com markup. None of it is live Reddit content; do not add real posts, usernames or comments to committed fixtures.

Fixture Represents
organic-advice.html genuine advice, limitations discussed, no links
founder-transparent.html “I’m the founder” + product link + limitation
undisclosed-workflow.html multi-tool workflow where only one obscure tool is linked (with a referral parameter), no disclosure
high-upvote-organic.html organic PSA with 24k upvotes
accused-comments.html detail page; one unsupported “this is an ad” comment; author answers publicly
dynamic-insert.html feed plus a <template> the scanner test inserts later (scanner is generic; the extension only points it at the main post of a post page)

Debugging on a live page

The content script prints [PromoLens] lines to the page console at the Verbose level (open DevTools > Console > set the level filter to include “Verbose”): ring attached, queued, extracted, local result, cache lookup, enrich. Warnings appear for anything unexpected.

Two things that look like bugs but are not:

Manual test checklist

Build (npm run build), load apps/extension/dist unpacked, then walk through:

Feeds

Post page

Client-side navigation

Light and dark themes

Hover, click, focus and keyboard

Popover contents

API unavailable

Malformed API response

Extension disabled

Author history

Developer: route deeper analysis through a local API server

The popup only offers “use my own OpenAI key”. To use apps/api instead (evaluation, other providers, a shared server), open the extension’s service-worker console from chrome://extensions and run:

chrome.storage.local.get("promolens:settings", ({ "promolens:settings": s }) =>
  chrome.storage.local.set({ "promolens:settings": { ...s, apiEnabled: true, aiProvider: "local-api", apiBaseUrl: "http://127.0.0.1:8787" } }));

The popup then shows the local-API fields (URL and Check connection) until you switch back to own-key mode.

AI provider (optional)

Permissions review