wip(est-test): 개발 중 저장 3 — 깃 싱크(11-08)
This commit is contained in:
@@ -12,6 +12,7 @@ for (const file of [
|
||||
"native-quantity.cjs",
|
||||
"native-wonga.js",
|
||||
"native-engine.js",
|
||||
"native-row-match.js",
|
||||
"native-escalation.js",
|
||||
"native-progress.js",
|
||||
"native-business.js",
|
||||
@@ -29,6 +30,7 @@ for (const file of [
|
||||
"native-c3r.js",
|
||||
"native-exchange.js",
|
||||
"native-verify.js",
|
||||
"native-block-actions.js",
|
||||
"native-extra.js",
|
||||
"native-business-actions.js",
|
||||
"native-forms.js",
|
||||
|
||||
@@ -1,6 +1,132 @@
|
||||
from test_est_rea_parity import run
|
||||
|
||||
|
||||
def test_machine_restore_group_uses_saved_baseline_without_changing_other_groups():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();
|
||||
p.tables.MachMainDat=[{PkID:1,Index:1,Code:'M1',Name:'장비1'},{PkID:2,Index:0,GN:2},
|
||||
{PkID:3,Index:1,Code:'M2',Name:'장비2'},{PkID:4,Index:0,GN:3}];
|
||||
p.tables.MachTitleDat=[{Code:'M1',Name:'장비1'},{Code:'M2',Name:'장비2'}];
|
||||
const q=EstModel.validate(JSON.parse(JSON.stringify(EstBusiness.baseline(p))));
|
||||
q.tables.MachMainDat[1].GN=9;q.tables.MachMainDat[3].GN=7;
|
||||
const before=JSON.stringify(q), rows=EstMachine.restoreGroup(q,'M1');
|
||||
const originalUntouched=JSON.stringify(q)===before;
|
||||
let missing=false,duplicate=false,collision=false;
|
||||
const legacy=structuredClone(q);delete legacy.baseline.tables.MachMainDat;
|
||||
try { EstMachine.restoreGroup(legacy,'M1'); } catch { missing=true; }
|
||||
const conflict=structuredClone(q);conflict.tables.MachMainDat[3].PkID=2;
|
||||
try { EstMachine.restoreGroup(conflict,'M1'); } catch { collision=true; }
|
||||
q.tables.MachMainDat.push({Index:1,Code:'M1'});
|
||||
try { EstMachine.restoreGroup(q,'M1'); } catch { duplicate=true; }
|
||||
return {restored:rows[1].GN,other:rows[3].GN,originalUntouched,
|
||||
baseline:q.baseline.tables.MachMainDat[1].GN,missing,duplicate,collision};
|
||||
})()""",
|
||||
)
|
||||
assert result == {
|
||||
"restored": 2,
|
||||
"other": 7,
|
||||
"originalUntouched": True,
|
||||
"baseline": 2,
|
||||
"missing": True,
|
||||
"duplicate": True,
|
||||
"collision": True,
|
||||
}
|
||||
|
||||
|
||||
def test_machine_change_includes_new_changed_and_deleted_titles():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();
|
||||
p.tables.MachTitleDat=[{Code:'M1',Name:'장비1',TC:150},{Code:'M3',Name:'신규',TC:50}];
|
||||
p.baseline={tables:{EstMainDat:[],MachTitleDat:[
|
||||
{Code:'M1',Name:'장비1',TC:100},{Code:'M2',Name:'삭제',TC:200}]}};
|
||||
return EstMachine.changes(p).map(({Code,originalTC,TC,difference,status,index})=>
|
||||
({Code,originalTC,TC,difference,status,index}));
|
||||
})()""",
|
||||
)
|
||||
assert result == [
|
||||
{
|
||||
"Code": "M1",
|
||||
"originalTC": 100,
|
||||
"TC": 150,
|
||||
"difference": 50,
|
||||
"status": "변경",
|
||||
"index": 0,
|
||||
},
|
||||
{
|
||||
"Code": "M3",
|
||||
"originalTC": None,
|
||||
"TC": 50,
|
||||
"difference": None,
|
||||
"status": "신규",
|
||||
"index": 1,
|
||||
},
|
||||
{
|
||||
"Code": "M2",
|
||||
"originalTC": 200,
|
||||
"TC": 0,
|
||||
"difference": -200,
|
||||
"status": "삭제",
|
||||
"index": None,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def test_recover_deleted_machine_group_and_reject_id_collision():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();
|
||||
p.tables.MachMainDat=[{PkID:1,Index:1,Code:'M1',Name:'first'},
|
||||
{PkID:2,Index:-2,GN:2,TU:100},{PkID:3,Index:1,Code:'M2',Name:'second'},
|
||||
{PkID:4,Index:-2,GN:3,TU:100}];
|
||||
p.tables.MachTitleDat=[{PkID:10,Code:'M1',Name:'first',TC:200},
|
||||
{PkID:11,Code:'M2',Name:'second',TC:300}];
|
||||
const q=EstBusiness.baseline(p);
|
||||
q.tables.MachMainDat=q.tables.MachMainDat.slice(2);
|
||||
q.tables.MachTitleDat=q.tables.MachTitleDat.slice(1);
|
||||
const before=JSON.stringify(q), restored=EstMachine.recoverGroup(q,'M1');
|
||||
const unchanged=JSON.stringify(q)===before;
|
||||
const collision=structuredClone(q);collision.tables.MachMainDat[1].PkID=2;
|
||||
let rejected=false;try { EstMachine.recoverGroup(collision,'M1'); } catch { rejected=true; }
|
||||
q.tables.MachMainDat=restored.rows;q.tables.MachTitleDat=restored.titles;
|
||||
return {codes:q.tables.MachTitleDat.map(x=>x.Code),quantity:q.tables.MachMainDat[1].GN,
|
||||
original:EstMachine.usage(q).find(x=>x.Code==='M1').originalPrice,unchanged,rejected};
|
||||
})()""",
|
||||
)
|
||||
assert result == {
|
||||
"codes": ["M1", "M2"],
|
||||
"quantity": 2,
|
||||
"original": 200,
|
||||
"unchanged": True,
|
||||
"rejected": True,
|
||||
}
|
||||
|
||||
|
||||
def test_recovered_machine_group_recalculates_linked_estimate_after_reopen():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();p.options.SourceRefresh=true;
|
||||
p.tables.MachMainDat=[{PkID:1,Index:1,Code:'M1',Name:'machine'},
|
||||
{PkID:2,Index:-2,Name:'fuel',GN:2,TU:100}];
|
||||
p.tables.MachTitleDat=[{PkID:10,Code:'M1',Name:'machine'}];
|
||||
p.tables.EstMainDat=[{PkID:20,Code:'M1',Name:'machine',LinkIndex:105,GN:2}];
|
||||
const q=EstBusiness.baseline(EstNative.calculate(p));
|
||||
q.tables.MachMainDat=[];q.tables.MachTitleDat=[];
|
||||
const restored=EstMachine.recoverGroup(JSON.parse(JSON.stringify(q)),'M1');
|
||||
q.tables.MachMainDat=restored.rows;q.tables.MachTitleDat=restored.titles;
|
||||
const out=EstNative.calculate(EstModel.validate(JSON.parse(JSON.stringify(q))));
|
||||
return {machine:out.tables.MachTitleDat[0].TC, estimate:out.tables.EstMainDat[0].TC,
|
||||
errors:out.calculation.errors};
|
||||
})()""",
|
||||
)
|
||||
assert result == {"machine": 200, "estimate": 400, "errors": []}
|
||||
|
||||
|
||||
def test_basic_prices_changes_for_material_and_labor():
|
||||
result = run(
|
||||
"math",
|
||||
|
||||
@@ -665,3 +665,12 @@ def test_vat_calculation_exclude_include_and_unit_rounding_for_materials():
|
||||
# M03: 12345 / 1.1 = 11222.72... -> 십원 단위 사사오입(11220), 절상(11230)
|
||||
assert result["m3RoundCost"] == 11220
|
||||
assert result["m3UpCost"] == 11230
|
||||
|
||||
|
||||
def test_vat_round_up_handles_small_remainders_and_exact_boundaries():
|
||||
result = run(
|
||||
"math",
|
||||
"""[EstVat.convertPrice(100.1, 0, 1, 2),
|
||||
EstVat.convertPrice(100, 0, 1, 2)]""",
|
||||
)
|
||||
assert result == [120, 110]
|
||||
|
||||
@@ -1,6 +1,84 @@
|
||||
from test_est_rea_parity import run
|
||||
|
||||
|
||||
def test_change_comparison_matches_reordered_duplicate_codes():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();p.options.SourceRefresh=false;
|
||||
p.tables.EstMainDat=[{Code:'A',Name:'first',GN:10,GU:80},
|
||||
{Code:'A',Name:'second',GN:20,GU:50}];
|
||||
const q=EstBusiness.baseline(EstNative.calculate(p));
|
||||
[q.tables.EstMainDat[0],q.tables.EstMainDat[1]]=[q.tables.EstMainDat[1],q.tables.EstMainDat[0]];
|
||||
q.tables.EstMainDat[0].GN=22;
|
||||
return EstBusiness.changes(JSON.parse(JSON.stringify(q))).map(({Name,originalGN,GN,status})=>({Name,originalGN,GN,status}));
|
||||
})()""",
|
||||
)
|
||||
assert result == [
|
||||
{"Name": "second", "originalGN": 20, "GN": 22, "status": "changed"},
|
||||
{"Name": "first", "originalGN": 10, "GN": 10, "status": "unchanged"},
|
||||
]
|
||||
|
||||
|
||||
def test_change_comparison_tracks_duplicate_code_additions_and_deletions():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();p.options.SourceRefresh=false;
|
||||
p.tables.EstMainDat=[{Code:'A',Name:'first',GN:10,GU:80},
|
||||
{Code:'A',Name:'second',GN:20,GU:50}];
|
||||
const q=EstBusiness.baseline(EstNative.calculate(p));
|
||||
q.tables.EstMainDat=[q.tables.EstMainDat[1],{Code:'A',Name:'new',GN:3,GU:10},
|
||||
{Code:'A',Name:'another',GN:4,GU:10}];
|
||||
return EstBusiness.changes(q).map(({Name,originalGN,status})=>({Name,originalGN,status}));
|
||||
})()""",
|
||||
)
|
||||
assert result == [
|
||||
{"Name": "second", "originalGN": 20, "status": "unchanged"},
|
||||
{"Name": "new", "originalGN": 0, "status": "new"},
|
||||
{"Name": "another", "originalGN": 0, "status": "new"},
|
||||
{"Name": "first", "originalGN": 10, "status": "deleted"},
|
||||
]
|
||||
|
||||
|
||||
def test_change_comparison_matches_duplicate_when_other_row_is_renamed():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();p.options.SourceRefresh=false;
|
||||
p.tables.EstMainDat=[{Code:'A',Name:'first',GN:10,GU:80},
|
||||
{Code:'A',Name:'second',GN:20,GU:50}];
|
||||
const q=EstBusiness.baseline(EstNative.calculate(p));
|
||||
[q.tables.EstMainDat[0],q.tables.EstMainDat[1]]=[q.tables.EstMainDat[1],q.tables.EstMainDat[0]];
|
||||
q.tables.EstMainDat[1].Name='renamed';
|
||||
return EstBusiness.changes(q).map(({Name,originalGN,status})=>({Name,originalGN,status}));
|
||||
})()""",
|
||||
)
|
||||
assert result == [
|
||||
{"Name": "second", "originalGN": 20, "status": "unchanged"},
|
||||
{"Name": "renamed", "originalGN": 10, "status": "changed"},
|
||||
]
|
||||
|
||||
|
||||
def test_change_costs_match_reordered_duplicate_codes():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();p.tables.Wonga=[{Gubun:'A',Name:'second',Cost:70},
|
||||
{Gubun:'A',Name:'new',Cost:5},{Gubun:'A',Name:'other',Cost:7}];
|
||||
p.baseline={name:'old',tables:{EstMainDat:[],Wonga:[
|
||||
{Gubun:'A',Name:'first',Cost:100},{Gubun:'A',Name:'second',Cost:60}]}};
|
||||
return EstBusiness.costChanges(p).map(({Name,originalCost,changedCost})=>({Name,originalCost,changedCost}));
|
||||
})()""",
|
||||
)
|
||||
assert result == [
|
||||
{"Name": "second", "originalCost": 60, "changedCost": 70},
|
||||
{"Name": "new", "originalCost": 0, "changedCost": 5},
|
||||
{"Name": "other", "originalCost": 0, "changedCost": 7},
|
||||
{"Name": "first", "originalCost": 100, "changedCost": 0},
|
||||
]
|
||||
|
||||
|
||||
def test_estimate_move_preserves_row_identity_and_blocks_ambiguous_or_total_rows():
|
||||
result = run(
|
||||
"math",
|
||||
@@ -712,4 +790,3 @@ def test_bid_rates_consecutive_apply_source_refresh_and_roundtrip_restore():
|
||||
assert result["manualBidRow"]["GU"] == 105
|
||||
assert result["manualResRow"]["NU"] == 100
|
||||
assert result["manualResRow"]["GU"] == 150
|
||||
|
||||
|
||||
@@ -20,6 +20,25 @@ def test_escalation_distinguishes_repeated_codes_without_row_ids():
|
||||
assert result["excludedTotal"] == 410
|
||||
|
||||
|
||||
def test_escalation_matches_reordered_duplicate_codes_to_original_rows():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();p.options.SourceRefresh=false;p.options.EscDesignTotal=1000;
|
||||
p.tables.EstMainDat=[{Code:'A',Name:'first',GN:10,GU:80},
|
||||
{Code:'A',Name:'second',GN:20,GU:50},{LinkIndex:54}];
|
||||
const q=EstBusiness.baseline(EstNative.calculate(p));
|
||||
[q.tables.EstMainDat[0],q.tables.EstMainDat[1]]=[q.tables.EstMainDat[1],q.tables.EstMainDat[0]];
|
||||
Object.assign(q.tables.EstMainDat[0],{AGU:100,GU:120,EscActual:5});
|
||||
Object.assign(q.tables.EstMainDat[1],{AGU:100,GU:120,EscActual:2});
|
||||
return EstEscalation.calculate(JSON.parse(JSON.stringify(q)));
|
||||
})()""",
|
||||
)
|
||||
assert [row["ContractGU"] for row in result["rows"][:2]] == [50, 80]
|
||||
assert [row["EscApplied"] for row in result["rows"][:2]] == [15, 8]
|
||||
assert result["excludedTotal"] == 410
|
||||
|
||||
|
||||
def test_progress_exclusions_preserve_new_rows_and_reject_ambiguous_keys():
|
||||
result = run(
|
||||
"math",
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
from test_est_row_actions import run_js
|
||||
|
||||
|
||||
def test_change_reset_restores_machine_details_and_preserves_legacy_machine_rows():
|
||||
result = run_js("""(() => {
|
||||
const p=EstNative.empty();
|
||||
p.tables.MachMainDat=[{PkID:1,Index:1,Code:'M1'}, {PkID:2,Index:-2,GN:2,GN_:123,TU:100}];
|
||||
const q=EstBusiness.baseline(p);
|
||||
q.tables.MachMainDat[1].GN=9;
|
||||
const state={project:q};
|
||||
globalThis.confirm=()=>true;globalThis.EstApp={change:fn=>fn()};
|
||||
EstBlockActions.resetChangeAction(state);
|
||||
const restored=q.tables.MachMainDat[1].GN, original=q.tables.MachMainDat[1].GN_;
|
||||
const legacy=EstBusiness.baseline(p);
|
||||
delete legacy.baseline.tables.MachMainDat;
|
||||
legacy.tables.MachMainDat[1].GN=7;legacy.tables.MachMainDat[1].GN_=123;
|
||||
EstBlockActions.resetChangeAction({project:legacy});
|
||||
return {restored, original, legacy:legacy.tables.MachMainDat[1].GN,
|
||||
legacyOriginal:legacy.tables.MachMainDat[1].GN_};
|
||||
})()""")
|
||||
assert result == {"restored": 2, "original": 123, "legacy": 7, "legacyOriginal": 123}
|
||||
@@ -0,0 +1,47 @@
|
||||
from test_est_row_actions import run_js
|
||||
|
||||
|
||||
def test_progress_page_preserves_original_indices_and_clamps():
|
||||
result = run_js("""(() => {
|
||||
const rows = Array.from({length:205}, (_, i) => ({Code:'R'+i, Level:0}));
|
||||
rows.splice(1, 0, {Code:'heading', Level:1});
|
||||
const state = {progressPage:1};
|
||||
const second = EstBlockActions.progressPage(state, rows);
|
||||
state.progressPage = 99;
|
||||
const last = EstBlockActions.progressPage(state, rows);
|
||||
return {secondCount:second.rows.length, secondIndex:second.rows[0].index,
|
||||
lastCount:last.rows.length, page:state.progressPage, pages:last.pages};
|
||||
})()""")
|
||||
assert result == {"secondCount": 100, "secondIndex": 101, "lastCount": 5, "page": 2, "pages": 3}
|
||||
|
||||
|
||||
def test_escalation_page_excludes_summary_and_preserves_original_indices():
|
||||
result = run_js("""(() => {
|
||||
const rows = Array.from({length:201}, (_, i) => ({Code:'R'+i, Level:0}));
|
||||
rows.splice(2, 0, {Name:'소계', LinkIndex:54});
|
||||
const state = {escalationPage:1};
|
||||
const page = EstBlockActions.escalationPage(state, rows);
|
||||
return {count:page.rows.length, index:page.rows[0].index, total:page.total, pages:page.pages};
|
||||
})()""")
|
||||
assert result == {"count": 100, "index": 101, "total": 201, "pages": 3}
|
||||
|
||||
|
||||
def test_change_page_includes_deleted_and_new_rows():
|
||||
result = run_js("""(() => {
|
||||
const rows = Array.from({length:200}, (_, i) => ({Code:'R'+i, status:'unchanged'}));
|
||||
rows.push({Code:'old',status:'deleted'}, {Code:'new',status:'new'});
|
||||
const state = {changePage:2};
|
||||
const page = EstBlockActions.changePage(state, rows);
|
||||
return {statuses:page.rows.map(x=>x.row.status), total:page.total, pages:page.pages};
|
||||
})()""")
|
||||
assert result == {"statuses": ["deleted", "new"], "total": 202, "pages": 3}
|
||||
|
||||
|
||||
def test_business_paging_filters_without_changing_original_indices():
|
||||
result = run_js("""(() => {
|
||||
const rows = [{Code:'A',Name:'첫 항목'}, {Code:'B',Name:'둘째 항목',Size:'특수 규격'}];
|
||||
const state = {query:'특수', progressPage:2};
|
||||
const page = EstBlockActions.progressPage(state, rows);
|
||||
return {total:page.total, index:page.rows[0].index, page:state.progressPage};
|
||||
})()""")
|
||||
assert result == {"total": 1, "index": 1, "page": 0}
|
||||
@@ -27,6 +27,25 @@ def test_progress_cannot_lock_when_previous_result_is_missing():
|
||||
assert result["rejected"]
|
||||
|
||||
|
||||
def test_progress_cannot_lock_when_previous_costs_are_missing():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();
|
||||
p.tables.EstMainDat=[{PkID:1,Code:'R',GN:10,GU:100},{LinkIndex:54}];
|
||||
p.tables.Wonga=[{Gubun:'D',BaseField:'TC'},{LinkIndex:55,VSusic:'D'}];
|
||||
p.progress=[{id:'one',locked:true,inputs:{},result:{rows:[]}},
|
||||
{id:'two',inputs:{'1':{Gum:2}}}];
|
||||
const calculated=EstProgress.calculate(p,'two');
|
||||
let rejected=false;
|
||||
try { EstProgress.lock(p,'two'); } catch { rejected=true; }
|
||||
return {errors:calculated.errors,rejected};
|
||||
})()""",
|
||||
)
|
||||
assert any("전회 기성 원가 결과가 없습니다" in error for error in result["errors"])
|
||||
assert result["rejected"]
|
||||
|
||||
|
||||
def test_progress_rejects_ambiguous_inputs_without_applying_them_to_multiple_rows():
|
||||
result = run(
|
||||
"math",
|
||||
|
||||
@@ -1,6 +1,44 @@
|
||||
from test_est_rea_parity import run
|
||||
|
||||
|
||||
def test_quantity_totals_handles_large_estimate_and_ambiguous_targets():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();
|
||||
p.tables.EstMainDat=Array.from({length:5000},(_,i)=>({PkID:i+1,Code:'C'+i,Level:0}));
|
||||
p.quantities=Array.from({length:5000},(_,i)=>({Code:'Q'+i,formula:'1',TargetKey:'id:'+(i+1)}));
|
||||
const totals=EstQuantity.totals(p,'manual');
|
||||
p.tables.EstMainDat.push({PkID:1,Code:'other',Level:0});
|
||||
let duplicate=false;
|
||||
try { EstQuantity.totals(p,'manual'); } catch(e) { duplicate=e.message.includes('중복'); }
|
||||
return {count:totals.size,first:totals.get(0),last:totals.get(4999),duplicate};
|
||||
})()""",
|
||||
)
|
||||
assert result == {"count": 5000, "first": 1, "last": 1, "duplicate": True}
|
||||
|
||||
|
||||
def test_quantity_target_options_are_loaded_when_focused():
|
||||
result = run(
|
||||
"math",
|
||||
"""(()=>{
|
||||
const p=EstNative.empty();
|
||||
p.tables.EstMainDat=[{PkID:1,Code:'A',Name:'첫 행'},
|
||||
{PkID:2,Code:'B',Name:'둘째 행'},{PkID:3,Code:'C',Level:1}];
|
||||
globalThis.EstApp={esc:v=>String(v),state:{project:p}};
|
||||
const handlers={}; globalThis.document={addEventListener:(name,fn)=>handlers[name]=fn};
|
||||
const html=EstQuantity.targetSelect(p,{TargetKey:'id:2',TargetCode:'B'},'manual',0);
|
||||
EstQuantity.install();
|
||||
const select={dataset:{quantitySelect:'manual'},value:'id:2',innerHTML:''};
|
||||
handlers.focusin({target:select});
|
||||
return {initialOptions:(html.match(/<option/g)||[]).length,
|
||||
loadedOptions:(select.innerHTML.match(/<option/g)||[]).length,
|
||||
selected:select.value,loaded:select.dataset.quantityOptionsLoaded};
|
||||
})()""",
|
||||
)
|
||||
assert result == {"initialOptions": 2, "loadedOptions": 3, "selected": "id:2", "loaded": "1"}
|
||||
|
||||
|
||||
def test_excel_quantity_cell_rejects_invalid_address_or_non_numeric_value():
|
||||
result = run(
|
||||
"math",
|
||||
|
||||
@@ -37,6 +37,7 @@ def run_js(code: str) -> dict:
|
||||
'tools/est-test/native-machine.js',
|
||||
'tools/est-test/native-wonga.js',
|
||||
'tools/est-test/native-engine.js',
|
||||
'tools/est-test/native-row-match.js',
|
||||
'tools/est-test/native-rows.js',
|
||||
'tools/est-test/native-business.js',
|
||||
'tools/est-test/native-extra.js',
|
||||
@@ -1154,6 +1155,25 @@ def test_reset_wonga_action():
|
||||
assert result["handledTotal"] == 1270500
|
||||
|
||||
|
||||
def test_reset_wonga_error_preserves_rows_and_options():
|
||||
result = run_js("""(() => {
|
||||
const p = EstNative.empty();
|
||||
p.options.TotalFix = true;
|
||||
p.tables.Wonga = [
|
||||
{Gubun:'A', Name:'A', VSusic:'1', VSusic_:'[A]', Cost:10, Cost_:20}
|
||||
];
|
||||
const state = {project:p, view:'Wonga', selection:new Set()};
|
||||
globalThis.confirm = () => true;
|
||||
globalThis.EstApp = {change(fn) { fn(); }};
|
||||
const before = JSON.stringify(p);
|
||||
let rejected = false;
|
||||
try { EstBlockActions.resetWongaAction(state); } catch { rejected = true; }
|
||||
return {rejected, unchanged: JSON.stringify(p) === before};
|
||||
})()""")
|
||||
assert result["rejected"]
|
||||
assert result["unchanged"]
|
||||
|
||||
|
||||
def test_reset_progress_inputs_action():
|
||||
code = """(() => {
|
||||
const p = EstNative.empty();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def test_watcher_detects_new_sources_but_ignores_data_directory(tmp_path):
|
||||
shutil.copy(ROOT / "tools/est-test/watch-server.cjs", tmp_path / "watch-server.cjs")
|
||||
(tmp_path / "server.cjs").write_text(
|
||||
"require('node:fs').appendFileSync('starts.txt', process.pid + String.fromCharCode(10));\n"
|
||||
"setInterval(() => {}, 1000);\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
data = tmp_path / "data"
|
||||
data.mkdir()
|
||||
process = subprocess.Popen(
|
||||
["node", "watch-server.cjs"],
|
||||
cwd=tmp_path,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
start_new_session=os.name != "nt",
|
||||
)
|
||||
|
||||
def starts():
|
||||
file = tmp_path / "starts.txt"
|
||||
return file.read_text(encoding="utf-8").splitlines() if file.exists() else []
|
||||
|
||||
def wait_for(count):
|
||||
deadline = time.monotonic() + 8
|
||||
while len(starts()) < count and time.monotonic() < deadline:
|
||||
time.sleep(0.05)
|
||||
assert len(starts()) == count
|
||||
|
||||
try:
|
||||
wait_for(1)
|
||||
source = tmp_path / "new-feature.js"
|
||||
source.write_text("// first\n", encoding="utf-8")
|
||||
wait_for(2)
|
||||
source.write_text("// second\n", encoding="utf-8")
|
||||
wait_for(3)
|
||||
(data / "work.js").write_text("// saved data\n", encoding="utf-8")
|
||||
time.sleep(0.8)
|
||||
assert len(starts()) == 3
|
||||
assert len(set(starts())) == 3
|
||||
finally:
|
||||
if os.name == "nt":
|
||||
subprocess.run(
|
||||
["taskkill", "/PID", str(process.pid), "/T", "/F"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
check=False,
|
||||
)
|
||||
else:
|
||||
os.killpg(process.pid, signal.SIGTERM)
|
||||
process.wait(timeout=5)
|
||||
Reference in New Issue
Block a user