From 18d16385623e9c919b7ce8dead3a8caa9a05af0a Mon Sep 17 00:00:00 2001 From: Ylun Wang Date: Fri, 8 Nov 2024 18:51:29 +0800 Subject: [PATCH] fix(tree): fix checked logic when lazy load (#1976) * fix(tree): fix checked logic when lazy load * fix(tree): fix treeNode.isChecked when lazyLoad and valueMode="onlyLeaf" * chore(tree): add unit test for lazy and valueMode --- js/tree-v1/tree-node.ts | 21 ++- js/tree/tree-node.ts | 21 ++- test/unit/tree/lazy.test.js | 282 +++++++++++++++++++++++++++++++++--- 3 files changed, 293 insertions(+), 31 deletions(-) diff --git a/js/tree-v1/tree-node.ts b/js/tree-v1/tree-node.ts index d556ba105e..ca0c31d8f9 100644 --- a/js/tree-v1/tree-node.ts +++ b/js/tree-v1/tree-node.ts @@ -827,20 +827,29 @@ export class TreeNode { */ public isChecked(map?: TypeIdMap): boolean { const { children, tree, value } = this; - const { checkStrictly } = tree.config; + const { checkStrictly, valueMode } = tree.config; // 节点不在当前树上,视为未选中 if (!tree.nodeMap.get(value)) return false; // 节点不可选,视为未选中 if (!this.isCheckable()) return false; const checkedMap = map || tree.checkedMap; + // 严格模式,则已经可以判定选中状态 + if (checkStrictly) { + return !!checkedMap.get(value); + } let checked = false; - // 如果在 checkedMap 中,则直接为 true - if (checkedMap.get(value)) { + // 在 checkedMap 中,则根据 valueMode 的值进行判断 + if (checkedMap.get(value) + && ( + // 如果 valueMode 为 all、parentFirst,则视为选中 + valueMode !== 'onlyLeaf' + // 如果 valueMode 为 onlyLeaf 并且当前节点是叶子节点,则视为选中 + || this.isLeaf() + ) + ) { return true; } - // 严格模式,则已经可以判定选中状态 - if (checkStrictly) return checked; - // 允许关联状态的情况下,需要进一步判断 + // 如果 valueMode 为 onlyLeaf 并且当前节点是父节点,则进一步判断 if (Array.isArray(children) && children.length > 0) { // 子节点全部选中,则当前节点选中 checked = children.every((node) => { diff --git a/js/tree/tree-node.ts b/js/tree/tree-node.ts index 58ead903a3..5fc3a705ad 100644 --- a/js/tree/tree-node.ts +++ b/js/tree/tree-node.ts @@ -864,22 +864,29 @@ export class TreeNode { */ public isChecked(map?: TypeIdMap): boolean { const { children, tree, value } = this; - const { checkStrictly } = tree.config; + const { checkStrictly, valueMode } = tree.config; // 节点不在当前树上,视为未选中 if (!tree.nodeMap.get(value)) return false; // 节点不可选,视为未选中 if (!this.isCheckable()) return false; const checkedMap = map || tree.checkedMap; - let checked = false; - // 如果在 checkedMap 中,则直接为 true - if (checkedMap.get(value)) { - return true; - } // 严格模式,则已经可以判定选中状态 if (checkStrictly) { return !!checkedMap.get(value); } - // 允许关联状态的情况下,需要进一步判断 + let checked = false; + // 在 checkedMap 中,则根据 valueMode 的值进行判断 + if (checkedMap.get(value) + && ( + // 如果 valueMode 为 all、parentFirst,则视为选中 + valueMode !== 'onlyLeaf' + // 如果 valueMode 为 onlyLeaf 并且当前节点是叶子节点,则视为选中 + || this.isLeaf() + ) + ) { + return true; + } + // 如果 valueMode 为 onlyLeaf 并且当前节点是父节点,则进一步判断 if (Array.isArray(children) && children.length > 0) { // 子节点全部选中,则当前节点选中 checked = children.every((node) => { diff --git a/test/unit/tree/lazy.test.js b/test/unit/tree/lazy.test.js index 5c84ff43e6..245e093b5e 100644 --- a/test/unit/tree/lazy.test.js +++ b/test/unit/tree/lazy.test.js @@ -1,6 +1,18 @@ import TreeStore from '../../../js/tree/tree-store'; import { delay } from './kit'; +const lazyLoad = async (tree, value) => { + const promise = new Promise((resolve) => { + tree.emitter.on('load', resolve); + }); + tree.getNode(value).setExpanded(true, { + directly: true, + }); + await promise; + // promise 触发后,还要再等一个 reflow 事件 + await delay(0); +}; + // 节点延迟加载 describe('tree:lazy', () => { describe('treeStore:lazy', () => { @@ -22,16 +34,8 @@ describe('tree:lazy', () => { let nodes = tree.getNodes(); expect(nodes.length).toBe(1); - const pm = new Promise((resolve) => { - tree.emitter.on('load', resolve); - }); - tree.getNode('t1').setExpanded(true, { - directly: true, - }); - await pm; - // promise 触发后,还要再等一个 reflow 事件 - await delay(0); + await lazyLoad(tree, 't1'); nodes = tree.getNodes(); expect(nodes.length).toBe(2); @@ -60,16 +64,8 @@ describe('tree:lazy', () => { let nodes = tree.getNodes(); expect(nodes.length).toBe(1); - const pm = new Promise((resolve) => { - tree.emitter.on('load', resolve); - }); - tree.getNode('t1').setExpanded(true, { - directly: true, - }); - await pm; - // promise 触发后,还要再等一个 reflow 事件 - await delay(0); + await lazyLoad(tree, 't1'); nodes = tree.getNodes(); expect(nodes.length).toBe(2); @@ -79,4 +75,254 @@ describe('tree:lazy', () => { expect(tree.getNode('t1.1').checked).toBe(true); }); }); + + describe('treeStore:valueMode', () => { + it('valueMode 默认为 onlyLeaf 时,子节点未加载的情况下,无法选中父节点', async () => { + const tree = new TreeStore({ + checkable: true, + lazy: true, + async load() { + await delay(0); + return [{ + value: 't1.1' + }]; + } + }); + tree.append([{ + value: 't1', + children: true, + }]); + + const t1 = tree.getNode('t1'); + t1.setChecked('t1', { + directly: true, + }); + await delay(0); + + expect(tree.getNodes().length).toBe(1); + expect(tree.getChecked().length).toBe(0); + expect(t1.checked).toBe(false); + }); + + it('valueMode 默认为 onlyLeaf 时,子节点全部选中的情况下,自动选中父节点', async () => { + const tree = new TreeStore({ + checkable: true, + lazy: true, + async load() { + await delay(0); + return [{ + value: 't1.1' + }, { + value: 't1.2' + }]; + } + }); + tree.append([{ + value: 't1', + children: true, + }]); + const t1 = tree.getNode('t1'); + + await lazyLoad(tree, 't1'); + + expect(tree.getNodes().length).toBe(3); + + const t1d1 = tree.getNode('t1.1'); + const t1d2 = tree.getNode('t1.2'); + + t1d1.setChecked(true, { + directly: true, + }); + t1d2.setChecked(true, { + directly: true, + }); + expect(tree.getChecked().length).toBe(2); + expect(t1.checked).toBe(true); + expect(t1d1.checked).toBe(true); + expect(t1d2.checked).toBe(true); + }); + + it('valueMode 为 parentFirst', async () => { + const tree = new TreeStore({ + checkable: true, + valueMode: 'parentFirst', + lazy: true, + async load() { + await delay(0); + return [{ + value: 't1.1' + }, { + value: 't1.2' + }]; + } + }); + tree.append([{ + value: 't1', + children: true, + }]); + const t1 = tree.getNode('t1'); + t1.setChecked(true, { + directly: true, + }); + await delay(0); + + expect(tree.getNodes().length).toBe(1); + expect(tree.getChecked().length).toBe(1); + expect(t1.checked).toBe(true); + + await lazyLoad(tree, 't1'); + + expect(tree.getNodes().length).toBe(3); + expect(tree.getChecked().length).toBe(1); + expect(t1.checked).toBe(true); + expect(tree.getNode('t1.1').checked).toBe(true); + expect(tree.getNode('t1.2').checked).toBe(true); + }); + + it('valueMode 为 parentFirst 的半选状态', async () => { + const tree = new TreeStore({ + checkable: true, + valueMode: 'parentFirst', + lazy: true, + async load(node) { + await delay(0); + if (node.value === 't1') { + return [{ + value: 't1.1', + children: true, + }]; + } + if (node.value === 't1.1') { + return [{ + value: 't1.1.1', + }, { + value: 't1.1.2' + }]; + } + return []; + } + }); + tree.append([{ + value: 't1', + children: true, + }]); + + // 展开 t1 + await lazyLoad(tree, 't1'); + + expect(tree.getNodes().length).toBe(2); + + // 展开 t1.1 + await lazyLoad(tree, 't1.1'); + + expect(tree.getNodes().length).toBe(4); + + tree.getNode('t1.1.1').setChecked(true, { + directly: true, + }); + + expect(tree.getChecked().length).toBe(1); + + expect(tree.getNode('t1').checked).toBe(false); + expect(tree.getNode('t1').indeterminate).toBe(true); + + expect(tree.getNode('t1.1').checked).toBe(false); + expect(tree.getNode('t1.1').indeterminate).toBe(true); + + expect(tree.getNode('t1.1.1').checked).toBe(true); + expect(tree.getNode('t1.1.2').checked).toBe(false); + }); + + it('valueMode 为 all', async () => { + const tree = new TreeStore({ + checkable: true, + valueMode: 'all', + lazy: true, + async load() { + await delay(0); + return [{ + value: 't1.1' + }, { + value: 't1.2' + }]; + } + }); + tree.append([{ + value: 't1', + children: true, + }]); + + const t1 = tree.getNode('t1'); + t1.setChecked(true, { + directly: true, + }); + await delay(0); + + expect(tree.getNodes().length).toBe(1); + expect(tree.getChecked().length).toBe(1); + expect(t1.checked).toBe(true); + + await lazyLoad(tree, 't1'); + + expect(tree.getNodes().length).toBe(3); + expect(tree.getChecked().length).toBe(3); + expect(t1.checked).toBe(true); + expect(tree.getNode('t1.1').checked).toBe(true); + expect(tree.getNode('t1.2').checked).toBe(true); + }); + + it('valueMode 为 all 的半选状态', async () => { + const tree = new TreeStore({ + checkable: true, + valueMode: 'all', + lazy: true, + async load(node) { + await delay(0); + if (node.value === 't1') { + return [{ + value: 't1.1', + children: true, + }]; + } + if (node.value === 't1.1') { + return [{ + value: 't1.1.1', + }, { + value: 't1.1.2' + }]; + } + return []; + } + }); + tree.append([{ + value: 't1', + children: true, + }]); + + // 展开 t1 + await lazyLoad(tree, 't1'); + + expect(tree.getNodes().length).toBe(2); + + // 展开 t1.1 + await lazyLoad(tree, 't1.1'); + + expect(tree.getNodes().length).toBe(4); + + tree.getNode('t1.1.1').setChecked(true, { + directly: true, + }); + + expect(tree.getChecked().length).toBe(1); + + expect(tree.getNode('t1').checked).toBe(false); + expect(tree.getNode('t1').indeterminate).toBe(true); + + expect(tree.getNode('t1.1').checked).toBe(false); + expect(tree.getNode('t1.1').indeterminate).toBe(true); + + expect(tree.getNode('t1.1.1').checked).toBe(true); + expect(tree.getNode('t1.1.2').checked).toBe(false); + }); + }); });