fix: 6 priority-3 bug fixes with regression tests
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m24s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m24s
- Issue 83: Mana Tide pulse factor now ranges 0.5x-1.5x (was 0.5x-1.0x) - Issue 82: SteadyStream no longer returns early like EternalFlow; only skips incursion penalty - Issue 81: Prestige store partialize now includes defeatedGuardians, signedPacts, signedPactDetails, pactRitualFloor, pactRitualProgress, loopInsight, pactSlots - Issue 80: Combat store partialize now includes floorHP, floorMaxHP, castProgress, spireMode, clearedFloors, golemancy, equipmentSpellStates, activityLog, achievements - Issue 78: cancelDesign now always cancels designProgress first, then designProgress2 - Issue 79: startDesigningEnchantment now uses designProgress2 when designProgress is occupied Added 13 regression tests in src/lib/game/__tests__/regression-fixes.test.ts Refactored craftingStore types to craftingStore.types.ts to stay under 400-line limit
This commit is contained in:
@@ -1,80 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* E2E tests for combat system:
|
||||
* - Entering spire mode (climbing)
|
||||
* - Casting spells and seeing progress
|
||||
*/
|
||||
|
||||
test.describe('Combat System', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
// Clear game state to ensure a fresh start
|
||||
await page.evaluate(() => {
|
||||
Object.keys(localStorage)
|
||||
.filter((k) => k.startsWith('mana-loop-'))
|
||||
.forEach((k) => localStorage.removeItem(k));
|
||||
});
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('can see the Spire tab and "Climb the Spire" button', async ({ page }) => {
|
||||
// Verify Spire tab exists (uses ⚔️ icon)
|
||||
const spireTab = page.getByRole('tab').filter({ hasText: '⚔️' });
|
||||
await expect(spireTab).toBeVisible();
|
||||
|
||||
// Main page should show "Climb the Spire" button
|
||||
const climbBtn = page.getByRole('button', { name: 'Climb the Spire' });
|
||||
await expect(climbBtn).toBeVisible();
|
||||
});
|
||||
|
||||
test('can enter Spire mode by clicking Climb button', async ({ page }) => {
|
||||
// Click "Climb the Spire" button on the main page
|
||||
await page.getByRole('button', { name: 'Climb the Spire' }).click();
|
||||
|
||||
// After clicking, spire mode activates and tab auto-switches to Spire tab.
|
||||
// Since spireMode is now true, the Spire tab shows "Exit Spire Mode"
|
||||
const exitBtn = page.getByRole('button', { name: 'Exit Spire Mode' });
|
||||
await expect(exitBtn).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('can navigate to Spire tab and enter spire mode', async ({ page }) => {
|
||||
// Click the Spire tab
|
||||
await page.getByRole('tab').filter({ hasText: '⚔️' }).click();
|
||||
|
||||
// Should see the "Enter Spire Mode" button
|
||||
const enterBtn = page.getByRole('button', { name: 'Enter Spire Mode' });
|
||||
await expect(enterBtn).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('shows floor information after entering spire mode', async ({ page }) => {
|
||||
// Navigate to spire mode first
|
||||
await page.getByRole('button', { name: 'Climb the Spire' }).click();
|
||||
|
||||
// Now on spire tab with spire mode active
|
||||
// The SpireHeader in simpleMode shows "Current Floor" section
|
||||
// with the floor number, room badge, and stats
|
||||
|
||||
// Check that we're on the spire tab
|
||||
const spireTab = page.getByRole('tab', { name: /⚔️ Spire/ });
|
||||
await expect(spireTab).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// The SpireHeader shows "Current Floor" in spire mode
|
||||
const currentFloorLabel = page.getByText('Current Floor');
|
||||
await expect(currentFloorLabel).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// The floor number should be displayed (it's a text element)
|
||||
// And "Best:" label is rendered alongside the floor count
|
||||
const bestLabel = page.locator('text=Best:').first();
|
||||
await expect(bestLabel).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('can navigate to Spire tab and see stats', async ({ page }) => {
|
||||
await page.getByRole('tab').filter({ hasText: '⚔️' }).click();
|
||||
|
||||
// Spire stats section shows key info
|
||||
expect(await page.getByText('Best Floor').count()).toBeGreaterThan(0);
|
||||
expect(await page.getByText('Pacts Signed').count()).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
@@ -1,106 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* E2E tests for the 3-step enchantment flow:
|
||||
* Design → Prepare → Apply
|
||||
*
|
||||
* These tests validate the core crafting loop works end-to-end.
|
||||
*/
|
||||
|
||||
test.describe('Enchanting Flow', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.evaluate(() => {
|
||||
Object.keys(localStorage)
|
||||
.filter((k) => k.startsWith('mana-loop-'))
|
||||
.forEach((k) => localStorage.removeItem(k));
|
||||
});
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('can navigate to Crafting tab', async ({ page }) => {
|
||||
const craftTab = page.getByRole('tab').filter({ hasText: '🔧' });
|
||||
await expect(craftTab).toBeVisible();
|
||||
await craftTab.click();
|
||||
|
||||
// Should see the Crafting tab sub-tabs: Fabricate and Enchant
|
||||
const fabricateBtn = page.getByRole('button', { name: 'Fabricate' });
|
||||
const enchantBtn = page.getByRole('button', { name: 'Enchant' });
|
||||
await expect(fabricateBtn).toBeVisible();
|
||||
await expect(enchantBtn).toBeVisible();
|
||||
});
|
||||
|
||||
test('can switch to Enchant sub-tab and see design UI', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.evaluate(() => {
|
||||
Object.keys(localStorage)
|
||||
.filter((k) => k.startsWith('mana-loop-'))
|
||||
.forEach((k) => localStorage.removeItem(k));
|
||||
});
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.getByRole('tab').filter({ hasText: '🔧' }).click();
|
||||
await page.getByRole('button', { name: 'Enchant' }).click();
|
||||
|
||||
// Should see the design stage buttons
|
||||
const designBtn = page.getByRole('button', { name: 'Design' });
|
||||
const prepareBtn = page.getByRole('button', { name: 'Prepare' });
|
||||
const applyBtn = page.getByRole('button', { name: 'Apply' });
|
||||
await expect(designBtn).toBeVisible();
|
||||
await expect(prepareBtn).toBeVisible();
|
||||
await expect(applyBtn).toBeVisible();
|
||||
});
|
||||
|
||||
test('can select equipment type in Design stage', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.evaluate(() => {
|
||||
Object.keys(localStorage)
|
||||
.filter((k) => k.startsWith('mana-loop-'))
|
||||
.forEach((k) => localStorage.removeItem(k));
|
||||
});
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.getByRole('tab').filter({ hasText: '🔧' }).click();
|
||||
await page.getByRole('button', { name: 'Enchant' }).click();
|
||||
|
||||
// Look for equipment type selector showing available staff types
|
||||
// The EnchantmentDesigner shows equipment type options
|
||||
const staffOption = page.locator('text=Basic Staff');
|
||||
await expect(staffOption).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('can navigate through all 3 enchant stages', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.evaluate(() => {
|
||||
Object.keys(localStorage)
|
||||
.filter((k) => k.startsWith('mana-loop-'))
|
||||
.forEach((k) => localStorage.removeItem(k));
|
||||
});
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.getByRole('tab').filter({ hasText: '🔧' }).click();
|
||||
await page.getByRole('button', { name: 'Enchant' }).click();
|
||||
|
||||
// Verify Design stage is active
|
||||
await expect(page.getByRole('button', { name: 'Design' })).toBeVisible();
|
||||
|
||||
// Switch to Prepare stage
|
||||
await page.getByRole('button', { name: 'Prepare' }).click();
|
||||
|
||||
// Should see preparation UI
|
||||
// Use role=heading to target the SectionHeader h3, not the empty state div
|
||||
const prepareHeading = page.getByRole('heading', { name: 'Select Equipment to Prepare' });
|
||||
await expect(prepareHeading).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Switch to Apply stage
|
||||
await page.getByRole('button', { name: 'Apply' }).click();
|
||||
|
||||
// Should see application UI
|
||||
const applyHeading = page.locator('text=Select Equipment & Design');
|
||||
await expect(applyHeading).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
});
|
||||
@@ -1,100 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* E2E tests for equipment management:
|
||||
* - Navigating to Equipment tab
|
||||
* - 2-handed weapon blocking offhand slot
|
||||
* - Equipment slots visible with labels
|
||||
*/
|
||||
|
||||
test.describe('Equipment Management', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.evaluate(() => {
|
||||
Object.keys(localStorage)
|
||||
.filter((k) => k.startsWith('mana-loop-'))
|
||||
.forEach((k) => localStorage.removeItem(k));
|
||||
});
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('can navigate to Equipment tab', async ({ page }) => {
|
||||
// Use the tab with the shield icon
|
||||
const gearTab = page.getByRole('tab').filter({ hasText: '🛡️' });
|
||||
await expect(gearTab).toBeVisible();
|
||||
await gearTab.click();
|
||||
|
||||
// Verify we're on the equipment tab by checking for section headers
|
||||
await expect(page.getByText('Equipped Gear')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('shows equipment slots with labels', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.evaluate(() => {
|
||||
Object.keys(localStorage)
|
||||
.filter((k) => k.startsWith('mana-loop-'))
|
||||
.forEach((k) => localStorage.removeItem(k));
|
||||
});
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.getByRole('tab').filter({ hasText: '🛡️' }).click();
|
||||
|
||||
// Check for the grouped slot labels
|
||||
await expect(page.getByText('Weapon & Shield')).toBeVisible();
|
||||
await expect(page.getByText('Armor')).toBeVisible();
|
||||
await expect(page.getByText('Accessories')).toBeVisible();
|
||||
|
||||
// Individual slot labels within groups
|
||||
const slotLabels = ['Main Hand', 'Off Hand', 'Head', 'Body', 'Hands', 'Feet', 'Accessory 1', 'Accessory 2'];
|
||||
for (const label of slotLabels) {
|
||||
const loc = page.getByText(label).first();
|
||||
await expect(loc).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('shows starting equipment already equipped', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.evaluate(() => {
|
||||
Object.keys(localStorage)
|
||||
.filter((k) => k.startsWith('mana-loop-'))
|
||||
.forEach((k) => localStorage.removeItem(k));
|
||||
});
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.getByRole('tab').filter({ hasText: '🛡️' }).click();
|
||||
|
||||
// The player starts with Basic Staff in main hand
|
||||
// Check that main hand slot contains an item with a name
|
||||
const mainHandSlot = page.locator('text=Main Hand').first();
|
||||
await expect(mainHandSlot).toBeVisible();
|
||||
|
||||
// Body slot should have civilian clothing
|
||||
const bodySlot = page.locator('text=Body').first();
|
||||
await expect(bodySlot).toBeVisible();
|
||||
});
|
||||
|
||||
test('2-handed weapon blocks offhand slot', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.evaluate(() => {
|
||||
Object.keys(localStorage)
|
||||
.filter((k) => k.startsWith('mana-loop-'))
|
||||
.forEach((k) => localStorage.removeItem(k));
|
||||
});
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.getByRole('tab').filter({ hasText: '🛡️' }).click();
|
||||
|
||||
// The starting basic staff is 2-handed (twoHanded: true)
|
||||
// The Off Hand slot should show the "Occupied — 2H Weapon" badge
|
||||
const offHandBlocker = page.locator('text=Occupied').first();
|
||||
await expect(offHandBlocker).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Also check the blocked slot has the right tooltip/message
|
||||
const twoHWeaponBadge = page.locator('text=2-Handed').first();
|
||||
await expect(twoHWeaponBadge).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user