// The discovery view — the parts that fail SILENTLY.
//
// ⛔ Everything here was a real defect, found by rendering the page in a browser and looking at it.
// None of it throws: a wrong nav arg bolds another tab, a missing static file is a 404 that a module
// script reports nowhere, and a canvas that overflows its box just draws outside it. Reading the
// file proved none of them.
import test from 'brittle'
import fs from '#node:fs'
import { join } from '#node:path'
import { explore } from '../lib/pages.js'
const staticDir = new URL('../static/', import.meta.url).pathname
test('★ the page names ITSELF, in the title and the nav', async (t) => {
const h = explore()
// `layout(title)` is both the <title> and the current-page mark. Passing 'graph' here titled the
// page after the other view and bolded its tab — MEASURED in a screenshot, invisible in the source.
const title = h.match(/<title>([^<]*)<\/title>/)
t.is(title && title[1], 'explore')
// The nav marks the current item. Whatever it marks must be this page.
const nav = h.slice(0, h.indexOf('<h1>'))
t.ok(/<b>explore<\/b>|class="here"[^>]*>explore</.test(nav) || !/<b>graph<\/b>/.test(nav),
'the graph tab is not the one marked current')
})
test('★★ every script the page loads EXISTS in static/', async (t) => {
// ⛔ A src that names no file is a 404, and for `type="module"` the browser reports it to nothing
// visible: the page renders, the script never runs, and every interactive assertion passes as
// "nothing happened". That shipped once already with the mention picker.
const srcs = [...explore().matchAll(/<script[^>]*src="\/static\/([^"]+)"/g)].map((m) => m[1])
t.ok(srcs.length > 0, 'the page loads at least one script')
for (const s of srcs) t.ok(fs.existsSync(join(staticDir, s)), s)
})
test('★★ three.js is VENDORED, and so is what it imports', async (t) => {
// static/README.md: vendored, not fetched. A page that pulled a megabyte of JS from a CDN would
// let that CDN run code beside the signer.
const js = fs.readFileSync(join(staticDir, 'explore.js'), 'utf8')
const imports = [...js.matchAll(/from\s+'([^']+)'/g)].map((m) => m[1])
t.ok(imports.length > 0)
for (const i of imports) {
t.ok(i.startsWith('./'), i + ' is a local path')
t.ok(fs.existsSync(join(staticDir, i)), i + ' is on disk')
}
// ⚠ three.module.min.js pulls three.core.min.js. A vendored file with an unvendored dependency is
// the same 404, one level down.
const three = fs.readFileSync(join(staticDir, 'three.module.min.js'), 'utf8')
for (const m of three.matchAll(/from"(\.\/[^"]+)"/g)) {
t.ok(fs.existsSync(join(staticDir, m[1])), 'three imports ' + m[1])
}
})
test('⚠ the scene box CONTAINS its canvas', async (t) => {
// A canvas is inline-level by default, so it sits on a text baseline and ends up taller than the
// height it was sized to — edges drew past the bottom border. Seen in a screenshot.
// CSS is not exported; it reaches the page through layout(), which is where it has to be right.
const page = explore()
const CSS = page.slice(page.indexOf('<style>') + 7, page.indexOf('</style>'))
const scene = CSS.slice(CSS.indexOf('.scene{'))
t.ok(/\.scene\{[^}]*overflow:hidden/.test(scene), '.scene clips')
t.ok(/\.scene canvas\{[^}]*display:block/.test(scene), 'the canvas is block-level')
})
test('⛔ it does not name a cluster and does not export', async (t) => {
// COMMUNITY-EMERGENCE: "the observation is an intervention." EDGE-GRAPH §6.4: the mitigation for
// the aggregation attack is no mechanism. Both are properties of this file, so they are asserted
// on this file — a reviewer adding either would have to delete a test that says why not.
const js = fs.readFileSync(join(staticDir, 'explore.js'), 'utf8')
t.absent(/toDataURL|toBlob|download|createObjectURL/.test(js), 'no export path')
t.absent(/cluster.*label|label.*cluster/i.test(js.replace(/\/\/.*/g, '')), 'no auto-naming')
})
// ── "i have no idea what the points are" ──────────────────────────────────────────────────────
test('★★ the page SAYS what the shapes mean, without JavaScript', async (t) => {
// ⛔ Server-rendered. An explanation that arrives only after 784K of WebGL has loaded and run is
// not an explanation — it is absent for exactly the reader who most needs it.
const h = explore()
const legend = h.slice(h.indexOf('class="legend"'), h.indexOf('id="scene"'))
// ⛔ Every KIND by name. One entry saying "an interaction" told you a line meant something
// happened and left you unable to tell a like from a repost — which is the whole of what the
// colours now carry, and a colour nobody can decode is a decoration.
for (const meaning of ['a person', 'a post', 'bytes never arrived', 'who wrote it',
'a reaction', 'a repost', 'a reply', 'a follow']) {
t.ok(legend.includes(meaning), 'legend explains: ' + meaning)
}
t.ok(legend.indexOf('<script') === -1, 'and it is markup, not a script')
})
test('★ the controls the view needs are IN the page, not created by the script', async (t) => {
const h = explore()
// Each of these is looked up by id in explore.js. A renamed or missing one throws inside a module
// whose failure the browser reports nowhere — the page renders and the feature is simply gone.
for (const id of ['scene', 'tip', 'stats', 'fullscreen']) {
t.ok(h.includes('id="' + id + '"'), '#' + id + ' exists for the script to find')
}
})
test('★★ every id the script reaches for is one the page actually renders', async (t) => {
// The general form of the bug above, asserted rather than listed: whatever explore.js looks up,
// explore() must render. Adding a control to one file and not the other is silent.
const js = fs.readFileSync(join(staticDir, 'explore.js'), 'utf8')
const h = explore()
const wanted = [...js.matchAll(/getElementById\('([^']+)'\)/g)].map((m) => m[1])
t.ok(wanted.length >= 3, 'the script does reach for controls')
for (const id of wanted) t.ok(h.includes('id="' + id + '"'), 'page renders #' + id)
})
test('⛔ the panel is drawn from CSS VARIABLES, not from baked colours', async (t) => {
// A canvas texture is pixels: nothing themes it afterwards. Chrome's force-dark inverts painted
// DOM and leaves computed variables at their light values, so a card built from hard-coded hex
// comes out a white sheet on a dark page — which is exactly what the first version did.
const js = fs.readFileSync(join(staticDir, 'explore.js'), 'utf8')
const paint = js.slice(js.indexOf('function paint'), js.indexOf('function closeCard'))
const fills = [...paint.matchAll(/fillStyle = ([^\n]+)/g)].map((m) => m[1])
t.ok(fills.length > 0)
for (const f of fills) t.ok(/cssVar\(|\bfg\b|\bdim\b/.test(f), 'themed fill: ' + f.trim())
})
test('⛔ an unreadable record says so, in the panel', async (t) => {
// EDGE-GRAPH §4 ③. The whole point of the view is that "held, bytes never arrived" is a DIFFERENT
// state from "no such post", and a panel that rendered it as empty text would erase the
// distinction at the exact moment a person went looking for it.
const js = fs.readFileSync(join(staticDir, 'explore.js'), 'utf8')
t.ok(js.includes('detail.readable'), 'the panel branches on readability')
t.ok(/bytes never arrived/.test(js), 'and names the state in words')
})
test('⛔ still no export, and still no cluster naming', async (t) => {
// Re-asserted after the click/panel work, because that work added the first code here that
// renders content — which is the shape an export would take if one ever appeared.
const js = fs.readFileSync(join(staticDir, 'explore.js'), 'utf8')
t.absent(/toDataURL|toBlob|download|createObjectURL/.test(js), 'no export path')
// ⚠ Strip BLOCK comments too. Stripping only // lines left the doc comments in, and a paragraph
// explaining why a dense cluster is capped read as the code naming one — the substring-for-
// structure mistake this suite has made four times now.
const code = js.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*/g, '')
t.absent(/cluster/i.test(code), 'nothing in the code names a cluster')
})
// ── the proximity reveal ──────────────────────────────────────────────────────────────────────
//
// Structural assertions on a WebGL module. Each one is a bug that shipped and was found by driving
// the page in a browser, and each is invisible to every other kind of test here.
test('⛔ a failed fetch must not BLACKLIST a feed for the life of the page', async (t) => {
const js = fs.readFileSync(join(staticDir, 'explore.js'), 'utf8')
const fn = js.slice(js.indexOf('function wantFeed'), js.indexOf('function revealCard'))
// Marking the feed before the request meant one dropped response left every post in it
// permanently unrevealable — with an empty catch making sure nobody could find out why.
t.ok(fn.indexOf('fetchedFeeds.add') > fn.indexOf('.then('), 'marked done only once it IS done')
t.absent(/catch\s*\(\s*\)\s*=>\s*\{\s*\}/.test(fn), 'the failure is not swallowed silently')
})
test('⛔ near is not enough — a revealed post must be IN FRONT of the camera', async (t) => {
const js = fs.readFileSync(join(staticDir, 'explore.js'), 'utf8')
const fn = js.slice(js.indexOf('function updateReveals'), js.indexOf('// ── picking'))
// Distance to the camera is a sphere. Measured at close zoom: three posts inside the threshold,
// none of them on screen, because the nearest points are as often behind your head as ahead.
t.ok(fn.includes('matrixWorldInverse'), 'tested in camera space, not by distance alone')
t.ok(fn.includes('project(camera)'), 'and against the screen it will be drawn on')
})
test('⚠ the camera matrices are refreshed BEFORE anything projects through them', async (t) => {
const js = fs.readFileSync(join(staticDir, 'explore.js'), 'utf8')
const frame = js.slice(js.indexOf('function frame'))
// lookAt sets a quaternion and refreshes nothing. Without this every visibility test answers for
// where the camera was one frame ago — wrong by exactly one frame, every frame you are moving.
t.ok(frame.indexOf('updateMatrixWorld') > frame.indexOf('lookAt'), 'after lookAt')
t.ok(frame.indexOf('updateMatrixWorld') < frame.indexOf('updateReveals'), 'and before the tests that read them')
})
test('⚠ the reveal threshold is DERIVED from the layout, not a magic number', async (t) => {
const js = fs.readFileSync(join(staticDir, 'explore.js'), 'utf8')
// A constant picked against one fixture admitted 1 of 14 posts at closest zoom, because the
// cloud's size is a function of node count and spring length.
t.ok(/const REVEAL = RADIUS \*/.test(js), 'scaled to the cloud it is measuring')
t.ok(/const RADIUS = /.test(js), 'and the cloud is measured, not assumed')
})
test('⛔ the two views take their edge colours from ONE place', async (t) => {
// The 2D view hard-coded #3aa76d for a repost and the 3D view would have hard-coded a second
// set. Two renderings of one substrate may disagree about layout, never about which line is a
// repost — and after somebody edits one rule, a shared variable is the only thing that holds.
const page = explore()
const CSS = page.slice(page.indexOf('<style>') + 7, page.indexOf('</style>'))
const js = fs.readFileSync(join(staticDir, 'explore.js'), 'utf8')
for (const kind of ['reaction', 'repost', 'reply', 'follow', 'authored']) {
t.ok(CSS.includes('--edge-' + kind + ':'), '--edge-' + kind + ' is declared')
t.ok(js.includes("'--edge-" + kind + "'"), 'and the renderer reads it')
}
// ⚠ No literal hex for an edge in the renderer, which is how the second set would creep back.
const edges = js.slice(js.indexOf('const EDGE_STYLE'), js.indexOf('// ── what is in the picture'))
t.absent(/#[0-9a-f]{6}/i.test(edges.replace(/'#8b98a5'/g, '')), 'no hard-coded edge colours')
})