관망도 레이어 전체켜기 기능 추가

This commit is contained in:
2026-09-23 13:50:50 +09:00
parent 35b9f813c4
commit 8818dd21c6
41 changed files with 461 additions and 51 deletions
@@ -0,0 +1,48 @@
const assert = require('node:assert/strict');
const test = require('node:test');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const ts = require('typescript');
const root = path.resolve(__dirname, '..');
const source = fs.readFileSync(path.join(root, 'src/shared/ui/grid-cell-tooltip.tsx'), 'utf8');
const css = fs.readFileSync(path.join(root, 'src/app/styles/grid-ellipsis.css'), 'utf8');
function mount() {
const handlers = {};
let cleanup;
class Cell {
constructor(text, overflowing = true) { this.textContent = text; this.title = ''; this.clientWidth = 100; this.scrollWidth = overflowing ? 300 : 100; }
closest() { return this; }
querySelector() { return null; }
querySelectorAll() { return []; }
hasAttribute() { return Boolean(this.title); }
removeAttribute() { this.title = ''; }
}
const module = {exports:{}};
vm.runInNewContext(ts.transpileModule(source, {compilerOptions:{module:ts.ModuleKind.CommonJS}}).outputText, {
module, exports:module.exports, Element:Cell,
require:()=>({useEffect:fn=>{cleanup=fn();}}),
document:{addEventListener:(name,handler)=>{handlers[name]=handler;},removeEventListener:name=>{delete handlers[name];}},
});
module.exports.GridCellTooltip();
return {Cell,handlers,cleanup};
}
test('CSS and tooltip target the same SN text cells and preserve controls', () => {
const selector = source.match(/const CELL_SELECTOR = '([^']+)'/)[1];
assert.ok(css.includes(selector + ' {'));
for (const token of ['input','textarea','button','[colspan]','[rowspan]','data-sn-cell-wrap']) assert.ok(selector.includes(token));
});
test('overflow tooltip retains full text and updates reused cells', () => {
const {Cell,handlers,cleanup}=mount(); const cell=new Cell('긴 원문\n내용');
handlers.pointerover({target:cell}); assert.equal(cell.title,cell.textContent);
cell.textContent='변경된 원문'; handlers.pointerover({target:cell}); assert.equal(cell.title,cell.textContent);
cell.scrollWidth=100; handlers.pointerover({target:cell}); assert.equal(cell.title,'');
cleanup(); assert.deepEqual(handlers,{});
});
test('existing explicit tooltip and editable-cell exclusions remain authoritative', () => {
const {Cell,handlers}=mount(); const cell=new Cell('원문'); cell.title='기존 설명';
handlers.pointerover({target:cell}); assert.equal(cell.title,'기존 설명');
const excluded=new Cell('입력'); excluded.closest=()=>null;
handlers.pointerover({target:excluded}); assert.equal(excluded.title,'');
});
@@ -0,0 +1,30 @@
const assert = require('node:assert/strict');
const test = require('node:test');
const { pathToFileURL } = require('node:url');
const path = require('node:path');
const moduleUrl = pathToFileURL(path.resolve(__dirname, '../../../packages/common/business_components/src/ui/map/layerBulkSelection.js'));
test('toggle state follows all, partial, off and empty selections', async () => {
const { getBulkLayerState } = await import(moduleUrl.href);
const nodes = [{id:'a',data:{exprsYn:'Y'}},{id:'b',data:{exprsYn:'Y'}},{id:'h',data:{exprsYn:'N'}}];
assert.deepEqual(getBulkLayerState(nodes, []), {disabled:false,allChecked:false,partiallyChecked:false});
assert.deepEqual(getBulkLayerState(nodes, ['a']), {disabled:false,allChecked:false,partiallyChecked:true});
assert.deepEqual(getBulkLayerState(nodes, ['a','b']), {disabled:false,allChecked:true,partiallyChecked:false});
assert.deepEqual(getBulkLayerState([], []), {disabled:true,allChecked:false,partiallyChecked:false});
});
test('bulk on checks visible parents and children, excluding hidden nodes', async () => {
const { getBulkLayerSelection } = await import(moduleUrl.href);
const nodes = [{id:1,data:{exprsYn:'Y'}},{id:2,parent:1,data:{exprsYn:'Y'}},{id:3,data:{exprsYn:'N'}}];
const result = getBulkLayerSelection(nodes, true);
assert.deepEqual(result.checkedIds, [1,2]);
assert.deepEqual(result.uncheckedIds, [3]);
assert.deepEqual(result.selectedNodes, nodes.slice(0,2));
});
test('bulk off clears every registered tree layer, without mutating input', async () => {
const { getBulkLayerSelection } = await import(moduleUrl.href);
const nodes = [{id:'a',data:{exprsYn:'Y'}},{id:'hidden',data:{exprsYn:'N'}}];
assert.deepEqual(getBulkLayerSelection(nodes, false), {checkedIds:[],selectedNodes:[],uncheckedIds:['a','hidden']});
assert.equal(nodes[0].data.exprsYn, 'Y');
assert.deepEqual(getBulkLayerSelection([], true), {checkedIds:[],selectedNodes:[],uncheckedIds:[]});
});
+44
View File
@@ -0,0 +1,44 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const test = require('node:test');
const ts = require('typescript');
const appRoot = path.resolve(__dirname, '..');
const source = fs.readFileSync(path.join(appRoot, 'src/shared/lib/row-number.ts'), 'utf8');
const compiled = ts.transpileModule(source, {
compilerOptions: { module: ts.ModuleKind.CommonJS },
}).outputText;
const moduleValue = { exports: {} };
vm.runInNewContext(compiled, { exports: moduleValue.exports, module: moduleValue });
const { getSnRowNumber } = moduleValue.exports;
test('non-paged rows start at one and do not depend on total count', () => {
assert.deepEqual([0, 1, 2].map(rowIndex => getSnRowNumber({total: 50, rowIndex})), [1, 2, 3]);
assert.equal(getSnRowNumber({total: 0, rowIndex: 0}), 1);
});
test('server pages continue numbering and preserve the final partial page', () => {
assert.equal(getSnRowNumber({total: 23, rowIndex: 0, pageIndex: 0, pageSize: 10}), 1);
assert.equal(getSnRowNumber({total: 23, rowIndex: 0, pageIndex: 1, pageSize: 10}), 11);
assert.equal(getSnRowNumber({total: 23, rowIndex: 2, pageIndex: 2, pageSize: 10}), 23);
});
test('page-size changes and refreshed result order use the new positions', () => {
assert.equal(getSnRowNumber({rowIndex: 0, pageIndex: 1, pageSize: 20}), 21);
assert.equal(getSnRowNumber({rowIndex: 0, pageIndex: 0, pageSize: 20}), 1);
});
function sourceFiles(directory) {
return fs.readdirSync(directory, {withFileTypes: true}).flatMap(entry => {
const full = path.join(directory, entry.name);
return entry.isDirectory() ? sourceFiles(full) : /\.tsx?$/.test(entry.name) ? [full] : [];
});
}
test('SN does not use the shared descending helper or legacy descending row formulas', () => {
for (const file of [...sourceFiles(path.join(appRoot, 'src')), ...sourceFiles(path.join(appRoot, 'app'))]) {
const text = fs.readFileSync(file, 'utf8');
assert.doesNotMatch(text, /getStandardRowNumber/, file);
assert.doesNotMatch(text, /(?:rowNumber|rowNo|no)\s*:\s*(?:[\w.]+\s*-\s*(?:index|detailIndex)|total\w*\s*-)/, file);
assert.doesNotMatch(text, /total(?:Elements|Count)?\s*-\s*\(\(?[^;{}\n]*(?:index|sourceIndex)/, file);
}
});