From 52588ae999be02bedccef165d1971d2d00a65bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 2 Oct 2026 17:23:12 +0200 Subject: [PATCH] fix(ckeditor): prevent null value from being converted to string 'null' on validation returns --- .../ts/bo/components/CkEditorComponent.vue | 15 +++++---- resources/ts/modules/ckeditor-utils.ts | 6 ++++ resources/ts/modules/ckeditor.test.ts | 33 +++++++++++++++++++ resources/ts/modules/ckeditor.ts | 1 + resources/views/bo/faqs/form.blade.php | 2 +- resources/views/bo/pages/form.blade.php | 2 +- .../views/bo/product-incos/form.blade.php | 6 ++-- resources/views/bo/products/form.blade.php | 2 +- resources/views/bo/ranges/form.blade.php | 2 +- resources/views/bo/slides/form.blade.php | 2 +- .../Controllers/Bo/ProductsControllerTest.php | 15 +++++++++ 11 files changed, 71 insertions(+), 15 deletions(-) create mode 100644 resources/ts/modules/ckeditor-utils.ts create mode 100644 resources/ts/modules/ckeditor.test.ts diff --git a/resources/ts/bo/components/CkEditorComponent.vue b/resources/ts/bo/components/CkEditorComponent.vue index fc40660..0481dce 100644 --- a/resources/ts/bo/components/CkEditorComponent.vue +++ b/resources/ts/bo/components/CkEditorComponent.vue @@ -26,7 +26,7 @@ import { useAttrs, } from "vue"; -import { builtinPlugins, frTranslations } from "@modules/ckeditor"; +import { builtinPlugins, frTranslations, normalizeEditorText } from "@modules/ckeditor"; import { uniqueId } from "@modules/uniqueid"; import "ckeditor5/ckeditor5.css"; @@ -70,7 +70,7 @@ const props = defineProps({ // * DATA const id = ref(props.id); const name = ref(props.name); -const text = ref(props.dbtext); +const text = ref(normalizeEditorText(props.dbtext)); const required = ref(props.required); /** Counter parameters. */ @@ -102,8 +102,8 @@ onMounted(() => { data = JSON.parse(json); if (!isUsedWithProps.value) { if (data.id) id.value = String(data.id); - name.value = String(data.name); - text.value = String(data.text); + if (data.name) name.value = String(data.name); + text.value = normalizeEditorText(data.text); if (data.required) required.value = Boolean(data.required); void makeSuperBuild(); } @@ -320,13 +320,14 @@ function onExternalSetData(event: Event): void { return; } - text.value = payload; + const cleanText = normalizeEditorText(payload); + text.value = cleanText; if (hiddenInput.value) { - hiddenInput.value.value = payload; + hiddenInput.value.value = cleanText; } if (editorInstance.value) { - editorInstance.value.setData(payload); + editorInstance.value.setData(cleanText); } } diff --git a/resources/ts/modules/ckeditor-utils.ts b/resources/ts/modules/ckeditor-utils.ts new file mode 100644 index 0000000..7c7eca7 --- /dev/null +++ b/resources/ts/modules/ckeditor-utils.ts @@ -0,0 +1,6 @@ +export function normalizeEditorText(value: unknown): string { + if (value === null || value === undefined || value === "null" || value === "undefined") { + return ""; + } + return String(value); +} diff --git a/resources/ts/modules/ckeditor.test.ts b/resources/ts/modules/ckeditor.test.ts new file mode 100644 index 0000000..73d1d21 --- /dev/null +++ b/resources/ts/modules/ckeditor.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from "vitest"; + +import { normalizeEditorText } from "./ckeditor-utils"; + +describe("normalizeEditorText", () => { + it("converts null and undefined to empty string", () => { + // GIVEN / WHEN / THEN + expect(normalizeEditorText(null)).toBe(""); + expect(normalizeEditorText(undefined)).toBe(""); + }); + + it("converts string 'null' and 'undefined' to empty string", () => { + // GIVEN / WHEN / THEN + expect(normalizeEditorText("null")).toBe(""); + expect(normalizeEditorText("undefined")).toBe(""); + }); + + it("preserves valid empty string", () => { + // GIVEN / WHEN / THEN + expect(normalizeEditorText("")).toBe(""); + }); + + it("preserves HTML and text content", () => { + // GIVEN / WHEN / THEN + expect(normalizeEditorText("

Hello world

")).toBe("

Hello world

"); + expect(normalizeEditorText("Some regular text")).toBe("Some regular text"); + }); + + it("converts numbers to string", () => { + // GIVEN / WHEN / THEN + expect(normalizeEditorText(123)).toBe("123"); + }); +}); diff --git a/resources/ts/modules/ckeditor.ts b/resources/ts/modules/ckeditor.ts index deb4ad5..c728eba 100644 --- a/resources/ts/modules/ckeditor.ts +++ b/resources/ts/modules/ckeditor.ts @@ -166,4 +166,5 @@ const builtinPlugins: Array = export { ClassicEditor } from "ckeditor5"; export { builtinPlugins, frTranslations }; +export { normalizeEditorText } from "./ckeditor-utils"; diff --git a/resources/views/bo/faqs/form.blade.php b/resources/views/bo/faqs/form.blade.php index 1bf2879..14c8e4d 100644 --- a/resources/views/bo/faqs/form.blade.php +++ b/resources/views/bo/faqs/form.blade.php @@ -54,7 +54,7 @@ 'id' => 'answer-text', 'csrf' => csrf_token(), 'name' => 'answer', - 'text' => old('answer', $faq->answer ?? ''), + 'text' => old('answer', $faq->answer ?? '') ?? '', ]; @endphp
diff --git a/resources/views/bo/pages/form.blade.php b/resources/views/bo/pages/form.blade.php index 2fa025f..0fa8ccf 100644 --- a/resources/views/bo/pages/form.blade.php +++ b/resources/views/bo/pages/form.blade.php @@ -77,7 +77,7 @@ 'id' => 'page-content', 'csrf' => csrf_token(), 'name' => 'content', - 'text' => old('content', $page->content ?? ''), + 'text' => old('content', $page->content ?? '') ?? '', ]; @endphp
diff --git a/resources/views/bo/product-incos/form.blade.php b/resources/views/bo/product-incos/form.blade.php index 6eb49d6..3f40c8b 100644 --- a/resources/views/bo/product-incos/form.blade.php +++ b/resources/views/bo/product-incos/form.blade.php @@ -66,7 +66,7 @@ 'id' => 'inco-ingredients', 'csrf' => csrf_token(), 'name' => 'ingredients', - 'text' => old('ingredients', $inco->ingredients ?? ''), + 'text' => old('ingredients', $inco->ingredients ?? '') ?? '', ]; @endphp
@@ -81,7 +81,7 @@ 'id' => 'inco-preparation', 'csrf' => csrf_token(), 'name' => 'preparation', - 'text' => old('preparation', $inco->preparation ?? ''), + 'text' => old('preparation', $inco->preparation ?? '') ?? '', ]; @endphp
@@ -96,7 +96,7 @@ 'id' => 'inco-selling-argument', 'csrf' => csrf_token(), 'name' => 'selling_argument', - 'text' => old('selling_argument', $inco->selling_argument ?? ''), + 'text' => old('selling_argument', $inco->selling_argument ?? '') ?? '', ]; @endphp
diff --git a/resources/views/bo/products/form.blade.php b/resources/views/bo/products/form.blade.php index a991fee..75910e6 100644 --- a/resources/views/bo/products/form.blade.php +++ b/resources/views/bo/products/form.blade.php @@ -109,7 +109,7 @@ 'id' => 'product-description', 'csrf' => csrf_token(), 'name' => 'description', - 'text' => old('description', $product->description ?? ''), + 'text' => old('description', $product->description ?? '') ?? '', ]; @endphp
diff --git a/resources/views/bo/ranges/form.blade.php b/resources/views/bo/ranges/form.blade.php index 1f73710..10b3878 100644 --- a/resources/views/bo/ranges/form.blade.php +++ b/resources/views/bo/ranges/form.blade.php @@ -48,7 +48,7 @@ 'id' => 'range-description', 'csrf' => csrf_token(), 'name' => 'description', - 'text' => old('description', $range->description ?? ''), + 'text' => old('description', $range->description ?? '') ?? '', ]; @endphp
diff --git a/resources/views/bo/slides/form.blade.php b/resources/views/bo/slides/form.blade.php index 7aa1bda..e431973 100644 --- a/resources/views/bo/slides/form.blade.php +++ b/resources/views/bo/slides/form.blade.php @@ -141,7 +141,7 @@ 'id' => 'slide-text', 'csrf' => csrf_token(), 'name' => 'text', - 'text' => old('text', $slide->text ?? ''), + 'text' => old('text', $slide->text ?? '') ?? '', ]; @endphp
diff --git a/tests/Feature/App/Http/Controllers/Bo/ProductsControllerTest.php b/tests/Feature/App/Http/Controllers/Bo/ProductsControllerTest.php index 63e0d2b..6ceb253 100644 --- a/tests/Feature/App/Http/Controllers/Bo/ProductsControllerTest.php +++ b/tests/Feature/App/Http/Controllers/Bo/ProductsControllerTest.php @@ -41,6 +41,21 @@ describe('ProductsController', function () { expect($response->status())->toBe(200); }); + it('renders empty string instead of null for ckeditor text when old input is null', function () { + // GIVEN + /** @var \Tests\TestCase $this */ + session()->put('_old_input', ['description' => null]); + + // WHEN + $response = $this->get('/bo/products/create'); + + // THEN + $response->assertOk(); + $content = $response->getContent(); + expect($content)->not->toContain('"text":null') + ->and($content)->toContain('"text":""'); + }); + it('can store a product', function () { // GIVEN $range = new Range();