Compare commits
22
Commits
1.8.5
...
10e9e7b2a4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10e9e7b2a4 | ||
|
|
29b11560b5 | ||
|
|
2a332c2d19 | ||
|
|
6cbbfee6a1 | ||
|
|
c7d49bc5cc | ||
|
|
6288739de0 | ||
|
|
d79f5f92c5 | ||
|
|
14c166614d | ||
|
|
428decf73a | ||
|
|
739d0f7af6 | ||
|
|
ed292877ef | ||
|
|
5a979fca48 | ||
|
|
1acc757981 | ||
|
|
16724922ed | ||
|
|
c1fc24adf9 | ||
|
|
f67dccbf52 | ||
|
|
c985a6ce5b | ||
|
|
56e9fe66d3 | ||
|
|
ab338b0696 | ||
|
|
9a91d969bd | ||
|
|
8da6fc442f | ||
|
|
235bdcbcb5 |
No files matched your search
@@ -0,0 +1,38 @@
|
||||
name: "Setup Godot"
|
||||
description: "Downloads Godot for Linux, puts it on PATH as `godot` and optionally installs the export templates"
|
||||
|
||||
inputs:
|
||||
version:
|
||||
description: "Godot version, e.g. 4.7.2"
|
||||
required: true
|
||||
export-templates:
|
||||
description: "Also install the export templates ('true' or 'false')"
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Download Godot
|
||||
shell: bash
|
||||
run: |
|
||||
URL="https://github.com/godotengine/godot-builds/releases/download/${{ inputs.version }}-stable"
|
||||
mkdir -p "$HOME/.godot-bin"
|
||||
curl --fail -L -o godot.zip "$URL/Godot_v${{ inputs.version }}-stable_linux.x86_64.zip"
|
||||
unzip -q godot.zip
|
||||
mv "Godot_v${{ inputs.version }}-stable_linux.x86_64" "$HOME/.godot-bin/godot"
|
||||
chmod +x "$HOME/.godot-bin/godot"
|
||||
rm godot.zip
|
||||
echo "$HOME/.godot-bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Install Export Templates
|
||||
if: inputs.export-templates == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
URL="https://github.com/godotengine/godot-builds/releases/download/${{ inputs.version }}-stable"
|
||||
TEMPLATES_DIR="$HOME/.local/share/godot/export_templates/${{ inputs.version }}.stable"
|
||||
curl --fail -L -o export_templates.tpz "$URL/Godot_v${{ inputs.version }}-stable_export_templates.tpz"
|
||||
unzip -q export_templates.tpz
|
||||
mkdir -p "$TEMPLATES_DIR"
|
||||
mv templates/* "$TEMPLATES_DIR"
|
||||
rm -r templates export_templates.tpz
|
||||
@@ -1,59 +1,113 @@
|
||||
#
|
||||
# .gitea/workflows/gitea-build.yaml
|
||||
#
|
||||
# Tests and exports MusicPlayer on every push and pull request to main and develop,
|
||||
# and releases it from main.
|
||||
#
|
||||
# Releases follow the version in project.godot (application/config/version). Bump it,
|
||||
# push to main, and once the tests and exports pass the builds are uploaded to the
|
||||
# package registry and the tag and Gitea release are created. If that version already
|
||||
# has a tag, nothing is released. See RELEASING.md.
|
||||
#
|
||||
# All jobs run in this one workflow, one after another: separate workflows started by
|
||||
# the same push run at the same time on the single runner and can break each other's
|
||||
# copy of a shared action.
|
||||
#
|
||||
|
||||
name: "Build"
|
||||
on:
|
||||
push:
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
|
||||
env:
|
||||
GODOT_VERSION: 4.6
|
||||
PROJECT_PATH: .
|
||||
EXPORT_NAME: MusicPlayer
|
||||
GODOT_VERSION: 4.7.2
|
||||
PROJECT_NAME: MusicPlayer
|
||||
|
||||
jobs:
|
||||
build:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Download Godot
|
||||
|
||||
- name: Setup Godot
|
||||
uses: ./.gitea/actions/setup-godot
|
||||
with:
|
||||
version: ${{ env.GODOT_VERSION }}
|
||||
|
||||
- name: Run Tests
|
||||
run: ./run_tests.sh
|
||||
|
||||
build:
|
||||
needs: test
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Read Version
|
||||
id: version
|
||||
run: |
|
||||
#echo "https://github.com/godotengine/godot-builds/releases/download/$GODOT_VERSION-stable/Godot_v$GODOT_VERSION-stable_linux.x86_64.zip"
|
||||
curl -L -o godot.zip https://github.com/godotengine/godot-builds/releases/download/$GODOT_VERSION-stable/Godot_v$GODOT_VERSION-stable_linux.x86_64.zip
|
||||
unzip godot.zip
|
||||
mv Godot_v$GODOT_VERSION-stable_linux.x86_64 godot
|
||||
chmod +x godot
|
||||
GAME_VERSION="$(sed -n 's/^config\/version="\(.*\)"$/\1/p' project.godot)"
|
||||
if [ -z "$GAME_VERSION" ]; then
|
||||
echo "No application/config/version in project.godot"
|
||||
exit 1
|
||||
fi
|
||||
echo "GAME_VERSION=$GAME_VERSION" >> "$GITHUB_ENV"
|
||||
if [ "$GITHUB_REF" != "refs/heads/main" ]; then
|
||||
echo "Not on main, building without releasing"
|
||||
echo "release=false" >> "$GITHUB_OUTPUT"
|
||||
elif git rev-parse -q --verify "refs/tags/$GAME_VERSION" > /dev/null; then
|
||||
echo "$GAME_VERSION is already released, building without releasing"
|
||||
echo "release=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Releasing $GAME_VERSION"
|
||||
echo "release=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Download Godot Export Templates
|
||||
- name: Setup Godot
|
||||
uses: ./.gitea/actions/setup-godot
|
||||
with:
|
||||
version: ${{ env.GODOT_VERSION }}
|
||||
export-templates: "true"
|
||||
|
||||
- name: Export
|
||||
run: |
|
||||
#echo "https://github.com/godotengine/godot-builds/releases/download/$GODOT_VERSION-stable/Godot_v$GODOT_VERSION-stable_export_templates.tpz"
|
||||
curl -L -o export_templates.zip https://github.com/godotengine/godot-builds/releases/download/$GODOT_VERSION-stable/Godot_v$GODOT_VERSION-stable_export_templates.tpz
|
||||
unzip export_templates.zip
|
||||
mkdir -p build
|
||||
godot --headless --path . --import
|
||||
godot --headless --path . --export-release "Windows Desktop" "build/${PROJECT_NAME}_${GAME_VERSION}.exe"
|
||||
godot --headless --path . --export-release "Linux" "build/${PROJECT_NAME}_${GAME_VERSION}.x86_64"
|
||||
|
||||
- name: Install Export Templates
|
||||
# Linux binaries can't hold an icon; the package adds it with a menu entry and installer
|
||||
- name: Package Linux
|
||||
run: |
|
||||
mkdir -p ~/.local/share/godot/export_templates/$GODOT_VERSION.stable
|
||||
mv ./templates/* ~/.local/share/godot/export_templates/$GODOT_VERSION.stable
|
||||
linux/package.sh "build/${PROJECT_NAME}_${GAME_VERSION}.x86_64" "$GAME_VERSION" build
|
||||
ls -la build
|
||||
|
||||
- name: Create build folder
|
||||
- name: Upload Artifacts
|
||||
if: steps.version.outputs.release == 'true'
|
||||
run: |
|
||||
mkdir -v -p build
|
||||
|
||||
- name: Windows Build
|
||||
run: |
|
||||
cd $PROJECT_PATH
|
||||
./godot --headless --verbose --export-release "Windows Desktop" "build/$EXPORT_NAME.exe"
|
||||
|
||||
- name: Linux Build
|
||||
run: |
|
||||
cd $PROJECT_PATH
|
||||
./godot --headless --verbose --export-release "Linux" "build/$EXPORT_NAME.x86_64"
|
||||
|
||||
# - name: MacOS Build
|
||||
# run: |
|
||||
# cd $PROJECT_PATH
|
||||
# ./godot --headless --verbose --export-release "macOS" "build/$EXPORT_NAME.zip"
|
||||
for FILE in build/${PROJECT_NAME}_${GAME_VERSION}*; do
|
||||
NAME="$(basename "$FILE")"
|
||||
echo "Uploading $NAME"
|
||||
curl --fail --user ${{ github.repository_owner }}:${{ secrets.TOKEN }} \
|
||||
--upload-file "$FILE" \
|
||||
https://gitea.sanplex.xyz/api/packages/sansan/generic/$PROJECT_NAME/$GAME_VERSION/$NAME
|
||||
done
|
||||
|
||||
- name: Create Release
|
||||
if: steps.version.outputs.release == 'true'
|
||||
uses: akkuman/gitea-release-action@v1
|
||||
env:
|
||||
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
|
||||
with:
|
||||
tag_name: ${{ env.GAME_VERSION }}
|
||||
target_commitish: ${{ github.sha }}
|
||||
files: |-
|
||||
build/${{ env.PROJECT_NAME }}_*
|
||||
@@ -1,90 +0,0 @@
|
||||
#
|
||||
# .gitea/workflows/gitea-release.yaml
|
||||
#
|
||||
#
|
||||
|
||||
name: "Publish"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*.*'
|
||||
|
||||
env:
|
||||
GODOT_VERSION: 4.6
|
||||
PROJECT_PATH: .
|
||||
PROJECT_NAME: MusicPlayer
|
||||
GAME_VERSION: ${{gitea.ref_name}}
|
||||
EXPORT_NAME: MusicPlayer_${{gitea.ref_name}}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Download Godot
|
||||
run: |
|
||||
#echo "https://github.com/godotengine/godot-builds/releases/download/$GODOT_VERSION-stable/Godot_v$GODOT_VERSION-stable_linux.x86_64.zip"
|
||||
curl -L -o godot.zip https://github.com/godotengine/godot-builds/releases/download/$GODOT_VERSION-stable/Godot_v$GODOT_VERSION-stable_linux.x86_64.zip
|
||||
unzip godot.zip
|
||||
mv Godot_v$GODOT_VERSION-stable_linux.x86_64 godot
|
||||
chmod +x godot
|
||||
|
||||
- name: Download Godot Export Templates
|
||||
run: |
|
||||
#echo "https://github.com/godotengine/godot-builds/releases/download/$GODOT_VERSION-stable/Godot_v$GODOT_VERSION-stable_export_templates.tpz"
|
||||
curl -L -o export_templates.zip https://github.com/godotengine/godot-builds/releases/download/$GODOT_VERSION-stable/Godot_v$GODOT_VERSION-stable_export_templates.tpz
|
||||
unzip export_templates.zip
|
||||
|
||||
- name: Install Export Templates
|
||||
run: |
|
||||
mkdir -p ~/.local/share/godot/export_templates/$GODOT_VERSION.stable
|
||||
mv ./templates/* ~/.local/share/godot/export_templates/$GODOT_VERSION.stable
|
||||
|
||||
- name: Create build folder
|
||||
run: |
|
||||
mkdir -v -p build
|
||||
|
||||
- name: Windows Build
|
||||
run: |
|
||||
cd $PROJECT_PATH
|
||||
./godot --headless --verbose --export-release "Windows Desktop" "build/$EXPORT_NAME.exe"
|
||||
|
||||
- name: Linux Build
|
||||
run: |
|
||||
cd $PROJECT_PATH
|
||||
./godot --headless --verbose --export-release "Linux" "build/$EXPORT_NAME.x86_64"
|
||||
|
||||
# - name: MacOS Build
|
||||
# run: |
|
||||
# cd $PROJECT_PATH
|
||||
# ./godot --headless --verbose --export-release "macOS" "build/$EXPORT_NAME.zip"
|
||||
|
||||
- name: Upload Artifact Windows
|
||||
run: |
|
||||
#ls -la build
|
||||
curl --user ${{ github.repository_owner }}:${{ secrets.TOKEN }} \
|
||||
--upload-file "build/$EXPORT_NAME.exe" \
|
||||
https://gitea.sanplex.xyz/api/packages/sansan/generic/$PROJECT_NAME/$GAME_VERSION/$EXPORT_NAME.exe
|
||||
|
||||
- name: Upload Artifact Linux
|
||||
run: |
|
||||
ls -la build
|
||||
curl --user ${{ github.repository_owner }}:${{ secrets.TOKEN }} \
|
||||
--upload-file "build/$EXPORT_NAME.x86_64" \
|
||||
https://gitea.sanplex.xyz/api/packages/sansan/generic/$PROJECT_NAME/$GAME_VERSION/$EXPORT_NAME.x86_64
|
||||
|
||||
# - name: Upload Artifact MacOS
|
||||
# run: |
|
||||
# ls -la build
|
||||
# curl --user ${{ github.repository_owner }}:${{ secrets.TOKEN }} \
|
||||
# --upload-file "build/$EXPORT_NAME.zip" \
|
||||
# https://gitea.sanplex.xyz/api/packages/sansan/generic/$PROJECT_NAME/$GAME_VERSION/$EXPORT_NAME.zip
|
||||
|
||||
- name: Create release
|
||||
uses: akkuman/gitea-release-action@v1
|
||||
env:
|
||||
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
|
||||
with:
|
||||
files: |-
|
||||
build/**
|
||||
@@ -6,3 +6,6 @@
|
||||
.idea
|
||||
.vscode
|
||||
.DS_Store
|
||||
|
||||
# GdUnit4 test reports
|
||||
reports/
|
||||
+9
-15
@@ -1,4 +1,4 @@
|
||||
extends Control
|
||||
extends PopupWindow
|
||||
|
||||
@onready
|
||||
var about_panel: Panel = %AboutPanel
|
||||
@@ -13,6 +13,12 @@ var whats_new_label: Label = %WhatsNewLabel
|
||||
|
||||
# Changelog as Dictionary with version as key and description as value
|
||||
const WHATS_NEW: Dictionary = {
|
||||
"1.9.0": "Made improvements to the Search window
|
||||
Made small changes to the inspiration list scrolling
|
||||
Made small changes to the player list
|
||||
Changed the term game to soundtrack
|
||||
#28: Made changes to the sync window
|
||||
#34: Fixed bug in Character Select when clicking or scrolling",
|
||||
"1.8.5": "#9: New winner screen with positions of all players
|
||||
#32: New setting for how long to hide the beginning
|
||||
#33: Fixed bug with hiding beginning when song restarts",
|
||||
@@ -45,16 +51,8 @@ const WHATS_NEW: Dictionary = {
|
||||
}
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if self.visible == true:
|
||||
if event is InputEventMouseButton && event.is_pressed():
|
||||
var evLocal: InputEvent = make_input_local(event)
|
||||
if !Rect2(Vector2(0, 0), about_panel.size).has_point(evLocal.position):
|
||||
self.visible = false
|
||||
|
||||
|
||||
func show_window() -> void:
|
||||
visible = true
|
||||
func open() -> void:
|
||||
super()
|
||||
version_label.text = Settings.version
|
||||
|
||||
# Build changelog text with version headers
|
||||
@@ -64,7 +62,3 @@ func show_window() -> void:
|
||||
changelog_text += WHATS_NEW[version] + "\n"
|
||||
|
||||
whats_new_label.text = changelog_text
|
||||
|
||||
|
||||
func hide_window() -> void:
|
||||
visible = false
|
||||
+7
-9
@@ -1,8 +1,10 @@
|
||||
[gd_scene format=3 uid="uid://cp54idaudb8dp"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b7bh0of7olbcb" path="res://AboutWindow.gd" id="1_about_script"]
|
||||
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
|
||||
|
||||
[node name="AboutControl" type="Control" unique_id=1626010355]
|
||||
[node name="AboutControl" type="Control" unique_id=1626010355 node_paths=PackedStringArray("panel")]
|
||||
theme = ExtResource("popup_theme")
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
@@ -10,6 +12,7 @@ anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_about_script")
|
||||
panel = NodePath("AboutPanel")
|
||||
|
||||
[node name="AboutPanel" type="Panel" parent="." unique_id=1907299275]
|
||||
unique_name_in_owner = true
|
||||
@@ -18,7 +21,6 @@ offset_right = 600.0
|
||||
offset_bottom = 640.0
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="AboutPanel" unique_id=200295721]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_right = 600.0
|
||||
offset_bottom = 50.0
|
||||
@@ -28,10 +30,11 @@ vertical_alignment = 1
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="AboutPanel" unique_id=417677913]
|
||||
layout_mode = 0
|
||||
offset_left = 10.0
|
||||
offset_left = 20.0
|
||||
offset_top = 50.0
|
||||
offset_right = 590.0
|
||||
offset_right = 580.0
|
||||
offset_bottom = 630.0
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="ContentVBox" type="VBoxContainer" parent="AboutPanel/ScrollContainer" unique_id=1746337912]
|
||||
layout_mode = 2
|
||||
@@ -40,14 +43,11 @@ size_flags_vertical = 3
|
||||
|
||||
[node name="VersionLabel" type="Label" parent="AboutPanel/ScrollContainer/ContentVBox" unique_id=352884126]
|
||||
unique_name_in_owner = true
|
||||
texture_filter = 1
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="DescriptionLabel" type="Label" parent="AboutPanel/ScrollContainer/ContentVBox" unique_id=543094263]
|
||||
texture_filter = 1
|
||||
custom_minimum_size = Vector2(560, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Try your video game music knowledge with this VGM randomizer, invite your friends and see who is the best."
|
||||
@@ -56,8 +56,6 @@ autowrap_mode = 2
|
||||
|
||||
[node name="WhatsNewLabel" type="Label" parent="AboutPanel/ScrollContainer/ContentVBox" unique_id=1363040291]
|
||||
unique_name_in_owner = true
|
||||
texture_filter = 1
|
||||
custom_minimum_size = Vector2(560, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
autowrap_mode = 2
|
||||
@@ -1,40 +0,0 @@
|
||||
extends Control
|
||||
|
||||
@onready
|
||||
var character_grid_container := $ScrollContainer2
|
||||
|
||||
@onready
|
||||
var character_grid := $ScrollContainer2/GridContainer
|
||||
|
||||
signal character_selected(file_name)
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
var characters := DirAccess.open("res://characters/")
|
||||
if characters:
|
||||
characters.list_dir_begin()
|
||||
var file_name = characters.get_next()
|
||||
while file_name != "":
|
||||
file_name = file_name.replace('.import', '') # <--- remove the .import
|
||||
if file_name.ends_with(".png"):
|
||||
|
||||
var texture = load("res://characters/" + file_name)
|
||||
|
||||
var texture_btn := TextureButton.new()
|
||||
|
||||
character_grid.add_child(texture_btn)
|
||||
texture_btn.custom_minimum_size = Vector2(80, 40)
|
||||
texture_btn.ignore_texture_size = true
|
||||
texture_btn.stretch_mode = TextureButton.STRETCH_KEEP_ASPECT
|
||||
texture_btn.texture_normal = texture
|
||||
|
||||
texture_btn.pressed.connect(select_character.bind(file_name))
|
||||
|
||||
file_name = characters.get_next()
|
||||
|
||||
func show_grid():
|
||||
character_grid_container.visible = true
|
||||
|
||||
func select_character(file_name: String):
|
||||
print("select_character")
|
||||
character_selected.emit(file_name)
|
||||
@@ -1 +0,0 @@
|
||||
uid://cmxl2w45vvmx6
|
||||
@@ -1,25 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b16on0oscg1bv"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cmxl2w45vvmx6" path="res://CharacterSelect.gd" id="1_4bba6"]
|
||||
|
||||
[node name="CharacterSelect" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
script = ExtResource("1_4bba6")
|
||||
|
||||
[node name="ScrollContainer2" type="ScrollContainer" parent="."]
|
||||
clip_contents = false
|
||||
custom_minimum_size = Vector2(100, 100)
|
||||
layout_mode = 0
|
||||
offset_left = 194.0
|
||||
offset_top = 152.0
|
||||
offset_right = 670.0
|
||||
offset_bottom = 375.0
|
||||
|
||||
[node name="GridContainer" type="GridContainer" parent="ScrollContainer2"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
columns = 3
|
||||
@@ -1,34 +1,38 @@
|
||||
extends ScrollContainer
|
||||
|
||||
var SCROLL: float = 0
|
||||
var delay: float = 0.02 #seconds
|
||||
var wait: float = 0
|
||||
# Scroll direction: 1 = down, -1 = up
|
||||
var scroll_direction: int = 1
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
wait = delay
|
||||
scroll_direction = 1
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta: float) -> void:
|
||||
wait -= delta
|
||||
if wait < 0:
|
||||
wait = delay
|
||||
#SCROLL DOWN
|
||||
if SCROLL == 1:
|
||||
if (scroll_vertical + get_v_scroll_bar().page) < get_v_scroll_bar().max_value:
|
||||
scroll_vertical += Settings.inspiration_list_speed
|
||||
else:
|
||||
scroll_back_up()
|
||||
#SCROLL UP
|
||||
elif SCROLL == -1:
|
||||
if scroll_vertical != 0:
|
||||
scroll_vertical -= Settings.inspiration_list_speed
|
||||
else:
|
||||
scroll_to_bottom()
|
||||
var speed_pixels_per_second: float = float(Settings.inspiration_list_speed) * 60.0
|
||||
var scroll_amount: float = speed_pixels_per_second * delta * float(scroll_direction)
|
||||
|
||||
var scroll_bar = get_v_scroll_bar()
|
||||
|
||||
# Don't scroll if list is empty or too short
|
||||
if scroll_bar.max_value <= 0:
|
||||
return
|
||||
|
||||
func scroll_back_up() -> void:
|
||||
SCROLL = -1
|
||||
var current_pos = scroll_bar.value
|
||||
|
||||
func scroll_to_bottom() -> void:
|
||||
scroll_vertical = 0 #Reset to top first.
|
||||
SCROLL = 1
|
||||
if scroll_direction == 1:
|
||||
# Scrolling down
|
||||
if current_pos + scroll_bar.page < scroll_bar.max_value:
|
||||
scroll_bar.value = min(current_pos + scroll_amount, scroll_bar.max_value - scroll_bar.page)
|
||||
else:
|
||||
# Reached bottom, reverse direction
|
||||
scroll_direction = -1
|
||||
scroll_bar.value = scroll_bar.max_value - scroll_bar.page
|
||||
else:
|
||||
# Scrolling up
|
||||
if current_pos > 0:
|
||||
scroll_bar.value = max(current_pos + scroll_amount, 0)
|
||||
else:
|
||||
# Reached top, reverse direction
|
||||
scroll_direction = 1
|
||||
scroll_bar.value = 0
|
||||
@@ -1,8 +1,10 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bijh5h5yrivm3"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cw41y87l64qo7" path="res://log.gd" id="1_q72ua"]
|
||||
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
|
||||
|
||||
[node name="Control" type="Control"]
|
||||
[node name="Control" type="Control" node_paths=PackedStringArray("panel")]
|
||||
theme = ExtResource("popup_theme")
|
||||
custom_minimum_size = Vector2(700, 700)
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
@@ -11,23 +13,35 @@ anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_q72ua")
|
||||
panel = NodePath("LogPanel")
|
||||
|
||||
[node name="LogPanel" type="Panel" parent="."]
|
||||
unique_name_in_owner = true
|
||||
visibility_layer = 513
|
||||
custom_minimum_size = Vector2(700, 700)
|
||||
layout_mode = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
offset_right = 700.0
|
||||
offset_bottom = 700.0
|
||||
|
||||
[node name="TitleLabel" type="Label" parent="LogPanel"]
|
||||
layout_mode = 0
|
||||
offset_top = 8.0
|
||||
offset_right = 700.0
|
||||
offset_bottom = 48.0
|
||||
theme_override_font_sizes/font_size = 25
|
||||
text = "Log"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="LogScrollContainer" type="ScrollContainer" parent="LogPanel"]
|
||||
custom_minimum_size = Vector2(700, 700)
|
||||
layout_mode = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
offset_left = 20.0
|
||||
offset_top = 56.0
|
||||
offset_right = 680.0
|
||||
offset_bottom = 684.0
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="LogVBoxContainer" type="VBoxContainer" parent="LogPanel/LogScrollContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(700, 700)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 1
|
||||
+141
-107
@@ -23,7 +23,7 @@ var inspiration_scroll: ScrollContainer = $ScrollContainer
|
||||
var inspiration_list: VBoxContainer = $ScrollContainer/VBoxContainer
|
||||
|
||||
@onready
|
||||
var game_label: Label = $VBoxContainer/GameLabel
|
||||
var soundtrack_label: Label = $VBoxContainer/SoundtrackLabel
|
||||
|
||||
@onready
|
||||
var song_label: Label = $VBoxContainer/SongLabel
|
||||
@@ -107,7 +107,7 @@ var music_list: VBoxContainer = $MusicListPanel/ScrollContainer/MusicList
|
||||
var player_container: VBoxContainer = $Players/VBoxContainer
|
||||
|
||||
@onready
|
||||
var character_select: Control = $CharacterSelect
|
||||
var players_list_container: VBoxContainer = $Players/VBoxContainer/ScrollContainer/PlayersListVBoxContainer
|
||||
|
||||
@onready
|
||||
var new_character_select: Control = %NewCharacterSelect
|
||||
@@ -134,11 +134,21 @@ var player: PackedScene = preload("res://Player.tscn")
|
||||
var song_list_object: PackedScene = preload("res://song_list_object.tscn")
|
||||
|
||||
var songs: Array= []
|
||||
var games: Array = []
|
||||
var soundtracks: Array = []
|
||||
var current_player: Node
|
||||
|
||||
## True while statistics are off because a player won, until points are reset.
|
||||
var stats_paused_by_win: bool = false
|
||||
|
||||
## Dims the window behind an open popup and keeps clicks from reaching the buttons under it.
|
||||
var popup_backdrop: ColorRect
|
||||
|
||||
## Seconds to wait before retrying a song download step that failed.
|
||||
var song_fetch_retry_seconds: float = 5.0
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
_setup_popup_backdrop()
|
||||
var debug: bool = OS.has_feature("debug")
|
||||
var is_mac: bool = OS.has_feature("macos")
|
||||
if debug:
|
||||
@@ -152,7 +162,7 @@ func _ready() -> void:
|
||||
next_button.pressed.connect(play_next_song)
|
||||
show_answer_button.pressed.connect(show_answer_pressed)
|
||||
search_button.pressed.connect(show_search)
|
||||
sync_button.pressed.connect(sync_games)
|
||||
sync_button.pressed.connect(sync_soundtracks)
|
||||
sound_test_button.pressed.connect(music_player_container.get_sound_test_song)
|
||||
statistics_button.pressed.connect(get_statistics)
|
||||
shortcuts_button.pressed.connect(show_shortcuts)
|
||||
@@ -162,12 +172,12 @@ func _ready() -> void:
|
||||
reset_playlist_button.pressed.connect(reset_playlist)
|
||||
reset_points_button.pressed.connect(reset_points)
|
||||
|
||||
character_select.connect("character_selected", _on_character_selected)
|
||||
new_character_select.connect("new_new_character_selected", _on_new_character_selected)
|
||||
new_player_name_field.connect("enter_key_pressed", add_player)
|
||||
music_player_container.connect("play_next_song", play_next_song)
|
||||
music_player_container.connect("update_song_list", update_song_list)
|
||||
settings_window.connect("server_changed", server_updated)
|
||||
settings_window.visibility_changed.connect(_on_settings_window_visibility_changed)
|
||||
sync_window.connect("sync_started", _on_sync_started)
|
||||
sync_window.connect("sync_finished", _on_sync_finished)
|
||||
|
||||
@@ -179,8 +189,7 @@ func _ready() -> void:
|
||||
|
||||
get_suggestion_list()
|
||||
fetch_song_list_at_start()
|
||||
fetch_song()
|
||||
search_view.get_list_of_games()
|
||||
search_view.get_list_of_soundtracks()
|
||||
new_character_select.load_characters()
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
@@ -209,9 +218,9 @@ func _input(event: InputEvent) -> void:
|
||||
if Settings.fullscreen == false:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
|
||||
if event.alt_pressed && event.keycode == KEY_K:
|
||||
shortcuts_window.visible = !shortcuts_window.visible
|
||||
shortcuts_window.toggle()
|
||||
if event.alt_pressed && event.keycode == KEY_L:
|
||||
log_window.visible = !log_window.visible
|
||||
log_window.toggle()
|
||||
|
||||
func server_updated() -> void:
|
||||
print("server_updated")
|
||||
@@ -220,7 +229,7 @@ func server_updated() -> void:
|
||||
|
||||
func update_from_server() -> void:
|
||||
print("update_from_server")
|
||||
search_view.get_list_of_games()
|
||||
search_view.get_list_of_soundtracks()
|
||||
get_suggestion_list()
|
||||
reset_playlist()
|
||||
new_character_select.load_characters()
|
||||
@@ -233,28 +242,29 @@ func show_answer_pressed() -> void:
|
||||
Playlist.song_is_answered()
|
||||
update_song_list()
|
||||
|
||||
# Loads the songs already in the server playlist, then starts preloading new ones.
|
||||
# Preloading waits for the list, because loading the list clears the playlist.
|
||||
func fetch_song_list_at_start() -> void:
|
||||
print("fetch_song_list_at_start")
|
||||
var fetch_song_list_at_start_done: Callable = func fetch_songs_at_start_done(data) -> void:
|
||||
Playlist.clear_song_list()
|
||||
if data == null:
|
||||
Playlist.update_last_played_song()
|
||||
update_song_list()
|
||||
return
|
||||
if typeof(data) == TYPE_ARRAY:
|
||||
for d: Dictionary in data:
|
||||
var new_song_object: SongObject = SongObject.new()
|
||||
new_song_object.song_number = d.SongNo
|
||||
new_song_object.game_title = d.Game
|
||||
new_song_object.soundtrack_title = d.Soundtrack
|
||||
new_song_object.song_title = d.Song
|
||||
new_song_object.is_answered = true
|
||||
new_song_object.has_played = true
|
||||
Playlist.add_to_playlist(new_song_object)
|
||||
else:
|
||||
elif data != null:
|
||||
print("Unexpected data")
|
||||
Playlist.update_last_played_song()
|
||||
update_song_list()
|
||||
Settings.make_request2("/music/list", fetch_song_list_at_start_done, true)
|
||||
if !Playlist.is_song_downloading:
|
||||
fetch_song()
|
||||
MusicServerClient.fetch_song_list(fetch_song_list_at_start_done,
|
||||
_retry_later.bind(fetch_song_list_at_start))
|
||||
|
||||
func play_next_song() -> void:
|
||||
print("play_next_song ", Playlist.latest_played_song)
|
||||
@@ -271,48 +281,81 @@ func play_next_song() -> void:
|
||||
#"/music/rand"
|
||||
#"/music/addQue"
|
||||
#"/music/info"
|
||||
func _popups() -> Array[PopupWindow]:
|
||||
return [search_view, sync_window, settings_window, qr_window, new_character_select,
|
||||
shortcuts_window, about_window, winner_window, log_window]
|
||||
|
||||
# The backdrop sits above the main window and below every popup. Popups close
|
||||
# themselves when clicked outside, and the backdrop keeps that click from reaching
|
||||
# the buttons behind them.
|
||||
func _setup_popup_backdrop() -> void:
|
||||
popup_backdrop = ColorRect.new()
|
||||
popup_backdrop.name = "PopupBackdrop"
|
||||
popup_backdrop.color = Color(0, 0, 0, 0.55)
|
||||
popup_backdrop.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
add_child(popup_backdrop)
|
||||
popup_backdrop.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
for popup: PopupWindow in _popups():
|
||||
move_child(popup, -1)
|
||||
popup.visibility_changed.connect(_update_popup_backdrop)
|
||||
_update_popup_backdrop()
|
||||
|
||||
func _update_popup_backdrop() -> void:
|
||||
popup_backdrop.visible = _popups().any(func(popup: PopupWindow) -> bool: return popup.visible)
|
||||
|
||||
# The cache size can be changed in the settings. Waiting until the settings are closed
|
||||
# avoids starting downloads while the user is still clicking the number up and down.
|
||||
func _on_settings_window_visibility_changed() -> void:
|
||||
if !settings_window.visible && !Playlist.is_song_downloading && Playlist.should_preload_more_songs():
|
||||
fetch_song()
|
||||
|
||||
func fetch_song() -> void:
|
||||
print("fetch_song")
|
||||
Playlist.is_song_downloading = true
|
||||
var url: String = ""
|
||||
if Settings.use_low_played_mode:
|
||||
url = "/music/rand/low"
|
||||
else:
|
||||
url = "/music/rand"
|
||||
var new_song_object: SongObject = SongObject.new()
|
||||
var fetch_song_done: Callable = func fetch_song_done(body: PackedByteArray) -> void:
|
||||
print("fetch_song_done")
|
||||
_download_random_song(SongObject.new())
|
||||
|
||||
# Fetching a song takes three requests. If one fails, only that step is retried, so
|
||||
# the server and the game keep the same songs in their playlists.
|
||||
func _download_random_song(new_song_object: SongObject) -> void:
|
||||
var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void:
|
||||
print("song_downloaded")
|
||||
var sound: AudioStream = AudioStreamMP3.new()
|
||||
sound.data = body
|
||||
new_song_object.song = sound
|
||||
|
||||
var added_to_que_done: Callable = func added_to_que_done() -> void:
|
||||
print("added_to_que_done")
|
||||
var fetch_song_info_done: Callable = func fetch_song_info_done(data_received: Dictionary) -> void:
|
||||
print("fetch_song_info_done")
|
||||
new_song_object.song_number = data_received.SongNo
|
||||
new_song_object.game_title = data_received.Game
|
||||
new_song_object.song_title = data_received.Song
|
||||
|
||||
print("new_song_object: ", new_song_object.to_string())
|
||||
Playlist.add_to_playlist(new_song_object)
|
||||
next_button.disabled = false
|
||||
if Playlist.should_preload_more_songs():
|
||||
fetch_song()
|
||||
else:
|
||||
Playlist.is_song_downloading = false
|
||||
if Settings.is_debug:
|
||||
update_song_list()
|
||||
Settings.make_request2("/music/info", fetch_song_info_done, true)
|
||||
_add_song_to_queue(new_song_object)
|
||||
MusicServerClient.fetch_random_song(Settings.use_low_played_mode, song_downloaded,
|
||||
_retry_later.bind(_download_random_song.bind(new_song_object)))
|
||||
|
||||
Settings.make_request2("/music/addQue", added_to_que_done, false)
|
||||
func _add_song_to_queue(new_song_object: SongObject) -> void:
|
||||
MusicServerClient.add_to_queue(_fetch_song_info.bind(new_song_object),
|
||||
_retry_later.bind(_add_song_to_queue.bind(new_song_object)))
|
||||
|
||||
func _fetch_song_info(new_song_object: SongObject) -> void:
|
||||
var song_info_fetched: Callable = func song_info_fetched(data_received: Dictionary) -> void:
|
||||
print("song_info_fetched")
|
||||
new_song_object.song_number = data_received.SongNo
|
||||
new_song_object.soundtrack_title = data_received.Soundtrack
|
||||
new_song_object.song_title = data_received.Song
|
||||
print("new_song_object: ", new_song_object.to_string())
|
||||
Playlist.add_to_playlist(new_song_object)
|
||||
next_button.disabled = false
|
||||
if Playlist.should_preload_more_songs():
|
||||
fetch_song()
|
||||
else:
|
||||
Playlist.is_song_downloading = false
|
||||
if Settings.is_debug:
|
||||
update_song_list()
|
||||
MusicServerClient.fetch_song_info(song_info_fetched,
|
||||
_retry_later.bind(_fetch_song_info.bind(new_song_object)))
|
||||
|
||||
func _retry_later(step: Callable) -> void:
|
||||
print("Song download failed, retrying in %s seconds" % song_fetch_retry_seconds)
|
||||
get_tree().create_timer(song_fetch_retry_seconds).timeout.connect(step)
|
||||
|
||||
Settings.make_request2(url, fetch_song_done, true)
|
||||
|
||||
func update_song_list() -> void:
|
||||
print("update_song_list")
|
||||
if Playlist.currently_playing_song >= 0:
|
||||
game_label.text = Playlist.get_current_game_title()
|
||||
soundtrack_label.text = Playlist.get_current_soundtrack_title()
|
||||
song_label.text = Playlist.get_current_song_title()
|
||||
Settings.delete_children(music_list)
|
||||
for s: SongObject in Playlist.get_playlist():
|
||||
@@ -332,7 +375,7 @@ func song_object_clicked(event: InputEvent, song_no: int) -> void:
|
||||
sound.data = body
|
||||
Playlist.add_song_to_song_object(song_no, sound)
|
||||
music_player_container.play_song_object(song_no)
|
||||
Settings.make_request2("/music?song=" + str(song_no), song_downloaded, true)
|
||||
MusicServerClient.fetch_song(song_no, song_downloaded)
|
||||
else:
|
||||
print("song already downloaded")
|
||||
music_player_container.play_song_object(song_no)
|
||||
@@ -342,39 +385,42 @@ func reset_playlist() -> void:
|
||||
var playlist_reset_done: Callable = func playlist_reset_done() -> void:
|
||||
Playlist.reset_playlist()
|
||||
fetch_song_list_at_start()
|
||||
fetch_song()
|
||||
|
||||
Settings.make_request2("/music/reset", playlist_reset_done, false)
|
||||
MusicServerClient.reset_playlist(playlist_reset_done)
|
||||
|
||||
func reset_points() -> void:
|
||||
print("reset_points")
|
||||
for player_objects: PlayerObject in Settings.player_array:
|
||||
player_objects.reset_points()
|
||||
load_players()
|
||||
# A new game starts, so turn statistics back on if winning turned them off
|
||||
if stats_paused_by_win:
|
||||
Settings.add_to_stats = true
|
||||
stats_paused_by_win = false
|
||||
|
||||
func show_about() -> void:
|
||||
print("show_about")
|
||||
about_window.show_window()
|
||||
about_window.open()
|
||||
|
||||
func show_shortcuts() -> void:
|
||||
print("show_shortcuts")
|
||||
shortcuts_window.visible = true
|
||||
shortcuts_window.open()
|
||||
|
||||
func show_settings() -> void:
|
||||
print("show_settings")
|
||||
settings_window.visible = true
|
||||
settings_window.open()
|
||||
|
||||
func show_qr() -> void:
|
||||
print("show_qr")
|
||||
qr_window.show_window()
|
||||
qr_window.open()
|
||||
|
||||
func get_statistics() -> void:
|
||||
print("get_statistics")
|
||||
statistic_popup.visible = true
|
||||
statistic_label.text = "Total amount of games in the playlist: " + str(games.size())
|
||||
statistic_label.text = "Total amount of soundtracks in the playlist: " + str(soundtracks.size())
|
||||
|
||||
func sync_games() -> void:
|
||||
print("sync_games")
|
||||
func sync_soundtracks() -> void:
|
||||
print("sync_soundtracks")
|
||||
sync_window.start_sync()
|
||||
|
||||
func _on_sync_started() -> void:
|
||||
@@ -397,20 +443,14 @@ func get_suggestion_list() -> void:
|
||||
print("get_suggestion_list")
|
||||
var populate_list: Callable = func populate_list(array) -> void:
|
||||
if typeof(array) == TYPE_ARRAY:
|
||||
games = []
|
||||
soundtracks = []
|
||||
Settings.delete_children(inspiration_list)
|
||||
games.append_array(array)
|
||||
for game: String in games:
|
||||
var inspiration_label: Label = Label.new()
|
||||
inspiration_label.set_texture_filter(TextureFilter.TEXTURE_FILTER_NEAREST)
|
||||
inspiration_label.add_theme_font_size_override("font_size", 20)
|
||||
inspiration_label.text = game
|
||||
inspiration_label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
inspiration_list.add_child(inspiration_label)
|
||||
inspiration_scroll.scroll_to_bottom()
|
||||
soundtracks.append_array(array)
|
||||
for i: int in soundtracks.size():
|
||||
inspiration_list.add_child(ListRow.create(soundtracks[i], i, 20))
|
||||
else:
|
||||
print("Unexpected data")
|
||||
Settings.make_request2("/music/all", populate_list, true)
|
||||
MusicServerClient.fetch_all_soundtracks(populate_list)
|
||||
|
||||
func add_players() -> void:
|
||||
print("add_players")
|
||||
@@ -421,62 +461,66 @@ func add_players() -> void:
|
||||
func add_player() -> void:
|
||||
print("add_player")
|
||||
if new_player_name_field.text != "" && Settings.player_array.size() < Settings.MAX_NUMBER_OF_PLAYERS:
|
||||
var new_player_object: PlayerObject = PlayerObject.new(new_player_name_field.text, Settings.player_array.size())
|
||||
var new_player_object: PlayerObject = PlayerObject.new(new_player_name_field.text)
|
||||
new_player_name_field.text = ""
|
||||
Settings.player_array.append(new_player_object)
|
||||
new_player_object.connect("first_point_triggerd", music_player_container._on_point_triggered.bind("first"))
|
||||
new_player_object.connect("match_point_triggerd", music_player_container._on_point_triggered.bind("match"))
|
||||
new_player_object.connect("winner_triggerd", _on_player_won.bind(new_player_object.id))
|
||||
new_player_object.connect("winner_triggerd", _on_player_won.bind(new_player_object))
|
||||
new_player_object.connect("point_given_sound", _on_make_point_given_sound)
|
||||
new_player_object.connect("player_point_given", _on_point_given.bind(new_player_object.id))
|
||||
new_player_object.connect("player_point_taken", _on_point_taken.bind(new_player_object.id))
|
||||
new_player_object.connect("player_point_given", _on_point_given.bind(new_player_object))
|
||||
new_player_object.connect("player_point_taken", _on_point_taken.bind(new_player_object))
|
||||
load_players()
|
||||
|
||||
func load_players() -> void:
|
||||
print("load_players")
|
||||
Settings.delete_player_children(player_container)
|
||||
Settings.delete_children(players_list_container)
|
||||
var counter: int = 0
|
||||
for player_object: PlayerObject in Settings.player_array:
|
||||
print(player_object.player_name)
|
||||
|
||||
var new_player: Player = player.instantiate()
|
||||
new_player.id = counter
|
||||
|
||||
new_player.connect("change_character_clicked", _on_player_change_character_clicked.bind(new_player, player_object.player_name))
|
||||
new_player.connect("player_removed", _on_player_removed.bind(counter))
|
||||
new_player.player_object = player_object
|
||||
new_player.list_index = counter
|
||||
|
||||
player_container.add_child(new_player)
|
||||
new_player.connect("change_character_clicked", _on_player_change_character_clicked.bind(new_player, player_object.player_name))
|
||||
new_player.connect("player_removed", _on_player_removed.bind(player_object))
|
||||
|
||||
players_list_container.add_child(new_player)
|
||||
counter += 1
|
||||
|
||||
func _on_point_given(player_given_point: int) -> void:
|
||||
func _on_point_given(player_object: PlayerObject) -> void:
|
||||
print("_on_point_given")
|
||||
log_window.add_log_row(Settings.player_array[player_given_point].player_name + " got a point")
|
||||
log_window.add_log_row(player_object.player_name + " got a point")
|
||||
if Playlist.currently_playing_song >= 0:
|
||||
Playlist.add_point(player_given_point)
|
||||
Playlist.add_point(player_object.id)
|
||||
update_song_list()
|
||||
|
||||
func _on_make_point_given_sound() -> void:
|
||||
music_player_container.play_sound_effect(preload("res://sounds/itemopen.wav"))
|
||||
|
||||
func _on_point_taken(player_taken_point: int) -> void:
|
||||
func _on_point_taken(player_object: PlayerObject) -> void:
|
||||
print("_on_point_taken")
|
||||
log_window.add_log_row(Settings.player_array[player_taken_point].player_name + " lost a point")
|
||||
log_window.add_log_row(player_object.player_name + " lost a point")
|
||||
music_player_container.play_sound_effect(preload("res://sounds/itemequip.wav"))
|
||||
if Playlist.currently_playing_song >= 0:
|
||||
Playlist.remove_point(player_taken_point)
|
||||
Playlist.remove_point(player_object.id)
|
||||
update_song_list()
|
||||
|
||||
func _on_player_won(winning_player_id: int) -> void:
|
||||
func _on_player_won(player_object: PlayerObject) -> void:
|
||||
print("_on_player_won")
|
||||
winner_window.show_winner(winning_player_id)
|
||||
winner_window.show_winner(player_object)
|
||||
music_player_container.play_song(preload("res://sounds/winning.mp3"))
|
||||
Settings.add_to_stats = false
|
||||
# Songs played after the game is won don't count towards the statistics
|
||||
if Settings.add_to_stats:
|
||||
Settings.add_to_stats = false
|
||||
stats_paused_by_win = true
|
||||
|
||||
func _on_player_removed(player_to_remove: int) -> void:
|
||||
print("_on_player_removed ", player_to_remove)
|
||||
Settings.player_array.remove_at(player_to_remove)
|
||||
func _on_player_removed(player_object: PlayerObject) -> void:
|
||||
print("_on_player_removed ", player_object.player_name)
|
||||
Settings.player_array.erase(player_object)
|
||||
load_players()
|
||||
Playlist.remove_player_from_list(player_to_remove)
|
||||
Playlist.remove_player_from_list(player_object.id)
|
||||
update_song_list()
|
||||
|
||||
func _on_player_change_character_clicked(new_player: Player, player_name: String) -> void:
|
||||
@@ -485,21 +529,13 @@ func _on_player_change_character_clicked(new_player: Player, player_name: String
|
||||
print("Change character for: " + player_name)
|
||||
new_character_select.show_grid(player_name)
|
||||
|
||||
func _on_character_selected(file_name: String) -> void:
|
||||
print("_on_character_selected")
|
||||
character_select.visible = false
|
||||
current_player.character_selected(file_name)
|
||||
|
||||
func _on_new_character_selected(character_texture: ImageTexture) -> void:
|
||||
print("_on_new_character_selected")
|
||||
current_player.new_character_selected(character_texture)
|
||||
|
||||
func show_search() -> void:
|
||||
print("show_search")
|
||||
if search_view.visible == false:
|
||||
search_view.visible = true
|
||||
else:
|
||||
search_view.visible = false
|
||||
search_view.toggle()
|
||||
|
||||
|
||||
###Local
|
||||
@@ -519,11 +555,11 @@ func dir_contents(path: String) -> void:
|
||||
dir.list_dir_begin()
|
||||
var file_name: String = dir.get_next()
|
||||
songs.clear()
|
||||
games = []
|
||||
soundtracks = []
|
||||
while file_name != "":
|
||||
if dir.current_is_dir():
|
||||
#print("Found directory: " + file_name)
|
||||
games.append(file_name)
|
||||
soundtracks.append(file_name)
|
||||
else:
|
||||
#print("Found file: " + file_name)
|
||||
if file_name.ends_with(".mp3"):
|
||||
@@ -532,7 +568,5 @@ func dir_contents(path: String) -> void:
|
||||
else:
|
||||
print("An error occurred when trying to access the path.")
|
||||
|
||||
for game: String in games:
|
||||
var local_inspiration_label: Label = Label.new()
|
||||
local_inspiration_label.text = game
|
||||
inspiration_list.add_child(local_inspiration_label)
|
||||
for i: int in soundtracks.size():
|
||||
inspiration_list.add_child(ListRow.create(soundtracks[i], i, 20))
|
||||
+40
-57
@@ -1,7 +1,6 @@
|
||||
[gd_scene format=3 uid="uid://xwq863o6uvsu"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b8f4g15cas2j2" path="res://MainWindow.gd" id="1_eu0t5"]
|
||||
[ext_resource type="PackedScene" uid="uid://b16on0oscg1bv" path="res://CharacterSelect.tscn" id="2_76kf4"]
|
||||
[ext_resource type="Script" uid="uid://elgn7gkxgagq" path="res://InsperationScrollContainer.gd" id="2_gxtxm"]
|
||||
[ext_resource type="Texture2D" uid="uid://bcfmpd7h512ef" path="res://icons/person_add_light.svg" id="5_31tjv"]
|
||||
[ext_resource type="PackedScene" uid="uid://ds15cgsf8vpvc" path="res://MusicPlayer.tscn" id="5_emn36"]
|
||||
@@ -17,6 +16,7 @@
|
||||
[ext_resource type="PackedScene" uid="uid://btupbowehiyyu" path="res://QRWindow.tscn" id="15_qr_window"]
|
||||
[ext_resource type="PackedScene" uid="uid://cp54idaudb8dp" path="res://AboutWindow.tscn" id="16_about_window"]
|
||||
[ext_resource type="PackedScene" uid="uid://cxlsmxnrp0agh" path="res://WinnerWindow.tscn" id="17_winner_window"]
|
||||
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_ychxr"]
|
||||
font_size = 25
|
||||
@@ -70,7 +70,11 @@ events = [SubResource("InputEventKey_ujjlu")]
|
||||
|
||||
[node name="Control" type="Control" unique_id=407149696]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_eu0t5")
|
||||
|
||||
[node name="TopPanelContainer" type="PanelContainer" parent="." unique_id=663404711]
|
||||
@@ -79,7 +83,6 @@ offset_right = 1920.0
|
||||
offset_bottom = 56.0
|
||||
|
||||
[node name="Title" type="Label" parent="." unique_id=1187907861]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_left = 64.0
|
||||
offset_top = 12.0
|
||||
@@ -121,14 +124,12 @@ follow_focus = true
|
||||
script = ExtResource("7_dj026")
|
||||
|
||||
[node name="MusicList" type="VBoxContainer" parent="MusicListPanel/ScrollContainer" unique_id=373398568]
|
||||
texture_filter = 1
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="." unique_id=498283761]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_left = 1216.0
|
||||
offset_top = 128.0
|
||||
@@ -139,7 +140,6 @@ vertical_scroll_mode = 3
|
||||
script = ExtResource("2_gxtxm")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer" unique_id=1215162949]
|
||||
texture_filter = 1
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
@@ -151,8 +151,7 @@ offset_top = 64.0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 384.0
|
||||
|
||||
[node name="GameLabel" type="Label" parent="VBoxContainer" unique_id=173147793]
|
||||
texture_filter = 1
|
||||
[node name="SoundtrackLabel" type="Label" parent="VBoxContainer" unique_id=173147793]
|
||||
custom_minimum_size = Vector2(1, 1)
|
||||
layout_mode = 2
|
||||
label_settings = SubResource("LabelSettings_qspbu")
|
||||
@@ -160,22 +159,12 @@ horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="SongLabel" type="Label" parent="VBoxContainer" unique_id=1687490027]
|
||||
texture_filter = 1
|
||||
custom_minimum_size = Vector2(1, 1)
|
||||
layout_mode = 2
|
||||
label_settings = SubResource("LabelSettings_3m52w")
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="CharacterSelect" parent="." unique_id=2079506802 instance=ExtResource("2_76kf4")]
|
||||
visible = false
|
||||
top_level = true
|
||||
layout_mode = 0
|
||||
offset_left = 616.0
|
||||
offset_top = 264.0
|
||||
offset_right = 776.0
|
||||
offset_bottom = 424.0
|
||||
|
||||
[node name="MusicPlayer" parent="." unique_id=1990459832 instance=ExtResource("5_emn36")]
|
||||
layout_mode = 0
|
||||
anchors_preset = 0
|
||||
@@ -205,7 +194,6 @@ custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Players/VBoxContainer/HBoxContainer" unique_id=1331357386]
|
||||
texture_filter = 1
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
@@ -215,7 +203,6 @@ horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="AddPlayersButton" type="TextureButton" parent="Players/VBoxContainer/HBoxContainer" unique_id=634410148]
|
||||
texture_filter = 1
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 8
|
||||
@@ -242,7 +229,6 @@ placeholder_text = "Player name"
|
||||
script = ExtResource("7_qsdfy")
|
||||
|
||||
[node name="AddPlayerButton" type="Button" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=245001313]
|
||||
texture_filter = 1
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
text = "Add"
|
||||
@@ -253,52 +239,57 @@ layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_k1ygi")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="Players/VBoxContainer" unique_id=1000000001]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 15
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="PlayersListVBoxContainer" type="VBoxContainer" parent="Players/VBoxContainer/ScrollContainer" unique_id=1000000002]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 15
|
||||
|
||||
[node name="ResetPlaylistButton" type="Button" parent="." unique_id=1754155620]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_left = 1359.0
|
||||
offset_top = 66.0
|
||||
offset_right = 1470.0
|
||||
offset_bottom = 97.0
|
||||
offset_left = 1360.0
|
||||
offset_top = 64.0
|
||||
offset_right = 1471.0
|
||||
offset_bottom = 95.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Reset playlist"
|
||||
|
||||
[node name="ResetPointsButton" type="Button" parent="." unique_id=942880776]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_left = 1487.0
|
||||
offset_top = 66.0
|
||||
offset_right = 1598.0
|
||||
offset_bottom = 97.0
|
||||
offset_left = 1488.0
|
||||
offset_top = 64.0
|
||||
offset_right = 1599.0
|
||||
offset_bottom = 95.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Reset points"
|
||||
|
||||
[node name="SyncButton" type="Button" parent="." unique_id=1228313556]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_left = 1615.0
|
||||
offset_top = 66.0
|
||||
offset_right = 1726.0
|
||||
offset_bottom = 97.0
|
||||
offset_left = 1616.0
|
||||
offset_top = 64.0
|
||||
offset_right = 1759.0
|
||||
offset_bottom = 95.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Sync games"
|
||||
text = "Sync soundtracks"
|
||||
|
||||
[node name="SoundTestButton" type="Button" parent="." unique_id=1332971879]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_left = 1743.0
|
||||
offset_top = 66.0
|
||||
offset_right = 1854.0
|
||||
offset_bottom = 97.0
|
||||
offset_left = 1776.0
|
||||
offset_top = 64.0
|
||||
offset_right = 1887.0
|
||||
offset_bottom = 95.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Sound test"
|
||||
|
||||
[node name="ShowAnswerButton" type="Button" parent="." unique_id=415388039]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_left = 65.0
|
||||
offset_top = 944.0
|
||||
@@ -310,7 +301,6 @@ shortcut = SubResource("Shortcut_a7fvb")
|
||||
text = "Show answer"
|
||||
|
||||
[node name="NextButton" type="Button" parent="." unique_id=1744703488]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_left = 193.0
|
||||
offset_top = 944.0
|
||||
@@ -323,7 +313,6 @@ shortcut = SubResource("Shortcut_d6fml")
|
||||
text = "Randomize new track"
|
||||
|
||||
[node name="SearchButton" type="Button" parent="." unique_id=1782453581]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_left = 1354.0
|
||||
offset_top = 8.0
|
||||
@@ -335,7 +324,6 @@ shortcut = SubResource("Shortcut_fbju4")
|
||||
text = "Search"
|
||||
|
||||
[node name="SettingsButton" type="Button" parent="." unique_id=1193400323]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_left = 1442.0
|
||||
offset_top = 8.0
|
||||
@@ -346,7 +334,6 @@ action_mode = 0
|
||||
text = "Settings"
|
||||
|
||||
[node name="StatisticsButton" type="Button" parent="." unique_id=2045021269]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_left = 1530.0
|
||||
offset_top = 8.0
|
||||
@@ -357,7 +344,6 @@ action_mode = 0
|
||||
text = "Statistics"
|
||||
|
||||
[node name="ShortcutsButton" type="Button" parent="." unique_id=2066498226]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_left = 1626.0
|
||||
offset_top = 8.0
|
||||
@@ -368,7 +354,6 @@ action_mode = 0
|
||||
text = "Shortcuts"
|
||||
|
||||
[node name="AboutButton" type="Button" parent="." unique_id=1057035253]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_left = 1722.0
|
||||
offset_top = 8.0
|
||||
@@ -379,7 +364,6 @@ action_mode = 0
|
||||
text = "About"
|
||||
|
||||
[node name="QRButton" type="Button" parent="." unique_id=2021908559]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_left = 1802.0
|
||||
offset_top = 8.0
|
||||
@@ -401,9 +385,9 @@ offset_bottom = 96.0
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 401.0
|
||||
offset_left = 210.0
|
||||
offset_top = 153.0
|
||||
offset_right = 401.0
|
||||
offset_right = 210.0
|
||||
offset_bottom = 153.0
|
||||
|
||||
[node name="SettingsWindow" parent="." unique_id=1323246978 instance=ExtResource("11_k62u5")]
|
||||
@@ -427,9 +411,9 @@ offset_bottom = 153.0
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 420.0
|
||||
offset_left = 405.0
|
||||
offset_top = 165.0
|
||||
offset_right = 1572.0
|
||||
offset_right = 1557.0
|
||||
offset_bottom = 805.0
|
||||
|
||||
[node name="ShortcutsWindow" parent="." unique_id=1513416242 instance=ExtResource("13_8eebo")]
|
||||
@@ -442,12 +426,12 @@ offset_right = 1262.0
|
||||
offset_bottom = 860.0
|
||||
|
||||
[node name="StatisticsPopupPanel" type="PopupPanel" parent="." unique_id=298311407]
|
||||
theme = ExtResource("popup_theme")
|
||||
oversampling_override = 1.0
|
||||
initial_position = 2
|
||||
size = Vector2i(450, 100)
|
||||
|
||||
[node name="Label" type="Label" parent="StatisticsPopupPanel" unique_id=548316493]
|
||||
texture_filter = 1
|
||||
offset_left = 4.0
|
||||
offset_top = 4.0
|
||||
offset_right = 446.0
|
||||
@@ -456,12 +440,11 @@ text = "Statistics"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="StatisticsLabel" type="Label" parent="StatisticsPopupPanel" unique_id=58226477]
|
||||
texture_filter = 1
|
||||
offset_left = 4.0
|
||||
offset_top = 4.0
|
||||
offset_right = 446.0
|
||||
offset_bottom = 96.0
|
||||
text = "Total amount of games in the playlist: 9000"
|
||||
text = "Total amount of soundtracks in the playlist: 9000"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
|
||||
+5
-8
@@ -28,9 +28,6 @@ var volume_slider: HSlider = $MusicPlayerContainer/VolumeSlider
|
||||
@onready
|
||||
var music_time_label: Label = $MusicPlayerContainer/MusicTimeLabel
|
||||
|
||||
@onready
|
||||
var path: String = '/Users/sebastian/ResilioSync/Sorterat_test/Metal Gear Solid 4 - Guns of the Patriots/2-16 Metal Gear Saga.mp3'
|
||||
|
||||
var play_icon: Texture = preload("res://icons/play_icon_light.svg")
|
||||
var pause_icon: Texture = preload("res://icons/pause_icon_light.svg")
|
||||
|
||||
@@ -63,12 +60,12 @@ func _process(_delta: float) -> void:
|
||||
music_time_label.text = format_text(progress_slider.value, 0.0)
|
||||
|
||||
func format_time(time: float) -> String:
|
||||
var mins: String = "%02d" % floor(time / 60)
|
||||
var sec: String = "%02d" % round(fmod(time, 60))
|
||||
return mins + ":" + sec
|
||||
var total_seconds: int = roundi(time)
|
||||
@warning_ignore("integer_division")
|
||||
return "%02d:%02d" % [total_seconds / 60, total_seconds % 60]
|
||||
|
||||
func format_text(part: float, total: float) -> String:
|
||||
if (Settings.hide_beginning && part <= Settings.hide_for_seconds) && Settings.hide_length:
|
||||
if (Settings.hide_beginning && part < Settings.hide_for_seconds) && Settings.hide_length:
|
||||
return "??:?? / ??:??"
|
||||
elif Settings.hide_length:
|
||||
return format_time(part) + " / ??:??"
|
||||
@@ -209,7 +206,7 @@ func get_sound_test_song() -> void:
|
||||
var sound: AudioStream = AudioStreamMP3.new()
|
||||
sound.data = body
|
||||
play_song(sound)
|
||||
Settings.make_request2("/music/soundTest", test_sound, true)
|
||||
MusicServerClient.fetch_sound_test_song(test_sound)
|
||||
|
||||
##### LOCAL
|
||||
var local_path: String = '/Users/sebastian/ResilioSync/Sorterat_test/Metal Gear Solid 4 - Guns of the Patriots/2-16 Metal Gear Saga.mp3'
|
||||
|
||||
@@ -19,15 +19,19 @@ var remove_player := $HBoxContainer/RemovePlayer
|
||||
signal change_character_clicked
|
||||
signal player_removed
|
||||
|
||||
@export
|
||||
var id: int
|
||||
## The player shown in this row.
|
||||
var player_object: PlayerObject
|
||||
|
||||
## Position in Settings.player_array. Only used for the keybinds: Alt+<list_index + 1>
|
||||
## gives this player a point and Ctrl+Alt+<list_index + 1> takes one away.
|
||||
var list_index: int
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
add_button.pressed.connect(add_point)
|
||||
add_button.set_shortcut(create_button_shortcut(id, false))
|
||||
add_button.set_shortcut(create_button_shortcut(list_index, false))
|
||||
minus_button.pressed.connect(minus_point)
|
||||
minus_button.set_shortcut(create_button_shortcut(id, true))
|
||||
minus_button.set_shortcut(create_button_shortcut(list_index, true))
|
||||
character.pressed.connect(change_character)
|
||||
remove_player.pressed.connect(func() -> void: player_removed.emit())
|
||||
update_score()
|
||||
@@ -36,44 +40,39 @@ func _ready() -> void:
|
||||
func _process(delta: float) -> void:
|
||||
remove_player.visible = Settings.edit_players
|
||||
|
||||
func create_button_shortcut(scancode : int, ctrl_pressed: bool) -> Shortcut:
|
||||
func create_button_shortcut(index: int, ctrl_pressed: bool) -> Shortcut:
|
||||
var button_shortcut: Shortcut = Shortcut.new()
|
||||
var button_event : InputEventKey = InputEventKey.new()
|
||||
button_event.keycode = scancode + 49
|
||||
button_event.keycode = KEY_1 + index
|
||||
button_event.alt_pressed = true
|
||||
button_event.ctrl_pressed = ctrl_pressed
|
||||
button_shortcut.events.append(button_event)
|
||||
return button_shortcut
|
||||
|
||||
func add_point() -> void:
|
||||
Settings.player_array[id].add_point()
|
||||
player_object.add_point()
|
||||
update_score()
|
||||
|
||||
func minus_point() -> void:
|
||||
Settings.player_array[id].minus_point()
|
||||
player_object.minus_point()
|
||||
update_score()
|
||||
|
||||
func change_character() -> void:
|
||||
change_character_clicked.emit()
|
||||
|
||||
func character_selected(file_name: String) -> void:
|
||||
print("Back in player list with: " + file_name)
|
||||
Settings.player_array[id].character = load("res://characters/" + file_name)
|
||||
set_player_character()
|
||||
|
||||
func new_character_selected(character_texture: ImageTexture) -> void:
|
||||
print("Back in player list with new")
|
||||
Settings.player_array[id].character = character_texture
|
||||
player_object.character = character_texture
|
||||
set_player_character()
|
||||
|
||||
func set_player_character() -> void:
|
||||
character.custom_minimum_size = Vector2(80, 40)
|
||||
character.ignore_texture_size = true
|
||||
character.stretch_mode = TextureButton.STRETCH_KEEP_ASPECT_CENTERED
|
||||
character.texture_normal = Settings.player_array[id].character
|
||||
character.texture_normal = player_object.character
|
||||
|
||||
func update_score() -> void:
|
||||
if OS.has_feature("debug"):
|
||||
player_name_field.text = str(Settings.player_array[id].id) + " " + Settings.player_array[id].player_name + ": " + str(Settings.player_array[id].player_score)
|
||||
player_name_field.text = str(list_index + 1) + " " + player_object.player_name + ": " + str(player_object.player_score)
|
||||
else:
|
||||
player_name_field.text = Settings.player_array[id].player_name + ": " + str(Settings.player_array[id].player_score)
|
||||
player_name_field.text = player_object.player_name + ": " + str(player_object.player_score)
|
||||
+10
-5
@@ -29,9 +29,12 @@ script = ExtResource("1_if4kc")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Panel4" type="Panel" parent="HBoxContainer"]
|
||||
custom_minimum_size = Vector2(5, 40)
|
||||
@@ -41,12 +44,15 @@ theme_override_styles/panel = SubResource("StyleBoxFlat_821k2")
|
||||
[node name="Name" type="Label" parent="HBoxContainer"]
|
||||
custom_minimum_size = Vector2(130, 40)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
text = ": 0"
|
||||
vertical_alignment = 1
|
||||
clip_text = true
|
||||
text_overrun_behavior = 3
|
||||
|
||||
[node name="Panel3" type="Panel" parent="HBoxContainer"]
|
||||
custom_minimum_size = Vector2(80, 2.08165e-12)
|
||||
custom_minimum_size = Vector2(16, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_w10e8")
|
||||
|
||||
@@ -91,7 +97,6 @@ theme_override_styles/panel = SubResource("StyleBoxFlat_b2crt")
|
||||
[node name="RemovePlayer" type="TextureButton" parent="HBoxContainer"]
|
||||
custom_minimum_size = Vector2(40, 40)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
texture_normal = ExtResource("3_j3uxe")
|
||||
|
||||
+24
-10
@@ -1,4 +1,4 @@
|
||||
extends Node
|
||||
extends RefCounted
|
||||
class_name PlayerObject
|
||||
|
||||
signal first_point_triggerd
|
||||
@@ -8,8 +8,8 @@ signal point_given_sound
|
||||
signal player_point_given
|
||||
signal player_point_taken
|
||||
|
||||
@export
|
||||
var id: int
|
||||
## Unique id that never changes, unlike the player's position in Settings.player_array.
|
||||
var id: String = _generate_uuid()
|
||||
|
||||
@export
|
||||
var player_name: String
|
||||
@@ -19,24 +19,38 @@ var player_score: int
|
||||
|
||||
var is_first_point: bool = true
|
||||
|
||||
var character: Texture = load("res://noCharacter.png")
|
||||
## Picture shown until the player picks a character.
|
||||
const NO_CHARACTER_PATH: String = "res://noCharacter.png"
|
||||
|
||||
func _init(new_name: String, new_id: int) -> void:
|
||||
var character: Texture = load(NO_CHARACTER_PATH)
|
||||
|
||||
func _init(new_name: String) -> void:
|
||||
player_name = new_name
|
||||
id = new_id
|
||||
player_score = 0
|
||||
|
||||
## Returns a random version 4 UUID, e.g. "3f2b8c1e-9a4d-4e7f-b2c6-5d8e1f0a7b93".
|
||||
static func _generate_uuid() -> String:
|
||||
var bytes: PackedByteArray = Crypto.new().generate_random_bytes(16)
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80
|
||||
var hex: String = bytes.hex_encode()
|
||||
return "%s-%s-%s-%s-%s" % [hex.substr(0, 8), hex.substr(8, 4), hex.substr(12, 4), hex.substr(16, 4), hex.substr(20, 12)]
|
||||
|
||||
func add_point() -> void:
|
||||
if player_score < Settings.winning_score:
|
||||
player_score += 1
|
||||
player_point_given.emit()
|
||||
if player_score == 1 && is_first_point:
|
||||
var first_point: bool = player_score == 1 && is_first_point
|
||||
if player_score == 1:
|
||||
is_first_point = false
|
||||
first_point_triggerd.emit()
|
||||
# With a low winning score the first point can also be match point or the win,
|
||||
# so the more important event is checked first
|
||||
if player_score == Settings.winning_score:
|
||||
winner_triggerd.emit()
|
||||
elif player_score == Settings.winning_score - 1:
|
||||
match_point_triggerd.emit()
|
||||
elif player_score == Settings.winning_score:
|
||||
winner_triggerd.emit()
|
||||
elif first_point:
|
||||
first_point_triggerd.emit()
|
||||
else:
|
||||
point_given_sound.emit()
|
||||
|
||||
|
||||
+3
-14
@@ -1,4 +1,4 @@
|
||||
extends Control
|
||||
extends PopupWindow
|
||||
|
||||
@onready
|
||||
var qr_panel: Panel = $QRPanel
|
||||
@@ -21,21 +21,10 @@ func _ready() -> void:
|
||||
url_label.text = qr_code_rect.data
|
||||
print("QR code generated for: ", url)
|
||||
|
||||
func show_window() -> void:
|
||||
visible = true
|
||||
func open() -> void:
|
||||
super()
|
||||
# Regenerate QR code in case settings changed
|
||||
var url: String = Settings.default_path + "/search"
|
||||
qr_code_rect.data = url
|
||||
url_label.text = qr_code_rect.data
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if self.visible == true:
|
||||
if event is InputEventMouseButton && event.is_pressed():
|
||||
var evLocal: InputEvent = make_input_local(event)
|
||||
if !Rect2(Vector2(0, 0), qr_panel.size).has_point(evLocal.position):
|
||||
self.visible = false
|
||||
|
||||
|
||||
func hide_window() -> void:
|
||||
visible = false
|
||||
+4
-1
@@ -2,8 +2,10 @@
|
||||
|
||||
[ext_resource type="Script" uid="uid://bsa6xg5xos6h7" path="res://QRWindow.gd" id="1_qr_script"]
|
||||
[ext_resource type="Script" uid="uid://dynulhnyq5hpq" path="res://addons/kenyoni/qr_code/qr_code_rect.gd" id="2_jpsxt"]
|
||||
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
|
||||
|
||||
[node name="QRControl" type="Control" unique_id=1996957585]
|
||||
[node name="QRControl" type="Control" unique_id=1996957585 node_paths=PackedStringArray("panel")]
|
||||
theme = ExtResource("popup_theme")
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
@@ -11,6 +13,7 @@ anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_qr_script")
|
||||
panel = NodePath("QRPanel")
|
||||
|
||||
[node name="QRPanel" type="Panel" parent="." unique_id=1021365843]
|
||||
unique_name_in_owner = true
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Releasing
|
||||
|
||||
Releases are made from the version in `project.godot`
|
||||
(**Project > Project Settings > Application > Config > Version**). The game shows the
|
||||
same version in the About window.
|
||||
|
||||
1. Bump the version, e.g. from `1.9.0` to `1.10.0`, and add a `WHATS_NEW` entry in
|
||||
`AboutWindow.gd`.
|
||||
2. Merge to `main` and push.
|
||||
|
||||
The `Build` workflow (`.gitea/workflows/gitea-build.yaml`) then:
|
||||
|
||||
1. runs the tests,
|
||||
2. exports the Windows and Linux builds, and packages Linux as
|
||||
`MusicPlayer_<version>_linux.tar.gz` with the icon, an app menu entry and `install.sh`
|
||||
(Linux programs can't carry their own icon the way a Windows .exe does),
|
||||
3. if no tag exists for the version yet: uploads the builds to the package registry
|
||||
under `MusicPlayer/<version>/`, and creates the `<version>` tag and Gitea release
|
||||
with the builds attached.
|
||||
|
||||
Pushes that don't change the version, and pushes to `develop`, only test and export.
|
||||
Don't create tags or `1.x.y` branches by hand; the workflow makes the tag.
|
||||
|
||||
## Installing on Linux
|
||||
|
||||
Unpack `MusicPlayer_<version>_linux.tar.gz` and run `./install.sh` inside the
|
||||
`MusicPlayer` folder. It installs the game to `~/.local/bin` and adds it, with its icon,
|
||||
to the app menu. The bare `MusicPlayer_<version>.x86_64` is still published for anyone
|
||||
who just wants to run the file.
|
||||
+143
-136
@@ -1,4 +1,4 @@
|
||||
extends Control
|
||||
extends PopupWindow
|
||||
|
||||
@onready
|
||||
var search_list: VBoxContainer = $ScrollContainer/VBoxContainer
|
||||
@@ -9,24 +9,26 @@ var search_panel: Panel = %SearchPanel
|
||||
@onready
|
||||
var clear_button: Button = $ClearButton
|
||||
|
||||
@onready
|
||||
var close_button: Button = $CloseButton
|
||||
|
||||
@onready
|
||||
var search_bar: TextEdit = $Searchbar
|
||||
|
||||
var games: Array = []
|
||||
var regex: RegEx
|
||||
var soundtracks: Array = []
|
||||
|
||||
# Precomputed soundtrack data: array of dicts with original, lowercase, and cleaned fields
|
||||
var precomputed_soundtracks: Array = []
|
||||
|
||||
# Regex cache: maps cleaned search terms to compiled RegEx objects
|
||||
var regex_cache: Dictionary = {}
|
||||
|
||||
var label_pool: Array[Label] = []
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
clear_button.pressed.connect(clear)
|
||||
close_button.pressed.connect(close)
|
||||
search_bar.grab_focus()
|
||||
search_bar.text_changed.connect(search)
|
||||
visibility_changed.connect(focus)
|
||||
regex = RegEx.new()
|
||||
|
||||
|
||||
func focus() -> void:
|
||||
if self.visible == true:
|
||||
search_bar.grab_focus()
|
||||
@@ -34,145 +36,150 @@ func focus() -> void:
|
||||
|
||||
func close() -> void:
|
||||
clear()
|
||||
self.visible = false
|
||||
super()
|
||||
|
||||
func search() -> void:
|
||||
print(search_bar.text)
|
||||
Settings.delete_children(search_list)
|
||||
var search_text: String = search_bar.text.to_lower()
|
||||
for game: String in games:
|
||||
if is_match_exact(search_text, game):
|
||||
add_game(game)
|
||||
var clean_search_text: String = clean_search_term(search_text)
|
||||
for game: String in games:
|
||||
if is_match_contains(clean_search_text, clean_game(game)):
|
||||
add_game(game)
|
||||
for game: String in games:
|
||||
if is_match_regex(clean_search_text, clean_game(game)):
|
||||
add_game(game)
|
||||
|
||||
func clean_search_term(term: String) -> String:
|
||||
return term.replace(" ", "").replace("é", "e").replace("+", "plus").replace("&", "and").replace("'n", "and")
|
||||
|
||||
func clean_game(term: String) -> String:
|
||||
return term.replace(" ", "").replace("é", "e").replace("+", "plus").replace("&", "and").replace("'n", "and").to_lower()
|
||||
|
||||
func is_match_exact(search_term: String, game_name: String) -> bool:
|
||||
game_name = game_name.to_lower()
|
||||
var search_text: String = search_bar.text
|
||||
|
||||
if search_term == "":
|
||||
return true
|
||||
elif game_name.contains(search_term):
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
if search_text == "":
|
||||
show_all_soundtracks()
|
||||
return
|
||||
|
||||
perform_search(search_text)
|
||||
|
||||
func is_match_contains(search_term: String, game_name: String) -> bool:
|
||||
if search_term == "":
|
||||
return true
|
||||
elif game_name.contains(search_term):
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
func is_match_regex(search_term: String, game_name: String) -> bool:
|
||||
if search_term == "":
|
||||
return true
|
||||
elif compile_regex(search_term).search(game_name):
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
func add_game(game: String) -> void:
|
||||
var label: Label = Label.new()
|
||||
label.text = game
|
||||
print("game: " + game)
|
||||
label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
if !check_if_game_exists(game):
|
||||
search_list.add_child(label)
|
||||
|
||||
func check_if_game_exists(game: String) -> bool:
|
||||
var game_exists: bool = false
|
||||
func show_all_soundtracks() -> void:
|
||||
# Hide all visible labels and add to pool
|
||||
for child: Label in search_list.get_children():
|
||||
if child.text == game:
|
||||
game_exists = true
|
||||
return game_exists
|
||||
|
||||
func compile_regex(search_term: String) -> RegEx:
|
||||
var regText: String = ".*"
|
||||
for letter: String in search_term:
|
||||
regText += letter + ".*"
|
||||
regex.compile(regText)
|
||||
return regex
|
||||
|
||||
func get_list_of_games() -> void:
|
||||
print("get_list_of_games")
|
||||
var handle_games: Callable = func handle_games(array: Array) -> void:
|
||||
if typeof(array) == TYPE_ARRAY:
|
||||
games = []
|
||||
Settings.delete_children(search_list)
|
||||
games.append_array(array)
|
||||
for game: String in games:
|
||||
var label: Label = Label.new()
|
||||
label.text = game
|
||||
label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
search_list.add_child(label)
|
||||
else:
|
||||
print("Unexpected data")
|
||||
Settings.make_request2("/music/all/order", handle_games, true)
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if self.visible == true:
|
||||
if event is InputEventMouseButton && event.is_pressed():
|
||||
var evLocal: InputEvent = make_input_local(event)
|
||||
if !Rect2(Vector2(0, 0), search_panel.size).has_point(evLocal.position):
|
||||
self.visible = false
|
||||
|
||||
func clear() -> void:
|
||||
search_bar.text = ""
|
||||
search()
|
||||
search_bar.grab_focus()
|
||||
if child.visible:
|
||||
child.visible = false
|
||||
label_pool.append(child)
|
||||
|
||||
func old_search() -> void:
|
||||
print(search_bar.text)
|
||||
Settings.delete_children(search_list)
|
||||
for game: String in games:
|
||||
if old_is_match_exact(search_bar.text, game):
|
||||
add_game(game)
|
||||
for game: String in games:
|
||||
if is_match_contains(clean_term(search_bar.text), clean_term(game)):
|
||||
add_game(game)
|
||||
for game: String in games:
|
||||
if old_is_match_regex(clean_term(search_bar.text), clean_term(game)):
|
||||
add_game(game)
|
||||
# Show all soundtracks in order
|
||||
for soundtrack: String in soundtracks:
|
||||
add_soundtrack_reuse(soundtrack)
|
||||
|
||||
func perform_search(search_text: String) -> void:
|
||||
# Hide all visible labels and add to pool
|
||||
for child: Label in search_list.get_children():
|
||||
if child.visible:
|
||||
child.visible = false
|
||||
label_pool.append(child)
|
||||
|
||||
for soundtrack: String in find_matches(search_text):
|
||||
add_soundtrack_reuse(soundtrack)
|
||||
|
||||
## Returns the soundtracks matching [param search_text], best matches first: plain
|
||||
## case-insensitive matches, then matches ignoring spaces and special characters,
|
||||
## then matches with the letters in order but not next to each other.
|
||||
func find_matches(search_text: String) -> Array[String]:
|
||||
var seen: Dictionary = {}
|
||||
var search_text_lower: String = search_text.to_lower()
|
||||
var clean_search_text: String = clean_term(search_text)
|
||||
|
||||
var matches: Array[String] = []
|
||||
|
||||
# Pass 1: Exact case-insensitive contains match on lowercase versions
|
||||
for i in range(precomputed_soundtracks.size()):
|
||||
var st_data = precomputed_soundtracks[i]
|
||||
if st_data["lowercase"].contains(search_text_lower):
|
||||
if not st_data["original"] in seen:
|
||||
seen[st_data["original"]] = true
|
||||
matches.append(st_data["original"])
|
||||
|
||||
# Pass 2: Contains match on cleaned terms
|
||||
for i in range(precomputed_soundtracks.size()):
|
||||
var st_data = precomputed_soundtracks[i]
|
||||
if st_data["cleaned"].contains(clean_search_text):
|
||||
if not st_data["original"] in seen:
|
||||
seen[st_data["original"]] = true
|
||||
matches.append(st_data["original"])
|
||||
|
||||
# Pass 3: Regex match on cleaned terms
|
||||
var regex = compile_regex_cached(clean_search_text)
|
||||
for i in range(precomputed_soundtracks.size()):
|
||||
var st_data = precomputed_soundtracks[i]
|
||||
if regex.search(st_data["cleaned"]):
|
||||
if not st_data["original"] in seen:
|
||||
seen[st_data["original"]] = true
|
||||
matches.append(st_data["original"])
|
||||
|
||||
return matches
|
||||
|
||||
# Cleans a term by removing spaces and replacing special characters.
|
||||
# Matches MusicServer's clean_term() function.
|
||||
func clean_term(term: String) -> String:
|
||||
return term.replace(" ", "").replace("é", "e").replace("+", "plus").replace("&", "and").replace("'n", "and").to_lower()
|
||||
|
||||
# Compiles a regex pattern with caching to avoid recompiling the same pattern multiple times.
|
||||
func compile_regex_cached(search_term: String) -> RegEx:
|
||||
if not search_term in regex_cache:
|
||||
var regex = RegEx.new()
|
||||
var regText: String = ".*"
|
||||
for letter: String in search_term:
|
||||
# Escape regex metacharacters so searches like "Q*bert" or "(Remix)" work
|
||||
if "\\^$.|?*+()[]{}".contains(letter):
|
||||
regText += "\\"
|
||||
regText += letter + ".*"
|
||||
regex.compile(regText)
|
||||
regex_cache[search_term] = regex
|
||||
return regex_cache[search_term]
|
||||
|
||||
func old_is_match_exact(search_term: String, game_name: String) -> bool:
|
||||
search_term = search_term.to_lower()
|
||||
game_name = game_name.to_lower()
|
||||
|
||||
if search_term == "":
|
||||
return true
|
||||
elif game_name.contains(search_term):
|
||||
return true
|
||||
func add_soundtrack(soundtrack: String) -> void:
|
||||
# Backward compatibility - delegates to reuse version
|
||||
add_soundtrack_reuse(soundtrack)
|
||||
|
||||
# Optimized version that reuses label nodes from pool instead of creating new ones
|
||||
func add_soundtrack_reuse(soundtrack: String) -> void:
|
||||
var label: Label
|
||||
if label_pool.size() > 0:
|
||||
label = label_pool.pop_front()
|
||||
label.text = soundtrack
|
||||
label.visible = true
|
||||
# Move to end to maintain display order
|
||||
search_list.move_child(label, search_list.get_child_count() - 1)
|
||||
else:
|
||||
return false
|
||||
label = Label.new()
|
||||
label.text = soundtrack
|
||||
label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
search_list.add_child(label)
|
||||
label.visible = true
|
||||
|
||||
func old_is_match_regex(search_term: String, game_name: String) -> bool:
|
||||
if search_term == "":
|
||||
return true
|
||||
elif compile_regex(search_term).search(game_name):
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
func check_if_soundtrack_exists(soundtrack: String) -> bool:
|
||||
var soundtrack_exists: bool = false
|
||||
for child: Label in search_list.get_children():
|
||||
if child.text == soundtrack and child.visible:
|
||||
soundtrack_exists = true
|
||||
return soundtrack_exists
|
||||
|
||||
func old_compile_regex(search_term: String) -> RegEx:
|
||||
var regex = RegEx.new()
|
||||
var regText: String = ".*"
|
||||
for letter in search_term:
|
||||
regText += letter + ".*"
|
||||
regex.compile(regText)
|
||||
return regex
|
||||
func get_list_of_soundtracks() -> void:
|
||||
print("get_list_of_soundtracks")
|
||||
var handle_soundtracks: Callable = func handle_soundtracks(array) -> void:
|
||||
if typeof(array) == TYPE_ARRAY:
|
||||
# Reuse existing labels: hide all and add to pool
|
||||
for child: Label in search_list.get_children():
|
||||
child.visible = false
|
||||
label_pool.append(child)
|
||||
set_soundtracks(array)
|
||||
for soundtrack: String in soundtracks:
|
||||
add_soundtrack_reuse(soundtrack)
|
||||
else:
|
||||
print("Unexpected data")
|
||||
MusicServerClient.fetch_all_soundtracks_ordered(handle_soundtracks)
|
||||
|
||||
## Replaces the searchable soundtracks and precomputes their search forms.
|
||||
func set_soundtracks(list: Array) -> void:
|
||||
soundtracks = []
|
||||
precomputed_soundtracks = []
|
||||
soundtracks.append_array(list)
|
||||
for soundtrack: String in soundtracks:
|
||||
precomputed_soundtracks.append({
|
||||
"original": soundtrack,
|
||||
"lowercase": soundtrack.to_lower(),
|
||||
"cleaned": clean_term(soundtrack)
|
||||
})
|
||||
|
||||
func clear() -> void:
|
||||
search_bar.text = ""
|
||||
show_all_soundtracks()
|
||||
search_bar.grab_focus()
|
||||
+18
-29
@@ -1,8 +1,10 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bxydgil1yifps"]
|
||||
[gd_scene format=3 uid="uid://bxydgil1yifps"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bg5yo08638600" path="res://SearchWindow.gd" id="1_m7bij"]
|
||||
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
|
||||
|
||||
[node name="Control" type="Control"]
|
||||
[node name="Control" type="Control" unique_id=616238617 node_paths=PackedStringArray("panel")]
|
||||
theme = ExtResource("popup_theme")
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
@@ -10,54 +12,41 @@ anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_m7bij")
|
||||
panel = NodePath("SearchPanel")
|
||||
|
||||
[node name="SearchPanel" type="Panel" parent="."]
|
||||
[node name="SearchPanel" type="Panel" parent="." unique_id=1262945994]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 640.0
|
||||
|
||||
[node name="ClearButton" type="Button" parent="."]
|
||||
texture_filter = 1
|
||||
[node name="ClearButton" type="Button" parent="." unique_id=1549439398]
|
||||
layout_mode = 0
|
||||
offset_left = 880.0
|
||||
offset_top = 24.0
|
||||
offset_right = 944.0
|
||||
offset_bottom = 64.0
|
||||
offset_left = 936.0
|
||||
offset_top = 16.0
|
||||
offset_right = 1027.0
|
||||
offset_bottom = 56.0
|
||||
scale = Vector2(2, 2)
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Clear"
|
||||
|
||||
[node name="CloseButton" type="Button" parent="."]
|
||||
texture_filter = 1
|
||||
[node name="Searchbar" type="TextEdit" parent="." unique_id=2112669372]
|
||||
layout_mode = 0
|
||||
offset_left = 1032.0
|
||||
offset_top = 24.0
|
||||
offset_right = 1082.0
|
||||
offset_bottom = 64.0
|
||||
scale = Vector2(2, 2)
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Close"
|
||||
|
||||
[node name="Searchbar" type="TextEdit" parent="."]
|
||||
texture_filter = 1
|
||||
layout_mode = 0
|
||||
offset_left = 64.0
|
||||
offset_top = 24.0
|
||||
offset_right = 448.0
|
||||
offset_bottom = 64.0
|
||||
offset_left = 32.0
|
||||
offset_top = 16.0
|
||||
offset_right = 468.0
|
||||
offset_bottom = 56.0
|
||||
scale = Vector2(2, 2)
|
||||
placeholder_text = "Search"
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="."]
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="." unique_id=213733486]
|
||||
layout_mode = 0
|
||||
offset_top = 128.0
|
||||
offset_right = 576.0
|
||||
offset_bottom = 384.0
|
||||
scale = Vector2(2, 2)
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer"]
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer" unique_id=1567403144]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
+3
-73
@@ -28,81 +28,11 @@ var edit_players: bool = false
|
||||
var currently_syncing: bool = false
|
||||
var character_select_open: bool = false
|
||||
|
||||
var version: String = "1.8.5"
|
||||
## The game version, set in Project Settings > Application > Config > Version.
|
||||
## Releases are made from it, see RELEASING.md.
|
||||
var version: String = ProjectSettings.get_setting("application/config/version", "")
|
||||
|
||||
func make_request2(address: String, func_name: Callable, expect_data: bool) -> void:
|
||||
var error_handling: Callable = func(_result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
print("request done to address: ", default_path + address)
|
||||
if response_code == 200:
|
||||
print("func_name: ", func_name.get_method())
|
||||
if !expect_data:
|
||||
func_name.call()
|
||||
else:
|
||||
var json: JSON = JSON.new()
|
||||
var error: int = json.parse(body.get_string_from_utf8())
|
||||
if error == OK:
|
||||
var data_received = json.get_data()
|
||||
print("data_received type: ", type_string(typeof(data_received)))
|
||||
if typeof(data_received) == TYPE_ARRAY:
|
||||
func_name.call(data_received)
|
||||
elif func_name != null:
|
||||
func_name.call(data_received)
|
||||
else:
|
||||
print("data_received type: ", type_string(typeof(body)))
|
||||
func_name.call(body)
|
||||
|
||||
var http_request: HTTPRequest = HTTPRequest.new()
|
||||
add_child(http_request)
|
||||
http_request.request_completed.connect(error_handling)
|
||||
|
||||
# Perform a GET request. The URL below returns JSON as of writing.
|
||||
print("address: ", default_path + address)
|
||||
var request_error: int = http_request.request(default_path + address)
|
||||
if request_error != OK:
|
||||
push_error("An error occurred in the HTTP request.")
|
||||
|
||||
func make_request3(address: String) -> void:
|
||||
var error_handling: Callable = func(_result: int, response_code: int, _headers: PackedStringArray, _body: PackedByteArray) -> void:
|
||||
print("request done to address: ", default_path + address)
|
||||
if response_code != 200:
|
||||
print("Error: " + str(response_code))
|
||||
|
||||
var http_request: HTTPRequest = HTTPRequest.new()
|
||||
add_child(http_request)
|
||||
http_request.request_completed.connect(error_handling)
|
||||
# Perform a GET request. The URL below returns JSON as of writing.
|
||||
var request_error: int = http_request.request(default_path + address)
|
||||
if request_error != OK:
|
||||
push_error("An error occurred in the HTTP request.")
|
||||
|
||||
func make_put_request(address: String) -> void:
|
||||
var error_handling: Callable = func(_result: int, response_code: int, _headers: PackedStringArray, _body: PackedByteArray) -> void:
|
||||
print("request done to address: ", default_path + address)
|
||||
if response_code != 200:
|
||||
print("Error: " + str(response_code))
|
||||
|
||||
var http_request: HTTPRequest = HTTPRequest.new()
|
||||
add_child(http_request)
|
||||
http_request.request_completed.connect(error_handling)
|
||||
# Perform a GET request. The URL below returns JSON as of writing.
|
||||
var headers: PackedStringArray = ["Content-Type: application/json"]
|
||||
var request_error: int = http_request.request(default_path + address, headers, HTTPClient.METHOD_PUT)
|
||||
if request_error != OK:
|
||||
push_error("An error occurred in the HTTP request.")
|
||||
|
||||
func delete_children(node: Node) -> void:
|
||||
for n: Node in node.get_children():
|
||||
node.remove_child(n)
|
||||
n.queue_free()
|
||||
|
||||
func delete_player_children(node: Node) -> void:
|
||||
for n: Node in node.get_children():
|
||||
print(n)
|
||||
print(n.name)
|
||||
if n.name == "HBoxContainer":
|
||||
pass
|
||||
elif n.name == "AddPlayerContainer":
|
||||
pass
|
||||
else:
|
||||
node.remove_child(n)
|
||||
n.queue_free()
|
||||
+7
-8
@@ -1,4 +1,4 @@
|
||||
extends Control
|
||||
extends PopupWindow
|
||||
|
||||
@onready
|
||||
var settings_panel: Panel = %SettingsPanel
|
||||
@@ -109,13 +109,12 @@ func _ready() -> void:
|
||||
update_hide_for_seconds_enabled()
|
||||
select_server_button.select(Settings.selected_server)
|
||||
select_server_button.item_selected.connect(select_server)
|
||||
visibility_changed.connect(_on_visibility_changed)
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if self.visible == true:
|
||||
if event is InputEventMouseButton && event.is_pressed():
|
||||
var evLocal: InputEvent = make_input_local(event)
|
||||
if !Rect2(Vector2(0, 0), settings_panel.size).has_point(evLocal.position):
|
||||
self.visible = false
|
||||
# Settings can change outside this window, e.g. winning turns statistics off
|
||||
func _on_visibility_changed() -> void:
|
||||
if visible:
|
||||
add_to_database_button.button_pressed = Settings.add_to_stats
|
||||
|
||||
func fullscreen() -> void:
|
||||
Settings.fullscreen = !Settings.fullscreen
|
||||
@@ -154,7 +153,7 @@ func hide_ticks() -> void:
|
||||
Settings.hide_ticks = !Settings.hide_ticks
|
||||
|
||||
func lower_winning_score() -> void:
|
||||
Settings.winning_score -= 1
|
||||
Settings.winning_score = max(Settings.winning_score - 1, 1)
|
||||
score_label.text = str(Settings.winning_score)
|
||||
|
||||
func increase_winning_score() -> void:
|
||||
|
||||
+31
-13
@@ -1,11 +1,13 @@
|
||||
[gd_scene format=3 uid="uid://dldpeo5y3l5hq"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dukqyjoduf1af" path="res://SettingsWindow.gd" id="1_bt55j"]
|
||||
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_3xrlm"]
|
||||
font_size = 25
|
||||
|
||||
[node name="SettingsControl" type="Control" unique_id=1298840328]
|
||||
[node name="SettingsControl" type="Control" unique_id=1298840328 node_paths=PackedStringArray("panel")]
|
||||
theme = ExtResource("popup_theme")
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
@@ -13,6 +15,7 @@ anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_bt55j")
|
||||
panel = NodePath("SettingsPanel")
|
||||
|
||||
[node name="SettingsPanel" type="Panel" parent="." unique_id=45537159]
|
||||
unique_name_in_owner = true
|
||||
@@ -22,8 +25,11 @@ offset_bottom = 640.0
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="SettingsPanel" unique_id=1325795358]
|
||||
layout_mode = 0
|
||||
offset_right = 500.0
|
||||
offset_bottom = 640.0
|
||||
offset_left = 20.0
|
||||
offset_top = 8.0
|
||||
offset_right = 480.0
|
||||
offset_bottom = 632.0
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="SettingsPanel/ScrollContainer" unique_id=1796635163]
|
||||
clip_contents = true
|
||||
@@ -40,7 +46,7 @@ horizontal_alignment = 1
|
||||
[node name="StopAfterCurrentCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=508305575]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_horizontal = 3
|
||||
button_pressed = true
|
||||
action_mode = 0
|
||||
text = "Stop after current"
|
||||
@@ -48,7 +54,7 @@ text = "Stop after current"
|
||||
[node name="HideNextTrackCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=1568244599]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_horizontal = 3
|
||||
button_pressed = true
|
||||
action_mode = 0
|
||||
text = "Hide next track"
|
||||
@@ -56,21 +62,21 @@ text = "Hide next track"
|
||||
[node name="AddToDatabaseCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=1673750542]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_horizontal = 3
|
||||
action_mode = 0
|
||||
text = "Turn on played to database"
|
||||
|
||||
[node name="LowPlayedCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=1672657048]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_horizontal = 3
|
||||
action_mode = 0
|
||||
text = "Use low played mode"
|
||||
|
||||
[node name="HideBeginningCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=1370617867]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_horizontal = 3
|
||||
action_mode = 0
|
||||
text = "Hide beginning of songs"
|
||||
|
||||
@@ -81,12 +87,15 @@ layout_mode = 2
|
||||
[node name="HideForSecondsTitle" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/HideForSecondsHBoxContainer" unique_id=1822369383]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Hide Start Seconds: "
|
||||
|
||||
[node name="HideForSecondsLabel" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/HideForSecondsHBoxContainer" unique_id=1580108945]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(48, 0)
|
||||
layout_mode = 2
|
||||
text = "5.0"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="LowerHideForSecondsButton" type="Button" parent="SettingsPanel/ScrollContainer/VBoxContainer/HideForSecondsHBoxContainer" unique_id=180643442]
|
||||
unique_name_in_owner = true
|
||||
@@ -107,14 +116,14 @@ text = "+1"
|
||||
[node name="HideLengthCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=67806372]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_horizontal = 3
|
||||
action_mode = 0
|
||||
text = "Hide length of songs"
|
||||
|
||||
[node name="HideTicksCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=706322752]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_horizontal = 3
|
||||
action_mode = 0
|
||||
text = "Hide ticks in playbar"
|
||||
|
||||
@@ -123,12 +132,15 @@ layout_mode = 2
|
||||
|
||||
[node name="Label2" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/HBoxContainer" unique_id=1892907487]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Winning Score: "
|
||||
|
||||
[node name="ScoreLabel" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/HBoxContainer" unique_id=444542662]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(48, 0)
|
||||
layout_mode = 2
|
||||
text = "20"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="LowerButton" type="Button" parent="SettingsPanel/ScrollContainer/VBoxContainer/HBoxContainer" unique_id=614806355]
|
||||
unique_name_in_owner = true
|
||||
@@ -151,12 +163,15 @@ layout_mode = 2
|
||||
|
||||
[node name="CacheTitleLabel" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/CacheHBoxContainer" unique_id=365012193]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Predownload songs: "
|
||||
|
||||
[node name="CacheLabel" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/CacheHBoxContainer" unique_id=1558370172]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(48, 0)
|
||||
layout_mode = 2
|
||||
text = "1"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="LowerCacheButton" type="Button" parent="SettingsPanel/ScrollContainer/VBoxContainer/CacheHBoxContainer" unique_id=1990385038]
|
||||
unique_name_in_owner = true
|
||||
@@ -179,12 +194,15 @@ layout_mode = 2
|
||||
|
||||
[node name="InspirationListSpeedTitle" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/InspirationHBoxContainer" unique_id=1397861506]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Inspiration List Speed: "
|
||||
|
||||
[node name="InspirationListSpeedLabel" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/InspirationHBoxContainer" unique_id=867387668]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(48, 0)
|
||||
layout_mode = 2
|
||||
text = "1"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="LowerInspirationSpeedButton" type="Button" parent="SettingsPanel/ScrollContainer/VBoxContainer/InspirationHBoxContainer" unique_id=845209534]
|
||||
unique_name_in_owner = true
|
||||
@@ -221,13 +239,13 @@ popup/item_4/id = 5
|
||||
[node name="FullscreenButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=400304684]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_horizontal = 3
|
||||
text = "Fullscreen"
|
||||
|
||||
[node name="QuickSyncButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer" unique_id=49497448]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_horizontal = 3
|
||||
button_pressed = true
|
||||
action_mode = 0
|
||||
text = "Quick sync"
|
||||
@@ -236,6 +254,6 @@ text = "Quick sync"
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_horizontal = 3
|
||||
disabled = true
|
||||
text = "Local"
|
||||
+15
-16
@@ -1,4 +1,4 @@
|
||||
extends Node
|
||||
extends RefCounted
|
||||
|
||||
class_name SongObject
|
||||
|
||||
@@ -6,9 +6,10 @@ var song_number: int
|
||||
|
||||
var song_title: String
|
||||
|
||||
var game_title: String
|
||||
var soundtrack_title: String
|
||||
|
||||
var players_given_point: Array[int] = []
|
||||
## Ids of the players who got a point for this song.
|
||||
var players_given_point: Array[String] = []
|
||||
|
||||
var is_answered: bool = false
|
||||
|
||||
@@ -18,9 +19,9 @@ var is_playing: bool = false
|
||||
|
||||
var song: AudioStream
|
||||
|
||||
func get_game_title() -> String:
|
||||
func get_soundtrack_title() -> String:
|
||||
if is_answered:
|
||||
return game_title
|
||||
return soundtrack_title
|
||||
else:
|
||||
return "????????"
|
||||
|
||||
@@ -33,24 +34,22 @@ func get_song_title() -> String:
|
||||
func get_song_info() -> String:
|
||||
if Settings.is_debug:
|
||||
var format_string: String = "%d. %s - %s | played: %s | answered: %s"
|
||||
return format_string % [(song_number + 1), game_title, song_title, has_played, is_answered]
|
||||
return format_string % [(song_number + 1), soundtrack_title, song_title, has_played, is_answered]
|
||||
elif is_answered:
|
||||
var format_string: String = "%d. %s - %s"
|
||||
return format_string % [(song_number + 1), game_title, song_title]
|
||||
return format_string % [(song_number + 1), soundtrack_title, song_title]
|
||||
else:
|
||||
return "??? - ???"
|
||||
|
||||
func add_point(id: int) -> void:
|
||||
func add_point(player_id: String) -> void:
|
||||
print("add_point")
|
||||
players_given_point.append(id)
|
||||
players_given_point.sort()
|
||||
if !players_given_point.has(player_id):
|
||||
players_given_point.append(player_id)
|
||||
|
||||
func remove_point(id: int) -> void:
|
||||
func remove_point(player_id: String) -> void:
|
||||
print("remove_point")
|
||||
if players_given_point.has(id):
|
||||
players_given_point.remove_at(players_given_point.find(id))
|
||||
players_given_point.sort()
|
||||
players_given_point.erase(player_id)
|
||||
|
||||
func _to_string() -> String:
|
||||
var format_string: String = "song_number: %d | game_title: %s | song_title: %s | is_answered: %s | has_played: %s | is_playing: %s"
|
||||
return format_string % [(song_number), game_title, song_title, is_answered, has_played, is_playing]
|
||||
var format_string: String = "song_number: %d | soundtrack_title: %s | song_title: %s | is_answered: %s | has_played: %s | is_playing: %s"
|
||||
return format_string % [(song_number), soundtrack_title, song_title, is_answered, has_played, is_playing]
|
||||
+82
-64
@@ -1,11 +1,13 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://dpdem7pdxweb5"]
|
||||
[gd_scene format=3 uid="uid://dpdem7pdxweb5"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c3ejukwth512a" path="res://sync_window.gd" id="1_8ba47"]
|
||||
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_anbfi"]
|
||||
font_size = 25
|
||||
|
||||
[node name="Control" type="Control"]
|
||||
[node name="Control2" type="Control" unique_id=1302914356 node_paths=PackedStringArray("panel")]
|
||||
theme = ExtResource("popup_theme")
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
@@ -13,23 +15,28 @@ anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_8ba47")
|
||||
panel = NodePath("SyncPanel")
|
||||
|
||||
[node name="SyncPanel" type="Panel" parent="."]
|
||||
[node name="SyncPanel" type="Panel" parent="." unique_id=1714634204]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 0
|
||||
offset_right = 1152.0
|
||||
offset_right = 1500.0
|
||||
offset_bottom = 640.0
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="SyncPanel"]
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="SyncPanel" unique_id=1289116349]
|
||||
layout_mode = 0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 640.0
|
||||
offset_left = 20.0
|
||||
offset_top = 8.0
|
||||
offset_right = 1480.0
|
||||
offset_bottom = 632.0
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="SyncVBoxContainer" type="VBoxContainer" parent="SyncPanel/ScrollContainer"]
|
||||
[node name="SyncVBoxContainer" type="VBoxContainer" parent="SyncPanel/ScrollContainer" unique_id=1594735567]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 12
|
||||
|
||||
[node name="SyncTitleLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer"]
|
||||
[node name="SyncTitleLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer" unique_id=2125198054]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 0
|
||||
@@ -37,7 +44,13 @@ text = "Sync"
|
||||
label_settings = SubResource("LabelSettings_anbfi")
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="NoChangesLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer"]
|
||||
[node name="SyncTime" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer" unique_id=1344333200]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Sync time: "
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="NoChangesLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer" unique_id=1059308183]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
@@ -46,105 +59,110 @@ text = "No changes since last sync"
|
||||
label_settings = SubResource("LabelSettings_anbfi")
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="ProgressHBoxContainer" type="HBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer"]
|
||||
[node name="ProgressHBoxContainer" type="HBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer" unique_id=196414805]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SyncProgressBar" type="ProgressBar" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/ProgressHBoxContainer"]
|
||||
[node name="SyncProgressBar" type="ProgressBar" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/ProgressHBoxContainer" unique_id=719135231]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(1000, 0)
|
||||
custom_minimum_size = Vector2(1300, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 6
|
||||
step = 1.0
|
||||
rounded = true
|
||||
|
||||
[node name="SyncHBoxContainer" type="HBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer"]
|
||||
[node name="SyncHBoxContainer" type="HBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer" unique_id=1156646398]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
theme_override_constants/separation = 30
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 16
|
||||
|
||||
[node name="VSeparator6" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GamesAddedVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
[node name="SoundtracksAddedVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=897546696]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="GamesAddedLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/GamesAddedVBox"]
|
||||
[node name="SoundtracksAddedLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksAddedVBox" unique_id=535797768]
|
||||
layout_mode = 2
|
||||
text = "GamesAdded"
|
||||
text = "SoundtracksAdded"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="GamesAddedSeparator" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
[node name="SoundtracksAddedSeparator" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=853642947]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GamesReAddedVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
[node name="SoundtracksReAddedVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=995736806]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="SoundtracksReAddedLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksReAddedVBox" unique_id=1142636525]
|
||||
layout_mode = 2
|
||||
text = "SoundtracksReAdded"
|
||||
|
||||
[node name="SoundtracksReAddedSeparator" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=1872445446]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GamesReAddedLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/GamesReAddedVBox"]
|
||||
[node name="SoundtracksChangedTitleVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=294212033]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "GamesReAdded"
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="GamesReAddedSeparator" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
[node name="SoundtracksChangedTitleLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksChangedTitleVBox" unique_id=696960827]
|
||||
layout_mode = 2
|
||||
text = "SoundtracksChangedTitle"
|
||||
|
||||
[node name="SoundtracksChangedTitleSeparator" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=1730403036]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GamesChangedTitleVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
[node name="SoundtracksChangedContentVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=1470922281]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="SoundtracksChangedContentLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksChangedContentVBox" unique_id=1209639099]
|
||||
layout_mode = 2
|
||||
text = "SoundtracksChangedContent"
|
||||
|
||||
[node name="SoundtracksChangedContentSeparator" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=626592535]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GamesChangedTitleLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/GamesChangedTitleVBox"]
|
||||
[node name="SoundtracksRemovedVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=1174594338]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "GamesChangedTitle"
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="GamesChangedTitleSeparator" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
[node name="SoundtracksRemovedLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/SoundtracksRemovedVBox" unique_id=877713862]
|
||||
layout_mode = 2
|
||||
text = "SoundtracksRemoved"
|
||||
|
||||
[node name="SoundtracksRemovedSeparator" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer" unique_id=839653516]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GamesChangedContentVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GamesChangedContentLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/GamesChangedContentVBox"]
|
||||
layout_mode = 2
|
||||
text = "GamesChangedContent"
|
||||
|
||||
[node name="GamesChangedContentSeparator" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GamesRemovedVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GamesRemovedLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/GamesRemovedVBox"]
|
||||
layout_mode = 2
|
||||
text = "GamesRemoved"
|
||||
|
||||
[node name="GamesRemovedSeparator" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SyncTime" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Sync time: "
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="CatchedErrorsVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer"]
|
||||
[node name="CatchedErrorsVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer" unique_id=63733711]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="CatchedErrorsLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/CatchedErrorsVBox"]
|
||||
[node name="CatchedErrorsLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/CatchedErrorsVBox" unique_id=1784389644]
|
||||
layout_mode = 2
|
||||
text = "CatchedErrors"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="SyncTimer" type="Timer" parent="."]
|
||||
[node name="RestartSyncButton" type="Button" parent="SyncPanel/ScrollContainer/SyncVBoxContainer" unique_id=372442237]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(200, 80)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
text = "Restart Sync"
|
||||
|
||||
[node name="SyncTimer" type="Timer" parent="." unique_id=1955169547]
|
||||
unique_name_in_owner = true
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
# Testing
|
||||
|
||||
Tests use [GdUnit4](https://github.com/godot-gdunit-labs/gdUnit4) 6.2.1, committed in
|
||||
`addons/gdUnit4/`. The whole suite runs headless in about 10 seconds.
|
||||
|
||||
## Running the tests
|
||||
|
||||
```bash
|
||||
./run_tests.sh # everything
|
||||
./run_tests.sh res://tests/unit # only unit tests
|
||||
./run_tests.sh res://tests/unit/test_search.gd # one suite
|
||||
```
|
||||
|
||||
The script finds Godot through `$GODOT`, then `godot` on your PATH, then
|
||||
`/Applications/Godot.app` or `/Applications/Godot_mono.app`. Reports are written to
|
||||
`reports/` (ignored by git).
|
||||
|
||||
In the editor, open the GdUnit4 panel or right-click the `tests` folder and choose
|
||||
**Run Tests**.
|
||||
|
||||
CI runs the suite in the `test` job of `.gitea/workflows/gitea-build.yaml` on every push
|
||||
and pull request to `main` and `develop`. The `build` job only starts once the tests
|
||||
pass.
|
||||
|
||||
## Layout
|
||||
|
||||
| Folder | Contents |
|
||||
|---|---|
|
||||
| `tests/unit/` | Logic without scenes: playlist, songs, player scoring, time formatting, search, the server client |
|
||||
| `tests/integration/` | Real scenes (`MainWindow.tscn`, `SyncWindow.tscn`) run against a fake server, including the point keybinds |
|
||||
| `tests/fakes/` | `FakeHttpTransport`, `AutoloadSnapshot` and the `MainWindowTestSuite` base class |
|
||||
| `tests/data/` | Files the fake server sends, e.g. a short MP3 |
|
||||
|
||||
## Faking the MusicServer
|
||||
|
||||
All server calls go through the `MusicServerClient` autoload, which sends requests with
|
||||
a swappable `transport`. Tests install a `FakeHttpTransport` that answers from canned
|
||||
responses and records what was requested:
|
||||
|
||||
```gdscript
|
||||
var server: FakeHttpTransport = FakeHttpTransport.new().install()
|
||||
server.respond_json("/music/list", [{"SongNo": 0, "Soundtrack": "Chrono Trigger", "Song": "Frog's Theme"}])
|
||||
server.respond_bytes("/music/rand", mp3_bytes)
|
||||
|
||||
# ... run the code under test ...
|
||||
|
||||
assert_int(server.count("/music/rand")).is_equal(1)
|
||||
server.uninstall()
|
||||
```
|
||||
|
||||
Paths without a response get a 404, so the code under test sees a failed request.
|
||||
|
||||
## Writing tests
|
||||
|
||||
- Settings and Playlist are global. Tests that change them restore them afterwards, either
|
||||
by hand or with `AutoloadSnapshot.take([Settings, Playlist])` and `restore()`.
|
||||
- Main window tests extend `MainWindowTestSuite`, which starts the window against the fake
|
||||
server and has helpers such as `_add_player()`, `_remove_player()` and
|
||||
`_give_point_with_keybind()`.
|
||||
- Player ids are UUID strings that never change. A player's position in
|
||||
`Settings.player_array` only decides their keybind (Alt+1 for the first row and so on),
|
||||
so tests check both: that points land on the right player id, and that keybinds follow
|
||||
the rows when players are added or removed.
|
||||
+4
-2
@@ -2,11 +2,13 @@
|
||||
|
||||
[ext_resource type="Script" uid="uid://dxpb22xbwf3t1" path="res://winner_window.gd" id="1_winner_script"]
|
||||
[ext_resource type="Texture2D" uid="uid://r4as0nmtoa7p" path="res://noCharacter.png" id="2_no_character"]
|
||||
[ext_resource type="Theme" path="res://popup_theme.tres" id="popup_theme"]
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_hr75l"]
|
||||
font_size = 35
|
||||
|
||||
[node name="WinnerControl" type="Control" unique_id=1111111111]
|
||||
[node name="WinnerControl" type="Control" unique_id=1111111111 node_paths=PackedStringArray("panel")]
|
||||
theme = ExtResource("popup_theme")
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
@@ -14,6 +16,7 @@ anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_winner_script")
|
||||
panel = NodePath("WinnerPanel")
|
||||
|
||||
[node name="WinnerPanel" type="Panel" parent="." unique_id=89212869]
|
||||
unique_name_in_owner = true
|
||||
@@ -40,7 +43,6 @@ stretch_mode = 5
|
||||
|
||||
[node name="WinnerLabel" type="Label" parent="WinnerPanel/VBoxContainer" unique_id=1437597286]
|
||||
unique_name_in_owner = true
|
||||
texture_filter = 1
|
||||
layout_mode = 2
|
||||
text = "Winner!"
|
||||
label_settings = SubResource("LabelSettings_hr75l")
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# GdUnit4 specific ignores
|
||||
*.gdunit
|
||||
*.gdunit.*
|
||||
/test_results.*
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Mike Schulze
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env -S godot -s
|
||||
extends SceneTree
|
||||
|
||||
|
||||
var _cli_runner: GdUnitTestCIRunner
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED)
|
||||
_cli_runner = GdUnitTestCIRunner.new()
|
||||
root.add_child(_cli_runner)
|
||||
|
||||
|
||||
# do not use print statements on _finalize it results in random crashes
|
||||
func _finalize() -> void:
|
||||
queue_delete(_cli_runner)
|
||||
if OS.is_stdout_verbose():
|
||||
prints("Finallize ..")
|
||||
prints("-Orphan nodes report-----------------------")
|
||||
Window.print_orphan_nodes()
|
||||
prints("Finallize .. done")
|
||||
@@ -0,0 +1 @@
|
||||
uid://bipm0dv6p07il
|
||||
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env -S godot -s
|
||||
extends MainLoop
|
||||
|
||||
const GdUnitTools := preload("res://addons/gdUnit4/src/core/GdUnitTools.gd")
|
||||
|
||||
# gdlint: disable=max-line-length
|
||||
const LOG_FRAME_TEMPLATE = """
|
||||
<!DOCTYPE html>
|
||||
<html style="display: inline-grid;">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Godot Logging</title>
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
</head>
|
||||
|
||||
<body style="background-color: #eee;">
|
||||
<div class="godot-report-frame"">
|
||||
${content}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
const NO_LOG_MESSAGE = """
|
||||
<h3>No logging available!</h3>
|
||||
</br>
|
||||
<p>In order for logging to take place, you must activate the Activate file logging option in the project settings.</p>
|
||||
<p>You can enable the logging under:
|
||||
<b>Project Settings</b> > <b>Debug</b> > <b>File Logging</b> > <b>Enable File Logging</b> in the project settings.</p>
|
||||
"""
|
||||
|
||||
#warning-ignore-all:return_value_discarded
|
||||
var _cmd_options := CmdOptions.new([
|
||||
CmdOption.new(
|
||||
"-rd, --report-directory",
|
||||
"-rd <directory>",
|
||||
"Specifies the output directory in which the reports are to be written. The default is res://reports/.",
|
||||
TYPE_STRING,
|
||||
true
|
||||
)
|
||||
])
|
||||
|
||||
|
||||
var _report_root_path: String
|
||||
var _current_report_path: String
|
||||
var _debug_cmd_args := PackedStringArray()
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
set_report_directory(GdUnitFileAccess.current_dir() + "reports")
|
||||
set_current_report_path()
|
||||
|
||||
|
||||
func _process(_delta: float) -> bool:
|
||||
# check if reports exists
|
||||
if not reports_available():
|
||||
prints("no reports found")
|
||||
return true
|
||||
|
||||
# only process if godot logging is enabled
|
||||
if not GdUnitSettings.is_log_enabled():
|
||||
write_report(NO_LOG_MESSAGE, "")
|
||||
return true
|
||||
|
||||
# parse possible custom report path,
|
||||
var cmd_parser := CmdArgumentParser.new(_cmd_options, "GdUnitCmdTool.gd")
|
||||
# ignore erros and exit quitly
|
||||
if cmd_parser.parse(get_cmdline_args(), true).is_error():
|
||||
return true
|
||||
CmdCommandHandler.new(_cmd_options).register_cb("-rd", set_report_directory)
|
||||
|
||||
var godot_log_file := scan_latest_godot_log()
|
||||
var result := read_log_file_content(godot_log_file)
|
||||
if result.is_error():
|
||||
write_report(result.error_message(), godot_log_file)
|
||||
return true
|
||||
write_report(result.value_as_string(), godot_log_file)
|
||||
return true
|
||||
|
||||
|
||||
func set_current_report_path() -> void:
|
||||
# scan for latest report directory
|
||||
var iteration := GdUnitFileAccess.find_last_path_index(
|
||||
_report_root_path, GdUnitConstants.REPORT_DIR_PREFIX
|
||||
)
|
||||
_current_report_path = "%s/%s%d" % [_report_root_path, GdUnitConstants.REPORT_DIR_PREFIX, iteration]
|
||||
|
||||
|
||||
func set_report_directory(path: String) -> void:
|
||||
_report_root_path = path
|
||||
|
||||
|
||||
func get_log_report_html() -> String:
|
||||
return _current_report_path + "/godot_report_log.html"
|
||||
|
||||
|
||||
func reports_available() -> bool:
|
||||
return DirAccess.dir_exists_absolute(_report_root_path)
|
||||
|
||||
|
||||
func scan_latest_godot_log() -> String:
|
||||
var path := GdUnitSettings.get_log_path().get_base_dir()
|
||||
var files_sorted := Array()
|
||||
for file in GdUnitFileAccess.scan_dir(path):
|
||||
var file_name := "%s/%s" % [path, file]
|
||||
files_sorted.append(file_name)
|
||||
# sort by name, the name contains the timestamp so we sort at the end by timestamp
|
||||
files_sorted.sort()
|
||||
return files_sorted.back()
|
||||
|
||||
|
||||
func read_log_file_content(log_file: String) -> GdUnitResult:
|
||||
var file := FileAccess.open(log_file, FileAccess.READ)
|
||||
if file == null:
|
||||
return GdUnitResult.error(
|
||||
"Can't find log file '%s'. Error: %s"
|
||||
% [log_file, error_string(FileAccess.get_open_error())]
|
||||
)
|
||||
var content := "<pre>" + file.get_as_text()
|
||||
# patch out console format codes
|
||||
for color_index in range(0, 256):
|
||||
var to_replace := "[38;5;%dm" % color_index
|
||||
content = content.replace(to_replace, "")
|
||||
content += "</pre>"
|
||||
content = content\
|
||||
.replace("[0m", "")\
|
||||
.replace(GdUnitCSIMessageWriter.CSI_BOLD, "")\
|
||||
.replace(GdUnitCSIMessageWriter.CSI_ITALIC, "")\
|
||||
.replace(GdUnitCSIMessageWriter.CSI_UNDERLINE, "")
|
||||
return GdUnitResult.success(content)
|
||||
|
||||
|
||||
func write_report(content: String, godot_log_file: String) -> GdUnitResult:
|
||||
var file := FileAccess.open(get_log_report_html(), FileAccess.WRITE)
|
||||
if file == null:
|
||||
return GdUnitResult.error(
|
||||
"Can't open to write '%s'. Error: %s"
|
||||
% [get_log_report_html(), error_string(FileAccess.get_open_error())]
|
||||
)
|
||||
var report_html := LOG_FRAME_TEMPLATE.replace("${content}", content)
|
||||
file.store_string(report_html)
|
||||
_update_index_html(godot_log_file)
|
||||
return GdUnitResult.success(file)
|
||||
|
||||
|
||||
func _update_index_html(godot_log_file: String) -> void:
|
||||
var index_path := "%s/index.html" % _current_report_path
|
||||
var index_file := FileAccess.open(index_path, FileAccess.READ_WRITE)
|
||||
if index_file == null:
|
||||
push_error(
|
||||
"Can't add log path '%s' to `%s`. Error: %s"
|
||||
% [godot_log_file, index_path, error_string(FileAccess.get_open_error())]
|
||||
)
|
||||
return
|
||||
var content := index_file.get_as_text()\
|
||||
.replace("${log_report}", get_log_report_html())\
|
||||
.replace("${godot_log_file}", godot_log_file)
|
||||
# overide it
|
||||
index_file.seek(0)
|
||||
index_file.store_string(content)
|
||||
|
||||
|
||||
func get_cmdline_args() -> PackedStringArray:
|
||||
if _debug_cmd_args.is_empty():
|
||||
return OS.get_cmdline_args()
|
||||
return _debug_cmd_args
|
||||
@@ -0,0 +1 @@
|
||||
uid://cyft2xqn77ffl
|
||||
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="gdUnit4"
|
||||
description="Unit Testing Framework for Godot Scripts"
|
||||
author="Mike Schulze"
|
||||
version="6.2.1"
|
||||
script="plugin.gd"
|
||||
@@ -0,0 +1,90 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
var _gd_inspector: Control
|
||||
var _gd_console: Control
|
||||
var _filesystem_context_menu: EditorContextMenuPlugin
|
||||
var _editor_context_menu: EditorContextMenuPlugin
|
||||
var _editor_code_context_menu: EditorContextMenuPlugin
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
var inferred_declaration := GdUnitSettings.validate_is_inferred_declaration_enabled()
|
||||
if inferred_declaration.is_error():
|
||||
printerr(inferred_declaration.error_message())
|
||||
printerr("Loading GdUnit4 Plugin failed.")
|
||||
return
|
||||
|
||||
if check_running_in_test_env():
|
||||
@warning_ignore("return_value_discarded")
|
||||
GdUnitCSIMessageWriter.new().prints_warning("It was recognized that GdUnit4 is running in a test environment, therefore the GdUnit4 plugin will not be executed!")
|
||||
return
|
||||
|
||||
if Engine.get_version_info().hex < 0x40500:
|
||||
prints("This GdUnit4 plugin version '%s' requires Godot version '4.5' or higher to run." % GdUnit4Version.current())
|
||||
return
|
||||
GdUnitSettings.setup()
|
||||
GdUnitEditorColorTheme.setup()
|
||||
# Install the GdUnit Inspector
|
||||
_gd_inspector = (load("res://addons/gdUnit4/src/ui/GdUnitInspector.tscn") as PackedScene).instantiate()
|
||||
_add_context_menus()
|
||||
add_control_to_dock(EditorPlugin.DOCK_SLOT_LEFT_UR, _gd_inspector)
|
||||
# Install the GdUnit Console
|
||||
_gd_console = (load("res://addons/gdUnit4/src/ui/GdUnitConsole.tscn") as PackedScene).instantiate()
|
||||
var control: Control = add_control_to_bottom_panel(_gd_console, "gdUnitConsole")
|
||||
@warning_ignore("unsafe_method_access")
|
||||
await _gd_console.setup_update_notification(control)
|
||||
if GdUnit4CSharpApiLoader.is_api_loaded():
|
||||
prints("GdUnit4Net version '%s' loaded." % GdUnit4CSharpApiLoader.version())
|
||||
else:
|
||||
prints("No GdUnit4Net found.")
|
||||
# Connect to be notified for script changes to be able to discover new tests
|
||||
GdUnitTestDiscoverGuard.instance()
|
||||
@warning_ignore("return_value_discarded")
|
||||
resource_saved.connect(_on_resource_saved)
|
||||
prints("Loading GdUnit4 Plugin success")
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if check_running_in_test_env():
|
||||
return
|
||||
if is_instance_valid(_gd_inspector):
|
||||
remove_control_from_docks(_gd_inspector)
|
||||
_gd_inspector.free()
|
||||
_remove_context_menus()
|
||||
if is_instance_valid(_gd_console):
|
||||
remove_control_from_bottom_panel(_gd_console)
|
||||
_gd_console.free()
|
||||
var gdUnitTools: GDScript = load("res://addons/gdUnit4/src/core/GdUnitTools.gd")
|
||||
@warning_ignore("unsafe_method_access")
|
||||
gdUnitTools.dispose_all(true)
|
||||
prints("Unload GdUnit4 Plugin success")
|
||||
|
||||
|
||||
func check_running_in_test_env() -> bool:
|
||||
var args: PackedStringArray = OS.get_cmdline_args()
|
||||
args.append_array(OS.get_cmdline_user_args())
|
||||
return DisplayServer.get_name() == "headless" or args.has("--selftest") or args.has("--add") or args.has("-a") or args.has("--quit-after") or args.has("--import")
|
||||
|
||||
|
||||
func _add_context_menus() -> void:
|
||||
_filesystem_context_menu = preload("res://addons/gdUnit4/src/ui/menu/GdUnitEditorFileSystemContextMenuHandler.gd").new()
|
||||
_editor_context_menu = preload("res://addons/gdUnit4/src/ui/menu/GdUnitScriptEditorContextMenuHandler.gd").new()
|
||||
_editor_code_context_menu = preload("res://addons/gdUnit4/src/ui/menu/GdUnitScriptEditorContextMenuHandler.gd").new()
|
||||
add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_FILESYSTEM, _filesystem_context_menu)
|
||||
add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR, _editor_context_menu)
|
||||
add_context_menu_plugin(EditorContextMenuPlugin.CONTEXT_SLOT_SCRIPT_EDITOR_CODE, _editor_code_context_menu)
|
||||
|
||||
|
||||
func _remove_context_menus() -> void:
|
||||
if is_instance_valid(_filesystem_context_menu):
|
||||
remove_context_menu_plugin(_filesystem_context_menu)
|
||||
if is_instance_valid(_editor_context_menu):
|
||||
remove_context_menu_plugin(_editor_context_menu)
|
||||
if is_instance_valid(_editor_code_context_menu):
|
||||
remove_context_menu_plugin(_editor_code_context_menu)
|
||||
|
||||
|
||||
func _on_resource_saved(resource: Resource) -> void:
|
||||
if resource is Script:
|
||||
await GdUnitTestDiscoverGuard.instance().discover(resource as Script)
|
||||
@@ -0,0 +1 @@
|
||||
uid://qmkf30a5bmap
|
||||
@@ -0,0 +1,65 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
:: Initialize variables
|
||||
set "godot_binary="
|
||||
set "filtered_args="
|
||||
|
||||
:: Process all arguments
|
||||
set "i=0"
|
||||
:parse_args
|
||||
if "%~1"=="" goto end_parse_args
|
||||
|
||||
if "%~1"=="--godot_binary" (
|
||||
set "godot_binary=%~2"
|
||||
shift
|
||||
shift
|
||||
) else (
|
||||
set "filtered_args=!filtered_args! %~1"
|
||||
shift
|
||||
)
|
||||
goto parse_args
|
||||
:end_parse_args
|
||||
|
||||
:: If --godot_binary wasn't provided, fallback to environment variable
|
||||
if "!godot_binary!"=="" (
|
||||
set "godot_binary=%GODOT_BIN%"
|
||||
)
|
||||
|
||||
:: Check if we have a godot_binary value from any source
|
||||
if "!godot_binary!"=="" (
|
||||
echo Godot binary path is not specified.
|
||||
echo Please either:
|
||||
echo - Set the environment variable: set GODOT_BIN=C:\path\to\godot.exe
|
||||
echo - Or use the --godot_binary argument: --godot_binary C:\path\to\godot.exe
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: Check if the Godot binary exists
|
||||
if not exist "!godot_binary!" (
|
||||
echo Error: The specified Godot binary '!godot_binary!' does not exist.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: Get Godot version and check if it's a mono build
|
||||
for /f "tokens=*" %%i in ('"!godot_binary!" --version') do set GODOT_VERSION=%%i
|
||||
echo !GODOT_VERSION! | findstr /I "mono" >nul
|
||||
if !errorlevel! equ 0 (
|
||||
echo Godot .NET detected
|
||||
echo Compiling c# classes ... Please Wait
|
||||
dotnet build --debug
|
||||
echo done !errorlevel!
|
||||
)
|
||||
|
||||
:: Run the tests with the filtered arguments.
|
||||
:: --remote-debug tcp://127.0.0.1:0 prevents Godot from activating its local interactive
|
||||
:: CLI debugger, which would cause an endless 'debug>' loop on script parse errors.
|
||||
:: Port 0 is used intentionally as it is never bound, so the connection is always refused.
|
||||
"!godot_binary!" --path . -s -d --remote-debug tcp://127.0.0.1:0 res://addons/gdUnit4/bin/GdUnitCmdTool.gd !filtered_args!
|
||||
set exit_code=%ERRORLEVEL%
|
||||
echo Run tests ends with %exit_code%
|
||||
|
||||
:: Run the copy log command
|
||||
"!godot_binary!" --headless --path . --quiet -s res://addons/gdUnit4/bin/GdUnitCopyLog.gd !filtered_args! > nul
|
||||
set exit_code2=%ERRORLEVEL%
|
||||
exit /b %exit_code%
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Check for command-line argument
|
||||
godot_binary=""
|
||||
filtered_args=""
|
||||
|
||||
# Process all arguments with a more compatible approach
|
||||
while [ $# -gt 0 ]; do
|
||||
if [ "$1" = "--godot_binary" ] && [ $# -gt 1 ]; then
|
||||
# Get the next argument as the value
|
||||
godot_binary="$2"
|
||||
shift 2
|
||||
else
|
||||
# Keep non-godot_binary arguments for passing to Godot
|
||||
filtered_args="$filtered_args $1"
|
||||
shift
|
||||
fi
|
||||
done
|
||||
|
||||
# If --godot_binary wasn't provided, fallback to environment variable
|
||||
if [ -z "$godot_binary" ]; then
|
||||
godot_binary="$GODOT_BIN"
|
||||
fi
|
||||
|
||||
# Check if we have a godot_binary value from any source
|
||||
if [ -z "$godot_binary" ]; then
|
||||
echo "Godot binary path is not specified."
|
||||
echo "Please either:"
|
||||
echo " - Set the environment variable: export GODOT_BIN=/path/to/godot"
|
||||
echo " - Or use the --godot_binary argument: --godot_binary /path/to/godot"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if the Godot binary exists and is executable
|
||||
if [ ! -f "$godot_binary" ]; then
|
||||
echo "Error: The specified Godot binary '$godot_binary' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$godot_binary" ]; then
|
||||
echo "Error: The specified Godot binary '$godot_binary' is not executable."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get Godot version and check if it's a .NET build
|
||||
GODOT_VERSION=$("$godot_binary" --version)
|
||||
if echo "$GODOT_VERSION" | grep -i "mono" > /dev/null; then
|
||||
echo "Godot .NET detected"
|
||||
echo "Compiling c# classes ... Please Wait"
|
||||
dotnet build --debug
|
||||
echo "done $?"
|
||||
fi
|
||||
|
||||
# Run the tests with the filtered arguments.
|
||||
# --remote-debug tcp://127.0.0.1:0 prevents Godot from activating its local interactive
|
||||
# CLI debugger, which would cause an endless 'debug>' loop on script parse errors.
|
||||
# Port 0 is used intentionally as it is never bound, so the connection is always refused.
|
||||
"$godot_binary" --path . -s -d --remote-debug tcp://127.0.0.1:0 res://addons/gdUnit4/bin/GdUnitCmdTool.gd $filtered_args
|
||||
exit_code=$?
|
||||
echo "Run tests ends with $exit_code"
|
||||
|
||||
# Run the copy log command
|
||||
"$godot_binary" --headless --path . --quiet -s res://addons/gdUnit4/bin/GdUnitCopyLog.gd $filtered_args > /dev/null
|
||||
exit_code2=$?
|
||||
exit $exit_code
|
||||
@@ -0,0 +1,12 @@
|
||||
class_name Comparator
|
||||
extends Resource
|
||||
|
||||
enum {
|
||||
EQUAL,
|
||||
LESS_THAN,
|
||||
LESS_EQUAL,
|
||||
GREATER_THAN,
|
||||
GREATER_EQUAL,
|
||||
BETWEEN_EQUAL,
|
||||
NOT_BETWEEN_EQUAL,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dapcyf8uxkfwt
|
||||
@@ -0,0 +1,80 @@
|
||||
## Factory class providing convenient static methods to create various fuzzer instances.[br]
|
||||
##
|
||||
## Fuzzers is a utility class that simplifies the creation of different fuzzer types
|
||||
## for testing purposes. It provides static factory methods that create pre-configured
|
||||
## fuzzers with sensible defaults, making it easier to set up fuzz testing in your
|
||||
## test suites without manually instantiating each fuzzer type.[br]
|
||||
##
|
||||
## This class acts as a central access point for all fuzzer types, improving code
|
||||
## readability and reducing boilerplate in test cases.[br]
|
||||
##
|
||||
## @tutorial(Fuzzing Testing): https://en.wikipedia.org/wiki/Fuzzing
|
||||
class_name Fuzzers
|
||||
extends Resource
|
||||
|
||||
|
||||
## Generates random strings with length between [param min_length] and
|
||||
## [param max_length] (inclusive), using characters from [param charset].
|
||||
## See [StringFuzzer] for detailed documentation and examples.
|
||||
static func rand_str(min_length: int, max_length: int, charset := StringFuzzer.DEFAULT_CHARSET) -> StringFuzzer:
|
||||
return StringFuzzer.new(min_length, max_length, charset)
|
||||
|
||||
|
||||
## Creates a [BoolFuzzer] for generating random boolean values.[br]
|
||||
##
|
||||
## See [BoolFuzzer] for detailed documentation and examples.
|
||||
static func boolean() -> BoolFuzzer:
|
||||
return BoolFuzzer.new()
|
||||
|
||||
|
||||
## Creates an [IntFuzzer] for generating random integers within a range.[br]
|
||||
##
|
||||
## Generates random integers between [param from] and [param to] (inclusive)
|
||||
## using [constant IntFuzzer.NORMAL] mode.
|
||||
## See [IntFuzzer] for detailed documentation and examples.
|
||||
static func rangei(from: int, to: int) -> IntFuzzer:
|
||||
return IntFuzzer.new(from, to)
|
||||
|
||||
|
||||
## Creates a [FloatFuzzer] for generating random floats within a range.[br]
|
||||
##
|
||||
## Generates random float values between [param from] and [param to] (inclusive).
|
||||
## See [FloatFuzzer] for detailed documentation and examples.
|
||||
static func rangef(from: float, to: float) -> FloatFuzzer:
|
||||
return FloatFuzzer.new(from, to)
|
||||
|
||||
|
||||
## Creates a [Vector2Fuzzer] for generating random 2D vectors within a range.[br]
|
||||
##
|
||||
## Generates random Vector2 values where each component is bounded by
|
||||
## [param from] and [param to] (inclusive).
|
||||
## See [Vector2Fuzzer] for detailed documentation and examples.
|
||||
static func rangev2(from: Vector2, to: Vector2) -> Vector2Fuzzer:
|
||||
return Vector2Fuzzer.new(from, to)
|
||||
|
||||
|
||||
## Creates a [Vector3Fuzzer] for generating random 3D vectors within a range.[br]
|
||||
##
|
||||
## Generates random Vector3 values where each component is bounded by
|
||||
## [param from] and [param to] (inclusive).
|
||||
## See [Vector3Fuzzer] for detailed documentation and examples.
|
||||
static func rangev3(from: Vector3, to: Vector3) -> Vector3Fuzzer:
|
||||
return Vector3Fuzzer.new(from, to)
|
||||
|
||||
|
||||
## Creates an [IntFuzzer] that generates only even integers.[br]
|
||||
##
|
||||
## Generates random even integers between [param from] and [param to] (inclusive)
|
||||
## using [constant IntFuzzer.EVEN] mode.
|
||||
## See [IntFuzzer] for detailed documentation about even number generation.
|
||||
static func eveni(from: int, to: int) -> IntFuzzer:
|
||||
return IntFuzzer.new(from, to, IntFuzzer.EVEN)
|
||||
|
||||
|
||||
## Creates an [IntFuzzer] that generates only odd integers.[br]
|
||||
##
|
||||
## Generates random odd integers between [param from] and [param to] (inclusive)
|
||||
## using [constant IntFuzzer.ODD] mode.
|
||||
## See [IntFuzzer] for detailed documentation about odd number generation.
|
||||
static func oddi(from: int, to: int) -> IntFuzzer:
|
||||
return IntFuzzer.new(from, to, IntFuzzer.ODD)
|
||||
@@ -0,0 +1 @@
|
||||
uid://b1ctmh8qmi5d0
|
||||
@@ -0,0 +1,122 @@
|
||||
## An Assertion Tool to verify array values
|
||||
@abstract class_name GdUnitArrayAssert
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is null.
|
||||
@abstract func is_null() -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
@abstract func is_not_null() -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array is equal to the given one.
|
||||
@abstract func is_equal(...expected: Array) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array is equal to the given one, ignoring case considerations.
|
||||
@abstract func is_equal_ignoring_case(...expected: Array) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array is not equal to the given one.
|
||||
@abstract func is_not_equal(...expected: Array) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array is not equal to the given one, ignoring case considerations.
|
||||
@abstract func is_not_equal_ignoring_case(...expected: Array) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Overrides the default failure message by given custom message.
|
||||
@abstract func override_failure_message(message: String) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.
|
||||
@abstract func append_failure_message(message: String) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array is empty, it has a size of 0.
|
||||
@abstract func is_empty() -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array is not empty, it has a size of minimum 1.
|
||||
@abstract func is_not_empty() -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array is the same. [br]
|
||||
## Compares the current by object reference equals
|
||||
@abstract func is_same(expected: Variant) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array is NOT the same. [br]
|
||||
## Compares the current by object reference equals
|
||||
@abstract func is_not_same(expected: Variant) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array has a size of given value.
|
||||
@abstract func has_size(expectd: int) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array contains the given values, in any order.[br]
|
||||
## The values are compared by deep parameter comparision, for object reference compare you have to use [method contains_same]
|
||||
@abstract func contains(...expected: Array) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array contains exactly only the given values and nothing else, in same order.[br]
|
||||
## The values are compared by deep parameter comparision, for object reference compare you have to use [method contains_same_exactly]
|
||||
@abstract func contains_exactly(...expected: Array) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array contains exactly only the given values and nothing else, in any order.[br]
|
||||
## The values are compared by deep parameter comparision, for object reference compare you have to use [method contains_same_exactly_in_any_order]
|
||||
@abstract func contains_exactly_in_any_order(...expected: Array) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array contains the given values, in any order.[br]
|
||||
## The values are compared by object reference, for deep parameter comparision use [method contains]
|
||||
@abstract func contains_same(...expected: Array) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array contains exactly only the given values and nothing else, in same order.[br]
|
||||
## The values are compared by object reference, for deep parameter comparision use [method contains_exactly]
|
||||
@abstract func contains_same_exactly(...expected: Array) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array contains exactly only the given values and nothing else, in any order.[br]
|
||||
## The values are compared by object reference, for deep parameter comparision use [method contains_exactly_in_any_order]
|
||||
@abstract func contains_same_exactly_in_any_order(...expected: Array) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array do NOT contains the given values, in any order.[br]
|
||||
## The values are compared by deep parameter comparision, for object reference compare you have to use [method not_contains_same]
|
||||
## [b]Example:[/b]
|
||||
## [codeblock]
|
||||
## # will succeed
|
||||
## assert_array([1, 2, 3, 4, 5]).not_contains(6)
|
||||
## # will fail
|
||||
## assert_array([1, 2, 3, 4, 5]).not_contains(2, 6)
|
||||
## [/codeblock]
|
||||
@abstract func not_contains(...expected: Array) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Verifies that the current Array do NOT contains the given values, in any order.[br]
|
||||
## The values are compared by object reference, for deep parameter comparision use [method not_contains]
|
||||
## [b]Example:[/b]
|
||||
## [codeblock]
|
||||
## # will succeed
|
||||
## assert_array([1, 2, 3, 4, 5]).not_contains(6)
|
||||
## # will fail
|
||||
## assert_array([1, 2, 3, 4, 5]).not_contains(2, 6)
|
||||
## [/codeblock]
|
||||
@abstract func not_contains_same(...expected: Array) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Extracts all values by given function name and optional arguments into a new ArrayAssert.
|
||||
## If the elements not accessible by `func_name` the value is converted to `"n.a"`, expecting null values
|
||||
@abstract func extract(func_name: String, ...func_args: Array) -> GdUnitArrayAssert
|
||||
|
||||
|
||||
## Extracts all values by given extractor's into a new ArrayAssert.
|
||||
## If the elements not extractable than the value is converted to `"n.a"`, expecting null values
|
||||
## -- The argument type is Array[GdUnitValueExtractor]
|
||||
@abstract func extractv(...extractors: Array) -> GdUnitArrayAssert
|
||||
@@ -0,0 +1 @@
|
||||
uid://cdmo2fwnaxtpe
|
||||
@@ -0,0 +1,47 @@
|
||||
## Base interface of all GdUnit asserts
|
||||
@abstract class_name GdUnitAssert
|
||||
extends RefCounted
|
||||
|
||||
|
||||
## Verifies that the current value is null.
|
||||
@abstract func is_null() -> GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
@abstract func is_not_null() -> GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to expected one.
|
||||
@abstract func is_equal(expected: Variant) -> GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to expected one.
|
||||
@abstract func is_not_equal(expected: Variant) -> GdUnitAssert
|
||||
|
||||
|
||||
## Overrides the default failure message by given custom message.[br]
|
||||
## This function allows you to replace the automatically generated failure message with a more specific
|
||||
## or user-friendly message that better describes the test failure context.[br]
|
||||
## Usage:
|
||||
## [codeblock]
|
||||
## # Override with custom context-specific message
|
||||
## func test_player_inventory():
|
||||
## assert_that(player.get_item_count("sword"))\
|
||||
## .override_failure_message("Player should have exactly one sword")\
|
||||
## .is_equal(1)
|
||||
## [/codeblock]
|
||||
@abstract func override_failure_message(message: String) -> GdUnitAssert
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.[br]
|
||||
## This can be used to add additional information to the generated failure message
|
||||
## while keeping the original assertion details for better debugging context.[br]
|
||||
## Usage:
|
||||
## [codeblock]
|
||||
## # Add context to existing failure message
|
||||
## func test_player_health():
|
||||
## assert_that(player.health)\
|
||||
## .append_failure_message("Player was damaged by: %s" % last_damage_source)\
|
||||
## .is_greater(0)
|
||||
## [/codeblock]
|
||||
@abstract func append_failure_message(message: String) -> GdUnitAssert
|
||||
@@ -0,0 +1 @@
|
||||
uid://doph0d61vwg81
|
||||
@@ -0,0 +1,73 @@
|
||||
class_name GdUnitAwaiter
|
||||
extends RefCounted
|
||||
|
||||
|
||||
# Waits for a specified signal in an interval of 50ms sent from the <source>, and terminates with an error after the specified timeout has elapsed.
|
||||
# source: the object from which the signal is emitted
|
||||
# signal_name: signal name
|
||||
# args: the expected signal arguments as an array
|
||||
# timeout: the timeout in ms, default is set to 2000ms
|
||||
func await_signal_on(source :Object, signal_name :String, args :Array = [], timeout_millis :int = 2000) -> Variant:
|
||||
# fail fast if the given source instance invalid
|
||||
var assert_that := GdUnitAssertImpl.new(signal_name)
|
||||
var stack_trace := GdUnitStackTrace.new()
|
||||
var line_number := stack_trace.get_line_number()
|
||||
if not is_instance_valid(source):
|
||||
@warning_ignore("return_value_discarded")
|
||||
assert_that.report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number)
|
||||
return await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
# fail fast if the given source instance invalid
|
||||
if not is_instance_valid(source):
|
||||
@warning_ignore("return_value_discarded")
|
||||
assert_that.report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number)
|
||||
return await await_idle_frame()
|
||||
var awaiter := GdUnitSignalAwaiter.new(timeout_millis)
|
||||
var value :Variant = await awaiter.on_signal(source, signal_name, args)
|
||||
if awaiter.is_interrupted():
|
||||
var failure := "await_signal_on(%s, %s) timed out after %sms" % [signal_name, args, timeout_millis]
|
||||
@warning_ignore("return_value_discarded")
|
||||
assert_that.report_error(failure, line_number)
|
||||
return value
|
||||
|
||||
|
||||
# Waits for a specified signal sent from the <source> between idle frames and aborts with an error after the specified timeout has elapsed
|
||||
# source: the object from which the signal is emitted
|
||||
# signal_name: signal name
|
||||
# args: the expected signal arguments as an array
|
||||
# timeout: the timeout in ms, default is set to 2000ms
|
||||
func await_signal_idle_frames(source :Object, signal_name :String, args :Array = [], timeout_millis :int = 2000) -> Variant:
|
||||
var line_number := GdUnitStackTrace.new().get_line_number()
|
||||
# fail fast if the given source instance invalid
|
||||
if not is_instance_valid(source):
|
||||
@warning_ignore("return_value_discarded")
|
||||
GdUnitAssertImpl.new(signal_name)\
|
||||
.report_error(GdAssertMessages.error_await_signal_on_invalid_instance(source, signal_name, args), line_number)
|
||||
return await await_idle_frame()
|
||||
var awaiter := GdUnitSignalAwaiter.new(timeout_millis, true)
|
||||
var value :Variant = await awaiter.on_signal(source, signal_name, args)
|
||||
if awaiter.is_interrupted():
|
||||
var failure := "await_signal_idle_frames(%s, %s) timed out after %sms" % [signal_name, args, timeout_millis]
|
||||
@warning_ignore("return_value_discarded")
|
||||
GdUnitAssertImpl.new(signal_name).report_error(failure, line_number)
|
||||
return value
|
||||
|
||||
|
||||
# Waits for for a given amount of milliseconds
|
||||
# example:
|
||||
# # waits for 100ms
|
||||
# await GdUnitAwaiter.await_millis(myNode, 100).completed
|
||||
# use this waiter and not `await get_tree().create_timer().timeout to prevent errors when a test case is timed out
|
||||
func await_millis(milliSec :int) -> void:
|
||||
var timer :Timer = Timer.new()
|
||||
timer.set_name("gdunit_await_millis_timer_%d" % timer.get_instance_id())
|
||||
(Engine.get_main_loop() as SceneTree).root.add_child(timer)
|
||||
timer.add_to_group("GdUnitTimers")
|
||||
timer.set_one_shot(true)
|
||||
timer.start(milliSec / 1000.0)
|
||||
await timer.timeout
|
||||
timer.queue_free()
|
||||
|
||||
|
||||
# Waits until the next idle frame
|
||||
func await_idle_frame() -> void:
|
||||
await (Engine.get_main_loop() as SceneTree).process_frame
|
||||
@@ -0,0 +1 @@
|
||||
uid://do0d4vult83qq
|
||||
@@ -0,0 +1,35 @@
|
||||
## An Assertion Tool to verify boolean values
|
||||
@abstract class_name GdUnitBoolAssert
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is null.
|
||||
@abstract func is_null() -> GdUnitBoolAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
@abstract func is_not_null() -> GdUnitBoolAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to the given one.
|
||||
@abstract func is_equal(expected: Variant) -> GdUnitBoolAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to the given one.
|
||||
@abstract func is_not_equal(expected: Variant) -> GdUnitBoolAssert
|
||||
|
||||
|
||||
## Overrides the default failure message by given custom message.
|
||||
@abstract func override_failure_message(message: String) -> GdUnitBoolAssert
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.
|
||||
@abstract func append_failure_message(message: String) -> GdUnitBoolAssert
|
||||
|
||||
|
||||
## Verifies that the current value is true.
|
||||
@abstract func is_true() -> GdUnitBoolAssert
|
||||
|
||||
|
||||
## Verifies that the current value is false.
|
||||
@abstract func is_false() -> GdUnitBoolAssert
|
||||
@@ -0,0 +1 @@
|
||||
uid://yucqgte8f3lr
|
||||
@@ -0,0 +1,10 @@
|
||||
class_name GdUnitConstants
|
||||
extends RefCounted
|
||||
|
||||
const NO_ARG :Variant = "<--null-->"
|
||||
|
||||
const EXPECT_ASSERT_REPORT_FAILURES := "expect_assert_report_failures"
|
||||
|
||||
## The maximum number of report history files to store
|
||||
const DEFAULT_REPORT_HISTORY_COUNT = 20
|
||||
const REPORT_DIR_PREFIX = "report_"
|
||||
@@ -0,0 +1 @@
|
||||
uid://b8wk88u0ki4n7
|
||||
@@ -0,0 +1,79 @@
|
||||
## An Assertion Tool to verify dictionary
|
||||
@abstract class_name GdUnitDictionaryAssert
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is null.
|
||||
@abstract func is_null() -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
@abstract func is_not_null() -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Verifies that the current dictionary is equal to the given one, ignoring order.
|
||||
@abstract func is_equal(expected: Variant) -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Verifies that the current dictionary is not equal to the given one, ignoring order.
|
||||
@abstract func is_not_equal(expected: Variant) -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Overrides the default failure message by given custom message.
|
||||
@abstract func override_failure_message(message: String) -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.
|
||||
@abstract func append_failure_message(message: String) -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Verifies that the current dictionary is empty, it has a size of 0.
|
||||
@abstract func is_empty() -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Verifies that the current dictionary is not empty, it has a size of minimum 1.
|
||||
@abstract func is_not_empty() -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Verifies that the current dictionary is the same. [br]
|
||||
## Compares the current by object reference equals
|
||||
@abstract func is_same(expected: Variant) -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Verifies that the current dictionary is NOT the same. [br]
|
||||
## Compares the current by object reference equals
|
||||
@abstract func is_not_same(expected: Variant) -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Verifies that the current dictionary has a size of given value.
|
||||
@abstract func has_size(expected: int) -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Verifies that the current dictionary contains the given key(s).[br]
|
||||
## The keys are compared by deep parameter comparision, for object reference compare you have to use [method contains_same_keys]
|
||||
@abstract func contains_keys(...expected: Array) -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Verifies that the current dictionary contains the given key and value.[br]
|
||||
## The key and value are compared by deep parameter comparision, for object reference compare you have to use [method contains_same_key_value]
|
||||
@abstract func contains_key_value(key: Variant, value: Variant) -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Verifies that the current dictionary not contains the given key(s).[br]
|
||||
## The keys are compared by deep parameter comparision, for object reference compare you have to use [method not_contains_same_keys]
|
||||
@abstract func not_contains_keys(...expected: Array) -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Verifies that the current dictionary contains the given key(s).[br]
|
||||
## The keys are compared by object reference, for deep parameter comparision use [method contains_keys]
|
||||
@abstract func contains_same_keys(expected: Array) -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Verifies that the current dictionary contains the given key and value.[br]
|
||||
## The key and value are compared by object reference, for deep parameter comparision use [method contains_key_value]
|
||||
@abstract func contains_same_key_value(key: Variant, value: Variant) -> GdUnitDictionaryAssert
|
||||
|
||||
|
||||
## Verifies that the current dictionary not contains the given key(s).
|
||||
## The keys are compared by object reference, for deep parameter comparision use [method not_contains_keys]
|
||||
@abstract func not_contains_same_keys(...expected: Array) -> GdUnitDictionaryAssert
|
||||
@@ -0,0 +1 @@
|
||||
uid://c52l1pf0xf6kd
|
||||
@@ -0,0 +1,56 @@
|
||||
## An assertion tool to verify GDUnit asserts.
|
||||
## This assert is for internal use only, to verify that failed asserts work as expected.
|
||||
@abstract class_name GdUnitFailureAssert
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is null.
|
||||
@abstract func is_null() -> GdUnitFailureAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
@abstract func is_not_null() -> GdUnitFailureAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to the given one.
|
||||
@abstract func is_equal(expected: Variant) -> GdUnitFailureAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to expected one.
|
||||
@abstract func is_not_equal(expected: Variant) -> GdUnitFailureAssert
|
||||
|
||||
|
||||
## Overrides the default failure message by given custom message.
|
||||
@abstract func override_failure_message(message: String) -> GdUnitFailureAssert
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.
|
||||
@abstract func append_failure_message(message: String) -> GdUnitFailureAssert
|
||||
|
||||
|
||||
## Verifies if the executed assert was successful
|
||||
@abstract func is_success() -> GdUnitFailureAssert
|
||||
|
||||
|
||||
## Verifies if the executed assert has failed
|
||||
@abstract func is_failed() -> GdUnitFailureAssert
|
||||
|
||||
|
||||
## Verifies the failure line is equal to expected one.
|
||||
@abstract func has_line(expected: int) -> GdUnitFailureAssert
|
||||
|
||||
|
||||
## Verifies the failure stack trace is equal to expected one.
|
||||
@abstract func has_stack_trace(stack_trace: Array[GdUnitStackTraceElement]) -> GdUnitFailureAssert
|
||||
|
||||
|
||||
## Verifies the failure message is equal to expected one.
|
||||
@abstract func has_message(expected: String) -> GdUnitFailureAssert
|
||||
|
||||
|
||||
## Verifies that the failure message starts with the expected message.
|
||||
@abstract func starts_with_message(expected: String) -> GdUnitFailureAssert
|
||||
|
||||
|
||||
## Verifies that the failure message contains the expected message.
|
||||
@abstract func contains_message(expected: String) -> GdUnitFailureAssert
|
||||
@@ -0,0 +1 @@
|
||||
uid://b7u30qhxenurx
|
||||
@@ -0,0 +1,38 @@
|
||||
@abstract class_name GdUnitFileAssert
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is null.
|
||||
@abstract func is_null() -> GdUnitFileAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
@abstract func is_not_null() -> GdUnitFileAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to the given one.
|
||||
@abstract func is_equal(expected: Variant) -> GdUnitFileAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to expected one.
|
||||
@abstract func is_not_equal(expected: Variant) -> GdUnitFileAssert
|
||||
|
||||
|
||||
## Overrides the default failure message by given custom message.
|
||||
@abstract func override_failure_message(message: String) -> GdUnitFileAssert
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.
|
||||
@abstract func append_failure_message(message: String) -> GdUnitFileAssert
|
||||
|
||||
|
||||
@abstract func is_file() -> GdUnitFileAssert
|
||||
|
||||
|
||||
@abstract func exists() -> GdUnitFileAssert
|
||||
|
||||
|
||||
@abstract func is_script() -> GdUnitFileAssert
|
||||
|
||||
|
||||
@abstract func contains_exactly(expected_rows :Array) -> GdUnitFileAssert
|
||||
@@ -0,0 +1 @@
|
||||
uid://oiy1n0qmt45d
|
||||
@@ -0,0 +1,75 @@
|
||||
## An Assertion Tool to verify float values
|
||||
@abstract class_name GdUnitFloatAssert
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is null.
|
||||
@abstract func is_null() -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
@abstract func is_not_null() -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to the given one.
|
||||
@abstract func is_equal(expected: Variant) -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to expected one.
|
||||
@abstract func is_not_equal(expected: Variant) -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Verifies that the current and expected value are approximately equal.
|
||||
@abstract func is_equal_approx(expected: float, approx: float) -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Overrides the default failure message by given custom message.
|
||||
@abstract func override_failure_message(message: String) -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.
|
||||
@abstract func append_failure_message(message: String) -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Verifies that the current value is less than the given one.
|
||||
@abstract func is_less(expected: float) -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Verifies that the current value is less than or equal the given one.
|
||||
@abstract func is_less_equal(expected: float) -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Verifies that the current value is greater than the given one.
|
||||
@abstract func is_greater(expected: float) -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Verifies that the current value is greater than or equal the given one.
|
||||
@abstract func is_greater_equal(expected: float) -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Verifies that the current value is negative.
|
||||
@abstract func is_negative() -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not negative.
|
||||
@abstract func is_not_negative() -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to zero.
|
||||
@abstract func is_zero() -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to zero.
|
||||
@abstract func is_not_zero() -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Verifies that the current value is in the given set of values.
|
||||
@abstract func is_in(expected: Array) -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not in the given set of values.
|
||||
@abstract func is_not_in(expected: Array) -> GdUnitFloatAssert
|
||||
|
||||
|
||||
## Verifies that the current value is between the given boundaries (inclusive).
|
||||
@abstract func is_between(from: float, to: float) -> GdUnitFloatAssert
|
||||
@@ -0,0 +1 @@
|
||||
uid://c82pxwpofcaa3
|
||||
@@ -0,0 +1,42 @@
|
||||
## An Assertion Tool to verify function callback values
|
||||
@abstract class_name GdUnitFuncAssert
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is null.
|
||||
@abstract func is_null() -> GdUnitFuncAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
@abstract func is_not_null() -> GdUnitFuncAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to the given one.
|
||||
@abstract func is_equal(expected: Variant) -> GdUnitFuncAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to expected one.
|
||||
@abstract func is_not_equal(expected: Variant) -> GdUnitFuncAssert
|
||||
|
||||
|
||||
## Overrides the default failure message by given custom message.
|
||||
@abstract func override_failure_message(message: String) -> GdUnitFuncAssert
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.
|
||||
@abstract func append_failure_message(message: String) -> GdUnitFuncAssert
|
||||
|
||||
|
||||
## Verifies that the current value is true.
|
||||
@abstract func is_true() -> GdUnitFuncAssert
|
||||
|
||||
|
||||
## Verifies that the current value is false.
|
||||
@abstract func is_false() -> GdUnitFuncAssert
|
||||
|
||||
|
||||
## Sets the timeout in ms to wait the function returnd the expected value, if the time over a failure is emitted.[br]
|
||||
## e.g.[br]
|
||||
## do wait until 5s the function `is_state` is returns 10 [br]
|
||||
## [code]assert_func(instance, "is_state").wait_until(5000).is_equal(10)[/code]
|
||||
@abstract func wait_until(timeout: int) -> GdUnitFuncAssert
|
||||
@@ -0,0 +1 @@
|
||||
uid://bf4ugqv5d4oy1
|
||||
@@ -0,0 +1,59 @@
|
||||
## An assertion tool to verify for Godot runtime errors like assert() and push notifications like push_error().
|
||||
@abstract class_name GdUnitGodotErrorAssert
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is null.
|
||||
@abstract func is_null() -> GdUnitGodotErrorAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
@abstract func is_not_null() -> GdUnitGodotErrorAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to the given one.
|
||||
@abstract func is_equal(expected: Variant) -> GdUnitGodotErrorAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to expected one.
|
||||
@abstract func is_not_equal(expected: Variant) -> GdUnitGodotErrorAssert
|
||||
|
||||
|
||||
## Overrides the default failure message by given custom message.
|
||||
@abstract func override_failure_message(message: String) -> GdUnitGodotErrorAssert
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.
|
||||
@abstract func append_failure_message(message: String) -> GdUnitGodotErrorAssert
|
||||
|
||||
|
||||
## Verifies if the executed code runs without any runtime errors
|
||||
## Usage:
|
||||
## [codeblock]
|
||||
## await assert_error(<callable>).is_success()
|
||||
## [/codeblock]
|
||||
@abstract func is_success() -> GdUnitGodotErrorAssert
|
||||
|
||||
|
||||
## Verifies if the executed code runs into a runtime error
|
||||
## Usage:
|
||||
## [codeblock]
|
||||
## await assert_error(<callable>).is_runtime_error(<expected error message>)
|
||||
## [/codeblock]
|
||||
@abstract func is_runtime_error(expected_error: Variant) -> GdUnitGodotErrorAssert
|
||||
|
||||
|
||||
## Verifies if the executed code has a push_warning() used
|
||||
## Usage:
|
||||
## [codeblock]
|
||||
## await assert_error(<callable>).is_push_warning(<expected push warning message>)
|
||||
## [/codeblock]
|
||||
@abstract func is_push_warning(expected_warning: Variant) -> GdUnitGodotErrorAssert
|
||||
|
||||
|
||||
## Verifies if the executed code has a push_error() used
|
||||
## Usage:
|
||||
## [codeblock]
|
||||
## await assert_error(<callable>).is_push_error(<expected push error message>)
|
||||
## [/codeblock]
|
||||
@abstract func is_push_error(expected_error: Variant) -> GdUnitGodotErrorAssert
|
||||
@@ -0,0 +1 @@
|
||||
uid://dkub4b1kkhi86
|
||||
@@ -0,0 +1,79 @@
|
||||
## An Assertion Tool to verify integer values
|
||||
@abstract class_name GdUnitIntAssert
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is null.
|
||||
@abstract func is_null() -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
@abstract func is_not_null() -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to the given one.
|
||||
@abstract func is_equal(expected: Variant) -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to expected one.
|
||||
@abstract func is_not_equal(expected: Variant) -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Overrides the default failure message by given custom message.
|
||||
@abstract func override_failure_message(message: String) -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.
|
||||
@abstract func append_failure_message(message: String) -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is less than the given one.
|
||||
@abstract func is_less(expected: int) -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is less than or equal the given one.
|
||||
@abstract func is_less_equal(expected: int) -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is greater than the given one.
|
||||
@abstract func is_greater(expected: int) -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is greater than or equal the given one.
|
||||
@abstract func is_greater_equal(expected: int) -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is even.
|
||||
@abstract func is_even() -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is odd.
|
||||
@abstract func is_odd() -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is negative.
|
||||
@abstract func is_negative() -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not negative.
|
||||
@abstract func is_not_negative() -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to zero.
|
||||
@abstract func is_zero() -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to zero.
|
||||
@abstract func is_not_zero() -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is in the given set of values.
|
||||
@abstract func is_in(expected: Array) -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not in the given set of values.
|
||||
@abstract func is_not_in(expected: Array) -> GdUnitIntAssert
|
||||
|
||||
|
||||
## Verifies that the current value is between the given boundaries (inclusive).
|
||||
@abstract func is_between(from: int, to: int) -> GdUnitIntAssert
|
||||
@@ -0,0 +1 @@
|
||||
uid://cb8iji5xfd6mw
|
||||
@@ -0,0 +1,61 @@
|
||||
## An Assertion Tool to verify Object values
|
||||
@abstract class_name GdUnitObjectAssert
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is null.
|
||||
@abstract func is_null() -> GdUnitObjectAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
@abstract func is_not_null() -> GdUnitObjectAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to the given one.
|
||||
@abstract func is_equal(expected: Variant) -> GdUnitObjectAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to expected one.
|
||||
@abstract func is_not_equal(expected: Variant) -> GdUnitObjectAssert
|
||||
|
||||
|
||||
## Overrides the default failure message by given custom message.
|
||||
@abstract func override_failure_message(message: String) -> GdUnitObjectAssert
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.
|
||||
@abstract func append_failure_message(message: String) -> GdUnitObjectAssert
|
||||
|
||||
|
||||
## Verifies that the current object is the same as the given one.
|
||||
@abstract func is_same(expected: Variant) -> GdUnitObjectAssert
|
||||
|
||||
|
||||
## Verifies that the current object is not the same as the given one.
|
||||
@abstract func is_not_same(expected: Variant) -> GdUnitObjectAssert
|
||||
|
||||
|
||||
## Verifies that the current object is an instance of the given type.
|
||||
@abstract func is_instanceof(type: Variant) -> GdUnitObjectAssert
|
||||
|
||||
|
||||
## Verifies the current object is a valid instance (e.g. has not been freed from memory).[br]
|
||||
## [codeblock]
|
||||
## var node := Node.new()
|
||||
## assert_object(node).is_valid() # passes — node is alive
|
||||
## node.free()
|
||||
## assert_object(node).is_valid() # fails — node has been freed
|
||||
## [/codeblock]
|
||||
@abstract func is_valid() -> GdUnitObjectAssert
|
||||
|
||||
|
||||
## Verifies that the current object is not an instance of the given type.
|
||||
@abstract func is_not_instanceof(type: Variant) -> GdUnitObjectAssert
|
||||
|
||||
|
||||
## Checks whether the current object inherits from the specified type.
|
||||
@abstract func is_inheriting(type: Variant) -> GdUnitObjectAssert
|
||||
|
||||
|
||||
## Checks whether the current object does NOT inherit from the specified type.
|
||||
@abstract func is_not_inheriting(type: Variant) -> GdUnitObjectAssert
|
||||
@@ -0,0 +1 @@
|
||||
uid://dvu6dibmft8cm
|
||||
@@ -0,0 +1,51 @@
|
||||
## An Assertion Tool to verify Results
|
||||
@abstract class_name GdUnitResultAssert
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is null.
|
||||
@abstract func is_null() -> GdUnitResultAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
@abstract func is_not_null() -> GdUnitResultAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to the given one.
|
||||
@abstract func is_equal(expected: Variant) -> GdUnitResultAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to expected one.
|
||||
@abstract func is_not_equal(expected: Variant) -> GdUnitResultAssert
|
||||
|
||||
|
||||
## Overrides the default failure message by given custom message.
|
||||
@abstract func override_failure_message(message: String) -> GdUnitResultAssert
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.
|
||||
@abstract func append_failure_message(message: String) -> GdUnitResultAssert
|
||||
|
||||
|
||||
## Verifies that the result is ends up with empty
|
||||
@abstract func is_empty() -> GdUnitResultAssert
|
||||
|
||||
|
||||
## Verifies that the result is ends up with success
|
||||
@abstract func is_success() -> GdUnitResultAssert
|
||||
|
||||
|
||||
## Verifies that the result is ends up with warning
|
||||
@abstract func is_warning() -> GdUnitResultAssert
|
||||
|
||||
|
||||
## Verifies that the result is ends up with error
|
||||
@abstract func is_error() -> GdUnitResultAssert
|
||||
|
||||
|
||||
## Verifies that the result contains the given message
|
||||
@abstract func contains_message(expected: String) -> GdUnitResultAssert
|
||||
|
||||
|
||||
## Verifies that the result contains the given value
|
||||
@abstract func is_value(expected: Variant) -> GdUnitResultAssert
|
||||
@@ -0,0 +1 @@
|
||||
uid://djbyj2882ht2s
|
||||
@@ -0,0 +1,350 @@
|
||||
## The Scene Runner is a tool used for simulating interactions on a scene.
|
||||
## With this tool, you can simulate input events such as keyboard or mouse input and/or simulate scene processing over a certain number of frames.
|
||||
## This tool is typically used for integration testing a scene.
|
||||
@abstract class_name GdUnitSceneRunner
|
||||
extends RefCounted
|
||||
|
||||
|
||||
## Simulates that an action has been pressed.[br]
|
||||
## [member action] : the action e.g. [code]"ui_up"[/code][br]
|
||||
## [member event_index] : [url=https://docs.godotengine.org/en/4.4/classes/class_inputeventaction.html#class-inputeventaction-property-event-index]default=-1[/url][br]
|
||||
@abstract func simulate_action_pressed(action: String, event_index := -1) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates that an action is pressed.[br]
|
||||
## [member action] : the action e.g. [code]"ui_up"[/code][br]
|
||||
## [member event_index] : [url=https://docs.godotengine.org/en/4.4/classes/class_inputeventaction.html#class-inputeventaction-property-event-index]default=-1[/url][br]
|
||||
@abstract func simulate_action_press(action: String, event_index := -1) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates that an action has been released.[br]
|
||||
## [member action] : the action e.g. [code]"ui_up"[/code][br]
|
||||
## [member event_index] : [url=https://docs.godotengine.org/en/4.4/classes/class_inputeventaction.html#class-inputeventaction-property-event-index]default=-1[/url][br]
|
||||
@abstract func simulate_action_release(action: String, event_index := -1) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates that a key has been pressed.[br]
|
||||
## @deprecated: the modifier [b]shift_pressed[/b] and [b]ctrl_pressed[/b] will be removed in v7.0
|
||||
## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
|
||||
## [codeblock]
|
||||
## func test_key_presssed():
|
||||
## var runner = scene_runner("res://scenes/simple_scene.tscn")
|
||||
## await runner.simulate_key_pressed(KEY_SPACE)
|
||||
## [/codeblock]
|
||||
@abstract func simulate_key_pressed(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates that a key is pressing.[br]
|
||||
## @deprecated: the modifier [b]shift_pressed[/b] and [b]ctrl_pressed[/b] will be removed in v7.0[br]See `test_key_shift_and_A_presssing` for example using key combinations
|
||||
## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
|
||||
## [codeblock]
|
||||
## # Do simulate key pressing A
|
||||
## func test_key_A_presssing():
|
||||
## var runner = scene_runner("res://scenes/simple_scene.tscn")
|
||||
## await runner.simulate_key_press(KEY_A)
|
||||
##
|
||||
##
|
||||
## # Do simulate keycombination pressing shift+A
|
||||
## func test_key_shift_and_A_presssing():
|
||||
## var runner = scene_runner("res://scenes/simple_scene.tscn")
|
||||
## runner.simulate_key_press(KEY_SHIFT)
|
||||
## runner.simulate_key_press(KEY_A)
|
||||
## await _runner.await_input_processed()
|
||||
## [/codeblock]
|
||||
@abstract func simulate_key_press(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates that a key has been released.[br]
|
||||
## [member key_code] : the key code e.g. [constant KEY_ENTER][br]
|
||||
## [codeblock]
|
||||
## # Do simulate releasing key A
|
||||
## func test_key_A_releasing():
|
||||
## var runner = scene_runner("res://scenes/simple_scene.tscn")
|
||||
## await runner.simulate_key_release(KEY_A)
|
||||
##
|
||||
##
|
||||
## # Do simulate keycombination pressing shift+A
|
||||
## func test_key_shift_and_A_releasing(():
|
||||
## var runner = scene_runner("res://scenes/simple_scene.tscn")
|
||||
## runner.simulate_key_release(KEY_SHIFT)
|
||||
## runner.simulate_key_release(KEY_A)
|
||||
## await _runner.await_input_processed()
|
||||
## [/codeblock]
|
||||
## @deprecated: the modifier [b]shift_pressed[/b] and [b]ctrl_pressed[/b] will be removed in v7.0[br]See `test_key_shift_and_A_releasing` for example using key combinations
|
||||
@abstract func simulate_key_release(key_code: int, shift_pressed := false, ctrl_pressed := false) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Sets the mouse position to the specified vector, provided in pixels and relative to an origin at the upper left corner of the currently focused Window Manager game window.[br]
|
||||
## [member position] : The absolute position in pixels as Vector2
|
||||
@abstract func set_mouse_position(position: Vector2) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Returns the mouse's position in this Viewport using the coordinate system of this Viewport.
|
||||
@abstract func get_mouse_position() -> Vector2
|
||||
|
||||
|
||||
## Gets the current global mouse position of the current window
|
||||
@abstract func get_global_mouse_position() -> Vector2
|
||||
|
||||
|
||||
## Simulates a mouse moved to final position.[br]
|
||||
## [member position] : The final mouse position
|
||||
@abstract func simulate_mouse_move(position: Vector2) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates a mouse move to the relative coordinates (offset).[br]
|
||||
## [color=yellow]You must use [b]await[/b] to wait until the simulated mouse movement is complete.[/color][br]
|
||||
## [br]
|
||||
## [member relative] : The relative position, indicating the mouse position offset.[br]
|
||||
## [member time] : The time to move the mouse by the relative position in seconds (default is 1 second).[br]
|
||||
## [member trans_type] : Sets the type of transition used (default is TRANS_LINEAR).[br]
|
||||
## [codeblock]
|
||||
## func test_move_mouse():
|
||||
## var runner = scene_runner("res://scenes/simple_scene.tscn")
|
||||
## await runner.simulate_mouse_move_relative(Vector2(100,100))
|
||||
## [/codeblock]
|
||||
@abstract func simulate_mouse_move_relative(relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates a mouse move to the absolute coordinates.[br]
|
||||
## [color=yellow]You must use [b]await[/b] to wait until the simulated mouse movement is complete.[/color][br]
|
||||
## [br]
|
||||
## [member position] : The final position of the mouse.[br]
|
||||
## [member time] : The time to move the mouse to the final position in seconds (default is 1 second).[br]
|
||||
## [member trans_type] : Sets the type of transition used (default is TRANS_LINEAR).[br]
|
||||
## [codeblock]
|
||||
## func test_move_mouse():
|
||||
## var runner = scene_runner("res://scenes/simple_scene.tscn")
|
||||
## await runner.simulate_mouse_move_absolute(Vector2(100,100))
|
||||
## [/codeblock]
|
||||
@abstract func simulate_mouse_move_absolute(position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates a mouse button pressed.[br]
|
||||
## [member button_index] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
|
||||
## [member double_click] : Set to true to simulate a double-click
|
||||
@abstract func simulate_mouse_button_pressed(button_index: MouseButton, double_click := false) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates a mouse button press (holding)[br]
|
||||
## [member button_index] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
|
||||
## [member double_click] : Set to true to simulate a double-click
|
||||
@abstract func simulate_mouse_button_press(button_index: MouseButton, double_click := false) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates a mouse button released.[br]
|
||||
## [member button_index] : The mouse button identifier, one of the [enum MouseButton] or button wheel constants.
|
||||
@abstract func simulate_mouse_button_release(button_index: MouseButton) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates a screen touch is pressed.[br]
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
## [member position] : The position to touch the screen.[br]
|
||||
## [member double_tap] : If true, the touch's state is a double tab.
|
||||
@abstract func simulate_screen_touch_pressed(index: int, position: Vector2, double_tap := false) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates a screen touch press without releasing it immediately, effectively simulating a "hold" action.[br]
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
## [member position] : The position to touch the screen.[br]
|
||||
## [member double_tap] : If true, the touch's state is a double tab.
|
||||
@abstract func simulate_screen_touch_press(index: int, position: Vector2, double_tap := false) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates a screen touch is released.[br]
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
## [member double_tap] : If true, the touch's state is a double tab.
|
||||
@abstract func simulate_screen_touch_release(index: int, double_tap := false) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates a touch drag and drop event to a relative position.[br]
|
||||
## [color=yellow]You must use [b]await[/b] to wait until the simulated drag&drop is complete.[/color][br]
|
||||
## [br]
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
## [member relative] : The relative position, indicating the drag&drop position offset.[br]
|
||||
## [member time] : The time to move to the relative position in seconds (default is 1 second).[br]
|
||||
## [member trans_type] : Sets the type of transition used (default is TRANS_LINEAR).[br]
|
||||
## [codeblock]
|
||||
## func test_touch_drag_drop():
|
||||
## var runner = scene_runner("res://scenes/simple_scene.tscn")
|
||||
## # start drag at position 50,50
|
||||
## runner.simulate_screen_touch_drag_begin(1, Vector2(50, 50))
|
||||
## # and drop it at final at 150,50 relative (50,50 + 100,0)
|
||||
## await runner.simulate_screen_touch_drag_relative(1, Vector2(100,0))
|
||||
## [/codeblock]
|
||||
@abstract func simulate_screen_touch_drag_relative(index: int, relative: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates a touch screen drop to the absolute coordinates (offset).[br]
|
||||
## [color=yellow]You must use [b]await[/b] to wait until the simulated drop is complete.[/color][br]
|
||||
## [br]
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
## [member position] : The final position, indicating the drop position.[br]
|
||||
## [member time] : The time to move to the final position in seconds (default is 1 second).[br]
|
||||
## [member trans_type] : Sets the type of transition used (default is TRANS_LINEAR).[br]
|
||||
## [codeblock]
|
||||
## func test_touch_drag_drop():
|
||||
## var runner = scene_runner("res://scenes/simple_scene.tscn")
|
||||
## # start drag at position 50,50
|
||||
## runner.simulate_screen_touch_drag_begin(1, Vector2(50, 50))
|
||||
## # and drop it at 100,50
|
||||
## await runner.simulate_screen_touch_drag_absolute(1, Vector2(100,50))
|
||||
## [/codeblock]
|
||||
@abstract func simulate_screen_touch_drag_absolute(index: int, position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates a complete drag and drop event from one position to another.[br]
|
||||
## This is ideal for testing complex drag-and-drop scenarios that require a specific start and end position.[br]
|
||||
## [color=yellow]You must use [b]await[/b] to wait until the simulated drop is complete.[/color][br]
|
||||
## [br]
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
## [member position] : The drag start position, indicating the drag position.[br]
|
||||
## [member drop_position] : The drop position, indicating the drop position.[br]
|
||||
## [member time] : The time to move to the final position in seconds (default is 1 second).[br]
|
||||
## [member trans_type] : Sets the type of transition used (default is TRANS_LINEAR).[br]
|
||||
## [codeblock]
|
||||
## func test_touch_drag_drop():
|
||||
## var runner = scene_runner("res://scenes/simple_scene.tscn")
|
||||
## # start drag at position 50,50 and drop it at 100,50
|
||||
## await runner.simulate_screen_touch_drag_drop(1, Vector2(50, 50), Vector2(100,50))
|
||||
## [/codeblock]
|
||||
@abstract func simulate_screen_touch_drag_drop(index: int, position: Vector2, drop_position: Vector2, time: float = 1.0, trans_type: Tween.TransitionType = Tween.TRANS_LINEAR) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates a touch screen drag event to given position.[br]
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
## [member position] : The drag start position, indicating the drag position.[br]
|
||||
@abstract func simulate_screen_touch_drag(index: int, position: Vector2) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Returns the actual position of the touchscreen drag position by given index.
|
||||
## [member index] : The touch index in the case of a multi-touch event.[br]
|
||||
@abstract func get_screen_touch_drag_position(index: int) -> Vector2
|
||||
|
||||
|
||||
## Sets how fast or slow the scene simulation is processed (clock ticks versus the real).[br]
|
||||
## It defaults to 1.0. A value of 2.0 means the game moves twice as fast as real life,
|
||||
## whilst a value of 0.5 means the game moves at half the regular speed.
|
||||
## [member time_factor] : A float representing the simulation speed.[br]
|
||||
## - Default is 1.0, meaning the simulation runs at normal speed.[br]
|
||||
## - A value of 2.0 means the simulation runs twice as fast as real time.[br]
|
||||
## - A value of 0.5 means the simulation runs at half the regular speed.[br]
|
||||
@abstract func set_time_factor(time_factor: float = 1.0) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates scene processing for a certain number of frames.[br]
|
||||
## [member frames] : amount of frames to process[br]
|
||||
## [member delta_milli] : the time delta between a frame in milliseconds
|
||||
@abstract func simulate_frames(frames: int, delta_milli: int = -1) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates scene processing until the given signal is emitted by the scene.[br]
|
||||
## [member signal_name] : the signal to stop the simulation[br]
|
||||
## [member args] : optional signal arguments to be matched for stop[br]
|
||||
@abstract func simulate_until_signal(signal_name: String, ...args: Array) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Simulates scene processing until the given signal is emitted by the given object.[br]
|
||||
## [member source] : the object that should emit the signal[br]
|
||||
## [member signal_name] : the signal to stop the simulation[br]
|
||||
## [member args] : optional signal arguments to be matched for stop
|
||||
@abstract func simulate_until_object_signal(source: Object, signal_name: String, ...args: Array) -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Waits for all input events to be processed by flushing any buffered input events
|
||||
## and then awaiting a full cycle of both the process and physics frames.[br]
|
||||
## [br]
|
||||
## This is typically used to ensure that any simulated or queued inputs are fully
|
||||
## processed before proceeding with the next steps in the scene.[br]
|
||||
## It's essential for reliable input simulation or when synchronizing logic based
|
||||
## on inputs.[br]
|
||||
##
|
||||
## Usage Example:
|
||||
## [codeblock]
|
||||
## await await_input_processed() # Ensure all inputs are processed before continuing
|
||||
## [/codeblock]
|
||||
@abstract func await_input_processed() -> void
|
||||
|
||||
|
||||
## The await_func function pauses execution until a specified function in the scene returns a value.[br]
|
||||
## It returns a [GdUnitFuncAssert], which provides a suite of assertion methods to verify the returned value.[br]
|
||||
## [member func_name] : The name of the function to wait for.[br]
|
||||
## [member args] : Optional function arguments
|
||||
## [br]
|
||||
## Usage Example:
|
||||
## [codeblock]
|
||||
## # Waits for 'calculate_score' function and verifies the result is equal to 100.
|
||||
## await_func("calculate_score").is_equal(100)
|
||||
## [/codeblock]
|
||||
@abstract func await_func(func_name: String, ...args: Array) -> GdUnitFuncAssert
|
||||
|
||||
|
||||
## The await_func_on function extends the functionality of await_func by allowing you to specify a source node within the scene.[br]
|
||||
## It waits for a specified function on that node to return a value and returns a [GdUnitFuncAssert] object for assertions.[br]
|
||||
## [member source] : The object where implements the function.[br]
|
||||
## [member func_name] : The name of the function to wait for.[br]
|
||||
## [member args] : optional function arguments
|
||||
## [br]
|
||||
## Usage Example:
|
||||
## [codeblock]
|
||||
## # Waits for 'calculate_score' function and verifies the result is equal to 100.
|
||||
## var my_instance := ScoreCalculator.new()
|
||||
## await_func(my_instance, "calculate_score").is_equal(100)
|
||||
## [/codeblock]
|
||||
@abstract func await_func_on(source: Object, func_name: String, ...args: Array) -> GdUnitFuncAssert
|
||||
|
||||
|
||||
## Waits for the specified signal to be emitted by the scene. If the signal is not emitted within the given timeout, the operation fails.[br]
|
||||
## [member signal_name] : The name of the signal to wait for[br]
|
||||
## [member args] : The signal arguments as an array[br]
|
||||
## [member timeout] : The maximum duration (in milliseconds) to wait for the signal to be emitted before failing
|
||||
@abstract func await_signal(signal_name: String, args := [], timeout := 2000 ) -> void
|
||||
|
||||
|
||||
## Waits for the specified signal to be emitted by a particular source node. If the signal is not emitted within the given timeout, the operation fails.[br]
|
||||
## [member source] : the object from which the signal is emitted[br]
|
||||
## [member signal_name] : The name of the signal to wait for[br]
|
||||
## [member args] : The signal arguments as an array[br]
|
||||
## [member timeout] : tThe maximum duration (in milliseconds) to wait for the signal to be emitted before failing
|
||||
@abstract func await_signal_on(source: Object, signal_name: String, args := [], timeout := 2000 ) -> void
|
||||
|
||||
|
||||
## Restores the scene window to a windowed mode and brings it to the foreground.[br]
|
||||
## This ensures that the scene is visible and active during testing, making it easier to observe and interact with.
|
||||
@abstract func move_window_to_foreground() -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Minimizes the scene window to a windowed mode and brings it to the background.[br]
|
||||
## This ensures that the scene is hidden during testing.
|
||||
@abstract func move_window_to_background() -> GdUnitSceneRunner
|
||||
|
||||
|
||||
## Return the current value of the property with the name <name>.[br]
|
||||
## [member name] : name of property[br]
|
||||
## [member return] : the value of the property
|
||||
@abstract func get_property(name: String) -> Variant
|
||||
|
||||
|
||||
## Set the value <value> of the property with the name <name>.[br]
|
||||
## [member name] : name of property[br]
|
||||
## [member value] : value of property[br]
|
||||
## [member return] : true|false depending on valid property name.
|
||||
@abstract func set_property(name: String, value: Variant) -> bool
|
||||
|
||||
|
||||
## executes the function specified by <name> in the scene and returns the result.[br]
|
||||
## [member name] : the name of the function to execute[br]
|
||||
## [member args] : optional function arguments[br]
|
||||
## [member return] : the function result
|
||||
@abstract func invoke(name: String, ...args: Array) -> Variant
|
||||
|
||||
|
||||
## Searches for the specified node with the name in the current scene and returns it, otherwise null.[br]
|
||||
## [member name] : the name of the node to find[br]
|
||||
## [member recursive] : enables/disables seraching recursive[br]
|
||||
## [member return] : the node if find otherwise null
|
||||
@abstract func find_child(name: String, recursive: bool = true, owned: bool = false) -> Node
|
||||
|
||||
|
||||
## Access to current running scene
|
||||
@abstract func scene() -> Node
|
||||
@@ -0,0 +1 @@
|
||||
uid://cd83xyqmgeb3n
|
||||
@@ -0,0 +1,158 @@
|
||||
## An Assertion Tool to verify for emitted signals until a waiting time
|
||||
@abstract class_name GdUnitSignalAssert
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is null.
|
||||
@abstract func is_null() -> GdUnitSignalAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
@abstract func is_not_null() -> GdUnitSignalAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to the given one.
|
||||
@abstract func is_equal(expected: Variant) -> GdUnitSignalAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to expected one.
|
||||
@abstract func is_not_equal(expected: Variant) -> GdUnitSignalAssert
|
||||
|
||||
|
||||
## Overrides the default failure message by given custom message.
|
||||
@abstract func override_failure_message(message: String) -> GdUnitSignalAssert
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.
|
||||
@abstract func append_failure_message(message: String) -> GdUnitSignalAssert
|
||||
|
||||
|
||||
## Verifies that the specified signal is emitted with the expected arguments.[br]
|
||||
##
|
||||
## This assertion waits for a signal to be emitted from the object under test and
|
||||
## validates that it was emitted with the correct arguments. The function supports
|
||||
## both typed signals (Signal type) and string-based signal names for flexibility
|
||||
## in different testing scenarios.[br]
|
||||
## [br]
|
||||
## [b]Parameters:[/b][br]
|
||||
## [param signal_name]: The signal to monitor. Can be either:[br]
|
||||
## • A [Signal] reference (recommended for type safety)[br]
|
||||
## • A [String] with the signal name
|
||||
## [param signal_args]: Optional expected signal arguments.[br]
|
||||
## When provided, verifies the signal was emitted with exactly these values.[br]
|
||||
## [br]
|
||||
## [b]Returns:[/b][br]
|
||||
## [GdUnitSignalAssert] - Returns self for method chaining.[br]
|
||||
## [br]
|
||||
## [b]Examples:[/b]
|
||||
## [codeblock]
|
||||
## signal signal_a(value: int)
|
||||
## signal signal_b(name: String, count: int)
|
||||
##
|
||||
## # Wait for signal emission without checking arguments
|
||||
## # Using Signal reference (type-safe)
|
||||
## await assert_signal(instance).is_emitted(signal_a)
|
||||
## # Using string name (dynamic)
|
||||
## await assert_signal(instance).is_emitted("signal_a")
|
||||
##
|
||||
## # Wait for signal emission with specific argument
|
||||
## await assert_signal(instance).is_emitted(signal_a, 10)
|
||||
##
|
||||
## # Wait for signal with multiple arguments
|
||||
## await assert_signal(instance).is_emitted(signal_b, "test", 42)
|
||||
##
|
||||
## # Wait max 500ms for signal with argument 10
|
||||
## await assert_signal(instance).wait_until(500).is_emitted(signal_a, 10)
|
||||
## [/codeblock]
|
||||
## [br]
|
||||
## [b]Note:[/b] This is an async operation - use [code]await[/code] when calling.[br]
|
||||
## The assertion fails if the signal is not emitted within the timeout period.
|
||||
@abstract func is_emitted(signal_name: Variant, ...signal_args: Array) -> GdUnitSignalAssert
|
||||
|
||||
|
||||
## Verifies that the specified signal is NOT emitted with the expected arguments.[br]
|
||||
##
|
||||
## This assertion waits for a specified time period and validates that a signal
|
||||
## was not emitted with the given arguments. Useful for ensuring certain conditions
|
||||
## don't trigger unwanted signals or for verifying signal filtering logic.[br]
|
||||
## [br]
|
||||
## [b]Parameters:[/b][br]
|
||||
## [param signal_name]: The signal to monitor. Can be either:[br]
|
||||
## • A [Signal] reference (recommended for type safety)[br]
|
||||
## • A [String] with the signal name
|
||||
## [param signal_args]: Optional expected signal arguments.[br]
|
||||
## When provided, verifies the signal was not emitted with these specific values.[br]
|
||||
## If omitted, verifies the signal was not emitted at all.[br]
|
||||
## [br]
|
||||
## [b]Returns:[/b][br]
|
||||
## [GdUnitSignalAssert] - Returns self for method chaining.[br]
|
||||
## [br]
|
||||
## [b]Examples:[/b]
|
||||
## [codeblock]
|
||||
## signal signal_a(value: int)
|
||||
## signal signal_b(name: String, count: int)
|
||||
##
|
||||
## # Verify signal is not emitted at all (without checking arguments)
|
||||
## await assert_signal(instance).wait_until(500).is_not_emitted(signal_a)
|
||||
## await assert_signal(instance).wait_until(500).is_not_emitted("signal_a")
|
||||
##
|
||||
## # Verify signal is not emitted with specific argument
|
||||
## await assert_signal(instance).wait_until(500).is_not_emitted(signal_a, 10)
|
||||
##
|
||||
## # Verify signal is not emitted with multiple arguments
|
||||
## await assert_signal(instance).wait_until(500).is_not_emitted(signal_b, "test", 42)
|
||||
##
|
||||
## # Can be emitted with different arguments (this passes)
|
||||
## instance.emit_signal("signal_a", 20) # Emits with 20, not 10
|
||||
## await assert_signal(instance).wait_until(500).is_not_emitted(signal_a, 10)
|
||||
## [/codeblock]
|
||||
## [br]
|
||||
## [b]Note:[/b] This is an async operation - use [code]await[/code] when calling.[br]
|
||||
## The assertion fails if the signal IS emitted with the specified arguments within the timeout period.
|
||||
@abstract func is_not_emitted(signal_name: Variant, ...signal_args: Array) -> GdUnitSignalAssert
|
||||
|
||||
|
||||
## Verifies that the specified signal exists on the emitter object.[br]
|
||||
##
|
||||
## This assertion checks if a signal is defined on the object under test,
|
||||
## regardless of whether it has been emitted. Useful for validating that
|
||||
## objects have the expected signals before testing their emission.[br]
|
||||
## [br]
|
||||
## [b]Parameters:[/b][br]
|
||||
## [param signal_name]: The signal to check. Can be either:[br]
|
||||
## • A [Signal] reference (recommended for type safety)[br]
|
||||
## • A [String] with the signal name
|
||||
## [br]
|
||||
## [b]Returns:[/b][br]
|
||||
## [GdUnitSignalAssert] - Returns self for method chaining.[br]
|
||||
## [br]
|
||||
## [b]Examples:[/b]
|
||||
## [codeblock]
|
||||
## signal my_signal(value: int)
|
||||
## signal another_signal()
|
||||
##
|
||||
## # Verify signal exists using Signal reference
|
||||
## assert_signal(instance).is_signal_exists(my_signal)
|
||||
##
|
||||
## # Verify signal exists using string name
|
||||
## assert_signal(instance).is_signal_exists("my_signal")
|
||||
##
|
||||
## # Chain with other assertions
|
||||
## assert_signal(instance) \
|
||||
## .is_signal_exists(my_signal) \
|
||||
## .is_emitted(my_signal, 42)
|
||||
##
|
||||
## [/codeblock]
|
||||
## [br]
|
||||
## [b]Note:[/b] This only checks signal definition, not emission.[br]
|
||||
## The assertion fails if the signal is not defined on the object.
|
||||
@abstract func is_signal_exists(signal_name: Variant) -> GdUnitSignalAssert
|
||||
|
||||
|
||||
## Sets the assert signal timeout in ms, if the time over a failure is reported.[br]
|
||||
## Example:
|
||||
## [codeblock]
|
||||
## do wait until 5s the instance has emitted the signal `signal_a`[br]
|
||||
## assert_signal(instance).wait_until(5000).is_emitted("signal_a")
|
||||
## [/codeblock]
|
||||
@abstract func wait_until(timeout: int) -> GdUnitSignalAssert
|
||||
@@ -0,0 +1 @@
|
||||
uid://dnno7sv5ruwws
|
||||
@@ -0,0 +1,71 @@
|
||||
## An Assertion Tool to verify String values
|
||||
@abstract class_name GdUnitStringAssert
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is null.
|
||||
@abstract func is_null() -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
@abstract func is_not_null() -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to the given one.
|
||||
@abstract func is_equal(expected: Variant) -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Verifies that the current String is equal to the given one, ignoring case considerations.
|
||||
@abstract func is_equal_ignoring_case(expected: Variant) -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to expected one.
|
||||
@abstract func is_not_equal(expected: Variant) -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Verifies that the current String is not equal to the given one, ignoring case considerations.
|
||||
@abstract func is_not_equal_ignoring_case(expected: Variant) -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Overrides the default failure message by given custom message.
|
||||
@abstract func override_failure_message(message: String) -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.
|
||||
@abstract func append_failure_message(message: String) -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Verifies that the current String is empty, it has a length of 0.
|
||||
@abstract func is_empty() -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Verifies that the current String is not empty, it has a length of minimum 1.
|
||||
@abstract func is_not_empty() -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Verifies that the current String contains the given String.
|
||||
@abstract func contains(expected: String) -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Verifies that the current String does not contain the given String.
|
||||
@abstract func not_contains(expected: String) -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Verifies that the current String does not contain the given String, ignoring case considerations.
|
||||
@abstract func contains_ignoring_case(expected: String) -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Verifies that the current String does not contain the given String, ignoring case considerations.
|
||||
@abstract func not_contains_ignoring_case(expected: String) -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Verifies that the current String starts with the given prefix.
|
||||
@abstract func starts_with(expected: String) -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Verifies that the current String ends with the given suffix.
|
||||
@abstract func ends_with(expected: String) -> GdUnitStringAssert
|
||||
|
||||
|
||||
## Verifies that the current String has the expected length by used comparator.
|
||||
@abstract func has_length(length: int, comparator: int = Comparator.EQUAL) -> GdUnitStringAssert
|
||||
@@ -0,0 +1 @@
|
||||
uid://c0l4juv45771a
|
||||
@@ -0,0 +1,774 @@
|
||||
## The main class for all GdUnit test suites[br]
|
||||
## This class is the main class to implement your unit tests[br]
|
||||
## You have to extend and implement your test cases as described[br]
|
||||
## e.g MyTests.gd [br]
|
||||
## [codeblock]
|
||||
## extends GdUnitTestSuite
|
||||
## # testcase
|
||||
## func test_case_a():
|
||||
## assert_that("value").is_equal("value")
|
||||
## [/codeblock]
|
||||
## @tutorial: https://mikeschulze.github.io/gdUnit4/faq/test-suite/
|
||||
|
||||
@icon("res://addons/gdUnit4/src/ui/settings/logo.png")
|
||||
class_name GdUnitTestSuite
|
||||
extends Node
|
||||
|
||||
|
||||
### internal runtime variables that must not be overwritten!!!
|
||||
@warning_ignore("unused_private_class_variable")
|
||||
var __is_skipped := false
|
||||
@warning_ignore("unused_private_class_variable")
|
||||
var __skip_reason := "Unknow."
|
||||
var __active_test_case: String
|
||||
var __awaiter := __gdunit_awaiter()
|
||||
|
||||
|
||||
### We now load all used asserts and tool scripts into the cache according to the principle of "lazy loading"
|
||||
### in order to noticeably reduce the loading time of the test suite.
|
||||
# We go this hard way to increase the loading performance to avoid reparsing all the used scripts
|
||||
# for more detailed info -> https://github.com/godotengine/godot/issues/67400
|
||||
static func __lazy_load(script_path: String) -> GDScript:
|
||||
return GdUnitAssertions.__lazy_load(script_path)
|
||||
|
||||
|
||||
func __gdunit_assert() -> GDScript:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd")
|
||||
|
||||
|
||||
func __gdunit_tools() -> GDScript:
|
||||
return __lazy_load("res://addons/gdUnit4/src/core/GdUnitTools.gd")
|
||||
|
||||
|
||||
func __gdunit_file_access() -> GDScript:
|
||||
return __lazy_load("res://addons/gdUnit4/src/core/GdUnitFileAccess.gd")
|
||||
|
||||
|
||||
func __gdunit_awaiter() -> Object:
|
||||
return __lazy_load("res://addons/gdUnit4/src/GdUnitAwaiter.gd").new()
|
||||
|
||||
|
||||
func __gdunit_argument_matchers() -> GDScript:
|
||||
return __lazy_load("res://addons/gdUnit4/src/matchers/GdUnitArgumentMatchers.gd")
|
||||
|
||||
|
||||
func __gdunit_object_interactions() -> GDScript:
|
||||
return __lazy_load("res://addons/gdUnit4/src/doubler/GdUnitObjectInteractions.gd")
|
||||
|
||||
|
||||
## This function is called before a test suite starts[br]
|
||||
## You can overwrite to prepare test data or initalizize necessary variables
|
||||
func before() -> void:
|
||||
pass
|
||||
|
||||
|
||||
## This function is called at least when a test suite is finished[br]
|
||||
## You can overwrite to cleanup data created during test running
|
||||
func after() -> void:
|
||||
pass
|
||||
|
||||
|
||||
## This function is called before a test case starts[br]
|
||||
## You can overwrite to prepare test case specific data
|
||||
func before_test() -> void:
|
||||
pass
|
||||
|
||||
|
||||
## This function is called after the test case is finished[br]
|
||||
## You can overwrite to cleanup your test case specific data
|
||||
func after_test() -> void:
|
||||
pass
|
||||
|
||||
|
||||
func is_failure() -> bool:
|
||||
return Engine.get_meta("GD_TEST_FAILURE") if Engine.has_meta("GD_TEST_FAILURE") else false
|
||||
|
||||
|
||||
func set_active_test_case(test_case: String) -> void:
|
||||
__active_test_case = test_case
|
||||
|
||||
|
||||
# === Tools ====================================================================
|
||||
# Mapps Godot error number to a readable error message. See at ERROR
|
||||
# https://docs.godotengine.org/de/stable/classes/class_@globalscope.html#enum-globalscope-error
|
||||
func error_as_string(error_number: int) -> String:
|
||||
return error_string(error_number)
|
||||
|
||||
|
||||
## A litle helper to auto freeing your created objects after test execution
|
||||
func auto_free(obj: Variant) -> Variant:
|
||||
var execution_context := GdUnitThreadManager.get_current_context().get_execution_context()
|
||||
|
||||
# If the execution context is null, we are not in the execution call stack, so we mark the object as “queued free.”
|
||||
if execution_context == null:
|
||||
if obj is Node:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
obj.queue_free()
|
||||
return obj
|
||||
|
||||
return execution_context.register_auto_free(obj)
|
||||
|
||||
|
||||
## Saves the current project settings into the active execution context.[br]
|
||||
## Useful when the "Auto Save Project Settings" setting is disabled and a test needs settings isolation on demand.[br]
|
||||
## [br]
|
||||
## Call this in [code]before()[/code]/[code]before_test()[/code] to snapshot the settings a test
|
||||
## is about to change, to restore see [method restore_project_settings].
|
||||
func save_project_settings() -> void:
|
||||
var execution_context := GdUnitThreadManager.get_current_context().get_execution_context()
|
||||
if execution_context == null:
|
||||
return
|
||||
execution_context.save_project_settings()
|
||||
|
||||
|
||||
## Restores the project settings previously captured by [method save_project_settings].[br]
|
||||
## [br]
|
||||
## Call this in [code]after()[/code]/[code]after_test()[/code] to undo any settings a test changed.
|
||||
func restore_project_settings() -> void:
|
||||
var execution_context := GdUnitThreadManager.get_current_context().get_execution_context()
|
||||
if execution_context == null:
|
||||
return
|
||||
execution_context.restore_project_settings()
|
||||
|
||||
|
||||
@warning_ignore("native_method_override")
|
||||
func add_child(node: Node, force_readable_name := false, internal := Node.INTERNAL_MODE_DISABLED) -> void:
|
||||
super.add_child(node, force_readable_name, internal)
|
||||
var execution_context := GdUnitThreadManager.get_current_context().get_execution_context()
|
||||
if execution_context != null:
|
||||
execution_context.orphan_monitor_start()
|
||||
|
||||
|
||||
## Discard the error message triggered by a timeout (interruption).[br]
|
||||
## By default, an interrupted test is reported as an error.[br]
|
||||
## This function allows you to change the message to Success when an interrupted error is reported.
|
||||
func discard_error_interupted_by_timeout() -> void:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
__gdunit_tools().register_expect_interupted_by_timeout(self, __active_test_case)
|
||||
|
||||
|
||||
## Creates a new directory under the temporary directory *user://tmp*[br]
|
||||
## Useful for storing data during test execution. [br]
|
||||
## The directory is automatically deleted after test suite execution
|
||||
func create_temp_dir(relative_path: String) -> String:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_file_access().create_temp_dir(relative_path)
|
||||
|
||||
|
||||
## Deletes the temporary base directory[br]
|
||||
## Is called automatically after each execution of the test suite
|
||||
func clean_temp_dir() -> void:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
__gdunit_file_access().clear_tmp()
|
||||
|
||||
|
||||
## Creates a new file under the temporary directory *user://tmp* + <relative_path>[br]
|
||||
## with given name <file_name> and given file <mode> (default = File.WRITE)[br]
|
||||
## If success the returned File is automatically closed after the execution of the test suite
|
||||
func create_temp_file(relative_path: String, file_name: String, mode := FileAccess.WRITE) -> FileAccess:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_file_access().create_temp_file(relative_path, file_name, mode)
|
||||
|
||||
|
||||
## Reads a resource by given path <resource_path> into a PackedStringArray.
|
||||
func resource_as_array(resource_path: String) -> PackedStringArray:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_file_access().resource_as_array(resource_path)
|
||||
|
||||
|
||||
## Reads a resource by given path <resource_path> and returned the content as String.
|
||||
func resource_as_string(resource_path: String) -> String:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_file_access().resource_as_string(resource_path)
|
||||
|
||||
|
||||
## Reads a resource by given path <resource_path> and return Variand translated by str_to_var
|
||||
func resource_as_var(resource_path: String) -> Variant:
|
||||
@warning_ignore("unsafe_method_access", "unsafe_cast")
|
||||
return str_to_var(__gdunit_file_access().resource_as_string(resource_path) as String)
|
||||
|
||||
|
||||
## Waits for given signal to be emitted by <source> until a specified timeout to fail[br]
|
||||
## source: the object from which the signal is emitted[br]
|
||||
## signal_name: signal name[br]
|
||||
## args: the expected signal arguments as an array[br]
|
||||
## timeout: the timeout in ms, default is set to 2000ms
|
||||
func await_signal_on(source: Object, signal_name: String, args: Array = [], timeout: int = 2000) -> Variant:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return await __awaiter.await_signal_on(source, signal_name, args, timeout)
|
||||
|
||||
|
||||
## Waits until the next idle frame
|
||||
func await_idle_frame() -> void:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
await __awaiter.await_idle_frame()
|
||||
|
||||
|
||||
## Waits for a given amount of milliseconds[br]
|
||||
## example:[br]
|
||||
## [codeblock]
|
||||
## # waits for 100ms
|
||||
## await await_millis(myNode, 100).completed
|
||||
## [/codeblock][br]
|
||||
## use this waiter and not `await get_tree().create_timer().timeout to prevent errors when a test case is timed out
|
||||
func await_millis(timeout: int) -> void:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
await __awaiter.await_millis(timeout)
|
||||
|
||||
|
||||
## Collects detailed information about orphaned nodes for debugging purposes.[br]
|
||||
##
|
||||
## This function gathers comprehensive details about nodes that remain in memory
|
||||
## after test execution (orphans). It provides debugging information to help
|
||||
## identify the source of memory leaks in tests. Must be manually called in
|
||||
## tests when orphan nodes are detected.[br]
|
||||
## [br]
|
||||
## [b]When to Use:[/b][br]
|
||||
## - When GdUnit4 reports orphan nodes after test execution[br]
|
||||
## - For debugging memory leaks in test scenarios[br]
|
||||
## - To get detailed information about unreleased nodes[br]
|
||||
## [br]
|
||||
## [b]Usage Pattern:[/b][br]
|
||||
## Add this call at the end of tests that are suspected to create orphans,
|
||||
## or when the test runner reports orphan detection.[br]
|
||||
## [br]
|
||||
## [b]Examples:[/b]
|
||||
## [codeblock]
|
||||
## func test_scene_management():
|
||||
## # Test code that might create orphan nodes
|
||||
## var scene = preload("res://TestScene.tscn").instantiate()
|
||||
## add_child(scene)
|
||||
##
|
||||
## # Do test operations
|
||||
## scene.some_method()
|
||||
##
|
||||
## # Clean up (but might miss some nodes)
|
||||
## scene.queue_free()
|
||||
##
|
||||
## # Collect orphan details if any are detected
|
||||
## collect_orphan_node_details()
|
||||
## [/codeblock]
|
||||
## [br]
|
||||
## [b]Note:[/b] This is a debugging utility function that should be removed
|
||||
## or commented out once orphan issues are resolved.
|
||||
func collect_orphan_node_details() -> void:
|
||||
GdUnitThreadManager.get_current_context().get_execution_context().orphan_monitor_collect()
|
||||
|
||||
|
||||
## Creates a new scene runner to allow simulate interactions checked a scene.[br]
|
||||
## The runner will manage the scene instance and release after the runner is released[br]
|
||||
## example:[br]
|
||||
## [codeblock]
|
||||
## # creates a runner by using a instanciated scene
|
||||
## var scene = load("res://foo/my_scne.tscn").instantiate()
|
||||
## var runner := scene_runner(scene)
|
||||
##
|
||||
## # or simply creates a runner by using the scene resource path
|
||||
## var runner := scene_runner("res://foo/my_scne.tscn")
|
||||
## [/codeblock]
|
||||
func scene_runner(scene: Variant, verbose := false) -> GdUnitSceneRunner:
|
||||
return auto_free(__lazy_load("res://addons/gdUnit4/src/core/GdUnitSceneRunnerImpl.gd").new(scene, verbose))
|
||||
|
||||
|
||||
# === Mocking & Spy ===========================================================
|
||||
|
||||
## do return a default value for primitive types or null
|
||||
const RETURN_DEFAULTS = GdUnitMock.RETURN_DEFAULTS
|
||||
## do call the real implementation
|
||||
const CALL_REAL_FUNC = GdUnitMock.CALL_REAL_FUNC
|
||||
## do return a default value for primitive types and a fully mocked value for Object types
|
||||
## builds full deep mocked object
|
||||
const RETURN_DEEP_STUB = GdUnitMock.RETURN_DEEP_STUB
|
||||
|
||||
|
||||
## Creates a mock for given class name
|
||||
func mock(clazz: Variant, mock_mode := RETURN_DEFAULTS) -> Variant:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __lazy_load("res://addons/gdUnit4/src/mocking/GdUnitMockBuilder.gd").build(clazz, mock_mode)
|
||||
|
||||
|
||||
## Creates a spy checked given object instance
|
||||
func spy(instance: Variant) -> Variant:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __lazy_load("res://addons/gdUnit4/src/spy/GdUnitSpyBuilder.gd").build(instance)
|
||||
|
||||
|
||||
## Configures a return value for the specified function and used arguments.[br]
|
||||
## [b]Example:
|
||||
## [codeblock]
|
||||
## # overrides the return value of myMock.is_selected() to false
|
||||
## do_return(false).on(myMock).is_selected()
|
||||
## [/codeblock]
|
||||
func do_return(value: Variant) -> GdUnitMock:
|
||||
return GdUnitMock.new(value)
|
||||
|
||||
|
||||
## Verifies certain behavior happened at least once or exact number of times
|
||||
func verify(obj: Variant, times := 1) -> Variant:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_object_interactions().verify(obj, times)
|
||||
|
||||
|
||||
## Verifies no interactions is happen checked this mock or spy
|
||||
func verify_no_interactions(obj: Variant) -> GdUnitAssert:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_object_interactions().verify_no_interactions(obj)
|
||||
|
||||
|
||||
## Verifies the given mock or spy has any unverified interaction.
|
||||
func verify_no_more_interactions(obj: Variant) -> GdUnitAssert:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_object_interactions().verify_no_more_interactions(obj)
|
||||
|
||||
|
||||
## Resets the saved function call counters checked a mock or spy
|
||||
func reset(obj: Variant) -> void:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
__gdunit_object_interactions().reset(obj)
|
||||
|
||||
|
||||
## Starts monitoring the specified source to collect all transmitted signals.[br]
|
||||
## The collected signals can then be checked with 'assert_signal'.[br]
|
||||
## By default, the specified source is automatically released when the test ends.
|
||||
## You can control this behavior by setting auto_free to false if you do not want the source to be automatically freed.[br]
|
||||
## Usage:
|
||||
## [codeblock]
|
||||
## var emitter := monitor_signals(MyEmitter.new())
|
||||
## # call the function to send the signal
|
||||
## emitter.do_it()
|
||||
## # verify the signial is emitted
|
||||
## await assert_signal(emitter).is_emitted('my_signal')
|
||||
## [/codeblock]
|
||||
func monitor_signals(source: Object, _auto_free := true) -> Object:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
__lazy_load("res://addons/gdUnit4/src/core/thread/GdUnitThreadManager.gd")\
|
||||
.get_current_context()\
|
||||
.get_signal_collector()\
|
||||
.register_emitter(source, true) # force recreate to start with a fresh monitoring
|
||||
return auto_free(source) if _auto_free else source
|
||||
|
||||
|
||||
# === Argument matchers ========================================================
|
||||
## Argument matcher to match any argument
|
||||
func any() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().any()
|
||||
|
||||
|
||||
## Argument matcher to match any boolean value
|
||||
func any_bool() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_BOOL)
|
||||
|
||||
|
||||
## Argument matcher to match any integer value
|
||||
func any_int() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_INT)
|
||||
|
||||
|
||||
## Argument matcher to match any float value
|
||||
func any_float() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_FLOAT)
|
||||
|
||||
|
||||
## Argument matcher to match any String value
|
||||
func any_string() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_STRING)
|
||||
|
||||
|
||||
## Argument matcher to match any Color value
|
||||
func any_color() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_COLOR)
|
||||
|
||||
|
||||
## Argument matcher to match any Vector typed value
|
||||
func any_vector() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_types([
|
||||
TYPE_VECTOR2,
|
||||
TYPE_VECTOR2I,
|
||||
TYPE_VECTOR3,
|
||||
TYPE_VECTOR3I,
|
||||
TYPE_VECTOR4,
|
||||
TYPE_VECTOR4I,
|
||||
])
|
||||
|
||||
|
||||
## Argument matcher to match any Vector2 value
|
||||
func any_vector2() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_VECTOR2)
|
||||
|
||||
|
||||
## Argument matcher to match any Vector2i value
|
||||
func any_vector2i() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_VECTOR2I)
|
||||
|
||||
|
||||
## Argument matcher to match any Vector3 value
|
||||
func any_vector3() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_VECTOR3)
|
||||
|
||||
|
||||
## Argument matcher to match any Vector3i value
|
||||
func any_vector3i() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_VECTOR3I)
|
||||
|
||||
|
||||
## Argument matcher to match any Vector4 value
|
||||
func any_vector4() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_VECTOR4)
|
||||
|
||||
|
||||
## Argument matcher to match any Vector4i value
|
||||
func any_vector4i() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_VECTOR4I)
|
||||
|
||||
|
||||
## Argument matcher to match any Rect2 value
|
||||
func any_rect2() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_RECT2)
|
||||
|
||||
|
||||
## Argument matcher to match any Plane value
|
||||
func any_plane() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PLANE)
|
||||
|
||||
|
||||
## Argument matcher to match any Quaternion value
|
||||
func any_quat() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_QUATERNION)
|
||||
|
||||
|
||||
## Argument matcher to match any AABB value
|
||||
func any_aabb() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_AABB)
|
||||
|
||||
|
||||
## Argument matcher to match any Basis value
|
||||
func any_basis() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_BASIS)
|
||||
|
||||
|
||||
## Argument matcher to match any Transform2D value
|
||||
func any_transform_2d() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM2D)
|
||||
|
||||
|
||||
## Argument matcher to match any Transform3D value
|
||||
func any_transform_3d() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_TRANSFORM3D)
|
||||
|
||||
|
||||
## Argument matcher to match any NodePath value
|
||||
func any_node_path() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_NODE_PATH)
|
||||
|
||||
|
||||
## Argument matcher to match any RID value
|
||||
func any_rid() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_RID)
|
||||
|
||||
|
||||
## Argument matcher to match any Object value
|
||||
func any_object() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_OBJECT)
|
||||
|
||||
|
||||
## Argument matcher to match any Dictionary value
|
||||
func any_dictionary() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_DICTIONARY)
|
||||
|
||||
|
||||
## Argument matcher to match any Array value
|
||||
func any_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedByteArray value
|
||||
func any_packed_byte_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_BYTE_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedInt32Array value
|
||||
func any_packed_int32_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_INT32_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedInt64Array value
|
||||
func any_packed_int64_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_INT64_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedFloat32Array value
|
||||
func any_packed_float32_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_FLOAT32_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedFloat64Array value
|
||||
func any_packed_float64_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_FLOAT64_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedStringArray value
|
||||
func any_packed_string_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_STRING_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedVector2Array value
|
||||
func any_packed_vector2_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_VECTOR2_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedVector3Array value
|
||||
func any_packed_vector3_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_VECTOR3_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any PackedColorArray value
|
||||
func any_packed_color_array() -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().by_type(TYPE_PACKED_COLOR_ARRAY)
|
||||
|
||||
|
||||
## Argument matcher to match any instance of given class
|
||||
func any_class(clazz :Object) -> GdUnitArgumentMatcher:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __gdunit_argument_matchers().any_class(clazz)
|
||||
|
||||
|
||||
# === value extract utils ======================================================
|
||||
## Builds an extractor by given function name and optional arguments
|
||||
func extr(func_name: String, args := Array()) -> GdUnitValueExtractor:
|
||||
return __lazy_load("res://addons/gdUnit4/src/extractors/GdUnitFuncValueExtractor.gd").new(func_name, args)
|
||||
|
||||
|
||||
## Creates a GdUnitTuple from the provided arguments for use in test assertions.
|
||||
## [br]
|
||||
## This is the primary helper function for creating tuples in GdUnit4 tests.
|
||||
## It provides a convenient way to group multiple expected values when using
|
||||
## [method extractv] assertions. The function enforces that tuples must contain
|
||||
## at least two values, as single-value extractions don't require tuple grouping.
|
||||
## [br]
|
||||
## [b]Parameters:[/b] [br]
|
||||
## - [code]...args[/code]: Variable number of arguments (minimum 2) to group into a tuple.
|
||||
## Each argument represents a value to be compared in assertions.
|
||||
## [br]
|
||||
## [b]Returns:[/b] [br]
|
||||
## A [GdUnitTuple] containing the provided values, or an empty tuple if fewer than
|
||||
## 2 arguments are provided (with an error message).
|
||||
## [br]
|
||||
## [b]Error Handling:[/b] [br]
|
||||
## [codeblock]
|
||||
## # This will push an error and return empty tuple
|
||||
## var invalid = tuple("single_value") # Error: requires at least 2 arguments
|
||||
## [br]
|
||||
## # Correct usage - minimum 2 arguments
|
||||
## var valid = tuple("name", "value")
|
||||
## var valid_multi = tuple(1, 2, 3, 4, 5) # Can have many values
|
||||
## [/codeblock]
|
||||
func tuple(...args: Array) -> GdUnitTuple:
|
||||
if args.size() < 2:
|
||||
push_error("Tuple requires at least two arguments.")
|
||||
return GdUnitTuple.new()
|
||||
return GdUnitTuple.new.callv(args)
|
||||
|
||||
|
||||
# === Asserts ==================================================================
|
||||
|
||||
## The common assertion tool to verify values.
|
||||
## It checks the given value by type to fit to the best assert
|
||||
func assert_that(current: Variant) -> GdUnitAssert:
|
||||
match typeof(current):
|
||||
TYPE_BOOL:
|
||||
return assert_bool(current)
|
||||
TYPE_INT:
|
||||
return assert_int(current)
|
||||
TYPE_FLOAT:
|
||||
return assert_float(current)
|
||||
TYPE_STRING:
|
||||
return assert_str(current)
|
||||
TYPE_VECTOR2, TYPE_VECTOR2I, TYPE_VECTOR3, TYPE_VECTOR3I, TYPE_VECTOR4, TYPE_VECTOR4I:
|
||||
return assert_vector(current, false)
|
||||
TYPE_DICTIONARY:
|
||||
return assert_dict(current)
|
||||
TYPE_ARRAY, TYPE_PACKED_BYTE_ARRAY, TYPE_PACKED_INT32_ARRAY, TYPE_PACKED_INT64_ARRAY,\
|
||||
TYPE_PACKED_FLOAT32_ARRAY, TYPE_PACKED_FLOAT64_ARRAY, TYPE_PACKED_STRING_ARRAY,\
|
||||
TYPE_PACKED_VECTOR2_ARRAY, TYPE_PACKED_VECTOR3_ARRAY, TYPE_PACKED_COLOR_ARRAY:
|
||||
return assert_array(current, false)
|
||||
TYPE_OBJECT, TYPE_NIL:
|
||||
return assert_object(current)
|
||||
_:
|
||||
return __gdunit_assert().new(current)
|
||||
|
||||
|
||||
## An assertion tool to verify boolean values.
|
||||
static func assert_bool(current: Variant) -> GdUnitBoolAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd").new(current)
|
||||
|
||||
|
||||
## An assertion tool to verify String values.
|
||||
func assert_str(current: Variant) -> GdUnitStringAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd").new(current)
|
||||
|
||||
|
||||
## An assertion tool to verify integer values.
|
||||
func assert_int(current: Variant) -> GdUnitIntAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd").new(current)
|
||||
|
||||
|
||||
## An assertion tool to verify float values.
|
||||
func assert_float(current: Variant) -> GdUnitFloatAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd").new(current)
|
||||
|
||||
|
||||
## An assertion tool to verify Vector values.[br]
|
||||
## This assertion supports all vector types.[br]
|
||||
## Usage:
|
||||
## [codeblock]
|
||||
## assert_vector(Vector2(1.2, 1.000001)).is_equal(Vector2(1.2, 1.000001))
|
||||
## [/codeblock]
|
||||
func assert_vector(current: Variant, type_check := true) -> GdUnitVectorAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd").new(current, type_check)
|
||||
|
||||
|
||||
## An assertion tool to verify arrays.
|
||||
func assert_array(current: Variant, type_check := true) -> GdUnitArrayAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd").new(current, type_check)
|
||||
|
||||
|
||||
## An assertion tool to verify dictionaries.
|
||||
func assert_dict(current: Variant) -> GdUnitDictionaryAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd").new(current)
|
||||
|
||||
|
||||
## An assertion tool to verify FileAccess.
|
||||
func assert_file(current: Variant) -> GdUnitFileAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd").new(current)
|
||||
|
||||
|
||||
## An assertion tool to verify Objects.
|
||||
func assert_object(current: Variant) -> GdUnitObjectAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd").new(current)
|
||||
|
||||
|
||||
func assert_result(current: Variant) -> GdUnitResultAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd").new(current)
|
||||
|
||||
|
||||
## An assertion tool that waits until a certain time for an expected function return value
|
||||
func assert_func(instance: Object, func_name: String, args := Array()) -> GdUnitFuncAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd").new(instance, func_name, args)
|
||||
|
||||
|
||||
## An assertion tool to verify for emitted signals until a certain time.
|
||||
func assert_signal(instance: Object) -> GdUnitSignalAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd").new(instance)
|
||||
|
||||
|
||||
## An assertion tool to test for failing assertions.[br]
|
||||
## This assert is only designed for internal use to verify failing asserts working as expected.[br]
|
||||
## Usage:
|
||||
## [codeblock]
|
||||
## assert_failure(func(): assert_bool(true).is_not_equal(true)) \
|
||||
## .has_message("Expecting:\n 'true'\n not equal to\n 'true'")
|
||||
## [/codeblock]
|
||||
func assert_failure(assertion: Callable) -> GdUnitFailureAssert:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd").new().execute(assertion)
|
||||
|
||||
|
||||
## An assertion tool to test for failing assertions.[br]
|
||||
## This assert is only designed for internal use to verify failing asserts working as expected.[br]
|
||||
## Usage:
|
||||
## [codeblock]
|
||||
## await assert_failure_await(func(): assert_bool(true).is_not_equal(true)) \
|
||||
## .has_message("Expecting:\n 'true'\n not equal to\n 'true'")
|
||||
## [/codeblock]
|
||||
func assert_failure_await(assertion: Callable) -> GdUnitFailureAssert:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
return await __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd").new().execute_and_await(assertion)
|
||||
|
||||
|
||||
## An assertion tool to verify Godot errors.[br]
|
||||
## You can use to verify certain Godot errors like failing assertions, push_error, push_warn.[br]
|
||||
## Usage:
|
||||
## [codeblock]
|
||||
## # tests no error occurred during execution of the code
|
||||
## await assert_error(func (): return 0 )\
|
||||
## .is_success()
|
||||
##
|
||||
## # tests a push_error('test error') occured during execution of the code
|
||||
## await assert_error(func (): push_error('test error') )\
|
||||
## .is_push_error('test error')
|
||||
## [/codeblock]
|
||||
func assert_error(current: Callable) -> GdUnitGodotErrorAssert:
|
||||
return __lazy_load("res://addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd").new(current)
|
||||
|
||||
|
||||
## Explicitly fails the current test indicating that the feature is not yet implemented.[br]
|
||||
## This function is useful during development when you want to write test cases before implementing the actual functionality.[br]
|
||||
## It provides a clear indication that the test failure is expected because the feature is still under development.[br]
|
||||
## Usage:
|
||||
## [codeblock]
|
||||
## # Test for a feature that will be implemented later
|
||||
## func test_advanced_ai_behavior():
|
||||
## assert_not_yet_implemented()
|
||||
##
|
||||
## [/codeblock]
|
||||
func assert_not_yet_implemented() -> void:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
__gdunit_assert().new(null).do_fail_not_implemented()
|
||||
|
||||
|
||||
## Explicitly fails the current test with a custom error message.[br]
|
||||
## This function reports an error but does not terminate test execution automatically.[br]
|
||||
## You must use 'return' after calling fail() to stop the test since GDScript has no exception support.[br]
|
||||
## Useful for complex conditional testing scenarios where standard assertions are insufficient.[br]
|
||||
## Usage:
|
||||
## [codeblock]
|
||||
## # Fail test when conditions are not met
|
||||
## if !custom_check(player):
|
||||
## fail("Player should be alive but has %d health" % player.health)
|
||||
## return
|
||||
##
|
||||
## # Continue with test if conditions pass
|
||||
## assert_that(player.health).is_greater(0)
|
||||
## [/codeblock]
|
||||
func fail(message: String) -> void:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
__gdunit_assert().new(null).do_fail_with_message(message)
|
||||
|
||||
|
||||
# --- internal stuff do not override!!!
|
||||
func ResourcePath() -> String:
|
||||
return get_script().resource_path
|
||||
@@ -0,0 +1 @@
|
||||
uid://bqh4y8rpdssrx
|
||||
@@ -0,0 +1,86 @@
|
||||
## A tuple implementation for GdUnit4 test assertions and value extraction.
|
||||
## @tutorial(GdUnit4 Array Assertions): https://mikeschulze.github.io/gdUnit4/latest/testing/assert-array/#extractv
|
||||
## @tutorial(GdUnit4 Testing Framework): https://mikeschulze.github.io/gdUnit4/
|
||||
## [br]
|
||||
## The GdUnitTuple class is a utility container designed specifically for the GdUnit4
|
||||
## testing framework. It enables advanced assertion operations, particularly when
|
||||
## extracting and comparing multiple values from complex test results.
|
||||
## [br]
|
||||
## [b]Primary Use Cases in Testing:[/b] [br]
|
||||
## - Extracting multiple properties from test objects with [method extractv]## [br]
|
||||
## - Grouping related assertion values for comparison## [br]
|
||||
## - Returning multiple values from test helper methods## [br]
|
||||
## - Organizing expected vs actual value pairs in assertions## [br]
|
||||
## [br]
|
||||
## [b]Example Usage in Tests:[/b]
|
||||
## [codeblock]
|
||||
## func test_player_stats_after_level_up():
|
||||
## var player = Player.new()
|
||||
## player.level_up()
|
||||
##
|
||||
## # Extract multiple properties using tuple
|
||||
## assert_array([player]) \
|
||||
## .extractv(extr("name"), extr("level"), extr("hp")) \
|
||||
## .contains(tuple("Hero", 2, 150))
|
||||
##
|
||||
## func test_enemy_spawn_positions():
|
||||
## var enemies: Array = spawn_enemies(3)
|
||||
##
|
||||
## # Verify multiple enemies have correct position data
|
||||
## assert_array(enemies) \
|
||||
## .extractv(extr("position.x"), extr("position.y")) \
|
||||
## .contains_exactly([
|
||||
## tuple(100, 200),
|
||||
## tuple(150, 200),
|
||||
## tuple(200, 200)
|
||||
## ])
|
||||
## [/codeblock]
|
||||
## [br]
|
||||
## [b]Integration with GdUnit4 Assertions:[/b] [br]
|
||||
## Tuples work seamlessly with array assertion methods like: [br]
|
||||
## - [code]contains()[/code] - Check if extracted values contain specific tuples [br]
|
||||
## - [code]contains_exactly()[/code] - Verify exact tuple matches [br]
|
||||
## - [code]is_equal()[/code] - Compare tuple equality [br]
|
||||
## [br]
|
||||
## [b]Note:[/b] This class is part of the GdUnit4 testing framework's internal
|
||||
## utilities and is primarily intended for use within test assertions rather
|
||||
## than production code.
|
||||
class_name GdUnitTuple
|
||||
extends RefCounted
|
||||
|
||||
var _values: Array = []
|
||||
|
||||
|
||||
## Initializes a new GdUnitTuple with test values.
|
||||
## [br]
|
||||
## Creates a tuple to hold multiple values extracted from test objects
|
||||
## or expected values for assertions. Commonly used with the [code]tuple()[/code]
|
||||
## helper function in GdUnit4 tests.
|
||||
## [br]
|
||||
## [b]Parameters:[/b]
|
||||
## - [code]...args[/code]: Variable number of values to store.
|
||||
func _init(...args: Array) -> void:
|
||||
_values = args
|
||||
|
||||
|
||||
## Returns the tuple's values as an array for assertion comparisons.
|
||||
## [br]
|
||||
## Provides access to the stored test values. Used internally by GdUnit4's
|
||||
## assertion system when comparing tuples in test validations.
|
||||
## [br]
|
||||
## [b]Returns:[/b]
|
||||
## An [Array] containing all values stored in the tuple.
|
||||
func values() -> Array:
|
||||
return _values
|
||||
|
||||
|
||||
## Returns a string representation for test output and debugging.
|
||||
## [br]
|
||||
## Formats the tuple for display in test results, error messages, and debug logs.
|
||||
## This method is automatically called by GdUnit4 when displaying assertion
|
||||
## failures involving tuples.
|
||||
## [br]
|
||||
## [b]Returns:[/b]
|
||||
## A [String] in the format "tuple([value1, value2, ...])"
|
||||
func _to_string() -> String:
|
||||
return "tuple(%s)" % str(_values)
|
||||
@@ -0,0 +1 @@
|
||||
uid://btxdkihimm6t2
|
||||
@@ -0,0 +1,9 @@
|
||||
## This is the base interface for value extraction
|
||||
class_name GdUnitValueExtractor
|
||||
extends RefCounted
|
||||
|
||||
|
||||
## Extracts a value by given implementation
|
||||
func extract_value(value :Variant) -> Variant:
|
||||
push_error("Uninplemented func 'extract_value'")
|
||||
return value
|
||||
@@ -0,0 +1 @@
|
||||
uid://be6abg0ghp37l
|
||||
@@ -0,0 +1,55 @@
|
||||
## An Assertion Tool to verify Vector values
|
||||
@abstract class_name GdUnitVectorAssert
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
## Verifies that the current value is null.
|
||||
@abstract func is_null() -> GdUnitVectorAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not null.
|
||||
@abstract func is_not_null() -> GdUnitVectorAssert
|
||||
|
||||
|
||||
## Verifies that the current value is equal to the given one.
|
||||
@abstract func is_equal(expected: Variant) -> GdUnitVectorAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not equal to expected one.
|
||||
@abstract func is_not_equal(expected: Variant) -> GdUnitVectorAssert
|
||||
|
||||
|
||||
## Verifies that the current and expected value are approximately equal.
|
||||
@abstract func is_equal_approx(expected: Variant, approx: Variant) -> GdUnitVectorAssert
|
||||
|
||||
|
||||
## Overrides the default failure message by given custom message.
|
||||
@abstract func override_failure_message(message: String) -> GdUnitVectorAssert
|
||||
|
||||
|
||||
## Appends a custom message to the failure message.
|
||||
@abstract func append_failure_message(message: String) -> GdUnitVectorAssert
|
||||
|
||||
|
||||
## Verifies that the current value is less than the given one.
|
||||
@abstract func is_less(expected: Variant) -> GdUnitVectorAssert
|
||||
|
||||
|
||||
## Verifies that the current value is less than or equal the given one.
|
||||
@abstract func is_less_equal(expected: Variant) -> GdUnitVectorAssert
|
||||
|
||||
|
||||
## Verifies that the current value is greater than the given one.
|
||||
@abstract func is_greater(expected: Variant) -> GdUnitVectorAssert
|
||||
|
||||
|
||||
## Verifies that the current value is greater than or equal the given one.
|
||||
@abstract func is_greater_equal(expected: Variant) -> GdUnitVectorAssert
|
||||
|
||||
|
||||
## Verifies that the current value is between the given boundaries (inclusive).
|
||||
@abstract func is_between(from: Variant, to: Variant) -> GdUnitVectorAssert
|
||||
|
||||
|
||||
## Verifies that the current value is not between the given boundaries (inclusive).
|
||||
@abstract func is_not_between(from: Variant, to: Variant) -> GdUnitVectorAssert
|
||||
@@ -0,0 +1 @@
|
||||
uid://bchhqrm7gxygm
|
||||
@@ -0,0 +1,25 @@
|
||||
# a value provider unsing a callback to get `next` value from a certain function
|
||||
class_name CallBackValueProvider
|
||||
extends ValueProvider
|
||||
|
||||
var _cb :Callable
|
||||
var _args :Array
|
||||
|
||||
|
||||
func _init(instance :Object, func_name :String, args :Array = Array(), force_error := true) -> void:
|
||||
_cb = Callable(instance, func_name);
|
||||
_args = args
|
||||
if force_error and not _cb.is_valid():
|
||||
push_error("Can't find function '%s' checked instance %s" % [func_name, instance])
|
||||
|
||||
|
||||
func get_value() -> Variant:
|
||||
if not _cb.is_valid():
|
||||
return null
|
||||
if _args.is_empty():
|
||||
return await _cb.call()
|
||||
return await _cb.callv(_args)
|
||||
|
||||
|
||||
func dispose() -> void:
|
||||
_cb = Callable()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bauwyi0k0fetp
|
||||
@@ -0,0 +1,13 @@
|
||||
# default value provider, simple returns the initial value
|
||||
class_name DefaultValueProvider
|
||||
extends ValueProvider
|
||||
|
||||
var _value: Variant
|
||||
|
||||
|
||||
func _init(value: Variant) -> void:
|
||||
_value = value
|
||||
|
||||
|
||||
func get_value() -> Variant:
|
||||
return _value
|
||||
@@ -0,0 +1 @@
|
||||
uid://b24r327dm6o6m
|
||||
@@ -0,0 +1,757 @@
|
||||
class_name GdAssertMessages
|
||||
extends Resource
|
||||
|
||||
|
||||
const SUB_COLOR := Color(1, 0, 0, .15)
|
||||
const ADD_COLOR := Color(0, 1, 0, .15)
|
||||
|
||||
const NO_ORPHAN_DETAILS := "⚠️No details available. Run tests in debug mode to collect details."
|
||||
|
||||
# Dictionary of control characters and their readable representations
|
||||
const CONTROL_CHARS = {
|
||||
"\n": "<LF>", # Line Feed
|
||||
"\r": "<CR>", # Carriage Return
|
||||
"\t": "<TAB>", # Tab
|
||||
"\b": "<BS>", # Backspace
|
||||
"\f": "<FF>", # Form Feed
|
||||
"\v": "<VT>", # Vertical Tab
|
||||
"\a": "<BEL>", # Bell
|
||||
"": "<ESC>" # Escape
|
||||
}
|
||||
|
||||
|
||||
static var _warn_color := GdUnitEditorColorTheme.state_warning.to_html()
|
||||
static var _error_color := GdUnitEditorColorTheme.state_failure.to_html()
|
||||
static var _value_color := GdUnitEditorColorTheme.value_color.to_html()
|
||||
|
||||
|
||||
static func format_dict(value :Variant) -> String:
|
||||
if not value is Dictionary:
|
||||
return str(value)
|
||||
|
||||
var dict_value: Dictionary = value
|
||||
if dict_value.is_empty():
|
||||
return "{ }"
|
||||
var as_rows := var_to_str(value).split("\n")
|
||||
for index in range( 1, as_rows.size()-1):
|
||||
as_rows[index] = " " + as_rows[index]
|
||||
as_rows[-1] = " " + as_rows[-1]
|
||||
return "\n".join(as_rows)
|
||||
|
||||
|
||||
# improved version of InputEvent as text
|
||||
static func input_event_as_text(event :InputEvent) -> String:
|
||||
var text := ""
|
||||
if event is InputEventKey:
|
||||
var key_event := event as InputEventKey
|
||||
text += """
|
||||
InputEventKey : keycode=%s (%s) pressed: %s
|
||||
physical_keycode: %s
|
||||
location: %s
|
||||
echo: %s""" % [
|
||||
key_event.keycode,
|
||||
key_event.as_text_keycode(),
|
||||
key_event.pressed,
|
||||
key_event.physical_keycode,
|
||||
key_event.location,
|
||||
key_event.echo]
|
||||
else:
|
||||
text += event.as_text()
|
||||
if event is InputEventMouse:
|
||||
var mouse_event := event as InputEventMouse
|
||||
text += """
|
||||
global_position: %s""" % mouse_event.global_position
|
||||
if event is InputEventWithModifiers:
|
||||
var mouse_event := event as InputEventWithModifiers
|
||||
text += """
|
||||
--------
|
||||
mods: %s
|
||||
shift: %s
|
||||
alt: %s
|
||||
control: %s
|
||||
meta: %s
|
||||
command: %s""" % [
|
||||
mouse_event.get_modifiers_mask(),
|
||||
mouse_event.shift_pressed,
|
||||
mouse_event.alt_pressed,
|
||||
mouse_event.ctrl_pressed,
|
||||
mouse_event.meta_pressed,
|
||||
mouse_event.command_or_control_autoremap]
|
||||
return text.dedent()
|
||||
|
||||
|
||||
static func _colored_string_div(characters: String) -> String:
|
||||
return colored_array_div(characters.to_utf32_buffer().to_int32_array())
|
||||
|
||||
|
||||
static func colored_array_div(characters: PackedInt32Array) -> String:
|
||||
if characters.is_empty():
|
||||
return "<empty>"
|
||||
var result := PackedInt32Array()
|
||||
var index := 0
|
||||
var missing_chars := PackedInt32Array()
|
||||
var additional_chars := PackedInt32Array()
|
||||
|
||||
while index < characters.size():
|
||||
var character := characters[index]
|
||||
match character:
|
||||
GdDiffTool.DIV_ADD:
|
||||
index += 1
|
||||
@warning_ignore("return_value_discarded")
|
||||
additional_chars.append(characters[index])
|
||||
GdDiffTool.DIV_SUB:
|
||||
index += 1
|
||||
@warning_ignore("return_value_discarded")
|
||||
missing_chars.append(characters[index])
|
||||
_:
|
||||
if not missing_chars.is_empty():
|
||||
result.append_array(format_chars(missing_chars, SUB_COLOR))
|
||||
missing_chars = PackedInt32Array()
|
||||
if not additional_chars.is_empty():
|
||||
result.append_array(format_chars(additional_chars, ADD_COLOR))
|
||||
additional_chars = PackedInt32Array()
|
||||
@warning_ignore("return_value_discarded")
|
||||
result.append(character)
|
||||
index += 1
|
||||
|
||||
result.append_array(format_chars(missing_chars, SUB_COLOR))
|
||||
result.append_array(format_chars(additional_chars, ADD_COLOR))
|
||||
return result.to_byte_array().get_string_from_utf32()
|
||||
|
||||
|
||||
static func _typed_value(value :Variant) -> String:
|
||||
return GdDefaultValueDecoder.decode(value)
|
||||
|
||||
|
||||
static func _warning(error :String) -> String:
|
||||
return "[color=%s]%s[/color]" % [_warn_color, error]
|
||||
|
||||
|
||||
static func _error(error :String) -> String:
|
||||
return "[color=%s]%s[/color]" % [_error_color, error]
|
||||
|
||||
|
||||
static func _nerror(number :Variant) -> String:
|
||||
match typeof(number):
|
||||
TYPE_INT:
|
||||
return "[color=%s]%d[/color]" % [_error_color, number]
|
||||
TYPE_FLOAT:
|
||||
return "[color=%s]%f[/color]" % [_error_color, number]
|
||||
_:
|
||||
return "[color=%s]%s[/color]" % [_error_color, str(number)]
|
||||
|
||||
|
||||
static func _colored(value: Variant, color: Color) -> String:
|
||||
return "[color=%s]%s[/color]" % [color.to_html(), value]
|
||||
|
||||
|
||||
static func _colored_value(value :Variant) -> String:
|
||||
match typeof(value):
|
||||
TYPE_STRING, TYPE_STRING_NAME:
|
||||
return "'[color=%s]%s[/color]'" % [_value_color, _colored_string_div(str(value))]
|
||||
TYPE_INT:
|
||||
return "[color=%s]%d[/color]" % [_value_color, value]
|
||||
TYPE_FLOAT:
|
||||
return "[color=%s]%s[/color]" % [_value_color, _typed_value(value)]
|
||||
TYPE_COLOR:
|
||||
return "'[color=%s]%s[/color]'" % [_value_color, _typed_value(value)]
|
||||
TYPE_OBJECT:
|
||||
if value == null:
|
||||
return "'[color=%s]<null>[/color]'" % [_value_color]
|
||||
if value is InputEvent:
|
||||
var ie: InputEvent = value
|
||||
return "[color=%s]<%s>[/color]" % [_value_color, input_event_as_text(ie)]
|
||||
var obj_value: Object = value
|
||||
if obj_value.has_method("_to_string"):
|
||||
return "[color=%s]<%s>[/color]" % [_value_color, str(value)]
|
||||
return "[color=%s]<%s>[/color]" % [_value_color, obj_value.get_class()]
|
||||
TYPE_DICTIONARY:
|
||||
return "'[color=%s]%s[/color]'" % [_value_color, format_dict(value)]
|
||||
_:
|
||||
if GdArrayTools.is_array_type(value):
|
||||
return "'[color=%s]%s[/color]'" % [_value_color, _typed_value(value)]
|
||||
return "'[color=%s]%s[/color]'" % [_value_color, value]
|
||||
|
||||
|
||||
|
||||
static func _index_report_as_table(index_reports :Array) -> String:
|
||||
var table := "[table=3]$cells[/table]"
|
||||
var header := "[cell][right][b]$text[/b][/right]\t[/cell]"
|
||||
var cell := "[cell][right]$text[/right]\t[/cell]"
|
||||
var cells := header.replace("$text", "Index") + header.replace("$text", "Current") + header.replace("$text", "Expected")
|
||||
for report :Variant in index_reports:
|
||||
var index :String = str(report["index"])
|
||||
var current :String = str(report["current"])
|
||||
var expected :String = str(report["expected"])
|
||||
cells += cell.replace("$text", index) + cell.replace("$text", current) + cell.replace("$text", expected)
|
||||
return table.replace("$cells", cells)
|
||||
|
||||
|
||||
static func orphan_warning(orphans_count: int, info: String) -> String:
|
||||
if EngineDebugger.is_active():
|
||||
return """
|
||||
%s Detected %s possible orphan nodes.
|
||||
To capture detailed information insert
|
||||
[code]%s[/code]
|
||||
""".dedent().trim_prefix("\n").trim_suffix("\n") % [
|
||||
_warning("WARNING:"),
|
||||
_colored_value(orphans_count),
|
||||
_colored(info, Color.STEEL_BLUE)
|
||||
]
|
||||
return """
|
||||
%s Detected %s possible orphan nodes.
|
||||
%s
|
||||
""".dedent().trim_prefix("\n").trim_suffix("\n") % [
|
||||
_warning("WARNING:"),
|
||||
_colored_value(orphans_count),
|
||||
_warning(NO_ORPHAN_DETAILS)
|
||||
]
|
||||
|
||||
|
||||
static func orphan_detected(orphan_count: int) -> String:
|
||||
if EngineDebugger.is_active():
|
||||
return "%s Detected %s orphan nodes." % [
|
||||
_warning("WARNING:"),
|
||||
_colored_value(orphan_count)
|
||||
]
|
||||
return """
|
||||
%s Detected %s orphan nodes.
|
||||
%s""".dedent().trim_prefix("\n") % [
|
||||
_warning("WARNING:"),
|
||||
_colored_value(orphan_count),
|
||||
_warning(NO_ORPHAN_DETAILS)
|
||||
]
|
||||
|
||||
|
||||
static func orphan_node_info(orphan_info: GdUnitOrphanNodeInfo) -> String:
|
||||
var message := "<%s> Id:%s" % [
|
||||
_colored(orphan_info._type, GdUnitEditorColorTheme.engine_type_color),
|
||||
_colored(orphan_info._id, GdUnitEditorColorTheme.engine_type_color)
|
||||
]
|
||||
if orphan_info._stack_element == null:
|
||||
message += _warning("\n\tNo source info available")
|
||||
return message
|
||||
|
||||
|
||||
static func fuzzer_interuped(iterations: int, error: String) -> String:
|
||||
return "%s %s %s\n %s" % [
|
||||
_error("Found an error after"),
|
||||
_colored_value(iterations + 1),
|
||||
_error("test iterations"),
|
||||
error]
|
||||
|
||||
|
||||
static func test_timeout(timeout :int) -> String:
|
||||
return "%s\n %s" % [_error("Timeout !"), _colored_value("Test timed out after %s" % LocalTime.elapsed(timeout))]
|
||||
|
||||
|
||||
static func test_session_terminated() -> String:
|
||||
return "%s" % _error("Test Session Terminated")
|
||||
|
||||
|
||||
# gdlint:disable = mixed-tabs-and-spaces
|
||||
static func test_suite_skipped(hint :String, skip_count :int) -> String:
|
||||
return """
|
||||
%s
|
||||
Skipped %s tests
|
||||
Reason: %s
|
||||
""".dedent().trim_prefix("\n")\
|
||||
% [_error("The Entire test-suite is skipped!"), _colored_value(skip_count), _colored_value(hint)]
|
||||
|
||||
|
||||
static func test_skipped(hint :String) -> String:
|
||||
return """
|
||||
%s
|
||||
Reason: %s
|
||||
""".dedent().trim_prefix("\n")\
|
||||
% [_error("This test is skipped!"), _colored_value(hint)]
|
||||
|
||||
|
||||
static func error_not_implemented() -> String:
|
||||
return _error("Test not implemented!")
|
||||
|
||||
|
||||
static func error_with_message(message: String) -> String:
|
||||
return _error(message)
|
||||
|
||||
|
||||
static func error_is_null(current :Variant) -> String:
|
||||
return "%s %s but was %s" % [_error("Expecting:"), _colored_value(null), _colored_value(current)]
|
||||
|
||||
|
||||
static func error_is_not_null() -> String:
|
||||
return "%s %s" % [_error("Expecting: not to be"), _colored_value(null)]
|
||||
|
||||
|
||||
static func error_equal(current :Variant, expected :Variant, index_reports :Array = []) -> String:
|
||||
var report := """
|
||||
%s
|
||||
%s
|
||||
but was
|
||||
%s""".dedent().trim_prefix("\n") % [_error("Expecting:"), _colored_value(expected), _colored_value(current)]
|
||||
if not index_reports.is_empty():
|
||||
report += "\n\n%s\n%s" % [_error("Differences found:"), _index_report_as_table(index_reports)]
|
||||
return report
|
||||
|
||||
|
||||
static func error_not_equal(current :Variant, expected :Variant) -> String:
|
||||
return "%s\n %s\n not equal to\n %s" % [_error("Expecting:"), _colored_value(expected), _colored_value(current)]
|
||||
|
||||
|
||||
static func error_not_equal_case_insensetiv(current :Variant, expected :Variant) -> String:
|
||||
return "%s\n %s\n not equal to (case insensitiv)\n %s" % [
|
||||
_error("Expecting:"), _colored_value(expected), _colored_value(current)]
|
||||
|
||||
|
||||
static func error_is_empty(current :Variant) -> String:
|
||||
return "%s\n must be empty but was\n %s" % [_error("Expecting:"), _colored_value(current)]
|
||||
|
||||
|
||||
static func error_is_not_empty() -> String:
|
||||
return "%s\n must not be empty" % [_error("Expecting:")]
|
||||
|
||||
|
||||
static func error_is_same(current :Variant, expected :Variant) -> String:
|
||||
return "%s\n %s\n to refer to the same object\n %s" % [_error("Expecting:"), _colored_value(expected), _colored_value(current)]
|
||||
|
||||
|
||||
@warning_ignore("unused_parameter")
|
||||
static func error_not_same(_current :Variant, expected :Variant) -> String:
|
||||
return "%s\n %s" % [_error("Expecting not same:"), _colored_value(expected)]
|
||||
|
||||
|
||||
static func error_not_same_error(current :Variant, expected :Variant) -> String:
|
||||
return "%s\n %s\n but was\n %s" % [_error("Expecting error message:"), _colored_value(expected), _colored_value(current)]
|
||||
|
||||
|
||||
static func error_is_instanceof(current: GdUnitResult, expected :GdUnitResult) -> String:
|
||||
return "%s\n %s\n But it was %s" % [_error("Expected instance of:"),\
|
||||
_colored_value(expected.or_else(null)), _colored_value(current.or_else(null))]
|
||||
|
||||
|
||||
# -- Boolean Assert specific messages -----------------------------------------------------
|
||||
static func error_is_true(current :Variant) -> String:
|
||||
return "%s %s but is %s" % [_error("Expecting:"), _colored_value(true), _colored_value(current)]
|
||||
|
||||
|
||||
static func error_is_false(current :Variant) -> String:
|
||||
return "%s %s but is %s" % [_error("Expecting:"), _colored_value(false), _colored_value(current)]
|
||||
|
||||
|
||||
# - Integer/Float Assert specific messages -----------------------------------------------------
|
||||
|
||||
static func error_is_even(current :Variant) -> String:
|
||||
return "%s\n %s must be even" % [_error("Expecting:"), _colored_value(current)]
|
||||
|
||||
|
||||
static func error_is_odd(current :Variant) -> String:
|
||||
return "%s\n %s must be odd" % [_error("Expecting:"), _colored_value(current)]
|
||||
|
||||
|
||||
static func error_is_negative(current :Variant) -> String:
|
||||
return "%s\n %s be negative" % [_error("Expecting:"), _colored_value(current)]
|
||||
|
||||
|
||||
static func error_is_not_negative(current :Variant) -> String:
|
||||
return "%s\n %s be not negative" % [_error("Expecting:"), _colored_value(current)]
|
||||
|
||||
|
||||
static func error_is_zero(current :Variant) -> String:
|
||||
return "%s\n equal to 0 but is %s" % [_error("Expecting:"), _colored_value(current)]
|
||||
|
||||
|
||||
static func error_is_not_zero() -> String:
|
||||
return "%s\n not equal to 0" % [_error("Expecting:")]
|
||||
|
||||
|
||||
static func error_is_wrong_type(current_type :Variant.Type, expected_type :Variant.Type) -> String:
|
||||
return "%s\n Expecting type %s but is %s" % [
|
||||
_error("Unexpected type comparison:"),
|
||||
_colored_value(GdObjects.type_as_string(current_type)),
|
||||
_colored_value(GdObjects.type_as_string(expected_type))]
|
||||
|
||||
|
||||
static func error_is_value(operation :int, current :Variant, expected :Variant, expected2 :Variant = null) -> String:
|
||||
match operation:
|
||||
Comparator.EQUAL:
|
||||
return "%s\n %s but was %s" % [_error("Expecting:"), _colored_value(expected), _colored_value(current)]
|
||||
Comparator.LESS_THAN:
|
||||
return "%s\n %s but was %s" % [_error("Expecting to be less than:"), _colored_value(expected), _colored_value(current)]
|
||||
Comparator.LESS_EQUAL:
|
||||
return "%s\n %s but was %s" % [_error("Expecting to be less than or equal:"), _colored_value(expected), _colored_value(current)]
|
||||
Comparator.GREATER_THAN:
|
||||
return "%s\n %s but was %s" % [_error("Expecting to be greater than:"), _colored_value(expected), _colored_value(current)]
|
||||
Comparator.GREATER_EQUAL:
|
||||
return "%s\n %s but was %s" % [_error("Expecting to be greater than or equal:"), _colored_value(expected), _colored_value(current)]
|
||||
Comparator.BETWEEN_EQUAL:
|
||||
return "%s\n %s\n in range between\n %s <> %s" % [
|
||||
_error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)]
|
||||
Comparator.NOT_BETWEEN_EQUAL:
|
||||
return "%s\n %s\n not in range between\n %s <> %s" % [
|
||||
_error("Expecting:"), _colored_value(current), _colored_value(expected), _colored_value(expected2)]
|
||||
return "TODO create expected message"
|
||||
|
||||
|
||||
static func error_is_in(current :Variant, expected :Array) -> String:
|
||||
return "%s\n %s\n is in\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(str(expected))]
|
||||
|
||||
|
||||
static func error_is_not_in(current :Variant, expected :Array) -> String:
|
||||
return "%s\n %s\n is not in\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(str(expected))]
|
||||
|
||||
|
||||
# - StringAssert ---------------------------------------------------------------------------------
|
||||
static func error_equal_ignoring_case(current :Variant, expected :Variant) -> String:
|
||||
return "%s\n %s\n but was\n %s (ignoring case)" % [_error("Expecting:"), _colored_value(expected), _colored_value(current)]
|
||||
|
||||
|
||||
static func error_contains(current :Variant, expected :Variant) -> String:
|
||||
return "%s\n %s\n do contains\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)]
|
||||
|
||||
|
||||
static func error_not_contains(current :Variant, expected :Variant) -> String:
|
||||
return "%s\n %s\n not do contain\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)]
|
||||
|
||||
|
||||
static func error_contains_ignoring_case(current :Variant, expected :Variant) -> String:
|
||||
return "%s\n %s\n contains\n %s\n (ignoring case)" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)]
|
||||
|
||||
|
||||
static func error_not_contains_ignoring_case(current :Variant, expected :Variant) -> String:
|
||||
return "%s\n %s\n not do contains\n %s\n (ignoring case)" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)]
|
||||
|
||||
|
||||
static func error_starts_with(current :Variant, expected :Variant) -> String:
|
||||
return "%s\n %s\n to start with\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)]
|
||||
|
||||
|
||||
static func error_ends_with(current :Variant, expected :Variant) -> String:
|
||||
return "%s\n %s\n to end with\n %s" % [_error("Expecting:"), _colored_value(current), _colored_value(expected)]
|
||||
|
||||
|
||||
static func error_has_length(current :Variant, expected: int, compare_operator :int) -> String:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
var current_length :Variant = current.length() if current != null else null
|
||||
match compare_operator:
|
||||
Comparator.EQUAL:
|
||||
return "%s\n %s but was %s in\n %s" % [
|
||||
_error("Expecting size:"), _colored_value(expected), _nerror(current_length), _colored_value(current)]
|
||||
Comparator.LESS_THAN:
|
||||
return "%s\n %s but was %s in\n %s" % [
|
||||
_error("Expecting size to be less than:"), _colored_value(expected), _nerror(current_length), _colored_value(current)]
|
||||
Comparator.LESS_EQUAL:
|
||||
return "%s\n %s but was %s in\n %s" % [
|
||||
_error("Expecting size to be less than or equal:"), _colored_value(expected),
|
||||
_nerror(current_length), _colored_value(current)]
|
||||
Comparator.GREATER_THAN:
|
||||
return "%s\n %s but was %s in\n %s" % [
|
||||
_error("Expecting size to be greater than:"), _colored_value(expected),
|
||||
_nerror(current_length), _colored_value(current)]
|
||||
Comparator.GREATER_EQUAL:
|
||||
return "%s\n %s but was %s in\n %s" % [
|
||||
_error("Expecting size to be greater than or equal:"), _colored_value(expected),
|
||||
_nerror(current_length), _colored_value(current)]
|
||||
return "TODO create expected message"
|
||||
|
||||
|
||||
# - ArrayAssert specific messgaes ---------------------------------------------------
|
||||
|
||||
static func error_arr_contains(current: Variant, expected: Variant, not_expect: Variant, not_found: Variant, by_reference: bool) -> String:
|
||||
var failure_message := "Expecting contains SAME elements:" if by_reference else "Expecting contains elements:"
|
||||
var error := "%s\n %s\n do contains (in any order)\n %s" % [
|
||||
_error(failure_message), _colored_value(current), _colored_value(expected)]
|
||||
if not is_empty(not_expect):
|
||||
error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect)
|
||||
if not is_empty(not_found):
|
||||
var prefix := "but" if is_empty(not_expect) else "and"
|
||||
error += "\n%s could not find elements:\n %s" % [prefix, _colored_value(not_found)]
|
||||
return error
|
||||
|
||||
|
||||
static func error_arr_contains_exactly(
|
||||
current: Variant,
|
||||
expected: Variant,
|
||||
not_expect: Variant,
|
||||
not_found: Variant, compare_mode: GdObjects.COMPARE_MODE) -> String:
|
||||
var failure_message := (
|
||||
"Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST
|
||||
else "Expecting contains SAME exactly elements:"
|
||||
)
|
||||
if is_empty(not_expect) and is_empty(not_found):
|
||||
var arr_current: Array = current
|
||||
var arr_expected: Array = expected
|
||||
var diff := _find_first_diff(arr_current, arr_expected)
|
||||
return "%s\n %s\n do contains (in same order)\n %s\n but has different order %s" % [
|
||||
_error(failure_message), _colored_value(current), _colored_value(expected), diff]
|
||||
|
||||
var error := "%s\n %s\n do contains (in same order)\n %s" % [
|
||||
_error(failure_message), _colored_value(current), _colored_value(expected)]
|
||||
if not is_empty(not_expect):
|
||||
error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect)
|
||||
if not is_empty(not_found):
|
||||
var prefix := "but" if is_empty(not_expect) else "and"
|
||||
error += "\n%s could not find elements:\n %s" % [prefix, _colored_value(not_found)]
|
||||
return error
|
||||
|
||||
|
||||
static func error_arr_contains_exactly_in_any_order(
|
||||
current: Variant,
|
||||
expected: Variant,
|
||||
not_expect: Variant,
|
||||
not_found: Variant,
|
||||
compare_mode: GdObjects.COMPARE_MODE) -> String:
|
||||
|
||||
var failure_message := (
|
||||
"Expecting contains exactly elements:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST
|
||||
else "Expecting contains SAME exactly elements:"
|
||||
)
|
||||
var error := "%s\n %s\n do contains exactly (in any order)\n %s" % [
|
||||
_error(failure_message), _colored_value(current), _colored_value(expected)]
|
||||
if not is_empty(not_expect):
|
||||
error += "\nbut some elements where not expected:\n %s" % _colored_value(not_expect)
|
||||
if not is_empty(not_found):
|
||||
var prefix := "but" if is_empty(not_expect) else "and"
|
||||
error += "\n%s could not find elements:\n %s" % [prefix, _colored_value(not_found)]
|
||||
return error
|
||||
|
||||
|
||||
static func error_arr_not_contains(current: Variant, expected: Variant, found: Variant, compare_mode: GdObjects.COMPARE_MODE) -> String:
|
||||
var failure_message := "Expecting:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST else "Expecting SAME:"
|
||||
var error := "%s\n %s\n do not contains\n %s" % [
|
||||
_error(failure_message), _colored_value(current), _colored_value(expected)]
|
||||
if not is_empty(found):
|
||||
error += "\n but found elements:\n %s" % _colored_value(found)
|
||||
return error
|
||||
|
||||
|
||||
# - DictionaryAssert specific messages ----------------------------------------------
|
||||
static func error_contains_keys(current :Array, expected :Array, keys_not_found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String:
|
||||
var failure := (
|
||||
"Expecting contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST
|
||||
else "Expecting contains SAME keys:"
|
||||
)
|
||||
return "%s\n %s\n to contains:\n %s\n but can't find key's:\n %s" % [
|
||||
_error(failure), _colored_value(current), _colored_value(expected), _colored_value(keys_not_found)]
|
||||
|
||||
|
||||
static func error_not_contains_keys(current :Array, expected :Array, keys_not_found :Array, compare_mode :GdObjects.COMPARE_MODE) -> String:
|
||||
var failure := (
|
||||
"Expecting NOT contains keys:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST
|
||||
else "Expecting NOT contains SAME keys"
|
||||
)
|
||||
return "%s\n %s\n do not contains:\n %s\n but contains key's:\n %s" % [
|
||||
_error(failure), _colored_value(current), _colored_value(expected), _colored_value(keys_not_found)]
|
||||
|
||||
|
||||
static func error_contains_key_value(key :Variant, value :Variant, current_value :Variant, compare_mode :GdObjects.COMPARE_MODE) -> String:
|
||||
var failure := (
|
||||
"Expecting contains key and value:" if compare_mode == GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST
|
||||
else "Expecting contains SAME key and value:"
|
||||
)
|
||||
return "%s\n %s : %s\n but contains\n %s : %s" % [
|
||||
_error(failure), _colored_value(key), _colored_value(value), _colored_value(key), _colored_value(current_value)]
|
||||
|
||||
|
||||
# - ResultAssert specific errors ----------------------------------------------------
|
||||
static func error_result_is_empty(current :GdUnitResult) -> String:
|
||||
return _result_error_message(current, GdUnitResult.EMPTY)
|
||||
|
||||
|
||||
static func error_result_is_success(current :GdUnitResult) -> String:
|
||||
return _result_error_message(current, GdUnitResult.SUCCESS)
|
||||
|
||||
|
||||
static func error_result_is_warning(current :GdUnitResult) -> String:
|
||||
return _result_error_message(current, GdUnitResult.WARN)
|
||||
|
||||
|
||||
static func error_result_is_error(current :GdUnitResult) -> String:
|
||||
return _result_error_message(current, GdUnitResult.ERROR)
|
||||
|
||||
|
||||
static func error_result_has_message(current :String, expected :String) -> String:
|
||||
return "%s\n %s\n but was\n %s." % [_error("Expecting:"), _colored_value(expected), _colored_value(current)]
|
||||
|
||||
|
||||
static func error_result_has_message_on_success(expected :String) -> String:
|
||||
return "%s\n %s\n but the GdUnitResult is a success." % [_error("Expecting:"), _colored_value(expected)]
|
||||
|
||||
|
||||
static func error_result_is_value(current :Variant, expected :Variant) -> String:
|
||||
return "%s\n %s\n but was\n %s." % [_error("Expecting to contain same value:"), _colored_value(expected), _colored_value(current)]
|
||||
|
||||
|
||||
static func _result_error_message(current :GdUnitResult, expected_type :int) -> String:
|
||||
if current == null:
|
||||
return _error("Expecting the result must be a %s but was <null>." % result_type(expected_type))
|
||||
if current.is_success():
|
||||
return _error("Expecting the result must be a %s but was SUCCESS." % result_type(expected_type))
|
||||
var error := "Expecting the result must be a %s but was %s:" % [result_type(expected_type), result_type(current._state)]
|
||||
return "%s\n %s" % [_error(error), _colored_value(result_message(current))]
|
||||
|
||||
|
||||
static func error_interrupted(func_name :String, expected :Variant, elapsed :String) -> String:
|
||||
func_name = humanized(func_name)
|
||||
if expected == null:
|
||||
return "%s %s but timed out after %s" % [_error("Expected:"), func_name, elapsed]
|
||||
return "%s %s %s but timed out after %s" % [_error("Expected:"), func_name, _colored_value(expected), elapsed]
|
||||
|
||||
|
||||
static func error_wait_signal(signal_name :String, args :Array, elapsed :String) -> String:
|
||||
if args.is_empty():
|
||||
return "%s %s but timed out after %s" % [
|
||||
_error("Expecting emit signal:"), _colored_value(signal_name + "()"), elapsed]
|
||||
return "%s %s but timed out after %s" % [
|
||||
_error("Expecting emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed]
|
||||
|
||||
|
||||
static func error_signal_emitted(signal_name :String, args :Array, elapsed :String) -> String:
|
||||
if args.is_empty():
|
||||
return "%s %s but is emitted after %s" % [
|
||||
_error("Expecting do not emit signal:"), _colored_value(signal_name + "()"), elapsed]
|
||||
return "%s %s but is emitted after %s" % [
|
||||
_error("Expecting do not emit signal:"), _colored_value(signal_name + "(" + str(args) + ")"), elapsed]
|
||||
|
||||
|
||||
static func error_await_signal_on_invalid_instance(source :Variant, signal_name :String, args :Array) -> String:
|
||||
return "%s\n await_signal_on(%s, %s, %s)" % [
|
||||
_error("Invalid source! Can't await on signal:"), _colored_value(source), signal_name, args]
|
||||
|
||||
|
||||
static func result_type(type :int) -> String:
|
||||
match type:
|
||||
GdUnitResult.SUCCESS: return "SUCCESS"
|
||||
GdUnitResult.WARN: return "WARNING"
|
||||
GdUnitResult.ERROR: return "ERROR"
|
||||
GdUnitResult.EMPTY: return "EMPTY"
|
||||
return "UNKNOWN"
|
||||
|
||||
|
||||
static func result_message(result :GdUnitResult) -> String:
|
||||
match result._state:
|
||||
GdUnitResult.SUCCESS: return ""
|
||||
GdUnitResult.WARN: return result.warn_message()
|
||||
GdUnitResult.ERROR: return result.error_message()
|
||||
GdUnitResult.EMPTY: return ""
|
||||
return "UNKNOWN"
|
||||
# -----------------------------------------------------------------------------------
|
||||
|
||||
# - Spy|Mock specific errors ----------------------------------------------------
|
||||
static func error_no_more_interactions(summary :Dictionary) -> String:
|
||||
var interactions := PackedStringArray()
|
||||
for args :Array in summary.keys():
|
||||
var times :int = summary[args]
|
||||
@warning_ignore("return_value_discarded")
|
||||
interactions.append(_format_arguments(args, times))
|
||||
return "%s\n%s\n%s" % [_error("Expecting no more interactions!"), _error("But found interactions on:"), "\n".join(interactions)]
|
||||
|
||||
|
||||
static func error_validate_interactions(current_interactions: Dictionary, expected_interactions: Dictionary) -> String:
|
||||
var collected_interactions := PackedStringArray()
|
||||
for args: Array in current_interactions.keys():
|
||||
var times: int = current_interactions[args]
|
||||
@warning_ignore("return_value_discarded")
|
||||
collected_interactions.append(_format_arguments(args, times))
|
||||
|
||||
var arguments: Array = expected_interactions.keys()[0]
|
||||
var interactions: int = expected_interactions.values()[0]
|
||||
var expected_interaction := _format_arguments(arguments, interactions)
|
||||
return "%s\n%s\n%s\n%s" % [
|
||||
_error("Expecting interaction on:"), expected_interaction, _error("But found interactions on:"), "\n".join(collected_interactions)]
|
||||
|
||||
|
||||
static func _format_arguments(args :Array, times :int) -> String:
|
||||
var fname :String = args[0]
|
||||
var fargs := args.slice(1) as Array
|
||||
var typed_args := _to_typed_args(fargs)
|
||||
var fsignature := _colored_value("%s(%s)" % [fname, ", ".join(typed_args)])
|
||||
return " %s %d time's" % [fsignature, times]
|
||||
|
||||
|
||||
static func _to_typed_args(args :Array) -> PackedStringArray:
|
||||
var typed := PackedStringArray()
|
||||
for arg :Variant in args:
|
||||
@warning_ignore("return_value_discarded")
|
||||
typed.append(_format_arg(arg) + " :" + GdObjects.type_as_string(typeof(arg)))
|
||||
return typed
|
||||
|
||||
|
||||
static func _format_arg(arg :Variant) -> String:
|
||||
if arg is InputEvent:
|
||||
var ie: InputEvent = arg
|
||||
return input_event_as_text(ie)
|
||||
return str(arg)
|
||||
|
||||
|
||||
static func _find_first_diff(left :Array, right :Array) -> String:
|
||||
for index in left.size():
|
||||
var l :Variant = left[index]
|
||||
var r :Variant = "<no entry>" if index >= right.size() else right[index]
|
||||
if not GdObjects.equals(l, r):
|
||||
return "at position %s\n '%s' vs '%s'" % [_colored_value(index), _typed_value(l), _typed_value(r)]
|
||||
return ""
|
||||
|
||||
|
||||
static func error_has_size(current :Variant, expected: int) -> String:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
var current_size :Variant = null if current == null else current.size()
|
||||
return "%s\n %s\n but was\n %s" % [_error("Expecting size:"), _colored_value(expected), _colored_value(current_size)]
|
||||
|
||||
|
||||
static func error_contains_exactly(current: Array, expected: Array) -> String:
|
||||
return "%s\n %s\n but was\n %s" % [_error("Expecting exactly equal:"), _colored_value(expected), _colored_value(current)]
|
||||
|
||||
|
||||
static func format_chars(characters: PackedInt32Array, type: Color) -> PackedInt32Array:
|
||||
if characters.size() == 0:# or characters[0] == 10:
|
||||
return characters
|
||||
|
||||
# Replace each control character with its readable form
|
||||
var formatted_text := characters.to_byte_array().get_string_from_utf32()
|
||||
for control_char: String in CONTROL_CHARS:
|
||||
var replace_text: String = CONTROL_CHARS[control_char]
|
||||
formatted_text = formatted_text.replace(control_char, replace_text)
|
||||
|
||||
# Handle special ASCII control characters (0x00-0x1F, 0x7F)
|
||||
var ascii_text := ""
|
||||
for i in formatted_text.length():
|
||||
var character := formatted_text[i]
|
||||
var code := character.unicode_at(0)
|
||||
if code < 0x20 and not CONTROL_CHARS.has(character): # Control characters not handled above
|
||||
ascii_text += "<0x%02X>" % code
|
||||
elif code == 0x7F: # DEL character
|
||||
ascii_text += "<DEL>"
|
||||
else:
|
||||
ascii_text += character
|
||||
|
||||
var message := "[bgcolor=#%s][color=white]%s[/color][/bgcolor]" % [
|
||||
type.to_html(),
|
||||
ascii_text
|
||||
]
|
||||
|
||||
var result := PackedInt32Array()
|
||||
result.append_array(message.to_utf32_buffer().to_int32_array())
|
||||
return result
|
||||
|
||||
|
||||
static func format_invalid(value :String) -> String:
|
||||
return "[bgcolor=#%s][color=with]%s[/color][/bgcolor]" % [SUB_COLOR.to_html(), value]
|
||||
|
||||
|
||||
static func humanized(value :String) -> String:
|
||||
return value.replace("_", " ")
|
||||
|
||||
|
||||
static func build_failure_message(failure :String, additional_failure_message: String, custom_failure_message: String) -> String:
|
||||
var message := failure if custom_failure_message.is_empty() else custom_failure_message
|
||||
if additional_failure_message.is_empty():
|
||||
return message
|
||||
return """
|
||||
%s
|
||||
[color=LIME_GREEN][b]Additional info:[/b][/color]
|
||||
%s""".dedent().trim_prefix("\n") % [message, additional_failure_message]
|
||||
|
||||
|
||||
static func is_empty(value: Variant) -> bool:
|
||||
var arry_value: Array = value
|
||||
return arry_value != null and arry_value.is_empty()
|
||||
@@ -0,0 +1 @@
|
||||
uid://cdmds6thunddc
|
||||
@@ -0,0 +1,57 @@
|
||||
class_name GdAssertReports
|
||||
extends RefCounted
|
||||
|
||||
const LAST_ERROR = "last_assert_error_message"
|
||||
const LAST_ERROR_LINE = "last_assert_error_line"
|
||||
|
||||
|
||||
static func report_success() -> void:
|
||||
GdUnitSignals.instance().gdunit_set_test_failed.emit(false)
|
||||
GdAssertReports.set_last_error_line_number(-1)
|
||||
Engine.remove_meta(LAST_ERROR)
|
||||
|
||||
|
||||
static func report_warning(message :String, line_number :int) -> void:
|
||||
GdUnitSignals.instance().gdunit_set_test_failed.emit(false)
|
||||
send_report(GdUnitReport.new().create(GdUnitReport.WARN, line_number, message))
|
||||
|
||||
|
||||
static func report_error(error: GdUnitError) -> void:
|
||||
GdUnitSignals.instance().gdunit_set_test_failed.emit(true)
|
||||
GdAssertReports.set_last_error_line_number(error._line_number)
|
||||
Engine.set_meta(LAST_ERROR, error._message)
|
||||
Engine.set_meta("GD_TEST_FAILURE", true)
|
||||
GdUnitThreadManager.get_current_context().get_execution_context().set_error(error)
|
||||
|
||||
# if we expect to fail we handle as success test
|
||||
if _do_expect_assert_failing():
|
||||
return
|
||||
send_report(GdUnitReport.new().from_error(GdUnitReport.FAILURE, error))
|
||||
|
||||
|
||||
static func reset_last_error_line_number() -> void:
|
||||
Engine.remove_meta(LAST_ERROR_LINE)
|
||||
|
||||
|
||||
static func set_last_error_line_number(line_number :int) -> void:
|
||||
Engine.set_meta(LAST_ERROR_LINE, line_number)
|
||||
|
||||
|
||||
static func get_last_error_line_number() -> int:
|
||||
if Engine.has_meta(LAST_ERROR_LINE):
|
||||
return Engine.get_meta(LAST_ERROR_LINE)
|
||||
return -1
|
||||
|
||||
|
||||
static func _do_expect_assert_failing() -> bool:
|
||||
if Engine.has_meta(GdUnitConstants.EXPECT_ASSERT_REPORT_FAILURES):
|
||||
return Engine.get_meta(GdUnitConstants.EXPECT_ASSERT_REPORT_FAILURES)
|
||||
return false
|
||||
|
||||
|
||||
static func current_failure() -> String:
|
||||
return Engine.get_meta(LAST_ERROR)
|
||||
|
||||
|
||||
static func send_report(report :GdUnitReport) -> void:
|
||||
GdUnitThreadManager.get_current_context().get_execution_context().add_report(report)
|
||||
@@ -0,0 +1 @@
|
||||
uid://d2yux5f864g6e
|
||||
@@ -0,0 +1,420 @@
|
||||
class_name GdUnitArrayAssertImpl
|
||||
extends GdUnitArrayAssert
|
||||
|
||||
|
||||
var _base: GdUnitAssertImpl
|
||||
var _current_value_provider: ValueProvider
|
||||
var _type_check: bool
|
||||
|
||||
|
||||
func _init(current: Variant, type_check := true) -> void:
|
||||
_type_check = type_check
|
||||
_current_value_provider = DefaultValueProvider.new(current)
|
||||
_base = GdUnitAssertImpl.new(current)
|
||||
# save the actual assert instance on the current thread context
|
||||
GdUnitThreadManager.get_current_context().set_assert(self)
|
||||
if not _validate_value_type(current):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error("GdUnitArrayAssert inital error, unexpected type <%s>" % GdObjects.typeof_as_string(current))
|
||||
|
||||
|
||||
func _notification(event: int) -> void:
|
||||
if event == NOTIFICATION_PREDELETE:
|
||||
if _base != null:
|
||||
_base.notification(event)
|
||||
_base = null
|
||||
|
||||
|
||||
func report_success() -> GdUnitArrayAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.report_success()
|
||||
return self
|
||||
|
||||
|
||||
func report_error(error: String) -> GdUnitArrayAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.report_error(error)
|
||||
return self
|
||||
|
||||
|
||||
func override_failure_message(message: String) -> GdUnitArrayAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.override_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func append_failure_message(message: String) -> GdUnitArrayAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.append_failure_message(message)
|
||||
return self
|
||||
|
||||
|
||||
func _validate_value_type(value: Variant) -> bool:
|
||||
return value == null or GdArrayTools.is_array_type(value)
|
||||
|
||||
|
||||
func get_current_value() -> Variant:
|
||||
return _current_value_provider.get_value()
|
||||
|
||||
|
||||
func max_length(left: Variant, right: Variant) -> int:
|
||||
var ls := str(left).length()
|
||||
var rs := str(right).length()
|
||||
return rs if ls < rs else ls
|
||||
|
||||
|
||||
# gdlint: disable=function-name
|
||||
func _toPackedStringArray(value: Variant) -> PackedStringArray:
|
||||
if GdArrayTools.is_array_type(value):
|
||||
@warning_ignore("unsafe_cast")
|
||||
return PackedStringArray(value as Array)
|
||||
return PackedStringArray([str(value)])
|
||||
|
||||
|
||||
func _array_equals_div(current: Variant, expected: Variant, case_sensitive: bool = true) -> Array:
|
||||
var current_value := _toPackedStringArray(current)
|
||||
var expected_value := _toPackedStringArray(expected)
|
||||
var index_report := Array()
|
||||
for index in current_value.size():
|
||||
var c := current_value[index]
|
||||
if index < expected_value.size():
|
||||
var e := expected_value[index]
|
||||
if not GdObjects.equals(c, e, case_sensitive):
|
||||
var length := max_length(c, e)
|
||||
current_value[index] = GdAssertMessages.format_invalid(c.lpad(length))
|
||||
expected_value[index] = e.lpad(length)
|
||||
index_report.push_back({"index": index, "current": c, "expected": e})
|
||||
else:
|
||||
current_value[index] = GdAssertMessages.format_invalid(c)
|
||||
index_report.push_back({"index": index, "current": c, "expected": "<N/A>"})
|
||||
|
||||
for index in range(current_value.size(), expected_value.size()):
|
||||
var value := expected_value[index]
|
||||
expected_value[index] = GdAssertMessages.format_invalid(value)
|
||||
index_report.push_back({"index": index, "current": "<N/A>", "expected": value})
|
||||
return [current_value, expected_value, index_report]
|
||||
|
||||
|
||||
func _array_div(compare_mode: GdObjects.COMPARE_MODE, left: Array[Variant], right: Array[Variant], _same_order := false) -> Array[Variant]:
|
||||
var not_expect := left.duplicate(true)
|
||||
var not_found := right.duplicate(true)
|
||||
for index_c in left.size():
|
||||
var c: Variant = left[index_c]
|
||||
for index_e in right.size():
|
||||
var e: Variant = right[index_e]
|
||||
if GdObjects.equals(c, e, true, compare_mode):
|
||||
GdArrayTools.erase_value(not_expect, e)
|
||||
GdArrayTools.erase_value(not_found, c)
|
||||
break
|
||||
return [not_expect, not_found]
|
||||
|
||||
|
||||
func _contains(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
|
||||
var by_reference := compare_mode == GdObjects.COMPARE_MODE.OBJECT_REFERENCE
|
||||
var current_value: Variant = get_current_value()
|
||||
var expected_value: Variant = _extract_variadic_value(expected)
|
||||
if not _validate_value_type(expected_value):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
|
||||
|
||||
if current_value == null:
|
||||
return report_error(GdAssertMessages.error_arr_contains(current_value, expected_value, [], expected_value, by_reference))
|
||||
@warning_ignore("unsafe_cast")
|
||||
var diffs := _array_div(compare_mode, current_value as Array[Variant], expected_value as Array[Variant])
|
||||
#var not_expect := diffs[0] as Array
|
||||
var not_found: Array = diffs[1]
|
||||
if not not_found.is_empty():
|
||||
return report_error(GdAssertMessages.error_arr_contains(current_value, expected_value, [], not_found, by_reference))
|
||||
return report_success()
|
||||
|
||||
|
||||
func _contains_exactly(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
|
||||
var current_value: Variant = get_current_value()
|
||||
var expected_value: Variant = _extract_variadic_value(expected)
|
||||
if not _validate_value_type(expected_value):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
|
||||
|
||||
if current_value == null:
|
||||
return report_error(GdAssertMessages.error_arr_contains_exactly(null, expected_value, [], expected_value, compare_mode))
|
||||
# has same content in same order
|
||||
if _is_equal(current_value, expected_value, true, compare_mode):
|
||||
return report_success()
|
||||
# check has same elements but in different order
|
||||
if _is_equals_sorted(current_value, expected_value, true, compare_mode):
|
||||
return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected_value, [], [], compare_mode))
|
||||
# find the difference
|
||||
@warning_ignore("unsafe_cast")
|
||||
var diffs := _array_div(compare_mode,
|
||||
current_value as Array[Variant],
|
||||
expected_value as Array[Variant],
|
||||
GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
|
||||
var not_expect: Array[Variant] = diffs[0]
|
||||
var not_found: Array[Variant] = diffs[1]
|
||||
return report_error(GdAssertMessages.error_arr_contains_exactly(current_value, expected_value, not_expect, not_found, compare_mode))
|
||||
|
||||
|
||||
func _contains_exactly_in_any_order(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
|
||||
var current_value: Variant = get_current_value()
|
||||
var expected_value: Variant = _extract_variadic_value(expected)
|
||||
if not _validate_value_type(expected_value):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
|
||||
|
||||
if current_value == null:
|
||||
return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected_value, [],
|
||||
expected_value, compare_mode))
|
||||
# find the difference
|
||||
@warning_ignore("unsafe_cast")
|
||||
var diffs := _array_div(compare_mode, current_value as Array[Variant], expected_value as Array[Variant], false)
|
||||
var not_expect: Array[Variant] = diffs[0]
|
||||
var not_found: Array[Variant] = diffs[1]
|
||||
if not_expect.is_empty() and not_found.is_empty():
|
||||
return report_success()
|
||||
return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected_value, not_expect,
|
||||
not_found, compare_mode))
|
||||
|
||||
|
||||
func _not_contains(expected: Array, compare_mode: GdObjects.COMPARE_MODE) -> GdUnitArrayAssert:
|
||||
var current_value: Variant = get_current_value()
|
||||
var expected_value: Variant = _extract_variadic_value(expected)
|
||||
if not _validate_value_type(expected_value):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
|
||||
if current_value == null:
|
||||
return report_error(GdAssertMessages.error_arr_contains_exactly_in_any_order(current_value, expected_value, [],
|
||||
expected_value, compare_mode))
|
||||
@warning_ignore("unsafe_cast")
|
||||
var diffs := _array_div(compare_mode, current_value as Array[Variant], expected_value as Array[Variant])
|
||||
var found: Array[Variant] = diffs[0]
|
||||
@warning_ignore("unsafe_cast")
|
||||
if found.size() == (current_value as Array).size():
|
||||
return report_success()
|
||||
@warning_ignore("unsafe_cast")
|
||||
var diffs2 := _array_div(compare_mode, expected_value as Array[Variant], diffs[1] as Array[Variant])
|
||||
return report_error(GdAssertMessages.error_arr_not_contains(current_value, expected_value, diffs2[0], compare_mode))
|
||||
|
||||
|
||||
func is_null() -> GdUnitArrayAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_null()
|
||||
return self
|
||||
|
||||
|
||||
func is_not_null() -> GdUnitArrayAssert:
|
||||
@warning_ignore("return_value_discarded")
|
||||
_base.is_not_null()
|
||||
return self
|
||||
|
||||
|
||||
func is_equal(...expected: Array) -> GdUnitArrayAssert:
|
||||
var current_value: Variant = get_current_value()
|
||||
var expected_value: Variant= _extract_variadic_value(expected)
|
||||
if not _validate_value_type(expected_value):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
|
||||
if current_value == null and expected_value != null:
|
||||
return report_error(GdAssertMessages.error_equal(null, expected_value))
|
||||
|
||||
if not _is_equal(current_value, expected_value):
|
||||
var diff := _array_equals_div(current_value, expected_value)
|
||||
var expected_as_list := GdArrayTools.as_string(diff[0], false)
|
||||
var current_as_list := GdArrayTools.as_string(diff[1], false)
|
||||
var index_report: Array = diff[2]
|
||||
return report_error(GdAssertMessages.error_equal(expected_as_list, current_as_list, index_report))
|
||||
return report_success()
|
||||
|
||||
|
||||
# Verifies that the current Array is equal to the given one, ignoring case considerations.
|
||||
func is_equal_ignoring_case(...expected: Array) -> GdUnitArrayAssert:
|
||||
var current_value: Variant = get_current_value()
|
||||
var expected_value: Variant = _extract_variadic_value(expected)
|
||||
if not _validate_value_type(expected_value):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
|
||||
if current_value == null and expected_value != null:
|
||||
@warning_ignore("unsafe_cast")
|
||||
return report_error(GdAssertMessages.error_equal(null, GdArrayTools.as_string(expected_value)))
|
||||
|
||||
if not _is_equal(current_value, expected_value, false):
|
||||
@warning_ignore("unsafe_cast")
|
||||
var diff := _array_equals_div(current_value, expected_value, true)
|
||||
var expected_as_list := GdArrayTools.as_string(diff[0])
|
||||
var current_as_list := GdArrayTools.as_string(diff[1])
|
||||
var index_report: Array = diff[2]
|
||||
return report_error(GdAssertMessages.error_equal(expected_as_list, current_as_list, index_report))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_not_equal(...expected: Array) -> GdUnitArrayAssert:
|
||||
var current_value: Variant = get_current_value()
|
||||
var expected_value: Variant = _extract_variadic_value(expected)
|
||||
if not _validate_value_type(expected_value):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
|
||||
|
||||
if _is_equal(current_value, expected_value):
|
||||
return report_error(GdAssertMessages.error_not_equal(current_value, expected_value))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_not_equal_ignoring_case(...expected: Array) -> GdUnitArrayAssert:
|
||||
var current_value: Variant = get_current_value()
|
||||
var expected_value: Variant = _extract_variadic_value(expected)
|
||||
if not _validate_value_type(expected_value):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected_value))
|
||||
|
||||
if _is_equal(current_value, expected_value, false):
|
||||
@warning_ignore("unsafe_cast")
|
||||
var c := GdArrayTools.as_string(current_value as Array)
|
||||
@warning_ignore("unsafe_cast")
|
||||
var e := GdArrayTools.as_string(expected_value)
|
||||
return report_error(GdAssertMessages.error_not_equal_case_insensetiv(c, e))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_empty() -> GdUnitArrayAssert:
|
||||
var current_value: Variant = get_current_value()
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current_value == null or (current_value as Array).size() > 0:
|
||||
return report_error(GdAssertMessages.error_is_empty(current_value))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_not_empty() -> GdUnitArrayAssert:
|
||||
var current_value: Variant = get_current_value()
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current_value != null and (current_value as Array).size() == 0:
|
||||
return report_error(GdAssertMessages.error_is_not_empty())
|
||||
return report_success()
|
||||
|
||||
|
||||
@warning_ignore("unused_parameter", "shadowed_global_identifier")
|
||||
func is_same(expected: Variant) -> GdUnitArrayAssert:
|
||||
if not _validate_value_type(expected):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected))
|
||||
var current: Variant = get_current_value()
|
||||
if not is_same(current, expected):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error(GdAssertMessages.error_is_same(current, expected))
|
||||
return self
|
||||
|
||||
|
||||
func is_not_same(expected: Variant) -> GdUnitArrayAssert:
|
||||
if not _validate_value_type(expected):
|
||||
return report_error("ERROR: expected value: <%s>\n is not a Array Type!" % GdObjects.typeof_as_string(expected))
|
||||
var current: Variant = get_current_value()
|
||||
if is_same(current, expected):
|
||||
@warning_ignore("return_value_discarded")
|
||||
report_error(GdAssertMessages.error_not_same(current, expected))
|
||||
return self
|
||||
|
||||
|
||||
func has_size(expected: int) -> GdUnitArrayAssert:
|
||||
var current_value: Variant = get_current_value()
|
||||
@warning_ignore("unsafe_cast")
|
||||
if current_value == null or (current_value as Array).size() != expected:
|
||||
return report_error(GdAssertMessages.error_has_size(current_value, expected))
|
||||
return report_success()
|
||||
|
||||
|
||||
func contains(...expected: Array) -> GdUnitArrayAssert:
|
||||
return _contains(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
|
||||
|
||||
|
||||
func contains_exactly(...expected: Array) -> GdUnitArrayAssert:
|
||||
return _contains_exactly(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
|
||||
|
||||
|
||||
func contains_exactly_in_any_order(...expected: Array) -> GdUnitArrayAssert:
|
||||
return _contains_exactly_in_any_order(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
|
||||
|
||||
|
||||
func contains_same(...expected: Array) -> GdUnitArrayAssert:
|
||||
return _contains(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
|
||||
|
||||
|
||||
func contains_same_exactly(...expected: Array) -> GdUnitArrayAssert:
|
||||
return _contains_exactly(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
|
||||
|
||||
|
||||
func contains_same_exactly_in_any_order(...expected: Array) -> GdUnitArrayAssert:
|
||||
return _contains_exactly_in_any_order(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
|
||||
|
||||
|
||||
func not_contains(...expected: Array) -> GdUnitArrayAssert:
|
||||
return _not_contains(expected, GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST)
|
||||
|
||||
|
||||
func not_contains_same(...expected: Array) -> GdUnitArrayAssert:
|
||||
return _not_contains(expected, GdObjects.COMPARE_MODE.OBJECT_REFERENCE)
|
||||
|
||||
|
||||
func is_instanceof(expected: Variant) -> GdUnitAssert:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
_base.is_instanceof(expected)
|
||||
return self
|
||||
|
||||
|
||||
func extract(func_name: String, ...func_args: Array) -> GdUnitArrayAssert:
|
||||
var extracted_elements := Array()
|
||||
var args: Array = _extract_variadic_value(func_args)
|
||||
var extractor := GdUnitFuncValueExtractor.new(func_name, args)
|
||||
var current: Variant = get_current_value()
|
||||
if current == null:
|
||||
_current_value_provider = DefaultValueProvider.new(null)
|
||||
else:
|
||||
for element: Variant in current:
|
||||
extracted_elements.append(extractor.extract_value(element))
|
||||
_current_value_provider = DefaultValueProvider.new(extracted_elements)
|
||||
return self
|
||||
|
||||
|
||||
func extractv(...extractors: Array) -> GdUnitArrayAssert:
|
||||
var extracted_elements := Array()
|
||||
var current: Variant = get_current_value()
|
||||
if current == null:
|
||||
_current_value_provider = DefaultValueProvider.new(null)
|
||||
else:
|
||||
for element: Variant in current:
|
||||
var ev: Array[Variant] = []
|
||||
for index: int in extractors.size():
|
||||
var extractor: GdUnitValueExtractor = extractors[index]
|
||||
ev.append(extractor.extract_value(element))
|
||||
if extractors.size() > 1:
|
||||
extracted_elements.append(GdUnitTuple.new.callv(ev))
|
||||
else:
|
||||
extracted_elements.append(ev[0])
|
||||
_current_value_provider = DefaultValueProvider.new(extracted_elements)
|
||||
return self
|
||||
|
||||
|
||||
## Small helper to support the old expected arguments as single array and variadic arguments
|
||||
func _extract_variadic_value(values: Variant) -> Variant:
|
||||
@warning_ignore("unsafe_method_access")
|
||||
if values != null and values.size() == 1 and GdArrayTools.is_array_type(values[0]):
|
||||
return values[0]
|
||||
return values
|
||||
|
||||
|
||||
@warning_ignore("incompatible_ternary")
|
||||
func _is_equal(
|
||||
left: Variant,
|
||||
right: Variant,
|
||||
case_sensitive := true,
|
||||
compare_mode := GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool:
|
||||
|
||||
@warning_ignore("unsafe_cast")
|
||||
return GdObjects.equals(
|
||||
(left as Array) if GdArrayTools.is_array_type(left) else left,
|
||||
(right as Array) if GdArrayTools.is_array_type(right) else right,
|
||||
case_sensitive,
|
||||
compare_mode
|
||||
)
|
||||
|
||||
|
||||
func _is_equals_sorted(
|
||||
left: Variant,
|
||||
right: Variant,
|
||||
case_sensitive := true,
|
||||
compare_mode := GdObjects.COMPARE_MODE.PARAMETER_DEEP_TEST) -> bool:
|
||||
|
||||
@warning_ignore("unsafe_cast")
|
||||
return GdObjects.equals_sorted(
|
||||
left as Array,
|
||||
right as Array,
|
||||
case_sensitive,
|
||||
compare_mode)
|
||||
@@ -0,0 +1 @@
|
||||
uid://0kg74xgfwp6k
|
||||
@@ -0,0 +1,78 @@
|
||||
class_name GdUnitAssertImpl
|
||||
extends GdUnitAssert
|
||||
|
||||
|
||||
var _current :Variant
|
||||
var _custom_failure_message :String = ""
|
||||
var _additional_failure_message: String = ""
|
||||
|
||||
|
||||
func _init(current :Variant) -> void:
|
||||
_current = current
|
||||
# save the actual assert instance on the current thread context
|
||||
GdUnitThreadManager.get_current_context().set_assert(self)
|
||||
GdAssertReports.reset_last_error_line_number()
|
||||
|
||||
|
||||
func current_value() -> Variant:
|
||||
return _current
|
||||
|
||||
|
||||
func report_success() -> GdUnitAssert:
|
||||
GdAssertReports.report_success()
|
||||
return self
|
||||
|
||||
|
||||
func report_error(failure :String, failure_line_number: int = -1) -> GdUnitAssert:
|
||||
var stack_trace := GdUnitStackTrace.new()
|
||||
var line_number := failure_line_number if failure_line_number != -1 else stack_trace.get_line_number()
|
||||
GdAssertReports.set_last_error_line_number(line_number)
|
||||
var failure_message := GdAssertMessages.build_failure_message(failure, _additional_failure_message, _custom_failure_message)
|
||||
GdAssertReports.report_error(GdUnitError.new(failure_message, line_number, stack_trace))
|
||||
return self
|
||||
|
||||
|
||||
func do_fail_not_implemented() -> GdUnitAssert:
|
||||
return report_error(GdAssertMessages.error_not_implemented())
|
||||
|
||||
|
||||
func do_fail_with_message(message: String) -> GdUnitAssert:
|
||||
return report_error(GdAssertMessages.error_with_message(message))
|
||||
|
||||
|
||||
func override_failure_message(message: String) -> GdUnitAssert:
|
||||
_custom_failure_message = message
|
||||
return self
|
||||
|
||||
|
||||
func append_failure_message(message: String) -> GdUnitAssert:
|
||||
_additional_failure_message = message
|
||||
return self
|
||||
|
||||
|
||||
func is_null() -> GdUnitAssert:
|
||||
var current :Variant = current_value()
|
||||
if current != null:
|
||||
return report_error(GdAssertMessages.error_is_null(current))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_not_null() -> GdUnitAssert:
|
||||
var current :Variant = current_value()
|
||||
if current == null:
|
||||
return report_error(GdAssertMessages.error_is_not_null())
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_equal(expected: Variant) -> GdUnitAssert:
|
||||
var current: Variant = current_value()
|
||||
if not GdObjects.equals(current, expected):
|
||||
return report_error(GdAssertMessages.error_equal(current, expected))
|
||||
return report_success()
|
||||
|
||||
|
||||
func is_not_equal(expected: Variant) -> GdUnitAssert:
|
||||
var current: Variant = current_value()
|
||||
if GdObjects.equals(current, expected):
|
||||
return report_error(GdAssertMessages.error_not_equal(current, expected))
|
||||
return report_success()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bdq1ga71d44gr
|
||||
@@ -0,0 +1,39 @@
|
||||
# Preloads all GdUnit assertions
|
||||
class_name GdUnitAssertions
|
||||
extends RefCounted
|
||||
|
||||
|
||||
@warning_ignore("return_value_discarded")
|
||||
func _init() -> void:
|
||||
# preload all gdunit assertions to speedup testsuite loading time
|
||||
# gdlint:disable=private-method-call
|
||||
@warning_ignore_start("return_value_discarded")
|
||||
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitAssertImpl.gd")
|
||||
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitBoolAssertImpl.gd")
|
||||
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitStringAssertImpl.gd")
|
||||
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitIntAssertImpl.gd")
|
||||
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFloatAssertImpl.gd")
|
||||
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitVectorAssertImpl.gd")
|
||||
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitArrayAssertImpl.gd")
|
||||
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitDictionaryAssertImpl.gd")
|
||||
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFileAssertImpl.gd")
|
||||
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitObjectAssertImpl.gd")
|
||||
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitResultAssertImpl.gd")
|
||||
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFuncAssertImpl.gd")
|
||||
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitSignalAssertImpl.gd")
|
||||
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitFailureAssertImpl.gd")
|
||||
GdUnitAssertions.__lazy_load("res://addons/gdUnit4/src/asserts/GdUnitGodotErrorAssertImpl.gd")
|
||||
@warning_ignore_restore("return_value_discarded")
|
||||
|
||||
|
||||
### We now load all used asserts and tool scripts into the cache according to the principle of "lazy loading"
|
||||
### in order to noticeably reduce the loading time of the test suite.
|
||||
# We go this hard way to increase the loading performance to avoid reparsing all the used scripts
|
||||
# for more detailed info -> https://github.com/godotengine/godot/issues/67400
|
||||
# gdlint:disable=function-name
|
||||
static func __lazy_load(script_path :String) -> GDScript:
|
||||
return ResourceLoader.load(script_path, "GDScript", ResourceLoader.CACHE_MODE_REUSE)
|
||||
|
||||
|
||||
static func validate_value_type(value :Variant, type :Variant.Type) -> bool:
|
||||
return value == null or typeof(value) == type
|
||||
@@ -0,0 +1 @@
|
||||
uid://d0y7ocrqr5ebt
|
||||
Loaded 100 of 618 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user