테스트코드 삭제
This commit is contained in:
+1
-1
@@ -228,7 +228,7 @@ VSCode나 별도 터미널에서 `sn` Next 개발 서버가 실행 중이면 `ne
|
||||
|
||||
### 프론트엔드 실행 프로세스 관리
|
||||
|
||||
- 모든 세션·역할·문서/검증 섹션에 공통 D:/workspace/AGENTS.md의 테스트 임시 파일·결과 파일 정리 기준을 적용한다. 일회성 검증 스크립트·캡처·Network/서버 로그·원시 결과 파일은 최종 보고서에 요약 후 삭제한다. 정식 회귀 테스트·최종 산출물·사용자 원본/복구 백업은 유지한다. 사용자 dev server가 쓰는 .next·공유 캐시·node_modules는 임시 검증물이라는 이유로 포괄 삭제하지 않는다.
|
||||
- 모든 세션·역할·문서/검증 섹션에 공통 D:/workspace/AGENTS.md의 테스트 파일 정리 기준을 적용한다. 에이전트가 테스트를 위해 만든 파일은 정식 회귀 테스트·재사용 가능한 소스·fixture·mock·임시 라우트를 포함해 위치와 관계없이 결과를 요약한 뒤 작업 종료 전에 삭제한다(2026-09-23 사용자 지시). 전용 빈 폴더와 끊어진 실행 명령도 정리한다. 명시 보존 요청 파일·최종 산출물·사용자 원본/복구 백업은 유지한다. 기존 타 작업자 테스트 자산은 삭제 권한을 먼저 확인한다. 사용자 dev server의 .next·공유 캐시·node_modules는 포괄 삭제하지 않는다.
|
||||
|
||||
- Codex 에이전트가 작업 또는 검증을 위해 직접 기동한 `next dev`, `dev:sn`, Turborepo dev와 기타 프론트엔드 장시간 실행 프로세스는 작업 종료 전에 반드시 종료한다.
|
||||
- 셸, 백그라운드, 숨김 창 등 VSCode 밖에서 에이전트가 기동한 프로세스도 종료 대상이다.
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
type ChartOptions,
|
||||
} from "chart.js";
|
||||
import { Bar } from "react-chartjs-2";
|
||||
|
||||
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
|
||||
|
||||
export default function Page() {
|
||||
const labels = ["한강", "낙동강", "영산강", "금강"];
|
||||
|
||||
// 데이터: [기타, 오수관로, 우수관로]
|
||||
const datasets = [
|
||||
{
|
||||
label: "기타",
|
||||
data: [15481, 6959, 1938, 1967],
|
||||
backgroundColor: "rgba(255, 178, 150, 0.9)", // 연한 주황(파스텔톤)
|
||||
borderColor: "rgba(255,178,150,1)",
|
||||
},
|
||||
{
|
||||
label: "오수관로",
|
||||
data: [15878, 16336, 5670, 8073],
|
||||
backgroundColor: "rgba(180, 240, 190, 0.95)", // 연한 초록(파스텔톤)
|
||||
borderColor: "rgba(180,240,190,1)",
|
||||
},
|
||||
{
|
||||
label: "우수관로",
|
||||
data: [12892, 10491, 4280, 4838],
|
||||
backgroundColor: "rgba(170, 210, 245, 0.95)", // 연한 파랑(파스텔톤)
|
||||
borderColor: "rgba(170,210,245,1)",
|
||||
},
|
||||
];
|
||||
|
||||
// 총합(라벨당) 계산
|
||||
const totals = labels.map((_, i) =>
|
||||
datasets.reduce((s, ds) => s + (ds.data[i] as number), 0),
|
||||
);
|
||||
|
||||
const options: ChartOptions<"bar"> = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: "top",
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (ctx) => `${ctx.dataset.label}: ${Number(ctx.parsed.y).toLocaleString()}km`,
|
||||
},
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
callback: (value) => `${Number(value).toLocaleString()}km`,
|
||||
},
|
||||
},
|
||||
},
|
||||
// 각 스택 위에 총합 표시
|
||||
animation: {
|
||||
onComplete: function (animation) {
|
||||
const chart = animation.chart;
|
||||
const ctx = chart.ctx;
|
||||
const lastMeta = chart.getDatasetMeta(datasets.length - 1);
|
||||
ctx.save();
|
||||
ctx.fillStyle = "#111";
|
||||
ctx.font = "gray bold 12px Arial";
|
||||
ctx.textAlign = "center";
|
||||
|
||||
totals.forEach((total, i) => {
|
||||
const bar = lastMeta.data[i];
|
||||
if (!bar) return;
|
||||
const x = bar.x;
|
||||
// y는 마지막 스택의 y 좌표(스택의 최상단)에서 조금 위로
|
||||
const y = bar.y - 8;
|
||||
ctx.fillText(`총 ${total.toLocaleString()}km`, x, y);
|
||||
});
|
||||
|
||||
ctx.restore();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const data = { labels, datasets };
|
||||
|
||||
return (
|
||||
<div style={{ width: "100%", maxWidth: 800, height: 360 }}>
|
||||
<Bar options={options} data={data} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
# SN 전체 목록 그리드 말줄임표
|
||||
|
||||
> 2026-09-23 후속 정리: 사용자 요청에 따라 `scripts/grid-cell-tooltip.test.cjs`를 삭제했다. 아래 테스트 명령과 결과는 삭제 전 실행 이력이며 테스트 소스 보존 문구는 더 이상 적용하지 않는다. 구현 코드와 이 최종 보고서는 유지한다.
|
||||
|
||||
2026-09-23 사용자 요청: 노후 하수관로 정비사업 화면처럼 긴 내용이 행 높이를 늘리는 현상을 SN 전체 그리드에서 정리한다.
|
||||
기존 원본 화면의 표 컬럼·업무값은 유지하고 이번 요청을 표시 방식 변경 근거로 적용했다.
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# SN 그리드 표시 순번 오름차순 통일
|
||||
|
||||
> 2026-09-23 후속 정리: 사용자 요청에 따라 `scripts/row-number.test.cjs`를 삭제했다. 아래 테스트 명령과 결과는 삭제 전 실행 이력이며 테스트 소스 보존 문구는 더 이상 적용하지 않는다. 구현 코드와 이 최종 보고서는 유지한다.
|
||||
|
||||
## 목적 및 기준
|
||||
|
||||
2026-09-23 사용자 지시: 하수관로 SN 전체 그리드의 표시 순번을 1, 2, 3…으로 통일한다.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 노후관로 목록 조회조건 및 표시항목 변경
|
||||
|
||||
> 2026-09-23 후속 정리: 사용자 요청에 따라 `scripts/aged-pipe-list.test.cjs`를 삭제했다. 아래 테스트 명령과 결과는 삭제 전 실행 이력이며 테스트 소스 보존 문구는 더 이상 적용하지 않는다. 구현 코드와 이 최종 보고서는 유지한다.
|
||||
|
||||
## 목적과 근거
|
||||
|
||||
- 2026-09-23 사용자 요청: 조회조건을 시도, 시군구, 경과년도 순으로 변경하고 관리기관, 설치일자, 경과년도를 연속 배치한다.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 하수관망도 레이어 전체 켜기·끄기
|
||||
|
||||
> 2026-09-23 후속 정리: 사용자 요청에 따라 `scripts/layer-bulk-selection.test.cjs`를 삭제했다. 아래 테스트 명령과 결과는 삭제 전 실행 이력이며 테스트 소스 보존 문구는 더 이상 적용하지 않는다. 구현 코드와 이 최종 보고서는 유지한다.
|
||||
|
||||
2026-09-23 사용자 요청으로 트리의 '레이어 관리' 제목 오른쪽에 전체 표시 토글을 추가했다.
|
||||
후속 요청에 따라 두 버튼을 단일 스위치로 바꿨다. 모두 선택이면 ON, 일부 선택이면 OFF 위치와 '일부 켜짐' 문구로 표시한다.
|
||||
OFF 또는 부분 선택 상태에서 누르면 전체 켜기, ON 상태에서 누르면 전체 끄기가 실행된다.
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
"build": "next build",
|
||||
"start:sn": "next start -p 3002",
|
||||
"lint": "next lint",
|
||||
"test:refactor": "node scripts/run-refactor-tests.cjs",
|
||||
"typecheck": "tsc --noEmit --incremental false --pretty false",
|
||||
"check:boundaries": "node scripts/check-module-boundaries.cjs",
|
||||
"lint:refactor": "eslint src/shared/api/http-transport.ts src/app/providers/app-provider.tsx src/features/dredging src/features/mapLayers src/features/gisManagement --rule \"@typescript-eslint/no-explicit-any:warn\" --max-warnings 22"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
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 directory = path.resolve(__dirname, '../app/(map-layout)/map/components/agedPipe');
|
||||
const source = fs.readFileSync(path.join(directory, 'agedPipeUtils.ts'), 'utf8');
|
||||
const compiled = ts.transpileModule(source, {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS },
|
||||
}).outputText;
|
||||
const exportsValue = {};
|
||||
vm.runInNewContext(compiled, {
|
||||
exports: exportsValue,
|
||||
require: (id) => {
|
||||
if (id.endsWith('/formatDisplayValue')) return { formatDisplayData: (row) => row };
|
||||
if (id === '../common/table') return { MS_SWAGE_PPL_CODES: [], MS_SWAGE_PPL_USE_CODES: [] };
|
||||
throw new Error(`Unexpected import: ${id}`);
|
||||
},
|
||||
});
|
||||
const { AGED_PIPE_LIST_HEADLIST: headers, getAgedPipeManagementName: name } = exportsValue;
|
||||
|
||||
test('list preserves existing columns and groups agency, installation date and age', () => {
|
||||
assert.deepEqual(Array.from(headers, (column) => column.id), [
|
||||
'rowNumber', 'fcltMngNo', 'mngInstNm', 'instlYmd', 'elapsedYears',
|
||||
'pdmtVl', 'prlgVl', 'usgCd', 'mqltCd',
|
||||
]);
|
||||
assert.equal(headers.reduce((total, column) => total + column.widthPercent, 0), 100);
|
||||
});
|
||||
|
||||
test('agency name uses existing API names without changing source codes', () => {
|
||||
const agencies = [{ mngInstCd: '123', lwrInstNm: '검증기관' }];
|
||||
const row = { mngInstCd: '123' };
|
||||
assert.equal(name(row, agencies), '검증기관');
|
||||
assert.equal(row.mngInstCd, '123');
|
||||
assert.equal(name({ mng_inst_cd: ' 123 ' }, agencies), '검증기관');
|
||||
assert.equal(name({ mngInstNm: '원본기관', mngInstCd: '123' }, agencies), '원본기관');
|
||||
assert.equal(name({ mngInstNm: '-', mngInstCd: '123' }, agencies), '검증기관');
|
||||
assert.equal(name({ mngInstCd: '999' }, agencies), '999');
|
||||
assert.equal(name({}, agencies), '-');
|
||||
});
|
||||
|
||||
test('search controls are ordered province, municipality, elapsed years', () => {
|
||||
const page = fs.readFileSync(path.join(directory, 'AgedPipePopupPage.tsx'), 'utf8');
|
||||
const panel = page.slice(page.indexOf('<PopupSearchPanel'), page.indexOf('</PopupSearchPanel>'));
|
||||
const labels = Array.from(panel.matchAll(/<span[^>]*>\s*([^<]+?)\s*<\/span>/g), (match) => match[1].trim());
|
||||
assert.deepEqual(labels, ['시도', '시군구', '경과년도']);
|
||||
});
|
||||
|
||||
test('aged pipe popup uses the common centered placement without changing its size', () => {
|
||||
const definitions = fs.readFileSync(path.resolve(__dirname, '../src/features/windowPopup/model/popupDefinitions.ts'), 'utf8');
|
||||
const exported = {};
|
||||
vm.runInNewContext(ts.transpileModule(definitions, {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS },
|
||||
}).outputText, { exports: exported });
|
||||
const popup = exported.windowPopupDefinitions.agedPipePopup;
|
||||
assert.equal(popup.width, 1160);
|
||||
assert.equal(popup.height, 820);
|
||||
assert.equal(popup.placement ?? 'center', 'center');
|
||||
assert.equal(popup.alignTop ?? false, false);
|
||||
assert.equal(popup.offsetLeft ?? 0, 0);
|
||||
assert.equal(popup.offsetTop ?? 0, 0);
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
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,'');
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
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:[]});
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
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);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user