feat(客户端更新): 登录页/首页更新面板显示更新包下载进度条
- 新增 shared/utils/update-progress.ts:进度事件解析/百分比钳制/文案格式化(纯函数) - 新增 shared/components/UpdateProgressBar.vue:两页共用,失败态琥珀色并停在断点 百分比、总大小未知走不确定态 - useVersionUpdate:先挂监听再发桥调用(避免丢首帧),监听 pywebview-update-progress (Python 侧 evaluate_js 推送,同 pywebview-download-progress 约定);失败保留上次进度 - 测试:tests/update-progress.test.ts(8 用例) 兼容:老客户端没有该事件源,进度条不出现,其余行为不变(优雅降级)。 注:本仓库 modules-withdraw / modules-image-video 有 3 个既存失败用例(隔离复现), 与本次改动无关;全仓库仅新增测试引用本次新增模块。
This commit is contained in:
@@ -23,6 +23,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="update-hint">{{ hint }}</div>
|
<div class="update-hint">{{ hint }}</div>
|
||||||
|
<UpdateProgressBar :progress="progress" />
|
||||||
<div v-if="hasUpdate || canDownload" style="margin-top: 4px;">
|
<div v-if="hasUpdate || canDownload" style="margin-top: 4px;">
|
||||||
<button type="button" class="btn-download-update" :disabled="updating" @click="doUpdate">
|
<button type="button" class="btn-download-update" :disabled="updating" @click="doUpdate">
|
||||||
{{ updating ? '更新中...' : '立即更新' }}
|
{{ updating ? '更新中...' : '立即更新' }}
|
||||||
@@ -61,12 +62,13 @@ import { computed, onMounted, ref } from 'vue'
|
|||||||
import { restoreLoginUser } from '@/shared/auth/ensure-auth'
|
import { restoreLoginUser } from '@/shared/auth/ensure-auth'
|
||||||
import { getCurrentUserAppColumnRaw, readCachedAppColumnPermissions, type PermissionMenuItem } from '@/shared/api/permission'
|
import { getCurrentUserAppColumnRaw, readCachedAppColumnPermissions, type PermissionMenuItem } from '@/shared/api/permission'
|
||||||
import { useVersionUpdate } from '@/shared/composables/useVersionUpdate'
|
import { useVersionUpdate } from '@/shared/composables/useVersionUpdate'
|
||||||
|
import UpdateProgressBar from '@/shared/components/UpdateProgressBar.vue'
|
||||||
import { resolvePageHref } from '@/shared/page-prefix'
|
import { resolvePageHref } from '@/shared/page-prefix'
|
||||||
|
|
||||||
const username = ref('')
|
const username = ref('')
|
||||||
const updatePanel = ref(false)
|
const updatePanel = ref(false)
|
||||||
const toastText = ref('')
|
const toastText = ref('')
|
||||||
const { checking, updating, currentVersion, hasUpdate, canDownload, hint, checked, runCheck, doUpdate } =
|
const { checking, updating, currentVersion, hasUpdate, canDownload, hint, checked, progress, runCheck, doUpdate } =
|
||||||
useVersionUpdate()
|
useVersionUpdate()
|
||||||
|
|
||||||
// 桌面端原 Flask /logout 已随瘦身下线:统一跳登录页并清本地 token(/login?logout=1)
|
// 桌面端原 Flask /logout 已随瘦身下线:统一跳登录页并清本地 token(/login?logout=1)
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="update-hint">{{ hint }}</div>
|
<div class="update-hint">{{ hint }}</div>
|
||||||
|
<UpdateProgressBar :progress="progress" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,6 +99,7 @@ import { onMounted, ref, watch } from 'vue'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { loginWithDevice } from '@/shared/api/user'
|
import { loginWithDevice } from '@/shared/api/user'
|
||||||
import { useVersionUpdate } from '@/shared/composables/useVersionUpdate'
|
import { useVersionUpdate } from '@/shared/composables/useVersionUpdate'
|
||||||
|
import UpdateProgressBar from '@/shared/components/UpdateProgressBar.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -127,6 +129,7 @@ const {
|
|||||||
canDownload,
|
canDownload,
|
||||||
hint,
|
hint,
|
||||||
checked,
|
checked,
|
||||||
|
progress,
|
||||||
runCheck,
|
runCheck,
|
||||||
doUpdate,
|
doUpdate,
|
||||||
} = useVersionUpdate()
|
} = useVersionUpdate()
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="progress"
|
||||||
|
class="update-progress"
|
||||||
|
:class="{ 'is-error': progress.status === 'failed', 'is-indeterminate': percent < 0 }"
|
||||||
|
>
|
||||||
|
<div class="update-progress-track">
|
||||||
|
<div
|
||||||
|
class="update-progress-fill"
|
||||||
|
:style="percent >= 0 ? { width: percent + '%' } : undefined"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div class="update-progress-text">{{ text }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* 客户端更新包下载进度条(登录页 / 首页更新面板共用)。
|
||||||
|
*
|
||||||
|
* 数据来自 Python 侧 do_update_app 推的 pywebview-update-progress 事件,
|
||||||
|
* 经 useVersionUpdate 的 progress 传入;总大小未知时走不确定态(滑动条纹),
|
||||||
|
* 失败时进度条停在断点处并用琥珀色区分"可重试续传"。
|
||||||
|
*/
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import {
|
||||||
|
formatUpdateProgressText,
|
||||||
|
updateProgressPercent,
|
||||||
|
type UpdateProgress,
|
||||||
|
} from '@/shared/utils/update-progress'
|
||||||
|
|
||||||
|
const props = defineProps<{ progress: UpdateProgress | null }>()
|
||||||
|
|
||||||
|
const percent = computed(() => updateProgressPercent(props.progress))
|
||||||
|
const text = computed(() => formatUpdateProgressText(props.progress))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.update-progress {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-progress-track {
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #e6e6e6;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
width: 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #28a745;
|
||||||
|
transition: width 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 总大小未知:用滑动条纹表示"在下载但算不出百分比" */
|
||||||
|
.update-progress.is-indeterminate .update-progress-fill {
|
||||||
|
width: 40%;
|
||||||
|
animation: update-progress-slide 1.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 失败:进度条停在断点处,用琥珀色区分"已暂停、可重试续传" */
|
||||||
|
.update-progress.is-error .update-progress-fill {
|
||||||
|
background: #d48806;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-progress-text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 4px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes update-progress-slide {
|
||||||
|
0% { transform: translateX(-100%); }
|
||||||
|
100% { transform: translateX(250%); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,8 +6,31 @@
|
|||||||
* 提供);旧客户端无此桥时降级:本机版本为空 → 只要线上有包就允许手动更新。
|
* 提供);旧客户端无此桥时降级:本机版本为空 → 只要线上有包就允许手动更新。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { getPywebviewApi } from '@/shared/bridges/pywebview'
|
import { getPywebviewApi } from '@/shared/bridges/pywebview'
|
||||||
|
import {
|
||||||
|
normalizeUpdateProgress,
|
||||||
|
type UpdateProgress,
|
||||||
|
} from '@/shared/utils/update-progress'
|
||||||
|
|
||||||
|
export type { UpdateProgress, UpdateProgressStatus } from '@/shared/utils/update-progress'
|
||||||
|
|
||||||
|
const updateProgress = ref<UpdateProgress | null>(null)
|
||||||
|
let progressListenerBound = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定一次全局监听:下载进度由 Python 侧主动推事件(同 pywebview-download-progress 约定),
|
||||||
|
* JS 侧不轮询——桥调用虽然并发安全,但轮询会给主进程平白加请求。
|
||||||
|
*/
|
||||||
|
function bindProgressListener() {
|
||||||
|
if (progressListenerBound || typeof window === 'undefined') return
|
||||||
|
progressListenerBound = true
|
||||||
|
window.addEventListener('pywebview-update-progress', (event) => {
|
||||||
|
const progress = normalizeUpdateProgress((event as CustomEvent<unknown>).detail)
|
||||||
|
if (!progress) return
|
||||||
|
updateProgress.value = progress
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function normVersion(value: unknown): string {
|
function normVersion(value: unknown): string {
|
||||||
return String(value ?? '').trim().replace(/^[vV]/, '')
|
return String(value ?? '').trim().replace(/^[vV]/, '')
|
||||||
@@ -124,19 +147,57 @@ export function useVersionUpdate() {
|
|||||||
updating.value = false
|
updating.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 先挂监听再发桥调用:进度事件在下载开始后立刻开推,晚绑会丢最前面的进度
|
||||||
|
bindProgressListener()
|
||||||
|
updateProgress.value = {
|
||||||
|
status: 'downloading',
|
||||||
|
message: '正在准备下载更新包...',
|
||||||
|
downloaded: 0,
|
||||||
|
total: 0,
|
||||||
|
percent: -1,
|
||||||
|
}
|
||||||
hint.value = '正在下载并准备更新,程序将自动退出...'
|
hint.value = '正在下载并准备更新,程序将自动退出...'
|
||||||
try {
|
try {
|
||||||
const result = await doUpdateApp!(fileUrl.value)
|
const result = await doUpdateApp!(fileUrl.value)
|
||||||
hint.value = result?.success
|
if (result?.success) {
|
||||||
? '更新已启动,程序即将退出...'
|
const last = updateProgress.value
|
||||||
: (result?.error || '更新启动失败,请重试')
|
updateProgress.value = {
|
||||||
|
status: 'ready',
|
||||||
|
message: '安装包已就绪,正在启动更新程序,程序即将退出...',
|
||||||
|
downloaded: last?.downloaded || 0,
|
||||||
|
total: last?.total || 0,
|
||||||
|
percent: 100,
|
||||||
|
}
|
||||||
|
hint.value = '更新已启动,程序即将退出...'
|
||||||
|
} else {
|
||||||
|
// 失败:进度条停在断点处,重试将从断点继续(Python 侧保留断点文件)
|
||||||
|
const message = result?.error || '更新启动失败,请重试'
|
||||||
|
updateProgress.value = {
|
||||||
|
status: 'failed',
|
||||||
|
message,
|
||||||
|
downloaded: updateProgress.value?.downloaded || 0,
|
||||||
|
total: updateProgress.value?.total || 0,
|
||||||
|
percent: updateProgress.value?.percent ?? -1,
|
||||||
|
}
|
||||||
|
hint.value = message
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
updateProgress.value = {
|
||||||
|
status: 'failed',
|
||||||
|
message: '请求更新失败,请重试',
|
||||||
|
downloaded: updateProgress.value?.downloaded || 0,
|
||||||
|
total: updateProgress.value?.total || 0,
|
||||||
|
percent: updateProgress.value?.percent ?? -1,
|
||||||
|
}
|
||||||
hint.value = '请求更新失败,请重试'
|
hint.value = '请求更新失败,请重试'
|
||||||
} finally {
|
} finally {
|
||||||
updating.value = false
|
updating.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 进度条的百分比/文案由 UpdateProgressBar 组件从 progress 自行派生(见 shared/utils/update-progress)
|
||||||
|
const progress = computed(() => updateProgress.value)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
checking,
|
checking,
|
||||||
updating,
|
updating,
|
||||||
@@ -147,6 +208,7 @@ export function useVersionUpdate() {
|
|||||||
canDownload,
|
canDownload,
|
||||||
hint,
|
hint,
|
||||||
checked,
|
checked,
|
||||||
|
progress,
|
||||||
loadCurrentVersion,
|
loadCurrentVersion,
|
||||||
runCheck,
|
runCheck,
|
||||||
doUpdate,
|
doUpdate,
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* 桌面端「更新包下载进度」的解析与文案(纯函数,便于 node --test 直接覆盖)。
|
||||||
|
*
|
||||||
|
* 数据源:Python 侧 do_update_app 通过 evaluate_js 推 pywebview-update-progress 事件
|
||||||
|
* (与 pywebview-download-progress 同一约定),detail 结构见下:
|
||||||
|
* { status, message, downloaded, total, percent, error }
|
||||||
|
* percent = -1 表示总大小未知(服务端未返回 Content-Length),进度条走不确定态。
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type UpdateProgressStatus = 'downloading' | 'verifying' | 'ready' | 'failed'
|
||||||
|
|
||||||
|
export type UpdateProgress = {
|
||||||
|
status: UpdateProgressStatus
|
||||||
|
message: string
|
||||||
|
downloaded: number
|
||||||
|
total: number
|
||||||
|
/** -1 表示总大小未知,进度条走不确定态 */
|
||||||
|
percent: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateProgressDetail = {
|
||||||
|
status?: string
|
||||||
|
message?: string
|
||||||
|
downloaded?: number
|
||||||
|
total?: number
|
||||||
|
percent?: number
|
||||||
|
error?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const KNOWN_STATUS: readonly string[] = ['downloading', 'verifying', 'ready', 'failed']
|
||||||
|
|
||||||
|
export function isUpdateProgressStatus(value: unknown): value is UpdateProgressStatus {
|
||||||
|
return typeof value === 'string' && KNOWN_STATUS.includes(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toByteCount(value: unknown): number {
|
||||||
|
const num = Number(value)
|
||||||
|
return Number.isFinite(num) && num > 0 ? num : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析进度事件 detail;状态缺失/非法返回 null,
|
||||||
|
* 让调用方保持原状态而不是被脏事件打回初始态。
|
||||||
|
*/
|
||||||
|
export function normalizeUpdateProgress(detail: unknown): UpdateProgress | null {
|
||||||
|
if (!detail || typeof detail !== 'object') return null
|
||||||
|
const raw = detail as UpdateProgressDetail
|
||||||
|
if (!isUpdateProgressStatus(raw.status)) return null
|
||||||
|
const percent = Number(raw.percent)
|
||||||
|
return {
|
||||||
|
status: raw.status,
|
||||||
|
message: String(raw.message ?? ''),
|
||||||
|
downloaded: toByteCount(raw.downloaded),
|
||||||
|
total: toByteCount(raw.total),
|
||||||
|
percent: Number.isFinite(percent) ? percent : -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 进度条宽度百分比(0-100 取整);总大小未知或非下载态返回 -1(不确定态) */
|
||||||
|
export function updateProgressPercent(progress: UpdateProgress | null): number {
|
||||||
|
if (!progress || !Number.isFinite(progress.percent) || progress.percent < 0) return -1
|
||||||
|
return Math.max(0, Math.min(100, Math.round(progress.percent)))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatBytes(value: number): string {
|
||||||
|
if (!Number.isFinite(value) || value <= 0) return '0 MB'
|
||||||
|
if (value < 1024 * 1024) return `${(value / 1024).toFixed(0)} KB`
|
||||||
|
return `${(value / 1024 / 1024).toFixed(1)} MB`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进度条文案:下载中给"百分比 + 已下载/总大小",
|
||||||
|
* 其余状态(校验中/已就绪/失败)直接用 Python 侧的中文说明。
|
||||||
|
*/
|
||||||
|
export function formatUpdateProgressText(progress: UpdateProgress | null): string {
|
||||||
|
if (!progress) return ''
|
||||||
|
if (progress.status !== 'downloading') return progress.message
|
||||||
|
if (progress.percent < 0) {
|
||||||
|
// 进度帧还没到(刚开始)或总大小未知:能算出已下载量就显示,否则退回阶段文案
|
||||||
|
return progress.downloaded > 0
|
||||||
|
? `正在下载更新包(已下载 ${formatBytes(progress.downloaded)})`
|
||||||
|
: (progress.message || '正在下载更新包...')
|
||||||
|
}
|
||||||
|
const size = progress.total > 0
|
||||||
|
? `(${formatBytes(progress.downloaded)}/${formatBytes(progress.total)})`
|
||||||
|
: ''
|
||||||
|
return `正在下载更新包 ${Math.round(progress.percent)}%${size}`
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import { test } from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
|
||||||
|
import {
|
||||||
|
formatBytes,
|
||||||
|
formatUpdateProgressText,
|
||||||
|
normalizeUpdateProgress,
|
||||||
|
updateProgressPercent,
|
||||||
|
type UpdateProgress,
|
||||||
|
} from '../src/shared/utils/update-progress.ts'
|
||||||
|
|
||||||
|
function progressOf(partial: Partial<UpdateProgress>): UpdateProgress {
|
||||||
|
return {
|
||||||
|
status: 'downloading',
|
||||||
|
message: '',
|
||||||
|
downloaded: 0,
|
||||||
|
total: 0,
|
||||||
|
percent: -1,
|
||||||
|
...partial,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('更新进度:正常进度帧解析出字节数与百分比', () => {
|
||||||
|
const progress = normalizeUpdateProgress({
|
||||||
|
status: 'downloading',
|
||||||
|
message: '正在下载更新包...',
|
||||||
|
downloaded: 62914560,
|
||||||
|
total: 104857600,
|
||||||
|
percent: 60,
|
||||||
|
error: '',
|
||||||
|
})
|
||||||
|
assert.ok(progress, '合法事件必须解析成功')
|
||||||
|
assert.equal(progress.status, 'downloading')
|
||||||
|
assert.equal(progress.downloaded, 62914560)
|
||||||
|
assert.equal(progress.total, 104857600)
|
||||||
|
assert.equal(progress.percent, 60)
|
||||||
|
assert.equal(updateProgressPercent(progress), 60)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('更新进度:状态缺失或非法的事件必须丢弃(不得把进度打回初始态)', () => {
|
||||||
|
assert.equal(normalizeUpdateProgress(null), null)
|
||||||
|
assert.equal(normalizeUpdateProgress(undefined), null)
|
||||||
|
assert.equal(normalizeUpdateProgress({}), null)
|
||||||
|
assert.equal(normalizeUpdateProgress({ downloaded: 100 }), null)
|
||||||
|
assert.equal(normalizeUpdateProgress({ status: 'unknown-stage' }), null)
|
||||||
|
assert.equal(normalizeUpdateProgress('downloading'), null)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('更新进度:缺字段的脏事件按 0 字节/未知总大小兜底,不产生 NaN', () => {
|
||||||
|
const progress = normalizeUpdateProgress({ status: 'failed', message: '下载失败' })
|
||||||
|
assert.ok(progress)
|
||||||
|
assert.equal(progress.downloaded, 0)
|
||||||
|
assert.equal(progress.total, 0)
|
||||||
|
assert.equal(progress.percent, -1, '缺百分比按未知处理')
|
||||||
|
assert.equal(updateProgressPercent(progress), -1, '未知总大小走不确定态')
|
||||||
|
assert.equal(Number.isNaN(progress.downloaded), false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('更新进度:百分比越界钳制到 0-100,负值视为不确定态', () => {
|
||||||
|
assert.equal(updateProgressPercent(progressOf({ percent: 120 })), 100)
|
||||||
|
assert.equal(updateProgressPercent(progressOf({ percent: -5 })), -1)
|
||||||
|
assert.equal(updateProgressPercent(progressOf({ percent: 99.6 })), 100, '四舍五入取整')
|
||||||
|
assert.equal(updateProgressPercent(null), -1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('更新进度文案:下载中显示百分比与已下载/总大小', () => {
|
||||||
|
const text = formatUpdateProgressText(progressOf({
|
||||||
|
downloaded: 62914560,
|
||||||
|
total: 104857600,
|
||||||
|
percent: 60,
|
||||||
|
}))
|
||||||
|
assert.equal(text, '正在下载更新包 60%(60.0 MB/100.0 MB)')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('更新进度文案:总大小未知时显示已下载量,刚开始时退回阶段文案', () => {
|
||||||
|
assert.equal(
|
||||||
|
formatUpdateProgressText(progressOf({ downloaded: 3145728, percent: -1 })),
|
||||||
|
'正在下载更新包(已下载 3.0 MB)',
|
||||||
|
)
|
||||||
|
assert.equal(
|
||||||
|
formatUpdateProgressText(progressOf({ message: '正在准备下载更新包...' })),
|
||||||
|
'正在准备下载更新包...',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('更新进度文案:校验中/已就绪/失败直接用 Python 侧说明(失败停在断点处)', () => {
|
||||||
|
assert.equal(
|
||||||
|
formatUpdateProgressText(progressOf({
|
||||||
|
status: 'verifying',
|
||||||
|
message: '安装包下载完成,正在校验完整性...',
|
||||||
|
})),
|
||||||
|
'安装包下载完成,正在校验完整性...',
|
||||||
|
)
|
||||||
|
assert.equal(
|
||||||
|
formatUpdateProgressText(progressOf({
|
||||||
|
status: 'ready',
|
||||||
|
message: '安装包已就绪,正在启动更新程序,程序即将退出...',
|
||||||
|
percent: 100,
|
||||||
|
})),
|
||||||
|
'安装包已就绪,正在启动更新程序,程序即将退出...',
|
||||||
|
)
|
||||||
|
const failed = progressOf({
|
||||||
|
status: 'failed',
|
||||||
|
message: '下载中断(65536/307510 字节),断点已保留,请重试续传',
|
||||||
|
downloaded: 65536,
|
||||||
|
total: 307510,
|
||||||
|
percent: 21.3,
|
||||||
|
})
|
||||||
|
assert.equal(formatUpdateProgressText(failed), '下载中断(65536/307510 字节),断点已保留,请重试续传')
|
||||||
|
assert.equal(updateProgressPercent(failed), 21, '失败时进度条停在断点百分比')
|
||||||
|
assert.equal(formatUpdateProgressText(null), '')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('更新进度文案:字节数格式化覆盖 KB/MB 与异常输入', () => {
|
||||||
|
assert.equal(formatBytes(0), '0 MB')
|
||||||
|
assert.equal(formatBytes(-1), '0 MB')
|
||||||
|
assert.equal(formatBytes(Number.NaN), '0 MB')
|
||||||
|
assert.equal(formatBytes(512 * 1024), '512 KB')
|
||||||
|
assert.equal(formatBytes(3 * 1024 * 1024), '3.0 MB')
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user