fix(ckeditor): prevent null value from being converted to string 'null' on validation returns

This commit is contained in:
Jérémy Munsch 2026-10-02 17:23:12 +02:00
parent f84a1a7b8c
commit 52588ae999
No known key found for this signature in database
GPG key ID: 2AAECDA6B7330F91
11 changed files with 71 additions and 15 deletions

View file

@ -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<string>(props.id);
const name = ref<string>(props.name);
const text = ref<string>(props.dbtext);
const text = ref<string>(normalizeEditorText(props.dbtext));
const required = ref<boolean>(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);
}
}
</script>

View file

@ -0,0 +1,6 @@
export function normalizeEditorText(value: unknown): string {
if (value === null || value === undefined || value === "null" || value === "undefined") {
return "";
}
return String(value);
}

View file

@ -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("<p>Hello world</p>")).toBe("<p>Hello world</p>");
expect(normalizeEditorText("Some regular text")).toBe("Some regular text");
});
it("converts numbers to string", () => {
// GIVEN / WHEN / THEN
expect(normalizeEditorText(123)).toBe("123");
});
});

View file

@ -166,4 +166,5 @@ const builtinPlugins: Array<PluginConstructor | typeof SpecialCharactersEmoji> =
export { ClassicEditor } from "ckeditor5";
export { builtinPlugins, frTranslations };
export { normalizeEditorText } from "./ckeditor-utils";

View file

@ -54,7 +54,7 @@
'id' => 'answer-text',
'csrf' => csrf_token(),
'name' => 'answer',
'text' => old('answer', $faq->answer ?? ''),
'text' => old('answer', $faq->answer ?? '') ?? '',
];
@endphp
<div class="ck-editor" data-json='@json($editor)'></div>

View file

@ -77,7 +77,7 @@
'id' => 'page-content',
'csrf' => csrf_token(),
'name' => 'content',
'text' => old('content', $page->content ?? ''),
'text' => old('content', $page->content ?? '') ?? '',
];
@endphp
<div class="ck-editor" data-json='@json($editor)'></div>

View file

@ -66,7 +66,7 @@
'id' => 'inco-ingredients',
'csrf' => csrf_token(),
'name' => 'ingredients',
'text' => old('ingredients', $inco->ingredients ?? ''),
'text' => old('ingredients', $inco->ingredients ?? '') ?? '',
];
@endphp
<div class="ck-editor" data-json='@json($editorIngredients)'></div>
@ -81,7 +81,7 @@
'id' => 'inco-preparation',
'csrf' => csrf_token(),
'name' => 'preparation',
'text' => old('preparation', $inco->preparation ?? ''),
'text' => old('preparation', $inco->preparation ?? '') ?? '',
];
@endphp
<div class="ck-editor" data-json='@json($editorPrep)'></div>
@ -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
<div class="ck-editor" data-json='@json($editorSelling)'></div>

View file

@ -109,7 +109,7 @@
'id' => 'product-description',
'csrf' => csrf_token(),
'name' => 'description',
'text' => old('description', $product->description ?? ''),
'text' => old('description', $product->description ?? '') ?? '',
];
@endphp
<div class="ck-editor" data-json='@json($editor)'></div>

View file

@ -48,7 +48,7 @@
'id' => 'range-description',
'csrf' => csrf_token(),
'name' => 'description',
'text' => old('description', $range->description ?? ''),
'text' => old('description', $range->description ?? '') ?? '',
];
@endphp
<div class="ck-editor" data-json='@json($editor)'></div>

View file

@ -141,7 +141,7 @@
'id' => 'slide-text',
'csrf' => csrf_token(),
'name' => 'text',
'text' => old('text', $slide->text ?? ''),
'text' => old('text', $slide->text ?? '') ?? '',
];
@endphp
<div class="ck-editor" data-json='@json($editor)'></div>

View file

@ -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();