First round of test
This commit is contained in:
@@ -275,8 +275,21 @@ Hooks.once("ready", () => {
|
|||||||
if (adapter.users.isGM()) {
|
if (adapter.users.isGM()) {
|
||||||
directorsBoard = new DirectorsBoard(stateStore, scryingPoolController, adapter, scenePresetManager, playerPrivacyManager);
|
directorsBoard = new DirectorsBoard(stateStore, scryingPoolController, adapter, scenePresetManager, playerPrivacyManager);
|
||||||
directorsBoard.init();
|
directorsBoard.init();
|
||||||
|
window.directorsBoard = directorsBoard;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-load participant-card as a Handlebars partial for directors-board
|
||||||
|
// ApplicationV2 requires partials to be registered explicitly
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('modules/video-view-manager/templates/participant-card.hbs');
|
||||||
|
const source = await resp.text();
|
||||||
|
Handlebars.registerPartial('modules/video-view-manager/templates/participant-card.hbs', source);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[ScryingPool] Failed to register participant-card partial:', err);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
// Story 4.1: Initialize PlayerPrivacyPanelMenu with DI dependencies
|
// Story 4.1: Initialize PlayerPrivacyPanelMenu with DI dependencies
|
||||||
// Story 4.2: Pass portraitFallbackHandler for portrait selection
|
// Story 4.2: Pass portraitFallbackHandler for portrait selection
|
||||||
initPlayerPrivacyPanelMenu(adapter, playerPrivacyManager, portraitFallbackHandler);
|
initPlayerPrivacyPanelMenu(adapter, playerPrivacyManager, portraitFallbackHandler);
|
||||||
|
|||||||
@@ -49,11 +49,29 @@ export function isInitialized() {
|
|||||||
return _isInitialized;
|
return _isInitialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conditional base class — test environment lacks foundry globals.
|
||||||
|
*/
|
||||||
|
const _MenuAppBase =
|
||||||
|
typeof foundry !== 'undefined' &&
|
||||||
|
foundry.applications?.api?.ApplicationV2
|
||||||
|
? foundry.applications.api.ApplicationV2
|
||||||
|
: class _FallbackMenuApp {
|
||||||
|
static DEFAULT_OPTIONS = {};
|
||||||
|
get rendered() { return this._rendered ?? false; }
|
||||||
|
set rendered(v) { this._rendered = v; }
|
||||||
|
get element() { return this._element ?? null; }
|
||||||
|
set element(v) { this._element = v; }
|
||||||
|
async render() { this._rendered = true; }
|
||||||
|
async close() { this._rendered = false; }
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PlayerPrivacyPanelMenu - Wrapper for Foundry settings menu.
|
* PlayerPrivacyPanelMenu - Wrapper for Foundry settings menu.
|
||||||
* When instantiated by Foundry, it creates a PlayerPrivacyPanel with the current user as target.
|
* When instantiated by Foundry, it creates a PlayerPrivacyPanel with the current user as target.
|
||||||
|
* Extends ApplicationV2 so it passes Foundry's registerMenu validation.
|
||||||
*/
|
*/
|
||||||
export class PlayerPrivacyPanelMenu {
|
export class PlayerPrivacyPanelMenu extends _MenuAppBase {
|
||||||
/**
|
/**
|
||||||
* @param {object} [options] - Foundry options (unused, but required by settings menu API)
|
* @param {object} [options] - Foundry options (unused, but required by settings menu API)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,57 +1,61 @@
|
|||||||
{{!-- Director's Board — GM camera-management overview window --}}
|
{{!-- Director's Board — GM camera-management overview window --}}
|
||||||
<section class="scrying-pool directors-board__content"
|
<div class="directors-board__inner">
|
||||||
role="list"
|
|
||||||
aria-label="{{localize "video-view-manager.directorsBoard.title"}}">
|
|
||||||
|
|
||||||
{{#unless isEmpty}}
|
<section class="scrying-pool directors-board__content"
|
||||||
{{#each participants}}
|
role="list"
|
||||||
{{> "modules/video-view-manager/templates/participant-card.hbs"}}
|
aria-label="{{localize "video-view-manager.directorsBoard.title"}}">
|
||||||
{{/each}}
|
|
||||||
{{else}}
|
|
||||||
<p class="directors-board__empty" role="listitem">
|
|
||||||
{{localize "video-view-manager.directorsBoard.empty"}}
|
|
||||||
</p>
|
|
||||||
{{/unless}}
|
|
||||||
|
|
||||||
</section>
|
{{#unless isEmpty}}
|
||||||
|
{{#each participants}}
|
||||||
|
{{> "modules/video-view-manager/templates/participant-card.hbs"}}
|
||||||
|
{{/each}}
|
||||||
|
{{else}}
|
||||||
|
<p class="directors-board__empty" role="listitem">
|
||||||
|
{{localize "video-view-manager.directorsBoard.empty"}}
|
||||||
|
</p>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="directors-board__bulk-bar">
|
||||||
|
<button type="button" class="directors-board__bulk-btn" data-action="show-all">
|
||||||
|
{{localize "video-view-manager.directorsBoard.bulk.showAll"}}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="directors-board__bulk-btn" data-action="hide-all">
|
||||||
|
{{localize "video-view-manager.directorsBoard.bulk.hideAll"}}
|
||||||
|
</button>
|
||||||
|
{{#if hasUndo}}
|
||||||
|
<button type="button" class="directors-board__bulk-btn directors-board__bulk-btn--undo" data-action="undo">
|
||||||
|
{{localize "video-view-manager.directorsBoard.bulk.undo"}}
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
|
{{#if hasRestore}}
|
||||||
|
<button type="button" class="directors-board__bulk-btn directors-board__bulk-btn--restore" data-action="restore-spotlight">
|
||||||
|
{{localize "video-view-manager.directorsBoard.bulk.restore"}}
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
|
<button type="button" class="directors-board__help-btn" data-action="open-shortcut-panel" aria-label="{{localize "video-view-manager.directorsBoard.shortcuts.openPanel"}}">?</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="directors-board__footer">
|
||||||
|
<button type="button" class="directors-board__footer-btn" data-action="save-preset">
|
||||||
|
{{localize "video-view-manager.directorsBoard.footer.savePreset"}}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="directors-board__footer-btn" data-action="load-preset" {{#unless hasPresets}}disabled{{/unless}}>
|
||||||
|
{{localize "video-view-manager.directorsBoard.footer.loadPreset"}}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="directors-board__footer-btn" data-action="export-presets">
|
||||||
|
{{localize "video-view-manager.directorsBoard.footer.exportPresets"}}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="directors-board__footer-btn" data-action="import-presets">
|
||||||
|
{{localize "video-view-manager.directorsBoard.footer.importPresets"}}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="directors-board__footer-btn" data-action="toggle-preset-panel">
|
||||||
|
{{localize "video-view-manager.directorsBoard.footer.autoApplySettings"}}
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
{{!-- Scene Preset Panel - rendered via JavaScript, not Handlebars --}}
|
||||||
|
{{!-- Panel is appended dynamically in DirectorsBoard._appendPresetPanel() --}}
|
||||||
|
|
||||||
<div class="directors-board__bulk-bar">
|
|
||||||
<button type="button" class="directors-board__bulk-btn" data-action="show-all">
|
|
||||||
{{localize "video-view-manager.directorsBoard.bulk.showAll"}}
|
|
||||||
</button>
|
|
||||||
<button type="button" class="directors-board__bulk-btn" data-action="hide-all">
|
|
||||||
{{localize "video-view-manager.directorsBoard.bulk.hideAll"}}
|
|
||||||
</button>
|
|
||||||
{{#if hasUndo}}
|
|
||||||
<button type="button" class="directors-board__bulk-btn directors-board__bulk-btn--undo" data-action="undo">
|
|
||||||
{{localize "video-view-manager.directorsBoard.bulk.undo"}}
|
|
||||||
</button>
|
|
||||||
{{/if}}
|
|
||||||
{{#if hasRestore}}
|
|
||||||
<button type="button" class="directors-board__bulk-btn directors-board__bulk-btn--restore" data-action="restore-spotlight">
|
|
||||||
{{localize "video-view-manager.directorsBoard.bulk.restore"}}
|
|
||||||
</button>
|
|
||||||
{{/if}}
|
|
||||||
<button type="button" class="directors-board__help-btn" data-action="open-shortcut-panel" aria-label="{{localize "video-view-manager.directorsBoard.shortcuts.openPanel"}}">?</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="directors-board__footer">
|
|
||||||
<button type="button" class="directors-board__footer-btn" data-action="save-preset">
|
|
||||||
{{localize "video-view-manager.directorsBoard.footer.savePreset"}}
|
|
||||||
</button>
|
|
||||||
<button type="button" class="directors-board__footer-btn" data-action="load-preset" {{#unless hasPresets}}disabled{{/unless}}>
|
|
||||||
{{localize "video-view-manager.directorsBoard.footer.loadPreset"}}
|
|
||||||
</button>
|
|
||||||
<button type="button" class="directors-board__footer-btn" data-action="export-presets">
|
|
||||||
{{localize "video-view-manager.directorsBoard.footer.exportPresets"}}
|
|
||||||
</button>
|
|
||||||
<button type="button" class="directors-board__footer-btn" data-action="import-presets">
|
|
||||||
{{localize "video-view-manager.directorsBoard.footer.importPresets"}}
|
|
||||||
</button>
|
|
||||||
<button type="button" class="directors-board__footer-btn" data-action="toggle-preset-panel">
|
|
||||||
{{localize "video-view-manager.directorsBoard.footer.autoApplySettings"}}
|
|
||||||
</button>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
{{!-- Scene Preset Panel - rendered via JavaScript, not Handlebars --}}
|
|
||||||
{{!-- Panel is appended dynamically in DirectorsBoard._appendPresetPanel() --}}
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
@@ -322,10 +322,10 @@ describe('DirectorsBoard', () => {
|
|||||||
expect(controller.action).toHaveBeenCalledWith('board', 'u2', 'active', expect.any(String), expect.any(Number));
|
expect(controller.action).toHaveBeenCalledWith('board', 'u2', 'active', expect.any(String), expect.any(Number));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clears _undoSnapshot after use (single-step only)', () => {
|
it('clears _undoSnapshot after use (single-step only)', async () => {
|
||||||
board._undoSnapshot = new Map([['u1', 'hidden']]);
|
board._undoSnapshot = new Map([['u1', 'hidden']]);
|
||||||
stateStore.getState.mockReturnValue('active');
|
stateStore.getState.mockReturnValue('active');
|
||||||
board.undo();
|
await board.undo();
|
||||||
expect(board._undoSnapshot).toBeNull();
|
expect(board._undoSnapshot).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -335,10 +335,10 @@ describe('DirectorsBoard', () => {
|
|||||||
expect(controller.action).not.toHaveBeenCalled();
|
expect(controller.action).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('second undo is unavailable after first (no-op)', () => {
|
it('second undo is unavailable after first (no-op)', async () => {
|
||||||
board._undoSnapshot = new Map([['u1', 'hidden']]);
|
board._undoSnapshot = new Map([['u1', 'hidden']]);
|
||||||
stateStore.getState.mockReturnValue('active');
|
stateStore.getState.mockReturnValue('active');
|
||||||
board.undo();
|
await board.undo();
|
||||||
board.undo();
|
board.undo();
|
||||||
expect(controller.action).toHaveBeenCalledTimes(1);
|
expect(controller.action).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
@@ -430,10 +430,10 @@ describe('DirectorsBoard spotlight', () => {
|
|||||||
expect(controller.action).toHaveBeenCalledWith('board', 'u2', 'active', expect.any(String), expect.any(Number));
|
expect(controller.action).toHaveBeenCalledWith('board', 'u2', 'active', expect.any(String), expect.any(Number));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clears _spotlightSnapshot after restore', () => {
|
it('clears _spotlightSnapshot after restore', async () => {
|
||||||
board._spotlightSnapshot = new Map([['u1', 'active']]);
|
board._spotlightSnapshot = new Map([['u1', 'active']]);
|
||||||
stateStore.getState.mockReturnValue('active');
|
stateStore.getState.mockReturnValue('active');
|
||||||
board.restoreSpotlight();
|
await board.restoreSpotlight();
|
||||||
expect(board._spotlightSnapshot).toBeNull();
|
expect(board._spotlightSnapshot).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user