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.
- package/lib/button.js +1 -1
- package/lib/icon.js +1 -1
- package/lib/index.js +1 -1
- package/lib/locale/format.js +1 -1
- package/lib/locale/index.js +1 -72
- package/lib/mixins/locale.js +1 -1
- package/lib/theme-chalk/button.css +1 -1
- package/lib/theme-chalk/icon.css +1 -1
- package/lib/theme-chalk/iconfont.1785242763644.ttf +0 -0
- package/lib/theme-chalk/iconfont.1785242763644.woff +0 -0
- package/lib/theme-chalk/iconfont.1785242763644.woff2 +0 -0
- package/lib/theme-chalk/index.css +1 -1
- package/lib/theme-chalk/u-table.css +1 -1
- package/lib/theme-chalk/ux-grid.css +1 -1
- package/lib/u-table-column.js +1 -1
- package/lib/u-table.js +1 -1
- package/lib/umy-ui.common.js +1 -1
- package/lib/utils/util.js +1 -1
- package/lib/ux-grid.js +1 -1
- package/lib/ux-table-column.js +1 -1
- package/package.json +4 -3
- package/packages/.DS_Store +0 -0
- package/packages/input/.DS_Store +0 -0
- package/packages/pagination/.DS_Store +0 -0
- package/packages/scrollbar/.DS_Store +0 -0
- package/packages/select/.DS_Store +0 -0
- package/packages/table/.DS_Store +0 -0
- package/packages/tag/.DS_Store +0 -0
- package/packages/tools/.DS_Store +0 -0
- package/packages/tools/locale/index.js +2 -48
- package/packages/tools/utils/.DS_Store +0 -0
- package/packages/tooltip/.DS_Store +0 -0
- package/packages/u-table/.DS_Store +0 -0
- package/packages/ux-grid/.DS_Store +0 -0
- package/packages/ux-grid/mixins/index.js +133 -25
- package/packages/ux-grid/src/dialog-draft.js +75 -0
- package/packages/ux-grid/src/element-renderer.js +809 -0
- package/packages/ux-grid/src/grid-height.js +75 -0
- package/packages/ux-grid/src/grid-kernel.js +14 -0
- package/packages/ux-grid/src/setup.js +42 -0
- package/packages/ux-grid/src/ux-grid.vue +215 -57
- package/packages/ux-grid/test/browser-acceptance.cjs +192 -0
- package/packages/ux-grid/test/browser-baseline-loader.cjs +10 -0
- package/packages/ux-grid/test/browser-entry.js +242 -0
- 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 +262 -0
- package/packages/ux-grid/test/browser-renderer-entry.js +129 -0
- package/packages/ux-grid/test/browser-resize-contracts.cjs +150 -0
- package/packages/ux-grid/test/compatibility.cjs +176 -0
- package/packages/ux-grid/test/contracts.cjs +281 -0
- package/packages/ux-grid/test/delivery-umd.cjs +48 -0
- package/packages/ux-grid/test/delivery.cjs +103 -0
- package/packages/ux-grid/test/integration.cjs +132 -0
- package/packages/ux-grid/test/real-kernel-contracts.cjs +335 -0
- package/packages/ux-grid/test/real-kernel-events.cjs +233 -0
- package/packages/ux-grid/test/renderer-async.cjs +140 -0
- package/packages/ux-grid/test/renderer-boundaries.cjs +196 -0
- package/packages/ux-grid/test/ui-components.cjs +103 -0
- package/tools/locale/format.js +1 -1
- package/tools/locale/index.js +1 -7
- package/tools/mixins/locale.js +1 -1
- package/tools/utils/util.js +1 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// Run with UX_GRID_TEST_DEPENDENCIES pointing to an installed legacy consumer.
|
|
2
|
+
// No dependencies downloaded; creates a temporary bundle and isolated Chrome profile.
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const http = require('http');
|
|
7
|
+
const { spawn } = require('child_process');
|
|
8
|
+
const webpack = require('webpack');
|
|
9
|
+
const WebSocket = require('ws');
|
|
10
|
+
const VueLoaderPlugin = require('vue-loader/lib/plugin');
|
|
11
|
+
const root = path.resolve(__dirname, '../../..');
|
|
12
|
+
const consumer = process.env.UX_GRID_TEST_DEPENDENCIES;
|
|
13
|
+
if (!consumer) throw Error('Set UX_GRID_TEST_DEPENDENCIES to legacy consumer project');
|
|
14
|
+
const output = fs.mkdtempSync(path.join(os.tmpdir(), 'ux-grid-browser-'));
|
|
15
|
+
const chromePath = process.env.UX_GRID_TEST_CHROME || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
|
16
|
+
let chrome, server, ws;
|
|
17
|
+
const results = {};
|
|
18
|
+
const uiOnly = process.env.UX_GRID_TEST_SUITE === 'ui';
|
|
19
|
+
const extendedOnly = process.env.UX_GRID_TEST_SUITE === 'extended';
|
|
20
|
+
const persist = () => fs.writeFileSync(path.join(output, 'results.json'), JSON.stringify(results, null, 2));
|
|
21
|
+
const pending = new Map();
|
|
22
|
+
let id = 0;
|
|
23
|
+
function call(method, params = {}, sessionId) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const requestId = ++id;
|
|
26
|
+
const timer = setTimeout(() => { pending.delete(requestId); reject(Error('CDP timeout: ' + method)); }, 60000);
|
|
27
|
+
pending.set(requestId, { resolve: result => { clearTimeout(timer); resolve(result); }, reject: error => { clearTimeout(timer); reject(error); } });
|
|
28
|
+
ws.send(JSON.stringify({ id: requestId, method, params, sessionId }));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
(async () => {
|
|
32
|
+
const config = {
|
|
33
|
+
mode: 'development', devtool: false, entry: path.join(__dirname, 'browser-entry.js'),
|
|
34
|
+
output: { path: output, filename: 'bundle.js' },
|
|
35
|
+
resolve: { extensions: ['.js', '.vue', '.json'], alias: {
|
|
36
|
+
'vue$': require.resolve('vue/dist/vue.runtime.esm.js'),
|
|
37
|
+
'element-ui': path.join(consumer, 'node_modules/element-ui'),
|
|
38
|
+
'umy-table': path.join(consumer, 'node_modules/umy-table'),
|
|
39
|
+
'umy-ui': root
|
|
40
|
+
}, modules: [path.join(root, 'node_modules'), path.join(consumer, 'node_modules')] },
|
|
41
|
+
module: { rules: [
|
|
42
|
+
{ test: /\.vue$/, loader: 'vue-loader' },
|
|
43
|
+
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' },
|
|
44
|
+
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
|
|
45
|
+
{ test: /\.(woff|ttf)$/, loader: 'file-loader' }
|
|
46
|
+
] }, plugins: [new VueLoaderPlugin(), new webpack.NormalModuleReplacementPlugin(/^xe-utils(?:\/.*)?$/, resource => {
|
|
47
|
+
if (resource.context.includes(path.join(consumer, 'node_modules/umy-table'))) resource.request = require.resolve(resource.request, { paths: [consumer] });
|
|
48
|
+
})], performance: { hints: false }
|
|
49
|
+
};
|
|
50
|
+
await new Promise((resolve, reject) => webpack(config, (error, stats) => error || stats.hasErrors() ? reject(error || Error(stats.toString({ all: false, errors: true }))) : resolve()));
|
|
51
|
+
const baselineConfig = { ...config, output: { ...config.output, filename: 'baseline.js' }, module: { rules: [
|
|
52
|
+
{ test: /\.(js|vue)$/, enforce: 'pre', include: path.join(root, 'packages/ux-grid'), exclude: path.join(__dirname), loader: path.join(__dirname, 'browser-baseline-loader.cjs') },
|
|
53
|
+
...config.module.rules
|
|
54
|
+
] } };
|
|
55
|
+
if (!uiOnly && !extendedOnly) await new Promise((resolve, reject) => webpack(baselineConfig, (error, stats) => error || stats.hasErrors() ? reject(error || Error(stats.toString({ all: false, errors: true }))) : resolve()));
|
|
56
|
+
server = http.createServer((request, response) => {
|
|
57
|
+
const asset = path.join(output, path.basename(request.url.split('?')[0]));
|
|
58
|
+
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); }
|
|
59
|
+
else { response.setHeader('Content-Type', 'text/html; charset=utf-8'); response.end('<!doctype html><html><head><meta charset="utf-8"></head><body><script>window.bootErrors=[];window.onerror=function(m,u,l,c,e){bootErrors.push({message:m,url:u,line:l,stack:e&&e.stack})}</script><script src="/' + (request.url.includes('baseline=1') ? 'baseline.js' : 'bundle.js') + '"></script></body></html>'); }
|
|
60
|
+
});
|
|
61
|
+
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
62
|
+
const endpoint = await new Promise((resolve, reject) => {
|
|
63
|
+
chrome = spawn(chromePath, ['--headless=new', '--disable-gpu', '--no-first-run', '--no-default-browser-check', '--remote-debugging-port=0', '--user-data-dir=' + path.join(output, 'profile'), 'about:blank']);
|
|
64
|
+
chrome.on('error', reject);
|
|
65
|
+
chrome.on('exit', code => reject(Error('Chrome exited: ' + code)));
|
|
66
|
+
chrome.stderr.on('data', data => { const match = String(data).match(/DevTools listening on (ws:\/\/[^\s]+)/); if (match) resolve(match[1]); });
|
|
67
|
+
});
|
|
68
|
+
ws = new WebSocket(endpoint);
|
|
69
|
+
await new Promise(resolve => ws.on('open', resolve));
|
|
70
|
+
ws.on('message', message => {
|
|
71
|
+
const data = JSON.parse(message);
|
|
72
|
+
if (pending.has(data.id)) { const entry = pending.get(data.id); pending.delete(data.id); data.error ? entry.reject(Error(JSON.stringify(data.error))) : entry.resolve(data.result); }
|
|
73
|
+
});
|
|
74
|
+
for (const version of (uiOnly || extendedOnly ? ['current'] : ['legacy', 'baseline', 'current'])) {
|
|
75
|
+
const legacy = version === 'legacy';
|
|
76
|
+
const { targetId } = await call('Target.createTarget', { url: 'about:blank' });
|
|
77
|
+
const { sessionId } = await call('Target.attachToTarget', { targetId, flatten: true });
|
|
78
|
+
await call('Emulation.setDeviceMetricsOverride', { width: 1200, height: 800, deviceScaleFactor: 1, mobile: false }, sessionId);
|
|
79
|
+
const evaluate = async expression => {
|
|
80
|
+
const result = await call('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true }, sessionId);
|
|
81
|
+
if (result.exceptionDetails) throw Error(JSON.stringify(result.exceptionDetails));
|
|
82
|
+
return result.result.value;
|
|
83
|
+
};
|
|
84
|
+
await call('Page.navigate', { url: 'http://127.0.0.1:' + server.address().port + '/?legacy=' + Number(legacy) + '&baseline=' + Number(version === 'baseline') }, sessionId);
|
|
85
|
+
for (let attempt = 0; attempt < 100; attempt++) {
|
|
86
|
+
if (await evaluate('Boolean(window.acceptance)')) break;
|
|
87
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
88
|
+
}
|
|
89
|
+
const suite = results[version] = {};
|
|
90
|
+
console.log('boot errors:', await evaluate('window.bootErrors'));
|
|
91
|
+
if (extendedOnly) {
|
|
92
|
+
suite.mount = await evaluate('acceptance.mount(20)');
|
|
93
|
+
suite.prepare = await evaluate('acceptance.pointerAndKeyboardPrepare()');
|
|
94
|
+
for (const [key, code] of [['ArrowDown', 40], ['Enter', 13]]) {
|
|
95
|
+
await call('Input.dispatchKeyEvent', { type: 'keyDown', key, code: key, windowsVirtualKeyCode: code }, sessionId);
|
|
96
|
+
await call('Input.dispatchKeyEvent', { type: 'keyUp', key, code: key, windowsVirtualKeyCode: code }, sessionId);
|
|
97
|
+
await evaluate('new Promise(resolve => setTimeout(resolve, 150))');
|
|
98
|
+
suite[key] = await evaluate('({selected: (grid.getSelectedCell() || {}).row && grid.getSelectedCell().row.id, field: (grid.getSelectedCell() || {}).column && grid.getSelectedCell().column.field, input: Boolean(document.querySelector(".el-input__inner")), errors: window.testErrors})');
|
|
99
|
+
}
|
|
100
|
+
suite.layout = await evaluate('acceptance.complexLayout()');
|
|
101
|
+
const shot = await call('Page.captureScreenshot', { format: 'png' }, sessionId);
|
|
102
|
+
fs.writeFileSync(path.join(output, 'extended.png'), Buffer.from(shot.data, 'base64'));
|
|
103
|
+
persist(); console.log(JSON.stringify(suite, null, 2));
|
|
104
|
+
if (suite.ArrowDown.selected !== 2 || suite.ArrowDown.field !== 'name' || suite.Enter.selected !== 3 || suite.Enter.field !== 'name' || !suite.layout.merged || !suite.layout.fixed || !suite.layout.bottomVisible || suite.layout.renderedRows > 100 || suite.layout.errors.length) process.exitCode = 1;
|
|
105
|
+
await call('Target.closeTarget', { targetId });
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (uiOnly) {
|
|
109
|
+
suite.mount = await evaluate('acceptance.mountUI()');
|
|
110
|
+
await call('Input.dispatchMouseEvent', { type: 'mouseMoved', x: suite.mount.x, y: suite.mount.y }, sessionId);
|
|
111
|
+
suite.tooltip = await evaluate('acceptance.tooltipResult()');
|
|
112
|
+
for (const [name, expression] of [['tooltip', null], ['validation', 'acceptance.validationUI()'], ['loading', 'acceptance.loadingUI(true)']]) {
|
|
113
|
+
if (expression) suite[name] = await evaluate(expression);
|
|
114
|
+
const shot = await call('Page.captureScreenshot', { format: 'png' }, sessionId);
|
|
115
|
+
fs.writeFileSync(path.join(output, name + '.png'), Buffer.from(shot.data, 'base64'));
|
|
116
|
+
}
|
|
117
|
+
suite.loadingOff = await evaluate('acceptance.loadingUI(false)');
|
|
118
|
+
suite.legacyIcons = await evaluate('acceptance.legacyIconStyles()');
|
|
119
|
+
suite.customIcon = await evaluate('acceptance.customIconOverride()');
|
|
120
|
+
persist();
|
|
121
|
+
console.log(JSON.stringify(suite, null, 2));
|
|
122
|
+
if (!suite.mount.overflow || !suite.tooltip.visible || !suite.validation.visible || !suite.loading.visible || !suite.loading.fontLoaded || !suite.loading.animated || !suite.loading.glyph || ['none', 'normal', '""'].includes(suite.loading.glyph) || !suite.loadingOff.hidden || Object.values(suite).some(item => item.failure || item.errors && item.errors.length)) process.exitCode = 1;
|
|
123
|
+
if (!suite.legacyIcons.every(icon => icon.preserved)) process.exitCode = 1;
|
|
124
|
+
if (!suite.customIcon.preserved) process.exitCode = 1;
|
|
125
|
+
await call('Target.closeTarget', { targetId });
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
for (const [name, expression] of [
|
|
129
|
+
['mount', 'acceptance.mount(20)'], ['interaction', 'acceptance.exercise()'],
|
|
130
|
+
['pointer', 'acceptance.pointerAndKeyboardPrepare()'],
|
|
131
|
+
['treeMount', 'acceptance.mount(2, true)'], ['tree', 'acceptance.tree()'],
|
|
132
|
+
['lazyTreeMount', 'acceptance.mount(2, "lazy")'], ['lazyTree', 'acceptance.tree()'],
|
|
133
|
+
['legacyScrollConfig', 'acceptance.mount(200, false, false)'],
|
|
134
|
+
['mount10000', 'acceptance.mount(10000)'], ['performance10000', 'acceptance.performance()'],
|
|
135
|
+
['mount50000', 'acceptance.mount(50000)'], ['performance50000', 'acceptance.performance()']
|
|
136
|
+
]) {
|
|
137
|
+
try { suite[name] = await evaluate(expression); } catch (error) { suite[name] = { failure: error.message }; }
|
|
138
|
+
console.log(version + ' ' + name + ': ' + JSON.stringify(suite[name]));
|
|
139
|
+
persist();
|
|
140
|
+
if (name === 'interaction') {
|
|
141
|
+
const screenshot = await call('Page.captureScreenshot', { format: 'png' }, sessionId);
|
|
142
|
+
fs.writeFileSync(path.join(output, version + '.png'), Buffer.from(screenshot.data, 'base64'));
|
|
143
|
+
await evaluate('acceptance.openSelect()');
|
|
144
|
+
const selectShot = await call('Page.captureScreenshot', { format: 'png' }, sessionId);
|
|
145
|
+
fs.writeFileSync(path.join(output, version + '-select.png'), Buffer.from(selectShot.data, 'base64'));
|
|
146
|
+
}
|
|
147
|
+
if (name === 'pointer' && !suite[name].failure) {
|
|
148
|
+
await call('Input.dispatchMouseEvent', { type: 'mousePressed', x: suite[name].x, y: suite[name].y, button: 'left', clickCount: 1 }, sessionId);
|
|
149
|
+
await call('Input.dispatchMouseEvent', { type: 'mouseReleased', x: suite[name].x, y: suite[name].y, button: 'left', clickCount: 1 }, sessionId);
|
|
150
|
+
await call('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Tab', code: 'Tab', windowsVirtualKeyCode: 9 }, sessionId);
|
|
151
|
+
await call('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Tab', code: 'Tab', windowsVirtualKeyCode: 9 }, sessionId);
|
|
152
|
+
suite.keyboard = await evaluate('acceptance.keyboardResult()');
|
|
153
|
+
console.log(version + ' keyboard: ' + JSON.stringify(suite.keyboard));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (const count of [10000, 50000]) {
|
|
157
|
+
suite['samples' + count] = [];
|
|
158
|
+
for (let round = 0; round < 3; round++) {
|
|
159
|
+
const mount = await evaluate('acceptance.mount(' + count + ')');
|
|
160
|
+
const operation = await evaluate('acceptance.performance()');
|
|
161
|
+
suite['samples' + count].push({ mountMs: mount.mountMs, ...operation });
|
|
162
|
+
}
|
|
163
|
+
console.log(version + ' samples' + count + ': ' + JSON.stringify(suite['samples' + count]));
|
|
164
|
+
}
|
|
165
|
+
await call('Target.closeTarget', { targetId });
|
|
166
|
+
}
|
|
167
|
+
console.log('Result directory: ' + output);
|
|
168
|
+
persist();
|
|
169
|
+
if (uiOnly || extendedOnly) return;
|
|
170
|
+
for (const version of ['legacy', 'current']) {
|
|
171
|
+
const suite = results[version];
|
|
172
|
+
const required = ['interaction', 'lazyTree', 'tree', 'legacyScrollConfig', 'pointer', 'keyboard'];
|
|
173
|
+
if (required.some(name => !suite[name] || suite[name].failure) || !Array.isArray(suite.pointer.selectionEvents)) {
|
|
174
|
+
suite.assertionFailure = 'Missing or failed required interaction result';
|
|
175
|
+
process.exitCode = 1;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const interaction = suite.interaction;
|
|
179
|
+
if (!suite.lazyTree || !suite.lazyTree.childVisible || suite.lazyTree.lazyCalls !== 1) process.exitCode = 1;
|
|
180
|
+
if (!interaction || interaction.sortedFirst !== 1 || interaction.selected !== 20 || interaction.edited !== 'Edited' || interaction.role !== 'b' || !interaction.invalid || !interaction.valid || !suite.tree.childVisible || suite.legacyScrollConfig.renderedRows >= 100 || suite.pointer.checked !== 1 || suite.pointer.selectionEvents.join(',') !== '1' || suite.keyboard.field !== 'amount') process.exitCode = 1;
|
|
181
|
+
for (const count of [10000, 50000]) {
|
|
182
|
+
if (!Array.isArray(suite['samples' + count]) || suite['samples' + count].some(sample => sample.selected !== count || !sample.lastRowVisible || !sample.editInputPresent || sample.renderedRows > 100 || !Array.isArray(sample.errors) || sample.errors.length)) process.exitCode = 1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
fs.writeFileSync(path.join(output, 'results.json'), JSON.stringify(results, null, 2));
|
|
186
|
+
if (Object.values(results).some(suite => Object.values(suite).some(result => result.failure || result.errors && result.errors.length))) process.exitCode = 1;
|
|
187
|
+
})().catch(error => { results.failure = error.stack || String(error); persist(); console.error(error); console.error('Result directory: ' + output); process.exitCode = 1; }).finally(() => {
|
|
188
|
+
if (ws) ws.close();
|
|
189
|
+
if (chrome) chrome.kill();
|
|
190
|
+
if (server) server.close();
|
|
191
|
+
setTimeout(() => process.exit(process.exitCode || 0), 500);
|
|
192
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Read committed source without modifying the worktree; all dependencies stay fixed.
|
|
2
|
+
const { execFileSync } = require('child_process');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
module.exports = function (source) {
|
|
5
|
+
const root = path.resolve(__dirname, '../../..');
|
|
6
|
+
const relative = path.relative(root, this.resourcePath);
|
|
7
|
+
if (!/^packages\/ux-grid\/(src|mixins)\//.test(relative)) return source;
|
|
8
|
+
try { return execFileSync('git', ['show', 'HEAD:' + relative], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }); }
|
|
9
|
+
catch (error) { throw Error('Missing committed baseline source: ' + relative); }
|
|
10
|
+
};
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/* Real-layout acceptance page; loaded in an isolated Chromium context. */
|
|
2
|
+
import 'babel-polyfill';
|
|
3
|
+
import Vue from 'vue';
|
|
4
|
+
import Element from 'element-ui';
|
|
5
|
+
import 'element-ui/lib/theme-chalk/index.css';
|
|
6
|
+
Vue.use(Element);
|
|
7
|
+
Vue.config.productionTip = false;
|
|
8
|
+
window.testErrors = [];
|
|
9
|
+
window.longTasks = [];
|
|
10
|
+
if (window.PerformanceObserver) new PerformanceObserver(list => window.longTasks.push(...list.getEntries().map(entry => ({ start: entry.startTime, duration: entry.duration })))).observe({ entryTypes: ['longtask'] });
|
|
11
|
+
Vue.config.errorHandler = error => window.testErrors.push(error.stack || String(error));
|
|
12
|
+
const legacy = new URLSearchParams(location.search).get('legacy') === '1';
|
|
13
|
+
let Grid;
|
|
14
|
+
if (legacy) {
|
|
15
|
+
const old = require('umy-table/lib/umy-table.common.js');
|
|
16
|
+
Vue.use(old.default || old);
|
|
17
|
+
Grid = Vue.options.components.UxGrid;
|
|
18
|
+
require('umy-table/lib/theme-chalk/index.css');
|
|
19
|
+
} else {
|
|
20
|
+
Grid = require('../src/ux-grid.vue').default;
|
|
21
|
+
require('../../../lib/theme-chalk/ux-grid.css');
|
|
22
|
+
}
|
|
23
|
+
const delay = (ms = 100) => new Promise(resolve => setTimeout(resolve, ms));
|
|
24
|
+
window.acceptance = {
|
|
25
|
+
async mountUI() {
|
|
26
|
+
if (window.vm) { window.vm.$destroy(); window.vm.$el.remove(); }
|
|
27
|
+
window.vm = new Vue({
|
|
28
|
+
data: () => ({ loading: false, rows: [{ id: 1, name: 'Overflow tooltip must display this entire long sentence', required: '' }] }),
|
|
29
|
+
render(h) { return h(Grid, { ref: 'grid', props: {
|
|
30
|
+
data: this.rows, height: 300, loading: this.loading, showOverflow: 'tooltip',
|
|
31
|
+
validConfig: { message: 'tooltip' }, editConfig: { trigger: 'click', mode: 'cell' },
|
|
32
|
+
rules: { required: [{ required: true, message: 'Required tooltip validation' }] },
|
|
33
|
+
columns: [{ field: 'name', title: 'Overflow', width: 120 }, { field: 'required', title: 'Required', width: 160, editRender: { name: 'ElInput' } }]
|
|
34
|
+
} }); }
|
|
35
|
+
}).$mount();
|
|
36
|
+
document.body.appendChild(vm.$el);
|
|
37
|
+
await delay(350);
|
|
38
|
+
window.grid = vm.$refs.grid.$refs.singleTable;
|
|
39
|
+
const cell = document.querySelector('tbody tr td .vxe-cell');
|
|
40
|
+
const rect = cell.getBoundingClientRect();
|
|
41
|
+
const textRange = document.createRange();
|
|
42
|
+
textRange.selectNodeContents(cell);
|
|
43
|
+
return { x: rect.x + 25, y: rect.y + 12, overflow: textRange.getBoundingClientRect().width > cell.closest('td').getBoundingClientRect().width };
|
|
44
|
+
},
|
|
45
|
+
async tooltipResult(validation = false) {
|
|
46
|
+
await delay(750);
|
|
47
|
+
const expected = validation ? 'Required tooltip validation' : vm.rows[0].name;
|
|
48
|
+
const popups = Array.from(document.querySelectorAll('.vxe-tooltip--wrapper'));
|
|
49
|
+
return { visible: popups.some(el => el.textContent.includes(expected) && el.getBoundingClientRect().width > 0 && getComputedStyle(el).visibility !== 'hidden' && getComputedStyle(el).display !== 'none'), expected, errors: window.testErrors };
|
|
50
|
+
},
|
|
51
|
+
async validationUI() {
|
|
52
|
+
try { await grid.fullValidate(vm.rows); } catch (_) { /* Legacy and new rejection conventions differ. */ }
|
|
53
|
+
return this.tooltipResult(true);
|
|
54
|
+
},
|
|
55
|
+
async loadingUI(value) {
|
|
56
|
+
vm.loading = value;
|
|
57
|
+
await delay(150);
|
|
58
|
+
const mask = document.querySelector('.vxe-loading.is--visible');
|
|
59
|
+
if (!value) return { hidden: !mask || getComputedStyle(mask).display === 'none', errors: window.testErrors };
|
|
60
|
+
const icon = mask && mask.querySelector('.vxe-loading--icon i');
|
|
61
|
+
if (!icon) return { visible: Boolean(mask), failure: 'Default loading icon missing' };
|
|
62
|
+
await document.fonts.ready;
|
|
63
|
+
const style = getComputedStyle(icon), pseudo = getComputedStyle(icon, '::before'), rect = icon.getBoundingClientRect();
|
|
64
|
+
const first = style.transform;
|
|
65
|
+
await delay(170);
|
|
66
|
+
return { visible: rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none',
|
|
67
|
+
glyph: pseudo.content, font: pseudo.fontFamily,
|
|
68
|
+
fontLoaded: Array.from(document.fonts).some(font => pseudo.fontFamily.includes(font.family.replace(/['"]/g, '')) && font.status === 'loaded'),
|
|
69
|
+
animated: first !== getComputedStyle(icon).transform, firstTransform: first, secondTransform: getComputedStyle(icon).transform, errors: window.testErrors };
|
|
70
|
+
},
|
|
71
|
+
legacyIconStyles() {
|
|
72
|
+
const parent = document.createElement('div');
|
|
73
|
+
parent.style.fontSize = '23px';
|
|
74
|
+
document.body.appendChild(parent);
|
|
75
|
+
const result = ['vxe-icon--upload', 'vxe-icon--eye'].map(name => {
|
|
76
|
+
const icon = document.createElement('i');
|
|
77
|
+
icon.className = name;
|
|
78
|
+
parent.appendChild(icon);
|
|
79
|
+
const style = getComputedStyle(icon);
|
|
80
|
+
return { name, font: style.fontFamily, size: style.fontSize, parentSize: getComputedStyle(parent).fontSize,
|
|
81
|
+
preserved: !style.fontFamily.includes('vxeiconfont') && style.fontSize === getComputedStyle(parent).fontSize };
|
|
82
|
+
});
|
|
83
|
+
parent.remove();
|
|
84
|
+
return result;
|
|
85
|
+
},
|
|
86
|
+
customIconOverride() {
|
|
87
|
+
const style = document.createElement('style');
|
|
88
|
+
style.textContent = '.vxe-icon-spinner{font-family:consumer-test!important}';
|
|
89
|
+
document.head.appendChild(style);
|
|
90
|
+
const icon = document.createElement('i');
|
|
91
|
+
icon.className = 'vxe-icon-spinner';
|
|
92
|
+
document.body.appendChild(icon);
|
|
93
|
+
const font = getComputedStyle(icon).fontFamily;
|
|
94
|
+
icon.remove(); style.remove();
|
|
95
|
+
return { font, preserved: font.includes('consumer-test') };
|
|
96
|
+
},
|
|
97
|
+
async complexLayout() {
|
|
98
|
+
await this.mount(200);
|
|
99
|
+
await grid.loadColumn([{ field: 'name', title: 'Fixed name', fixed: 'left', width: 200 }, { field: 'amount', title: 'Merged amount', width: 300 }, { field: 'role', title: 'Wide role', width: 1400 }]);
|
|
100
|
+
await grid.setMergeCells([{ row: 0, col: 1, rowspan: 2, colspan: 1 }]);
|
|
101
|
+
await delay(200);
|
|
102
|
+
const merged = document.querySelector('td[rowspan="2"]');
|
|
103
|
+
const fixed = document.querySelector('.vxe-table--fixed-left-wrapper');
|
|
104
|
+
const result = { merged: Boolean(merged), fixed: Boolean(fixed && fixed.getBoundingClientRect().width > 0), renderedRows: document.querySelectorAll('tbody tr').length };
|
|
105
|
+
await grid.scrollTo(800, 100000);
|
|
106
|
+
await delay(250);
|
|
107
|
+
result.bottomVisible = document.body.innerText.includes('Row 199');
|
|
108
|
+
result.errors = window.testErrors;
|
|
109
|
+
return result;
|
|
110
|
+
},
|
|
111
|
+
async mount(count, tree = false, explicitEnabled = true) {
|
|
112
|
+
if (window.vm) { window.vm.$destroy(); window.vm.$el.remove(); }
|
|
113
|
+
const rows = Array.from({ length: count }, (_, index) => ({ id: index + 1, name: 'Row ' + index, amount: count - index, role: 'a' }));
|
|
114
|
+
window.lazyCalls = 0;
|
|
115
|
+
if (tree === 'lazy') rows[0].hasChildren = true;
|
|
116
|
+
else if (tree) rows[0].children = [{ id: 'child', name: 'Child row', amount: 0, role: 'a' }];
|
|
117
|
+
const start = performance.now();
|
|
118
|
+
window.vm = new Vue({
|
|
119
|
+
data: () => ({ rows }),
|
|
120
|
+
render(h) {
|
|
121
|
+
return h(Grid, { ref: 'grid', props: {
|
|
122
|
+
data: this.rows, height: 420, rowId: 'id', border: true,
|
|
123
|
+
scrollY: explicitEnabled ? { enabled: true, gt: 0 } : { gt: 0 }, scrollX: { enabled: true, gt: 0 },
|
|
124
|
+
treeConfig: tree === 'lazy' ? { children: 'children', lazy: true, hasChild: 'hasChildren', loadMethod: ({ row }) => { window.lazyCalls++; return Promise.resolve([{ id: row.id + '-child', name: 'Child row', amount: 0, role: 'a' }]); } } : tree ? { children: 'children' } : null,
|
|
125
|
+
editConfig: { trigger: 'click', mode: 'cell' },
|
|
126
|
+
keyboardConfig: { isTab: true, isArrow: true, isEdit: true, isEnter: true }, mouseConfig: { selected: true },
|
|
127
|
+
rules: { name: [{ required: true, message: 'Name required' }] },
|
|
128
|
+
columns: [
|
|
129
|
+
{ type: 'checkbox', width: 50 },
|
|
130
|
+
{ field: 'name', title: 'Name', width: 200, treeNode: tree, editRender: { name: 'ElInput' } },
|
|
131
|
+
{ field: 'amount', title: 'Amount', width: 150, sortable: true },
|
|
132
|
+
{ field: 'role', title: 'Role', width: 180, editRender: { name: 'ElSelect', options: [{ label: 'Alpha', value: 'a' }, { label: 'Beta', value: 'b' }] } }
|
|
133
|
+
]
|
|
134
|
+
} });
|
|
135
|
+
}
|
|
136
|
+
}).$mount();
|
|
137
|
+
document.body.appendChild(window.vm.$el);
|
|
138
|
+
await delay(350);
|
|
139
|
+
window.wrapper = window.vm.$refs.grid;
|
|
140
|
+
window.grid = window.wrapper.$refs.singleTable;
|
|
141
|
+
return { count, mountMs: performance.now() - start, renderedRows: document.querySelectorAll('tbody tr').length, text: document.body.innerText.slice(0, 250) };
|
|
142
|
+
},
|
|
143
|
+
async exercise() {
|
|
144
|
+
const g = window.grid;
|
|
145
|
+
await g.sort('amount', 'asc');
|
|
146
|
+
const sorted = g.getTableData().visibleData || g.getTableData().fullData;
|
|
147
|
+
await g.setAllCheckboxRow(true);
|
|
148
|
+
const selected = g.getCheckboxRecords().length;
|
|
149
|
+
await g.clearCheckboxRow();
|
|
150
|
+
const row = window.vm.rows[0];
|
|
151
|
+
await (g.setEditCell || g.setActiveCell).call(g, row, 'name');
|
|
152
|
+
await delay();
|
|
153
|
+
const input = document.querySelector('.el-input__inner');
|
|
154
|
+
if (!input) throw Error('Element edit input missing');
|
|
155
|
+
input.value = 'Edited';
|
|
156
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
157
|
+
await Vue.nextTick();
|
|
158
|
+
const edited = row.name;
|
|
159
|
+
await (g.clearEdit || g.clearActived).call(g);
|
|
160
|
+
await (g.setEditCell || g.setActiveCell).call(g, row, 'role');
|
|
161
|
+
await delay();
|
|
162
|
+
document.querySelector('.el-select .el-input__inner').click();
|
|
163
|
+
await delay();
|
|
164
|
+
const options = Array.from(document.querySelectorAll('.el-select-dropdown__item'));
|
|
165
|
+
const labels = options.map(option => option.textContent.trim());
|
|
166
|
+
const beta = options.find(option => option.textContent.trim() === 'Beta');
|
|
167
|
+
if (beta) beta.click();
|
|
168
|
+
await delay();
|
|
169
|
+
await (g.clearEdit || g.clearActived).call(g);
|
|
170
|
+
row.name = '';
|
|
171
|
+
let invalid = false;
|
|
172
|
+
try { const errors = await g.fullValidate([row]); invalid = Boolean(errors && errors.name); }
|
|
173
|
+
catch (errors) { invalid = Boolean(errors && errors.name); }
|
|
174
|
+
row.name = 'Edited';
|
|
175
|
+
await g.clearValidate();
|
|
176
|
+
const valid = !(await g.fullValidate([row]));
|
|
177
|
+
return { sortedFirst: sorted[0].amount, selected, edited, labels, role: row.role, invalid, valid, errors: window.testErrors };
|
|
178
|
+
},
|
|
179
|
+
async openSelect() {
|
|
180
|
+
await (grid.setEditCell || grid.setActiveCell).call(grid, vm.rows[0], 'role');
|
|
181
|
+
await delay();
|
|
182
|
+
document.querySelector('.el-select .el-input__inner').click();
|
|
183
|
+
await delay();
|
|
184
|
+
return { optionCount: document.querySelectorAll('.el-select-dropdown__item').length };
|
|
185
|
+
},
|
|
186
|
+
async pointerAndKeyboardPrepare() {
|
|
187
|
+
await (grid.clearEdit || grid.clearActived).call(grid);
|
|
188
|
+
await grid.clearSort();
|
|
189
|
+
await grid.clearCheckboxRow();
|
|
190
|
+
await grid.scrollTo(0, 0);
|
|
191
|
+
await delay();
|
|
192
|
+
window.selectionEvents = [];
|
|
193
|
+
wrapper.$on('selection-change', rows => window.selectionEvents.push(typeof rows === 'function' ? rows().length : rows.length));
|
|
194
|
+
const checkbox = document.querySelector('tbody tr .vxe-cell--checkbox,tbody tr .elx-cell--checkbox');
|
|
195
|
+
checkbox.click();
|
|
196
|
+
const checked = grid.getCheckboxRecords().length;
|
|
197
|
+
const cell = document.querySelector('tbody tr td:nth-child(2)');
|
|
198
|
+
const contextEvent = new MouseEvent('contextmenu', { bubbles: true, cancelable: true });
|
|
199
|
+
cell.dispatchEvent(contextEvent);
|
|
200
|
+
await grid.setSelectCell(vm.rows[0], 'name');
|
|
201
|
+
const bounds = cell.getBoundingClientRect();
|
|
202
|
+
return { checked, selectionEvents: window.selectionEvents, contextPrevented: contextEvent.defaultPrevented, x: bounds.x + 30, y: bounds.y + 15 };
|
|
203
|
+
},
|
|
204
|
+
async keyboardResult() {
|
|
205
|
+
await delay();
|
|
206
|
+
const selected = (grid.getSelectedCell || grid.getSelectCell).call(grid);
|
|
207
|
+
return { field: selected && selected.column && (selected.column.field || selected.column.property), errors: window.testErrors };
|
|
208
|
+
},
|
|
209
|
+
async performance() {
|
|
210
|
+
const g = window.grid;
|
|
211
|
+
const operationStart = performance.now();
|
|
212
|
+
let start = performance.now();
|
|
213
|
+
await g.setAllCheckboxRow(true);
|
|
214
|
+
const selectMs = performance.now() - start;
|
|
215
|
+
const selected = g.getCheckboxRecords().length;
|
|
216
|
+
await g.clearCheckboxRow();
|
|
217
|
+
const body = document.querySelector('.vxe-table--scroll-y-handle') || document.querySelector('.vxe-table--body-wrapper,.elx-table--body-wrapper');
|
|
218
|
+
start = performance.now();
|
|
219
|
+
body.scrollTop = body.scrollHeight;
|
|
220
|
+
body.dispatchEvent(new Event('scroll'));
|
|
221
|
+
await delay(200);
|
|
222
|
+
const result = { selectMs, selected, scrollMs: performance.now() - start, scrollTop: body.scrollTop, renderedRows: document.querySelectorAll('tbody tr').length, lastRowVisible: document.body.innerText.includes('Row ' + (window.vm.rows.length - 1)), errors: window.testErrors };
|
|
223
|
+
start = performance.now();
|
|
224
|
+
await (g.setEditCell || g.setActiveCell).call(g, window.vm.rows[window.vm.rows.length - 1], 'name');
|
|
225
|
+
await Vue.nextTick();
|
|
226
|
+
result.editMs = performance.now() - start;
|
|
227
|
+
result.editInputPresent = Boolean(document.querySelector('.el-input__inner'));
|
|
228
|
+
await (g.clearEdit || g.clearActived).call(g);
|
|
229
|
+
start = performance.now();
|
|
230
|
+
await g.refreshColumn();
|
|
231
|
+
result.refreshColumnMs = performance.now() - start;
|
|
232
|
+
await delay(50);
|
|
233
|
+
result.longTasks = window.longTasks.filter(task => task.start >= operationStart);
|
|
234
|
+
result.usedJSHeapSize = performance.memory ? performance.memory.usedJSHeapSize : null;
|
|
235
|
+
return result;
|
|
236
|
+
},
|
|
237
|
+
async tree() {
|
|
238
|
+
await window.grid.setTreeExpand(window.vm.rows[0], true);
|
|
239
|
+
await delay();
|
|
240
|
+
return { childVisible: document.body.innerText.includes('Child row'), lazyCalls: window.lazyCalls, errors: window.testErrors };
|
|
241
|
+
}
|
|
242
|
+
};
|
|
@@ -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
|
+
};
|