Skip to content

Commit

Permalink
Test: automated test mechanism + first test
Browse files Browse the repository at this point in the history
  • Loading branch information
dieulot committed Aug 22, 2023
1 parent 77f50aa commit 766bf63
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 11 deletions.
98 changes: 98 additions & 0 deletions test/automatedTests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import fs from 'node:fs/promises'
import assert from 'node:assert/strict'

import constants from './constants.js'

export async function servePage(req, res) {
if (req.url == '/tests/instantpage.js') {
return serveInstantpageScript(res)
}

if (req.url == '/tests/postData') {
return handlePostData(req, res)
}

const filePath = new URL(`.${req.url}`, import.meta.url)
let fileContent = await fs.readFile(filePath, {encoding: 'utf8'})
fileContent = await fillConstantsAndEnvironment(req, fileContent)

const headers = {
'Content-Type': 'text/html',
}

await exertEnvironment(req, res, headers)

res.writeHead(200, headers)
res.write(fileContent)
res.end()
}

async function fillConstantsAndEnvironment(req, pageContent) {
const {environment} = await getConfig(req.url)
const environmentString = JSON.stringify(environment)
const constantsString = JSON.stringify(constants)
const filledPageContent = pageContent.replace(
'async (event, constants, environment) =>',
`async (event, constants = ${constantsString}, environment = ${environmentString}) =>`,
)
return filledPageContent
}

async function getConfig(reqUrl) {
const urlPath = new URL(`.${reqUrl}`, import.meta.url)
const configPath = new URL('./config.js', urlPath)
const testConfig = await import(configPath)
return testConfig
}

async function serveInstantpageScript(res) {
const path = new URL('../instantpage.js', import.meta.url)
const content = await fs.readFile(path)
res.writeHead(200, {
'Content-Type': 'text/javascript',
})
res.end(content)
}

async function exertEnvironment(req, res, headers) {
const {environment} = await getConfig(req.url)

if (environment.pageLoadTime) {
await new Promise(_ => setTimeout(_, environment.pageLoadTime))
}

if (environment.cacheMaxAge) {
assert.ok(environment.cacheMaxAge <= 20, 'cacheMaxAge > 20 (seconds!)')
headers['Cache-Control'] = `max-age=${environment.cacheMaxAge}`
}
}

async function handlePostData(req, res) {
const data = await new Promise((resolve) => {
const chunks = []
req.on('data', (chunk) => {
chunks.push(chunk)
})

req.on('end', () => {
const bodyRaw = Buffer.concat(chunks).toString()
const bodyObject = JSON.parse(bodyRaw)
resolve(bodyObject)
})
})

const testPath = new URL(req.headers.referer).pathname
const {checkExpectation} = await getConfig(testPath)
const isResultAsExpected = checkExpectation(data)
if (isResultAsExpected) {
console.log(`✅ ok!`)
}
else {
console.log(`❌ BAD`)
console.log(data)
console.log(checkExpectation.toString())
}

res.writeHead(204)
res.end()
}
4 changes: 1 addition & 3 deletions test/client/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@
<inspag-body></inspag-body>
</form>

<nav>
<u>Launch experimental automated test</u>
</nav>
<nav></nav>

<main>
10 changes: 5 additions & 5 deletions test/client/stylesheet.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ form {

nav {
grid-area: nav;
max-width: 250px;
inline-size: 250px;

background-color: hsl(0 0% 100% / .375);
}
Expand All @@ -34,7 +34,7 @@ main {
grid-area: main;
}

a {
main a {
display: block;
background: hsla(0, 0%, 100%, .25);
padding: .5em;
Expand All @@ -45,15 +45,15 @@ a {
font-size: 1.5em;
}

a:hover {
main a:hover {
border-color: hsla(0, 0%, 100%, 1);
}

a:active {
main a:active {
background: hsla(0, 0%, 0%, .25);
}

a small {
main a small {
font-size: .75em;
opacity: .75;
}
Expand Down
6 changes: 6 additions & 0 deletions test/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const constants = {
INSTANTPAGE_HOVER_DELAY: 65,
LOAD_TIME_TOLERANCE: 100, // A browser with developer tools open takes a noticeable amount of additional time to load a page
}

export default constants
45 changes: 42 additions & 3 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import fs from 'node:fs/promises'
import crypto from 'node:crypto'
import util from 'node:util'

import * as automatedTests from './automatedTests.js'

const sleep = util.promisify(setTimeout)

let PORT = parseInt(process.argv[2])
Expand All @@ -29,8 +31,11 @@ async function requestListener(req, res) {
const isPrefetched = req.headers['x-moz'] == 'prefetch' /* Firefox 109 */ || req.headers['purpose'] == 'prefetch' /* Chrome 110 & Safari 16.3 */
const prefetchIndicator = isPrefetched ? 'PF' : ' F'
const type = req.headers['sec-fetch-dest'] ? req.headers['sec-fetch-dest'].toUpperCase()[0] : '.'
const spaces = ' '.repeat(Math.max(0, 15 - req.url.length))
console.log(`${prefetchIndicator} ${type} ${req.url} ${spaces}${req.headers['user-agent']}`)
const spaces = ' '.repeat(Math.max(0, 16 - req.url.length))
const date = new Date
const dateTime = date.toLocaleTimeString('en-US', { hour12: false })
const dateMilliseconds = String(date.getMilliseconds()).padStart(3, '0')
console.log(`${dateTime}.${dateMilliseconds} ${prefetchIndicator} ${type} ${req.url} ${spaces} ${req.headers['user-agent']}`)

handleCookies(req)

Expand Down Expand Up @@ -65,6 +70,9 @@ async function requestListener(req, res) {
const favicon = await fs.readFile(faviconPath)
content = favicon
}
else if (pathString.startsWith('tests/')) {
return automatedTests.servePage(req, res)
}
else if (!isNaN(page)) {
await sleep(SLEEP_TIME)

Expand All @@ -77,7 +85,9 @@ async function requestListener(req, res) {
}

const headerPath = new URL('client/header.html', import.meta.url)
content += await fs.readFile(headerPath)
const headerTemplate = await fs.readFile(headerPath, {encoding: 'utf8'})
const header = await fillHeaderWithTests(headerTemplate)
content += header

const stylesheetPath = new URL('client/stylesheet.css', import.meta.url)
const stylesheet = await fs.readFile(stylesheetPath, {encoding: 'utf-8'})
Expand Down Expand Up @@ -208,3 +218,32 @@ function escapeHTMLTags(html) {
.replace('>', '&gt;')
return escaped
}

async function fillHeaderWithTests(header) {
const tests = []
const path = new URL('tests', import.meta.url)
const dir = await fs.readdir(path)
for (const testDir of dir) {
if (testDir == '_template') {
continue
}

const path = new URL(`tests/${testDir}/config.js`, import.meta.url)

let testConfig
try {
testConfig = await import(path)
}
catch (e) {
console.log(e.message)
continue
}

tests.push({testDir, ...testConfig})
}

let testsHtml = tests.map((value) => `<a href="/tests/${value.testDir}/index.html" data-no-instant>${value.title}${value.expectation}</a>`)
header = header.replace('<nav></nav>', `<nav>${testsHtml}</nav>`)

return header
}
20 changes: 20 additions & 0 deletions test/tests/_template/2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!doctype html>
<meta charset="utf-8">
<title>.</title>
<meta name="viewport" content="width=device-width, initial-scale=1">

<script>
addEventListener('load', async (event) => {
//

await fetch('/tests/postData', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
//
}),
})

location.assign('/')
})
</script>
12 changes: 12 additions & 0 deletions test/tests/_template/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const title = ''

export const environment = {
//pageLoadTime: 0,
//cacheMaxAge: false,
}

export const expectation = ''

export function checkExpectation(data) {
return false
}
13 changes: 13 additions & 0 deletions test/tests/_template/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<meta charset="utf-8">
<title>.</title>
<meta name="viewport" content="width=device-width, initial-scale=1">

<!-- dom -->

<script>
addEventListener('load', async (event, constants, environment) => {
//
})
</script>
<script src="../instantpage.js" type="module"></script>
20 changes: 20 additions & 0 deletions test/tests/hover-long-enough-then-click/2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!doctype html>
<meta charset="utf-8">
<title>.</title>
<meta name="viewport" content="width=device-width, initial-scale=1">

<script>
addEventListener('load', async (event) => {
const navigationPerformanceEntry = performance.getEntriesByType('navigation')[0]

await fetch('/tests/postData', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
transferSize: navigationPerformanceEntry.transferSize,
}),
})

location.assign('/')
})
</script>
12 changes: 12 additions & 0 deletions test/tests/hover-long-enough-then-click/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const title = 'Hover long enough to prefetch, then click (no cache)'

export const environment = {
pageLoadTime: 200,
cacheMaxAge: false,
}

export const expectation = 'Navigated page is from prefetch cache'

export function checkExpectation(data) {
return data.transferSize === 0
}
16 changes: 16 additions & 0 deletions test/tests/hover-long-enough-then-click/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<meta charset="utf-8">
<title>.</title>
<meta name="viewport" content="width=device-width, initial-scale=1">

<a href="2.html">.</a>

<script>
addEventListener('load', async (event, constants, environment) => {
const $a = document.querySelector('a')
$a.dispatchEvent(new MouseEvent('mouseover'))
await new Promise(_ => setTimeout(_, constants.INSTANTPAGE_HOVER_DELAY + environment.pageLoadTime + constants.LOAD_TIME_TOLERANCE))
$a.click()
})
</script>
<script src="../instantpage.js" type="module"></script>

0 comments on commit 766bf63

Please sign in to comment.