feat:昵称重复、显示分销人分销tree
This commit is contained in:
1 parent
c1411e0fde
commit
ee06dbaf9b
9 files changed
+1243
-268
No files matched your search
@@ -0,0 +1,352 @@
|
||||
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, compileTemplate } = require('vue/compiler-sfc')
|
||||
|
||||
const detailDirectory = path.join(__dirname, '../src/views/member/user/detail')
|
||||
const hookSource = fs.readFileSync(path.join(detailDirectory, 'useBrokerageTree.ts'), 'utf8')
|
||||
const transpile = (source) =>
|
||||
ts.transpileModule(source, {
|
||||
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS }
|
||||
}).outputText
|
||||
const child = (id, bindUserId, directChildCount = 0) => ({
|
||||
id,
|
||||
bindUserId,
|
||||
directChildCount,
|
||||
userCode: `U${id}`,
|
||||
nickname: `会员${id}`,
|
||||
memberExists: true
|
||||
})
|
||||
const member = (id = 1, inviterId = 90) => ({
|
||||
id,
|
||||
userCode: `U${id}`,
|
||||
nickname: `会员${id}`,
|
||||
inviter: inviterId
|
||||
? { id: inviterId, nickname: '邀请人', userCode: `U${inviterId}`, memberExists: true }
|
||||
: null
|
||||
})
|
||||
const deferred = () => {
|
||||
let resolve, reject
|
||||
const promise = new Promise((done, fail) => {
|
||||
resolve = done
|
||||
reject = fail
|
||||
})
|
||||
return { promise, resolve, reject }
|
||||
}
|
||||
const flush = async () => {
|
||||
await vue.nextTick()
|
||||
for (let index = 0; index < 8; index++) await Promise.resolve()
|
||||
}
|
||||
|
||||
function harness(overrides = {}, initial = member()) {
|
||||
const calls = []
|
||||
const api = {
|
||||
getBrokerageChildrenPage: async () => ({ list: [], total: 0 }),
|
||||
getUser: async (id) => member(id),
|
||||
...overrides
|
||||
}
|
||||
const childQuery = api.getBrokerageChildrenPage
|
||||
api.getBrokerageChildrenPage = (query) => {
|
||||
calls.push({ ...query })
|
||||
return childQuery(query)
|
||||
}
|
||||
let unmount
|
||||
const runtimeVue = {
|
||||
...vue,
|
||||
onBeforeUnmount: (fn) => {
|
||||
unmount = fn
|
||||
}
|
||||
}
|
||||
const module = { exports: {} }
|
||||
vm.runInNewContext(transpile(hookSource), {
|
||||
exports: module.exports,
|
||||
require: (name) => {
|
||||
if (name === 'vue') return runtimeVue
|
||||
if (name === '@/api/member/user') return api
|
||||
throw new Error(`Unexpected import ${name}`)
|
||||
}
|
||||
})
|
||||
const user = vue.ref(initial)
|
||||
const scope = vue.effectScope()
|
||||
const tree = scope.run(() => module.exports.useBrokerageTree(user))
|
||||
return {
|
||||
tree,
|
||||
user,
|
||||
api,
|
||||
calls,
|
||||
stop: () => {
|
||||
unmount()
|
||||
scope.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
const nodeIds = (tree) => Array.from(tree.graph.value.nodes, ({ member }) => member.id)
|
||||
|
||||
test('会员列表与详情模板可编译,邀请人紧跟禁用列,关系树使用懒加载页签', () => {
|
||||
for (const file of [
|
||||
path.join(detailDirectory, '../index.vue'),
|
||||
path.join(detailDirectory, 'index.vue'),
|
||||
path.join(detailDirectory, 'UserBrokerageTree.vue')
|
||||
]) {
|
||||
const { descriptor } = parse(fs.readFileSync(file, 'utf8'))
|
||||
assert.deepEqual(
|
||||
compileTemplate({ source: descriptor.template.content, filename: file, id: 'brokerage' })
|
||||
.errors,
|
||||
[]
|
||||
)
|
||||
}
|
||||
const list = fs.readFileSync(path.join(detailDirectory, '../index.vue'), 'utf8')
|
||||
assert.match(
|
||||
list,
|
||||
/label="是否禁用"[\s\S]*?<\/el-table-column>\s*<el-table-column label="邀请人"/
|
||||
)
|
||||
assert.match(list, /openDetail\(row.inviter.id\)/)
|
||||
const detail = fs.readFileSync(path.join(detailDirectory, 'index.vue'), 'utf8')
|
||||
assert.match(detail, /label="分销关系" name="brokerage" lazy/)
|
||||
assert.match(detail, /defineAsyncComponent\(\(\) => import\('\.\/UserBrokerageTree.vue'\)\)/)
|
||||
assert.match(detail, /label="推广用户" lazy/)
|
||||
})
|
||||
|
||||
test('等待会员资料时不查询,未绑定邀请人仍展示当前会员', async () => {
|
||||
const h = harness({}, null)
|
||||
assert.equal(h.calls.length, 0)
|
||||
h.user.value = member(1, null)
|
||||
await flush()
|
||||
assert.deepEqual(nodeIds(h.tree), [1])
|
||||
assert.equal(h.tree.branches[1].total, 0)
|
||||
h.stop()
|
||||
})
|
||||
|
||||
test('直属下级按20人分页,去重后继续正确推进页码', async () => {
|
||||
const h = harness({
|
||||
getBrokerageChildrenPage: async ({ pageNo }) =>
|
||||
pageNo === 1
|
||||
? { list: Array.from({ length: 20 }, (_, index) => child(30 - index, 1)), total: 24 }
|
||||
: { list: [child(11, 1), child(10, 1), child(9, 1), child(8, 1)], total: 24 }
|
||||
})
|
||||
await flush()
|
||||
assert.deepEqual(nodeIds(h.tree).slice(0, 3), [90, 1, 30])
|
||||
await h.tree.loadChildren(1)
|
||||
assert.equal(h.tree.branches[1].children.length, 23)
|
||||
assert.equal(h.tree.hasMore(h.tree.branches[1]), false)
|
||||
await h.tree.loadChildren(1)
|
||||
assert.deepEqual(h.calls, [
|
||||
{ bindUserId: 1, pageNo: 1, pageSize: 20 },
|
||||
{ bindUserId: 1, pageNo: 2, pageSize: 20 }
|
||||
])
|
||||
h.stop()
|
||||
})
|
||||
|
||||
test('可展开超过三级,收起后再次展开复用已加载的分支与展开状态', async () => {
|
||||
const h = harness({
|
||||
getBrokerageChildrenPage: async ({ bindUserId }) => ({
|
||||
list: bindUserId < 4 ? [child(bindUserId + 1, bindUserId, 1)] : [],
|
||||
total: bindUserId < 4 ? 1 : 0
|
||||
})
|
||||
})
|
||||
await flush()
|
||||
await h.tree.toggleBranch(2)
|
||||
await h.tree.toggleBranch(3)
|
||||
assert.deepEqual(nodeIds(h.tree), [90, 1, 2, 3, 4])
|
||||
await h.tree.toggleBranch(2)
|
||||
assert.deepEqual(nodeIds(h.tree), [90, 1, 2])
|
||||
await h.tree.toggleBranch(2)
|
||||
assert.deepEqual(nodeIds(h.tree), [90, 1, 2, 3, 4])
|
||||
assert.equal(h.calls.length, 3)
|
||||
h.stop()
|
||||
})
|
||||
|
||||
test('分支失败后可重试原页,已有节点继续显示', async () => {
|
||||
let attempts = 0
|
||||
const h = harness({
|
||||
getBrokerageChildrenPage: async ({ pageNo }) => {
|
||||
if (pageNo === 1) return { list: [child(2, 1)], total: 21 }
|
||||
if (++attempts === 1) throw new Error('offline')
|
||||
return { list: [child(3, 1)], total: 21 }
|
||||
}
|
||||
})
|
||||
await flush()
|
||||
await h.tree.loadChildren(1)
|
||||
assert.equal(h.tree.branches[1].pageNo, 1)
|
||||
assert.match(h.tree.branches[1].error, /重试/)
|
||||
assert.deepEqual(nodeIds(h.tree), [90, 1, 2])
|
||||
await h.tree.loadChildren(1)
|
||||
assert.equal(h.tree.branches[1].error, '')
|
||||
assert.deepEqual(nodeIds(h.tree), [90, 1, 2, 3])
|
||||
assert.equal(h.calls.at(-1).pageNo, 2)
|
||||
h.stop()
|
||||
})
|
||||
|
||||
test('初次加载失败可重试,并发重复点击只发出一次请求', async () => {
|
||||
const pending = deferred()
|
||||
const h = harness({ getBrokerageChildrenPage: () => pending.promise })
|
||||
await h.tree.loadChildren(1)
|
||||
assert.equal(h.calls.length, 1)
|
||||
pending.reject(new Error('offline'))
|
||||
await flush()
|
||||
assert.equal(h.tree.branches[1].loading, false)
|
||||
h.api.getBrokerageChildrenPage = async () => ({ list: [child(2, 1)], total: 1 })
|
||||
await h.tree.loadChildren(1)
|
||||
assert.deepEqual(nodeIds(h.tree), [90, 1, 2])
|
||||
h.stop()
|
||||
})
|
||||
|
||||
test('切换会员后忽略旧分支的迟到响应', async () => {
|
||||
const old = deferred()
|
||||
const h = harness({
|
||||
getBrokerageChildrenPage: ({ bindUserId }) =>
|
||||
bindUserId === 1 ? old.promise : Promise.resolve({ list: [child(20, 10)], total: 1 })
|
||||
})
|
||||
h.user.value = member(10, 99)
|
||||
await flush()
|
||||
old.resolve({ list: [child(2, 1)], total: 1 })
|
||||
await flush()
|
||||
assert.deepEqual(nodeIds(h.tree), [99, 10, 20])
|
||||
assert.equal(h.tree.branches[1], undefined)
|
||||
assert.equal(h.tree.branches[2], undefined)
|
||||
h.stop()
|
||||
})
|
||||
|
||||
test('刷新重新读取邀请人并清空旧分支,连续点击只刷新一次', async () => {
|
||||
const pending = deferred()
|
||||
let reads = 0
|
||||
const h = harness({
|
||||
getUser: () => {
|
||||
reads++
|
||||
return pending.promise
|
||||
}
|
||||
})
|
||||
await flush()
|
||||
const task = h.tree.refresh()
|
||||
await h.tree.refresh()
|
||||
assert.equal(reads, 1)
|
||||
pending.resolve(member(1, 99))
|
||||
await task
|
||||
assert.deepEqual(nodeIds(h.tree), [99, 1])
|
||||
assert.equal(h.tree.refreshing.value, false)
|
||||
h.stop()
|
||||
})
|
||||
|
||||
test('刷新失败保留已加载关系并允许再次刷新,切换会员会清除错误', async () => {
|
||||
const h = harness({
|
||||
getUser: async () => {
|
||||
throw new Error('offline')
|
||||
}
|
||||
})
|
||||
await flush()
|
||||
await h.tree.refresh()
|
||||
assert.match(h.tree.refreshError.value, /重试/)
|
||||
assert.deepEqual(nodeIds(h.tree), [90, 1])
|
||||
h.api.getUser = async () => member(1, 99)
|
||||
await h.tree.refresh()
|
||||
assert.equal(h.tree.refreshError.value, '')
|
||||
assert.deepEqual(nodeIds(h.tree), [99, 1])
|
||||
h.stop()
|
||||
})
|
||||
|
||||
test('历史循环、错误父节点和重复数据不会形成环或重复节点,缺失会员资料仍可查看关系', async () => {
|
||||
const h = harness({
|
||||
getBrokerageChildrenPage: async ({ bindUserId }) =>
|
||||
bindUserId === 1
|
||||
? {
|
||||
list: [
|
||||
child(1, 1),
|
||||
child(90, 1),
|
||||
child(8, 999),
|
||||
{ ...child(2, 1, 1), memberExists: false },
|
||||
child(2, 1)
|
||||
],
|
||||
total: 5
|
||||
}
|
||||
: { list: [child(1, 2), child(3, 2)], total: 2 }
|
||||
})
|
||||
await flush()
|
||||
assert.deepEqual(nodeIds(h.tree), [90, 1, 2])
|
||||
assert.match(h.tree.branches[1].warning, /异常/)
|
||||
assert.equal(h.tree.graph.value.nodes.at(-1).member.memberExists, false)
|
||||
await h.tree.toggleBranch(2)
|
||||
assert.deepEqual(nodeIds(h.tree), [90, 1, 2, 3])
|
||||
assert.match(h.tree.branches[2].warning, /异常/)
|
||||
h.stop()
|
||||
})
|
||||
|
||||
test('会员邀请自己时显示异常提示,组件卸载后忽略请求结果', async () => {
|
||||
const pending = deferred()
|
||||
const h = harness({ getBrokerageChildrenPage: () => pending.promise }, member(1, 1))
|
||||
assert.deepEqual(nodeIds(h.tree), [1])
|
||||
assert.match(h.tree.branches[1].warning, /自己/)
|
||||
h.stop()
|
||||
pending.resolve({ list: [child(2, 1)], total: 1 })
|
||||
await flush()
|
||||
assert.equal(h.tree.branches[2], undefined)
|
||||
})
|
||||
|
||||
test('实际Dagre布局在宽分支和三级关系中无节点重叠,节点详情跳转使用内部编号', async () => {
|
||||
const h = harness({
|
||||
getBrokerageChildrenPage: async () => ({ list: [child(2, 1, 1), child(3, 1)], total: 2 })
|
||||
})
|
||||
await flush()
|
||||
const { descriptor } = parse(
|
||||
fs.readFileSync(path.join(detailDirectory, 'UserBrokerageTree.vue'), 'utf8')
|
||||
)
|
||||
const props = vue.reactive({ user: member(), active: true })
|
||||
const routes = []
|
||||
const context = {
|
||||
exports: {},
|
||||
defineProps: () => props,
|
||||
useRouter: () => ({ push: (route) => routes.push(route) }),
|
||||
require: (name) => {
|
||||
if (name === 'vue') return { ...vue, useId: () => 'test' }
|
||||
if (name === '@vue-flow/core')
|
||||
return {
|
||||
Position: { Top: 'top', Bottom: 'bottom' },
|
||||
MarkerType: { ArrowClosed: 'arrowclosed' },
|
||||
ConnectionMode: { Strict: 'strict' },
|
||||
useVueFlow: () => ({
|
||||
onNodesInitialized: () => {},
|
||||
fitView: async () => {},
|
||||
zoomIn: () => {},
|
||||
zoomOut: () => {}
|
||||
})
|
||||
}
|
||||
if (name === '@vue-flow/controls' || name.endsWith('.css')) return {}
|
||||
if (name === '@dagrejs/dagre') return require('@dagrejs/dagre')
|
||||
if (name === '@/utils/constants') return { CommonStatusEnum: { DISABLE: 1 } }
|
||||
if (name === '@/utils/formatTime') return { formatDate: (value) => value.toISOString() }
|
||||
if (name === './useBrokerageTree') return { useBrokerageTree: () => h.tree }
|
||||
throw new Error(`Unexpected import ${name}`)
|
||||
}
|
||||
}
|
||||
const scope = vue.effectScope()
|
||||
scope.run(() =>
|
||||
vm.runInNewContext(
|
||||
transpile(descriptor.scriptSetup.content) + '\nglobalThis.state = { layout, openDetail };',
|
||||
context
|
||||
)
|
||||
)
|
||||
const nodes = context.state.layout.value.nodes
|
||||
assert.equal(nodes.length, 4)
|
||||
assert.ok(nodes.every((node) => node.style.pointerEvents === 'all'))
|
||||
assert.ok(
|
||||
context.state.layout.value.edges.every(
|
||||
(edge) => edge.sourceHandle === 'out' && edge.targetHandle === 'in'
|
||||
)
|
||||
)
|
||||
for (let index = 0; index < nodes.length; index++) {
|
||||
for (let other = index + 1; other < nodes.length; other++) {
|
||||
assert.ok(
|
||||
Math.abs(nodes[index].position.x - nodes[other].position.x) >= 248 ||
|
||||
Math.abs(nodes[index].position.y - nodes[other].position.y) >= 174
|
||||
)
|
||||
}
|
||||
}
|
||||
context.state.openDetail(2)
|
||||
assert.equal(routes[0].params.id, 2)
|
||||
assert.equal(routes[0].name, 'MemberUserDetail')
|
||||
scope.stop()
|
||||
h.stop()
|
||||
})
|
||||
Reference in new issue
Block a user