dolibarr/test/phpunit/ModuleBuilderLibTest.php

80 lines
2.7 KiB
PHP
Raw Permalink Normal View History

NEW: ModuleBuilder - selectable object tabs (+ fixes for multi-object generation) (#38570) * NEW : add ModuleBuilder object-tabs map and filter helpers Add getModuleBuilderObjectTabs() (source-of-truth map of optional object tabs) and filterEnabledTabs() (sanitizes user-requested tab keys against the map). Covered by ModuleBuilderLibTest. Next: add BEGIN/END markers around tab flags and blocks in template lib (Task 2) * NEW : wrap object tab flags and blocks with MODULEBUILDER markers Surround each optional tab flag declaration (TABFLAG) and each prepareHead tab block (TAB) with BEGIN/END MODULEBUILDER comment markers, so the generator can purge a whole tab atomically while keeping $h and $head intact. Card tab is not wrapped (always generated). Next: add tab selection checkboxes to the newobject form in modulebuilder/index.php (Task 3) * NEW : add object tab selection checkboxes to newobject form Render one checkbox per optional tab (contact, note, document, agenda) in the ModuleBuilder newobject form, all checked by default and reflecting posted state on redisplay. Next: handle enabledtab in initobject — exclude files, purge blocks, hardcode flags (Task 4) * NEW : apply object tab selection during object generation Excludes page files of unselected tabs, purges their prepareHead flag and block via MODULEBUILDER markers, and hardcodes the show flag to 1 for selected tabs. Emits a best-effort warning when regenerating an existing object. Next: add language keys EnabledTabsForObject(Help) + WarningTabSelectionOnRegeneration (Task 5) * NEW : add language keys for object tab selection Next: add ChangeLog entry (Task 6) * DOC : changelog entry for ModuleBuilder selectable object tabs * FIX : purge agenda card widget when agenda tab is excluded Audit finding: myobject_card.php hardcodes a 'SeeAll' link to myobject_agenda.php in an event widget. When the agenda tab is excluded the page is not generated, leaving a latent dead link if the widget is enabled. Wrap the widget with MODULEBUILDER TAB AGENDA markers and purge it from the generated card when the agenda tab is not selected. Next: functional verification (step 5 of dev workflow) * FIX : check file operation results when applying object tab selection PR review: align with the nogeneratelines pattern by checking the return of removePatternFromFile/dolReplaceInFile and raising $error + dol_syslog with the failing tab/file context, instead of ignoring silent write failures during object generation. Add PR reference to the changelog entry. * FIX : do not abort object generation when nogeneratelines targets a missing file The nogeneratelines handler purges the MODULEBUILDER LINES block from class, API and card files. When API generation is disabled the api_<module>.class.php file does not exist, so removePatternFromFile returns false and $error was incremented, which skipped the whole success path (name substitution, rebuildObjectClass, $tabobj assignment). Generated classes kept the literal 'class MyObject', so dolGetListOfObjectClasses listed every object as MyObject. Guard each purge with file_exists so a missing optional file is skipped. * FIX : keep module descriptor out of the blanket name substitution The 'substitute all module php files' pass applied the object/module name map to every .php file including the module descriptor. That rewrote the persistent /* ... MODULEBUILDER TOPMENU/LEFTMENU MYOBJECT */ marker placeholders to the first object name, so generating a second object could no longer find them (checkExistComment returned -1, 'comments not found for section Menus' warning) and its menu entries were not inserted. Skip the descriptor in that loop: its module name is already resolved by initmodule and its object entries are added by the dedicated menu/permission blocks. * FIX : preserve MODULEBUILDER markers when substituting the module descriptor The previous commit only skipped the descriptor in the secondary substitution pass, but the primary pass (over $filetogenerate, which includes the descriptor) still rewrote its persistent /* ... MODULEBUILDER TOPMENU/LEFTMENU MYOBJECT */ markers to the first object name. Those markers cannot simply be excluded from substitution because the descriptor also has functional MYOBJECT/MYMODULE tokens (e.g. the MYMODULE_MYOBJECT_ADDON numbering constant) that must be resolved. Add dolReplaceInFilePreservingModuleBuilderMarkers(): it hides every MODULEBUILDER marker behind a sentinel, applies the substitution, then restores the markers verbatim. Use it for the descriptor in the primary pass; the descriptor stays excluded from the secondary pass. Generating a second object now finds the markers (checkExistComment) and inserts its menu entries without warning. Verified by CLI: markers preserved across two successive objects, while content (rights, exports, addon constant) is correctly substituted. * FIX : silence phan on the new ModuleBuilder test and typed closure CI phan (diff-only) flagged the freshly added test file: PhanUndeclaredExtendedClass (\CommonClassTest) and PhanUndeclaredMethod (assertSame), like the sibling NamingContractTest. Add the same @phan-file-suppress block (plus PhanTypeMismatchArgumentProbablyReal for the non-array guard test case). Also type the preg_replace_callback parameter (array $matches) to clear PhanPluginUnknownClosureParamType. phpstan is already green; no phpstan annotations added. * FIX : drop closure in marker-preserving substitution to satisfy phan phan's PhanPluginUnknownArrayClosureParamType wanted key/value types on the preg_replace_callback closure parameter. Replace the callback with preg_match_all plus a str_replace loop over the unique markers: same behaviour, no closure, no plugin warning. --------- Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-06-09 17:04:00 +00:00
<?php
/* Copyright (C) 2026 ATM Consulting <contact@atm-consulting.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* \file test/phpunit/ModuleBuilderLibTest.php
* \ingroup test
* \brief PHPUnit test for modulebuilder.lib.php tab selection helpers
* \remarks To run this script as CLI: phpunit filename.php
*/
global $conf,$user,$langs,$db;
require_once dirname(__FILE__).'/../../htdocs/master.inc.php';
require_once dirname(__FILE__).'/../../htdocs/core/lib/modulebuilder.lib.php';
require_once dirname(__FILE__).'/CommonClassTest.class.php';
/**
* Class for PHPUnit tests
*
* @backupGlobals disabled
* @backupStaticAttributes enabled
* @remarks backupGlobals must be disabled to have db,conf,user and lang not erased.
* @phan-file-suppress PhanUndeclaredClass
* @phan-file-suppress PhanUndeclaredExtendedClass
* @phan-file-suppress PhanUndeclaredMethod
* @phan-file-suppress PhanTypeMismatchArgumentProbablyReal
*/
class ModuleBuilderLibTest extends CommonClassTest
{
/**
* testGetModuleBuilderObjectTabs
*
* @return void
*/
public function testGetModuleBuilderObjectTabs()
{
$map = getModuleBuilderObjectTabs();
$this->assertSame(array('contact', 'note', 'document', 'agenda'), array_keys($map));
$this->assertSame('myobject_contact.php', $map['contact']['file']);
$this->assertSame('showtabofpageagenda', $map['agenda']['var']);
$this->assertSame('DOCUMENT', $map['document']['marker']);
}
/**
* testFilterEnabledTabs
*
* @return void
*/
public function testFilterEnabledTabs()
{
$map = getModuleBuilderObjectTabs();
// Nominal: returns requested keys in map order
$this->assertSame(array('contact', 'agenda'), filterEnabledTabs(array('agenda', 'contact'), $map));
// Unknown key is rejected
$this->assertSame(array('contact'), filterEnabledTabs(array('contact', 'evil'), $map));
// Empty / non-array returns empty
$this->assertSame(array(), filterEnabledTabs(array(), $map));
$this->assertSame(array(), filterEnabledTabs('', $map));
// Duplicates collapsed
$this->assertSame(array('note'), filterEnabledTabs(array('note', 'note'), $map));
}
}