umy-ui-table 1.2.5 → 1.2.6
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.
- package/lib/index.js +1 -1
- package/lib/umy-ui.common.js +1 -1
- package/lib/ux-grid.js +1 -1
- package/package.json +1 -1
- package/packages/ux-grid/src/element-renderer.js +25 -0
- package/packages/ux-grid/src/grid-height.js +11 -1
- package/packages/ux-grid/test/browser-layout-contracts.cjs +105 -0
- package/packages/ux-grid/test/browser-layout-entry.js +66 -0
- package/packages/ux-grid/test/browser-renderer-contracts.cjs +18 -2
- package/packages/ux-grid/test/delivery-umd.cjs +48 -0
- package/packages/ux-grid/test/renderer-async.cjs +140 -0
- package/packages/ux-grid/test/renderer-boundaries.cjs +33 -2
package/package.json
CHANGED
|
@@ -304,6 +304,17 @@ function sameCascaderIdentity(left, right) {
|
|
|
304
304
|
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
+
function getCascaderRenderIdentity(options, config, identity = []) {
|
|
308
|
+
if (!Array.isArray(options)) return identity;
|
|
309
|
+
identity.push(options, options.length);
|
|
310
|
+
options.forEach((option) => {
|
|
311
|
+
identity.push(option);
|
|
312
|
+
Object.keys(option).forEach((key) => identity.push(key, option[key]));
|
|
313
|
+
getCascaderRenderIdentity(option[config.children || "children"], config, identity);
|
|
314
|
+
});
|
|
315
|
+
return identity;
|
|
316
|
+
}
|
|
317
|
+
|
|
307
318
|
function getCascaderCache(params, create, renderOpts) {
|
|
308
319
|
if (!params || !params.row || !params.column) return;
|
|
309
320
|
let columns = cascaderLabelCache.get(params.row);
|
|
@@ -430,7 +441,21 @@ function cascaderEditRender(h, renderOpts, params) {
|
|
|
430
441
|
// hook runs in that same mount turn, before asynchronous results can arrive.
|
|
431
442
|
const hooks = nodes[0].data.hook || {};
|
|
432
443
|
const onInsert = hooks.insert;
|
|
444
|
+
const onPrepatch = hooks.prepatch;
|
|
445
|
+
nodes[0].data.umyCascaderCache = cache;
|
|
446
|
+
nodes[0].data.umyCascaderOptionsIdentity = getCascaderRenderIdentity(props.options || renderOpts.options, config);
|
|
433
447
|
nodes[0].data.hook = Object.assign({}, hooks, {
|
|
448
|
+
prepatch(oldVnode, vnode) {
|
|
449
|
+
// Hydration is for a new editor. Feeding newly loaded children back as
|
|
450
|
+
// options into the same live Element panel resets its store/expanded
|
|
451
|
+
// menus on an unrelated table render (e.g. popup mousedown).
|
|
452
|
+
// Genuine external option edits must still reach the panel unchanged.
|
|
453
|
+
if (oldVnode.data.umyCascaderCache === cache &&
|
|
454
|
+
sameCascaderIdentity(oldVnode.data.umyCascaderOptionsIdentity || [], vnode.data.umyCascaderOptionsIdentity)) {
|
|
455
|
+
vnode.componentOptions.propsData.options = oldVnode.componentOptions.propsData.options;
|
|
456
|
+
}
|
|
457
|
+
if (onPrepatch) onPrepatch.apply(this, arguments);
|
|
458
|
+
},
|
|
434
459
|
insert(...args) {
|
|
435
460
|
if (onInsert) onInsert.apply(this, args);
|
|
436
461
|
pendingMountRequests.forEach((request) => {
|
|
@@ -54,12 +54,22 @@ export function observeGridHeight(vm) {
|
|
|
54
54
|
// Pagination can appear/disappear after mount; updated() reconnects when its
|
|
55
55
|
// element identity changes. Root observation also covers hidden -> visible.
|
|
56
56
|
const pager = vm.$refs.myPagination;
|
|
57
|
-
if (pager) observer.observe(pager);
|
|
57
|
+
if (pager) observer.observe(pager, { box: "border-box" });
|
|
58
58
|
const view = root.ownerDocument.defaultView;
|
|
59
|
+
// Native ResizeObserver supports border-box; older polyfills ignore it.
|
|
60
|
+
// Padding/border-only changes still change offsetHeight in that fallback.
|
|
61
|
+
const pagerObserver = pager && view && view.MutationObserver
|
|
62
|
+
? new view.MutationObserver(() => vm.scheduleHeight())
|
|
63
|
+
: null;
|
|
64
|
+
if (pagerObserver) pagerObserver.observe(pager, {
|
|
65
|
+
attributes: true, attributeFilter: ["style", "class"], childList: true,
|
|
66
|
+
characterData: true, subtree: true
|
|
67
|
+
});
|
|
59
68
|
const onResize = () => vm.scheduleHeight();
|
|
60
69
|
if (view) view.addEventListener("resize", onResize);
|
|
61
70
|
return () => {
|
|
62
71
|
observer.disconnect();
|
|
72
|
+
if (pagerObserver) pagerObserver.disconnect();
|
|
63
73
|
if (view) view.removeEventListener("resize", onResize);
|
|
64
74
|
};
|
|
65
75
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Real Chrome geometry, not jsdom. Uses only installed dependencies, isolated assets/profile.
|
|
2
|
+
const fs = require('fs'), os = require('os'), path = require('path'), http = require('http');
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const assert = require('assert');
|
|
5
|
+
const webpack = require('webpack'), WebSocket = require('ws');
|
|
6
|
+
const VueLoaderPlugin = require('vue-loader/lib/plugin');
|
|
7
|
+
const root = path.resolve(__dirname, '../../..');
|
|
8
|
+
const consumer = process.env.UX_GRID_TEST_DEPENDENCIES;
|
|
9
|
+
if (!consumer) throw Error('Set UX_GRID_TEST_DEPENDENCIES');
|
|
10
|
+
const output = fs.mkdtempSync(path.join(os.tmpdir(), 'ux-grid-layout-browser-'));
|
|
11
|
+
const results = {};
|
|
12
|
+
results.observerMode = process.env.UX_GRID_LAYOUT_POLYFILL === '1' ? 'polyfill' : 'native';
|
|
13
|
+
results.manifestVersion = require('vxe-table/package.json').version;
|
|
14
|
+
let chrome, server, ws, nextId = 0;
|
|
15
|
+
const pending = new Map();
|
|
16
|
+
function call(method, params = {}, sessionId) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const id = ++nextId, timer = setTimeout(() => { pending.delete(id); reject(Error('Timeout: ' + method)); }, 60000);
|
|
19
|
+
pending.set(id, { resolve: result => { clearTimeout(timer); resolve(result); }, reject: error => { clearTimeout(timer); reject(error); } });
|
|
20
|
+
ws.send(JSON.stringify({ id, method, params, sessionId }));
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function check(m, limit, exact = true) {
|
|
24
|
+
assert.deepStrictEqual(m.errors, []);
|
|
25
|
+
assert(m.table.height > 0 && m.body.height > 0, 'actual table/body visible');
|
|
26
|
+
const end = m.pager ? m.pager.bottom : m.table.bottom;
|
|
27
|
+
if (m.pager) assert(m.table.bottom <= m.pager.top + 1, 'pager overlaps table');
|
|
28
|
+
assert(end <= m.root.top + limit + 1, 'height budget exceeded: ' + JSON.stringify(m));
|
|
29
|
+
if (exact) assert(Math.abs(m.root.height - limit) <= 1, 'root height differs');
|
|
30
|
+
if (exact) assert(Math.abs(end - (m.root.bottom - m.bottomInset)) <= 1, 'unused height budget');
|
|
31
|
+
assert(m.calls < 30, 'unbounded observer callbacks');
|
|
32
|
+
}
|
|
33
|
+
(async () => {
|
|
34
|
+
await new Promise((resolve, reject) => webpack({ mode:'development', devtool:false,
|
|
35
|
+
entry:path.join(__dirname, 'browser-layout-entry.js'), output:{ path:output, filename:'bundle.js' },
|
|
36
|
+
resolve:{ extensions:['.js','.vue','.json'], alias:{ 'vue$':require.resolve('vue/dist/vue.runtime.esm.js'), 'element-ui':path.join(consumer,'node_modules/element-ui'), 'umy-ui':root }, modules:[path.join(root,'node_modules'),path.join(consumer,'node_modules')] },
|
|
37
|
+
module:{ rules:[{test:/\.vue$/,loader:'vue-loader'}, {test:/\.js$/,exclude:/node_modules/,loader:'babel-loader'}, {test:/\.css$/,use:['style-loader','css-loader']}, {test:/\.(woff|ttf)$/,loader:'file-loader'}] },
|
|
38
|
+
plugins:[new VueLoaderPlugin()], performance:{hints:false}
|
|
39
|
+
}, (error, stats) => error || stats.hasErrors() ? reject(error || Error(stats.toString({all:false, errors:true}))) : resolve()));
|
|
40
|
+
server = http.createServer((request, response) => {
|
|
41
|
+
const asset = path.join(output, path.basename(request.url));
|
|
42
|
+
if (fs.existsSync(asset) && fs.statSync(asset).isFile()) { response.setHeader('Content-Type', request.url.endsWith('.js') ? 'application/javascript; charset=utf-8' : 'application/octet-stream'); fs.createReadStream(asset).pipe(response); }
|
|
43
|
+
else { response.setHeader('Content-Type','text/html; charset=utf-8'); response.end('<!doctype html><html><head><meta charset="utf-8"></head><body><script>' + (results.observerMode === 'polyfill' ? 'window.ResizeObserver=undefined;' : '') + 'window.bootErrors=[];window.onerror=function(m,u,l,c,e){bootErrors.push(String(e&&e.stack||m))}</script><script src="/bundle.js"></script></body></html>'); }
|
|
44
|
+
});
|
|
45
|
+
await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0,'127.0.0.1',resolve); });
|
|
46
|
+
const endpoint = await new Promise((resolve, reject) => {
|
|
47
|
+
chrome = spawn(process.env.UX_GRID_TEST_CHROME || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', ['--headless=new','--disable-gpu','--no-first-run','--no-default-browser-check','--remote-debugging-port=0','--user-data-dir=' + path.join(output,'profile'),'about:blank']);
|
|
48
|
+
chrome.on('error',reject); chrome.on('exit',code => reject(Error('Chrome exited: ' + code)));
|
|
49
|
+
chrome.stderr.on('data',data => { const m = String(data).match(/DevTools listening on (ws:\/\/[^\s]+)/); if(m) resolve(m[1]); });
|
|
50
|
+
});
|
|
51
|
+
ws = new WebSocket(endpoint); await new Promise(resolve => ws.on('open',resolve));
|
|
52
|
+
ws.on('message',message => { const data = JSON.parse(message), entry = pending.get(data.id); if(entry) { pending.delete(data.id); data.error ? entry.reject(Error(JSON.stringify(data.error))) : entry.resolve(data.result); } });
|
|
53
|
+
const {targetId} = await call('Target.createTarget',{url:'about:blank'});
|
|
54
|
+
const {sessionId} = await call('Target.attachToTarget',{targetId,flatten:true});
|
|
55
|
+
await call('Emulation.setDeviceMetricsOverride',{width:1200,height:900,deviceScaleFactor:1,mobile:false},sessionId);
|
|
56
|
+
const evaluate = async expression => { const r = await call('Runtime.evaluate',{expression,awaitPromise:true,returnByValue:true},sessionId); if(r.exceptionDetails) throw Error(JSON.stringify(r.exceptionDetails)); return r.result.value; };
|
|
57
|
+
await call('Page.navigate',{url:'http://127.0.0.1:' + server.address().port},sessionId);
|
|
58
|
+
for(let i=0;i<100;i++) { if(await evaluate('Boolean(window.layout)')) break; await new Promise(resolve=>setTimeout(resolve,100)); }
|
|
59
|
+
results.boot = await evaluate('window.bootErrors'); console.log('boot',results.boot);
|
|
60
|
+
assert.deepStrictEqual(results.boot, []);
|
|
61
|
+
for(const [name,config,limit,exact] of [
|
|
62
|
+
['numeric',{height:400},400,true], ['px',{height:'400px'},400,true],
|
|
63
|
+
['percent',{height:'80%'},400,true], ['auto',{height:'auto'},500,true],
|
|
64
|
+
['calc',{height:'calc(100% - 100px)'},400,true],
|
|
65
|
+
['border-box',{height:400,rootStyle:{boxSizing:'border-box',padding:'10px',border:'2px solid red'}},400,true],
|
|
66
|
+
['flex',{height:'100%',parent:{display:'flex',flexDirection:'column'}},500,true],
|
|
67
|
+
['grid',{height:'100%',parent:{display:'grid',gridTemplateRows:'500px'}},500,true],
|
|
68
|
+
['flex-header',{height:'auto',parent:{display:'flex',flexDirection:'column'},sibling:{height:'100px',flex:'none'},rootStyle:{flex:'1',minHeight:0}},400,true],
|
|
69
|
+
['flex-two-grow',{height:'50%',parent:{display:'flex',flexDirection:'column'},sibling:{height:'50%',flex:'1',minHeight:0},rootStyle:{flex:'1',minHeight:0}},250,true],
|
|
70
|
+
['grid-second-track',{height:'100%',parent:{display:'grid',gridTemplateRows:'100px 1fr'},sibling:{},rootStyle:{minHeight:0}},400,true],
|
|
71
|
+
['grid-second-track-half',{height:'50%',parent:{display:'grid',gridTemplateRows:'100px 1fr'},sibling:{},rootStyle:{minHeight:0}},200,true],
|
|
72
|
+
['max-many',{maxHeight:'80%'},400,false], ['max-few',{maxHeight:'80%',rows:1},400,false],
|
|
73
|
+
['max-calc',{maxHeight:'calc(100% - 100px)'},400,false], ['no-pager',{height:400,pager:false},400,true]
|
|
74
|
+
]) { const m = results[name] = await evaluate('layout.mount(' + JSON.stringify(config) + ')'); check(m,limit,exact); console.log('PASS ' + name); }
|
|
75
|
+
assert(results['max-few'].root.height < results['max-many'].root.height - 100, 'maxHeight preserves short content natural height');
|
|
76
|
+
results.priority = await evaluate('layout.mount({height:400,maxHeight:200})'); check(results.priority,400); assert.strictEqual(results.priority.maxHeight,null);
|
|
77
|
+
results.fallbackMax = await evaluate('layout.patch({height:null})'); check(results.fallbackMax,200,false); assert.strictEqual(results.fallbackMax.maxHeight,200-results.fallbackMax.pager.height);
|
|
78
|
+
results.resizeMount = await evaluate('layout.mount({height:"80%"})');
|
|
79
|
+
results.resize = await evaluate('layout.patch({parent:{height:"600px"}})'); check(results.resize,480);
|
|
80
|
+
results.pagerOff = await evaluate('layout.patch({pager:false})'); check(results.pagerOff,480);
|
|
81
|
+
results.pagerOn = await evaluate('layout.patch({pager:true})'); check(results.pagerOn,480);
|
|
82
|
+
results.pagerSize = await evaluate('layout.pagerSize()'); check(results.pagerSize,480);
|
|
83
|
+
assert(results.pagerSize.pager.height > results.pagerOn.pager.height && results.pagerSize.table.height < results.pagerOn.table.height);
|
|
84
|
+
results.pagerShrink = await evaluate('layout.pagerSize(0)'); check(results.pagerShrink,480); assert.strictEqual(results.pagerShrink.table.height,results.pagerOn.table.height);
|
|
85
|
+
if (results.observerMode === 'native') {
|
|
86
|
+
results.parentTheme = await evaluate('layout.parentTheme(true)'); check(results.parentTheme,480); assert(results.parentTheme.pager.height > results.pagerOn.pager.height);
|
|
87
|
+
results.parentThemeReset = await evaluate('layout.parentTheme(false)'); check(results.parentThemeReset,480);
|
|
88
|
+
}
|
|
89
|
+
await evaluate('layout.hide(true)'); results.show = await evaluate('layout.hide(false)'); check(results.show,480);
|
|
90
|
+
results.deactivated = await evaluate('layout.active(false)'); assert(!results.deactivated.observing);
|
|
91
|
+
results.inactiveMutation = await evaluate('layout.inactiveMutation()'); assert.strictEqual(results.inactiveMutation,0);
|
|
92
|
+
results.activated = await evaluate('layout.active(true)'); check(results.activated,480);
|
|
93
|
+
results.stable = await evaluate('layout.stable()'); assert(results.stable.delta <= 2);
|
|
94
|
+
results.clearHeight = await evaluate('layout.patch({height:null})'); assert.strictEqual(results.clearHeight.height,null); assert(!results.clearHeight.observing);
|
|
95
|
+
results.destroyed = await evaluate('layout.destroy()'); assert.deepStrictEqual(results.destroyed,{delta:0,scheduleDelta:0,observing:false});
|
|
96
|
+
results.natural = await evaluate('layout.mount({height:"100%",rows:2,parent:{height:"auto"}})'); assert.strictEqual(results.natural.height,null); assert(results.natural.observing);
|
|
97
|
+
results.naturalStable = await evaluate('layout.stable()'); assert(results.naturalStable.delta <= 2);
|
|
98
|
+
results.naturalDestroyed = await evaluate('layout.destroy()'); assert.deepStrictEqual(results.naturalDestroyed,{delta:0,scheduleDelta:0,observing:false});
|
|
99
|
+
results.finalErrors = await evaluate('({boot:window.bootErrors,vue:window.layoutErrors})'); assert.deepStrictEqual(results.finalErrors,{boot:[],vue:[]});
|
|
100
|
+
console.log('PASS lifecycle/resize/pager/stability/destruction');
|
|
101
|
+
})().catch(error => { results.failure = error.stack || String(error); console.error(error); process.exitCode=1; }).finally(() => {
|
|
102
|
+
fs.writeFileSync(path.join(output,'results.json'),JSON.stringify(results,null,2)); console.log('Result directory: ' + output);
|
|
103
|
+
if(ws) ws.close(); if(chrome) chrome.kill(); if(server) server.close();
|
|
104
|
+
setTimeout(()=>process.exit(process.exitCode || 0),500);
|
|
105
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import 'babel-polyfill';
|
|
2
|
+
import Vue from 'vue';
|
|
3
|
+
import Element from 'element-ui';
|
|
4
|
+
import 'element-ui/lib/theme-chalk/index.css';
|
|
5
|
+
import Grid from '../src/ux-grid.vue';
|
|
6
|
+
import { version as runtimeVersion } from 'vxe-table/lib/ui';
|
|
7
|
+
import '../../../lib/theme-chalk/ux-grid.css';
|
|
8
|
+
Vue.use(Element);
|
|
9
|
+
window.layoutErrors = [];
|
|
10
|
+
Vue.config.errorHandler = error => layoutErrors.push(String(error.stack || error));
|
|
11
|
+
const theme = document.createElement('style');
|
|
12
|
+
theme.textContent = '.layout-padded-pager .myPagination { padding-block: 12px !important; }';
|
|
13
|
+
document.head.appendChild(theme);
|
|
14
|
+
const wait = (ms = 350) => new Promise(resolve => setTimeout(resolve, ms));
|
|
15
|
+
const rect = el => {
|
|
16
|
+
if (!el) return null;
|
|
17
|
+
const r = el.getBoundingClientRect();
|
|
18
|
+
return { top: r.top, bottom: r.bottom, height: r.height, width: r.width };
|
|
19
|
+
};
|
|
20
|
+
let vm, wrapper, calls, schedules;
|
|
21
|
+
window.layout = {
|
|
22
|
+
async mount(config) {
|
|
23
|
+
if (vm) { vm.$destroy(); vm.$el.remove(); }
|
|
24
|
+
calls = 0;
|
|
25
|
+
schedules = 0;
|
|
26
|
+
vm = new Vue({
|
|
27
|
+
data: () => ({ config, active: true, hidden: false }),
|
|
28
|
+
render(h) {
|
|
29
|
+
const c = this.config;
|
|
30
|
+
return h('div', { style: Object.assign({ width: '800px', height: '500px' }, c.parent, this.hidden ? { display: 'none' } : {}) }, [
|
|
31
|
+
c.sibling ? h('div', { style: c.sibling }, ['Sibling']) : null,
|
|
32
|
+
h('keep-alive', [this.active ? h(Grid, { ref: 'grid', style: c.rootStyle || {}, props: {
|
|
33
|
+
data: Array.from({ length: c.rows === undefined ? 100 : c.rows }, (_, i) => ({ id: i, name: 'Row ' + i })),
|
|
34
|
+
columns: [{ field: 'name', title: 'Name', width: 160 }], height: c.height, maxHeight: c.maxHeight,
|
|
35
|
+
paginationShow: c.pager !== false, total: 100, pageSize: 10
|
|
36
|
+
} }) : h('div')])
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
}).$mount();
|
|
40
|
+
document.body.appendChild(vm.$el);
|
|
41
|
+
wrapper = vm.$refs.grid;
|
|
42
|
+
const original = wrapper.setHeight;
|
|
43
|
+
wrapper.setHeight = function () { calls++; return original.apply(this, arguments); };
|
|
44
|
+
const schedule = wrapper.scheduleHeight;
|
|
45
|
+
wrapper.scheduleHeight = function () { schedules++; return schedule.apply(this, arguments); };
|
|
46
|
+
await wait();
|
|
47
|
+
return this.measure();
|
|
48
|
+
},
|
|
49
|
+
measure() {
|
|
50
|
+
const root = wrapper.$el;
|
|
51
|
+
const style = getComputedStyle(root);
|
|
52
|
+
return { root: rect(root), parent: rect(vm.$el), table: rect(wrapper.$refs.singleTable.$el),
|
|
53
|
+
body: rect(root.querySelector('.vxe-table--main-wrapper .vxe-table--body-wrapper')),
|
|
54
|
+
pager: rect(wrapper.$refs.myPagination), height: wrapper.newHeight, maxHeight: wrapper.newMaxHeight,
|
|
55
|
+
bottomInset: parseFloat(style.paddingBottom) + parseFloat(style.borderBottomWidth),
|
|
56
|
+
runtimeVersion, calls, observing: !!wrapper._stopHeightObserver, errors: layoutErrors.slice() };
|
|
57
|
+
},
|
|
58
|
+
async patch(config) { vm.config = Object.assign({}, vm.config, config); await wait(); return this.measure(); },
|
|
59
|
+
async hide(value) { vm.hidden = value; await wait(); return this.measure(); },
|
|
60
|
+
async active(value) { vm.active = value; await wait(); return this.measure(); },
|
|
61
|
+
async stable() { const before = calls; await wait(650); return { delta: calls - before, measurement: this.measure() }; },
|
|
62
|
+
async pagerSize(size = 20) { wrapper.$refs.myPagination.style.padding = size + 'px 0'; await wait(); return this.measure(); },
|
|
63
|
+
async parentTheme(value) { vm.$el.classList.toggle('layout-padded-pager', value); await wait(); return this.measure(); },
|
|
64
|
+
async inactiveMutation() { const before = schedules; wrapper.$refs.myPagination.style.padding = '10px'; await wait(); return schedules - before; },
|
|
65
|
+
async destroy() { const root = vm.$el, pager = wrapper.$refs.myPagination; vm.$destroy(); root.remove(); const before = calls, beforeSchedules = schedules; if (pager) pager.style.padding = '15px'; window.dispatchEvent(new Event('resize')); await wait(); return { delta: calls - before, scheduleDelta: schedules - beforeSchedules, observing: !!wrapper._stopHeightObserver }; }
|
|
66
|
+
};
|
|
@@ -168,12 +168,16 @@ function call(method, params = {}, sessionId) {
|
|
|
168
168
|
const label={select:'Beta',remote:'Beta',multiple:'Zero, False',cascader:'Parent / Child',cascaderLazy:'Parent / Child',cascaderMultiple:'Parent / Child, Parent / Other',date:'2026-09-19',dateObject:'2026-09-19',datetime:'2026-09-19 13:24:56',dateRange:'2026-09-20 - 2026-09-22',time:'14:24:56',timeObject:'14:24:56',timeAmPm:'02:24:56 PM',timeRange:'12:24:56 - 14:25:57'}[kind];
|
|
169
169
|
assert(suite.display.text.includes(label),kind+' closed display retains correct label/time format');
|
|
170
170
|
}
|
|
171
|
-
if(kind==='cascaderLazy') {
|
|
171
|
+
if(kind==='cascaderLazy' && version==='legacy') {
|
|
172
172
|
assert.deepStrictEqual(suite.display.errors,[]);
|
|
173
173
|
console.log(version+' '+kind+' selection/display passed');
|
|
174
|
-
continue; //
|
|
174
|
+
continue; // Legacy has no remount cache; current must verify its added restoration.
|
|
175
175
|
}
|
|
176
176
|
await evaluate('rendererTest.activate()');
|
|
177
|
+
if(kind==='cascaderLazy') {
|
|
178
|
+
const restored = await evaluate("document.querySelector('tbody .el-cascader input').value");
|
|
179
|
+
assert.equal(restored,'Parent / Child','lazy editor restores selected path on remount');
|
|
180
|
+
}
|
|
177
181
|
if(kind==='multiple') {
|
|
178
182
|
await evaluate('rendererTest.resetCallbacks()');
|
|
179
183
|
await click('tbody .el-tag__close');
|
|
@@ -201,6 +205,18 @@ function call(method, params = {}, sessionId) {
|
|
|
201
205
|
assert.equal(suite.reselected.callbacks.filter(e=>e.type==='change').length,1);
|
|
202
206
|
assert.equal(suite.reselected.validationCalls.length,1);
|
|
203
207
|
}
|
|
208
|
+
if(kind==='cascaderLazy') {
|
|
209
|
+
await evaluate('rendererTest.resetCallbacks()');
|
|
210
|
+
if(!await evaluate("rendererTest.hasVisible('.el-cascader__dropdown')")) await click('tbody .el-input__inner');
|
|
211
|
+
await click('.el-cascader-node__label','Parent');
|
|
212
|
+
await click('.el-cascader-node__label','Child');
|
|
213
|
+
suite.reselected=await evaluate('rendererTest.state()');persist();
|
|
214
|
+
assert.deepStrictEqual(suite.reselected.value,['p','c']);
|
|
215
|
+
assert(suite.reselected.active);
|
|
216
|
+
assert.equal(suite.reselected.callbacks.filter(e=>e.type==='change').length,1);
|
|
217
|
+
assert.equal(suite.reselected.validationCalls.length,1);
|
|
218
|
+
assert.equal(suite.reselected.lazyCalls,suite.selected.lazyCalls,'remount reuses loaded children without refetch');
|
|
219
|
+
}
|
|
204
220
|
await outside();
|
|
205
221
|
suite.outside=await evaluate('rendererTest.state()');persist();
|
|
206
222
|
assert(!suite.outside.active,kind+' outside click closes edit');
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Run after npm run lib. Exercise the actual script-tag UMD product, without
|
|
2
|
+
// CommonJS/AMD globals or a bundler hiding missing browser runtime dependencies.
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const root = path.resolve(__dirname, '../../..');
|
|
7
|
+
const dependencyRoot = process.env.UX_GRID_TEST_DEPENDENCIES || root;
|
|
8
|
+
const { JSDOM, VirtualConsole } = require(require.resolve('jsdom', { paths: [dependencyRoot] }));
|
|
9
|
+
const errors = [];
|
|
10
|
+
const virtualConsole = new VirtualConsole();
|
|
11
|
+
virtualConsole.on('jsdomError', error => errors.push(error));
|
|
12
|
+
const dom = new JSDOM('<html><body><script src="/lib/index.js"></script></body></html>', {
|
|
13
|
+
url: 'http://localhost/', runScripts: 'outside-only', pretendToBeVisual: true, virtualConsole
|
|
14
|
+
});
|
|
15
|
+
const { window } = dom;
|
|
16
|
+
let vm;
|
|
17
|
+
(async () => {
|
|
18
|
+
window.eval(fs.readFileSync(require.resolve('vue/dist/vue.js'), 'utf8'));
|
|
19
|
+
window.Vue.config.productionTip = false;
|
|
20
|
+
window.Vue.config.devtools = false;
|
|
21
|
+
window.Vue.config.errorHandler = error => errors.push(error);
|
|
22
|
+
window.eval(fs.readFileSync(path.join(root, 'lib/index.js'), 'utf8'));
|
|
23
|
+
const product = window.UMYUI;
|
|
24
|
+
assert(product && product.UxGrid && product.UxTableColumn && product.interceptor);
|
|
25
|
+
assert.strictEqual(product.version, require(path.join(root, 'package.json')).version);
|
|
26
|
+
assert(window.Vue.options.components.UxGrid, 'script tag automatically installs UxGrid');
|
|
27
|
+
vm = new window.Vue({
|
|
28
|
+
render(h) { return h(product.UxGrid, { props: {
|
|
29
|
+
data: [{ name: 'umd-row' }], columns: [{ field: 'name' }], height: 240,
|
|
30
|
+
paginationShow: true, total: 42, layout: 'total'
|
|
31
|
+
} }); }
|
|
32
|
+
}).$mount();
|
|
33
|
+
window.document.body.appendChild(vm.$el);
|
|
34
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
35
|
+
assert(vm.$el.textContent.includes('umd-row'), 'UMD product mounts real VXE row');
|
|
36
|
+
window.eval(fs.readFileSync(path.join(root, 'lib/umd/locale/en.js'), 'utf8'));
|
|
37
|
+
assert(product.lang && product.lang.en, 'UMD locale script registers language');
|
|
38
|
+
product.locale(product.lang.en);
|
|
39
|
+
const forceTree = node => { node.$forceUpdate(); node.$children.forEach(forceTree); };
|
|
40
|
+
forceTree(vm);
|
|
41
|
+
await new Promise(resolve => setTimeout(resolve, 80));
|
|
42
|
+
assert(vm.$el.textContent.includes('Total 42'), 'UMD locale changes pagination labels');
|
|
43
|
+
assert.deepStrictEqual(errors, []);
|
|
44
|
+
console.log('delivery UMD: browser-global product, auto-install, real rows and locale script passed');
|
|
45
|
+
})().catch(error => { console.error(error); process.exitCode = 1; }).finally(() => {
|
|
46
|
+
if (vm) vm.$destroy();
|
|
47
|
+
dom.window.close();
|
|
48
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Real Element Cascader lifecycle/store behavior in jsdom. Direct wrapper calls
|
|
2
|
+
// below separately verify loader return/this/throw contracts, not UI interaction.
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const root = process.env.UX_GRID_TEST_DEPENDENCIES;
|
|
5
|
+
if (!root) throw Error('Set UX_GRID_TEST_DEPENDENCIES');
|
|
6
|
+
const external = name => require(require.resolve(name, { paths: [root] }));
|
|
7
|
+
const { JSDOM } = external('jsdom');
|
|
8
|
+
const dom = new JSDOM('<html><body><script></script></body></html>', { url: 'http://localhost/' });
|
|
9
|
+
for (const key of ['window', 'document', 'Element', 'HTMLElement', 'Node', 'MutationObserver', 'getComputedStyle', 'name', 'Image']) global[key] = dom.window[key];
|
|
10
|
+
Object.defineProperty(global, 'navigator', { configurable: true, value: dom.window.navigator });
|
|
11
|
+
global.requestAnimationFrame = cb => setTimeout(cb, 0);
|
|
12
|
+
global.cancelAnimationFrame = clearTimeout;
|
|
13
|
+
const Vue = require('vue');
|
|
14
|
+
for (const base of [root, require.resolve('vxe-table'), require.resolve('vxe-pc-ui')]) require.cache[require.resolve('vue', { paths: [base] })] = require.cache[require.resolve('vue')];
|
|
15
|
+
Vue.config.productionTip = false;
|
|
16
|
+
Vue.config.devtools = false;
|
|
17
|
+
const errors = [];
|
|
18
|
+
Vue.config.errorHandler = error => errors.push(error.stack || String(error));
|
|
19
|
+
Vue.use(external('element-ui'));
|
|
20
|
+
require('../../../lib/ux-grid.js');
|
|
21
|
+
const { renderer } = require('vxe-table/lib/ui');
|
|
22
|
+
const cascader = renderer.get('ElCascader');
|
|
23
|
+
const wait = () => new Promise(resolve => setTimeout(resolve, 40));
|
|
24
|
+
const mounted = [];
|
|
25
|
+
function fixture(options, loader) {
|
|
26
|
+
const requests = [];
|
|
27
|
+
const row = { value: [] };
|
|
28
|
+
const column = { field: 'value' };
|
|
29
|
+
const renderOpts = { name: 'ElCascader', props: { clearable: true, options, props: { lazy: true, lazyLoad: loader || function(node, resolve) { requests.push({ node, resolve, receiver: this }); } } } };
|
|
30
|
+
const params = { row, column, $table: { updateStatus() {} } };
|
|
31
|
+
const vm = new Vue({ data: { renderOpts, row, shown: true }, render(h) {
|
|
32
|
+
return h('div', this.shown ? cascader.renderTableEdit(h, this.renderOpts, params) : []);
|
|
33
|
+
} }).$mount();
|
|
34
|
+
document.body.appendChild(vm.$el);
|
|
35
|
+
mounted.push(vm);
|
|
36
|
+
return { vm, requests, renderOpts, component: () => vm.$children[0] };
|
|
37
|
+
}
|
|
38
|
+
function nodes(component) { return component.$refs.panel.store.getFlattedNodes(false, false); }
|
|
39
|
+
async function expand(f, label) {
|
|
40
|
+
f.vm.$el.querySelector('input').click(); await wait();
|
|
41
|
+
const el = Array.from(document.querySelectorAll('.el-cascader-node')).find(el => el.textContent.trim() === label);
|
|
42
|
+
assert(el, 'real cascader option ' + label); el.click(); await wait();
|
|
43
|
+
}
|
|
44
|
+
(async () => {
|
|
45
|
+
// Initial root request binds to the mounted panel; two callbacks cannot append twice.
|
|
46
|
+
const rootLoad = fixture([]); await wait();
|
|
47
|
+
assert.equal(rootLoad.requests.length, 1);
|
|
48
|
+
const rootRequest = rootLoad.requests[0];
|
|
49
|
+
rootRequest.resolve([{ value: 'a', label: 'A', leaf: true }]); await wait();
|
|
50
|
+
rootRequest.resolve([{ value: 'duplicate', label: 'Duplicate', leaf: true }]); await wait();
|
|
51
|
+
assert.deepStrictEqual(nodes(rootLoad.component()).map(n => n.value), ['a']);
|
|
52
|
+
rootLoad.vm.$destroy();
|
|
53
|
+
// Replacing options invalidates the pending root callback without touching current store.
|
|
54
|
+
const rootStale = fixture([]); await wait();
|
|
55
|
+
const staleRoot = rootStale.requests[0];
|
|
56
|
+
rootStale.renderOpts.props.options = [{ value: 'replacement', label: 'Replacement', leaf: true }]; await wait();
|
|
57
|
+
staleRoot.resolve([{ value: 'stale', label: 'Stale', leaf: true }]); await wait();
|
|
58
|
+
assert.deepStrictEqual(nodes(rootStale.component()).map(n => n.value), ['replacement']);
|
|
59
|
+
rootStale.vm.$destroy();
|
|
60
|
+
// Two live generations, B resolves before A: obsolete A cannot contaminate
|
|
61
|
+
// either Element's replacement store or the renderer's hydrated remount cache.
|
|
62
|
+
const generations = fixture([]); await wait();
|
|
63
|
+
const requestA = generations.requests[0];
|
|
64
|
+
generations.renderOpts.props.options = []; await wait();
|
|
65
|
+
assert.equal(generations.requests.length, 2, 'replacing empty options starts generation B');
|
|
66
|
+
generations.requests[1].resolve([{ value: 'B', label: 'Current B', leaf: true }]); await wait();
|
|
67
|
+
requestA.resolve([{ value: 'A', label: 'Obsolete A', leaf: true }]); await wait();
|
|
68
|
+
assert.deepStrictEqual(nodes(generations.component()).map(n => n.value), ['B']);
|
|
69
|
+
generations.vm.shown = false; await wait();
|
|
70
|
+
generations.vm.shown = true; await wait();
|
|
71
|
+
assert.deepStrictEqual(nodes(generations.component()).map(n => n.value), ['B'], 'remount hydrates only the winning generation');
|
|
72
|
+
assert.equal(generations.requests.length, 2, 'hydrated root does not issue a redundant loader request');
|
|
73
|
+
generations.vm.$destroy();
|
|
74
|
+
// Unmount must leave the destroyed Element store untouched.
|
|
75
|
+
const destroyed = fixture([]); await wait();
|
|
76
|
+
const oldComponent = destroyed.component();
|
|
77
|
+
const oldStore = oldComponent.$refs.panel.store;
|
|
78
|
+
destroyed.vm.shown = false; await wait();
|
|
79
|
+
destroyed.requests[0].resolve([{ value: 'late', label: 'Late', leaf: true }]); await wait();
|
|
80
|
+
assert.equal(oldComponent._isDestroyed, true);
|
|
81
|
+
assert.deepStrictEqual(oldStore.getFlattedNodes(false, false), []);
|
|
82
|
+
destroyed.vm.$destroy();
|
|
83
|
+
// Child request and options replacement, with a real expand click.
|
|
84
|
+
const child = fixture([{ value: 'parent', label: 'Parent' }]); await wait();
|
|
85
|
+
await expand(child, 'Parent');
|
|
86
|
+
assert.equal(child.requests.length, 1);
|
|
87
|
+
assert.equal(child.requests[0].node.value, 'parent');
|
|
88
|
+
child.renderOpts.props.options = [{ value: 'fresh', label: 'Fresh', leaf: true }]; await wait();
|
|
89
|
+
child.requests[0].resolve([{ value: 'lateChild', label: 'Late Child', leaf: true }]); await wait();
|
|
90
|
+
assert.deepStrictEqual(nodes(child.component()).map(n => n.value), ['fresh']);
|
|
91
|
+
child.vm.$destroy();
|
|
92
|
+
const childOnce = fixture([{ value: 'parent', label: 'Once Parent' }]); await wait();
|
|
93
|
+
await expand(childOnce, 'Once Parent');
|
|
94
|
+
childOnce.requests[0].resolve([{ value: 'child', label: 'Once Child', leaf: true }]); await wait();
|
|
95
|
+
const activeStore = childOnce.component().$refs.panel.store;
|
|
96
|
+
childOnce.vm.$forceUpdate(); await wait();
|
|
97
|
+
assert.strictEqual(childOnce.component().$refs.panel.store, activeStore, 'unrelated renderer rerender must preserve active lazy store');
|
|
98
|
+
childOnce.requests[0].resolve([{ value: 'duplicate', label: 'Duplicate', leaf: true }]); await wait();
|
|
99
|
+
assert.deepStrictEqual(nodes(childOnce.component()).map(n => n.value), ['parent', 'child']);
|
|
100
|
+
childOnce.vm.shown = false; await wait();
|
|
101
|
+
childOnce.vm.shown = true; await wait();
|
|
102
|
+
childOnce.renderOpts.props.options[0].label = 'Renamed';
|
|
103
|
+
Vue.set(childOnce.renderOpts.props.options[0], 'disabled', true); await wait();
|
|
104
|
+
assert.equal(nodes(childOnce.component())[0].label, 'Renamed', 'external label edits reach hydrated nodes');
|
|
105
|
+
assert.equal(nodes(childOnce.component())[0].isDisabled, true, 'external disabled edits reach hydrated nodes');
|
|
106
|
+
Vue.set(childOnce.renderOpts.props.options[0], 'children', [{ value: 'newChild', label: 'New Child', leaf: true }]); await wait();
|
|
107
|
+
assert.deepStrictEqual(nodes(childOnce.component()).map(n => n.value), ['parent', 'newChild'], 'external children edits replace cached children');
|
|
108
|
+
childOnce.vm.$destroy();
|
|
109
|
+
const childDestroyed = fixture([{ value: 'parent', label: 'Doomed Parent' }]); await wait();
|
|
110
|
+
await expand(childDestroyed, 'Doomed Parent');
|
|
111
|
+
const oldChildStore = childDestroyed.component().$refs.panel.store;
|
|
112
|
+
childDestroyed.vm.shown = false; await wait();
|
|
113
|
+
childDestroyed.requests[0].resolve([{ value: 'late', label: 'Late', leaf: true }]); await wait();
|
|
114
|
+
assert.deepStrictEqual(oldChildStore.getFlattedNodes(false, false).map(n => n.value), ['parent']);
|
|
115
|
+
childDestroyed.vm.$destroy();
|
|
116
|
+
// Same options identity, but Element resets its store after deep label change.
|
|
117
|
+
const sameOptions = fixture([{ value: 'parent', label: 'Before' }]); await wait();
|
|
118
|
+
await expand(sameOptions, 'Before');
|
|
119
|
+
sameOptions.renderOpts.props.options[0].label = 'After'; await wait();
|
|
120
|
+
sameOptions.requests[0].resolve([{ value: 'obsolete', label: 'Obsolete', leaf: true }]); await wait();
|
|
121
|
+
assert.deepStrictEqual(nodes(sameOptions.component()).map(n => n.value), ['parent']);
|
|
122
|
+
sameOptions.vm.$destroy();
|
|
123
|
+
// The wrapper itself retains the exact business loader call contract.
|
|
124
|
+
const host = new Vue();
|
|
125
|
+
const row = { value: [] }, column = { field: 'value' };
|
|
126
|
+
const params = { row, column, $table: { updateStatus() {} } };
|
|
127
|
+
const receiver = {}, returned = {}, failure = Error('business failure');
|
|
128
|
+
let captured;
|
|
129
|
+
const options = { name: 'ElCascader', props: { options: [{ value: 'root' }], props: { lazy: true, lazyLoad(node, resolve) { assert.strictEqual(this, receiver); captured = resolve; return returned; } } } };
|
|
130
|
+
let vnode = cascader.renderTableEdit(host.$createElement, options, params)[0];
|
|
131
|
+
let resolutions = 0;
|
|
132
|
+
assert.strictEqual(vnode.componentOptions.propsData.props.lazyLoad.call(receiver, { root: true }, () => resolutions++), returned);
|
|
133
|
+
captured([]); captured([]); assert.equal(resolutions, 1);
|
|
134
|
+
options.props.props.lazyLoad = () => { throw failure; };
|
|
135
|
+
vnode = cascader.renderTableEdit(host.$createElement, options, params)[0];
|
|
136
|
+
assert.throws(() => vnode.componentOptions.propsData.props.lazyLoad({ root: true }, () => {}), error => error === failure);
|
|
137
|
+
host.$destroy();
|
|
138
|
+
assert.deepStrictEqual(errors, []);
|
|
139
|
+
console.log('renderer async passed: real Element root/child/stale/store/destroy lifecycle; direct loader this/return/throw/once contracts');
|
|
140
|
+
})().catch(error => { console.error(error); process.exitCode = 1; }).finally(() => { mounted.forEach(vm => vm.$destroy()); dom.window.close(); });
|
|
@@ -37,7 +37,7 @@ let validations = 0;
|
|
|
37
37
|
columns: [
|
|
38
38
|
{ field: 'choice', editRender: { name: 'ElSelect', options, props: { clearable: true } } },
|
|
39
39
|
{ field: 'cascade', editRender: { name: 'ElCascader', options: [{ value: 'root', label: 'Root', children: [{ value: 'leaf', label: 'Leaf' }] }] } },
|
|
40
|
-
{ field: 'lazy', editRender: { name: 'ElCascader', props: { options: [{ value: 'parent', label: 'Lazy Parent' }], props: { lazy: true, lazyLoad(node, resolve) { setTimeout(() => resolve([{ value: 'child', label: 'Lazy Child', leaf: true }]), 0); } } } } },
|
|
40
|
+
{ field: 'lazy', editRender: { name: 'ElCascader', props: { clearable: true, options: [{ value: 'parent', label: 'Lazy Parent' }], props: { lazy: true, lazyLoad(node, resolve) { setTimeout(() => resolve([{ value: 'child', label: 'Lazy Child', leaf: true }]), 0); } } } } },
|
|
41
41
|
{ field: 'time', editRender: { name: 'ElTimePicker', props: { valueFormat: 'HH:mm:ss' } } }
|
|
42
42
|
]
|
|
43
43
|
} }); } }).$mount();
|
|
@@ -120,6 +120,7 @@ let validations = 0;
|
|
|
120
120
|
vm.$el.querySelector('.el-cascader input').click(); await wait();
|
|
121
121
|
const parent = Array.from(document.querySelectorAll('.el-cascader-node')).find(el => el.textContent.trim() === 'Lazy Parent');
|
|
122
122
|
assert(parent); parent.click(); await wait();
|
|
123
|
+
table.$forceUpdate(); await wait();
|
|
123
124
|
const child = Array.from(document.querySelectorAll('.el-cascader-node')).find(el => el.textContent.trim() === 'Lazy Child');
|
|
124
125
|
assert(child); child.click(); await wait();
|
|
125
126
|
assert.deepStrictEqual(errors, []);
|
|
@@ -132,12 +133,27 @@ let validations = 0;
|
|
|
132
133
|
const lazyRenderer = renderer.get('ElCascader');
|
|
133
134
|
assert.equal(lazyRenderer.tableExportMethod(lazyParams), 'Lazy Parent / Lazy Child');
|
|
134
135
|
const lazyOptions = lazyColumn.editRender;
|
|
135
|
-
lazyOptions.props.options =
|
|
136
|
+
lazyOptions.props.options[0].label = 'Renamed Parent';
|
|
136
137
|
assert.equal(lazyRenderer.renderTableCell(null, lazyOptions, lazyParams)[0], 'Renamed Parent / Lazy Child', 'latest static prefix wins over lazy cache');
|
|
137
138
|
lazyOptions.props.separator = '>';
|
|
138
139
|
assert.equal(lazyRenderer.renderTableCell(null, lazyOptions, lazyParams)[0], 'Renamed Parent > Lazy Child');
|
|
139
140
|
lazyOptions.props.showAllLevels = false;
|
|
140
141
|
assert.equal(lazyRenderer.renderTableCell(null, lazyOptions, lazyParams)[0], 'Lazy Child');
|
|
142
|
+
await grid.setActiveCell(row, 'lazy'); await wait();
|
|
143
|
+
assert.equal(vm.$el.querySelector('.el-cascader input').value, 'Lazy Child', 'lazy selection restored after editor remount');
|
|
144
|
+
vm.$el.querySelector('.el-cascader').dispatchEvent(new window.MouseEvent('mouseenter', { bubbles: false }));
|
|
145
|
+
await wait();
|
|
146
|
+
const lazyClear = vm.$el.querySelector('.el-cascader .el-icon-circle-close');
|
|
147
|
+
assert(lazyClear, 'restored lazy selection has a real clear control');
|
|
148
|
+
lazyClear.click(); await wait();
|
|
149
|
+
assert.deepStrictEqual(Array.from(row.lazy), [], 'real clear updates lazy model after remount');
|
|
150
|
+
vm.$el.querySelector('.el-cascader input').click(); await wait();
|
|
151
|
+
Array.from(document.querySelectorAll('.el-cascader-node')).find(el => el.textContent.trim() === 'Renamed Parent').click(); await wait();
|
|
152
|
+
Array.from(document.querySelectorAll('.el-cascader-node')).find(el => el.textContent.trim() === 'Lazy Child').click(); await wait();
|
|
153
|
+
assert.deepStrictEqual(Array.from(row.lazy), ['parent', 'child'], 'restored lazy children remain selectable after clear');
|
|
154
|
+
await grid.clearActived(); await wait();
|
|
155
|
+
lazyOptions.props.options = [{ value: 'parent', label: 'Replacement Parent' }];
|
|
156
|
+
assert.equal(lazyRenderer.renderTableCell(null, lazyOptions, lazyParams)[0], 'Replacement Parent', 'replaced option identity cannot reuse obsolete lazy labels');
|
|
141
157
|
lazyOptions.props.options = [{ value: 'parent', label: 'New Parent', children: [{ value: 'child', label: 'New Child' }] }];
|
|
142
158
|
assert.equal(lazyRenderer.renderTableCell(null, lazyOptions, lazyParams)[0], 'New Child', 'fresh complete options win over cached labels');
|
|
143
159
|
row.lazy = [];
|
|
@@ -160,6 +176,21 @@ let validations = 0;
|
|
|
160
176
|
const dateFilter = renderer.get('ElDatePicker').tableFilterDefaultMethod;
|
|
161
177
|
const match = value => dateFilter({ option: { data: ['2026-09-01', '2026-09-17'] }, row: { value }, column: { field: 'value', filterRender: { props: { type: 'dates', valueFormat: 'yyyy-MM-dd' } } } });
|
|
162
178
|
assert(match('2026-09-01')); assert(match('2026-09-17')); assert(!match('2026-09-10')); assert(!match(null));
|
|
179
|
+
const filter = (name, value, data, props) => renderer.get(name).tableFilterDefaultMethod({ option: { data }, row: { value }, column: { field: 'value', filterRender: { props } } });
|
|
180
|
+
assert(filter('ElDatePicker', new Date(2026, 8, 17, 21), new Date(2026, 8, 17, 8), { type: 'date' }), 'date filter ignores time on Date models');
|
|
181
|
+
assert(filter('ElDatePicker', '17/09/2026', ['01/09/2026', '30/09/2026'], { type: 'daterange', valueFormat: 'dd/MM/yyyy' }), 'custom formatted range compares chronological dates');
|
|
182
|
+
assert(!filter('ElDatePicker', '01/10/2026', ['01/09/2026', '30/09/2026'], { type: 'daterange', valueFormat: 'dd/MM/yyyy' }));
|
|
183
|
+
assert(filter('ElDatePicker', '2026-09-17 14:25:00', ['2026-09-17 14:24:59', '2026-09-17 14:25:00'], { type: 'datetimerange', valueFormat: 'yyyy-MM-dd HH:mm:ss' }));
|
|
184
|
+
assert(!filter('ElDatePicker', '2026-09-17 14:25:01', ['2026-09-17 14:24:59', '2026-09-17 14:25:00'], { type: 'datetimerange', valueFormat: 'yyyy-MM-dd HH:mm:ss' }));
|
|
185
|
+
assert(filter('ElDatePicker', '2026-09', ['2026-08', '2026-10'], { type: 'monthrange', valueFormat: 'yyyy-MM' }));
|
|
186
|
+
for (const invalid of [null, undefined, '', 'invalid']) {
|
|
187
|
+
assert(!filter('ElDatePicker', invalid, invalid, { type: 'date' }), 'invalid date cannot equal invalid date');
|
|
188
|
+
assert(!filter('ElTimePicker', invalid, invalid, { valueFormat: 'HH:mm:ss' }), 'invalid time cannot equal invalid time');
|
|
189
|
+
assert(!filter('ElTimePicker', '14:24:56', [invalid, '18:00:00'], { isRange: true, valueFormat: 'HH:mm:ss' }));
|
|
190
|
+
}
|
|
191
|
+
assert(filter('ElTimePicker', '02:24:56 PM', ['01:00:00 PM', '02:24:56 PM'], { isRange: true, valueFormat: 'hh:mm:ss A' }));
|
|
192
|
+
assert(!filter('ElTimePicker', '02:24:57 PM', ['01:00:00 PM', '02:24:56 PM'], { isRange: true, valueFormat: 'hh:mm:ss A' }));
|
|
193
|
+
assert(!filter('ElTimePicker', '14:24:56', ['18:00:00', '13:00:00'], { isRange: true, valueFormat: 'HH:mm:ss' }));
|
|
163
194
|
assert.deepStrictEqual(errors, []);
|
|
164
195
|
console.log('renderer boundaries passed: dynamic real registry, veto/priority/reload, Element Select/Cascader DOM, date/time formats and dates filter');
|
|
165
196
|
})().catch(error => { console.error(error); process.exitCode = 1; }).finally(() => { if (vm) vm.$destroy(); dom.window.close(); });
|