Some checks failed
ci / go (push) Has been cancelled
ci / admin-ui (push) Has been cancelled
ci / runtime-sdk (push) Has been cancelled
ci / helm (push) Has been cancelled
ci / openapi (push) Has been cancelled
ci / container (push) Has been cancelled
ci / release-config (push) Has been cancelled
ci / shared styling (push) Has been cancelled
ci / security (push) Has been cancelled
ci / all green (push) Has been cancelled
167 lines
No EOL
7.4 KiB
JavaScript
167 lines
No EOL
7.4 KiB
JavaScript
/**
|
|
* The go-tell pilot check.
|
|
*
|
|
* Injected into a *copy* of go-tell's shell; go-tell's own source is untouched.
|
|
*
|
|
* go-tell is the hard case among the applications: before this it had no CSS custom
|
|
* properties at all, 340 hardcoded hex values and 153 per-palette override rules. Its
|
|
* stylesheet now reads from the shared contract, so the question this answers is
|
|
* whether that rewrite actually works, and whether the two shipped controls can drive
|
|
* an application whose mechanism is `html[data-theme="…"]` rather than a class or a
|
|
* `--theme` variable.
|
|
*/
|
|
import { getContractState } from '/assets/components.js'
|
|
|
|
const results = []
|
|
const check = (name, pass, detail = '') => results.push({ name, pass: !!pass, detail: String(detail) })
|
|
|
|
const root = document.documentElement
|
|
const state = getContractState()
|
|
|
|
/** The computed background of the page, which is what a palette actually looks like. */
|
|
const pageBg = () => getComputedStyle(document.body).backgroundColor
|
|
/** The computed colour of a heading, which proves the text role resolved. */
|
|
const headingColour = () => {
|
|
const el = document.querySelector('.brand, h1, .header')
|
|
return el ? getComputedStyle(el).color : ''
|
|
}
|
|
|
|
/**
|
|
* Waits past this application's own CSS transitions.
|
|
*
|
|
* go-tell transitions `background` over 300ms, so reading a computed background sooner
|
|
* than that returns a value mid-animation and makes a working palette switch look
|
|
* broken. The wait is deliberately longer than the longest transition in the
|
|
* stylesheet rather than merely "long enough today".
|
|
*/
|
|
async function settle() {
|
|
await new Promise((r) => setTimeout(r, 450))
|
|
}
|
|
|
|
async function run() {
|
|
await new Promise((r) => setTimeout(r, 600)) // the selector's fetch
|
|
|
|
const selector = document.querySelector('bananas-theme-selector')
|
|
const toggle = document.querySelector('bananas-appearance-toggle')
|
|
const shadow = (el) => /** @type {ShadowRoot} */ (el.shadowRoot)
|
|
|
|
check('the controls upgraded', !!selector && !!toggle && !!shadow(selector) && !!shadow(toggle))
|
|
if (!selector || !toggle) return finish()
|
|
|
|
const buttons = [...shadow(selector).querySelectorAll('button')]
|
|
const labels = buttons.map((b) => b.textContent.trim())
|
|
check('palettes are listed from the service', buttons.length > 1, labels.join(', '))
|
|
|
|
// go-tell's own four palettes must all be offered, because those are the slugs its
|
|
// stylesheet and its rules key on.
|
|
for (const name of ['Cyberpunk', 'Nordic Moss', 'Deep Space Obsidian', 'Terminal Retro']) {
|
|
check(`go-tell's "${name}" is offered`, labels.some((l) => l.includes(name)))
|
|
}
|
|
|
|
// The decisive one: the contract must reach go-tell's page, which no longer
|
|
// defines a single custom property of its own.
|
|
const surface = getComputedStyle(document.body).getPropertyValue('--bn-surface').trim()
|
|
|
|
// Diagnostics, because "the contract did not reach the page" has three very
|
|
// different causes and the symptom alone cannot tell them apart.
|
|
const link = document.querySelector('link[data-theme-contract]')
|
|
const linkHref = link ? link.href : '(no link element)'
|
|
let linkLoaded = 'unknown'
|
|
if (link && link.sheet) {
|
|
try {
|
|
linkLoaded = String(link.sheet.cssRules.length) + ' rules'
|
|
} catch {
|
|
// A cross-origin sheet is unreadable by design. Reaching this branch means the
|
|
// stylesheet loaded and the browser is refusing to expose its rules, which is
|
|
// the healthy case for a cross-origin link.
|
|
linkLoaded = 'loaded (cross-origin, rules not readable)'
|
|
}
|
|
} else if (link) {
|
|
linkLoaded = 'not loaded'
|
|
}
|
|
|
|
check('the contract link is present', !!link, linkHref)
|
|
check('the contract stylesheet loaded', linkLoaded !== 'not loaded', linkLoaded)
|
|
check('the contract reaches go-tell', surface !== '', 'href=' + linkHref + ' loaded=' + linkLoaded + ' data-theme=' + root.getAttribute('data-theme') + ' data-mode=' + root.getAttribute('data-mode'))
|
|
check('--bn-surface has a real value', surface !== '' && surface !== 'rgba(0, 0, 0, 0)', surface)
|
|
|
|
// Switching a palette must move the page, through go-tell's own rules.
|
|
let startBg = pageBg()
|
|
const startText = headingColour()
|
|
|
|
for (const name of ['Nordic Moss', 'Deep Space Obsidian', 'Terminal Retro', 'Cyberpunk']) {
|
|
const button = buttons.find((b) => b.textContent.trim().includes(name))
|
|
if (!button) {
|
|
check(`selecting ${name}`, false, 'button not found')
|
|
continue
|
|
}
|
|
|
|
// The loop returns to Cyberpunk at the end, which is where it started. Selecting
|
|
// the palette that is already showing cannot change anything, so the "did the
|
|
// page move" assertion does not apply to that step.
|
|
const alreadyShowing = buttons.find((b) => b.getAttribute('aria-checked') === 'true')
|
|
const wasThis = alreadyShowing && alreadyShowing.textContent.trim().includes(name)
|
|
|
|
button.click()
|
|
await settle()
|
|
|
|
const slug = root.getAttribute('data-theme')
|
|
const contractBg = getComputedStyle(document.body).getPropertyValue('--bn-bg').trim()
|
|
check(`${name} writes data-theme`, !!slug, slug)
|
|
if (wasThis) {
|
|
check(`${name} is a no-op when already showing`, pageBg() === startBg, pageBg())
|
|
} else {
|
|
check(`${name} changes the page`, pageBg() !== startBg || headingColour() !== startText,
|
|
`slug=${slug} mode=${root.getAttribute('data-mode')} bodyBg=${pageBg()} --bn-bg=${contractBg} text=${headingColour()}`)
|
|
}
|
|
startBg = pageBg()
|
|
}
|
|
|
|
// The terminal palette forces a monospace face with !important on every element.
|
|
// Shadow DOM is supposed to make that impossible to reach the controls in; if the
|
|
// controls inherit the terminal face here, the isolation has failed.
|
|
const terminal = buttons.find((b) => b.textContent.trim().includes('Terminal'))
|
|
if (terminal) {
|
|
terminal.click()
|
|
await settle()
|
|
const font = getComputedStyle(terminal).fontFamily
|
|
check('the controls are not restyled by the terminal palette', !font.includes('Courier'), font)
|
|
check('the app itself did adopt the monospace face',
|
|
getComputedStyle(document.body).fontFamily.includes('Courier') ||
|
|
getComputedStyle(document.body).fontFamily.includes('monospace'),
|
|
getComputedStyle(document.body).fontFamily)
|
|
}
|
|
|
|
// The appearance axis: go-tell had no light mode before this. The control writing
|
|
// data-mode is what makes one possible.
|
|
const toggleButtons = [...shadow(toggle).querySelectorAll('button')]
|
|
check('the appearance toggle has three states', toggleButtons.length === 3,
|
|
toggleButtons.map((b) => b.getAttribute('aria-label')).join(', '))
|
|
|
|
const light = toggleButtons.find((b) => b.getAttribute('aria-label') === 'Light')
|
|
if (light) {
|
|
light.click()
|
|
await settle()
|
|
check('light writes data-mode', root.getAttribute('data-mode') === 'light', root.getAttribute('data-mode'))
|
|
}
|
|
|
|
check('the contract store is reachable', state && typeof state.selectTheme === 'function')
|
|
finish()
|
|
}
|
|
|
|
function finish() {
|
|
const failed = results.filter((r) => !r.pass)
|
|
const out = document.createElement('pre')
|
|
out.id = 'pilot'
|
|
out.textContent = failed.length
|
|
? 'PILOT FAIL ' + failed.length + '/' + results.length + ' :: ' + failed.map((f) => f.name + ' [' + f.detail + ']').join(' | ')
|
|
: 'PILOT PASS ' + results.length + ' :: ' + results.map((f) => f.name).join(' | ')
|
|
document.body.append(out)
|
|
}
|
|
|
|
run().catch((error) => {
|
|
const out = document.createElement('pre')
|
|
out.id = 'pilot'
|
|
out.textContent = 'PILOT FAIL the check threw: ' + (error instanceof Error ? error.message : String(error))
|
|
document.body.append(out)
|
|
}) |