public void Create_CreatesFlexiTableBlock()
        {
            // Arrange
            const string         dummyBlockName         = "dummyBlockName";
            const string         dummyResolvedBlockName = "dummyResolvedBlockName";
            const FlexiTableType dummyType           = FlexiTableType.FixedTitles;
            BlockProcessor       dummyBlockProcessor = MarkdigTypesFactory.CreateBlockProcessor();
            var dummyAttributes = new ReadOnlyDictionary <string, string>(new Dictionary <string, string>());
            Mock <IFlexiTableBlockOptions> mockFlexiTableBlockOptions = _mockRepository.Create <IFlexiTableBlockOptions>();

            mockFlexiTableBlockOptions.Setup(f => f.BlockName).Returns(dummyBlockName);
            mockFlexiTableBlockOptions.Setup(f => f.Type).Returns(dummyType);
            mockFlexiTableBlockOptions.Setup(f => f.Attributes).Returns(dummyAttributes);
            Mock <IOptionsService <IFlexiTableBlockOptions, IFlexiTableBlocksExtensionOptions> > mockOptionsService = _mockRepository.
                                                                                                                      Create <IOptionsService <IFlexiTableBlockOptions, IFlexiTableBlocksExtensionOptions> >();

            mockOptionsService.Setup(f => f.CreateOptions(dummyBlockProcessor)).
            Returns((mockFlexiTableBlockOptions.Object, null));
            var             dummyProxyTableBlock          = new ProxyTableBlock(null);
            FlexiTableBlock dummyFlexiTableBlock          = CreateFlexiTableBlock();
            Mock <FlexiTableBlockFactory> mockTestSubject = CreateMockFlexiTableBlockFactory(mockOptionsService.Object);

            mockTestSubject.CallBase = true;
            mockTestSubject.Setup(t => t.ResolveBlockName(dummyBlockName)).Returns(dummyResolvedBlockName);
            mockTestSubject.Setup(t => t.ValidateType(dummyType));
            mockTestSubject.Setup(t => t.CreateFlexiTableBlock(dummyResolvedBlockName, dummyType, dummyAttributes, dummyProxyTableBlock, dummyBlockProcessor)).Returns(dummyFlexiTableBlock);

            // Act
            FlexiTableBlock result = mockTestSubject.Object.Create(dummyProxyTableBlock, dummyBlockProcessor);

            // Assert
            _mockRepository.VerifyAll();
        }
        internal virtual FlexiTableRowBlock CreateFlexiTableRowBlock(FlexiTableType type,
                                                                     BlockProcessor childBlockProcessor,
                                                                     List <ColumnDefinition> columnDefinitions,
                                                                     int numColumns,
                                                                     int rowIndex,
                                                                     Row row,
                                                                     bool isHeaderRow)
        {
            var flexiTableRowBlock = new FlexiTableRowBlock(isHeaderRow);

            for (int columnIndex = 0; columnIndex < numColumns;)
            {
                Cell cell = row[columnIndex];
                columnIndex = cell.EndColumnIndex + 1;

                if (cell.StartRowIndex < rowIndex) // Cell with rowspan that's already been created
                {
                    continue;
                }

                // Create cell block
                FlexiTableCellBlock flexiTableCellBlock = CreateFlexiTableCellBlock(type, childBlockProcessor, columnDefinitions, cell);

                // Add cell block to row block
                flexiTableRowBlock.Add(flexiTableCellBlock);
            }

            // Could be empty if all cells have rowspan and have already been created. We return it anyway since it could be relevant.
            // For example, if we remove this row and some cells in the row have rowspan 3 while others only have rowspan 2, the cells
            // with rowspan 3 will erroneously span into an extra row.
            return(flexiTableRowBlock);
        }
 /// <summary>
 /// Creates a <see cref="FlexiTableBlockOptions"/>.
 /// </summary>
 /// <param name="blockName">
 /// <para>The <see cref="FlexiTableBlock"/>'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="FlexiTableBlock"/>'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="FlexiTableBlock"/>'s block name is "flexi-table".</para>
 /// <para>Defaults to "flexi-table".</para>
 /// </param>
 /// <param name="type">
 /// <para>The <see cref="FlexiTableBlock"/>'s type.</para>
 /// <para>This value is used in the root element's default <a href="https://en.bem.info/methodology/quick-start/#modifier">modifier class</a>,
 /// "&lt;<paramref name="blockName"/>&gt;_type_&lt;<paramref name="type"/>&gt;".</para>
 /// <para>This value affects the structure of generated HTML.</para>
 /// <para>Defaults to <see cref="FlexiTableType.Cards"/>.</para>
 /// </param>
 /// <param name="attributes">
 /// <para>The HTML attributes for the <see cref="FlexiTableBlock"/>'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 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 FlexiTableBlockOptions(
     string blockName    = "flexi-table",
     FlexiTableType type = FlexiTableType.Cards,
     IDictionary <string, string> attributes = default) : base(blockName, attributes)
 {
     Type = type;
 }
        public void CreateFlexiTableBlock_CreatesFlexiTableBlock(FlexiTableType dummyType,
                                                                 Row[] dummyRows)
        {
            // Arrange
            const int      dummyNumColumns        = 6;
            const int      dummyColumn            = 4;
            const int      dummyLine              = 5;
            const int      dummyLineStart         = 3;
            const int      dummyLineEnd           = 10;
            const string   dummyBlockName         = "dummyBlockName";
            BlockParser    dummyBlockParser       = _mockRepository.Create <BlockParser>().Object;
            var            dummyColumnDefinitions = new List <ColumnDefinition>();
            var            dummyAttributes        = new ReadOnlyDictionary <string, string>(new Dictionary <string, string>());
            BlockProcessor dummyBlockProcessor    = MarkdigTypesFactory.CreateBlockProcessor();
            var            dummyProxyTableBlock   = new ProxyTableBlock(dummyBlockParser);

            dummyProxyTableBlock.Column = dummyColumn;
            dummyProxyTableBlock.Line   = dummyLine;
            dummyProxyTableBlock.Span   = new SourceSpan(dummyLineStart, dummyLineEnd);
            foreach (Row row in dummyRows)
            {
                dummyProxyTableBlock.Rows.Add(row);
            }
            dummyProxyTableBlock.ColumnDefinitions = dummyColumnDefinitions;
            dummyProxyTableBlock.NumColumns        = dummyNumColumns;
            var dummyFlexiTableRowBlock1 = new FlexiTableRowBlock(false);
            var dummyFlexiTableRowBlock2 = new FlexiTableRowBlock(false);
            var dummyFlexiTableRowBlock3 = new FlexiTableRowBlock(false);
            Mock <FlexiTableBlockFactory> mockTestSubject = CreateMockFlexiTableBlockFactory();

            mockTestSubject.CallBase = true;
            mockTestSubject.
            Setup(t => t.CreateFlexiTableRowBlock(dummyType, It.IsAny <BlockProcessor>(), dummyColumnDefinitions, dummyNumColumns, 0, dummyRows[0], dummyRows[0].IsHeaderRow)).
            Returns(dummyFlexiTableRowBlock1);
            mockTestSubject.
            Setup(t => t.CreateFlexiTableRowBlock(dummyType, It.IsAny <BlockProcessor>(), dummyColumnDefinitions, dummyNumColumns, 1, dummyRows[1], dummyRows[1].IsHeaderRow)).
            Returns(dummyFlexiTableRowBlock2);
            mockTestSubject.
            Setup(t => t.CreateFlexiTableRowBlock(dummyType, It.IsAny <BlockProcessor>(), dummyColumnDefinitions, dummyNumColumns, 2, dummyRows[2], dummyRows[2].IsHeaderRow)).
            Returns(dummyFlexiTableRowBlock3);

            // Act
            FlexiTableBlock result = mockTestSubject.Object.CreateFlexiTableBlock(dummyBlockName, dummyType, dummyAttributes, dummyProxyTableBlock, dummyBlockProcessor);

            // Assert
            _mockRepository.VerifyAll();
            Assert.Equal(dummyBlockName, result.BlockName);
            Assert.Equal(dummyType, result.Type);
            Assert.Same(dummyAttributes, result.Attributes);
            Assert.Same(dummyBlockParser, result.Parser);
            Assert.Equal(dummyColumn, result.Column);
            Assert.Equal(dummyLine, result.Line);
            Assert.Equal(dummyLineStart, result.Span.Start);
            Assert.Equal(dummyLineEnd, result.Span.End);
            Assert.Equal(3, result.Count);
            Assert.Same(dummyFlexiTableRowBlock1, result[0]);
            Assert.Same(dummyFlexiTableRowBlock2, result[1]);
            Assert.Same(dummyFlexiTableRowBlock3, result[2]);
        }
 /// <summary>
 /// Creates a <see cref="FlexiTableBlock"/>.
 /// </summary>
 /// <param name="blockName">The <see cref="FlexiTableBlock"/>'s BEM block name.</param>
 /// <param name="type">The <see cref="FlexiTableBlock"/>'s type.</param>
 /// <param name="attributes">HTML attributes for the <see cref="FlexiTableBlock"/>'s root element.</param>
 /// <param name="blockParser">The <see cref="BlockParser"/> parsing the <see cref="FlexiTableBlock"/>.</param>
 public FlexiTableBlock(string blockName,
                        FlexiTableType type,
                        ReadOnlyDictionary <string, string> attributes,
                        BlockParser blockParser) : base(blockParser)
 {
     BlockName  = blockName;
     Type       = type;
     Attributes = attributes;
 }
 internal virtual void ValidateType(FlexiTableType type)
 {
     if (!Enum.IsDefined(typeof(FlexiTableType), type))
     {
         throw new OptionsException(nameof(IFlexiTableBlockOptions.Type),
                                    string.Format(Strings.OptionsException_Shared_ValueMustBeAValidEnumValue,
                                                  type,
                                                  nameof(FlexiTableType)));
     }
 }
        internal virtual FlexiTableBlock CreateFlexiTableBlock(string blockName,
                                                               FlexiTableType type,
                                                               ReadOnlyDictionary <string, string> attributes,
                                                               ProxyTableBlock proxyTableBlock,
                                                               BlockProcessor blockProcessor)
        {
            // Create table block
            var flexiTableBlock = new FlexiTableBlock(blockName, type, attributes, proxyTableBlock.Parser)
            {
                Column = proxyTableBlock.Column,
                Line   = proxyTableBlock.Line,
                Span   = proxyTableBlock.Span
            };

            // Create row blocks
            bool                    headerRowFound      = false;
            BlockProcessor          childBlockProcessor = blockProcessor.CreateChild();
            List <Row>              rows = proxyTableBlock.Rows;
            List <ColumnDefinition> columnDefinitions = proxyTableBlock.ColumnDefinitions;
            int  numColumns         = proxyTableBlock.NumColumns;
            int  numRows            = rows.Count;
            bool typeIsUnresponsive = type == FlexiTableType.Unresponsive;

            for (int rowIndex = 0; rowIndex < numRows; rowIndex++)
            {
                Row  row         = rows[rowIndex];
                bool isHeaderRow = row.IsHeaderRow;
                if (!typeIsUnresponsive && isHeaderRow)
                {
                    if (headerRowFound)
                    {
                        throw new OptionsException(nameof(IFlexiTableBlockOptions.Type), Strings.OptionsException_FlexiTableBlockFactory_TypeInvalidForTablesWithMultipleHeaderRows);
                    }
                    headerRowFound = true;
                }

                // Create and add row
                flexiTableBlock.Add(CreateFlexiTableRowBlock(type,
                                                             childBlockProcessor,
                                                             columnDefinitions,
                                                             numColumns,
                                                             rowIndex,
                                                             row,
                                                             isHeaderRow));
            }

            // Release for reuse
            childBlockProcessor.ReleaseChild();

            return(flexiTableBlock);
        }
        private static FlexiTableBlock CreateFlexiTableBlock(string blockName    = default,
                                                             FlexiTableType type = default,
                                                             ReadOnlyDictionary <string, string> attributes = default,
                                                             BlockParser blockParser = default,
                                                             params FlexiTableRowBlock[] flexiTableRowBlocks)
        {
            var result = new FlexiTableBlock(blockName, type, attributes, blockParser);

            foreach (FlexiTableRowBlock flexiTableRowBlock in flexiTableRowBlocks)
            {
                result.Add(flexiTableRowBlock);
            }

            return(result);
        }
        public void ValidateType_ThrowsOptionsExceptionIfTypeIsInvalid()
        {
            // Arrange
            FlexiTableBlockFactory testSubject = CreateFlexiTableBlockFactory();
            const FlexiTableType   dummyType   = (FlexiTableType)9;

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

            Assert.Equal(string.Format(Strings.OptionsException_OptionsException_InvalidOption,
                                       nameof(IFlexiTableBlockOptions.Type),
                                       string.Format(Strings.OptionsException_Shared_ValueMustBeAValidEnumValue, dummyType,
                                                     nameof(FlexiTableType))),
                         result.Message);
        }
        /// <inheritdoc />
        public FlexiTableBlock Create(ProxyTableBlock proxyTableBlock, BlockProcessor blockProcessor)
        {
            (IFlexiTableBlockOptions flexiTableBlockOptions, IFlexiTableBlocksExtensionOptions _) = _optionsService.
                                                                                                    CreateOptions(blockProcessor);

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

            // Type
            FlexiTableType type = flexiTableBlockOptions.Type;

            ValidateType(type);

            // Create block
            return(CreateFlexiTableBlock(blockName, type, flexiTableBlockOptions.Attributes, proxyTableBlock, blockProcessor));
        }
Beispiel #11
0
        public static IEnumerable <object[]> FlexiTableBlockOptions_CanBePopulated_Data()
        {
            const string         dummyBlockName       = "dummyBlockName";
            const FlexiTableType dummyType            = FlexiTableType.FixedTitles;
            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 FlexiTableBlockOptions containing default values
                new object[]
                {
                    new SerializableWrapper <FlexiTableBlockOptions>(new FlexiTableBlockOptions()),
                    new SerializableWrapper <FlexiTableBlockOptions>(new FlexiTableBlockOptions(dummyBlockName,
                                                                                                dummyType,
                                                                                                dummyAttributes1)),
                    $@"{{
    ""{nameof(FlexiTableBlockOptions.BlockName)}"": ""{dummyBlockName}"",
    ""{nameof(FlexiTableBlockOptions.Type)}"": ""{dummyType}"",
    ""{nameof(FlexiTableBlockOptions.Attributes)}"": {{
        ""{dummyAttribute1}"": ""{dummyAttributeValue1}""
    }}
}}"
                },

                // Populating FlexiTableBlockOptions with an existing attributes collection (should be replaced instead of appended to)
                new object[]
                {
                    new SerializableWrapper <FlexiTableBlockOptions>(new FlexiTableBlockOptions(attributes: dummyAttributes1)),
                    new SerializableWrapper <FlexiTableBlockOptions>(new FlexiTableBlockOptions(attributes: dummyAttributes2)),
                    $@"{{
    ""{nameof(FlexiTableBlockOptions.Attributes)}"": {{
        ""{dummyAttribute2}"": ""{dummyAttributeValue2}""
    }}
}}"
                }
            });
        }
        internal virtual FlexiTableCellBlock CreateFlexiTableCellBlock(FlexiTableType type,
                                                                       BlockProcessor childBlockProcessor,
                                                                       List <ColumnDefinition> columnDefinitions,
                                                                       Cell cell)
        {
            // Colspan and rowspan
            int startColumnIndex = cell.StartColumnIndex;
            int colspan          = cell.EndColumnIndex - startColumnIndex + 1;
            int rowspan          = cell.EndRowIndex - cell.StartRowIndex + 1;

            if (type != FlexiTableType.Unresponsive && (rowspan > 1 || colspan > 1))
            {
                throw new OptionsException(nameof(IFlexiTableBlockOptions.Type), Strings.OptionsException_FlexiTableBlockFactory_TypeInvalidForTablesWithCellsThatHaveRowspanOrColspan);
            }

            // Create
            var flexiTableCellBlock = new FlexiTableCellBlock(colspan,
                                                              rowspan,
                                                              columnDefinitions?[startColumnIndex].ContentAlignment ?? ContentAlignment.None);

            // Process cell block contents
            childBlockProcessor.LineIndex = cell.LineIndex;
            childBlockProcessor.Open(flexiTableCellBlock);
            ref StringLineGroup stringLineGroup = ref cell.Lines;
        public void CreateFlexiTableBlock_ThrowsOptionsExceptionIfTypeIsNotUnresponsiveAndTableHasMultipleHeaderRows(FlexiTableType dummyType,
                                                                                                                     Row[] dummyRows)
        {
            // Arrange
            const int      dummyNumColumns        = 6;
            var            dummyColumnDefinitions = new List <ColumnDefinition>();
            var            dummyAttributes        = new ReadOnlyDictionary <string, string>(new Dictionary <string, string>());
            BlockProcessor dummyBlockProcessor    = MarkdigTypesFactory.CreateBlockProcessor();
            var            dummyProxyTableBlock   = new ProxyTableBlock(null);

            foreach (Row row in dummyRows)
            {
                dummyProxyTableBlock.Rows.Add(row);
            }
            dummyProxyTableBlock.ColumnDefinitions = dummyColumnDefinitions;
            dummyProxyTableBlock.NumColumns        = dummyNumColumns;
            Mock <FlexiTableBlockFactory> mockTestSubject = CreateMockFlexiTableBlockFactory();

            mockTestSubject.CallBase = true;
            mockTestSubject.
            Setup(t => t.CreateFlexiTableRowBlock(dummyType, It.IsAny <BlockProcessor>(), dummyColumnDefinitions, dummyNumColumns, 0, dummyRows[0], dummyRows[0].IsHeaderRow)).
            Returns(new FlexiTableRowBlock(true));

            // Act and assert
            OptionsException result = Assert.Throws <OptionsException>(() => mockTestSubject.Object.CreateFlexiTableBlock(default, dummyType, dummyAttributes, dummyProxyTableBlock, dummyBlockProcessor));
Beispiel #14
0
        /// <summary>
        /// Renders a <see cref="FlexiTableBlock"/> as HTML.
        /// </summary>
        /// <param name="htmlRenderer">The renderer to write to.</param>
        /// <param name="block">The <see cref="FlexiTableBlock"/> to render.</param>
        protected override void WriteBlock(HtmlRenderer htmlRenderer, FlexiTableBlock block)
        {
            if (!htmlRenderer.EnableHtmlForBlock)
            {
                htmlRenderer.WriteChildren(block, false);
                return;
            }

            ReadOnlyDictionary <string, string> attributes = block.Attributes;
            string         blockName = block.BlockName;
            FlexiTableType type      = block.Type;

            // Root element
            // Wrap table in a div. Why?
            // - The "auto" algorithm for determining a table's width basically adds up the minimum content widths (MCW) of each column > https://www.w3.org/TR/CSS2/tables.html#auto-table-layout.
            // - When using "overflow-wrap: break-word" MCW does not take soft wrap oppurtunities into account > https://www.w3.org/TR/css-text-3/#valdef-overflow-wrap-break-word.
            // - The above two points result in long words not wrapping in table cells. Instead, long words cause long cells, in turn causing tables to overflow their parents.
            // - This will no longer be an issue when "overflow-wrap: anywhere" works.
            // - For now, <table> elements must be wrapped in <div>s with "overflow: auto". It is possible to set "overflow: auto" on tables themselves but this will not always work because table widths
            //   are overriden by sum of MCWs of its columns (i.e even if you set a fixed width for a table, it gets overriden in most cases). It is possible to make "overflow: auto" on tables work by
            //   setting the table's display to block (Github does this), but this is a hack that just happens to work (that "display: block" doesn't affect rendering of the table, which should have
            //   "display: table", is a coincidence).
            htmlRenderer.
            Write("<div class=\"").
            Write(blockName).
            WriteBlockKeyValueModifierClass(blockName, "type", _types[(int)type]).
            WriteAttributeValue(attributes, "class").
            Write('"').
            WriteAttributesExcept(attributes, "class").
            WriteLine(">");

            // Table
            htmlRenderer.WriteStartTagLine("table", blockName, "table");

            // Table - Rows
            FlexiTableRowBlock labelsFlexiTableRowBlock = null;
            int  numRows = block.Count;
            bool headStartRendered = false, headEndRendered = false, bodyStartRendered = false;

            for (int rowIndex = 0; rowIndex < numRows; rowIndex++)
            {
                var flexiTableRowBlock = block[rowIndex] as FlexiTableRowBlock;

                if (flexiTableRowBlock.IsHeaderRow)
                {
                    if (!headStartRendered)
                    {
                        htmlRenderer.WriteStartTagLine("thead", blockName, "head");
                        headStartRendered = true;

                        if (type == FlexiTableType.Cards)
                        {
                            labelsFlexiTableRowBlock = flexiTableRowBlock; // TODO we're only using content from first header row in labels, should concatenate content from all header rows
                        }
                    }
                }
                else
                {
                    if (headStartRendered && !headEndRendered) // Table may not have header rows
                    {
                        htmlRenderer.WriteEndTagLine("thead");
                        headEndRendered = true;
                    }

                    if (!bodyStartRendered)
                    {
                        htmlRenderer.WriteStartTagLine("tbody", blockName, "body");
                        bodyStartRendered = true;
                    }
                }

                WriteRowBlock(htmlRenderer, blockName, labelsFlexiTableRowBlock, flexiTableRowBlock);
            }
            htmlRenderer.
            WriteEndTagLine(headStartRendered && !headEndRendered, "thead").
            WriteEndTagLine(bodyStartRendered, "tbody").
            WriteEndTagLine("table").
            WriteEndTagLine("div");
        }