Skip to content
Merged
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
6 changes: 3 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
<Copyright>Copyright © nekron</Copyright>
<Description>A pilot's logbook for Star Citizen: second-screen dashboard, in-game overlay, and flight-log analysis for Star Citizen, driven by Game.log.</Description>

<Version>0.8.18</Version>
<AssemblyVersion>0.8.18.0</AssemblyVersion>
<FileVersion>0.8.18.0</FileVersion>
<Version>0.8.19</Version>
<AssemblyVersion>0.8.19.0</AssemblyVersion>
<FileVersion>0.8.19.0</FileVersion>

<RepositoryUrl>https://github.com/peans99/QuantumWake</RepositoryUrl>
<PackageProjectUrl>https://github.com/peans99/QuantumWake</PackageProjectUrl>
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,12 @@ affiliated with or endorsed by Cloud Imperium Games.
Newest first. Each version's section is what the GitHub release says too — the
release workflow lifts it from here, so it is written once.

### 0.8.19

- **The update button says what the download costs.** Ninety megabytes is
worth knowing before agreeing to it rather than after, and on a metered
connection it is somebody’s actual money.

### 0.8.18

- **Updating is one click.** When a new version is out, Quantum Wake can
Expand Down
4 changes: 4 additions & 0 deletions src/Quantumwake.Server/ServerHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,10 @@ static void MustRevalidate(StaticFileResponseContext context) =>
result.Newer, result.Current, result.Latest, result.Url,
result.Notes, result.PublishedAt,
canInstall = selfUpdate.Possible && result.Asset is not null,

// Ninety megabytes is worth knowing before agreeing to it, not
// after: a metered connection is somebody's actual money.
downloadBytes = result.Asset?.Size,
});
});

Expand Down
75 changes: 75 additions & 0 deletions tests/Quantumwake.WebTests/UpdateBannerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
namespace Quantumwake.WebTests;

/// <summary>
/// The banner that announces a release, and what it offers.
/// </summary>
/// <remarks>
/// The button replaces the application the reader is running, so what it says
/// before being pressed matters: whether one click is even possible on this
/// build, and how much of somebody's connection it is about to spend.
/// </remarks>
public class UpdateBannerTests
{
private static Page Announcing(string json)
{
var page = new Page();
page.Serve("/api/updates/check", json);
page.Serve("/api/updates", "{\"asked\":true,\"automatic\":false}");
page.Do("await runUpdateCheck({ quiet: true });");
return page;
}

private const string Installable = """
{"newer":true,"current":"0.8.18","latest":"0.8.19",
"url":"https://example/release","canInstall":true,"downloadBytes":91743987}
""";

[Fact]
public void An_installable_release_offers_one_click_and_says_what_it_costs()
{
var actions = Announcing(Installable).NodeText("#update-actions");

Assert.Contains("Update to 0.8.19", actions);
Assert.Contains("87 MB", actions);
Assert.Contains("Read the notes", actions);
}

/// <summary>
/// A source build cannot replace itself, and a button that explains that
/// only after being pressed is worse than one that was never there.
/// </summary>
[Fact]
public void A_build_that_cannot_replace_itself_offers_the_release_page_instead()
{
var actions = Announcing("""
{"newer":true,"current":"0.8.18","latest":"0.8.19",
"url":"https://example/release","canInstall":false}
""").NodeText("#update-actions");

Assert.DoesNotContain("Update to", actions);
Assert.Contains("Open the release page", actions);
}

/// <summary>A release with no size reported still offers the button.</summary>
[Fact]
public void An_unknown_download_size_is_left_off_rather_than_guessed()
{
var actions = Announcing("""
{"newer":true,"current":"0.8.18","latest":"0.8.19",
"url":"https://example/release","canInstall":true}
""").NodeText("#update-actions");

Assert.Contains("Update to 0.8.19", actions);
Assert.DoesNotContain("MB", actions);
}

[Fact]
public void Being_current_says_so_and_offers_nothing()
{
var page = Announcing("""
{"newer":false,"current":"0.8.19","latest":"0.8.19","canInstall":true}
""");

Assert.True(page.Truth("__dom.node('#update').hidden"));
}
}
9 changes: 8 additions & 1 deletion web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -11038,7 +11038,14 @@ async function runUpdateCheck({ quiet }) {
* so rather than leaving the reader guessing.
*/
function updateNowButton(result) {
const install = el('button', 'ghost on', `Update to ${result.latest}`);
// The size is on the button rather than in the detail line, because it is
// part of what is being agreed to: on a metered connection ninety megabytes
// is somebody's actual money, and finding out afterwards is too late.
const size = result.downloadBytes
? ` · ${Math.round(result.downloadBytes / 1048576)} MB`
: '';

const install = el('button', 'ghost on', `Update to ${result.latest}${size}`);
install.title = 'Downloads it, checks it, and restarts into the new version';

install.addEventListener('click', async () => {
Expand Down
Loading