Files
tickte-system/tests/unit/workersRooms.test.mjs
T

337 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import test from 'node:test'
import assert from 'node:assert/strict'
import { loadModule } from './load.mjs'
import { makeCtx } from './helpers/fakeCtx.mjs'
// Räume nach kind, Pokal-Seiten, graue Räume und Gebäudehülle (Quelle: entwuerfe/scene-turm.js Z. 99-314)
const R = await loadModule('src/components/workers/engine/rooms.js')
const B = await loadModule('src/components/workers/engine/building.js')
const { buildLayout, ROOM_META } = await loadModule('src/components/workers/engine/layout.js')
const { PAL, TEAM } = await loadModule('src/components/workers/engine/sprites.js')
const { FLOOR_TITLES, ROOM_TITLES } = await loadModule('src/lib/workersRoster.js')
const layout = buildLayout()
const draw = (look, t = 1) => {
const m = makeCtx()
R.drawRooms(m.ctx, layout, t, look)
return m
}
const SIGN = 'noch nicht angeschlossen'
const looks = (patch = {}) => Object.assign(R.createLook(), patch)
const count = (rects, fill) => rects.filter((r) => r.fill === fill).length
// Pokale sind die 7 × 3 großen Goldflächen (k = 3: 21 × 9 Geräte-Pixel)
const trophies = (look) => draw(look).rects.filter((r) => r.fill === PAL.gold && r.w === 21 && r.h === 9).length
test('trophyPaging: 8 je Seite, Seite wird geklemmt', () => {
assert.deepEqual(R.trophyPaging(0, 0), { shown: 0, page: 0, pages: 1, from: 0 })
assert.deepEqual(R.trophyPaging(8, 0), { shown: 8, page: 0, pages: 1, from: 0 })
assert.deepEqual(R.trophyPaging(9, 1), { shown: 1, page: 1, pages: 2, from: 8 })
assert.deepEqual(R.trophyPaging(9, 5), { shown: 1, page: 1, pages: 2, from: 8 })
assert.deepEqual(R.trophyPaging(25, -1), { shown: 8, page: 0, pages: 4, from: 0 })
})
test('alle 21 Räume zeichnen, jeder Titel erscheint', () => {
const { texts, rects } = draw(R.createLook())
assert.ok(rects.length > 300)
for (const title of Object.values(ROOM_TITLES)) assert.ok(texts.some((x) => x.text === title), title)
})
test('graue Räume tragen das Schild, sonst keins (Review Focus 4)', () => {
const look = R.createLook()
look.greyRooms = new Set(['kleinanzeigen', 'portale'])
assert.equal(draw(look).texts.filter((x) => x.text === SIGN).length, 2)
assert.equal(draw(R.createLook()).texts.filter((x) => x.text === SIGN).length, 0)
})
test('Pult-Zahl, Pokal- und Bot-Lead-Zähler', () => {
const look = R.createLook()
look.pultOpen = 7
look.dealsMonth = 9
look.botLeads = 112
const t = draw(look).texts.map((x) => x.text)
assert.ok(t.includes('7'))
assert.ok(t.includes('9 Deals'))
assert.ok(t.includes('112 Bot-Leads'))
assert.ok(!draw(R.createLook()).texts.some((x) => x.text === '7'))
})
test('Gebäude: Etagenschilder, Lampen, Pausenschleier', () => {
const m = makeCtx()
B.drawTower(m.ctx, layout, 1, { at: (i) => i === 3 })
for (const title of Object.values(FLOOR_TITLES)) assert.ok(m.texts.some((x) => x.text === title), title)
const on = makeCtx()
const look = R.createLook()
look.paused = true
B.drawPauseVeil(on.ctx, look)
assert.ok(on.texts.some((x) => x.text === 'Pausiert'))
const off = makeCtx()
B.drawPauseVeil(off.ctx, R.createLook())
assert.equal(off.texts.length, 0)
B.drawOutside(makeCtx().ctx)
})
// ---- Ergänzungen: Verhalten, das die fünf Tests oben nicht berühren ----
test('createLook: Ausgangszustand ohne gemeinsame Mengen zwischen zwei Aufrufen', () => {
const a = R.createLook()
const b = R.createLook()
assert.ok(a.greyRooms instanceof Set && a.busyRooms instanceof Set)
assert.equal(a.greyRooms.size + a.busyRooms.size, 0)
assert.deepEqual({ ...a, greyRooms: 0, busyRooms: 0 }, { greyRooms: 0, busyRooms: 0, pultOpen: 0, dealsMonth: 0, botLeads: 0, trophyPage: 0, pageT: 0, trophyPop: 0, vaultFlash: 0, paused: false })
a.busyRooms.add('netz')
assert.equal(b.busyRooms.size, 0)
})
test('ROOM_DRAWERS: eine Zeichnung für jede Raumart des Layouts, eingefroren', () => {
const kinds = new Set(Object.values(ROOM_META).map((m) => m.kind))
assert.equal(kinds.size, 16)
for (const kind of kinds) assert.equal(typeof R.ROOM_DRAWERS[kind], 'function', kind)
assert.ok(Object.isFrozen(R.ROOM_DRAWERS))
})
test('jeder Raum zeichnet sichtbaren Inhalt über Tint und Titel hinaus (kein leerer Raum)', () => {
// Rechtecke je Raum zählen: Raum für Raum in ein eigenes Layout mit nur dieser Etage
for (const floor of layout.floors) {
for (const room of floor.rooms) {
const single = { ...layout, floors: [{ ...floor, rooms: [room] }] }
const m = makeCtx()
R.drawRooms(m.ctx, single, 1, R.createLook())
assert.ok(m.rects.length >= 5, room.id)
}
}
})
test('trophyPaging: ungültige Werte fallen auf eine leere erste Seite zurück', () => {
const empty = { shown: 0, page: 0, pages: 1, from: 0 }
assert.deepEqual(R.trophyPaging(NaN, NaN), empty)
assert.deepEqual(R.trophyPaging(undefined, undefined), empty)
assert.deepEqual(R.trophyPaging(-4, 2), empty)
assert.deepEqual(R.trophyPaging(16, 1), { shown: 8, page: 1, pages: 2, from: 8 })
assert.deepEqual(R.trophyPaging(5, 0, 2), { shown: 2, page: 0, pages: 3, from: 0 })
})
test('Pokalbrett zeigt die gewählte Seite; der neueste Pokal blinkt bei trophyPop', () => {
assert.equal(trophies(looks()), 0)
assert.equal(trophies(looks({ dealsMonth: 3 })), 3)
assert.equal(trophies(looks({ dealsMonth: 20 })), 8)
assert.equal(trophies(looks({ dealsMonth: 9, trophyPage: 1 })), 1)
assert.equal(trophies(looks({ dealsMonth: 9, trophyPage: 7 })), 1)
// floor(0.25 * 10) = 2 -> gerade: sichtbar; floor(0.15 * 10) = 1 -> ungerade: der neueste Pokal fehlt
assert.equal(trophies(looks({ dealsMonth: 3, trophyPop: 0.25 })), 3)
assert.equal(trophies(looks({ dealsMonth: 3, trophyPop: 0.15 })), 2)
})
test('Tresor blitzt Gold bei vaultFlash, sonst nicht', () => {
const gold = (look) => draw(look).alphas.filter((a) => a > 0.1).length
assert.ok(gold(looks({ vaultFlash: 0.7 })) > gold(looks()))
})
test('Pult-Zahl: 0 zeigt keine Zahl, ab 100 steht 99+', () => {
const nums = (n) => draw(looks({ pultOpen: n })).texts.map((x) => x.text).filter((x) => /^\d+\+?$/.test(x))
assert.deepEqual(nums(0), [])
assert.deepEqual(nums(120), ['99+'])
assert.deepEqual(nums(99), ['99'])
})
test('Zähler zeigen immer ihre Zahl, auch 0', () => {
const t = draw(R.createLook()).texts.map((x) => x.text)
assert.ok(t.includes('0 Deals'))
assert.ok(t.includes('0 Bot-Leads'))
})
test('Monitore laufen nur in Räumen aus busyRooms', () => {
const lines = (look) => count(draw(look).rects, TEAM.aerzte)
const idle = lines(looks())
const busy = lines(looks({ busyRooms: new Set(['aerzte']) }))
assert.ok(busy > idle)
// ein anderer Raum arbeitet: Ärzte & Praxen bleibt dunkel
assert.equal(lines(looks({ busyRooms: new Set(['handwerk']) })), idle)
})
test('graue Räume: Monitor aus trotz busyRooms, pult und tresor werden nie grau', () => {
const lines = (look) => count(draw(look).rects, TEAM.aerzte)
const grey = looks({ greyRooms: new Set(['aerzte']), busyRooms: new Set(['aerzte']) })
const greyIdle = looks({ greyRooms: new Set(['aerzte']) })
assert.equal(lines(grey), lines(greyIdle))
const never = looks({ greyRooms: new Set(['pult', 'tresor', 'gibt-es-nicht']) })
assert.equal(draw(never).texts.filter((x) => x.text === SIGN).length, 0)
})
test('Schild steht innerhalb des grauen Raums und in der Etage seiner Figur', () => {
const look = looks({ greyRooms: new Set(['kleinanzeigen']) })
const m = draw(look)
const sign = m.texts.find((x) => x.text === SIGN)
const floor = layout.floors.find((f) => f.id === 'anfragen')
const room = floor.rooms.find((r) => r.id === 'kleinanzeigen')
// Mitte des Raums (k = 3, align center), Zeile innerhalb der Etage
assert.equal(sign.x, Math.round((room.x + room.w / 2) * 3))
assert.ok(sign.y >= floor.top * 3 && sign.y < floor.floorY * 3)
})
test('unbekannte Raumart zeichnet nur Titel und Tint, ohne Wurf', () => {
const odd = { ...layout, floors: layout.floors.map((f) => ({ ...f, rooms: f.rooms.map((r) => ({ ...r, kind: 'gibt-es-nicht' })) })) }
const m = makeCtx()
assert.doesNotThrow(() => R.drawRooms(m.ctx, odd, 1, R.createLook()))
assert.ok(m.texts.some((x) => x.text === ROOM_TITLES.netz))
})
test('drawRooms hält save/restore im Gleichgewicht', () => {
const m = draw(looks({ greyRooms: new Set(['netz']), pultOpen: 3, dealsMonth: 2 }))
assert.equal(m.counts.save, m.counts.restore)
})
test('Gebäude: Bodenlinie liegt auf dem Boden der Tresor-Etage (y 224)', () => {
const tresor = layout.floors.find((f) => f.id === 'tresor')
assert.equal(tresor.floorY, 224)
const m = makeCtx()
B.drawOutside(m.ctx)
// k = 3: volle Breite 960, eine logische Einheit hoch, auf y 672
assert.ok(m.rects.some((r) => r.x === 0 && r.y === 224 * 3 && r.w === 960 && r.h === 3))
// Erde unter der Bodenlinie, bis zum Weltrand
assert.ok(m.rects.some((r) => r.y === 225 * 3 && r.h === (256 - 225) * 3))
})
test('Gebäude: Lampe der Etage mit Kabine leuchtet grün, keine ohne Kabine', () => {
const green = (at) => {
const m = makeCtx()
B.drawTower(m.ctx, layout, 1, { at })
return count(m.rects, PAL.green)
}
const none = green(() => false)
assert.equal(green((i) => i === 3), none + 1)
assert.equal(green((i) => i === 0 || i === 9), none + 2)
})
test('Gebäude: Etagenschilder rechts außen bei x 234, in der Zeile ihrer Etage', () => {
const m = makeCtx()
B.drawTower(m.ctx, layout, 1, { at: () => false })
for (const floor of layout.floors) {
const sign = m.texts.find((x) => x.text === floor.title)
assert.equal(sign.x, 234 * 3, floor.id)
assert.ok(sign.y >= floor.top * 3 && sign.y < floor.floorY * 3, floor.id)
}
})
test('Gebäude: Hülle reicht bis zum Fundament der letzten Etage', () => {
const m = makeCtx()
B.drawTower(m.ctx, layout, 1, { at: () => false })
const last = layout.floors[layout.floors.length - 1]
const bottom = Math.max(...m.rects.map((r) => (r.y + r.h) / 3))
assert.equal(bottom, last.floorY + 2)
assert.ok(m.rects.every((r) => r.x >= 0 && (r.x + r.w) / 3 <= 320))
})
test('Pausenschleier: 45 % Weiß über der ganzen Welt, Aufrufe gleichen sich aus', () => {
const m = makeCtx()
B.drawPauseVeil(m.ctx, looks({ paused: true }))
assert.ok(m.alphas.includes(0.45))
const veil = m.rects.find((r) => r.fill === PAL.w)
assert.ok(veil.x <= 0 && veil.y <= 0 && veil.x + veil.w >= 960 && veil.y + veil.h >= 768)
assert.equal(m.counts.save, m.counts.restore)
const off = makeCtx()
B.drawPauseVeil(off.ctx, R.createLook())
assert.equal(off.rects.length, 0)
})
test('Gebäude: Layout ohne Etagen zeichnet nichts und wirft nicht', () => {
const m = makeCtx()
assert.doesNotThrow(() => B.drawTower(m.ctx, { floors: [], homes: {}, spots: {} }, 1, { at: () => false }))
assert.doesNotThrow(() => R.drawRooms(m.ctx, { floors: [], homes: {}, spots: {} }, 1, R.createLook()))
})
// ---- Fix-Runde 1: Zeichnung und Effektorte, graue Räume ----
// Rechtecke der Raumzeichnung, die den Mittelpunkt der Einheitszelle (x, y) überdecken; die Tönung, die den ganzen Raum füllt, zählt nicht
const hitsAt = (floorId, spot, look = R.createLook()) => {
const floor = layout.floors.find((f) => f.id === floorId)
const full = Math.max(...floor.rooms.map((r) => r.w)) * 3
const m = makeCtx()
R.drawRooms(m.ctx, { ...layout, floors: [floor] }, 1, look)
const cx = spot.x * 3 + 1
const cy = spot.y * 3 + 1
return m.rects.filter((r) => r.w < full && cx >= r.x && cx < r.x + r.w && cy >= r.y && cy < r.y + r.h)
}
test('Zeichnung trifft den Spot: Briefkasten am Pult, Spitze genau bei spots.pult', () => {
const spot = layout.spots.pult
assert.ok(hitsAt('freigabe', spot).some((r) => r.fill === TEAM.freigabe), 'Briefkasten unter dem Spot')
// Spitze: eine Zeile darüber ist kein Briefkasten mehr
assert.ok(!hitsAt('freigabe', { x: spot.x, y: spot.y - 1 }).some((r) => r.fill === TEAM.freigabe))
})
test('Zeichnung trifft den Spot: Tresortür in der Mitte von spots.tresor', () => {
const hit = hitsAt('tresor', layout.spots.tresor)
assert.ok(hit.some((r) => r.fill === PAL.metal2 && r.w === 22 * 3 && r.h === 20 * 3), 'Tresorkörper unter dem Spot')
// die 6 × 6 große Türplatte (x + 8 ... x + 14) umschließt den Spot
assert.ok(hit.some((r) => r.fill === PAL.metal && r.w === 6 * 3 && r.h === 6 * 3), 'Türplatte unter dem Spot')
})
test('Zeichnung trifft den Spot: Schublade eines Aktenschranks bei spots.archiv, nicht auf Arne', () => {
const spot = layout.spots.archiv
const hit = hitsAt('keller', spot)
// oberste Schubladenfront (10 × 5) des ersten Aktenschranks
assert.ok(hit.some((r) => r.fill === PAL.metal && r.w === 10 * 3 && r.h === 5 * 3), 'Schubladenfront unter dem Spot')
assert.ok(hit.some((r) => r.fill === PAL.metal2 && r.w === 12 * 3), 'Schrankkorpus unter dem Spot')
// Arnes Platz (Körper 12 breit, 14 hoch, Hut ab y - 2) liegt nicht auf dem Spot
const arne = layout.homes.arne
assert.ok(!(spot.x >= arne.x && spot.x < arne.x + 12 && spot.y >= arne.y - 2 && spot.y < arne.y + 14))
})
test('grauer Raum: Teamfarbton bleibt, das Schild ersetzt den Titel (kein Platz für beides)', () => {
const floor = layout.floors.find((f) => f.id === 'anfragen')
const room = floor.rooms.find((r) => r.id === 'kleinanzeigen')
assert.equal(room.w, 46)
const run = (look) => {
const m = makeCtx()
R.drawRooms(m.ctx, { ...layout, floors: [{ ...floor, rooms: [room] }] }, 1, look)
return m
}
const normal = run(looks())
const grey = run(looks({ greyRooms: new Set(['kleinanzeigen']) }))
const tintRects = (m) => m.rects.filter((r) => r.fill === TEAM[room.team] && r.w === room.w * 3 && r.h === floor.h * 3)
assert.equal(tintRects(normal).length, 1)
assert.equal(tintRects(grey).length, 1, 'Teamfarbton auch im grauen Raum')
assert.ok(grey.alphas.includes(0.1) && grey.alphas.includes(0.45), 'Teamfarbton 0,10, darüber die Grautönung 0,45')
assert.ok(normal.texts.some((x) => x.text === room.title))
assert.ok(!grey.texts.some((x) => x.text === room.title), 'im grauen Raum steht das Schild an der Stelle des Titels')
assert.equal(grey.texts.filter((x) => x.text === SIGN).length, 1)
assert.equal(normal.texts.filter((x) => x.text === SIGN).length, 0)
})
test('Schilder: drawRooms mit signs: false lässt sie aus (Grautönung bleibt), drawSigns zeichnet genau diese Schilder an dieselbe Stelle', () => {
const look = looks({ greyRooms: new Set(['kleinanzeigen', 'portale']) })
const withSigns = draw(look)
const m = makeCtx()
R.drawRooms(m.ctx, layout, 1, look, { signs: false })
assert.equal(m.texts.filter((x) => x.text === SIGN).length, 0)
assert.equal(m.rects.length < withSigns.rects.length, true)
assert.ok(m.alphas.includes(0.45), 'die Grautönung gehört weiter zum Raum')
assert.deepEqual(m.texts.filter((x) => x.text !== SIGN).map((x) => x.text), withSigns.texts.filter((x) => x.text !== SIGN).map((x) => x.text))
const s = makeCtx()
R.drawSigns(s.ctx, layout, look)
assert.deepEqual(s.texts.map((x) => x.text), [SIGN, SIGN])
assert.deepEqual(s.texts.map((x) => [x.x, x.y]), withSigns.texts.filter((x) => x.text === SIGN).map((x) => [x.x, x.y]))
const none = makeCtx()
R.drawSigns(none.ctx, layout, R.createLook())
assert.equal(none.rects.length + none.texts.length, 0)
const noWorker = makeCtx()
R.drawSigns(noWorker.ctx, layout, looks({ greyRooms: new Set(['pult', 'tresor']) })) // Räume ohne Worker werden nie grau
assert.equal(noWorker.texts.length, 0)
})
test('Raumtitel: über den Möbeln gezeichnet (ein Monitor deckt sie nicht zu) und mit hellem Rand', () => {
const floor = layout.floors.find((f) => f.id === 'gespraeche')
const room = floor.rooms.find((r) => r.id === 'mail')
const m = makeCtx()
R.drawRooms(m.ctx, { ...layout, floors: [{ ...floor, rooms: [room] }] }, 1, R.createLook())
assert.equal(m.strokes.length, 1)
assert.equal(m.strokes[0].text, room.title)
assert.ok(m.strokes[0].style.startsWith('rgba(255, 255, 255'))
assert.equal(m.strokes[0].rects, m.rects.length, 'nach dem Titel kommt keine Fläche mehr: Möbel, Tisch und Monitor liegen darunter')
assert.ok(m.rects.length > 20)
assert.equal(m.texts.filter((x) => x.text === room.title).length, 1)
const grey = makeCtx()
R.drawRooms(grey.ctx, { ...layout, floors: [{ ...floor, rooms: [{ ...room, id: 'kleinanzeigen' }] }] }, 1, looks({ greyRooms: new Set(['kleinanzeigen']) }))
assert.equal(grey.strokes.length, 0, 'im grauen Raum steht kein Titel')
})