Esempio n. 1
0
        public void Create_CreatesFlexiSectionBlockAndUpdatesOpenFlexiSectionBlocks()
        {
            // Arrange
            var          dummyLine                                  = new StringSlice("dummyLine", 4, 8);
            const int    dummyColumn                                = 4;
            const string dummyBlockName                             = "dummyBlockName";
            const string dummyResolvedBlockName                     = "dummyResolvedBlockName";
            const SectioningContentElement dummyElement             = SectioningContentElement.Aside;
            const string dummyLinkIcon                              = "dummyLinkIcon";
            const FlexiSectionBlockRenderingMode dummyRenderingMode = FlexiSectionBlockRenderingMode.Classic;
            const int dummyLevel      = 5;
            var       dummyAttributes = new ReadOnlyDictionary <string, string>(new Dictionary <string, string>());
            Mock <IFlexiSectionBlockOptions> dummyFlexiSectionBlockOptions = _mockRepository.Create <IFlexiSectionBlockOptions>();

            dummyFlexiSectionBlockOptions.Setup(f => f.BlockName).Returns(dummyBlockName);
            dummyFlexiSectionBlockOptions.Setup(f => f.Element).Returns(dummyElement);
            dummyFlexiSectionBlockOptions.Setup(f => f.RenderingMode).Returns(dummyRenderingMode);
            dummyFlexiSectionBlockOptions.Setup(f => f.LinkIcon).Returns(dummyLinkIcon);
            dummyFlexiSectionBlockOptions.Setup(f => f.Attributes).Returns(dummyAttributes);
            Mock <BlockParser> dummyBlockParser    = _mockRepository.Create <BlockParser>();
            BlockProcessor     dummyBlockProcessor = MarkdigTypesFactory.CreateBlockProcessor();

            dummyBlockProcessor.Column = dummyColumn;
            dummyBlockProcessor.Line   = dummyLine;
            var dummyFlexiSectionHeadingBlock = new FlexiSectionHeadingBlock(null);
            Mock <IFlexiSectionHeadingBlockFactory> mockFlexiSectionHeadingBlockFactory = _mockRepository.Create <IFlexiSectionHeadingBlockFactory>();

            mockFlexiSectionHeadingBlockFactory.
            Setup(f => f.Create(dummyBlockProcessor, dummyFlexiSectionBlockOptions.Object, dummyBlockParser.Object)).
            Returns(dummyFlexiSectionHeadingBlock);
            Mock <IOptionsService <IFlexiSectionBlockOptions, IFlexiSectionBlocksExtensionOptions> > mockOptionsService = _mockRepository.Create <IOptionsService <IFlexiSectionBlockOptions, IFlexiSectionBlocksExtensionOptions> >();

            mockOptionsService.Setup(o => o.CreateOptions(dummyBlockProcessor)).Returns((dummyFlexiSectionBlockOptions.Object, null));
            Mock <FlexiSectionBlockFactory> mockTestSubject = CreateMockFlexiSectionBlockFactory(mockOptionsService.Object, mockFlexiSectionHeadingBlockFactory.Object);

            mockTestSubject.CallBase = true;
            mockTestSubject.Setup(t => t.ValidateLevel(dummyLevel));
            mockTestSubject.Setup(t => t.ResolveBlockName(dummyBlockName)).Returns(dummyResolvedBlockName);
            mockTestSubject.Setup(t => t.ValidateElement(dummyElement));
            mockTestSubject.Setup(t => t.ValidateRenderingMode(dummyRenderingMode));
            mockTestSubject.Setup(t => t.UpdateOpenFlexiSectionBlocks(dummyBlockProcessor, It.IsAny <FlexiSectionBlock>()));

            // Act
            FlexiSectionBlock result = mockTestSubject.Object.Create(dummyLevel, dummyBlockProcessor, dummyBlockParser.Object);

            // Assert
            _mockRepository.VerifyAll();
            Assert.Equal(dummyResolvedBlockName, result.BlockName);
            Assert.Equal(dummyElement, result.Element);
            Assert.Equal(dummyLinkIcon, result.LinkIcon);
            Assert.Equal(dummyRenderingMode, result.RenderingMode);
            Assert.Equal(dummyLevel, result.Level);
            Assert.Same(dummyAttributes, result.Attributes);
            Assert.Same(dummyBlockParser.Object, result.Parser);
            Assert.Equal(dummyColumn, result.Column);
            Assert.Equal(dummyLine.Start, result.Span.Start);
            Assert.Equal(dummyLine.End, result.Span.End);
            Assert.Single(result);
            Assert.Same(dummyFlexiSectionHeadingBlock, result[0]);
        }
        public static IEnumerable <object[]> FlexiSectionBlockOptions_CanBePopulated_Data()
        {
            const string dummyBlockName = "dummyBlockName";
            const SectioningContentElement dummyElement = SectioningContentElement.Aside;
            const bool   dummyGenerateID        = false;
            const string dummyLinkIcon          = "dummyLinkIcon";
            const bool   dummyReferenceLinkable = false;
            const FlexiSectionBlockRenderingMode dummyRenderingMode = FlexiSectionBlockRenderingMode.Classic;
            const string dummyAttribute1      = "dummyAttribute1";
            const string dummyAttributeValue1 = "dummyAttributeValue1";
            var          dummyAttributes1     = new Dictionary <string, string> {
                { dummyAttribute1, dummyAttributeValue1 }
            };
            const string dummyAttribute2      = "dummyAttribute2";
            const string dummyAttributeValue2 = "dummyAttributeValue2";
            var          dummyAttributes2     = new Dictionary <string, string> {
                { dummyAttribute2, dummyAttributeValue2 }
            };

            return(new object[][]
            {
                // Populating FlexiSectionBlockOptions containing default values
                new object[]
                {
                    new SerializableWrapper <FlexiSectionBlockOptions>(new FlexiSectionBlockOptions()),
                    new SerializableWrapper <FlexiSectionBlockOptions>(new FlexiSectionBlockOptions(dummyBlockName,
                                                                                                    dummyElement,
                                                                                                    dummyGenerateID,
                                                                                                    dummyLinkIcon,
                                                                                                    dummyReferenceLinkable,
                                                                                                    dummyRenderingMode,
                                                                                                    dummyAttributes1)),
                    $@"{{
    ""{nameof(FlexiSectionBlockOptions.BlockName)}"": ""{dummyBlockName}"",
    ""{nameof(FlexiSectionBlockOptions.Element)}"": ""{dummyElement}"",
    ""{nameof(FlexiSectionBlockOptions.GenerateID)}"": ""{dummyGenerateID}"",
    ""{nameof(FlexiSectionBlockOptions.LinkIcon)}"": ""{dummyLinkIcon}"",
    ""{nameof(FlexiSectionBlockOptions.ReferenceLinkable)}"": ""{dummyReferenceLinkable}"",
    ""{nameof(FlexiSectionBlockOptions.RenderingMode)}"": ""{dummyRenderingMode}"",
    ""{nameof(FlexiSectionBlockOptions.Attributes)}"": {{
        ""{dummyAttribute1}"": ""{dummyAttributeValue1}""
    }}
}}"
                },

                // Populating FlexiSectionBlockOptions with an existing attributes collection (should be replaced instead of appended to)
                new object[]
                {
                    new SerializableWrapper <FlexiSectionBlockOptions>(new FlexiSectionBlockOptions(attributes: dummyAttributes1)),
                    new SerializableWrapper <FlexiSectionBlockOptions>(new FlexiSectionBlockOptions(attributes: dummyAttributes2)),
                    $@"{{
    ""{nameof(FlexiSectionBlockOptions.Attributes)}"": {{
        ""{dummyAttribute2}"": ""{dummyAttributeValue2}""
    }}
}}"
                }
            });
        }
 private static FlexiSectionBlock CreateFlexiSectionBlock(string blockName = default,
                                                          SectioningContentElement element = default,
                                                          string linkIcon = default,
                                                          FlexiSectionBlockRenderingMode renderingMode = default,
                                                          int level = default,
                                                          ReadOnlyDictionary <string, string> attributes = default,
                                                          BlockParser blockParser = default)
 {
     return(new FlexiSectionBlock(blockName, element, linkIcon, renderingMode, level, attributes, blockParser));
 }
Esempio n. 4
0
 internal virtual void ValidateElement(SectioningContentElement element)
 {
     if (!Enum.IsDefined(typeof(SectioningContentElement), element))
     {
         throw new OptionsException(nameof(IFlexiSectionBlockOptions.Element),
                                    string.Format(Strings.OptionsException_Shared_ValueMustBeAValidEnumValue,
                                                  element,
                                                  nameof(SectioningContentElement)));
     }
 }
Esempio n. 5
0
 /// <summary>
 /// Creates a <see cref="FlexiSectionBlock"/>.
 /// </summary>
 /// <param name="blockName">The <see cref="FlexiSectionBlock"/>'s BEM block name.</param>
 /// <param name="element">The <see cref="FlexiSectionBlock"/> root element type.</param>
 /// <param name="linkIcon">The <see cref="FlexiSectionBlock"/>'s link icon as an HTML fragment.</param>
 /// <param name="renderingMode">The <see cref="FlexiSectionBlock"/>'s rendering mode.</param>
 /// <param name="level">The <see cref="FlexiSectionBlock"/>'s level.</param>
 /// <param name="attributes">The HTML attributes for the <see cref="FlexiSectionBlock"/>'s root element.</param>
 /// <param name="blockParser">The <see cref="BlockParser"/> parsing the <see cref="FlexiSectionBlock"/>.</param>
 public FlexiSectionBlock(string blockName,
                          SectioningContentElement element,
                          string linkIcon,
                          FlexiSectionBlockRenderingMode renderingMode,
                          int level,
                          ReadOnlyDictionary <string, string> attributes,
                          BlockParser blockParser) : base(blockParser)
 {
     BlockName     = blockName;
     Element       = element;
     LinkIcon      = linkIcon;
     RenderingMode = renderingMode;
     Level         = level;
     Attributes    = attributes;
 }
Esempio n. 6
0
        public void ValidateElement_ThrowsOptionsExceptionIfElementIsInvalid()
        {
            // Arrange
            FlexiSectionBlockFactory       testSubject  = CreateFlexiSectionBlockFactory();
            const SectioningContentElement dummyElement = (SectioningContentElement)9;

            // Act and assert
            OptionsException result = Assert.Throws <OptionsException>(() => testSubject.ValidateElement(dummyElement));

            Assert.Equal(string.Format(Strings.OptionsException_OptionsException_InvalidOption,
                                       nameof(IFlexiSectionBlockOptions.Element),
                                       string.Format(Strings.OptionsException_Shared_ValueMustBeAValidEnumValue, dummyElement,
                                                     nameof(SectioningContentElement))),
                         result.Message);
        }
Esempio n. 7
0
 /// <summary>
 /// Creates a <see cref="FlexiSectionBlockOptions"/>.
 /// </summary>
 /// <param name="blockName">
 /// <para>The <see cref="FlexiSectionBlock"/>'s <a href="https://en.bem.info/methodology/naming-convention/#block-name">BEM block name</a>.</para>
 /// <para>In compliance with <a href="https://en.bem.info">BEM methodology</a>, this value is the <see cref="FlexiSectionBlock"/>'s root element's class as well as the prefix for all other classes in the block.</para>
 /// <para>This value should contain only valid <a href="https://www.w3.org/TR/CSS21/syndata.html#characters">CSS class characters</a>.</para>
 /// <para>If this value is <c>null</c>, whitespace or an empty string, the <see cref="FlexiSectionBlock"/>'s block name is "flexi-section".</para>
 /// <para>Defaults to "flexi-section".</para>
 /// </param>
 /// <param name="element">
 /// <para>The <see cref="FlexiSectionBlock"/>'s root element's type.</para>
 /// <para>The element must be a <a href="https://html.spec.whatwg.org/#sectioning-content">sectioning content</a> element.</para>
 /// <para>Defaults to <see cref="SectioningContentElement.Section"/>.</para>
 /// </param>
 /// <param name="generateID">
 /// <para>The value specifying whether to generate an ID for the <see cref="FlexiSectionBlock"/>.</para>
 /// <para>The generated ID is assigned to the <see cref="FlexiSectionBlock"/>'s root element.</para>
 /// <para>The generated ID is the <see cref="FlexiSectionBlock"/>'s heading content in kebab-case (lowercase words joined by dashes).
 /// For example, if the heading content is "Foo Bar Baz", the generated ID is "foo-bar-baz".</para>
 /// <para>If the generated ID is a duplicate of another <see cref="FlexiSectionBlock"/>'s ID, "-&lt;duplicate index&gt;" is appended.
 /// For example, the second <see cref="FlexiSectionBlock"/> with heading content "Foo Bar Baz" will have ID "foo-bar-baz-1".</para>
 /// <para>The generated ID precedence over any ID specified in <paramref name="attributes"/>.</para>
 /// <para>Defaults to true.</para>
 /// </param>
 /// <param name="linkIcon">
 /// <para>The <see cref="FlexiSectionBlock"/>'s link icon as an HTML fragment.</para>
 /// <para>A class attribute with value "&lt;<paramref name="blockName"/>&gt;__link-icon" is added to this fragment's first start tag.</para>
 /// <para>If this value is <c>null</c>, whitespace or an empty string, no link icon is rendered.</para>
 /// <para>Defaults to the <a href="https://material.io/tools/icons/?icon=link&amp;style=baseline">Material Design link icon</a>.</para>
 /// </param>
 /// <param name="referenceLinkable">
 /// <para>The value specifying whether the <see cref="FlexiSectionBlock"/> is <a href="https://spec.commonmark.org/0.28/#reference-link">reference-linkable</a>.</para>
 /// <para>If this value and <paramref name="generateID"/> are both true, the <see cref="FlexiSectionBlock"/> is reference-linkable.
 /// Otherwise, it isn't.</para>
 /// <para>If a <see cref="FlexiSectionBlock"/> is reference-linkable, its <a href="https://spec.commonmark.org/0.28/#link-label">link label</a> content
 /// is its heading content. For example, "## Foo Bar Baz" can be linked to using "[Foo Bar Baz]".</para>
 /// <para>If a <see cref="FlexiSectionBlock"/>'s ID has "-&lt;duplicate index&gt;" appended (see <paramref name="generateID"/>),
 /// you can link to it using "&lt;heading content&gt; &lt;duplicate index&gt;".
 /// For example, the second "## Foo Bar baz" can be linked to using "[Foo Bar Baz 1]".</para>
 /// <para>Defaults to true.</para>
 /// </param>
 /// <param name="renderingMode">
 /// <para>The <see cref="FlexiSectionBlock"/>'s rendering mode.</para>
 /// <para>Defaults to <see cref="FlexiSectionBlockRenderingMode.Standard"/>.</para>
 /// </param>
 /// <param name="attributes">
 /// <para>The HTML attributes for the <see cref="FlexiSectionBlock"/>'s root element.</para>
 /// <para>Attribute names must be lowercase.</para>
 /// <para>If classes are specified, they are appended to default classes. This facilitates <a href="https://en.bem.info/methodology/quick-start/#mix">BEM mixes</a>.</para>
 /// <para>If the <see cref="FlexiSectionBlock"/> has a generated ID, it takes precedence over any ID in this value.</para>
 /// <para>If this value is <c>null</c>, default classes are still assigned to the root element.</para>
 /// <para>Defaults to <c>null</c>.</para>
 /// </param>
 public FlexiSectionBlockOptions(
     string blockName = "flexi-section",
     SectioningContentElement element = SectioningContentElement.Section,
     bool generateID        = true,
     string linkIcon        = MaterialDesignIcons.MATERIAL_DESIGN_LINK,
     bool referenceLinkable = true,
     FlexiSectionBlockRenderingMode renderingMode = FlexiSectionBlockRenderingMode.Standard,
     IDictionary <string, string> attributes      = default) : base(blockName, attributes)
 {
     Element           = element;
     GenerateID        = generateID;
     LinkIcon          = linkIcon;
     ReferenceLinkable = referenceLinkable;
     RenderingMode     = renderingMode;
 }
Esempio n. 8
0
        /// <inheritdoc />
        public FlexiSectionBlock Create(int level, BlockProcessor blockProcessor, BlockParser blockParser)
        {
            (IFlexiSectionBlockOptions flexiSectionBlockOptions, IFlexiSectionBlocksExtensionOptions _) = _optionsService.CreateOptions(blockProcessor);

            // Level
            ValidateLevel(level);

            // Block name
            string blockName = ResolveBlockName(flexiSectionBlockOptions.BlockName);

            // Element
            SectioningContentElement element = flexiSectionBlockOptions.Element;

            ValidateElement(element);

            // Rendering mode
            FlexiSectionBlockRenderingMode renderingMode = flexiSectionBlockOptions.RenderingMode;

            ValidateRenderingMode(renderingMode);

            // Create FlexiSectionBlock
            var flexiSectionBlock = new FlexiSectionBlock(blockName,
                                                          element,
                                                          flexiSectionBlockOptions.LinkIcon,
                                                          renderingMode,
                                                          level,
                                                          flexiSectionBlockOptions.Attributes,
                                                          blockParser)
            {
                Column = blockProcessor.Column,
                Span   = new SourceSpan(blockProcessor.Start, blockProcessor.Line.End), // TODO span should include children
                // BlockProcessor will assign Line
            };

            // Create FlexiSectionHeadingBlock
            FlexiSectionHeadingBlock flexiSectionHeadingBlock = _flexiSectionHeadingBlockFactory.Create(blockProcessor, flexiSectionBlockOptions, blockParser);

            flexiSectionBlock.Add(flexiSectionHeadingBlock);

            // Close FlexiSectionBlocks with same or lower levels
            UpdateOpenFlexiSectionBlocks(blockProcessor, flexiSectionBlock);

            return(flexiSectionBlock);
        }
        public static IEnumerable <object[]> WriteStandard_RendersStandardFlexiSectionBlock_Data()
        {
            const string dummyBlockName         = "dummyBlockName";
            const string dummyLinkIcon          = "<dummyLinkIcon></dummyLinkIcon>";
            const string dummyLinkIconWithClass = "<dummyLinkIcon class=\"__link-icon\"></dummyLinkIcon>";
            const string dummyAttributeKey1     = "dummyAttributeKey1";
            const string dummyAttributeValue1   = "dummyAttributeValue1";
            const string dummyAttributeKey2     = "dummyAttributeKey2";
            const string dummyAttributeValue2   = "dummyAttributeValue2";
            const string dummyClass             = "dummyClass";
            const string dummyID          = "dummyID";
            const string dummyGeneratedID = "dummyGeneratedID";
            const SectioningContentElement dummyElement = SectioningContentElement.Aside;
            const int    dummyLevel          = 2;
            const string dummyHeadingContent = "dummyHeadingContent";

            var dummyHeadingContainerInline = new ContainerInline();

            dummyHeadingContainerInline.AppendChild(new LiteralInline(dummyHeadingContent));
            FlexiSectionHeadingBlock dummyFlexiSectionHeadingBlockWithContent = CreateFlexiSectionHeadingBlock();

            dummyFlexiSectionHeadingBlockWithContent.Inline = dummyHeadingContainerInline;

            return(new object[][]
            {
                // BlockName is assigned as a class of the root element and all default classes are prepended with it
                new object[]
                {
                    CreateFlexiSectionBlock(dummyBlockName, level: dummyLevel),
                    CreateFlexiSectionHeadingBlock(),
                    $@"<section class=""{dummyBlockName} {dummyBlockName}_level_2 {dummyBlockName}_no-link-icon"">
<header class=""{dummyBlockName}__header"">
<h2 class=""{dummyBlockName}__heading""></h2>
<button class=""{dummyBlockName}__link-button"" aria-label=""Copy link"">
</button>
</header>
</section>
"
                },
                // If LinkIcon is valid HTML, it is rendered with a default class and a _has-link-icon class is rendered
                new object[]
                {
                    CreateFlexiSectionBlock(linkIcon: dummyLinkIcon, level: dummyLevel),
                    CreateFlexiSectionHeadingBlock(),
                    $@"<section class="" _level_2 _has-link-icon"">
<header class=""__header"">
<h2 class=""__heading""></h2>
<button class=""__link-button"" aria-label=""Copy link"">
{dummyLinkIconWithClass}
</button>
</header>
</section>
"
                },
                // If LinkIcon is null, whitespace or an empty string, no copy icon is rendered and a _no-link-icon class is rendered (null case verified by other tests)
                new object[]
                {
                    CreateFlexiSectionBlock(linkIcon: " ", level: dummyLevel),
                    CreateFlexiSectionHeadingBlock(),
                    @"<section class="" _level_2 _no-link-icon"">
<header class=""__header"">
<h2 class=""__heading""></h2>
<button class=""__link-button"" aria-label=""Copy link"">
</button>
</header>
</section>
"
                },
                new object[]
                {
                    CreateFlexiSectionBlock(linkIcon: string.Empty, level: dummyLevel),
                    CreateFlexiSectionHeadingBlock(),
                    @"<section class="" _level_2 _no-link-icon"">
<header class=""__header"">
<h2 class=""__heading""></h2>
<button class=""__link-button"" aria-label=""Copy link"">
</button>
</header>
</section>
"
                },
                // If an ID is generated, it is written
                new object[]
                {
                    CreateFlexiSectionBlock(level: dummyLevel),
                    CreateFlexiSectionHeadingBlock(generatedID: dummyGeneratedID),
                    $@"<section class="" _level_2 _no-link-icon"" id=""{dummyGeneratedID}"">
<header class=""__header"">
<h2 class=""__heading""></h2>
<button class=""__link-button"" aria-label=""Copy link"">
</button>
</header>
</section>
"
                },
                // If attributes specified, they're written
                new object[]
                {
                    CreateFlexiSectionBlock(level: dummyLevel,
                                            attributes: new ReadOnlyDictionary <string, string>(new Dictionary <string, string> {
                        { dummyAttributeKey1, dummyAttributeValue1 }, { dummyAttributeKey2, dummyAttributeValue2 }
                    })),
                    CreateFlexiSectionHeadingBlock(),
                    $@"<section class="" _level_2 _no-link-icon"" {dummyAttributeKey1}=""{dummyAttributeValue1}"" {dummyAttributeKey2}=""{dummyAttributeValue2}"">
<header class=""__header"">
<h2 class=""__heading""></h2>
<button class=""__link-button"" aria-label=""Copy link"">
</button>
</header>
</section>
"
                },
                // Class attribute value is appended to default classes
                new object[]
                {
                    CreateFlexiSectionBlock(level: dummyLevel,
                                            attributes: new ReadOnlyDictionary <string, string>(new Dictionary <string, string> {
                        { "class", dummyClass }
                    })),
                    CreateFlexiSectionHeadingBlock(),
                    $@"<section class="" _level_2 _no-link-icon {dummyClass}"">
<header class=""__header"">
<h2 class=""__heading""></h2>
<button class=""__link-button"" aria-label=""Copy link"">
</button>
</header>
</section>
"
                },
                // Generated ID takes precedence over any ID in attributes
                new object[]
                {
                    CreateFlexiSectionBlock(level: dummyLevel,
                                            attributes: new ReadOnlyDictionary <string, string>(new Dictionary <string, string> {
                        { "id", dummyID }
                    })),
                    CreateFlexiSectionHeadingBlock(generatedID: dummyGeneratedID),
                    $@"<section class="" _level_2 _no-link-icon"" id=""{dummyGeneratedID}"">
<header class=""__header"">
<h2 class=""__heading""></h2>
<button class=""__link-button"" aria-label=""Copy link"">
</button>
</header>
</section>
"
                },
                // Specified sectioning content element is rendered
                new object[]
                {
                    CreateFlexiSectionBlock(element: dummyElement, level: dummyLevel),
                    CreateFlexiSectionHeadingBlock(),
                    $@"<{dummyElement.ToString().ToLower()} class="" _level_2 _no-link-icon"">
<header class=""__header"">
<h2 class=""__heading""></h2>
<button class=""__link-button"" aria-label=""Copy link"">
</button>
</header>
</{dummyElement.ToString().ToLower()}>
"
                },
                // Header content is rendered
                new object[]
                {
                    CreateFlexiSectionBlock(level: dummyLevel),
                    dummyFlexiSectionHeadingBlockWithContent,
                    $@"<section class="" _level_2 _no-link-icon"">
<header class=""__header"">
<h2 class=""__heading"">{dummyHeadingContent}</h2>
<button class=""__link-button"" aria-label=""Copy link"">
</button>
</header>
</section>
"
                }
            });
        }