Files
bare-operating-system/packages/bare-os-coreutils/test/summon-engine.test.mjs
T
2026-08-18 18:11:34 -04:00

236 lines
8.4 KiB
JavaScript

import test from 'brittle'
import { readFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
async function load() {
const parts = await Promise.all([
readFile(join(__dirname, '../lib/summon/summon-url.js'), 'utf8'),
readFile(join(__dirname, '../lib/summon/summon-cookie.js'), 'utf8'),
readFile(join(__dirname, '../lib/summon/summon-http.js'), 'utf8'),
readFile(join(__dirname, '../lib/summon/summon-html.js'), 'utf8'),
readFile(join(__dirname, '../lib/summon/summon-css.js'), 'utf8'),
readFile(join(__dirname, '../lib/summon/summon-layout.js'), 'utf8'),
readFile(join(__dirname, '../lib/summon/summon-form.js'), 'utf8'),
readFile(join(__dirname, '../lib/summon/summon-dom.js'), 'utf8'),
readFile(join(__dirname, '../lib/summon/summon-js.js'), 'utf8'),
readFile(join(__dirname, '../lib/summon/summon-session.js'), 'utf8')
])
const hook = {}
new Function(
'exports',
parts.join('\n') +
`
exports.bareSummonParseUrl = bareSummonParseUrl;
exports.bareSummonResolveUrl = bareSummonResolveUrl;
exports.bareSummonUrlOk = bareSummonUrlOk;
exports.bareSummonCreateCookieJar = bareSummonCreateCookieJar;
exports.bareSummonFetch = bareSummonFetch;
exports.bareSummonParseHtml = bareSummonParseHtml;
exports.bareSummonTextContent = bareSummonTextContent;
exports.bareSummonParseStylesheet = bareSummonParseStylesheet;
exports.bareSummonLayout = bareSummonLayout;
exports.bareSummonReaderText = bareSummonReaderText;
exports.bareSummonEncodeForm = bareSummonEncodeForm;
exports.bareSummonCollectForm = bareSummonCollectForm;
exports.bareSummonSubmitUrl = bareSummonSubmitUrl;
exports.bareSummonCreateSession = bareSummonCreateSession;
exports.bareSummonCurrent = bareSummonCurrent;
exports.bareSummonAboutBody = bareSummonAboutBody;
exports.bareSummonApplyDocument = bareSummonApplyDocument;
exports.bareSummonNavigate = bareSummonNavigate;
exports.bareSummonAddBookmark = bareSummonAddBookmark;
exports.bareSummonNewTab = bareSummonNewTab;
exports.bareSummonMatches = bareSummonMatches;
exports.bareSummonQueryAll = bareSummonQueryAll;
exports.bareSummonCreateDocumentApi = bareSummonCreateDocumentApi;
exports.bareSummonRunDocumentJs = bareSummonRunDocumentJs;
exports.bareSummonEnhanceDocument = bareSummonEnhanceDocument;
exports.bareSummonScriptSource = bareSummonScriptSource;
`
)(hook)
return hook
}
test('summon URL parse / resolve / reject javascript', async (t) => {
const h = await load()
const about = h.bareSummonParseUrl('about:summon')
t.ok(about)
t.is(about.protocol, 'about:')
t.is(about.pathname, 'summon')
const https = h.bareSummonParseUrl('https://example.com/a')
t.ok(h.bareSummonUrlOk(https))
const rel = h.bareSummonResolveUrl('/b', 'https://example.com/a')
t.is(rel.href, 'https://example.com/b')
t.is(
h.bareSummonResolveUrl('javascript:alert(1)', 'https://example.com/'),
null
)
})
test('summon cookie jar first-party + path', async (t) => {
const h = await load()
const jar = h.bareSummonCreateCookieJar()
t.ok(
jar.put(
'sid=abc; Path=/app; Domain=example.com',
'https://example.com/app/page'
)
)
t.is(jar.headerFor('https://example.com/app/x'), 'sid=abc')
t.is(jar.headerFor('https://other.com/app/x'), '')
t.absent(
jar.put('evil=1; Domain=other.com', 'https://example.com/', {
thirdParty: false
})
)
})
test('summon HTML tokenizer + tree + script count', async (t) => {
const h = await load()
const doc = h.bareSummonParseHtml(
'<!doctype html><html><head><title>Hi &amp; Bye</title><script>evil()</script></head>' +
'<body><p>Hello <b>world</b></p><img src="x.png" alt="pic"><br><a href="/n">next</a></body></html>'
)
t.is(doc.title, 'Hi & Bye')
t.is(doc.scriptsBlocked, 1)
const text = h.bareSummonTextContent(doc.body)
t.ok(text.includes('Hello'))
t.ok(text.includes('world'))
t.absent(text.includes('evil'))
})
test('summon CSS hide script + layout numbers links', async (t) => {
const h = await load()
const doc = h.bareSummonParseHtml(
'<html><head><title>T</title><style>p { display: none }</style></head>' +
'<body><h1>Head</h1><p>hidden</p><a href="https://ex.org/a">Alpha</a> and ' +
'<a href="/b">Beta</a></body></html>'
)
const sheets = []
function collect(n) {
if (n && n.type === 'element' && n.name === 'style') {
sheets.push(h.bareSummonParseStylesheet(h.bareSummonTextContent(n)))
}
const ch = (n && n.children) || []
for (const c of ch) collect(c)
}
collect(doc.head)
const layout = h.bareSummonLayout(doc, { cols: 40, sheets })
t.is(layout.title, 'T')
t.is(layout.links.length, 2)
t.ok(layout.plain.join('\n').includes('[1]'))
t.ok(layout.plain.join('\n').includes('Alpha'))
t.absent(layout.plain.join('\n').includes('hidden'))
})
test('summon form GET encode', async (t) => {
const h = await load()
const doc = h.bareSummonParseHtml(
'<form action="/s" method="get"><input name="q" value="hi there"><button type="submit">go</button></form>'
)
const form = doc.body.children.find((n) => n.name === 'form')
const spec = h.bareSummonCollectForm(form)
t.is(spec.method, 'GET')
const sub = h.bareSummonSubmitUrl(spec, 'https://example.com/page')
t.ok(sub.url.includes('https://example.com/s?'))
t.ok(sub.url.includes('q=hi%20there'))
})
test('summon about: pages + navigate without HTTP', async (t) => {
const h = await load()
const ctx = {
env: { HOME: '/home/guest' },
vfs: {},
console: { log() {}, error() {} }
}
const session = h.bareSummonCreateSession(ctx, { url: 'about:summon' })
const tab = await h.bareSummonNavigate(session, 'about:summon')
t.is(tab.status, 200)
t.ok(tab.layout.plain.join('\n').toLowerCase().includes('summon'))
h.bareSummonAddBookmark(session, 'https://example.com/', 'ex')
const marks = await h.bareSummonNavigate(session, 'about:bookmarks')
t.ok(
(marks.layout.links || []).some((l) =>
String(l.href || '').includes('example.com')
)
)
h.bareSummonNewTab(session, 'about:blank')
t.is(session.tabs.length, 2)
})
test('summon HTTP fetch follows redirect and stores cookie', async (t) => {
const h = await load()
const hops = []
const ctx = {
httpFetch: async (url, opts) => {
hops.push({
url,
method: opts.method,
cookie: (opts.headers || {}).cookie
})
if (url === 'https://example.com/go') {
return {
status: 302,
headers: { location: '/next', 'set-cookie': 'tok=1; Path=/' },
text: async () => ''
}
}
return {
status: 200,
headers: { 'content-type': 'text/html' },
text: async () => '<html><body><p>ok</p></body></html>'
}
}
}
const jar = h.bareSummonCreateCookieJar()
const res = await h.bareSummonFetch(ctx, 'https://example.com/go', { jar })
t.ok(res.ok)
t.is(res.url, 'https://example.com/next')
t.is(res.redirects, 1)
t.ok(res.body.includes('ok'))
t.is(jar.headerFor('https://example.com/next'), 'tok=1')
t.is(hops.length, 2)
})
test('summon keeps script source and matches descendant CSS', async (t) => {
const h = await load()
const doc = h.bareSummonParseHtml(
'<div id="box"><p class="x">hi</p><span>no</span></div><script>var n=1</script>'
)
t.ok(doc.scripts.length >= 1)
t.ok(h.bareSummonScriptSource(doc.scripts[0]).includes('var n=1'))
const p = h.bareSummonQueryAll(doc.body, 'div p.x', 10)
t.is(p.length, 1)
t.ok(h.bareSummonMatches('div > p', p[0]))
t.absent(h.bareSummonMatches('div > span.x', p[0]))
})
test('summon page JS mutates DOM then layout sees it', async (t) => {
const h = await load()
const ctx = {
env: { HOME: '/home/guest' },
vfs: {},
console: { log() {}, error() {} }
}
const session = h.bareSummonCreateSession(ctx, {
url: 'about:blank',
cols: 40
})
session.config.javascript = true
const html =
'<html><head><title>old</title></head><body>' +
'<p id="t">before</p>' +
'<script>document.getElementById("t").textContent="after"; document.title="js-ok"</script>' +
'</body></html>'
const tab = session.tabs[0]
h.bareSummonApplyDocument(session, tab, html, 'about:blank')
await h.bareSummonEnhanceDocument(session, tab, 'about:blank')
t.is(tab.js.ran, 1)
t.ok(tab.layout.plain.join('\n').includes('after'))
t.absent(tab.layout.plain.join('\n').includes('before'))
t.is(tab.doc.title, 'js-ok')
})