mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-10-29 18:12:51 +00:00
## Summary
- New **Support CodeNomad** help page (10 languages): what sponsorship
pays for, the funding policy (a contribution, not a purchase; never
required to download, use or update) and one button per funding channel
naming who receives the money.
- **Download page** ends with a short *Support development* section with
the same buttons; the help **header** gains a *Sponsor* link; the former
support page is retitled **Bug reports and source builds** (ids
unchanged) so the two are not confused.
- **App**: *Support CodeNomad* in Settings → Info (secondary button
between Updates and Diagnostics), in the Help menu after *CodeNomad
Help* (Electron and Tauri, English help-page fallback) and as a palette
command without a default shortcut. All open the localized help page.
- **Repository**: `.github/FUNDING.yml` lists the maintainer's GitHub
Sponsors profile and the Support page (the Sponsor button needs
*Settings → General → Features → Sponsorships*).
Channels live in `docs/help/funding.json` and render through a
`{{funding}}` directive allowed only on the download and sponsor pages,
so adding a channel needs no retranslation. Entries are validated (exact
keys, unique ids, `https://github.com/sponsors/<login>` only) and
escaped; a page using `{{funding}}` with no channel fails the build. CSP
unchanged.
Agreed with the co-maintainer before publishing.
## Validation
- `npm run test:help`: 31/31 (27 pages × 10 locales, funding validation
and placement).
- `node scripts/test-help-browser.mjs`: 47/47 (header overflow at 390 px
in en/he/ne/ja/zh-Hans, button geometry).
- `npm run typecheck`; unit tests help-url, hotkey-bindings,
menu-labels; browser tests help-command, server-info, view-menu-electron
(22/22); `cargo test` for the Tauri crate (291/291, including the
English sponsor fallback).
- Independent review: zero findings.
146 lines
8.9 KiB
TypeScript
146 lines
8.9 KiB
TypeScript
import assert from "node:assert/strict"
|
||
import { after, before, test } from "node:test"
|
||
import { fileURLToPath } from "node:url"
|
||
import { mkdir } from "node:fs/promises"
|
||
import { join } from "node:path"
|
||
import { tmpdir } from "node:os"
|
||
import { borrowFixture } from "./fixture-borrow"
|
||
import { chromium, type Browser, type Page } from "playwright"
|
||
import { armFixtureNavigationBudget } from "./fixture-navigation-budget"
|
||
|
||
let cache: Awaited<ReturnType<typeof createFixtureCache>>, browser: Browser, url: string
|
||
before(async () => {
|
||
const fixture = await borrowFixture({
|
||
name: "help-command",
|
||
routes: [
|
||
{ path: "/fixture", html: "<html><body><div id=\"root\"></div><script type=\"module\" src=\"/tests/browser/fixtures/help-command.tsx\"></script></body></html>" },
|
||
],
|
||
})
|
||
url = fixture.url
|
||
browser = await chromium.launch({ executablePath: process.env.CODENOMAD_BROWSER_PATH || undefined })
|
||
armFixtureNavigationBudget(browser)
|
||
})
|
||
after(async () => { try { await browser?.close() } finally { try { } finally { await cache?.dispose() } } })
|
||
|
||
const hostScript = (host: string, context: string) => `
|
||
const host = ${JSON.stringify(host)}
|
||
window.__CODENOMAD_RUNTIME_HOST__ = host
|
||
window.__CODENOMAD_WINDOW_CONTEXT__ = ${JSON.stringify(context)}
|
||
window.opened = []
|
||
window.open = (...args) => { window.opened.push(args); return null }
|
||
window.failOpen = false
|
||
window.open = (...args) => { if(window.failOpen) throw new Error('opener failure'); window.opened.push(args); return null }
|
||
window.preferences = []
|
||
if (host === "electron") window.electronAPI = { onMenuAction: handler => { window.deliverMenu = handler; return () => {} },
|
||
openPreferences: async (...args) => { window.preferences.push(args[0]) } }
|
||
if (host === "tauri") {
|
||
let callback, handler
|
||
window.__TAURI_INTERNALS__ = {
|
||
metadata: { currentWebview: { label: 'main' }, currentWindow: { label: 'main' } },
|
||
transformCallback: fn => { callback = fn; return 1 },
|
||
invoke: async (command, args) => {
|
||
if (command === 'plugin:event|listen') { handler = args.handler; window.deliverMenu = payload => callback({payload}); return handler }
|
||
if (command === 'open_external_url') { if(window.failOpen) throw new Error('opener failure'); window.opened.push([command, args]) }
|
||
if (command === 'open_preferences_window') window.preferences.push(args.request.section)
|
||
},
|
||
}
|
||
}
|
||
`
|
||
|
||
const readOpenedUrl = (page: Page) => page.evaluate(() => {
|
||
const opened = (window as any).opened.at(-1)
|
||
return opened[0] === "open_external_url" ? opened[1].url : opened[0]
|
||
})
|
||
|
||
for (const [host, context] of [["web", "remote"], ["electron", "local"], ["tauri", "local"], ["electron", "remote"], ["tauri", "remote"]]) test(`${host}/${context}: native help command and palette open the live UI locale externally`, async () => {
|
||
const page = await browser.newPage()
|
||
const errors: string[] = []
|
||
page.on("pageerror", (error) => errors.push(error.message))
|
||
await page.addInitScript({ content: hostScript(host, context) })
|
||
await page.route("**/api/**", (route) => route.fulfill({ contentType: "application/json", body: "{}" }))
|
||
try {
|
||
await page.goto(url)
|
||
await page.getByRole("button", { name: "Palette", exact: true }).waitFor()
|
||
await page.getByRole("textbox", { name: "Draft" }).fill("Preserved unsent draft")
|
||
await page.evaluate(() => (window as any).helpFixture.setLocale("fr"))
|
||
if (context === "remote") {
|
||
await page.getByRole("button", { name: "Palette" }).click()
|
||
await page.getByText("Ouvrir l’aide", { exact: true }).click()
|
||
} else await page.evaluate(() => (window as any).deliverMenu("open-help"))
|
||
await page.waitForFunction(() => (window as any).opened.length === 1)
|
||
const readUrl = () => page.evaluate(() => {
|
||
const opened = (window as any).opened.at(-1)
|
||
return opened[0] === "open_external_url" ? opened[1].url : opened[0]
|
||
})
|
||
assert.equal(await readUrl(), "https://neuralnomadsai.github.io/CodeNomad/fr/")
|
||
if (context === "local") {
|
||
await page.evaluate(() => { (window as any).deliverMenu('new-instance'); (window as any).deliverMenu({invalid:'payload'}); (window as any).deliverMenu('unknown-action') })
|
||
assert.equal(await page.evaluate(() => (window as any).newInstanceCount), 1, 'other commands keep their shared dispatch')
|
||
assert.equal(await page.evaluate(() => (window as any).opened.length), 1, 'unknown menu payloads do nothing')
|
||
}
|
||
await page.evaluate(() => (window as any).helpFixture.setLocale("he"))
|
||
await page.getByRole("button", { name: "Palette" }).click()
|
||
await page.getByText("פתיחת עזרה", { exact: true }).click()
|
||
await page.waitForFunction(() => (window as any).opened.length === 2)
|
||
assert.equal(await readUrl(), "https://neuralnomadsai.github.io/CodeNomad/he/")
|
||
assert.equal(page.url(), url, "help never replaces the renderer")
|
||
assert.equal(await page.locator('textarea[aria-label="Draft"]').inputValue(), "Preserved unsent draft")
|
||
assert.equal(await page.getByRole("dialog").count(), 0, "noopener null is not a failure")
|
||
const screenshots = join(tmpdir(), "codenomad-help")
|
||
await mkdir(screenshots, { recursive: true })
|
||
await page.getByRole("button", { name: "Palette" }).click()
|
||
await page.screenshot({ path: join(screenshots, `app-help-${host}-${context}-he.png`) })
|
||
await page.evaluate(() => { (window as any).failOpen = true })
|
||
await page.getByText("פתיחת עזרה", { exact: true }).click()
|
||
await page.getByRole("dialog").waitFor()
|
||
assert.ok((await page.getByRole("dialog").innerText()).includes("https://neuralnomadsai.github.io/CodeNomad/"))
|
||
assert.equal(await page.evaluate(() => (window as any).opened.length), 2, "failed opening never replays")
|
||
assert.equal(await page.locator('textarea[aria-label="Draft"]').inputValue(), "Preserved unsent draft")
|
||
// About opens Settings → Info: native Preferences locally, the embedded screen remotely.
|
||
await page.keyboard.press("Escape")
|
||
if (context === "local") {
|
||
await page.evaluate(() => (window as any).deliverMenu("open-about"))
|
||
await page.waitForFunction(() => (window as any).preferences.length === 1)
|
||
assert.deepEqual(await page.evaluate(() => (window as any).preferences), ["info"])
|
||
} else {
|
||
await page.getByRole("button", { name: "Palette" }).click()
|
||
await page.getByText("אודות CodeNomad", { exact: true }).click()
|
||
await page.waitForFunction(() => (window as any).helpFixture.settings().open)
|
||
assert.deepEqual(await page.evaluate(() => (window as any).helpFixture.settings()), { open: true, section: "info" })
|
||
}
|
||
assert.deepEqual(errors, [])
|
||
} finally { await page.close() }
|
||
})
|
||
|
||
for (const [host, context] of [["web", "remote"], ["electron", "local"], ["tauri", "local"]]) test(`${host}/${context}: Support CodeNomad opens the localized sponsor page from the menu and palette`, async () => {
|
||
const page = await browser.newPage()
|
||
const errors: string[] = []
|
||
page.on("pageerror", (error) => errors.push(error.message))
|
||
await page.addInitScript({ content: hostScript(host, context) })
|
||
await page.route("**/api/**", (route) => route.fulfill({ contentType: "application/json", body: "{}" }))
|
||
try {
|
||
await page.goto(url)
|
||
await page.getByRole("button", { name: "Palette", exact: true }).waitFor()
|
||
await page.evaluate(() => (window as any).helpFixture.setLocale("fr"))
|
||
if (context === "remote") {
|
||
await page.getByRole("button", { name: "Palette" }).click()
|
||
await page.getByText("Soutenir CodeNomad", { exact: true }).click()
|
||
} else await page.evaluate(() => (window as any).deliverMenu("open-sponsor"))
|
||
await page.waitForFunction(() => (window as any).opened.length === 1)
|
||
assert.equal(await readOpenedUrl(page), "https://neuralnomadsai.github.io/CodeNomad/fr/sponsor.html")
|
||
await page.evaluate(() => (window as any).helpFixture.setLocale("he"))
|
||
await page.getByRole("button", { name: "Palette" }).click()
|
||
await page.getByText("תמכו ב־CodeNomad", { exact: true }).click()
|
||
await page.waitForFunction(() => (window as any).opened.length === 2)
|
||
assert.equal(await readOpenedUrl(page), "https://neuralnomadsai.github.io/CodeNomad/he/sponsor.html")
|
||
assert.equal(page.url(), url, "the sponsor page never replaces the renderer")
|
||
assert.equal(await page.getByRole("dialog").count(), 0)
|
||
await page.evaluate(() => { (window as any).failOpen = true })
|
||
await page.getByRole("button", { name: "Palette" }).click()
|
||
await page.getByText("תמכו ב־CodeNomad", { exact: true }).click()
|
||
await page.getByRole("dialog").waitFor()
|
||
assert.ok((await page.getByRole("dialog").innerText()).includes("https://neuralnomadsai.github.io/CodeNomad/he/sponsor.html"))
|
||
assert.equal(await page.evaluate(() => (window as any).opened.length), 2, "failed opening never replays")
|
||
assert.deepEqual(errors, [])
|
||
} finally { await page.close() }
|
||
})
|