diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/AdditionalSetup/App_Plugins/block-custom-view/block-custom-view.js b/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/AdditionalSetup/App_Plugins/block-custom-view/block-custom-view.js
new file mode 100644
index 000000000000..f275d6e50251
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/AdditionalSetup/App_Plugins/block-custom-view/block-custom-view.js
@@ -0,0 +1,43 @@
+import { LitElement as c, html as m, css as h, property as a, customElement as u } from "@umbraco-cms/backoffice/external/lit";
+import { UmbElementMixin as b } from "@umbraco-cms/backoffice/element-api";
+var d = Object.defineProperty, f = Object.getOwnPropertyDescriptor, l = (p, o, s, r) => {
+ for (var e = r > 1 ? void 0 : r ? f(o, s) : o, i = p.length - 1, n; i >= 0; i--)
+ (n = p[i]) && (e = (r ? n(o, s, e) : n(e)) || e);
+ return r && e && d(o, s, e), e;
+};
+let t = class extends b(c) {
+ render() {
+ return m`
+
My Custom View
+ Heading and Theme: ${this.content?.heading} - ${this.settings?.theme}
+ `;
+ }
+};
+t.styles = [
+ h`
+ :host {
+ display: block;
+ height: 100%;
+ box-sizing: border-box;
+ background-color: darkgreen;
+ color: white;
+ border-radius: 9px;
+ padding: 12px;
+ }
+ `
+];
+l([
+ a({ attribute: !1 })
+], t.prototype, "content", 2);
+l([
+ a({ attribute: !1 })
+], t.prototype, "settings", 2);
+t = l([
+ u("block-custom-view")
+], t);
+const w = t;
+export {
+ t as BlockCustomView,
+ w as default
+};
+//# sourceMappingURL=block-custom-view.js.map
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/AdditionalSetup/App_Plugins/block-custom-view/block-custom-view.js.map b/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/AdditionalSetup/App_Plugins/block-custom-view/block-custom-view.js.map
new file mode 100644
index 000000000000..48adaed1164a
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/AdditionalSetup/App_Plugins/block-custom-view/block-custom-view.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"block-custom-view.js","sources":["../../block-custom-view/src/block-custom-view.ts"],"sourcesContent":["import { html, customElement, LitElement, property, css } from '@umbraco-cms/backoffice/external/lit';\nimport { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';\nimport type { UmbBlockDataType } from '@umbraco-cms/backoffice/block';\nimport type { UmbBlockEditorCustomViewElement } from '@umbraco-cms/backoffice/block-custom-view';\n\n@customElement('block-custom-view')\nexport class BlockCustomView extends UmbElementMixin(LitElement) implements UmbBlockEditorCustomViewElement {\n\t\n\t@property({ attribute: false })\n\tcontent?: UmbBlockDataType;\n\n\t@property({ attribute: false })\n\tsettings?: UmbBlockDataType;\n\n\trender() {\n\t\treturn html`\n\t\t\tMy Custom View
\n\t\t\tHeading and Theme: ${this.content?.heading} - ${this.settings?.theme}
\n\t\t`;\n\t}\n\n\tstatic styles = [\n\t\tcss`\n\t\t\t:host {\n\t\t\t\tdisplay: block;\n\t\t\t\theight: 100%;\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\tbackground-color: darkgreen;\n\t\t\t\tcolor: white;\n\t\t\t\tborder-radius: 9px;\n\t\t\t\tpadding: 12px;\n\t\t\t}\n\t\t`,\n\t];\n\t\n}\nexport default BlockCustomView;\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'block-custom-view': BlockCustomView;\n\t}\n}\n"],"names":["BlockCustomView","UmbElementMixin","LitElement","html","css","__decorateClass","property","customElement","BlockCustomView$1"],"mappings":";;;;;;;AAMO,IAAMA,IAAN,cAA8BC,EAAgBC,CAAU,EAA6C;AAAA,EAQ3G,SAAS;AACR,WAAOC;AAAA;AAAA,2BAEkB,KAAK,SAAS,OAAO,MAAM,KAAK,UAAU,KAAK;AAAA;AAAA,EAEzE;AAgBD;AA7BaH,EAeL,SAAS;AAAA,EACfI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWD;AAxBAC,EAAA;AAAA,EADCC,EAAS,EAAE,WAAW,GAAA,CAAO;AAAA,GAFlBN,EAGZ,WAAA,WAAA,CAAA;AAGAK,EAAA;AAAA,EADCC,EAAS,EAAE,WAAW,GAAA,CAAO;AAAA,GALlBN,EAMZ,WAAA,YAAA,CAAA;AANYA,IAANK,EAAA;AAAA,EADNE,EAAc,mBAAmB;AAAA,GACrBP,CAAA;AA8Bb,MAAAQ,IAAeR;"}
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/AdditionalSetup/App_Plugins/block-custom-view/block-grid-custom-view.js b/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/AdditionalSetup/App_Plugins/block-custom-view/block-grid-custom-view.js
new file mode 100644
index 000000000000..30af1b6993b6
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/AdditionalSetup/App_Plugins/block-custom-view/block-grid-custom-view.js
@@ -0,0 +1,23 @@
+import { LitElement as n, html as c, customElement as u } from "@umbraco-cms/backoffice/external/lit";
+import { UmbElementMixin as a } from "@umbraco-cms/backoffice/element-api";
+var d = Object.getOwnPropertyDescriptor, p = (o, l, i, m) => {
+ for (var e = m > 1 ? void 0 : m ? d(l, i) : l, t = o.length - 1, s; t >= 0; t--)
+ (s = o[t]) && (e = s(e) || e);
+ return e;
+};
+let r = class extends a(n) {
+ render() {
+ return c`
+ Block Grid Custom View
+ `;
+ }
+};
+r = p([
+ u("block-grid-custom-view")
+], r);
+const f = r;
+export {
+ r as BlockGridCustomView,
+ f as default
+};
+//# sourceMappingURL=block-grid-custom-view.js.map
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/AdditionalSetup/App_Plugins/block-custom-view/block-grid-custom-view.js.map b/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/AdditionalSetup/App_Plugins/block-custom-view/block-grid-custom-view.js.map
new file mode 100644
index 000000000000..d918c3052096
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/AdditionalSetup/App_Plugins/block-custom-view/block-grid-custom-view.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"block-grid-custom-view.js","sources":["../../block-custom-view/src/block-grid-custom-view.ts"],"sourcesContent":["import { html, customElement, LitElement } from '@umbraco-cms/backoffice/external/lit';\nimport { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';\nimport type { UmbBlockEditorCustomViewElement }\r\nfrom '@umbraco-cms/backoffice/block-custom-view';\n\n@customElement('block-grid-custom-view')\nexport class BlockGridCustomView extends UmbElementMixin(LitElement) implements UmbBlockEditorCustomViewElement\r\n{\r\n render() {\r\n return html`\n Block Grid Custom View
\n\t\t`;\r\n }\n\t\n}\nexport default BlockGridCustomView;\n\ndeclare global\r\n{\n\r\n interface HTMLElementTagNameMap\r\n{\n\t\t'block-grid-custom-view': BlockGridCustomView;\n\t}\n}\n"],"names":["BlockGridCustomView","UmbElementMixin","LitElement","html","__decorateClass","customElement","BlockGridCustomView$1"],"mappings":";;;;;;;AAMO,IAAMA,IAAN,cAAkCC,EAAgBC,CAAU,EACnE;AAAA,EACI,SAAS;AACL,WAAOC;AAAA;AAAA;AAAA,EAGX;AAEJ;AARaH,IAANI,EAAA;AAAA,EADNC,EAAc,wBAAwB;AAAA,GAC1BL,CAAA;AASb,MAAAM,IAAeN;"}
\ No newline at end of file
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/AdditionalSetup/App_Plugins/block-custom-view/umbraco-package.json b/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/AdditionalSetup/App_Plugins/block-custom-view/umbraco-package.json
new file mode 100644
index 000000000000..d332f18297ab
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/AdditionalSetup/App_Plugins/block-custom-view/umbraco-package.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "../../umbraco-package-schema.json",
+ "name": "My.CustomViewPackage",
+ "version": "0.1.0",
+ "extensions": [
+ {
+ "type": "blockEditorCustomView",
+ "alias": "my.blockEditorCustomView.Example",
+ "name": "My Example Custom View",
+ "element": "/App_Plugins/block-custom-view/block-custom-view.js",
+ "forContentTypeAlias": "elementTypeForCustomBlockView"
+ },
+ {
+ "type": "blockEditorCustomView",
+ "alias": "my.blockGridCustomView.Example",
+ "name": "My Block Grid Custom View",
+ "element": "/App_Plugins/block-custom-view/block-grid-custom-view.js",
+ "forBlockEditor": "block-grid"
+ }
+ ]
+}
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/BlockCustomView.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/BlockCustomView.spec.ts
new file mode 100644
index 000000000000..8ef280fdbaa9
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/ExtensionRegistry/BlockCustomView.spec.ts
@@ -0,0 +1,168 @@
+import {ConstantHelper, test} from '@umbraco/playwright-testhelpers';
+
+// Content
+const contentName = 'TestContent';
+const contentGroupName = 'TestContentGroup';
+// DocumentType
+const documentTypeName = 'TestDocumentTypeForContent';
+// DataType
+const dataTypeName = 'Textstring';
+const textAreaDataTypeName = 'Textarea';
+// BlockType
+const blockGridName = 'TestBlockGridForContent';
+const blockListName = 'TestBlockListForContent';
+// ElementType
+const elementGroupName = 'TestElementGroupForContent';
+const firstElementTypeName = 'TestElementTypeForContent';
+const secondElementTypeName = 'Element Type For Custom Block View';
+// Setting Model
+const settingModelName = 'Test Setting Model';
+const groupName = 'Test Group';
+// Block Custom View
+const blockGridCustomViewLocator = 'block-grid-custom-view';
+const blockCustomViewLocator = 'block-custom-view';
+// Property Editor
+const propertyEditorName = 'Heading';
+const propertyEditorSettingName = 'Theme';
+
+test.afterEach(async ({ umbracoApi }) => {
+ await umbracoApi.document.ensureNameNotExists(contentName);
+ await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(blockGridName);
+ await umbracoApi.dataType.ensureNameNotExists(blockListName);
+ await umbracoApi.documentType.ensureNameNotExists(firstElementTypeName);
+ await umbracoApi.documentType.ensureNameNotExists(secondElementTypeName);
+ await umbracoApi.dataType.ensureNameNotExists(dataTypeName);
+});
+
+test('block custom view appears in a specific block type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const textStringDataType = await umbracoApi.dataType.getByName(dataTypeName);
+ const elementTypeId = await umbracoApi.documentType.createDefaultElementType(firstElementTypeName, elementGroupName, dataTypeName, textStringDataType.id);
+ const blockGridId = await umbracoApi.dataType.createBlockGridWithABlock(blockGridName, elementTypeId);
+ const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockGridName, blockGridId, contentGroupName);
+ await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
+
+ // Act
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.content.goToSection(ConstantHelper.sections.content);
+ await umbracoUi.content.goToContentWithName(contentName);
+ await umbracoUi.content.clickAddBlockElementButton();
+ await umbracoUi.content.clickBlockElementWithName(firstElementTypeName);
+ await umbracoUi.content.clickCreateModalButton();
+
+ // Assert
+ await umbracoUi.content.isBlockCustomViewVisible(blockGridCustomViewLocator);
+ await umbracoUi.content.isSingleBlockElementVisible(false);
+});
+
+test('block custom view does not appear in block list editor when configured for block grid only', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const textStringDataType = await umbracoApi.dataType.getByName(dataTypeName);
+ const elementTypeId = await umbracoApi.documentType.createDefaultElementType(firstElementTypeName, elementGroupName, dataTypeName, textStringDataType.id);
+ const blockListId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(blockListName, elementTypeId);
+ const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, contentGroupName);
+ await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
+
+ // Act
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.content.goToSection(ConstantHelper.sections.content);
+ await umbracoUi.content.goToContentWithName(contentName);
+ await umbracoUi.content.clickAddBlockElementButton();
+ await umbracoUi.content.clickBlockElementWithName(firstElementTypeName);
+ await umbracoUi.content.clickCreateModalButton();
+
+ // Assert
+ await umbracoUi.content.isBlockCustomViewVisible(blockGridCustomViewLocator, false);
+ await umbracoUi.content.isSingleBlockElementVisible();
+});
+
+test('block custom view applies to correct content type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const textStringDataType = await umbracoApi.dataType.getByName(dataTypeName);
+ const firstElementTypeId = await umbracoApi.documentType.createDefaultElementType(firstElementTypeName, elementGroupName, dataTypeName, textStringDataType.id);
+ const secondElementTypeId = await umbracoApi.documentType.createDefaultElementType(secondElementTypeName, elementGroupName, dataTypeName, textStringDataType.id);
+ const blockListId = await umbracoApi.dataType.createBlockListDataTypeWithTwoBlocks(blockListName, firstElementTypeId, secondElementTypeId);
+ const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, contentGroupName);
+ await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
+
+ // Act
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.content.goToSection(ConstantHelper.sections.content);
+ await umbracoUi.content.goToContentWithName(contentName);
+ await umbracoUi.content.clickAddBlockElementButton();
+ await umbracoUi.content.clickBlockCardWithName(secondElementTypeName);
+ await umbracoUi.content.clickCreateModalButton();
+ await umbracoUi.content.clickAddBlockElementButton();
+ await umbracoUi.content.clickBlockCardWithName(firstElementTypeName);
+ await umbracoUi.content.clickCreateModalButton();
+
+ // Assert
+ await umbracoUi.content.isBlockCustomViewVisible(blockCustomViewLocator);
+ await umbracoUi.content.isSingleBlockElementVisible();
+});
+
+test('block custom view can display values from the content and settings parts', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const contentValue = 'This is block test';
+ const settingValue = 'This is setting test';
+ const valueText = `Heading and Theme: ${contentValue} - ${settingValue}`;
+ const textStringDataType = await umbracoApi.dataType.getByName(dataTypeName);
+ const textAreaDataType = await umbracoApi.dataType.getByName(textAreaDataTypeName);
+ const elementTypeId = await umbracoApi.documentType.createDefaultElementType(secondElementTypeName, elementGroupName, propertyEditorName, textStringDataType.id);
+ const settingsElementTypeId = await umbracoApi.documentType.createDefaultElementType(settingModelName, groupName, propertyEditorSettingName, textAreaDataType.id);
+ const blockListId = await umbracoApi.dataType.createBlockListDataTypeWithContentAndSettingsElementType(blockListName, elementTypeId, settingsElementTypeId);
+ const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, contentGroupName);
+ await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
+
+ // Act
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.content.goToSection(ConstantHelper.sections.content);
+ await umbracoUi.content.goToContentWithName(contentName);
+ await umbracoUi.content.clickAddBlockElementButton();
+ await umbracoUi.content.clickBlockCardWithName(secondElementTypeName);
+ await umbracoUi.content.enterTextstring(contentValue);
+ await umbracoUi.content.clickAddBlockSettingsTabButton();
+ await umbracoUi.content.enterTextArea(settingValue);
+ await umbracoUi.content.clickCreateModalButton();
+
+ // Assert
+ await umbracoUi.content.isBlockCustomViewVisible(blockCustomViewLocator);
+ await umbracoUi.content.doesBlockCustomViewHaveValue(blockCustomViewLocator, valueText);
+});
+
+test('block custom view can display values from the content and settings parts after update', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const contentValue = 'This is block test';
+ const settingValue = 'This is setting test';
+ const updatedContentValue = 'This is updated block test';
+ const updatedSettingValue = 'This is updated setting test';
+ const updatedValueText = `Heading and Theme: ${updatedContentValue} - ${updatedSettingValue}`;
+ const textStringDataType = await umbracoApi.dataType.getByName(dataTypeName);
+ const textAreaDataType = await umbracoApi.dataType.getByName(textAreaDataTypeName);
+ const elementTypeId = await umbracoApi.documentType.createDefaultElementType(secondElementTypeName, elementGroupName, propertyEditorName, textStringDataType.id);
+ const settingsElementTypeId = await umbracoApi.documentType.createDefaultElementType(settingModelName, groupName, propertyEditorSettingName, textAreaDataType.id);
+ const blockListId = await umbracoApi.dataType.createBlockListDataTypeWithContentAndSettingsElementType(blockListName, elementTypeId, settingsElementTypeId);
+ const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, blockListName, blockListId, contentGroupName);
+ await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
+
+ // Act
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.content.goToSection(ConstantHelper.sections.content);
+ await umbracoUi.content.goToContentWithName(contentName);
+ await umbracoUi.content.clickAddBlockElementButton();
+ await umbracoUi.content.clickBlockCardWithName(secondElementTypeName);
+ await umbracoUi.content.enterTextstring(contentValue);
+ await umbracoUi.content.clickAddBlockSettingsTabButton();
+ await umbracoUi.content.enterTextArea(settingValue);
+ await umbracoUi.content.clickCreateModalButton();
+ await umbracoUi.content.clickEditBlockListBlockButton();
+ await umbracoUi.content.enterTextstring(updatedContentValue);
+ await umbracoUi.content.clickAddBlockSettingsTabButton();
+ await umbracoUi.content.enterTextArea(updatedSettingValue);
+ await umbracoUi.content.clickUpdateButton();
+
+ // Assert
+ await umbracoUi.content.isBlockCustomViewVisible(blockCustomViewLocator);
+ await umbracoUi.content.doesBlockCustomViewHaveValue(blockCustomViewLocator, updatedValueText);
+});
\ No newline at end of file