mirror of https://github.com/Squidex/squidex.git
Browse Source
* GraphQL Data loader fix. * Fix tests * Fix event store. * Simplify testspull/1009/head
committed by
GitHub
114 changed files with 1366 additions and 2036 deletions
@ -1,93 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections; |
|
||||
using System.Diagnostics.CodeAnalysis; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Text; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Scripting; |
|
||||
|
|
||||
public class ScriptContext : IEnumerable<KeyValuePair<string, (object? Value, bool IsReadonly)>> |
|
||||
{ |
|
||||
private readonly Dictionary<string, (object? Value, bool IsReadonly)> values = new Dictionary<string, (object? Value, bool IsReadonly)>(StringComparer.OrdinalIgnoreCase); |
|
||||
|
|
||||
public void CopyFrom(ScriptVars vars) |
|
||||
{ |
|
||||
Guard.NotNull(vars); |
|
||||
|
|
||||
foreach (var (key, item) in vars) |
|
||||
{ |
|
||||
if (!values.ContainsKey(key)) |
|
||||
{ |
|
||||
SetItem(key, item); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void SetItem(string? key, (object? Value, bool IsReadonly) item) |
|
||||
{ |
|
||||
Set(key, item.Value, item.IsReadonly); |
|
||||
} |
|
||||
|
|
||||
public void Set(string? key, object? value, bool isReadonly = false) |
|
||||
{ |
|
||||
if (string.IsNullOrWhiteSpace(key)) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
var finalKey = key.ToCamelCase(); |
|
||||
|
|
||||
if (values.TryGetValue(finalKey, out var existing) && existing.IsReadonly) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
values[finalKey] = (value, isReadonly); |
|
||||
} |
|
||||
|
|
||||
public bool TryGetValue(string key, [MaybeNullWhen(false)] out object? value) |
|
||||
{ |
|
||||
Guard.NotNull(key); |
|
||||
|
|
||||
value = default!; |
|
||||
|
|
||||
if (values.TryGetValue(key, out var item)) |
|
||||
{ |
|
||||
value = item.Value; |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
public bool TryGetValue<T>(string key, [MaybeNullWhen(false)] out T value) |
|
||||
{ |
|
||||
Guard.NotNull(key); |
|
||||
|
|
||||
value = default!; |
|
||||
|
|
||||
if (values.TryGetValue(key, out var item) && item.Value is T typed) |
|
||||
{ |
|
||||
value = typed; |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
public IEnumerator<KeyValuePair<string, (object? Value, bool IsReadonly)>> GetEnumerator() |
|
||||
{ |
|
||||
return values.GetEnumerator(); |
|
||||
} |
|
||||
|
|
||||
IEnumerator IEnumerable.GetEnumerator() |
|
||||
{ |
|
||||
return values.GetEnumerator(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,124 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections; |
|
||||
using System.Diagnostics.CodeAnalysis; |
|
||||
using Squidex.Domain.Apps.Core.Assets; |
|
||||
using Squidex.Infrastructure.Json.Objects; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards; |
|
||||
|
|
||||
public sealed class ScriptMetadataWrapper : IDictionary<string, object?> |
|
||||
{ |
|
||||
private readonly AssetMetadata metadata; |
|
||||
|
|
||||
public int Count |
|
||||
{ |
|
||||
get => metadata.Count; |
|
||||
} |
|
||||
|
|
||||
public ICollection<string> Keys |
|
||||
{ |
|
||||
get => metadata.Keys; |
|
||||
} |
|
||||
|
|
||||
public ICollection<object?> Values |
|
||||
{ |
|
||||
get => metadata.Values.Cast<object?>().ToList(); |
|
||||
} |
|
||||
|
|
||||
public object? this[string key] |
|
||||
{ |
|
||||
get => metadata[key]; |
|
||||
set => metadata[key] = JsonValue.Create(value); |
|
||||
} |
|
||||
|
|
||||
public bool IsReadOnly |
|
||||
{ |
|
||||
get => false; |
|
||||
} |
|
||||
|
|
||||
public ScriptMetadataWrapper(AssetMetadata metadata) |
|
||||
{ |
|
||||
this.metadata = metadata; |
|
||||
} |
|
||||
|
|
||||
public bool TryGetValue(string key, [MaybeNullWhen(false)] out object? value) |
|
||||
{ |
|
||||
if (metadata.TryGetValue(key, out var temp)) |
|
||||
{ |
|
||||
value = temp; |
|
||||
return true; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
value = null; |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void Add(string key, object? value) |
|
||||
{ |
|
||||
metadata.Add(key, JsonValue.Create(value)); |
|
||||
} |
|
||||
|
|
||||
public void Add(KeyValuePair<string, object?> item) |
|
||||
{ |
|
||||
Add(item.Key, item.Value); |
|
||||
} |
|
||||
|
|
||||
public bool Remove(string key) |
|
||||
{ |
|
||||
return metadata.Remove(key); |
|
||||
} |
|
||||
|
|
||||
public bool Remove(KeyValuePair<string, object?> item) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
public void Clear() |
|
||||
{ |
|
||||
metadata.Clear(); |
|
||||
} |
|
||||
|
|
||||
public bool Contains(KeyValuePair<string, object?> item) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
public bool ContainsKey(string key) |
|
||||
{ |
|
||||
return metadata.ContainsKey(key); |
|
||||
} |
|
||||
|
|
||||
public void CopyTo(KeyValuePair<string, object?>[] array, int arrayIndex) |
|
||||
{ |
|
||||
var i = arrayIndex; |
|
||||
|
|
||||
foreach (var item in metadata) |
|
||||
{ |
|
||||
if (i >= array.Length) |
|
||||
{ |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
array[i] = new KeyValuePair<string, object?>(item.Key, item.Value); |
|
||||
i++; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public IEnumerator<KeyValuePair<string, object?>> GetEnumerator() |
|
||||
{ |
|
||||
return metadata.Select(x => new KeyValuePair<string, object?>(x.Key, x.Value)).GetEnumerator(); |
|
||||
} |
|
||||
|
|
||||
IEnumerator IEnumerable.GetEnumerator() |
|
||||
{ |
|
||||
return ((IEnumerable)metadata).GetEnumerator(); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,70 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using GraphQL.DataLoader; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Caching; |
||||
|
|
||||
|
#pragma warning disable MA0048 // File name must match type name
|
||||
|
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
|
||||
|
#pragma warning disable RECS0082 // Parameter has the same name as a member and hides it
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Cache; |
||||
|
|
||||
|
record struct CacheableId<T>(T Id, TimeSpan CacheDuration = default); |
||||
|
|
||||
|
internal class CachingBatchDataLoader<TKey, T> : DataLoaderBase<CacheableId<TKey>, T> where TKey : notnull where T : class |
||||
|
{ |
||||
|
private readonly IQueryCache<TKey, T> queryCache; |
||||
|
private readonly Func<IEnumerable<TKey>, CancellationToken, Task<IDictionary<TKey, T>>> queryDelegate; |
||||
|
|
||||
|
public CachingBatchDataLoader(IQueryCache<TKey, T> queryStore, |
||||
|
Func<IEnumerable<TKey>, CancellationToken, Task<IDictionary<TKey, T>>> queryDelegate, bool canCache = true, int maxBatchSize = int.MaxValue) |
||||
|
: base(canCache, maxBatchSize) |
||||
|
{ |
||||
|
this.queryCache = queryStore; |
||||
|
this.queryDelegate = queryDelegate; |
||||
|
} |
||||
|
|
||||
|
protected override async Task FetchAsync(IEnumerable<DataLoaderPair<CacheableId<TKey>, T>> list, |
||||
|
CancellationToken cancellationToken) |
||||
|
{ |
||||
|
var unmatched = new List<DataLoaderPair<CacheableId<TKey>, T>>(list.Count()); |
||||
|
|
||||
|
foreach (var entry in list) |
||||
|
{ |
||||
|
if (entry.Key.CacheDuration != default && queryCache.TryGet(entry.Key.Id, out var cached)) |
||||
|
{ |
||||
|
entry.SetResult(cached); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
unmatched.Add(entry); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (unmatched.Count == 0) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var ids = unmatched.Select(x => x.Key.Id).Distinct(); |
||||
|
|
||||
|
var entries = await queryDelegate(ids, cancellationToken); |
||||
|
|
||||
|
foreach (var entry in unmatched) |
||||
|
{ |
||||
|
entries.TryGetValue(entry.Key.Id, out var value); |
||||
|
entry.SetResult(value!); |
||||
|
|
||||
|
if (value != null && entry.Key.CacheDuration != default) |
||||
|
{ |
||||
|
queryCache.Set(entry.Key.Id, value, entry.Key.CacheDuration); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using GraphQL.DataLoader; |
||||
|
using Squidex.Infrastructure.Caching; |
||||
|
using Squidex.Infrastructure.Translations; |
||||
|
using TagLib.IFD.Tags; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Cache; |
||||
|
|
||||
|
internal static class CachingDataLoaderExtensions |
||||
|
{ |
||||
|
public static IDataLoader<CacheableId<TKey>, T> GetOrAddCachingLoader<TKey, T>(this DataLoaderContext dataLoaderContext, IQueryCache<TKey, T> queryCache, string loaderKey, |
||||
|
Func<IEnumerable<TKey>, CancellationToken, Task<IDictionary<TKey, T>>> queryDelegate, bool canCache = true, int maxBatchSize = int.MaxValue) |
||||
|
where TKey : notnull where T : class |
||||
|
{ |
||||
|
return dataLoaderContext.GetOrAdd(loaderKey, () => |
||||
|
{ |
||||
|
return new CachingBatchDataLoader<TKey, T>(queryCache, queryDelegate, canCache, maxBatchSize); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static IDataLoader<TKey, T> GetOrAddNonCachingBatchLoader<TKey, T>(this DataLoaderContext dataLoaderContext, string loaderKey, |
||||
|
Func<IEnumerable<TKey>, CancellationToken, Task<IDictionary<TKey, T>>> queryDelegate, int maxBatchSize = int.MaxValue) |
||||
|
where TKey : notnull where T : class |
||||
|
{ |
||||
|
return dataLoaderContext.GetOrAdd(loaderKey, () => |
||||
|
{ |
||||
|
return new NonCachingBatchLoader<TKey, T>(queryDelegate, maxBatchSize); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using GraphQL.DataLoader; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Cache; |
||||
|
|
||||
|
public sealed class EmptyDataLoaderResult<T> : IDataLoaderResult<T[]> |
||||
|
{ |
||||
|
public Task<T[]> GetResultAsync( |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return Task.FromResult(Array.Empty<T>()); |
||||
|
} |
||||
|
|
||||
|
Task<object?> IDataLoaderResult.GetResultAsync( |
||||
|
CancellationToken cancellationToken) |
||||
|
{ |
||||
|
return Task.FromResult<object?>(Array.Empty<T>()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using GraphQL.DataLoader; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Cache; |
||||
|
|
||||
|
internal class NonCachingBatchLoader<TKey, T> : DataLoaderBase<TKey, T> where TKey : notnull where T : class |
||||
|
{ |
||||
|
private readonly Func<IEnumerable<TKey>, CancellationToken, Task<IDictionary<TKey, T>>> queryDelegate; |
||||
|
|
||||
|
public NonCachingBatchLoader(Func<IEnumerable<TKey>, CancellationToken, Task<IDictionary<TKey, T>>> queryDelegate, int maxBatchSize = int.MaxValue) |
||||
|
: base(false, maxBatchSize) |
||||
|
{ |
||||
|
this.queryDelegate = queryDelegate; |
||||
|
} |
||||
|
|
||||
|
protected override async Task FetchAsync(IEnumerable<DataLoaderPair<TKey, T>> list, |
||||
|
CancellationToken cancellationToken) |
||||
|
{ |
||||
|
var dictionary = await queryDelegate(list.Select(x => x.Key), cancellationToken).ConfigureAwait(false); |
||||
|
|
||||
|
foreach (var item in list) |
||||
|
{ |
||||
|
dictionary.TryGetValue(item.Key, out var value); |
||||
|
|
||||
|
item.SetResult(value!); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,125 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.Collections; |
|
||||
|
|
||||
public partial class ListDictionary<TKey, TValue> |
|
||||
{ |
|
||||
private sealed class KeyCollection : ICollection<TKey> |
|
||||
{ |
|
||||
private readonly ListDictionary<TKey, TValue> dictionary; |
|
||||
|
|
||||
public int Count |
|
||||
{ |
|
||||
get => dictionary.Count; |
|
||||
} |
|
||||
|
|
||||
public bool IsReadOnly |
|
||||
{ |
|
||||
get => false; |
|
||||
} |
|
||||
|
|
||||
public KeyCollection(ListDictionary<TKey, TValue> dictionary) |
|
||||
{ |
|
||||
this.dictionary = dictionary; |
|
||||
} |
|
||||
|
|
||||
public void Add(TKey item) |
|
||||
{ |
|
||||
throw new NotSupportedException(); |
|
||||
} |
|
||||
|
|
||||
public void Clear() |
|
||||
{ |
|
||||
throw new NotSupportedException(); |
|
||||
} |
|
||||
|
|
||||
public void CopyTo(TKey[] array, int arrayIndex) |
|
||||
{ |
|
||||
var i = 0; |
|
||||
foreach (var (key, _) in dictionary.entries) |
|
||||
{ |
|
||||
array[arrayIndex + i] = key; |
|
||||
i++; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public bool Remove(TKey item) |
|
||||
{ |
|
||||
throw new NotSupportedException(); |
|
||||
} |
|
||||
|
|
||||
public bool Contains(TKey item) |
|
||||
{ |
|
||||
foreach (var entry in dictionary.entries) |
|
||||
{ |
|
||||
if (dictionary.comparer.Equals(entry.Key, item)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
public IEnumerator<TKey> GetEnumerator() |
|
||||
{ |
|
||||
return new Enumerator(dictionary); |
|
||||
} |
|
||||
|
|
||||
IEnumerator IEnumerable.GetEnumerator() |
|
||||
{ |
|
||||
return new Enumerator(dictionary); |
|
||||
} |
|
||||
|
|
||||
private struct Enumerator : IEnumerator<TKey>, IEnumerator |
|
||||
{ |
|
||||
private readonly ListDictionary<TKey, TValue> dictionary; |
|
||||
private int index = -1; |
|
||||
private TKey value = default!; |
|
||||
|
|
||||
readonly TKey IEnumerator<TKey>.Current |
|
||||
{ |
|
||||
get => value!; |
|
||||
} |
|
||||
|
|
||||
readonly object IEnumerator.Current |
|
||||
{ |
|
||||
get => value!; |
|
||||
} |
|
||||
|
|
||||
public Enumerator(ListDictionary<TKey, TValue> dictionary) |
|
||||
{ |
|
||||
this.dictionary = dictionary; |
|
||||
} |
|
||||
|
|
||||
public readonly void Dispose() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public bool MoveNext() |
|
||||
{ |
|
||||
if (index >= dictionary.entries.Count - 1) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
index++; |
|
||||
|
|
||||
value = dictionary.entries[index].Key; |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
public void Reset() |
|
||||
{ |
|
||||
index = -1; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,125 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.Collections; |
|
||||
|
|
||||
public partial class ListDictionary<TKey, TValue> |
|
||||
{ |
|
||||
private sealed class ValueCollection : ICollection<TValue> |
|
||||
{ |
|
||||
private readonly ListDictionary<TKey, TValue> dictionary; |
|
||||
|
|
||||
public int Count |
|
||||
{ |
|
||||
get => dictionary.Count; |
|
||||
} |
|
||||
|
|
||||
public bool IsReadOnly |
|
||||
{ |
|
||||
get => false; |
|
||||
} |
|
||||
|
|
||||
public ValueCollection(ListDictionary<TKey, TValue> dictionary) |
|
||||
{ |
|
||||
this.dictionary = dictionary; |
|
||||
} |
|
||||
|
|
||||
public void Add(TValue item) |
|
||||
{ |
|
||||
throw new NotSupportedException(); |
|
||||
} |
|
||||
|
|
||||
public void Clear() |
|
||||
{ |
|
||||
throw new NotSupportedException(); |
|
||||
} |
|
||||
|
|
||||
public void CopyTo(TValue[] array, int arrayIndex) |
|
||||
{ |
|
||||
var i = 0; |
|
||||
foreach (var (_, value) in dictionary.entries) |
|
||||
{ |
|
||||
array[arrayIndex + i] = value; |
|
||||
i++; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public bool Remove(TValue item) |
|
||||
{ |
|
||||
throw new NotSupportedException(); |
|
||||
} |
|
||||
|
|
||||
public bool Contains(TValue item) |
|
||||
{ |
|
||||
foreach (var entry in dictionary.entries) |
|
||||
{ |
|
||||
if (Equals(entry.Value, item)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
public IEnumerator<TValue> GetEnumerator() |
|
||||
{ |
|
||||
return new Enumerator(dictionary); |
|
||||
} |
|
||||
|
|
||||
IEnumerator IEnumerable.GetEnumerator() |
|
||||
{ |
|
||||
return new Enumerator(dictionary); |
|
||||
} |
|
||||
|
|
||||
private struct Enumerator : IEnumerator<TValue>, IEnumerator |
|
||||
{ |
|
||||
private readonly ListDictionary<TKey, TValue> dictionary; |
|
||||
private int index = -1; |
|
||||
private TValue value = default!; |
|
||||
|
|
||||
readonly TValue IEnumerator<TValue>.Current |
|
||||
{ |
|
||||
get => value!; |
|
||||
} |
|
||||
|
|
||||
readonly object IEnumerator.Current |
|
||||
{ |
|
||||
get => value!; |
|
||||
} |
|
||||
|
|
||||
public Enumerator(ListDictionary<TKey, TValue> dictionary) |
|
||||
{ |
|
||||
this.dictionary = dictionary; |
|
||||
} |
|
||||
|
|
||||
public readonly void Dispose() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public bool MoveNext() |
|
||||
{ |
|
||||
if (index >= dictionary.entries.Count - 1) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
index++; |
|
||||
|
|
||||
value = dictionary.entries[index].Value; |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
public void Reset() |
|
||||
{ |
|
||||
index = -1; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,273 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections; |
|
||||
using System.Diagnostics.CodeAnalysis; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.Collections; |
|
||||
|
|
||||
public partial class ListDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> where TKey : notnull |
|
||||
{ |
|
||||
private readonly List<KeyValuePair<TKey, TValue>> entries = new List<KeyValuePair<TKey, TValue>>(); |
|
||||
private readonly IEqualityComparer<TKey> comparer; |
|
||||
|
|
||||
private struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>, IEnumerator |
|
||||
{ |
|
||||
private readonly ListDictionary<TKey, TValue> dictionary; |
|
||||
private int index = -1; |
|
||||
private KeyValuePair<TKey, TValue> value = default!; |
|
||||
|
|
||||
readonly KeyValuePair<TKey, TValue> IEnumerator<KeyValuePair<TKey, TValue>>.Current |
|
||||
{ |
|
||||
get => value!; |
|
||||
} |
|
||||
|
|
||||
readonly object IEnumerator.Current |
|
||||
{ |
|
||||
get => value!; |
|
||||
} |
|
||||
|
|
||||
public Enumerator(ListDictionary<TKey, TValue> dictionary) |
|
||||
{ |
|
||||
this.dictionary = dictionary; |
|
||||
} |
|
||||
|
|
||||
public readonly void Dispose() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public bool MoveNext() |
|
||||
{ |
|
||||
if (index >= dictionary.entries.Count - 1) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
index++; |
|
||||
|
|
||||
value = dictionary.entries[index]; |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
public void Reset() |
|
||||
{ |
|
||||
index = -1; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public TValue this[TKey key] |
|
||||
{ |
|
||||
get |
|
||||
{ |
|
||||
if (!TryGetValue(key, out var result)) |
|
||||
{ |
|
||||
ThrowHelper.KeyNotFoundException(); |
|
||||
return default!; |
|
||||
} |
|
||||
|
|
||||
return result; |
|
||||
} |
|
||||
set |
|
||||
{ |
|
||||
var index = -1; |
|
||||
|
|
||||
for (var i = 0; i < entries.Count; i++) |
|
||||
{ |
|
||||
if (comparer.Equals(entries[i].Key, key)) |
|
||||
{ |
|
||||
index = i; |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (index >= 0) |
|
||||
{ |
|
||||
entries[index] = new KeyValuePair<TKey, TValue>(key, value); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
entries.Add(new KeyValuePair<TKey, TValue>(key, value)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public ICollection<TKey> Keys |
|
||||
{ |
|
||||
get => new KeyCollection(this); |
|
||||
} |
|
||||
|
|
||||
public ICollection<TValue> Values |
|
||||
{ |
|
||||
get => new ValueCollection(this); |
|
||||
} |
|
||||
|
|
||||
public int Count |
|
||||
{ |
|
||||
get => entries.Count; |
|
||||
} |
|
||||
|
|
||||
public int Capacity |
|
||||
{ |
|
||||
get => entries.Capacity; |
|
||||
} |
|
||||
|
|
||||
public bool IsReadOnly |
|
||||
{ |
|
||||
get => false; |
|
||||
} |
|
||||
|
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys |
|
||||
{ |
|
||||
get => new KeyCollection(this); |
|
||||
} |
|
||||
|
|
||||
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values |
|
||||
{ |
|
||||
get => new ValueCollection(this); |
|
||||
} |
|
||||
|
|
||||
public ListDictionary() |
|
||||
: this(1, null) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public ListDictionary(ListDictionary<TKey, TValue> source, IEqualityComparer<TKey>? comparer = null) |
|
||||
{ |
|
||||
Guard.NotNull(source); |
|
||||
|
|
||||
entries = source.entries.ToList(); |
|
||||
|
|
||||
this.comparer = comparer ?? EqualityComparer<TKey>.Default; |
|
||||
} |
|
||||
|
|
||||
public ListDictionary(int capacity, IEqualityComparer<TKey>? comparer = null) |
|
||||
{ |
|
||||
Guard.GreaterEquals(capacity, 0); |
|
||||
|
|
||||
entries = new List<KeyValuePair<TKey, TValue>>(capacity); |
|
||||
|
|
||||
this.comparer = comparer ?? EqualityComparer<TKey>.Default; |
|
||||
} |
|
||||
|
|
||||
public void Add(TKey key, TValue value) |
|
||||
{ |
|
||||
if (ContainsKey(key)) |
|
||||
{ |
|
||||
ThrowHelper.ArgumentException("Key already exists.", nameof(key)); |
|
||||
} |
|
||||
|
|
||||
AddUnsafe(key, value); |
|
||||
} |
|
||||
|
|
||||
public void Add(KeyValuePair<TKey, TValue> item) |
|
||||
{ |
|
||||
Add(item.Key, item.Value); |
|
||||
} |
|
||||
|
|
||||
public void AddUnsafe(TKey key, TValue value) |
|
||||
{ |
|
||||
entries.Add(new KeyValuePair<TKey, TValue>(key, value)); |
|
||||
} |
|
||||
|
|
||||
public void Clear() |
|
||||
{ |
|
||||
entries.Clear(); |
|
||||
} |
|
||||
|
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item) |
|
||||
{ |
|
||||
foreach (var entry in entries) |
|
||||
{ |
|
||||
if (comparer.Equals(entry.Key, item.Key) && Equals(entry.Value, item.Value)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
public bool ContainsKey(TKey key) |
|
||||
{ |
|
||||
foreach (var entry in entries) |
|
||||
{ |
|
||||
if (comparer.Equals(entry.Key, key)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) |
|
||||
{ |
|
||||
foreach (var entry in entries) |
|
||||
{ |
|
||||
if (comparer.Equals(entry.Key, key)) |
|
||||
{ |
|
||||
value = entry.Value; |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
value = default; |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
public bool Remove(TKey key) |
|
||||
{ |
|
||||
for (var i = 0; i < entries.Count; i++) |
|
||||
{ |
|
||||
var entry = entries[i]; |
|
||||
|
|
||||
if (comparer.Equals(entry.Key, key)) |
|
||||
{ |
|
||||
entries.RemoveAt(i); |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item) |
|
||||
{ |
|
||||
for (var i = 0; i < entries.Count; i++) |
|
||||
{ |
|
||||
var entry = entries[i]; |
|
||||
|
|
||||
if (comparer.Equals(entry.Key, item.Key) && Equals(entry.Value, item.Value)) |
|
||||
{ |
|
||||
entries.RemoveAt(i); |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
public void TrimExcess() |
|
||||
{ |
|
||||
entries.TrimExcess(); |
|
||||
} |
|
||||
|
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) |
|
||||
{ |
|
||||
entries.CopyTo(array, arrayIndex); |
|
||||
} |
|
||||
|
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() |
|
||||
{ |
|
||||
return new Enumerator(this); |
|
||||
} |
|
||||
|
|
||||
IEnumerator IEnumerable.GetEnumerator() |
|
||||
{ |
|
||||
return new Enumerator(this); |
|
||||
} |
|
||||
} |
|
||||
@ -1,478 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections; |
|
||||
using Squidex.Infrastructure.TestHelpers; |
|
||||
|
|
||||
#pragma warning disable xUnit2017 // Do not use Contains() to check if a value exists in a collection
|
|
||||
#pragma warning disable IDE0028 // Simplify collection initialization
|
|
||||
#pragma warning disable CA1841 // Prefer Dictionary.Contains methods
|
|
||||
|
|
||||
namespace Squidex.Infrastructure.Collections; |
|
||||
|
|
||||
public class ListDictionaryTests |
|
||||
{ |
|
||||
[Fact] |
|
||||
public void Should_create_empty() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
Assert.Empty(sut); |
|
||||
Assert.Equal(1, sut.Capacity); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_create_with_capacity() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(20); |
|
||||
|
|
||||
Assert.Empty(sut); |
|
||||
Assert.Equal(20, sut.Capacity); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_create_as_copy() |
|
||||
{ |
|
||||
var source = new ListDictionary<int, int>(); |
|
||||
|
|
||||
source.Add(1, 10); |
|
||||
source.Add(2, 20); |
|
||||
|
|
||||
var sut = new ListDictionary<int, int>(source); |
|
||||
|
|
||||
Assert.Equal(2, sut.Count); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_not_be_readonly() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
Assert.False(sut.IsReadOnly); |
|
||||
Assert.False(sut.Keys.IsReadOnly); |
|
||||
Assert.False(sut.Values.IsReadOnly); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_add_item() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
|
|
||||
Assert.Single(sut); |
|
||||
Assert.Equal(10, sut[1]); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_add_item_unsafe() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.AddUnsafe(1, 10); |
|
||||
|
|
||||
Assert.Single(sut); |
|
||||
Assert.Equal(10, sut[1]); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_add_item_as_pair() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(new KeyValuePair<int, int>(1, 10)); |
|
||||
|
|
||||
Assert.Single(sut); |
|
||||
Assert.Equal(10, sut[1]); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_throw_exception_if_adding_existing_key() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
|
|
||||
Assert.Throws<ArgumentException>(() => sut.Add(1, 20)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_throw_exception_if_adding_pair_with_existing_key() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
|
|
||||
Assert.Throws<ArgumentException>(() => sut.Add(new KeyValuePair<int, int>(1, 20))); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_set_item() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut[1] = 10; |
|
||||
|
|
||||
Assert.Single(sut); |
|
||||
Assert.Equal(10, sut[1]); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_override_item() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut[1] = 20; |
|
||||
|
|
||||
Assert.Single(sut); |
|
||||
Assert.Equal(20, sut[1]); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_return_true_when_dictionary_contains_value() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
|
|
||||
Assert.True(sut.Contains(new KeyValuePair<int, int>(1, 10))); |
|
||||
Assert.True(sut.ContainsKey(1)); |
|
||||
Assert.True(sut.Keys.Contains(1)); |
|
||||
Assert.True(sut.Values.Contains(10)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_return_false_when_dictionary_does_not_contains_value() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
|
|
||||
Assert.False(sut.Contains(new KeyValuePair<int, int>(1, 20))); |
|
||||
Assert.False(sut.ContainsKey(2)); |
|
||||
Assert.False(sut.Keys.Contains(2)); |
|
||||
Assert.False(sut.Values.Contains(20)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_get_count() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
sut.Add(3, 30); |
|
||||
|
|
||||
Assert.Equal(3, sut.Count); |
|
||||
Assert.Equal(3, sut.Keys.Count); |
|
||||
Assert.Equal(3, sut.Values.Count); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_clear() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
sut.Add(3, 30); |
|
||||
sut.Clear(); |
|
||||
|
|
||||
Assert.Empty(sut); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_remove_key() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
sut.Add(3, 30); |
|
||||
|
|
||||
Assert.True(sut.Remove(2)); |
|
||||
Assert.False(sut.ContainsKey(2)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_not_remove_key_if_not_found() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
sut.Add(3, 30); |
|
||||
|
|
||||
Assert.False(sut.Remove(4)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_remove_item() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
sut.Add(3, 30); |
|
||||
|
|
||||
Assert.True(sut.Remove(new KeyValuePair<int, int>(2, 20))); |
|
||||
Assert.False(sut.ContainsKey(2)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_not_remove_item_if_key_not_found() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
sut.Add(3, 30); |
|
||||
|
|
||||
Assert.False(sut.Remove(new KeyValuePair<int, int>(4, 40))); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_not_remove_item_if_value_not_equal() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
sut.Add(3, 30); |
|
||||
|
|
||||
Assert.False(sut.Remove(new KeyValuePair<int, int>(2, 40))); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_get_value_by_method_if_found() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
|
|
||||
Assert.True(sut.TryGetValue(2, out var found)); |
|
||||
Assert.Equal(20, found); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_not_get_value_by_method_if_not_found() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(3, 30); |
|
||||
|
|
||||
Assert.False(sut.TryGetValue(4, out var found)); |
|
||||
Assert.Equal(0, found); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_get_value_by_indexer_if_found() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
|
|
||||
Assert.Equal(20, sut[2]); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_not_get_value_by_indexer_if_not_found() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
|
|
||||
Assert.Throws<KeyNotFoundException>(() => sut[4]); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_loop_over_entries() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
|
|
||||
var actual = new List<KeyValuePair<int, int>>(); |
|
||||
|
|
||||
foreach (var entry in sut) |
|
||||
{ |
|
||||
actual.Add(entry); |
|
||||
} |
|
||||
|
|
||||
Assert.Equal(new[] |
|
||||
{ |
|
||||
new KeyValuePair<int, int>(1, 10), |
|
||||
new KeyValuePair<int, int>(2, 20) |
|
||||
}, actual.ToArray()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_loop_over_entries_with_old_enumerator() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
|
|
||||
var actual = new List<KeyValuePair<int, int>>(); |
|
||||
|
|
||||
foreach (KeyValuePair<int, int> entry in (IEnumerable)sut) |
|
||||
{ |
|
||||
actual.Add(entry); |
|
||||
} |
|
||||
|
|
||||
Assert.Equal(new[] |
|
||||
{ |
|
||||
new KeyValuePair<int, int>(1, 10), |
|
||||
new KeyValuePair<int, int>(2, 20) |
|
||||
}, actual.ToArray()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_copy_entries_to_array() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
|
|
||||
Assert.Equal(new[] |
|
||||
{ |
|
||||
new KeyValuePair<int, int>(1, 10), |
|
||||
new KeyValuePair<int, int>(2, 20) |
|
||||
}, sut.ToArray()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_loop_over_keys() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
|
|
||||
var actual = new List<int>(); |
|
||||
|
|
||||
foreach (var entry in sut.Keys) |
|
||||
{ |
|
||||
actual.Add(entry); |
|
||||
} |
|
||||
|
|
||||
Assert.Equal(new[] { 1, 2 }, actual.ToArray()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_loop_over_keys_with_old_enumerator() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
|
|
||||
var actual = new List<int>(); |
|
||||
|
|
||||
foreach (int entry in (IEnumerable)sut.Keys) |
|
||||
{ |
|
||||
actual.Add(entry); |
|
||||
} |
|
||||
|
|
||||
Assert.Equal(new[] { 1, 2 }, actual.ToArray()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_copy_keys_to_array() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
|
|
||||
Assert.Equal(new[] { 1, 2 }, sut.Keys.ToArray()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_loop_over_values() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
|
|
||||
var actual = new List<int>(); |
|
||||
|
|
||||
foreach (var entry in sut.Values) |
|
||||
{ |
|
||||
actual.Add(entry); |
|
||||
} |
|
||||
|
|
||||
Assert.Equal(new[] { 10, 20 }, actual.ToArray()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_loop_over_values_with_old_enumerator() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
|
|
||||
var actual = new List<int>(); |
|
||||
|
|
||||
foreach (int entry in (IEnumerable)sut.Values) |
|
||||
{ |
|
||||
actual.Add(entry); |
|
||||
} |
|
||||
|
|
||||
Assert.Equal(new[] { 10, 20 }, actual.ToArray()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_copy_values_to_array() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
|
|
||||
Assert.Equal(new[] { 10, 20 }, sut.Values.ToArray()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_trim() |
|
||||
{ |
|
||||
var sut = new ListDictionary<int, int>(20); |
|
||||
|
|
||||
sut.Add(1, 10); |
|
||||
sut.Add(2, 20); |
|
||||
|
|
||||
Assert.Equal(20, sut.Capacity); |
|
||||
|
|
||||
sut.TrimExcess(); |
|
||||
|
|
||||
Assert.Equal(2, sut.Capacity); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_serialize_and_deserialize() |
|
||||
{ |
|
||||
var sut = new Dictionary<int, int> |
|
||||
{ |
|
||||
[11] = 1, |
|
||||
[12] = 2, |
|
||||
[13] = 3 |
|
||||
}.ToReadonlyDictionary(); |
|
||||
|
|
||||
var serialized = sut.SerializeAndDeserialize(); |
|
||||
|
|
||||
Assert.Equal(sut, serialized); |
|
||||
} |
|
||||
} |
|
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue