import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync, copyFileSync, mkdtempSync, readdirSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; import { TsConfigFile } from '../src/io/TsConfigFile'; const here = dirname(fileURLToPath(import.meta.url)); const fixture = join(here, 'fixtures', 'heroSet.sample.ts'); test('load + getKeys', () => { const f = new TsConfigFile(fixture, 'HeroInfo'); f.load(); assert.deepEqual(f.getKeys(), ['5011', '6001']); }); test('read returns structured value with speed/enumRef preserved', () => { const f = new TsConfigFile(fixture, 'HeroInfo'); f.load(); const v = f.read('5011')!; assert.equal(v.kind, 'obj'); assert.deepEqual(v.props['fac'], { kind: 'enumRef', qualifier: 'FacSet', member: 'HERO' }); const skill = v.props['skills'].props['6001']; assert.deepEqual(skill.props['cd'], { kind: 'speed', level: 'Slow3' }); }); test('getText after load equals original file (no mutation on load)', () => { const f = new TsConfigFile(fixture, 'HeroInfo'); f.load(); assert.equal(f.getText(), readFileSync(fixture, 'utf8')); }); test('read missing key returns null', () => { const f = new TsConfigFile(fixture, 'HeroInfo'); f.load(); assert.equal(f.read('9999'), null); }); function withTempFixture(): { dir: string; file: string } { const dir = mkdtempSync(join(tmpdir(), 'phcfg-')); const file = join(dir, 'heroSet.sample.ts'); copyFileSync(fixture, file); return { dir, file }; } test('patch updates one entry; reload reads new value; other entries intact', () => { const { file } = withTempFixture(); const f = new TsConfigFile(file, 'HeroInfo'); f.load(); const v = f.read('5011')!; v.props['ap'] = { kind: 'num', value: 99 }; f.patch('5011', v); assert.equal(f.isDirty(), true); assert.equal(f.save().ok, true); const f2 = new TsConfigFile(file, 'HeroInfo'); f2.load(); assert.deepEqual(f2.read('5011')!.props['ap'], { kind: 'num', value: 99 }); assert.equal(f2.read('6001')!.props['name'].value, '兽人战士'); // 另一条未破坏 }); test('patch preserves speed + enumRef on reload', () => { const { file } = withTempFixture(); const f = new TsConfigFile(file, 'HeroInfo'); f.load(); const v = f.read('5011')!; v.props['hp'] = { kind: 'num', value: 500 }; f.patch('5011', v); f.save(); const f2 = new TsConfigFile(file, 'HeroInfo'); f2.load(); const again = f2.read('5011')!; assert.deepEqual(again.props['fac'], { kind: 'enumRef', qualifier: 'FacSet', member: 'HERO' }); assert.deepEqual(again.props['skills'].props['6001'].props['cd'], { kind: 'speed', level: 'Slow3' }); }); test('add appends entry readable after save+reload', () => { const { file } = withTempFixture(); const f = new TsConfigFile(file, 'HeroInfo'); f.load(); f.add('5099', { kind: 'obj', props: { uuid: { kind: 'num', value: 5099 }, name: { kind: 'str', value: '新英雄' } } }); f.save(); const f2 = new TsConfigFile(file, 'HeroInfo'); f2.load(); assert.ok(f2.getKeys().includes('5099')); assert.equal(f2.read('5099')!.props['name'].value, '新英雄'); }); test('delete removes entry', () => { const { file } = withTempFixture(); const f = new TsConfigFile(file, 'HeroInfo'); f.load(); f.delete('6001'); f.save(); const f2 = new TsConfigFile(file, 'HeroInfo'); f2.load(); assert.ok(!f2.getKeys().includes('6001')); assert.ok(f2.getKeys().includes('5011')); }); test('save writes a .bak backup of the pre-save file', () => { const { file } = withTempFixture(); const f = new TsConfigFile(file, 'HeroInfo'); f.load(); const v = f.read('5011')!; v.props['hp'] = { kind: 'num', value: 1 }; f.patch('5011', v); f.save(); assert.equal(readFileSync(file + '.bak', 'utf8'), readFileSync(fixture, 'utf8')); }); test('save with no edits is a no-op (ok, no .bak written)', () => { const { file, dir } = withTempFixture(); const f = new TsConfigFile(file, 'HeroInfo'); f.load(); assert.equal(f.save().ok, true); assert.equal(readdirSync(dir).some(p => p.endsWith('.bak')), false); });