// Rôle : vérifie les invariants statiques des toolboxes et de leurs outils.
import { readFile } from "node:fs/promises";
import { test } from "node:test";
import assert from "node:assert/strict";
import { SOCKET_SHAPES, formatValue, getSocketItemName, setItemCategory } from "../website/src/features/toolboxes/modules/equipmentPlannerOptions.js";
import { applyDueResets, getTaskParentMap, getTaskTreeIds, isTaskResetSoon, moveTask } from "../website/src/features/toolboxes/modules/taskPlannerUtils.js";
import { getMemosDataAfterPlannerRemoval, getPlannerTaskTickerItems, resolveMemosPlannerLink } from "../website/src/features/toolboxes/modules/memosPlannerUtils.js";
import { createInput, getInputLabel, normalizeCustomKey, parseHoldSeconds } from "../website/src/features/toolboxes/modules/combosUtils.js";
import { getBoundsFromStartAndSize, getSelectionBounds, moveSelectionCells, parseTsvBlock, pasteCellBlock } from "../website/src/features/toolboxes/modules/tableUtils.js";
import { canToggleAutoRefresh, getCountdownValidationError, isAlertGuarded } from "../website/src/features/toolboxes/modules/timerOptions.js";
import { getCalendarOccurrences, getCurrentCalendarOccurrence, getMonthEnd, getMonthStart, getNextCalendarAlertTime, getWeekStart } from "../website/src/features/toolboxes/modules/calendarUtils.js";
import { exportModuleText, importModuleText } from "../website/src/features/toolboxes/modules/textImport.js";
import { compactCalendarDataForStorage, normalizeCalendarData } from "../website/src/features/toolboxes/storage/modules/calendar.js";
function countLines(source) {
return source.trimEnd().split("\n").length;
}
test("toolbox cards and pages retain their public UI conventions", async () => {
const toolboxCard = await readFile("website/src/features/toolboxes/ToolboxCard.jsx", "utf8");
const toolboxPages = await readFile("website/src/features/toolboxes/ToolboxPages.jsx", "utf8");
const toolboxCoverEditor = await readFile("website/src/features/toolboxes/ToolboxCoverEditor.jsx", "utf8");
const toolboxActions = await readFile("website/src/features/toolboxes/useToolboxActions.js", "utf8");
const toolboxStorage = await readFile("website/src/features/toolboxes/storage/toolboxStorage.js", "utf8");
const checklistStorage = await readFile("website/src/features/toolboxes/storage/modules/checklist.js", "utf8");
const timerStorage = await readFile("website/src/features/toolboxes/storage/modules/timer.js", "utf8");
const calendarStorage = await readFile("website/src/features/toolboxes/storage/modules/calendar.js", "utf8");
const resetUtils = await readFile("website/src/features/toolboxes/modules/resetUtils.js", "utf8");
const equipmentStorage = await readFile("website/src/features/toolboxes/storage/modules/equipmentPlanner.js", "utf8");
const storageQuota = await readFile("website/src/components/StorageQuota.jsx", "utf8");
const importButton = await readFile("website/src/components/ImportButton.jsx", "utf8");
const guidedTooltipTour = await readFile("website/src/components/GuidedTooltipTour.jsx", "utf8");
assert.match(toolboxActions, /creationTemplate/);
assert.match(toolboxActions, /type: "memos"/);
assert.match(toolboxActions, /type: "links"/);
assert.match(toolboxActions, /#\/library/);
assert.match(toolboxActions, /async function createChecklistFromResults/);
assert.match(toolboxActions, /persistToolboxSnapshot/);
assert.match(toolboxActions, /createChecklistFromList/);
assert.match(toolboxStorage, /TOOLBOX_ICON_FILES/);
assert.match(toolboxStorage, /export function normalizeToolboxIcon/);
assert.match(toolboxStorage, /export function normalizeToolboxCustomCover/);
assert.match(toolboxStorage, /customCover: normalizeToolboxCustomCover/);
assert.match(toolboxStorage, /compact\.customCover = normalized\.customCover/);
assert.match(checklistStorage, /qtyTarget/);
assert.match(checklistStorage, /qtyCurrent/);
assert.match(checklistStorage, /hideCompletedSections/);
assert.match(checklistStorage, /hideCompletedSectionsFully/);
assert.match(checklistStorage, /hideWhenComplete/);
assert.match(equipmentStorage, /collapsed/);
assert.match(toolboxStorage, /module\.scrollable/);
assert.match(timerStorage, /scrollResults/);
assert.match(calendarStorage, /normalizeCalendarData/);
assert.match(resetUtils, /export function getLatestDailyReset/);
assert.match(resetUtils, /export function getLatestWeeklyReset/);
assert.match(toolboxStorage, /nextId\("calendar"\)/);
assert.match(toolboxCard, /export function ToolboxCard/);
assert.match(toolboxCard, /export function ToolboxIconPicker/);
assert.match(toolboxCard, /export function ToolboxGameIcon/);
assert.match(toolboxCard, /toolbox-card-drag-handle/);
assert.match(toolboxCard, /reorder\.getItemProps/);
assert.match(toolboxCard, /toolbox-card-cover-link/);
assert.match(toolboxCard, /toolbox-icon-cover/);
assert.match(toolboxPages, /\.\/ToolboxCard\.jsx/);
assert.match(toolboxPages, /updateToolboxOrder/);
assert.match(toolboxPages, /useGroupedReorder/);
assert.match(toolboxPages, /useInlineEdit/);
assert.match(toolboxPages, /getContentEditableProps/);
assert.match(toolboxPages, /orientation: "horizontal"/);
assert.match(toolboxPages, /components\/StorageQuota\.jsx/);
assert.match(toolboxPages, /components\/ImportButton\.jsx/);
assert.match(toolboxPages, /import-icon-button/);
assert.match(toolboxPages, /getToolboxGame\(toolbox\)/);
assert.match(toolboxPages, /Vers la page de jeu/);
assert.match(toolboxPages, /Vers la toolbox complète/);
assert.match(toolboxPages, /#\/games\/\$\{toolboxGame\.id\}/);
assert.match(toolboxPages, /#\/toolbox\/\$\{toolbox\.id\}/);
assert.match(toolboxPages, /setDrawerGameId\(""\)/);
assert.match(toolboxPages, /moduleColumns/);
assert.match(toolboxPages, /normalizeCombosData/);
assert.match(guidedTooltipTour, /--tutorial-arrow-left/);
assert.match(guidedTooltipTour, /--tutorial-arrow-top/);
assert.match(guidedTooltipTour, /data-placement/);
assert.doesNotMatch(toolboxPages, /usePointerReorder/);
assert.match(storageQuota, /export function StorageQuota/);
assert.match(storageQuota, /role="progressbar"/);
assert.match(importButton, /export function ImportButton/);
assert.match(importButton, /accept="application\/json"/);
assert.doesNotMatch(toolboxCoverEditor, /react-easy-crop|react-image-crop/);
});
test("toolbox module registry and modules expose expected behavior", async () => {
const source = await readFile("website/src/main.jsx", "utf8");
const moduleRegistry = await readFile("website/src/features/toolboxes/modules/index.jsx", "utf8");
const moduleComponents = await readFile("website/src/features/toolboxes/modules/toolboxModuleComponents.jsx", "utf8");
const addToolControls = await readFile("website/src/features/toolboxes/modules/AddToolControls.jsx", "utf8");
const toolboxPages = await readFile("website/src/features/toolboxes/ToolboxPages.jsx", "utf8");
const toolboxModules = await readFile("website/src/features/toolboxes/modules/ToolboxModules.jsx", "utf8");
const moduleShell = await readFile("website/src/features/toolboxes/modules/ModuleShell.jsx", "utf8");
const moduleConfiguration = await readFile("website/src/features/toolboxes/modules/ModuleConfiguration.jsx", "utf8");
const moduleRegistryBundle = `${moduleRegistry}\n${moduleComponents}\n${addToolControls}\n${toolboxModules}\n${moduleShell}\n${moduleConfiguration}`;
const moduleMetadataRegistry = await readFile("website/src/features/toolboxes/toolboxModuleRegistry.js", "utf8");
const notepadModule = await readFile("website/src/features/toolboxes/modules/NotepadModule.jsx", "utf8");
const notepadToolbar = await readFile("website/src/features/toolboxes/modules/NotepadToolbar.jsx", "utf8");
const notepadUtils = await readFile("website/src/features/toolboxes/modules/notepadUtils.js", "utf8");
const combosModule = await readFile("website/src/features/toolboxes/modules/CombosModule.jsx", "utf8");
const combosEditor = await readFile("website/src/features/toolboxes/modules/CombosEditor.jsx", "utf8");
const combosParts = await readFile("website/src/features/toolboxes/modules/CombosParts.jsx", "utf8");
const combosList = await readFile("website/src/features/toolboxes/modules/CombosList.jsx", "utf8");
const combosEditorState = await readFile("website/src/features/toolboxes/modules/useCombosEditorState.js", "utf8");
const combosReorder = await readFile("website/src/features/toolboxes/modules/useCombosReorder.js", "utf8");
const combosBundle = `${combosModule}\n${combosEditor}\n${combosParts}\n${combosList}\n${combosEditorState}\n${combosReorder}`;
const combosUtils = await readFile("website/src/features/toolboxes/modules/combosUtils.js", "utf8");
const calculatorModule = await readFile("website/src/features/toolboxes/modules/CalculatorModule.jsx", "utf8");
const tableFormulaEngine = await readFile("website/src/features/toolboxes/modules/tableFormulaEngine.js", "utf8");
const timerModule = await readFile("website/src/features/toolboxes/modules/TimerModule.jsx", "utf8");
const timerCountdown = await readFile("website/src/features/toolboxes/modules/TimerCountdown.jsx", "utf8");
const timerSharedControls = await readFile("website/src/features/toolboxes/modules/TimerSharedControls.jsx", "utf8");
const timerStopwatch = await readFile("website/src/features/toolboxes/modules/TimerStopwatch.jsx", "utf8");
const timerBundle = `${timerModule}\n${timerCountdown}\n${timerSharedControls}\n${timerStopwatch}`;
const timerOptions = await readFile("website/src/features/toolboxes/modules/timerOptions.js", "utf8");
const calendarModule = await readFile("website/src/features/toolboxes/modules/CalendarModule.jsx", "utf8");
const calendarController = await readFile("website/src/features/toolboxes/modules/useCalendarController.js", "utf8");
const calendarDraftControls = await readFile("website/src/features/toolboxes/modules/useCalendarDraftControls.js", "utf8");
const calendarEventActions = await readFile("website/src/features/toolboxes/modules/calendarEventActions.js", "utf8");
const calendarLifecycle = await readFile("website/src/features/toolboxes/modules/useCalendarLifecycle.js", "utf8");
const calendarModeChoice = await readFile("website/src/features/toolboxes/modules/CalendarModeChoice.jsx", "utf8");
const calendarContent = await readFile("website/src/features/toolboxes/modules/CalendarContent.jsx", "utf8");
const calendarDraft = await readFile("website/src/features/toolboxes/modules/calendarDraft.js", "utf8");
const calendarEventForm = await readFile("website/src/features/toolboxes/modules/CalendarEventForm.jsx", "utf8");
const calendarEventControls = await readFile("website/src/features/toolboxes/modules/CalendarEventControls.jsx", "utf8");
const calendarMonthDatePicker = await readFile("website/src/features/toolboxes/modules/CalendarMonthDatePicker.jsx", "utf8");
const calendarViews = await readFile("website/src/features/toolboxes/modules/CalendarViews.jsx", "utf8");
const calendarBundle = `${calendarModule}\n${calendarController}\n${calendarDraftControls}\n${calendarEventActions}\n${calendarLifecycle}\n${calendarModeChoice}\n${calendarContent}\n${calendarDraft}\n${calendarEventForm}\n${calendarEventControls}\n${calendarMonthDatePicker}\n${calendarViews}`;
const calendarUtils = await readFile("website/src/features/toolboxes/modules/calendarUtils.js", "utf8");
const taskPlannerModule = await readFile("website/src/features/toolboxes/modules/TaskPlannerModule.jsx", "utf8");
const taskPlannerBranch = await readFile("website/src/features/toolboxes/modules/TaskPlannerBranch.jsx", "utf8");
const taskPlannerItem = await readFile("website/src/features/toolboxes/modules/TaskPlannerItem.jsx", "utf8");
const taskPlannerItemPanels = await readFile("website/src/features/toolboxes/modules/TaskPlannerItemPanels.jsx", "utf8");
const taskPlannerReorder = await readFile("website/src/features/toolboxes/modules/useTaskPlannerReorder.js", "utf8");
const taskPlannerTopbar = await readFile("website/src/features/toolboxes/modules/TaskPlannerTopbar.jsx", "utf8");
const taskPlannerSettings = await readFile("website/src/features/toolboxes/modules/TaskPlannerSettings.jsx", "utf8");
const taskPlannerBundle = `${taskPlannerModule}\n${taskPlannerBranch}\n${taskPlannerItem}\n${taskPlannerItemPanels}\n${taskPlannerReorder}\n${taskPlannerTopbar}\n${taskPlannerSettings}`;
const goalTreeModule = await readFile("website/src/features/toolboxes/modules/GoalTreeModule.jsx", "utf8");
const goalTreeEditor = await readFile("website/src/features/toolboxes/modules/GoalTreeEditor.jsx", "utf8");
const goalTreeParts = await readFile("website/src/features/toolboxes/modules/GoalTreeParts.jsx", "utf8");
const goalTreeBranch = await readFile("website/src/features/toolboxes/modules/GoalTreeBranch.jsx", "utf8");
const goalTreeEditorFields = await readFile("website/src/features/toolboxes/modules/GoalTreeEditorFields.jsx", "utf8");
const goalTreeNodeContent = await readFile("website/src/features/toolboxes/modules/GoalTreeNodeContent.jsx", "utf8");
const goalTreeController = await readFile("website/src/features/toolboxes/modules/useGoalTreeController.js", "utf8");
const goalTreeView = await readFile("website/src/features/toolboxes/modules/GoalTreeView.jsx", "utf8");
const goalTreeBundle = `${goalTreeModule}\n${goalTreeEditor}\n${goalTreeParts}\n${goalTreeBranch}\n${goalTreeEditorFields}\n${goalTreeNodeContent}\n${goalTreeController}\n${goalTreeView}`;
const goalTreeDisplay = await readFile("website/src/features/toolboxes/modules/goalTreeDisplay.js", "utf8");
const goalTreeUtils = await readFile("website/src/features/toolboxes/modules/goalTreeUtils.js", "utf8");
const equipmentPlannerModule = await readFile("website/src/features/toolboxes/modules/EquipmentPlannerModule.jsx", "utf8");
const equipmentPlannerEditors = await readFile("website/src/features/toolboxes/modules/EquipmentPlannerEditors.jsx", "utf8");
const equipmentPlannerCharacteristics = await readFile("website/src/features/toolboxes/modules/EquipmentPlannerCharacteristics.jsx", "utf8");
const equipmentPlannerMaterials = await readFile("website/src/features/toolboxes/modules/EquipmentPlannerMaterials.jsx", "utf8");
const equipmentPlannerSockets = await readFile("website/src/features/toolboxes/modules/EquipmentPlannerSockets.jsx", "utf8");
const equipmentPlannerBundle = `${equipmentPlannerEditors}\n${equipmentPlannerCharacteristics}\n${equipmentPlannerMaterials}\n${equipmentPlannerSockets}`;
const equipmentPlannerItem = await readFile("website/src/features/toolboxes/modules/EquipmentPlannerItem.jsx", "utf8");
const equipmentPlannerPickers = await readFile("website/src/features/toolboxes/modules/EquipmentPlannerPickers.jsx", "utf8");
const equipmentPlannerSummary = await readFile("website/src/features/toolboxes/modules/EquipmentPlannerSummary.jsx", "utf8");
const equipmentPlannerOptions = await readFile("website/src/features/toolboxes/modules/equipmentPlannerOptions.js", "utf8");
const checklistModule = await readFile("website/src/features/toolboxes/modules/ChecklistModule.jsx", "utf8");
const checklistParts = await readFile("website/src/features/toolboxes/modules/ChecklistParts.jsx", "utf8");
const checklistUtils = await readFile("website/src/features/toolboxes/modules/checklistUtils.js", "utf8");
const checklistBundle = `${checklistModule}\n${checklistParts}\n${checklistUtils}`;
const imagesModule = await readFile("website/src/features/toolboxes/modules/ImagesModule.jsx", "utf8");
const imageAnnotationModule = await readFile("website/src/features/toolboxes/modules/ImageAnnotationModule.jsx", "utf8");
const linksModule = await readFile("website/src/features/toolboxes/modules/LinksModule.jsx", "utf8");
const countersModule = await readFile("website/src/features/toolboxes/modules/CountersModule.jsx", "utf8");
const textImport = await readFile("website/src/features/toolboxes/modules/textImport.js", "utf8");
const textImportShared = await readFile("website/src/features/toolboxes/modules/textImport/shared.js", "utf8");
const textImportPlanning = await readFile("website/src/features/toolboxes/modules/textImport/planning.js", "utf8");
const textImportTime = await readFile("website/src/features/toolboxes/modules/textImport/time.js", "utf8");
const imageViewer = await readFile("website/src/components/ImageViewer.jsx", "utf8");
const imageGallery = await readFile("website/src/components/ImageGallery.jsx", "utf8");
const moduleEmptyMessage = await readFile("website/src/features/toolboxes/modules/ModuleEmptyMessage.jsx", "utf8");
const randomizerModule = await readFile("website/src/features/toolboxes/modules/RandomizerModule.jsx", "utf8");
const randomizerDice = await readFile("website/src/features/toolboxes/modules/randomizer/RandomizerDice.jsx", "utf8");
const randomizerWheel = await readFile("website/src/features/toolboxes/modules/randomizer/RandomizerWheel.jsx", "utf8");
const randomizerTeams = await readFile("website/src/features/toolboxes/modules/randomizer/RandomizerTeams.jsx", "utf8");
const randomizerShared = await readFile("website/src/features/toolboxes/modules/randomizer/RandomizerShared.jsx", "utf8");
const randomizerUiUtils = await readFile("website/src/features/toolboxes/modules/randomizer/randomizerUiUtils.js", "utf8");
const randomizerBundle = `${randomizerModule}\n${randomizerDice}\n${randomizerWheel}\n${randomizerTeams}\n${randomizerShared}\n${randomizerUiUtils}`;
[
["CalendarModule.jsx", calendarModule],
["modules/index.jsx", moduleRegistry],
["GoalTreeParts.jsx", goalTreeParts],
["RandomizerModule.jsx", randomizerModule]
].forEach(([fileName, fileSource]) => {
assert.ok(countLines(fileSource) <= 220, `${fileName} must remain a short orchestrator/barrel`);
});
[
["AddToolControls.jsx", addToolControls],
["ToolboxModules.jsx", toolboxModules],
["ModuleShell.jsx", moduleShell],
["useCalendarController.js", calendarController],
["useCalendarDraftControls.js", calendarDraftControls],
["calendarEventActions.js", calendarEventActions],
["useCalendarLifecycle.js", calendarLifecycle],
["CalendarModeChoice.jsx", calendarModeChoice],
["GoalTreeBranch.jsx", goalTreeBranch],
["GoalTreeEditorFields.jsx", goalTreeEditorFields],
["GoalTreeNodeContent.jsx", goalTreeNodeContent],
["TimerCountdown.jsx", timerCountdown],
["TimerSharedControls.jsx", timerSharedControls],
["TimerStopwatch.jsx", timerStopwatch],
["TaskPlannerBranch.jsx", taskPlannerBranch],
["TaskPlannerItem.jsx", taskPlannerItem]
].forEach(([fileName, fileSource]) => {
assert.ok(countLines(fileSource) <= 300, `${fileName} must stay focused; split by responsibility before growing further`);
});
assert.match(moduleMetadataRegistry, /export const TOOLBOX_MODULES/);
assert.match(moduleMetadataRegistry, /export const TOOLBOX_MODULE_DEFINITIONS/);
assert.match(moduleMetadataRegistry, /export function getDefaultModuleTitle/);
assert.match(moduleRegistry, /export \{ AddToolControls \}/);
assert.match(moduleRegistry, /export \{ ToolboxModules \}/);
assert.match(moduleComponents, /MODULE_COMPONENTS/);
assert.match(moduleShell, /Suspense fallback=\{\}/);
assert.match(moduleMetadataRegistry, /Planificateur d'équipements/);
assert.match(moduleMetadataRegistry, /Annotation d'images/);
assert.match(moduleShell, /module-edit-button/);
assert.match(moduleShell, /itemReorder\.startDrag/);
assert.match(moduleRegistryBundle, /onPointerDown/);
assert.match(toolboxPages, /useGroupedReorder/);
assert.match(toolboxModules, /reorder=\{reorder\}/);
assert.match(moduleShell, /useInlineEdit/);
assert.match(moduleShell, /getContentEditableProps/);
assert.match(addToolControls, /tool-add-card/);
assert.match(addToolControls, /tool-quick-add-button/);
assert.match(addToolControls, /tool-add-quick-toggle/);
assert.match(moduleMetadataRegistry, /scrollable: true/);
assert.match(moduleShell, /module-content/);
assert.match(moduleShell, /scrollIntoView/);
assert.match(moduleShell, /data-module-id/);
assert.match(moduleShell, /module-scroll-button/);
assert.match(moduleShell, /module-collapse-button/);
assert.match(moduleShell, /aria-expanded=\{!collapsed\}/);
assert.match(moduleShell, /!collapsed \|\| configurationOpen/);
assert.match(moduleShell, /module-configuration-button/);
assert.match(moduleShell, /aria-controls=\{configurationId\}/);
assert.match(moduleShell, /module-configuration-panel/);
assert.match(moduleShell, /hidden=\{collapsed\}/);
assert.match(moduleConfiguration, /createPortal/);
assert.doesNotMatch(moduleShell, /module-exchange-button/);
assert.match(addToolControls, /Icon name="dropdown"/);
assert.match(addToolControls, /CompactDropdown/);
assert.doesNotMatch(source, /