Skip to content

Commit d607ce6

Browse files
committed
[Toolkit] Restore the "available since" note on recipe install steps
| Q | A | -------------- | --- | Bug fix? | yes | New feature? | no | Deprecations? | no | Documentation? | no | Issues | Fix #3857 | License | MIT Reported in #3857: a Toolkit recipe that's merged but not yet tagged shows up in the documentation with nothing to say it can't be installed yet, so running `php bin/console ux:install form` fails with `The recipe "form" does not exist in any official kit` and gives no clue why. The docs used to carry a note reading `Available since UX Toolkit X.Y.`, built from the `version-added` field each recipe manifest declares. It got lost when kit documentation became generic: the per-recipe template that held the note went away, and the `::: installation` directive that replaced it never carried it over. This restores it in `_installation.md.twig`, for both the plain Markdown output and the tabbed HTML one. The `login-01` and `login-02` blocks, both added after `v3.4.0`, had no `version-added` at all, so they'd have stayed silent even with the note restored; they now declare `3.5`. Nothing was holding the note in place before, which is how it disappeared without a test turning red, so there's now a test covering both output formats plus a recipe that declares no version.
1 parent 535e965 commit d607ce6

4 files changed

Lines changed: 46 additions & 18 deletions

File tree

src/Toolkit/kits/shadcn/login-01/manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"$schema": "../../../schema-kit-recipe-v1.json",
33
"type": "block",
4+
"version-added": "3.5",
45
"name": "LoginForm",
56
"description": "A simple login form centered in a card.",
67
"copy-files": {

src/Toolkit/kits/shadcn/login-02/manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"$schema": "../../../schema-kit-recipe-v1.json",
33
"type": "block",
4+
"version-added": "3.5",
45
"name": "LoginForm",
56
"description": "A two-column login page with a cover image.",
67
"copy-files": {

src/Toolkit/templates/doc/_installation.md.twig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
{% if format == 'markdown' %}
2+
{% if recipe.manifest.versionAdded is not null %}
3+
> [!NOTE]
4+
> Available since UX Toolkit {{ recipe.manifest.versionAdded }}.
5+
6+
{% endif %}
27
```shell
38
php bin/console ux:install {{ recipe.name }} --kit {{ kit_id }}
49
```
@@ -7,6 +12,11 @@ php bin/console ux:install {{ recipe.name }} --kit {{ kit_id }}
712

813
:: tab Automatic
914

15+
{% if recipe.manifest.versionAdded is not null %}
16+
> [!NOTE]
17+
> Available since UX Toolkit {{ recipe.manifest.versionAdded }}.
18+
19+
{% endif %}
1020
```shell
1121
php bin/console ux:install {{ recipe.name }} --kit {{ kit_id }}
1222
```

src/Toolkit/tests/Doc/RecipeDocRendererTest.php

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,28 @@ public function testRenderAsMarkdownResolvesExamplesAndOmitsInteractiveDirective
4848
$this->assertStringContainsString('<twig:PostLink>', $markdown);
4949
}
5050

51+
public function testTheInstallStepsAnnounceTheVersionTheRecipeWasAddedIn(): void
52+
{
53+
[$kit, $recipe] = $this->loadPostLinkRecipe();
54+
$markdown = $this->renderer()->renderAsMarkdown($kit, $recipe);
55+
$this->assertStringContainsString('Available since UX Toolkit 3.4.', $markdown);
56+
57+
$urlGenerator = $this->previewUrlGenerator();
58+
59+
$rendered = $this->renderer()->renderAsHtml($kit, $recipe, $urlGenerator);
60+
$this->assertStringContainsString('Available since UX Toolkit 3.4.', $rendered->html);
61+
62+
// A recipe that declares no version says nothing.
63+
[$kit, $recipe] = $this->loadWidgetRecipe();
64+
$this->assertNull($recipe->manifest->versionAdded);
65+
$this->assertStringNotContainsString('Available since UX Toolkit', $this->renderer()->renderAsMarkdown($kit, $recipe));
66+
}
67+
5168
public function testRenderAsHtmlProducesTabsAndALivePreview(): void
5269
{
5370
[$kit, $recipe] = $this->loadPostLinkRecipe();
5471

55-
$urlGenerator = new class implements PreviewUrlGenerator {
56-
public function generate(string $code, CodeOptions $options): ?string
57-
{
58-
return 'https://preview.test/render';
59-
}
60-
};
72+
$urlGenerator = $this->previewUrlGenerator();
6173

6274
$rendered = $this->renderer()->renderAsHtml($kit, $recipe, $urlGenerator);
6375

@@ -126,12 +138,7 @@ public function testRenderAsHtmlIncludesStimulusControllerApiReference(): void
126138
{
127139
[$kit, $recipe] = $this->loadWidgetRecipe();
128140

129-
$urlGenerator = new class implements PreviewUrlGenerator {
130-
public function generate(string $code, CodeOptions $options): ?string
131-
{
132-
return 'https://preview.test/render';
133-
}
134-
};
141+
$urlGenerator = $this->previewUrlGenerator();
135142

136143
$html = $this->renderer()->renderAsHtml($kit, $recipe, $urlGenerator)->html;
137144

@@ -153,12 +160,7 @@ public function testHtmlInstallationStepsCarryTheFileNameInTheInfoString(): void
153160
// A doc with only the install directive: no previews/alerts, so a small environment is enough.
154161
$recipe = new Recipe($recipe->name, $recipe->absolutePath, $recipe->manifest, doc: "## Installation\n\n::: installation");
155162

156-
$urlGenerator = new class implements PreviewUrlGenerator {
157-
public function generate(string $code, CodeOptions $options): ?string
158-
{
159-
return null;
160-
}
161-
};
163+
$urlGenerator = $this->previewUrlGenerator(null);
162164

163165
// Mirror the host: a fenced-code renderer that surfaces the info string so we can assert the
164166
// filename travels with the block (as ux.symfony.com's renderer reads it back).
@@ -179,6 +181,20 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer): s
179181
$this->assertStringContainsString('&quot;filename&quot;:&quot;templates/components/PostLink.html.twig&quot;', $html);
180182
}
181183

184+
private function previewUrlGenerator(?string $url = 'https://preview.test/render'): PreviewUrlGenerator
185+
{
186+
return new class($url) implements PreviewUrlGenerator {
187+
public function __construct(private ?string $url)
188+
{
189+
}
190+
191+
public function generate(string $code, CodeOptions $options): ?string
192+
{
193+
return $this->url;
194+
}
195+
};
196+
}
197+
182198
private function renderer(): RecipeDocRenderer
183199
{
184200
return new RecipeDocRenderer(self::getContainer()->get('twig'));

0 commit comments

Comments
 (0)