From 3cfeac94a4a51087f291baa38c56697890b38ff4 Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 27 Jun 2022 14:41:59 +0200 Subject: [PATCH] V10: Fix error when opening recycle bin (#12619) * Make object type parameter nullable in configuration editor * Add delete from recycle bin test * Clean up after test --- .../ContentApps/ListViewContentAppFactory.cs | 11 ++- .../ContentEditing/ContentPropertyDisplay.cs | 5 ++ .../PropertyEditors/IConfigurationEditor.cs | 5 ++ .../cypress/integration/Content/recycleBin.ts | 78 +++++++++++++++++++ 4 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/recycleBin.ts diff --git a/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs index 466c9d7a3b..87c755fdc9 100644 --- a/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.PropertyEditors; @@ -80,8 +80,7 @@ public class ListViewContentAppFactory : IContentAppFactory throw new NullReferenceException("The property editor with alias " + dt.EditorAlias + " does not exist"); } - IDictionary listViewConfig = - editor.GetConfigurationEditor().ToConfigurationEditor(dt.Configuration); + IDictionary listViewConfig = editor.GetConfigurationEditor().ToConfigurationEditorNullable(dt.Configuration); // add the entity type to the config listViewConfig["entityType"] = entityType; @@ -90,7 +89,7 @@ public class ListViewContentAppFactory : IContentAppFactory if (listViewConfig.ContainsKey("tabName")) { var configTabName = listViewConfig["tabName"]; - if (string.IsNullOrWhiteSpace(configTabName.ToString()) == false) + if (string.IsNullOrWhiteSpace(configTabName?.ToString()) == false) { contentApp.Name = configTabName.ToString(); } @@ -100,7 +99,7 @@ public class ListViewContentAppFactory : IContentAppFactory if (listViewConfig.ContainsKey("icon")) { var configIcon = listViewConfig["icon"]; - if (string.IsNullOrWhiteSpace(configIcon.ToString()) == false) + if (string.IsNullOrWhiteSpace(configIcon?.ToString()) == false) { contentApp.Icon = configIcon.ToString(); } @@ -123,7 +122,7 @@ public class ListViewContentAppFactory : IContentAppFactory Value = null, View = editor.GetValueEditor().View, HideLabel = true, - Config = listViewConfig, + ConfigNullable = listViewConfig, }, }; diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs index ca8c2f1fc2..d0f2b9aed6 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs @@ -26,9 +26,14 @@ public class ContentPropertyDisplay : ContentPropertyBasic [Required(AllowEmptyStrings = false)] public string? View { get; set; } + [Obsolete("The value type parameter of the dictionary will be made nullable in V11, use ConfigNullable instead.")] [DataMember(Name = "config")] public IDictionary? Config { get; set; } + // TODO: Obsolete in V11. + [IgnoreDataMember] + public IDictionary? ConfigNullable { get => Config!; set => Config = value!; } + [DataMember(Name = "hideLabel")] public bool HideLabel { get; set; } diff --git a/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs index d61dcd0e98..cbcb945c77 100644 --- a/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs @@ -73,8 +73,13 @@ public interface IConfigurationEditor /// Converts the configuration object to values for the configuration editor. /// /// The configuration. + [Obsolete("The value type parameter of the dictionary will be made nullable in V11, use ToConfigurationEditorNullable.")] IDictionary ToConfigurationEditor(object? configuration); + // TODO: Obsolete in V11. + IDictionary ToConfigurationEditorNullable(object? configuration) => + ToConfigurationEditor(configuration)!; + /// /// Converts the configuration object to values for the value editor. /// diff --git a/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/recycleBin.ts b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/recycleBin.ts new file mode 100644 index 0000000000..292d54acc0 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/recycleBin.ts @@ -0,0 +1,78 @@ +/// +import { + ContentBuilder, + DocumentTypeBuilder, +} from 'umbraco-cypress-testhelpers'; + +context('Recycle bin', () => { + + beforeEach(() => { + cy.umbracoLogin(Cypress.env('username'), Cypress.env('password')); + }); + + function refreshContentTree() { + // Refresh to update the tree + cy.get('li .umb-tree-root:contains("Content")').should("be.visible").rightclick(); + cy.umbracoContextMenuAction("action-refreshNode").click(); + // We have to wait in case the execution is slow, otherwise we'll try and click the item before it appears in the UI + cy.get('.umb-tree-item__inner').should('exist', { timeout: 10000 }); + } + + it('Can delete content from recycle bin', () => { + const contentToDeleteName = "DeleteMe"; + const contentToNotDeleteName = "DontDelete"; + const testType = "TestType"; + + cy.umbracoEnsureDocumentTypeNameNotExists(testType); + cy.deleteAllContent(); + + const docType = new DocumentTypeBuilder() + .withName(testType) + .build(); + + cy.saveDocumentType(docType).then((savedDocType) => { + const contentToDelete = new ContentBuilder() + .withContentTypeAlias(savedDocType.alias) + .withAction("saveNew") + .addVariant() + .withName(contentToDeleteName) + .withSave(true) + .done() + .build(); + + const contentToNotDelete = new ContentBuilder() + .withContentTypeAlias(savedDocType.alias) + .withAction("saveNew") + .addVariant() + .withName(contentToNotDeleteName) + .withSave(true) + .done() + .build(); + + // Put it in the recycle bin + cy.saveContent(contentToDelete).then(savedToDelete => { + cy.deleteContentById(savedToDelete.id); + }); + cy.saveContent(contentToNotDelete).then(savedNotToDelete => { + cy.deleteContentById(savedNotToDelete.id) + }); + }); + + refreshContentTree(); + cy.umbracoTreeItem('content', ["Recycle Bin"]).click(); + cy.get('.umb-content-grid__content').contains(contentToDeleteName).closest('div').click(); + cy.umbracoButtonByLabelKey('actions_delete').click(); + cy.umbracoButtonByLabelKey('contentTypeEditor_yesDelete').click(); + + cy.umbracoSuccessNotification().should('be.visible'); + + cy.get('.umb-content-grid__content').contains(contentToDeleteName).should('not.exist'); + cy.umbracoTreeItem('content', ["Recycle Bin", contentToDeleteName]).should('not.exist'); + + cy.get('.umb-content-grid__content').contains(contentToNotDeleteName).should('be.visible'); + cy.umbracoTreeItem('content', ["Recycle Bin", contentToNotDeleteName]).should('be.visible'); + + cy.deleteAllContent(); + cy.umbracoEnsureDocumentTypeNameNotExists(testType); + }); +});