124 lines
6.3 KiB
JavaScript
124 lines
6.3 KiB
JavaScript
|
|
/**
|
||
|
|
* The pilot check's reporter.
|
||
|
|
*
|
||
|
|
* It is injected into a *copy* of go-loose's page; go-loose's own source is untouched.
|
||
|
|
*
|
||
|
|
* The question it answers is narrow and specific: does the component shipped by
|
||
|
|
* go-bananas change the attributes that go-loose's own hand-written stylesheet already
|
||
|
|
* keys on? If it does, go-loose is themed by the shared contract without a single edit
|
||
|
|
* to its CSS, which is the entire claim of ADR 0005.
|
||
|
|
*/
|
||
|
|
import { getContractState } from '/rt/v1/components.js'
|
||
|
|
|
||
|
|
const results = []
|
||
|
|
/** @param {string} name @param {boolean} pass @param {string} [detail] */
|
||
|
|
const check = (name, pass, detail = '') => results.push({ name, pass: !!pass, detail: String(detail) })
|
||
|
|
|
||
|
|
const root = document.documentElement
|
||
|
|
const state = getContractState()
|
||
|
|
|
||
|
|
/** The computed background of go-loose's own page surface. */
|
||
|
|
function pageBackground() {
|
||
|
|
return getComputedStyle(document.body).backgroundColor
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The value go-loose's page sees for a contract custom property.
|
||
|
|
*
|
||
|
|
* This is the claim worth testing: go-loose's stylesheet never mentions --bn-* or
|
||
|
|
* --surface being defined anywhere, yet reading the property off its own body proves
|
||
|
|
* the contract reached the page through the one stylesheet link.
|
||
|
|
*/
|
||
|
|
function contractValueOnHost(name) {
|
||
|
|
return getComputedStyle(document.body).getPropertyValue(name).trim()
|
||
|
|
}
|
||
|
|
|
||
|
|
/** The text colour go-loose's own CSS takes from --text. */
|
||
|
|
function pageText() {
|
||
|
|
return getComputedStyle(document.body).color
|
||
|
|
}
|
||
|
|
|
||
|
|
async function run() {
|
||
|
|
// The selector fetches its palette list from the service. The wait has to cover a
|
||
|
|
// real cross-origin round trip, not the instant rejection that used to happen when
|
||
|
|
// the service sent no CORS headers: that failure was fast enough to hide behind a
|
||
|
|
// short settle time, and the wait was tuned to it.
|
||
|
|
await new Promise((r) => setTimeout(r, 4000))
|
||
|
|
|
||
|
|
const selector = document.querySelector('bananas-theme-selector')
|
||
|
|
const toggle = document.querySelector('bananas-appearance-toggle')
|
||
|
|
const shadow = (el) => /** @type {ShadowRoot} */ (el.shadowRoot)
|
||
|
|
|
||
|
|
check('the selector element upgraded', !!shadow(selector), selector ? selector.tagName : 'absent')
|
||
|
|
check('the toggle element upgraded', !!shadow(toggle), toggle ? toggle.tagName : 'absent')
|
||
|
|
|
||
|
|
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-loose's four palettes must all be offered, because those are the slugs its own
|
||
|
|
// CSS has blocks for.
|
||
|
|
for (const slug of ['Tokyo Night', 'Catppuccin', 'Tomorrow', 'Kanagawa']) {
|
||
|
|
check(`go-loose's "${slug}" is offered`, labels.includes(slug))
|
||
|
|
}
|
||
|
|
|
||
|
|
// The contract stylesheet loaded and reached go-loose's page.
|
||
|
|
const bgTokyo = pageBackground()
|
||
|
|
check('go-loose\'s page is themed through the contract', bgTokyo !== 'rgba(0, 0, 0, 0)' && bgTokyo !== '', bgTokyo)
|
||
|
|
|
||
|
|
// The decisive test: change the palette with the component, and confirm go-loose's
|
||
|
|
// OWN stylesheet responded. Nothing in style.css was edited.
|
||
|
|
const startBg = bgTokyo
|
||
|
|
const startText = pageText()
|
||
|
|
const kanagawa = buttons.find((b) => b.textContent.trim() === 'Kanagawa')
|
||
|
|
if (!kanagawa) {
|
||
|
|
check('the Kanagawa button was found', false, labels.join(', '))
|
||
|
|
} else {
|
||
|
|
kanagawa.click()
|
||
|
|
await new Promise((r) => setTimeout(r, 60))
|
||
|
|
check('selecting a palette writes data-theme', root.getAttribute('data-theme') === 'kanagawa', root.getAttribute('data-theme'))
|
||
|
|
check('selecting a palette writes the legacy attribute', root.getAttribute('data-bananas-theme') === 'kanagawa', root.getAttribute('data-bananas-theme'))
|
||
|
|
check("go-loose's own CSS followed the palette", pageBackground() !== startBg, startBg + ' -> ' + pageBackground())
|
||
|
|
// The alias layer reached a page that never referenced it.
|
||
|
|
check('go-loose\'s page reads --surface from the contract', contractValueOnHost('--surface') !== '', contractValueOnHost('--surface'))
|
||
|
|
check('go-loose\'s page reads --bn-surface from the contract', contractValueOnHost('--bn-surface') !== '', contractValueOnHost('--bn-surface'))
|
||
|
|
check('the alias and the contract agree', contractValueOnHost('--surface') === contractValueOnHost('--bn-surface'),
|
||
|
|
contractValueOnHost('--surface') + ' vs ' + contractValueOnHost('--bn-surface'))
|
||
|
|
}
|
||
|
|
|
||
|
|
// And the appearance axis, which go-loose has never had.
|
||
|
|
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 new Promise((r) => setTimeout(r, 60))
|
||
|
|
check('light writes data-mode', root.getAttribute('data-mode') === 'light', root.getAttribute('data-mode'))
|
||
|
|
check("go-loose's own CSS followed the appearance", pageBackground() !== 'rgb(0, 0, 0)', pageBackground())
|
||
|
|
}
|
||
|
|
|
||
|
|
// Isolation: go-loose has a broad `.card button { all: unset }`-style surface, and a
|
||
|
|
// control that leaked would be reset by it.
|
||
|
|
const first = shadow(selector).querySelector('button')
|
||
|
|
const style = first ? getComputedStyle(first) : null
|
||
|
|
check('the control is not reset by the host page', !!style && style.padding !== '0px', style ? style.padding : 'no button')
|
||
|
|
check('the control is not inheriting the page font', !!style && !style.fontFamily.includes('DM Sans'), style ? style.fontFamily : '')
|
||
|
|
|
||
|
|
// The store is reachable, so go-loose can read or set the selection itself.
|
||
|
|
check('the contract store is reachable from the module', state && typeof state.selectTheme === 'function')
|
||
|
|
|
||
|
|
const failed = results.filter((r) => !r.pass)
|
||
|
|
const out = document.getElementById('pilot') || 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)
|
||
|
|
})
|