Files
Friendship-and-chat-pc/tests/app-setting.test.cjs
T
2026-11-12 14:03:26 +08:00

165 lines
7.2 KiB
JavaScript

const { test } = require('node:test')
const assert = require('node:assert/strict')
const fs = require('node:fs')
const path = require('node:path')
const vm = require('node:vm')
const ts = require('typescript')
const vue = require('vue')
const { parse, compileScript, compileTemplate } = require('vue/compiler-sfc')
const filename = path.join(__dirname, '../src/views/app/setting/index.vue')
const { descriptor } = parse(fs.readFileSync(filename, 'utf8'))
const transpile = (source) => ts.transpileModule(source, {
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS }
}).outputText
function evaluate(source, imports, globals = {}) {
const module = { exports: {} }
vm.runInNewContext(transpile(source), {
exports: module.exports,
require: (name) => {
if (!(name in imports)) throw new Error(`Unexpected import: ${name}`)
return imports[name]
},
...globals
})
return module.exports
}
const api = evaluate(fs.readFileSync(path.join(__dirname, '../src/api/app/setting/index.ts'), 'utf8'), {
'@/config/axios': { default: {} }
})
function harness(overrides = {}, editable = true) {
const calls = []
const backend = {
...api,
getSetting: async () => null,
getRooms: async () => [],
saveSetting: async (data) => { calls.push(['setting', data]); return true },
getBookingPage: async () => ({ list: [], total: 0 }),
getClosurePage: async () => ({ list: [], total: 0 }),
updateBookingStatus: async (data) => { calls.push(['booking', data]); return true },
updateClosureStatus: async (data) => { calls.push(['closure', data]); return true },
...overrides
}
const component = evaluate(compileScript(descriptor, { id: 'app-setting' }).content, {
vue: { ...vue, onMounted: () => {} },
'vue-router': { onBeforeRouteLeave: () => {} },
'element-plus': { ElMessageBox: { prompt: async () => ({ value: '客人要求取消' }) } },
'@/components/UploadFile': { UploadImg: {} },
'@/utils/permission': { checkPermi: () => editable },
'@/utils/formatTime': { formatDate: (value) => value },
'@/api/app/setting': backend
}, {
useMessage: () => ({ success: () => {}, warning: () => {}, confirm: async () => true })
}).default
return { page: component.setup({}, { expose: () => {} }), calls, backend }
}
const deferred = () => { let resolve; const promise = new Promise((done) => { resolve = done }); return { promise, resolve } }
test('settings template compiles with all five management sections', () => {
assert.deepEqual(compileTemplate({ source: descriptor.template.content, filename, id: 'app-setting' }).errors, [])
for (const section of ['运行模式', '门店信息', '包间管理', '预约管理', '注销申请']) assert.ok(descriptor.template.content.includes(section))
})
test('price inputs convert decimal currency exactly and reject unsupported values', () => {
assert.equal(api.yuanToFen('0.29'), 29)
assert.equal(api.yuanToFen('1.01'), 101)
assert.equal(api.yuanToFen('21474836.47'), 2147483647)
assert.equal(api.yuanToFen('21474836.48'), null)
for (const invalid of ['-1', '1.001', '1e4', '', '1,000', 'Infinity']) assert.equal(api.yuanToFen(invalid), null)
assert.equal(api.fenToYuan(1), '0.01')
assert.equal(api.fenToYuan('900719925474099301'), '9007199254740993.01')
})
test('tea activation requires configured store, valid non-overlapping slots and an enabled room', async () => {
const { page, calls } = harness()
await page.loadSettings(); await page.loadRooms()
page.settings.iosMode = 'tea'
await page.submitSettings()
assert.equal(calls.length, 0)
assert.ok(page.settingErrors.value.activation)
Object.assign(page.settings, {
storeName: '茶舍', storeCover: '/cover.jpg', storeAddress: '门店地址', storePhone: '12345678',
openTime: '09:00', closeTime: '18:00', slots: [{ startTime: '10:00', endTime: '12:00' }, { startTime: '11:30', endTime: '14:00' }]
})
page.rooms.value = [{ id: 1, enabled: true }]
await page.submitSettings()
assert.equal(calls.length, 0)
assert.match(page.settingErrors.value.slots, /重叠/)
page.settings.slots[1].startTime = '12:00'
await page.submitSettings()
assert.equal(calls.length, 1)
assert.equal(calls[0][1].iosMode, 'tea')
assert.equal(calls[0][1].androidMode, 'chat')
assert.equal(page.settingsDirty.value, false)
})
test('save failure retains user draft and unsaved state', async () => {
const { page } = harness({ saveSetting: async () => { throw new Error('network') } })
await page.loadSettings()
page.settings.storeName = '尚未保存的茶舍'
await page.submitSettings()
assert.equal(page.settings.storeName, '尚未保存的茶舍')
assert.equal(page.settingsDirty.value, true)
assert.equal(page.settingsSaving.value, false)
})
test('chat mode settings retain valid default hours and cannot save blank hours', async () => {
const { page, calls } = harness()
await page.loadSettings()
assert.equal(page.settings.openTime, '10:00')
assert.equal(page.settings.closeTime, '22:00')
page.settings.openTime = ''
page.settings.closeTime = ''
await page.submitSettings()
assert.equal(calls.length, 0)
assert.ok(page.settingErrors.value.businessHours)
})
test('late booking responses cannot overwrite the current filter result', async () => {
const first = deferred(); const second = deferred(); let count = 0
const { page } = harness({ getBookingPage: () => (++count === 1 ? first.promise : second.promise) })
const a = page.loadBookings(); const b = page.loadBookings()
second.resolve({ list: [{ id: 2 }], total: 1 }); await b
first.resolve({ list: [{ id: 1 }], total: 3 }); await a
assert.equal(page.bookings.value[0].id, 2)
assert.equal(page.bookingTotal.value, 1)
})
test('booking actions use server capabilities and do not submit unavailable actions', async () => {
const { page, calls } = harness()
const booking = { id: 1, canConfirm: false, canReject: true, canComplete: false, canAdminCancel: false }
await page.changeBooking(booking, 'confirmed')
assert.equal(calls.length, 0)
await page.changeBooking(booking, 'rejected')
assert.equal(calls.length, 1)
assert.equal(calls[0][1].status, 'rejected')
assert.equal(calls[0][1].reason, '客人要求取消')
})
test('account closure requires processing remarks and only records an outcome', async () => {
const { page, calls } = harness()
page.openClosure({ id: 7, status: 'pending' })
await page.submitClosure()
assert.equal(calls.length, 0)
assert.ok(page.closureRemarkError.value)
page.closureRemark.value = '已核实申请并完成必要的人工处理'
page.closureNextStatus.value = 'resolved'
await page.submitClosure()
assert.equal(calls.length, 1)
assert.equal(calls[0][0], 'closure')
assert.equal(calls[0][1].status, 'resolved')
assert.equal(page.closureDialog.value, false)
})
test('read-only operators cannot save settings or process requests', async () => {
const { page, calls } = harness({}, false)
await page.loadSettings()
page.settings.storeName = '无写权限'
await page.submitSettings()
page.openClosure({ id: 7, status: 'pending' })
page.closureRemark.value = '测试'
await page.submitClosure()
await page.changeBooking({ id: 1, canConfirm: true }, 'confirmed')
assert.equal(calls.length, 0)
})