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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 18 additions & 17 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,34 @@ on:
branches: [ main ]

jobs:
build:

build-and-test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
- uses: actions/checkout@v3

- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x

- name: Restore dependencies
run: dotnet restore

- name: Build
run: dotnet build --no-restore

- name: Run Tests
run: dotnet test --no-build --verbosity normal

doxygen:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Doxygen Action
# You may pin to the exact commit or the version.
# uses: mattnotmitt/doxygen-action@cdd5472f8e48e141b89d2633c1ae72991a21cb6a
uses: mattnotmitt/doxygen-action@1.9.2
with:
# Path to Doxyfile
doxyfile-path: ./Doxyfile
# Working directory
working-directory: .
6 changes: 6 additions & 0 deletions BlazorObservers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ObserverLibrary", "Observer
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObserverExample", "ObserverExample\ObserverExample.csproj", "{5F06C015-629D-432E-93E7-C6EA2FAA5ACD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObserverLibraryTests", "ObserverLibraryTests\ObserverLibraryTests.csproj", "{1A3B38A4-7739-49B6-AC27-EB0D916293B3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -21,6 +23,10 @@ Global
{5F06C015-629D-432E-93E7-C6EA2FAA5ACD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F06C015-629D-432E-93E7-C6EA2FAA5ACD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F06C015-629D-432E-93E7-C6EA2FAA5ACD}.Release|Any CPU.Build.0 = Release|Any CPU
{1A3B38A4-7739-49B6-AC27-EB0D916293B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A3B38A4-7739-49B6-AC27-EB0D916293B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A3B38A4-7739-49B6-AC27-EB0D916293B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A3B38A4-7739-49B6-AC27-EB0D916293B3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
5 changes: 5 additions & 0 deletions ObserverLibrary/ObserverLibrary.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ Currently only Resize observer is present.</Description>
</None>
</ItemGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>ObserverLibraryTests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

<ItemGroup>
<SupportedPlatform Include="browser" />
Expand Down
3 changes: 3 additions & 0 deletions ObserverLibrary/Services/ResizeObserverService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public ResizeObserverService(IJSRuntime jsRuntime) : base(jsRuntime)
/// <exception cref="ArgumentException">Thrown if targetElements is an empty array</exception>
public Task<ResizeTask> RegisterObserver(Action<JsResizeObserverEntry[]> onObserve, params ElementReference[] targetElements)
{
if (onObserve is null)
throw new ArgumentNullException(nameof(onObserve));

return ValidateObserverRegistration((entries) => { onObserve(entries); return ValueTask.CompletedTask; }, targetElements);
}

Expand Down
2 changes: 1 addition & 1 deletion ObserverLibrary/Tasks/ObserverTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ private protected ObserverTask(Func<T, ValueTask> taskFunc)
/// <exception cref="ArgumentException"></exception>
public void OnlyTriggerLast(int delay)
{
if (delay < 0) throw new ArgumentException("Delay can not be negative");
if (delay < 0) throw new ArgumentException($"{nameof(delay)} must be positive");
_paused = false;
_delay = delay;
_delayTriggering = true;
Expand Down
20 changes: 20 additions & 0 deletions ObserverLibraryTests/ObserverLibraryTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
ο»Ώ<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ObserverLibrary\ObserverLibrary.csproj" />
</ItemGroup>
</Project>
230 changes: 230 additions & 0 deletions ObserverLibraryTests/Services/ResizeObserverServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
ο»Ώusing System.Xml.Linq;
using BlazorObservers.ObserverLibrary.JsModels;
using BlazorObservers.ObserverLibrary.Services;
using BlazorObservers.ObserverLibrary.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using Moq;
using NUnit.Framework;

namespace BlazorObservers.ObserverLibrary.Tests.Services
{
[TestFixture]
public class ResizeObserverServiceTests
{
private Mock<IJSRuntime> _jsRuntimeMock = null!;
private Mock<IJSObjectReference> _jsModuleMock = null!;
private ResizeObserverService _service = null!;

private readonly Dictionary<ElementReference, string> _elementIdMap = new();

[SetUp]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2012:Use ValueTasks correctly", Justification = "Mock Setup")]
public void SetUp()
{
// clear the elementIdMap before each test
_elementIdMap.Clear();

// Setup the JS module mock
_jsModuleMock = new Mock<IJSObjectReference>();
_jsModuleMock
.Setup(m => m.InvokeAsync<string[]>(
"ObserverManager.CreateNewResizeObserver",
It.IsAny<object[]>()))
.Returns((string _, object[] args) =>
{
var elements = args.Skip(2).Cast<ElementReference>().ToArray();

List<string> ids = new List<string>(elements.Length);
foreach (var element in elements)
{
if (!_elementIdMap.TryGetValue(element, out var id))
{
id = Guid.NewGuid().ToString();
_elementIdMap[element] = id;
ids.Add(id);
}
}

return new ValueTask<string[]>(Task.FromResult(ids.ToArray()));
});

_jsModuleMock
.Setup(m => m.InvokeAsync<string?>(
"ObserverManager.StartObserving",
It.IsAny<object[]>()))
.Returns((string _, object[] args) =>
{
var element = (ElementReference)args[1];

if (!_elementIdMap.TryGetValue(element, out var id))
{
id = Guid.NewGuid().ToString();
_elementIdMap[element] = id;
}

return new ValueTask<string?>(Task.FromResult<string?>(id));
});

_jsModuleMock
.Setup(m => m.InvokeAsync<bool>(
"ObserverManager.StopObserving",
It.IsAny<object[]>()))
.Returns((string _, object[] args) =>
{
var taskId = args[0]?.ToString();
var element = (ElementReference)args[1];

return new ValueTask<bool>(Task.FromResult(_elementIdMap.Remove(element)));
});


// Setup the JS runtime mock
_jsRuntimeMock = new Mock<IJSRuntime>();
_jsRuntimeMock
.Setup(r => r.InvokeAsync<IJSObjectReference>(
"import",
It.Is<object[]>(args => args.Length == 1 && args[0]!.ToString() == "./_content/BlazorObservers/ObserverManager.js")))
.Returns(new ValueTask<IJSObjectReference>(Task.FromResult(_jsModuleMock.Object)));

// Create the service under test
_service = new ResizeObserverService(_jsRuntimeMock.Object);
}

[Test]
public async Task RegisterObserver_SynchronousFunction_ValidatesAndRegisters()
{
var element = new ElementReference(Guid.NewGuid().ToString());
var onObserve = new Action<JsResizeObserverEntry[]>(entries => { });

var task = await _service.RegisterObserver(onObserve, element);

Assert.IsNotNull(task);
Assert.AreEqual(1, task.ConnectedElements.Count);

_jsModuleMock.Verify(m =>
m.InvokeAsync<string[]>(
"ObserverManager.CreateNewResizeObserver",
It.Is<object[]>(args => args.Contains(element))
),
Times.Once);
}

[Test]
public async Task RegisterObserver_AsyncFunction_ValidatesAndRegisters()
{
var element = new ElementReference(Guid.NewGuid().ToString());
var onObserve = new Func<JsResizeObserverEntry[], Task>(entries => Task.CompletedTask);

var task = await _service.RegisterObserver(onObserve, element);

Assert.IsNotNull(task);
Assert.AreEqual(1, task.ConnectedElements.Count);


_jsModuleMock.Verify(m =>
m.InvokeAsync<string[]>(
"ObserverManager.CreateNewResizeObserver",
It.Is<object[]>(args => args.Contains(element))
),
Times.Once);
}

[Test]
public async Task StartObserving_ValidElement_AddsToObservedElements()
{
var element1 = new ElementReference(Guid.NewGuid().ToString());
var element2 = new ElementReference(Guid.NewGuid().ToString());
var onObserve = new Action<JsResizeObserverEntry[]>(entries => { });
var task = await _service.RegisterObserver(onObserve, element1);

var result = await _service.StartObserving(task, element2);

Assert.IsTrue(result);
Assert.AreEqual(2, task.ConnectedElements.Count);
_jsModuleMock.Verify(m =>
m.InvokeAsync<string?>(
"ObserverManager.StartObserving",
It.Is<object[]>(args =>
args.Length == 2 &&
args[0]!.ToString() == task.TaskId.ToString() &&
args[1].GetType() == typeof(ElementReference) && ((ElementReference)args[1]).Equals(element2)
)
),
Times.Once);
}

[Test]
public async Task StopObserving_ValidElement_RemovesFromObservedElements()
{
var element = new ElementReference(Guid.NewGuid().ToString());
var onObserve = new Action<JsResizeObserverEntry[]>(entries => { });
var task = await _service.RegisterObserver(onObserve, element);

var result = await _service.StopObserving(task, element);

Assert.IsTrue(result);
Assert.AreEqual(0, task.ConnectedElements.Count);
_jsModuleMock.Verify(m =>
m.InvokeAsync<bool>(
"ObserverManager.StopObserving",
It.Is<object[]>(args =>
args.Length == 2 &&
args[0]!.ToString() == task.TaskId.ToString() &&
args[1].GetType() == typeof(ElementReference) && element.Equals((ElementReference)args[1])
)
),
Times.Once);
}

[Test]
public async Task DeregisterObserver_RemovesObserverAndElements()
{
var element = new ElementReference(Guid.NewGuid().ToString());
var task = await _service.RegisterObserver(entries => { }, element);

await _service.DeregisterObserver(task);

Assert.AreEqual(0, task.ConnectedElements.Count);
}

[Test]
public async Task DeregisterObserver_ById_RemovesObserverAndElements()
{
var element = new ElementReference(Guid.NewGuid().ToString());
var task = await _service.RegisterObserver(entries => { }, element);

await _service.DeregisterObserver(task.TaskId);

Assert.AreEqual(0, task.ConnectedElements.Count);
}

[Test]
public void RegisterObserver_ThrowsOnNullAction()
{
Assert.ThrowsAsync<ArgumentNullException>(() =>
_service.RegisterObserver((Action<JsResizeObserverEntry[]>)null, new ElementReference()));

Check warning on line 206 in ObserverLibraryTests/Services/ResizeObserverServiceTests.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Cannot convert null literal to non-nullable reference type.

Check warning on line 206 in ObserverLibraryTests/Services/ResizeObserverServiceTests.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Converting null literal or possible null value to non-nullable type.

Check warning on line 206 in ObserverLibraryTests/Services/ResizeObserverServiceTests.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Cannot convert null literal to non-nullable reference type.

Check warning on line 206 in ObserverLibraryTests/Services/ResizeObserverServiceTests.cs

View workflow job for this annotation

GitHub Actions / build-and-test

Converting null literal or possible null value to non-nullable type.
}

[Test]
public void RegisterObserver_ThrowsOnNullElement()
{
Assert.ThrowsAsync<ArgumentNullException>(() =>
_service.RegisterObserver(entries => Task.CompletedTask, null!));
}

[Test]
public void StartObserving_ThrowsOnNullTask()
{
Assert.ThrowsAsync<ArgumentNullException>(async () =>
await _service.StartObserving(null!, new ElementReference()));
}

[Test]
public void StopObserving_ThrowsOnNullTask()
{
Assert.ThrowsAsync<ArgumentNullException>(async () =>
await _service.StopObserving(null!, new ElementReference()));
}
}
}
Loading
Loading