@mzzsfy/dsh-cron-board 0.1.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.
@@ -0,0 +1,92 @@
1
+ // store 测试:envs/jobs/runs 三集合的原子持久化(场景嵌入各测试注释)。
2
+ // 数据目录经 io 注入临时目录,不触碰真实 ~/.dsh。
3
+
4
+ import { test } from 'node:test'
5
+ import assert from 'node:assert/strict'
6
+ import { mkdtemp, rm } from 'node:fs/promises'
7
+ import { tmpdir } from 'node:os'
8
+ import { join } from 'node:path'
9
+
10
+ import { createStore } from '../src/store.mjs'
11
+
12
+ async function makeStore(t) {
13
+ const dir = await mkdtemp(join(tmpdir(), 'cron-board-store-'))
14
+ t.after(() => rm(dir, { recursive: true, force: true }))
15
+ return { dir, store: await createStore({ dir }) }
16
+ }
17
+
18
+ test('store:环境变量增改删持久化,新实例重开同目录数据仍在', async (t) => {
19
+ // Given 空数据目录
20
+ const { dir, store } = await makeStore(t)
21
+ // When 创建一条环境变量并更新
22
+ const created = await store.envs.create({ name: 'WX_KEY', value: 'v1', remarks: '', enabled: true })
23
+ await store.envs.update(created.id, { value: 'v2' })
24
+ // Then 内存读回一致
25
+ const listed = store.envs.list()
26
+ assert.equal(listed.length, 1)
27
+ assert.equal(listed[0].name, 'WX_KEY')
28
+ assert.equal(listed[0].value, 'v2')
29
+ assert.equal(listed[0].enabled, true)
30
+ assert.ok(listed[0].createdAt > 0)
31
+ // Then 新实例重开同目录(模拟重启)数据仍在
32
+ const reopened = await createStore({ dir })
33
+ assert.equal(reopened.envs.list().length, 1)
34
+ assert.equal(reopened.envs.list()[0].value, 'v2')
35
+ })
36
+
37
+ test('store:任务创建携带运行态字段且 nextRunAt 可更新', async (t) => {
38
+ // Given 空数据目录
39
+ const { store } = await makeStore(t)
40
+ // When 创建任务并推进 nextRunAt
41
+ const job = await store.jobs.create({
42
+ name: '签到', kind: 'shell', command: 'echo hi', schedule: '30 8 * * *',
43
+ enabled: true, timeoutMs: 60 * 1000,
44
+ })
45
+ await store.jobs.update(job.id, { nextRunAt: 1750000000000 })
46
+ // Then 读回运行态字段
47
+ const [row] = store.jobs.list()
48
+ assert.equal(row.name, '签到')
49
+ assert.equal(row.nextRunAt, 1750000000000)
50
+ assert.equal(row.enabled, true)
51
+ })
52
+
53
+ test('store:运行记录追加与按任务过滤,删除任务连带删除其记录', async (t) => {
54
+ // Given 一条任务
55
+ const { store } = await makeStore(t)
56
+ const job = await store.jobs.create({ name: 'j', kind: 'shell', command: 'x', schedule: '* * * * *', enabled: true, timeoutMs: 1000 })
57
+ // When 追加两条运行记录(分属两任务)并删除任务
58
+ await store.runs.create({ runId: 'r1', jobId: job.id, trigger: 'manual', status: 'success' })
59
+ await store.runs.create({ runId: 'r2', jobId: 'other', trigger: 'cron', status: 'fail' })
60
+ assert.equal(store.runs.list(job.id).length, 1)
61
+ await store.jobs.remove(job.id)
62
+ // Then 该任务记录连带清除,他任务记录保留
63
+ assert.equal(store.runs.list(job.id).length, 0)
64
+ assert.equal(store.runs.list('other').length, 1)
65
+ })
66
+
67
+ test('store:同名环境变量多条合法', async (t) => {
68
+ // Given 已有一条 WX_KEY
69
+ const { store } = await makeStore(t)
70
+ await store.envs.create({ name: 'WX_KEY', value: 'a', remarks: '', enabled: true })
71
+ // When 再创建同名不同值
72
+ await store.envs.create({ name: 'WX_KEY', value: 'b', remarks: '', enabled: true })
73
+ // Then 两条并存(青龙多账号语义)
74
+ const same = store.envs.list().filter((row) => row.name === 'WX_KEY')
75
+ assert.equal(same.length, 2)
76
+ })
77
+
78
+ test('store:数据文件损坏时备份 .bak 并拒绝写入,他集合不受影响', async (t) => {
79
+ // Given envs.json 内容非法
80
+ const { dir, store } = await makeStore(t)
81
+ const { writeFile } = await import('node:fs/promises')
82
+ await writeFile(join(dir, 'envs.json'), 'not-json', 'utf8')
83
+ const reopened = await createStore({ dir })
84
+ // When 重开实例后尝试写入 envs
85
+ await assert.rejects(() => reopened.envs.create({ name: 'X', value: '1', enabled: true }))
86
+ // Then 坏文件已备份为 .bak,其余集合读写正常
87
+ const backup = await import('node:fs/promises').then((fs) => fs.readFile(join(dir, 'envs.json.bak'), 'utf8'))
88
+ assert.equal(backup, 'not-json')
89
+ await reopened.jobs.create({ name: 'j', kind: 'shell', command: 'x', schedule: '* * * * *', enabled: true, timeoutMs: 1000 })
90
+ assert.equal(reopened.jobs.list().length, 1)
91
+ void store
92
+ })
@@ -0,0 +1,63 @@
1
+ // 开关样式守卫 BDD:裸 checkbox/裸 input 状态选择器会静默损坏 UI 或误伤同 label 的文本输入,
2
+ // 属无报错的 UI 退化,静态断言锁死 cb-switch 结构(参照 mce-switch 同款守卫)。
3
+ import { readFileSync } from 'node:fs'
4
+ import { dirname, join } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import test from 'node:test'
7
+ import assert from 'node:assert/strict'
8
+
9
+ const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
10
+ const source = readFileSync(join(PKG_ROOT, 'src', 'client.js'), 'utf8')
11
+
12
+ test('switch 隐藏规则以 input[type="checkbox"] 精确匹配', () => {
13
+ assert.match(source, /\.cb-switch input\[type="checkbox"\] \{ position:absolute/)
14
+ })
15
+
16
+ test('switch 状态选择器禁止裸 input 锚定(防误伤同 label 的文本输入)', () => {
17
+ const bare = source.match(/\.cb-switch input:(?!\[type)[a-z-]+/g)
18
+ assert.equal(bare, null, `裸 input 状态选择器: ${bare}`)
19
+ })
20
+
21
+ test('checkbox 仅允许出现在 switchToggle 工厂内,禁止裸 checkbox 直出', () => {
22
+ const occurrences = [...source.matchAll(/type: 'checkbox'/g)].map((match) => match.index)
23
+ assert.equal(occurrences.length, 1, `checkbox 字面量出现 ${occurrences.length} 次`)
24
+ const factoryStart = source.indexOf('function switchToggle')
25
+ const factoryEnd = source.indexOf('}', source.indexOf('__thumb', factoryStart))
26
+ assert.ok(occurrences[0] > factoryStart && occurrences[0] < factoryEnd, 'checkbox 字面量不在 switchToggle 工厂内')
27
+ })
28
+
29
+ test('switchToggle 产出顺序为 input 在前 track 在后', () => {
30
+ const factory = source.slice(source.indexOf('function switchToggle'))
31
+ assert.ok(factory.indexOf("h('input'") < factory.indexOf('cb-switch__track'), '工厂内 input 必须先于 track')
32
+ })
33
+
34
+ test('disabled 态 hover 高亮被 :has 守卫排除', () => {
35
+ assert.match(source, /\.cb-switch:not\(:has\(input\[type="checkbox"\]:disabled\)\):hover/)
36
+ })
37
+
38
+ test('checked/focus-visible/disabled 三态均以 checkbox 锚定', () => {
39
+ assert.match(source, /\.cb-switch input\[type="checkbox"\]:checked \+/)
40
+ assert.match(source, /\.cb-switch input\[type="checkbox"\]:focus-visible \+/)
41
+ assert.match(source, /\.cb-switch input\[type="checkbox"\]:disabled \+/)
42
+ })
43
+
44
+ test('client.js 无顶层词法声明(经典 script 书挡内安全)', () => {
45
+ // IIFE 书挡内允许任意声明;守卫确认整文件被书挡包裹
46
+ const trimmed = source.trim()
47
+ assert.ok(trimmed.startsWith('(() => {'), '必须以 IIFE 书挡开头')
48
+ assert.ok(trimmed.endsWith('})()'), '必须以 IIFE 书挡结尾')
49
+ })
50
+
51
+ test('client.js 主页面双形态挂载契约(better-sidebar 优先/主界面回退)', () => {
52
+ // Given 用户要求:面板入口迁主页面,优先 dsh-better-sidebar 扩展槽(单实例),未装回退主界面(taskboard 形态)
53
+ // Then 静态锁定:模块注册形态、服务软探测、tab 单实例、主界面容器与互斥事件
54
+ assert.match(source, /window\.__ModuleLoader__\.load\(\{ id: '@mzzsfy\/dsh-cron-board', factory \}\)/)
55
+ assert.match(source, /ctx\.get\('betterSidebar'\)/)
56
+ assert.match(source, /single: true/)
57
+ assert.match(source, /ctx\.inject\(\['betterSidebar'\]/)
58
+ assert.match(source, /data-cb-board-active/)
59
+ assert.match(source, /dsh-panel-activate/)
60
+ assert.match(source, /function mountStandaloneBoard/)
61
+ // 不再挂设置页分区(入口唯一)
62
+ assert.doesNotMatch(source, /settings\.section/)
63
+ })