rapid-router 5.18.0__py2.py3-none-any.whl → 7.6.8__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- example_project/rapid_router_test_settings.py +19 -7
- example_project/settings.py +21 -8
- example_project/urls.py +5 -6
- game/__init__.py +1 -1
- game/admin.py +7 -2
- game/character.py +8 -0
- game/decor.py +40 -0
- game/end_to_end_tests/base_game_test.py +34 -27
- game/end_to_end_tests/editor_page.py +15 -0
- game/end_to_end_tests/game_page.py +88 -20
- game/end_to_end_tests/selenium_test_case.py +1 -20
- game/end_to_end_tests/test_cow_crashes.py +3 -5
- game/end_to_end_tests/test_level_editor.py +273 -10
- game/end_to_end_tests/test_level_selection.py +25 -3
- game/end_to_end_tests/test_play_through.py +222 -127
- game/end_to_end_tests/test_python_levels.py +41 -7
- game/end_to_end_tests/test_saving_workspace.py +2 -1
- game/forms.py +7 -1
- game/level_management.py +26 -11
- game/messages.py +899 -337
- game/migrations/0001_squashed_0025_levels_ordering_pt1.py +19 -1
- game/migrations/0026_levels_pt2.py +13 -2
- game/migrations/0032_cannot_turn_left_level.py +13 -2
- game/migrations/0033_recursion_level.py +13 -2
- game/migrations/0034_joes_level.py +13 -2
- game/migrations/0035_disable_route_score_level_70.py +0 -2
- game/migrations/0036_level_score_73.py +0 -2
- game/migrations/0037_level_score_79.py +0 -2
- game/migrations/0038_level_score_40.py +0 -1
- game/migrations/0042_level_score_73.py +0 -2
- game/migrations/0048_add_cow_field_and_blocks.py +0 -2
- game/migrations/0049_level_score_34.py +0 -2
- game/migrations/0050_level_score_40.py +0 -2
- game/migrations/0051_level_score_49.py +0 -1
- game/migrations/0086_loop_levels.py +13 -2
- game/migrations/0092_disable_algo_score_in_custom_levels.py +28 -0
- game/migrations/0093_alter_level_character_name.py +18 -0
- game/migrations/0094_add_hint_lesson_subtitle_to_levels.py +28 -0
- game/migrations/0095_level_commands.py +18 -0
- game/migrations/0096_alter_level_commands.py +18 -0
- game/migrations/0097_add_python_den_levels.py +1515 -0
- game/migrations/0098_add_episode_link_fields.py +44 -0
- game/migrations/0099_python_episodes_links.py +103 -0
- game/migrations/0100_reorder_python_levels.py +179 -0
- game/migrations/0101_rename_episodes.py +45 -0
- game/migrations/0102_reoder_episodes_13_14.py +136 -0
- game/migrations/0103_level_1015_solution.py +26 -0
- game/migrations/0104_remove_level_direct_drive.py +17 -0
- game/migrations/0105_delete_invalid_attempts.py +18 -0
- game/migrations/0106_fields_to_snake_case.py +48 -0
- game/migrations/0107_rename_worksheet_link_episode_student_worksheet_link.py +18 -0
- game/migrations/0108_episode_indy_worksheet_link.py +18 -0
- game/migrations/0109_create_episodes_23_and_24.py +99 -0
- game/migrations/0110_remove_episode_indy_worksheet_link_and_more.py +100 -0
- game/migrations/0111_create_worksheets.py +149 -0
- game/migrations/0112_worksheet_locked_classes.py +21 -0
- game/migrations/0113_level_needs_approval.py +18 -0
- game/migrations/0114_default_and_non_student_levels_no_approval.py +31 -0
- game/migrations/0115_level_level__default_does_not_need_approval.py +22 -0
- game/migrations/0116_update_worksheet_video_links.py +68 -0
- game/migrations/0117_update_solutions_to_if_else.py +61 -0
- game/models.py +127 -17
- game/permissions.py +51 -19
- game/python_den_urls.py +26 -0
- game/random_road.py +9 -9
- game/serializers.py +12 -17
- game/static/django_reverse_js/js/reverse.js +171 -0
- game/static/game/css/LilitaOne-Regular.ttf +0 -0
- game/static/game/css/backgrounds.css +8 -12
- game/static/game/css/dataTables.custom.css +3 -2
- game/static/game/css/editor.css +47 -0
- game/static/game/css/game.css +37 -43
- game/static/game/css/game_screen.css +16 -0
- game/static/game/css/level_editor.css +5 -0
- game/static/game/css/level_selection.css +17 -2
- game/static/game/image/Python_Den_hero_student.png +0 -0
- game/static/game/image/Python_levels_page.svg +1954 -0
- game/static/game/image/characters/front_view/Electric_van.svg +448 -0
- game/static/game/image/characters/top_view/Electric_van.svg +448 -0
- game/static/game/image/decor/city/solar_panel.svg +1200 -0
- game/static/game/image/decor/farm/solar_panel.svg +86 -0
- game/static/game/image/decor/grass/solar_panel.svg +86 -0
- game/static/game/image/decor/snow/solar_panel.svg +173 -0
- game/static/game/image/electric_van.svg +448 -0
- game/static/game/image/icons/description.svg +1 -0
- game/static/game/image/icons/hint.svg +1 -0
- game/static/game/image/icons/python.svg +1 -1
- game/static/game/image/pigeon.svg +684 -0
- game/static/game/image/python_den_header.svg +19 -0
- game/static/game/js/animation.js +65 -24
- game/static/game/js/blockly/msg/js/bg.js +52 -1
- game/static/game/js/blockly/msg/js/ca.js +52 -1
- game/static/game/js/blockly/msg/js/en-gb.js +2 -0
- game/static/game/js/blockly/msg/js/en.js +2 -0
- game/static/game/js/blockly/msg/js/es.js +52 -1
- game/static/game/js/blockly/msg/js/fr.js +2 -0
- game/static/game/js/blockly/msg/js/hi.js +2 -0
- game/static/game/js/blockly/msg/js/it.js +52 -1
- game/static/game/js/blockly/msg/js/pl.js +52 -1
- game/static/game/js/blockly/msg/js/pt-br.js +52 -1
- game/static/game/js/blockly/msg/js/ru.js +52 -1
- game/static/game/js/blockly/msg/js/ur.js +52 -1
- game/static/game/js/blocklyCustomBlocks.js +93 -52
- game/static/game/js/button.js +12 -0
- game/static/game/js/cow.js +11 -7
- game/static/game/js/drawing.js +68 -29
- game/static/game/js/editor.js +23 -0
- game/static/game/js/game.js +74 -110
- game/static/game/js/level_editor.js +646 -274
- game/static/game/js/level_moderation.js +33 -2
- game/static/game/js/level_selection.js +1 -1
- game/static/game/js/loadLanguages.js +2 -2
- game/static/game/js/model.js +32 -2
- game/static/game/js/pythonControl.js +14 -1
- game/static/game/js/scoreboard.js +0 -37
- game/static/game/js/scoreboardSharedLevels.js +48 -0
- game/static/game/js/skulpt/skulpt-stdlib.js +1 -1
- game/static/game/js/sound.js +52 -5
- game/static/game/raphael_image/characters/top_view/Electric_van.svg +448 -0
- game/static/game/raphael_image/decor/city/solar_panel.svg +1200 -0
- game/static/game/raphael_image/decor/farm/solar_panel.svg +86 -0
- game/static/game/raphael_image/decor/grass/solar_panel.svg +86 -0
- game/static/game/raphael_image/decor/snow/solar_panel.svg +173 -0
- game/static/game/raphael_image/pigeon.svg +685 -0
- game/static/game/sass/game.scss +2 -2
- game/static/game/sound/clown_horn.mp3 +0 -0
- game/static/game/sound/clown_horn.ogg +0 -0
- game/static/game/sound/electric_van_starting.mp3 +0 -0
- game/static/game/sound/electric_van_starting.ogg +0 -0
- game/static/game/sound/pigeon.mp3 +0 -0
- game/static/game/sound/pigeon.ogg +0 -0
- game/static/game/sound/sleigh_bells.mp3 +0 -0
- game/static/game/sound/sleigh_bells.ogg +0 -0
- game/static/game/sound/sleigh_crash.mp3 +0 -0
- game/static/game/sound/sleigh_crash.ogg +0 -0
- game/templates/game/base.html +34 -14
- game/templates/game/basenonav.html +11 -5
- game/templates/game/game.html +142 -38
- game/templates/game/level_editor.html +340 -236
- game/templates/game/level_moderation.html +19 -6
- game/templates/game/level_selection.html +18 -110
- game/templates/game/python_den_level_selection.html +291 -0
- game/templates/game/python_den_worksheet.html +101 -0
- game/templates/game/scoreboard.html +83 -64
- game/tests/test_level_editor.py +94 -26
- game/tests/test_level_selection.py +149 -46
- game/tests/test_python_den_worksheet.py +85 -0
- game/tests/test_scoreboard.py +34 -7
- game/tests/utils/level.py +32 -26
- game/theme.py +5 -5
- game/urls.py +199 -61
- game/views/language_code_conversions.py +86 -86
- game/views/level.py +155 -63
- game/views/level_editor.py +88 -55
- game/views/level_moderation.py +23 -0
- game/views/level_selection.py +116 -47
- game/views/level_solutions.py +491 -106
- game/views/scoreboard.py +76 -51
- game/views/worksheet.py +25 -0
- rapid_router-7.6.8.dist-info/METADATA +174 -0
- {rapid_router-5.18.0.dist-info → rapid_router-7.6.8.dist-info}/RECORD +164 -104
- {rapid_router-5.18.0.dist-info → rapid_router-7.6.8.dist-info}/WHEEL +1 -1
- example_project/manage.py +0 -10
- game/static/game/image/actions/go.svg +0 -18
- game/static/game/js/js-reverse.js +0 -14
- game/static/game/js/pqselect.min.js +0 -9
- game/static/game/js/widget-scroller.js +0 -906
- rapid_router-5.18.0.dist-info/METADATA +0 -17
- {rapid_router-5.18.0.dist-info → rapid_router-7.6.8.dist-info/licenses}/LICENSE.md +0 -0
- {rapid_router-5.18.0.dist-info → rapid_router-7.6.8.dist-info}/top_level.txt +0 -0
|
@@ -69,13 +69,12 @@ STATICFILES_DIRS = [os.path.join(BASE_DIR, "game/static")]
|
|
|
69
69
|
SECRET_KEY = "bad_test_secret"
|
|
70
70
|
ROOT_URLCONF = "example_project.urls"
|
|
71
71
|
|
|
72
|
-
WSGI_APPLICATION = "wsgi.application"
|
|
72
|
+
WSGI_APPLICATION = "example_project.wsgi.application"
|
|
73
73
|
|
|
74
74
|
INSTALLED_APPS = (
|
|
75
75
|
"game",
|
|
76
76
|
"pipeline",
|
|
77
77
|
"portal",
|
|
78
|
-
"aimmo",
|
|
79
78
|
"common",
|
|
80
79
|
"django.contrib.admin",
|
|
81
80
|
"django.contrib.admindocs",
|
|
@@ -84,7 +83,7 @@ INSTALLED_APPS = (
|
|
|
84
83
|
"django.contrib.sessions",
|
|
85
84
|
"django.contrib.messages",
|
|
86
85
|
"django.contrib.staticfiles",
|
|
87
|
-
"
|
|
86
|
+
"django_reverse_js",
|
|
88
87
|
"django_otp",
|
|
89
88
|
"django_otp.plugins.otp_static",
|
|
90
89
|
"django_otp.plugins.otp_totp",
|
|
@@ -115,15 +114,15 @@ PIPELINE = {
|
|
|
115
114
|
os.path.join(BASE_DIR, "static/portal/sass/colorbox.scss"),
|
|
116
115
|
os.path.join(BASE_DIR, "static/portal/sass/styles.scss"),
|
|
117
116
|
),
|
|
118
|
-
"output_filename": "portal.css",
|
|
117
|
+
"output_filename": "portal/css/portal.css",
|
|
119
118
|
},
|
|
120
119
|
"popup": {
|
|
121
120
|
"source_filenames": (os.path.join(BASE_DIR, "static/portal/sass/partials/_popup.scss"),),
|
|
122
|
-
"output_filename": "popup.css",
|
|
121
|
+
"output_filename": "portal/css/popup.css",
|
|
123
122
|
},
|
|
124
123
|
"game-scss": {
|
|
125
124
|
"source_filenames": (os.path.join(BASE_DIR, "static/game/sass/game.scss"),),
|
|
126
|
-
"output_filename": "game.css",
|
|
125
|
+
"output_filename": "game/css/gamestyles.css",
|
|
127
126
|
},
|
|
128
127
|
},
|
|
129
128
|
"CSS_COMPRESSOR": None,
|
|
@@ -134,7 +133,12 @@ STATICFILES_FINDERS = [
|
|
|
134
133
|
"django.contrib.staticfiles.finders.FileSystemFinder",
|
|
135
134
|
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
|
136
135
|
]
|
|
137
|
-
|
|
136
|
+
STORAGES = {
|
|
137
|
+
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
|
|
138
|
+
"staticfiles": {
|
|
139
|
+
"BACKEND": "pipeline.storage.PipelineManifestStorage",
|
|
140
|
+
},
|
|
141
|
+
}
|
|
138
142
|
|
|
139
143
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
|
140
144
|
|
|
@@ -150,3 +154,11 @@ except ImportError:
|
|
|
150
154
|
pass
|
|
151
155
|
|
|
152
156
|
from common.csp_config import *
|
|
157
|
+
|
|
158
|
+
if MODULE_NAME == "local":
|
|
159
|
+
# NOTE: This is only used locally for testing purposes.
|
|
160
|
+
os.environ.setdefault(
|
|
161
|
+
"ENCRYPTION_KEY", "XTgWqMlZCMI_E5BvCArkif9nrJIIhe_6Ic6Q_UcWJDk="
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
ENCRYPTION_KEY = os.environ["ENCRYPTION_KEY"]
|
example_project/settings.py
CHANGED
|
@@ -54,15 +54,14 @@ STATIC_URL = "/static/"
|
|
|
54
54
|
STATICFILES_DIRS = [os.path.join(BASE_DIR, "game/static")]
|
|
55
55
|
|
|
56
56
|
SECRET_KEY = "not-a-secret"
|
|
57
|
-
ROOT_URLCONF = "urls"
|
|
57
|
+
ROOT_URLCONF = "example_project.urls"
|
|
58
58
|
|
|
59
|
-
WSGI_APPLICATION = "wsgi.application"
|
|
59
|
+
WSGI_APPLICATION = "example_project.wsgi.application"
|
|
60
60
|
|
|
61
61
|
INSTALLED_APPS = (
|
|
62
62
|
"game",
|
|
63
63
|
"pipeline",
|
|
64
64
|
"portal",
|
|
65
|
-
"aimmo",
|
|
66
65
|
"common",
|
|
67
66
|
"django.contrib.admin",
|
|
68
67
|
"django.contrib.admindocs",
|
|
@@ -71,7 +70,8 @@ INSTALLED_APPS = (
|
|
|
71
70
|
"django.contrib.sessions",
|
|
72
71
|
"django.contrib.messages",
|
|
73
72
|
"django.contrib.staticfiles",
|
|
74
|
-
"
|
|
73
|
+
"django_extensions",
|
|
74
|
+
"django_reverse_js",
|
|
75
75
|
"django_otp",
|
|
76
76
|
"django_otp.plugins.otp_static",
|
|
77
77
|
"django_otp.plugins.otp_totp",
|
|
@@ -102,15 +102,15 @@ PIPELINE = {
|
|
|
102
102
|
os.path.join(BASE_DIR, "static/portal/sass/colorbox.scss"),
|
|
103
103
|
os.path.join(BASE_DIR, "static/portal/sass/styles.scss"),
|
|
104
104
|
),
|
|
105
|
-
"output_filename": "portal.css",
|
|
105
|
+
"output_filename": "portal/css/portal.css",
|
|
106
106
|
},
|
|
107
107
|
"popup": {
|
|
108
108
|
"source_filenames": (os.path.join(BASE_DIR, "static/portal/sass/partials/_popup.scss"),),
|
|
109
|
-
"output_filename": "popup.css",
|
|
109
|
+
"output_filename": "portal/css/popup.css",
|
|
110
110
|
},
|
|
111
111
|
"game-scss": {
|
|
112
112
|
"source_filenames": (os.path.join(BASE_DIR, "static/game/sass/game.scss"),),
|
|
113
|
-
"output_filename": "game.css",
|
|
113
|
+
"output_filename": "game/css/gamestyles.css",
|
|
114
114
|
},
|
|
115
115
|
},
|
|
116
116
|
"CSS_COMPRESSOR": None,
|
|
@@ -121,7 +121,12 @@ STATICFILES_FINDERS = [
|
|
|
121
121
|
"django.contrib.staticfiles.finders.FileSystemFinder",
|
|
122
122
|
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
|
123
123
|
]
|
|
124
|
-
|
|
124
|
+
STORAGES = {
|
|
125
|
+
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
|
|
126
|
+
"staticfiles": {
|
|
127
|
+
"BACKEND": "pipeline.storage.PipelineManifestStorage",
|
|
128
|
+
},
|
|
129
|
+
}
|
|
125
130
|
|
|
126
131
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
|
127
132
|
|
|
@@ -137,3 +142,11 @@ except ImportError:
|
|
|
137
142
|
pass
|
|
138
143
|
|
|
139
144
|
from common.csp_config import *
|
|
145
|
+
|
|
146
|
+
if MODULE_NAME == "local":
|
|
147
|
+
# NOTE: This is only used locally for testing purposes.
|
|
148
|
+
os.environ.setdefault(
|
|
149
|
+
"ENCRYPTION_KEY", "XTgWqMlZCMI_E5BvCArkif9nrJIIhe_6Ic6Q_UcWJDk="
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
ENCRYPTION_KEY = os.environ["ENCRYPTION_KEY"]
|
example_project/urls.py
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
from aimmo import urls as aimmo_urls
|
|
2
|
-
from django.conf.urls import include, url
|
|
3
1
|
from django.contrib import admin
|
|
4
|
-
from django.urls import path
|
|
2
|
+
from django.urls import include, path, re_path
|
|
5
3
|
from portal import urls as portal_urls
|
|
6
4
|
|
|
7
5
|
from game import urls as game_urls
|
|
6
|
+
from game import python_den_urls
|
|
8
7
|
|
|
9
8
|
admin.autodiscover()
|
|
10
9
|
|
|
11
10
|
urlpatterns = [
|
|
12
|
-
|
|
11
|
+
re_path(r"^", include(portal_urls)),
|
|
13
12
|
path("administration/", admin.site.urls),
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
re_path(r"^rapidrouter/", include(game_urls)),
|
|
14
|
+
re_path(r"^pythonden/", include(python_den_urls)),
|
|
16
15
|
]
|
game/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "
|
|
1
|
+
__version__ = "7.6.8"
|
game/admin.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from django.contrib import admin
|
|
2
2
|
|
|
3
|
-
from game.models import Level, Block, Episode, Workspace, LevelDecor, Attempt
|
|
3
|
+
from game.models import Level, Block, Episode, Workspace, LevelDecor, Attempt, Worksheet
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class LevelAdmin(admin.ModelAdmin):
|
|
@@ -43,7 +43,11 @@ class AttemptAdmin(admin.ModelAdmin):
|
|
|
43
43
|
|
|
44
44
|
class LevelDecorAdmin(admin.ModelAdmin):
|
|
45
45
|
search_fields = ["level__name"]
|
|
46
|
-
list_display = ["id", "level", "x", "y", "
|
|
46
|
+
list_display = ["id", "level", "x", "y", "decor_name"]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class WorksheetAdmin(admin.ModelAdmin):
|
|
50
|
+
list_display = ["id", "episode"]
|
|
47
51
|
|
|
48
52
|
|
|
49
53
|
admin.site.register(Level, LevelAdmin)
|
|
@@ -52,3 +56,4 @@ admin.site.register(Workspace, WorkspaceAdmin)
|
|
|
52
56
|
admin.site.register(Block)
|
|
53
57
|
admin.site.register(Attempt, AttemptAdmin)
|
|
54
58
|
admin.site.register(LevelDecor, LevelDecorAdmin)
|
|
59
|
+
admin.site.register(Worksheet, WorksheetAdmin)
|
game/character.py
CHANGED
|
@@ -67,6 +67,14 @@ CHARACTER_DATA = {
|
|
|
67
67
|
height="40",
|
|
68
68
|
width="40",
|
|
69
69
|
),
|
|
70
|
+
"Electric van": Character(
|
|
71
|
+
pk=7,
|
|
72
|
+
name="Electric van",
|
|
73
|
+
en_face="characters/front_view/Electric_van.svg",
|
|
74
|
+
top_down="characters/top_view/Electric_van.svg",
|
|
75
|
+
height="20",
|
|
76
|
+
width="40"
|
|
77
|
+
),
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
|
game/decor.py
CHANGED
|
@@ -345,6 +345,46 @@ DECOR_DATA = {
|
|
|
345
345
|
theme=get_theme("city"),
|
|
346
346
|
pk=32,
|
|
347
347
|
),
|
|
348
|
+
("solar_panel", "grass"): Decor(
|
|
349
|
+
z_index=4,
|
|
350
|
+
name="solar_panel",
|
|
351
|
+
url="decor/grass/solar_panel.svg",
|
|
352
|
+
xmas_url="decor/snow/tree1.svg",
|
|
353
|
+
height=100,
|
|
354
|
+
width=100,
|
|
355
|
+
theme=get_theme("grass"),
|
|
356
|
+
pk=33
|
|
357
|
+
),
|
|
358
|
+
("solar_panel", "farm"): Decor(
|
|
359
|
+
z_index=4,
|
|
360
|
+
name="solar_panel",
|
|
361
|
+
url="decor/farm/solar_panel.svg",
|
|
362
|
+
xmas_url="decor/snow/tree1.svg",
|
|
363
|
+
height=100,
|
|
364
|
+
width=100,
|
|
365
|
+
theme=get_theme("farm"),
|
|
366
|
+
pk=34
|
|
367
|
+
),
|
|
368
|
+
("solar_panel", "snow"): Decor(
|
|
369
|
+
z_index=4,
|
|
370
|
+
name="solar_panel",
|
|
371
|
+
url="decor/snow/solar_panel.svg",
|
|
372
|
+
xmas_url="decor/snow/solar_panel.svg",
|
|
373
|
+
height=100,
|
|
374
|
+
width=100,
|
|
375
|
+
theme=get_theme("snow"),
|
|
376
|
+
pk=35
|
|
377
|
+
),
|
|
378
|
+
("solar_panel", "city"): Decor(
|
|
379
|
+
z_index=4,
|
|
380
|
+
name="solar_panel",
|
|
381
|
+
url="decor/city/solar_panel.svg",
|
|
382
|
+
xmas_url="decor/snow/solar_panel.svg",
|
|
383
|
+
height=100,
|
|
384
|
+
width=100,
|
|
385
|
+
theme=get_theme("city"),
|
|
386
|
+
pk=36
|
|
387
|
+
)
|
|
348
388
|
}
|
|
349
389
|
|
|
350
390
|
|
|
@@ -43,15 +43,17 @@ class BaseGameTest(SeleniumTestCase):
|
|
|
43
43
|
else:
|
|
44
44
|
break
|
|
45
45
|
|
|
46
|
-
def _complete_level(self, level_number, **kwargs):
|
|
47
|
-
page = self.go_to_level(level_number)
|
|
48
|
-
self.complete_and_check_level(level_number, page, **kwargs)
|
|
46
|
+
def _complete_level(self, level_number, from_python_den=False, **kwargs):
|
|
47
|
+
page = self.go_to_level(level_number, from_python_den)
|
|
48
|
+
(self.complete_and_check_level(level_number, page, from_python_den, **kwargs))
|
|
49
49
|
|
|
50
50
|
def complete_and_check_level(
|
|
51
51
|
self,
|
|
52
52
|
level_number,
|
|
53
53
|
page,
|
|
54
|
+
from_python_den=False,
|
|
54
55
|
next_episode=None,
|
|
56
|
+
redirects=False,
|
|
55
57
|
check_algorithm_score=True,
|
|
56
58
|
check_route_score=True,
|
|
57
59
|
final_level=False,
|
|
@@ -60,18 +62,21 @@ class BaseGameTest(SeleniumTestCase):
|
|
|
60
62
|
if check_algorithm_score:
|
|
61
63
|
page.assert_algorithm_score(10)
|
|
62
64
|
if check_route_score:
|
|
63
|
-
if level_number < 13:
|
|
65
|
+
if level_number < 13 and not from_python_den:
|
|
64
66
|
page.assert_route_score(20)
|
|
65
67
|
else:
|
|
66
68
|
page.assert_route_score(10)
|
|
67
69
|
if final_level:
|
|
68
70
|
return page
|
|
69
71
|
if next_episode is None:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
if redirects:
|
|
73
|
+
page.next_level_redirected(from_python_den)
|
|
74
|
+
else:
|
|
75
|
+
page.next_level(from_python_den)
|
|
76
|
+
page.assert_level_number(level_number + 1, from_python_den)
|
|
72
77
|
else:
|
|
73
78
|
page.next_episode()
|
|
74
|
-
page.assert_episode_number(next_episode)
|
|
79
|
+
page.assert_episode_number(next_episode, from_python_den)
|
|
75
80
|
return page
|
|
76
81
|
|
|
77
82
|
def go_to_reverse(self, url_reverse):
|
|
@@ -84,12 +89,26 @@ class BaseGameTest(SeleniumTestCase):
|
|
|
84
89
|
self._go_to_path(path)
|
|
85
90
|
return HomePage(self.selenium)
|
|
86
91
|
|
|
87
|
-
def go_to_level(self, level_name):
|
|
88
|
-
|
|
92
|
+
def go_to_level(self, level_name, from_python_den=False):
|
|
93
|
+
viewname = (
|
|
94
|
+
"play_python_default_level" if from_python_den else "play_default_level"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
path = reverse(viewname, kwargs={"level_name": str(level_name)})
|
|
89
98
|
self._go_to_path(path)
|
|
90
99
|
|
|
91
100
|
return GamePage(self.selenium)
|
|
92
101
|
|
|
102
|
+
def go_to_level_without_dismissing_dialog(self, level_name, from_python_den=False):
|
|
103
|
+
viewname = (
|
|
104
|
+
"play_python_default_level" if from_python_den else "play_default_level"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
path = reverse(viewname, kwargs={"level_name": str(level_name)})
|
|
108
|
+
self._go_to_path(path)
|
|
109
|
+
|
|
110
|
+
return BasePage(self.selenium)
|
|
111
|
+
|
|
93
112
|
def go_to_custom_level(self, level):
|
|
94
113
|
path = reverse("play_custom_level", kwargs={"levelId": str(level.id)})
|
|
95
114
|
self._go_to_path(path)
|
|
@@ -102,7 +121,10 @@ class BaseGameTest(SeleniumTestCase):
|
|
|
102
121
|
return EditorPage(self.selenium)
|
|
103
122
|
|
|
104
123
|
def go_to_episode(self, episodeId):
|
|
105
|
-
|
|
124
|
+
if int(episodeId) > 9:
|
|
125
|
+
path = reverse("start_python_episode", kwargs={"episodeId": str(episodeId)})
|
|
126
|
+
else:
|
|
127
|
+
path = reverse("start_episode", kwargs={"episodeId": str(episodeId)})
|
|
106
128
|
self._go_to_path(path)
|
|
107
129
|
|
|
108
130
|
return GamePage(self.selenium)
|
|
@@ -128,23 +150,8 @@ class BaseGameTest(SeleniumTestCase):
|
|
|
128
150
|
self.go_to_level(level).load_solution(workspace_id).run_crashing_program()
|
|
129
151
|
)
|
|
130
152
|
|
|
131
|
-
def
|
|
132
|
-
return self.
|
|
133
|
-
|
|
134
|
-
def run_clear_console_test(self, level):
|
|
135
|
-
return self.go_to_level(level).write_to_then_clear_console()
|
|
136
|
-
|
|
137
|
-
def run_console_parse_error_test(self, level):
|
|
138
|
-
return self.go_to_level(level).run_parse_error_program()
|
|
139
|
-
|
|
140
|
-
def run_console_attribute_error_test(self, level):
|
|
141
|
-
return self.go_to_level(level).run_attribute_error_program()
|
|
142
|
-
|
|
143
|
-
def run_console_print_test(self, level):
|
|
144
|
-
return self.go_to_level(level).run_print_program()
|
|
145
|
-
|
|
146
|
-
def run_invalid_import_test(self, level):
|
|
147
|
-
return self.go_to_level(level).run_invalid_import_program()
|
|
153
|
+
def run_animal_sound_horn_test(self, level):
|
|
154
|
+
return self.go_to_custom_level(level).run_animal_sound_horn_program()
|
|
148
155
|
|
|
149
156
|
def running_out_of_instructions_test(self, level, workspace_file):
|
|
150
157
|
user_profile = self.login_once()
|
|
@@ -21,3 +21,18 @@ class EditorPage(BasePage):
|
|
|
21
21
|
|
|
22
22
|
def go_to_code_tab(self):
|
|
23
23
|
self.browser.find_element(By.ID, "blocks_tab").click()
|
|
24
|
+
|
|
25
|
+
def go_to_scenery_tab(self):
|
|
26
|
+
self.browser.find_element(By.ID, "scenery_tab").click()
|
|
27
|
+
|
|
28
|
+
def go_to_character_tab(self):
|
|
29
|
+
self.browser.find_element(By.ID, "character_tab").click()
|
|
30
|
+
|
|
31
|
+
def go_to_description_tab(self):
|
|
32
|
+
self.browser.find_element(By.ID, "description_tab").click()
|
|
33
|
+
|
|
34
|
+
def go_to_hint_tab(self):
|
|
35
|
+
self.browser.find_element(By.ID, "hint_tab").click()
|
|
36
|
+
|
|
37
|
+
def go_to_save_tab(self):
|
|
38
|
+
self.browser.find_element(By.ID, "save_tab").click()
|
|
@@ -19,7 +19,9 @@ class GamePage(BasePage):
|
|
|
19
19
|
def __init__(self, browser):
|
|
20
20
|
super(GamePage, self).__init__(browser)
|
|
21
21
|
|
|
22
|
-
self.browser.execute_script(
|
|
22
|
+
self.browser.execute_script(
|
|
23
|
+
"ocargo.animation.FAST_ANIMATION_DURATION = 1;"
|
|
24
|
+
)
|
|
23
25
|
|
|
24
26
|
assert self.on_correct_page("game_page")
|
|
25
27
|
|
|
@@ -30,18 +32,25 @@ class GamePage(BasePage):
|
|
|
30
32
|
return self
|
|
31
33
|
|
|
32
34
|
def dismiss_dialog(self, button_id):
|
|
33
|
-
self.wait_for_element_to_be_clickable(
|
|
35
|
+
self.wait_for_element_to_be_clickable(
|
|
36
|
+
(By.ID, button_id), wait_seconds=15
|
|
37
|
+
)
|
|
34
38
|
self.browser.find_element(By.ID, button_id).click()
|
|
35
|
-
self.wait_for_element_to_be_invisible(
|
|
39
|
+
self.wait_for_element_to_be_invisible(
|
|
40
|
+
(By.ID, button_id), wait_seconds=15
|
|
41
|
+
)
|
|
36
42
|
|
|
37
43
|
def save_solution(self, workspace_name):
|
|
38
44
|
self.browser.find_element(By.ID, "save_tab").click()
|
|
39
|
-
self.browser.find_element(By.ID, "workspaceNameInput").send_keys(
|
|
45
|
+
self.browser.find_element(By.ID, "workspaceNameInput").send_keys(
|
|
46
|
+
workspace_name
|
|
47
|
+
)
|
|
40
48
|
self.browser.find_element(By.ID, "saveWorkspace").click()
|
|
41
49
|
return self
|
|
42
50
|
|
|
43
51
|
def load_solution_by_name(self, solution_name):
|
|
44
52
|
self.browser.find_element(By.ID, "load_tab").click()
|
|
53
|
+
time.sleep(1)
|
|
45
54
|
self.browser.find_element(By.ID, solution_name).click()
|
|
46
55
|
self.browser.find_element(By.ID, "loadWorkspace").click()
|
|
47
56
|
time.sleep(1)
|
|
@@ -70,7 +79,9 @@ class GamePage(BasePage):
|
|
|
70
79
|
|
|
71
80
|
def solution_button(self):
|
|
72
81
|
self.browser.find_element(By.ID, "solution_tab").click()
|
|
73
|
-
solution_loaded = self.browser.execute_script(
|
|
82
|
+
solution_loaded = self.browser.execute_script(
|
|
83
|
+
"return ocargo.solutionLoaded;"
|
|
84
|
+
)
|
|
74
85
|
timeout = time.time() + 30
|
|
75
86
|
|
|
76
87
|
while not solution_loaded:
|
|
@@ -89,13 +100,27 @@ class GamePage(BasePage):
|
|
|
89
100
|
self.browser.find_element(By.ID, "clear_console").click()
|
|
90
101
|
return self
|
|
91
102
|
|
|
92
|
-
def assert_level_number(self, level_number):
|
|
93
|
-
|
|
94
|
-
|
|
103
|
+
def assert_level_number(self, level_number, from_python_den):
|
|
104
|
+
viewname = (
|
|
105
|
+
"play_python_default_level"
|
|
106
|
+
if from_python_den
|
|
107
|
+
else "play_default_level"
|
|
108
|
+
)
|
|
95
109
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
path = reverse(viewname, kwargs={"level_name": str(level_number)})
|
|
111
|
+
assert_that(
|
|
112
|
+
self.browser.current_url.replace("#myModal", ""), ends_with(path)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def assert_episode_number(self, episode_number, from_python_den):
|
|
116
|
+
viewname = (
|
|
117
|
+
"start_python_episode" if from_python_den else "start_episode"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
path = reverse(viewname, kwargs={"episodeId": str(episode_number)})
|
|
121
|
+
assert_that(
|
|
122
|
+
self.browser.current_url.replace("#myModal", ""), ends_with(path)
|
|
123
|
+
)
|
|
99
124
|
|
|
100
125
|
def assert_is_green_light(self, traffic_light_index):
|
|
101
126
|
self._assert_light_is_on(traffic_light_index, "green")
|
|
@@ -104,8 +129,8 @@ class GamePage(BasePage):
|
|
|
104
129
|
self._assert_light_is_on(traffic_light_index, "red")
|
|
105
130
|
|
|
106
131
|
def _assert_light_is_on(self, traffic_light_index, colour):
|
|
107
|
-
image = self.browser.find_element(
|
|
108
|
-
"trafficLight_%s_%s" % (traffic_light_index, colour)
|
|
132
|
+
image = self.browser.find_element(
|
|
133
|
+
By.ID, "trafficLight_%s_%s" % (traffic_light_index, colour)
|
|
109
134
|
)
|
|
110
135
|
|
|
111
136
|
assert_that(image.get_attribute("opacity"), equal_to("1"))
|
|
@@ -114,7 +139,9 @@ class GamePage(BasePage):
|
|
|
114
139
|
self.browser.find_element(By.ID, "fast_tab").click()
|
|
115
140
|
|
|
116
141
|
try:
|
|
117
|
-
self.wait_for_element_to_be_clickable(
|
|
142
|
+
self.wait_for_element_to_be_clickable(
|
|
143
|
+
(By.ID, wait_for_element_id), 45
|
|
144
|
+
)
|
|
118
145
|
except TimeoutException as e:
|
|
119
146
|
millis = int(round(time.time() * 1000))
|
|
120
147
|
screenshot_filename = "/tmp/game_tests_%s-%s.png" % (
|
|
@@ -130,7 +157,9 @@ class GamePage(BasePage):
|
|
|
130
157
|
def run_retry_program(self):
|
|
131
158
|
self.run_program("try_again_button")
|
|
132
159
|
modal_content = self.browser.find_element(By.ID, "modal-content").text
|
|
133
|
-
assert_that(
|
|
160
|
+
assert_that(
|
|
161
|
+
modal_content, contains_string("Try creating a simpler program.")
|
|
162
|
+
)
|
|
134
163
|
self.browser.find_element(By.ID, "try_again_button").click()
|
|
135
164
|
time.sleep(1)
|
|
136
165
|
return self
|
|
@@ -184,6 +213,20 @@ class GamePage(BasePage):
|
|
|
184
213
|
"from van import Va", "You can only import 'Van' from 'van'"
|
|
185
214
|
)
|
|
186
215
|
|
|
216
|
+
def run_animal_sound_horn_program(self):
|
|
217
|
+
return self._run_working_program(
|
|
218
|
+
"""
|
|
219
|
+
while not my_van.at_destination():
|
|
220
|
+
if my_van.is_animal_crossing():
|
|
221
|
+
my_van.sound_horn()
|
|
222
|
+
if my_van.is_road_forward():
|
|
223
|
+
my_van.move_forwards()
|
|
224
|
+
elif my_van.is_road_left():
|
|
225
|
+
my_van.turn_left()
|
|
226
|
+
else:
|
|
227
|
+
my_van.turn_right()""",
|
|
228
|
+
)
|
|
229
|
+
|
|
187
230
|
def check_python_commands(self):
|
|
188
231
|
self.python_commands_button()
|
|
189
232
|
time.sleep(1)
|
|
@@ -203,19 +246,36 @@ class GamePage(BasePage):
|
|
|
203
246
|
assert_that(console.text == "")
|
|
204
247
|
return self
|
|
205
248
|
|
|
206
|
-
def next_episode(self):
|
|
249
|
+
def next_episode(self, from_python_den=False):
|
|
207
250
|
self.assert_success()
|
|
208
251
|
self.browser.find_element(By.ID, "next_episode_button").click()
|
|
252
|
+
|
|
253
|
+
tabId = "python_tab" if from_python_den else "blockly_tab"
|
|
254
|
+
|
|
255
|
+
WebDriverWait(self.browser, 10).until(
|
|
256
|
+
presence_of_all_elements_located((By.ID, tabId))
|
|
257
|
+
)
|
|
258
|
+
return self
|
|
259
|
+
|
|
260
|
+
def next_level(self, from_python_den=False):
|
|
261
|
+
self.assert_success()
|
|
262
|
+
self.browser.find_element(By.ID, "next_level_button").click()
|
|
263
|
+
|
|
264
|
+
tabId = "python_tab" if from_python_den else "blockly_tab"
|
|
265
|
+
|
|
209
266
|
WebDriverWait(self.browser, 10).until(
|
|
210
|
-
presence_of_all_elements_located((By.ID,
|
|
267
|
+
presence_of_all_elements_located((By.ID, tabId))
|
|
211
268
|
)
|
|
212
269
|
return self
|
|
213
270
|
|
|
214
|
-
def
|
|
271
|
+
def next_level_redirected(self, from_python_den=False):
|
|
215
272
|
self.assert_success()
|
|
216
273
|
self.browser.find_element(By.ID, "next_level_button").click()
|
|
274
|
+
|
|
275
|
+
episodeId = "episode-16" if from_python_den else "episode-1"
|
|
276
|
+
|
|
217
277
|
WebDriverWait(self.browser, 10).until(
|
|
218
|
-
presence_of_all_elements_located((By.ID,
|
|
278
|
+
presence_of_all_elements_located((By.ID, episodeId))
|
|
219
279
|
)
|
|
220
280
|
return self
|
|
221
281
|
|
|
@@ -241,6 +301,12 @@ class GamePage(BasePage):
|
|
|
241
301
|
assert_that(error_message, contains_string(text))
|
|
242
302
|
return self
|
|
243
303
|
|
|
304
|
+
def _run_working_program(self, code):
|
|
305
|
+
self._write_code(code)
|
|
306
|
+
self.browser.find_element(By.ID, "fast_tab").click()
|
|
307
|
+
time.sleep(1)
|
|
308
|
+
return self
|
|
309
|
+
|
|
244
310
|
def _write_code(self, code):
|
|
245
311
|
self.browser.execute_script(
|
|
246
312
|
"ocargo.pythonControl.appendCode(arguments[0])", code
|
|
@@ -249,7 +315,9 @@ class GamePage(BasePage):
|
|
|
249
315
|
|
|
250
316
|
def _run_procedure_error_program(self, text):
|
|
251
317
|
self.run_program("close_button")
|
|
252
|
-
error_message = self.browser.find_element(
|
|
318
|
+
error_message = self.browser.find_element(
|
|
319
|
+
By.ID, "myModal-mainText"
|
|
320
|
+
).text
|
|
253
321
|
assert_that(error_message, contains_string(text))
|
|
254
322
|
|
|
255
323
|
def _assert_score(self, element_id, score):
|
|
@@ -6,26 +6,7 @@ This solves a bug introduced when upgrading to Django 1.11,
|
|
|
6
6
|
see more information here: https://github.com/jazzband/django-pipeline/issues/593
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from django.
|
|
10
|
-
from django.test.testcases import (
|
|
11
|
-
LiveServerTestCase,
|
|
12
|
-
LiveServerThread,
|
|
13
|
-
QuietWSGIRequestHandler,
|
|
14
|
-
)
|
|
9
|
+
from django.contrib.staticfiles.testing import LiveServerTestCase
|
|
15
10
|
from django_selenium_clean import SeleniumTestCase
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
class NonThreadedLiveServerThread(LiveServerThread):
|
|
19
|
-
"""
|
|
20
|
-
Replaces ThreadedWSGIServer with WSGIServer as the threaded one doesn't close the DB connections properly, thus
|
|
21
|
-
triggering random "DB table locked" errors.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
def _create_server(self):
|
|
25
|
-
return WSGIServer(
|
|
26
|
-
(self.host, self.port), QuietWSGIRequestHandler, allow_reuse_address=False
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
12
|
SeleniumTestCase.__bases__ = (LiveServerTestCase,)
|
|
31
|
-
SeleniumTestCase.server_thread_class = NonThreadedLiveServerThread
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
from game.character import get_character
|
|
1
2
|
from game.end_to_end_tests.base_game_test import BaseGameTest
|
|
2
3
|
from game.models import Level, Block, LevelBlock
|
|
3
|
-
|
|
4
4
|
from game.theme import get_theme
|
|
5
|
-
from game.character import get_character
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
class TestCowCrashes(BaseGameTest):
|
|
@@ -48,18 +47,17 @@ class TestCowCrashes(BaseGameTest):
|
|
|
48
47
|
TestCowCrashes.cow_level = Level(
|
|
49
48
|
name="Cow crashing",
|
|
50
49
|
anonymous=False,
|
|
51
|
-
|
|
50
|
+
blockly_enabled=True,
|
|
52
51
|
character=van,
|
|
53
52
|
cows='[{"minCows":"7","maxCows":"7","potentialCoordinates":[{"x":4,"y":4},{"x":2,"y":4},{"x":3,"y":7},{"x":4,"y":6},{"x":2,"y":6},{"x":3,"y":1},{"x":4,"y":2}],"type":"WHITE"}]',
|
|
54
53
|
default=False,
|
|
55
54
|
destinations="[[4,5]]",
|
|
56
|
-
direct_drive=True,
|
|
57
55
|
fuel_gauge=False,
|
|
58
56
|
max_fuel=50,
|
|
59
57
|
model_solution="[1]",
|
|
60
58
|
origin='{"coordinate":[2,5],"direction":"E"}',
|
|
61
59
|
path='[{"coordinate":[2,5],"connectedNodes":[1]},{"coordinate":[3,5],"connectedNodes":[0,4,2,5]},{"coordinate":[4,5],"connectedNodes":[1]},{"coordinate":[3,7],"connectedNodes":[4]},{"coordinate":[3,6],"connectedNodes":[8,3,6,1]},{"coordinate":[3,4],"connectedNodes":[10,1,11,16]},{"coordinate":[4,6],"connectedNodes":[4,7]},{"coordinate":[4,7],"connectedNodes":[6]},{"coordinate":[2,6],"connectedNodes":[9,4]},{"coordinate":[2,7],"connectedNodes":[8]},{"coordinate":[2,4],"connectedNodes":[13,5,12]},{"coordinate":[4,4],"connectedNodes":[5,14,15]},{"coordinate":[2,3],"connectedNodes":[10]},{"coordinate":[1,4],"connectedNodes":[10]},{"coordinate":[5,4],"connectedNodes":[11]},{"coordinate":[4,3],"connectedNodes":[11,19]},{"coordinate":[3,3],"connectedNodes":[5,17]},{"coordinate":[3,2],"connectedNodes":[18,16,19,20]},{"coordinate":[2,2],"connectedNodes":[17]},{"coordinate":[4,2],"connectedNodes":[17,15,23,22]},{"coordinate":[3,1],"connectedNodes":[21,17,22]},{"coordinate":[2,1],"connectedNodes":[20]},{"coordinate":[4,1],"connectedNodes":[20,19]},{"coordinate":[5,2],"connectedNodes":[19]}]',
|
|
62
|
-
|
|
60
|
+
python_enabled=False,
|
|
63
61
|
theme=grass,
|
|
64
62
|
threads=1,
|
|
65
63
|
traffic_lights="[]",
|