Compare commits
68
Commits
1.8.0
...
ae231975bd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae231975bd | ||
|
|
b9fa3c3d63 | ||
|
|
de94691073 | ||
|
|
94e8dfb602 | ||
|
|
70e970b24c | ||
|
|
e4352a9f34 | ||
|
|
4a934840f6 | ||
|
|
78e1facaf0 | ||
|
|
84e137e16a | ||
|
|
a4e6da4c28 | ||
|
|
cd4331881c | ||
|
|
06f457b027 | ||
|
|
222d231b1b | ||
|
|
b120440404 | ||
|
|
41974ea5e3 | ||
|
|
240c331ab2 | ||
|
|
62fa63fd8b | ||
|
|
4a45e8516d | ||
|
|
d21b3e2ce0 | ||
|
|
92e3a66075 | ||
|
|
b510aa47f2 | ||
|
|
8d21b0617d | ||
|
|
b2d547b650 | ||
|
|
845d08c260 | ||
|
|
13fdb3f68c | ||
|
|
e1e05f3904 | ||
|
|
b40b160fde | ||
|
|
a21b6e9b22 | ||
|
|
f7619baf34 | ||
|
|
8b71723a34 | ||
|
|
a7bf38c5f9 | ||
|
|
5519afb5f6 | ||
|
|
d77b72159d | ||
|
|
a0428e3475 | ||
|
|
674604425f | ||
|
|
390da577d7 | ||
|
|
b7d67b573e | ||
|
|
5831e1e73d | ||
|
|
5cdec5cc8f | ||
|
|
e94c75e84d | ||
|
|
bad4b3ad8b | ||
|
|
8c1987807c | ||
|
|
3c7b4e5c9d | ||
|
|
75c59eb223 | ||
|
|
10e9e7b2a4 | ||
|
|
29b11560b5 | ||
|
|
2a332c2d19 | ||
|
|
6cbbfee6a1 | ||
|
|
c7d49bc5cc | ||
|
|
6288739de0 | ||
|
|
d79f5f92c5 | ||
|
|
14c166614d | ||
|
|
428decf73a | ||
|
|
739d0f7af6 | ||
|
|
ed292877ef | ||
|
|
5a979fca48 | ||
|
|
1acc757981 | ||
|
|
16724922ed | ||
|
|
c1fc24adf9 | ||
|
|
f67dccbf52 | ||
|
|
c985a6ce5b | ||
|
|
56e9fe66d3 | ||
|
|
ab338b0696 | ||
|
|
9a91d969bd | ||
|
|
8da6fc442f | ||
|
|
235bdcbcb5 | ||
|
|
6989b53dda | ||
|
|
899e99864f |
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/
|
||||
@@ -1,48 +0,0 @@
|
||||
When writing or reviewing GDScript code, strictly follow the official Godot Engine GDScript style guide and file naming conventions:
|
||||
|
||||
---
|
||||
|
||||
### **Code Formatting**
|
||||
1. **Indentation**: Use **4 spaces** (no tabs).
|
||||
2. **Line Length**: Keep lines under **100 characters** where possible.
|
||||
3. **Naming Conventions**:
|
||||
- **Variables/functions**: `snake_case` (e.g., `player_health`, `calculate_damage()`).
|
||||
- **Constants**: `UPPER_SNAKE_CASE` (e.g., `MAX_SPEED = 100`).
|
||||
- **Class Names**: `PascalCase` (e.g., `class_name Player.gd`).
|
||||
- **Signals**: `snake_case` (e.g., `signal player_died`).
|
||||
4. **Spacing**:
|
||||
- Add spaces around operators (`x = y + z`, not `x=y+z`).
|
||||
- No space after `(` or before `)` in function calls (e.g., `func(a, b)`).
|
||||
5. **Braces**: Open braces on the same line for `if`, `for`, `func`, etc.:
|
||||
```gdscript
|
||||
if x > 0:
|
||||
print("Positive")
|
||||
```
|
||||
6. **Docstrings**: Use **Godot’s built-in docstring format** for functions:
|
||||
```gdscript
|
||||
## Calculates the player's speed based on input.
|
||||
## @param delta: Time since last frame (float).
|
||||
## @return: The adjusted speed (float).
|
||||
func calculate_speed(delta: float) -> float:
|
||||
return base_speed * delta
|
||||
```
|
||||
7. **Type Hints**: Always include type hints for parameters and return values (Godot 4+).
|
||||
8. **Avoid Redundancy**: Omit redundant `self.` unless required for clarity.
|
||||
|
||||
---
|
||||
### **File Naming Conventions**
|
||||
1. **Script Files**:
|
||||
- Use **`snake_case`** for `.gd` files (e.g., `player.gd`, `enemy_ai.gd`).
|
||||
- Match the filename to the **class name** (if the script defines a class).
|
||||
Example: If the class is `Player`, the file should be `player.gd`.
|
||||
2. **Scene Files**:
|
||||
- Use **`PascalCase`** for `.tscn` files (e.g., `Player.tscn`, `MainMenu.tscn`).
|
||||
- Match the filename to the **root node** of the scene.
|
||||
3. **Resource Files**:
|
||||
- Use **`snake_case`** for `.tres` or `.res` files (e.g., `player_stats.tres`).
|
||||
4. **Folders**:
|
||||
- Use **`snake_case`** for directories (e.g., `scripts/`, `assets/sprites/`).
|
||||
- Group related files logically (e.g., `player/` for `player.gd`, `player.tscn`).
|
||||
|
||||
---
|
||||
**Reference**: [Godot GDScript Style Guide](https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_styleguide.html).
|
||||
@@ -1,67 +0,0 @@
|
||||
extends Control
|
||||
|
||||
@onready
|
||||
var about_panel: Panel = %AboutPanel
|
||||
|
||||
@onready
|
||||
var version_label: Label = %VersionLabel
|
||||
|
||||
@onready
|
||||
var whats_new_label: Label = %WhatsNewLabel
|
||||
|
||||
|
||||
|
||||
# Changelog as Dictionary with version as key and description as value
|
||||
const WHATS_NEW: Dictionary = {
|
||||
"1.8.0": "#14: Updated About page
|
||||
#15: Create new keymap page
|
||||
#20: The winner song can now be stopped
|
||||
#24: The speed of the inspiration list can now be changed
|
||||
#29: New settings to hide time left and things like that
|
||||
#31: Added a QR page",
|
||||
"1.7.0": "#22: Now block add player if player name is empty
|
||||
#23: Add a log for when points are given to players
|
||||
#25: Fixed some graphical stuff
|
||||
#26: Changed so the same character can be on a song multiple times
|
||||
#27: Turning off statistics after win",
|
||||
"1.6.1": "#17: Removed Debug text in release
|
||||
#18: Fixed bug with new character select screen",
|
||||
"1.6.0": "#1: Fixed bug with inspiration list not reloading after sync
|
||||
#2: New dialog for sync
|
||||
#4: Now downloads characters from the server
|
||||
#5: New character select screen
|
||||
#6: Now show progress during sync
|
||||
#7: Blocking all requests to the server during sync
|
||||
#8: Added functionality to cache more than one song",
|
||||
"1.5.0": "Made big changes to players and the song list and how the local song list works",
|
||||
"0.9.0-Beta": "Fixed settings and updated the player view",
|
||||
"0.8.0-Beta": "Fixed reset buttons and some other small things",
|
||||
"0.7.8-Beta": "Added shortcuts. Added dialog for winner. Started cleaning code.",
|
||||
"0.7.5-Beta": "Added settings menu, most things don't do anything yet",
|
||||
"0.7-Beta": "Can now hop between songs"
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
version_label.text = Settings.version
|
||||
|
||||
# Build changelog text with version headers
|
||||
var changelog_text: String = ""
|
||||
for version: String in WHATS_NEW.keys():
|
||||
changelog_text += "\n" + version + "\n"
|
||||
changelog_text += WHATS_NEW[version] + "\n"
|
||||
|
||||
whats_new_label.text = changelog_text
|
||||
|
||||
|
||||
func hide_window() -> void:
|
||||
visible = false
|
||||
@@ -1,63 +0,0 @@
|
||||
[gd_scene format=3 uid="uid://cp54idaudb8dp"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://b7bh0of7olbcb" path="res://AboutWindow.gd" id="1_about_script"]
|
||||
|
||||
[node name="AboutControl" type="Control" unique_id=1626010355]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_about_script")
|
||||
|
||||
[node name="AboutPanel" type="Panel" parent="." unique_id=1907299275]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 0
|
||||
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
|
||||
text = "About"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="AboutPanel" unique_id=417677913]
|
||||
layout_mode = 0
|
||||
offset_left = 10.0
|
||||
offset_top = 50.0
|
||||
offset_right = 590.0
|
||||
offset_bottom = 630.0
|
||||
|
||||
[node name="ContentVBox" type="VBoxContainer" parent="AboutPanel/ScrollContainer" unique_id=1746337912]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
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."
|
||||
horizontal_alignment = 1
|
||||
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 +0,0 @@
|
||||
extends ScrollContainer
|
||||
|
||||
var SCROLL: float = 0
|
||||
var delay: float = 0.02 #seconds
|
||||
var wait: float = 0
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
wait = delay
|
||||
|
||||
# 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()
|
||||
|
||||
func scroll_back_up() -> void:
|
||||
SCROLL = -1
|
||||
|
||||
func scroll_to_bottom() -> void:
|
||||
scroll_vertical = 0 #Reset to top first.
|
||||
SCROLL = 1
|
||||
@@ -1,7 +0,0 @@
|
||||
[gd_resource type="Theme" load_steps=2 format=3 uid="uid://rxexo3ur85as"]
|
||||
|
||||
[ext_resource type="StyleBox" uid="uid://blf3g88mugovr" path="res://neutral_gray.tres" id="1_alhp2"]
|
||||
|
||||
[resource]
|
||||
Label/styles/normal = ExtResource("1_alhp2")
|
||||
PanelContainer/styles/panel = ExtResource("1_alhp2")
|
||||
@@ -1,33 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bijh5h5yrivm3"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cw41y87l64qo7" path="res://log.gd" id="1_q72ua"]
|
||||
|
||||
[node name="Control" type="Control"]
|
||||
custom_minimum_size = Vector2(700, 700)
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_q72ua")
|
||||
|
||||
[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
|
||||
|
||||
[node name="LogScrollContainer" type="ScrollContainer" parent="LogPanel"]
|
||||
custom_minimum_size = Vector2(700, 700)
|
||||
layout_mode = 0
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
|
||||
[node name="LogVBoxContainer" type="VBoxContainer" parent="LogPanel/LogScrollContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(700, 700)
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 1
|
||||
-549
@@ -1,549 +0,0 @@
|
||||
extends Control
|
||||
|
||||
# 601 LOC 11/9 - 2023
|
||||
# 628 LOC 15/9 - 2023
|
||||
# 631 LOC 16/9 - 2023
|
||||
# 554 LOC 30/5 - 2024
|
||||
# 551 LOC 3/6 - 2024
|
||||
# 386 LOC 12/6 - 2024
|
||||
|
||||
##TODO
|
||||
# 15. Fix for local play
|
||||
|
||||
@onready
|
||||
var open_button: Button = $Open
|
||||
|
||||
@onready
|
||||
var fileDialog: FileDialog = $FileDialog
|
||||
|
||||
@onready
|
||||
var inspiration_scroll: ScrollContainer = $ScrollContainer
|
||||
|
||||
@onready
|
||||
var inspiration_list: VBoxContainer = $ScrollContainer/VBoxContainer
|
||||
|
||||
@onready
|
||||
var game_label: Label = $VBoxContainer/GameLabel
|
||||
|
||||
@onready
|
||||
var song_label: Label = $VBoxContainer/SongLabel
|
||||
|
||||
@onready
|
||||
var add_player_container: HBoxContainer = $Players/VBoxContainer/AddPlayerContainer
|
||||
|
||||
@onready
|
||||
var add_players_button: TextureButton = $Players/VBoxContainer/HBoxContainer/AddPlayersButton
|
||||
|
||||
@onready
|
||||
var add_player_button: Button = $Players/VBoxContainer/AddPlayerContainer/AddPlayerButton
|
||||
|
||||
@onready
|
||||
var new_player_name_field: TextEdit = $Players/VBoxContainer/AddPlayerContainer/PlayerNameField
|
||||
|
||||
@onready
|
||||
var reset_playlist_button: Button = $ResetPlaylistButton
|
||||
|
||||
@onready
|
||||
var reset_points_button: Button = $ResetPointsButton
|
||||
|
||||
@onready
|
||||
var sound_test_button: Button = $SoundTestButton
|
||||
|
||||
@onready
|
||||
var sync_button: Button = $SyncButton
|
||||
|
||||
@onready
|
||||
var sync_window: Control = %SyncWindow
|
||||
|
||||
@onready
|
||||
var settings_button: Button = $SettingsButton
|
||||
|
||||
@onready
|
||||
var settings_window: Control = $SettingsWindow
|
||||
|
||||
@onready
|
||||
var shortcuts_window: Control = %ShortcutsWindow
|
||||
|
||||
@onready
|
||||
var qr_window: Control = %QRWindow
|
||||
|
||||
@onready
|
||||
var qr_button: Button = $QRButton
|
||||
|
||||
@onready
|
||||
var statistics_button: Button = $StatisticsButton
|
||||
|
||||
@onready
|
||||
var statistic_popup: PopupPanel = $StatisticsPopupPanel
|
||||
|
||||
@onready
|
||||
var statistic_label: Label = $StatisticsPopupPanel/StatisticsLabel
|
||||
|
||||
@onready
|
||||
var shortcuts_button: Button = $ShortcutsButton
|
||||
|
||||
@onready
|
||||
var about_button: Button = $AboutButton
|
||||
|
||||
@onready
|
||||
var about_window: Control = %AboutWindow
|
||||
|
||||
@onready
|
||||
var show_answer_button: Button = $ShowAnswerButton
|
||||
|
||||
@onready
|
||||
var next_button: Button = $NextButton
|
||||
|
||||
@onready
|
||||
var music_list_scroll: ScrollContainer = $MusicListPanel/ScrollContainer
|
||||
|
||||
@onready
|
||||
var music_list: VBoxContainer = $MusicListPanel/ScrollContainer/MusicList
|
||||
|
||||
@onready
|
||||
var player_container: VBoxContainer = $Players/VBoxContainer
|
||||
|
||||
@onready
|
||||
var character_select: Control = $CharacterSelect
|
||||
|
||||
@onready
|
||||
var new_character_select: Control = %NewCharacterSelect
|
||||
|
||||
@onready
|
||||
var search_button: Button = $SearchButton
|
||||
|
||||
@onready
|
||||
var search_view: Control= $Search
|
||||
|
||||
@onready
|
||||
var winner_popup: PopupPanel = $WinnerPopupPanel
|
||||
|
||||
@onready
|
||||
var winner_label: Label = %WinnerLabel
|
||||
|
||||
@onready
|
||||
var winner_picture: TextureRect = %WinnerPicture
|
||||
|
||||
@onready
|
||||
var auto_repeat_song_button: CheckButton = $RepeatSongCheckButton
|
||||
|
||||
@onready
|
||||
var music_player_container: PanelContainer = $MusicPlayer
|
||||
|
||||
@onready
|
||||
var log: Control = %Log
|
||||
|
||||
@onready
|
||||
var debug_label: Label = $DebugLabel
|
||||
|
||||
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 current_player: Node
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
var debug: bool = OS.has_feature("debug")
|
||||
var is_mac: bool = OS.has_feature("macos")
|
||||
if debug:
|
||||
print("is_debug")
|
||||
debug_label.visible = true
|
||||
Settings.is_debug = true
|
||||
settings_window.set_winning_score(5)
|
||||
settings_window.set_selected_server(4)
|
||||
if is_mac:
|
||||
print("is_mac")
|
||||
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)
|
||||
sound_test_button.pressed.connect(music_player_container.get_sound_test_song)
|
||||
statistics_button.pressed.connect(get_statistics)
|
||||
shortcuts_button.pressed.connect(show_shortcuts)
|
||||
about_button.pressed.connect(show_about)
|
||||
settings_button.pressed.connect(show_settings)
|
||||
qr_button.pressed.connect(show_qr)
|
||||
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)
|
||||
sync_window.connect("sync_started", _on_sync_started)
|
||||
sync_window.connect("sync_finished", _on_sync_finished)
|
||||
|
||||
|
||||
add_players_button.pressed.connect(add_players)
|
||||
add_player_button.pressed.connect(add_player)
|
||||
open_button.pressed.connect(open)
|
||||
auto_repeat_song_button.pressed.connect(repeat_song)
|
||||
|
||||
get_suggestion_list()
|
||||
fetch_song_list_at_start()
|
||||
fetch_song()
|
||||
search_view.get_list_of_games()
|
||||
new_character_select.load_characters()
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed:
|
||||
if event.alt_pressed && event.keycode == KEY_UP:
|
||||
print("Alt + UP pressed")
|
||||
music_player_container.change_volume(0.05)
|
||||
if event.alt_pressed && event.keycode == KEY_DOWN:
|
||||
print("Alt + DOWN pressed")
|
||||
music_player_container.change_volume(-0.05)
|
||||
if event.alt_pressed && event.ctrl_pressed && event.keycode == KEY_LEFT:
|
||||
print("Ctrl + Alt + LEFT pressed")
|
||||
music_player_container.restart()
|
||||
elif event.alt_pressed && event.keycode == KEY_LEFT:
|
||||
print("Alt + LEFT pressed")
|
||||
music_player_container.seek(-5)
|
||||
if event.alt_pressed && event.keycode == KEY_RIGHT:
|
||||
print("Alt + RIGHT pressed")
|
||||
music_player_container.seek(5)
|
||||
if event.alt_pressed && event.keycode == KEY_ENTER:
|
||||
print("Alt + Enter pressed")
|
||||
Settings.fullscreen = !Settings.fullscreen
|
||||
settings_window.fullscreen_button.button_pressed = Settings.fullscreen
|
||||
if Settings.fullscreen == true:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
|
||||
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
|
||||
if event.alt_pressed && event.keycode == KEY_L:
|
||||
log.visible = !log.visible
|
||||
|
||||
func server_updated() -> void:
|
||||
print("server_updated")
|
||||
update_from_server()
|
||||
reset_points()
|
||||
|
||||
func update_from_server() -> void:
|
||||
print("update_from_server")
|
||||
search_view.get_list_of_games()
|
||||
get_suggestion_list()
|
||||
reset_playlist()
|
||||
new_character_select.load_characters()
|
||||
|
||||
func repeat_song() -> void:
|
||||
Settings.auto_repeat_song = !Settings.auto_repeat_song
|
||||
|
||||
func show_answer_pressed() -> void:
|
||||
print("show_answer_pressed")
|
||||
Playlist.song_is_answered()
|
||||
update_song_list()
|
||||
|
||||
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.song_title = d.Song
|
||||
new_song_object.is_answered = true
|
||||
new_song_object.has_played = true
|
||||
Playlist.add_to_playlist(new_song_object)
|
||||
else:
|
||||
print("Unexpected data")
|
||||
Playlist.update_last_played_song()
|
||||
update_song_list()
|
||||
Settings.make_request2("/music/list", fetch_song_list_at_start_done, true)
|
||||
|
||||
func play_next_song() -> void:
|
||||
print("play_next_song ", Playlist.latest_played_song)
|
||||
if Playlist.first_song_played:
|
||||
Playlist.set_latest_played_song()
|
||||
Playlist.first_song_played = true
|
||||
music_player_container.play_song_object(Playlist.latest_played_song)
|
||||
if !(Playlist.get_preloaded_songs() > 0):
|
||||
next_button.disabled = true
|
||||
if !Playlist.is_song_downloading:
|
||||
fetch_song()
|
||||
|
||||
#not first "/music/addPlayed"
|
||||
#"/music/rand"
|
||||
#"/music/addQue"
|
||||
#"/music/info"
|
||||
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")
|
||||
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)
|
||||
|
||||
Settings.make_request2("/music/addQue", added_to_que_done, false)
|
||||
|
||||
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()
|
||||
song_label.text = Playlist.get_current_song_title()
|
||||
Settings.delete_children(music_list)
|
||||
for s: SongObject in Playlist.get_playlist():
|
||||
var new_song_list_object: SongListObject = song_list_object.instantiate()
|
||||
new_song_list_object.song_object = s
|
||||
new_song_list_object.gui_input.connect(song_object_clicked.bind(s.song_number))
|
||||
music_list.add_child(new_song_list_object)
|
||||
|
||||
func song_object_clicked(event: InputEvent, song_no: int) -> void:
|
||||
if (event is InputEventMouseButton && event.pressed && event.button_index == MOUSE_BUTTON_LEFT):
|
||||
print("Clicked Song Object: ", Playlist.song_object_array[song_no].to_string())
|
||||
if Settings.character_select_open == false:
|
||||
if Playlist.get_song(song_no) == null:
|
||||
print("has no song, need to download")
|
||||
var song_downloaded: Callable = func song_downloaded(body: PackedByteArray) -> void:
|
||||
var sound: AudioStream = AudioStreamMP3.new()
|
||||
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)
|
||||
else:
|
||||
print("song already downloaded")
|
||||
music_player_container.play_song_object(song_no)
|
||||
|
||||
func reset_playlist() -> void:
|
||||
print("reset_playlist")
|
||||
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)
|
||||
|
||||
func reset_points() -> void:
|
||||
print("reset_points")
|
||||
for player_objects: PlayerObject in Settings.player_array:
|
||||
player_objects.reset_points()
|
||||
load_players()
|
||||
|
||||
func show_about() -> void:
|
||||
print("show_about")
|
||||
about_window.show_window()
|
||||
|
||||
func show_shortcuts() -> void:
|
||||
print("show_shortcuts")
|
||||
shortcuts_window.visible = true
|
||||
|
||||
func show_settings() -> void:
|
||||
print("show_settings")
|
||||
settings_window.visible = true
|
||||
|
||||
func show_qr() -> void:
|
||||
print("show_qr")
|
||||
qr_window.show_window()
|
||||
|
||||
func get_statistics() -> void:
|
||||
print("get_statistics")
|
||||
statistic_popup.visible = true
|
||||
statistic_label.text = "Total amount of games in the playlist: " + str(games.size())
|
||||
|
||||
func sync_games() -> void:
|
||||
print("sync_games")
|
||||
sync_window.start_sync()
|
||||
|
||||
func _on_sync_started() -> void:
|
||||
print("_on_sync_started")
|
||||
sync_button.disabled = false
|
||||
sound_test_button.disabled = true
|
||||
reset_playlist_button.disabled = true
|
||||
show_answer_button.disabled = true
|
||||
next_button.disabled = true
|
||||
|
||||
func _on_sync_finished() -> void:
|
||||
print("_on_sync_finished")
|
||||
update_from_server()
|
||||
sound_test_button.disabled = false
|
||||
reset_playlist_button.disabled = false
|
||||
show_answer_button.disabled = false
|
||||
reset_points()
|
||||
|
||||
func get_suggestion_list() -> void:
|
||||
print("get_suggestion_list")
|
||||
var populate_list: Callable = func populate_list(array) -> void:
|
||||
if typeof(array) == TYPE_ARRAY:
|
||||
games = []
|
||||
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()
|
||||
else:
|
||||
print("Unexpected data")
|
||||
Settings.make_request2("/music/all", populate_list, true)
|
||||
|
||||
func add_players() -> void:
|
||||
print("add_players")
|
||||
Settings.edit_players = !Settings.edit_players
|
||||
add_player_container.visible = !add_player_container.visible
|
||||
new_player_name_field.grab_focus()
|
||||
|
||||
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())
|
||||
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("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))
|
||||
load_players()
|
||||
|
||||
func load_players() -> void:
|
||||
print("load_players")
|
||||
Settings.delete_player_children(player_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))
|
||||
|
||||
player_container.add_child(new_player)
|
||||
counter += 1
|
||||
|
||||
func _on_point_given(player_given_point: int) -> void:
|
||||
print("_on_point_given")
|
||||
log.add_log_row(Settings.player_array[player_given_point].player_name + " got a point")
|
||||
if Playlist.currently_playing_song >= 0:
|
||||
Playlist.add_point(player_given_point)
|
||||
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:
|
||||
print("_on_point_taken")
|
||||
log.add_log_row(Settings.player_array[player_taken_point].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)
|
||||
update_song_list()
|
||||
|
||||
func _on_player_won(winning_player_id: int) -> void:
|
||||
print("_on_player_won")
|
||||
winner_popup.visible = true
|
||||
winner_label.text = Settings.player_array[winning_player_id].player_name + " won!!"
|
||||
winner_picture.custom_minimum_size = Vector2(692, 300)
|
||||
winner_picture.expand_mode = TextureRect.EXPAND_FIT_WIDTH_PROPORTIONAL
|
||||
winner_picture.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
winner_picture.texture = Settings.player_array[winning_player_id].character
|
||||
music_player_container.play_song(preload("res://sounds/winning.mp3"))
|
||||
Settings.add_to_stats = false
|
||||
|
||||
func _on_player_removed(player_to_remove: int) -> void:
|
||||
print("_on_player_removed ", player_to_remove)
|
||||
Settings.player_array.remove_at(player_to_remove)
|
||||
load_players()
|
||||
Playlist.remove_player_from_list(player_to_remove)
|
||||
update_song_list()
|
||||
|
||||
func _on_player_change_character_clicked(new_player: Player, player_name: String) -> void:
|
||||
print("_on_player_change_character_clicked")
|
||||
current_player = new_player
|
||||
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
|
||||
|
||||
|
||||
###Local
|
||||
|
||||
var local_path: String = '/Users/sebastian/ResilioSync/Sorterat_test/Metal Gear Solid 4 - Guns of the Patriots/2-16 Metal Gear Saga.mp3'
|
||||
|
||||
func open() -> void:
|
||||
fileDialog.popup()
|
||||
|
||||
func _on_file_dialog_dir_selected(path: String) -> void:
|
||||
print(path)
|
||||
dir_contents(path)
|
||||
|
||||
func dir_contents(path: String) -> void:
|
||||
var dir: DirAccess = DirAccess.open(path)
|
||||
if dir:
|
||||
dir.list_dir_begin()
|
||||
var file_name: String = dir.get_next()
|
||||
songs.clear()
|
||||
games = []
|
||||
while file_name != "":
|
||||
if dir.current_is_dir():
|
||||
#print("Found directory: " + file_name)
|
||||
games.append(file_name)
|
||||
else:
|
||||
#print("Found file: " + file_name)
|
||||
if file_name.ends_with(".mp3"):
|
||||
songs.append(path + "/" + file_name)
|
||||
file_name = dir.get_next()
|
||||
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)
|
||||
-552
@@ -1,552 +0,0 @@
|
||||
[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"]
|
||||
[ext_resource type="PackedScene" uid="uid://cdy4kvemwaiom" path="res://new_character_select.tscn" id="6_dhrvg"]
|
||||
[ext_resource type="Script" uid="uid://gf1bk6xbe3j" path="res://MusicListScrollContainer.gd" id="7_dj026"]
|
||||
[ext_resource type="Script" uid="uid://cgakje8gb37tf" path="res://PlayerNameField.gd" id="7_qsdfy"]
|
||||
[ext_resource type="Theme" uid="uid://rxexo3ur85as" path="res://LightGrayTheme.tres" id="7_wxbv6"]
|
||||
[ext_resource type="PackedScene" uid="uid://bxydgil1yifps" path="res://SearchWindow.tscn" id="9_5ijvr"]
|
||||
[ext_resource type="PackedScene" uid="uid://dpdem7pdxweb5" path="res://SyncWindow.tscn" id="10_yxw0b"]
|
||||
[ext_resource type="Texture2D" uid="uid://r4as0nmtoa7p" path="res://noCharacter.png" id="11_1qef0"]
|
||||
[ext_resource type="PackedScene" uid="uid://dldpeo5y3l5hq" path="res://SettingsWindow.tscn" id="11_k62u5"]
|
||||
[ext_resource type="PackedScene" uid="uid://bd1by80q1v27e" path="res://shortcutsWindow.tscn" id="13_8eebo"]
|
||||
[ext_resource type="PackedScene" uid="uid://bijh5h5yrivm3" path="res://Log.tscn" id="14_26rwn"]
|
||||
[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"]
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_ychxr"]
|
||||
font_size = 25
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_qspbu"]
|
||||
font_size = 50
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_3m52w"]
|
||||
font_size = 35
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_03bm3"]
|
||||
device = -1
|
||||
alt_pressed = true
|
||||
keycode = 65
|
||||
unicode = 63743
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_jafqj"]
|
||||
events = [SubResource("InputEventKey_03bm3")]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qk7sj"]
|
||||
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k1ygi"]
|
||||
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_06rl4"]
|
||||
device = -1
|
||||
alt_pressed = true
|
||||
keycode = 86
|
||||
unicode = 8249
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_a7fvb"]
|
||||
events = [SubResource("InputEventKey_06rl4")]
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_7dnqw"]
|
||||
device = -1
|
||||
alt_pressed = true
|
||||
keycode = 67
|
||||
unicode = 231
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_d6fml"]
|
||||
events = [SubResource("InputEventKey_7dnqw")]
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_ujjlu"]
|
||||
device = -1
|
||||
alt_pressed = true
|
||||
keycode = 83
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_fbju4"]
|
||||
events = [SubResource("InputEventKey_ujjlu")]
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_hr75l"]
|
||||
font_size = 35
|
||||
|
||||
[node name="Control" type="Control" unique_id=407149696]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
script = ExtResource("1_eu0t5")
|
||||
|
||||
[node name="TopPanelContainer" type="PanelContainer" parent="." unique_id=663404711]
|
||||
layout_mode = 0
|
||||
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
|
||||
offset_right = 369.0
|
||||
offset_bottom = 50.0
|
||||
size_flags_horizontal = 0
|
||||
text = "Music Player Randomizer"
|
||||
label_settings = SubResource("LabelSettings_ychxr")
|
||||
|
||||
[node name="Open" type="Button" parent="." unique_id=165052329]
|
||||
visible = false
|
||||
layout_mode = 0
|
||||
offset_left = 1296.0
|
||||
offset_top = 66.0
|
||||
offset_right = 1346.0
|
||||
offset_bottom = 97.0
|
||||
text = "Open"
|
||||
|
||||
[node name="FileDialog" type="FileDialog" parent="." unique_id=485771559]
|
||||
title = "Open a Directory"
|
||||
initial_position = 2
|
||||
size = Vector2i(616, 400)
|
||||
ok_button_text = "Select Current Folder"
|
||||
file_mode = 2
|
||||
access = 2
|
||||
show_hidden_files = true
|
||||
|
||||
[node name="MusicListPanel" type="PanelContainer" parent="." unique_id=2011292375]
|
||||
layout_mode = 0
|
||||
offset_left = 64.0
|
||||
offset_top = 384.0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 896.0
|
||||
theme = ExtResource("7_wxbv6")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="MusicListPanel" unique_id=707750459]
|
||||
layout_mode = 2
|
||||
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
|
||||
offset_right = 1857.0
|
||||
offset_bottom = 512.0
|
||||
horizontal_scroll_mode = 0
|
||||
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
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=42468498]
|
||||
layout_mode = 0
|
||||
offset_left = 64.0
|
||||
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
|
||||
custom_minimum_size = Vector2(1, 1)
|
||||
layout_mode = 2
|
||||
label_settings = SubResource("LabelSettings_qspbu")
|
||||
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
|
||||
anchor_left = 0.0
|
||||
anchor_top = 0.0
|
||||
anchor_right = 0.0
|
||||
anchor_bottom = 0.0
|
||||
offset_left = 65.0
|
||||
offset_top = 992.0
|
||||
offset_right = 1856.0
|
||||
offset_bottom = 1024.0
|
||||
grow_horizontal = 1
|
||||
grow_vertical = 1
|
||||
|
||||
[node name="Players" type="PanelContainer" parent="." unique_id=1708375945]
|
||||
layout_mode = 0
|
||||
offset_left = 1216.0
|
||||
offset_top = 576.0
|
||||
offset_right = 1857.0
|
||||
offset_bottom = 977.0
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Players" unique_id=270084211]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Players/VBoxContainer" unique_id=513047023]
|
||||
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
|
||||
size_flags_vertical = 1
|
||||
text = "Players"
|
||||
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
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
shortcut = SubResource("Shortcut_jafqj")
|
||||
texture_normal = ExtResource("5_31tjv")
|
||||
|
||||
[node name="AddPlayerContainer" type="HBoxContainer" parent="Players/VBoxContainer" unique_id=1371948739]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Panel" type="Panel" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=1569854056]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_qk7sj")
|
||||
|
||||
[node name="PlayerNameField" type="TextEdit" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=332535327]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
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"
|
||||
|
||||
[node name="Panel2" type="Panel" parent="Players/VBoxContainer/AddPlayerContainer" unique_id=60215448]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_k1ygi")
|
||||
|
||||
[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
|
||||
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
|
||||
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
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Sync games"
|
||||
|
||||
[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
|
||||
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
|
||||
offset_right = 176.0
|
||||
offset_bottom = 975.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
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
|
||||
offset_right = 368.0
|
||||
offset_bottom = 975.0
|
||||
focus_mode = 0
|
||||
disabled = true
|
||||
action_mode = 0
|
||||
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
|
||||
offset_right = 1424.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
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
|
||||
offset_right = 1512.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
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
|
||||
offset_right = 1600.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
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
|
||||
offset_right = 1696.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
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
|
||||
offset_right = 1792.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
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
|
||||
offset_right = 1872.0
|
||||
offset_bottom = 48.0
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "QR"
|
||||
|
||||
[node name="Search" parent="." unique_id=1661250375 instance=ExtResource("9_5ijvr")]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 56.0
|
||||
offset_top = 96.0
|
||||
offset_right = 56.0
|
||||
offset_bottom = 96.0
|
||||
|
||||
[node name="SyncWindow" parent="." unique_id=182499699 instance=ExtResource("10_yxw0b")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 401.0
|
||||
offset_top = 153.0
|
||||
offset_right = 401.0
|
||||
offset_bottom = 153.0
|
||||
|
||||
[node name="SettingsWindow" parent="." unique_id=1323246978 instance=ExtResource("11_k62u5")]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 720.0
|
||||
offset_top = 153.0
|
||||
offset_right = 720.0
|
||||
offset_bottom = 153.0
|
||||
|
||||
[node name="QRWindow" parent="." unique_id=589733821 instance=ExtResource("15_qr_window")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 720.0
|
||||
offset_top = 153.0
|
||||
offset_right = 720.0
|
||||
offset_bottom = 153.0
|
||||
|
||||
[node name="NewCharacterSelect" parent="." unique_id=1679856322 instance=ExtResource("6_dhrvg")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 420.0
|
||||
offset_top = 165.0
|
||||
offset_right = 1572.0
|
||||
offset_bottom = 805.0
|
||||
|
||||
[node name="ShortcutsWindow" parent="." unique_id=1513416242 instance=ExtResource("13_8eebo")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 562.0
|
||||
offset_top = 160.0
|
||||
offset_right = 1262.0
|
||||
offset_bottom = 860.0
|
||||
|
||||
[node name="StatisticsPopupPanel" type="PopupPanel" parent="." unique_id=298311407]
|
||||
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
|
||||
offset_bottom = 96.0
|
||||
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"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="AboutWindow" parent="." unique_id=141261008 instance=ExtResource("16_about_window")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 720.0
|
||||
offset_top = 153.0
|
||||
offset_right = 720.0
|
||||
offset_bottom = 153.0
|
||||
|
||||
[node name="WinnerPopupPanel" type="PopupPanel" parent="." unique_id=938181211]
|
||||
oversampling_override = 1.0
|
||||
initial_position = 2
|
||||
size = Vector2i(700, 350)
|
||||
|
||||
[node name="Panel" type="Panel" parent="WinnerPopupPanel" unique_id=89212869]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 4.0
|
||||
offset_top = 4.0
|
||||
offset_right = 696.0
|
||||
offset_bottom = 346.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="WinnerPopupPanel/Panel" unique_id=471159349]
|
||||
layout_mode = 0
|
||||
offset_right = 692.0
|
||||
offset_bottom = 342.0
|
||||
alignment = 1
|
||||
|
||||
[node name="WinnerPicture" type="TextureRect" parent="WinnerPopupPanel/Panel/VBoxContainer" unique_id=1543774067]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(692, 300)
|
||||
layout_mode = 2
|
||||
texture = ExtResource("11_1qef0")
|
||||
expand_mode = 3
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="WinnerLabel" type="Label" parent="WinnerPopupPanel/Panel/VBoxContainer" unique_id=1437597286]
|
||||
unique_name_in_owner = true
|
||||
texture_filter = 1
|
||||
layout_mode = 2
|
||||
text = "Sansan won!!"
|
||||
label_settings = SubResource("LabelSettings_hr75l")
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 2
|
||||
|
||||
[node name="RepeatSongCheckButton" type="CheckButton" parent="." unique_id=776835053]
|
||||
layout_mode = 0
|
||||
offset_left = 64.0
|
||||
offset_top = 1032.0
|
||||
offset_right = 208.0
|
||||
offset_bottom = 1063.0
|
||||
focus_mode = 0
|
||||
button_pressed = true
|
||||
action_mode = 0
|
||||
text = "Auto repeat"
|
||||
|
||||
[node name="DebugLabel" type="Label" parent="." unique_id=1959221257]
|
||||
visible = false
|
||||
layout_mode = 0
|
||||
offset_left = 974.0
|
||||
offset_top = 904.0
|
||||
offset_right = 1014.0
|
||||
offset_bottom = 927.0
|
||||
scale = Vector2(3, 3)
|
||||
text = "DEBUG"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="Log" parent="." unique_id=1289881388 instance=ExtResource("14_26rwn")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_left = 562.0
|
||||
offset_top = 132.0
|
||||
offset_right = 1262.0
|
||||
offset_bottom = 832.0
|
||||
|
||||
[connection signal="dir_selected" from="FileDialog" to="." method="_on_file_dialog_dir_selected"]
|
||||
@@ -1,8 +0,0 @@
|
||||
extends ScrollContainer
|
||||
|
||||
var max_value = 0;
|
||||
|
||||
func _draw():
|
||||
if max_value != self.get_v_scroll_bar().max_value:
|
||||
max_value = self.get_v_scroll_bar().max_value
|
||||
self.scroll_vertical = max_value
|
||||
@@ -1,94 +0,0 @@
|
||||
[gd_scene load_steps=10 format=3 uid="uid://ds15cgsf8vpvc"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://d1v0rquma6nt8" path="res://MusicPlayer.gd" id="1_t24ra"]
|
||||
[ext_resource type="AudioStream" uid="uid://n2g8jddr85h2" path="res://sounds/01. Opening.mp3" id="2_xti80"]
|
||||
[ext_resource type="Texture2D" uid="uid://comxqfiykp54f" path="res://icons/play_icon_light.svg" id="3_6g308"]
|
||||
[ext_resource type="Texture2D" uid="uid://ccb6rvbldlgdg" path="res://icons/reload_light_icon.svg" id="4_jleuo"]
|
||||
[ext_resource type="PackedScene" uid="uid://w400rnew7453" path="res://volume_slider.tscn" id="5_iifuj"]
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_r4qo3"]
|
||||
device = -1
|
||||
alt_pressed = true
|
||||
keycode = 88
|
||||
physical_keycode = 88
|
||||
key_label = 88
|
||||
unicode = 120
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_ipcfh"]
|
||||
events = [SubResource("InputEventKey_r4qo3")]
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_2nvce"]
|
||||
device = -1
|
||||
alt_pressed = true
|
||||
keycode = 90
|
||||
unicode = 90
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_i7swq"]
|
||||
events = [SubResource("InputEventKey_2nvce")]
|
||||
|
||||
[node name="MusicPlayer" type="PanelContainer"]
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -121.0
|
||||
offset_top = -15.5
|
||||
offset_right = 121.0
|
||||
offset_bottom = 16.5
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_vertical = 4
|
||||
script = ExtResource("1_t24ra")
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
|
||||
stream = ExtResource("2_xti80")
|
||||
bus = &"music"
|
||||
|
||||
[node name="AudioSoundPlayer" type="AudioStreamPlayer" parent="."]
|
||||
stream = ExtResource("2_xti80")
|
||||
bus = &"music"
|
||||
|
||||
[node name="AudioSoundEffectPlayer" type="AudioStreamPlayer" parent="."]
|
||||
stream = ExtResource("2_xti80")
|
||||
bus = &"music"
|
||||
|
||||
[node name="MusicPlayerContainer" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
|
||||
[node name="PlayTextureButton" type="TextureButton" parent="MusicPlayerContainer"]
|
||||
custom_minimum_size = Vector2(32, 32)
|
||||
layout_mode = 2
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
shortcut = SubResource("Shortcut_ipcfh")
|
||||
texture_normal = ExtResource("3_6g308")
|
||||
ignore_texture_size = true
|
||||
stretch_mode = 0
|
||||
|
||||
[node name="RestartTextureButton" type="TextureButton" parent="MusicPlayerContainer"]
|
||||
custom_minimum_size = Vector2(32, 32)
|
||||
layout_mode = 2
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
shortcut = SubResource("Shortcut_i7swq")
|
||||
texture_normal = ExtResource("4_jleuo")
|
||||
ignore_texture_size = true
|
||||
stretch_mode = 0
|
||||
flip_h = true
|
||||
|
||||
[node name="MusicPlayerSlider" type="HSlider" parent="MusicPlayerContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 4
|
||||
scrollable = false
|
||||
|
||||
[node name="MusicTimeLabel" type="Label" parent="MusicPlayerContainer"]
|
||||
layout_mode = 2
|
||||
text = "0:00 / 0:00"
|
||||
|
||||
[node name="VolumeSlider" parent="MusicPlayerContainer" instance=ExtResource("5_iifuj")]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
@@ -1,79 +0,0 @@
|
||||
class_name Player
|
||||
extends Control
|
||||
|
||||
@onready
|
||||
var player_name_field := $HBoxContainer/Name
|
||||
|
||||
@onready
|
||||
var add_button := $HBoxContainer/AddPoint
|
||||
|
||||
@onready
|
||||
var minus_button := $HBoxContainer/RemovePoint
|
||||
|
||||
@onready
|
||||
var character := $HBoxContainer/Character
|
||||
|
||||
@onready
|
||||
var remove_player := $HBoxContainer/RemovePlayer
|
||||
|
||||
signal change_character_clicked
|
||||
signal player_removed
|
||||
|
||||
@export
|
||||
var id: 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))
|
||||
minus_button.pressed.connect(minus_point)
|
||||
minus_button.set_shortcut(create_button_shortcut(id, true))
|
||||
character.pressed.connect(change_character)
|
||||
remove_player.pressed.connect(func() -> void: player_removed.emit())
|
||||
update_score()
|
||||
set_player_character()
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
remove_player.visible = Settings.edit_players
|
||||
|
||||
func create_button_shortcut(scancode : int, ctrl_pressed: bool) -> Shortcut:
|
||||
var button_shortcut: Shortcut = Shortcut.new()
|
||||
var button_event : InputEventKey = InputEventKey.new()
|
||||
button_event.keycode = scancode + 49
|
||||
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()
|
||||
update_score()
|
||||
|
||||
func minus_point() -> void:
|
||||
Settings.player_array[id].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
|
||||
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
|
||||
|
||||
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)
|
||||
else:
|
||||
player_name_field.text = Settings.player_array[id].player_name + ": " + str(Settings.player_array[id].player_score)
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://cslogy2csnd7a"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dl6no2wr2c86y" path="res://Player.gd" id="1_if4kc"]
|
||||
[ext_resource type="Texture2D" uid="uid://r4as0nmtoa7p" path="res://noCharacter.png" id="2_hpj3s"]
|
||||
[ext_resource type="Texture2D" uid="uid://b2kj6m8qpsgb1" path="res://icons/person_remove_light.svg" id="3_j3uxe"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_821k2"]
|
||||
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_w10e8"]
|
||||
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_78a8g"]
|
||||
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_i56hb"]
|
||||
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_b2crt"]
|
||||
bg_color = Color(0.180392, 0.180392, 0.180392, 1)
|
||||
|
||||
[node name="Player" type="Control"]
|
||||
custom_minimum_size = Vector2(2.08165e-12, 40)
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_bottom = 40.0
|
||||
size_flags_horizontal = 3
|
||||
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
|
||||
|
||||
[node name="Panel4" type="Panel" parent="HBoxContainer"]
|
||||
custom_minimum_size = Vector2(5, 40)
|
||||
layout_mode = 2
|
||||
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_vertical = 1
|
||||
text = ": 0"
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Panel3" type="Panel" parent="HBoxContainer"]
|
||||
custom_minimum_size = Vector2(80, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_w10e8")
|
||||
|
||||
[node name="Character" type="TextureButton" parent="HBoxContainer"]
|
||||
custom_minimum_size = Vector2(80, 40)
|
||||
layout_mode = 2
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
texture_normal = ExtResource("2_hpj3s")
|
||||
ignore_texture_size = true
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="Panel2" type="Panel" parent="HBoxContainer"]
|
||||
custom_minimum_size = Vector2(30, 40)
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_78a8g")
|
||||
|
||||
[node name="AddPoint" type="Button" parent="HBoxContainer"]
|
||||
custom_minimum_size = Vector2(40, 40)
|
||||
layout_mode = 2
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "+1"
|
||||
|
||||
[node name="Panel5" type="Panel" parent="HBoxContainer"]
|
||||
custom_minimum_size = Vector2(3, 40)
|
||||
layout_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_i56hb")
|
||||
|
||||
[node name="RemovePoint" type="Button" parent="HBoxContainer"]
|
||||
custom_minimum_size = Vector2(40, 40)
|
||||
layout_mode = 2
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "-1"
|
||||
|
||||
[node name="Panel" type="Panel" parent="HBoxContainer"]
|
||||
custom_minimum_size = Vector2(30, 40)
|
||||
layout_mode = 2
|
||||
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")
|
||||
stretch_mode = 0
|
||||
@@ -1,38 +0,0 @@
|
||||
extends TextEdit
|
||||
|
||||
signal enter_key_pressed
|
||||
signal close_pressed
|
||||
|
||||
@export
|
||||
var LIMIT: int = 10
|
||||
var current_text = ''
|
||||
var cursor_line = 0
|
||||
var cursor_column = 0
|
||||
|
||||
func _ready():
|
||||
text_changed.connect(_on_text_changed)
|
||||
|
||||
func _input(event):
|
||||
if event is InputEventKey and event.pressed:
|
||||
if event.alt_pressed && event.keycode == KEY_A:
|
||||
close_pressed.emit()
|
||||
if has_focus():
|
||||
if event.keycode == KEY_ENTER || event.keycode == KEY_KP_ENTER:
|
||||
enter_key_pressed.emit()
|
||||
|
||||
func _on_text_changed():
|
||||
if text.contains("\n"):
|
||||
text = text.replace("\n", "")
|
||||
|
||||
var new_text : String = text
|
||||
if new_text.length() > LIMIT:
|
||||
text = current_text
|
||||
# when replacing the text, the cursor will get moved to the beginning of the
|
||||
# text, so move it back to where it was
|
||||
set_caret_line(cursor_line)
|
||||
set_caret_column(cursor_column)
|
||||
|
||||
current_text = text
|
||||
# save current position of cursor for when we have reached the limit
|
||||
cursor_line = get_caret_line()
|
||||
cursor_column = get_caret_column()
|
||||
@@ -1 +0,0 @@
|
||||
uid://cgakje8gb37tf
|
||||
@@ -1,52 +0,0 @@
|
||||
extends Node
|
||||
class_name PlayerObject
|
||||
|
||||
signal first_point_triggerd
|
||||
signal match_point_triggerd
|
||||
signal winner_triggerd
|
||||
signal point_given_sound
|
||||
signal player_point_given
|
||||
signal player_point_taken
|
||||
|
||||
@export
|
||||
var id: int
|
||||
|
||||
@export
|
||||
var player_name: String
|
||||
|
||||
@export
|
||||
var player_score: int
|
||||
|
||||
var is_first_point: bool = true
|
||||
|
||||
var character: Texture = load("res://noCharacter.png")
|
||||
|
||||
func _init(new_name: String, new_id: int) -> void:
|
||||
player_name = new_name
|
||||
id = new_id
|
||||
player_score = 0
|
||||
|
||||
func add_point() -> void:
|
||||
if player_score < Settings.winning_score:
|
||||
player_score += 1
|
||||
player_point_given.emit()
|
||||
if player_score == 1 && is_first_point:
|
||||
is_first_point = false
|
||||
first_point_triggerd.emit()
|
||||
elif player_score == Settings.winning_score - 1:
|
||||
match_point_triggerd.emit()
|
||||
elif player_score == Settings.winning_score:
|
||||
winner_triggerd.emit()
|
||||
else:
|
||||
point_given_sound.emit()
|
||||
|
||||
func minus_point() -> void:
|
||||
if player_score > 0:
|
||||
player_score -= 1
|
||||
player_point_taken.emit()
|
||||
if player_score == 0:
|
||||
is_first_point = true
|
||||
|
||||
func reset_points() -> void:
|
||||
player_score = 0
|
||||
is_first_point = true
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
extends Control
|
||||
|
||||
@onready
|
||||
var qr_panel: Panel = $QRPanel
|
||||
|
||||
@onready
|
||||
var qr_code_rect: QRCodeRect = $QRPanel/QRCodeRect
|
||||
|
||||
@onready
|
||||
var url_label: Label = $QRPanel/URLLabel
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
qr_code_rect.light_module_color = Color.WHITE
|
||||
qr_code_rect.dark_module_color = Color.BLACK
|
||||
qr_code_rect.quiet_zone_size = 4
|
||||
|
||||
# Generate QR code for the search URL
|
||||
var url: String = Settings.default_path + "/search"
|
||||
qr_code_rect.data = url
|
||||
url_label.text = qr_code_rect.data
|
||||
print("QR code generated for: ", url)
|
||||
|
||||
func show_window() -> void:
|
||||
visible = true
|
||||
# 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
|
||||
@@ -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
|
||||
`popups/about_popup.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.
|
||||
-178
@@ -1,178 +0,0 @@
|
||||
extends Control
|
||||
|
||||
@onready
|
||||
var search_list: VBoxContainer = $ScrollContainer/VBoxContainer
|
||||
|
||||
@onready
|
||||
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
|
||||
|
||||
# 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()
|
||||
clear()
|
||||
|
||||
func close() -> void:
|
||||
clear()
|
||||
self.visible = false
|
||||
|
||||
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()
|
||||
|
||||
if search_term == "":
|
||||
return true
|
||||
elif game_name.contains(search_term):
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
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
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
func clean_term(term: String) -> String:
|
||||
return term.replace(" ", "").replace("é", "e").replace("+", "plus").replace("&", "and").replace("'n", "and").to_lower()
|
||||
|
||||
|
||||
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
|
||||
else:
|
||||
return false
|
||||
|
||||
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 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
|
||||
@@ -1,63 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bxydgil1yifps"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bg5yo08638600" path="res://SearchWindow.gd" id="1_m7bij"]
|
||||
|
||||
[node name="Control" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_m7bij")
|
||||
|
||||
[node name="SearchPanel" type="Panel" parent="."]
|
||||
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
|
||||
layout_mode = 0
|
||||
offset_left = 880.0
|
||||
offset_top = 24.0
|
||||
offset_right = 944.0
|
||||
offset_bottom = 64.0
|
||||
scale = Vector2(2, 2)
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "Clear"
|
||||
|
||||
[node name="CloseButton" type="Button" parent="."]
|
||||
texture_filter = 1
|
||||
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
|
||||
scale = Vector2(2, 2)
|
||||
placeholder_text = "Search"
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="."]
|
||||
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"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
-107
@@ -1,107 +0,0 @@
|
||||
extends Node
|
||||
|
||||
const MAX_NUMBER_OF_PLAYERS: int = 7
|
||||
|
||||
var default_path: String = "https://music.sanplex.xyz"
|
||||
var selected_server: int = 0
|
||||
|
||||
var is_local: bool = false
|
||||
|
||||
var is_debug: bool = false
|
||||
|
||||
var stop_after_current: bool = true
|
||||
var auto_repeat_song: bool = false
|
||||
var hide_next_track: bool = true
|
||||
var add_to_stats: bool = true
|
||||
var use_low_played_mode: bool = false
|
||||
var winning_score: int = 20
|
||||
var fullscreen: bool = false
|
||||
var play_local: bool = false
|
||||
var inspiration_list_speed: int = 1
|
||||
var hide_beginning: bool = false
|
||||
var hide_length: bool = false
|
||||
var hide_ticks: bool = false
|
||||
|
||||
var player_array: Array[PlayerObject]
|
||||
var edit_players: bool = false
|
||||
var currently_syncing: bool = false
|
||||
var character_select_open: bool = false
|
||||
|
||||
var version: String = "1.8.0"
|
||||
|
||||
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()
|
||||
@@ -1,189 +0,0 @@
|
||||
extends Control
|
||||
|
||||
@onready
|
||||
var settings_panel: Panel = %SettingsPanel
|
||||
|
||||
@onready
|
||||
var stop_after_current_button: CheckButton = %StopAfterCurrentCheckButton
|
||||
|
||||
@onready
|
||||
var hide_next_track_button: CheckButton = %HideNextTrackCheckButton
|
||||
|
||||
@onready
|
||||
var add_to_database_button: CheckButton = %AddToDatabaseCheckButton
|
||||
|
||||
@onready
|
||||
var low_played_button: CheckButton = %LowPlayedCheckButton
|
||||
|
||||
@onready
|
||||
var hide_beginning_button: CheckButton = %HideBeginningCheckButton
|
||||
|
||||
@onready
|
||||
var hide_length_button: CheckButton = %HideLengthCheckButton
|
||||
|
||||
@onready
|
||||
var hide_ticks_button: CheckButton = %HideTicksCheckButton
|
||||
|
||||
@onready
|
||||
var score_label: Label = %ScoreLabel
|
||||
|
||||
@onready
|
||||
var lower_winning_score_button: Button = %LowerButton
|
||||
|
||||
@onready
|
||||
var increase_winning_score_button: Button = %IncreaseButton
|
||||
|
||||
@onready
|
||||
var cache_label: Label = %CacheLabel
|
||||
|
||||
@onready
|
||||
var lower_cache_button: Button = %LowerCacheButton
|
||||
|
||||
@onready
|
||||
var increase_cache_button: Button = %IncreaseCacheButton
|
||||
|
||||
@onready
|
||||
var inspiration_speed_label: Label = %InspirationListSpeedLabel
|
||||
|
||||
@onready
|
||||
var lower_inspiration_speed_button: Button = %LowerInspirationSpeedButton
|
||||
|
||||
@onready
|
||||
var increase_inspiration_speed_button: Button = %IncreaseInspirationSpeedButton
|
||||
|
||||
@onready
|
||||
var select_server_button: OptionButton = %SelectServerButton
|
||||
|
||||
@onready
|
||||
var fullscreen_button: CheckButton = %FullscreenButton
|
||||
|
||||
@onready
|
||||
var local_button: CheckButton = %LocalButton
|
||||
|
||||
signal server_changed
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
fullscreen_button.button_pressed = Settings.fullscreen
|
||||
select_server_button.selected = Settings.selected_server
|
||||
|
||||
stop_after_current_button.pressed.connect(stop_after_current)
|
||||
hide_next_track_button.pressed.connect(hide_next_track)
|
||||
add_to_database_button.pressed.connect(add_to_database)
|
||||
low_played_button.pressed.connect(low_played)
|
||||
hide_beginning_button.pressed.connect(hide_beginning)
|
||||
hide_length_button.pressed.connect(hide_length)
|
||||
hide_ticks_button.pressed.connect(hide_ticks)
|
||||
lower_winning_score_button.pressed.connect(lower_winning_score)
|
||||
increase_winning_score_button.pressed.connect(increase_winning_score)
|
||||
lower_cache_button.pressed.connect(lower_cache)
|
||||
increase_cache_button.pressed.connect(increase_cache)
|
||||
lower_inspiration_speed_button.pressed.connect(lower_inspiration_speed)
|
||||
increase_inspiration_speed_button.pressed.connect(increase_inspiration_speed)
|
||||
fullscreen_button.pressed.connect(fullscreen)
|
||||
local_button.pressed.connect(local_play)
|
||||
|
||||
stop_after_current_button.button_pressed = Settings.stop_after_current
|
||||
hide_next_track_button.button_pressed = Settings.hide_next_track
|
||||
add_to_database_button.button_pressed = Settings.add_to_stats
|
||||
low_played_button.button_pressed = Settings.use_low_played_mode
|
||||
hide_beginning_button.button_pressed = Settings.hide_beginning
|
||||
hide_length_button.button_pressed = Settings.hide_length
|
||||
hide_ticks_button.button_pressed = Settings.hide_ticks
|
||||
score_label.text = str(Settings.winning_score)
|
||||
cache_label.text = str(Playlist.number_of_tracks_to_preload)
|
||||
inspiration_speed_label.text = str(Settings.inspiration_list_speed)
|
||||
fullscreen_button.button_pressed = Settings.fullscreen
|
||||
select_server_button.select(Settings.selected_server)
|
||||
select_server_button.item_selected.connect(select_server)
|
||||
|
||||
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
|
||||
|
||||
func fullscreen() -> void:
|
||||
Settings.fullscreen = !Settings.fullscreen
|
||||
if Settings.fullscreen == true:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
|
||||
if Settings.fullscreen == false:
|
||||
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
|
||||
|
||||
func local_play() -> void:
|
||||
Settings.play_local = !Settings.play_local
|
||||
|
||||
func stop_after_current() -> void:
|
||||
Settings.stop_after_current = !Settings.stop_after_current
|
||||
|
||||
func hide_next_track() -> void:
|
||||
Settings.hide_next_track = !Settings.hide_next_track
|
||||
|
||||
func add_to_database() -> void:
|
||||
Settings.add_to_stats = !Settings.add_to_stats
|
||||
|
||||
func low_played() -> void:
|
||||
Settings.use_low_played_mode = !Settings.use_low_played_mode
|
||||
|
||||
func hide_beginning() -> void:
|
||||
Settings.hide_beginning = !Settings.hide_beginning
|
||||
|
||||
func hide_length() -> void:
|
||||
Settings.hide_length = !Settings.hide_length
|
||||
|
||||
func hide_ticks() -> void:
|
||||
Settings.hide_ticks = !Settings.hide_ticks
|
||||
|
||||
func lower_winning_score() -> void:
|
||||
Settings.winning_score -= 1
|
||||
score_label.text = str(Settings.winning_score)
|
||||
|
||||
func increase_winning_score() -> void:
|
||||
Settings.winning_score += 1
|
||||
score_label.text = str(Settings.winning_score)
|
||||
|
||||
func set_winning_score(new_score: int) -> void:
|
||||
Settings.winning_score = new_score
|
||||
score_label.text = str(Settings.winning_score)
|
||||
|
||||
func lower_cache() -> void:
|
||||
Playlist.number_of_tracks_to_preload -= 1
|
||||
if Playlist.number_of_tracks_to_preload < 1:
|
||||
Playlist.number_of_tracks_to_preload = 1
|
||||
cache_label.text = str(Playlist.number_of_tracks_to_preload)
|
||||
|
||||
func increase_cache() -> void:
|
||||
Playlist.number_of_tracks_to_preload += 1
|
||||
cache_label.text = str(Playlist.number_of_tracks_to_preload)
|
||||
|
||||
func lower_inspiration_speed() -> void:
|
||||
Settings.inspiration_list_speed -= 1
|
||||
if Settings.inspiration_list_speed < 1:
|
||||
Settings.inspiration_list_speed = 1
|
||||
inspiration_speed_label.text = str(Settings.inspiration_list_speed)
|
||||
|
||||
func increase_inspiration_speed() -> void:
|
||||
Settings.inspiration_list_speed += 1
|
||||
inspiration_speed_label.text = str(Settings.inspiration_list_speed )
|
||||
|
||||
func select_server(new_server: int) -> void:
|
||||
print("select_server")
|
||||
Settings.default_path = select_server_button.get_item_text(new_server)
|
||||
Settings.selected_server = new_server
|
||||
print("Settings.default_path: " + Settings.default_path)
|
||||
print("Settings.selected_server: " + str(Settings.selected_server))
|
||||
print("new_server: " + str(new_server))
|
||||
print("select_server_button.get_item_text(new_server): " + select_server_button.get_item_text(new_server))
|
||||
server_changed.emit()
|
||||
|
||||
func set_selected_server(new_server: int) -> void:
|
||||
print("set_selected_server")
|
||||
select_server_button.select(new_server)
|
||||
Settings.default_path = select_server_button.get_item_text(new_server)
|
||||
Settings.selected_server = new_server
|
||||
print("Settings.default_path: " + Settings.default_path)
|
||||
print("Settings.selected_server: " + str(Settings.selected_server))
|
||||
print("new_server: " + str(new_server))
|
||||
print("select_server_button.get_item_text(new_server): " + select_server_button.get_item_text(new_server))
|
||||
server_changed.emit()
|
||||
@@ -1,211 +0,0 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://dldpeo5y3l5hq"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dukqyjoduf1af" path="res://SettingsWindow.gd" id="1_bt55j"]
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_3xrlm"]
|
||||
font_size = 25
|
||||
|
||||
[node name="SettingsControl" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_bt55j")
|
||||
|
||||
[node name="SettingsPanel" type="Panel" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 0
|
||||
offset_right = 500.0
|
||||
offset_bottom = 640.0
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="SettingsPanel"]
|
||||
layout_mode = 0
|
||||
offset_right = 500.0
|
||||
offset_bottom = 640.0
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="SettingsPanel/ScrollContainer"]
|
||||
clip_contents = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Label" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 0
|
||||
text = "Settings"
|
||||
label_settings = SubResource("LabelSettings_3xrlm")
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="StopAfterCurrentCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
button_pressed = true
|
||||
action_mode = 0
|
||||
text = "Stop after current"
|
||||
|
||||
[node name="HideNextTrackCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
button_pressed = true
|
||||
action_mode = 0
|
||||
text = "Hide next track"
|
||||
|
||||
[node name="AddToDatabaseCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
action_mode = 0
|
||||
text = "Turn on played to database"
|
||||
|
||||
[node name="LowPlayedCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
action_mode = 0
|
||||
text = "Use low played mode"
|
||||
|
||||
[node name="HideBeginningCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
action_mode = 0
|
||||
text = "Hide beginning of songs"
|
||||
|
||||
[node name="HideLengthCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
action_mode = 0
|
||||
text = "Hide length of songs"
|
||||
|
||||
[node name="HideTicksCheckButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
action_mode = 0
|
||||
text = "Hide ticks in playbar"
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="SettingsPanel/ScrollContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label2" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Winning Score: "
|
||||
|
||||
[node name="ScoreLabel" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "20"
|
||||
|
||||
[node name="LowerButton" type="Button" parent="SettingsPanel/ScrollContainer/VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(30, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "-1"
|
||||
|
||||
[node name="IncreaseButton" type="Button" parent="SettingsPanel/ScrollContainer/VBoxContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(30, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "+1"
|
||||
|
||||
[node name="CacheHBoxContainer" type="HBoxContainer" parent="SettingsPanel/ScrollContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CacheTitleLabel" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/CacheHBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Predownload songs: "
|
||||
|
||||
[node name="CacheLabel" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/CacheHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "1"
|
||||
|
||||
[node name="LowerCacheButton" type="Button" parent="SettingsPanel/ScrollContainer/VBoxContainer/CacheHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(30, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "-1"
|
||||
|
||||
[node name="IncreaseCacheButton" type="Button" parent="SettingsPanel/ScrollContainer/VBoxContainer/CacheHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(30, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "+1"
|
||||
|
||||
[node name="InspirationHBoxContainer" type="HBoxContainer" parent="SettingsPanel/ScrollContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="InspirationListSpeedTitle" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/InspirationHBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Inspiration List Speed: "
|
||||
|
||||
[node name="InspirationListSpeedLabel" type="Label" parent="SettingsPanel/ScrollContainer/VBoxContainer/InspirationHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "1"
|
||||
|
||||
[node name="LowerInspirationSpeedButton" type="Button" parent="SettingsPanel/ScrollContainer/VBoxContainer/InspirationHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(30, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "-1"
|
||||
|
||||
[node name="IncreaseInspirationSpeedButton" type="Button" parent="SettingsPanel/ScrollContainer/VBoxContainer/InspirationHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(30, 2.08165e-12)
|
||||
layout_mode = 2
|
||||
focus_mode = 0
|
||||
action_mode = 0
|
||||
text = "+1"
|
||||
|
||||
[node name="SelectServerButton" type="OptionButton" parent="SettingsPanel/ScrollContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
selected = 0
|
||||
item_count = 5
|
||||
popup/item_0/text = "https://music.sanplex.xyz"
|
||||
popup/item_0/id = 1
|
||||
popup/item_1/text = "https://tmusic.sanplex.xyz"
|
||||
popup/item_1/id = 2
|
||||
popup/item_2/text = "https://rmusic.sanplex.xyz"
|
||||
popup/item_2/id = 3
|
||||
popup/item_3/text = "https://old-music.sanplex.xyz"
|
||||
popup/item_3/id = 4
|
||||
popup/item_4/text = "http://localhost:8080"
|
||||
popup/item_4/id = 5
|
||||
|
||||
[node name="FullscreenButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
text = "Fullscreen"
|
||||
|
||||
[node name="QuickSyncButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
button_pressed = true
|
||||
action_mode = 0
|
||||
text = "Quick sync"
|
||||
|
||||
[node name="LocalButton" type="CheckButton" parent="SettingsPanel/ScrollContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
disabled = true
|
||||
text = "Local"
|
||||
@@ -1,56 +0,0 @@
|
||||
extends Node
|
||||
|
||||
class_name SongObject
|
||||
|
||||
var song_number: int
|
||||
|
||||
var song_title: String
|
||||
|
||||
var game_title: String
|
||||
|
||||
var players_given_point: Array[int] = []
|
||||
|
||||
var is_answered: bool = false
|
||||
|
||||
var has_played: bool = false
|
||||
|
||||
var is_playing: bool = false
|
||||
|
||||
var song: AudioStream
|
||||
|
||||
func get_game_title() -> String:
|
||||
if is_answered:
|
||||
return game_title
|
||||
else:
|
||||
return "????????"
|
||||
|
||||
func get_song_title() -> String:
|
||||
if is_answered:
|
||||
return song_title
|
||||
else:
|
||||
return "??????"
|
||||
|
||||
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]
|
||||
elif is_answered:
|
||||
var format_string: String = "%d. %s - %s"
|
||||
return format_string % [(song_number + 1), game_title, song_title]
|
||||
else:
|
||||
return "??? - ???"
|
||||
|
||||
func add_point(id: int) -> void:
|
||||
print("add_point")
|
||||
players_given_point.append(id)
|
||||
players_given_point.sort()
|
||||
|
||||
func remove_point(id: int) -> void:
|
||||
print("remove_point")
|
||||
if players_given_point.has(id):
|
||||
players_given_point.remove_at(players_given_point.find(id))
|
||||
players_given_point.sort()
|
||||
|
||||
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]
|
||||
-150
@@ -1,150 +0,0 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://dpdem7pdxweb5"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c3ejukwth512a" path="res://sync_window.gd" id="1_8ba47"]
|
||||
|
||||
[sub_resource type="LabelSettings" id="LabelSettings_anbfi"]
|
||||
font_size = 25
|
||||
|
||||
[node name="Control" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_8ba47")
|
||||
|
||||
[node name="SyncPanel" type="Panel" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 640.0
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="SyncPanel"]
|
||||
layout_mode = 0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 640.0
|
||||
|
||||
[node name="SyncVBoxContainer" type="VBoxContainer" parent="SyncPanel/ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="SyncTitleLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 0
|
||||
text = "Sync"
|
||||
label_settings = SubResource("LabelSettings_anbfi")
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="NoChangesLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 0
|
||||
text = "No changes since last sync"
|
||||
label_settings = SubResource("LabelSettings_anbfi")
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="ProgressHBoxContainer" type="HBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SyncProgressBar" type="ProgressBar" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/ProgressHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(1000, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 6
|
||||
step = 1.0
|
||||
rounded = true
|
||||
|
||||
[node name="SyncHBoxContainer" type="HBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
theme_override_constants/separation = 30
|
||||
|
||||
[node name="VSeparator6" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GamesAddedVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GamesAddedLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/GamesAddedVBox"]
|
||||
layout_mode = 2
|
||||
text = "GamesAdded"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="GamesAddedSeparator" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GamesReAddedVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GamesReAddedLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/GamesReAddedVBox"]
|
||||
layout_mode = 2
|
||||
text = "GamesReAdded"
|
||||
|
||||
[node name="GamesReAddedSeparator" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GamesChangedTitleVBox" type="VBoxContainer" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GamesChangedTitleLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer/GamesChangedTitleVBox"]
|
||||
layout_mode = 2
|
||||
text = "GamesChangedTitle"
|
||||
|
||||
[node name="GamesChangedTitleSeparator" type="VSeparator" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/SyncHBoxContainer"]
|
||||
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"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CatchedErrorsLabel" type="Label" parent="SyncPanel/ScrollContainer/SyncVBoxContainer/CatchedErrorsVBox"]
|
||||
layout_mode = 2
|
||||
text = "CatchedErrors"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="SyncTimer" type="Timer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
# 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 and the API classes |
|
||||
| `tests/integration/` | Real scenes (`MainWindow.tscn`, `SyncPopup.tscn`) run against a fake server, including the point keybinds and the Statistics tab |
|
||||
| `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 are relative to the API (`/music/list` is `<server>/api/v1/music/list`). Paths
|
||||
without a response get a 404, so the code under test sees a failed request.
|
||||
|
||||
Like the real server, the fake hands out tokens on `POST /token` and answers 401 to
|
||||
requests without a valid one, so the client's token handling runs in every test. Token
|
||||
requests are counted in `server.token_requests`, not in `requested_paths`. Use
|
||||
`server.expire_tokens()` to make the client renew its token, `server.hold("/token")` to
|
||||
keep it waiting for one, and `server.respond_json("/token", {...}, 401)` for a refused
|
||||
password.
|
||||
|
||||
## API classes
|
||||
|
||||
The classes in `server/generated/` (`SongInfo`, `StatisticsSummary`, ...) are generated
|
||||
from the server's OpenAPI spec, committed as `server/openapi.json`. To update them, copy
|
||||
a new spec there (`just openapi` in MusicServer, or `<server>/openapi.json`) and run:
|
||||
|
||||
```bash
|
||||
godot --headless --path . -s tools/generate_api_types.gd
|
||||
```
|
||||
|
||||
`tests/unit/test_api_types.gd` parses a sample response with each class.
|
||||
|
||||
## 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.
|
||||
@@ -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()
|
||||
Loaded 100 of 890 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user