Files
saphid--frame-control/site/test/feedback.test.mjs
T
saphidandClaude Opus 5.5 7d328e20a5 Website with a feedback form that opens GitHub issues, and pi's contributor gate
- site/: landing page, /feedback/ and /privacy/ on Cloudflare Pages
  (frame-control.pages.dev). POST /api/feedback validates the form and opens
  a labelled issue with a fine-grained token; honeypot, minimum fill time and
  KV rate limits keep spam out. Ko-fi donate buttons appear once the page
  name is set in site/public/js/site.js.
- .github: the issue and PR gate from badlogic/pi-mono. New contributors'
  issues and PRs are auto-closed; a maintainer replying lgtmi/lgtm approves
  them via APPROVED_CONTRIBUTORS. Issue templates and CONTRIBUTING.md.
- CI runs the website tests; README points feedback at the form.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 14:26:33 +10:00

70 lines
2.9 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// node --test site/test/*.test.mjs
import assert from "node:assert/strict";
import { test } from "node:test";
import { buildIssue, defang, hashIp, MIN_FILL_MS, validate } from "../lib/feedback.js";
const now = 1_800_000_000_000;
const form = (over = {}) => ({
kind: "bug",
title: "Live view freezes",
message: "After about a minute the live view stops updating.",
started: now - MIN_FILL_MS - 1,
...over,
});
test("accepts a normal report and trims it", () => {
const { value, error } = validate(form({ title: " Live view freezes ", os: " macOS 26 " }), now);
assert.equal(error, undefined);
assert.equal(value.title, "Live view freezes");
assert.equal(value.os, "macOS 26");
assert.equal(value.kind, "bug");
});
test("flags the honeypot and too-fast submissions as spam", () => {
assert.deepEqual(validate(form({ website: "http://spam" }), now), { spam: true });
assert.deepEqual(validate(form({ started: now - 500 }), now), { spam: true });
assert.deepEqual(validate(form({ started: undefined }), now), { spam: true });
});
test("rejects short, long and malformed input", () => {
assert.match(validate(form({ title: "hi" }), now).error, /title/);
assert.match(validate(form({ message: "short" }), now).error, /more/);
assert.match(validate(form({ message: "x".repeat(5001) }), now).error, /under/);
assert.match(validate(form({ github: "not a user!" }), now).error, /GitHub/);
assert.match(validate(null, now).error, /JSON/);
});
test("unknown kinds become other feedback", () => {
assert.equal(validate(form({ kind: "__proto__" }), now).value.kind, "other");
});
test("builds a labelled issue that credits a GitHub user", () => {
const { value } = validate(form({ github: "@octocat", version: "0.3.1", steamos: "20260922" }), now);
const issue = buildIssue(value);
assert.equal(issue.title, "Bug report: Live view freezes");
assert.deepEqual(issue.labels, ["feedback", "bug"]);
assert.match(issue.body, /\| Frame Control version \| 0\.3\.1 \|/);
assert.match(issue.body, /by @octocat\.$/);
});
test("anonymous feedback says replies won't reach the sender", () => {
const issue = buildIssue(validate(form({ kind: "other" }), now).value);
assert.deepEqual(issue.labels, ["feedback"]);
assert.match(issue.body, /won't see replies/);
});
test("breaks mentions, issue refs and table cells in user text", () => {
assert.equal(defang("ping @valve about #12"), "ping @​valve about #​12");
assert.equal(defang("email me@example.com"), "email me@​example.com");
const issue = buildIssue(validate(form({ os: "a | b" }), now).value);
assert.match(issue.body, /\| a \\\| b \|/);
});
test("hashes IPs without keeping them", async () => {
const a = await hashIp("203.0.113.9", "salt");
assert.equal(a.length, 24);
assert.equal(a, await hashIp("203.0.113.9", "salt"));
assert.notEqual(a, await hashIp("203.0.113.10", "salt"));
assert.ok(!a.includes("203"));
});