import { setShopConfig, getShopConfig, findEntry, canBuyItem, buyItem, getArtifactUpgradeCost, resetShopItems, applyArtifactEffects, } from '../src/engine/shopEngine.js'; // Load shop config import shopConfig from '../src/config/shop.json' assert { type: 'json' }; function createState(overrides = {}) { return { money: 0, age: 0, stats: {}, careers: {}, worldFlags: {}, shopItems: {}, activeBuffs: {}, artifacts: {}, ...overrides, }; } function assertEqual(actual, expected, message) { if (actual !== expected) { console.error(`FAIL: ${message}`); console.error(` Expected: ${expected}`); console.error(` Actual: ${actual}`); throw new Error(`Assertion failed: ${message}`); } } function assertTrue(value, message) { if (!value) { console.error(`FAIL: ${message}`); throw new Error(`Assertion failed: ${message}`); } } function assertDeepEqual(actual, expected, message) { const actualStr = JSON.stringify(actual); const expectedStr = JSON.stringify(expected); if (actualStr !== expectedStr) { console.error(`FAIL: ${message}`); console.error(` Expected: ${expectedStr}`); console.error(` Actual: ${actualStr}`); throw new Error(`Assertion failed: ${message}`); } } function testCanBuyItem() { console.log('Running testCanBuyItem...'); // Test 1: Basic item - enough money, no unlock conditions let state = createState({ money: 100 }); assertTrue(canBuyItem(shopConfig, state, 'power_pill'), 'Should be able to buy power_pill with enough money'); // Test 2: Basic item - not enough money state = createState({ money: 10 }); assertEqual(canBuyItem(shopConfig, state, 'power_pill'), false, 'Should not afford power_pill with insufficient money'); // Test 3: Item with unlock conditions - age not met state = createState({ money: 1000, age: 5, identity: 'taoist_orphan' }); assertEqual(canBuyItem(shopConfig, state, 'taoist_license'), false, 'Should not buy taoist_license if age < 10'); // Test 4: Item with unlock conditions - age met, identity matches state = createState({ money: 1000, age: 15, identity: 'taoist_orphan' }); assertTrue(canBuyItem(shopConfig, state, 'taoist_license'), 'Should buy taoist_license with age >= 10 and correct identity'); // Test 5: Item with unlock conditions - age met, worldFlag matches state = createState({ money: 1000, age: 15, worldFlags: { met_taoist_priest: true } }); assertTrue(canBuyItem(shopConfig, state, 'taoist_license'), 'Should buy taoist_license with age >= 10 and worldFlag'); // Test 6: Item with unlock conditions - age met, neither condition matches state = createState({ money: 1000, age: 15, identity: 'commoner' }); assertEqual(canBuyItem(shopConfig, state, 'taoist_license'), false, 'Should not buy taoist_license without matching identity or flag'); // Test 7: Buff - enough money state = createState({ money: 300 }); assertTrue(canBuyItem(shopConfig, state, 'clever_brush'), 'Should be able to buy clever_brush with enough money'); // Test 8: Artifact - unlock condition not met state = createState({ money: 2000 }); assertEqual(canBuyItem(shopConfig, state, 'immortal_sword'), false, 'Should not buy immortal_sword without career level'); // Test 9: Artifact - unlock condition met state = createState({ money: 2000, careers: { taoist_priest: { level: 50, exp: 0 } } }); assertTrue(canBuyItem(shopConfig, state, 'immortal_sword'), 'Should buy immortal_sword with sufficient career level'); // Test 10: Artifact - max level reached state = createState({ money: 100000, careers: { taoist_priest: { level: 50, exp: 0 } }, artifacts: { immortal_sword: 10 } }); assertEqual(canBuyItem(shopConfig, state, 'immortal_sword'), false, 'Should not buy artifact at max level'); console.log(' testCanBuyItem PASSED'); } function testBuyItem() { console.log('Running testBuyItem...'); // Test 1: Buy basic item let state = createState({ money: 100 }); const result1 = buyItem(shopConfig, state, 'power_pill'); assertTrue(result1, 'buyItem should return true for successful purchase'); assertEqual(state.money, 50, 'Should deduct cost from money'); assertEqual(state.shopItems.power_pill, 1, 'Should record item purchase'); assertEqual(state.stats.body, 5, 'Should apply effect (addStat body +5)'); // Test 2: Buy item that fails canBuyItem state = createState({ money: 10 }); const result2 = buyItem(shopConfig, state, 'power_pill'); assertEqual(result2, false, 'buyItem should return false when cannot afford'); assertEqual(state.money, 10, 'Should not deduct money when purchase fails'); // Test 3: Buy buff state = createState({ money: 300 }); const result3 = buyItem(shopConfig, state, 'clever_brush'); assertTrue(result3, 'buyItem should return true for buff purchase'); assertEqual(state.money, 100, 'Should deduct buff cost'); assertDeepEqual(state.activeBuffs.clever_brush, { expiresDay: -1 }, 'Should record buff with expiresDay: -1'); assertEqual(state.stats.wisdom, 10, 'Should apply buff effect (addStat wisdom +10)'); // Test 4: Buy artifact state = createState({ money: 2000, careers: { taoist_priest: { level: 50, exp: 0 } } }); const result4 = buyItem(shopConfig, state, 'immortal_sword'); assertTrue(result4, 'buyItem should return true for artifact purchase'); assertEqual(state.money, 1000, 'Should deduct artifact cost (baseCost=1000)'); assertEqual(state.artifacts.immortal_sword, 1, 'Should record artifact level'); assertEqual(state.stats.body, 10, 'Should apply artifact effect (addStat body +10)'); // Test 5: Buy item with multiple effects state = createState({ money: 1000, age: 15, identity: 'taoist_orphan' }); const result5 = buyItem(shopConfig, state, 'taoist_license'); assertTrue(result5, 'buyItem should return true for taoist_license'); assertEqual(state.money, 500, 'Should deduct taoist_license cost'); assertEqual(state.shopItems.taoist_license, 1, 'Should record taoist_license purchase'); assertTrue(state.careers.taoist_priest !== undefined, 'Should unlock career'); assertEqual(state.worldFlags.has_taoist_license, true, 'Should set world flag'); console.log(' testBuyItem PASSED'); } function testGetArtifactUpgradeCost() { console.log('Running testGetArtifactUpgradeCost...'); // Level 0: baseCost * 1.5^0 = 1000 * 1 = 1000 const cost0 = getArtifactUpgradeCost(shopConfig, 'immortal_sword', 0); assertEqual(cost0, 1000, 'Level 0 cost should be baseCost'); // Level 1: baseCost * 1.5^1 = 1000 * 1.5 = 1500 const cost1 = getArtifactUpgradeCost(shopConfig, 'immortal_sword', 1); assertEqual(cost1, 1500, 'Level 1 cost should be baseCost * 1.5'); // Level 2: baseCost * 1.5^2 = 1000 * 2.25 = 2250 const cost2 = getArtifactUpgradeCost(shopConfig, 'immortal_sword', 2); assertEqual(cost2, 2250, 'Level 2 cost should be baseCost * 1.5^2'); // Level 3: baseCost * 1.5^3 = 1000 * 3.375 = 3375 const cost3 = getArtifactUpgradeCost(shopConfig, 'immortal_sword', 3); assertEqual(cost3, 3375, 'Level 3 cost should be baseCost * 1.5^3'); // Verify Math.floor is applied const cost5 = getArtifactUpgradeCost(shopConfig, 'immortal_sword', 5); const expected5 = Math.floor(1000 * Math.pow(1.5, 5)); assertEqual(cost5, expected5, 'Level 5 cost should use Math.floor'); console.log(' testGetArtifactUpgradeCost PASSED'); } function testResetShopItems() { console.log('Running testResetShopItems...'); let state = createState({ shopItems: { power_pill: 3, taoist_license: 1 }, activeBuffs: { clever_brush: { expiresDay: -1 } }, artifacts: { immortal_sword: 5 }, }); resetShopItems(state); assertDeepEqual(state.shopItems, {}, 'shopItems should be reset to empty object'); assertDeepEqual(state.activeBuffs, {}, 'activeBuffs should be reset to empty object'); assertEqual(state.artifacts.immortal_sword, 5, 'artifacts should be preserved'); console.log(' testResetShopItems PASSED'); } function testApplyArtifactEffects() { console.log('Running testApplyArtifactEffects...'); // Test 1: Single artifact, level 1 let state = createState({ artifacts: { immortal_sword: 1 } }); applyArtifactEffects(shopConfig, state); assertEqual(state.stats.body, 10, 'Level 1 artifact should add 10 body'); // Test 2: Single artifact, level 3 state = createState({ artifacts: { immortal_sword: 3 } }); applyArtifactEffects(shopConfig, state); assertEqual(state.stats.body, 30, 'Level 3 artifact should add 30 body (10 * 3)'); // Test 3: Multiple artifacts (simulate by adding another artifact config) const customConfig = { artifacts: [ { id: 'immortal_sword', effects: [{ type: 'addStat', stat: 'body', value: 10 }], }, { id: 'wisdom_scroll', effects: [{ type: 'addStat', stat: 'wisdom', value: 5 }], }, ], }; state = createState({ artifacts: { immortal_sword: 2, wisdom_scroll: 4 } }); applyArtifactEffects(customConfig, state); assertEqual(state.stats.body, 20, 'Multiple artifacts: immortal_sword level 2 should add 20 body'); assertEqual(state.stats.wisdom, 20, 'Multiple artifacts: wisdom_scroll level 4 should add 20 wisdom (5 * 4)'); // Test 4: Artifact level 0 should not apply state = createState({ artifacts: { immortal_sword: 0 } }); applyArtifactEffects(shopConfig, state); assertEqual(state.stats.body, undefined, 'Level 0 artifact should not apply effects'); console.log(' testApplyArtifactEffects PASSED'); } function testSetAndGetShopConfig() { console.log('Running testSetAndGetShopConfig...'); const customConfig = { items: [], buffs: [], artifacts: [] }; setShopConfig(customConfig); assertEqual(getShopConfig(), customConfig, 'getShopConfig should return set config'); // Reset to shopConfig for other tests setShopConfig(shopConfig); console.log(' testSetAndGetShopConfig PASSED'); } function testFindEntry() { console.log('Running testFindEntry...'); const item = findEntry(shopConfig, 'power_pill'); assertTrue(item !== null, 'findEntry should find existing item'); assertEqual(item.id, 'power_pill', 'findEntry should return correct item id'); assertEqual(item.section, 'items', 'findEntry should return correct section for item'); const buff = findEntry(shopConfig, 'clever_brush'); assertTrue(buff !== null, 'findEntry should find existing buff'); assertEqual(buff.section, 'buffs', 'findEntry should return correct section for buff'); const artifact = findEntry(shopConfig, 'immortal_sword'); assertTrue(artifact !== null, 'findEntry should find existing artifact'); assertEqual(artifact.section, 'artifacts', 'findEntry should return correct section for artifact'); const notFound = findEntry(shopConfig, 'nonexistent'); assertEqual(notFound, null, 'findEntry should return null for nonexistent id'); console.log(' testFindEntry PASSED'); } function runAllTests() { console.log('=== shopEngine Tests ===\n'); try { testSetAndGetShopConfig(); testFindEntry(); testCanBuyItem(); testBuyItem(); testGetArtifactUpgradeCost(); testResetShopItems(); testApplyArtifactEffects(); console.log('\nAll shopEngine tests PASS'); process.exit(0); } catch (error) { console.error(`\nTests FAILED: ${error.message}`); process.exit(1); } } runAllTests();