Update the game from inside it (#13)
"Version X is available" in the top bar is now a button that opens an Update popup: - Update and restart gets the download link from the server (the redirect is not followed, as the Gitea link needs no token), downloads the new game next to the running one with a progress bar, checks it (complete, and an MZ or ELF file), swaps it in and restarts. The quiz is saved first and continues. On Windows the running .exe is renamed to .old, put back if the swap fails, and deleted at the next start. - Download in browser opens the .exe, the Linux package, or the release page on other platforms. - From the editor, an unwritable folder or another platform, only the browser download is offered, with the reason. The message now fits between the tabs and the buttons in every theme; in Nord and Christmas it ran into Sync. The macOS export preset is removed, and the unused download_client_* functions are replaced by fetch_client_download_url. Closes #13 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c03c0e4efe
commit
f39865fffc
30 files changed
+2363
-354
No files matched your search
@@ -7,7 +7,7 @@ extends MainWindowTestSuite
|
||||
const MAIN_WINDOW_CHILDREN: Array[String] = [
|
||||
"Background", "TopBar", "Quiz", "PopupBackdrop", "SearchPopup", "SyncPopup", "QRPopup",
|
||||
"ShortcutsPopup", "StatisticsView", "AboutPopup", "LogPopup", "SoundtrackStatsPopup",
|
||||
"SettingsPopup",
|
||||
"SettingsPopup", "UpdatePopup",
|
||||
]
|
||||
|
||||
## The parts of the quiz and the scene each one comes from.
|
||||
@@ -28,7 +28,7 @@ const LOCKED_BY_SYNC: Array[String] = [
|
||||
]
|
||||
|
||||
const TOP_BAR_CHILDREN: Array[String] = [
|
||||
"TopPanelContainer", "Title", "DebugLabel", "UpdateLabel", "OfflineLabel", "Buttons",
|
||||
"TopPanelContainer", "Title", "DebugLabel", "UpdateBox", "OfflineLabel", "Buttons",
|
||||
"QuizTab", "StatisticsTab",
|
||||
]
|
||||
## The buttons in the top bar's row on the right, in order.
|
||||
|
||||
@@ -47,6 +47,7 @@ const STYLES: Dictionary = {
|
||||
"PlayerButtonPrimary": "Button",
|
||||
"PlayerTime": "Label",
|
||||
"PlayerIcon": "Button",
|
||||
"UpdateButton": "Button",
|
||||
"ProgressSlider": "HSlider",
|
||||
"VolumeBar": "HSlider",
|
||||
"StatisticsCard": "PanelContainer",
|
||||
|
||||
@@ -10,7 +10,7 @@ func before_test() -> void:
|
||||
|
||||
|
||||
func test_no_message_when_this_is_the_newest_version() -> void:
|
||||
assert_bool(_top_bar.update_label.visible).is_false()
|
||||
assert_bool(_top_bar.update_box.visible).is_false()
|
||||
assert_int(_server.count("/download")).is_equal(1)
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@ func test_newer_release_shows_a_message() -> void:
|
||||
|
||||
_top_bar.check_for_update()
|
||||
|
||||
assert_bool(_top_bar.update_label.is_visible_in_tree()).is_true()
|
||||
assert_str(_top_bar.update_label.text).is_equal("Version 99.0.0 is available")
|
||||
assert_str(_top_bar.update_label.tooltip_text).contains(Settings.version)
|
||||
assert_bool(_top_bar.update_button.is_visible_in_tree()).is_true()
|
||||
assert_str(_top_bar.update_button.text).is_equal("Version 99.0.0 is available")
|
||||
assert_str(_top_bar.update_button.tooltip_text).contains(Settings.version)
|
||||
|
||||
|
||||
func test_message_fits_between_the_tabs_and_the_buttons() -> void:
|
||||
@@ -29,8 +29,10 @@ func test_message_fits_between_the_tabs_and_the_buttons() -> void:
|
||||
_top_bar.check_for_update()
|
||||
await _runner.simulate_frames(1)
|
||||
|
||||
var label: Rect2 = _top_bar.update_label.get_global_rect()
|
||||
var text_width: float = _top_bar.update_label.get_minimum_size().x
|
||||
var label: Rect2 = _top_bar.update_box.get_global_rect()
|
||||
var button: Button = _top_bar.update_button
|
||||
var text_width: float = button.get_theme_font(&"font").get_string_size(button.text,
|
||||
HORIZONTAL_ALIGNMENT_LEFT, -1, button.get_theme_font_size(&"font_size")).x
|
||||
assert_float(text_width).is_less_equal(label.size.x)
|
||||
assert_float(label.position.x).is_greater(
|
||||
(_top_bar.get_node("%StatisticsTab") as Control).get_global_rect().end.x)
|
||||
@@ -44,7 +46,7 @@ func test_message_stays_on_the_statistics_tab() -> void:
|
||||
|
||||
_window.show_tab(&"statistics")
|
||||
|
||||
assert_bool(_top_bar.update_label.is_visible_in_tree()).is_true()
|
||||
assert_bool(_top_bar.update_button.is_visible_in_tree()).is_true()
|
||||
|
||||
|
||||
func test_server_change_checks_again() -> void:
|
||||
@@ -52,7 +54,7 @@ func test_server_change_checks_again() -> void:
|
||||
|
||||
_quiz.on_server_changed()
|
||||
|
||||
assert_bool(_top_bar.update_label.visible).is_true()
|
||||
assert_bool(_top_bar.update_box.visible).is_true()
|
||||
|
||||
|
||||
func test_failed_check_shows_nothing() -> void:
|
||||
@@ -60,7 +62,7 @@ func test_failed_check_shows_nothing() -> void:
|
||||
|
||||
_top_bar.check_for_update()
|
||||
|
||||
assert_bool(_top_bar.update_label.visible).is_false()
|
||||
assert_bool(_top_bar.update_box.visible).is_false()
|
||||
|
||||
|
||||
func test_version_comparison() -> void:
|
||||
@@ -72,3 +74,19 @@ func test_version_comparison() -> void:
|
||||
assert_bool(TopBar.is_newer_version("2.0", "2.0.0")).is_false()
|
||||
assert_bool(TopBar.is_newer_version("1.9.0", "2.0.0")).is_false()
|
||||
assert_bool(TopBar.is_newer_version("1.9.9", "1.10.0")).is_false()
|
||||
|
||||
|
||||
@warning_ignore("unused_parameter")
|
||||
func test_message_stays_clear_of_the_buttons_in_every_theme(theme_id: String,
|
||||
test_parameters: Array = [["classic"], ["original_web"], ["nord"], ["christmas"]]) -> void:
|
||||
Settings.apply_theme(theme_id)
|
||||
_server.respond_json("/download", "99.10.10")
|
||||
_top_bar.check_for_update()
|
||||
await _runner.simulate_frames(2)
|
||||
|
||||
var message: Rect2 = _top_bar.update_box.get_global_rect()
|
||||
assert_float(message.end.x).is_less(
|
||||
(_top_bar.get_node("%SyncButton") as Control).get_global_rect().position.x)
|
||||
assert_float(message.position.x).is_greater(
|
||||
(_top_bar.get_node("%StatisticsTab") as Control).get_global_rect().end.x)
|
||||
Settings.apply_theme("classic")
|
||||
@@ -0,0 +1,160 @@
|
||||
extends MainWindowTestSuite
|
||||
## Getting the newest version from "Version X is available" in the top bar: updating in
|
||||
## place and restarting, or downloading in the browser.
|
||||
|
||||
const LINK: String = "https://gitea.example/releases/download/99.0.0/MusicPlayer_99.0.0.exe"
|
||||
|
||||
var _top_bar: TopBar
|
||||
var _popup: UpdatePopup
|
||||
var _updater: FakeUpdater
|
||||
var _opened: Array[String] = []
|
||||
var _dir: String
|
||||
|
||||
|
||||
## Downloads nothing: hands over a prepared file, or fails, at once.
|
||||
class FakeUpdater:
|
||||
extends Updater
|
||||
|
||||
var downloaded: PackedByteArray = [0x4D, 0x5A, 0x90, 0x00]
|
||||
var fail_with: String = ""
|
||||
var urls: Array[String] = []
|
||||
var started: Array[String] = []
|
||||
var quits: int = 0
|
||||
|
||||
func _init() -> void:
|
||||
super()
|
||||
start_process = func(path: String, _arguments: PackedStringArray) -> int:
|
||||
started.append(path)
|
||||
return 1
|
||||
quit = func() -> void: quits += 1
|
||||
|
||||
func download(url: String, on_progress: Callable, on_done: Callable,
|
||||
on_failed: Callable) -> void:
|
||||
urls.append(url)
|
||||
if !fail_with.is_empty():
|
||||
on_failed.call(fail_with)
|
||||
return
|
||||
on_progress.call(downloaded.size() / 2, downloaded.size())
|
||||
var file: FileAccess = FileAccess.open(update_path(), FileAccess.WRITE)
|
||||
file.store_buffer(downloaded)
|
||||
file.close()
|
||||
on_done.call(update_path())
|
||||
|
||||
|
||||
func _before_window_starts() -> void:
|
||||
_server.respond_json("/download", "99.0.0")
|
||||
|
||||
|
||||
func before_test() -> void:
|
||||
# The quiz is saved before restarting, next to the settings
|
||||
OS.set_environment(Settings.CONFIG_DIR_ENV, create_temp_dir("update_%d" % randi()))
|
||||
await super()
|
||||
_top_bar = _window.get_node("TopBar")
|
||||
_popup = _window.get_node("UpdatePopup")
|
||||
_opened = []
|
||||
_popup.open_url = func(url: String) -> Error:
|
||||
_opened.append(url)
|
||||
return OK
|
||||
_dir = create_temp_dir("game_%d" % randi())
|
||||
_updater = FakeUpdater.new()
|
||||
_updater.in_editor = false
|
||||
_updater.os_name = "Windows"
|
||||
_updater.executable_path = _dir.path_join("MusicPlayer.exe")
|
||||
var file: FileAccess = FileAccess.open(_updater.executable_path, FileAccess.WRITE)
|
||||
file.store_string("old game")
|
||||
file.close()
|
||||
_popup.add_child(_updater)
|
||||
_popup.updater = _updater
|
||||
|
||||
|
||||
func _open_popup() -> void:
|
||||
_top_bar.update_button.pressed.emit()
|
||||
|
||||
|
||||
func test_clicking_the_message_opens_the_popup() -> void:
|
||||
_open_popup()
|
||||
|
||||
assert_bool(_popup.visible).is_true()
|
||||
assert_str(_popup.title_label.text).is_equal("Version 99.0.0 is available")
|
||||
assert_str(_popup.info_label.text).contains(Settings.version)
|
||||
assert_bool(_popup.update_now_button.visible).is_true()
|
||||
|
||||
|
||||
func test_update_downloads_swaps_and_restarts() -> void:
|
||||
_server.respond_redirect("/download/windows", LINK)
|
||||
_open_popup()
|
||||
|
||||
_popup.update_now()
|
||||
|
||||
assert_array(_updater.urls).contains_exactly([LINK])
|
||||
var exe: String = _updater.executable_path
|
||||
assert_array(FileAccess.get_file_as_bytes(exe)).is_equal(_updater.downloaded)
|
||||
assert_str(FileAccess.get_file_as_string(exe + Updater.OLD_SUFFIX)).is_equal("old game")
|
||||
assert_array(_updater.started).contains_exactly([exe])
|
||||
assert_int(_updater.quits).is_equal(1)
|
||||
# The quiz continues after the restart
|
||||
assert_bool(FileAccess.file_exists(QuizSave.path())).is_true()
|
||||
|
||||
|
||||
func test_failed_download_says_why_and_keeps_the_game() -> void:
|
||||
_server.respond_redirect("/download/windows", LINK)
|
||||
_updater.fail_with = "The download failed. Check the connection and try again."
|
||||
_open_popup()
|
||||
|
||||
_popup.update_now()
|
||||
|
||||
assert_str(_popup.status_label.text).is_equal(_updater.fail_with)
|
||||
assert_bool(_popup.update_now_button.disabled).is_false()
|
||||
assert_bool(_popup.browser_button.disabled).is_false()
|
||||
assert_str(FileAccess.get_file_as_string(_updater.executable_path)).is_equal("old game")
|
||||
assert_int(_updater.quits).is_equal(0)
|
||||
|
||||
|
||||
func test_no_download_on_the_server_says_so() -> void:
|
||||
_open_popup()
|
||||
|
||||
_popup.update_now()
|
||||
|
||||
assert_array(_updater.urls).is_empty()
|
||||
assert_str(_popup.status_label.text).contains("no download")
|
||||
assert_int(_updater.quits).is_equal(0)
|
||||
|
||||
|
||||
func test_browser_gets_the_package_on_linux() -> void:
|
||||
var package: String = LINK.replace(".exe", "_linux.tar.gz")
|
||||
_server.respond_redirect("/download/linux-package", package)
|
||||
_updater.os_name = "Linux"
|
||||
_open_popup()
|
||||
|
||||
_popup.download_in_browser()
|
||||
|
||||
assert_array(_opened).contains_exactly([package])
|
||||
|
||||
|
||||
func test_browser_gets_the_release_page_on_other_platforms() -> void:
|
||||
_updater.os_name = "macOS"
|
||||
_open_popup()
|
||||
|
||||
_popup.download_in_browser()
|
||||
|
||||
assert_array(_opened).contains_exactly([Updater.RELEASES_PAGE])
|
||||
assert_int(_server.count("/download/windows")).is_equal(0)
|
||||
|
||||
|
||||
func test_browser_gets_the_release_page_when_the_server_has_no_link() -> void:
|
||||
_open_popup()
|
||||
|
||||
_popup.download_in_browser()
|
||||
|
||||
assert_array(_opened).contains_exactly([Updater.RELEASES_PAGE])
|
||||
|
||||
|
||||
func test_from_the_editor_only_the_browser_is_offered() -> void:
|
||||
_updater.in_editor = true
|
||||
|
||||
_open_popup()
|
||||
|
||||
assert_bool(_popup.update_now_button.visible).is_false()
|
||||
assert_bool(_popup.reason_label.visible).is_true()
|
||||
assert_str(_popup.reason_label.text).contains("editor")
|
||||
assert_bool(_popup.browser_button.visible).is_true()
|
||||
@@ -0,0 +1 @@
|
||||
uid://d4lsj4jjqht4d
|
||||
Reference in new issue
Block a user