Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
<RootNamespace>DeviceDetectorNET.Icons.Tests</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Shouldly" Version="4.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DeviceDetector.NET\DeviceDetector.NET.csproj" />
<ProjectReference Include="..\DeviceDetector.NET.Icons\DeviceDetector.NET.Icons.csproj" />
</ItemGroup>

</Project>
53 changes: 53 additions & 0 deletions DeviceDetector.NET.Icons.Tests/IconResolverConstructionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using DeviceDetectorNET.Icons;
using Shouldly;
using Xunit;

namespace DeviceDetectorNET.Icons.Tests
{
public class IconResolverConstructionTests
{
[Fact]
public void ThrowsWhenOptionsIsNull()
{
Should.Throw<ArgumentNullException>(() => new IconResolver(null));
}

[Fact]
public void ThrowsWhenNeitherPhysicalRootPathNorFileExistsIsSet()
{
var options = new IconResolverOptions();

Should.Throw<ArgumentException>(() => new IconResolver(options));
}

[Fact]
public void ThrowsWhenExtensionPriorityIsEmpty()
{
var options = new IconResolverOptions
{
PhysicalRootPath = "C:\\icons",
ExtensionPriority = new List<string>()
};

Should.Throw<ArgumentException>(() => new IconResolver(options));
}

[Fact]
public void DoesNotThrowWhenPhysicalRootPathIsSet()
{
var options = new IconResolverOptions { PhysicalRootPath = "C:\\icons" };

Should.NotThrow(() => new IconResolver(options));
}

[Fact]
public void DoesNotThrowWhenFileExistsDelegateIsSetWithoutPhysicalRootPath()
{
var options = new IconResolverOptions { FileExists = _ => false };

Should.NotThrow(() => new IconResolver(options));
}
}
}
43 changes: 43 additions & 0 deletions DeviceDetector.NET.Icons.Tests/IconResolverEndToEndTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using DeviceDetectorNET.Icons;
using Shouldly;
using Xunit;

namespace DeviceDetectorNET.Icons.Tests
{
public class IconResolverEndToEndTests
{
[Fact]
public void ResolvesIconsForARealParsedUserAgent()
{
const string userAgent =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36";

var parseResult = DeviceDetectorNET.DeviceDetector.GetInfoFromUserAgent(userAgent);
parseResult.Success.ShouldBeTrue();

var resolver = new IconResolver(new IconResolverOptions { FileExists = _ => true });

var icons = resolver.GetIcons(parseResult.Match);

icons.BotIcon.ShouldBeNull();
icons.ClientIcon.ShouldContain("Chrome");
icons.OsIcon.ShouldContain("Windows");
}

[Fact]
public void ResolvesBotIconForARealParsedBotUserAgent()
{
const string userAgent = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)";

var parseResult = DeviceDetectorNET.DeviceDetector.GetInfoFromUserAgent(userAgent);
parseResult.Success.ShouldBeTrue();
parseResult.Match.IsBoot.ShouldBeTrue();

var resolver = new IconResolver(new IconResolverOptions { FileExists = _ => true });

var icons = resolver.GetIcons(parseResult.Match);

icons.BotIcon.ShouldContain("Googlebot");
}
}
}
126 changes: 126 additions & 0 deletions DeviceDetector.NET.Icons.Tests/IconResolverFallbackChainTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using DeviceDetectorNET.Icons;
using Shouldly;
using Xunit;

namespace DeviceDetectorNET.Icons.Tests
{
public class IconResolverFallbackChainTests
{
[Fact]
public void GetBotPrefersBotNameOverCategory()
{
using var dir = new TempIconDirectory();
dir.CreateFile("bot/Googlebot.svg");
dir.CreateFile("bot/category/Search bot.svg");

var resolver = new IconResolver(new IconResolverOptions { PhysicalRootPath = dir.RootPath });

resolver.GetBot("Googlebot", "Search bot").ShouldBe("/assets/images/devicedetector/bot/Googlebot.svg");
}

[Fact]
public void GetBotFallsBackToCategoryWhenBotIconMissing()
{
using var dir = new TempIconDirectory();
dir.CreateFile("bot/category/Search bot.svg");

var resolver = new IconResolver(new IconResolverOptions { PhysicalRootPath = dir.RootPath });

resolver.GetBot("UnknownBot", "Search bot").ShouldBe("/assets/images/devicedetector/bot/category/Search bot.svg");
}

[Fact]
public void GetBotFallsBackToFallbackIconWhenNothingMatches()
{
using var dir = new TempIconDirectory();

var resolver = new IconResolver(new IconResolverOptions { PhysicalRootPath = dir.RootPath });

resolver.GetBot("UnknownBot", "UnknownCategory").ShouldBe("/assets/images/devicedetector/Matomo.svg");
}

[Fact]
public void GetBrowserFallsThroughFamilyThenEngineThenGenericBrowserIcon()
{
using var dir = new TempIconDirectory();
dir.CreateFile("client/browser/engine/Blink.svg");
dir.CreateFile("client/type/browser.svg");

var resolver = new IconResolver(new IconResolverOptions { PhysicalRootPath = dir.RootPath });

resolver.GetBrowser("SomeNewBrowser", "SomeNewFamily", "Blink")
.ShouldBe("/assets/images/devicedetector/client/browser/engine/Blink.svg");
}

[Fact]
public void GetBrowserFallsBackToGenericBrowserIconWhenNothingElseMatches()
{
using var dir = new TempIconDirectory();
dir.CreateFile("client/type/browser.svg");

var resolver = new IconResolver(new IconResolverOptions { PhysicalRootPath = dir.RootPath });

resolver.GetBrowser("SomeNewBrowser", "SomeNewFamily", "SomeNewEngine")
.ShouldBe("/assets/images/devicedetector/client/type/browser.svg");
}

[Fact]
public void GetOsFallsThroughFamilyThenGenericOsIcon()
{
using var dir = new TempIconDirectory();
dir.CreateFile("client/type/os.svg");

var resolver = new IconResolver(new IconResolverOptions { PhysicalRootPath = dir.RootPath });

resolver.GetOs("SomeNewOs", "SomeNewFamily").ShouldBe("/assets/images/devicedetector/client/type/os.svg");
}

[Fact]
public void GetClientUsesTypeSubfolderThenGenericTypeIcon()
{
using var dir = new TempIconDirectory();
dir.CreateFile("client/feed reader/Feedly.svg");

var resolver = new IconResolver(new IconResolverOptions { PhysicalRootPath = dir.RootPath });

resolver.GetClient("Feedly", "feed reader")
.ShouldBe("/assets/images/devicedetector/client/feed reader/Feedly.svg");
}

[Fact]
public void GetClientFallsBackToGenericTypeIconWhenClientIconMissing()
{
using var dir = new TempIconDirectory();
dir.CreateFile("client/type/feed reader.svg");

var resolver = new IconResolver(new IconResolverOptions { PhysicalRootPath = dir.RootPath });

resolver.GetClient("SomeUnknownReader", "feed reader")
.ShouldBe("/assets/images/devicedetector/client/type/feed reader.svg");
}

[Fact]
public void GetBrandPrefersBrandIconOverDeviceType()
{
using var dir = new TempIconDirectory();
dir.CreateFile("device/brand/Apple.svg");
dir.CreateFile("device/type/smartphone.svg");

var resolver = new IconResolver(new IconResolverOptions { PhysicalRootPath = dir.RootPath });

resolver.GetBrand("Apple", "smartphone").ShouldBe("/assets/images/devicedetector/device/brand/Apple.svg");
}

[Fact]
public void GetBrandFallsBackToDeviceTypeIcon()
{
using var dir = new TempIconDirectory();
dir.CreateFile("device/type/smartphone.svg");

var resolver = new IconResolver(new IconResolverOptions { PhysicalRootPath = dir.RootPath });

resolver.GetBrand("SomeUnknownBrand", "smartphone")
.ShouldBe("/assets/images/devicedetector/device/type/smartphone.svg");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Collections.Generic;
using DeviceDetectorNET.Icons;
using Shouldly;
using Xunit;

namespace DeviceDetectorNET.Icons.Tests
{
public class IconResolverFileExistsOverrideTests
{
[Fact]
public void UsesFileExistsDelegateInsteadOfPhysicalDisk()
{
var existingPaths = new HashSet<string> { "/device/type/tv.svg" };

var resolver = new IconResolver(new IconResolverOptions
{
FileExists = existingPaths.Contains
});

resolver.GetDeviceType("tv").ShouldBe("/assets/images/devicedetector/device/type/tv.svg");
resolver.GetDeviceType("desktop").ShouldBe("/assets/images/devicedetector/Matomo.svg");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using DeviceDetectorNET.Icons;
using Shouldly;
using Xunit;

namespace DeviceDetectorNET.Icons.Tests
{
public class IconResolverNameReplacementsTests
{
[Fact]
public void MergesCustomReplacementsOverDefaults()
{
using var dir = new TempIconDirectory();
dir.CreateFile("client/os/MyCustomSafeName.svg");

var options = new IconResolverOptions { PhysicalRootPath = dir.RootPath };
options.NameReplacements["Custom/Unsafe:Name"] = "MyCustomSafeName";

var resolver = new IconResolver(options);

resolver.GetOs("Custom/Unsafe:Name").ShouldBe("/assets/images/devicedetector/client/os/MyCustomSafeName.svg");
}

[Fact]
public void StillAppliesDefaultReplacementsWhenExtended()
{
using var dir = new TempIconDirectory();
dir.CreateFile("client/os/OS2.svg");

var options = new IconResolverOptions { PhysicalRootPath = dir.RootPath };
options.NameReplacements["Another/Unsafe"] = "AnotherSafe";

var resolver = new IconResolver(options);

resolver.GetOs("OS/2").ShouldBe("/assets/images/devicedetector/client/os/OS2.svg");
}
}
}
32 changes: 32 additions & 0 deletions DeviceDetector.NET.Icons.Tests/IconResolverOptionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using DeviceDetectorNET.Icons;
using Shouldly;
using Xunit;

namespace DeviceDetectorNET.Icons.Tests
{
public class IconResolverOptionsTests
{
[Fact]
public void DefaultsMatchUpstreamDDCIcons()
{
var options = new IconResolverOptions();

options.UrlBasePath.ShouldBe("/assets/images/devicedetector");
options.FallbackIconPath.ShouldBe("/Matomo.svg");
options.ExtensionPriority.ShouldBe(new[] { "svg", "avif", "webp", "png", "jpg", "jpeg", "jxl", "heic", "gif" });
options.NameReplacements.Count.ShouldBe(10);
options.NameReplacements["OS/2"].ShouldBe("OS2");
options.NameReplacements["GNU/Linux"].ShouldBe("GNULinux");
options.NameReplacements["MTK / Nucleus"].ShouldBe("MTK Nucleus");
options.NameReplacements["Perl REST::Client"].ShouldBe("Perl RESTClient");
options.NameReplacements["HTTP:Tiny"].ShouldBe("HTTP Tiny");
options.NameReplacements["AUX"].ShouldBe("AUX");
options.NameReplacements["MariaDB/MySQL Knowledge Base"].ShouldBe("MariaDB MySQL Knowledge Base");
options.NameReplacements["Sandoba//Crawler"].ShouldBe("Sandoba Crawler");
options.NameReplacements["WeSEE:Search"].ShouldBe("WeSEE Search");
options.NameReplacements["Yeti/Naverbot"].ShouldBe("Yeti Naverbot");
options.PhysicalRootPath.ShouldBeNull();
options.FileExists.ShouldBeNull();
}
}
}
Loading
Loading