diff --git a/docs/superpowers/plans/2026-10-05-version-leftovers.md b/docs/superpowers/plans/2026-10-05-version-leftovers.md new file mode 100644 index 0000000..45ecd0d --- /dev/null +++ b/docs/superpowers/plans/2026-10-05-version-leftovers.md @@ -0,0 +1,244 @@ +# Version PrestaShop : restes de la relecture : plan d'implémentation + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:subagent-driven-development (recommended) or superpowers-extended-cc:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal :** une seule règle « majeure → namespace », sans test mort dans `Translations`, avec un montage cohérent dans le test des tags visuels. + +**Architecture :** `Version::namespaceFromMajor()` (statique) est utilisée par `getMajorVersion()` et `ImportPage`. Le reste n'est que du nettoyage, sans changement de comportement. + +**Tech Stack :** PHP ^8.1, PHPUnit 10.5. + +**Spec :** `docs/superpowers/specs/2026-10-05-version-leftovers-design.md` + +--- + +## Contexte pour l'implémenteur + +- **Dépôt :** worktree `/Users/jonathan/Documents/GitHub/PrestaFlow/php-library-visual`, branche `chore/version-namespace-helper`. Les commandes se lancent depuis ce dossier. +- **Ne pas toucher** `/Users/jonathan/Documents/GitHub/PrestaFlow/php-library`. +- **PHP :** + - `php` = 8.4 ; + - `"$HOME/Library/Application Support/Herd/bin/php81"` = 8.1. +- **Tests :** + - suite complète : `php vendor/bin/phpunit` (542 tests verts au départ) ; + - redirige TOUJOURS la sortie vers un fichier, par exemple `/private/tmp/claude-501/-Users-jonathan-Documents-GitHub-PrestaFlow-app/2393cd17-28d4-44c5-96f2-fa24f981a8c8/scratchpad/.txt 2>&1`, puis lance `tail -3`. Un processus Chrome enfant bloque les pipes. +- **Commits :** + - chemins explicites, jamais `git add -A`, `git add .` ni `git commit -a` ; + - messages en français, terminés par une ligne vide puis `Co-Authored-By: Claude Opus 5.5 `. +- **Lecture des fichiers :** `rtk proxy cat` ou `sed -n`. Garde les commentaires non visés. + +--- + +### Task 1 : `Version::namespaceFromMajor()` et `Translations` + +**Goal :** la règle « majeure → namespace » est écrite une seule fois, et le test mort de `getCatalog` disparaît. + +**Files :** +- Modify : `src/Traits/Version.php` (`getMajorVersion()` vers la l.135 ; nouvelle méthode juste avant) +- Modify : `src/Traits/ImportPage.php` (`pageNamespaceVersion()` vers la l.75) +- Modify : `src/Resolvers/Translations.php` (vers la l.80) +- Modify : `tests/Unit/Pages/PageVersionIsolationTest.php` (un test ajouté) + +**Acceptance Criteria :** +- [ ] `namespaceFromMajor('1.7')` vaut `'7'`, `('1.6')` vaut `'6'`, `('8')` vaut `'8'`, `('9')` vaut `'9'`. +- [ ] `getMajorVersion(namespace: true)` et `pageNamespaceVersion()` l'utilisent. Plus aucun `substr(…, strlen('1.'))` ni `substr($major, 2)` pour cette règle. +- [ ] `getCatalog()` n'a plus `if ($this->getMajorVersion() !== null)`. +- [ ] Suite complète verte en 8.4 et en 8.1. + +**Verify :** `php vendor/bin/phpunit tests/Unit/Pages/PageVersionIsolationTest.php` → OK + +**Steps :** + +- [ ] **Step 1 : test** + +Ajoute dans `tests/Unit/Pages/PageVersionIsolationTest.php` : + +```php + public function test_namespace_from_major_drops_the_leading_1(): void + { + // Un appel statique direct sur un trait est déprécié : on passe par une classe qui l'utilise. + $parser = new class { + use Version; + public array $globals = []; + }; + + $this->assertSame('7', $parser::namespaceFromMajor('1.7')); + $this->assertSame('6', $parser::namespaceFromMajor('1.6')); + $this->assertSame('8', $parser::namespaceFromMajor('8')); + $this->assertSame('9', $parser::namespaceFromMajor('9')); + } +``` + +Lance `php vendor/bin/phpunit tests/Unit/Pages/PageVersionIsolationTest.php` (sortie vers un fichier) → FAIL, méthode inexistante. + +- [ ] **Step 2 : implémentation** + +Dans `src/Traits/Version.php`, juste avant le docblock de `getMajorVersion()`, ajoute : + +```php + /** Segment de namespace des pages pour une majeure : '1.7' → '7', '1.6' → '6', '9' → '9'. */ + public static function namespaceFromMajor(string $majorVersion): string + { + return str_starts_with($majorVersion, '1.') ? substr($majorVersion, strlen('1.')) : $majorVersion; + } +``` + +Dans `getMajorVersion()`, remplace la fin : + +```php + $majorVersion = $this->versions['majorVersion']; + + if ($namespace && str_starts_with($majorVersion, '1.')) { + return substr($majorVersion, strlen('1.')); + } + + return $majorVersion; +``` + +par : + +```php + $majorVersion = $this->versions['majorVersion']; + + return $namespace ? self::namespaceFromMajor($majorVersion) : $majorVersion; +``` + +Dans `src/Traits/ImportPage.php`, `pageNamespaceVersion()`, remplace : + +```php + $major = self::parseVersions($patchVersion)['majorVersion']; + + return str_starts_with($major, '1.') ? substr($major, 2) : $major; +``` + +par : + +```php + return self::namespaceFromMajor(self::parseVersions($patchVersion)['majorVersion']); +``` + +Dans `src/Resolvers/Translations.php`, `getCatalog()`, remplace : + +```php + if ($this->getMajorVersion() !== null) { + $pathToCatalog = $basePath.$this->getMajorVersion().'/'.$fileName; + if (file_exists($pathToCatalog)) { + $majorCatalog = json_decode(file_get_contents($pathToCatalog), true); + if (!is_array($majorCatalog)) { + $majorCatalog = []; + } + } + } +``` + +par le même bloc sans la condition englobante, dé-indenté d'un niveau : + +```php + $pathToCatalog = $basePath.$this->getMajorVersion().'/'.$fileName; + if (file_exists($pathToCatalog)) { + $majorCatalog = json_decode(file_get_contents($pathToCatalog), true); + if (!is_array($majorCatalog)) { + $majorCatalog = []; + } + } +``` + +Les blocs mineure et patch qui suivent ne changent pas. + +- [ ] **Step 3 : vérifier** + +```bash +php vendor/bin/phpunit tests/Unit/Pages/PageVersionIsolationTest.php +grep -n "substr(\$major, 2)\|strlen('1.')" src/Traits/ImportPage.php src/Traits/Version.php +php vendor/bin/phpunit +"$HOME/Library/Application Support/Herd/bin/php81" vendor/bin/phpunit +``` + +Expected : +- `PageVersionIsolationTest` : OK ; +- le `grep` ne renvoie que la ligne de `namespaceFromMajor` ; +- la suite complète est verte (543) en 8.4 et en 8.1. + +- [ ] **Step 4 : commit** + +```bash +git add src/Traits/Version.php src/Traits/ImportPage.php src/Resolvers/Translations.php tests/Unit/Pages/PageVersionIsolationTest.php +git commit -m "refactor(version): namespaceFromMajor, seule règle majeure → namespace ; test mort retiré de getCatalog + +Co-Authored-By: Claude Opus 5.5 " +``` + +--- + +### Task 2 : montage cohérent du test des tags visuels + +**Goal :** `makePage()` de `VisualCheckpointTagTest` construit la page avec un `patchVersion`, au lieu de forcer une majeure. + +**Files :** +- Modify : `tests/Unit/Visual/VisualCheckpointTagTest.php` + +**Acceptance Criteria :** +- [ ] `makePage(array $globals = [], string $patchVersion = '9.0.0')` et plus aucun `setMajorVersion` dans `makePage`. +- [ ] Les assertions de tag sont inchangées (`auto-v9-…`, `auto-v1.7-…`, `auto-v8-…`) et vertes. + +**Verify :** `php vendor/bin/phpunit tests/Unit/Visual/VisualCheckpointTagTest.php` → OK + +**Steps :** + +- [ ] **Step 1 : `makePage`** + +- Signature : `private function makePage(array $globals = [], ?string $major = '9'): CommonPage` devient `private function makePage(array $globals = [], string $patchVersion = '9.0.0'): CommonPage`. +- La classe anonyme est construite avec `('en', $patchVersion, $globals)` au lieu de `('en', '8.1.0', $globals)`. +- Supprime ce bloc : + +```php + if ($major !== null) { + $page->setMajorVersion($major); + } +``` + +- [ ] **Step 2 : appels** + +- `testPs17MajorVersionIsNotRenderedAsQuestionMark` : `$this->makePage(['PS_VERSION' => '1.7.8.11'], '1.7')` devient `$this->makePage([], '1.7.8.11')`. +- `testMajorVersionComesFromThisPageOnly` : remplace les trois lignes + +```php + $page = $this->makePage([], null); + $other = $this->makePage([], null); + $other->setMajorVersion('1.7'); +``` + +par : + +```php + $page = $this->makePage([], '8.1.0'); + $other = $this->makePage([], '1.7.8.11'); +``` + + et adapte le commentaire au-dessus : « Deux pages de versions différentes : le tag de l'une ne dépend pas de l'autre. » +- Les autres appels `makePage()` sans argument restent tels quels (défaut `'9.0.0'` → `auto-v9-…`). + +- [ ] **Step 3 : vérifier** + +```bash +php vendor/bin/phpunit tests/Unit/Visual/VisualCheckpointTagTest.php +php vendor/bin/phpunit +``` + +Expected : OK, puis la suite complète verte. + +- [ ] **Step 4 : commit** + +```bash +git add tests/Unit/Visual/VisualCheckpointTagTest.php +git commit -m "test(visual): makePage construit la page avec son patch au lieu de forcer la majeure + +Co-Authored-By: Claude Opus 5.5 " +``` + +--- + +### Task 3 : livraison (coordinateur) + +- [ ] Relecture finale, PR → `dev`, CI verte. +- [ ] Sur demande : merge, release → `main`, puis mise à jour de la lib dans l'app. diff --git a/docs/superpowers/plans/2026-10-05-version-leftovers.md.tasks.json b/docs/superpowers/plans/2026-10-05-version-leftovers.md.tasks.json new file mode 100644 index 0000000..f2c6693 --- /dev/null +++ b/docs/superpowers/plans/2026-10-05-version-leftovers.md.tasks.json @@ -0,0 +1,30 @@ +{ + "planPath": "docs/superpowers/plans/2026-10-05-version-leftovers.md", + "tasks": [ + { + "id": 1, + "subject": "Task 1: namespaceFromMajor et Translations", + "status": "completed", + "description": "Voir le plan.\n\n```json:metadata\n{\"files\": [], \"verifyCommand\": \"\", \"acceptanceCriteria\": []}\n```" + }, + { + "id": 2, + "subject": "Task 2: montage du test des tags", + "status": "completed", + "description": "Voir le plan.\n\n```json:metadata\n{\"files\": [], \"verifyCommand\": \"\", \"acceptanceCriteria\": []}\n```", + "blockedBy": [ + 1 + ] + }, + { + "id": 3, + "subject": "Task 3: livraison", + "status": "completed", + "description": "Voir le plan.\n\n```json:metadata\n{\"files\": [], \"verifyCommand\": \"\", \"acceptanceCriteria\": []}\n```", + "blockedBy": [ + 2 + ] + } + ], + "lastUpdated": "2026-10-05T15:33:59+02:00" +} \ No newline at end of file diff --git a/docs/superpowers/specs/2026-10-05-version-leftovers-design.md b/docs/superpowers/specs/2026-10-05-version-leftovers-design.md new file mode 100644 index 0000000..8380b7c --- /dev/null +++ b/docs/superpowers/specs/2026-10-05-version-leftovers-design.md @@ -0,0 +1,60 @@ +# Version PrestaShop : restes de la relecture : design + +Date : 2026-10-05 +Statut : implémenté (branche chore/version-namespace-helper) + +## Contexte + +La relecture finale de PrestaFlow/php-library#112 (version par instance) a +laissé trois points mineurs. Aucun ne change de comportement. + +## 1. Une seule règle « majeure → namespace » + +Aujourd'hui, la même règle existe deux fois : +- dans `Version::getMajorVersion(namespace: true)` ; +- dans `ImportPage::pageNamespaceVersion()`. + +La règle : `'1.7'` donne `'7'`, `'1.6'` donne `'6'`, `'8'` et `'9'` restent +inchangés. + +Changement : +- nouvelle méthode statique `Version::namespaceFromMajor(string $major): string`, + qui applique cette règle ; +- `getMajorVersion(namespace: true)` et `pageNamespaceVersion()` l'appellent ; +- aucune autre logique ne change. + +## 2. `Translations::getCatalog` : test mort + +`if ($this->getMajorVersion() !== null)` est toujours vrai : +`getMajorVersion()` renvoie une chaîne ou lève une exception. + +Changement : retirer la condition. Le chargement du catalogue de la majeure +reste identique. + +## 3. `VisualCheckpointTagTest` : montage cohérent + +Aujourd'hui, `makePage(array $globals = [], ?string $major = '9')` construit +une page en `8.1.0`, puis force une autre majeure avec `setMajorVersion()`. Le +patch et la majeure de la page ne concordent donc pas. + +Changement : +- `makePage(array $globals = [], string $patchVersion = '9.0.0')` construit la + page avec ce patch ; +- les appels s'adaptent : + - le cas 1.7 passe `'1.7.8.11'` ; + - `testMajorVersionComesFromThisPageOnly` construit sa page en `'8.1.0'` et + l'autre page en `'1.7.8.11'`, au lieu de forcer la majeure. +- Les assertions restent les mêmes : `auto-v9-…`, `auto-v1.7-…`, `auto-v8-…`. + +## Tests + +- `namespaceFromMajor` : `'1.7'` donne `'7'`, `'1.6'` donne `'6'`, `'8'` donne + `'8'`, `'9'` donne `'9'`. +- Les tests existants de `getMajorVersion(namespace: true)`, de `importPage` et + du tag visuel restent verts. +- Suite complète verte sous PHP 8.1 et 8.4. + +## Livraison + +PR → `dev`, puis release → `main`, puis mise à jour de la lib dans l'app, sur +demande. diff --git a/src/Resolvers/Translations.php b/src/Resolvers/Translations.php index 1692ab5..8b85c95 100644 --- a/src/Resolvers/Translations.php +++ b/src/Resolvers/Translations.php @@ -77,13 +77,11 @@ public function getCatalog() } } - if ($this->getMajorVersion() !== null) { - $pathToCatalog = $basePath.$this->getMajorVersion().'/'.$fileName; - if (file_exists($pathToCatalog)) { - $majorCatalog = json_decode(file_get_contents($pathToCatalog), true); - if (!is_array($majorCatalog)) { - $majorCatalog = []; - } + $pathToCatalog = $basePath.$this->getMajorVersion().'/'.$fileName; + if (file_exists($pathToCatalog)) { + $majorCatalog = json_decode(file_get_contents($pathToCatalog), true); + if (!is_array($majorCatalog)) { + $majorCatalog = []; } } diff --git a/src/Traits/ImportPage.php b/src/Traits/ImportPage.php index 72a0f4f..cefbf3e 100644 --- a/src/Traits/ImportPage.php +++ b/src/Traits/ImportPage.php @@ -78,8 +78,6 @@ private function pageNamespaceVersion(string $patchVersion): string return (string) $this->getMajorVersion(namespace: true); } - $major = self::parseVersions($patchVersion)['majorVersion']; - - return str_starts_with($major, '1.') ? substr($major, 2) : $major; + return self::namespaceFromMajor(self::parseVersions($patchVersion)['majorVersion']); } } diff --git a/src/Traits/Version.php b/src/Traits/Version.php index 736c1cd..a352f4e 100644 --- a/src/Traits/Version.php +++ b/src/Traits/Version.php @@ -127,6 +127,12 @@ public function setMajorVersion(string $majorVersion) $this->versions['majorVersion'] = $majorVersion; } + /** Segment de namespace des pages pour une majeure : '1.7' → '7', '1.6' → '6', '9' → '9'. */ + public static function namespaceFromMajor(string $majorVersion): string + { + return str_starts_with($majorVersion, '1.') ? substr($majorVersion, strlen('1.')) : $majorVersion; + } + /** * Majeure de cet objet ('1.7', '8', '9'). Si elle n'a jamais été posée, * elle se déduit du PS_VERSION des globals ; sans lui, erreur explicite @@ -148,11 +154,7 @@ public function getMajorVersion(bool $namespace = false) $majorVersion = $this->versions['majorVersion']; - if ($namespace && str_starts_with($majorVersion, '1.')) { - return substr($majorVersion, strlen('1.')); - } - - return $majorVersion; + return $namespace ? self::namespaceFromMajor($majorVersion) : $majorVersion; } public function exctractVersions(string $patchVersion) diff --git a/tests/Unit/Pages/PageVersionIsolationTest.php b/tests/Unit/Pages/PageVersionIsolationTest.php index bb0f5fa..ae5c174 100644 --- a/tests/Unit/Pages/PageVersionIsolationTest.php +++ b/tests/Unit/Pages/PageVersionIsolationTest.php @@ -79,4 +79,18 @@ public function test_parse_versions_splits_a_patch_version(): void $parser::parseVersions('9.2.0') ); } + + public function test_namespace_from_major_drops_the_leading_1(): void + { + // Un appel statique direct sur un trait est déprécié : on passe par une classe qui l'utilise. + $parser = new class { + use Version; + public array $globals = []; + }; + + $this->assertSame('7', $parser::namespaceFromMajor('1.7')); + $this->assertSame('6', $parser::namespaceFromMajor('1.6')); + $this->assertSame('8', $parser::namespaceFromMajor('8')); + $this->assertSame('9', $parser::namespaceFromMajor('9')); + } } diff --git a/tests/Unit/Visual/VisualCheckpointTagTest.php b/tests/Unit/Visual/VisualCheckpointTagTest.php index 1e5a80d..e736245 100644 --- a/tests/Unit/Visual/VisualCheckpointTagTest.php +++ b/tests/Unit/Visual/VisualCheckpointTagTest.php @@ -38,9 +38,9 @@ private function png(string $path, int $w = 40, int $h = 40): void imagedestroy($img); } - private function makePage(array $globals = [], ?string $major = '9'): CommonPage + private function makePage(array $globals = [], string $patchVersion = '9.0.0'): CommonPage { - $page = new class ('en', '8.1.0', $globals) extends CommonPage { + $page = new class ('en', $patchVersion, $globals) extends CommonPage { public function getPage() { return new class { @@ -75,9 +75,6 @@ public function getReturnValue() } }; - if ($major !== null) { - $page->setMajorVersion($major); - } $page->setLocale('fr'); return $page; @@ -115,7 +112,7 @@ public function testExplicitTagIsPreservedVerbatimInResultAndPath(): void public function testPs17MajorVersionIsNotRenderedAsQuestionMark(): void { // Régression : getMajorVersion() vaut '1.7' en 1.7.x → rejeté par ctype_digit → « v? ». - $page = $this->makePage(['PS_VERSION' => '1.7.8.11'], '1.7'); + $page = $this->makePage([], '1.7.8.11'); $page->visualCheckpoint('scene.header'); @@ -124,11 +121,9 @@ public function testPs17MajorVersionIsNotRenderedAsQuestionMark(): void public function testMajorVersionComesFromThisPageOnly(): void { - // makePage() construit la page avec patchVersion '8.1.0'. Une autre - // page passée en 1.7 ensuite ne doit pas changer son tag. - $page = $this->makePage([], null); - $other = $this->makePage([], null); - $other->setMajorVersion('1.7'); + // Deux pages de versions différentes : le tag de l'une ne dépend pas de l'autre. + $page = $this->makePage([], '8.1.0'); + $other = $this->makePage([], '1.7.8.11'); $page->visualCheckpoint('home');