umy-ui-table 1.2.4 → 1.2.5

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.
Files changed (59) hide show
  1. package/lib/button.js +1 -1
  2. package/lib/icon.js +1 -1
  3. package/lib/index.js +1 -1
  4. package/lib/locale/format.js +1 -1
  5. package/lib/locale/index.js +1 -72
  6. package/lib/mixins/locale.js +1 -1
  7. package/lib/theme-chalk/button.css +1 -1
  8. package/lib/theme-chalk/icon.css +1 -1
  9. package/lib/theme-chalk/iconfont.1785242763644.ttf +0 -0
  10. package/lib/theme-chalk/iconfont.1785242763644.woff +0 -0
  11. package/lib/theme-chalk/iconfont.1785242763644.woff2 +0 -0
  12. package/lib/theme-chalk/index.css +1 -1
  13. package/lib/theme-chalk/u-table.css +1 -1
  14. package/lib/theme-chalk/ux-grid.css +1 -1
  15. package/lib/u-table-column.js +1 -1
  16. package/lib/u-table.js +1 -1
  17. package/lib/umy-ui.common.js +1 -1
  18. package/lib/utils/util.js +1 -1
  19. package/lib/ux-grid.js +1 -1
  20. package/lib/ux-table-column.js +1 -1
  21. package/package.json +4 -3
  22. package/packages/.DS_Store +0 -0
  23. package/packages/input/.DS_Store +0 -0
  24. package/packages/pagination/.DS_Store +0 -0
  25. package/packages/scrollbar/.DS_Store +0 -0
  26. package/packages/select/.DS_Store +0 -0
  27. package/packages/table/.DS_Store +0 -0
  28. package/packages/tag/.DS_Store +0 -0
  29. package/packages/tools/.DS_Store +0 -0
  30. package/packages/tools/locale/index.js +2 -48
  31. package/packages/tools/utils/.DS_Store +0 -0
  32. package/packages/tooltip/.DS_Store +0 -0
  33. package/packages/u-table/.DS_Store +0 -0
  34. package/packages/ux-grid/.DS_Store +0 -0
  35. package/packages/ux-grid/mixins/index.js +133 -25
  36. package/packages/ux-grid/src/dialog-draft.js +75 -0
  37. package/packages/ux-grid/src/element-renderer.js +784 -0
  38. package/packages/ux-grid/src/grid-height.js +65 -0
  39. package/packages/ux-grid/src/grid-kernel.js +14 -0
  40. package/packages/ux-grid/src/setup.js +42 -0
  41. package/packages/ux-grid/src/ux-grid.vue +215 -57
  42. package/packages/ux-grid/test/browser-acceptance.cjs +192 -0
  43. package/packages/ux-grid/test/browser-baseline-loader.cjs +10 -0
  44. package/packages/ux-grid/test/browser-entry.js +242 -0
  45. package/packages/ux-grid/test/browser-renderer-contracts.cjs +246 -0
  46. package/packages/ux-grid/test/browser-renderer-entry.js +129 -0
  47. package/packages/ux-grid/test/browser-resize-contracts.cjs +150 -0
  48. package/packages/ux-grid/test/compatibility.cjs +176 -0
  49. package/packages/ux-grid/test/contracts.cjs +281 -0
  50. package/packages/ux-grid/test/delivery.cjs +103 -0
  51. package/packages/ux-grid/test/integration.cjs +132 -0
  52. package/packages/ux-grid/test/real-kernel-contracts.cjs +335 -0
  53. package/packages/ux-grid/test/real-kernel-events.cjs +233 -0
  54. package/packages/ux-grid/test/renderer-boundaries.cjs +165 -0
  55. package/packages/ux-grid/test/ui-components.cjs +103 -0
  56. package/tools/locale/format.js +1 -1
  57. package/tools/locale/index.js +1 -7
  58. package/tools/mixins/locale.js +1 -1
  59. package/tools/utils/util.js +1 -1
@@ -0,0 +1,233 @@
1
+ // Real Vue/Element/OLD or NEW kernel and actual DOM dispatched events.
2
+ // jsdom is not evidence of layout, resize or physical pointer hit testing.
3
+ const assert = require('assert');
4
+ const path = require('path');
5
+ const root = process.env.UX_GRID_TEST_DEPENDENCIES;
6
+ if (!root) throw Error('Set UX_GRID_TEST_DEPENDENCIES to the legacy consumer');
7
+ const mode = process.argv[2];
8
+ if (!mode) {
9
+ const results = {};
10
+ const traces = {};
11
+ for (const name of ['old', 'new']) {
12
+ const child = require('child_process').spawnSync(process.execPath, [__filename, name], { env: process.env, encoding: 'utf8' });
13
+ assert.equal(child.status, 0, name + '\n' + child.stdout + child.stderr);
14
+ results[name] = JSON.parse(child.stdout.split('\n').find(line => line.startsWith('RESULT ')).slice(7));
15
+ const timingLine = child.stdout.split('\n').find(line => line.startsWith('TIMING '));
16
+ assert(timingLine); traces[name] = JSON.parse(timingLine.slice(7));
17
+ console.log(name + ' ' + timingLine);
18
+ console.log(name + ' EVENTS ' + JSON.stringify(results[name]));
19
+ }
20
+ assert.deepStrictEqual(results.new, results.old);
21
+ for (const label of Object.keys(traces.old)) {
22
+ assert.deepStrictEqual(traces.new[label].filter(phase => phase !== 'nextTick'), traces.old[label].filter(phase => phase !== 'nextTick'), label + ' same event order relative to synchronous DOM dispatch return');
23
+ }
24
+ console.log('real-kernel DOM events passed');
25
+ process.exit(0);
26
+ }
27
+ const external = name => require(require.resolve(name, { paths: [root] }));
28
+ require('babel-polyfill');
29
+ const dom = new (external('jsdom').JSDOM)('<html><body><script></script></body></html>', { url: 'http://localhost/' });
30
+ for (const key of ['window', 'document', 'Element', 'HTMLElement', 'HTMLTableElement', 'Node', 'MutationObserver', 'getComputedStyle', 'name', 'Image']) global[key] = dom.window[key];
31
+ Object.defineProperty(global, 'navigator', { configurable: true, value: dom.window.navigator });
32
+ global.requestAnimationFrame = cb => setTimeout(cb, 0);
33
+ global.cancelAnimationFrame = clearTimeout;
34
+ // jsdom has no layout engine and returns null for every offsetParent. The
35
+ // legacy filter positioning assumes one exists; supply ancestry only, never
36
+ // claim these zero-size DOM tests verify popup placement or scrolling.
37
+ Object.defineProperty(HTMLElement.prototype, 'offsetParent', { configurable: true, get() { return this.parentElement; } });
38
+ const Vue = require('vue');
39
+ for (const location of [root, require.resolve('vxe-table'), require.resolve('vxe-pc-ui')]) require.cache[require.resolve('vue', { paths: [location] })] = require.cache[require.resolve('vue')];
40
+ Vue.config.productionTip = false; Vue.config.devtools = false;
41
+ const errors = [];
42
+ Vue.config.errorHandler = error => errors.push(error.stack || String(error));
43
+ Vue.use(external('element-ui'));
44
+ let Grid;
45
+ if (mode === 'old') {
46
+ const old = external('umy-table/lib/umy-table.common.js'); Vue.use(old.default || old); Grid = Vue.options.components.UxGrid;
47
+ } else { const current = require('../../../lib/ux-grid.js'); Grid = current.default || current; }
48
+ const settle = () => new Promise(resolve => setTimeout(resolve, 80));
49
+ const observations = {};
50
+ const eventTiming = {};
51
+ let vm;
52
+ (async () => {
53
+ vm = new Vue({ data: () => ({ allowEdit: true }), render(h) { return h(Grid, { ref: 'grid', props: {
54
+ height: 300, data: [{ id: 1, name: 'One' }, { id: 2, name: 'Two' }], rowId: 'id',
55
+ paginationShow: true, total: 30, pageSize: 10, currentPage: 1, layout: 'prev,pager,next',
56
+ highlightCurrentRow: true,
57
+ editConfig: { mode: 'cell', trigger: 'click', activeMethod: () => this.allowEdit, beforeEditMethod: () => this.allowEdit },
58
+ columns: [{ type: 'checkbox' }, { type: 'expand' },
59
+ { field: 'id', title: 'ID', sortable: true, filters: [{ label: 'One', value: 1 }] },
60
+ { field: 'name', title: 'Name', filters: [{ label: 'One', value: 'One' }], editRender: { name: 'ElInput' } },
61
+ { field: 'hidden', visible: false, filters: [{ label: 'Hidden', value: 'hidden' }] }]
62
+ } }); } }).$mount();
63
+ document.body.appendChild(vm.$el); await settle();
64
+ const wrapper = vm.$refs.grid;
65
+ let emitted = []; let sequence = [];
66
+ const names = ['selection-change', 'select', 'select-all', 'cell-click', 'row-click', 'cell-dblclick', 'row-dblclick', 'row-contextmenu', 'header-click', 'header-contextmenu', 'sort-change', 'filter-change', 'edit-actived', 'edit-closed', 'edit-disabled', 'expand-change', 'current-change', 'radio-change', 'cell-mouse-enter', 'cell-mouse-leave'];
67
+ for (const name of names) wrapper.$on(name, (...args) => { emitted.push({ name, args }); sequence.push(name); });
68
+ async function dispatch(label, selector, type, expected) {
69
+ emitted = []; sequence = ['before-dispatch'];
70
+ const element = vm.$el.querySelector(selector);
71
+ assert(element, label + ': control ' + selector);
72
+ const domEvent = new window.MouseEvent(type, { bubbles: true, cancelable: true });
73
+ element.dispatchEvent(domEvent);
74
+ if (type === 'contextmenu') assert.equal(domEvent.defaultPrevented, false, 'empty menu retains native menu');
75
+ sequence.push('returned'); await Vue.nextTick(); sequence.push('nextTick'); await settle();
76
+ expected.forEach(name => assert(emitted.some(event => event.name === name), label + ' emits ' + name));
77
+ const cell = element.closest('td,th');
78
+ const kernel = wrapper.tableExample();
79
+ const columnNode = cell && kernel.getColumnNode(cell);
80
+ const rowNode = cell && cell.tagName === 'TD' && kernel.getRowNode(cell.parentElement);
81
+ const column = columnNode && columnNode.item;
82
+ const row = rowNode && rowNode.item;
83
+ for (const event of emitted) {
84
+ const args = event.args;
85
+ if (['cell-click', 'cell-dblclick', 'cell-mouse-enter', 'cell-mouse-leave'].includes(event.name)) {
86
+ assert.strictEqual(args[0], row); assert.strictEqual(args[1], column);
87
+ assert.strictEqual(args[2], cell); assert.strictEqual(args[3], domEvent);
88
+ } else if (['row-click', 'row-dblclick', 'row-contextmenu'].includes(event.name)) {
89
+ assert.strictEqual(args[0], row); assert.strictEqual(args[1], column); assert.strictEqual(args[2], domEvent);
90
+ } else if (['header-click', 'header-contextmenu'].includes(event.name)) {
91
+ assert.strictEqual(args[0], column); assert.strictEqual(args[1], domEvent);
92
+ } else if (['edit-actived', 'edit-disabled', 'expand-change'].includes(event.name)) {
93
+ assert.strictEqual(args[0].row, row); assert.strictEqual(args[0].column, column);
94
+ assert.equal(typeof args[0].rowIndex, 'number'); assert.equal(typeof args[0].columnIndex, 'number');
95
+ if (event.name === 'expand-change') { assert.equal(typeof args[0].expanded, 'boolean'); assert.strictEqual(args[0].$event, domEvent); }
96
+ } else if (event.name === 'current-change' || event.name === 'radio-change') {
97
+ assert.strictEqual(args[0], row);
98
+ if (event.name === 'radio-change') assert.strictEqual(args[1], column);
99
+ }
100
+ }
101
+ eventTiming[label] = sequence.slice();
102
+ observations[label] = emitted.map(event => event.name);
103
+ return emitted;
104
+ }
105
+ await dispatch('checkbox', 'tbody tr .elx-cell--checkbox,tbody tr .vxe-cell--checkbox', 'click', ['select', 'selection-change']);
106
+ assert.deepStrictEqual(emitted.filter(event => ['select', 'selection-change'].includes(event.name)).map(event => event.name), ['select', 'selection-change']);
107
+ assert(!emitted.some(event => event.name === 'row-click'));
108
+ assert.equal(emitted[0].args[1].id, 1); assert(Array.isArray(emitted[1].args[0]));
109
+ assert(sequence.indexOf('selection-change') < sequence.indexOf('returned'));
110
+ await dispatch('checkbox-all', 'thead .elx-cell--checkbox,thead .vxe-cell--checkbox', 'click', ['select-all', 'selection-change']);
111
+ await dispatch('cell-click', 'tbody tr td:nth-child(3)', 'click', ['cell-click', 'row-click']);
112
+ await dispatch('cell-dblclick', 'tbody tr td:nth-child(3)', 'dblclick', ['cell-dblclick', 'row-dblclick']);
113
+ await dispatch('row-contextmenu', 'tbody tr td:nth-child(3)', 'contextmenu', ['row-contextmenu']);
114
+ await dispatch('header-click', 'thead th:nth-child(3)', 'click', ['header-click']);
115
+ await dispatch('header-contextmenu', 'thead th:nth-child(3)', 'contextmenu', ['header-contextmenu']);
116
+ await dispatch('sort', 'thead .elx-sort--desc-btn,thead .vxe-sort--desc-btn', 'click', ['sort-change']);
117
+ assert.equal(emitted.find(event => event.name === 'sort-change').args[0].prop, 'id');
118
+ await dispatch('filter-open', 'thead th:nth-child(3) .elx-filter--btn,thead th:nth-child(3) .vxe-filter--btn', 'click', []);
119
+ await dispatch('filter-option', '.elx-table--filter-body .elx-table--filter-option,.vxe-table--filter-body .vxe-table--filter-option', 'click', []);
120
+ await dispatch('filter-confirm', '.elx-table--filter-footer button:first-child,.vxe-table--filter-footer button:first-child', 'click', ['filter-change']);
121
+ const filterPayload = emitted.find(event => event.name === 'filter-change').args[0];
122
+ observations.filters = filterPayload.filters.map(item => ({ property: item.property || item.field, values: item.values }));
123
+ assert.deepStrictEqual(observations.filters, [{ property: 'id', values: [1] }, { property: 'name', values: [] }]);
124
+ await dispatch('filter-reopen', 'thead th:nth-child(3) .elx-filter--btn,thead th:nth-child(3) .vxe-filter--btn', 'click', []);
125
+ await dispatch('filter-reset', '.elx-table--filter-footer button:last-child,.vxe-table--filter-footer button:last-child', 'click', ['filter-change']);
126
+ observations.resetFilters = emitted.find(event => event.name === 'filter-change').args[0].filters.map(item => ({ property: item.property || item.field, values: item.values }));
127
+ assert.deepStrictEqual(observations.resetFilters, [{ property: 'id', values: [] }, { property: 'name', values: [] }]);
128
+ emitted = [];
129
+ assert.strictEqual(wrapper.clearFilter(), undefined); await settle();
130
+ assert(!emitted.some(event => event.name === 'filter-change'), 'programmatic clearFilter is silent');
131
+ await dispatch('edit', 'tbody tr td:nth-child(4)', 'click', ['edit-actived']);
132
+ assert.equal(emitted.filter(event => event.name === 'edit-actived').length, 1);
133
+ emitted = []; await wrapper.clearActived(); await settle();
134
+ assert.equal(emitted.filter(event => event.name === 'edit-closed').length, 1);
135
+ vm.allowEdit = false; await Vue.nextTick();
136
+ await dispatch('edit-disabled', 'tbody tr td:nth-child(4)', 'click', ['edit-disabled']);
137
+ assert.equal(emitted.filter(event => event.name === 'edit-disabled').length, 1);
138
+ await dispatch('mouseenter', 'tbody tr td:nth-child(3)', mode === 'old' ? 'mouseenter' : 'mouseover', ['cell-mouse-enter']);
139
+ await dispatch('mouseleave', 'tbody tr td:nth-child(3)', 'mouseleave', ['cell-mouse-leave']);
140
+ await dispatch('expand-row', 'tbody tr .elx-table--expanded,tbody tr .vxe-table--expand-btn', 'click', ['expand-change']);
141
+ wrapper.clearSelection(); wrapper.toggleRowSelection([{ row: wrapper.getRowById(1), selected: true }]);
142
+ assert.strictEqual(wrapper.removeCheckboxRow(), undefined); await settle();
143
+ assert.deepStrictEqual(wrapper.getTableData().fullData.map(row => row.id), [2]);
144
+ await wrapper.loadColumn([{ type: 'radio' }, { field: 'name' }]); await settle();
145
+ await dispatch('radio', 'tbody tr .elx-cell--radio,tbody tr .vxe-cell--radio', 'click', ['radio-change']);
146
+ assert.equal(emitted.find(event => event.name === 'radio-change').args[0].id, 2);
147
+ for (const name of ['row-contextmenu', 'header-contextmenu']) wrapper.$off(name);
148
+ await dispatch('unobserved-contextmenu', 'tbody tr td:nth-child(2)', 'contextmenu', []);
149
+ // Auxiliary wrapper-direct contracts: no claim that these are user DOM input.
150
+ const auxiliary = [];
151
+ for (const name of ['handlePageSize', 'show-field', 'reset', 'amend-field', 'handle-field']) wrapper.$on(name, (...args) => auxiliary.push({ name, args }));
152
+ vm.$el.querySelector('.el-pagination .btn-next').click(); await settle();
153
+ assert.deepStrictEqual(auxiliary.map(event => event.args[0]), [{ size: 10, page: 2 }]);
154
+ observations['pagination DOM'] = auxiliary[0].args[0]; auxiliary.length = 0;
155
+ wrapper.handleSizeChange(25); wrapper.handleCurrentChange(3);
156
+ assert.deepStrictEqual(auxiliary.slice(0, 2).map(event => event.args[0]), [{ size: 25, page: 2 }, { size: 25, page: 3 }]);
157
+ wrapper.plDialogOpens(); await new Promise(resolve => setTimeout(resolve, 240));
158
+ assert.equal(wrapper.$refs.plDialog.style.width, '300px');
159
+ Array.from(vm.$el.querySelectorAll('.table-cus-footer button')).find(button => button.textContent.trim() === '确定').click(); assert.equal(auxiliary[2].name, 'show-field'); assert(Array.isArray(auxiliary[2].args[0]));
160
+ wrapper.plDialogOpens(); await new Promise(resolve => setTimeout(resolve, 240));
161
+ Array.from(vm.$el.querySelectorAll('.table-cus-footer button')).find(button => button.textContent.trim() === '重置').click(); assert.equal(auxiliary[3].name, 'reset');
162
+ const field = { name: 'Auxiliary' };
163
+ wrapper.amendField(field, 0); assert.strictEqual(auxiliary[4].args[0], field); assert.equal(auxiliary[4].args[1], 0);
164
+ wrapper.handleField('下移', 0, [field, { name: 'Second' }]);
165
+ assert.equal(auxiliary[5].name, 'handle-field');
166
+ assert.deepStrictEqual(auxiliary[5].args, ['下移', 0]);
167
+ wrapper.plDialogOpens(); wrapper.closeModal(); assert.equal(wrapper.$refs.plDialog.style.width, '0px');
168
+ observations['wrapper-direct auxiliary'] = auxiliary.map(event => event.name);
169
+ vm.$destroy(); vm.$el.remove();
170
+ const parentRow = { id: 10, name: 'Tree', children: [{ id: 11, name: 'Child' }] };
171
+ vm = new Vue({ render: h => h(Grid, { ref: 'grid', props: {
172
+ height: 240, data: [parentRow], treeConfig: { children: 'children' },
173
+ keyboardConfig: { isArrow: true, isTab: true }, mouseConfig: { selected: true },
174
+ columns: [{ field: 'name', treeNode: true }]
175
+ } }) }).$mount();
176
+ document.body.appendChild(vm.$el); await settle();
177
+ let treePayload;
178
+ const treeEvents = [];
179
+ vm.$refs.grid.$on('toggle-tree-expand', value => { treePayload = value; treeEvents.push('toggle-tree-expand'); });
180
+ vm.$refs.grid.$on('cell-click', (row, column, cell, event) => {
181
+ assert.strictEqual(row, parentRow); assert.strictEqual(event, treeEvent);
182
+ assert.strictEqual(column, vm.$refs.grid.getColumnByField('name')); assert(cell instanceof HTMLElement);
183
+ treeEvents.push('cell-click');
184
+ });
185
+ vm.$refs.grid.$on('row-click', (row, column, event) => { assert.strictEqual(row, parentRow); assert.strictEqual(event, treeEvent); treeEvents.push('row-click'); });
186
+ const treeButton = vm.$el.querySelector('.elx-tree--node-btn,.vxe-cell--tree-btn');
187
+ assert(treeButton, 'real tree expand control');
188
+ const treeEvent = new window.MouseEvent('click', { bubbles: true });
189
+ treeButton.dispatchEvent(treeEvent); await settle();
190
+ assert(treePayload); assert.strictEqual(treePayload.row, parentRow);
191
+ assert.strictEqual(treePayload.$event, treeEvent); assert.equal(treePayload.expanded, true);
192
+ assert(vm.$refs.grid.isTreeExpandByRow(parentRow));
193
+ observations['tree DOM'] = { expanded: treePayload.expanded, row: treePayload.row.id };
194
+ observations['tree DOM events'] = treeEvents;
195
+ let keyPayload;
196
+ vm.$refs.grid.$on('keydown', payload => { keyPayload = payload; });
197
+ const firstCell = vm.$el.querySelector('tbody td');
198
+ firstCell.dispatchEvent(new window.MouseEvent('mousedown', { bubbles: true, button: 0 }));
199
+ const keyEvent = new window.KeyboardEvent('keydown', { bubbles: true, key: 'ArrowDown', code: 'ArrowDown', keyCode: 40, which: 40 });
200
+ document.dispatchEvent(keyEvent); await settle();
201
+ assert(keyPayload); assert.strictEqual(keyPayload.$event, keyEvent);
202
+ observations['keydown DOM'] = Object.keys(keyPayload);
203
+ let scrollPayload; let scrollSource;
204
+ vm.$refs.grid.$on('table-body-scroll', (payload, event) => { scrollPayload = payload; scrollSource = event; });
205
+ const scrollBody = vm.$el.querySelector('.vxe-table--scroll-y-handle') || vm.$el.querySelector('.elx-table--body-wrapper,.vxe-table--body-wrapper');
206
+ scrollBody.scrollTop = 30;
207
+ const scrollEvent = new window.Event('scroll'); scrollBody.dispatchEvent(scrollEvent); await settle();
208
+ assert(scrollPayload); assert.strictEqual(scrollSource, scrollEvent);
209
+ assert.equal(scrollPayload.scrollTop, 30); assert.equal(scrollPayload.isY, true);
210
+ observations['scroll DOM fields'] = Object.keys(scrollPayload).sort();
211
+ for (const kind of ['expand', 'tree']) {
212
+ vm.$destroy(); vm.$el.remove();
213
+ vm = new Vue({ render: h => h(Grid, { ref: 'grid', props: {
214
+ height: 240, data: [parentRow], highlightCurrentRow: true,
215
+ expandConfig: kind === 'expand' ? { trigger: 'row' } : null,
216
+ treeConfig: kind === 'tree' ? { children: 'children', trigger: 'row' } : null,
217
+ columns: kind === 'expand' ? [{ type: 'expand' }, { field: 'name' }] : [{ field: 'name', treeNode: true }]
218
+ } }) }).$mount();
219
+ document.body.appendChild(vm.$el); await settle();
220
+ const eventName = kind === 'expand' ? 'expand-change' : 'toggle-tree-expand';
221
+ const order = [];
222
+ for (const name of [eventName, 'current-change', 'cell-click', 'row-click']) vm.$refs.grid.$on(name, () => order.push(name));
223
+ vm.$el.querySelector(kind === 'expand' ? 'tbody td:nth-child(2)' : 'tbody td').click();
224
+ order.push('returned'); await settle();
225
+ assert.deepStrictEqual(order, [eventName, 'current-change', 'cell-click', 'row-click', 'returned'], kind + ' row-trigger retains native event ordering');
226
+ observations[kind + ' row-trigger'] = order;
227
+ }
228
+ assert.deepStrictEqual(errors, []);
229
+ console.log('RESULT ' + JSON.stringify(observations));
230
+ console.log('TIMING ' + JSON.stringify(eventTiming));
231
+ })().catch(error => { console.error(error.stack); process.exitCode = 1; }).finally(() => {
232
+ if (vm) vm.$destroy(); dom.window.close(); setImmediate(() => process.exit(process.exitCode || 0));
233
+ });
@@ -0,0 +1,165 @@
1
+ // Real Vue/Element/VXE registry and DOM events; layout is covered by Chrome.
2
+ const assert = require('assert');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const dependencyRoot = process.env.UX_GRID_TEST_DEPENDENCIES;
6
+ if (!dependencyRoot) throw Error('Set UX_GRID_TEST_DEPENDENCIES');
7
+ const external = name => require(require.resolve(name, { paths: [dependencyRoot] }));
8
+ const { JSDOM } = external('jsdom');
9
+ const dom = new JSDOM('<html><body><script></script></body></html>', { url: 'http://localhost/' });
10
+ for (const key of ['window', 'document', 'Element', 'HTMLElement', 'HTMLTableElement', 'Node', 'MutationObserver', 'getComputedStyle', 'name', 'Image']) global[key] = dom.window[key];
11
+ Object.defineProperty(global, 'navigator', { configurable: true, value: dom.window.navigator });
12
+ global.requestAnimationFrame = cb => setTimeout(cb, 0);
13
+ global.cancelAnimationFrame = clearTimeout;
14
+ const Vue = require('vue');
15
+ for (const root of [dependencyRoot, require.resolve('vxe-table'), require.resolve('vxe-pc-ui')]) require.cache[require.resolve('vue', { paths: [root] })] = require.cache[require.resolve('vue')];
16
+ Vue.config.productionTip = false;
17
+ Vue.config.devtools = false;
18
+ const errors = [];
19
+ Vue.config.errorHandler = error => errors.push(error.stack || String(error));
20
+ Vue.use(external('element-ui'));
21
+ const { renderer, interceptor } = require('vxe-table/lib/ui');
22
+ const GridModule = require('../../../lib/ux-grid.js');
23
+ const Grid = GridModule.default || GridModule;
24
+ const wait = () => new Promise(resolve => setTimeout(resolve, 90));
25
+ let vm;
26
+ let validations = 0;
27
+ (async () => {
28
+ const row = { id: 1, choice: 'a', cascade: [], lazy: [], time: '14:24:56' };
29
+ const options = [{ value: 'a', label: 'Alpha' }, { value: 'b', label: 'Beta' }];
30
+ vm = new Vue({ render(h) { return h(Grid, { ref: 'grid', props: {
31
+ height: 280, data: [row], editConfig: { trigger: 'click', mode: 'cell' },
32
+ validConfig: { message: 'inline' },
33
+ rules: { choice: [{ trigger: 'change', validator({ cellValue }) {
34
+ validations++;
35
+ return cellValue === 'b' ? Promise.reject(new Error('Beta rejected')) : Promise.resolve();
36
+ } }] },
37
+ columns: [
38
+ { field: 'choice', editRender: { name: 'ElSelect', options, props: { clearable: true } } },
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); } } } } },
41
+ { field: 'time', editRender: { name: 'ElTimePicker', props: { valueFormat: 'HH:mm:ss' } } }
42
+ ]
43
+ } }); } }).$mount();
44
+ document.body.appendChild(vm.$el);
45
+ await wait();
46
+ const grid = vm.$refs.grid;
47
+ const table = grid.tableExample();
48
+ const event = new window.MouseEvent('mousedown', { bubbles: true });
49
+ const trace = [];
50
+ const first = params => { trace.push('first'); params.marker = 'mutated'; };
51
+ const second = params => { trace.push('second'); assert.equal(params.marker, 'original'); };
52
+ interceptor.add('event.clearActived', first);
53
+ interceptor.add('event.clearActived', second);
54
+ const invoke = () => table.preventEvent(event, 'event.clearEdit', { marker: 'original' }, () => { trace.push('next'); return 'next-result'; });
55
+ assert.equal(invoke(), 'next-result');
56
+ assert.deepStrictEqual(trace.splice(0), ['first', 'second', 'next']);
57
+ const veto = () => false;
58
+ interceptor.add('event.clearActived', veto);
59
+ assert.equal(invoke(), null);
60
+ assert.deepStrictEqual(trace.splice(0), ['first', 'second']);
61
+ interceptor.delete('event.clearActived', veto);
62
+ const result = { exact: true };
63
+ const resultVeto = () => ({ status: false, result });
64
+ interceptor.add('event.clearActived', resultVeto);
65
+ assert.strictEqual(invoke(), result);
66
+ trace.length = 0;
67
+ interceptor.delete('event.clearActived', resultVeto);
68
+ const modern = () => { trace.push('modern'); };
69
+ interceptor.add('event.clearEdit', modern);
70
+ invoke();
71
+ assert.deepStrictEqual(trace.splice(0), ['modern', 'next']);
72
+ // Actual Element dropdown DOM receives mousedown/click while modern callbacks exist.
73
+ await grid.setActiveCell(row, 'choice'); await wait();
74
+ vm.$el.querySelector('.el-select input').dispatchEvent(new window.MouseEvent('mousedown', { bubbles: true }));
75
+ vm.$el.querySelector('.el-select input').click(); await wait();
76
+ const beta = Array.from(document.querySelectorAll('.el-select-dropdown__item')).find(el => el.textContent.trim() === 'Beta');
77
+ assert(beta, 'actual Element option mounted');
78
+ vm.$el.querySelector('.el-select input').focus();
79
+ const focusedInput = document.activeElement;
80
+ validations = 0;
81
+ beta.dispatchEvent(new window.MouseEvent('mousedown', { bubbles: true }));
82
+ beta.click(); await wait();
83
+ assert.equal(row.choice, 'b');
84
+ assert.strictEqual(document.activeElement, focusedInput, 'validation activation does not steal DOM focus');
85
+ assert.equal(validations, 1, 'one real kernel validation per Element change');
86
+ assert(vm.$el.querySelector('.col--valid-error'), 'validator rejection marks invalid cell');
87
+ assert(table.getEditRecord(), 'popup preserves active edit with modern interceptor');
88
+ vm.$el.querySelector('.el-select .el-input').dispatchEvent(new window.MouseEvent('mouseenter', { bubbles: false }));
89
+ await wait();
90
+ const clear = vm.$el.querySelector('.el-select .el-icon-circle-close');
91
+ assert(clear, 'real Element clear control'); clear.click(); await wait();
92
+ assert.equal(row.choice, '');
93
+ assert.equal(validations, 2, 'clear triggers exactly one additional kernel validation');
94
+ assert(!vm.$el.querySelector('.col--valid-error'), 'clear removes rejected value validation state');
95
+ document.body.dispatchEvent(new window.MouseEvent('mousedown', { bubbles: true }));
96
+ await wait();
97
+ assert(!table.getEditRecord(), 'outside click closes editor');
98
+ interceptor.delete('event.clearEdit', modern);
99
+ trace.length = 0;
100
+ invoke(); assert.deepStrictEqual(trace.splice(0), ['first', 'second', 'next']);
101
+ // Simulate another separately bundled renderer module sharing the actual registry.
102
+ const source = fs.readFileSync(path.join(__dirname, '../src/element-renderer.js'), 'utf8').replace(/^import .*;$/gm, '');
103
+ const counts = ['event.clearEdit', 'event.clearActived', 'event.clearFilter'].map(name => interceptor.get(name).length);
104
+ require('vm').runInNewContext(source, { XEUtils: require('xe-utils'), renderer, interceptor, document });
105
+ assert.deepStrictEqual(['event.clearEdit', 'event.clearActived', 'event.clearFilter'].map(name => interceptor.get(name).length), counts);
106
+ invoke(); assert.deepStrictEqual(trace.splice(0), ['first', 'second', 'next']);
107
+ interceptor.delete('event.clearActived', first);
108
+ interceptor.delete('event.clearActived', second);
109
+ await grid.setActiveCell(row, 'cascade'); await wait();
110
+ vm.$el.querySelector('.el-cascader input').click(); await wait();
111
+ const root = Array.from(document.querySelectorAll('.el-cascader-node')).find(el => el.textContent.trim() === 'Root');
112
+ assert(root, 'top-level renderer options reach real Cascader');
113
+ root.click(); await wait();
114
+ const leaf = Array.from(document.querySelectorAll('.el-cascader-node')).find(el => el.textContent.trim() === 'Leaf');
115
+ assert(leaf); leaf.click(); await wait();
116
+ assert.deepStrictEqual(Array.from(row.cascade), ['root', 'leaf']);
117
+ await grid.clearActived(); await wait();
118
+ assert(vm.$el.textContent.includes('Root / Leaf'));
119
+ await grid.setActiveCell(row, 'lazy'); await wait();
120
+ vm.$el.querySelector('.el-cascader input').click(); await wait();
121
+ const parent = Array.from(document.querySelectorAll('.el-cascader-node')).find(el => el.textContent.trim() === 'Lazy Parent');
122
+ assert(parent); parent.click(); await wait();
123
+ const child = Array.from(document.querySelectorAll('.el-cascader-node')).find(el => el.textContent.trim() === 'Lazy Child');
124
+ assert(child); child.click(); await wait();
125
+ assert.deepStrictEqual(errors, []);
126
+ assert.deepStrictEqual(Array.from(row.lazy), ['parent', 'child']);
127
+ await grid.clearActived(); await wait();
128
+ assert(vm.$el.textContent.includes('Lazy Parent / Lazy Child'), 'lazy checked node label survives closing edit');
129
+ // Cache uses selected values and unformatted path labels; export shares it.
130
+ const lazyColumn = table.getColumnByField('lazy');
131
+ const lazyParams = { row, column: lazyColumn };
132
+ const lazyRenderer = renderer.get('ElCascader');
133
+ assert.equal(lazyRenderer.tableExportMethod(lazyParams), 'Lazy Parent / Lazy Child');
134
+ const lazyOptions = lazyColumn.editRender;
135
+ lazyOptions.props.options = [{ value: 'parent', label: 'Renamed Parent' }];
136
+ assert.equal(lazyRenderer.renderTableCell(null, lazyOptions, lazyParams)[0], 'Renamed Parent / Lazy Child', 'latest static prefix wins over lazy cache');
137
+ lazyOptions.props.separator = '>';
138
+ assert.equal(lazyRenderer.renderTableCell(null, lazyOptions, lazyParams)[0], 'Renamed Parent > Lazy Child');
139
+ lazyOptions.props.showAllLevels = false;
140
+ assert.equal(lazyRenderer.renderTableCell(null, lazyOptions, lazyParams)[0], 'Lazy Child');
141
+ lazyOptions.props.options = [{ value: 'parent', label: 'New Parent', children: [{ value: 'child', label: 'New Child' }] }];
142
+ assert.equal(lazyRenderer.renderTableCell(null, lazyOptions, lazyParams)[0], 'New Child', 'fresh complete options win over cached labels');
143
+ row.lazy = [];
144
+ assert.equal(lazyRenderer.renderTableCell(null, lazyOptions, lazyParams)[0], '', 'empty selection cannot resurrect cached label');
145
+ const display = (name, value, props) => renderer.get(name).renderTableCell(null, { name, props }, { row: { value }, column: { field: 'value' } })[0];
146
+ assert.equal(display('ElTimePicker', '14:24:56', { valueFormat: 'HH:mm:ss' }), '14:24:56');
147
+ assert.equal(display('ElTimePicker', ['14:24:56', '18:25:57'], { valueFormat: 'HH:mm:ss', rangeSeparator: '' }), '14:24:56 18:25:57');
148
+ assert.equal(display('ElTimePicker', '14:24', { valueFormat: 'HH:mm', format: 'HH:mm' }), '14:24');
149
+ assert.equal(display('ElTimePicker', '02:24:56 PM', { valueFormat: 'hh:mm:ss A' }), '14:24:56');
150
+ assert.equal(display('ElTimePicker', '12:24:56 AM', { valueFormat: 'hh:mm:ss A' }), '00:24:56');
151
+ assert.equal(display('ElTimePicker', '12:24:56 PM', { valueFormat: 'hh:mm:ss A' }), '12:24:56');
152
+ assert.equal(display('ElTimePicker', 'PM 02:24:56', { valueFormat: 'A hh:mm:ss' }), '14:24:56');
153
+ assert.equal(display('ElTimePicker', '24:56 02 PM', { valueFormat: 'mm:ss hh A' }), '14:24:56');
154
+ assert.equal(display('ElTimePicker', '02:24:56 PM', { valueFormat: 'hh:mm:ss A', format: 'hh:mm:ss A' }), '02:24:56 PM');
155
+ const stamp = String(new Date(2026, 8, 17, 14, 24, 56).getTime());
156
+ assert.equal(display('ElDatePicker', stamp, { valueFormat: 'timestamp' }), '2026-09-17');
157
+ assert.equal(display('ElTimePicker', stamp, { valueFormat: 'timestamp' }), '14:24:56');
158
+ assert.equal(display('ElDatePicker', ['2026-09-01', '2026-09-17'], { type: 'daterange', valueFormat: 'yyyy-MM-dd', rangeSeparator: '' }), '2026-09-01 2026-09-17');
159
+ for (const value of [null, undefined, '']) assert.equal(display('ElTimePicker', value, {}), '');
160
+ const dateFilter = renderer.get('ElDatePicker').tableFilterDefaultMethod;
161
+ 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
+ assert(match('2026-09-01')); assert(match('2026-09-17')); assert(!match('2026-09-10')); assert(!match(null));
163
+ assert.deepStrictEqual(errors, []);
164
+ console.log('renderer boundaries passed: dynamic real registry, veto/priority/reload, Element Select/Cascader DOM, date/time formats and dates filter');
165
+ })().catch(error => { console.error(error); process.exitCode = 1; }).finally(() => { if (vm) vm.$destroy(); dom.window.close(); });
@@ -0,0 +1,103 @@
1
+ // Real official UI dependency wiring; browser suite measures visual geometry.
2
+ const assert = require('assert');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const dependencyRoot = process.env.UX_GRID_TEST_DEPENDENCIES || process.cwd();
6
+ const external = name => require(require.resolve(name, { paths: [dependencyRoot] }));
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', 'HTMLTableElement', 'Node', 'MutationObserver', 'getComputedStyle', 'name', 'Image']) global[key] = dom.window[key];
10
+ Object.defineProperty(global, 'navigator', { configurable: true, value: dom.window.navigator });
11
+ global.requestAnimationFrame = callback => setTimeout(callback, 0);
12
+ global.cancelAnimationFrame = clearTimeout;
13
+ const Vue = require('vue');
14
+ const vueModule = require.cache[require.resolve('vue')];
15
+ for (const root of [dependencyRoot, require.resolve('vxe-table'), require.resolve('vxe-pc-ui')]) require.cache[require.resolve('vue', { paths: [root] })] = vueModule;
16
+ Vue.config.productionTip = false;
17
+ Vue.config.devtools = false;
18
+ const errors = [];
19
+ Vue.config.errorHandler = error => errors.push(error);
20
+ const scenario = process.argv[2];
21
+ if (!scenario) {
22
+ for (const name of ['empty', 'short', 'tooltip', 'loading', 'both']) {
23
+ const child = require('child_process').spawnSync(process.execPath, [__filename, name], { env: process.env, encoding: 'utf8' });
24
+ assert.strictEqual(child.status, 0, name + '\n' + child.stdout + child.stderr);
25
+ }
26
+ }
27
+ const { VxeUI } = require('vxe-table/lib/ui');
28
+ const translator = key => 'custom:' + key;
29
+ VxeUI.setConfig({ i18n: translator, tooltip: { enterDelay: 0 }, table: { rowConfig: { height: 47 } } });
30
+ VxeUI.setIcon({ TABLE_SORT_ASC: 'consumer-sort' });
31
+ const expectedArrays = scenario === 'empty'
32
+ ? { iconPicker: { icons: [] }, upload: { imageTypes: [] }, watermark: { gap: [] } }
33
+ : { iconPicker: { icons: [{ name: 'custom', children: [] }] }, upload: { imageTypes: ['custom'] }, watermark: { gap: [17] } };
34
+ VxeUI.setConfig(expectedArrays);
35
+ const customTooltip = { name: 'VxeTooltip', render(h) { return h('span'); } };
36
+ const customLoading = { name: 'VxeLoading', render(h) { return h('span'); } };
37
+ if (scenario === 'tooltip' || scenario === 'both') VxeUI.component(customTooltip);
38
+ if (scenario === 'loading' || scenario === 'both') VxeUI.component(customLoading);
39
+ const beforeTooltip = VxeUI.getComponent('VxeTooltip');
40
+ const beforeLoading = VxeUI.getComponent('VxeLoading');
41
+ if (scenario) VxeUI.setIcon({ LOADING: 'consumer-loading-icon' });
42
+ const setup = fs.readFileSync(path.join(__dirname, '../src/setup.js'), 'utf8').replace(/^import .*;$/gm, '');
43
+ function runSetup() {
44
+ require('vm').runInNewContext(setup, { require, VxeUI, XEUtils: require('xe-utils'), zhCN: {} });
45
+ }
46
+ runSetup();
47
+ assert.strictEqual(VxeUI.getConfig().i18n, translator);
48
+ assert.equal(VxeUI.getConfig().table.rowConfig.height, 47);
49
+ assert.equal(VxeUI.getConfig().tooltip.enterDelay, 0);
50
+ assert.equal(VxeUI.getIcon().TABLE_SORT_ASC, 'consumer-sort');
51
+ for (const name of Object.keys(expectedArrays)) {
52
+ for (const field of Object.keys(expectedArrays[name])) {
53
+ assert.deepStrictEqual(VxeUI.getConfig()[name][field], expectedArrays[name][field]);
54
+ }
55
+ }
56
+ if (beforeTooltip) assert.strictEqual(VxeUI.getComponent('VxeTooltip'), beforeTooltip);
57
+ if (beforeLoading) assert.strictEqual(VxeUI.getComponent('VxeLoading'), beforeLoading);
58
+ if (scenario) {
59
+ assert.equal(VxeUI.getIcon().LOADING, 'consumer-loading-icon');
60
+ dom.window.close();
61
+ process.exit(0);
62
+ }
63
+ assert(VxeUI.getComponent('VxeTooltip') && VxeUI.getComponent('VxeLoading'));
64
+ const originalTooltip = VxeUI.getComponent('VxeTooltip');
65
+ const originalLoading = VxeUI.getComponent('VxeLoading');
66
+ runSetup();
67
+ assert.strictEqual(VxeUI.getComponent('VxeTooltip'), originalTooltip);
68
+ assert.strictEqual(VxeUI.getComponent('VxeLoading'), originalLoading);
69
+ assert.strictEqual(require('vxe-pc-ui/lib/ui').VxeUI, VxeUI, 'shared UI singleton');
70
+ const Grid = require('../../../lib/ux-grid.js');
71
+ let vm;
72
+ const delay = () => new Promise(resolve => setTimeout(resolve, 100));
73
+ (async () => {
74
+ vm = new Vue({
75
+ data: () => ({ loading: true }),
76
+ render(h) { return h(Grid.default || Grid, { ref: 'grid', props: {
77
+ loading: this.loading, height: 200, data: [{ name: '' }], columns: [{ field: 'name' }],
78
+ showOverflow: true, rules: { name: [{ required: true, message: 'Required name' }] }, validConfig: { message: 'tooltip' }
79
+ } }); }
80
+ }).$mount();
81
+ document.body.appendChild(vm.$el);
82
+ await delay();
83
+ assert(vm.$el.querySelector('.vxe-loading.is--visible'), 'official loading mask mounted');
84
+ assert(vm.$el.querySelector('.vxe-loading .vxe-icon-spinner'), 'official default spinner rendered');
85
+ vm.loading = false;
86
+ await delay();
87
+ assert(!vm.$el.querySelector('.vxe-loading.is--visible'));
88
+ const table = vm.$refs.grid.$refs.singleTable.$refs.refTable;
89
+ for (const name of ['refTooltip', 'refValidTooltip']) {
90
+ const tip = table.$refs[name];
91
+ assert(tip, name + ' exists');
92
+ await tip.open(vm.$el.querySelector('td'), 'Official popup');
93
+ await delay();
94
+ assert(document.body.textContent.includes('Official popup'));
95
+ await tip.close();
96
+ }
97
+ assert.deepStrictEqual(errors, []);
98
+ console.log('ux-grid official Tooltip/Loading wiring and consumer configuration passed');
99
+ })().catch(error => { console.error(error); process.exitCode = 1; }).finally(() => {
100
+ if (vm) vm.$destroy();
101
+ dom.window.close();
102
+ setImmediate(() => process.exit(process.exitCode || 0));
103
+ });
@@ -1,4 +1,4 @@
1
- import { hasOwn } from 'umy-ui/tools/utils/util';
1
+ import { hasOwn } from '../utils/util';
2
2
 
3
3
  const RE_NARGS = /(%|)\{([0-9a-zA-Z_]+)\}/g;
4
4
  /**
@@ -1,6 +1,4 @@
1
- import defaultLang from 'umy-ui/tools/locale/lang/zh-CN';
2
- // 如果是按需加载,设置u-table的locale(本地 u-table 组件统一使用 packages/tools/locale)
3
- import tableLocale from '../../packages/tools/locale'
1
+ import defaultLang from './lang/zh-CN';
4
2
  import Vue from 'vue';
5
3
  import deepmerge from 'deepmerge';
6
4
  import Format from './format';
@@ -41,10 +39,6 @@ export const t = function(path, options) {
41
39
 
42
40
  export const use = function(l) {
43
41
  lang = l || lang;
44
- if (l) {
45
- // 如果是按需加载,设置u-table语言
46
- tableLocale.use(l)
47
- }
48
42
  };
49
43
 
50
44
  export const i18n = function(fn) {
@@ -1,4 +1,4 @@
1
- import { t } from 'umy-ui/tools/locale';
1
+ import { t } from '../locale';
2
2
 
3
3
  export default {
4
4
  methods: {
@@ -1,5 +1,5 @@
1
1
  import Vue from 'vue';
2
- import { isString, isObject } from 'umy-ui/tools/utils/types';
2
+ import { isString, isObject } from './types';
3
3
 
4
4
  const hasOwnProperty = Object.prototype.hasOwnProperty;
5
5