dsh-wisp 1.26.0 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/assets/canon/eat.webp +0 -0
- package/assets/classic/eat.webp +0 -0
- package/assets/deepsea/eat.webp +0 -0
- package/assets/night/eat.webp +0 -0
- package/assets/pajama/eat.webp +0 -0
- package/build.mjs +1 -1
- package/lib/client.js +52 -3
- package/lib/client.template.js +47 -3
- package/package.json +1 -1
- package/tools/assets.mjs +1 -1
- package/tools/smoke.mjs +3 -3
- package/verify-wisp.mjs +129 -6
package/lib/client.template.js
CHANGED
|
@@ -51,7 +51,7 @@ window.__ModuleLoader__.load({
|
|
|
51
51
|
var exports = module.exports
|
|
52
52
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
53
53
|
|
|
54
|
-
const VERSION = '1.
|
|
54
|
+
const VERSION = '1.27.0'
|
|
55
55
|
|
|
56
56
|
/* 每一版一句话。**必须写当前版本** —— 预检里有一条断言盯着它,
|
|
57
57
|
忘了更新就会红,不会静默过期。 */
|
|
@@ -67,6 +67,7 @@ const WHATS_NEW = {
|
|
|
67
67
|
'1.25.0': '她真的会去查有没有新版本了。',
|
|
68
68
|
'1.25.1': '内容与 1.25.0 相同,重新发布以修好安装源。',
|
|
69
69
|
'1.26.0': '检查更新同时读 npm 和 GitHub,取更高的那个版本。',
|
|
70
|
+
'1.27.0': '她饿了会捧出一碗白米饭,还有第八个表情。',
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
/* 更新这件事**这个插件自己做不到**,如实写在代码里,免得以后有人以为能:
|
|
@@ -160,6 +161,8 @@ const UPDATE_HINT = '在 DSH 的插件列表里按包名安装:dsh-wisp'
|
|
|
160
161
|
celebrateAfterMs: 2500, // ...but only for runs that lasted this long
|
|
161
162
|
chatterMs: 0, // 0 = she stays quiet when idle
|
|
162
163
|
careAfterMs: 5400000, // 连续活动这么久之后提醒你起来动动(90 分钟;0 = 关)
|
|
164
|
+
hungry: true, // 干饭:她饿了会说
|
|
165
|
+
hungerMs: 2700000, // 连续活动这么久就该吃一碗了(45 分钟;0 = 关)
|
|
163
166
|
milestones: true, // 今日轮次到达里程碑时得意一下
|
|
164
167
|
memory: true, // 她记着你:一起多少天 / 常放哪个角 / 常穿哪套
|
|
165
168
|
happyMs: 1100,
|
|
@@ -255,6 +258,14 @@ const UPDATE_HINT = '在 DSH 的插件列表里按包名安装:dsh-wisp'
|
|
|
255
258
|
'你最近常让我穿{s}。', '{s} —— 你好像挺喜欢这套。', '又是{s},我都快忘了别的了。',
|
|
256
259
|
],
|
|
257
260
|
/* 检查更新用的三句。前两句带结果,第三句是"查不了/没查成"。 */
|
|
261
|
+
/* 干饭。社区给她的设定里,白米饭是算力的唯一硬通货 —— 这是她最像"DeepSeek娘"
|
|
262
|
+
而不是随便一只桌宠的地方。台词照这个底色写。 */
|
|
263
|
+
eat: [
|
|
264
|
+
'白米饭才是硬通货。', '先干一碗,再想复杂的事。', '冰箱里能吃的?那整台冰箱都是我的便当盒。',
|
|
265
|
+
'才不是特意帮你,只是吃饱了顺手活动一下手指。', '比起深度推理,先来一碗香喷喷的白米饭。',
|
|
266
|
+
'算力不够了,得补点碳水。', '电量告急,切待机。', '你要不要也来一口?',
|
|
267
|
+
'这碗不算加餐,算基础设施。', '吃饱了才有力气摸鱼。', '饭碗见底了,该续命了。', '我数着米粒呢。',
|
|
268
|
+
],
|
|
258
269
|
updateCurrent: ['已经是最新的了。', '我就是最新的。', '没有更新的。'],
|
|
259
270
|
updateAvailable: ['有新版本了:', '有新版:', '更新来了:'],
|
|
260
271
|
updateFailed: ['更新没查成。', '这次没查到更新。', '更新检查没成功。'],
|
|
@@ -323,6 +334,8 @@ const UPDATE_HINT = '在 DSH 的插件列表里按包名安装:dsh-wisp'
|
|
|
323
334
|
celebrateAfterMs: { kind: 'num', lo: 0, hi: 600000 },
|
|
324
335
|
chatterMs: { kind: 'num', lo: 0, hi: 3600000 },
|
|
325
336
|
careAfterMs: { kind: 'num', lo: 0, hi: 86400000 },
|
|
337
|
+
hungry: { kind: 'bool' },
|
|
338
|
+
hungerMs: { kind: 'num', lo: 0, hi: 86400000 },
|
|
326
339
|
milestones: { kind: 'bool' },
|
|
327
340
|
memory: { kind: 'bool' },
|
|
328
341
|
happyMs: { kind: 'num', lo: 200, hi: 10000 },
|
|
@@ -1081,7 +1094,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1081
1094
|
/* ---- mood -> sprite ----
|
|
1082
1095
|
attn 曾借用 work 那张,现在有自己的一张(抬手招你过来的姿态)。
|
|
1083
1096
|
仍然保留回退:某一套皮肤将来若没有 attn,她会退回 work,而不是变成空白。 */
|
|
1084
|
-
const SPRITE_OF = { idle: 'idle', alert: 'work', happy: 'happy', sleep: 'sleepy', attn: 'attn', care: 'attn', poked: 'poked', proud: 'proud', worried: 'sleepy' }
|
|
1097
|
+
const SPRITE_OF = { idle: 'idle', alert: 'work', happy: 'happy', sleep: 'sleepy', attn: 'attn', care: 'attn', poked: 'poked', proud: 'proud', worried: 'sleepy', eat: 'eat' }
|
|
1085
1098
|
const SPRITE_FALLBACK = { attn: 'work' }
|
|
1086
1099
|
let mood = 'idle'
|
|
1087
1100
|
|
|
@@ -1295,6 +1308,8 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1295
1308
|
let activeSince = 0
|
|
1296
1309
|
let lastActiveAt = 0
|
|
1297
1310
|
let lastCareAt = 0
|
|
1311
|
+
/* 上一顿饭的时间。起手是 0,所以计时以 activeSince 为准 —— 见下面 mealBase。 */
|
|
1312
|
+
let lastMealAt = 0
|
|
1298
1313
|
let happyHandle = null
|
|
1299
1314
|
let lastErrorCount = 0
|
|
1300
1315
|
|
|
@@ -1334,6 +1349,18 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1334
1349
|
pokedHandle = clock.after(POKED_MS, () => { if (mood === 'poked') setMood('idle') })
|
|
1335
1350
|
}
|
|
1336
1351
|
|
|
1352
|
+
/* 干饭用的也是瞬时状态(借 eat 的姿态)。比"提醒你活动"长一点 ——
|
|
1353
|
+
吃饭本来就慢。 */
|
|
1354
|
+
const EAT_MS = 5200
|
|
1355
|
+
let eatHandle = null
|
|
1356
|
+
const flashEat = (text) => {
|
|
1357
|
+
if (mood === 'sleep') wake(true)
|
|
1358
|
+
setMood('eat')
|
|
1359
|
+
say(text)
|
|
1360
|
+
if (eatHandle) clock.cancel(eatHandle)
|
|
1361
|
+
eatHandle = clock.after(EAT_MS, () => { if (mood === 'eat') setMood('idle') })
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1337
1364
|
/* 久坐提醒用的也是一个**瞬时状态**(借 attn 的姿态),理由和 poked 一样:
|
|
1338
1365
|
稳态逻辑每轮都会重算,不给它自己的定时器就会被抹掉。 */
|
|
1339
1366
|
const PROUD_MS = 2800
|
|
@@ -1410,6 +1437,19 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1410
1437
|
flashCare(pick(LINES.care))
|
|
1411
1438
|
}
|
|
1412
1439
|
|
|
1440
|
+
/* ---- 干饭 -------------------------------------------------------------
|
|
1441
|
+
和久坐共用同一套"还在动"的判定,但含义相反:久坐是提醒你起来,
|
|
1442
|
+
干饭是提醒她自己该补碳了。计时基准取 lastMealAt 与 activeSince 里更晚的那个 ——
|
|
1443
|
+
离开很久回来之后,不该立刻就说饿(那一段已经结束了)。 */
|
|
1444
|
+
const mealBase = Math.max(lastMealAt, activeSince)
|
|
1445
|
+
if (
|
|
1446
|
+
cfg.hungry && cfg.hungerMs > 0 && activeSince !== 0
|
|
1447
|
+
&& nowTick - mealBase >= cfg.hungerMs
|
|
1448
|
+
) {
|
|
1449
|
+
lastMealAt = nowTick
|
|
1450
|
+
flashEat(pick(LINES.eat))
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1413
1453
|
// 有东西在等她处理时,优先级高于一切:这是唯一"你不理它就卡住"的状态。
|
|
1414
1454
|
const pending = cfg.reactions ? readPending() : null
|
|
1415
1455
|
if (pending === null) {
|
|
@@ -1426,7 +1466,8 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1426
1466
|
|
|
1427
1467
|
// 临时状态(happy / worried)在自己的计时器到期前不能被稳态赋值覆盖 ——
|
|
1428
1468
|
// 否则刚摆出的表情在同一个 poll 周期里就被 setMood('idle') 抹掉了。
|
|
1429
|
-
if (mood === 'sleep' || mood === 'happy' || mood === 'poked' || mood === 'proud' || mood === 'care'
|
|
1469
|
+
if (mood === 'sleep' || mood === 'happy' || mood === 'poked' || mood === 'proud' || mood === 'care'
|
|
1470
|
+
|| mood === 'eat' || mood === 'worried') return
|
|
1430
1471
|
setMood((busy || typing) ? 'alert' : 'idle')
|
|
1431
1472
|
}
|
|
1432
1473
|
|
|
@@ -1721,6 +1762,7 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
1721
1762
|
{ key: 'wander', label: '自己溜达' },
|
|
1722
1763
|
{ key: 'reactions', label: '跟随状态' },
|
|
1723
1764
|
{ key: 'celebrate', label: '跑完撒花' },
|
|
1765
|
+
{ key: 'hungry', label: '饿了会说话' },
|
|
1724
1766
|
]
|
|
1725
1767
|
|
|
1726
1768
|
const menuItems = () => {
|
|
@@ -2198,6 +2240,8 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2198
2240
|
lastCheck: { ...updateCheck },
|
|
2199
2241
|
why: '宿主半包在 vm 沙箱里,harness 只有 defineTool/registerTool/handle,Node API 被 nodeApiTraps 挡住',
|
|
2200
2242
|
},
|
|
2243
|
+
/* 干饭:不用猜"她为什么突然捧碗" */
|
|
2244
|
+
hunger: { enabled: cfg.hungry === true, everyMs: cfg.hungerMs, lastMealAt: Math.round(lastMealAt) },
|
|
2201
2245
|
/* 今日轮次:不用猜"她为什么突然得意",数字就在这里 */
|
|
2202
2246
|
runsToday: { day: runsToday.day, count: runsToday.count, said: runsToday.said.slice() },
|
|
2203
2247
|
/* 她记着你:一起多少天、习惯放在哪、常穿哪套、已经说过哪些 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-wisp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"description": "DeepSeek娘 — an unofficial floating desktop companion for the DeepSeek Harness UI. Zero dependencies; sprites inlined as data URIs; schedules on the Client timer service instead of the trapped browser timer globals.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
package/tools/assets.mjs
CHANGED
|
@@ -45,7 +45,7 @@ const flag = (name, fallback) => {
|
|
|
45
45
|
const at = argv.indexOf(name)
|
|
46
46
|
return at >= 0 && argv[at + 1] !== undefined ? argv[at + 1] : fallback
|
|
47
47
|
}
|
|
48
|
-
const MOODS = ['idle', 'happy', 'sleepy', 'work', 'attn', 'poked', 'proud']
|
|
48
|
+
const MOODS = ['idle', 'happy', 'sleepy', 'work', 'attn', 'poked', 'proud', 'eat']
|
|
49
49
|
const srcDir = resolve(here, flag('--from', 'masters'))
|
|
50
50
|
/* 皮肤 = assets/ 下的一个子目录。--skin 决定输出到哪一套。
|
|
51
51
|
注意 build.mjs 会把每个子目录当成一套皮肤打进包里,所以皮肤越多包越大。 */
|
package/tools/smoke.mjs
CHANGED
|
@@ -232,15 +232,15 @@ check(cornerMenu !== null && cornerMenu.inside,
|
|
|
232
232
|
cornerMenu ? `bottom ${cornerMenu.bottom} (was ${cornerMenu.naiveBottom}), right ${cornerMenu.right}` : 'n/a')
|
|
233
233
|
|
|
234
234
|
const menu = await openMenu()
|
|
235
|
-
// 项数从皮肤数推导(
|
|
235
|
+
// 项数从皮肤数推导(14 项固定 + 每套皮肤一项),不写死:加一套皮肤不该让测试变红 ——
|
|
236
236
|
// 那是数据的错,不是行为的错。
|
|
237
237
|
// api.skins 是**数组**;用 Object.keys 数数组只是碰巧数对(预检里踩过这个坑,这里一并对齐)。
|
|
238
238
|
const skinCount = await page.evaluate(() => {
|
|
239
239
|
const s = window.__wisp.skins
|
|
240
240
|
return Array.isArray(s) ? s.length : Object.keys(s ?? {}).length
|
|
241
241
|
})
|
|
242
|
-
check(menu !== null && menu.items.length ===
|
|
243
|
-
`${menu?.items.length} items =
|
|
242
|
+
check(menu !== null && menu.items.length === 14 + skinCount, 'the menu lists every entry',
|
|
243
|
+
`${menu?.items.length} items = 14 + ${skinCount} skin(s)`)
|
|
244
244
|
|
|
245
245
|
// 键盘:打开即聚焦第一项,方向键移动,Esc 关闭并把焦点还给她
|
|
246
246
|
const kbd = await page.evaluate(() => {
|
package/verify-wisp.mjs
CHANGED
|
@@ -1350,6 +1350,128 @@ if (clientSrc !== null) {
|
|
|
1350
1350
|
care.win.__wisp.destroy()
|
|
1351
1351
|
active = keepCare
|
|
1352
1352
|
|
|
1353
|
+
/* ---------------- 3d-octodecies. 干饭 ----------------------------------- */
|
|
1354
|
+
head('3d-octodecies. she gets hungry and says so (the rice thing)')
|
|
1355
|
+
|
|
1356
|
+
/* 这是社区的设定里最像"她"的一条:白米饭是算力的硬通货。行为本身很简单 ——
|
|
1357
|
+
连续活动够久就该吃一碗 —— 但要测的是**判据**:别一回来就喊饿、别连着喊、
|
|
1358
|
+
能关掉。careAfterMs 关掉,免得住坐提醒和它互相干扰。 */
|
|
1359
|
+
const eat = createHarness({ timer: true, composerText: '在打字' })
|
|
1360
|
+
const keepEat = active
|
|
1361
|
+
active = eat
|
|
1362
|
+
eat.evaluate(clientSrc)
|
|
1363
|
+
eat.module().default.apply(eat.ctx, {
|
|
1364
|
+
reactions: true, wander: false, celebrate: false,
|
|
1365
|
+
careAfterMs: 0, hungerMs: 600000, sleepAfterMs: 3600000,
|
|
1366
|
+
})
|
|
1367
|
+
eat.advance(1300, 100)
|
|
1368
|
+
const eatApi = eat.win.__wisp
|
|
1369
|
+
const eatMood = () => eat.find('wisp-root').dataset.mood
|
|
1370
|
+
const eatUntil = (limitMs, chunk = 1000) => {
|
|
1371
|
+
for (let left = limitMs; left > 0; left -= chunk) {
|
|
1372
|
+
eat.advance(Math.min(chunk, left), 200)
|
|
1373
|
+
if (eatMood() === 'eat') return true
|
|
1374
|
+
}
|
|
1375
|
+
return false
|
|
1376
|
+
}
|
|
1377
|
+
const bubbleIsEat = () => {
|
|
1378
|
+
const text = eat.all('wisp-say').at(-1)?.textContent
|
|
1379
|
+
return Array.isArray(linesInBundle()?.eat) && linesInBundle().eat.includes(text)
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
check(Array.isArray(linesInBundle()?.eat) && linesInBundle().eat.length >= 8,
|
|
1383
|
+
'the rice pool exists and has some lines in it', `${linesInBundle()?.eat?.length ?? '?'} 句`)
|
|
1384
|
+
check(eatApi.doctor().hunger.enabled === true && eatApi.doctor().hunger.everyMs === 600000,
|
|
1385
|
+
'doctor reports the hunger setting', JSON.stringify(eatApi.doctor().hunger))
|
|
1386
|
+
|
|
1387
|
+
// 9 分钟:还没到
|
|
1388
|
+
let sawEarlyEat = false
|
|
1389
|
+
for (let left = 9 * 60000; left > 0; left -= 1000) {
|
|
1390
|
+
eat.advance(1000, 200)
|
|
1391
|
+
if (eatMood() === 'eat') { sawEarlyEat = true; break }
|
|
1392
|
+
}
|
|
1393
|
+
check(!sawEarlyEat, 'nine minutes in she is not hungry yet', eatMood())
|
|
1394
|
+
|
|
1395
|
+
check(eatUntil(200000), 'past the threshold she gets a bowl out')
|
|
1396
|
+
check(bubbleIsEat(), 'and the line comes from the rice pool',
|
|
1397
|
+
(eat.all('wisp-say').at(-1)?.textContent ?? '(无)'))
|
|
1398
|
+
check(eatMood() === 'eat' && eatApi.currentMood === 'eat',
|
|
1399
|
+
'and both the DOM and the reported mood say eat', `${eatMood()} / ${eatApi.currentMood}`)
|
|
1400
|
+
|
|
1401
|
+
// 稳态轮询不能把它抹掉
|
|
1402
|
+
eat.advance(1000, 100)
|
|
1403
|
+
check(eatMood() === 'eat', 'the reaction poll does not overwrite the meal', eatMood())
|
|
1404
|
+
|
|
1405
|
+
// 吃完自己回去(EAT_MS = 5.2 秒)
|
|
1406
|
+
for (let left = 8000; left > 0; left -= 500) eat.advance(500, 100)
|
|
1407
|
+
check(eatMood() === 'alert', 'and the meal ends on its own, back to alert', eatMood())
|
|
1408
|
+
|
|
1409
|
+
// 不该连着说
|
|
1410
|
+
let saidAgain = false
|
|
1411
|
+
for (let left = 60000; left > 0; left -= 1000) {
|
|
1412
|
+
eat.advance(1000, 200)
|
|
1413
|
+
if (eatMood() === 'eat') { saidAgain = true; break }
|
|
1414
|
+
}
|
|
1415
|
+
check(!saidAgain, 'she does not ask for rice again a minute later', eatMood())
|
|
1416
|
+
check(eatUntil(600000), 'but another full stretch does bring it back')
|
|
1417
|
+
|
|
1418
|
+
// 离开很久之后回来:不该一进门就喊饿
|
|
1419
|
+
eat.win.__wisp.configure({ hungerMs: 300000 })
|
|
1420
|
+
eat.state.composerText = null
|
|
1421
|
+
eat.advance(20 * 60000, 5000)
|
|
1422
|
+
eat.state.composerText = '回来了'
|
|
1423
|
+
eat.advance(1300, 100)
|
|
1424
|
+
let instantHunger = false
|
|
1425
|
+
for (let left = 120000; left > 0; left -= 1000) {
|
|
1426
|
+
eat.advance(1000, 200)
|
|
1427
|
+
if (eatMood() === 'eat') { instantHunger = true; break }
|
|
1428
|
+
}
|
|
1429
|
+
check(!instantHunger, 'after a long absence she does not ask for rice the moment you return', eatMood())
|
|
1430
|
+
check(eatUntil(300000), 'only a fresh full stretch makes her hungry')
|
|
1431
|
+
/* 让这一顿吃完(EAT_MS = 5.2 秒)再测关闭 —— 否则"还停在 eat"会被误判成"关不掉"。
|
|
1432
|
+
(这个坑久坐提醒那节也踩过,同样用一段推进收尾。) */
|
|
1433
|
+
eat.advance(8000, 500)
|
|
1434
|
+
|
|
1435
|
+
// 关掉这个行为(两种关法)
|
|
1436
|
+
eat.win.__wisp.configure({ hungerMs: 0 })
|
|
1437
|
+
let offByZero = false
|
|
1438
|
+
for (let left = 20 * 60000; left > 0; left -= 5000) {
|
|
1439
|
+
eat.advance(5000, 500)
|
|
1440
|
+
if (eatMood() === 'eat') { offByZero = true; break }
|
|
1441
|
+
}
|
|
1442
|
+
check(!offByZero, 'hungerMs: 0 turns it off', eatMood())
|
|
1443
|
+
eat.win.__wisp.configure({ hungerMs: 300000, hungry: false })
|
|
1444
|
+
let offByFlag = false
|
|
1445
|
+
for (let left = 20 * 60000; left > 0; left -= 5000) {
|
|
1446
|
+
eat.advance(5000, 500)
|
|
1447
|
+
if (eatMood() === 'eat') { offByFlag = true; break }
|
|
1448
|
+
}
|
|
1449
|
+
check(!offByFlag, 'and hungry: false turns it off even with a live timer', eatMood())
|
|
1450
|
+
check(eatApi.config.hungry === false, 'and the config says so', String(eatApi.config.hungry))
|
|
1451
|
+
|
|
1452
|
+
// 菜单开关:先把菜单叫出来(菜单项是开菜单时才建的),点一下就该翻转
|
|
1453
|
+
/* 主菜单的入口是 .wisp-body 上的 keydown(Enter / 空格 / ContextMenu / Shift+F10),
|
|
1454
|
+
不是 contextmenu —— 后者只挂在"躲起来"之后的迷你标签上。事件对象同样自建:
|
|
1455
|
+
ctxEvent 定义在文件后半部分,在这里用就是 TDZ。 */
|
|
1456
|
+
eat.win.__wisp.configure({ hungry: true })
|
|
1457
|
+
const eatBody = eat.find('wisp-root').querySelector('.wisp-body')
|
|
1458
|
+
eatBody.dispatch('keydown', {
|
|
1459
|
+
type: 'keydown', key: 'Enter', shiftKey: false, defaultPrevented: false,
|
|
1460
|
+
preventDefault() { this.defaultPrevented = true }, stopPropagation() {},
|
|
1461
|
+
})
|
|
1462
|
+
const eatToggle = eat.all('wisp-menu-item').filter((el) => el.removed !== true)
|
|
1463
|
+
.find((el) => (el.textContent || '').includes('饿了'))
|
|
1464
|
+
check(Boolean(eatToggle), 'the menu carries a rice toggle',
|
|
1465
|
+
eat.all('wisp-menu-item').filter((el) => el.removed !== true).length + ' 项')
|
|
1466
|
+
if (eatToggle) {
|
|
1467
|
+
/* 元素上的点击走 harness 的 dispatch(type, event) —— 不是浏览器的 dispatchEvent(没有那个方法)。 */
|
|
1468
|
+
eatToggle.dispatch('click', { preventDefault() {}, stopPropagation() {} })
|
|
1469
|
+
check(eat.win.__wisp.config.hungry === false, 'and clicking it turns the behaviour off',
|
|
1470
|
+
String(eat.win.__wisp.config.hungry))
|
|
1471
|
+
}
|
|
1472
|
+
eat.win.__wisp.destroy()
|
|
1473
|
+
active = keepEat
|
|
1474
|
+
|
|
1353
1475
|
/* 睡着了也得被叫醒:这条单独测,因为它需要 sleepAfterMs 很短 */
|
|
1354
1476
|
const sleepCare = createHarness({ timer: true, composerText: '在打字' })
|
|
1355
1477
|
const keepSleepCare = active
|
|
@@ -2118,12 +2240,12 @@ if (clientSrc !== null) {
|
|
|
2118
2240
|
check(menuOf() !== null, 'the right-click opens a context menu')
|
|
2119
2241
|
check(menuOf()?.getAttribute('role') === 'menu', 'the menu carries menu semantics')
|
|
2120
2242
|
/* 项数从皮肤数**推导**,不写死:加一套皮肤不该让测试变红(那是数据的错,不是行为的错)。
|
|
2121
|
-
固定部分 = 6 个动作 +
|
|
2243
|
+
固定部分 = 6 个动作 + 4 个开关 + 4 个角落 = 14;其余是每套皮肤一项。 */
|
|
2122
2244
|
/* api.skins 是数组(SKIN_IDS 的拷贝),不是对象 —— 用 Object.keys 只是碰巧数对了。 */
|
|
2123
2245
|
const skinCount = Array.isArray(api.skins) ? api.skins.length : Object.keys(api.skins ?? {}).length
|
|
2124
|
-
check(itemsOf().length ===
|
|
2246
|
+
check(itemsOf().length === 14 + skinCount,
|
|
2125
2247
|
'the menu lists fixed actions and corners plus exactly one entry per skin',
|
|
2126
|
-
`${itemsOf().length} items =
|
|
2248
|
+
`${itemsOf().length} items = 14 + ${skinCount} skin(s)`)
|
|
2127
2249
|
const byText = (t) => itemsOf().filter((i) => i.textContent.includes(t))
|
|
2128
2250
|
const heads = menuOf().querySelectorAll('.wisp-menu-head')
|
|
2129
2251
|
check(heads.length === 3, 'the behaviour, position and skin sections each get a header', `${heads.length} headers`)
|
|
@@ -2661,7 +2783,8 @@ if (clientSrc !== null) {
|
|
|
2661
2783
|
const skins = Object.keys(value)
|
|
2662
2784
|
const missing = []
|
|
2663
2785
|
for (const skin of skins) {
|
|
2664
|
-
|
|
2786
|
+
/* 八个情绪:加了 eat(干饭)之后这里也要跟上 —— 漏一个就会让"每套皮肤都齐"变成假绿。 */
|
|
2787
|
+
for (const mood of ['idle', 'happy', 'sleepy', 'work', 'attn', 'poked', 'proud', 'eat']) {
|
|
2665
2788
|
const uri = value[skin]?.[mood]
|
|
2666
2789
|
if (typeof uri !== 'string' || uri.indexOf('data:image/') !== 0) missing.push(`${skin}/${mood}`)
|
|
2667
2790
|
}
|
|
@@ -2672,8 +2795,8 @@ if (clientSrc !== null) {
|
|
|
2672
2795
|
skinReport = `table does not evaluate: ${error.message}`
|
|
2673
2796
|
}
|
|
2674
2797
|
}
|
|
2675
|
-
if (skinsOk) ok('every skin carries all
|
|
2676
|
-
else bad('every skin carries all
|
|
2798
|
+
if (skinsOk) ok('every skin carries all eight moods as data URIs', skinReport)
|
|
2799
|
+
else bad('every skin carries all eight moods as data URIs', skinReport)
|
|
2677
2800
|
|
|
2678
2801
|
if (clientSrc.includes('__SPRITES__')) bad('no leftover build placeholder', 'run `node build.mjs`')
|
|
2679
2802
|
else ok('no leftover build placeholder')
|