From 04259c385e39aa87a57a57332035d7112a2b112b Mon Sep 17 00:00:00 2001 From: Ferenc Hubicsak <4752853+fhubi@users.noreply.github.com> Date: Tue, 3 Sep 2024 21:06:10 +0200 Subject: [PATCH] Basic layout persistence implementation --- .../dotnet/LayoutPersistence.sln | 39 +++++++ .../Abstractions/ILayoutPersistence.cs | 21 ++++ .../Abstractions/ILayoutSerializer.cs | 21 ++++ .../FileLayoutPersistence.cs | 98 +++++++++++++++++ ...Stanley.ComposeUI.LayoutPersistence.csproj | 9 ++ .../Serializers/JsonLayoutSerializer.cs | 53 +++++++++ .../Serializers/XmlLayoutSerializer.cs | 61 +++++++++++ .../FileLayoutPersistenceTests.cs | 102 ++++++++++++++++++ .../JsonLayoutSerializerTests.cs | 42 ++++++++ .../LayoutData.cs | 37 +++++++ ...y.ComposeUI.LayoutPersistence.Tests.csproj | 29 +++++ .../Window.cs | 51 +++++++++ .../XmlLayoutSerializerTests.cs | 42 ++++++++ 13 files changed, 605 insertions(+) create mode 100644 src/layout-persistence/dotnet/LayoutPersistence.sln create mode 100644 src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Abstractions/ILayoutPersistence.cs create mode 100644 src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Abstractions/ILayoutSerializer.cs create mode 100644 src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/FileLayoutPersistence.cs create mode 100644 src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/MorganStanley.ComposeUI.LayoutPersistence.csproj create mode 100644 src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/JsonLayoutSerializer.cs create mode 100644 src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/XmlLayoutSerializer.cs create mode 100644 src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/FileLayoutPersistenceTests.cs create mode 100644 src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/JsonLayoutSerializerTests.cs create mode 100644 src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/LayoutData.cs create mode 100644 src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/MorganStanley.ComposeUI.LayoutPersistence.Tests.csproj create mode 100644 src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/Window.cs create mode 100644 src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/XmlLayoutSerializerTests.cs diff --git a/src/layout-persistence/dotnet/LayoutPersistence.sln b/src/layout-persistence/dotnet/LayoutPersistence.sln new file mode 100644 index 000000000..8afefde33 --- /dev/null +++ b/src/layout-persistence/dotnet/LayoutPersistence.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34723.18 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MorganStanley.ComposeUI.LayoutPersistence", "src\MorganStanley.ComposeUI.LayoutPersistence\MorganStanley.ComposeUI.LayoutPersistence.csproj", "{BC4F5C92-F894-45A7-BBEA-A956F89C617B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{15375C6D-AEF5-42DA-B809-36C55A5133B0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{387B5C73-2D89-45B1-92E0-71CDBB825F46}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MorganStanley.ComposeUI.LayoutPersistence.Tests", "tests\MorganStanley.ComposeUI.LayoutPersistence.Tests\MorganStanley.ComposeUI.LayoutPersistence.Tests.csproj", "{9727D812-2697-4BB5-8C65-80774842FE99}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BC4F5C92-F894-45A7-BBEA-A956F89C617B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC4F5C92-F894-45A7-BBEA-A956F89C617B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC4F5C92-F894-45A7-BBEA-A956F89C617B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC4F5C92-F894-45A7-BBEA-A956F89C617B}.Release|Any CPU.Build.0 = Release|Any CPU + {9727D812-2697-4BB5-8C65-80774842FE99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9727D812-2697-4BB5-8C65-80774842FE99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9727D812-2697-4BB5-8C65-80774842FE99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9727D812-2697-4BB5-8C65-80774842FE99}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {BC4F5C92-F894-45A7-BBEA-A956F89C617B} = {15375C6D-AEF5-42DA-B809-36C55A5133B0} + {9727D812-2697-4BB5-8C65-80774842FE99} = {387B5C73-2D89-45B1-92E0-71CDBB825F46} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5AC4D360-3DCE-47E1-8128-C76C86113476} + EndGlobalSection +EndGlobal diff --git a/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Abstractions/ILayoutPersistence.cs b/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Abstractions/ILayoutPersistence.cs new file mode 100644 index 000000000..ad59afe81 --- /dev/null +++ b/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Abstractions/ILayoutPersistence.cs @@ -0,0 +1,21 @@ +/* + * Morgan Stanley makes this available to you under the Apache License, + * Version 2.0 (the "License"). You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0. + * + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. Unless required by applicable law or agreed + * to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +namespace MorganStanley.ComposeUI.LayoutPersistence.Abstractions; + +public interface ILayoutPersistence +{ + Task SaveLayoutAsync(string layoutName, T layoutData, CancellationToken cancellationToken = default); + Task LoadLayoutAsync(string layoutName, CancellationToken cancellationToken = default); +} diff --git a/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Abstractions/ILayoutSerializer.cs b/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Abstractions/ILayoutSerializer.cs new file mode 100644 index 000000000..8b441bb67 --- /dev/null +++ b/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Abstractions/ILayoutSerializer.cs @@ -0,0 +1,21 @@ +/* + * Morgan Stanley makes this available to you under the Apache License, + * Version 2.0 (the "License"). You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0. + * + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. Unless required by applicable law or agreed + * to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +namespace MorganStanley.ComposeUI.LayoutPersistence.Abstractions; + +public interface ILayoutSerializer +{ + Task SerializeAsync(T layoutObject, CancellationToken cancellationToken = default); + Task DeserializeAsync(string layoutData, CancellationToken cancellationToken = default); +} diff --git a/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/FileLayoutPersistence.cs b/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/FileLayoutPersistence.cs new file mode 100644 index 000000000..c5bfd177a --- /dev/null +++ b/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/FileLayoutPersistence.cs @@ -0,0 +1,98 @@ +/* + * Morgan Stanley makes this available to you under the Apache License, + * Version 2.0 (the "License"). You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0. + * + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. Unless required by applicable law or agreed + * to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +using MorganStanley.ComposeUI.LayoutPersistence.Abstractions; + +namespace MorganStanley.ComposeUI.LayoutPersistence; + +public class FileLayoutPersistence : ILayoutPersistence +{ + private readonly string _basePath; + private readonly ILayoutSerializer _serializer; + private readonly SemaphoreSlim _semaphore = new(1,1); + + public FileLayoutPersistence(string basePath, ILayoutSerializer serializer) + { + _basePath = NormalizeFilePath(basePath); + _serializer = serializer; + + if (!Directory.Exists(_basePath)) + { + Directory.CreateDirectory(_basePath); + } + } + + public async Task SaveLayoutAsync(string layoutName, T layoutData, CancellationToken cancellationToken = default) + { + var filePath = GetFilePath(layoutName); + + await _semaphore.WaitAsync(cancellationToken); + + try + { + var serializedData = await _serializer.SerializeAsync(layoutData, cancellationToken); + await File.WriteAllTextAsync(filePath, serializedData, cancellationToken); + } + finally + { + _semaphore.Release(); + } + } + + public async Task LoadLayoutAsync(string layoutName, CancellationToken cancellationToken = default) + { + var filePath = GetFilePath(layoutName); + + if (!File.Exists(filePath)) + { + throw new FileNotFoundException($"Layout file not found: {filePath}"); + } + + await _semaphore.WaitAsync(cancellationToken); + + try + { + var serializedData = await File.ReadAllTextAsync(filePath, cancellationToken); + return await _serializer.DeserializeAsync(serializedData, cancellationToken); + } + finally + { + _semaphore.Release(); + } + } + + private string GetFilePath(string layoutName) + { + var combinedPath = Path.Combine(_basePath, $"{layoutName}.layout"); + var fullPath = Path.GetFullPath(combinedPath); + + if (!fullPath.StartsWith(_basePath, StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("Invalid layoutName argument. File cannot be saved outside of the base directory.", layoutName); + } + + return fullPath; + } + + private static string NormalizeFilePath(string path) + { + if (path.StartsWith("file://", StringComparison.OrdinalIgnoreCase)) + { + var normalizedPath = Uri.UnescapeDataString(path[7..]); + return Path.GetFullPath(normalizedPath); + } + + return Path.GetFullPath(path); + } +} diff --git a/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/MorganStanley.ComposeUI.LayoutPersistence.csproj b/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/MorganStanley.ComposeUI.LayoutPersistence.csproj new file mode 100644 index 000000000..27ac3865b --- /dev/null +++ b/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/MorganStanley.ComposeUI.LayoutPersistence.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/JsonLayoutSerializer.cs b/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/JsonLayoutSerializer.cs new file mode 100644 index 000000000..88fa86ab2 --- /dev/null +++ b/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/JsonLayoutSerializer.cs @@ -0,0 +1,53 @@ +/* + * Morgan Stanley makes this available to you under the Apache License, + * Version 2.0 (the "License"). You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0. + * + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. Unless required by applicable law or agreed + * to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +using System.Text; +using System.Text.Json; +using MorganStanley.ComposeUI.LayoutPersistence.Abstractions; + +namespace MorganStanley.ComposeUI.LayoutPersistence.Serializers; + +public class JsonLayoutSerializer : ILayoutSerializer +{ + private readonly JsonSerializerOptions? _jsonSerializerOptions; + + public JsonLayoutSerializer(JsonSerializerOptions? jsonSerializerOptions = null) + { + _jsonSerializerOptions = jsonSerializerOptions; + } + + public async Task SerializeAsync(T layoutObject, CancellationToken cancellationToken = default) + { + if (layoutObject == null) + { + throw new ArgumentNullException(nameof(layoutObject), "The layout object to serialize cannot be null."); + } + + using var stream = new MemoryStream(); + await JsonSerializer.SerializeAsync(stream, layoutObject, _jsonSerializerOptions, cancellationToken); + + return Encoding.UTF8.GetString(stream.ToArray()); + } + + public async Task DeserializeAsync(string layoutData, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(layoutData)) + { + throw new ArgumentException("The layout data cannot be null or empty.", nameof(layoutData)); + } + + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(layoutData)); + return await JsonSerializer.DeserializeAsync(stream, _jsonSerializerOptions, cancellationToken); + } +} diff --git a/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/XmlLayoutSerializer.cs b/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/XmlLayoutSerializer.cs new file mode 100644 index 000000000..a5edc9d0b --- /dev/null +++ b/src/layout-persistence/dotnet/src/MorganStanley.ComposeUI.LayoutPersistence/Serializers/XmlLayoutSerializer.cs @@ -0,0 +1,61 @@ +/* + * Morgan Stanley makes this available to you under the Apache License, + * Version 2.0 (the "License"). You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0. + * + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. Unless required by applicable law or agreed + * to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +using MorganStanley.ComposeUI.LayoutPersistence.Abstractions; +using System.Text; +using System.Xml.Serialization; + +namespace MorganStanley.ComposeUI.LayoutPersistence.Serializers; + +public class XmlLayoutSerializer : ILayoutSerializer +{ + public async Task SerializeAsync(T layoutObject, CancellationToken cancellationToken = default) + { + if (layoutObject == null) + { + throw new ArgumentNullException(nameof(layoutObject), "The layout object to serialize cannot be null."); + } + + using var stream = new MemoryStream(); + var serializer = new XmlSerializer(typeof(T)); + + using (var writer = new StreamWriter(stream, Encoding.UTF8, leaveOpen: true)) + { + serializer.Serialize(writer, layoutObject); + await writer.FlushAsync(); + } + + cancellationToken.ThrowIfCancellationRequested(); + + return Encoding.UTF8.GetString(stream.ToArray()); + } + + public async Task DeserializeAsync(string layoutData, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(layoutData)) + { + throw new ArgumentException("The layout data cannot be null or empty.", nameof(layoutData)); + } + + var dataBytes = Encoding.UTF8.GetBytes(layoutData); + + using var stream = new MemoryStream(dataBytes); + + cancellationToken.ThrowIfCancellationRequested(); + + var serializer = new XmlSerializer(typeof(T)); + + return await Task.FromResult((T?)serializer.Deserialize(stream)); + } +} diff --git a/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/FileLayoutPersistenceTests.cs b/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/FileLayoutPersistenceTests.cs new file mode 100644 index 000000000..4a1c1f082 --- /dev/null +++ b/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/FileLayoutPersistenceTests.cs @@ -0,0 +1,102 @@ +/* + * Morgan Stanley makes this available to you under the Apache License, + * Version 2.0 (the "License"). You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0. + * + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. Unless required by applicable law or agreed + * to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +using FluentAssertions; +using MorganStanley.ComposeUI.LayoutPersistence.Serializers; + +namespace MorganStanley.ComposeUI.LayoutPersistence.Tests; + +public class FileLayoutPersistenceTests : IDisposable +{ + private readonly string _testDirectory = "test_layouts"; + private readonly FileLayoutPersistence _persistence; + + public FileLayoutPersistenceTests() + { + _persistence = new FileLayoutPersistence($"file://{_testDirectory}", new JsonLayoutSerializer()); + } + + [Fact] + public async Task SaveLayout_ShouldCreateFile() + { + var layoutData = new LayoutData + { + Windows = new List + { + new() { Id = "1", X = 10, Y = 20, Width = 300, Height = 200 } + } + }; + + var layoutName = "TestLayout"; + + await _persistence.SaveLayoutAsync(layoutName, layoutData); + var filePath = Path.Combine(_testDirectory, "TestLayout.layout"); + + File.Exists(filePath).Should().BeTrue(); + } + + [Fact] + public async Task LoadLayout_ShouldReturnCorrectContent() + { + var layoutData = new LayoutData + { + Windows = new List + { + new() { Id = "1", X = 10, Y = 20, Width = 300, Height = 200 } + } + }; + + var layoutName = "TestLayout"; + + await _persistence.SaveLayoutAsync(layoutName, layoutData); + var loadedData = await _persistence.LoadLayoutAsync(layoutName); + + loadedData.Should().Be(layoutData); + } + + [Fact] + public async Task SaveLayout_WithInvalidLayoutName_ShouldThrowArgumentException() + { + var layoutData = new LayoutData + { + Windows = new List + { + new() { Id = "1", X = 10, Y = 20, Width = 300, Height = 200 } + } + }; + + var layoutName = "../TestLayout"; + + Func act = async () => await _persistence.SaveLayoutAsync(layoutName, layoutData); + await act.Should().ThrowAsync() + .WithMessage("Invalid layoutName argument. File cannot be saved outside of the base directory. *"); + } + + [Fact] + public async Task LoadLayout_NonExistentFile_ShouldThrowFileNotFoundException() + { + Func act = async () => await _persistence.LoadLayoutAsync("NonExistentLayout"); + + await act.Should().ThrowAsync() + .WithMessage("Layout file not found: *"); + } + + public void Dispose() + { + if (Directory.Exists(_testDirectory)) + { + Directory.Delete(_testDirectory, true); + } + } +} diff --git a/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/JsonLayoutSerializerTests.cs b/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/JsonLayoutSerializerTests.cs new file mode 100644 index 000000000..3777f7415 --- /dev/null +++ b/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/JsonLayoutSerializerTests.cs @@ -0,0 +1,42 @@ +/* + * Morgan Stanley makes this available to you under the Apache License, + * Version 2.0 (the "License"). You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0. + * + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. Unless required by applicable law or agreed + * to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +using FluentAssertions; +using MorganStanley.ComposeUI.LayoutPersistence.Serializers; + +namespace MorganStanley.ComposeUI.LayoutPersistence.Tests; + +public class JsonLayoutSerializerTests +{ + private readonly JsonLayoutSerializer _serializer = new(); + + [Fact] + public async Task Serialize_Then_Deserialize_ShouldReturnTheOriginalObject() + { + var layoutObject = new LayoutData + { + Windows = new List + { + new() { Id = "1", X = 10, Y = 20, Width = 300, Height = 200 }, + new() { Id = "2", X = 30, Y = 40, Width = 500, Height = 400 } + } + }; + + var json = await _serializer.SerializeAsync(layoutObject); + + var loadedData = await _serializer.DeserializeAsync(json); + + loadedData.Should().Be(layoutObject); + } +} \ No newline at end of file diff --git a/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/LayoutData.cs b/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/LayoutData.cs new file mode 100644 index 000000000..89c132b16 --- /dev/null +++ b/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/LayoutData.cs @@ -0,0 +1,37 @@ +/* + * Morgan Stanley makes this available to you under the Apache License, + * Version 2.0 (the "License"). You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0. + * + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. Unless required by applicable law or agreed + * to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +namespace MorganStanley.ComposeUI.LayoutPersistence.Tests; + +public class LayoutData +{ + public List Windows { get; set; } = new List(); + + public override bool Equals(object? obj) + { + if (obj is LayoutData other) + { + return Windows.SequenceEqual(other.Windows); + } + return false; + } + + public override int GetHashCode() + { + unchecked + { + return Windows.Aggregate(0, (hash, window) => (hash * 397) ^ (window?.GetHashCode() ?? 0)); + } + } +} diff --git a/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/MorganStanley.ComposeUI.LayoutPersistence.Tests.csproj b/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/MorganStanley.ComposeUI.LayoutPersistence.Tests.csproj new file mode 100644 index 000000000..c65ef23a3 --- /dev/null +++ b/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/MorganStanley.ComposeUI.LayoutPersistence.Tests.csproj @@ -0,0 +1,29 @@ + + + + net6.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/Window.cs b/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/Window.cs new file mode 100644 index 000000000..d1809bcdf --- /dev/null +++ b/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/Window.cs @@ -0,0 +1,51 @@ +/* + * Morgan Stanley makes this available to you under the Apache License, + * Version 2.0 (the "License"). You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0. + * + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. Unless required by applicable law or agreed + * to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +namespace MorganStanley.ComposeUI.LayoutPersistence.Tests; + +public class Window +{ + public string Id { get; set; } + public int X { get; set; } + public int Y { get; set; } + public int Width { get; set; } + public int Height { get; set; } + + public override bool Equals(object? obj) + { + if (obj is Window other) + { + return Id == other.Id && + X == other.X && + Y == other.Y && + Width == other.Width && + Height == other.Height; + } + return false; + } + + public override int GetHashCode() + { + unchecked + { + int hashCode = 17; + hashCode = hashCode * 31 + (Id?.GetHashCode() ?? 0); + hashCode = hashCode * 31 + X; + hashCode = hashCode * 31 + Y; + hashCode = hashCode * 31 + Width; + hashCode = hashCode * 31 + Height; + return hashCode; + } + } +} diff --git a/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/XmlLayoutSerializerTests.cs b/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/XmlLayoutSerializerTests.cs new file mode 100644 index 000000000..140137eb4 --- /dev/null +++ b/src/layout-persistence/dotnet/tests/MorganStanley.ComposeUI.LayoutPersistence.Tests/XmlLayoutSerializerTests.cs @@ -0,0 +1,42 @@ +/* + * Morgan Stanley makes this available to you under the Apache License, + * Version 2.0 (the "License"). You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0. + * + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. Unless required by applicable law or agreed + * to in writing, software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +using FluentAssertions; +using MorganStanley.ComposeUI.LayoutPersistence.Serializers; + +namespace MorganStanley.ComposeUI.LayoutPersistence.Tests; + +public class XmlLayoutSerializerTests +{ + private readonly XmlLayoutSerializer _serializer = new(); + + [Fact] + public async Task Serialize_Then_Deserialize_ShouldReturnTheOriginalObject() + { + var layoutObject = new LayoutData + { + Windows = new List + { + new() { Id = "1", X = 10, Y = 20, Width = 300, Height = 200 }, + new() { Id = "2", X = 30, Y = 40, Width = 500, Height = 400 } + } + }; + + var xml = await _serializer.SerializeAsync(layoutObject); + + var loadedData = await _serializer.DeserializeAsync(xml); + + loadedData.Should().Be(layoutObject); + } +}