241 lines
11 KiB
JavaScript
241 lines
11 KiB
JavaScript
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()))
|
||
})
|