UNPKG

@rayners/foundry-test-utils

Version:

Shared testing utilities and mocks for FoundryVTT modules

458 lines (457 loc) 14.1 kB
/** * Comprehensive Foundry VTT Mock Setup * * This file provides a complete mock environment for Foundry VTT testing. * It can be shared between multiple projects that need Foundry mocks. * * Usage: * ```typescript * import './foundry-mocks'; * // Or import specific parts: * import { setupFoundryMocks, mockFoundryDocuments } from './foundry-mocks'; * ``` */ /// <reference types="@rayners/foundry-dev-tools/types" /> import { vi } from 'vitest'; // ============================================================================ // MOCK FACTORIES // ============================================================================ export function createMockScene(options = {}) { const regions = options.regions || new Map(); // Add filter method to regions map to match Foundry's Collection interface regions.filter = function (callback) { const results = []; for (const [id, region] of this.entries()) { if (callback(region)) { results.push(region); } } return results; }; return { id: options.id || 'test-scene', name: options.name || 'Test Scene', width: options.width || 4000, height: options.height || 3000, regions: regions, getFlag: vi.fn(), setFlag: vi.fn(), unsetFlag: vi.fn(), createEmbeddedDocuments: vi.fn().mockResolvedValue([]), deleteEmbeddedDocuments: vi.fn().mockResolvedValue([]), grid: { units: 'feet', type: 1, // SQUARE ...(options.grid || {}) } }; } export function createMockRegion(options = {}) { return { id: options.id || 'test-region', name: options.name || 'Test Region', shapes: options.shapes || [], flags: options.flags || {}, getFlag: vi.fn().mockImplementation((scope, key) => { return options.flags?.[scope]?.[key]; }), setFlag: vi.fn(), unsetFlag: vi.fn(), update: vi.fn(), delete: vi.fn(), testPoint: vi.fn().mockReturnValue(true) }; } export function createMockActor(options = {}) { return { id: options.id || 'test-actor', name: options.name || 'Test Actor', type: options.type || 'character', uuid: options.uuid || `Actor.${options.id || 'test-actor'}`, system: options.system || {}, items: options.items || [], flags: options.flags || {}, getFlag: vi.fn(), setFlag: vi.fn(), unsetFlag: vi.fn(), update: vi.fn(), delete: vi.fn() }; } export function createMockUser(options = {}) { return { id: options.id || 'test-user', isGM: options.isGM ?? false, getFlag: vi.fn(), setFlag: vi.fn(), unsetFlag: vi.fn() }; } export function createMockRollTable(options = {}) { return { id: options.id || 'test-table', name: options.name || 'Test Table', folder: options.folder || null, results: options.results || [], flags: options.flags || {}, roll: vi.fn().mockResolvedValue({ results: [{ text: 'Test Result', name: 'Test Result' }], total: 1 }), getFlag: vi.fn(), setFlag: vi.fn() }; } export function createMockFolder(options = {}) { return { id: options.id || 'test-folder', name: options.name || 'Test Folder', type: options.type || 'RollTable', color: options.color || '#000000', description: options.description || 'Test folder' }; } // ============================================================================ // FOUNDRY DOCUMENT CLASSES // ============================================================================ export class MockActorClass { static async create(data) { return createMockActor(data); } static async createDocuments(data) { return data.map(d => createMockActor(d)); } } export class MockRollTableClass { static async create(data) { return createMockRollTable(data); } static async createDocuments(data) { return data.map(d => createMockRollTable(d)); } } export class MockFolderClass { static async create(data) { return createMockFolder(data); } static async createDocuments(data) { return data.map(d => createMockFolder(d)); } } export class MockDialogClass { constructor(options = {}) { this.options = options; } static async confirm(options = {}) { return options.defaultYes !== false; } static async prompt(options = {}) { return options.defaultValue || null; } render(force) { // Mock render } close() { // Mock close } } // ============================================================================ // FOUNDRY GLOBALS SETUP // ============================================================================ export function setupFoundryGlobals() { // Foundry utility functions globalThis.foundry = { abstract: { TypeDataModel: class MockTypeDataModel { constructor(data = {}) { Object.assign(this, data); } prepareDerivedData() { } } }, data: { fields: { HTMLField: class { constructor(options = {}) { this.options = options; } }, StringField: class { constructor(options = {}) { this.options = options; } }, NumberField: class { constructor(options = {}) { this.options = options; if (typeof options.initial === 'function') { this.initial = options.initial(); } else { this.initial = options.initial || 0; } } }, BooleanField: class { constructor(options = {}) { this.options = options; this.initial = options.initial || false; } }, ObjectField: class { constructor(options = {}) { this.options = options; } }, SchemaField: class { constructor(schema = {}) { this.schema = schema; } }, ArrayField: class { constructor(element) { this.element = element; } }, DocumentIdField: class { constructor(options = {}) { this.options = options; } }, FilePathField: class { constructor(options = {}) { this.options = options; } } } }, utils: { mergeObject: vi.fn((original, other, options = {}) => ({ ...original, ...other })), duplicate: vi.fn(obj => JSON.parse(JSON.stringify(obj))), setProperty: vi.fn(), getProperty: vi.fn(), hasProperty: vi.fn(), expandObject: vi.fn(), flattenObject: vi.fn(), isNewerVersion: vi.fn(), randomID: vi.fn(() => Math.random().toString(36).substr(2, 9)) }, documents: { BaseRegion: class MockBaseRegion { constructor(data = {}) { Object.assign(this, data); } static defineSchema() { return {}; } } }, canvas: { layers: { RegionLayer: class MockRegionLayer { constructor(options = {}) { this.options = options; } activate() { } deactivate() { } draw() { } tearDown() { } } } } }; const g = globalThis; // Template functions g.loadTemplates = vi.fn().mockResolvedValue({}); g.renderTemplate = vi.fn().mockResolvedValue('<div>Mock Template</div>'); g.getTemplate = vi.fn().mockResolvedValue(() => '<div>Mock Template</div>'); // Document lookup functions g.fromUuid = vi.fn(); g.fromUuidSync = vi.fn(); // Text editor g.TextEditor = { enrichHTML: vi.fn(content => content) }; // Handlebars g.Handlebars = { registerHelper: vi.fn(), registerPartial: vi.fn() }; // Hooks system g.Hooks = { on: vi.fn(), once: vi.fn(), off: vi.fn(), call: vi.fn(), callAll: vi.fn() }; // PIXI Graphics (for canvas-based tests) g.PIXI = { Graphics: vi.fn(() => ({ beginFill: vi.fn(), drawPolygon: vi.fn(), endFill: vi.fn(), clear: vi.fn(), destroy: vi.fn() })), Point: vi.fn((x, y) => ({ x, y })) }; // Canvas Layer base class g.CanvasLayer = class MockCanvasLayer { constructor(options = {}) { this.options = options; this.name = options.name || 'mock'; } static get layerOptions() { return {}; } activate() { } deactivate() { } draw() { } tearDown() { } }; // CONST object g.CONST = { KEYBINDING_SCOPES: { GLOBAL: 'global', CLIENT: 'client' } }; } export function setupFoundryDocuments() { const g = globalThis; // Document classes g.Actor = MockActorClass; g.RollTable = MockRollTableClass; g.Folder = MockFolderClass; g.Dialog = MockDialogClass; // Sheet classes g.ActorSheet = class MockActorSheet { }; g.Application = class MockApplication { }; g.FormApplication = class MockFormApplication { }; // Other document classes g.ChatMessage = class MockChatMessage { static async create() { } }; // Roll class g.Roll = class MockRoll { constructor(formula) { this.formula = formula; this.total = 10; // Default total } async evaluate() { return this; } }; } export function setupFoundryGame(options = {}) { const mockUser = createMockUser(options.user); const mockScenes = options.scenes || [createMockScene()]; globalThis.game = { user: mockUser, userId: mockUser.id, users: new Map([[mockUser.id, mockUser]]), actors: new Map(), scenes: new Map(mockScenes.map(s => [s.id, s])), tables: new Map(), folders: new Map(), modules: new Map(), settings: { get: vi.fn(), set: vi.fn(), register: vi.fn() }, i18n: { localize: vi.fn((key) => key), format: vi.fn((key, data) => key) }, system: { id: options.systemId || 'test-system', title: 'Test System', data: {} }, version: '13.331', documentTypes: { Actor: ['character', 'npc'], Item: ['weapon', 'armor'], RollTable: ['RollTable'] } }; } export function setupFoundryUI() { globalThis.ui = { notifications: { info: vi.fn(), warn: vi.fn(), error: vi.fn() } }; } export function setupFoundryCanvas(scene) { const mockScene = scene || createMockScene(); globalThis.canvas = { scene: mockScene, regions: { activate: vi.fn(), deactivate: vi.fn() }, ready: true }; } export function setupFoundryConfig() { globalThis.CONFIG = { Actor: { documentClass: MockActorClass, typeLabels: {} }, debug: { hooks: false }, DND5E: { skills: { acr: { label: 'Acrobatics' }, ani: { label: 'Animal Handling' }, arc: { label: 'Arcana' }, ath: { label: 'Athletics' }, dec: { label: 'Deception' }, his: { label: 'History' }, ins: { label: 'Insight' }, inti: { label: 'Intimidation' }, inv: { label: 'Investigation' }, med: { label: 'Medicine' }, nat: { label: 'Nature' }, prc: { label: 'Perception' }, per: { label: 'Performance' }, prs: { label: 'Persuasion' }, rel: { label: 'Religion' }, slt: { label: 'Sleight of Hand' }, ste: { label: 'Stealth' }, sur: { label: 'Survival' } } } }; } // ============================================================================ // COMPLETE SETUP FUNCTION // ============================================================================ /** * Set up a complete Foundry VTT mock environment */ export function setupFoundryMocks(options = {}) { setupFoundryGlobals(); setupFoundryDocuments(); setupFoundryGame(options); setupFoundryUI(); setupFoundryConfig(); if (options.includeCanvas !== false) { setupFoundryCanvas(options.scenes?.[0]); } } // ============================================================================ // AUTO-SETUP (when imported) // ============================================================================ // Automatically set up basic mocks when this file is imported setupFoundryMocks();