Repo-wide grep sweep (apps/web/src, apps/api/src) for leftover German content strings missed by Tasks 1-9, mostly in spec fixtures/assertions that mirror already-translated seed content (monster/item names, POI titles and action labels, location names/descriptions) plus a few real source-file gaps: - inventory-detail-panel.component.ts: STAT_LABELS (Waffenschaden, Angriff, Leben, Rüstung) were never translated by Task 6; now match the identical English labels already used in inventory-page.component.html. - app-shell.component.html: aria-label="Spielinhalt" -> "Game content" (this file was outside every prior task's file list). - location-interaction-panel.component.spec.ts: dead NPC-quote fixture translated to match the real wounded-scout POI text. Code comments referencing German source-spec section titles or not-yet-seeded faction names, and inline calculation-documentation comments, are left as-is per the source spec's scope (dev-facing comments may stay German). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACkMEDYiwtcfchqKkiUJNX
658 lines
22 KiB
TypeScript
658 lines
22 KiB
TypeScript
import { DataSource } from 'typeorm';
|
|
import { Character } from '../../characters/entities/character.entity';
|
|
import { CharacterEquipment } from '../../equipment/entities/character-equipment.entity';
|
|
import { CharacterItem } from '../../items/entities/character-item.entity';
|
|
import { ItemDefinition } from '../../items/entities/item-definition.entity';
|
|
import { LootTableEntry } from '../../loot/entities/loot-table-entry.entity';
|
|
import { LootTable } from '../../loot/entities/loot-table.entity';
|
|
import { LocationMonster } from '../../monsters/entities/location-monster.entity';
|
|
import { MonsterDefinition } from '../../monsters/entities/monster-definition.entity';
|
|
import { LocationConnection } from '../../world/entities/location-connection.entity';
|
|
import { LocationDefinition } from '../../world/entities/location-definition.entity';
|
|
import { ReputationFaction } from '../../reputation/entities/reputation-faction.entity';
|
|
import { TurnInDefinition } from '../../turn-in/entities/turn-in-definition.entity';
|
|
import { DEMO_CHARACTER_STARTING_WEAPON_ITEM_ID } from '../../demo/demo-character.constants';
|
|
import {
|
|
ASH_RAT_LOOT_TABLE_ID,
|
|
ITEM_IDS,
|
|
ROAD_BANDIT_LOOT_TABLE_ID,
|
|
} from './item.constants';
|
|
import { seedVisibleVerticalSlice } from './vertical-slice.seed';
|
|
|
|
type Row = Record<string, unknown>;
|
|
|
|
const DEMO_CHARACTER_ID = '10000000-0000-4000-8000-000000000001';
|
|
const SOUTH_GATE_ID = '20000000-0000-4000-8000-000000000001';
|
|
const BURNED_ROAD_ID = '20000000-0000-4000-8000-000000000002';
|
|
const ASH_RAT_MONSTER_ID = '30000000-0000-4000-8000-000000000001';
|
|
const ROAD_BANDIT_MONSTER_ID = '30000000-0000-4000-8000-000000000002';
|
|
const WILD_ROAD_DOG_MONSTER_ID = '30000000-0000-4000-8000-000000000003';
|
|
const CHARRED_LOOTER_MONSTER_ID = '30000000-0000-4000-8000-000000000004';
|
|
|
|
class InMemoryRepository {
|
|
readonly rows: Row[] = [];
|
|
readonly upsert = jest.fn(
|
|
async (
|
|
values: Row | Row[],
|
|
conflictPaths: string[] | { conflictPaths: string[] },
|
|
) => {
|
|
const conflictKeys = Array.isArray(conflictPaths)
|
|
? conflictPaths
|
|
: conflictPaths.conflictPaths;
|
|
|
|
for (const value of Array.isArray(values) ? values : [values]) {
|
|
const existing = this.rows.find((row) =>
|
|
conflictKeys.every((key) => row[key] === value[key]),
|
|
);
|
|
|
|
if (existing) {
|
|
Object.assign(existing, value);
|
|
} else {
|
|
this.rows.push({ ...value });
|
|
}
|
|
}
|
|
},
|
|
);
|
|
readonly findOneBy = jest.fn(async (criteria: Row) =>
|
|
this.rows.find((row) =>
|
|
Object.entries(criteria).every(([key, value]) => row[key] === value),
|
|
),
|
|
);
|
|
readonly insert = jest.fn(async (value: Row) => {
|
|
this.rows.push({ ...value });
|
|
});
|
|
readonly update = jest.fn(async (criteria: string | Row, value: Row) => {
|
|
const row = this.rows.find((candidate) =>
|
|
typeof criteria === 'string'
|
|
? candidate.id === criteria
|
|
: Object.entries(criteria).every(
|
|
([key, expected]) => candidate[key] === expected,
|
|
),
|
|
);
|
|
|
|
if (row) {
|
|
Object.assign(row, value);
|
|
}
|
|
});
|
|
}
|
|
|
|
function createDataSource(
|
|
locationRepository: InMemoryRepository,
|
|
connectionRepository: InMemoryRepository,
|
|
characterRepository: InMemoryRepository,
|
|
monsterRepository: InMemoryRepository,
|
|
locationMonsterRepository: InMemoryRepository,
|
|
itemRepository: InMemoryRepository = new InMemoryRepository(),
|
|
lootTableRepository: InMemoryRepository = new InMemoryRepository(),
|
|
lootEntryRepository: InMemoryRepository = new InMemoryRepository(),
|
|
characterItemRepository: InMemoryRepository = new InMemoryRepository(),
|
|
characterEquipmentRepository: InMemoryRepository = new InMemoryRepository(),
|
|
reputationFactionRepository: InMemoryRepository = new InMemoryRepository(),
|
|
turnInDefinitionRepository: InMemoryRepository = new InMemoryRepository(),
|
|
): DataSource {
|
|
return {
|
|
getRepository: jest.fn((entity: unknown) => {
|
|
if (entity === LocationDefinition) return locationRepository;
|
|
if (entity === LocationConnection) return connectionRepository;
|
|
if (entity === Character) return characterRepository;
|
|
if (entity === MonsterDefinition) return monsterRepository;
|
|
if (entity === LocationMonster) return locationMonsterRepository;
|
|
if (entity === ItemDefinition) return itemRepository;
|
|
if (entity === LootTable) return lootTableRepository;
|
|
if (entity === LootTableEntry) return lootEntryRepository;
|
|
if (entity === CharacterItem) return characterItemRepository;
|
|
if (entity === CharacterEquipment) return characterEquipmentRepository;
|
|
if (entity === ReputationFaction) return reputationFactionRepository;
|
|
if (entity === TurnInDefinition) return turnInDefinitionRepository;
|
|
|
|
throw new Error('Unexpected repository');
|
|
}),
|
|
} as unknown as DataSource;
|
|
}
|
|
|
|
describe('seedVisibleVerticalSlice', () => {
|
|
it('upserts the two locations and directed connections while inserting one stable demo character', async () => {
|
|
const locationRepository = new InMemoryRepository();
|
|
const connectionRepository = new InMemoryRepository();
|
|
const characterRepository = new InMemoryRepository();
|
|
const monsterRepository = new InMemoryRepository();
|
|
const locationMonsterRepository = new InMemoryRepository();
|
|
const dataSource = createDataSource(
|
|
locationRepository,
|
|
connectionRepository,
|
|
characterRepository,
|
|
monsterRepository,
|
|
locationMonsterRepository,
|
|
);
|
|
|
|
await seedVisibleVerticalSlice(dataSource);
|
|
Object.assign(characterRepository.rows[0], {
|
|
currentLocationId: BURNED_ROAD_ID,
|
|
currentHp: 57,
|
|
// Deliberately NOT the seed default of 1: if a re-seed clobbered player
|
|
// state back to defaults, an assertion on the default value could not
|
|
// tell the difference.
|
|
renown: 5,
|
|
});
|
|
await seedVisibleVerticalSlice(dataSource);
|
|
|
|
expect(connectionRepository.upsert).toHaveBeenCalledWith(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
fromLocationId: SOUTH_GATE_ID,
|
|
toLocationId: BURNED_ROAD_ID,
|
|
}),
|
|
expect.objectContaining({
|
|
fromLocationId: BURNED_ROAD_ID,
|
|
toLocationId: SOUTH_GATE_ID,
|
|
}),
|
|
]),
|
|
['fromLocationId', 'toLocationId'],
|
|
);
|
|
expect(characterRepository.insert).toHaveBeenCalledWith(
|
|
expect.objectContaining({ id: DEMO_CHARACTER_ID }),
|
|
);
|
|
expect(locationRepository.rows).toHaveLength(2);
|
|
expect(locationRepository.rows).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
key: 'south-gate',
|
|
artworkPath: '/images/backgrounds/Suedtor.png',
|
|
}),
|
|
expect.objectContaining({
|
|
key: 'burned-road',
|
|
artworkPath: '/images/backgrounds/Aschestrasse.png',
|
|
}),
|
|
]),
|
|
);
|
|
expect(connectionRepository.rows).toHaveLength(2);
|
|
expect(characterRepository.rows).toHaveLength(1);
|
|
expect(characterRepository.rows[0]).toEqual(
|
|
expect.objectContaining({
|
|
currentLocationId: BURNED_ROAD_ID,
|
|
currentHp: 57,
|
|
renown: 5,
|
|
}),
|
|
);
|
|
|
|
expect(monsterRepository.insert).toHaveBeenCalledTimes(4);
|
|
expect(monsterRepository.rows).toHaveLength(4);
|
|
expect(monsterRepository.rows).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
key: 'ash-rat',
|
|
name: 'Ash Rat',
|
|
level: 1,
|
|
maxHp: 45,
|
|
attack: 5,
|
|
armor: 0,
|
|
silverMin: 0,
|
|
silverMax: 0,
|
|
artworkPath: '/images/monsters/ash-rat.png',
|
|
}),
|
|
expect.objectContaining({
|
|
key: 'road-bandit',
|
|
name: 'Road Bandit',
|
|
level: 2,
|
|
maxHp: 75,
|
|
attack: 9,
|
|
armor: 5,
|
|
silverMin: 0,
|
|
silverMax: 0,
|
|
artworkPath: '/images/monsters/road-bandit.png',
|
|
}),
|
|
expect.objectContaining({
|
|
key: 'wild-road-dog',
|
|
name: 'Feral Road Hound',
|
|
level: 1,
|
|
silverMin: 0,
|
|
silverMax: 0,
|
|
artworkPath: '/images/monsters/wild-road-dog.png',
|
|
iconPath: '/images/combat/icons/wild-road-dog-128.png',
|
|
}),
|
|
expect.objectContaining({
|
|
key: 'charred-looter',
|
|
name: 'Charred Raider',
|
|
level: 2,
|
|
silverMin: 0,
|
|
silverMax: 0,
|
|
artworkPath: '/images/monsters/charred-looter.png',
|
|
iconPath: '/images/combat/icons/charred-looter-128.png',
|
|
}),
|
|
]),
|
|
);
|
|
|
|
expect(locationMonsterRepository.upsert).toHaveBeenCalledWith(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
locationId: BURNED_ROAD_ID,
|
|
monsterId: ASH_RAT_MONSTER_ID,
|
|
weight: 40,
|
|
}),
|
|
expect.objectContaining({
|
|
locationId: BURNED_ROAD_ID,
|
|
monsterId: WILD_ROAD_DOG_MONSTER_ID,
|
|
weight: 30,
|
|
}),
|
|
expect.objectContaining({
|
|
locationId: BURNED_ROAD_ID,
|
|
monsterId: ROAD_BANDIT_MONSTER_ID,
|
|
weight: 20,
|
|
}),
|
|
expect.objectContaining({
|
|
locationId: BURNED_ROAD_ID,
|
|
monsterId: CHARRED_LOOTER_MONSTER_ID,
|
|
weight: 10,
|
|
}),
|
|
]),
|
|
['locationId', 'monsterId'],
|
|
);
|
|
expect(locationMonsterRepository.rows).toHaveLength(4);
|
|
});
|
|
|
|
it('seeds the local view content of the Burned Road with four points of interest', async () => {
|
|
const locationRepository = new InMemoryRepository();
|
|
const dataSource = createDataSource(
|
|
locationRepository,
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
);
|
|
|
|
await seedVisibleVerticalSlice(dataSource);
|
|
|
|
const burnedRoad = locationRepository.rows.find(
|
|
(row) => row.key === 'burned-road',
|
|
) as Row;
|
|
|
|
expect(burnedRoad).toEqual(
|
|
expect.objectContaining({
|
|
regionName: 'Ashen Fields',
|
|
regionTierLabel: 'Tier 1',
|
|
locationType: 'HUNTING_GROUND',
|
|
localArtworkPath: '/images/backgrounds/Aschestrasse.png',
|
|
}),
|
|
);
|
|
expect(burnedRoad.localDescription).toContain(
|
|
'An old trade road, burned to ash by fire and war.',
|
|
);
|
|
|
|
const pointsOfInterest = burnedRoad.localPointsOfInterest as {
|
|
key: string;
|
|
type: string;
|
|
}[];
|
|
expect(pointsOfInterest.map((poi) => poi.key)).toEqual([
|
|
'hunt-area',
|
|
'inspect-tracks',
|
|
'search-abandoned-wagon',
|
|
'wounded-scout',
|
|
]);
|
|
expect(pointsOfInterest.map((poi) => poi.type)).toEqual([
|
|
'HUNT',
|
|
'INVESTIGATE',
|
|
'SEARCH',
|
|
'NPC',
|
|
]);
|
|
|
|
const primaryActions = burnedRoad.localPrimaryActions as {
|
|
label: string;
|
|
}[];
|
|
expect(primaryActions.map((action) => action.label)).toEqual([
|
|
'Begin Hunt',
|
|
'Investigate tracks',
|
|
'Search surroundings',
|
|
'To Map',
|
|
]);
|
|
});
|
|
|
|
it('gives the South Gate its own local content so a second location needs no new component', async () => {
|
|
const locationRepository = new InMemoryRepository();
|
|
const dataSource = createDataSource(
|
|
locationRepository,
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
);
|
|
|
|
await seedVisibleVerticalSlice(dataSource);
|
|
|
|
const southGate = locationRepository.rows.find(
|
|
(row) => row.key === 'south-gate',
|
|
) as Row;
|
|
|
|
expect(southGate).toEqual(
|
|
expect.objectContaining({
|
|
locationType: 'TRANSITION',
|
|
localArtworkPath: '/images/backgrounds/Suedtor.png',
|
|
}),
|
|
);
|
|
expect(southGate.localPointsOfInterest).toHaveLength(3);
|
|
// A transition location offers no hunt, so no HUNT hotspot may appear.
|
|
expect(
|
|
(southGate.localPointsOfInterest as { type: string }[]).some(
|
|
(poi) => poi.type === 'HUNT',
|
|
),
|
|
).toBe(false);
|
|
});
|
|
|
|
it('preserves existing location IDs and uses them for the directed connections', async () => {
|
|
const locationRepository = new InMemoryRepository();
|
|
const connectionRepository = new InMemoryRepository();
|
|
const characterRepository = new InMemoryRepository();
|
|
const monsterRepository = new InMemoryRepository();
|
|
const locationMonsterRepository = new InMemoryRepository();
|
|
const persistedSouthGateId = '40000000-0000-4000-8000-000000000001';
|
|
locationRepository.rows.push({
|
|
id: persistedSouthGateId,
|
|
key: 'south-gate',
|
|
name: 'Outdated South Gate',
|
|
});
|
|
const dataSource = createDataSource(
|
|
locationRepository,
|
|
connectionRepository,
|
|
characterRepository,
|
|
monsterRepository,
|
|
locationMonsterRepository,
|
|
);
|
|
|
|
await seedVisibleVerticalSlice(dataSource);
|
|
|
|
expect(locationRepository.rows).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
id: persistedSouthGateId,
|
|
key: 'south-gate',
|
|
name: 'Graufurt South Gate',
|
|
}),
|
|
expect.objectContaining({
|
|
id: BURNED_ROAD_ID,
|
|
key: 'burned-road',
|
|
}),
|
|
]),
|
|
);
|
|
expect(connectionRepository.rows).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
fromLocationId: persistedSouthGateId,
|
|
toLocationId: BURNED_ROAD_ID,
|
|
}),
|
|
expect.objectContaining({
|
|
fromLocationId: BURNED_ROAD_ID,
|
|
toLocationId: persistedSouthGateId,
|
|
}),
|
|
]),
|
|
);
|
|
expect(characterRepository.rows).toEqual([
|
|
expect.objectContaining({
|
|
id: DEMO_CHARACTER_ID,
|
|
currentLocationId: persistedSouthGateId,
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it('seeds the tier-1 items and both loot tables idempotently and wires them to the monsters', async () => {
|
|
const locationRepository = new InMemoryRepository();
|
|
const connectionRepository = new InMemoryRepository();
|
|
const characterRepository = new InMemoryRepository();
|
|
const monsterRepository = new InMemoryRepository();
|
|
const locationMonsterRepository = new InMemoryRepository();
|
|
const itemRepository = new InMemoryRepository();
|
|
const lootTableRepository = new InMemoryRepository();
|
|
const lootEntryRepository = new InMemoryRepository();
|
|
const dataSource = createDataSource(
|
|
locationRepository,
|
|
connectionRepository,
|
|
characterRepository,
|
|
monsterRepository,
|
|
locationMonsterRepository,
|
|
itemRepository,
|
|
lootTableRepository,
|
|
lootEntryRepository,
|
|
);
|
|
|
|
await seedVisibleVerticalSlice(dataSource);
|
|
await seedVisibleVerticalSlice(dataSource);
|
|
|
|
expect(itemRepository.rows).toHaveLength(13);
|
|
expect(itemRepository.rows).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
key: 'bandit-blade',
|
|
name: 'Bandit Blade',
|
|
type: 'EQUIPMENT',
|
|
equipmentSlot: 'WEAPON',
|
|
rarity: 'COMMON',
|
|
weaponDamage: 11,
|
|
bonusAttack: 1,
|
|
sellPrice: 0,
|
|
iconPath: '/images/items/bandit-blade.png',
|
|
}),
|
|
expect.objectContaining({
|
|
key: 'ash-pelt',
|
|
name: 'Ash Pelt',
|
|
type: 'TRADE_GOOD',
|
|
equipmentSlot: null,
|
|
}),
|
|
expect.objectContaining({
|
|
key: 'bandit-insignia',
|
|
name: 'Bandit Insignia',
|
|
type: 'TROPHY',
|
|
equipmentSlot: null,
|
|
}),
|
|
]),
|
|
);
|
|
|
|
expect(lootTableRepository.rows).toHaveLength(2);
|
|
expect(lootEntryRepository.rows).toHaveLength(6);
|
|
expect(lootEntryRepository.rows).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
lootTableId: ASH_RAT_LOOT_TABLE_ID,
|
|
itemDefinitionId: ITEM_IDS['ash-pelt'],
|
|
position: 1,
|
|
dropChance: '0.6000',
|
|
}),
|
|
expect.objectContaining({
|
|
lootTableId: ROAD_BANDIT_LOOT_TABLE_ID,
|
|
itemDefinitionId: ITEM_IDS['bandit-blade'],
|
|
position: 1,
|
|
dropChance: '0.1800',
|
|
}),
|
|
]),
|
|
);
|
|
|
|
// The Kleiner Heiltrank entry is deliberately deferred (spec §16).
|
|
expect(
|
|
lootEntryRepository.rows.some(
|
|
(row) => row.itemDefinitionId === ITEM_IDS['small-healing-potion'],
|
|
),
|
|
).toBe(false);
|
|
|
|
expect(monsterRepository.rows).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
key: 'ash-rat',
|
|
lootTableId: ASH_RAT_LOOT_TABLE_ID,
|
|
}),
|
|
expect.objectContaining({
|
|
key: 'road-bandit',
|
|
lootTableId: ROAD_BANDIT_LOOT_TABLE_ID,
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('seeds the starting sword as a real, equipped CharacterItem idempotently', async () => {
|
|
const locationRepository = new InMemoryRepository();
|
|
const connectionRepository = new InMemoryRepository();
|
|
const characterRepository = new InMemoryRepository();
|
|
const monsterRepository = new InMemoryRepository();
|
|
const locationMonsterRepository = new InMemoryRepository();
|
|
const characterItemRepository = new InMemoryRepository();
|
|
const characterEquipmentRepository = new InMemoryRepository();
|
|
const dataSource = createDataSource(
|
|
locationRepository,
|
|
connectionRepository,
|
|
characterRepository,
|
|
monsterRepository,
|
|
locationMonsterRepository,
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
characterItemRepository,
|
|
characterEquipmentRepository,
|
|
);
|
|
|
|
await seedVisibleVerticalSlice(dataSource);
|
|
await seedVisibleVerticalSlice(dataSource);
|
|
|
|
expect(characterItemRepository.rows).toHaveLength(1);
|
|
expect(characterItemRepository.rows[0]).toEqual(
|
|
expect.objectContaining({
|
|
characterId: DEMO_CHARACTER_ID,
|
|
itemDefinitionId: ITEM_IDS['worn-short-sword'],
|
|
quantity: 1,
|
|
}),
|
|
);
|
|
expect(characterEquipmentRepository.rows).toHaveLength(1);
|
|
expect(characterEquipmentRepository.rows[0]).toEqual(
|
|
expect.objectContaining({
|
|
characterId: DEMO_CHARACTER_ID,
|
|
slot: 'WEAPON',
|
|
characterItemId: DEMO_CHARACTER_STARTING_WEAPON_ITEM_ID,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('never re-equips the starting sword once the player has equipped different gear', async () => {
|
|
const locationRepository = new InMemoryRepository();
|
|
const connectionRepository = new InMemoryRepository();
|
|
const characterRepository = new InMemoryRepository();
|
|
const monsterRepository = new InMemoryRepository();
|
|
const locationMonsterRepository = new InMemoryRepository();
|
|
const characterItemRepository = new InMemoryRepository();
|
|
const characterEquipmentRepository = new InMemoryRepository();
|
|
const dataSource = createDataSource(
|
|
locationRepository,
|
|
connectionRepository,
|
|
characterRepository,
|
|
monsterRepository,
|
|
locationMonsterRepository,
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
characterItemRepository,
|
|
characterEquipmentRepository,
|
|
);
|
|
|
|
await seedVisibleVerticalSlice(dataSource);
|
|
// Simulate the player having equipped earned loot instead.
|
|
characterEquipmentRepository.rows[0]['characterItemId'] =
|
|
'earned-bandit-blade-item-id';
|
|
|
|
await seedVisibleVerticalSlice(dataSource);
|
|
|
|
expect(characterEquipmentRepository.rows).toHaveLength(1);
|
|
expect(characterEquipmentRepository.rows[0]['characterItemId']).toBe(
|
|
'earned-bandit-blade-item-id',
|
|
);
|
|
expect(characterItemRepository.rows).toHaveLength(1);
|
|
});
|
|
|
|
it('reuses a naturally-looted starting sword instead of inserting a duplicate CharacterItem', async () => {
|
|
const locationRepository = new InMemoryRepository();
|
|
const connectionRepository = new InMemoryRepository();
|
|
const characterRepository = new InMemoryRepository();
|
|
const monsterRepository = new InMemoryRepository();
|
|
const locationMonsterRepository = new InMemoryRepository();
|
|
const characterItemRepository = new InMemoryRepository();
|
|
const characterEquipmentRepository = new InMemoryRepository();
|
|
const dataSource = createDataSource(
|
|
locationRepository,
|
|
connectionRepository,
|
|
characterRepository,
|
|
monsterRepository,
|
|
locationMonsterRepository,
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
characterItemRepository,
|
|
characterEquipmentRepository,
|
|
);
|
|
|
|
// Simulate the demo character having already looted a worn-short-sword
|
|
// naturally, under a DB-generated id that differs from the seed's
|
|
// stable literal constant.
|
|
const naturallyLootedItemId = 'naturally-looted-sword-item-id';
|
|
characterItemRepository.rows.push({
|
|
id: naturallyLootedItemId,
|
|
characterId: DEMO_CHARACTER_ID,
|
|
itemDefinitionId: ITEM_IDS['worn-short-sword'],
|
|
quantity: 1,
|
|
});
|
|
|
|
await seedVisibleVerticalSlice(dataSource);
|
|
|
|
expect(characterItemRepository.rows).toHaveLength(1);
|
|
expect(characterEquipmentRepository.rows).toHaveLength(1);
|
|
expect(characterEquipmentRepository.rows[0]).toEqual(
|
|
expect.objectContaining({
|
|
characterId: DEMO_CHARACTER_ID,
|
|
slot: 'WEAPON',
|
|
characterItemId: naturallyLootedItemId,
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('seeds the Grenzwacht faction', async () => {
|
|
const reputationFactionRepository = new InMemoryRepository();
|
|
const dataSource = createDataSource(
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
reputationFactionRepository,
|
|
);
|
|
|
|
await seedVisibleVerticalSlice(dataSource);
|
|
|
|
const faction = await dataSource
|
|
.getRepository(ReputationFaction)
|
|
.findOneBy({ key: 'border-guard' });
|
|
|
|
expect(faction).toMatchObject({ name: 'Border Watch', enabled: true });
|
|
});
|
|
|
|
it('seeds both turn-in definitions', async () => {
|
|
const turnInDefinitionRepository = new InMemoryRepository();
|
|
const dataSource = createDataSource(
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
new InMemoryRepository(),
|
|
turnInDefinitionRepository,
|
|
);
|
|
|
|
await seedVisibleVerticalSlice(dataSource);
|
|
|
|
expect(
|
|
turnInDefinitionRepository.rows.map((row: Row) => row.key).sort(),
|
|
).toEqual(['ash-pelt-border-guard', 'bandit-insignia-border-guard']);
|
|
});
|
|
});
|