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>, /// "<<paramref name="blockName"/>>_type_<<paramref name="type"/>>".</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)); }
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));
/// <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"); }