diff --git a/README.md b/README.md
index a29b9e4..65568ad 100644
--- a/README.md
+++ b/README.md
@@ -36,10 +36,11 @@ A simple measure of dependency freshness
`-r`, `--recursive`: search recursively for all compatible files, even if one is found in a directory passed as an argument
+`-o`, `--output`: sets how the output is displayed, valid options are `table` (default) or `json`. If `json` is selected, `--quiet` minifies the outputted JSON.
+
#### Limits:
`-l`, `--limit`: exits with error code if total libyears behind is greater than this value
`-p`, `--limit-project`: exits with error code if any project is more libyears behind than this value
-
`-a`, `--limit-any`: exits with error code if any dependency is more libyears behind than this value
\ No newline at end of file
diff --git a/src/LibYear.Core/LibYear.Core.csproj b/src/LibYear.Core/LibYear.Core.csproj
index d5064ce..92dfaa3 100644
--- a/src/LibYear.Core/LibYear.Core.csproj
+++ b/src/LibYear.Core/LibYear.Core.csproj
@@ -19,7 +19,7 @@
-
+
diff --git a/src/LibYear/App.cs b/src/LibYear/App.cs
index b2e4fc9..1bdd055 100644
--- a/src/LibYear/App.cs
+++ b/src/LibYear/App.cs
@@ -1,4 +1,7 @@
using LibYear.Core;
+using LibYear.Output;
+using LibYear.Output.Json;
+using LibYear.Output.Table;
using Spectre.Console;
namespace LibYear;
@@ -27,7 +30,8 @@ public async Task Run(Settings settings)
}
var result = await _checker.GetPackages(projects);
- DisplayAllResultsTables(result, settings.QuietMode);
+ var output = GetOutputMethod(settings);
+ output.DisplayAllResults(result, settings.QuietMode);
if (settings.Update)
{
@@ -44,63 +48,11 @@ public async Task Run(Settings settings)
: 0;
}
- private void DisplayAllResultsTables(SolutionResult allResults, bool quietMode)
- {
- if (!allResults.Details.Any())
- return;
-
- int MaxLength(Func field) => allResults.Details.Max(results => results.Details.Any() ? results.Details.Max(field) : 0);
- var namePad = Math.Max("Package".Length, MaxLength(r => r.Name.Length));
- var installedPad = Math.Max("Installed".Length, MaxLength(r => r.Installed?.Version.ToString().Length ?? 0));
- var latestPad = Math.Max("Latest".Length, MaxLength(r => r.Latest?.Version.ToString().Length ?? 0));
-
- var width = allResults.Details.Max(r => r.ProjectFile.FileName.Length);
- foreach (var results in allResults.Details)
- GetResultsTable(results, width, namePad, installedPad, latestPad, quietMode);
-
- if (allResults.Details.Count > 1)
- {
- _console.WriteLine($"Total is {allResults.YearsBehind:F1} libyears behind");
- }
- }
-
- private void GetResultsTable(ProjectResult results, int titlePad, int namePad, int installedPad, int latestPad, bool quietMode)
- {
- if (results.Details.Count == 0)
- return;
-
- var width = Math.Max(titlePad + 2, namePad + installedPad + latestPad + 48) + 2;
- var table = new Table
+ private IOutput GetOutputMethod(Settings settings) =>
+ settings.Output switch
{
- Title = new TableTitle($" {results.ProjectFile.FileName}".PadRight(width)),
- Caption = new TableTitle(($" Project is {results.YearsBehind:F1} libyears behind").PadRight(width)),
- Width = width
+ OutputOption.Table => new TableOutput(_console),
+ OutputOption.Json => new JsonOutput(_console),
+ _ => throw new NotImplementedException()
};
- table.AddColumn(new TableColumn("Package").Width(namePad));
- table.AddColumn(new TableColumn("Installed").Width(installedPad));
- table.AddColumn(new TableColumn("Released"));
- table.AddColumn(new TableColumn("Latest").Width(latestPad));
- table.AddColumn(new TableColumn("Released"));
- table.AddColumn(new TableColumn("Age (y)"));
-
- foreach (var result in results.Details.Where(r => !quietMode || r.YearsBehind > 0))
- {
- table.AddRow(
- result.Name,
- result.Installed?.Version.ToString() ?? string.Empty,
- result.Installed?.Date.ToString("yyyy-MM-dd") ?? string.Empty,
- result.Latest?.Version.ToString() ?? string.Empty,
- result.Latest?.Date.ToString("yyyy-MM-dd") ?? string.Empty,
- result.YearsBehind.ToString("F1")
- );
- }
-
- if (quietMode && Math.Abs(results.YearsBehind) < double.Epsilon)
- {
- table.ShowHeaders = false;
- }
-
- _console.Write(table);
- _console.WriteLine();
- }
}
\ No newline at end of file
diff --git a/src/LibYear/LibYear.csproj b/src/LibYear/LibYear.csproj
index eccbe80..44f9cb8 100644
--- a/src/LibYear/LibYear.csproj
+++ b/src/LibYear/LibYear.csproj
@@ -22,7 +22,7 @@
-
+
diff --git a/src/LibYear/Output/IOutput.cs b/src/LibYear/Output/IOutput.cs
new file mode 100644
index 0000000..f0cda37
--- /dev/null
+++ b/src/LibYear/Output/IOutput.cs
@@ -0,0 +1,8 @@
+using LibYear.Core;
+
+namespace LibYear.Output;
+
+public interface IOutput
+{
+ public void DisplayAllResults(SolutionResult allResults, bool quietMode);
+}
\ No newline at end of file
diff --git a/src/LibYear/Output/Json/DateTimeConverter.cs b/src/LibYear/Output/Json/DateTimeConverter.cs
new file mode 100644
index 0000000..2c9d6d4
--- /dev/null
+++ b/src/LibYear/Output/Json/DateTimeConverter.cs
@@ -0,0 +1,22 @@
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace LibYear.Output.Json;
+
+public sealed class DateTimeConverter : JsonConverter
+{
+ public override DateTime Read(
+ ref Utf8JsonReader reader,
+ Type typeToConvert,
+ JsonSerializerOptions options) =>
+ DateTime.ParseExact(reader.GetString()!,
+ "yyyy-MM-dd", CultureInfo.InvariantCulture);
+
+ public override void Write(
+ Utf8JsonWriter writer,
+ DateTime dateTimeValue,
+ JsonSerializerOptions options) =>
+ writer.WriteStringValue(dateTimeValue.ToString(
+ "yyyy-MM-dd", CultureInfo.InvariantCulture));
+}
\ No newline at end of file
diff --git a/src/LibYear/Output/Json/DisplayVersion.cs b/src/LibYear/Output/Json/DisplayVersion.cs
new file mode 100644
index 0000000..9952e53
--- /dev/null
+++ b/src/LibYear/Output/Json/DisplayVersion.cs
@@ -0,0 +1,15 @@
+using System.Text.Json.Serialization;
+using LibYear.Core;
+
+namespace LibYear.Output.Json;
+
+internal sealed record DisplayVersion
+{
+ public string VersionNumber { get; init; } = string.Empty;
+ public DateTime ReleaseDate { get; init; }
+ public DisplayVersion(Release release)
+ {
+ VersionNumber = release.Version.ToString();
+ ReleaseDate = release.Date;
+ }
+}
\ No newline at end of file
diff --git a/src/LibYear/Output/Json/DoubleFormatter.cs b/src/LibYear/Output/Json/DoubleFormatter.cs
new file mode 100644
index 0000000..707943d
--- /dev/null
+++ b/src/LibYear/Output/Json/DoubleFormatter.cs
@@ -0,0 +1,18 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace LibYear.Output.Json;
+
+internal sealed class DoubleFormatter : JsonConverter
+{
+ public override double Read(
+ ref Utf8JsonReader reader,
+ Type typeToConvert,
+ JsonSerializerOptions options) => reader.GetDouble();
+
+ public override void Write(
+ Utf8JsonWriter writer,
+ double value,
+ JsonSerializerOptions options) =>
+ writer.WriteNumberValue(Math.Round(value, 1));
+}
\ No newline at end of file
diff --git a/src/LibYear/Output/Json/JsonOutput.cs b/src/LibYear/Output/Json/JsonOutput.cs
new file mode 100644
index 0000000..7af18ee
--- /dev/null
+++ b/src/LibYear/Output/Json/JsonOutput.cs
@@ -0,0 +1,39 @@
+using System.Text.Json;
+using LibYear.Core;
+using Spectre.Console;
+
+namespace LibYear.Output.Json;
+
+public sealed class JsonOutput : IOutput
+{
+ private readonly IAnsiConsole _console;
+
+ public JsonOutput(IAnsiConsole console)
+ {
+ _console = console;
+ }
+
+ public void DisplayAllResults(SolutionResult allResults, bool quietMode)
+ {
+ if (allResults.Details.Count == 0)
+ return;
+ var output = FormatOutput(allResults, quietMode);
+ _console.WriteLine(output);
+ }
+
+ public static string FormatOutput(SolutionResult allResults, bool quietMode)
+ {
+ var model = new ResultOutput(allResults);
+ var serializerOptions = new JsonSerializerOptions
+ {
+ Converters =
+ {
+ new DoubleFormatter(),
+ new DateTimeConverter()
+ },
+ WriteIndented = !quietMode
+ };
+ var output = JsonSerializer.Serialize(model, serializerOptions);
+ return output;
+ }
+}
\ No newline at end of file
diff --git a/src/LibYear/Output/Json/PackageResult.cs b/src/LibYear/Output/Json/PackageResult.cs
new file mode 100644
index 0000000..f81b3e2
--- /dev/null
+++ b/src/LibYear/Output/Json/PackageResult.cs
@@ -0,0 +1,20 @@
+using System.Text.Json.Serialization;
+using LibYear.Core;
+
+namespace LibYear.Output.Json;
+
+internal sealed record PackageResult
+{
+ public string PackageName { get; set; } = string.Empty;
+ public DisplayVersion? CurrentVersion { get; init; }
+ public DisplayVersion? LatestVersion { get; init; }
+ public double YearsBehind { get; init; }
+
+ public PackageResult(Result result)
+ {
+ PackageName = result.Name;
+ YearsBehind = result.YearsBehind;
+ CurrentVersion = result.Installed is null ? null : new DisplayVersion(result.Installed);
+ LatestVersion = result.Latest is null ? null : new DisplayVersion(result.Latest);
+ }
+}
\ No newline at end of file
diff --git a/src/LibYear/Output/Json/ProjectFormatResult.cs b/src/LibYear/Output/Json/ProjectFormatResult.cs
new file mode 100644
index 0000000..2bb6a0b
--- /dev/null
+++ b/src/LibYear/Output/Json/ProjectFormatResult.cs
@@ -0,0 +1,17 @@
+using LibYear.Core;
+
+namespace LibYear.Output.Json;
+
+internal sealed record ProjectFormatResult
+{
+ public string Project { get; init; } = string.Empty;
+ public double YearsBehind { get; init; }
+ public IReadOnlyCollection Packages { get; init; } = [];
+
+ public ProjectFormatResult(ProjectResult projectResult)
+ {
+ Project = projectResult.ProjectFile.FileName;
+ YearsBehind = projectResult.YearsBehind;
+ Packages = projectResult.Details.Select(result => new PackageResult(result)).ToArray();
+ }
+}
\ No newline at end of file
diff --git a/src/LibYear/Output/Json/ResultOutput.cs b/src/LibYear/Output/Json/ResultOutput.cs
new file mode 100644
index 0000000..05daeca
--- /dev/null
+++ b/src/LibYear/Output/Json/ResultOutput.cs
@@ -0,0 +1,22 @@
+using System.Text.Json.Serialization;
+using LibYear.Core;
+
+namespace LibYear.Output.Json;
+
+internal sealed record ResultOutput
+{
+ public double YearsBehind { get; init; }
+ public double DaysBehind { get; init; }
+ public IReadOnlyCollection Projects { get; set; } = [];
+
+ public ResultOutput()
+ {
+ }
+
+ public ResultOutput(SolutionResult solutionResult)
+ {
+ YearsBehind = solutionResult.YearsBehind;
+ DaysBehind = solutionResult.DaysBehind;
+ Projects = solutionResult.Details?.Select(project => new ProjectFormatResult(project)).ToArray() ?? Array.Empty();
+ }
+}
\ No newline at end of file
diff --git a/src/LibYear/Output/OutputOption.cs b/src/LibYear/Output/OutputOption.cs
new file mode 100644
index 0000000..15ed072
--- /dev/null
+++ b/src/LibYear/Output/OutputOption.cs
@@ -0,0 +1,7 @@
+namespace LibYear.Output;
+
+public enum OutputOption
+{
+ Table,
+ Json
+}
\ No newline at end of file
diff --git a/src/LibYear/Output/Table/TableOutput.cs b/src/LibYear/Output/Table/TableOutput.cs
new file mode 100644
index 0000000..d7a3df1
--- /dev/null
+++ b/src/LibYear/Output/Table/TableOutput.cs
@@ -0,0 +1,73 @@
+using LibYear.Core;
+using Spectre.Console;
+
+namespace LibYear.Output.Table;
+
+public sealed class TableOutput : IOutput
+{
+ private readonly IAnsiConsole _console;
+
+ public TableOutput(IAnsiConsole console)
+ {
+ _console = console;
+ }
+
+ public void DisplayAllResults(SolutionResult allResults, bool quietMode)
+ {
+ if (!allResults.Details.Any())
+ return;
+
+ int MaxLength(Func field) => allResults.Details.Max(results => results.Details.Any() ? results.Details.Max(field) : 0);
+ var namePad = Math.Max("Package".Length, MaxLength(r => r.Name.Length));
+ var installedPad = Math.Max("Installed".Length, MaxLength(r => r.Installed?.Version.ToString().Length ?? 0));
+ var latestPad = Math.Max("Latest".Length, MaxLength(r => r.Latest?.Version.ToString().Length ?? 0));
+ var width = allResults.Details.Max(r => r.ProjectFile.FileName.Length);
+ foreach (var results in allResults.Details)
+ GetResultsTable(results, width, namePad, installedPad, latestPad, quietMode);
+
+ if (allResults.Details.Count > 1)
+ {
+ _console.WriteLine($"Total is {allResults.YearsBehind:F1} libyears behind");
+ }
+ }
+
+ private void GetResultsTable(ProjectResult results, int titlePad, int namePad, int installedPad, int latestPad, bool quietMode)
+ {
+ if (results.Details.Count == 0)
+ return;
+
+ var width = Math.Max(titlePad + 2, namePad + installedPad + latestPad + 48) + 2;
+ var table = new Spectre.Console.Table
+ {
+ Title = new TableTitle($" {results.ProjectFile.FileName}".PadRight(width)),
+ Caption = new TableTitle(($" Project is {results.YearsBehind:F1} libyears behind").PadRight(width)),
+ Width = width
+ };
+ table.AddColumn(new TableColumn("Package").Width(namePad));
+ table.AddColumn(new TableColumn("Installed").Width(installedPad));
+ table.AddColumn(new TableColumn("Released"));
+ table.AddColumn(new TableColumn("Latest").Width(latestPad));
+ table.AddColumn(new TableColumn("Released"));
+ table.AddColumn(new TableColumn("Age (y)"));
+
+ foreach (var result in results.Details.Where(r => !quietMode || r.YearsBehind > 0))
+ {
+ table.AddRow(
+ result.Name,
+ result.Installed?.Version.ToString() ?? string.Empty,
+ result.Installed?.Date.ToString("yyyy-MM-dd") ?? string.Empty,
+ result.Latest?.Version.ToString() ?? string.Empty,
+ result.Latest?.Date.ToString("yyyy-MM-dd") ?? string.Empty,
+ result.YearsBehind.ToString("F1")
+ );
+ }
+
+ if (quietMode && Math.Abs(results.YearsBehind) < double.Epsilon)
+ {
+ table.ShowHeaders = false;
+ }
+
+ _console.Write(table);
+ _console.WriteLine();
+ }
+}
\ No newline at end of file
diff --git a/src/LibYear/Settings.cs b/src/LibYear/Settings.cs
index 3658a3d..b61a697 100644
--- a/src/LibYear/Settings.cs
+++ b/src/LibYear/Settings.cs
@@ -1,4 +1,5 @@
using System.ComponentModel;
+using LibYear.Output;
using Spectre.Console.Cli;
namespace LibYear;
@@ -32,4 +33,8 @@ public class Settings : CommandSettings
[CommandOption("-r|--recursive")]
[Description("search recursively for all compatible files, even if one is found in a directory passed as an argument")]
public bool Recursive { get; set; }
-}
\ No newline at end of file
+
+ [CommandOption("-o|--output")]
+ [Description("output format (table or JSON)")]
+ public OutputOption Output { get; set; } = OutputOption.Table;
+}
diff --git a/test/LibYear.Core.Tests/LibYear.Core.Tests.csproj b/test/LibYear.Core.Tests/LibYear.Core.Tests.csproj
index e5e4b32..9db7eab 100644
--- a/test/LibYear.Core.Tests/LibYear.Core.Tests.csproj
+++ b/test/LibYear.Core.Tests/LibYear.Core.Tests.csproj
@@ -6,14 +6,13 @@
-
+
-
+
-
-
+
+
-
diff --git a/test/LibYear.Tests/AppTests.cs b/test/LibYear.Tests/AppTests.cs
index 092b375..6c1e8bf 100644
--- a/test/LibYear.Tests/AppTests.cs
+++ b/test/LibYear.Tests/AppTests.cs
@@ -1,6 +1,7 @@
using LibYear.Core;
using LibYear.Core.FileTypes;
using LibYear.Core.Tests;
+using LibYear.Output;
using NSubstitute;
using Spectre.Console.Testing;
using Xunit;
@@ -105,6 +106,36 @@ public async Task MultiplePackagesShowsGrandTotal()
Assert.Contains("Total", console.Output);
}
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task ShouldUseJsonIfSelected(bool quiet)
+ {
+ //arrange
+ var checker = Substitute.For();
+ var projectFile1 = new TestProjectFile("test project 1");
+ var projectFile2 = new TestProjectFile("test project 2");
+ var results = new SolutionResult(new []
+ {
+ new ProjectResult(projectFile1, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)) }),
+ new ProjectResult(projectFile2, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)) })
+ });
+ checker.GetPackages(Arg.Any>()).Returns(results);
+
+ var manager = Substitute.For();
+ manager.GetAllProjects(Arg.Any>()).Returns(new IProjectFile[] { projectFile1, projectFile2 });
+
+ var console = new TestConsole();
+ var app = new App(checker, manager, console);
+
+ //act
+ await app.Run(new Settings { QuietMode = quiet, Output = OutputOption.Json});
+
+ //assert
+ Assert.StartsWith("{", console.Output.Trim());
+ Assert.EndsWith("}", console.Output.Trim());
+ }
+
[Fact]
public async Task EmptyResultsAreSkipped()
{
diff --git a/test/LibYear.Tests/LibYear.Tests.csproj b/test/LibYear.Tests/LibYear.Tests.csproj
index 0e292b5..90f2264 100644
--- a/test/LibYear.Tests/LibYear.Tests.csproj
+++ b/test/LibYear.Tests/LibYear.Tests.csproj
@@ -6,11 +6,11 @@
-
+
-
-
-
+
+
+
diff --git a/test/LibYear.Tests/Output/Json/DateTimeConverterTests.cs b/test/LibYear.Tests/Output/Json/DateTimeConverterTests.cs
new file mode 100644
index 0000000..7f80fd8
--- /dev/null
+++ b/test/LibYear.Tests/Output/Json/DateTimeConverterTests.cs
@@ -0,0 +1,40 @@
+using LibYear.Output.Json;
+using System.Text.Json;
+using Xunit;
+
+namespace LibYear.Tests.Output.Json;
+
+public class DateTimeConverterTests
+{
+ private static TestObject DateTimeObject = new()
+ {
+ Test = new DateTime(2020, 01, 01)
+ };
+ private static string ExpectedJson = @"{""Test"":""2020-01-01""}";
+ private static JsonSerializerOptions Options = new JsonSerializerOptions
+ {
+ Converters =
+ {
+ new DateTimeConverter()
+ }
+ };
+
+ [Fact]
+ public void ShouldSerializeProperly()
+ {
+ var serialized = JsonSerializer.Serialize(DateTimeObject, Options);
+ Assert.Equal(ExpectedJson, serialized);
+ }
+
+ [Fact]
+ public void ShouldDeserializeProperly()
+ {
+ var deserialized = JsonSerializer.Deserialize(ExpectedJson, Options);
+ Assert.Equal(DateTimeObject, deserialized);
+ }
+
+ private sealed record TestObject
+ {
+ public DateTime Test { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/test/LibYear.Tests/Output/Json/DoubleConverterTests.cs b/test/LibYear.Tests/Output/Json/DoubleConverterTests.cs
new file mode 100644
index 0000000..a42c305
--- /dev/null
+++ b/test/LibYear.Tests/Output/Json/DoubleConverterTests.cs
@@ -0,0 +1,40 @@
+using System.Text.Json;
+using LibYear.Output.Json;
+using Xunit;
+
+namespace LibYear.Tests.Output.Json;
+
+public class DoubleConverterTests
+{
+ private static TestObject DateTimeObject = new()
+ {
+ Test = 15
+ };
+ private static string ExpectedJson = @"{""Test"":15}";
+ private static JsonSerializerOptions Options = new JsonSerializerOptions
+ {
+ Converters =
+ {
+ new DateTimeConverter()
+ }
+ };
+
+ [Fact]
+ public void ShouldSerializeProperly()
+ {
+ var serialized = JsonSerializer.Serialize(DateTimeObject, Options);
+ Assert.Equal(ExpectedJson, serialized);
+ }
+
+ [Fact]
+ public void ShouldDeserializeProperly()
+ {
+ var deserialized = JsonSerializer.Deserialize(ExpectedJson, Options);
+ Assert.Equal(DateTimeObject, deserialized);
+ }
+
+ private sealed record TestObject
+ {
+ public double Test { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/test/LibYear.Tests/Output/Json/JsonOutputTests.cs b/test/LibYear.Tests/Output/Json/JsonOutputTests.cs
new file mode 100644
index 0000000..15b00da
--- /dev/null
+++ b/test/LibYear.Tests/Output/Json/JsonOutputTests.cs
@@ -0,0 +1,113 @@
+using System.Security.AccessControl;
+using LibYear.Core;
+using LibYear.Core.Tests;
+using LibYear.Output.Json;
+using NSubstitute;
+using Spectre.Console;
+using Spectre.Console.Testing;
+using Xunit;
+
+namespace LibYear.Tests.Output.Json;
+
+public class JsonOutputTests
+{
+ [Fact]
+ public void NoResultsProducesNoOutput()
+ {
+ //arrange
+ var console = Substitute.For();
+
+ // act
+ var output = new JsonOutput(console);
+ var result = new SolutionResult(Array.Empty());
+ output.DisplayAllResults(result, false);
+
+ // assert
+ console.DidNotReceive().WriteLine();
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void ResultsShouldPrintToConsole(bool quietMode)
+ {
+ //arrange
+ var console = new TestConsole();
+ var projectFile1 = new TestProjectFile("test project 1");
+ var solutionResults = new SolutionResult(new[]
+ {
+ new ProjectResult(projectFile1, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)) }),
+ });
+
+ // act
+ var sut = new JsonOutput(console);
+ sut.DisplayAllResults(solutionResults, quietMode);
+
+ // assert
+ Assert.NotEmpty(console.Output);
+ }
+
+ [Fact]
+ public void QuietModeResultInSingleLineOutput()
+ {
+ //arrange
+ var projectFile1 = new TestProjectFile("test project 1");
+ var dateTime = new DateTime(2020, 01, 02);
+ var solutionResults = new SolutionResult(new[]
+ {
+ new ProjectResult(projectFile1, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), dateTime), new Release(new PackageVersion(1, 2, 3), dateTime)) }),
+ });
+
+ // act
+ var result = JsonOutput.FormatOutput(solutionResults, true);
+
+ // assert
+ var expectedJsonOutput = @"{""YearsBehind"":0,""DaysBehind"":0,""Projects"":[{""Project"":""test project 1"",""YearsBehind"":0,""Packages"":[{""PackageName"":""test1"",""CurrentVersion"":{""VersionNumber"":""1.2.3"",""ReleaseDate"":""2020-01-02""},""LatestVersion"":{""VersionNumber"":""1.2.3"",""ReleaseDate"":""2020-01-02""},""YearsBehind"":0}]}]}";
+ Assert.Equal(expectedJsonOutput, result);
+ }
+
+ [Fact]
+ public void NonQuietModeShouldResultInMultiLineOutput()
+ {
+ //arrange
+
+ var projectFile1 = new TestProjectFile("test project 1");
+ var dateTime = new DateTime(2020, 01, 02);
+ var results = new SolutionResult(new []
+ {
+ new ProjectResult(projectFile1, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), dateTime), new Release(new PackageVersion(1, 2, 3), dateTime)) }),
+ });
+
+ // act
+ var result = JsonOutput.FormatOutput(results, false);
+
+ // assert
+ var expectedOutput = """
+ {
+ "YearsBehind": 0,
+ "DaysBehind": 0,
+ "Projects": [
+ {
+ "Project": "test project 1",
+ "YearsBehind": 0,
+ "Packages": [
+ {
+ "PackageName": "test1",
+ "CurrentVersion": {
+ "VersionNumber": "1.2.3",
+ "ReleaseDate": "2020-01-02"
+ },
+ "LatestVersion": {
+ "VersionNumber": "1.2.3",
+ "ReleaseDate": "2020-01-02"
+ },
+ "YearsBehind": 0
+ }
+ ]
+ }
+ ]
+ }
+ """;
+ Assert.Equal(expectedOutput.ReplaceLineEndings(), result);
+ }
+}
\ No newline at end of file
diff --git a/test/LibYear.Tests/Output/Table/TableOutputTests.cs b/test/LibYear.Tests/Output/Table/TableOutputTests.cs
new file mode 100644
index 0000000..bb6f7c2
--- /dev/null
+++ b/test/LibYear.Tests/Output/Table/TableOutputTests.cs
@@ -0,0 +1,70 @@
+using System.Text.RegularExpressions;
+using LibYear.Core;
+using LibYear.Core.Tests;
+using LibYear.Output.Table;
+using Spectre.Console.Testing;
+using Xunit;
+
+namespace LibYear.Tests.Output.Table;
+
+public class TableOutputTests
+{
+ [Fact]
+ public void NoResultsProducesNoOutput()
+ {
+ // arrange
+ var console = new TestConsole();
+
+ // act
+ var output = new TableOutput(console);
+ var result = new SolutionResult(Array.Empty());
+ output.DisplayAllResults(result, false);
+
+ // assert
+ Assert.Empty(console.Output);
+ }
+
+ [Fact]
+ public void ShouldPrintATableIfQuietModeDisabled()
+ {
+ //arrange
+ var console = new TestConsole();
+ var legacyDateTime = new DateTime(2020, 01, 03);
+ var newDateTime = new DateTime(2020, 01, 05);
+
+ // act
+ var output = new TableOutput(console);
+ var projectFile1 = new TestProjectFile("test project 1");
+ var results = new SolutionResult(new []
+ {
+ new ProjectResult(projectFile1, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), legacyDateTime), new Release(new PackageVersion(1, 2, 3), newDateTime)) }),
+ });
+ output.DisplayAllResults(results, false);
+
+ // assert
+
+ Assert.NotEmpty(console.Output);
+ Assert.Contains("│ Package │ Installed │ Released │ Latest │ Released │ Age (y) │", console.Output);
+ Assert.Contains("│ test1 │ 1.2.3 │ 2020-01-03 │ 1.2.3 │ 2020-01-05 │ 0.0 │", console.Output);
+ }
+
+ [Fact]
+ public void ShouldPrintSimplifiedIfInQuietMode()
+ {
+ // arrange
+ var console = new TestConsole();
+
+ // act
+ var output = new TableOutput(console);
+ var projectFile1 = new TestProjectFile("test project 1");
+ var results = new SolutionResult(new []
+ {
+ new ProjectResult(projectFile1, new[] { new Result("test1", new Release(new PackageVersion(1, 2, 3), DateTime.Today), new Release(new PackageVersion(1, 2, 3), DateTime.Today)) }),
+ });
+ output.DisplayAllResults(results, true);
+
+ // assert
+ Assert.NotEmpty(console.Output);
+ Assert.Contains(" Project is 0.0 libyears ", console.Output);
+ }
+}
\ No newline at end of file