mirror of
https://github.com/OliveTin/OliveTin
synced 2026-09-23 02:05:42 +00:00
fix: resolve remaining PR #1115 review items
Use AbortSignal.timeout for theme fetches, load themes without blocking init, and add pageGeneration regression coverage for runner config changes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
"stylelint-config-standard": "^40.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node --test resources/vue/components/*.test.mjs resources/vue/utils/*.test.mjs resources/vue/stores/*.test.mjs"
|
||||
"test": "node --test resources/vue/components/*.test.mjs resources/vue/utils/*.test.mjs resources/vue/stores/*.test.mjs ../integration-tests/runner.test.mjs"
|
||||
},
|
||||
"author": "",
|
||||
"parcelIgnore": [
|
||||
|
||||
@@ -340,7 +340,9 @@ async function updateHeaderFromInit () {
|
||||
loadCustomJsIfEnabled()
|
||||
|
||||
renderNavigation()
|
||||
await applyTheme()
|
||||
applyTheme().catch((err) => {
|
||||
console.warn('Failed to load theme CSS:', err)
|
||||
})
|
||||
|
||||
if (loginRequired.value) {
|
||||
connectEventStreamIfNeeded()
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
const DEFAULT_THEME_FETCH_TIMEOUT_MS = 5000
|
||||
|
||||
async function fetchWithTimeout (url, options, timeoutMs) {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
|
||||
try {
|
||||
return await fetch(url, { ...options, signal: controller.signal })
|
||||
} finally {
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
}
|
||||
|
||||
export async function applyThemeStyles (themePreference = '', timeoutMs = DEFAULT_THEME_FETCH_TIMEOUT_MS) {
|
||||
export async function applyThemeStyles (themePreference = '') {
|
||||
let themeStyle = document.getElementById('theme-style')
|
||||
|
||||
if (!themeStyle) {
|
||||
@@ -24,7 +14,10 @@ export async function applyThemeStyles (themePreference = '', timeoutMs = DEFAUL
|
||||
? `/custom-webui/themes/${encodeURIComponent(themePreference)}/theme.css`
|
||||
: '/theme.css'
|
||||
|
||||
const response = await fetchWithTimeout(themeUrl, { cache: 'no-store' }, timeoutMs)
|
||||
const response = await fetch(themeUrl, {
|
||||
cache: 'no-store',
|
||||
signal: AbortSignal.timeout(DEFAULT_THEME_FETCH_TIMEOUT_MS)
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw new Error(`theme fetch failed: ${response.status}`)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
function installThemeDom () {
|
||||
const styleEl = { textContent: '' }
|
||||
const body = { attributes: {}, setAttribute (name, value) { this.attributes[name] = value } }
|
||||
|
||||
globalThis.document = {
|
||||
getElementById: (id) => (id === 'theme-style' ? styleEl : null),
|
||||
createElement: () => styleEl,
|
||||
head: { appendChild: () => {} },
|
||||
body
|
||||
}
|
||||
|
||||
return { styleEl, body }
|
||||
}
|
||||
|
||||
test('applyThemeStyles times out when the response body stalls', async (t) => {
|
||||
const originalFetch = globalThis.fetch
|
||||
const originalDocument = globalThis.document
|
||||
const originalAbortSignal = globalThis.AbortSignal
|
||||
|
||||
t.after(() => {
|
||||
globalThis.fetch = originalFetch
|
||||
globalThis.document = originalDocument
|
||||
globalThis.AbortSignal = originalAbortSignal
|
||||
})
|
||||
|
||||
installThemeDom()
|
||||
|
||||
globalThis.AbortSignal = {
|
||||
timeout () {
|
||||
const controller = new AbortController()
|
||||
setTimeout(() => controller.abort(), 50)
|
||||
return controller.signal
|
||||
}
|
||||
}
|
||||
|
||||
globalThis.fetch = (_url, options) => Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
text () {
|
||||
return new Promise((_resolve, reject) => {
|
||||
options.signal.addEventListener('abort', () => {
|
||||
reject(new DOMException('The operation was aborted.', 'AbortError'))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const { applyThemeStyles } = await import('./themeLoader.js')
|
||||
|
||||
await assert.rejects(
|
||||
() => applyThemeStyles(''),
|
||||
(err) => err.name === 'AbortError' || err.name === 'TimeoutError'
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,38 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import getRunner from './runner.mjs'
|
||||
|
||||
test('OliveTinTestRunnerVm.start advances pageGeneration', async (t) => {
|
||||
const originalRunner = process.env.OLIVETIN_TEST_RUNNER
|
||||
const originalIp = process.env.IP
|
||||
const originalPort = process.env.PORT
|
||||
|
||||
t.after(() => {
|
||||
if (originalRunner === undefined) {
|
||||
delete process.env.OLIVETIN_TEST_RUNNER
|
||||
} else {
|
||||
process.env.OLIVETIN_TEST_RUNNER = originalRunner
|
||||
}
|
||||
if (originalIp === undefined) {
|
||||
delete process.env.IP
|
||||
} else {
|
||||
process.env.IP = originalIp
|
||||
}
|
||||
if (originalPort === undefined) {
|
||||
delete process.env.PORT
|
||||
} else {
|
||||
process.env.PORT = originalPort
|
||||
}
|
||||
})
|
||||
|
||||
process.env.OLIVETIN_TEST_RUNNER = 'vm'
|
||||
process.env.IP = '127.0.0.1'
|
||||
process.env.PORT = '1337'
|
||||
|
||||
const runner = getRunner()
|
||||
const before = runner.pageGeneration
|
||||
|
||||
await runner.start('pageGenerationFirst')
|
||||
|
||||
assert.equal(runner.pageGeneration, before + 1)
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
import { describe, it, after, afterEach } from 'mocha'
|
||||
import { expect } from 'chai'
|
||||
import {
|
||||
getRootAndWait,
|
||||
takeScreenshotOnFailure,
|
||||
} from '../../lib/elements.js'
|
||||
|
||||
describe('runner: pageGeneration', function () {
|
||||
after(async () => {
|
||||
await runner.stop()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
takeScreenshotOnFailure(this.currentTest, webdriver)
|
||||
})
|
||||
|
||||
it('reloads the dashboard after sequential config changes', async function () {
|
||||
await runner.start('pageGenerationFirst')
|
||||
await getRootAndWait()
|
||||
|
||||
let loadedDashboard = await webdriver.executeScript(
|
||||
'return document.body.getAttribute("loaded-dashboard")'
|
||||
)
|
||||
expect(loadedDashboard).to.equal('Page Generation First')
|
||||
|
||||
await runner.start('pageGenerationSecond')
|
||||
await getRootAndWait()
|
||||
|
||||
loadedDashboard = await webdriver.executeScript(
|
||||
'return document.body.getAttribute("loaded-dashboard")'
|
||||
)
|
||||
expect(loadedDashboard).to.equal('Page Generation Second')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
|
||||
actions:
|
||||
- title: page-gen-first-action
|
||||
shell: echo first
|
||||
|
||||
dashboards:
|
||||
- title: Page Generation First
|
||||
contents:
|
||||
- title: page-gen-first-action
|
||||
@@ -0,0 +1,13 @@
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
|
||||
actions:
|
||||
- title: page-gen-second-action
|
||||
shell: echo second
|
||||
|
||||
dashboards:
|
||||
- title: Page Generation Second
|
||||
contents:
|
||||
- title: page-gen-second-action
|
||||
Reference in New Issue
Block a user