From 78f0a96fbe41e7f5c6242b1386bf1e7255dd1e1e Mon Sep 17 00:00:00 2001
From: Marius Dumitru Florea <marius@xwiki.com>
Date: Thu, 11 Nov 2021 17:28:01 +0200
Subject: [PATCH] XWIKI-19113: Can't edit in place the Title and Content fields

---
 .../appwithinminutes/test/ui/AllITs.java      |  6 ++
 .../test/ui/DocumentFieldsIT.java             | 79 +++++++++++-------
 .../AppWithinMinutes/ClassSheetGenerator.xml  |  4 +-
 .../model/script/ModelScriptService.java      | 16 ++++
 .../model/script/ModelScriptServiceTest.java  | 11 +++
 .../main/java/com/xpn/xwiki/api/Object.java   | 21 +++++
 .../java/com/xpn/xwiki/api/ObjectTest.java    | 81 +++++++++++++++++++
 .../ui/po/DocumentSyntaxPropertyPane.java     |  2 +-
 .../test/ui/po/EditablePropertyPane.java      | 60 ++++++++++++--
 .../xwiki/xclass/test/ui/ClassSheetIT.java    | 26 +++++-
 .../src/test/resources/contentDisplayer.wiki  | 11 +++
 .../src/main/resources/XWiki/ObjectSheet.xml  |  2 +-
 12 files changed, 280 insertions(+), 39 deletions(-)
 rename xwiki-platform-distribution/xwiki-platform-distribution-flavor/xwiki-platform-distribution-flavor-test/xwiki-platform-distribution-flavor-test-ui/src/test/it/org/xwiki/test/ui/appwithinminutes/DocumentFieldsTest.java => xwiki-platform-core/xwiki-platform-appwithinminutes/xwiki-platform-appwithinminutes-test/xwiki-platform-appwithinminutes-test-docker/src/test/it/org/xwiki/appwithinminutes/test/ui/DocumentFieldsIT.java (62%)
 create mode 100644 xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/api/ObjectTest.java
 create mode 100644 xwiki-platform-core/xwiki-platform-xclass/xwiki-platform-xclass-test/xwiki-platform-xclass-test-docker/src/test/resources/contentDisplayer.wiki

diff --git a/xwiki-platform-core/xwiki-platform-appwithinminutes/xwiki-platform-appwithinminutes-test/xwiki-platform-appwithinminutes-test-docker/src/test/it/org/xwiki/appwithinminutes/test/ui/AllITs.java b/xwiki-platform-core/xwiki-platform-appwithinminutes/xwiki-platform-appwithinminutes-test/xwiki-platform-appwithinminutes-test-docker/src/test/it/org/xwiki/appwithinminutes/test/ui/AllITs.java
index da8ff799f28..9c06dea8937 100644
--- a/xwiki-platform-core/xwiki-platform-appwithinminutes/xwiki-platform-appwithinminutes-test/xwiki-platform-appwithinminutes-test-docker/src/test/it/org/xwiki/appwithinminutes/test/ui/AllITs.java
+++ b/xwiki-platform-core/xwiki-platform-appwithinminutes/xwiki-platform-appwithinminutes-test/xwiki-platform-appwithinminutes-test-docker/src/test/it/org/xwiki/appwithinminutes/test/ui/AllITs.java
@@ -61,4 +61,10 @@ class NestedLiveTableGeneratorIT extends LiveTableGeneratorIT
     class NestedAddEntryIT extends AddEntryIT
     {
     }
+
+    @Nested
+    @DisplayName("Document fields test")
+    class NestedDocumentFieldsIT extends DocumentFieldsIT
+    {
+    }
 }
diff --git a/xwiki-platform-distribution/xwiki-platform-distribution-flavor/xwiki-platform-distribution-flavor-test/xwiki-platform-distribution-flavor-test-ui/src/test/it/org/xwiki/test/ui/appwithinminutes/DocumentFieldsTest.java b/xwiki-platform-core/xwiki-platform-appwithinminutes/xwiki-platform-appwithinminutes-test/xwiki-platform-appwithinminutes-test-docker/src/test/it/org/xwiki/appwithinminutes/test/ui/DocumentFieldsIT.java
similarity index 62%
rename from xwiki-platform-distribution/xwiki-platform-distribution-flavor/xwiki-platform-distribution-flavor-test/xwiki-platform-distribution-flavor-test-ui/src/test/it/org/xwiki/test/ui/appwithinminutes/DocumentFieldsTest.java
rename to xwiki-platform-core/xwiki-platform-appwithinminutes/xwiki-platform-appwithinminutes-test/xwiki-platform-appwithinminutes-test-docker/src/test/it/org/xwiki/appwithinminutes/test/ui/DocumentFieldsIT.java
index 8fa1008c329..6f720f895f4 100644
--- a/xwiki-platform-distribution/xwiki-platform-distribution-flavor/xwiki-platform-distribution-flavor-test/xwiki-platform-distribution-flavor-test-ui/src/test/it/org/xwiki/test/ui/appwithinminutes/DocumentFieldsTest.java
+++ b/xwiki-platform-core/xwiki-platform-appwithinminutes/xwiki-platform-appwithinminutes-test/xwiki-platform-appwithinminutes-test-docker/src/test/it/org/xwiki/appwithinminutes/test/ui/DocumentFieldsIT.java
@@ -17,14 +17,13 @@
  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  */
-package org.xwiki.test.ui.appwithinminutes;
+package org.xwiki.appwithinminutes.test.ui;
 
 import java.util.Arrays;
 
 import org.apache.commons.lang3.RandomStringUtils;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
 import org.xwiki.appwithinminutes.test.po.ApplicationClassEditPage;
 import org.xwiki.appwithinminutes.test.po.ApplicationCreatePage;
 import org.xwiki.appwithinminutes.test.po.ApplicationHomeEditPage;
@@ -33,26 +32,45 @@
 import org.xwiki.appwithinminutes.test.po.EntryEditPage;
 import org.xwiki.appwithinminutes.test.po.EntryNamePane;
 import org.xwiki.model.reference.LocalDocumentReference;
-import org.xwiki.test.ui.AbstractTest;
-import org.xwiki.test.ui.AdminAuthenticationRule;
+import org.xwiki.test.docker.junit5.UITest;
+import org.xwiki.test.ui.TestUtils;
+import org.xwiki.test.ui.po.EditablePropertyPane;
 import org.xwiki.test.ui.po.LiveTableElement;
 import org.xwiki.test.ui.po.ViewPage;
 import org.xwiki.test.ui.po.editor.WikiEditPage;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
 /**
  * Tests the special document fields available in the class editor, such as Title and Content.
  * 
  * @version $Id$
- * @since 4.5RC1
+ * @since 12.10.11
+ * @since 13.4.6
+ * @since 13.10RC1
  */
-public class DocumentFieldsTest extends AbstractTest
+@UITest
+class DocumentFieldsIT
 {
-    @Rule
-    public AdminAuthenticationRule adminAuthenticationRule = new AdminAuthenticationRule(true, getUtil());
+    @BeforeAll
+    static void beforeAll(TestUtils setup)
+    {
+        // By default the minimal distribution used for the tests doesn't have any rights setup. Let's create an Admin
+        // user part of the Admin Group and make sure that this Admin Group has admin rights in the wiki. We could also
+        // have given that Admin user the admin right directly but the solution we chose is closer to the XS
+        // distribution.
+        setup.loginAsSuperAdmin();
+        setup.setGlobalRights("XWiki.XWikiAdminGroup", "", "admin", true);
+        setup.createAdminUser();
+        setup.loginAsAdmin();
+    }
 
     @Test
-    public void titleAndContent()
+    void titleAndContent(TestUtils setup)
     {
+        setup.loginAsAdmin();
+
         // Create a new application.
         String appName = RandomStringUtils.randomAlphabetic(6);
         ApplicationCreatePage appCreatePage = ApplicationCreatePage.gotoPage();
@@ -88,37 +106,44 @@ public void titleAndContent()
         EntryNamePane entryNamePane = homeEditPage.clickFinish().clickAddNewEntry();
         entryNamePane.setName("Test");
         EntryEditPage entryEditPage = entryNamePane.clickAdd();
-        Assert.assertEquals("13", entryEditPage.getValue("number1"));
+        assertEquals("13", entryEditPage.getValue("number1"));
         // The page name is used as the default value for the title field.
-        Assert.assertEquals("Test", entryEditPage.getDocumentTitle());
-        Assert.assertEquals("Test", entryEditPage.getTitle());
+        assertEquals("Test", entryEditPage.getDocumentTitle());
+        assertEquals("Test", entryEditPage.getTitle());
         entryEditPage.setTitle("Foo");
-        Assert.assertEquals(defaultContent, entryEditPage.getContent());
+        assertEquals(defaultContent, entryEditPage.getContent());
         entryEditPage.setContent("Bar");
 
         // Check that the title and the content of the entry have been updated.
         ViewPage entryViewPage = entryEditPage.clickSaveAndView();
-        Assert.assertEquals("Foo", entryViewPage.getDocumentTitle());
-        Assert.assertTrue(entryViewPage.getContent().contains("Bar"));
-        entryViewPage.clickBreadcrumbLink(appName);
+        assertEquals("Foo", entryViewPage.getDocumentTitle());
+        assertTrue(entryViewPage.getContent().contains("Bar"));
+
+        // Verify that we can edit the document fields in-place.
+        String propertyReference = String.format("%s.Code.%1$sClass[0].title1", appName);
+        EditablePropertyPane<String> titleProperty = new EditablePropertyPane<>(propertyReference);
+        assertEquals("Foo", titleProperty.clickEdit().getValue());
+        titleProperty.setValue("Book").clickSave();
+        assertEquals("Book", titleProperty.getDisplayValue());
 
         // Check the entries live table.
+        entryViewPage.clickBreadcrumbLink(appName);
         LiveTableElement liveTable = new ApplicationHomePage().getEntriesLiveTable();
         liveTable.waitUntilReady();
-        Assert.assertEquals(1, liveTable.getRowCount());
-        Assert.assertTrue(liveTable.hasRow("My Title", "Foo"));
-        Assert.assertTrue(liveTable.hasRow("My Content", "Bar"));
+        assertEquals(1, liveTable.getRowCount());
+        assertTrue(liveTable.hasRow("My Title", "Book"));
+        assertTrue(liveTable.hasRow("My Content", "Bar"));
 
         // Check that the title and the content of the class have not been changed.
-        getUtil().gotoPage(new LocalDocumentReference(Arrays.asList(appName, "Code"), appName + "Class"), "edit",
+        setup.gotoPage(new LocalDocumentReference(Arrays.asList(appName, "Code"), appName + "Class"), "edit",
             "editor=wiki");
         WikiEditPage editPage = new WikiEditPage();
-        Assert.assertEquals(appName + " Class", editPage.getTitle());
-        Assert.assertEquals("", editPage.getContent());
+        assertEquals(appName + " Class", editPage.getTitle());
+        assertEquals("", editPage.getContent());
 
         // Now edit the class and check if the default values for title and content are taken from the template.
-        editPage.editInline();
-        Assert.assertEquals(defaultTitle, new ClassFieldEditPane("title1").getDefaultValue());
-        Assert.assertEquals(defaultContent, new ClassFieldEditPane("content1").getDefaultValue());
+        editPage.edit();
+        assertEquals(defaultTitle, new ClassFieldEditPane("title1").getDefaultValue());
+        assertEquals(defaultContent, new ClassFieldEditPane("content1").getDefaultValue());
     }
 }
diff --git a/xwiki-platform-core/xwiki-platform-appwithinminutes/xwiki-platform-appwithinminutes-ui/src/main/resources/AppWithinMinutes/ClassSheetGenerator.xml b/xwiki-platform-core/xwiki-platform-appwithinminutes/xwiki-platform-appwithinminutes-ui/src/main/resources/AppWithinMinutes/ClassSheetGenerator.xml
index babeff1b72e..832bd0acc61 100644
--- a/xwiki-platform-core/xwiki-platform-appwithinminutes/xwiki-platform-appwithinminutes-ui/src/main/resources/AppWithinMinutes/ClassSheetGenerator.xml
+++ b/xwiki-platform-core/xwiki-platform-appwithinminutes/xwiki-platform-appwithinminutes-ui/src/main/resources/AppWithinMinutes/ClassSheetGenerator.xml
@@ -20,7 +20,7 @@
  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 -->
 
-<xwikidoc version="1.3" reference="AppWithinMinutes.ClassSheetGenerator" locale="">
+<xwikidoc version="1.4" reference="AppWithinMinutes.ClassSheetGenerator" locale="">
   <web>AppWithinMinutes</web>
   <name>ClassSheetGenerator</name>
   <language/>
@@ -47,7 +47,7 @@
 #macro(displayProperty $property $indentation)
 ${indentation}&lt;dt ${escapetool.h}if (!${escapetool.d}editing &amp;&amp; ${escapetool.d}hasEdit)
 ${indentation}    class="editableProperty"
-${indentation}    data-property="${escapetool.d}escapetool.xml(${escapetool.d}services.model.serialize(${escapetool.d}object.getProperty('#escapeSingleQuotes($property.name)').reference))"
+${indentation}    data-property="${escapetool.d}escapetool.xml(${escapetool.d}services.model.serialize(${escapetool.d}object.getPropertyReference('#escapeSingleQuotes($property.name)')))"
 ${indentation}    data-property-type="object"${escapetool.h}end&gt;
 ${indentation}  &lt;label${escapetool.h}if (${escapetool.d}editing) for="$escapetool.xml("${doc.fullName}_0_$property.name")"${escapetool.h}end&gt;
 ${indentation}    ${escapetool.d}escapetool.xml(${escapetool.d}doc.displayPrettyName('#escapeSingleQuotes($property.name)', false, false))
diff --git a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/main/java/org/xwiki/model/script/ModelScriptService.java b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/main/java/org/xwiki/model/script/ModelScriptService.java
index 6686844e8d9..2484c8fa1c4 100644
--- a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/main/java/org/xwiki/model/script/ModelScriptService.java
+++ b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/main/java/org/xwiki/model/script/ModelScriptService.java
@@ -309,6 +309,22 @@ public PageAttachmentReference createPageAttachmentReference(PageReference pageR
         return new PageAttachmentReference(fileName, pageReference);
     }
 
+    /**
+     * Creates an {@link ObjectPropertyReference} from a property name and the reference of the object having that
+     * property.
+     * 
+     * @param propertyName the property name
+     * @param objectReference the object reference
+     * @return the reference of the specified object property
+     * @since 12.10.11
+     * @since 13.4.6
+     * @since 13.10RC1
+     */
+    public ObjectPropertyReference createObjectPropertyReference(String propertyName, ObjectReference objectReference)
+    {
+        return new ObjectPropertyReference(propertyName, objectReference);
+    }
+
     /**
      * Creates a {@link WikiReference} from a string representing the wiki name.
      *
diff --git a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/script/ModelScriptServiceTest.java b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/script/ModelScriptServiceTest.java
index 1ceb94697a9..9fc8b17d0e6 100644
--- a/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/script/ModelScriptServiceTest.java
+++ b/xwiki-platform-core/xwiki-platform-model/xwiki-platform-model-api/src/test/java/org/xwiki/model/script/ModelScriptServiceTest.java
@@ -35,6 +35,8 @@
 import org.xwiki.model.reference.EntityReference;
 import org.xwiki.model.reference.EntityReferenceResolver;
 import org.xwiki.model.reference.EntityReferenceValueProvider;
+import org.xwiki.model.reference.ObjectPropertyReference;
+import org.xwiki.model.reference.ObjectReference;
 import org.xwiki.model.reference.PageReference;
 import org.xwiki.model.reference.PageReferenceResolver;
 import org.xwiki.model.reference.SpaceReference;
@@ -329,4 +331,13 @@ public void resolveClassPropertyWithHintAndParameters() throws Exception
         assertEquals(reference,
             this.service.resolveClassProperty("Class^property", "custom", parameters[0], parameters[1]));
     }
+
+    @Test
+    void createObjectPropertyReference()
+    {
+        ObjectReference objectReference =
+            new ObjectReference("objectName", new DocumentReference("test", "Some", "Page"));
+        assertEquals(new ObjectPropertyReference("test", objectReference),
+            this.service.createObjectPropertyReference("test", objectReference));
+    }
 }
diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/api/Object.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/api/Object.java
index 546a554b404..bb50d7fd3c2 100644
--- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/api/Object.java
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/api/Object.java
@@ -19,6 +19,8 @@
  */
 package com.xpn.xwiki.api;
 
+import org.xwiki.model.reference.ObjectPropertyReference;
+
 import com.xpn.xwiki.XWikiContext;
 import com.xpn.xwiki.XWikiException;
 import com.xpn.xwiki.doc.XWikiDocument;
@@ -126,4 +128,23 @@ public BaseObjectReference getReference()
     {
         return getBaseObject().getReference();
     }
+
+    /**
+     * Helper method used to obtain the reference of an object property even when the object might not have the property
+     * (e.g. because it's a computed property which doesn't have a stored value so it's not saved on the object). This
+     * is a safe alternative to {@code getProperty(propertyName).getReference()} when you're not sure whether the object
+     * has the specified property or not.
+     * 
+     * @param propertyName the property name
+     * @return the object property reference
+     * @see <a href="https://jira.xwiki.org/browse/XWIKI-19031">XWIKI-19031: Computed fields are stored as empty string
+     *      properties in the database</a>
+     * @since 12.10.11
+     * @since 13.4.6
+     * @since 13.10RC1
+     */
+    public ObjectPropertyReference getPropertyReference(String propertyName)
+    {
+        return new ObjectPropertyReference(propertyName, getReference());
+    }
 }
diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/api/ObjectTest.java b/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/api/ObjectTest.java
new file mode 100644
index 00000000000..48540f716ce
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/api/ObjectTest.java
@@ -0,0 +1,81 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.api;
+
+import javax.inject.Named;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.xwiki.model.reference.DocumentReference;
+import org.xwiki.model.reference.EntityReferenceSerializer;
+import org.xwiki.model.reference.ObjectPropertyReference;
+import org.xwiki.test.junit5.mockito.MockComponent;
+
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.objects.BaseObject;
+import com.xpn.xwiki.objects.BaseObjectReference;
+import com.xpn.xwiki.test.junit5.mockito.OldcoreTest;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for {@link Object}.
+ * 
+ * @version $Id$
+ * @since 12.10.11
+ * @since 13.4.6
+ * @since 13.10RC1
+ */
+@OldcoreTest
+class ObjectTest
+{
+    private Object object;
+
+    @Mock
+    private BaseObject baseObject;
+
+    @Mock
+    private XWikiContext xcontext;
+
+    @MockComponent
+    @Named("compactwiki")
+    private EntityReferenceSerializer<String> compactWikiSerializer;
+
+    @BeforeEach
+    void setUp()
+    {
+        DocumentReference classReference = new DocumentReference("test", "Some", "Class");
+        DocumentReference documentReference = new DocumentReference("test", "Some", "Page");
+        when(this.compactWikiSerializer.serialize(classReference, documentReference)).thenReturn("Some.Class");
+        BaseObjectReference objectReference = new BaseObjectReference(classReference, 0, documentReference);
+        when(this.baseObject.getReference()).thenReturn(objectReference);
+
+        this.object = new Object(this.baseObject, this.xcontext);
+    }
+
+    @Test
+    void getPropertyReference()
+    {
+        assertEquals(new ObjectPropertyReference("age", this.baseObject.getReference()),
+            object.getPropertyReference("age"));
+    }
+}
diff --git a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/DocumentSyntaxPropertyPane.java b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/DocumentSyntaxPropertyPane.java
index 11362199110..5090e5e964b 100644
--- a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/DocumentSyntaxPropertyPane.java
+++ b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/DocumentSyntaxPropertyPane.java
@@ -26,7 +26,7 @@
  * @since 12.6.3
  * @since 12.9RC1
  */
-public class DocumentSyntaxPropertyPane extends EditablePropertyPane
+public class DocumentSyntaxPropertyPane extends EditablePropertyPane<String>
 {
     public DocumentSyntaxPropertyPane()
     {
diff --git a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/EditablePropertyPane.java b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/EditablePropertyPane.java
index 889bee7c480..e0dc616ca4a 100644
--- a/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/EditablePropertyPane.java
+++ b/xwiki-platform-core/xwiki-platform-test/xwiki-platform-test-ui/src/main/java/org/xwiki/test/ui/po/EditablePropertyPane.java
@@ -27,11 +27,12 @@
 /**
  * The page object used to edit a document or object property in-place.
  * 
+ * @param <T> the property value type
  * @version $Id$
  * @since 12.6.3
  * @since 12.9RC1
  */
-public class EditablePropertyPane extends BaseElement
+public class EditablePropertyPane<T> extends BaseElement
 {
     protected final WebElement element;
 
@@ -47,8 +48,8 @@ public class EditablePropertyPane extends BaseElement
 
     public EditablePropertyPane(String property)
     {
-        this.element = getDriver().findElement(
-            By.cssSelector("dt.editableProperty[data-property=\"" + property + "\"]"));
+        this.element =
+            getDriver().findElement(By.cssSelector("dt.editableProperty[data-property=\"" + property + "\"]"));
         this.viewer =
             this.element.findElement(By.xpath("./following-sibling::dd[contains(@class, 'editableProperty-viewer')]"));
         this.editor =
@@ -58,21 +59,21 @@ public EditablePropertyPane(String property)
         this.saveButton = this.element.findElement(By.className("editableProperty-save"));
     }
 
-    public EditablePropertyPane clickEdit()
+    public EditablePropertyPane<T> clickEdit()
     {
         this.editButton.click();
         getDriver().waitUntilCondition(visibilityOf(this.editor));
         return this;
     }
 
-    public EditablePropertyPane clickCancel()
+    public EditablePropertyPane<T> clickCancel()
     {
         this.cancelButton.click();
         getDriver().waitUntilCondition(visibilityOf(this.editButton));
         return this;
     }
 
-    public EditablePropertyPane clickSave()
+    public EditablePropertyPane<T> clickSave()
     {
         this.saveButton.click();
         getDriver().waitUntilCondition(visibilityOf(this.editButton));
@@ -88,4 +89,51 @@ public String getDisplayValue()
     {
         return this.viewer.getText();
     }
+
+    /**
+     * @return the property value while the property is being edited
+     * @since 12.10.11
+     * @since 13.4.6
+     * @since 13.10RC1
+     */
+    @SuppressWarnings("unchecked")
+    public T getValue()
+    {
+        WebElement inputField = getInputField();
+        if ("checkbox".equals(inputField.getAttribute("type"))) {
+            return (T) Boolean.valueOf(inputField.isSelected());
+        } else {
+            return (T) inputField.getAttribute("value");
+        }
+    }
+
+    /**
+     * Sets the property value while the property is being edited.
+     * 
+     * @param value the new property value
+     * @return this property pane
+     * @since 12.10.11
+     * @since 13.4.6
+     * @since 13.10RC1
+     */
+    public EditablePropertyPane<T> setValue(T value)
+    {
+        WebElement inputField = getInputField();
+        if (value instanceof Boolean) {
+            // If the value is boolean then we assume the input field is a checkbox.
+            if (inputField.isSelected() != (Boolean) value) {
+                inputField.click();
+            }
+        } else {
+            // Assume the input field is a text field (text input or text area).
+            inputField.clear();
+            inputField.sendKeys(String.valueOf(value));
+        }
+        return this;
+    }
+
+    private WebElement getInputField()
+    {
+        return getDriver().findElementWithoutWaiting(this.editor, By.cssSelector("[name]"));
+    }
 }
diff --git a/xwiki-platform-core/xwiki-platform-xclass/xwiki-platform-xclass-test/xwiki-platform-xclass-test-docker/src/test/it/org/xwiki/xclass/test/ui/ClassSheetIT.java b/xwiki-platform-core/xwiki-platform-xclass/xwiki-platform-xclass-test/xwiki-platform-xclass-test-docker/src/test/it/org/xwiki/xclass/test/ui/ClassSheetIT.java
index 491e1f73618..67efa0d10c3 100644
--- a/xwiki-platform-core/xwiki-platform-xclass/xwiki-platform-xclass-test/xwiki-platform-xclass-test-docker/src/test/it/org/xwiki/xclass/test/ui/ClassSheetIT.java
+++ b/xwiki-platform-core/xwiki-platform-xclass/xwiki-platform-xclass-test/xwiki-platform-xclass-test-docker/src/test/it/org/xwiki/xclass/test/ui/ClassSheetIT.java
@@ -19,12 +19,14 @@
  */
 package org.xwiki.xclass.test.ui;
 
+import org.apache.commons.io.IOUtils;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Order;
 import org.junit.jupiter.api.Test;
 import org.xwiki.test.docker.junit5.TestReference;
 import org.xwiki.test.docker.junit5.UITest;
 import org.xwiki.test.ui.TestUtils;
+import org.xwiki.test.ui.po.EditablePropertyPane;
 import org.xwiki.test.ui.po.InlinePage;
 import org.xwiki.test.ui.po.ViewPage;
 import org.xwiki.test.ui.po.editor.ClassEditPage;
@@ -55,7 +57,7 @@ void setup(TestUtils setup)
      */
     @Test
     @Order(1)
-    void createClass(TestUtils setup, TestReference reference)
+    void createClass(TestUtils setup, TestReference reference) throws Exception
     {
         //TODO: rewrite the test to not rely on the breadcrumb based on parent/child mechanism.
         setup.setHierarchyMode("parentchild");
@@ -89,6 +91,12 @@ void createClass(TestUtils setup, TestReference reference)
             // Add a new property.
             classEditor = classSheetPage.waitUntilPageIsLoaded().clickEditClassLink();
             classEditor.addProperty("age", "Number").setPrettyName("Your current age");
+
+            // Add a computed property.
+            String titleDisplayer =
+                IOUtils.toString(this.getClass().getResourceAsStream("/contentDisplayer.wiki"), "UTF-8");
+            classEditor.addProperty("description", "ComputedField").setPrettyName("Description")
+                .setMetaProperty("customDisplay", titleDisplayer);
             classEditor.clickSaveAndView();
 
             // We have to wait for the page to load because Selenium doesn't do it all the time when we click on Save & View
@@ -99,6 +107,7 @@ void createClass(TestUtils setup, TestReference reference)
             // Assert that the properties are listed.
             assertTrue(classSheetPage.hasProperty("color", "Your favorite color", "String"));
             assertTrue(classSheetPage.hasProperty("age", "Your current age", "Number"));
+            assertTrue(classSheetPage.hasProperty("description", "Description", "Computed Field"));
 
             // Create and bind a sheet.
             classSheetPage = classSheetPage.clickCreateSheetButton().waitUntilPageIsLoaded()
@@ -134,8 +143,21 @@ void createClass(TestUtils setup, TestReference reference)
             editPage.setValue("age", "27");
             ViewPage viewPage = editPage.clickSaveAndView();
 
+            // Verify that the properties can be edited in-place.
+            EditablePropertyPane<String> colorProperty =
+                new EditablePropertyPane<>(String.format("%s.%s[0].color", spaceName, classDocName));
+            assertEquals("blue", colorProperty.clickEdit().getValue());
+            colorProperty.setValue("pink").clickSave();
+            assertEquals("pink", colorProperty.getDisplayValue());
+
+            EditablePropertyPane<String> descriptionProperty =
+                new EditablePropertyPane<>(String.format("%s.%s[0].description", spaceName, classDocName));
+            assertEquals("", descriptionProperty.clickEdit().getValue());
+            descriptionProperty.setValue("Tester").clickSave();
+            assertEquals("Tester", descriptionProperty.getDisplayValue());
+
             assertEquals(pageName, viewPage.getDocumentTitle());
-            assertEquals("YOUR FAVORITE COLOR\nblue\nYOUR CURRENT AGE\n27", viewPage.getContent());
+            assertEquals("YOUR FAVORITE COLOR\npink\nYOUR CURRENT AGE\n27\nDESCRIPTION\nTester", viewPage.getContent());
             viewPage.clickBreadcrumbLink(classTitle);
             classSheetPage.waitUntilPageIsLoaded();
 
diff --git a/xwiki-platform-core/xwiki-platform-xclass/xwiki-platform-xclass-test/xwiki-platform-xclass-test-docker/src/test/resources/contentDisplayer.wiki b/xwiki-platform-core/xwiki-platform-xclass/xwiki-platform-xclass-test/xwiki-platform-xclass-test-docker/src/test/resources/contentDisplayer.wiki
new file mode 100644
index 00000000000..d8d27b9d727
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-xclass/xwiki-platform-xclass-test/xwiki-platform-xclass-test-docker/src/test/resources/contentDisplayer.wiki
@@ -0,0 +1,11 @@
+{{velocity}}
+#if ($type == 'edit')
+  {{html clean="false"}}
+  <textarea name="content">$!escapetool.xml($tdoc.content)</textarea>
+  {{/html}}
+#elseif ("$!type" != '')
+  {{include reference="" /}}
+#else
+  The display mode is not specified!
+#end
+{{/velocity}}
\ No newline at end of file
diff --git a/xwiki-platform-core/xwiki-platform-xclass/xwiki-platform-xclass-ui/src/main/resources/XWiki/ObjectSheet.xml b/xwiki-platform-core/xwiki-platform-xclass/xwiki-platform-xclass-ui/src/main/resources/XWiki/ObjectSheet.xml
index 528321ccf33..18eed82bfe2 100644
--- a/xwiki-platform-core/xwiki-platform-xclass/xwiki-platform-xclass-ui/src/main/resources/XWiki/ObjectSheet.xml
+++ b/xwiki-platform-core/xwiki-platform-xclass/xwiki-platform-xclass-ui/src/main/resources/XWiki/ObjectSheet.xml
@@ -60,7 +60,7 @@
   #foreach ($property in $xclass.properties)
     &lt;dt #if (!$editing &amp;&amp; $hasEdit)
         class="editableProperty"
-        #set ($xobjectPropertyReference = $xobject.getProperty($property.name).reference)
+        #set ($xobjectPropertyReference = $xobject.getPropertyReference($property.name))
         data-property="$escapetool.xml($services.model.serialize($xobjectPropertyReference))"
         data-property-type="object"#end&gt;
       ## This must match the id generated by the $doc.display() method below.
-- 
GitLab