extends SceneTree const MainScene = preload("res://main.tscn") const FIXTURES := [ {"size": Vector2i(1280, 720), "locale": "zh_CN", "scale": 1.0}, {"size": Vector2i(844, 390), "locale": "en", "scale": 1.0}, {"size": Vector2i(390, 844), "locale": "zh_CN", "scale": 1.3}, ] const PAGES := ["boot", "title"] var failures: Array[String] = [] var fixture_count := 0 func _initialize() -> void: call_deferred("run_test") func run_test() -> void: for fixture in FIXTURES: for page in PAGES: await verify_fixture(fixture, page) if failures.is_empty(): print("ABG_P9_0_LAYOUT_OK fixtures=%d viewports=3 pages=2 locales=2 text_130=true min_touch=44" % fixture_count) quit(0) else: for failure in failures: push_error(failure) quit(1) func verify_fixture(fixture: Dictionary, page_name: String) -> void: var viewport := SubViewport.new() viewport.size = fixture.size viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS root.add_child(viewport) var game := MainScene.instantiate() game.auto_boot = false viewport.add_child(game) await settle() game.locale = fixture.locale game.text_scale = fixture.scale game.page = game.AppPage.BOOT if page_name == "boot" else game.AppPage.TITLE game.boot_verified = page_name == "title" game.render() await settle() fixture_count += 1 var label := "%sx%s/%s/%s/%s" % [fixture.size.x, fixture.size.y, fixture.locale, str(fixture.scale), page_name] var bounds := Rect2(Vector2.ZERO, Vector2(fixture.size)) var screen_name := "BootScreen" if page_name == "boot" else "TitleScreen" var screen := find_control(game, screen_name) expect(screen != null, "%s missing %s" % [label, screen_name]) if screen != null: expect(contains_rect(bounds, screen.get_global_rect()), "%s screen outside viewport: %s" % [label, screen.get_global_rect()]) if page_name == "boot": expect(find_control(game, "StartNewGame") == null, "%s exposes Start before verification" % label) else: expect(find_control(game, "NoRunStatus") != null, "%s missing no-run state" % label) expect(find_control(game, "StartNewGame") != null, "%s missing primary action" % label) verify_interactive_bounds(game, bounds, label) game.queue_free() viewport.queue_free() await drain_cleanup() func verify_interactive_bounds(node: Node, bounds: Rect2, label: String) -> void: if node is BaseButton and node.is_visible_in_tree(): var rect: Rect2 = node.get_global_rect() expect(contains_rect(bounds, rect), "%s button %s outside viewport: %s" % [label, node.name, rect]) expect(rect.size.x >= 44.0 and rect.size.y >= 44.0, "%s button %s below 44pt: %s" % [label, node.name, rect.size]) for child in node.get_children(): verify_interactive_bounds(child, bounds, label) func find_control(node: Node, node_name: String) -> Control: if node.name == node_name and node is Control and not node.is_queued_for_deletion(): return node for child in node.get_children(): var found := find_control(child, node_name) if found != null: return found return null func contains_rect(outer: Rect2, inner: Rect2) -> bool: return inner.position.x >= -0.5 and inner.position.y >= -0.5 and inner.end.x <= outer.end.x + 0.5 and inner.end.y <= outer.end.y + 0.5 func settle() -> void: for frame in range(6): await process_frame func drain_cleanup() -> void: for frame in range(30): await process_frame func expect(condition: bool, message: String) -> void: if not condition: failures.append("P9.0 layout: %s" % message)