@cdmx/wappler_ag_grid 2.1.3 → 2.1.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.
- package/.claude/settings.json +7 -0
- package/CHANGELOG.md +23 -1
- package/app_connect/components.hjson +6 -5
- package/dmx-ag-grid.js +37 -12
- package/package.json +5 -2
- package/tests/01-basic-render.html +106 -0
- package/tests/02-themes-locale.html +98 -0
- package/tests/03-editing.html +119 -0
- package/tests/04-selection.html +103 -0
- package/tests/05-action-buttons.html +97 -0
- package/tests/06-export.html +80 -0
- package/tests/07-import.html +80 -0
- package/tests/08-formatting.html +91 -0
- package/tests/09-grouping.html +74 -0
- package/tests/10-state-persistence.html +84 -0
- package/tests/11-layout.html +72 -0
- package/tests/12-methods.html +93 -0
- package/tests/13-flags-styles.html +374 -0
- package/tests/14-compact-view.html +112 -0
- package/tests/README.md +146 -0
- package/tests/data/sample-data.js +52 -0
- package/tests/dmxAppConnect/dmxAppConnect.js +10 -0
- package/tests/dmxAppConnect/dmxAppConnect.js.map +1 -0
- package/tests/index.html +107 -0
- package/tests/lib/common-head.html +30 -0
- package/tests/lib/page-template.js +54 -0
- package/tests/lib/run-collector.js +32 -0
- package/tests/lib/test-helpers.js +145 -0
- package/tests/lib/test-suite.css +178 -0
- package/tests/run-all-puppeteer.cjs +180 -0
- package/tests/run-all.cjs +144 -0
- package/tests/serve.cjs +66 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// Alternative runner using puppeteer-core against the system Chrome.
|
|
2
|
+
// More reliable than the agent-browser CLI driver for long sequential runs.
|
|
3
|
+
// Works on Windows/Linux/macOS — set PUPPETEER_EXECUTABLE_PATH to override the
|
|
4
|
+
// browser auto-detection.
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const puppeteer = require('puppeteer-core');
|
|
8
|
+
|
|
9
|
+
const BASE = process.env.AG_TEST_BASE || 'http://localhost:8765/tests';
|
|
10
|
+
const OUT_FILE = path.join(__dirname, 'findings.json');
|
|
11
|
+
|
|
12
|
+
const PAGES = [
|
|
13
|
+
{ file: '01-basic-render.html', grids: ['basicGrid'] },
|
|
14
|
+
{ file: '02-themes-locale.html', grids: ['g_alpine','g_balham','g_material','g_quartz','g_custom','g_dark','g_he','g_ru','g_es'] },
|
|
15
|
+
{ file: '03-editing.html', grids: ['cellEdit','rowEdit','selectEdit'] },
|
|
16
|
+
{ file: '04-selection.html', grids: ['selGrid','singleSel'] },
|
|
17
|
+
{ file: '05-action-buttons.html', grids: ['actGrid'] },
|
|
18
|
+
{ file: '06-export.html', grids: ['expGrid','noExp'] },
|
|
19
|
+
{ file: '07-import.html', grids: ['importGrid'], expectEmpty: true },
|
|
20
|
+
{ file: '08-formatting.html', grids: ['fmtGrid','ctypeGrid','hdrGrid'] },
|
|
21
|
+
{ file: '09-grouping.html', grids: ['grpGrid','aggGrid'] },
|
|
22
|
+
{ file: '10-state-persistence.html',grids: ['stateGrid'] },
|
|
23
|
+
{ file: '11-layout.html', grids: ['autoH','fixedH','scrollH'] },
|
|
24
|
+
{ file: '12-methods.html', grids: ['m'] },
|
|
25
|
+
{ file: '13-flags-styles.html', grids: ['flagsGrid','rsFn','rsOp','rsCompound','rsShort','rsFirst','rsLegacy','csCell','transformGrid','tipGrid'], pageAssertion: '__rsCheck' },
|
|
26
|
+
{ file: '14-compact-view.html', grids: ['compactGrid','extButtons','cellEvtGrid'] }
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
function findChrome() {
|
|
30
|
+
if (process.env.PUPPETEER_EXECUTABLE_PATH) return process.env.PUPPETEER_EXECUTABLE_PATH;
|
|
31
|
+
const candidates = process.platform === 'win32' ? [
|
|
32
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
33
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
34
|
+
'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
|
|
35
|
+
'C:\\Users\\Roney\\.agent-browser\\browsers\\chrome-148.0.7778.97\\chrome.exe'
|
|
36
|
+
] : process.platform === 'darwin' ? [
|
|
37
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
38
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium'
|
|
39
|
+
] : [
|
|
40
|
+
'/usr/bin/google-chrome',
|
|
41
|
+
'/usr/bin/chromium',
|
|
42
|
+
'/usr/bin/chromium-browser',
|
|
43
|
+
'/snap/bin/chromium'
|
|
44
|
+
];
|
|
45
|
+
return candidates.find(p => fs.existsSync(p));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
(async () => {
|
|
49
|
+
const exe = findChrome();
|
|
50
|
+
if (!exe) {
|
|
51
|
+
console.error('No Chrome executable found — set PUPPETEER_EXECUTABLE_PATH');
|
|
52
|
+
process.exit(2);
|
|
53
|
+
}
|
|
54
|
+
console.log('chrome:', exe);
|
|
55
|
+
|
|
56
|
+
const browser = await puppeteer.launch({
|
|
57
|
+
executablePath: exe,
|
|
58
|
+
headless: 'new',
|
|
59
|
+
args: ['--no-sandbox', '--disable-dev-shm-usage']
|
|
60
|
+
});
|
|
61
|
+
const page = await browser.newPage();
|
|
62
|
+
page.setDefaultTimeout(30000);
|
|
63
|
+
|
|
64
|
+
const findings = { startedAt: new Date().toISOString(), base: BASE, pages: [] };
|
|
65
|
+
const pageErrors = [];
|
|
66
|
+
page.on('pageerror', e => pageErrors.push({ msg: e.message }));
|
|
67
|
+
page.on('console', msg => {
|
|
68
|
+
if (msg.type() === 'error') pageErrors.push({ msg: msg.text() });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
for (const spec of PAGES) {
|
|
72
|
+
pageErrors.length = 0;
|
|
73
|
+
const url = BASE + '/' + spec.file;
|
|
74
|
+
console.log('\n--- ' + spec.file);
|
|
75
|
+
try {
|
|
76
|
+
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.log(' navigation failed:', e.message);
|
|
79
|
+
findings.pages.push({ file: spec.file, status: 'NAV_FAIL', error: e.message });
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Wait for gridsReady or 8s, whichever comes first.
|
|
84
|
+
try {
|
|
85
|
+
await page.waitForFunction(
|
|
86
|
+
() => document.body.dataset.gridsReady === 'true',
|
|
87
|
+
{ timeout: spec.expectEmpty ? 2000 : 8000 }
|
|
88
|
+
);
|
|
89
|
+
} catch (_) { /* fine for expectEmpty pages */ }
|
|
90
|
+
// Settle render
|
|
91
|
+
await new Promise(r => setTimeout(r, 600));
|
|
92
|
+
|
|
93
|
+
const data = await page.evaluate(() => {
|
|
94
|
+
try {
|
|
95
|
+
if (!window.TestKit) return { fatal: 'no-testkit' };
|
|
96
|
+
const grids = TestKit.list();
|
|
97
|
+
const out = { url: location.href, appConnect: window.dmx && dmx.version, ready: document.body.dataset.gridsReady === 'true', grids: {} };
|
|
98
|
+
grids.forEach(id => {
|
|
99
|
+
const c = TestKit.grid(id);
|
|
100
|
+
if (!c) return;
|
|
101
|
+
out.grids[id] = {
|
|
102
|
+
count: c.count,
|
|
103
|
+
state: c.state,
|
|
104
|
+
dataLen: Array.isArray(c.data) ? c.data.length : null,
|
|
105
|
+
selectedRowsLen: (c.selectedRows || []).length,
|
|
106
|
+
filterStateKeys: c.filterState && typeof c.filterState === 'object' ? Object.keys(c.filterState) : [],
|
|
107
|
+
columnStateLen: (c.columnState || []).length,
|
|
108
|
+
domRows: document.querySelectorAll('#' + id + '-grid .ag-row').length
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
return out;
|
|
112
|
+
} catch (e) { return { fatal: e.message }; }
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const expectedGrids = spec.grids;
|
|
116
|
+
const actualGrids = data.grids ? Object.keys(data.grids) : [];
|
|
117
|
+
const missing = expectedGrids.filter(g => !actualGrids.includes(g));
|
|
118
|
+
const judgeReady = g => {
|
|
119
|
+
const s = data.grids && data.grids[g] && data.grids[g].state;
|
|
120
|
+
if (!s) return false;
|
|
121
|
+
if (spec.expectEmpty) return true;
|
|
122
|
+
// Any of the three event flags counts. The module overwrites state on each
|
|
123
|
+
// event (so only the last-fired flag is observable), but any of them
|
|
124
|
+
// appearing proves the grid mounted and at least one onGrid* event fired.
|
|
125
|
+
return !!(s.gridReady || s.firstDataRendered || s.rowDataUpdated);
|
|
126
|
+
};
|
|
127
|
+
const judgeRows = g => spec.expectEmpty ? true : (data.grids && data.grids[g] && data.grids[g].domRows > 0);
|
|
128
|
+
const allReady = data.grids && expectedGrids.every(judgeReady);
|
|
129
|
+
const allHaveRows = data.grids && expectedGrids.every(judgeRows);
|
|
130
|
+
// Run a page-defined assertion (e.g. scenario 13 verifies rstyles actually applied).
|
|
131
|
+
let assertion = null;
|
|
132
|
+
if (spec.pageAssertion) {
|
|
133
|
+
try {
|
|
134
|
+
assertion = await page.evaluate((key) => window[key] || null, spec.pageAssertion);
|
|
135
|
+
} catch (e) { assertion = { error: e.message }; }
|
|
136
|
+
}
|
|
137
|
+
const assertionFailed = assertion && assertion.ok === false;
|
|
138
|
+
|
|
139
|
+
const status = data.fatal ? 'ERROR'
|
|
140
|
+
: missing.length ? 'MISSING'
|
|
141
|
+
: !allReady ? 'NOT_READY'
|
|
142
|
+
: !allHaveRows ? 'NO_DOM_ROWS'
|
|
143
|
+
: assertionFailed ? 'ASSERTION_FAIL'
|
|
144
|
+
: 'PASS';
|
|
145
|
+
console.log(' status:', status);
|
|
146
|
+
if (data.fatal) console.log(' fatal:', data.fatal);
|
|
147
|
+
if (missing.length) console.log(' missing:', missing.join(','));
|
|
148
|
+
if (data.grids) {
|
|
149
|
+
for (const g of expectedGrids) {
|
|
150
|
+
const x = data.grids[g];
|
|
151
|
+
if (!x) continue;
|
|
152
|
+
console.log(' ' + g + ': count=' + x.count + ' domRows=' + x.domRows + ' state=' + JSON.stringify(x.state));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (pageErrors.length) {
|
|
156
|
+
console.log(' page errors:', pageErrors.length);
|
|
157
|
+
pageErrors.slice(0, 3).forEach(e => console.log(' ', e.msg.slice(0, 200)));
|
|
158
|
+
}
|
|
159
|
+
if (assertion) {
|
|
160
|
+
if (assertionFailed) {
|
|
161
|
+
console.log(' assertion FAILED — checks that did not match:');
|
|
162
|
+
(assertion.failed || []).forEach(k => {
|
|
163
|
+
const c = assertion.checks && assertion.checks[k];
|
|
164
|
+
console.log(' ' + k + ' expected ' + (c && c.expect) + ' got ' + (c && c.actual));
|
|
165
|
+
});
|
|
166
|
+
} else if (assertion.ok) {
|
|
167
|
+
const n = Object.keys(assertion.checks || {}).length;
|
|
168
|
+
console.log(' page assertions passed (' + n + ' checks)');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
findings.pages.push({ file: spec.file, status, expectedGrids, missing, result: data, pageErrors: pageErrors.slice(), assertion });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await browser.close();
|
|
175
|
+
findings.finishedAt = new Date().toISOString();
|
|
176
|
+
fs.writeFileSync(OUT_FILE, JSON.stringify(findings, null, 2));
|
|
177
|
+
console.log('\n=== summary ===');
|
|
178
|
+
console.table(findings.pages.map(p => ({ file: p.file, status: p.status })));
|
|
179
|
+
console.log('wrote ' + OUT_FILE);
|
|
180
|
+
})().catch(e => { console.error(e); process.exit(1); });
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Drives agent-browser through every scenario and writes
|
|
2
|
+
// tests/findings.json + a console summary. One comprehensive eval per page
|
|
3
|
+
// keeps the WS connection short-lived and immune to daemon timeouts.
|
|
4
|
+
const { execFileSync } = require('child_process');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
// Use the npm shim (`agent-browser` / `agent-browser.cmd`) with shell:true on both
|
|
9
|
+
// platforms. The native .exe entry point hangs when stdout is captured, but the
|
|
10
|
+
// shim wraps it properly. Override via AGENT_BROWSER_BIN if needed.
|
|
11
|
+
const AB = process.env.AGENT_BROWSER_BIN || 'agent-browser';
|
|
12
|
+
|
|
13
|
+
const BASE = process.env.AG_TEST_BASE || 'http://localhost:8765/tests';
|
|
14
|
+
const OUT_FILE = path.join(__dirname, 'findings.json');
|
|
15
|
+
const PAGES = [
|
|
16
|
+
{ file: '01-basic-render.html', grids: ['basicGrid'] },
|
|
17
|
+
{ file: '02-themes-locale.html', grids: ['g_alpine','g_balham','g_material','g_quartz','g_custom','g_dark','g_he','g_ru','g_es'] },
|
|
18
|
+
{ file: '03-editing.html', grids: ['cellEdit','rowEdit','selectEdit'] },
|
|
19
|
+
{ file: '04-selection.html', grids: ['selGrid','singleSel'] },
|
|
20
|
+
{ file: '05-action-buttons.html', grids: ['actGrid'] },
|
|
21
|
+
{ file: '06-export.html', grids: ['expGrid','noExp'] },
|
|
22
|
+
{ file: '07-import.html', grids: ['importGrid'], expectEmpty: true },
|
|
23
|
+
{ file: '08-formatting.html', grids: ['fmtGrid','ctypeGrid','hdrGrid'] },
|
|
24
|
+
{ file: '09-grouping.html', grids: ['grpGrid','aggGrid'] },
|
|
25
|
+
{ file: '10-state-persistence.html',grids: ['stateGrid'] },
|
|
26
|
+
{ file: '11-layout.html', grids: ['autoH','fixedH','scrollH'] },
|
|
27
|
+
{ file: '12-methods.html', grids: ['m'] },
|
|
28
|
+
{ file: '13-flags-styles.html', grids: ['flagsGrid','stylesGrid','transformGrid','tipGrid'] },
|
|
29
|
+
{ file: '14-compact-view.html', grids: ['compactGrid','extButtons','cellEvtGrid'] }
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
function ab(args, { allowFail = false, timeout = 60000 } = {}) {
|
|
33
|
+
if (process.env.AB_DEBUG) console.error('[ab] ' + AB + ' ' + args.join(' '));
|
|
34
|
+
try {
|
|
35
|
+
return execFileSync(AB, args, { encoding: 'utf8', timeout, shell: true });
|
|
36
|
+
} catch (e) {
|
|
37
|
+
if (allowFail) return '__ERR__ ' + (e.stderr || e.message || e.stdout || '');
|
|
38
|
+
throw e;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// agent-browser --json wraps the result as
|
|
43
|
+
// {"success":true,"data":{"origin":"…","result":"<stringified JS>"},"error":null}
|
|
44
|
+
// Pull out data.result and parse it again if it's a stringified JSON.
|
|
45
|
+
function decodeEval(raw) {
|
|
46
|
+
let outer;
|
|
47
|
+
try { outer = JSON.parse(raw); } catch (_) { return { parseError: 'outer JSON', raw: raw.slice(0,400) }; }
|
|
48
|
+
if (!outer || outer.error) return { parseError: 'agent-browser error', detail: outer && outer.error };
|
|
49
|
+
const r = outer.data && outer.data.result;
|
|
50
|
+
if (typeof r === 'string') {
|
|
51
|
+
try { return JSON.parse(r); } catch (_) { return { result: r }; }
|
|
52
|
+
}
|
|
53
|
+
return outer;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
57
|
+
|
|
58
|
+
// The actual collector lives in tests/lib/run-collector.js and is injected via
|
|
59
|
+
// --init-script. Each eval call here is tiny so shell quoting stays trivial.
|
|
60
|
+
// Use forward slashes — Windows cmd handles them fine and avoids backslash escaping.
|
|
61
|
+
const COLLECTOR_SCRIPT = path.join(__dirname, 'lib', 'run-collector.js').replace(/\\/g, '/');
|
|
62
|
+
const COLLECT_EXPR = '__abCollect()';
|
|
63
|
+
|
|
64
|
+
(async () => {
|
|
65
|
+
console.log('=== AG Grid test suite run ===');
|
|
66
|
+
console.log('base:', BASE);
|
|
67
|
+
|
|
68
|
+
// Reuse the existing session — closing forces a slow daemon restart, and
|
|
69
|
+
// the same Chrome tab can navigate between scenarios just fine.
|
|
70
|
+
|
|
71
|
+
const findings = { startedAt: new Date().toISOString(), base: BASE, pages: [] };
|
|
72
|
+
|
|
73
|
+
function runOne(page) {
|
|
74
|
+
const url = BASE + '/' + page.file;
|
|
75
|
+
let opened = ab(['open', '"' + url + '"', '--init-script', '"' + COLLECTOR_SCRIPT + '"'], { allowFail: true, timeout: 45000 });
|
|
76
|
+
// Disconnect → restart daemon and try once more.
|
|
77
|
+
if (opened.startsWith('__ERR__') || /Failed to read|10060|timed out/i.test(opened)) {
|
|
78
|
+
ab(['close', '--all'], { allowFail: true, timeout: 20000 });
|
|
79
|
+
opened = ab(['open', '"' + url + '"', '--init-script', '"' + COLLECTOR_SCRIPT + '"'], { allowFail: true, timeout: 60000 });
|
|
80
|
+
}
|
|
81
|
+
// Page-template.js sets document.body.dataset.gridsReady once all grids are up.
|
|
82
|
+
// Sleep first instead of polling — fewer round-trips means fewer chances for
|
|
83
|
+
// the WS connection to time out.
|
|
84
|
+
return new Promise(resolve => setTimeout(() => resolve(), Math.max(3000, page.grids.length * 800)));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const page of PAGES) {
|
|
88
|
+
console.log('\n--- ' + page.file);
|
|
89
|
+
await runOne(page);
|
|
90
|
+
let raw = ab(['eval', '"' + COLLECT_EXPR + '"', '--json'], { allowFail: true, timeout: 30000 });
|
|
91
|
+
let parsed = decodeEval(raw);
|
|
92
|
+
if (parsed.parseError || raw.startsWith('__ERR__')) {
|
|
93
|
+
ab(['close', '--all'], { allowFail: true, timeout: 20000 });
|
|
94
|
+
await sleep(1500);
|
|
95
|
+
await runOne(page);
|
|
96
|
+
raw = ab(['eval', '"' + COLLECT_EXPR + '"', '--json'], { allowFail: true, timeout: 30000 });
|
|
97
|
+
parsed = decodeEval(raw);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const expectedGrids = page.grids;
|
|
101
|
+
const actualGrids = parsed.grids ? Object.keys(parsed.grids) : [];
|
|
102
|
+
const missing = expectedGrids.filter(g => !actualGrids.includes(g));
|
|
103
|
+
// Scenarios marked `expectEmpty` start with no data — the grid mounts but never
|
|
104
|
+
// renders rows. Treat presence + empty data as a PASS for those.
|
|
105
|
+
const judgeReady = (g) => {
|
|
106
|
+
const s = parsed.grids && parsed.grids[g] && parsed.grids[g].state;
|
|
107
|
+
if (!s) return false;
|
|
108
|
+
if (page.expectEmpty) return true; // mounted, intentionally empty
|
|
109
|
+
return !!(s.gridReady || s.firstDataRendered);
|
|
110
|
+
};
|
|
111
|
+
const judgeRows = (g) => {
|
|
112
|
+
if (page.expectEmpty) return true;
|
|
113
|
+
const r = parsed.grids && parsed.grids[g];
|
|
114
|
+
return r && r.domRows > 0;
|
|
115
|
+
};
|
|
116
|
+
const allReady = parsed.grids && expectedGrids.every(judgeReady);
|
|
117
|
+
const allHaveRows = parsed.grids && expectedGrids.every(judgeRows);
|
|
118
|
+
|
|
119
|
+
const status = parsed.fatal ? 'ERROR' :
|
|
120
|
+
(missing.length ? 'MISSING' :
|
|
121
|
+
(!allReady ? 'NOT_READY' :
|
|
122
|
+
(!allHaveRows ? 'NO_DOM_ROWS' : 'PASS')));
|
|
123
|
+
|
|
124
|
+
console.log(' status:', status);
|
|
125
|
+
if (missing.length) console.log(' missing grids:', missing.join(','));
|
|
126
|
+
if (parsed.grids) {
|
|
127
|
+
for (const g of expectedGrids) {
|
|
128
|
+
const x = parsed.grids[g];
|
|
129
|
+
if (!x) continue;
|
|
130
|
+
console.log(' ' + g + ': count=' + x.count + ' domRows=' + x.domRows + ' state=' + JSON.stringify(x.state));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (parsed.fatal) console.log(' fatal:', parsed.fatal);
|
|
134
|
+
|
|
135
|
+
findings.pages.push({ file: page.file, status, expectedGrids, missing, result: parsed });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
findings.finishedAt = new Date().toISOString();
|
|
139
|
+
fs.writeFileSync(OUT_FILE, JSON.stringify(findings, null, 2));
|
|
140
|
+
const summary = findings.pages.map(p => ({ file: p.file, status: p.status, missing: p.missing }));
|
|
141
|
+
console.log('\\n=== summary ===');
|
|
142
|
+
console.table(summary);
|
|
143
|
+
console.log('wrote ' + OUT_FILE);
|
|
144
|
+
})().catch(e => { console.error(e); process.exit(1); });
|
package/tests/serve.cjs
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Minimal static server for the AG Grid test suite.
|
|
2
|
+
// Serves the repo root on http://localhost:8765 with no caching.
|
|
3
|
+
const http = require('http');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const url = require('url');
|
|
7
|
+
|
|
8
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
9
|
+
const PORT = Number(process.env.AG_TEST_PORT || 8765);
|
|
10
|
+
|
|
11
|
+
const MIME = {
|
|
12
|
+
'.html': 'text/html; charset=utf-8',
|
|
13
|
+
'.js': 'application/javascript; charset=utf-8',
|
|
14
|
+
'.cjs': 'application/javascript; charset=utf-8',
|
|
15
|
+
'.mjs': 'application/javascript; charset=utf-8',
|
|
16
|
+
'.css': 'text/css; charset=utf-8',
|
|
17
|
+
'.json': 'application/json; charset=utf-8',
|
|
18
|
+
'.map': 'application/json; charset=utf-8',
|
|
19
|
+
'.svg': 'image/svg+xml',
|
|
20
|
+
'.png': 'image/png',
|
|
21
|
+
'.jpg': 'image/jpeg',
|
|
22
|
+
'.gif': 'image/gif',
|
|
23
|
+
'.ico': 'image/x-icon',
|
|
24
|
+
'.woff': 'font/woff',
|
|
25
|
+
'.woff2':'font/woff2',
|
|
26
|
+
'.ttf': 'font/ttf',
|
|
27
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
28
|
+
'.csv': 'text/csv; charset=utf-8'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const server = http.createServer((req, res) => {
|
|
32
|
+
const parsed = url.parse(req.url);
|
|
33
|
+
let rel = decodeURIComponent(parsed.pathname || '/');
|
|
34
|
+
if (rel === '/' || rel === '') rel = '/tests/index.html';
|
|
35
|
+
const abs = path.join(ROOT, rel);
|
|
36
|
+
if (!abs.startsWith(ROOT)) {
|
|
37
|
+
res.writeHead(403); res.end('forbidden'); return;
|
|
38
|
+
}
|
|
39
|
+
fs.stat(abs, (err, st) => {
|
|
40
|
+
if (err) { res.writeHead(404); res.end('not found: ' + rel); return; }
|
|
41
|
+
if (st.isDirectory()) {
|
|
42
|
+
const idx = path.join(abs, 'index.html');
|
|
43
|
+
if (fs.existsSync(idx)) {
|
|
44
|
+
sendFile(idx, res);
|
|
45
|
+
} else {
|
|
46
|
+
res.writeHead(404); res.end('directory listing disabled');
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
sendFile(abs, res);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
function sendFile(abs, res) {
|
|
55
|
+
const ext = path.extname(abs).toLowerCase();
|
|
56
|
+
res.writeHead(200, {
|
|
57
|
+
'content-type': MIME[ext] || 'application/octet-stream',
|
|
58
|
+
'cache-control': 'no-store',
|
|
59
|
+
'access-control-allow-origin': '*'
|
|
60
|
+
});
|
|
61
|
+
fs.createReadStream(abs).pipe(res);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
server.listen(PORT, '127.0.0.1', () => {
|
|
65
|
+
console.log('ag-grid test suite serving on http://localhost:' + PORT + '/tests/index.html');
|
|
66
|
+
});
|