diff --git a/Plugins/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin/Wrappers/Table.cs b/Plugins/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin/Wrappers/Table.cs index 35e7b910c..98efb495b 100644 --- a/Plugins/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin/Wrappers/Table.cs +++ b/Plugins/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin/Wrappers/Table.cs @@ -1,3 +1,60 @@ -// ReSharper disable once CheckNamespace +using System.ComponentModel; +using System.Globalization; +using System; + +// ReSharper disable once CheckNamespace namespace TechTalk.SpecFlow; -public class Table(params string[] header) : Reqnroll.Table(header); + +[TypeConverter(typeof(SpecFlowTableConverter))] +public class Table : Reqnroll.Table +{ + public Table(params string[] header) : base(header) + { + } + + protected internal Table(Reqnroll.Table copyFrom) : base(copyFrom) + { + } +} + +internal class SpecFlowTableConverter : TypeConverter +{ + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return typeof(Reqnroll.Table).IsAssignableFrom(sourceType); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value == null) + return null; + + if (value is Table specFlowTable) + return specFlowTable; + + if (value is Reqnroll.Table tableValue) + { + return new Table(tableValue); + } + + return base.ConvertFrom(context, culture, value); + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + return destinationType == typeof(Reqnroll.Table); + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (destinationType == typeof(Table) || value == null) + return value; + + if (destinationType == typeof(Reqnroll.Table) && value is Table specFlowTable) + { + return new Reqnroll.Table(specFlowTable); + } + + return base.ConvertTo(context, culture, value, destinationType); + } +} \ No newline at end of file diff --git a/Plugins/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin/Wrappers/TestThreadContext.cs b/Plugins/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin/Wrappers/TestThreadContext.cs new file mode 100644 index 000000000..7f60e2502 --- /dev/null +++ b/Plugins/Reqnroll.SpecFlowCompatibility.ReqnrollPlugin/Wrappers/TestThreadContext.cs @@ -0,0 +1,13 @@ +using System; +using BoDi; +using Reqnroll; + +// ReSharper disable once CheckNamespace +namespace TechTalk.SpecFlow; + +public class TestThreadContext(Reqnroll.TestThreadContext originalContext) : ITestThreadContext +{ + public Exception TestError => originalContext.TestError; + + public IObjectContainer TestThreadContainer => originalContext.TestThreadContainer; +} diff --git a/Reqnroll/AssemblyAttributes.cs b/Reqnroll/AssemblyAttributes.cs index 1d841ef20..44461642d 100644 --- a/Reqnroll/AssemblyAttributes.cs +++ b/Reqnroll/AssemblyAttributes.cs @@ -6,7 +6,9 @@ #if REQNROLL_ENABLE_STRONG_NAME_SIGNING [assembly: InternalsVisibleTo("Reqnroll.RuntimeTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010059bb085601a8b65a8a7b00f34e6d85df230f1e4913d3c0eaadcd13c1fd9cd15c3f01567c49d5f41f617dbda6f544ea3e2d5b5a042f307a7c8d21a079254509ba543efaefce96fac977abd0a76206b721abc33f84ee45ae5383cf50eeb8e234595656fd4af916e1dcde644ce20bb9a68bd72bc7957b759560524c63ca294585ca")] [assembly: InternalsVisibleTo("Reqnroll.PluginTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010059bb085601a8b65a8a7b00f34e6d85df230f1e4913d3c0eaadcd13c1fd9cd15c3f01567c49d5f41f617dbda6f544ea3e2d5b5a042f307a7c8d21a079254509ba543efaefce96fac977abd0a76206b721abc33f84ee45ae5383cf50eeb8e234595656fd4af916e1dcde644ce20bb9a68bd72bc7957b759560524c63ca294585ca")] +[assembly: InternalsVisibleTo("Reqnroll.SpecFlowCompatibility.ReqnrollPlugin, PublicKey=002400000480000094000000060200000024000052534131000400000100010059bb085601a8b65a8a7b00f34e6d85df230f1e4913d3c0eaadcd13c1fd9cd15c3f01567c49d5f41f617dbda6f544ea3e2d5b5a042f307a7c8d21a079254509ba543efaefce96fac977abd0a76206b721abc33f84ee45ae5383cf50eeb8e234595656fd4af916e1dcde644ce20bb9a68bd72bc7957b759560524c63ca294585ca")] #else [assembly: InternalsVisibleTo("Reqnroll.RuntimeTests")] [assembly: InternalsVisibleTo("Reqnroll.PluginTests")] +[assembly: InternalsVisibleTo("Reqnroll.SpecFlowCompatibility.ReqnrollPlugin")] #endif diff --git a/Reqnroll/Bindings/StepArgumentTypeConverter.cs b/Reqnroll/Bindings/StepArgumentTypeConverter.cs index 852b170a5..1d7a5cdc3 100644 --- a/Reqnroll/Bindings/StepArgumentTypeConverter.cs +++ b/Reqnroll/Bindings/StepArgumentTypeConverter.cs @@ -57,7 +57,7 @@ private async Task DoTransformAsync(IStepArgumentTransformationBinding s if (stepTransformation.Regex != null && value is string stringValue) arguments = await GetStepTransformationArgumentsFromRegexAsync(stepTransformation, stringValue, cultureInfo); else - arguments = new[] {value}; + arguments = new[] { await ConvertAsync(value, stepTransformation.Method.Parameters.ElementAtOrDefault(0)?.Type ?? new RuntimeBindingType(typeof(object)), cultureInfo)}; var result = await bindingInvoker.InvokeBindingAsync(stepTransformation, contextManager, arguments, testTracer, new DurationHolder()); @@ -103,14 +103,22 @@ private bool CanConvert(IStepArgumentTransformationBinding stepTransformationBin if (stepTransformationBinding.Regex != null && valueToConvert is string stringValue) return stepTransformationBinding.Regex.IsMatch(stringValue); - var transformationFirstArgumentTypeName = stepTransformationBinding.Method.Parameters.FirstOrDefault()?.Type.FullName; + var transformationFirstArgumentType = stepTransformationBinding.Method.Parameters.FirstOrDefault()?.Type; - var isTableStepTransformation = transformationFirstArgumentTypeName == typeof(Table).FullName; + var isTableStepTransformation = transformationFirstArgumentType != null && + IsDataTableType(transformationFirstArgumentType); var valueIsTable = valueToConvert is Table; return isTableStepTransformation == valueIsTable; } + protected virtual bool IsDataTableType(IBindingType bindingType) + { + return bindingType.FullName == typeof(Table).FullName || + (bindingType is RuntimeBindingType runtimeBindingType && + typeof(Table).IsAssignableFrom(runtimeBindingType)); + } + private static object ConvertSimple(IBindingType typeToConvertTo, object value, CultureInfo cultureInfo) { if (typeToConvertTo is not RuntimeBindingType runtimeBindingType) diff --git a/Reqnroll/Table.cs b/Reqnroll/Table.cs index a2559bf27..b50d1535a 100644 --- a/Reqnroll/Table.cs +++ b/Reqnroll/Table.cs @@ -17,33 +17,39 @@ public class Table internal const string ERROR_COLUMN_NAME_NOT_FOUND = "Could not find a column named '{0}' in the table."; internal const string ERROR_CELLS_NOT_MATCHING_HEADERS = "The number of cells ({0}) you are trying to add doesn't match the number of columns ({1})"; - private readonly string[] header; - private readonly TableRows rows = new TableRows(); + private readonly string[] _header; + private readonly TableRows _rows = new(); - public ICollection Header - { - get { return header; } - } + public ICollection Header => _header; - public TableRows Rows - { - get { return rows; } - } + public TableRows Rows => _rows; - public int RowCount + public int RowCount => _rows.Count; + + public Table(params string[] header) : this(header, null) { - get { return rows.Count; } } - public Table(params string[] header) + protected internal Table(string[] header, string[][] dataRows) { if (header == null || header.Length == 0) { - throw new ArgumentException(ERROR_NO_HEADER_TO_ADD, nameof(header)); + throw new ArgumentNullException(nameof(header)); } for (int colIndex = 0; colIndex < header.Length; colIndex++) header[colIndex] ??= string.Empty; - this.header = header; + _header = header; + if (dataRows != null) + { + foreach (var dataRow in dataRows) + { + _rows.Add(new TableRow(this, dataRow)); + } + } + } + + protected internal Table(Table copyFrom) : this(copyFrom._header, copyFrom._rows.ToArray()) + { } public bool ContainsColumn(string column) @@ -53,7 +59,7 @@ public bool ContainsColumn(string column) internal int GetHeaderIndex(string column, bool throwIfNotFound = true) { - int index = Array.IndexOf(header, column); + int index = Array.IndexOf(_header, column); if (!throwIfNotFound) return index; if (index < 0) @@ -69,7 +75,7 @@ internal int GetHeaderIndex(string column, bool throwIfNotFound = true) public void AddRow(IDictionary values) { - string[] cells = new string[header.Length]; + string[] cells = new string[_header.Length]; foreach (var value in values) { int headerIndex = GetHeaderIndex(value.Key); @@ -84,52 +90,52 @@ public void AddRow(params string[] cells) if (cells == null) throw new Exception(ERROR_NO_CELLS_TO_ADD); - if (cells.Length != header.Length) + if (cells.Length != _header.Length) { var mess = string.Format( ERROR_CELLS_NOT_MATCHING_HEADERS + ".\nThe table looks like this\n{2}", cells.Length, - header.Length, + _header.Length, this); throw new ArgumentException(mess); } var row = new TableRow(this, cells); - rows.Add(row); + _rows.Add(row); } public void RenameColumn(string oldColumn, string newColumn) { int colIndex = GetHeaderIndex(oldColumn); - header[colIndex] = newColumn; + _header[colIndex] = newColumn; } public override string ToString() { - return ToString(false, true); + return ToString(false); } - public string ToString(bool headersOnly = false, bool withNewline = true) + public string ToString(bool headersOnly, bool withNewline = true) { - int[] columnWidths = new int[header.Length]; - for (int colIndex = 0; colIndex < header.Length; colIndex++) - columnWidths[colIndex] = header[colIndex].Length; + int[] columnWidths = new int[_header.Length]; + for (int colIndex = 0; colIndex < _header.Length; colIndex++) + columnWidths[colIndex] = _header[colIndex].Length; if (!headersOnly) { - foreach (TableRow row in rows) + foreach (TableRow row in _rows) { - for (int colIndex = 0; colIndex < header.Length; colIndex++) + for (int colIndex = 0; colIndex < _header.Length; colIndex++) columnWidths[colIndex] = Math.Max(columnWidths[colIndex], row[colIndex].Length); } } StringBuilder builder = new StringBuilder(); - AddTableRow(builder, header, columnWidths); + AddTableRow(builder, _header, columnWidths); if (!headersOnly) { - foreach (TableRow row in rows) + foreach (TableRow row in _rows) AddTableRow(builder, row.Select(pair => pair.Value), columnWidths); } @@ -171,18 +177,15 @@ private void AddTableRow(StringBuilder builder, IEnumerable cells, int[] #endif public class TableRows : IEnumerable { - private readonly List innerList = new List(); + private readonly List _innerList = new(); - public int Count { get { return innerList.Count; } } + public int Count => _innerList.Count; - public TableRow this[int index] - { - get { return innerList[index]; } - } + public TableRow this[int index] => _innerList[index]; public IEnumerator GetEnumerator() { - return innerList.GetEnumerator(); + return _innerList.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() @@ -192,7 +195,12 @@ IEnumerator IEnumerable.GetEnumerator() internal void Add(TableRow row) { - innerList.Add(row); + _innerList.Add(row); + } + + internal string[][] ToArray() + { + return _innerList.Select(tr => tr.Items).ToArray(); } } @@ -201,52 +209,45 @@ internal void Add(TableRow row) #endif public class TableRow : IDictionary { - private readonly Table table; - private readonly string[] items; + private readonly Table _table; + private readonly string[] _items; internal TableRow(Table table, string[] items) { for (int colIndex = 0; colIndex < items.Length; colIndex++) items[colIndex] ??= string.Empty; - this.table = table; - this.items = items; + _table = table; + _items = items; } public string this[string header] { get { - int itemIndex = table.GetHeaderIndex(header); - return items[itemIndex]; + int itemIndex = _table.GetHeaderIndex(header); + return _items[itemIndex]; } set { - int keyIndex = table.GetHeaderIndex(header, true); - items[keyIndex] = value; + int keyIndex = _table.GetHeaderIndex(header); + _items[keyIndex] = value; } } - public string this[int index] - { - get - { - return items[index]; - } - } + public string this[int index] => _items[index]; - public int Count - { - get { return items.Length; } - } + public int Count => _items.Length; + + internal string[] Items => _items; public IEnumerator> GetEnumerator() { - Debug.Assert(items.Length == table.Header.Count); + Debug.Assert(_items.Length == _table.Header.Count); int itemIndex = 0; - foreach (string header in table.Header) + foreach (string header in _table.Header) { - yield return new KeyValuePair(header, items[itemIndex]); + yield return new KeyValuePair(header, _items[itemIndex]); itemIndex++; } } @@ -275,10 +276,10 @@ void ICollection>.Clear() bool ICollection>.Contains(KeyValuePair item) { - int keyIndex = table.GetHeaderIndex(item.Key, false); + int keyIndex = _table.GetHeaderIndex(item.Key, false); if (keyIndex < 0) return false; - return items[keyIndex].Equals(item.Value); + return _items[keyIndex].Equals(item.Value); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) @@ -302,7 +303,7 @@ bool ICollection>.IsReadOnly public bool ContainsKey(string key) { - return table.Header.Contains(key); + return _table.Header.Contains(key); } void IDictionary.Add(string key, string value) @@ -317,25 +318,25 @@ bool IDictionary.Remove(string key) public bool TryGetValue(string key, out string value) { - int keyIndex = table.GetHeaderIndex(key, false); + int keyIndex = _table.GetHeaderIndex(key, false); if (keyIndex < 0) { value = null; return false; } - value = items[keyIndex]; + value = _items[keyIndex]; return true; } public ICollection Keys { - get { return table.Header; } + get { return _table.Header; } } public ICollection Values { - get { return items; } + get { return _items; } } #endregion diff --git a/Tests/Reqnroll.RuntimeTests/TableTests.cs b/Tests/Reqnroll.RuntimeTests/TableTests.cs index 60a25dbbc..0497d28fe 100644 --- a/Tests/Reqnroll.RuntimeTests/TableTests.cs +++ b/Tests/Reqnroll.RuntimeTests/TableTests.cs @@ -38,7 +38,7 @@ public void should_return_nice_errormessage_when_adding_null_as_headers() var mess = string.Empty; try { - new Table(null); + new Table((string[])null); } catch (ArgumentException ex) {