From ecf8307c27417b8f684b2f1776907b37ea75b86f Mon Sep 17 00:00:00 2001 From: comintern Date: Tue, 11 Dec 2018 23:56:40 -0600 Subject: [PATCH] Add unit tests, address results. --- .../AddRemoveReferences/ReferenceModel.cs | 26 +- .../ReferenceReconciler.cs | 20 +- .../RegisteredLibraryFinderService.cs | 18 +- .../RegisteredLibraryInfo.cs | 6 +- .../Settings/ReferenceConfigProvider.cs | 10 +- Rubberduck.Core/Settings/ReferenceSettings.cs | 69 ++- .../ComReflection/ComDocumentation.cs | 10 +- .../ComReflection/ComLibraryProvider.cs | 32 ++ .../ComReflection/IComLibraryProvider.cs | 3 + Rubberduck.Resources/RubberduckUI.Designer.cs | 11 + Rubberduck.Resources/RubberduckUI.resx | 4 + .../AddRemoveReferencesSetup.cs | 185 ++++++++ .../ReferenceReconcilerTests.cs | 412 ++++++++++++++++++ RubberduckTests/Mocks/MockProjectBuilder.cs | 8 +- .../Settings/ReferenceSettingsTests.cs | 389 +++++++++++++++++ 15 files changed, 1148 insertions(+), 55 deletions(-) create mode 100644 RubberduckTests/AddRemoveReferences/AddRemoveReferencesSetup.cs create mode 100644 RubberduckTests/AddRemoveReferences/ReferenceReconcilerTests.cs create mode 100644 RubberduckTests/Settings/ReferenceSettingsTests.cs diff --git a/Rubberduck.Core/AddRemoveReferences/ReferenceModel.cs b/Rubberduck.Core/AddRemoveReferences/ReferenceModel.cs index ead33537bb..a73bf8ebea 100644 --- a/Rubberduck.Core/AddRemoveReferences/ReferenceModel.cs +++ b/Rubberduck.Core/AddRemoveReferences/ReferenceModel.cs @@ -83,22 +83,18 @@ public ReferenceModel(IReference reference, int priority) : this() Type = reference.Type; } - public ReferenceModel(ITypeLib reference) : this() + public ReferenceModel(string path, ITypeLib reference, IComLibraryProvider provider) : this() { - var documentation = new ComDocumentation(reference, ComDocumentation.LibraryIndex); + FullPath = path; + + var documentation = provider.GetComDocumentation(reference); Name = documentation.Name; Description = documentation.DocString; - reference.GetLibAttr(out var attributes); - using (DisposalActionContainer.Create(attributes, reference.ReleaseTLibAttr)) - { - var typeAttr = Marshal.PtrToStructure(attributes); - - Major = typeAttr.wMajorVerNum; - Minor = typeAttr.wMinorVerNum; - Flags = (TypeLibTypeFlags)typeAttr.wLibFlags; - Guid = typeAttr.guid; - } + var info = provider.GetReferenceInfo(reference, Name, path); + Guid = info.Guid; + Major = info.Major; + Minor = info.Minor; } public ReferenceModel(string path, bool broken = false) : this() @@ -106,7 +102,7 @@ public ReferenceModel(string path, bool broken = false) : this() FullPath = path; try { - Name = Path.GetFileName(path); + Name = Path.GetFileName(path) ?? path; Description = Name; } catch @@ -139,10 +135,10 @@ public bool IsPinned public string Name { get; } = string.Empty; public Guid Guid { get; } public string Description { get; } = string.Empty; - public string FullPath { get; } = string.Empty; + public string FullPath { get; } public string LocaleName { get; } = string.Empty; - public bool IsBuiltIn { get; } + public bool IsBuiltIn { get; set; } public bool IsBroken { get; } public TypeLibTypeFlags Flags { get; set; } public ReferenceKind Type { get; } diff --git a/Rubberduck.Core/AddRemoveReferences/ReferenceReconciler.cs b/Rubberduck.Core/AddRemoveReferences/ReferenceReconciler.cs index ed3832008d..292c239f0c 100644 --- a/Rubberduck.Core/AddRemoveReferences/ReferenceReconciler.cs +++ b/Rubberduck.Core/AddRemoveReferences/ReferenceReconciler.cs @@ -24,22 +24,27 @@ public interface IReferenceReconciler public class ReferenceReconciler : IReferenceReconciler { + public static readonly List TypeLibraryExtensions = new List { ".olb", ".tlb", ".dll", ".ocx", ".exe" }; + private readonly IMessageBox _messageBox; private readonly IConfigProvider _settings; - private readonly IComLibraryProvider _tlbProvider; + private readonly IComLibraryProvider _libraryProvider; - public ReferenceReconciler(IMessageBox messageBox, IConfigProvider settings, IComLibraryProvider tlbProvider) + public ReferenceReconciler( + IMessageBox messageBox, + IConfigProvider settings, + IComLibraryProvider libraryProvider) { _messageBox = messageBox; _settings = settings; - _tlbProvider = tlbProvider; + _libraryProvider = libraryProvider; } public List ReconcileReferences(IAddRemoveReferencesModel model) { if (model?.NewReferences is null || !model.NewReferences.Any()) { - return null; + return new List(); } return ReconcileReferences(model, model.NewReferences.ToList()); @@ -83,8 +88,6 @@ public List ReconcileReferences(IAddRemoveReferencesModel model, return output; } - private static readonly List InterestingExtensions = new List { ".olb", ".tlb", ".dll", ".ocx", ".exe" }; - public ReferenceModel GetLibraryInfoFromPath(string path) { try @@ -96,9 +99,10 @@ public ReferenceModel GetLibraryInfoFromPath(string path) } // LoadTypeLibrary will attempt to open files in the host, so only attempt on possible COM servers. - if (InterestingExtensions.Contains(extension)) + if (TypeLibraryExtensions.Contains(extension)) { - return new ReferenceModel(_tlbProvider.LoadTypeLibrary(path)); + var type = _libraryProvider.LoadTypeLibrary(path); + return new ReferenceModel(path, type, _libraryProvider); } return new ReferenceModel(path); } diff --git a/Rubberduck.Core/AddRemoveReferences/RegisteredLibraryFinderService.cs b/Rubberduck.Core/AddRemoveReferences/RegisteredLibraryFinderService.cs index e1648e244a..ff184a6a1e 100644 --- a/Rubberduck.Core/AddRemoveReferences/RegisteredLibraryFinderService.cs +++ b/Rubberduck.Core/AddRemoveReferences/RegisteredLibraryFinderService.cs @@ -6,15 +6,6 @@ namespace Rubberduck.AddRemoveReferences { - public static class RegistryKeyExtensions - { - public static string GetKeyName(this RegistryKey key) - { - var name = key?.Name; - return name?.Substring(name.LastIndexOf(@"\", StringComparison.InvariantCultureIgnoreCase) + 1) ?? string.Empty; - } - } - public interface IRegisteredLibraryFinderService { IEnumerable FindRegisteredLibraries(); @@ -97,4 +88,13 @@ private IEnumerable EnumerateSubKeys(RegistryKey key) } } } + + public static class RegistryKeyExtensions + { + public static string GetKeyName(this RegistryKey key) + { + var name = key?.Name; + return name?.Substring(name.LastIndexOf(@"\", StringComparison.InvariantCultureIgnoreCase) + 1) ?? string.Empty; + } + } } \ No newline at end of file diff --git a/Rubberduck.Core/AddRemoveReferences/RegisteredLibraryInfo.cs b/Rubberduck.Core/AddRemoveReferences/RegisteredLibraryInfo.cs index 0056b4ee1e..838928889a 100644 --- a/Rubberduck.Core/AddRemoveReferences/RegisteredLibraryInfo.cs +++ b/Rubberduck.Core/AddRemoveReferences/RegisteredLibraryInfo.cs @@ -24,7 +24,7 @@ public class RegisteredLibraryInfo { private static readonly Dictionary NativeLocaleNames = new Dictionary { - { 0, "Standard" } + { 0, Resources.RubberduckUI.References_DefaultLocale } }; public RegisteredLibraryKey UniqueId { get; } @@ -54,8 +54,8 @@ public string LocaleName } catch { - NativeLocaleNames.Add(LocaleId, "Standard"); - return "Standard"; + NativeLocaleNames.Add(LocaleId, Resources.RubberduckUI.References_DefaultLocale); + return Resources.RubberduckUI.References_DefaultLocale; } } } diff --git a/Rubberduck.Core/Settings/ReferenceConfigProvider.cs b/Rubberduck.Core/Settings/ReferenceConfigProvider.cs index c003fafd7b..8fb697195c 100644 --- a/Rubberduck.Core/Settings/ReferenceConfigProvider.cs +++ b/Rubberduck.Core/Settings/ReferenceConfigProvider.cs @@ -29,7 +29,7 @@ public ReferenceConfigProvider(IPersistanceService persister, var settings = Create(); _listening = settings.AddToRecentOnReferenceEvents; - if (_listening) + if (_listening && _events != null) { _events.ProjectReferenceAdded += ReferenceAddedHandler; } @@ -52,13 +52,13 @@ public ReferenceSettings CreateDefaults() defaults.PinReference(new ReferenceInfo(new Guid(RubberduckGuid.RubberduckTypeLibGuid), string.Empty, string.Empty, version.Major, version.Minor)); defaults.PinReference(new ReferenceInfo(new Guid(RubberduckGuid.RubberduckApiTypeLibGuid), string.Empty, string.Empty, version.Major, version.Minor)); - var documents = _environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + var documents = _environment?.GetFolderPath(Environment.SpecialFolder.MyDocuments); if (!string.IsNullOrEmpty(documents)) { defaults.ProjectPaths.Add(documents); } - var appdata = _environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var appdata = _environment?.GetFolderPath(Environment.SpecialFolder.ApplicationData); if (!string.IsNullOrEmpty(documents)) { var addins = Path.Combine(appdata, "Microsoft", "AddIns"); @@ -79,13 +79,13 @@ public ReferenceSettings CreateDefaults() public void Save(ReferenceSettings settings) { - if (_listening && !settings.AddToRecentOnReferenceEvents) + if (_listening && _events != null && !settings.AddToRecentOnReferenceEvents) { _events.ProjectReferenceAdded -= ReferenceAddedHandler; _listening = false; } - if (_listening && !settings.AddToRecentOnReferenceEvents) + if (_listening && _events != null && !settings.AddToRecentOnReferenceEvents) { _events.ProjectReferenceAdded += ReferenceAddedHandler; _listening = true; diff --git a/Rubberduck.Core/Settings/ReferenceSettings.cs b/Rubberduck.Core/Settings/ReferenceSettings.cs index 2c9360be8a..beb5a6f2ed 100644 --- a/Rubberduck.Core/Settings/ReferenceSettings.cs +++ b/Rubberduck.Core/Settings/ReferenceSettings.cs @@ -61,13 +61,21 @@ public ReferenceSettings(ReferenceSettings other) AddToRecentOnReferenceEvents = other.AddToRecentOnReferenceEvents; ProjectPaths = new List(other.ProjectPaths); other.SerializationPrep(new StreamingContext(StreamingContextStates.All)); - _recent = other._recent; - _pinned = other._pinned; + _recent = other._recent.Select(use => new HostUsages(use)).ToList(); + RecentLibraryReferences = other.RecentLibraryReferences.ToList(); + _pinned = other._pinned.Select(pin => new HostPins(pin)).ToList(); + PinnedLibraryReferences = other.PinnedLibraryReferences.ToList(); DeserializationLoad(new StreamingContext(StreamingContextStates.All)); } + private int _tracked; + [DataMember(IsRequired = true)] - public int RecentReferencesTracked { get; set; } + public int RecentReferencesTracked + { + get => _tracked; + set => _tracked = value < 0 ? 0 : Math.Min(value, RecentTrackingLimit); + } [DataMember(IsRequired = true)] public bool FixBrokenReferences { get; set; } @@ -128,7 +136,8 @@ public void TrackUsage(ReferenceInfo reference, string host = null) { var use = new ReferenceUsage(reference); if (string.IsNullOrEmpty(host)) - { + { + RecentLibraryReferences.RemoveAll(usage => usage.Matches(reference)); RecentLibraryReferences.Add(use); RecentLibraryReferences = RecentLibraryReferences .OrderByDescending(usage => usage.Timestamp) @@ -154,12 +163,20 @@ public void TrackUsage(ReferenceInfo reference, string host = null) .Take(RecentReferencesTracked).ToList(); } + // This is so close to damned near impossible that I was tempted to hard code it false, but it's useful for testing. public bool Equals(ReferenceSettings other) { + if (ReferenceEquals(this, other)) + { + return true; + } + if (other is null || - RecentReferencesTracked != other.RecentReferencesTracked || - !PinnedLibraryReferences.OrderBy(_ => _).SequenceEqual(other.PinnedLibraryReferences.OrderBy(_ => _)) || - !RecentLibraryReferences.OrderBy(_ => _).SequenceEqual(other.RecentLibraryReferences.OrderBy(_ => _))) + RecentReferencesTracked != other.RecentReferencesTracked || + PinnedLibraryReferences.Count != other.PinnedLibraryReferences.Count || + RecentLibraryReferences.Count != other.RecentLibraryReferences.Count || + PinnedLibraryReferences.Any(pin => !other.PinnedLibraryReferences.Any(lib => lib.Equals(pin))) || + RecentLibraryReferences.Any(recent => !other.RecentLibraryReferences.Any(lib => lib.Equals(recent)))) { return false; } @@ -167,7 +184,9 @@ public bool Equals(ReferenceSettings other) foreach (var host in PinnedProjectReferences) { if (!other.PinnedProjectReferences.ContainsKey(host.Key) || - !host.Value.OrderBy(_ => _).SequenceEqual(other.PinnedProjectReferences[host.Key].OrderBy(_ => _))) + !(other.PinnedProjectReferences[host.Key] is List otherHost) || + otherHost.Count != host.Value.Count || + host.Value.Any(pin => !otherHost.Any(lib => lib.Equals(pin)))) { return false; } @@ -176,8 +195,9 @@ public bool Equals(ReferenceSettings other) foreach (var host in RecentProjectReferences) { if (!other.RecentProjectReferences.ContainsKey(host.Key) || - !host.Value.OrderBy(usage => usage.Timestamp).Select(usage => usage.Reference) - .SequenceEqual(other.RecentProjectReferences[host.Key].OrderBy(usage => usage.Timestamp).Select(usage => usage.Reference))) + !(other.RecentProjectReferences[host.Key] is List otherHost) || + otherHost.Count != host.Value.Count || + host.Value.Any(pin => !otherHost.Any(lib => lib.Reference.Equals(pin.Reference) && lib.Timestamp.Equals(pin.Timestamp)))) { return false; } @@ -188,7 +208,7 @@ public bool Equals(ReferenceSettings other) public List GetPinnedReferencesForHost(string host) { - var key = host.ToUpperInvariant(); + var key = host?.ToUpperInvariant() ?? string.Empty; return PinnedLibraryReferences.Union(PinnedProjectReferences.ContainsKey(key) ? PinnedProjectReferences[key].ToList() : new List()).ToList(); @@ -196,7 +216,7 @@ public List GetPinnedReferencesForHost(string host) public List GetRecentReferencesForHost(string host) { - var key = host.ToUpperInvariant(); + var key = host?.ToUpperInvariant() ?? string.Empty; return RecentLibraryReferences .Concat(RecentProjectReferences.ContainsKey(key) ? RecentProjectReferences[key] @@ -207,6 +227,14 @@ public List GetRecentReferencesForHost(string host) public void UpdatePinnedReferencesForHost(string host, List pinned) { + var key = host?.ToUpperInvariant() ?? string.Empty; + + PinnedLibraryReferences.Clear(); + if (PinnedProjectReferences.ContainsKey(key)) + { + PinnedProjectReferences.Remove(key); + } + foreach (var reference in pinned) { PinReference(reference, reference.Guid.Equals(Guid.Empty) ? host : string.Empty); @@ -229,16 +257,19 @@ protected class ReferenceUsage public ReferenceInfo Reference { get; protected set; } [DataMember(IsRequired = true)] - public DateTime Timestamp { get; protected set; } = DateTime.Now; + public DateTime Timestamp { get; protected set; } public ReferenceUsage(ReferenceInfo reference) { Reference = reference; + Timestamp = DateTime.Now; } public bool Matches(ReferenceInfo other) { return Reference.FullPath.Equals(other.FullPath, StringComparison.OrdinalIgnoreCase) || + !Reference.Guid.Equals(Guid.Empty) && + !other.Guid.Equals(Guid.Empty) && Reference.Guid.Equals(other.Guid) && Reference.Major == other.Major && Reference.Minor == other.Minor; @@ -256,6 +287,12 @@ public HostUsages(string host, List usages) Host = host; Usages = usages; } + + public HostUsages(HostUsages other) + { + Host = other.Host; + Usages = other.Usages.ToList(); + } } [DataContract] @@ -269,6 +306,12 @@ public HostPins(string host, List usages) Host = host; Pins = usages; } + + public HostPins(HostPins other) + { + Host = other.Host; + Pins = other.Pins.ToList(); + } } } } diff --git a/Rubberduck.Parsing/ComReflection/ComDocumentation.cs b/Rubberduck.Parsing/ComReflection/ComDocumentation.cs index 75fc77bbd5..dcfc7256f0 100644 --- a/Rubberduck.Parsing/ComReflection/ComDocumentation.cs +++ b/Rubberduck.Parsing/ComReflection/ComDocumentation.cs @@ -3,8 +3,16 @@ namespace Rubberduck.Parsing.ComReflection { + public interface IComDocumentation + { + string Name { get; } + string DocString { get; } + string HelpFile { get; } + int HelpContext { get; } + } + [DataContract] - public class ComDocumentation + public class ComDocumentation : IComDocumentation { public const int LibraryIndex = -1; diff --git a/Rubberduck.Parsing/ComReflection/ComLibraryProvider.cs b/Rubberduck.Parsing/ComReflection/ComLibraryProvider.cs index 8cf90fdd1a..e8fd5aa6c0 100644 --- a/Rubberduck.Parsing/ComReflection/ComLibraryProvider.cs +++ b/Rubberduck.Parsing/ComReflection/ComLibraryProvider.cs @@ -1,5 +1,7 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; +using Rubberduck.VBEditor; +using Rubberduck.VBEditor.Utility; namespace Rubberduck.Parsing.ComReflection { @@ -38,5 +40,35 @@ public ITypeLib LoadTypeLibrary(string libraryPath) LoadTypeLibEx(libraryPath, REGKIND.REGKIND_NONE, out var typeLibrary); return typeLibrary; } + + public IComDocumentation GetComDocumentation(ITypeLib typelib) + { + try + { + return new ComDocumentation(typelib, ComDocumentation.LibraryIndex); + } + catch + { + return null; + } + } + + public ReferenceInfo GetReferenceInfo(ITypeLib typelib, string name, string path) + { + try + { + typelib.GetLibAttr(out var attributes); + using (DisposalActionContainer.Create(attributes, typelib.ReleaseTLibAttr)) + { + var typeAttr = Marshal.PtrToStructure(attributes); + + return new ReferenceInfo(typeAttr.guid, name, path, typeAttr.wMajorVerNum, typeAttr.wMinorVerNum); + } + } + catch + { + return ReferenceInfo.Empty; + } + } } } \ No newline at end of file diff --git a/Rubberduck.Parsing/ComReflection/IComLibraryProvider.cs b/Rubberduck.Parsing/ComReflection/IComLibraryProvider.cs index 4f715d91c1..044929c26f 100644 --- a/Rubberduck.Parsing/ComReflection/IComLibraryProvider.cs +++ b/Rubberduck.Parsing/ComReflection/IComLibraryProvider.cs @@ -1,9 +1,12 @@ using System.Runtime.InteropServices.ComTypes; +using Rubberduck.VBEditor; namespace Rubberduck.Parsing.ComReflection { public interface IComLibraryProvider { ITypeLib LoadTypeLibrary(string libraryPath); + IComDocumentation GetComDocumentation(ITypeLib typelib); + ReferenceInfo GetReferenceInfo(ITypeLib typelib, string name, string path); } } diff --git a/Rubberduck.Resources/RubberduckUI.Designer.cs b/Rubberduck.Resources/RubberduckUI.Designer.cs index 54855941e7..f75594a866 100644 --- a/Rubberduck.Resources/RubberduckUI.Designer.cs +++ b/Rubberduck.Resources/RubberduckUI.Designer.cs @@ -2866,6 +2866,17 @@ public static string References_Caption } } + /// + /// Looks up a localized string similar to Standard. + /// + public static string References_DefaultLocale + { + get + { + return ResourceManager.GetString("References_DefaultLocale", resourceCulture); + } + } + /// /// Looks up a localized string similar to Locale:. /// diff --git a/Rubberduck.Resources/RubberduckUI.resx b/Rubberduck.Resources/RubberduckUI.resx index a498e15280..e8710dcf8c 100644 --- a/Rubberduck.Resources/RubberduckUI.resx +++ b/Rubberduck.Resources/RubberduckUI.resx @@ -1434,4 +1434,8 @@ NOTE: Restart is required for the setting to take effect. All Visio Files ({0})|{0} {0} = semi-colon delimited extension list in the format of *.ext + + Standard + Displayed as LCID description when locale is not specified. + \ No newline at end of file diff --git a/RubberduckTests/AddRemoveReferences/AddRemoveReferencesSetup.cs b/RubberduckTests/AddRemoveReferences/AddRemoveReferencesSetup.cs new file mode 100644 index 0000000000..49c361c438 --- /dev/null +++ b/RubberduckTests/AddRemoveReferences/AddRemoveReferencesSetup.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices.ComTypes; +using Moq; +using Rubberduck.AddRemoveReferences; +using Rubberduck.Interaction; +using Rubberduck.Parsing.ComReflection; +using Rubberduck.Parsing.Symbols; +using Rubberduck.Resources.Registration; +using Rubberduck.Settings; +using Rubberduck.SettingsProvider; +using Rubberduck.UI.AddRemoveReferences; +using Rubberduck.VBEditor; +using Rubberduck.VBEditor.SafeComWrappers; +using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using RubberduckTests.Mocks; + +namespace RubberduckTests.AddRemoveReferences +{ + public static class AddRemoveReferencesSetup + { + // Note that these are just random for tests, don't use these for vbe7.dll or excel.exe in reality... + public static Guid VbaGuid = new Guid("c331e9a5-9f55-45d8-ab1c-3a6cb9b4e3c9"); + public static Guid ExcelGuid = new Guid("e58523e5-ad69-48fe-990c-712df2180ebc"); + public static Guid DummyGuidOne = new Guid(Enumerable.Range(1, 16).Select(x => (byte)x).ToArray()); + public static Guid DummyGuidTwo = new Guid(Enumerable.Range(2, 16).Select(x => (byte)x).ToArray()); + + public static List LibraryReferenceInfoList => + Enumerable.Range(1, 5) + .Select(info => + new ReferenceInfo(new Guid(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte)info), $"Reference{info}", $@"C:\Windows\System32\ref{info}.dll", 1, 0)) + .ToList(); + + public static List ProjectReferenceInfoList => + Enumerable.Range(1, 5) + .Select(info => + new ReferenceInfo(Guid.Empty, $"VBProject{info}", $@"C:\Users\Rubberduck\Documents\Book{info}.xlsm", 0, 0)) + .ToList(); + + public static List RecentProjectReferenceInfoList => + Enumerable.Range(1, 3) + .Select(info => + new ReferenceInfo(Guid.Empty, $"RecentProject{info}", $@"C:\Users\Rubberduck\Documents\RecentBook{info}.xlsm", 0, 0)) + .ToList(); + + public static List RecentLibraryReferenceInfoList => + Enumerable.Range(1, 5) + .Select(info => + new ReferenceInfo(new Guid(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte)info), $"Recent{info}", $@"C:\Windows\System32\recent{info}.dll", 1, 0)) + .ToList(); + + public static List MockedReferencesList => new List + { + new ReferenceModel(new ReferenceInfo(VbaGuid, "VBA", @"C:\Shortcut\VBE7.DLL", 4, 2), ReferenceKind.TypeLibrary) {IsBuiltIn = true, IsReferenced = true, Priority = 1 }, + new ReferenceModel(new ReferenceInfo(ExcelGuid, "Excel", @"C:\Office\EXCEL.EXE", 15, 0), ReferenceKind.TypeLibrary) {IsBuiltIn = true, IsReferenced = true, Priority = 2}, + new ReferenceModel(new ReferenceInfo(DummyGuidOne, "ReferenceOne", @"C:\Libs\reference1.dll", 1, 1), ReferenceKind.TypeLibrary) {IsReferenced = true, Priority = 3 }, + new ReferenceModel(new ReferenceInfo(DummyGuidTwo, "ReferenceTwo", @"C:\Libs\reference2.dll", 2, 2), ReferenceKind.TypeLibrary) {IsReferenced = true, Priority = 4 } + }; + + public static ReferenceSettings GetDefaultReferenceSettings() + { + var defaults = new ReferenceSettings + { + RecentReferencesTracked = 20 + }; + defaults.PinReference(new ReferenceInfo(new Guid(RubberduckGuid.RubberduckTypeLibGuid), string.Empty, string.Empty, 2, 4)); + defaults.PinReference(new ReferenceInfo(new Guid(RubberduckGuid.RubberduckApiTypeLibGuid), string.Empty, string.Empty, 2, 4)); + defaults.ProjectPaths.Add(@"C:\Users\Rubberduck\Documents"); + + return defaults; + } + + public static ReferenceSettings GetNonDefaultReferenceSettings() + { + var settings = new ReferenceSettings + { + RecentReferencesTracked = 42, + FixBrokenReferences = true, + AddToRecentOnReferenceEvents = true, + ProjectPaths = new List { @"C:\Users\SomeOtherUser\Documents" } + }; + + settings.UpdatePinnedReferencesForHost(null, LibraryReferenceInfoList); + settings.UpdatePinnedReferencesForHost("EXCEL.EXE", ProjectReferenceInfoList); + settings.UpdateRecentReferencesForHost(null, RecentLibraryReferenceInfoList); + settings.UpdateRecentReferencesForHost("EXCEL.EXE", RecentProjectReferenceInfoList); + + return settings; + } + + public static IConfigProvider GetReferenceSettingsProvider(ReferenceSettings settings = null) + { + return GetMockReferenceSettingsProvider(settings).Object; + } + + public static Mock> GetMockReferenceSettingsProvider(ReferenceSettings settings = null) + { + var output = new Mock>(); + + output.Setup(m => m.Create()).Returns(() => settings ?? GetDefaultReferenceSettings()); + output.Setup(m => m.CreateDefaults()).Returns(GetDefaultReferenceSettings); + output.Setup(m => m.Save(It.IsAny())); + + return output; + } + + public static ReferenceReconciler ArrangeReferenceReconciler( + ReferenceSettings settings, + out Mock messageBox, + out Mock libraryProvider) + { + messageBox = new Mock(); + libraryProvider = new Mock(); + return new ReferenceReconciler(messageBox.Object, GetReferenceSettingsProvider(settings), libraryProvider.Object); + } + + public static void SetupIComLibraryProvider(Mock provider, ReferenceInfo reference, string path, string description = "") + { + var documentation = new Mock(); + documentation.Setup(p => p.DocString).Returns(description); + documentation.Setup(p => p.Name).Returns(reference.Name); + documentation.Setup(p => p.HelpContext).Returns(0); + documentation.Setup(p => p.HelpFile).Returns(string.Empty); + + provider.Setup(m => m.GetComDocumentation(It.IsAny())).Returns(documentation.Object); + provider.Setup(m => m.GetReferenceInfo(It.IsAny(), reference.Name, path)).Returns(reference); + } + + public static Mock GetReferencesMock(out Mock project, out MockProjectBuilder builder) + { + builder = new MockProjectBuilder("TestBook", @"C:\TestBook.xlsm", ProjectProtection.Unprotected, ProjectType.HostProject, null, null); + var references = builder + .AddReference("VBA", @"C:\Shortcut\VBE7.DLL", 4, 2, true) + .AddReference("Excel", @"C:\Office\EXCEL.EXE", 15, 0, true) + .AddReference("ReferenceOne", @"C:\Libs\reference1.dll", 1, 1) + .AddReference("ReferenceTwo", @"C:\Libs\reference2.dll", 2, 2) + .GetMockedReferences(out project); + + return references; + } + + public static Mock ArrangeAddRemoveReferencesModel(List input, List output, ReferenceSettings settings = null) + { + var model = new Mock(); + + model.Setup(p => p.HostApplication).Returns("EXCEL.EXE"); + model.Setup(p => p.Settings).Returns(settings); + model.Setup(p => p.References).Returns(input); + model.Setup(p => p.NewReferences).Returns(output); + + return model; + } + + public static Mock ArrangeParsedAddRemoveReferencesModel( + List input, + List output, + List registered, + out Mock references, + out MockProjectBuilder projectBuilder) + { + var builder = new MockVbeBuilder(); + + projectBuilder = builder.ProjectBuilder("TestProject1", ProjectProtection.Unprotected) + .AddComponent("TestModule", ComponentType.StandardModule, string.Empty); + + references = projectBuilder + .AddReference("VBA", @"C:\Shortcut\VBE7.DLL", 4, 2, true) + .AddReference("Excel", @"C:\Office\EXCEL.EXE", 15, 0, true) + .AddReference("ReferenceOne", @"C:\Libs\reference1.dll", 1, 1) + .AddReference("ReferenceTwo", @"C:\Libs\reference2.dll", 2, 2) + .GetMockedReferences(out _); + + builder.AddProject(projectBuilder.Build()); + + var parser = MockParser.CreateAndParse(builder.Build().Object); + var declaration = parser.AllUserDeclarations.OfType().Single(); + + var model = ArrangeAddRemoveReferencesModel(input, output, GetDefaultReferenceSettings()); + model.Setup(m => m.Project).Returns(declaration); + + return model; + } + } +} diff --git a/RubberduckTests/AddRemoveReferences/ReferenceReconcilerTests.cs b/RubberduckTests/AddRemoveReferences/ReferenceReconcilerTests.cs new file mode 100644 index 0000000000..d112264cc9 --- /dev/null +++ b/RubberduckTests/AddRemoveReferences/ReferenceReconcilerTests.cs @@ -0,0 +1,412 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +using Moq; +using NUnit.Framework; +using Rubberduck.AddRemoveReferences; +using Rubberduck.Resources; +using Rubberduck.VBEditor; +using Rubberduck.VBEditor.SafeComWrappers; + + +namespace RubberduckTests.AddRemoveReferences +{ + [TestFixture] + public class ReferenceReconcilerTests + { + private static readonly ReferenceInfo DummyReferenceInfo = new ReferenceInfo(Guid.Empty, "RecentProject", @"C:\Users\Rubberduck\Documents\RecentBook.xlsm", 0, 0); + + [Test] + [Category("AddRemoveReferences")] + public void GetLibraryInfoFromPath_HandlesProjects() + { + const string path = @"C:\Users\Rubberduck\Documents\Book1.xlsm"; + + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out _); + var model = reconciler.GetLibraryInfoFromPath(path); + + Assert.AreEqual(path, model.FullPath); + Assert.IsFalse(model.IsBroken); + } + + [Test] + [Category("AddRemoveReferences")] + public void GetLibraryInfoFromPath_ProjectsDoNotCallLoadLibrary() + { + const string path = @"C:\Users\Rubberduck\Documents\Book1.xlsm"; + + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out var provider); + reconciler.GetLibraryInfoFromPath(path); + + provider.Verify(m => m.LoadTypeLibrary(It.IsAny()), Times.Never); + } + + [Test] + [TestCase(".olb")] + [TestCase(".tlb")] + [TestCase(".dll")] + [TestCase(".ocx")] + [TestCase(".exe")] + [Category("AddRemoveReferences")] + public void GetLibraryInfoFromPath_LoadLibraryCalledOnTypeExtensions(string extension) + { + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out var provider); + var path = $@"C:\Windows\System32\library{extension}"; + + AddRemoveReferencesSetup.SetupIComLibraryProvider(provider, new ReferenceInfo(Guid.Empty, "Library", path, 1, 1), path, "Library 1.1"); + reconciler.GetLibraryInfoFromPath(path); + + provider.Verify(m => m.LoadTypeLibrary(It.IsAny()), Times.Once); + } + + [Test] + [Category("AddRemoveReferences")] + public void GetLibraryInfoFromPath_NoExtensionReturnsNull() + { + const string path = @"C:\Users\Rubberduck\Documents\Book1"; + + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out _); + var model = reconciler.GetLibraryInfoFromPath(path); + + Assert.IsNull(model); + } + + [Test] + [Category("AddRemoveReferences")] + public void GetLibraryInfoFromPath_GivesBrokenReferenceOnThrow() + { + const string path = @"C:\Windows\System32\bad.dll"; + + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out var provider); + provider.Setup(m => m.LoadTypeLibrary(path)).Throws(new COMException()); + var model = reconciler.GetLibraryInfoFromPath(path); + + Assert.IsTrue(model.IsBroken); + } + + [Test] + [Category("AddRemoveReferences")] + public void GetLibraryInfoFromPath_LoadLibraryLoadsModel() + { + const string path = @"C:\Windows\System32\library.dll"; + const string name = "Library"; + const string description = "Library 1.1"; + + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out var provider); + var info = new ReferenceInfo(AddRemoveReferencesSetup.DummyGuidOne, name, path, 1, 1); + AddRemoveReferencesSetup.SetupIComLibraryProvider(provider, info, path, description); + + var model = reconciler.GetLibraryInfoFromPath(path); + + Assert.Multiple(() => + { + Assert.AreEqual(model.Guid, AddRemoveReferencesSetup.DummyGuidOne); + Assert.AreEqual(model.Name, name); + Assert.AreEqual(model.Description, description); + Assert.AreEqual(model.FullPath, path); + Assert.AreEqual(model.Major, 1); + Assert.AreEqual(model.Minor, 1); + Assert.IsFalse(model.IsBroken); + }); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + [Category("AddRemoveReferences")] + public void UpdateSettings_UpdatesRecentLibrariesBasedOnFlag(bool updating) + { + var settings = AddRemoveReferencesSetup.GetNonDefaultReferenceSettings(); + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(settings, out _, out _); + + var input = settings.GetRecentReferencesForHost(null).Select(info => + new ReferenceModel(info, ReferenceKind.TypeLibrary) { IsRecent = true }).ToList(); + + var added = new ReferenceInfo(AddRemoveReferencesSetup.DummyGuidOne, "Reference", @"C:\Windows\System32\reference.dll", 1, 0); + var output = input.Union(new []{ new ReferenceModel(added, ReferenceKind.TypeLibrary) { IsReferenced = true } }).ToList(); + + var model = AddRemoveReferencesSetup.ArrangeAddRemoveReferencesModel(output, null, settings); + + reconciler.UpdateSettings(model.Object, updating); + + var actual = settings.GetRecentReferencesForHost(null); + var expected = (updating ? output : input).Select(reference => reference.ToReferenceInfo()).ToList(); + + Assert.AreEqual(expected.Count, actual.Count); + Assert.IsTrue(expected.All(info => actual.Contains(info))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + [Category("AddRemoveReferences")] + public void UpdateSettings_UpdatesRecentProjectsBasedOnFlag(bool updating) + { + var settings = AddRemoveReferencesSetup.GetNonDefaultReferenceSettings(); + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(settings, out _, out _); + + var input = settings.GetRecentReferencesForHost("EXCEL.EXE").Select(info => + new ReferenceModel(info, ReferenceKind.Project) { IsRecent = true }).ToList(); + + var added = DummyReferenceInfo; + var output = input.Union(new[] { new ReferenceModel(added, ReferenceKind.TypeLibrary) { IsReferenced = true } }).ToList(); + + var model = AddRemoveReferencesSetup.ArrangeAddRemoveReferencesModel(output, null, settings); + + reconciler.UpdateSettings(model.Object, updating); + + var actual = settings.GetRecentReferencesForHost("EXCEL.EXE"); + var expected = (updating ? output : input).Select(reference => reference.ToReferenceInfo()).ToList(); + + Assert.AreEqual(updating ? expected.Count : input.Count, actual.Count); + Assert.IsTrue(expected.All(info => actual.Contains(info))); + } + + [Test] + [Category("AddRemoveReferences")] + public void UpdateSettings_AddsPinnedLibraries() + { + var settings = AddRemoveReferencesSetup.GetDefaultReferenceSettings(); + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(settings, out _, out _); + + var input = settings.GetPinnedReferencesForHost(null).Select(info => + new ReferenceModel(info, ReferenceKind.TypeLibrary) {IsPinned = true}).ToList(); + + var output = input.Union(AddRemoveReferencesSetup.LibraryReferenceInfoList.Select(info => + new ReferenceModel(info, ReferenceKind.TypeLibrary) {IsPinned = true})).ToList(); + + var model = AddRemoveReferencesSetup.ArrangeAddRemoveReferencesModel(output, null, settings); + + reconciler.UpdateSettings(model.Object); + + var actual = settings.GetPinnedReferencesForHost(null); + var expected = output.Select(reference => reference.ToReferenceInfo()).ToList(); + + Assert.AreEqual(expected.Count, actual.Count); + Assert.IsTrue(expected.All(info => actual.Contains(info))); + } + + [Test] + [Category("AddRemoveReferences")] + public void UpdateSettings_RemovesPinnedLibraries() + { + var settings = AddRemoveReferencesSetup.GetDefaultReferenceSettings(); + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(settings, out _, out _); + + var input = settings.GetPinnedReferencesForHost(null).Select(info => + new ReferenceModel(info, ReferenceKind.TypeLibrary) { IsPinned = true }).ToList(); + + var output = input.Take(1).ToList(); + + var model = AddRemoveReferencesSetup.ArrangeAddRemoveReferencesModel(output, null, settings); + + reconciler.UpdateSettings(model.Object); + + var actual = settings.GetPinnedReferencesForHost(null); + var expected = output.Select(reference => reference.ToReferenceInfo()).ToList(); + + Assert.AreEqual(expected.Count, actual.Count); + Assert.IsTrue(expected.All(info => actual.Contains(info))); + } + + [Test] + [Category("AddRemoveReferences")] + public void TryAddReferenceString_CallsAddFromFile() + { + const string file = @"C:\Windows\System32\reference.dll"; + + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out _); + var references = AddRemoveReferencesSetup.GetReferencesMock(out var project, out _); + + reconciler.TryAddReference(project.Object, file); + + references.Verify(m => m.AddFromFile(file), Times.Once); + } + + [Test] + [Category("AddRemoveReferences")] + public void TryAddReferenceString_ReturnsNullOnThrow() + { + const string file = @"C:\Windows\System32\reference.dll"; + + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out _); + var references = AddRemoveReferencesSetup.GetReferencesMock(out var project, out _); + references.Setup(m => m.AddFromFile(file)).Throws(new COMException()); + + var model = reconciler.TryAddReference(project.Object, file); + + Assert.IsNull(model); + } + + [Test] + [Category("AddRemoveReferences")] + public void TryAddReferenceString_DisplaysMessageOnThrow() + { + const string file = @"C:\Windows\System32\reference.dll"; + const string exception = "Don't mock me."; + + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out var messageBox, out _); + var references = AddRemoveReferencesSetup.GetReferencesMock(out var project, out _); + references.Setup(m => m.AddFromFile(file)).Throws(new COMException(exception)); + + reconciler.TryAddReference(project.Object, file); + + messageBox.Verify(m => m.NotifyWarn(exception, RubberduckUI.References_AddFailedCaption)); + } + + [Test] + [Category("AddRemoveReferences")] + public void TryAddReferenceString_ReturnedReferenceIsRecent() + { + const string file = @"C:\Windows\System32\reference.dll"; + + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out _); + var references = AddRemoveReferencesSetup.GetReferencesMock(out var project, out var builder); + + var returned = builder.CreateReferenceMock("Reference", file, 1, 1, false).Object; + references.Setup(m => m.AddFromFile(file)).Returns(returned); + + var model = reconciler.TryAddReference(project.Object, file); + + Assert.IsTrue(model.IsRecent); + } + + [Test] + [Category("AddRemoveReferences")] + public void TryAddReferenceReferenceModel_ReturnedReferenceIsRecent() + { + var input = new ReferenceModel(DummyReferenceInfo, 0); + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out _); + AddRemoveReferencesSetup.GetReferencesMock(out var project, out _); + + var model = reconciler.TryAddReference(project.Object, input); + + Assert.IsTrue(model.IsRecent); + } + + [Test] + [Category("AddRemoveReferences")] + public void TryAddReferenceReferenceModel_ReturnedReferenceIsLastPriority() + { + var input = new ReferenceModel(DummyReferenceInfo, 0); + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out _); + var references = AddRemoveReferencesSetup.GetReferencesMock(out var project, out _).Object; + + var priority = reconciler.TryAddReference(project.Object, input).Priority; + + Assert.IsTrue(priority > 0); + } + + [Test] + [Category("AddRemoveReferences")] + public void TryAddReferenceReferenceModel_DisplaysMessageOnThrow() + { + var input = new ReferenceModel(DummyReferenceInfo, 0); + const string exception = "Don't mock me."; + + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out var messageBox, out _); + var references = AddRemoveReferencesSetup.GetReferencesMock(out var project, out _); + references.Setup(m => m.AddFromFile(input.FullPath)).Throws(new COMException(exception)); + + reconciler.TryAddReference(project.Object, input); + + messageBox.Verify(m => m.NotifyWarn(exception, RubberduckUI.References_AddFailedCaption)); + } + + [Test] + [Category("AddRemoveReferences")] + public void TryAddReferenceReferenceModel_ReturnsNullOnThrow() + { + var input = new ReferenceModel(DummyReferenceInfo, 0); + + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out _); + var references = AddRemoveReferencesSetup.GetReferencesMock(out var project, out _); + references.Setup(m => m.AddFromFile(input.FullPath)).Throws(new COMException()); + + var model = reconciler.TryAddReference(project.Object, input); + + Assert.IsNull(model); + } + + [Test] + [Category("AddRemoveReferences")] + public void ReconcileReferences_ReturnsEmptyWithoutNewReferences() + { + var model = AddRemoveReferencesSetup.ArrangeParsedAddRemoveReferencesModel(null, null, null, out _, out _); + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out _); + + var output = reconciler.ReconcileReferences(model.Object); + + Assert.IsEmpty(output); + } + + [Test] + [Category("AddRemoveReferences")] + public void ReconcileReferencesOverload_ReturnsEmptyWithoutNewReferences() + { + var model = AddRemoveReferencesSetup.ArrangeParsedAddRemoveReferencesModel(null, null, null, out _, out _); + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out _); + var output = reconciler.ReconcileReferences(model.Object, null); + + Assert.IsEmpty(output); + } + + [Test] + [Category("AddRemoveReferences")] + public void ReconcileReferences_UpdatesSettingsPinned() + { + var newReferences = AddRemoveReferencesSetup.LibraryReferenceInfoList + .Select(reference => new ReferenceModel(reference, ReferenceKind.TypeLibrary)).ToList(); + + var model = AddRemoveReferencesSetup.ArrangeParsedAddRemoveReferencesModel(null, newReferences, null, out _, out _); + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out _); + + var pinned = newReferences.First(); + pinned.IsPinned = true; + + reconciler.ReconcileReferences(model.Object, newReferences); + var result = model.Object.Settings.GetPinnedReferencesForHost(null).Exists(info => pinned.Matches(info)); + + Assert.IsTrue(result); + } + + [Test] + [Category("AddRemoveReferences")] + public void ReconcileReferences_AllReferencesAreAdded() + { + var newReferences = AddRemoveReferencesSetup.LibraryReferenceInfoList + .Select(reference => new ReferenceModel(reference, ReferenceKind.TypeLibrary)).ToList(); + + var model = AddRemoveReferencesSetup.ArrangeParsedAddRemoveReferencesModel(newReferences, newReferences, newReferences, out var references, out var builder); + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out _); + + var priority = references.Object.Count; + foreach (var item in newReferences) + { + item.Priority = ++priority; + var result = builder.CreateReferenceMock(item.Name, item.FullPath, item.Major, item.Minor); + references.Setup(m => m.AddFromFile(item.FullPath)).Returns(result.Object); + } + + var added = reconciler.ReconcileReferences(model.Object, newReferences); + + Assert.IsTrue(newReferences.All(reference => added.Contains(reference))); + } + + [Test] + [Category("AddRemoveReferences")] + public void ReconcileReferences_RemoveNotCalledOnBuiltIn() + { + var registered = AddRemoveReferencesSetup.MockedReferencesList; + var model = AddRemoveReferencesSetup.ArrangeParsedAddRemoveReferencesModel(registered, registered, registered, out var references, out _); + var reconciler = AddRemoveReferencesSetup.ArrangeReferenceReconciler(null, out _, out _); + + var vba = references.Object.First(lib => lib.Name.Equals("VBA")); + var excel = references.Object.First(lib => lib.Name.Equals("Excel")); + + reconciler.ReconcileReferences(model.Object, registered); + references.Verify(m => m.Remove(vba), Times.Never); + references.Verify(m => m.Remove(excel), Times.Never); + } + } +} \ No newline at end of file diff --git a/RubberduckTests/Mocks/MockProjectBuilder.cs b/RubberduckTests/Mocks/MockProjectBuilder.cs index 8d8204e9ab..201c477940 100644 --- a/RubberduckTests/Mocks/MockProjectBuilder.cs +++ b/RubberduckTests/Mocks/MockProjectBuilder.cs @@ -229,6 +229,12 @@ private Mock CreateComponentsMock() return result; } + public Mock GetMockedReferences(out Mock project) + { + project = Build(); + return _vbReferences; + } + private Mock CreateReferencesMock() { var result = new Mock(); @@ -244,7 +250,7 @@ private Mock CreateReferencesMock() return result; } - private Mock CreateReferenceMock(string name, string filePath, int major, int minor, bool isBuiltIn = true) + public Mock CreateReferenceMock(string name, string filePath, int major, int minor, bool isBuiltIn = true) { var result = new Mock(); diff --git a/RubberduckTests/Settings/ReferenceSettingsTests.cs b/RubberduckTests/Settings/ReferenceSettingsTests.cs new file mode 100644 index 0000000000..0656fd5839 --- /dev/null +++ b/RubberduckTests/Settings/ReferenceSettingsTests.cs @@ -0,0 +1,389 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Moq; +using NUnit.Framework; +using Rubberduck.Settings; +using Rubberduck.UI; +using Rubberduck.UI.Settings; +using Rubberduck.VBEditor; +using RubberduckTests.AddRemoveReferences; + +namespace RubberduckTests.Settings +{ + [TestFixture] + public class ReferenceSettingsTests + { + private static AddRemoveReferencesUserSettingsViewModel GetSettingsViewModel(ReferenceSettings settings) + { + return new AddRemoveReferencesUserSettingsViewModel(AddRemoveReferencesSetup.GetReferenceSettingsProvider(settings), new Mock().Object); + } + + [Test] + [Category("Settings")] + public void CopyCtorCopiesAllValues() + { + var settings = AddRemoveReferencesSetup.GetNonDefaultReferenceSettings(); + var copy = new ReferenceSettings(settings); + + Assert.IsTrue(settings.Equals(copy)); + } + + [Test] + [Category("Settings")] + public void PinReference_RejectsDuplicateLibraries() + { + var library = new ReferenceInfo(AddRemoveReferencesSetup.DummyGuidOne, "Reference", @"C:\Windows\System32\reference.dll", 1, 0); + + var settings = new ReferenceSettings(); + settings.PinReference(library); + settings.PinReference(library); + + Assert.AreEqual(1, settings.GetPinnedReferencesForHost(null).Count); + } + + [Test] + [Category("Settings")] + public void PinReference_RejectsDuplicateProjects() + { + const string host = "EXCEL.EXE"; + var project = new ReferenceInfo(Guid.Empty, "RecentProject", @"C:\Users\Rubberduck\Documents\RecentBook.xlsm", 0, 0); + + var settings = new ReferenceSettings(); + settings.PinReference(project, host); + settings.PinReference(project, host); + + Assert.AreEqual(1, settings.GetPinnedReferencesForHost(host).Count); + } + + [Test] + [Category("Settings")] + public void UpdatePinnedReferencesForHost_RejectsDuplicateLibraries() + { + var library = new ReferenceInfo(AddRemoveReferencesSetup.DummyGuidOne, "Reference", @"C:\Windows\System32\reference.dll", 1, 0); + + var settings = new ReferenceSettings(); + settings.UpdatePinnedReferencesForHost(null, new List { library }); + settings.UpdatePinnedReferencesForHost(null, new List { library }); + + Assert.AreEqual(1, settings.GetPinnedReferencesForHost(null).Count); + } + + [Test] + [Category("Settings")] + public void UpdatePinnedReferencesForHost_RejectsDuplicateProjects() + { + const string host = "EXCEL.EXE"; + var project = new ReferenceInfo(Guid.Empty, "RecentProject", @"C:\Users\Rubberduck\Documents\RecentBook.xlsm", 0, 0); + + var settings = new ReferenceSettings(); + settings.UpdatePinnedReferencesForHost(host, new List { project }); + settings.UpdatePinnedReferencesForHost(host, new List { project }); + + Assert.AreEqual(1, settings.GetPinnedReferencesForHost(host).Count); + } + + [Test] + [Category("Settings")] + public void TrackUsage_RejectsDuplicateLibraries() + { + var library = new ReferenceInfo(AddRemoveReferencesSetup.DummyGuidOne, "Reference", @"C:\Windows\System32\reference.dll", 1, 0); + + var settings = new ReferenceSettings { RecentReferencesTracked = 20 }; + settings.TrackUsage(library); + settings.TrackUsage(library); + + Assert.AreEqual(1, settings.GetRecentReferencesForHost(null).Count); + } + + [Test] + [Category("Settings")] + public void TrackUsage_RejectsDuplicateProjects() + { + const string host = "EXCEL.EXE"; + var project = new ReferenceInfo(Guid.Empty, "RecentProject", @"C:\Users\Rubberduck\Documents\RecentBook.xlsm", 0, 0); + + var settings = new ReferenceSettings { RecentReferencesTracked = 20 }; + settings.TrackUsage(project, host); + settings.TrackUsage(project, host); + + Assert.AreEqual(1, settings.GetRecentReferencesForHost(host).Count); + } + + [Test] + [Category("Settings")] + public void UpdateRecentReferencesForHost_RejectsDuplicateLibraries() + { + var library = new ReferenceInfo(AddRemoveReferencesSetup.DummyGuidOne, "Reference", @"C:\Windows\System32\reference.dll", 1, 0); + + var settings = new ReferenceSettings { RecentReferencesTracked = 20 }; + settings.UpdateRecentReferencesForHost(null, new List { library }); + settings.UpdateRecentReferencesForHost(null, new List { library }); + + Assert.AreEqual(1, settings.GetRecentReferencesForHost(null).Count); + } + + [Test] + [Category("Settings")] + public void UpdateRecentReferencesForHost_RejectsDuplicateProjects() + { + const string host = "EXCEL.EXE"; + var project = new ReferenceInfo(Guid.Empty, "RecentProject", @"C:\Users\Rubberduck\Documents\RecentBook.xlsm", 0, 0); + + var settings = new ReferenceSettings { RecentReferencesTracked = 20 }; + settings.UpdateRecentReferencesForHost(host, new List { project }); + settings.UpdateRecentReferencesForHost(host, new List { project }); + + Assert.AreEqual(1, settings.GetRecentReferencesForHost(host).Count); + } + + [Test] + [Category("Settings")] + public void TrackUsage_KeepsNewestLibraries() + { + var settings = new ReferenceSettings { RecentReferencesTracked = AddRemoveReferencesSetup.LibraryReferenceInfoList.Count }; + settings.UpdateRecentReferencesForHost(null, AddRemoveReferencesSetup.LibraryReferenceInfoList); + + var expected = AddRemoveReferencesSetup.LibraryReferenceInfoList.First(); + settings.TrackUsage(expected); + + var actual = settings.GetRecentReferencesForHost(null).Last(); + + Assert.IsTrue(expected.Equals(actual)); + } + + [Test] + [Category("Settings")] + public void TrackUsage_KeepsNewestProjects() + { + const string host = "EXCEL.EXE"; + + var settings = new ReferenceSettings { RecentReferencesTracked = AddRemoveReferencesSetup.ProjectReferenceInfoList.Count }; + settings.UpdateRecentReferencesForHost(host, AddRemoveReferencesSetup.ProjectReferenceInfoList); + + var expected = AddRemoveReferencesSetup.ProjectReferenceInfoList.First(); + settings.TrackUsage(expected, host); + + var actual = settings.GetRecentReferencesForHost(host).Last(); + + Assert.IsTrue(expected.Equals(actual)); + } + + [Test] + [Category("Settings")] + [TestCase(10, 10)] + [TestCase(-1, 0)] + [TestCase(100, ReferenceSettings.RecentTrackingLimit)] + public void RecentReferencesTracked_LimitedToRange(int input, int expected) + { + var settings = new ReferenceSettings { RecentReferencesTracked = input }; + Assert.AreEqual(expected, settings.RecentReferencesTracked); + } + + [Test] + [Category("Settings")] + public void GetRecentReferencesForHostLibraries_LimitedByRecentReferencesTracked() + { + const int tracked = 3; + + var settings = new ReferenceSettings { RecentReferencesTracked = tracked }; + settings.UpdateRecentReferencesForHost(null, AddRemoveReferencesSetup.LibraryReferenceInfoList); + + Assert.AreEqual(tracked, settings.GetRecentReferencesForHost(null).Count); + } + + [Test] + [Category("Settings")] + public void GetRecentReferencesForHostProjects_LimitedByRecentReferencesTracked() + { + const string host = "EXCEL.EXE"; + const int tracked = 3; + + var settings = new ReferenceSettings { RecentReferencesTracked = tracked }; + settings.UpdateRecentReferencesForHost(host, AddRemoveReferencesSetup.RecentProjectReferenceInfoList); + + Assert.AreEqual(tracked, settings.GetRecentReferencesForHost(host).Count); + } + + [Test] + [Category("Settings")] + public void GetRecentReferencesForHostCombined_LimitedByRecentReferencesTracked() + { + const string host = "EXCEL.EXE"; + const int tracked = 7; + + var settings = new ReferenceSettings { RecentReferencesTracked = tracked }; + settings.UpdateRecentReferencesForHost(null, AddRemoveReferencesSetup.RecentLibraryReferenceInfoList); + settings.UpdateRecentReferencesForHost(host, AddRemoveReferencesSetup.RecentProjectReferenceInfoList); + + Assert.AreEqual(tracked, settings.GetRecentReferencesForHost(host).Count); + } + + [Test] + [Category("Settings")] + [TestCase(@"C:\Foo\bar.xlsm", @"C:\Foo\bar.xlsm", "EXCEL.EXE", "EXCEL.EXE", true)] + [TestCase(@"C:\FOO\BAR.XLSM", @"c:\foo\bar.xlsm", "EXCEL.EXE", "EXCEL.EXE", true)] + [TestCase(@"c:\foo\bar.xlsm", @"C:\FOO\BAR.XLSM", "EXCEL.EXE", "EXCEL.EXE", true)] + [TestCase(@"C:\Foo\bar.xlsm", @"C:\Bar\foo.xlsm", "EXCEL.EXE", "EXCEL.EXE", false)] + [TestCase(@"C:\Foo\bar.xlsm", @"X:\Foo\bar.xlsm", "EXCEL.EXE", "EXCEL.EXE", false)] + [TestCase(@"C:\Foo\bar.xlsm", @"C:\Foo\bar.xlsm", "WINWORD.EXE", "EXCEL.EXE", false)] + [TestCase(@"C:\Foo\bar.xlsm", @"C:\Foo\bar.xlsm", "EXCEL.EXE", "WINWORD.EXE", false)] + public void IsPinnedProject_CorrectResult(string pinned, string tested, string host1, string host2, bool expected) + { + var settings = new ReferenceSettings(); + settings.PinReference(new ReferenceInfo(Guid.Empty, string.Empty, pinned, 0, 0), host1); + + Assert.AreEqual(expected, settings.IsPinnedProject(tested, host2)); + } + + [Test] + [Category("Settings")] + [TestCase(@"C:\Foo\bar.xlsm", @"C:\Foo\bar.xlsm", "EXCEL.EXE", "EXCEL.EXE", true)] + [TestCase(@"C:\FOO\BAR.XLSM", @"c:\foo\bar.xlsm", "EXCEL.EXE", "EXCEL.EXE", true)] + [TestCase(@"c:\foo\bar.xlsm", @"C:\FOO\BAR.XLSM", "EXCEL.EXE", "EXCEL.EXE", true)] + [TestCase(@"C:\Foo\bar.xlsm", @"C:\Bar\foo.xlsm", "EXCEL.EXE", "EXCEL.EXE", false)] + [TestCase(@"C:\Foo\bar.xlsm", @"X:\Foo\bar.xlsm", "EXCEL.EXE", "EXCEL.EXE", false)] + [TestCase(@"C:\Foo\bar.xlsm", @"C:\Foo\bar.xlsm", "WINWORD.EXE", "EXCEL.EXE", false)] + [TestCase(@"C:\Foo\bar.xlsm", @"C:\Foo\bar.xlsm", "EXCEL.EXE", "WINWORD.EXE", false)] + public void IsRecentProject_CorrectResult(string pinned, string tested, string host1, string host2, bool expected) + { + var settings = new ReferenceSettings { RecentReferencesTracked = 20 }; + settings.TrackUsage(new ReferenceInfo(Guid.Empty, string.Empty, pinned, 0, 0), host1); + + Assert.AreEqual(expected, settings.IsRecentProject(tested, host2)); + } + + [Test] + [Category("Settings")] + public void UpdateConfig_CallsSave() + { + var clean = AddRemoveReferencesSetup.GetDefaultReferenceSettings(); + var provider = AddRemoveReferencesSetup.GetMockReferenceSettingsProvider(clean); + var viewModel = new AddRemoveReferencesUserSettingsViewModel(provider.Object, new Mock().Object); + + viewModel.UpdateConfig(null); + provider.Verify(m => m.Save(It.IsAny()), Times.Once); + } + + [Test] + [Category("Settings")] + public void UpdateConfig_UsesLoadedSettingsInstance() + { + var clean = AddRemoveReferencesSetup.GetDefaultReferenceSettings(); + var provider = AddRemoveReferencesSetup.GetMockReferenceSettingsProvider(clean); + var viewModel = new AddRemoveReferencesUserSettingsViewModel(provider.Object, new Mock().Object); + + viewModel.UpdateConfig(null); + provider.Verify(m => m.Save(clean), Times.Once); + } + + [Test] + [Category("Settings")] + [TestCase("EXCEL.EXE")] + [TestCase(null)] + public void UpdateConfig_DoesNotChangePinned(string host) + { + var clean = AddRemoveReferencesSetup.GetNonDefaultReferenceSettings(); + var expected = clean.GetPinnedReferencesForHost(host); + + var viewModel = GetSettingsViewModel(clean); + viewModel.UpdateConfig(null); + var actual = clean.GetPinnedReferencesForHost(host); + + Assert.AreEqual(expected.Count, actual.Count); + Assert.IsTrue(expected.All(reference => actual.Contains(reference))); + } + + [Test] + [Category("Settings")] + [TestCase("EXCEL.EXE")] + [TestCase(null)] + public void UpdateConfig_DoesNotChangeRecent(string host) + { + var clean = AddRemoveReferencesSetup.GetNonDefaultReferenceSettings(); + var expected = clean.GetRecentReferencesForHost(host); + + var viewModel = GetSettingsViewModel(clean); + viewModel.UpdateConfig(null); + var actual = clean.GetRecentReferencesForHost(host); + + Assert.AreEqual(expected.Count, actual.Count); + Assert.IsTrue(expected.All(reference => actual.Contains(reference))); + } + + [Test] + [Category("Settings")] + [TestCase("EXCEL.EXE")] + [TestCase(null)] + public void SetDefaults_DoesNotChangePinned(string host) + { + var clean = AddRemoveReferencesSetup.GetNonDefaultReferenceSettings(); + var expected = clean.GetPinnedReferencesForHost(host); + + var viewModel = GetSettingsViewModel(clean); + viewModel.SetToDefaults(null); + var actual = clean.GetPinnedReferencesForHost(host); + + Assert.AreEqual(expected.Count, actual.Count); + Assert.IsTrue(expected.All(reference => actual.Contains(reference))); + } + + [Test] + [Category("Settings")] + [TestCase("EXCEL.EXE")] + [TestCase(null)] + public void SetDefaults_DoesNotChangeRecent(string host) + { + var clean = AddRemoveReferencesSetup.GetNonDefaultReferenceSettings(); + var expected = clean.GetRecentReferencesForHost(host); + + var viewModel = GetSettingsViewModel(clean); + viewModel.SetToDefaults(null); + var actual = clean.GetRecentReferencesForHost(host); + + Assert.AreEqual(expected.Count, actual.Count); + Assert.IsTrue(expected.All(reference => actual.Contains(reference))); + } + + [Test] + [Category("Settings")] + public void SettingsTransferToViewModel() + { + var clean = AddRemoveReferencesSetup.GetNonDefaultReferenceSettings(); + var viewModel = GetSettingsViewModel(clean); + + Assert.Multiple(() => + { + Assert.AreEqual(clean.RecentReferencesTracked, viewModel.RecentReferencesTracked); + Assert.AreEqual(clean.FixBrokenReferences, viewModel.FixBrokenReferences); + Assert.AreEqual(clean.AddToRecentOnReferenceEvents, viewModel.AddToRecentOnReferenceEvents); + Assert.IsTrue(clean.ProjectPaths.SequenceEqual(viewModel.ProjectPaths)); + }); + } + + [Test] + [Category("Settings")] + public void ViewModelTransfersToSettings() + { + var clean = AddRemoveReferencesSetup.GetDefaultReferenceSettings(); + var viewModel = GetSettingsViewModel(clean); + + viewModel.RecentReferencesTracked = 42; + viewModel.FixBrokenReferences = true; + viewModel.AddToRecentOnReferenceEvents = true; + + var paths = new List { @"C:\Foo" }; + viewModel.ProjectPaths = new ObservableCollection(paths); + viewModel.UpdateConfig(null); + + Assert.Multiple(() => + { + Assert.AreEqual(clean.RecentReferencesTracked, 42); + Assert.AreEqual(clean.FixBrokenReferences, true); + Assert.AreEqual(clean.AddToRecentOnReferenceEvents, true); + Assert.IsTrue(clean.ProjectPaths.SequenceEqual(paths)); + }); + } + } +}