feat(inventory): remove the requiredLevel equip gate from the detail panel

The API can no longer emit ITEM_LEVEL_REQUIREMENT_NOT_MET (Task 8 removed
the level gate server-side) and InventoryItem.item no longer carries
requiredLevel (Task 12). Drop the characterLevel input, the
meetsLevelRequirement computed, the associated template branch and copy,
and the dead German error-message mapping. Update fixtures across the
inventory specs to the real InventoryItem/CharacterResponse shapes.
This commit is contained in:
Bastian Wagner
2026-08-21 15:06:00 +02:00
parent df4c0c5527
commit c0401d5c60
8 changed files with 10 additions and 33 deletions

View File

@@ -6,7 +6,7 @@
<h2 class="inventory-detail__name" data-detail-name>{{ item.item.name }}</h2>
<p class="inventory-detail__rarity" data-detail-rarity>{{ rarityLabel() }}</p>
@if (slotLabel(); as slot) {
<p class="inventory-detail__meta">{{ slot }} · Stufe {{ item.item.requiredLevel }}</p>
<p class="inventory-detail__meta">{{ slot }}</p>
}
</div>
</div>
@@ -38,10 +38,6 @@
<span class="inventory-detail__equipped" data-detail-equipped>Ausgerüstet</span>
} @else if (!isEquippable()) {
<span class="inventory-detail__note">Nicht ausrüstbar</span>
} @else if (!meetsLevelRequirement()) {
<button type="button" class="inventory-detail__equip" data-detail-equip disabled>
Benötigt Stufe {{ item.item.requiredLevel }}
</button>
} @else {
<button
type="button"

View File

@@ -12,7 +12,6 @@ const wornSword: InventoryItem = {
name: 'Abgenutztes Kurzschwert',
rarity: 'COMMON',
equipmentSlot: 'WEAPON',
requiredLevel: 1,
weaponDamage: 8,
bonusAttack: 0,
bonusHp: 0,
@@ -30,7 +29,6 @@ const banditBlade: InventoryItem = {
name: 'Räuberklinge',
rarity: 'COMMON',
equipmentSlot: 'WEAPON',
requiredLevel: 1,
weaponDamage: 11,
bonusAttack: 1,
bonusHp: 0,
@@ -42,7 +40,6 @@ const banditBlade: InventoryItem = {
async function setup(overrides: {
item?: InventoryItem | null;
equippedItemInSlot?: InventoryItem | null;
characterLevel?: number;
busy?: boolean;
}) {
TestBed.resetTestingModule();
@@ -50,7 +47,6 @@ async function setup(overrides: {
const fixture = TestBed.createComponent(InventoryDetailPanelComponent);
fixture.componentRef.setInput('item', overrides.item ?? null);
fixture.componentRef.setInput('equippedItemInSlot', overrides.equippedItemInSlot ?? null);
fixture.componentRef.setInput('characterLevel', overrides.characterLevel ?? 1);
fixture.componentRef.setInput('busy', overrides.busy ?? false);
fixture.detectChanges();
return fixture;
@@ -79,12 +75,13 @@ describe('InventoryDetailPanelComponent', () => {
expect(text).toContain('+1');
});
it('shows a disabled Benötigt Stufe X button when the level requirement is not met', async () => {
const fixture = await setup({ item: { ...banditBlade, item: { ...banditBlade.item, requiredLevel: 5 } }, characterLevel: 1 });
const button = (fixture.nativeElement as HTMLElement).querySelector<HTMLButtonElement>('[data-detail-equip]');
it('always shows the plain equip button for an owned equippable item', async () => {
const fixture = await setup({ item: banditBlade });
const element = fixture.nativeElement as HTMLElement;
expect(button?.textContent).toContain('Benötigt Stufe 5');
expect(button?.disabled).toBe(true);
const button = element.querySelector<HTMLButtonElement>('[data-detail-equip]');
expect(button?.disabled).toBe(false);
expect(button?.textContent?.trim()).toBe('Ausrüsten');
});
it('emits equip with the characterItemId when Ausrüsten is clicked', async () => {

View File

@@ -35,7 +35,6 @@ const STAT_LABELS: ReadonlyArray<{ label: string; key: StatKey }> = [
export class InventoryDetailPanelComponent {
readonly item = input<InventoryItem | null>(null);
readonly equippedItemInSlot = input<InventoryItem | null>(null);
readonly characterLevel = input(1);
readonly busy = input(false);
readonly equip = output<string>();
@@ -66,11 +65,6 @@ export class InventoryDetailPanelComponent {
protected readonly isEquippable = computed(() => !!this.item()?.item.equipmentSlot);
protected readonly meetsLevelRequirement = computed(() => {
const item = this.item();
return item ? item.item.requiredLevel <= this.characterLevel() : true;
});
protected onEquip(): void {
const item = this.item();
if (item) {

View File

@@ -26,7 +26,6 @@
<app-inventory-detail-panel
[item]="inventoryStore.selectedItem()"
[equippedItemInSlot]="equippedItemInSelectedSlot()"
[characterLevel]="characterLevel()"
[busy]="inventoryStore.equipping()"
(equip)="equipSelected($event)"
/>

View File

@@ -19,7 +19,6 @@ const inventory: InventoryResponse = {
name: 'Abgenutztes Kurzschwert',
rarity: 'COMMON',
equipmentSlot: 'WEAPON',
requiredLevel: 1,
weaponDamage: 8,
bonusAttack: 0,
bonusHp: 0,
@@ -36,7 +35,6 @@ const inventory: InventoryResponse = {
name: 'Räuberklinge',
rarity: 'COMMON',
equipmentSlot: 'WEAPON',
requiredLevel: 1,
weaponDamage: 11,
bonusAttack: 1,
bonusHp: 0,
@@ -63,8 +61,7 @@ const equipment: EquipmentResponse = {
const character: CharacterResponse = {
id: 'character-1',
name: 'Aric Duskwalker',
level: 1,
experience: 0,
renown: 0,
silver: 0,
currentHp: 100,
maxHp: 100,
@@ -178,7 +175,6 @@ describe('InventoryPageComponent', () => {
name: 'Eiserner Helm',
rarity: 'COMMON',
equipmentSlot: 'HEAD',
requiredLevel: 1,
weaponDamage: 0,
bonusAttack: 0,
bonusHp: 5,

View File

@@ -37,8 +37,6 @@ export class InventoryPageComponent implements OnInit {
protected readonly slotOrder = SLOT_ORDER;
protected readonly slotLabels = SLOT_LABELS;
protected readonly characterLevel = computed(() => this.worldStore.character()?.level ?? 1);
protected readonly equippedItemInSelectedSlot = computed(() => {
const selected = this.inventoryStore.selectedItem();
if (!selected?.item.equipmentSlot) {

View File

@@ -18,7 +18,6 @@ const inventory: InventoryResponse = {
name: 'Abgenutztes Kurzschwert',
rarity: 'COMMON',
equipmentSlot: 'WEAPON',
requiredLevel: 1,
weaponDamage: 8,
bonusAttack: 0,
bonusHp: 0,
@@ -35,7 +34,6 @@ const inventory: InventoryResponse = {
name: 'Räuberklinge',
rarity: 'COMMON',
equipmentSlot: 'WEAPON',
requiredLevel: 1,
weaponDamage: 11,
bonusAttack: 1,
bonusHp: 0,
@@ -135,11 +133,11 @@ describe('InventoryStore', () => {
it('surfaces a German message for a known equip error', async () => {
await store.load();
api.equipItem.mockReturnValue(
throwError(() => new HttpErrorResponse({ error: { code: 'ITEM_LEVEL_REQUIREMENT_NOT_MET' }, status: 400 })),
throwError(() => new HttpErrorResponse({ error: { code: 'ITEM_NOT_EQUIPPABLE' }, status: 400 })),
);
await store.equip('item-blade');
expect(store.error()).toBe('Du erfüllst die Stufenanforderung nicht.');
expect(store.error()).toBe('Dieser Gegenstand kann nicht ausgerüstet werden.');
});
});

View File

@@ -12,7 +12,6 @@ const EQUIPMENT_ERROR_MESSAGES: Readonly<Record<string, string>> = {
CHARACTER_ITEM_NOT_FOUND: 'Dieser Gegenstand konnte nicht gefunden werden.',
ITEM_NOT_OWNED: 'Dieser Gegenstand gehört dir nicht.',
ITEM_NOT_EQUIPPABLE: 'Dieser Gegenstand kann nicht ausgerüstet werden.',
ITEM_LEVEL_REQUIREMENT_NOT_MET: 'Du erfüllst die Stufenanforderung nicht.',
INVALID_EQUIPMENT_SLOT: 'Dieser Ausrüstungsplatz ist ungültig.',
CHARACTER_IN_COMBAT: 'Ausrüstung kann während eines Kampfes nicht geändert werden.',
};