Prevent dotnet-watch script injection staleness (#27778)
The browser refresh mechansim used by dotnet-watch and VS modifies HTML content. The modified content includes a script block that has a WebSocket url that changes for each new execution of dotnet watch run (not rebuilds, but watch itself). HTML content can come from views or static html files on disk. For the latter, ASP.NET Core participates in browser caching by sending (and invalidating) etag headers. One way to fix this problem is remove or modify the etag headers. The risk here is that might cause differences in behavior in development users may come to rely on that are unavailable in production. This change instead modifies the HTML content so the output is always consistent and consequently safe to cache. The dynamic content is served separately by the injected middleware. This change fixes the issue of multiple instances of dotnet-watch. While this issue may crop up if you alternate between dotnet run and dotnet watch run but we haven't seen this being an issue as yet. Fixes #27548 Summary Running dotnet watch run multiple times in Blazor WASM apps (or any app that serves static html files) can produce console errors and prevent the browser refresh feature from working. Given that we've been telling our users to use dotnet watch run as their primary way to work outside of VS, it's likely more users would run in this. Customer impact A hard browser refresh (Ctrl + R) is needed to get the refresh behavior to work. Regression No. This has existed since the feature was introduced we did not get reports of it Risk Low. The fix is isolated to dotnet-watch and VS's browser refresh mechanism which is in preview. The change was tested locally, but if there's a regression or if the change interferes with user's workflow, users have the ability to disable this feature.
This commit is contained in:
parent
fc1ebaaaa6
commit
b34c5ddd5f
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Watch.BrowserRefresh
|
||||
{
|
||||
/// <summary>
|
||||
/// Responds with the contennts of WebSocketScriptInjection.js with the stub WebSocket url replaced by the
|
||||
/// one specified by the launching app.
|
||||
/// </summary>
|
||||
public sealed class BrowserScriptMiddleware
|
||||
{
|
||||
private readonly byte[] _scriptBytes;
|
||||
private readonly string _contentLength;
|
||||
|
||||
public BrowserScriptMiddleware(RequestDelegate next)
|
||||
: this(Environment.GetEnvironmentVariable("ASPNETCORE_AUTO_RELOAD_WS_ENDPOINT")!)
|
||||
{
|
||||
}
|
||||
|
||||
internal BrowserScriptMiddleware(string webSocketUrl)
|
||||
{
|
||||
_scriptBytes = GetWebSocketClientJavaScript(webSocketUrl);
|
||||
_contentLength = _scriptBytes.Length.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
context.Response.Headers["Cache-Control"] = "no-store";
|
||||
context.Response.Headers["Content-Length"] = _contentLength;
|
||||
context.Response.Headers["Content-Type"] = "application/javascript; charset=utf-8";
|
||||
|
||||
await context.Response.Body.WriteAsync(_scriptBytes.AsMemory(), context.RequestAborted);
|
||||
}
|
||||
|
||||
internal static byte[] GetWebSocketClientJavaScript(string hostString)
|
||||
{
|
||||
var jsFileName = "Microsoft.AspNetCore.Watch.BrowserRefresh.WebSocketScriptInjection.js";
|
||||
using var reader = new StreamReader(typeof(WebSocketScriptInjection).Assembly.GetManifestResourceStream(jsFileName)!);
|
||||
var script = reader.ReadToEnd().Replace("{{hostString}}", hostString);
|
||||
|
||||
return Encoding.UTF8.GetBytes(script);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
@ -22,6 +23,7 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh
|
|||
{
|
||||
return app =>
|
||||
{
|
||||
app.Map(WebSocketScriptInjection.WebSocketScriptUrl, app1 => app1.UseMiddleware<BrowserScriptMiddleware>());
|
||||
app.UseMiddleware<BrowserRefreshMiddleware>();
|
||||
next(app);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh
|
|||
OnWrite();
|
||||
if (IsHtmlResponse && !ScriptInjectionPerformed)
|
||||
{
|
||||
ScriptInjectionPerformed = WebSocketScriptInjection.Instance.TryInjectLiveReloadScript(_baseStream, buffer);
|
||||
ScriptInjectionPerformed = WebSocketScriptInjection.TryInjectLiveReloadScript(_baseStream, buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh
|
|||
|
||||
if (IsHtmlResponse && !ScriptInjectionPerformed)
|
||||
{
|
||||
ScriptInjectionPerformed = WebSocketScriptInjection.Instance.TryInjectLiveReloadScript(_baseStream, buffer.AsSpan(offset, count));
|
||||
ScriptInjectionPerformed = WebSocketScriptInjection.TryInjectLiveReloadScript(_baseStream, buffer.AsSpan(offset, count));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh
|
|||
|
||||
if (IsHtmlResponse && !ScriptInjectionPerformed)
|
||||
{
|
||||
ScriptInjectionPerformed = await WebSocketScriptInjection.Instance.TryInjectLiveReloadScriptAsync(_baseStream, buffer.AsMemory(offset, count), cancellationToken);
|
||||
ScriptInjectionPerformed = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(_baseStream, buffer.AsMemory(offset, count), cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -106,7 +106,7 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh
|
|||
|
||||
if (IsHtmlResponse && !ScriptInjectionPerformed)
|
||||
{
|
||||
ScriptInjectionPerformed = await WebSocketScriptInjection.Instance.TryInjectLiveReloadScriptAsync(_baseStream, buffer, cancellationToken);
|
||||
ScriptInjectionPerformed = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(_baseStream, buffer, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,22 +13,18 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh
|
|||
/// Helper class that handles the HTML injection into
|
||||
/// a string or byte array.
|
||||
/// </summary>
|
||||
public class WebSocketScriptInjection
|
||||
public static class WebSocketScriptInjection
|
||||
{
|
||||
private const string BodyMarker = "</body>";
|
||||
internal const string WebSocketScriptUrl = "/_framework/aspnetcore-browser-refresh.js";
|
||||
|
||||
private readonly byte[] _bodyBytes = Encoding.UTF8.GetBytes(BodyMarker);
|
||||
private readonly byte[] _scriptInjectionBytes;
|
||||
private static readonly byte[] _bodyBytes = Encoding.UTF8.GetBytes(BodyMarker);
|
||||
|
||||
public static WebSocketScriptInjection Instance { get; } = new WebSocketScriptInjection(
|
||||
GetWebSocketClientJavaScript(Environment.GetEnvironmentVariable("ASPNETCORE_AUTO_RELOAD_WS_ENDPOINT")));
|
||||
internal static string InjectedScript { get; } = $"<script src=\"{WebSocketScriptUrl}\"></script>";
|
||||
|
||||
public WebSocketScriptInjection(string clientScript)
|
||||
{
|
||||
_scriptInjectionBytes = Encoding.UTF8.GetBytes(clientScript);
|
||||
}
|
||||
private static readonly byte[] _injectedScriptBytes = Encoding.UTF8.GetBytes(InjectedScript);
|
||||
|
||||
public bool TryInjectLiveReloadScript(Stream baseStream, ReadOnlySpan<byte> buffer)
|
||||
public static bool TryInjectLiveReloadScript(Stream baseStream, ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
var index = buffer.LastIndexOf(_bodyBytes);
|
||||
if (index == -1)
|
||||
|
|
@ -44,14 +40,14 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh
|
|||
}
|
||||
|
||||
// Write the injected script
|
||||
baseStream.Write(_scriptInjectionBytes);
|
||||
baseStream.Write(_injectedScriptBytes);
|
||||
|
||||
// Write the rest of the buffer/HTML doc
|
||||
baseStream.Write(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async ValueTask<bool> TryInjectLiveReloadScriptAsync(Stream baseStream, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
public static async ValueTask<bool> TryInjectLiveReloadScriptAsync(Stream baseStream, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var index = buffer.Span.LastIndexOf(_bodyBytes);
|
||||
if (index == -1)
|
||||
|
|
@ -67,20 +63,12 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh
|
|||
}
|
||||
|
||||
// Write the injected script
|
||||
await baseStream.WriteAsync(_scriptInjectionBytes, cancellationToken);
|
||||
await baseStream.WriteAsync(_injectedScriptBytes, cancellationToken);
|
||||
|
||||
// Write the rest of the buffer/HTML doc
|
||||
await baseStream.WriteAsync(buffer, cancellationToken);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static string GetWebSocketClientJavaScript(string? hostString)
|
||||
{
|
||||
var jsFileName = "Microsoft.AspNetCore.Watch.BrowserRefresh.WebSocketScriptInjection.js";
|
||||
using var reader = new StreamReader(typeof(WebSocketScriptInjection).Assembly.GetManifestResourceStream(jsFileName)!);
|
||||
var script = reader.ReadToEnd().Replace("{{hostString}}", hostString);
|
||||
|
||||
return $"<script>{script}</script>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Watch.BrowserRefresh
|
||||
{
|
||||
public class BrowserScriptMiddlewareTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task InvokeAsync_ReturnsScript()
|
||||
{
|
||||
// Arrange
|
||||
var context = new DefaultHttpContext();
|
||||
var stream = new MemoryStream();
|
||||
context.Response.Body = stream;
|
||||
var middleware = new BrowserScriptMiddleware("some-host");
|
||||
|
||||
// Act
|
||||
await middleware.InvokeAsync(context);
|
||||
|
||||
// Assert
|
||||
stream.Position = 0;
|
||||
var script = Encoding.UTF8.GetString(stream.ToArray());
|
||||
Assert.Contains("// dotnet-watch browser reload script", script);
|
||||
Assert.Contains("'some-host'", script);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAsync_ConfiguresHeaders()
|
||||
{
|
||||
// Arrange
|
||||
var context = new DefaultHttpContext();
|
||||
context.Response.Body = new MemoryStream();
|
||||
var middleware = new BrowserScriptMiddleware("some-host");
|
||||
|
||||
// Act
|
||||
await middleware.InvokeAsync(context);
|
||||
|
||||
// Assert
|
||||
var response = context.Response;
|
||||
Assert.Collection(
|
||||
response.Headers.OrderBy(h => h.Key),
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("Cache-Control", kvp.Key);
|
||||
Assert.Equal("no-store", kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("Content-Length", kvp.Key);
|
||||
Assert.NotEmpty(kvp.Value);
|
||||
},
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal("Content-Type", kvp.Key);
|
||||
Assert.Equal("application/javascript; charset=utf-8", kvp.Value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
We'll simply test against it as source.
|
||||
-->
|
||||
<Compile Include="..\src\BrowserRefreshMiddleware.cs" LinkBase="src" />
|
||||
<Compile Include="..\src\BrowserScriptMiddleware.cs" LinkBase="src" />
|
||||
<Compile Include="..\src\ResponseStreamWrapper.cs" LinkBase="src" />
|
||||
<Compile Include="..\src\WebSocketScriptInjection.cs" LinkBase="src" />
|
||||
<EmbeddedResource Include="..\src\WebSocketScriptInjection.js" />
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh.Tests
|
|||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains("dotnet-watch browser reload script", content);
|
||||
Assert.Contains(WebSocketScriptInjection.InjectedScript, content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh.Tests
|
|||
|
||||
Assert.False(response.Content.Headers.TryGetValues("Content-Length", out _), "We shouldn't send a Content-Length header.");
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains("dotnet-watch browser reload script", content);
|
||||
Assert.Contains(WebSocketScriptInjection.InjectedScript, content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh.Tests
|
|||
|
||||
Assert.False(response.Content.Headers.TryGetValues("Content-Length", out _), "We shouldn't send a Content-Length header.");
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains("dotnet-watch browser reload script", content);
|
||||
Assert.Contains(WebSocketScriptInjection.InjectedScript, content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -98,7 +98,7 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh.Tests
|
|||
|
||||
Assert.False(response.Content.Headers.TryGetValues("Content-Length", out _), "We shouldn't send a Content-Length header.");
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains("dotnet-watch browser reload script", content);
|
||||
Assert.Contains(WebSocketScriptInjection.InjectedScript, content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh.Tests
|
|||
|
||||
Assert.False(response.Content.Headers.TryGetValues("Content-Length", out _), "We shouldn't send a Content-Length header.");
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains("dotnet-watch browser reload script", content);
|
||||
Assert.Contains(WebSocketScriptInjection.InjectedScript, content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -164,7 +164,7 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh.Tests
|
|||
|
||||
Assert.False(response.Content.Headers.TryGetValues("Content-Length", out _), "We shouldn't send a Content-Length header.");
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
Assert.Contains("dotnet-watch browser reload script", content);
|
||||
Assert.Contains(WebSocketScriptInjection.InjectedScript, content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -11,9 +11,6 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh
|
|||
{
|
||||
public class WebSockerScriptInjectionTest
|
||||
{
|
||||
private const string ClientScript = "<script><!--My cool script--></script>";
|
||||
private readonly WebSocketScriptInjection ScriptInjection = new WebSocketScriptInjection(ClientScript);
|
||||
|
||||
[Fact]
|
||||
public async Task TryInjectLiveReloadScriptAsync_DoesNotInjectMarkup_IfInputDoesNotContainBodyTag()
|
||||
{
|
||||
|
|
@ -22,7 +19,7 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh
|
|||
var input = Encoding.UTF8.GetBytes("<div>this is not a real body tag.</div>");
|
||||
|
||||
// Act
|
||||
var result = await ScriptInjection.TryInjectLiveReloadScriptAsync(stream, input);
|
||||
var result = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(stream, input);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
|
|
@ -37,7 +34,7 @@ namespace Microsoft.AspNetCore.Watch.BrowserRefresh
|
|||
$@"<footer>
|
||||
This is the footer
|
||||
</footer>
|
||||
{ClientScript}</body>
|
||||
{WebSocketScriptInjection.InjectedScript}</body>
|
||||
</html>";
|
||||
var stream = new MemoryStream();
|
||||
var input = Encoding.UTF8.GetBytes(
|
||||
|
|
@ -48,7 +45,7 @@ $@"<footer>
|
|||
</html>");
|
||||
|
||||
// Act
|
||||
var result = await ScriptInjection.TryInjectLiveReloadScriptAsync(stream, input);
|
||||
var result = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(stream, input);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
|
|
@ -60,12 +57,12 @@ $@"<footer>
|
|||
public async Task TryInjectLiveReloadScriptAsync_WithOffsetBodyTagAppearsInMiddle()
|
||||
{
|
||||
// Arrange
|
||||
var expected = $"</table>{ClientScript}</body>";
|
||||
var expected = $"</table>{WebSocketScriptInjection.InjectedScript}</body>";
|
||||
var stream = new MemoryStream();
|
||||
var input = Encoding.UTF8.GetBytes("unused</table></body>");
|
||||
|
||||
// Act
|
||||
var result = await ScriptInjection.TryInjectLiveReloadScriptAsync(stream, input.AsMemory(6));
|
||||
var result = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(stream, input.AsMemory(6));
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
|
|
@ -77,12 +74,12 @@ $@"<footer>
|
|||
public async Task TryInjectLiveReloadScriptAsync_WithOffsetBodyTagAppearsAtStartOfOffset()
|
||||
{
|
||||
// Arrange
|
||||
var expected = $"{ClientScript}</body>";
|
||||
var expected = $"{WebSocketScriptInjection.InjectedScript}</body>";
|
||||
var stream = new MemoryStream();
|
||||
var input = Encoding.UTF8.GetBytes("unused</body>");
|
||||
|
||||
// Act
|
||||
var result = await ScriptInjection.TryInjectLiveReloadScriptAsync(stream, input.AsMemory(6));
|
||||
var result = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(stream, input.AsMemory(6));
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
|
|
@ -94,12 +91,12 @@ $@"<footer>
|
|||
public async Task TryInjectLiveReloadScriptAsync_InjectsMarkupIfBodyTagAppearsAtTheStartOfOutput()
|
||||
{
|
||||
// Arrange
|
||||
var expected = $"{ClientScript}</body></html>";
|
||||
var expected = $"{WebSocketScriptInjection.InjectedScript}</body></html>";
|
||||
var stream = new MemoryStream();
|
||||
var input = Encoding.UTF8.GetBytes("</body></html>");
|
||||
|
||||
// Act
|
||||
var result = await ScriptInjection.TryInjectLiveReloadScriptAsync(stream, input);
|
||||
var result = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(stream, input);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
|
|
@ -111,12 +108,12 @@ $@"<footer>
|
|||
public async Task TryInjectLiveReloadScriptAsync_InjectsMarkupIfBodyTagAppearsByItself()
|
||||
{
|
||||
// Arrange
|
||||
var expected = $"{ClientScript}</body>";
|
||||
var expected = $"{WebSocketScriptInjection.InjectedScript}</body>";
|
||||
var stream = new MemoryStream();
|
||||
var input = Encoding.UTF8.GetBytes("</body>");
|
||||
|
||||
// Act
|
||||
var result = await ScriptInjection.TryInjectLiveReloadScriptAsync(stream, input);
|
||||
var result = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(stream, input);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
|
|
@ -128,12 +125,12 @@ $@"<footer>
|
|||
public async Task TryInjectLiveReloadScriptAsync_MultipleBodyTags()
|
||||
{
|
||||
// Arrange
|
||||
var expected = $"<p></body>some text</p>{ClientScript}</body>";
|
||||
var expected = $"<p></body>some text</p>{WebSocketScriptInjection.InjectedScript}</body>";
|
||||
var stream = new MemoryStream();
|
||||
var input = Encoding.UTF8.GetBytes("abc<p></body>some text</p></body>").AsMemory(3);
|
||||
|
||||
// Act
|
||||
var result = await ScriptInjection.TryInjectLiveReloadScriptAsync(stream, input);
|
||||
var result = await WebSocketScriptInjection.TryInjectLiveReloadScriptAsync(stream, input);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
|
|
@ -150,7 +147,7 @@ $@"<footer>
|
|||
var input = Encoding.UTF8.GetBytes(expected).AsSpan();
|
||||
|
||||
// Act
|
||||
var result = ScriptInjection.TryInjectLiveReloadScript(stream, input);
|
||||
var result = WebSocketScriptInjection.TryInjectLiveReloadScript(stream, input);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
|
|
@ -162,12 +159,12 @@ $@"<footer>
|
|||
public void TryInjectLiveReloadScript_NoOffset()
|
||||
{
|
||||
// Arrange
|
||||
var expected = $"</table>{ClientScript}</body>";
|
||||
var expected = $"</table>{WebSocketScriptInjection.InjectedScript}</body>";
|
||||
var stream = new MemoryStream();
|
||||
var input = Encoding.UTF8.GetBytes("</table></body>").AsSpan();
|
||||
|
||||
// Act
|
||||
var result = ScriptInjection.TryInjectLiveReloadScript(stream, input);
|
||||
var result = WebSocketScriptInjection.TryInjectLiveReloadScript(stream, input);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
|
|
@ -179,28 +176,17 @@ $@"<footer>
|
|||
public void TryInjectLiveReloadScript_WithOffset()
|
||||
{
|
||||
// Arrange
|
||||
var expected = $"</table>{ClientScript}</body>";
|
||||
var expected = $"</table>{WebSocketScriptInjection.InjectedScript}</body>";
|
||||
var stream = new MemoryStream();
|
||||
var input = Encoding.UTF8.GetBytes("unused</table></body>").AsSpan(6);
|
||||
|
||||
// Act
|
||||
var result = ScriptInjection.TryInjectLiveReloadScript(stream, input);
|
||||
var result = WebSocketScriptInjection.TryInjectLiveReloadScript(stream, input);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
var output = Encoding.UTF8.GetString(stream.ToArray());
|
||||
Assert.Equal(expected, output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetWebSocketClientJavaScript_Works()
|
||||
{
|
||||
// Act
|
||||
var script = WebSocketScriptInjection.GetWebSocketClientJavaScript("some-host");
|
||||
|
||||
// Assert
|
||||
Assert.Contains("// dotnet-watch browser reload script", script);
|
||||
Assert.Contains("'some-host'", script);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue