mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-20 11:10:44 +08:00
Fix preset publication, volatile settings and client form binding
This commit is contained in:
parent
fe0fe70d33
commit
0b9a3a82bc
66 changed files with 8546 additions and 3690 deletions
107
test/client-settings-binding.test.mjs
Normal file
107
test/client-settings-binding.test.mjs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { homedir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { runInNewContext } from 'node:vm'
|
||||
import { Context, Service } from '@deepseek-ai/cordis'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
// The shell seeds React from its own dependency tree, not the plugin's local
|
||||
// development junction. Use that same installed peer tree for this built smoke.
|
||||
const profileHome = process.env.DSH_HOME || join(homedir(), '.dsh')
|
||||
const renderer = ['', 'desktop', 'web'].map(profile => join(profileHome, 'profiles', profile, 'node_modules',
|
||||
'@deepseek-ai', 'dsh-client-ui-renderer', 'package.json')).find(existsSync)
|
||||
const sharedRequire = renderer ? createRequire(renderer) : require
|
||||
function loadClient() {
|
||||
let client
|
||||
runInNewContext(readFileSync(new URL('../lib/client.js', import.meta.url), 'utf8'), {
|
||||
window: { __ModuleLoader__: { load({ factory }) { client = factory(spec =>
|
||||
spec === 'react' || spec.startsWith('react/') ? sharedRequire(spec) : require(spec)) } } },
|
||||
setTimeout, clearTimeout, setInterval, clearInterval, console, AbortController,
|
||||
})
|
||||
return client
|
||||
}
|
||||
const pause = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||
|
||||
function hostForm() {
|
||||
let snapshot = { status: 'ready', writable: true, value: { toolsEnabled: true, groupOpsPresets: 'standard', apiUrl: 'http://127.0.0.1:8890' },
|
||||
user: {}, base: {}, revision: 1 }
|
||||
const listeners = new Set(), writes = []
|
||||
return {
|
||||
writes, getSnapshot: () => snapshot,
|
||||
subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn) },
|
||||
async set(field, value) {
|
||||
writes.push([field, value])
|
||||
snapshot = { ...snapshot, value: { ...snapshot.value, [field]: value }, user: { ...snapshot.user, [field]: value }, revision: snapshot.revision + 1 }
|
||||
for (const fn of listeners) fn()
|
||||
return true
|
||||
},
|
||||
async unset(field) {
|
||||
const user = { ...snapshot.user }; delete user[field]
|
||||
snapshot = { ...snapshot, user, revision: snapshot.revision + 1 }
|
||||
for (const fn of listeners) fn()
|
||||
return true
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function fixture(t, legacy = false) {
|
||||
const root = new Context(), owners = [], slots = new Map(), actual = hostForm()
|
||||
class Slots extends Service {
|
||||
constructor(ctx) { super(ctx, 'slots') }
|
||||
inject(name, register) { return register() }
|
||||
register(spec) {
|
||||
const key = `${spec.name}:${spec.id ?? spec.key}`
|
||||
slots.set(key, spec)
|
||||
return () => slots.delete(key)
|
||||
}
|
||||
}
|
||||
class Locale extends Service {
|
||||
constructor(ctx) { super(ctx, 'locale') }
|
||||
register() { return () => {} }
|
||||
bind() { return key => key }
|
||||
}
|
||||
class Forms extends Service {
|
||||
constructor(ctx) { super(ctx, legacy ? 'settingsScope' : 'configForms') }
|
||||
get(id) { return id === 'netxops' ? actual : { getSnapshot: () => ({ status: 'unavailable', writable: false }) } }
|
||||
bind({ namespace }) { return this.get(namespace) }
|
||||
whileServed(ids, register) { return register(new Set(['netxops'])) }
|
||||
}
|
||||
t.after(async () => { for (const owner of owners.reverse()) await owner.dispose() })
|
||||
for (const Provider of [Slots, Locale, Forms]) owners.push(await root.plugin(Provider))
|
||||
const client = loadClient()
|
||||
const owner = await root.plugin({ ...client, name: 'netxops-client-binding' })
|
||||
owners.push(owner)
|
||||
return { root, owner, slots, actual }
|
||||
}
|
||||
|
||||
test('built settings card uses optional lookup in real Cordis and saves through the Host form', async t => {
|
||||
const { owner, slots, actual } = await fixture(t)
|
||||
assert.equal(owner.state, 2 /* ACTIVE */)
|
||||
assert.throws(() => owner.ctx.configForms, /without inject/)
|
||||
const section = slots.get('settings.section:dsh-netxops')
|
||||
assert.ok(section)
|
||||
const face = section.inject()
|
||||
assert.equal(face.hooks.netxopsCard.getSnapshot().available, false)
|
||||
await pause(80)
|
||||
assert.equal(face.hooks.netxopsCard.getSnapshot().available, true)
|
||||
assert.equal(face.hooks.netxopsCard.getSnapshot().writable, true)
|
||||
assert.equal(face.hooks.netxopsCard.getSnapshot().groupOpsPresets.text, 'standard')
|
||||
face.edit('toolsEnabled', 'false')
|
||||
await pause(20)
|
||||
assert.deepEqual(actual.writes, [['toolsEnabled', false]])
|
||||
assert.equal(face.hooks.netxopsCard.getSnapshot().toolsEnabled.text, 'false')
|
||||
assert.ok(slots.has('settings.plugin.item:netxops'))
|
||||
})
|
||||
|
||||
test('built settings card still follows the legacy Host settings scope', async t => {
|
||||
const { slots, actual } = await fixture(t, true)
|
||||
const face = slots.get('settings.section:dsh-netxops').inject()
|
||||
await pause(80)
|
||||
assert.equal(face.hooks.netxopsCard.getSnapshot().available, true)
|
||||
face.edit('groupOpsPresets', '')
|
||||
await pause(20)
|
||||
assert.deepEqual(actual.writes, [['groupOpsPresets', '']])
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue