Skip to content

Commit

Permalink
Merge pull request #66 from Blazored/65-fix-serialisation
Browse files Browse the repository at this point in the history
Fix for deserialisation issue when overwriting existing value
  • Loading branch information
chrissainty committed Mar 31, 2020
2 parents f1d9d08 + 93a45cb commit 5fda447
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 35 deletions.
2 changes: 1 addition & 1 deletion src/Blazored.LocalStorage/Blazored.LocalStorage.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<RootNamespace>Blazored.LocalStorage</RootNamespace>

<PackageId>Blazored.LocalStorage</PackageId>
<Version>2.1.4</Version>
<Version>2.1.5</Version>
<Authors>Chris Sainty</Authors>
<Description>A library to provide access to local storage in Blazor applications</Description>
<Copyright>Copyright 2018 (c) Chris Sainty. All rights reserved.</Copyright>
Expand Down
52 changes: 49 additions & 3 deletions src/Blazored.LocalStorage/LocalStorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ public T GetItem<T>(string key)
return default(T);

if (serialisedData.StartsWith("{") && serialisedData.EndsWith("}")
|| serialisedData.StartsWith("\"") && serialisedData.EndsWith("\""))
|| serialisedData.StartsWith("\"") && serialisedData.EndsWith("\"")
|| typeof(T) != typeof(string))
{
return JsonSerializer.Deserialize<T>(serialisedData, _jsonOptions);
}
Expand Down Expand Up @@ -180,7 +181,7 @@ private async Task<ChangingEventArgs> RaiseOnChangingAsync(string key, object da
var e = new ChangingEventArgs
{
Key = key,
OldValue = await GetItemAsync<object>(key),
OldValue = await GetItemInternalAsync<object>(key),
NewValue = data
};

Expand All @@ -189,12 +190,33 @@ private async Task<ChangingEventArgs> RaiseOnChangingAsync(string key, object da
return e;
}

private async Task<T> GetItemInternalAsync<T>(string key)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException(nameof(key));

var serialisedData = await _jSRuntime.InvokeAsync<string>("localStorage.getItem", key);

if (string.IsNullOrWhiteSpace(serialisedData))
return default(T);

if (serialisedData.StartsWith("{") && serialisedData.EndsWith("}")
|| serialisedData.StartsWith("\"") && serialisedData.EndsWith("\""))
{
return JsonSerializer.Deserialize<T>(serialisedData, _jsonOptions);
}
else
{
return (T)(object)serialisedData;
}
}

private ChangingEventArgs RaiseOnChangingSync(string key, object data)
{
var e = new ChangingEventArgs
{
Key = key,
OldValue = ((ISyncLocalStorageService)this).GetItem<object>(key),
OldValue = GetItemInternal<object>(key),
NewValue = data
};

Expand All @@ -203,6 +225,30 @@ private ChangingEventArgs RaiseOnChangingSync(string key, object data)
return e;
}

public T GetItemInternal<T>(string key)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException(nameof(key));

if (_jSInProcessRuntime == null)
throw new InvalidOperationException("IJSInProcessRuntime not available");

var serialisedData = _jSInProcessRuntime.Invoke<string>("localStorage.getItem", key);

if (string.IsNullOrWhiteSpace(serialisedData))
return default(T);

if (serialisedData.StartsWith("{") && serialisedData.EndsWith("}")
|| serialisedData.StartsWith("\"") && serialisedData.EndsWith("\""))
{
return JsonSerializer.Deserialize<T>(serialisedData, _jsonOptions);
}
else
{
return (T)(object)serialisedData;
}
}

public event EventHandler<ChangedEventArgs> Changed;
private void RaiseOnChanged(string key, object oldValue, object data)
{
Expand Down
103 changes: 103 additions & 0 deletions tests/Blazored.LocalStorage.Tests/LocalStorageServiceTests/GetItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using Blazored.LocalStorage.JsonConverters;
using Blazored.LocalStorage.Tests.Mocks;
using Blazored.LocalStorage.Tests.TestAssets;
using FluentAssertions;
using Moq;
using System.Text.Json;
using System.Threading.Tasks;
using Xunit;

namespace Blazored.LocalStorage.Tests.LocalStorageServiceTests
{
public class GetItem
{
private JsonSerializerOptions _jsonOptions;
private Mock<JSRuntimeWrapper> _mockJSRuntime;
private LocalStorageService _sut;

private static string _key = "testKey";

public GetItem()
{
_mockJSRuntime = new Mock<JSRuntimeWrapper>();
_jsonOptions = new JsonSerializerOptions();
_jsonOptions.Converters.Add(new TimespanJsonConverter());
_sut = new LocalStorageService(_mockJSRuntime.Object);
}

[Theory]
[InlineData("stringTest")]
[InlineData(11)]
[InlineData(11.11)]
public void Should_DeserialiseToCorrectType<T>(T value)
{
// Arrange
var serialisedData = "";
if (typeof(T) == typeof(string))
serialisedData = value.ToString();
else
serialisedData = JsonSerializer.Serialize(value, _jsonOptions);

_mockJSRuntime.Setup(x => x.Invoke<string>("localStorage.getItem", new[] { _key }))
.Returns(() => serialisedData);

// Act
var result = _sut.GetItem<T>(_key);

// Assert
Assert.Equal(value, result);
_mockJSRuntime.Verify();
}

[Fact]
public void Should_DeserialiseValueToNullableInt()
{
// Arrange
int? value = 6;
var serialisedData = JsonSerializer.Serialize(value, _jsonOptions);

_mockJSRuntime.Setup(x => x.Invoke<string>("localStorage.getItem", new[] { _key }))
.Returns(() => serialisedData);

// Act
var result = _sut.GetItem<int?>(_key);

// Assert
Assert.Equal(value, result);
}

[Fact]
public void Should_DeserialiseValueToDecimal()
{
// Arrange
decimal value = 6.00m;
var serialisedData = JsonSerializer.Serialize(value, _jsonOptions);

_mockJSRuntime.Setup(x => x.Invoke<string>("localStorage.getItem", new[] { _key }))
.Returns(() => serialisedData);

// Act
var result = _sut.GetItem<decimal>(_key);

// Assert
Assert.Equal(value, result);
}

[Fact]
public void Should_DeserialiseValueToComplexType()
{
// Arrange
TestObject value = new TestObject { Id = 1, Name = "John Smith" };
var serialisedData = JsonSerializer.Serialize(value, _jsonOptions);

_mockJSRuntime.Setup(x => x.Invoke<string>("localStorage.getItem", new[] { _key }))
.Returns(() => serialisedData);

// Act
var result = _sut.GetItem<TestObject>(_key);

// Assert
result.Should().BeEquivalentTo(value);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Blazored.LocalStorage.JsonConverters;
using Blazored.LocalStorage.Tests.Mocks;
using Blazored.LocalStorage.Tests.TestAssets;
using FluentAssertions;
using Moq;
using System.Text.Json;
Expand All @@ -11,14 +12,14 @@ namespace Blazored.LocalStorage.Tests.LocalStorageServiceTests
public class GetItemAsync
{
private JsonSerializerOptions _jsonOptions;
private Mock<JSRuntimeWrapper> _mockJSRuntime;
private Mock<JSRuntimeWrapperAsync> _mockJSRuntime;
private LocalStorageService _sut;

private static string _key = "testKey";

public GetItemAsync()
{
_mockJSRuntime = new Mock<JSRuntimeWrapper>();
_mockJSRuntime = new Mock<JSRuntimeWrapperAsync>();
_jsonOptions = new JsonSerializerOptions();
_jsonOptions.Converters.Add(new TimespanJsonConverter());
_sut = new LocalStorageService(_mockJSRuntime.Object);
Expand Down Expand Up @@ -99,10 +100,4 @@ public async Task Should_DeserialiseValueToComplexType()
result.Should().BeEquivalentTo(value);
}
}

public class TestObject
{
public int Id { get; set; }
public string Name { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Blazored.LocalStorage.JsonConverters;
using Blazored.LocalStorage.Tests.Mocks;
using FluentAssertions;
using Moq;
using System.Text.Json;
using Xunit;

namespace Blazored.LocalStorage.Tests.LocalStorageServiceTests
{
public class SetItem
{
private JsonSerializerOptions _jsonOptions;
private Mock<JSRuntimeWrapper> _mockJSRuntime;
private LocalStorageService _sut;

private static string _key = "testKey";

public SetItem()
{
_mockJSRuntime = new Mock<JSRuntimeWrapper>();
_jsonOptions = new JsonSerializerOptions();
_jsonOptions.Converters.Add(new TimespanJsonConverter());
_sut = new LocalStorageService(_mockJSRuntime.Object);
}

[Fact]
public void Should_OverwriteExistingValue()
{
// Arrange
string existingValue = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmlzdHJhdG9yIiwiZXhwIjoxNTg1NjYwNzEyLCJpc3MiOiJDb2RlUmVkQm9va2luZy5TZXJ2ZXIiLCJhdWQiOiJDb2RlUmVkQm9va2luZy5DbGllbnRzIn0.JhK1M1H7NLCFexujJYCDjTn9La0HloGYADMHXGCFksU";
string newValue = "6QLE0LL7iw7tHPAwold31qUENt3lVTUZxDGqeXQFx38=";

_mockJSRuntime.Setup(x => x.Invoke<string>("localStorage.getItem", new[] { _key }))
.Returns(() => existingValue);
_mockJSRuntime.Setup(x => x.InvokeVoid("localStorage.setItem", new[] { _key, newValue }));

// Act
_sut.SetItem(_key, newValue);

// Assert
_mockJSRuntime.Verify();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Blazored.LocalStorage.JsonConverters;
using Blazored.LocalStorage.Tests.Mocks;
using FluentAssertions;
using Moq;
using System.Text.Json;
using System.Threading.Tasks;
using Xunit;

namespace Blazored.LocalStorage.Tests.LocalStorageServiceTests
{
public class SetItemAsync
{
private JsonSerializerOptions _jsonOptions;
private Mock<JSRuntimeWrapperAsync> _mockJSRuntime;
private LocalStorageService _sut;

private static string _key = "testKey";

public SetItemAsync()
{
_mockJSRuntime = new Mock<JSRuntimeWrapperAsync>();
_jsonOptions = new JsonSerializerOptions();
_jsonOptions.Converters.Add(new TimespanJsonConverter());
_sut = new LocalStorageService(_mockJSRuntime.Object);
}

[Fact]
public async Task Should_OverwriteExistingValue()
{
// Arrange
string existingValue = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbmlzdHJhdG9yIiwiZXhwIjoxNTg1NjYwNzEyLCJpc3MiOiJDb2RlUmVkQm9va2luZy5TZXJ2ZXIiLCJhdWQiOiJDb2RlUmVkQm9va2luZy5DbGllbnRzIn0.JhK1M1H7NLCFexujJYCDjTn9La0HloGYADMHXGCFksU";
string newValue = "6QLE0LL7iw7tHPAwold31qUENt3lVTUZxDGqeXQFx38=";

_mockJSRuntime.Setup(x => x.InvokeAsync<string>("localStorage.getItem", new[] { _key }))
.Returns(() => new ValueTask<string>(existingValue));
_mockJSRuntime.Setup(x => x.InvokeVoidAsync("localStorage.setItem", new[] { _key, newValue }));

// Act
await _sut.SetItemAsync(_key, newValue);

// Assert
_mockJSRuntime.Verify();
}
}
}
23 changes: 0 additions & 23 deletions tests/Blazored.LocalStorage.Tests/Mocks/JSRuntimeWrapper.cs

This file was deleted.

Loading

0 comments on commit 5fda447

Please sign in to comment.