umy-ui-table 1.2.4 → 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.
Files changed (63) 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 +809 -0
  38. package/packages/ux-grid/src/grid-height.js +75 -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-layout-contracts.cjs +105 -0
  46. package/packages/ux-grid/test/browser-layout-entry.js +66 -0
  47. package/packages/ux-grid/test/browser-renderer-contracts.cjs +262 -0
  48. package/packages/ux-grid/test/browser-renderer-entry.js +129 -0
  49. package/packages/ux-grid/test/browser-resize-contracts.cjs +150 -0
  50. package/packages/ux-grid/test/compatibility.cjs +176 -0
  51. package/packages/ux-grid/test/contracts.cjs +281 -0
  52. package/packages/ux-grid/test/delivery-umd.cjs +48 -0
  53. package/packages/ux-grid/test/delivery.cjs +103 -0
  54. package/packages/ux-grid/test/integration.cjs +132 -0
  55. package/packages/ux-grid/test/real-kernel-contracts.cjs +335 -0
  56. package/packages/ux-grid/test/real-kernel-events.cjs +233 -0
  57. package/packages/ux-grid/test/renderer-async.cjs +140 -0
  58. package/packages/ux-grid/test/renderer-boundaries.cjs +196 -0
  59. package/packages/ux-grid/test/ui-components.cjs +103 -0
  60. package/tools/locale/format.js +1 -1
  61. package/tools/locale/index.js +1 -7
  62. package/tools/mixins/locale.js +1 -1
  63. package/tools/utils/util.js +1 -1
@@ -0,0 +1,176 @@
1
+ // Run: node packages/ux-grid/test/compatibility.cjs
2
+ const assert = require('assert');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const vm = require('vm');
6
+ const root = path.resolve(__dirname, '..');
7
+ const source = file => fs.readFileSync(path.join(root, file), 'utf8');
8
+ const registered = {};
9
+ const existing = { renderTableEdit: () => 'custom' };
10
+ registered.ElInput = existing;
11
+ const loadRenderers = registry => vm.runInNewContext(source('src/element-renderer.js').replace(/^import .*;$/gm, ''), {
12
+ XEUtils: require('xe-utils'),
13
+ renderer: { get: name => registry[name], add: (name, config) => { registry[name] = config; } },
14
+ interceptor: {
15
+ callbacks: {},
16
+ get(name) { return this.callbacks[name] || []; },
17
+ add(name, callback) { (this.callbacks[name] || (this.callbacks[name] = [])).push(callback); }
18
+ }
19
+ });
20
+ loadRenderers(registered);
21
+ const h = (tag, data, children) => ({ tag, data, children });
22
+ let updates = 0;
23
+ const params = { row: { value: 1 }, column: { field: 'value' }, $table: { updateStatus() { updates++; } } };
24
+ const select = { name: 'ElSelect', optionProps: { value: 'id', label: 'text', disabled: 'locked' },
25
+ optionGroupProps: { label: 'title', options: 'items' },
26
+ optionGroups: [{ title: 'Group', items: [{ id: 1, text: 'One', locked: true }] }] };
27
+ const edit = registered.ElSelect.renderTableEdit(h, select, params)[0];
28
+ assert.equal(edit.children[0].tag, 'el-option-group');
29
+ assert.equal(edit.children[0].children[0].data.props.disabled, true);
30
+ edit.data.on.input(2);
31
+ assert.equal(params.row.value, 2);
32
+ assert.equal(updates, 0, 'Select input writes value without prematurely validating');
33
+ edit.data.on.change(2);
34
+ assert.equal(updates, 1);
35
+ assert.strictEqual(registered.ElInput, existing, 'preserve user renderer');
36
+ assert.equal(registered.ElButtons.renderTableDefault(h, { children: [{ content: 'Edit' }] }, params)[0].tag, 'ElButton');
37
+ let checked;
38
+ let filterUpdates = 0;
39
+ const filterOption = { data: [1] };
40
+ const filter = registered.ElSelect.renderTableFilter(h, { name: 'ElSelect', props: { multiple: true } }, {
41
+ column: { filters: [filterOption] }, $panel: { changeOption(event, value) { checked = value; filterUpdates++; } }
42
+ })[0];
43
+ filter.data.on.input([]);
44
+ assert.equal(filterOption.data.length, 0);
45
+ assert.equal(filterUpdates, 0, 'Select filter input only writes the pending value');
46
+ filter.data.on.change([]);
47
+ assert.equal(checked, false);
48
+ assert.equal(filterUpdates, 1);
49
+
50
+ // Validate the observable event contract in all three renderer contexts. Element
51
+ // emits input before change/select; user handlers must still run exactly once.
52
+ const eventRenderers = {};
53
+ loadRenderers(eventRenderers);
54
+ for (const name of ['ElInput', 'ElInputNumber', 'ElSelect', 'ElAutocomplete']) {
55
+ const statusEvent = name === 'ElAutocomplete' ? 'select'
56
+ : name === 'ElInput' || name === 'ElInputNumber' ? 'input' : 'change';
57
+ for (const context of ['edit', 'filter', 'form']) {
58
+ let count = 0;
59
+ const seen = [];
60
+ const events = Object.fromEntries(['input', 'change', 'select'].map(event => [event,
61
+ (received, value) => { assert.strictEqual(received, eventParams); seen.push([event, value]); }]));
62
+ const eventParams = {
63
+ row: { value: 1 }, column: { field: 'value', filters: [{ data: 1 }] },
64
+ data: { value: 1 }, field: 'value',
65
+ $table: { updateStatus() { count++; } },
66
+ $panel: { changeOption() { count++; } },
67
+ $form: { updateStatus() { count++; } }
68
+ };
69
+ const method = context === 'edit' ? 'renderTableEdit'
70
+ : context === 'filter' ? 'renderTableFilter' : 'renderFormItemContent';
71
+ const ons = eventRenderers[name][method](h, { name, events }, eventParams)[0].data.on;
72
+ ons.input(2);
73
+ assert.equal(context === 'edit' ? eventParams.row.value
74
+ : context === 'filter' ? eventParams.column.filters[0].data : eventParams.data.value, 2);
75
+ assert.equal(count, statusEvent === 'input' ? 1 : 0, `${name} ${context} input timing`);
76
+ ons.change(2);
77
+ assert.equal(count, statusEvent === 'select' ? 0 : 1, `${name} ${context} change timing`);
78
+ ons.select(2);
79
+ assert.equal(count, 1, `${name} ${context} updates once per interaction`);
80
+ assert.deepStrictEqual(seen, [['input', 2], ['change', 2], ['select', 2]]);
81
+ }
82
+ }
83
+ for (const [name, value] of [['ElSwitch', false], ['ElInputNumber', 0]]) {
84
+ const option = { data: null };
85
+ let enabled;
86
+ const ons = eventRenderers[name].renderTableFilter(h, { name }, {
87
+ column: { filters: [option] }, $panel: { changeOption(event, state) { enabled = state; } }
88
+ })[0].data.on;
89
+ ons.input(value);
90
+ if (ons.change) ons.change(value);
91
+ assert.strictEqual(option.data, value);
92
+ assert.equal(enabled, true, `${name} retains valid false/zero filters`);
93
+ }
94
+ params.row.value = '09:30';
95
+ assert.equal(registered.ElTimeSelect.renderTableCell(h, { name: 'ElTimeSelect' }, params)[0], '09:30');
96
+ params.row.value = ['20260905', '20260906'];
97
+ params.column.editRender = { name: 'ElDatePicker', props: { valueFormat: 'yyyyMMdd', type: 'dates' } };
98
+ assert.equal(registered.ElDatePicker.tableExportMethod(params), '2026-09-05, 2026-09-06');
99
+ params.row.value = '20260905';
100
+ params.column.filterRender = { props: { type: 'daterange', valueFormat: 'yyyyMMdd', format: 'dd/MM/yyyy' } };
101
+ assert.equal(registered.ElDatePicker.tableFilterDefaultMethod(Object.assign({ option: { data: ['20260831', '20260906'] } }, params)), true);
102
+ params.row.value = 1;
103
+ const remote = { props: { remote: true }, options: [{ value: 1, label: 'One' }] };
104
+ assert.equal(registered.ElSelect.renderTableCell(h, remote, params)[0], 'One');
105
+ remote.options = [];
106
+ assert.equal(registered.ElSelect.renderTableCell(h, remote, params)[0], 'One');
107
+ const form = { data: { value: [] }, field: 'value', $form: { updateStatus() {} } };
108
+ const group = registered.ElCheckbox.renderFormItemContent(h, {
109
+ name: 'ElCheckbox', options: [{ value: 1, label: 'One' }]
110
+ }, form)[0];
111
+ assert.equal(group.tag, 'ElCheckboxGroup');
112
+ group.data.on.input([1]);
113
+ assert.equal(form.data.value[0], 1);
114
+ const cascader = { name: 'ElCascader', props: { options: [{ id: 1, text: 'Parent', nodes: [{ id: 2, text: 'Child' }, { id: 3, text: 'Other' }] }], props: { value: 'id', label: 'text', children: 'nodes', emitPath: false } } };
115
+ params.row.value = 2;
116
+ assert.equal(registered.ElCascader.renderTableCell(h, cascader, params)[0], 'Parent / Child');
117
+ cascader.props.props.multiple = true;
118
+ params.row.value = [2, 3];
119
+ assert.equal(registered.ElCascader.renderTableCell(h, cascader, params)[0], 'Parent / Child, Parent / Other');
120
+ cascader.props.props.emitPath = true;
121
+ params.row.value = [[1, 2], [1, 3]];
122
+ assert.equal(registered.ElCascader.renderTableCell(h, cascader, params)[0], 'Parent / Child, Parent / Other');
123
+ cascader.props.showAllLevels = false;
124
+ assert.equal(registered.ElCascader.renderTableCell(h, cascader, params)[0], 'Child, Other');
125
+
126
+ const context = { Vue: {}, draggable: {}, ElPagination: {}, ElCheckbox: {}, ElButton: {}, VxeGrid: {}, uxGridMixin: {}, console };
127
+ vm.runInNewContext(source('src/ux-grid.vue').split('<script type="text/babel">')[1].split('</script>')[0]
128
+ .replace(/^import .*;$/gm, '').replace('export default', 'component ='), context);
129
+ const methods = context.component.methods;
130
+ const column = (slot, children) => ({ componentOptions: {
131
+ Ctor: { options: { name: 'UxTableColumn' } }, propsData: { field: 'value' }, children
132
+ }, data: { scopedSlots: slot ? { default: slot } : {} } });
133
+ const state = Object.assign({ $slots: { default: [column(() => 'old')] } }, methods);
134
+ state.syncSlotColumns();
135
+ const first = state.slotColumns;
136
+ state.syncSlotColumns();
137
+ assert.strictEqual(state.slotColumns, first, 'self update must not reload columns');
138
+ state.$slots.default = [column(() => 'new')];
139
+ state.syncSlotColumns();
140
+ assert.equal(state.slotColumns[0].slots.default(), 'new');
141
+ assert.strictEqual(state.slotColumns, first, 'render-only update preserves column configuration identity');
142
+ const text = { text: 'static content' };
143
+ assert.strictEqual(state.vnodeToColumn(column(null, [text])).slots.default()[0], text);
144
+ assert.equal(state.vnodeToColumn(column(null, [column()])).children.length, 1);
145
+ const configured = [{ field: 'configured' }];
146
+ state.columns = configured;
147
+ state.$slots.default = [];
148
+ state.syncSlotColumns();
149
+ assert.strictEqual(context.component.computed.tableColumns.call(state), configured, 'explicit columns retain priority');
150
+ for (const axis of ['X', 'Y']) {
151
+ const scroll = 'scroll' + axis;
152
+ const virtual = 'virtual' + axis + 'Config';
153
+ const evaluate = (legacy, modern, spanMethod) => {
154
+ const instance = { [scroll]: legacy, [virtual]: modern, spanMethod, $options: { propsData: { [scroll]: legacy, [virtual]: modern } } };
155
+ instance[scroll + 'Config'] = context.component.computed[scroll + 'Config'].call(instance);
156
+ return context.component.computed['computedVirtual' + axis + 'Config'].call(instance);
157
+ };
158
+ const legacy = { gt: 0, oSize: 8 };
159
+ const compatible = evaluate(legacy);
160
+ assert.equal(compatible.enabled, true, 'legacy ' + scroll + ' enables virtualization without explicit enabled');
161
+ assert.equal(compatible.gt, 0);
162
+ assert.equal(compatible.oSize, 8);
163
+ assert.equal(legacy.enabled, undefined, 'normalization does not mutate caller configuration');
164
+ assert.equal(evaluate({ gt: 0, enabled: undefined }).enabled, true);
165
+ assert.equal(evaluate({ enabled: false, gt: 0 }).enabled, false);
166
+ assert.equal(evaluate(legacy, null, () => null).enabled, false, 'spanMethod disables implicit virtual scroll');
167
+ assert.equal(evaluate({ enabled: true, gt: 0 }, null, () => null).enabled, true, 'explicit legacy enabled survives spanMethod');
168
+ assert.equal(evaluate({ enabled: false, gt: 0 }, null, () => null).enabled, false);
169
+ for (const enabled of [true, false, undefined]) {
170
+ const modern = { enabled, gt: 12, oSize: 6 };
171
+ assert.strictEqual(evaluate(legacy, modern, () => null), modern, 'new virtual config has unchanged priority');
172
+ }
173
+ const defaults = { enabled: true, gt: axis === 'Y' ? 100 : 24, oSize: 0 };
174
+ assert.equal(evaluate(defaults).gt, defaults.gt, 'modern default threshold preserved');
175
+ }
176
+ console.log('ux-grid compatibility regression checks passed');
@@ -0,0 +1,281 @@
1
+ // Wrapper contracts only: these assertions do not stand in for VXE integration.
2
+ // Run: node packages/ux-grid/test/contracts.cjs
3
+ const assert = require('assert');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const vm = require('vm');
7
+ const Vue = require('vue');
8
+ const root = path.resolve(__dirname, '..');
9
+ const read = name => fs.readFileSync(path.join(root, name), 'utf8');
10
+ const nodes = [];
11
+ const document = {
12
+ createElement: () => ({ style: {} }),
13
+ body: {
14
+ appendChild(node) { nodes.push(node); node.parentNode = this; },
15
+ removeChild(node) { nodes.splice(nodes.indexOf(node), 1); node.parentNode = null; }
16
+ }
17
+ };
18
+ const context = { XEUtils: require('xe-utils'), isArrayFn: Array.isArray, console,
19
+ document, setTimeout, clearTimeout, Promise };
20
+ vm.runInNewContext(read('src/dialog-draft.js').replace(/^import .*;$/gm, '').replace(/export function/g, 'function'), context);
21
+ vm.runInNewContext(read('mixins/index.js').replace(/^import .*;$/gm, '').replace('export default', 'mixin ='), context);
22
+ const methods = context.mixin.methods;
23
+ const events = [];
24
+ const state = Object.assign({ $listeners: {}, $refs: {}, $emit: (...args) => events.push(args),
25
+ $nextTick: () => Promise.resolve() }, methods);
26
+ const tick = async () => { for (let i = 0; i < 8; i++) await Promise.resolve(); };
27
+
28
+ async function run() {
29
+ // Real Vue listener bookkeeping: $off leaves empty arrays, $once removes itself.
30
+ const listenerVm = new Vue({ methods });
31
+ const selectedRows = [{ id: 1 }];
32
+ let reads = 0;
33
+ listenerVm.$refs.singleTable = {
34
+ getCheckboxRecords() { reads++; return selectedRows; },
35
+ setAllCheckboxRow() {}, clearCheckboxRow() {}, setCheckboxRow() {}, toggleCheckboxRow() {}
36
+ };
37
+ for (const [event, method] of [['select', 'plSelect'], ['select-all', 'selectAll'], ['selection-change', 'plSelect']]) {
38
+ let received;
39
+ const callback = value => { received = value; };
40
+ listenerVm.$on(event, callback);
41
+ listenerVm[method]({ row: selectedRows[0] });
42
+ assert.strictEqual(received, selectedRows);
43
+ listenerVm.$off(event, callback);
44
+ const before = reads;
45
+ listenerVm[method]({});
46
+ assert.equal(reads, before, '$off retains lazy selection reads');
47
+ received = null;
48
+ listenerVm.$once(event, callback);
49
+ listenerVm[method]({});
50
+ assert.strictEqual(received, selectedRows);
51
+ const afterOnce = reads;
52
+ listenerVm[method]({});
53
+ assert.equal(reads, afterOnce, '$once removal retains lazy reads');
54
+ }
55
+ let programEvents = 0;
56
+ listenerVm.$on('selection-change', () => programEvents++);
57
+ listenerVm.toggleAllSelection();
58
+ listenerVm.clearSelection();
59
+ listenerVm.newtoggleRowSelections([{ row: selectedRows[0], selected: true }]);
60
+ assert.equal(programEvents, 3);
61
+ listenerVm.$off();
62
+ const beforeProgram = reads;
63
+ listenerVm.toggleAllSelection(); listenerVm.clearSelection();
64
+ assert.equal(reads, beforeProgram);
65
+ listenerVm.$destroy();
66
+
67
+ const kernelContext = { Promise, VxeGrid: { methods: {} } };
68
+ vm.runInNewContext(read('src/grid-kernel.js').replace(/^import .*;$/gm, '').replace('export default', 'kernel ='), kernelContext);
69
+ const loadColumn = kernelContext.kernel.methods.loadColumn;
70
+ const active = {};
71
+ const marker = Promise.resolve('loaded');
72
+ const kernelArgs = [[], { extra: true }];
73
+ kernelContext.VxeGrid.methods.loadColumn = function (...values) {
74
+ assert.strictEqual(this, active);
75
+ assert.deepStrictEqual(values, kernelArgs);
76
+ return marker;
77
+ };
78
+ assert.strictEqual(loadColumn.apply(active, kernelArgs), marker);
79
+ const kernelFailure = new Error('kernel failure');
80
+ kernelContext.VxeGrid.methods.loadColumn = () => { throw kernelFailure; };
81
+ assert.throws(() => loadColumn.call(active), error => error === kernelFailure);
82
+ for (const flag of ['_isDestroyed', '_isBeingDestroyed']) {
83
+ assert.strictEqual(await loadColumn.call({ [flag]: true }), undefined);
84
+ }
85
+ const args = [{ row: 1 }, { column: 2 }, 'field'];
86
+ const token = Promise.resolve({ done: true });
87
+ // All simple public forwarding methods retain argument order and return type.
88
+ const forwarded = {
89
+ reloadData: 1, loadData: 1, reloadColumn: 1, loadColumn: 1, insertRow: 2,
90
+ reloadRow: 3, revertData: 1, updateFooter: 0, setFilter: 2, updateData: 0,
91
+ clearActived: 0, clearValidate: 0, getColumnByField: 1, getInsertRecords: 0,
92
+ getCheckboxRecords: 0, getTableColumn: 0, getColumnIndex: 1, getUpdateRecords: 0,
93
+ isUpdateByRow: 1, isCheckedByCheckboxRow: 1, isActiveByRow: 1, getTableData: 0,
94
+ getRowById: 1, toggleRowExpand: 1, setRowExpand: 2, setAllRowExpand: 1,
95
+ clearRowExpand: 0, setAllTreeExpand: 1, clearTreeExpand: 0, getTreeExpandRecords: 0,
96
+ toggleTreeExpand: 1, setTreeExpand: 2, isTreeExpandByRow: 1
97
+ };
98
+ for (const [name, count] of Object.entries(forwarded)) {
99
+ const kernelName = name === 'insertRow' ? 'insertAt' : name;
100
+ let received;
101
+ state.$refs.singleTable = { [kernelName](...values) { received = values; return token; } };
102
+ assert.strictEqual(state[name](...args.slice(0, count)), token, name + ' return');
103
+ assert.deepStrictEqual(received, args.slice(0, count), name + ' arguments');
104
+ }
105
+ const discarded = { setCurrentRow: 1, clearSort: 0, clearFilter: 1, sort: 2,
106
+ doLayout: 0, scrollToRow: 1, refreshColumn: 0, refreshScroll: 0,
107
+ setActiveRow: 1, setActiveCell: 2, updateStatus: 1, remove: 1, removeCheckboxRow: 0 };
108
+ for (const [name, count] of Object.entries(discarded)) {
109
+ let received;
110
+ state.$refs.singleTable = { [name === 'doLayout' ? 'recalculate' : name](...values) { received = values; return token; } };
111
+ assert.strictEqual(state[name](...args.slice(0, count)), undefined, name + ' return');
112
+ assert.deepStrictEqual(received, args.slice(0, count), name + ' arguments');
113
+ await tick();
114
+ }
115
+ let scrollArgs;
116
+ state.$refs.singleTable = { scrollTo(...values) { scrollArgs = values; return token; } };
117
+ assert.strictEqual(state.pagingScrollTopLeft(10, 20), undefined);
118
+ assert.deepStrictEqual(scrollArgs, [20, 10]);
119
+ const inserted = [];
120
+ state.$refs.singleTable = { getRecordset: () => ({ insertRecords: inserted, removeRecords: [{}] }) };
121
+ assert.strictEqual(state.getRecordset(), inserted);
122
+ assert.strictEqual(state.tableExample(), state.$refs.singleTable);
123
+ delete state.$refs.singleTable;
124
+ assert.strictEqual(state.tableExample(), undefined);
125
+ for (const name of ['validate', 'fullValidate']) {
126
+ for (const rows of [undefined, [], [{ value: 1 }]]) {
127
+ for (const fails of [false, true]) {
128
+ const result = { validation: fails ? 'error' : 'success' };
129
+ let received;
130
+ state.$refs.singleTable = { [name](...values) {
131
+ received = values;
132
+ return fails ? Promise.reject(result) : Promise.resolve(result);
133
+ } };
134
+ assert.strictEqual(await state[name](rows), result);
135
+ assert.deepStrictEqual(received, rows === undefined ? [] : [rows]);
136
+ }
137
+ }
138
+ }
139
+ const records = [{ selected: true }];
140
+ let scans = 0;
141
+ state.$refs.singleTable = { getCheckboxRecords() { scans++; return records; } };
142
+ for (const name of ['selectAll', 'plSelect']) {
143
+ state.$listeners = {};
144
+ state[name]({ records: () => { throw Error('must remain lazy without listeners'); } });
145
+ state.$listeners = { 'selection-change': true };
146
+ for (const value of [records, () => records, undefined]) {
147
+ state[name]({ records: value, row: args[0] });
148
+ assert.strictEqual(events.pop()[1], records);
149
+ events.pop();
150
+ }
151
+ }
152
+ assert.equal(scans, 2);
153
+ const operations = [];
154
+ state.$refs.singleTable = {
155
+ setCheckboxRow: (...values) => operations.push(['set', ...values]),
156
+ toggleCheckboxRow: row => operations.push(['toggle', row]),
157
+ setAllCheckboxRow: value => operations.push(['all', value]),
158
+ clearCheckboxRow: () => operations.push(['clear']),
159
+ getCheckboxRecords: () => records
160
+ };
161
+ state.$listeners = { 'selection-change': true };
162
+ const row = {};
163
+ assert.strictEqual(state.toggleRowSelection([{ row, selected: true }, { row, selected: false }, { row }]), undefined);
164
+ assert.deepStrictEqual(operations, [['set', row, true], ['set', row, false], ['toggle', row]]);
165
+ assert.strictEqual(events.pop()[1], records);
166
+ state.toggleAllSelection(); assert.deepStrictEqual(operations.pop(), ['all', true]);
167
+ assert.strictEqual(events.pop()[1], records);
168
+ state.clearSelection(); assert.deepStrictEqual(operations.pop(), ['clear']);
169
+ assert.strictEqual(events.pop()[1], records);
170
+ state.sortChange({ field: 'value', order: 'asc' });
171
+ assert.equal(events.pop()[1].prop, 'value');
172
+ state.sortChange({ property: 'old', field: 'new' });
173
+ assert.equal(events.pop()[1].prop, 'old');
174
+ for (const filters of [[], () => records]) {
175
+ state.filterChange({ field: 'value', filters });
176
+ const payload = events.pop()[1];
177
+ assert.equal(payload.property, 'value');
178
+ assert.strictEqual(payload.filters, typeof filters === 'function' ? records : filters);
179
+ }
180
+ const selected = { row: {}, column: {} };
181
+ const cell = {};
182
+ state.$listeners = { 'edit-currently-active': true };
183
+ state.$refs.singleTable = { recalculate: () => token, refreshColumn: () => token,
184
+ getSelectedCell: () => selected, getCellElement: () => cell };
185
+ events.length = 0;
186
+ state.doLayout(); state.refreshColumn(); state.dataRendered({});
187
+ await tick();
188
+ assert.equal(events.filter(event => event[0] === 'edit-currently-active').length, 1);
189
+ assert.strictEqual(events.find(event => event[0] === 'edit-currently-active')[1].cell, cell);
190
+ events.length = 0;
191
+ state.doLayout(); state.editCurrentlyActive(selected);
192
+ await tick();
193
+ assert.equal(events.length, 1, 'native selection suppresses pending refresh duplicate');
194
+ events.length = 0;
195
+ const emit = state.$emit;
196
+ state.$emit = (...args) => {
197
+ emit(...args);
198
+ if (args[0] === 'data-rendered') state.editCurrentlyActive(selected);
199
+ };
200
+ state.dataRendered({});
201
+ await tick();
202
+ assert.equal(events.filter(event => event[0] === 'edit-currently-active').length, 1,
203
+ 'synchronous selection from consumer data-rendered handler is not emitted twice');
204
+ state.$emit = emit;
205
+ state.$refs.singleTable.getSelectedCell = () => null;
206
+ events.length = 0;
207
+ state.doLayout(); await tick();
208
+ assert.equal(events.length, 0, 'cleared selection is not resurrected');
209
+ const logged = [];
210
+ context.console = { error: (...values) => logged.push(values) };
211
+ const failure = new Error('layout failed');
212
+ state.$refs.singleTable.recalculate = () => Promise.reject(failure);
213
+ state.doLayout(); await tick();
214
+ assert.strictEqual(logged[0][1], failure, 'failed discarded Promise is reported without unhandled rejection');
215
+ context.console = console;
216
+
217
+ const keyless = [{ name: 'A', state: false }, { name: 'B', state: false }];
218
+ const initial = context.createDialogDraft(keyless);
219
+ initial.draft[0].state = true;
220
+ const merged = context.mergeDialogDraft(initial, [keyless[1], keyless[0]], initial.draft);
221
+ assert.equal(merged.value[1].state, true, 'keyless source object identity survives parent reorder');
222
+ for (const insertionIndex of [0, 1, 2]) {
223
+ const source = [{ name: 'A', state: false }, { name: 'B', state: false }];
224
+ const initial = context.createDialogDraft(source);
225
+ initial.draft[0].state = true;
226
+ const draft = initial.draft.slice().reverse();
227
+ const next = source.slice();
228
+ next.splice(insertionIndex, 0, { name: 'NEW', state: false });
229
+ const merged = context.mergeDialogDraft(initial, next, draft);
230
+ assert.equal(merged.value.map(item => item.name).join(','), 'B,A,NEW', 'keyless insertion preserves each item and draft order');
231
+ assert.equal(merged.value.find(item => item.name === 'A').state, true);
232
+ assert.equal(merged.value.find(item => item.name === 'NEW').state, false, 'new item cannot inherit another item draft');
233
+ const repeated = context.mergeDialogDraft(merged, next, merged.value);
234
+ assert.equal(repeated.value.map(item => item.name).join(','), 'B,A,NEW');
235
+ assert.equal(repeated.value.find(item => item.name === 'A').state, true);
236
+ }
237
+ const replaced = context.mergeDialogDraft(initial,
238
+ [{ name: 'Replacement A', state: false }, { name: 'Replacement B', state: false }], initial.draft);
239
+ assert.equal(replaced.value[0].state, true, 'fully replaced keyless objects retain positional fallback');
240
+
241
+ // Real Vue 2 watchers, with DOM creation isolated to a small owned-node fake.
242
+ const Grid = Vue.extend({ mixins: [context.mixin], data: () => ({ newDialogData: [], plDialogFals: false }) });
243
+ const grid = new Grid({ propsData: { dialogData: [
244
+ { field: 'a', name: 'A', state: false }, { field: 'b', name: 'B', state: true }
245
+ ] } });
246
+ grid.$refs.plDialog = { style: {} };
247
+ const unrelated = document.createElement('div'); document.body.appendChild(unrelated);
248
+ grid.plDialogOpens();
249
+ assert.equal(nodes.length, 2);
250
+ grid.plDialogOpens(); assert.equal(nodes.length, 2, 'opening twice does not leak backdrop');
251
+ grid.newDialogData[0].state = true;
252
+ grid.newDialogData.reverse();
253
+ grid.dialogData[0].name = 'Updated A';
254
+ grid.dialogData.push({ field: 'c', name: 'C', state: false });
255
+ await Vue.nextTick();
256
+ assert.deepStrictEqual(grid.newDialogData.map(item => item.field), ['b', 'a', 'c']);
257
+ assert.equal(grid.newDialogData[1].state, true);
258
+ assert.equal(grid.newDialogData[1].name, 'Updated A');
259
+ grid.dialogData.splice(1, 1);
260
+ await Vue.nextTick();
261
+ assert.deepStrictEqual(grid.newDialogData.map(item => item.field), ['a', 'c']);
262
+ let confirmed;
263
+ grid.$on('show-field', value => { confirmed = value; });
264
+ grid.dialogData[0].name = 'Same tick update';
265
+ grid.confirmField();
266
+ assert.equal(confirmed[0].name, 'Same tick update');
267
+ assert.equal(confirmed[0].state, true);
268
+ assert.equal(grid._dialogUnwatch, null);
269
+ assert.deepStrictEqual(nodes, [unrelated]);
270
+ const closedDraft = grid.newDialogData;
271
+ grid.dialogData[0].name = 'Closed update';
272
+ await Vue.nextTick();
273
+ assert.strictEqual(grid.newDialogData, closedDraft, 'no closed deep synchronization');
274
+ grid.plDialogOpens();
275
+ assert.equal(grid.newDialogData[0].name, 'Closed update');
276
+ grid.$destroy();
277
+ assert.equal(grid._dialogUnwatch, null);
278
+ assert.deepStrictEqual(nodes, [unrelated]);
279
+ console.log('ux-grid wrapper contracts passed (mock kernel; real Vue dialog watcher)');
280
+ }
281
+ run().catch(error => { console.error(error); process.exitCode = 1; });
@@ -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,103 @@
1
+ // Run after common/component, lib:utils and lib:theme builds.
2
+ // Resolves only declared runtime dependencies from the built entries, rejecting
3
+ // accidental self aliases even when a developer's node_modules provides them.
4
+ const assert = require('assert');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const Module = require('module');
8
+ const root = path.resolve(__dirname, '../../..');
9
+ const manifest = require(path.join(root, 'package.json'));
10
+ const dependencies = Object.assign({}, manifest.dependencies, manifest.peerDependencies);
11
+ const externalRoot = process.env.UX_GRID_TEST_DEPENDENCIES || root;
12
+ const { JSDOM } = require(require.resolve('jsdom', { paths: [externalRoot] }));
13
+ const dom = new JSDOM('<html><body><script></script></body></html>', { url: 'http://localhost/' });
14
+ for (const key of ['window', 'document', 'Element', 'HTMLElement', 'HTMLTableElement', 'Node', 'MutationObserver', 'getComputedStyle', 'name', 'Image']) global[key] = dom.window[key];
15
+ Object.defineProperty(global, 'navigator', { configurable: true, value: dom.window.navigator });
16
+ global.requestAnimationFrame = callback => setTimeout(callback, 0);
17
+ global.cancelAnimationFrame = clearTimeout;
18
+ const Vue = require('vue');
19
+ Vue.config.productionTip = false;
20
+ Vue.config.devtools = false;
21
+ const vueModule = require.cache[require.resolve('vue')];
22
+ for (const location of [externalRoot, require.resolve('vxe-table')]) {
23
+ require.cache[require.resolve('vue', { paths: [location] })] = vueModule;
24
+ }
25
+ const originalLoad = Module._load;
26
+ Module._load = function (request, parent, isMain) {
27
+ if (parent && parent.filename.startsWith(path.join(root, 'lib') + path.sep) && !request.startsWith('.') && !path.isAbsolute(request)) {
28
+ const name = request.startsWith('@') ? request.split('/').slice(0, 2).join('/') : request.split('/')[0];
29
+ assert(dependencies[name], 'Undeclared built runtime dependency: ' + request);
30
+ }
31
+ return originalLoad.apply(this, arguments);
32
+ };
33
+ const unwrap = value => value.default || value;
34
+ const errors = [];
35
+ Vue.config.errorHandler = error => errors.push(error);
36
+ // The setup must not replace a consumer's language, dictionary or translator.
37
+ const setupSource = fs.readFileSync(path.join(root, 'packages/ux-grid/src/setup.js'), 'utf8').replace(/^import .*;$/gm, '');
38
+ for (const scenario of [
39
+ { language: '', dictionary: false, expected: ['register', 'language'] },
40
+ { language: '', dictionary: true, expected: ['language'] },
41
+ { language: 'en-US', dictionary: false, expected: [] },
42
+ { language: '', dictionary: false, custom: true, expected: [] }
43
+ ]) {
44
+ const calls = [];
45
+ require('vm').runInNewContext(setupSource, { zhCN: {}, VxeUI: {
46
+ getComponent: () => ({}),
47
+ getLanguage: () => scenario.language,
48
+ getConfig: () => ({ i18n: scenario.custom ? () => 'custom' : null }),
49
+ hasLanguage: () => scenario.dictionary,
50
+ setI18n: () => calls.push('register'), setLanguage: () => calls.push('language')
51
+ } });
52
+ assert.deepEqual(calls, scenario.expected);
53
+ }
54
+ const mounted = [];
55
+ (async () => {
56
+ const common = require(path.join(root, manifest.main));
57
+ assert(common.UxGrid && common.UxTableColumn && common.interceptor);
58
+ const ui = require('vxe-table/lib/ui');
59
+ assert.strictEqual(common.interceptor, ui.interceptor);
60
+ assert.equal(ui.VxeUI.getLanguage(), 'zh-CN');
61
+ assert.notEqual(ui.getI18n('vxe.table.confirmFilter'), 'vxe.table.confirmFilter', 'Default VXE labels are translated');
62
+ Vue.use(common);
63
+ const standalone = unwrap(require(path.join(root, 'lib/ux-grid.js')));
64
+ Vue.use(standalone);
65
+ for (const Grid of [common.UxGrid, standalone]) {
66
+ const vm = new Vue({ render: h => h(Grid, { props: { data: [{ name: 'delivery-row' }], columns: [{ field: 'name' }], height: 240, paginationShow: true, total: 42, layout: 'total' } }) }).$mount();
67
+ mounted.push(vm);
68
+ document.body.appendChild(vm.$el);
69
+ await new Promise(resolve => setTimeout(resolve, 150));
70
+ assert(vm.$el.textContent.includes('delivery-row'), 'Built entry mounts and renders rows');
71
+ }
72
+ const locale = require(path.join(root, 'lib/locale'));
73
+ assert.equal(typeof locale.t, 'function');
74
+ assert.equal(typeof locale.use, 'function');
75
+ const forceTree = node => { node.$forceUpdate(); node.$children.forEach(forceTree); };
76
+ for (const [setLanguage, marker] of [[locale.use, 'SHARED_LOCALE'], [common.locale, 'COMMON_LOCALE']]) {
77
+ setLanguage({ el: { pagination: { total: marker + ' {total}' } } });
78
+ mounted.forEach(forceTree);
79
+ await new Promise(resolve => setTimeout(resolve, 80));
80
+ mounted.forEach(vm => assert(vm.$el.textContent.includes(marker + ' 42'), 'Public locale updates common and on-demand pagination'));
81
+ }
82
+ common.i18n(key => key === 'el.pagination.total' ? 'SHARED_TRANSLATOR' : key);
83
+ mounted.forEach(forceTree);
84
+ await new Promise(resolve => setTimeout(resolve, 80));
85
+ mounted.forEach(vm => assert(vm.$el.textContent.includes('SHARED_TRANSLATOR'), 'Custom translator is shared across bundles'));
86
+ require(path.join(root, 'lib/mixins/locale'));
87
+ require(path.join(root, 'lib/utils/util'));
88
+ for (const file of ['index.css', 'ux-grid.css']) {
89
+ const css = fs.readFileSync(path.join(root, 'lib/theme-chalk', file), 'utf8');
90
+ assert(css.includes('.vxe-grid') && css.includes('var(--vxe-'), 'Theme preserves VXE selectors and custom properties');
91
+ assert(!/\[class\*=(?:"vxe-icon-"|'vxe-icon-'|vxe-icon-)\](?!:not\()/g.test(css), 'Modern icon namespace must exclude legacy double-hyphen icons');
92
+ assert(css.includes('vxe-icon-spinner'), 'Official modern spinner glyph retained');
93
+ }
94
+ for (const file of fs.readdirSync(path.join(root, 'theme/fonts'))) {
95
+ assert(fs.readFileSync(path.join(root, 'theme/fonts', file)).equals(fs.readFileSync(path.join(root, 'lib/theme-chalk/fonts', file))), 'Font copied unchanged: ' + file);
96
+ }
97
+ assert.deepEqual(errors, []);
98
+ console.log('delivery: common/on-demand DOM, declared runtime dependencies, standalone locale, CSS and fonts passed');
99
+ })().catch(error => { console.error(error); process.exitCode = 1; }).finally(() => {
100
+ Module._load = originalLoad;
101
+ mounted.forEach(vm => { vm.$destroy(); vm.$el.remove(); });
102
+ dom.window.close();
103
+ });