236 lines
8.3 KiB
JavaScript
236 lines
8.3 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-url.js'), 'utf8'),
|
|
readFile(join(__dirname, '../lib/summon-cookie.js'), 'utf8'),
|
|
readFile(join(__dirname, '../lib/summon-http.js'), 'utf8'),
|
|
readFile(join(__dirname, '../lib/summon-html.js'), 'utf8'),
|
|
readFile(join(__dirname, '../lib/summon-css.js'), 'utf8'),
|
|
readFile(join(__dirname, '../lib/summon-layout.js'), 'utf8'),
|
|
readFile(join(__dirname, '../lib/summon-form.js'), 'utf8'),
|
|
readFile(join(__dirname, '../lib/summon-dom.js'), 'utf8'),
|
|
readFile(join(__dirname, '../lib/summon-js.js'), 'utf8'),
|
|
readFile(join(__dirname, '../lib/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 & 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')
|
|
})
|