Added initial test cases. Fixed circle search provider logic.
This commit is contained in:
@@ -3,4 +3,5 @@
|
|||||||
public class CircleSearchCriteria
|
public class CircleSearchCriteria
|
||||||
{
|
{
|
||||||
public string? Name { get; init; }
|
public string? Name { get; init; }
|
||||||
|
public CircleStatus? Status { get; init; }
|
||||||
}
|
}
|
||||||
9
JSMR.Application/Circles/Queries/Search/CircleStatus.cs
Normal file
9
JSMR.Application/Circles/Queries/Search/CircleStatus.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace JSMR.Application.Circles.Queries.Search;
|
||||||
|
|
||||||
|
public enum CircleStatus
|
||||||
|
{
|
||||||
|
NotBlacklisted,
|
||||||
|
Favorited,
|
||||||
|
Blacklisted,
|
||||||
|
Spam
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
|
|
||||||
namespace JSMR.Infrastructure.Data;
|
namespace JSMR.Infrastructure.Data;
|
||||||
|
|
||||||
public class AppDbContext : DbContext
|
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
|
||||||
{
|
{
|
||||||
public DbSet<VoiceWork> VoiceWorks { get; set; }
|
public DbSet<VoiceWork> VoiceWorks { get; set; }
|
||||||
public DbSet<EnglishVoiceWork> EnglishVoiceWorks { get; set; }
|
public DbSet<EnglishVoiceWork> EnglishVoiceWorks { get; set; }
|
||||||
|
|||||||
@@ -9,58 +9,65 @@ public class CircleSearchProvider(AppDbContext context) : SearchProvider<CircleS
|
|||||||
{
|
{
|
||||||
protected override IQueryable<CircleSearchItem> GetBaseQuery()
|
protected override IQueryable<CircleSearchItem> GetBaseQuery()
|
||||||
{
|
{
|
||||||
// Precompute LatestProductId per circle (by productId length, then value)
|
// Project from Circles so we can use correlated subqueries per CircleId.
|
||||||
var latestPerCircle =
|
var q =
|
||||||
from vw in context.VoiceWorks.AsNoTracking()
|
from c in context.Circles.AsNoTracking()
|
||||||
group vw by vw.CircleId into g
|
select new CircleSearchItem
|
||||||
let latest = g
|
{
|
||||||
.OrderByDescending(x => x.ProductId.Length)
|
CircleId = c.CircleId,
|
||||||
.ThenByDescending(x => x.ProductId)
|
Name = c.Name,
|
||||||
.Select(x => x.ProductId)
|
MakerId = c.MakerId,
|
||||||
|
Favorite = c.Favorite,
|
||||||
|
Blacklisted = c.Blacklisted,
|
||||||
|
Spam = c.Spam,
|
||||||
|
|
||||||
|
// Aggregates
|
||||||
|
Downloads = context.VoiceWorks
|
||||||
|
.Where(v => v.CircleId == c.CircleId)
|
||||||
|
.Select(v => (int?)v.Downloads) // make nullable for Sum over empty set
|
||||||
|
.Sum() ?? 0,
|
||||||
|
|
||||||
|
Releases = context.VoiceWorks
|
||||||
|
.Count(v => v.CircleId == c.CircleId && v.SalesDate != null),
|
||||||
|
|
||||||
|
Pending = context.VoiceWorks
|
||||||
|
.Count(v => v.CircleId == c.CircleId && v.ExpectedDate != null),
|
||||||
|
|
||||||
|
FirstReleaseDate = context.VoiceWorks
|
||||||
|
.Where(v => v.CircleId == c.CircleId)
|
||||||
|
.Select(v => v.SalesDate)
|
||||||
|
.Min(),
|
||||||
|
|
||||||
|
LatestReleaseDate = context.VoiceWorks
|
||||||
|
.Where(v => v.CircleId == c.CircleId)
|
||||||
|
.Select(v => v.SalesDate)
|
||||||
|
.Max(),
|
||||||
|
|
||||||
|
// "Latest" by ProductId length, then value
|
||||||
|
LatestProductId = context.VoiceWorks
|
||||||
|
.Where(v => v.CircleId == c.CircleId)
|
||||||
|
.OrderByDescending(v => v.ProductId.Length)
|
||||||
|
.ThenByDescending(v => v.ProductId)
|
||||||
|
.Select(v => v.ProductId)
|
||||||
|
.FirstOrDefault(),
|
||||||
|
|
||||||
|
// If you want these two in base query too:
|
||||||
|
LatestVoiceWorkHasImage = context.VoiceWorks
|
||||||
|
.Where(v => v.CircleId == c.CircleId)
|
||||||
|
.OrderByDescending(v => v.ProductId.Length)
|
||||||
|
.ThenByDescending(v => v.ProductId)
|
||||||
|
.Select(v => (bool?)v.HasImage)
|
||||||
|
.FirstOrDefault(),
|
||||||
|
|
||||||
|
LatestVoiceWorkSalesDate = context.VoiceWorks
|
||||||
|
.Where(v => v.CircleId == c.CircleId)
|
||||||
|
.OrderByDescending(v => v.ProductId.Length)
|
||||||
|
.ThenByDescending(v => v.ProductId)
|
||||||
|
.Select(v => v.SalesDate)
|
||||||
.FirstOrDefault()
|
.FirstOrDefault()
|
||||||
select new { CircleId = g.Key, LatestProductId = latest };
|
};
|
||||||
|
|
||||||
// Aggregates per circle
|
return q;
|
||||||
var aggregates =
|
|
||||||
from vw in context.VoiceWorks.AsNoTracking()
|
|
||||||
group vw by vw.CircleId into g
|
|
||||||
select new
|
|
||||||
{
|
|
||||||
CircleId = g.Key,
|
|
||||||
Downloads = g.Sum(x => x.Downloads ?? 0),
|
|
||||||
Releases = g.Count(x => x.SalesDate != null),
|
|
||||||
Pending = g.Count(x => x.ExpectedDate != null),
|
|
||||||
FirstReleaseDate = g.Min(x => x.SalesDate),
|
|
||||||
LatestReleaseDate = g.Max(x => x.SalesDate)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Join circles with aggregates and latest product id
|
|
||||||
var baseQuery =
|
|
||||||
from c in context.Circles.AsNoTracking()
|
|
||||||
join agg in aggregates on c.CircleId equals agg.CircleId into aggs
|
|
||||||
from a in aggs.DefaultIfEmpty()
|
|
||||||
join lp in latestPerCircle on c.CircleId equals lp.CircleId into lps
|
|
||||||
from l in lps.DefaultIfEmpty()
|
|
||||||
select new CircleSearchItem
|
|
||||||
{
|
|
||||||
CircleId = c.CircleId,
|
|
||||||
Name = c.Name,
|
|
||||||
MakerId = c.MakerId,
|
|
||||||
Favorite = c.Favorite,
|
|
||||||
Blacklisted = c.Blacklisted,
|
|
||||||
Spam = c.Spam,
|
|
||||||
Downloads = a != null ? a.Downloads : 0,
|
|
||||||
Releases = a != null ? a.Releases : 0,
|
|
||||||
Pending = a != null ? a.Pending : 0,
|
|
||||||
FirstReleaseDate = a != null ? a.FirstReleaseDate : null,
|
|
||||||
LatestReleaseDate = a != null ? a.LatestReleaseDate : null,
|
|
||||||
LatestProductId = l != null ? l.LatestProductId : null,
|
|
||||||
// these two get filled in during Select stage (below)
|
|
||||||
LatestVoiceWorkHasImage = null,
|
|
||||||
LatestVoiceWorkSalesDate = null
|
|
||||||
};
|
|
||||||
|
|
||||||
return baseQuery;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IQueryable<CircleSearchItem> ApplyFilters(IQueryable<CircleSearchItem> query, CircleSearchCriteria criteria)
|
protected override IQueryable<CircleSearchItem> ApplyFilters(IQueryable<CircleSearchItem> query, CircleSearchCriteria criteria)
|
||||||
@@ -74,14 +81,21 @@ public class CircleSearchProvider(AppDbContext context) : SearchProvider<CircleS
|
|||||||
EF.Functions.Like(x.MakerId, term));
|
EF.Functions.Like(x.MakerId, term));
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (criteria.Status is CircleStatus.NotBlacklisted)
|
switch (criteria.Status)
|
||||||
// query = query.Where(x => !x.Blacklisted);
|
{
|
||||||
//else if (criteria.Status is CircleStatus.Favorited)
|
case CircleStatus.NotBlacklisted:
|
||||||
// query = query.Where(x => x.Favorite);
|
query = query.Where(x => !x.Blacklisted);
|
||||||
//else if (criteria.Status is CircleStatus.Blacklisted)
|
break;
|
||||||
// query = query.Where(x => x.Blacklisted);
|
case CircleStatus.Favorited:
|
||||||
//else if (criteria.Status is CircleStatus.Spam)
|
query = query.Where(x => x.Favorite);
|
||||||
// query = query.Where(x => x.Spam);
|
break;
|
||||||
|
case CircleStatus.Blacklisted:
|
||||||
|
query = query.Where(x => x.Blacklisted);
|
||||||
|
break;
|
||||||
|
case CircleStatus.Spam:
|
||||||
|
query = query.Where(x => x.Spam);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|||||||
56
JSMR.Tests/Integration/CircleSearchProviderTests.cs
Normal file
56
JSMR.Tests/Integration/CircleSearchProviderTests.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using JSMR.Application.Circles.Contracts;
|
||||||
|
using JSMR.Application.Circles.Queries.Search;
|
||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
using JSMR.Infrastructure.Data;
|
||||||
|
using JSMR.Infrastructure.Data.Repositories.Circles;
|
||||||
|
|
||||||
|
namespace JSMR.Tests.Integration;
|
||||||
|
|
||||||
|
public class CircleSearchProviderTests(MariaDbFixture fixture) : IClassFixture<MariaDbFixture>
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Search_ByName_Filters_And_Sorts()
|
||||||
|
{
|
||||||
|
await fixture.ResetAsync();
|
||||||
|
await using AppDbContext context = fixture.CreateDbContext();
|
||||||
|
await Seed.SeedCirclesWithWorksAsync(context);
|
||||||
|
|
||||||
|
CircleSearchProvider provider = new(context);
|
||||||
|
|
||||||
|
var options = new SearchOptions<CircleSearchCriteria, CircleSortField>
|
||||||
|
{
|
||||||
|
PageNumber = 1,
|
||||||
|
PageSize = 50,
|
||||||
|
SortOptions = [new SortOption<CircleSortField>(CircleSortField.Name, SortDirection.Ascending)],
|
||||||
|
Criteria = new CircleSearchCriteria { Name = "Circle" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await provider.SearchAsync(options, CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.True(result.TotalItems >= 2);
|
||||||
|
Assert.Equal("Circle A", result.Items[0].Name);
|
||||||
|
Assert.Equal("Circle B", result.Items[1].Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
//[Fact]
|
||||||
|
//public async Task Search_Status_Favorited_Only()
|
||||||
|
//{
|
||||||
|
// await fixture.ResetAsync();
|
||||||
|
// await using var db = fixture.CreateDbContext();
|
||||||
|
// await Seed.SeedCirclesWithWorksAsync(db);
|
||||||
|
|
||||||
|
// var provider = new CircleSearchProvider(db);
|
||||||
|
|
||||||
|
// var options = new SearchOptions<CircleSearchCriteria, CircleSortField>
|
||||||
|
// {
|
||||||
|
// PageNumber = 1,
|
||||||
|
// PageSize = 50,
|
||||||
|
// SortOptions = Array.Empty<SortOption<CircleSortField>>(),
|
||||||
|
// Criteria = new CircleSearchCriteria { Status = Application.Circles.Queries.Search.CircleStatus.Favorited }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// var result = await provider.SearchAsync(options, CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert.All(result.Items, i => Assert.True(i.Favorite));
|
||||||
|
//}
|
||||||
|
}
|
||||||
89
JSMR.Tests/Integration/MariaDbFixture.cs
Normal file
89
JSMR.Tests/Integration/MariaDbFixture.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
using DotNet.Testcontainers.Builders;
|
||||||
|
using JSMR.Infrastructure.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Testcontainers.MariaDb;
|
||||||
|
|
||||||
|
namespace JSMR.Tests.Integration;
|
||||||
|
|
||||||
|
public sealed class MariaDbFixture : IAsyncLifetime
|
||||||
|
{
|
||||||
|
public MariaDbContainer? MariaDbContainer { get; private set; }
|
||||||
|
|
||||||
|
public string ConnectionString { get; private set; } = default!;
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
MariaDbContainer = new MariaDbBuilder()
|
||||||
|
.WithImage("mariadb:10.11.6")
|
||||||
|
.WithEnvironment("MARIADB_ROOT_PASSWORD", "rootpwd")
|
||||||
|
.WithEnvironment("MARIADB_DATABASE", "appdb")
|
||||||
|
.WithPortBinding(3307, 3306) // host:container; avoid conflicts
|
||||||
|
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(3306))
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
await MariaDbContainer.StartAsync();
|
||||||
|
|
||||||
|
ConnectionString =
|
||||||
|
"Server=localhost;Port=3307;Database=appdb;User=root;Password=rootpwd;SslMode=None;AllowPublicKeyRetrieval=True;";
|
||||||
|
|
||||||
|
//ConnectionString = MariaDbContainer.GetConnectionString();
|
||||||
|
|
||||||
|
// Run migrations here to create schema
|
||||||
|
await using AppDbContext context = CreateDbContext();
|
||||||
|
await context.Database.EnsureCreatedAsync();
|
||||||
|
//await context.Database.MigrateAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DisposeAsync()
|
||||||
|
{
|
||||||
|
if (MariaDbContainer is not null)
|
||||||
|
{
|
||||||
|
await MariaDbContainer.StopAsync();
|
||||||
|
await MariaDbContainer.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppDbContext CreateDbContext()
|
||||||
|
{
|
||||||
|
MySqlServerVersion serverVersion = new(new Version(10, 11, 6));
|
||||||
|
|
||||||
|
DbContextOptions<AppDbContext> options = new DbContextOptionsBuilder<AppDbContext>()
|
||||||
|
.UseMySql(ConnectionString, serverVersion,
|
||||||
|
o => o.EnableRetryOnFailure())
|
||||||
|
.EnableSensitiveDataLogging()
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
return new AppDbContext(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Clean tables between tests; use Respawn or manual TRUNCATE in correct FK order.</summary>
|
||||||
|
public async Task ResetAsync()
|
||||||
|
{
|
||||||
|
await using AppDbContext context = CreateDbContext();
|
||||||
|
|
||||||
|
await context.Database.EnsureDeletedAsync();
|
||||||
|
await context.Database.EnsureCreatedAsync();
|
||||||
|
|
||||||
|
//await using var connection = context.Database.GetDbConnection();
|
||||||
|
//await connection.OpenAsync();
|
||||||
|
|
||||||
|
//using var cmd = connection.CreateCommand();
|
||||||
|
//cmd.CommandText = "SELECT DATABASE()";
|
||||||
|
|
||||||
|
//var dbName = (string?)await cmd.ExecuteScalarAsync();
|
||||||
|
//Console.WriteLine($"[TEST] Connected to DB: {dbName}");
|
||||||
|
|
||||||
|
// Fast reset (example): disable FK checks, truncate, re-enable
|
||||||
|
//await context.Database.ExecuteSqlRawAsync("SET FOREIGN_KEY_CHECKS = 0;");
|
||||||
|
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE voice_work_creators;");
|
||||||
|
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE voice_work_tags;");
|
||||||
|
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE english_tags;");
|
||||||
|
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE english_voice_works;");
|
||||||
|
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE voice_work_searches;");
|
||||||
|
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE voice_works;");
|
||||||
|
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE creators;");
|
||||||
|
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE tags;");
|
||||||
|
//await context.Database.ExecuteSqlRawAsync("TRUNCATE TABLE circles;");
|
||||||
|
//await context.Database.ExecuteSqlRawAsync("SET FOREIGN_KEY_CHECKS = 1;");
|
||||||
|
}
|
||||||
|
}
|
||||||
45
JSMR.Tests/Integration/Seed.cs
Normal file
45
JSMR.Tests/Integration/Seed.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using JSMR.Domain.Entities;
|
||||||
|
using JSMR.Infrastructure.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace JSMR.Tests.Integration;
|
||||||
|
|
||||||
|
public static class Seed
|
||||||
|
{
|
||||||
|
public static async Task SeedBasicTagsAsync(AppDbContext context)
|
||||||
|
{
|
||||||
|
if (await context.Tags.AnyAsync())
|
||||||
|
return;
|
||||||
|
|
||||||
|
context.Tags.AddRange(
|
||||||
|
new() { TagId = 1, Name = "OL", Favorite = false, Blacklisted = false },
|
||||||
|
new() { TagId = 2, Name = "ほのぼの", Favorite = true, Blacklisted = false },
|
||||||
|
new() { TagId = 3, Name = "ツンデレ", Favorite = false, Blacklisted = true }
|
||||||
|
);
|
||||||
|
|
||||||
|
context.EnglishTags.AddRange(
|
||||||
|
new() { EnglishTagId = 1, TagId = 1, Name = "Office Lady" },
|
||||||
|
new() { EnglishTagId = 2, TagId = 2, Name = "Heartwarming" },
|
||||||
|
new() { EnglishTagId = 3, TagId = 3, Name = "Tsundere" }
|
||||||
|
);
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task SeedCirclesWithWorksAsync(AppDbContext context)
|
||||||
|
{
|
||||||
|
var c1 = new Circle { Name = "Circle A", MakerId = "mk001", Favorite = false, Blacklisted = false, Spam = false };
|
||||||
|
var c2 = new Circle { Name = "Circle B", MakerId = "mk002", Favorite = true, Blacklisted = false, Spam = false };
|
||||||
|
context.Circles.AddRange(c1, c2);
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
context.VoiceWorks.AddRange(
|
||||||
|
new VoiceWork { CircleId = c1.CircleId, ProductId = "R-1", ProductName = "Work 1", Downloads = 100, SalesDate = new DateTime(2024, 1, 1), HasImage = true },
|
||||||
|
new VoiceWork { CircleId = c1.CircleId, ProductId = "R-10", ProductName = "Work 10", Downloads = 50, SalesDate = new DateTime(2024, 2, 1), HasImage = false },
|
||||||
|
new VoiceWork { CircleId = c2.CircleId, ProductId = "R-2", ProductName = "Work 2", Downloads = 200, SalesDate = new DateTime(2024, 3, 1), HasImage = true }
|
||||||
|
);
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
34
JSMR.Tests/JSMR.Tests.csproj
Normal file
34
JSMR.Tests/JSMR.Tests.csproj
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Testcontainers" Version="4.6.0" />
|
||||||
|
<PackageReference Include="Testcontainers.MariaDb" Version="4.6.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\JSMR.Infrastructure\JSMR.Infrastructure.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
6
JSMR.sln
6
JSMR.sln
@@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JSMR.Application", "JSMR.Ap
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JSMR.Infrastructure", "JSMR.Infrastructure\JSMR.Infrastructure.csproj", "{10099B7E-DB1D-4EED-B12C-70BEB0C1D996}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JSMR.Infrastructure", "JSMR.Infrastructure\JSMR.Infrastructure.csproj", "{10099B7E-DB1D-4EED-B12C-70BEB0C1D996}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JSMR.Tests", "JSMR.Tests\JSMR.Tests.csproj", "{9C17C2F2-B43E-48F9-960E-E8AEA9F7763E}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -27,6 +29,10 @@ Global
|
|||||||
{10099B7E-DB1D-4EED-B12C-70BEB0C1D996}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{10099B7E-DB1D-4EED-B12C-70BEB0C1D996}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{10099B7E-DB1D-4EED-B12C-70BEB0C1D996}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{10099B7E-DB1D-4EED-B12C-70BEB0C1D996}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{10099B7E-DB1D-4EED-B12C-70BEB0C1D996}.Release|Any CPU.Build.0 = Release|Any CPU
|
{10099B7E-DB1D-4EED-B12C-70BEB0C1D996}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{9C17C2F2-B43E-48F9-960E-E8AEA9F7763E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{9C17C2F2-B43E-48F9-960E-E8AEA9F7763E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{9C17C2F2-B43E-48F9-960E-E8AEA9F7763E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{9C17C2F2-B43E-48F9-960E-E8AEA9F7763E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
Reference in New Issue
Block a user