diff --git a/src/Education.UnitTests/Layout/Logical/ContextualRuleDescriptionTabTests.cs b/src/Education.UnitTests/Layout/Logical/ContextualRuleDescriptionTabTests.cs new file mode 100644 index 0000000000..8ed06a49c6 --- /dev/null +++ b/src/Education.UnitTests/Layout/Logical/ContextualRuleDescriptionTabTests.cs @@ -0,0 +1,141 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 3 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using System.Collections.Generic; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using SonarLint.VisualStudio.Education.Layout.Logical; +using SonarLint.VisualStudio.Education.Layout.Visual; +using SonarLint.VisualStudio.Education.Layout.Visual.Tabs; +using SonarLint.VisualStudio.Education.XamlGenerator; + +namespace SonarLint.VisualStudio.Education.UnitTests.Layout.Logical; + +[TestClass] +public class ContextualRuleDescriptionTabTests +{ + private const string Context1 = "context1"; + private const string ContextTabTitle1 = "contexttitle1"; + private const string ContextTabContent1 = "htmlcontent1"; + private const string ContextTabXamlContent1 = "xamlcontent1"; + private const string DefaultContextTabTitle = "default"; + private const string DefaultContext = "defaultcontext"; + private const string DefaultContextTabContent = "defaultcontent"; + private const string DefaultContextTabXamlContent = "defaultxamlcontent"; + private const string ContextTabTitle2 = "contexttitle2"; + private const string Context2 = "context2"; + private const string ContextTabContent2 = "htmlcontent2"; + private const string ContextTabXamlContent2 = "xamlcontent2"; + + [TestMethod] + public void Title_ReturnsCorrectTitle() + { + const string title = "title"; + + var testSubject = new ContextualRuleDescriptionTab(title, null, null); + + testSubject.Title.Should().BeSameAs(title); + } + + [TestMethod] + public void ProduceVisualNode_ReturnsCorrectStructure() + { + var testSubject = new ContextualRuleDescriptionTab("title", + DefaultContext, + GetContextTabs()); + var translatorMock = new Mock(); + SetupHtmlToXamlConversion(translatorMock); + + var visualNode = testSubject.ProduceVisualNode(new VisualizationParameters(translatorMock.Object, Context1)); + + visualNode.Should().BeEquivalentTo( + new TabGroup(new List + { + new TabItem(ContextTabTitle1, new ContentSection(ContextTabXamlContent1)), + new TabItem(DefaultContextTabTitle, new ContentSection(DefaultContextTabXamlContent)), + new TabItem(ContextTabTitle2, new ContentSection(ContextTabXamlContent2)), + }, 0)); + } + + [DataRow(Context1, 0)] + [DataRow(DefaultContext, 1)] + [DataRow(Context2, 2)] + [DataTestMethod] + public void ProduceVisualNode_PrefersSelectedContext(string context, int expectedIndex) + { + var testSubject = new ContextualRuleDescriptionTab("title", + DefaultContext, + GetContextTabs()); + var translatorMock = new Mock(); + SetupHtmlToXamlConversion(translatorMock); + + var visualNode = testSubject.ProduceVisualNode(new VisualizationParameters(translatorMock.Object, context)); + + visualNode.Should().BeOfType().Which.selectedTabIndex.Should().Be(expectedIndex); + } + + [TestMethod] + public void ProduceVisualNode_NoSelectedContext_FallsBackToDefaultContext() + { + var testSubject = new ContextualRuleDescriptionTab("title", + DefaultContext, + GetContextTabs()); + var translatorMock = new Mock(); + SetupHtmlToXamlConversion(translatorMock); + + var visualNode = testSubject.ProduceVisualNode(new VisualizationParameters(translatorMock.Object, null)); + + visualNode.Should().BeOfType().Which.selectedTabIndex.Should().Be(1); + } + + [TestMethod] + public void ProduceVisualNode_NoContextProvided_SelectsFirstTab() + { + string NOCONTEXT = null; + + var testSubject = new ContextualRuleDescriptionTab("title", + NOCONTEXT, + GetContextTabs()); + var translatorMock = new Mock(); + SetupHtmlToXamlConversion(translatorMock); + + var visualNode = testSubject.ProduceVisualNode(new VisualizationParameters(translatorMock.Object, NOCONTEXT)); + + visualNode.Should().BeOfType().Which.selectedTabIndex.Should().Be(0); + } + + private static List GetContextTabs() + { + return new List + { + new(ContextTabTitle1, Context1, ContextTabContent1), + new(DefaultContextTabTitle, DefaultContext, DefaultContextTabContent), + new(ContextTabTitle2, Context2, ContextTabContent2), + }; + } + + private static void SetupHtmlToXamlConversion(Mock translator) + { + translator.Setup(x => x.TranslateHtmlToXaml(ContextTabContent1)).Returns(ContextTabXamlContent1); + translator.Setup(x => x.TranslateHtmlToXaml(ContextTabContent2)).Returns(ContextTabXamlContent2); + translator.Setup(x => x.TranslateHtmlToXaml(DefaultContextTabContent)).Returns(DefaultContextTabXamlContent); + } +} diff --git a/src/Education.UnitTests/Layout/Logical/NonContextualRuleDescriptionTabTests.cs b/src/Education.UnitTests/Layout/Logical/NonContextualRuleDescriptionTabTests.cs new file mode 100644 index 0000000000..1615718942 --- /dev/null +++ b/src/Education.UnitTests/Layout/Logical/NonContextualRuleDescriptionTabTests.cs @@ -0,0 +1,62 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 3 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using SonarLint.VisualStudio.Education.Layout.Logical; +using SonarLint.VisualStudio.Education.Layout.Visual; +using SonarLint.VisualStudio.Education.XamlGenerator; + +namespace SonarLint.VisualStudio.Education.UnitTests.Layout.Logical; + +[TestClass] +public class NonContextualRuleDescriptionTabTests +{ + [TestMethod] + public void ProduceVisualNode_ReturnsSingleContentSection() + { + const string contentHtml = "contenthtml"; + const string contentXaml = "contentxaml"; + var translatorMock = new Mock(); + translatorMock.Setup(x => x.TranslateHtmlToXaml(contentHtml)).Returns(contentXaml); + var parameters = new VisualizationParameters(translatorMock.Object, "context"); + + var testSubject = new NonContextualRuleDescriptionTab("title", contentHtml); + + + var visualNode = testSubject.ProduceVisualNode(parameters); + + + visualNode.Should().BeEquivalentTo(new ContentSection(contentXaml)); + translatorMock.Verify(x => x.TranslateHtmlToXaml(contentHtml), Times.Once); + translatorMock.VerifyNoOtherCalls(); + } + + [TestMethod] + public void Title_ReturnsCorrectTitle() + { + const string title = "title"; + + var testSubject = new NonContextualRuleDescriptionTab(title, "contenthtml"); + + testSubject.Title.Should().BeSameAs(title); + } +} diff --git a/src/Education.UnitTests/Layout/Logical/RuleSplitDescriptionTests.cs b/src/Education.UnitTests/Layout/Logical/RuleSplitDescriptionTests.cs new file mode 100644 index 0000000000..873d53db86 --- /dev/null +++ b/src/Education.UnitTests/Layout/Logical/RuleSplitDescriptionTests.cs @@ -0,0 +1,62 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 3 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using System.Collections.Generic; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using SonarLint.VisualStudio.Education.Layout.Logical; +using SonarLint.VisualStudio.Education.Layout.Visual; +using SonarLint.VisualStudio.Education.Layout.Visual.Tabs; +using SonarLint.VisualStudio.Education.XamlGenerator; + +namespace SonarLint.VisualStudio.Education.UnitTests.Layout.Logical; + +[TestClass] +public class RuleSplitDescriptionTests +{ + [TestMethod] + public void ProduceVisualNode_ProducesMultiBlockSectionWithIntroAndTabs() + { + const string introHtml = "introhtml"; + const string introXaml = "introxaml"; + const string tabTitle = "tabTitle"; + var translatorMock = new Mock(); + translatorMock.Setup(x => x.TranslateHtmlToXaml(introHtml)).Returns(introXaml); + var parameters = new VisualizationParameters(translatorMock.Object, "context"); + var tabMock = new Mock(); + tabMock.SetupGet(x => x.Title).Returns(tabTitle); + var tabVisualNodeMock = new Mock(); + tabMock.Setup(x => x.ProduceVisualNode(parameters)).Returns(tabVisualNodeMock.Object); + + var testSubject = new RuleSplitDescription(introHtml, new List { tabMock.Object }); + + + var visualNode = testSubject.ProduceVisualNode(parameters); + + + visualNode.Should().BeEquivalentTo( + new MultiBlockSection( + new ContentSection(introXaml), + new TabGroup(new List{new TabItem(tabTitle, tabVisualNodeMock.Object)}, 0))); + translatorMock.Verify(x => x.TranslateHtmlToXaml(introHtml), Times.Once); + translatorMock.VerifyNoOtherCalls(); + } +} diff --git a/src/Education/Education.csproj b/src/Education/Education.csproj index 0baddb0594..58d70eb5cc 100644 --- a/src/Education/Education.csproj +++ b/src/Education/Education.csproj @@ -9,6 +9,7 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + 8 SonarLint.VisualStudio.Education diff --git a/src/Education/Layout/Logical/ContextualRuleDescriptionTab.cs b/src/Education/Layout/Logical/ContextualRuleDescriptionTab.cs new file mode 100644 index 0000000000..795f16266f --- /dev/null +++ b/src/Education/Layout/Logical/ContextualRuleDescriptionTab.cs @@ -0,0 +1,89 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 3 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using SonarLint.VisualStudio.Education.Layout.Visual; +using SonarLint.VisualStudio.Education.Layout.Visual.Tabs; + +namespace SonarLint.VisualStudio.Education.Layout.Logical +{ + internal class ContextualRuleDescriptionTab : IRuleDescriptionTab + { + private readonly List contexts; + private readonly string defaultContext; + + public ContextualRuleDescriptionTab(string title, string defaultContext, List contexts) + { + Title = title; + this.contexts = contexts; + this.defaultContext = defaultContext; + } + + public string Title { get; } + + public IAbstractVisualizationTreeNode ProduceVisualNode(VisualizationParameters parameters) + { + var contextTabs = contexts + .Select(x => new TabItem(x.Title, + new ContentSection(parameters.HtmlToXamlTranslator.TranslateHtmlToXaml(x.HtmlContent)))) + .ToList(); + + return new TabGroup(contextTabs, GetSelectedTabIndex(parameters.RelevantContext)); + } + + private int GetSelectedTabIndex(string contextToDisplay) + { + var selectedIndex = FindContextIndex(contextToDisplay); + + if (selectedIndex == -1) + { + selectedIndex = FindContextIndex(defaultContext); + } + + return Math.Max(selectedIndex, 0); + } + + private int FindContextIndex(string context) + { + if (context == null) + { + return -1; + } + + return contexts.FindIndex(x => x.ContextKey.Equals(context)); + } + + internal class ContextContentTab + { + public ContextContentTab(string title, string contextKey, string htmlContent) + { + Title = title; + ContextKey = contextKey; + HtmlContent = htmlContent; + } + + public string Title { get; } + public string ContextKey { get; } + public string HtmlContent { get; } + } + } +} diff --git a/src/Education/Layout/Logical/IRuleDescriptionTab.cs b/src/Education/Layout/Logical/IRuleDescriptionTab.cs new file mode 100644 index 0000000000..0741868630 --- /dev/null +++ b/src/Education/Layout/Logical/IRuleDescriptionTab.cs @@ -0,0 +1,27 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 3 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace SonarLint.VisualStudio.Education.Layout.Logical +{ + internal interface IRuleDescriptionTab : IVisualNodeProducer + { + string Title { get; } + } +} diff --git a/src/Education/Layout/Logical/IVisualNodeProducer.cs b/src/Education/Layout/Logical/IVisualNodeProducer.cs index b688335201..e47a3d57d7 100644 --- a/src/Education/Layout/Logical/IVisualNodeProducer.cs +++ b/src/Education/Layout/Logical/IVisualNodeProducer.cs @@ -27,6 +27,12 @@ namespace SonarLint.VisualStudio.Education.Layout.Logical [ExcludeFromCodeCoverage] internal class VisualizationParameters { + public VisualizationParameters(IRuleHelpXamlTranslator htmlToXamlTranslator, string relevantContext) + { + HtmlToXamlTranslator = htmlToXamlTranslator; + RelevantContext = relevantContext; + } + public IRuleHelpXamlTranslator HtmlToXamlTranslator { get; } public string RelevantContext { get; } } diff --git a/src/Education/Layout/Logical/NonContextualRuleDescriptionTab.cs b/src/Education/Layout/Logical/NonContextualRuleDescriptionTab.cs new file mode 100644 index 0000000000..adb5cee61f --- /dev/null +++ b/src/Education/Layout/Logical/NonContextualRuleDescriptionTab.cs @@ -0,0 +1,42 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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 3 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using SonarLint.VisualStudio.Education.Layout.Visual; + +namespace SonarLint.VisualStudio.Education.Layout.Logical +{ + internal class NonContextualRuleDescriptionTab : IRuleDescriptionTab + { + private readonly string htmlContent; + + public NonContextualRuleDescriptionTab(string title, string htmlContent) + { + Title = title; + this.htmlContent = htmlContent; + } + + public string Title { get; } + + public IAbstractVisualizationTreeNode ProduceVisualNode(VisualizationParameters parameters) + { + return new ContentSection(parameters.HtmlToXamlTranslator.TranslateHtmlToXaml(htmlContent)); + } + } +} diff --git a/src/Education/Layout/Logical/RuleSplitDescription.cs b/src/Education/Layout/Logical/RuleSplitDescription.cs index 83abe469f9..b1fa0ebfd4 100644 --- a/src/Education/Layout/Logical/RuleSplitDescription.cs +++ b/src/Education/Layout/Logical/RuleSplitDescription.cs @@ -19,12 +19,12 @@ */ using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; +using System.Linq; using SonarLint.VisualStudio.Education.Layout.Visual; +using SonarLint.VisualStudio.Education.Layout.Visual.Tabs; namespace SonarLint.VisualStudio.Education.Layout.Logical { - [ExcludeFromCodeCoverage] internal class RuleSplitDescription : IVisualNodeProducer { private readonly string introductionHtml; @@ -38,66 +38,9 @@ public RuleSplitDescription(string introductionHtml, List t public IAbstractVisualizationTreeNode ProduceVisualNode(VisualizationParameters parameters) { - throw new System.NotImplementedException(); - } - } - - internal interface IRuleDescriptionTab : IVisualNodeProducer - { - string Title { get; } - } - - [ExcludeFromCodeCoverage] - internal class NonContextualRuleDescriptionTab : IRuleDescriptionTab - { - private readonly string htmlContent; - - public NonContextualRuleDescriptionTab(string title, string htmlContent) - { - Title = title; - this.htmlContent = htmlContent; - } - - public string Title { get; } - - public IAbstractVisualizationTreeNode ProduceVisualNode(VisualizationParameters parameters) - { - throw new System.NotImplementedException(); - } - } - - [ExcludeFromCodeCoverage] - internal class ContextualRuleDescriptionTab : IRuleDescriptionTab - { - private readonly List contexts; - private readonly string defaultContext; - - public ContextualRuleDescriptionTab(string title, string defaultContext, List contexts) - { - Title = title; - this.contexts = contexts; - this.defaultContext = defaultContext; - } - - public string Title { get; set; } - - public IAbstractVisualizationTreeNode ProduceVisualNode(VisualizationParameters parameters) - { - throw new System.NotImplementedException(); - } - - internal class ContextContentTab - { - public ContextContentTab(string title, string contextKey, string htmlContent) - { - Title = title; - ContextKey = contextKey; - HtmlContent = htmlContent; - } - - public string Title { get; } - public string ContextKey { get; } - public string HtmlContent { get; } + return new MultiBlockSection( + new ContentSection(parameters.HtmlToXamlTranslator.TranslateHtmlToXaml(introductionHtml)), + new TabGroup(tabs.Select(x => new TabItem(x.Title, x.ProduceVisualNode(parameters))).Cast().ToList(), 0)); } } } diff --git a/src/Education/Layout/Visual/Tabs/TabItem.cs b/src/Education/Layout/Visual/Tabs/TabItem.cs index b7f3b936b4..0c97b25e44 100644 --- a/src/Education/Layout/Visual/Tabs/TabItem.cs +++ b/src/Education/Layout/Visual/Tabs/TabItem.cs @@ -34,7 +34,7 @@ internal class TabItem // NOTE: this does not implement IAbstractVisualizationTr : ITabItem { internal /* for testing */ readonly IAbstractVisualizationTreeNode content; - private readonly string displayName; + internal /* for testing */ readonly string displayName; public TabItem(string displayName, IAbstractVisualizationTreeNode content) {