Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/components/cascader/README.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ filter | Function | - | Typescript: `CascaderFilterFunction ` `type CascaderFilt
filter-placeholder | String | - | \- | N
filterable | Boolean | false | \- | N
keys | Object | - | Typescript: `CascaderKeysType` `type CascaderKeysType = TreeKeysType`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/common/common.ts)。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/cascader/type.ts) | N
load | Function | - | loading subtree data (only effective when the node's children value is true)。Typescript: `(node: CascaderOption) => Promise<Array<CascaderOption>>` | N
options | Array | [] | Typescript: `Array<CascaderOption>` | N
placeholder | String | - | \- | N
sub-titles | Array | [] | Typescript: `Array<string>` | N
Expand Down
1 change: 1 addition & 0 deletions packages/components/cascader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ filter | Function | - | 自定义过滤函数。返回 true 表示匹配,未
filter-placeholder | String | - | 搜索框占位符描述文本 | N
filterable | Boolean | false | 是否可搜索,开启后顶部会展示一个搜索框 | N
keys | Object | - | 用来定义 value / label / children / disabled 在 `options` 中对应的字段别名。TS 类型:`CascaderKeysType` `type CascaderKeysType = TreeKeysType`。[通用类型定义](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/common/common.ts)。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/blob/develop/packages/components/cascader/type.ts) | N
load | Function | - | 加载子树数据的方法(仅当节点 children 为 true 时生效)。TS 类型:`(node: CascaderOption) => Promise<Array<CascaderOption>>` | N
options | Array | [] | 可选项数据源。TS 类型:`Array<CascaderOption>` | N
placeholder | String | - | 未选中时的提示文案。组件内置默认值为:'选择选项' | N
sub-titles | Array | [] | 每级展示的次标题。TS 类型:`Array<string>` | N
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,33 @@ exports[`Cascader Cascader keys demo works fine 1`] = `
</keys>
`;

exports[`Cascader Cascader lazy-load demo works fine 1`] = `
<lazy-load>
<t-cell
arrow="{{true}}"
note="中国/广东省/深圳市"
title="地址"
bind:click="showCascader"
/>
<t-cascader
load="{{[Function]}}"
options="{{
Array [
Object {
"children": true,
"label": "中国",
"value": "CN",
},
]
}}"
title="请选择地址"
value="440300"
visible="{{false}}"
bind:change="onChange"
/>
</lazy-load>
`;

exports[`Cascader Cascader theme-tab demo works fine 1`] = `
<theme-tab>
<t-cell
Expand Down
2 changes: 1 addition & 1 deletion packages/components/cascader/__test__/demo.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import path from 'path';
import simulate from 'miniprogram-simulate';

const mapper = ['base', 'check-strictly', 'filterable', 'keys', 'theme-tab', 'with-title', 'with-value'];
const mapper = ['base', 'check-strictly', 'filterable', 'keys', 'lazy-load', 'theme-tab', 'with-title', 'with-value'];

describe('Cascader', () => {
mapper.forEach((demoName) => {
Expand Down
135 changes: 135 additions & 0 deletions packages/components/cascader/__test__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,139 @@ describe('cascader', () => {
expect(leaf.value).toBe('110108');
});
});

describe(': lazy load', () => {
const renderCascader = ({ options, value = null, load, keys } = {}) => {
const id = simulate.load({
template: `<t-cascader id="cas" options="{{options}}" value="{{value}}" load="{{load}}" keys="{{keys}}" />`,
data: { options, value, load, keys },
usingComponents: { 't-cascader': cascader },
});
const comp = simulate.render(id);
comp.attach(document.createElement('parent-wrapper'));
return comp;
};

const select = ($cascader, level, value) => {
$cascader.instance.handleSelect({
target: { dataset: { level } },
detail: { value },
});
};

it('loads children:true nodes on selection without mutating options', async () => {
const options = [{ label: '中国', value: 'CN', children: true }];
const load = jest.fn(() => Promise.resolve([{ label: '广东省', value: '440000' }]));
const comp = renderCascader({ options, load });
const $cascader = comp.querySelector('#cas');

expect(load).not.toHaveBeenCalled();
select($cascader, 0, 'CN');
await simulate.sleep();

expect(load).toHaveBeenCalledTimes(1);
expect(load.mock.calls[0][0]).toMatchObject({ label: '中国', value: 'CN' });
expect($cascader.instance.data.items[1]).toEqual([{ label: '广东省', value: '440000', disabled: undefined }]);
expect(options[0].children).toBe(true);
});

it('initializes a value whose full path already exists', () => {
const options = [
{
label: '中国',
value: 'CN',
children: [{ label: '深圳市', value: '440300' }],
},
];
const load = jest.fn();
const comp = renderCascader({ options, value: '440300', load });
const $cascader = comp.querySelector('#cas');

expect($cascader.instance.data.selectedIndexes).toEqual([0, 0]);
expect($cascader.instance.data.selectedValue).toEqual(['CN', '440300']);
expect(load).not.toHaveBeenCalled();
});

it('restores an unresolved value after its path is loaded', async () => {
const options = [{ label: '中国', value: 'CN', children: true }];
const load = jest.fn((node) => {
if (node.value === 'CN') {
return Promise.resolve([{ label: '广东省', value: '440000', children: true }]);
}
return Promise.resolve([{ label: '深圳市', value: '440300' }]);
});
const comp = renderCascader({ options, value: '440300', load });
const $cascader = comp.querySelector('#cas');
const triggerChange = jest.spyOn($cascader.instance, 'triggerChange');

expect($cascader.instance.data.selectedIndexes).toEqual([]);
expect(load).not.toHaveBeenCalled();

select($cascader, 0, 'CN');
await simulate.sleep();
expect($cascader.instance.data.selectedIndexes).toEqual([0]);

select($cascader, 1, '440000');
await simulate.sleep();

expect(load).toHaveBeenCalledTimes(2);
expect($cascader.instance.data.selectedIndexes).toEqual([0, 0, 0]);
expect($cascader.instance.data.selectedValue).toEqual(['CN', '440000', '440300']);
expect(triggerChange).not.toHaveBeenCalled();
});

it('deduplicates an in-flight request and caches the loaded children', async () => {
let resolveLoad;
const load = jest.fn(
() =>
new Promise((resolve) => {
resolveLoad = resolve;
}),
);
const comp = renderCascader({ options: [{ label: '中国', value: 'CN', children: true }], load });
const $cascader = comp.querySelector('#cas');

select($cascader, 0, 'CN');
select($cascader, 0, 'CN');
expect(load).toHaveBeenCalledTimes(1);

resolveLoad([{ label: '广东省', value: '440000' }]);
await simulate.sleep();
select($cascader, 0, 'CN');
await simulate.sleep();

expect(load).toHaveBeenCalledTimes(1);
expect($cascader.instance.data.items[1][0].value).toBe('440000');
});

it('keeps children:true after a rejected request so it can retry', async () => {
const load = jest
.fn()
.mockRejectedValueOnce(new Error('network error'))
.mockResolvedValueOnce([{ label: '广东省', value: '440000' }]);
const comp = renderCascader({ options: [{ label: '中国', value: 'CN', children: true }], load });
const $cascader = comp.querySelector('#cas');

select($cascader, 0, 'CN');
await simulate.sleep();
select($cascader, 0, 'CN');
await simulate.sleep();

expect(load).toHaveBeenCalledTimes(2);
expect($cascader.instance.data.items[1][0].value).toBe('440000');
});

it('supports lazy loading with custom keys', async () => {
const keys = { label: 'name', value: 'code', children: 'nodes', disabled: 'blocked' };
const load = jest.fn(() => Promise.resolve([{ name: '广东省', code: '440000' }]));
const comp = renderCascader({ options: [{ name: '中国', code: 'CN', nodes: true }], load, keys });
const $cascader = comp.querySelector('#cas');

select($cascader, 0, 'CN');
await simulate.sleep();

expect(load).toHaveBeenCalledTimes(1);
expect($cascader.instance.data.items[1]).toEqual([{ name: '广东省', code: '440000', blocked: undefined }]);
});
});
});
1 change: 1 addition & 0 deletions packages/components/cascader/_example/cascader.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"with-value": "./with-value",
"with-title": "./with-title",
"check-strictly": "./check-strictly",
"lazy-load": "./lazy-load",
"filterable": "./filterable"
}
}
4 changes: 4 additions & 0 deletions packages/components/cascader/_example/cascader.wxml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
<check-strictly />
</t-demo>

<t-demo desc="懒加载选项">
<lazy-load />
</t-demo>

<t-demo desc="支持搜索">
<filterable />
</t-demo>
Expand Down
33 changes: 33 additions & 0 deletions packages/components/cascader/_example/lazy-load/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const childrenMap = {
CN: [{ label: '广东省', value: '440000', children: true }],
440000: [
{ label: '广州市', value: '440100' },
{ label: '深圳市', value: '440300' },
],
};

Component({
data: {
options: [{ label: '中国', value: 'CN', children: true }],
value: '440300',
visible: false,
note: '中国/广东省/深圳市',
load(node) {
return new Promise((resolve) => {
setTimeout(() => resolve(childrenMap[node.value] || []), 500);
});
},
},
methods: {
showCascader() {
this.setData({ visible: true });
},
onChange(e) {
const { value, selectedOptions } = e.detail;
this.setData({
value,
note: selectedOptions.map((item) => item.label).join('/'),
});
},
},
});
7 changes: 7 additions & 0 deletions packages/components/cascader/_example/lazy-load/index.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"t-cell": "tdesign-miniprogram/cell/cell",
"t-cascader": "tdesign-miniprogram/cascader/cascader"
}
}
10 changes: 10 additions & 0 deletions packages/components/cascader/_example/lazy-load/index.wxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<t-cell title="地址" note="{{note}}" bind:click="showCascader" arrow />

<t-cascader
visible="{{visible}}"
value="{{value}}"
options="{{options}}"
load="{{load}}"
title="请选择地址"
bind:change="onChange"
/>
Empty file.
Loading
Loading