import assert from 'node:assert/strict' import test from 'node:test' import { addHomeWidget, createDefaultHomeDashboard, createHomeDashboardSaveQueue, enableFreeHomeDashboard, findNearestFreeHomeWidgetPosition, getHomeGridColumnCount, getHomeWidgetDimensions, getHomeWidgetSpan, moveHomeWidget, normalizeHomeDashboard, packHomeWidgets, removeHomeWidget, replaceHomeDashboardWidgets, resizeHomeWidget, setHomeDashboardLayout, setHomeGreetingOptions, setHomeRecentLimit, setHomeWidgetPosition, } from './home-dashboard.ts' test('derives one to four columns from the dashboard container width', () => { assert.equal(getHomeGridColumnCount(0), 1) assert.equal(getHomeGridColumnCount(495), 1) assert.equal(getHomeGridColumnCount(496), 2) assert.equal(getHomeGridColumnCount(752), 3) assert.equal(getHomeGridColumnCount(2000), 4) }) test('derives responsive widget dimensions from the current grid', () => { assert.deepEqual(getHomeWidgetDimensions('2x2', 4, 1008), { width: 496, height: 336, }) assert.deepEqual(getHomeWidgetDimensions('2x1', 1, 320), { width: 320, height: 160, }) assert.deepEqual(getHomeWidgetDimensions('3x2', 4, 1008), { width: 752, height: 336, }) }) test('temporarily clamps wide widgets without changing their preferred size', () => { const config = createDefaultHomeDashboard() const preferredSize = config.widgets[0].size assert.deepEqual(getHomeWidgetSpan('2x2', 1), { columns: 1, rows: 2 }) assert.deepEqual(getHomeWidgetSpan('2x2', 2), { columns: 2, rows: 2 }) packHomeWidgets(config.widgets, 1) packHomeWidgets(config.widgets, 4) assert.equal(config.widgets[0].size, preferredSize) }) test('adds, reorders, resizes, and removes independent placements', () => { const original = createDefaultHomeDashboard() const pinnedInstances = original.widgets.find((widget) => widget.kind === 'pinned-instances')! const duplicate = { ...pinnedInstances, id: 'duplicate-pinned-instances' } const added = addHomeWidget(original, duplicate) assert.equal(added.widgets.length, original.widgets.length + 1) assert.equal(added.widgets.filter((widget) => widget.kind === 'pinned-instances').length, 2) const moved = moveHomeWidget(added, added.widgets.length - 1, -1) assert.equal(moved.widgets.at(-2)?.id, duplicate.id) const resized = resizeHomeWidget(moved, duplicate.id, '1x1') assert.equal(resized.widgets.find((widget) => widget.id === duplicate.id)?.size, '1x1') const removed = removeHomeWidget(resized, duplicate.id) assert.deepEqual(removed.widgets, original.widgets) }) test('accepts draggable order and restores the complete default layout', () => { const original = createDefaultHomeDashboard() const reversed = replaceHomeDashboardWidgets(original, [...original.widgets].reverse()) assert.deepEqual( reversed.widgets.map((widget) => widget.id), original.widgets.map((widget) => widget.id).reverse(), ) assert.deepEqual( createDefaultHomeDashboard().widgets.map(({ kind, size }) => ({ kind, size })), [ { kind: 'greeting', size: '2x1' }, { kind: 'calendar', size: '1x2' }, { kind: 'recent', size: '2x2' }, { kind: 'pinned-worlds', size: '1x2' }, { kind: 'pinned-servers', size: '2x2' }, { kind: 'pinned-instances', size: '2x1' }, ], ) }) test('enables free layout from packed positions and preserves manual coordinates', () => { const original = createDefaultHomeDashboard() const positioned = setHomeWidgetPosition(original, original.widgets[0].id, { column: 1, row: 3 }) const free = enableFreeHomeDashboard(positioned, 4) assert.equal(free.layout, 'free') assert.deepEqual(free.widgets[0].position, { column: 1, row: 3 }) assert.deepEqual(free.widgets[1].position, { column: 2, row: 0 }) assert.equal(setHomeDashboardLayout(free, 'grid').layout, 'grid') }) test('snaps manual placement to the nearest open cell without moving other widgets', () => { const free = enableFreeHomeDashboard(createDefaultHomeDashboard(), 4) const before = free.widgets.map((widget) => widget.position) const moving = free.widgets.at(-1)! const occupied = free.widgets[0].position! const resolved = findNearestFreeHomeWidgetPosition(free.widgets, moving, occupied, 4) assert.notDeepEqual(resolved, occupied) assert.deepEqual( free.widgets.map((widget) => widget.position), before, ) }) test('rolls back the latest failed save and reports the error', async () => { const original = createDefaultHomeDashboard() const changed = removeHomeWidget(original, original.widgets[0].id) let current = changed const errors: unknown[] = [] const queue = createHomeDashboardSaveQueue( async () => { throw new Error('save failed') }, (config) => { current = config }, (error) => errors.push(error), ) await queue.enqueue(changed, original) await queue.flush() assert.deepEqual(current, original) assert.equal(errors.length, 1) }) test('normalizes a saved layout when entering Home again', () => { const saved = createDefaultHomeDashboard() const restored = normalizeHomeDashboard(JSON.parse(JSON.stringify(saved))) assert.deepEqual(restored, saved) }) test('normalizes legacy layouts and persisted free positions', () => { const legacy = normalizeHomeDashboard({ version: 1, widgets: [{ id: 'legacy', kind: 'calendar', size: '1x2' }], }) const free = normalizeHomeDashboard({ version: 1, layout: 'free', widgets: [ { id: 'valid', kind: 'calendar', size: '1x2', position: { column: 2, row: 4 } }, { id: 'invalid', kind: 'calendar', size: '1x2', position: { column: -10, row: 'top' } }, ], }) assert.equal(legacy?.layout, 'grid') assert.equal(free?.layout, 'free') assert.deepEqual(free?.widgets[0].position, { column: 2, row: 4 }) assert.equal(free?.widgets[1].position, undefined) }) test('packs widgets into the earliest available cells', () => { const config = createDefaultHomeDashboard() const packed = packHomeWidgets(config.widgets, 4) assert.deepEqual( packed.map(({ column, row, effectiveColumns, effectiveRows }) => ({ column, row, effectiveColumns, effectiveRows, })), [ { column: 1, row: 1, effectiveColumns: 2, effectiveRows: 1 }, { column: 3, row: 1, effectiveColumns: 1, effectiveRows: 2 }, { column: 1, row: 2, effectiveColumns: 2, effectiveRows: 2 }, { column: 4, row: 1, effectiveColumns: 1, effectiveRows: 2 }, { column: 3, row: 3, effectiveColumns: 2, effectiveRows: 2 }, { column: 1, row: 4, effectiveColumns: 2, effectiveRows: 1 }, ], ) }) test('normalizes malformed sizes while retaining duplicate widgets', () => { const normalized = normalizeHomeDashboard({ version: 1, widgets: [ { id: 'one', kind: 'calendar', size: '9x9' }, { id: 'two', kind: 'calendar', size: '1x1' }, { id: 'three', kind: 'world', size: '1x1', target: { instanceId: 'a' } }, ], }) assert.deepEqual( normalized?.widgets.map(({ kind, size }) => ({ kind, size })), [ { kind: 'calendar', size: '1x2' }, { kind: 'calendar', size: '1x2' }, ], ) }) test('normalizes every persisted calendar placement to the fixed 1x2 size', () => { const normalized = normalizeHomeDashboard({ version: 1, widgets: [ { id: 'small', kind: 'calendar', size: '1x1' }, { id: 'wide', kind: 'calendar', size: '2x2' }, ], }) assert.deepEqual( normalized?.widgets.map((widget) => widget.size), ['1x2', '1x2'], ) }) test('normalizes every persisted greeting placement to the fixed 2x1 size', () => { const normalized = normalizeHomeDashboard({ version: 1, widgets: [ { id: 'small', kind: 'greeting', size: '1x1' }, { id: 'tall', kind: 'greeting', size: '1x2', options: { greetingMode: 'text', greetingText: ' Ready to play ' }, }, ], }) assert.deepEqual( normalized?.widgets.map((widget) => widget.size), ['2x1', '2x1'], ) assert.deepEqual( normalized?.widgets.map((widget) => widget.options), [ { greetingMode: 'greeting', greetingFont: 'sans', greetingFontSize: 22 }, { greetingMode: 'text', greetingText: 'Ready to play', greetingFont: 'sans', greetingFontSize: 22, }, ], ) assert.deepEqual( resizeHomeWidget(normalized!, 'small', '1x1').widgets.map((widget) => widget.size), ['2x1', '2x1'], ) assert.deepEqual( setHomeGreetingOptions(normalized!, 'small', 'text-and-greeting', ' Hi ', 'minecraft', 29) .widgets[0].options, { greetingMode: 'text-and-greeting', greetingText: 'Hi', greetingFont: 'minecraft', greetingFontSize: 29, }, ) }) test('normalizes greeting font settings and clamps font size', () => { const normalized = normalizeHomeDashboard({ version: 1, widgets: [ { id: 'large', kind: 'greeting', size: '2x1', options: { greetingFont: 'serif', greetingFontSize: 80 }, }, { id: 'invalid', kind: 'greeting', size: '2x1', options: { greetingFont: 'comic-sans', greetingFontSize: 'large' }, }, ], })! assert.deepEqual( normalized.widgets.map((widget) => ({ font: widget.options?.greetingFont, fontSize: widget.options?.greetingFontSize, })), [ { font: 'serif', fontSize: 32 }, { font: 'sans', fontSize: 22 }, ], ) }) test('normalizes and updates recently played item limits', () => { const normalized = normalizeHomeDashboard({ version: 1, widgets: [ { id: 'legacy', kind: 'recent', size: '2x2' }, { id: 'valid', kind: 'recent', size: '2x1', options: { recentLimit: 8 } }, { id: 'invalid', kind: 'recent', size: '1x2', options: { recentLimit: 99 } }, ], })! assert.deepEqual( normalized.widgets.map((widget) => widget.options?.recentLimit), [4, 8, 4], ) assert.equal(setHomeRecentLimit(normalized, 'legacy', 6).widgets[0].options?.recentLimit, 6) }) test('accepts and resizes the recent widget to 3-column layouts', () => { const normalized = normalizeHomeDashboard({ version: 1, widgets: [{ id: 'recent', kind: 'recent', size: '3x2' }], })! const config = createDefaultHomeDashboard() const recent = config.widgets.find((widget) => widget.kind === 'recent')! assert.equal(normalized.widgets[0].size, '3x2') assert.equal( resizeHomeWidget(config, recent.id, '3x1').widgets.find((widget) => widget.id === recent.id) ?.size, '3x1', ) })