From 4003498bfe687e01e6b3265724beae4b29a33300 Mon Sep 17 00:00:00 2001 From: fallstray Date: Fri, 29 May 2026 20:48:06 +0000 Subject: [PATCH] Add regression tests for image language fallback selection - Add xUnit test project (EmbyImageLanguageFallback.Tests) - 74 tests covering BuildFallbackSelection, NormalizeLanguage, IsLanguageNeutral, LanguageMatches, RewriteSelectedFallbackImages, and PluginOptions language canonicalization - Tests verify fallback chain: primary -> fallback -> neutral - Tests cover missing primary/fallback scenarios, language alias matching (zh/ja/en variants), deduplication, and rewrite behavior - Exclude test directory from main project compilation Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../EmbyImageLanguageFallback.Tests.csproj | 30 + .../FallbackSelectionTests.cs | 766 ++++++++++++++++++ .../GlobalUsings.cs | 1 + EmbyImageLanguageFallback.csproj | 1 + EmbyImageLanguageFallback.sln | 6 + 5 files changed, 804 insertions(+) create mode 100644 EmbyImageLanguageFallback.Tests/EmbyImageLanguageFallback.Tests.csproj create mode 100644 EmbyImageLanguageFallback.Tests/FallbackSelectionTests.cs create mode 100644 EmbyImageLanguageFallback.Tests/GlobalUsings.cs diff --git a/EmbyImageLanguageFallback.Tests/EmbyImageLanguageFallback.Tests.csproj b/EmbyImageLanguageFallback.Tests/EmbyImageLanguageFallback.Tests.csproj new file mode 100644 index 0000000..c13fc11 --- /dev/null +++ b/EmbyImageLanguageFallback.Tests/EmbyImageLanguageFallback.Tests.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + enable + enable + + false + true + SYSLIB0050 + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/EmbyImageLanguageFallback.Tests/FallbackSelectionTests.cs b/EmbyImageLanguageFallback.Tests/FallbackSelectionTests.cs new file mode 100644 index 0000000..7dcd75a --- /dev/null +++ b/EmbyImageLanguageFallback.Tests/FallbackSelectionTests.cs @@ -0,0 +1,766 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using Xunit; + +namespace EmbyImageLanguageFallback.Tests +{ + /// + /// Regression tests for the image language fallback selection logic in + /// . These tests invoke the + /// private BuildFallbackSelection, RewriteSelectedFallbackImages, + /// and related helper methods via reflection so that the production surface + /// remains unchanged. + /// + public class FallbackSelectionTests + { + private readonly object _provider; + private readonly MethodInfo _buildFallbackSelection; + private readonly MethodInfo _rewriteSelectedFallbackImages; + private readonly MethodInfo _normalizeLanguage; + private readonly MethodInfo _isLanguageNeutral; + private readonly MethodInfo _languageMatches; + + public FallbackSelectionTests() + { + var providerType = typeof(FallbackRemoteImageProvider); + + // Create an instance without calling the constructor (avoids Emby DI dependencies). + _provider = FormatterServices.GetUninitializedObject(providerType); + + _buildFallbackSelection = providerType.GetMethod( + "BuildFallbackSelection", + BindingFlags.NonPublic | BindingFlags.Instance)!; + + _rewriteSelectedFallbackImages = providerType.GetMethod( + "RewriteSelectedFallbackImages", + BindingFlags.NonPublic | BindingFlags.Instance, + null, + new[] { typeof(IEnumerable), typeof(IEnumerable) }, + null)!; + + _normalizeLanguage = providerType.GetMethod( + "NormalizeLanguage", + BindingFlags.NonPublic | BindingFlags.Instance)!; + + _isLanguageNeutral = providerType.GetMethod( + "IsLanguageNeutral", + BindingFlags.NonPublic | BindingFlags.Instance)!; + + _languageMatches = providerType.GetMethod( + "LanguageMatches", + BindingFlags.NonPublic | BindingFlags.Instance)!; + } + + #region Helpers + + private List InvokeBuildFallbackSelection( + List images, + IReadOnlyCollection primaryLanguages, + IReadOnlyCollection fallbackLanguages, + bool includeNeutral) + { + return (List)_buildFallbackSelection.Invoke( + _provider, + new object[] { images, primaryLanguages, fallbackLanguages, includeNeutral })!; + } + + private List InvokeRewriteSelectedFallbackImages( + IEnumerable images, + IEnumerable fallbackLanguages) + { + return (List)_rewriteSelectedFallbackImages.Invoke( + _provider, + new object[] { images, fallbackLanguages })!; + } + + private string? InvokeNormalizeLanguage(string? language) + { + return (string?)_normalizeLanguage.Invoke(_provider, new object?[] { language }); + } + + private bool InvokeIsLanguageNeutral(RemoteImageInfo image) + { + return (bool)_isLanguageNeutral.Invoke(_provider, new object[] { image })!; + } + + private bool InvokeLanguageMatches(RemoteImageInfo image, string language) + { + return (bool)_languageMatches.Invoke(_provider, new object[] { image, language })!; + } + + private static RemoteImageInfo MakeImage(string url, string? language, ImageType type = ImageType.Primary, string? displayLanguage = null) + { + return new RemoteImageInfo + { + Url = url, + Language = language, + Type = type, + DisplayLanguage = displayLanguage + }; + } + + #endregion + + // ────────────────────────────────────────────── + // BuildFallbackSelection — core regression tests + // ────────────────────────────────────────────── + + [Fact] + public void PrimaryLanguageImages_AreSelectedFirst() + { + var images = new List + { + MakeImage("http://img/zh1", "zh"), + MakeImage("http://img/en1", "en"), + MakeImage("http://img/ja1", "ja"), + }; + + var result = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "en" }, + includeNeutral: false); + + Assert.Equal(2, result.Count); + Assert.Equal("http://img/zh1", result[0].Url); + Assert.Equal("http://img/en1", result[1].Url); + } + + [Fact] + public void FallbackLanguageImages_UsedWhenPrimaryMissing() + { + var images = new List + { + MakeImage("http://img/en1", "en"), + MakeImage("http://img/ja1", "ja"), + }; + + var result = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "ja" }, + includeNeutral: false); + + Assert.Single(result); + Assert.Equal("http://img/ja1", result[0].Url); + } + + [Fact] + public void NeutralImages_UsedWhenPrimaryAndFallbackMissing() + { + var images = new List + { + MakeImage("http://img/neutral1", null), + MakeImage("http://img/de1", "de"), + }; + + var result = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "ja" }, + includeNeutral: true); + + Assert.Single(result); + Assert.Equal("http://img/neutral1", result[0].Url); + } + + [Fact] + public void NeutralImages_ExcludedWhenDisabled() + { + var images = new List + { + MakeImage("http://img/neutral1", null), + MakeImage("http://img/de1", "de"), + }; + + var result = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "ja" }, + includeNeutral: false); + + Assert.Empty(result); + } + + [Fact] + public void FallbackOrder_PrimaryThenFallbackThenNeutral() + { + var images = new List + { + MakeImage("http://img/neutral", null), + MakeImage("http://img/ja1", "ja"), + MakeImage("http://img/zh1", "zh"), + }; + + var result = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "ja" }, + includeNeutral: true); + + Assert.Equal(3, result.Count); + Assert.Equal("http://img/zh1", result[0].Url); + Assert.Equal("http://img/ja1", result[1].Url); + Assert.Equal("http://img/neutral", result[2].Url); + } + + [Fact] + public void DuplicateUrls_AreDeduplicatedAcrossTiers() + { + var images = new List + { + MakeImage("http://img/same", "zh"), + MakeImage("http://img/same", "ja"), + MakeImage("http://img/same", null), + }; + + var result = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "ja" }, + includeNeutral: true); + + Assert.Single(result); + Assert.Equal("zh", result[0].Language); + } + + [Fact] + public void MultiplePrimaryLanguages_AllMatched() + { + var images = new List + { + MakeImage("http://img/zh1", "zh"), + MakeImage("http://img/ko1", "ko"), + MakeImage("http://img/en1", "en"), + }; + + var result = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh", "ko" }, + fallbackLanguages: new[] { "en" }, + includeNeutral: false); + + Assert.Equal(3, result.Count); + Assert.Contains(result, r => r.Url == "http://img/zh1"); + Assert.Contains(result, r => r.Url == "http://img/ko1"); + Assert.Contains(result, r => r.Url == "http://img/en1"); + } + + [Fact] + public void MultipleFallbackLanguages_AllMatched() + { + var images = new List + { + MakeImage("http://img/en1", "en"), + MakeImage("http://img/ja1", "ja"), + MakeImage("http://img/ko1", "ko"), + }; + + var result = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "en", "ja" }, + includeNeutral: false); + + Assert.Equal(2, result.Count); + Assert.Contains(result, r => r.Url == "http://img/en1"); + Assert.Contains(result, r => r.Url == "http://img/ja1"); + } + + [Fact] + public void FallbackLanguages_ExcludesPrimaryOverlap() + { + var images = new List + { + MakeImage("http://img/zh1", "zh"), + MakeImage("http://img/zh2", "zh-CN"), + }; + + var result = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "zh", "ja" }, + includeNeutral: false); + + // "zh" in fallback should be excluded since it's already a primary language. + // Both zh images should come via primary match, not duplicated. + Assert.Equal(2, result.Count); + } + + [Fact] + public void EmptyPrimaryAndFallback_OnlyNeutralReturned() + { + var images = new List + { + MakeImage("http://img/en1", "en"), + MakeImage("http://img/neutral", null), + }; + + var result = InvokeBuildFallbackSelection( + images, + primaryLanguages: Array.Empty(), + fallbackLanguages: Array.Empty(), + includeNeutral: true); + + Assert.Single(result); + Assert.Equal("http://img/neutral", result[0].Url); + } + + [Fact] + public void EmptyPrimaryAndFallback_NeutralDisabled_ReturnsEmpty() + { + var images = new List + { + MakeImage("http://img/en1", "en"), + MakeImage("http://img/neutral", null), + }; + + var result = InvokeBuildFallbackSelection( + images, + primaryLanguages: Array.Empty(), + fallbackLanguages: Array.Empty(), + includeNeutral: false); + + Assert.Empty(result); + } + + [Fact] + public void NoImagesProvided_ReturnsEmpty() + { + var result = InvokeBuildFallbackSelection( + new List(), + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "ja" }, + includeNeutral: true); + + Assert.Empty(result); + } + + [Fact] + public void UnrelatedLanguages_Excluded() + { + var images = new List + { + MakeImage("http://img/de1", "de"), + MakeImage("http://img/fr1", "fr"), + MakeImage("http://img/es1", "es"), + }; + + var result = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "ja" }, + includeNeutral: false); + + Assert.Empty(result); + } + + // ────────────────────────────────────────────── + // Language alias / variant matching + // ────────────────────────────────────────────── + + [Theory] + [InlineData("zh-CN")] + [InlineData("zh_CN")] + [InlineData("zh")] + [InlineData("zho")] + [InlineData("chi")] + [InlineData("chinese")] + public void ChineseAliases_MatchPrimaryZh(string imageLanguage) + { + var images = new List + { + MakeImage("http://img/1", imageLanguage), + }; + + var result = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: Array.Empty(), + includeNeutral: false); + + Assert.Single(result); + } + + [Theory] + [InlineData("ja")] + [InlineData("ja-JP")] + [InlineData("ja_JP")] + [InlineData("jpn")] + [InlineData("japanese")] + public void JapaneseAliases_MatchFallbackJa(string imageLanguage) + { + var images = new List + { + MakeImage("http://img/1", imageLanguage), + }; + + var result = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "ja" }, + includeNeutral: false); + + Assert.Single(result); + } + + [Theory] + [InlineData("en")] + [InlineData("en-US")] + [InlineData("en_US")] + [InlineData("eng")] + [InlineData("english")] + public void EnglishAliases_MatchFallbackEn(string imageLanguage) + { + var images = new List + { + MakeImage("http://img/1", imageLanguage), + }; + + var result = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "en" }, + includeNeutral: false); + + Assert.Single(result); + } + + // ────────────────────────────────────────────── + // IsLanguageNeutral + // ────────────────────────────────────────────── + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + [InlineData("null")] + [InlineData("00")] + public void IsLanguageNeutral_TrueForNeutralValues(string? language) + { + var image = MakeImage("http://img/1", language); + Assert.True(InvokeIsLanguageNeutral(image)); + } + + [Theory] + [InlineData("zh")] + [InlineData("en")] + [InlineData("ja")] + [InlineData("de")] + public void IsLanguageNeutral_FalseForRealLanguages(string language) + { + var image = MakeImage("http://img/1", language); + Assert.False(InvokeIsLanguageNeutral(image)); + } + + // ────────────────────────────────────────────── + // NormalizeLanguage + // ────────────────────────────────────────────── + + [Theory] + [InlineData("zh-CN", "zh-Hans")] + [InlineData("zh_CN", "zh-Hans")] + [InlineData("chinese", "zh-Hans")] + [InlineData("ja-JP", "ja")] + [InlineData("jpn", "ja")] + [InlineData("eng", "en")] + [InlineData("en-US", "en")] + public void NormalizeLanguage_MapsToCanonical(string input, string expected) + { + Assert.Equal(expected, InvokeNormalizeLanguage(input)); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void NormalizeLanguage_NullOrWhitespace_ReturnsNull(string? input) + { + Assert.Null(InvokeNormalizeLanguage(input)); + } + + // ────────────────────────────────────────────── + // RewriteSelectedFallbackImages + // ────────────────────────────────────────────── + + [Fact] + public void RewriteFallback_ChangesLanguageToEnglish() + { + var images = new List + { + MakeImage("http://img/ja1", "ja"), + }; + + var result = InvokeRewriteSelectedFallbackImages(images, new[] { "ja" }); + + Assert.Single(result); + Assert.Equal("en", result[0].Language); + Assert.Equal("English", result[0].DisplayLanguage); + } + + [Fact] + public void RewriteFallback_DoesNotChangePrimaryLanguageImages() + { + var images = new List + { + MakeImage("http://img/zh1", "zh"), + }; + + var result = InvokeRewriteSelectedFallbackImages(images, new[] { "ja" }); + + Assert.Single(result); + Assert.Equal("zh", result[0].Language); + } + + [Fact] + public void RewriteFallback_PreservesOriginalUrl() + { + var images = new List + { + MakeImage("http://img/ja1", "ja"), + }; + + var result = InvokeRewriteSelectedFallbackImages(images, new[] { "ja" }); + + Assert.Equal("http://img/ja1", result[0].Url); + } + + [Fact] + public void RewriteFallback_MixedLanguages_OnlyFallbackRewritten() + { + var images = new List + { + MakeImage("http://img/zh1", "zh"), + MakeImage("http://img/ja1", "ja"), + MakeImage("http://img/neutral", null), + }; + + var result = InvokeRewriteSelectedFallbackImages(images, new[] { "ja" }); + + Assert.Equal(3, result.Count); + Assert.Equal("zh", result[0].Language); + Assert.Equal("en", result[1].Language); + Assert.Null(result[2].Language); + } + + // ────────────────────────────────────────────── + // Full fallback chain regression tests + // ────────────────────────────────────────────── + + [Fact] + public void FullChain_PrimaryAvailable_NoFallbackUsed() + { + var images = new List + { + MakeImage("http://img/zh1", "zh", ImageType.Primary), + MakeImage("http://img/ja1", "ja", ImageType.Primary), + MakeImage("http://img/en1", "en", ImageType.Primary), + MakeImage("http://img/neutral", null, ImageType.Primary), + }; + + var selected = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "ja" }, + includeNeutral: true); + + // Primary match comes first, then fallback, then neutral + Assert.True(selected.Count >= 1); + Assert.Equal("http://img/zh1", selected[0].Url); + } + + [Fact] + public void FullChain_PrimaryMissing_FallbackUsed() + { + var images = new List + { + MakeImage("http://img/ja1", "ja", ImageType.Primary), + MakeImage("http://img/en1", "en", ImageType.Primary), + MakeImage("http://img/neutral", null, ImageType.Primary), + }; + + var selected = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "ja" }, + includeNeutral: true); + + Assert.Equal(2, selected.Count); + Assert.Equal("http://img/ja1", selected[0].Url); + Assert.Equal("http://img/neutral", selected[1].Url); + } + + [Fact] + public void FullChain_PrimaryAndFallbackMissing_NeutralUsed() + { + var images = new List + { + MakeImage("http://img/en1", "en", ImageType.Primary), + MakeImage("http://img/de1", "de", ImageType.Primary), + MakeImage("http://img/neutral", null, ImageType.Primary), + }; + + var selected = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "ja" }, + includeNeutral: true); + + Assert.Single(selected); + Assert.Equal("http://img/neutral", selected[0].Url); + } + + [Fact] + public void FullChain_AllMissing_NeutralDisabled_ReturnsEmpty() + { + var images = new List + { + MakeImage("http://img/en1", "en", ImageType.Primary), + MakeImage("http://img/de1", "de", ImageType.Primary), + MakeImage("http://img/neutral", null, ImageType.Primary), + }; + + var selected = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "ja" }, + includeNeutral: false); + + Assert.Empty(selected); + } + + [Fact] + public void FullChain_MultipleImageTypes_EachTypeHandledIndependently() + { + // The BuildFallbackSelection method processes all images regardless of + // type. The per-type grouping happens in GetImages. Verify that images + // of different types coexist. + var images = new List + { + MakeImage("http://img/poster-zh", "zh", ImageType.Primary), + MakeImage("http://img/banner-ja", "ja", ImageType.Banner), + MakeImage("http://img/thumb-neutral", null, ImageType.Thumb), + }; + + var selected = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "ja" }, + includeNeutral: true); + + Assert.Equal(3, selected.Count); + Assert.Contains(selected, r => r.Url == "http://img/poster-zh"); + Assert.Contains(selected, r => r.Url == "http://img/banner-ja"); + Assert.Contains(selected, r => r.Url == "http://img/thumb-neutral"); + } + + [Fact] + public void FullChain_BackdropImages_FallbackChainWorks() + { + var images = new List + { + MakeImage("http://img/backdrop-en", "en", ImageType.Backdrop), + MakeImage("http://img/backdrop-ja", "ja", ImageType.Backdrop), + MakeImage("http://img/backdrop-neutral", null, ImageType.Backdrop), + }; + + var selected = InvokeBuildFallbackSelection( + images, + primaryLanguages: new[] { "zh" }, + fallbackLanguages: new[] { "ja" }, + includeNeutral: true); + + Assert.Equal(2, selected.Count); + Assert.Equal("http://img/backdrop-ja", selected[0].Url); + Assert.Equal("http://img/backdrop-neutral", selected[1].Url); + } + + // ────────────────────────────────────────────── + // LanguageMatches regression tests + // ────────────────────────────────────────────── + + [Fact] + public void LanguageMatches_ZhCN_MatchesZh() + { + var image = MakeImage("http://img/1", "zh-CN"); + Assert.True(InvokeLanguageMatches(image, "zh")); + } + + [Fact] + public void LanguageMatches_CaseInsensitive() + { + var image = MakeImage("http://img/1", "ZH-CN"); + Assert.True(InvokeLanguageMatches(image, "zh")); + } + + [Fact] + public void LanguageMatches_DisplayLanguageFallback_NonNormalizableText() + { + // Display language text that is NOT normalizable (so it survives NormalizeLanguage) + // but still contains the keyword used in DisplayLanguageMatches. + var image = MakeImage("http://img/1", "unknown", displayLanguage: "Japanese (Standard)"); + Assert.True(InvokeLanguageMatches(image, "ja")); + } + + [Fact] + public void LanguageMatches_DisplayLanguage_NormalizedAway_DoesNotMatch() + { + // "Japanese" normalizes to "ja", so the substring check in DisplayLanguageMatches + // cannot find "japanese" in the normalized value "ja". This is expected behavior. + var image = MakeImage("http://img/1", "unknown", displayLanguage: "Japanese"); + Assert.False(InvokeLanguageMatches(image, "ja")); + } + + [Fact] + public void LanguageMatches_NoMatch() + { + var image = MakeImage("http://img/1", "de"); + Assert.False(InvokeLanguageMatches(image, "ja")); + } + + // ────────────────────────────────────────────── + // PluginOptions tests + // ────────────────────────────────────────────── + + [Theory] + [InlineData("zh-CN", "zh-Hans")] + [InlineData("zh_CN", "zh-Hans")] + [InlineData("ZH-CN", "zh-Hans")] + [InlineData("chinese", "zh-Hans")] + [InlineData("jpn", "ja")] + [InlineData("eng", "en")] + [InlineData("kor", "ko")] + [InlineData("unknown-lang", "unknown-lang")] + public void GetCanonicalLanguageValue_ResolvesCorrectly(string input, string expected) + { + Assert.Equal(expected, PluginOptions.GetCanonicalLanguageValue(input)); + } + + [Fact] + public void GetCanonicalLanguageValue_NullInput_ReturnsNull() + { + Assert.Null(PluginOptions.GetCanonicalLanguageValue(null)); + } + + [Fact] + public void GetLanguageAliases_ReturnsAllAliasesForZh() + { + var aliases = PluginOptions.GetLanguageAliases("zh"); + Assert.Contains("zh-Hans", aliases, StringComparer.OrdinalIgnoreCase); + Assert.Contains("zh", aliases, StringComparer.OrdinalIgnoreCase); + Assert.Contains("zh-CN", aliases, StringComparer.OrdinalIgnoreCase); + } + + [Fact] + public void GetLanguageAliases_UnknownLanguage_ReturnsSingleAlias() + { + var aliases = PluginOptions.GetLanguageAliases("xx-custom"); + Assert.Single(aliases); + Assert.Equal("xx-custom", aliases.First()); + } + } +} diff --git a/EmbyImageLanguageFallback.Tests/GlobalUsings.cs b/EmbyImageLanguageFallback.Tests/GlobalUsings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/EmbyImageLanguageFallback.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/EmbyImageLanguageFallback.csproj b/EmbyImageLanguageFallback.csproj index fc52dc9..449f663 100644 --- a/EmbyImageLanguageFallback.csproj +++ b/EmbyImageLanguageFallback.csproj @@ -2,6 +2,7 @@ netstandard2.0 + $(DefaultItemExcludes);EmbyImageLanguageFallback.Tests\** diff --git a/EmbyImageLanguageFallback.sln b/EmbyImageLanguageFallback.sln index bc5feb0..7e571e4 100644 --- a/EmbyImageLanguageFallback.sln +++ b/EmbyImageLanguageFallback.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 18.3.11520.95 d18.3 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbyImageLanguageFallback", "EmbyImageLanguageFallback.csproj", "{B012E32B-0643-1CD5-1A8C-B23BF9340CAD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbyImageLanguageFallback.Tests", "EmbyImageLanguageFallback.Tests\EmbyImageLanguageFallback.Tests.csproj", "{C7216891-843E-4A93-87A9-72627B183DA4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {B012E32B-0643-1CD5-1A8C-B23BF9340CAD}.Debug|Any CPU.Build.0 = Debug|Any CPU {B012E32B-0643-1CD5-1A8C-B23BF9340CAD}.Release|Any CPU.ActiveCfg = Release|Any CPU {B012E32B-0643-1CD5-1A8C-B23BF9340CAD}.Release|Any CPU.Build.0 = Release|Any CPU + {C7216891-843E-4A93-87A9-72627B183DA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7216891-843E-4A93-87A9-72627B183DA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7216891-843E-4A93-87A9-72627B183DA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7216891-843E-4A93-87A9-72627B183DA4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE