chore: commit investigation state
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m25s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m25s
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useManaStore } from '../stores/manaStore';
|
||||
import { ELEMENTS, BASE_UNLOCKED_ELEMENTS } from '../constants/elements';
|
||||
|
||||
// ─── Regression test: Earth element desync after Unlock All ─────────────────
|
||||
// https://gitea.tailf367e3.ts.net/Anexim/Mana-Loop/issues/338
|
||||
// https://gitea.tailf367e3.ts.net/Anexim/Mana-Loop/issues/339
|
||||
|
||||
function resetManaStore() {
|
||||
useManaStore.setState({
|
||||
rawMana: 10,
|
||||
meditateTicks: 0,
|
||||
totalManaGathered: 0,
|
||||
elements: Object.fromEntries(
|
||||
Object.keys(ELEMENTS).map(k => [
|
||||
k,
|
||||
{
|
||||
current: BASE_UNLOCKED_ELEMENTS.includes(k) ? 0 : 0,
|
||||
max: 10,
|
||||
baseMax: 10,
|
||||
unlocked: BASE_UNLOCKED_ELEMENTS.includes(k),
|
||||
},
|
||||
])
|
||||
) as Record<string, { current: number; max: number; baseMax: number; unlocked: boolean }>,
|
||||
elementRegen: {},
|
||||
});
|
||||
}
|
||||
|
||||
describe('Earth element desync after Unlock All', () => {
|
||||
beforeEach(() => {
|
||||
resetManaStore();
|
||||
});
|
||||
|
||||
it('earth element exists in initial state', () => {
|
||||
const elements = useManaStore.getState().elements;
|
||||
expect(elements.earth).toBeDefined();
|
||||
expect(elements.earth.unlocked).toBe(false);
|
||||
});
|
||||
|
||||
it('unlockElement unlocks earth correctly', () => {
|
||||
const result = useManaStore.getState().unlockElement('earth', 0);
|
||||
expect(result.success).toBe(true);
|
||||
const earth = useManaStore.getState().elements.earth;
|
||||
expect(earth).toBeDefined();
|
||||
expect(earth.unlocked).toBe(true);
|
||||
expect(earth.current).toBe(0);
|
||||
expect(earth.max).toBe(10);
|
||||
expect(earth.baseMax).toBe(10);
|
||||
});
|
||||
|
||||
it('unlockElement preserves earth state fields', () => {
|
||||
// First give earth some mana
|
||||
useManaStore.setState((s) => ({
|
||||
elements: {
|
||||
...s.elements,
|
||||
earth: { ...s.elements.earth, current: 5, max: 20, baseMax: 20 },
|
||||
},
|
||||
}));
|
||||
|
||||
const result = useManaStore.getState().unlockElement('earth', 0);
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
const earth = useManaStore.getState().elements.earth;
|
||||
expect(earth.unlocked).toBe(true);
|
||||
expect(earth.current).toBe(5);
|
||||
expect(earth.max).toBe(20);
|
||||
expect(earth.baseMax).toBe(20);
|
||||
});
|
||||
|
||||
it('unlock all elements via unlockElement preserves all fields', () => {
|
||||
const elements = useManaStore.getState().elements;
|
||||
for (const id of Object.keys(elements)) {
|
||||
if (!elements[id].unlocked) {
|
||||
useManaStore.getState().unlockElement(id, 0);
|
||||
}
|
||||
}
|
||||
|
||||
const updatedElements = useManaStore.getState().elements;
|
||||
const earth = updatedElements.earth;
|
||||
|
||||
expect(earth).toBeDefined();
|
||||
expect(earth.unlocked).toBe(true);
|
||||
expect(earth.current).toBe(0);
|
||||
expect(earth.max).toBe(10);
|
||||
expect(earth.baseMax).toBe(10);
|
||||
});
|
||||
|
||||
it('all 22 elements are unlocked after unlock all', () => {
|
||||
const elements = useManaStore.getState().elements;
|
||||
for (const id of Object.keys(elements)) {
|
||||
if (!elements[id].unlocked) {
|
||||
useManaStore.getState().unlockElement(id, 0);
|
||||
}
|
||||
}
|
||||
|
||||
const updatedElements = useManaStore.getState().elements;
|
||||
const unlockedCount = Object.values(updatedElements).filter(e => e.unlocked).length;
|
||||
expect(unlockedCount).toBe(Object.keys(ELEMENTS).length);
|
||||
});
|
||||
|
||||
it('earth shows in unlocked elements list after unlock all', () => {
|
||||
const elements = useManaStore.getState().elements;
|
||||
for (const id of Object.keys(elements)) {
|
||||
if (!elements[id].unlocked) {
|
||||
useManaStore.getState().unlockElement(id, 0);
|
||||
}
|
||||
}
|
||||
|
||||
const updatedElements = useManaStore.getState().elements;
|
||||
const unlockedIds = Object.entries(updatedElements)
|
||||
.filter(([, state]) => state.unlocked)
|
||||
.map(([id]) => id);
|
||||
|
||||
expect(unlockedIds).toContain('earth');
|
||||
});
|
||||
|
||||
it('earth element has valid state after unlock (not missing fields)', () => {
|
||||
useManaStore.getState().unlockElement('earth', 0);
|
||||
const earth = useManaStore.getState().elements.earth;
|
||||
|
||||
// These would be undefined if the element state was corrupted
|
||||
expect(earth.current).toBeDefined();
|
||||
expect(earth.max).toBeDefined();
|
||||
expect(earth.baseMax).toBeDefined();
|
||||
expect(earth.unlocked).toBe(true);
|
||||
});
|
||||
|
||||
it('unlockElement with cost 0 does not deduct raw mana', () => {
|
||||
const before = useManaStore.getState().rawMana;
|
||||
useManaStore.getState().unlockElement('earth', 0);
|
||||
const after = useManaStore.getState().rawMana;
|
||||
expect(after).toBe(before);
|
||||
});
|
||||
|
||||
it('unlockElement twice for earth is a no-op', () => {
|
||||
const result1 = useManaStore.getState().unlockElement('earth', 0);
|
||||
expect(result1.success).toBe(true);
|
||||
|
||||
const result2 = useManaStore.getState().unlockElement('earth', 0);
|
||||
expect(result2.success).toBe(false); // Already unlocked
|
||||
|
||||
const earth = useManaStore.getState().elements.earth;
|
||||
expect(earth.unlocked).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -105,7 +105,13 @@ export const useManaStore = create<ManaStore>()(
|
||||
if (state.elements[element]?.unlocked) return fail(ErrorCode.INVALID_INPUT, `Element ${element} is already unlocked`);
|
||||
if (state.rawMana < cost) return fail(ErrorCode.INSUFFICIENT_MANA, `Need ${cost} raw mana, have ${state.rawMana}`);
|
||||
|
||||
set({ rawMana: state.rawMana - cost, elements: { ...state.elements, [element]: { ...state.elements[element], unlocked: true } } });
|
||||
// If the element doesn't exist in the store (e.g. from an old save), create it
|
||||
const existing = state.elements[element];
|
||||
const newElement = existing
|
||||
? { ...existing, unlocked: true }
|
||||
: { current: 0, max: 10, baseMax: 10, unlocked: true };
|
||||
|
||||
set({ rawMana: state.rawMana - cost, elements: { ...state.elements, [element]: newElement } });
|
||||
return okVoid();
|
||||
},
|
||||
|
||||
@@ -174,6 +180,18 @@ export const useManaStore = create<ManaStore>()(
|
||||
persistedState.elements[k].baseMax = persistedState.elements[k].max ?? 10;
|
||||
}
|
||||
}
|
||||
// Add any missing elements that exist in ELEMENTS but not in the save
|
||||
for (const k of Object.keys(ELEMENTS)) {
|
||||
if (!persistedState.elements[k]) {
|
||||
const isUnlocked = BASE_UNLOCKED_ELEMENTS.includes(k);
|
||||
persistedState.elements[k] = {
|
||||
current: isUnlocked ? 0 : 0,
|
||||
max: 10,
|
||||
baseMax: 10,
|
||||
unlocked: isUnlocked,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return persistedState;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user