feat(client): display semantic asset resource type instead of filename during boot

This commit is contained in:
troytt 2026-10-01 13:28:45 +00:00
parent 309738cf79
commit 5977d81c2d
4 changed files with 225 additions and 43 deletions

View File

@ -377,7 +377,7 @@
#d2-boot-progress-file { #d2-boot-progress-file {
color: #f0dfb8; color: #f0dfb8;
font-family: monospace; font-family: var(--font-body);
font-size: 12px; font-size: 12px;
max-width: 290px; max-width: 290px;
overflow: hidden; overflow: hidden;
@ -422,7 +422,7 @@
</div> </div>
<div class="d2-boot-meta"> <div class="d2-boot-meta">
<span class="d2-boot-file-label">正在下载:</span> <span class="d2-boot-file-label">正在下载:</span>
<span id="d2-boot-progress-file">initializing...</span> <span id="d2-boot-progress-file">正在初始化...</span>
<span id="d2-boot-progress-count">(0/0)</span> <span id="d2-boot-progress-count">(0/0)</span>
</div> </div>
</div> </div>

View File

@ -238,7 +238,7 @@ async function bootPlayApp(): Promise<PlayAppController> {
onPanAutomap: (dx, dy) => minimap.pan(dx, dy), onPanAutomap: (dx, dy) => minimap.pan(dx, dy),
}) })
await hudManager.load('/ui') await hudManager.load('/ui')
bootProgress.setStageFloor(25, 'manifest.json') bootProgress.setStageFloor(25, '游戏界面资源')
// Fail fast: without the packed act tiles there is no 1.13c map (automap, collision, picking). // Fail fast: without the packed act tiles there is no 1.13c map (automap, collision, picking).
let mapService: MapService let mapService: MapService
@ -247,7 +247,7 @@ async function bootPlayApp(): Promise<PlayAppController> {
} catch (err) { } catch (err) {
throw new Error(`MapService: failed to load /act-packs/tiles: ${err instanceof Error ? err.message : String(err)}`) throw new Error(`MapService: failed to load /act-packs/tiles: ${err instanceof Error ? err.message : String(err)}`)
} }
bootProgress.setStageFloor(45, 'drlg-bundle.bin') bootProgress.setStageFloor(45, '地图地形资源')
// Kick off asynchronous GPU atlas preloading for WebGL2 world rendering // Kick off asynchronous GPU atlas preloading for WebGL2 world rendering
tileGpuCache.ensureActPalette(1) tileGpuCache.ensureActPalette(1)
@ -679,9 +679,9 @@ async function bootPlayApp(): Promise<PlayAppController> {
}) })
await frontendRenderer.load('/ui/frontend') await frontendRenderer.load('/ui/frontend')
bootProgress.setStageFloor(78, 'bg-main-menu.png') bootProgress.setStageFloor(78, '主菜单与大厅界面')
await assetsReadyPromise await assetsReadyPromise
bootProgress.setStageFloor(96, 'items-atlas.png') bootProgress.setStageFloor(96, '物品图标资源')
await bootProgress.waitForIdle(2500) await bootProgress.waitForIdle(2500)
bootProgress.complete() bootProgress.complete()

View File

@ -8,14 +8,16 @@
* *
* Renders: * Renders:
* 1. Total download progress percentage (`0%` – `100%`, strictly monotonic). * 1. Total download progress percentage (`0%` – `100%`, strictly monotonic).
* 2. The filename of the resource currently being downloaded (`currentFile`). * 2. The semantic resource type of the asset currently being downloaded (`currentFile` / `currentResourceType`).
*/ */
export interface BootProgressSnapshot { export interface BootProgressSnapshot {
/** Monotonic total download percentage in `[0, 100]`. */ /** Monotonic total download percentage in `[0, 100]`. */
readonly percent: number readonly percent: number
/** Basename of the resource currently being downloaded (e.g. `drlg-bundle.bin`, `items-atlas.png`). */ /** Semantic resource type of the resource currently being downloaded (e.g. `地图地形资源`, `物品图标资源`). */
readonly currentFile: string readonly currentFile: string
/** Semantic resource type of the resource currently being downloaded. */
readonly currentResourceType: string
/** Normalized URL/path of the resource currently being downloaded. */ /** Normalized URL/path of the resource currently being downloaded. */
readonly currentFilePath: string readonly currentFilePath: string
/** Number of resources that have finished downloading. */ /** Number of resources that have finished downloading. */
@ -26,7 +28,7 @@ export interface BootProgressSnapshot {
readonly inFlightCount: number readonly inFlightCount: number
/** Lifecycle status of the boot progress tracker. */ /** Lifecycle status of the boot progress tracker. */
readonly status: 'idle' | 'loading' | 'completed' readonly status: 'idle' | 'loading' | 'completed'
/** Ordered list of resource filenames that have completed downloading. */ /** Ordered list of resource types that have completed downloading. */
readonly loadedFiles: readonly string[] readonly loadedFiles: readonly string[]
} }
@ -35,7 +37,7 @@ export interface BootProgressOptions {
readonly stageEl?: HTMLElement | null readonly stageEl?: HTMLElement | null
/** Baseline expected number of boot resource requests used to prevent early 100% spikes. */ /** Baseline expected number of boot resource requests used to prevent early 100% spikes. */
readonly expectedTotal?: number readonly expectedTotal?: number
/** Initial placeholder filename before the first request starts. */ /** Initial placeholder label before the first request starts. */
readonly initialFileLabel?: string readonly initialFileLabel?: string
/** Whether to automatically hide the overlay when `complete()` is called (default: `true`). */ /** Whether to automatically hide the overlay when `complete()` is called (default: `true`). */
readonly hideOnComplete?: boolean readonly hideOnComplete?: boolean
@ -44,6 +46,7 @@ export interface BootProgressOptions {
interface InFlightEntry { interface InFlightEntry {
readonly id: number readonly id: number
readonly fileName: string readonly fileName: string
readonly resourceType: string
readonly filePath: string readonly filePath: string
readonly promise: Promise<void> readonly promise: Promise<void>
readonly resolve: () => void readonly resolve: () => void
@ -58,9 +61,29 @@ interface InFlightEntry {
*/ */
export const DEFAULT_EXPECTED_BOOT_ASSETS = 910 export const DEFAULT_EXPECTED_BOOT_ASSETS = 910
const INITIAL_PLACEHOLDER_LABELS = new Set(['initializing...', '正在初始化...'])
const KNOWN_RESOURCE_TYPES = new Set([
'字体资源',
'小地图资源',
'技能图标资源',
'物品图标资源',
'投射物特效资源',
'光环与状态特效',
'角色美术资源',
'怪物美术资源',
'地图地形资源',
'主菜单与大厅界面',
'游戏界面资源',
'音频资源',
'资源配置数据',
'美术贴图资源',
'游戏资源',
])
/** /**
* Extracts a clean resource filename from a URL or relative path. * Extracts a clean resource filename from a URL or relative path.
* Strips query parameters (`?...`) and hash fragments (`#...`). * Strips query parameters and hash fragments.
* *
* Examples: * Examples:
* - `/act-packs/tiles/drlg-bundle.bin` -> `drlg-bundle.bin` * - `/act-packs/tiles/drlg-bundle.bin` -> `drlg-bundle.bin`
@ -108,6 +131,133 @@ export function normalizeAssetPath(urlOrPath: string): string {
return noQuery.replace(/\\/g, '/') return noQuery.replace(/\\/g, '/')
} }
/**
* Classifies a resource URL or path into a human-readable semantic asset type label
* (replacing raw filenames in the homepage boot download progress UI).
*
* Examples:
* - `/ui/fonts/metrics.json` -> `'字体资源'`
* - `/act-packs/tiles/drlg-bundle.bin` -> `'地图地形资源'`
* - `/ui/frontend/bg-main-menu.png` -> `'主菜单与大厅界面'`
* - `samples/d2-packs/items/items-atlas.png` -> `'物品图标资源'`
* - `samples/d2-packs/entities/char-so.r8` -> `'角色美术资源'`
* - `samples/d2-packs/entities/monster-fallen1-hth.r8` -> `'怪物美术资源'`
* - `/missiles/firebolt.png` -> `'投射物特效资源'`
* - `/overlays/AuraDefianceBack.png` -> `'光环与状态特效'`
* - `/skills/icon_36.png` -> `'技能图标资源'`
* - `/assets/ui/automap/MaxiMap.png` -> `'小地图资源'`
* - `/ui/ctrlpnl-800.png` -> `'游戏界面资源'`
*/
export function extractAssetResourceType(urlOrPath: string): string {
const trimmed = urlOrPath.trim()
if (!trimmed || trimmed.startsWith('data:') || trimmed.startsWith('blob:')) {
return ''
}
if (KNOWN_RESOURCE_TYPES.has(trimmed)) {
return trimmed
}
const rawFileName = extractAssetFileName(trimmed)
if (!rawFileName) return ''
const fileName = rawFileName.toLowerCase()
const normPath = normalizeAssetPath(trimmed).toLowerCase()
if (
normPath.includes('/fonts/') ||
normPath.startsWith('fonts/') ||
fileName === 'metrics.json' ||
fileName.startsWith('font') ||
/\.(?:ttf|woff2?|otf)$/i.test(fileName)
) {
return '字体资源'
}
if (
normPath.includes('/automap/') ||
/^(?:automap|maximap|act[24]map|extnmap)/i.test(fileName)
) {
return '小地图资源'
}
if (
normPath.includes('/skills/') ||
normPath.startsWith('skills/') ||
/^(?:skill|generic-skills|bg_(?:ama|ass|bar|dru|nec|pal|sor)_)/i.test(fileName)
) {
return '技能图标资源'
}
if (normPath.includes('/items/') || fileName.startsWith('items-atlas')) {
return '物品图标资源'
}
if (normPath.includes('/missiles/') || normPath.startsWith('missiles/')) {
return '投射物特效资源'
}
if (normPath.includes('/overlays/') || normPath.startsWith('overlays/')) {
return '光环与状态特效'
}
if (normPath.includes('/chars/') || /^char-(?!sheet|select)/i.test(fileName)) {
return '角色美术资源'
}
if (normPath.includes('/entities/') || /^(?:monster-|monstats)/i.test(fileName)) {
return '怪物美术资源'
}
if (
normPath.includes('/tiles/') ||
normPath.startsWith('act-packs/tiles') ||
/(?:^|\/)packs\/act[1-5]\//i.test(normPath) ||
/^(?:drlg-bundle|palette\.bin|world-graph|tiles-|objects-|scene\.json)/i.test(fileName) ||
/\.(?:dt1|ds1|pal|pl2)$/i.test(fileName)
) {
return '地图地形资源'
}
if (
normPath.includes('/ui/frontend/') ||
normPath.startsWith('ui/frontend/') ||
/^(?:bg-main-menu|bg-char-create|bg-char-select|bg-waiting-room|loading-door-strip|create-game-bg|charselectbox|realmselect|popup-|logo-|gembutton|textbox|clickbox|joingameclickbox|radiobutton|grey-level-box|btn-)/i.test(
fileName,
)
) {
return '主菜单与大厅界面'
}
if (
normPath.includes('/ui/') ||
normPath.startsWith('ui/') ||
/^(?:ctrlpnl|globe-|overlap-|border-|char-sheet|inv-sheet|inv-tab|quest-|quests-atlas|waypoint-|stash-bg|cube-bg|vendor-|popbelt|minipanel|runbutton|menubutton|buysellbtn|goldcoinbtn|miniconvert|level-btn|level-socket|cursor|boxpieces|focus16)/i.test(
fileName,
)
) {
return '游戏界面资源'
}
if (
normPath.includes('/audio/') ||
normPath.includes('/sound/') ||
normPath.includes('/music/') ||
/\.(?:mp3|ogg|wav)$/i.test(fileName)
) {
return '音频资源'
}
if (normPath.includes('/anim/') || /\.(?:json|bin|d2|txt|tbl|dat)$/i.test(fileName)) {
return '资源配置数据'
}
if (/\.(?:png|r8|dc6|dcc|pcx|cel)$/i.test(fileName)) {
return '美术贴图资源'
}
return '游戏资源'
}
export class BootProgressTracker { export class BootProgressTracker {
private readonly stageEl: HTMLElement | null private readonly stageEl: HTMLElement | null
private readonly expectedTotal: number private readonly expectedTotal: number
@ -139,7 +289,7 @@ export class BootProgressTracker {
constructor(options: BootProgressOptions = {}) { constructor(options: BootProgressOptions = {}) {
this.stageEl = options.stageEl ?? null this.stageEl = options.stageEl ?? null
this.expectedTotal = Math.max(1, options.expectedTotal ?? DEFAULT_EXPECTED_BOOT_ASSETS) this.expectedTotal = Math.max(1, options.expectedTotal ?? DEFAULT_EXPECTED_BOOT_ASSETS)
this.currentFile = options.initialFileLabel ?? 'initializing...' this.currentFile = options.initialFileLabel ?? '正在初始化...'
this.hideOnComplete = options.hideOnComplete ?? true this.hideOnComplete = options.hideOnComplete ?? true
this.bindOrCreateDom() this.bindOrCreateDom()
} }
@ -164,7 +314,7 @@ export class BootProgressTracker {
' </div>', ' </div>',
' <div class="d2-boot-meta">', ' <div class="d2-boot-meta">',
' <span class="d2-boot-file-label">正在下载:</span>', ' <span class="d2-boot-file-label">正在下载:</span>',
' <span id="d2-boot-progress-file">initializing...</span>', ' <span id="d2-boot-progress-file">正在初始化...</span>',
' <span id="d2-boot-progress-count">(0/0)</span>', ' <span id="d2-boot-progress-count">(0/0)</span>',
' </div>', ' </div>',
'</div>', '</div>',
@ -337,13 +487,14 @@ export class BootProgressTracker {
} }
/** /**
* Records the start of a resource download and updates the active filename & percentage. * Records the start of a resource download and updates the active resource type & percentage.
* Returns a non-zero tracking ID (or `0` if the URL is ignored, e.g. `data:`/`blob:`). * Returns a non-zero tracking ID (or `0` if the URL is ignored, e.g. `data:`/`blob:`).
*/ */
beginFile(urlOrPath: string): number { beginFile(urlOrPath: string): number {
if (this.status === 'completed') return 0 if (this.status === 'completed') return 0
const fileName = extractAssetFileName(urlOrPath) const fileName = extractAssetFileName(urlOrPath)
if (!fileName) return 0 if (!fileName) return 0
const resourceType = extractAssetResourceType(urlOrPath) || '游戏资源'
const filePath = normalizeAssetPath(urlOrPath) || fileName const filePath = normalizeAssetPath(urlOrPath) || fileName
if (this.status === 'idle') { if (this.status === 'idle') {
@ -360,12 +511,13 @@ export class BootProgressTracker {
this.inFlight.set(id, { this.inFlight.set(id, {
id, id,
fileName, fileName,
resourceType,
filePath, filePath,
promise, promise,
resolve: resolveFn, resolve: resolveFn,
}) })
this.currentFile = fileName this.currentFile = resourceType
this.currentFilePath = filePath this.currentFilePath = filePath
this.recalculatePercent() this.recalculatePercent()
this.renderDom() this.renderDom()
@ -385,21 +537,21 @@ export class BootProgressTracker {
if (this.status === 'completed') return if (this.status === 'completed') return
this.loadedCount++ this.loadedCount++
this.loadedFiles.push(entry.fileName) this.loadedFiles.push(entry.resourceType)
// If other files are still downloading, show the most recently started in-flight file; // If other files are still downloading, show the most recently started in-flight resource type;
// otherwise keep the just-finished file name visible. // otherwise keep the just-finished resource type visible.
if (this.inFlight.size > 0) { if (this.inFlight.size > 0) {
let latest: InFlightEntry | undefined let latest: InFlightEntry | undefined
for (const item of this.inFlight.values()) { for (const item of this.inFlight.values()) {
latest = item latest = item
} }
if (latest) { if (latest) {
this.currentFile = latest.fileName this.currentFile = latest.resourceType
this.currentFilePath = latest.filePath this.currentFilePath = latest.filePath
} }
} else { } else {
this.currentFile = entry.fileName this.currentFile = entry.resourceType
this.currentFilePath = entry.filePath this.currentFilePath = entry.filePath
} }
@ -431,8 +583,8 @@ export class BootProgressTracker {
if (clamped > this.stageFloorPercent) { if (clamped > this.stageFloorPercent) {
this.stageFloorPercent = clamped this.stageFloorPercent = clamped
} }
if (fallbackFile && (!this.currentFile || this.currentFile === 'initializing...')) { if (fallbackFile && (!this.currentFile || INITIAL_PLACEHOLDER_LABELS.has(this.currentFile))) {
this.currentFile = extractAssetFileName(fallbackFile) || fallbackFile this.currentFile = extractAssetResourceType(fallbackFile) || fallbackFile
this.currentFilePath = normalizeAssetPath(fallbackFile) || this.currentFile this.currentFilePath = normalizeAssetPath(fallbackFile) || this.currentFile
} }
this.recalculatePercent() this.recalculatePercent()
@ -462,7 +614,7 @@ export class BootProgressTracker {
complete(): void { complete(): void {
for (const entry of this.inFlight.values()) { for (const entry of this.inFlight.values()) {
this.loadedCount++ this.loadedCount++
this.loadedFiles.push(entry.fileName) this.loadedFiles.push(entry.resourceType)
entry.resolve() entry.resolve()
} }
this.inFlight.clear() this.inFlight.clear()
@ -470,8 +622,8 @@ export class BootProgressTracker {
this.uninstall() this.uninstall()
this.status = 'completed' this.status = 'completed'
this.percent = 100 this.percent = 100
if (!this.currentFile || this.currentFile === 'initializing...') { if (!this.currentFile || INITIAL_PLACEHOLDER_LABELS.has(this.currentFile)) {
this.currentFile = this.loadedFiles[this.loadedFiles.length - 1] ?? 'play.html' this.currentFile = this.loadedFiles[this.loadedFiles.length - 1] ?? '游戏资源'
} }
this.renderDom() this.renderDom()
if (this.overlayEl) { if (this.overlayEl) {
@ -508,7 +660,7 @@ export class BootProgressTracker {
} }
if (this.fileEl) { if (this.fileEl) {
this.fileEl.textContent = this.currentFile this.fileEl.textContent = this.currentFile
this.fileEl.setAttribute('title', this.currentFilePath || this.currentFile) this.fileEl.setAttribute('title', this.currentFile)
} }
if (this.countEl) { if (this.countEl) {
this.countEl.textContent = `(${this.loadedCount}/${totalDisplay})` this.countEl.textContent = `(${this.loadedCount}/${totalDisplay})`
@ -530,12 +682,13 @@ export class BootProgressTracker {
return { return {
percent: this.percent, percent: this.percent,
currentFile: this.currentFile, currentFile: this.currentFile,
currentResourceType: this.currentFile,
currentFilePath: this.currentFilePath, currentFilePath: this.currentFilePath,
loadedCount: this.loadedCount, loadedCount: this.loadedCount,
totalCount: Math.max(this.loadedCount, this.totalDiscovered), totalCount: Math.max(this.loadedCount, this.totalDiscovered),
inFlightCount: this.inFlight.size, inFlightCount: this.inFlight.size,
status: this.status, status: this.status,
loadedFiles: [...this.loadedFiles], loadedFiles: Array.from(this.loadedFiles),
} }
} }

View File

@ -4,6 +4,7 @@ import { afterEach, describe, expect, it } from 'vitest'
import { import {
BootProgressTracker, BootProgressTracker,
extractAssetFileName, extractAssetFileName,
extractAssetResourceType,
normalizeAssetPath, normalizeAssetPath,
type BootProgressSnapshot, type BootProgressSnapshot,
} from '../src/client/ui/boot-progress.ts' } from '../src/client/ui/boot-progress.ts'
@ -124,6 +125,32 @@ describe('Client Boot Resource Download Progress Bar (boot-progress)', () => {
expect(extractAssetFileName('')).toBe('') expect(extractAssetFileName('')).toBe('')
}) })
it('extractAssetResourceType classifies asset paths into semantic resource types instead of raw filenames', () => {
expect(extractAssetResourceType('/ui/fonts/metrics.json')).toBe('字体资源')
expect(extractAssetResourceType('/ui/frontend/fonts/fontformal12-c4.png')).toBe('字体资源')
expect(extractAssetResourceType('/act-packs/tiles/drlg-bundle.bin')).toBe('地图地形资源')
expect(extractAssetResourceType('data\\global\\tiles\\act1\\town\\town.dt1')).toBe('地图地形资源')
expect(extractAssetResourceType('http://127.0.0.1:5173/ui/frontend/bg-main-menu.png#top')).toBe(
'主菜单与大厅界面',
)
expect(extractAssetResourceType('/ui/ctrlpnl-800.png')).toBe('游戏界面资源')
expect(extractAssetResourceType('samples/d2-packs/items/items-atlas.png?v=123')).toBe(
'物品图标资源',
)
expect(extractAssetResourceType('/skills/icon_36.png')).toBe('技能图标资源')
expect(extractAssetResourceType('/assets/ui/automap/MaxiMap.png')).toBe('小地图资源')
expect(extractAssetResourceType('samples/d2-packs/entities/char-so.r8')).toBe('角色美术资源')
expect(extractAssetResourceType('samples/d2-packs/entities/monster-fallen1-hth.r8')).toBe(
'怪物美术资源',
)
expect(extractAssetResourceType('/missiles/firebolt.png')).toBe('投射物特效资源')
expect(extractAssetResourceType('/overlays/AuraDefianceBack.png')).toBe('光环与状态特效')
expect(extractAssetResourceType('./asset-manifest.json')).toBe('资源配置数据')
expect(extractAssetResourceType('data:image/png;base64,iVBORw0KGgo=')).toBe('')
expect(extractAssetResourceType('blob:http://127.0.0.1:5173/abc-123')).toBe('')
expect(extractAssetResourceType('')).toBe('')
})
it('normalizeAssetPath strips origin, query, and hash while preserving path', () => { it('normalizeAssetPath strips origin, query, and hash while preserving path', () => {
expect(normalizeAssetPath('http://127.0.0.1:5173/ui/frontend/bg-main-menu.png?t=1#a')).toBe( expect(normalizeAssetPath('http://127.0.0.1:5173/ui/frontend/bg-main-menu.png?t=1#a')).toBe(
'/ui/frontend/bg-main-menu.png', '/ui/frontend/bg-main-menu.png',
@ -131,7 +158,7 @@ describe('Client Boot Resource Download Progress Bar (boot-progress)', () => {
expect(normalizeAssetPath('/act-packs/tiles/index.json')).toBe('/act-packs/tiles/index.json') expect(normalizeAssetPath('/act-packs/tiles/index.json')).toBe('/act-packs/tiles/index.json')
}) })
it('tracks downloading filenames, monotonic percentage (0% -> 100%), and updates DOM elements', () => { it('tracks downloading resource types (instead of raw filenames), monotonic percentage (0% -> 100%), and updates DOM elements', () => {
const dom = createMockDomEnvironment() const dom = createMockDomEnvironment()
cleanupDom = dom.cleanup cleanupDom = dom.cleanup
@ -151,19 +178,21 @@ describe('Client Boot Resource Download Progress Bar (boot-progress)', () => {
expect(overlayEl.hidden).toBe(false) expect(overlayEl.hidden).toBe(false)
expect(percentEl.textContent).toBe('0%') expect(percentEl.textContent).toBe('0%')
// Start downloading file 1 & file 2 concurrently // Start downloading file 1 & file 2 concurrently -> displays resource types instead of filenames
const id1 = tracker.beginFile('/ui/fonts/metrics.json') const id1 = tracker.beginFile('/ui/fonts/metrics.json')
expect(tracker.getSnapshot().currentFile).toBe('metrics.json') expect(tracker.getSnapshot().currentFile).toBe('字体资源')
expect(fileEl.textContent).toBe('metrics.json') expect(tracker.getSnapshot().currentResourceType).toBe('字体资源')
expect(fileEl.textContent).toBe('字体资源')
expect(fileEl.getAttribute('title')).toBe('字体资源')
const id2 = tracker.beginFile('/act-packs/tiles/drlg-bundle.bin') const id2 = tracker.beginFile('/act-packs/tiles/drlg-bundle.bin')
expect(tracker.getSnapshot().currentFile).toBe('drlg-bundle.bin') expect(tracker.getSnapshot().currentFile).toBe('地图地形资源')
expect(fileEl.textContent).toBe('drlg-bundle.bin') expect(fileEl.textContent).toBe('地图地形资源')
// Finish file 2 while file 1 is still in flight -> currentFile falls back to in-flight file 1 // Finish file 2 while file 1 is still in flight -> currentFile falls back to in-flight file 1's resource type
tracker.endFile(id2) tracker.endFile(id2)
expect(tracker.getSnapshot().percent).toBe(25) expect(tracker.getSnapshot().percent).toBe(25)
expect(tracker.getSnapshot().currentFile).toBe('metrics.json') expect(tracker.getSnapshot().currentFile).toBe('字体资源')
expect(percentEl.textContent).toBe('25%') expect(percentEl.textContent).toBe('25%')
expect(fillEl.style.width).toBe('25%') expect(fillEl.style.width).toBe('25%')
@ -174,12 +203,12 @@ describe('Client Boot Resource Download Progress Bar (boot-progress)', () => {
// Start & finish file 3 & file 4 // Start & finish file 3 & file 4
const id3 = tracker.beginFile('/ui/frontend/bg-main-menu.png') const id3 = tracker.beginFile('/ui/frontend/bg-main-menu.png')
expect(fileEl.textContent).toBe('bg-main-menu.png') expect(fileEl.textContent).toBe('主菜单与大厅界面')
tracker.endFile(id3) tracker.endFile(id3)
expect(tracker.getSnapshot().percent).toBe(75) expect(tracker.getSnapshot().percent).toBe(75)
const id4 = tracker.beginFile('samples/d2-packs/items/items-atlas.png') const id4 = tracker.beginFile('samples/d2-packs/items/items-atlas.png')
expect(fileEl.textContent).toBe('items-atlas.png') expect(fileEl.textContent).toBe('物品图标资源')
tracker.endFile(id4) tracker.endFile(id4)
expect(tracker.getSnapshot().percent).toBe(99) expect(tracker.getSnapshot().percent).toBe(99)
@ -190,10 +219,10 @@ describe('Client Boot Resource Download Progress Bar (boot-progress)', () => {
expect(finalSnap.status).toBe('completed') expect(finalSnap.status).toBe('completed')
expect(finalSnap.loadedCount).toBe(4) expect(finalSnap.loadedCount).toBe(4)
expect(finalSnap.loadedFiles).toEqual([ expect(finalSnap.loadedFiles).toEqual([
'drlg-bundle.bin', '地图地形资源',
'metrics.json', '字体资源',
'bg-main-menu.png', '主菜单与大厅界面',
'items-atlas.png', '物品图标资源',
]) ])
expect(percentEl.textContent).toBe('100%') expect(percentEl.textContent).toBe('100%')
expect(fillEl.style.width).toBe('100%') expect(fillEl.style.width).toBe('100%')
@ -228,13 +257,13 @@ describe('Client Boot Resource Download Progress Bar (boot-progress)', () => {
expect(globalThis.fetch).not.toBe(mockFetch) expect(globalThis.fetch).not.toBe(mockFetch)
const res1 = await globalThis.fetch('/act-packs/tiles/index.json') const res1 = await globalThis.fetch('/act-packs/tiles/index.json')
expect(tracker.getSnapshot().currentFile).toBe('index.json') expect(tracker.getSnapshot().currentFile).toBe('地图地形资源')
await res1.json() await res1.json()
expect(tracker.getSnapshot().loadedCount).toBe(1) expect(tracker.getSnapshot().loadedCount).toBe(1)
expect(tracker.getSnapshot().percent).toBe(50) expect(tracker.getSnapshot().percent).toBe(50)
const res2 = await globalThis.fetch('/ui/frontend/manifest.json') const res2 = await globalThis.fetch('/ui/frontend/manifest.json')
expect(tracker.getSnapshot().currentFile).toBe('manifest.json') expect(tracker.getSnapshot().currentFile).toBe('主菜单与大厅界面')
await res2.json() await res2.json()
expect(tracker.getSnapshot().loadedCount).toBe(2) expect(tracker.getSnapshot().loadedCount).toBe(2)