Hustler-Pixelwelt: Hochhaus zusammengesteckt (Welt mit Regie, Kopfzeile, Detailansicht), CSS-Importe, forwardRef im Mini-Läufer
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4c5074f4f6
commit
b4e67a43a2
7 files changed
+135
-27
No files matched your search
@@ -1,8 +1,16 @@
|
|||||||
// Bildschirm „Hochhaus“ (Spielwelt, Kopfzeile, Detailansicht). Gerüst von T3: Zustände wie bisher im WorkersBoard; T5–T8 füllen die Welt.
|
import { useRef, useState } from 'react'
|
||||||
|
import HochhausKopf from './HochhausKopf'
|
||||||
|
import HochhausWelt from './HochhausWelt'
|
||||||
|
import HustlerDialog from './HustlerDialog'
|
||||||
|
import RegieEbene from './RegieEbene'
|
||||||
|
|
||||||
|
// Bildschirm „Hochhaus“: Spielwelt mit Regie-Ebene (T5, T8), Kopfzeile (T6) und Detailansicht eines Hustlers (T7). Zustände wie bisher im WorkersBoard.
|
||||||
// Reine Darstellung ohne Auth-Bezug: isAdmin, der Feed (feed = { state, error, loaded, refresh }), die Hustler-Tabelle (workers), die Regie-Anbindung
|
// Reine Darstellung ohne Auth-Bezug: isAdmin, der Feed (feed = { state, error, loaded, refresh }), die Hustler-Tabelle (workers), die Regie-Anbindung
|
||||||
// (regieRef, die Seite ruft regieRef.current.push(events) bei neuen Ereignissen) und der Bildschirmwechsel (onOpen(tab)) kommen von der Seite.
|
// (regieRef, die Seite ruft regieRef.current.push(events) bei neuen Ereignissen) und der Bildschirmwechsel (onOpen(tab)) kommen von der Seite.
|
||||||
// Kein CSS-Import und keine Bilder: die Stile hängt WorkersPage ein (Regeln dieser Datei in styles/hustler/hustler-ui.css).
|
// Kein CSS-Import und keine Bilder: die Stile hängt WorkersPage ein (Regeln dieser Datei in styles/hustler/hustler-ui.css).
|
||||||
export default function Hochhaus({ api, isAdmin, feed, workers, regieRef, onOpen }) {
|
export default function Hochhaus({ api, isAdmin, feed, workers, regieRef, onOpen }) {
|
||||||
|
const weltRef = useRef(null)
|
||||||
|
const [auswahl, setAuswahl] = useState(null) // workerId der offenen Detailansicht (null = keine)
|
||||||
const ready = feed.loaded && Boolean(feed.state)
|
const ready = feed.loaded && Boolean(feed.state)
|
||||||
// Dienst noch nie erreicht: Pixel-Leerzustand mit Fehlertext und „Neu laden“
|
// Dienst noch nie erreicht: Pixel-Leerzustand mit Fehlertext und „Neu laden“
|
||||||
if (!ready && feed.error) {
|
if (!ready && feed.error) {
|
||||||
@@ -24,10 +32,37 @@ export default function Hochhaus({ api, isAdmin, feed, workers, regieRef, onOpen
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// T5 füllt diesen Rahmen mit der Welt (Kopfzeile T6, Detailansicht T7, Regie T8)
|
// Die Regie-Ebene liegt in der skalierten Bühne (Weltkoordinaten); ihr Handle ist regieRef der Seite, die neue Ereignisse hineinreicht.
|
||||||
|
// Solange die Detailansicht offen ist, ruhen Rad, Ziehen und Tasten der Kamera (gesperrt).
|
||||||
return (
|
return (
|
||||||
<div className="hk-hochhaus">
|
<div className="hk-hochhaus">
|
||||||
<div className="hk-welt-rahmen" />
|
<HochhausWelt
|
||||||
|
ref={weltRef}
|
||||||
|
state={feed.state}
|
||||||
|
workers={workers}
|
||||||
|
onZeige={setAuswahl}
|
||||||
|
onOpen={onOpen}
|
||||||
|
gesperrt={auswahl !== null}
|
||||||
|
>
|
||||||
|
<RegieEbene ref={regieRef} />
|
||||||
|
</HochhausWelt>
|
||||||
|
<HochhausKopf
|
||||||
|
api={api}
|
||||||
|
isAdmin={isAdmin}
|
||||||
|
state={feed.state}
|
||||||
|
error={feed.error}
|
||||||
|
refresh={feed.refresh}
|
||||||
|
onGanz={() => { if (weltRef.current) weltRef.current.ganz() }}
|
||||||
|
/>
|
||||||
|
{auswahl !== null && (
|
||||||
|
<HustlerDialog
|
||||||
|
api={api}
|
||||||
|
workerId={auswahl}
|
||||||
|
workers={workers}
|
||||||
|
onClose={() => setAuswahl(null)}
|
||||||
|
onWechsel={setAuswahl}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,10 @@ import '../styles/hustler/hochhaus.css'
|
|||||||
import '../styles/hustler/keller.css'
|
import '../styles/hustler/keller.css'
|
||||||
import '../styles/hustler/pult.css'
|
import '../styles/hustler/pult.css'
|
||||||
import '../styles/hustler/hustler-ui.css'
|
import '../styles/hustler/hustler-ui.css'
|
||||||
|
import '../styles/hustler/hustler-welt.css'
|
||||||
|
import '../styles/hustler/hustler-kopf.css'
|
||||||
|
import '../styles/hustler/hustler-detail.css'
|
||||||
|
import '../styles/hustler/hustler-regie.css'
|
||||||
|
|
||||||
// Bildschirme der Seite: das Hochhaus, das Pult (Freigabe von P3) und der Keller (Bot-Leads von P6). Eine Reiterleiste gibt es nicht mehr;
|
// Bildschirme der Seite: das Hochhaus, das Pult (Freigabe von P3) und der Keller (Bot-Leads von P6). Eine Reiterleiste gibt es nicht mehr;
|
||||||
// gewechselt wird im Haus (Etage Freigabe, Etage Keller) und über „◂ Hochhaus“ in Pult und Keller.
|
// gewechselt wird im Haus (Etage Freigabe, Etage Keller) und über „◂ Hochhaus“ in Pult und Keller.
|
||||||
|
|||||||
@@ -112,6 +112,13 @@ export function useEffect(fn, deps) {
|
|||||||
slot.cleanup = fn()
|
slot.cleanup = fn()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// forwardRef: der ref kommt wie in React 18 als zweites Argument (der Mini-Läufer lässt ref in den props stehen)
|
||||||
|
export function forwardRef(render) { return (props) => render(props, props.ref ?? null) }
|
||||||
|
// useImperativeHandle: Objekt-Ref sofort beim Rendern setzen (wie React vor den Effekten), beim Abbau wieder null
|
||||||
|
export function useImperativeHandle(ref, create) {
|
||||||
|
if (ref && typeof ref === 'object') ref.current = create()
|
||||||
|
useEffect(() => () => { if (ref && typeof ref === 'object') ref.current = null }, [ref])
|
||||||
|
}
|
||||||
`
|
`
|
||||||
export const fakeReact = {
|
export const fakeReact = {
|
||||||
name: 'fake-react',
|
name: 'fake-react',
|
||||||
|
|||||||
@@ -11,16 +11,13 @@ import { makeState, IDS } from './helpers/workersFixtures.mjs'
|
|||||||
// 1. Figuren nach Status (E5), graue Räume samt Zettel (E4), Schilder, Etagen (E8), Pause, Beschriftungen (E14).
|
// 1. Figuren nach Status (E5), graue Räume samt Zettel (E4), Schilder, Etagen (E8), Pause, Beschriftungen (E14).
|
||||||
// 2. Kamera mit nachgestellten Zeigerereignissen: Rad um den Mauspunkt, Ziehen und unterdrückter Klick danach, Pinch, Doppelklick/-tipp, Tasten, Gleiten, Größenänderung, gesperrt.
|
// 2. Kamera mit nachgestellten Zeigerereignissen: Rad um den Mauspunkt, Ziehen und unterdrückter Klick danach, Pinch, Doppelklick/-tipp, Tasten, Gleiten, Größenänderung, gesperrt.
|
||||||
// Der Mini-Läufer ruft nur Objekt-Refs (globalThis.__fakeRef); die Kamera hängt über einen ref-Callback am Sichtfenster, den der Test wie React beim Einhängen selbst mit
|
// Der Mini-Läufer ruft nur Objekt-Refs (globalThis.__fakeRef); die Kamera hängt über einen ref-Callback am Sichtfenster, den der Test wie React beim Einhängen selbst mit
|
||||||
// einer Element-Attrappe ruft. forwardRef und useImperativeHandle fehlen im gemeinsamen Mini-Läufer: dieser Test ergänzt sie lokal.
|
// einer Element-Attrappe ruft.
|
||||||
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 settle = () => new Promise((resolve) => setImmediate(resolve))
|
const settle = () => new Promise((resolve) => setImmediate(resolve))
|
||||||
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
|
||||||
const FAKE_REACT_PLUS = `${FAKE_REACT}
|
const FAKE_REACT_PLUS = FAKE_REACT // forwardRef und useImperativeHandle liefert inzwischen der gemeinsame Mini-Läufer
|
||||||
export function forwardRef(render) { return (props) => render(props, props.ref ?? null) }
|
|
||||||
export function useImperativeHandle(ref, create) { if (ref && typeof ref === 'object') ref.current = create() }
|
|
||||||
`
|
|
||||||
const fakeReactPlus = {
|
const fakeReactPlus = {
|
||||||
name: 'fake-react',
|
name: 'fake-react',
|
||||||
setup(build) {
|
setup(build) {
|
||||||
|
|||||||
@@ -541,13 +541,8 @@ test('Quelltext: rein (kein React, window, document, Zeitgeber); Importe nur wor
|
|||||||
|
|
||||||
// ---------- RegieEbene (Mini-Läufer) ----------
|
// ---------- RegieEbene (Mini-Läufer) ----------
|
||||||
|
|
||||||
// fakeReact kennt forwardRef und useImperativeHandle nicht: hier angehängt (ref kommt wie bei React 18 als zweites Argument)
|
// forwardRef und useImperativeHandle liefert der gemeinsame Mini-Läufer (helpers/fakeReact.mjs)
|
||||||
const FAKE_REACT_MIT_REF = `${FAKE_REACT}
|
const FAKE_REACT_MIT_REF = FAKE_REACT
|
||||||
export function forwardRef(render) { return (props) => render(props, props.ref ?? null) }
|
|
||||||
export function useImperativeHandle(ref, create, deps) {
|
|
||||||
useEffect(() => { if (ref) ref.current = create(); return () => { if (ref) ref.current = null } }, deps)
|
|
||||||
}
|
|
||||||
`
|
|
||||||
const fakeReactMitRef = {
|
const fakeReactMitRef = {
|
||||||
name: 'fake-react-mit-ref',
|
name: 'fake-react-mit-ref',
|
||||||
setup(build) {
|
setup(build) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { fakeReact } from './helpers/fakeReact.mjs'
|
|||||||
import { ev, makeState } from './helpers/workersFixtures.mjs'
|
import { ev, makeState } from './helpers/workersFixtures.mjs'
|
||||||
|
|
||||||
// Das Gerüst der Hustler-Pixelwelt (T3) läuft wirklich: im Mini-Läufer anstelle von React (Muster botleadList.test.mjs, Baustein helpers/fakeReact.mjs).
|
// Das Gerüst der Hustler-Pixelwelt (T3) läuft wirklich: im Mini-Läufer anstelle von React (Muster botleadList.test.mjs, Baustein helpers/fakeReact.mjs).
|
||||||
// 1. Hochhaus.jsx: Laden, Dienst nicht erreichbar („Neu laden“), geladen -> Rahmen der Welt.
|
// 1. Hochhaus.jsx: Laden, Dienst nicht erreichbar („Neu laden“), geladen -> Welt, Kopfzeile, Detailansicht auf Klick.
|
||||||
// 2. WorkersPage.jsx: mit esbuild-Attrappen für Router, Anmeldung, Dienst-Client, Feed-Hook, CSS und die drei Bildschirme. Geprüft wird,
|
// 2. WorkersPage.jsx: mit esbuild-Attrappen für Router, Anmeldung, Dienst-Client, Feed-Hook, CSS und die drei Bildschirme. Geprüft wird,
|
||||||
// welcher Bildschirm zu ?tab gehört, welche Props er bekommt, dass der Feed einmal pro Seite läuft (kein Neustart beim Bildschirmwechsel),
|
// welcher Bildschirm zu ?tab gehört, welche Props er bekommt, dass der Feed einmal pro Seite läuft (kein Neustart beim Bildschirmwechsel),
|
||||||
// dass Ereignisse die Hustler-Tabelle fortschreiben und an die Regie gehen, und dass ein Schnappschuss die Tabelle neu setzt.
|
// dass Ereignisse die Hustler-Tabelle fortschreiben und an die Regie gehen, und dass ein Schnappschuss die Tabelle neu setzt.
|
||||||
@@ -27,20 +27,49 @@ const byClass = (tree, name) => nodes(tree, (n) => typeof n.tag === 'string' &&
|
|||||||
|
|
||||||
// ---------- Hochhaus ----------
|
// ---------- Hochhaus ----------
|
||||||
|
|
||||||
|
// Die vier Teile des Hochhauses (Welt T5, Kopfzeile T6, Detailansicht T7, Regie T8) haben eigene Tests; hier sind sie Attrappen,
|
||||||
|
// geprüft wird das Zusammenstecken (Props, Auswahl, Sperre der Kamera, Regie-Handle, „Ganzes Haus zeigen“).
|
||||||
|
const TEILE = {
|
||||||
|
welt: `import { jsx, forwardRef, useImperativeHandle } from 'react'
|
||||||
|
export default forwardRef(function HochhausWelt(props, ref) {
|
||||||
|
useImperativeHandle(ref, () => ({ ganz: () => { globalThis.__hh.ganz += 1 } }))
|
||||||
|
globalThis.__hh.welt = props
|
||||||
|
return jsx('div', { 'data-teil': 'welt', children: props.children })
|
||||||
|
})`,
|
||||||
|
kopf: "import { jsx } from 'react'\nexport default function HochhausKopf(props) { globalThis.__hh.kopf = props; return jsx('div', { 'data-teil': 'kopf' }) }",
|
||||||
|
dialog: "import { jsx } from 'react'\nexport default function HustlerDialog(props) { globalThis.__hh.dialog = props; return jsx('div', { 'data-teil': 'dialog' }) }",
|
||||||
|
regie: `import { jsx, forwardRef, useImperativeHandle } from 'react'
|
||||||
|
export default forwardRef(function RegieEbene(props, ref) {
|
||||||
|
useImperativeHandle(ref, () => ({ push: (events) => { globalThis.__hh.pushed.push(events) }, flush() {} }))
|
||||||
|
return jsx('div', { 'data-teil': 'regie' })
|
||||||
|
})`,
|
||||||
|
}
|
||||||
|
const teilePlugin = {
|
||||||
|
name: 'hochhaus-teile',
|
||||||
|
setup(build) {
|
||||||
|
for (const [filter, key] of [[/^\.\/HochhausWelt$/, 'welt'], [/^\.\/HochhausKopf$/, 'kopf'], [/^\.\/HustlerDialog$/, 'dialog'], [/^\.\/RegieEbene$/, 'regie']]) {
|
||||||
|
build.onResolve({ filter }, () => ({ path: key, namespace: 'hochhaus-teil' }))
|
||||||
|
}
|
||||||
|
build.onLoad({ filter: /.*/, namespace: 'hochhaus-teil' }, (args) => ({ contents: TEILE[args.path], loader: 'js' }))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
let hochhausBundle
|
let hochhausBundle
|
||||||
const loadHochhaus = () => (hochhausBundle ??= esbuild.build({
|
const loadHochhaus = () => (hochhausBundle ??= esbuild.build({
|
||||||
stdin: { contents: "export { __mount, jsx } from 'react'\nexport { default as Hochhaus } from './src/components/workers/hochhaus/Hochhaus.jsx'", resolveDir: ROOT, loader: 'js' },
|
stdin: { contents: "export { __mount, jsx } from 'react'\nexport { default as Hochhaus } from './src/components/workers/hochhaus/Hochhaus.jsx'", resolveDir: ROOT, loader: 'js' },
|
||||||
bundle: true, format: 'esm', platform: 'neutral', write: false, jsx: 'automatic', loader: { '.jsx': 'jsx' }, plugins: [fakeReact],
|
bundle: true, format: 'esm', platform: 'neutral', write: false, jsx: 'automatic', loader: { '.jsx': 'jsx' }, plugins: [fakeReact, teilePlugin],
|
||||||
}).then(importBundle))
|
}).then(importBundle))
|
||||||
|
|
||||||
async function hochhaus(feed, extra = {}) {
|
async function hochhaus(feed, extra = {}) {
|
||||||
const { __mount, jsx, Hochhaus } = await loadHochhaus()
|
const { __mount, jsx, Hochhaus } = await loadHochhaus()
|
||||||
const props = { api: {}, isAdmin: false, workers: {}, regieRef: { current: null }, onOpen() {}, ...extra, feed }
|
globalThis.__hh = { ganz: 0, pushed: [], welt: null, kopf: null, dialog: null }
|
||||||
|
const props = { api: { name: 'api' }, isAdmin: false, workers: {}, regieRef: { current: null }, onOpen() {}, ...extra, feed }
|
||||||
const mounted = __mount(jsx(() => jsx(Hochhaus, props), {}))
|
const mounted = __mount(jsx(() => jsx(Hochhaus, props), {}))
|
||||||
await settle()
|
await settle()
|
||||||
return { tree: () => mounted.tree(), end: () => mounted.unmount() }
|
return { props, hh: globalThis.__hh, tree: () => mounted.tree(), end: () => mounted.unmount() }
|
||||||
}
|
}
|
||||||
const counting = (n = { refresh: 0 }) => ({ n, refresh: () => { n.refresh += 1 } })
|
const counting = (n = { refresh: 0 }) => ({ n, refresh: () => { n.refresh += 1 } })
|
||||||
|
const teil = (tree, name) => nodes(tree, (n) => n.props && n.props['data-teil'] === name)
|
||||||
|
|
||||||
test('Hochhaus: solange nichts geladen ist, steht „Wird geladen …“ mit role=status in der Wurzel hk-hochhaus', async () => {
|
test('Hochhaus: solange nichts geladen ist, steht „Wird geladen …“ mit role=status in der Wurzel hk-hochhaus', async () => {
|
||||||
const c = counting()
|
const c = counting()
|
||||||
@@ -50,7 +79,7 @@ test('Hochhaus: solange nichts geladen ist, steht „Wird geladen …“ mit rol
|
|||||||
assert.equal(root.tag, 'div'); assert.equal(root.props.className, 'hk-hochhaus')
|
assert.equal(root.tag, 'div'); assert.equal(root.props.className, 'hk-hochhaus')
|
||||||
const laden = byClass(root, 'hk-laden')
|
const laden = byClass(root, 'hk-laden')
|
||||||
assert.equal(laden.length, 1); assert.equal(laden[0].props.role, 'status'); assert.equal(textOf(laden[0]), 'Wird geladen …')
|
assert.equal(laden.length, 1); assert.equal(laden[0].props.role, 'status'); assert.equal(textOf(laden[0]), 'Wird geladen …')
|
||||||
assert.equal(byClass(root, 'hk-welt-rahmen').length, 0); assert.equal(byClass(root, 'hk-leer').length, 0)
|
assert.equal(teil(root, 'welt').length, 0); assert.equal(byClass(root, 'hk-leer').length, 0)
|
||||||
assert.equal(c.n.refresh, 0)
|
assert.equal(c.n.refresh, 0)
|
||||||
} finally { view.end() }
|
} finally { view.end() }
|
||||||
})
|
})
|
||||||
@@ -70,7 +99,7 @@ test('Hochhaus: Dienst nie erreicht -> Pixel-Leerzustand mit Titel, Fehlertext a
|
|||||||
const knoepfe = nodes(root, (n) => n.tag === 'button')
|
const knoepfe = nodes(root, (n) => n.tag === 'button')
|
||||||
assert.equal(knoepfe.length, 1)
|
assert.equal(knoepfe.length, 1)
|
||||||
assert.equal(textOf(knoepfe[0]), 'Neu laden'); assert.equal(knoepfe[0].props.type, 'button'); assert.ok(hasClass(knoepfe[0], 'hk-knopf'))
|
assert.equal(textOf(knoepfe[0]), 'Neu laden'); assert.equal(knoepfe[0].props.type, 'button'); assert.ok(hasClass(knoepfe[0], 'hk-knopf'))
|
||||||
assert.equal(byClass(root, 'hk-laden').length, 0); assert.equal(byClass(root, 'hk-welt-rahmen').length, 0)
|
assert.equal(byClass(root, 'hk-laden').length, 0); assert.equal(teil(root, 'welt').length, 0)
|
||||||
knoepfe[0].props.onClick({ preventDefault() {} })
|
knoepfe[0].props.onClick({ preventDefault() {} })
|
||||||
assert.equal(c.n.refresh, 1)
|
assert.equal(c.n.refresh, 1)
|
||||||
knoepfe[0].props.onClick({ preventDefault() {} })
|
knoepfe[0].props.onClick({ preventDefault() {} })
|
||||||
@@ -78,28 +107,69 @@ test('Hochhaus: Dienst nie erreicht -> Pixel-Leerzustand mit Titel, Fehlertext a
|
|||||||
} finally { view.end() }
|
} finally { view.end() }
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Hochhaus: geladen -> leerer Rahmen hk-welt-rahmen; ein späterer Verbindungsfehler ersetzt die Welt nicht', async () => {
|
test('Hochhaus: geladen -> Welt mit Regie-Ebene darin, Kopfzeile, noch keine Detailansicht; ein späterer Verbindungsfehler ersetzt die Welt nicht', async () => {
|
||||||
const c = counting()
|
const c = counting()
|
||||||
const state = makeState()
|
const state = makeState()
|
||||||
const view = await hochhaus({ state, error: null, loaded: true, refresh: c.refresh })
|
const workers = { lea: { status: 'arbeitet', task: 'x', lastTs: null, connected: true, counters: {} } }
|
||||||
|
const onOpen = () => {}
|
||||||
|
const view = await hochhaus({ state, error: null, loaded: true, refresh: c.refresh }, { isAdmin: true, workers, onOpen })
|
||||||
try {
|
try {
|
||||||
const root = view.tree()
|
const root = view.tree()
|
||||||
assert.equal(root.props.className, 'hk-hochhaus')
|
assert.equal(root.props.className, 'hk-hochhaus')
|
||||||
assert.equal(byClass(root, 'hk-welt-rahmen').length, 1)
|
const welt = teil(root, 'welt')
|
||||||
|
assert.equal(welt.length, 1); assert.equal(teil(welt[0], 'regie').length, 1) // Regie liegt in der Bühne der Welt
|
||||||
|
assert.equal(teil(root, 'kopf').length, 1); assert.equal(teil(root, 'dialog').length, 0)
|
||||||
assert.equal(byClass(root, 'hk-laden').length, 0); assert.equal(byClass(root, 'hk-leer').length, 0)
|
assert.equal(byClass(root, 'hk-laden').length, 0); assert.equal(byClass(root, 'hk-leer').length, 0)
|
||||||
|
const w = view.hh.welt
|
||||||
|
assert.equal(w.state, state); assert.equal(w.workers, workers); assert.equal(w.onOpen, onOpen); assert.equal(w.gesperrt, false)
|
||||||
|
const k = view.hh.kopf
|
||||||
|
assert.equal(k.api, view.props.api); assert.equal(k.isAdmin, true); assert.equal(k.state, state); assert.equal(k.error, null); assert.equal(k.refresh, c.refresh)
|
||||||
} finally { view.end() }
|
} finally { view.end() }
|
||||||
const gestoert = await hochhaus({ state, error: 'Verbindung weg', loaded: true, refresh: c.refresh })
|
const gestoert = await hochhaus({ state, error: 'Verbindung weg', loaded: true, refresh: c.refresh })
|
||||||
try {
|
try {
|
||||||
assert.equal(byClass(gestoert.tree(), 'hk-welt-rahmen').length, 1)
|
assert.equal(teil(gestoert.tree(), 'welt').length, 1)
|
||||||
assert.equal(byClass(gestoert.tree(), 'hk-leer').length, 0)
|
assert.equal(byClass(gestoert.tree(), 'hk-leer').length, 0)
|
||||||
|
assert.equal(gestoert.hh.kopf.error, 'Verbindung weg')
|
||||||
} finally { gestoert.end() }
|
} finally { gestoert.end() }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Hochhaus: das Regie-Handle landet in regieRef der Seite, Ereignisse kommen bei der Ebene an; nach dem Abbau ist es weg', async () => {
|
||||||
|
const regieRef = { current: null }
|
||||||
|
const view = await hochhaus({ state: makeState(), error: null, loaded: true, refresh() {} }, { regieRef })
|
||||||
|
assert.equal(typeof regieRef.current?.push, 'function')
|
||||||
|
const events = [ev('fund', 'lea')]
|
||||||
|
regieRef.current.push(events)
|
||||||
|
assert.deepEqual(view.hh.pushed, [events])
|
||||||
|
view.end()
|
||||||
|
await settle()
|
||||||
|
assert.equal(regieRef.current, null)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Hochhaus: Figur-Klick öffnet die Detailansicht und sperrt die Kamera, Blättern wechselt, Schließen gibt frei; „Ganzes Haus zeigen“ ruft die Welt', async () => {
|
||||||
|
const workers = { lea: { status: 'arbeitet', task: 'x', lastTs: null, connected: true, counters: {} } }
|
||||||
|
const view = await hochhaus({ state: makeState(), error: null, loaded: true, refresh() {} }, { workers })
|
||||||
|
try {
|
||||||
|
view.hh.welt.onZeige('lea')
|
||||||
|
await settle()
|
||||||
|
assert.equal(teil(view.tree(), 'dialog').length, 1)
|
||||||
|
assert.equal(view.hh.dialog.workerId, 'lea'); assert.equal(view.hh.dialog.workers, workers); assert.equal(view.hh.dialog.api, view.props.api)
|
||||||
|
assert.equal(view.hh.welt.gesperrt, true)
|
||||||
|
view.hh.dialog.onWechsel('bea')
|
||||||
|
await settle()
|
||||||
|
assert.equal(view.hh.dialog.workerId, 'bea')
|
||||||
|
view.hh.dialog.onClose()
|
||||||
|
await settle()
|
||||||
|
assert.equal(teil(view.tree(), 'dialog').length, 0); assert.equal(view.hh.welt.gesperrt, false)
|
||||||
|
view.hh.kopf.onGanz()
|
||||||
|
assert.equal(view.hh.ganz, 1)
|
||||||
|
} finally { view.end() }
|
||||||
|
})
|
||||||
|
|
||||||
test('Hochhaus: ohne Zustand gilt auch ein geladen-Flag nicht als bereit (wie bisher im Board)', async () => {
|
test('Hochhaus: ohne Zustand gilt auch ein geladen-Flag nicht als bereit (wie bisher im Board)', async () => {
|
||||||
const c = counting()
|
const c = counting()
|
||||||
const view = await hochhaus({ state: null, error: 'weg', loaded: true, refresh: c.refresh })
|
const view = await hochhaus({ state: null, error: 'weg', loaded: true, refresh: c.refresh })
|
||||||
try {
|
try {
|
||||||
assert.equal(byClass(view.tree(), 'hk-welt-rahmen').length, 0)
|
assert.equal(teil(view.tree(), 'welt').length, 0)
|
||||||
assert.equal(byClass(view.tree(), 'hk-leer').length, 1)
|
assert.equal(byClass(view.tree(), 'hk-leer').length, 1)
|
||||||
} finally { view.end() }
|
} finally { view.end() }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ test('WorkersPage: freigabe zeigt die PultList, botleads die BotLeadList (api, b
|
|||||||
})
|
})
|
||||||
test('CSS-Importe der Hustler-Oberfläche stehen nur in WorkersPage, nicht in den Komponenten (sonst nicht unter Node mit fakeReact ladbar)', () => {
|
test('CSS-Importe der Hustler-Oberfläche stehen nur in WorkersPage, nicht in den Komponenten (sonst nicht unter Node mit fakeReact ladbar)', () => {
|
||||||
const s = readFileSync(new URL('../../src/pages/WorkersPage.jsx', import.meta.url), 'utf8')
|
const s = readFileSync(new URL('../../src/pages/WorkersPage.jsx', import.meta.url), 'utf8')
|
||||||
for (const css of ['workers', 'hustler/schrift', 'hustler/hochhaus', 'hustler/keller', 'hustler/pult', 'hustler/hustler-ui']) assert.ok(s.includes(`import '../styles/${css}.css'`), css)
|
for (const css of ['workers', 'hustler/schrift', 'hustler/hochhaus', 'hustler/keller', 'hustler/pult', 'hustler/hustler-ui', 'hustler/hustler-welt', 'hustler/hustler-kopf', 'hustler/hustler-detail', 'hustler/hustler-regie']) assert.ok(s.includes(`import '../styles/${css}.css'`), css)
|
||||||
const walk = (dir) => readdirSync(dir, { withFileTypes: true }).flatMap((e) => (e.isDirectory() ? walk(path.join(dir, e.name)) : [path.join(dir, e.name)]))
|
const walk = (dir) => readdirSync(dir, { withFileTypes: true }).flatMap((e) => (e.isDirectory() ? walk(path.join(dir, e.name)) : [path.join(dir, e.name)]))
|
||||||
const root = fileURLToPath(new URL('../../src/components/workers/', import.meta.url))
|
const root = fileURLToPath(new URL('../../src/components/workers/', import.meta.url))
|
||||||
const files = walk(root).filter((f) => /\.(jsx?|mjs)$/.test(f))
|
const files = walk(root).filter((f) => /\.(jsx?|mjs)$/.test(f))
|
||||||
|
|||||||
Reference in new issue
Block a user