workers: workersPultUi.test nutzt den gemeinsamen Mini-Läufer aus helpers/fakeReact.mjs
Die zeichengleiche Inline-Kopie (FAKE_REACT und Plug-in) ist entfernt; der Baustein ist jetzt die einzige Fassung für workersPultUi.test.mjs und botleadList.test.mjs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3d830ced38
commit
999731d882
2 files changed
+5
-124
No files matched your search
@@ -1,5 +1,5 @@
|
|||||||
// Mini-Läufer anstelle von React für Tests der Komponenten unter Node (Hooks, Zustand, Effekte; gerendert wird in einen Baum aus { tag, props, children }).
|
// Mini-Läufer anstelle von React für Tests der Komponenten unter Node (Hooks, Zustand, Effekte; gerendert wird in einen Baum aus { tag, props, children }).
|
||||||
// Dieselbe Fassung wie in workersPultUi.test.mjs (FAKE_REACT), hier als Baustein für weitere Komponententests. Die Bauteile laufen wirklich, Klicks und
|
// Die einzige Fassung: workersPultUi.test.mjs und botleadList.test.mjs laden sie von hier (workersProspect.test.mjs hat eine eigene, abweichende Fassung mit Panel-Attrappen). Die Bauteile laufen wirklich, Klicks und
|
||||||
// Eingaben ruft der Test von Hand auf. `esbuild` bündelt die Komponente mit dem Plug-in `fakeReact` (react und react/jsx-runtime kommen von hier).
|
// Eingaben ruft der Test von Hand auf. `esbuild` bündelt die Komponente mit dem Plug-in `fakeReact` (react und react/jsx-runtime kommen von hier).
|
||||||
export const FAKE_REACT = `
|
export const FAKE_REACT = `
|
||||||
export const Fragment = Symbol('fragment')
|
export const Fragment = Symbol('fragment')
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import fs from 'node:fs'
|
|||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import esbuild from 'esbuild'
|
import esbuild from 'esbuild'
|
||||||
|
import { fakeReact } from './helpers/fakeReact.mjs'
|
||||||
|
|
||||||
// Die React-Teile sind unter Node nicht ladbar (JSX, Router): geprüft wird der Quelltext (Muster adminRoute.test.mjs); die Logik
|
// Die React-Teile sind unter Node nicht ladbar (JSX, Router): geprüft wird der Quelltext (Muster adminRoute.test.mjs); die Logik
|
||||||
// steckt in P3-T18. usePultFeed und PultList laufen wirklich: im Mini-Läufer anstelle von React (siehe unten), PultList zusätzlich als Text gerendert.
|
// steckt in P3-T18. usePultFeed und PultList laufen wirklich: im Mini-Läufer anstelle von React (helpers/fakeReact.mjs), PultList zusätzlich als Text gerendert.
|
||||||
const read = (p) => fs.readFileSync(new URL('../../' + p, import.meta.url), 'utf8')
|
const read = (p) => fs.readFileSync(new URL('../../' + p, import.meta.url), 'utf8')
|
||||||
|
|
||||||
test('WorkersPage hängt den Reiter freigabe an hochhaus an und zeigt dort die PultList', () => {
|
test('WorkersPage hängt den Reiter freigabe an hochhaus an und zeigt dort die PultList', () => {
|
||||||
@@ -45,132 +46,12 @@ test('PultList: Aktionen über pultAction, "Als gesendet" sendet den bearbeitete
|
|||||||
assert.doesNotMatch(s, /innerHTML/)
|
assert.doesNotMatch(s, /innerHTML/)
|
||||||
})
|
})
|
||||||
|
|
||||||
// ---- gemeinsame Bausteine der Läufer: EIN Mini-Läufer anstelle von React (Hooks, Zustand, Effekte, Baum aus { tag, props, children }) ----
|
// ---- gemeinsame Bausteine der Läufer: EIN Mini-Läufer anstelle von React (Hooks, Zustand, Effekte, Baum aus { tag, props, children }), er liegt in helpers/fakeReact.mjs ----
|
||||||
// ---- und EINE Tabelle mit Attrappen für alles, was PultList von außen holt. usePultFeed und PultList laufen im selben Läufer. ----
|
// ---- und EINE Tabelle mit Attrappen für alles, was PultList von außen holt. usePultFeed und PultList laufen im selben Läufer. ----
|
||||||
|
|
||||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..')
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..')
|
||||||
const importBundle = (out) => import(`data:text/javascript;base64,${Buffer.from(out.outputFiles[0].text).toString('base64')}`)
|
const importBundle = (out) => import(`data:text/javascript;base64,${Buffer.from(out.outputFiles[0].text).toString('base64')}`)
|
||||||
|
|
||||||
const FAKE_REACT = `
|
|
||||||
export const Fragment = Symbol('fragment')
|
|
||||||
export function jsx(type, props, key) { return { type, props: props ?? {}, key } }
|
|
||||||
export const jsxs = jsx
|
|
||||||
|
|
||||||
let current = null // Instanz des Bauteils, dessen Render gerade läuft
|
|
||||||
let hookIndex = 0
|
|
||||||
|
|
||||||
const flat = (children, out = []) => {
|
|
||||||
if (Array.isArray(children)) children.forEach((c) => flat(c, out))
|
|
||||||
else if (children !== null && children !== undefined && children !== false && children !== true) out.push(children)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hängt ein Element (Bauteile mit Hooks, darunter Tags) ein und rendert es; jedes Bauteil behält seinen Zustand unter seinem Pfad im Baum.
|
|
||||||
export function __mount(element) {
|
|
||||||
const owner = { render, effects: [], late: 0 } // late: Zustandsänderungen, die nach dem Abbau noch ankamen
|
|
||||||
const instances = new Map()
|
|
||||||
let tree = null
|
|
||||||
let rendering = false
|
|
||||||
let dirty = false
|
|
||||||
|
|
||||||
const dispose = (inst) => {
|
|
||||||
inst.alive = false
|
|
||||||
for (const h of inst.hooks) if (h && typeof h.cleanup === 'function') h.cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
const expand = (node, path, visited) => {
|
|
||||||
if (typeof node === 'string' || typeof node === 'number') return String(node)
|
|
||||||
const { type, props } = node
|
|
||||||
if (typeof type === 'function') {
|
|
||||||
visited.add(path)
|
|
||||||
let inst = instances.get(path)
|
|
||||||
if (!inst) { inst = { hooks: [], alive: true, owner }; instances.set(path, inst) }
|
|
||||||
current = inst
|
|
||||||
hookIndex = 0
|
|
||||||
const out = type(props)
|
|
||||||
current = null
|
|
||||||
return out === null || out === undefined || out === false ? null : expand(out, path + '/c', visited)
|
|
||||||
}
|
|
||||||
const children = flat(props.children).map((c, i) => expand(c, path + '/' + (c && c.key != null ? 'k:' + c.key : i), visited))
|
|
||||||
// ref-Objekt an einem Tag: wie React vor den Effekten gesetzt; das Element liefert der Test (globalThis.__fakeRef), sonst null
|
|
||||||
if (typeof type === 'string' && props.ref && typeof props.ref === 'object') props.ref.current = globalThis.__fakeRef ? globalThis.__fakeRef(type, props) : null
|
|
||||||
return { tag: type === Fragment ? 'fragment' : type, props, children }
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
if (rendering) { dirty = true; return }
|
|
||||||
rendering = true
|
|
||||||
do {
|
|
||||||
dirty = false
|
|
||||||
const visited = new Set()
|
|
||||||
tree = expand(element, 'r', visited)
|
|
||||||
for (const [path, inst] of [...instances]) {
|
|
||||||
if (visited.has(path)) continue
|
|
||||||
dispose(inst)
|
|
||||||
instances.delete(path)
|
|
||||||
}
|
|
||||||
for (const run of owner.effects.splice(0)) run()
|
|
||||||
} while (dirty)
|
|
||||||
rendering = false
|
|
||||||
}
|
|
||||||
render()
|
|
||||||
return {
|
|
||||||
tree: () => tree,
|
|
||||||
rerender: render,
|
|
||||||
lateUpdates: () => owner.late,
|
|
||||||
unmount: () => { for (const inst of instances.values()) dispose(inst); instances.clear() },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useState(initial) {
|
|
||||||
const inst = current
|
|
||||||
const i = hookIndex++
|
|
||||||
if (!inst.hooks[i]) inst.hooks[i] = { value: typeof initial === 'function' ? initial() : initial }
|
|
||||||
const slot = inst.hooks[i]
|
|
||||||
return [slot.value, (next) => {
|
|
||||||
if (!inst.alive) { inst.owner.late += 1; return }
|
|
||||||
const value = typeof next === 'function' ? next(slot.value) : next
|
|
||||||
if (Object.is(value, slot.value)) return
|
|
||||||
slot.value = value
|
|
||||||
inst.owner.render()
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
export function useRef(initial) {
|
|
||||||
const inst = current
|
|
||||||
const i = hookIndex++
|
|
||||||
if (!inst.hooks[i]) inst.hooks[i] = { current: initial }
|
|
||||||
return inst.hooks[i]
|
|
||||||
}
|
|
||||||
export function useCallback(fn, deps) {
|
|
||||||
const inst = current
|
|
||||||
const i = hookIndex++
|
|
||||||
const slot = inst.hooks[i]
|
|
||||||
if (slot && deps.every((d, k) => Object.is(d, slot.deps[k]))) return slot.fn
|
|
||||||
inst.hooks[i] = { fn, deps }
|
|
||||||
return fn
|
|
||||||
}
|
|
||||||
export function useEffect(fn, deps) {
|
|
||||||
const inst = current
|
|
||||||
const i = hookIndex++
|
|
||||||
if (!inst.hooks[i]) inst.hooks[i] = { deps: undefined, cleanup: undefined, ran: false }
|
|
||||||
const slot = inst.hooks[i]
|
|
||||||
if (slot.ran && deps && slot.deps && deps.every((d, k) => Object.is(d, slot.deps[k]))) return
|
|
||||||
inst.owner.effects.push(() => {
|
|
||||||
if (typeof slot.cleanup === 'function') slot.cleanup()
|
|
||||||
slot.deps = deps
|
|
||||||
slot.ran = true
|
|
||||||
slot.cleanup = fn()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
`
|
|
||||||
const fakeReact = {
|
|
||||||
name: 'fake-react',
|
|
||||||
setup(build) {
|
|
||||||
build.onResolve({ filter: /^react(\/jsx-runtime)?$/ }, () => ({ path: 'react', namespace: 'fake-react' }))
|
|
||||||
build.onLoad({ filter: /.*/, namespace: 'fake-react' }, () => ({ contents: FAKE_REACT, loader: 'js' }))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Was PultList von außen holt: der Feed (je Filter eine Antwort aus globalThis.__pultFeedFor), die Mitarbeiterliste, die Zwischenablage, das Panel (Dialog)
|
// Was PultList von außen holt: der Feed (je Filter eine Antwort aus globalThis.__pultFeedFor), die Mitarbeiterliste, die Zwischenablage, das Panel (Dialog)
|
||||||
const STUBS = {
|
const STUBS = {
|
||||||
usePultFeed: 'export function usePultFeed({ statuses } = {}) { return globalThis.__pultFeedFor(statuses) }',
|
usePultFeed: 'export function usePultFeed({ statuses } = {}) { return globalThis.__pultFeedFor(statuses) }',
|
||||||
@@ -614,7 +495,7 @@ test('PultList (gerendert, P4-T32): Aktionen je Art – Antworten, Freigeben, Ba
|
|||||||
} finally { delete globalThis.__pultFeedFor; delete globalThis.__pultEmployees }
|
} finally { delete globalThis.__pultFeedFor; delete globalThis.__pultEmployees }
|
||||||
})
|
})
|
||||||
|
|
||||||
// ---- PultList wird bedient (P4-T32): derselbe Mini-Läufer (FAKE_REACT) statt React und DOM. Die Komponenten laufen wirklich (Zustand, Effekte, Ereignisse), ----
|
// ---- PultList wird bedient (P4-T32): derselbe Mini-Läufer (helpers/fakeReact.mjs) statt React und DOM. Die Komponenten laufen wirklich (Zustand, Effekte, Ereignisse), ----
|
||||||
// ---- gerendert wird in einen Baum aus { tag, props, children }; Klicks und Eingaben ruft der Test von Hand auf. Der Dienst ist eine Attrappe. ----
|
// ---- gerendert wird in einen Baum aus { tag, props, children }; Klicks und Eingaben ruft der Test von Hand auf. Der Dienst ist eine Attrappe. ----
|
||||||
|
|
||||||
async function loadUi() {
|
async function loadUi() {
|
||||||
|
|||||||
Reference in new issue
Block a user