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.
Files changed (170) hide show
  1. example_project/rapid_router_test_settings.py +19 -7
  2. example_project/settings.py +21 -8
  3. example_project/urls.py +5 -6
  4. game/__init__.py +1 -1
  5. game/admin.py +7 -2
  6. game/character.py +8 -0
  7. game/decor.py +40 -0
  8. game/end_to_end_tests/base_game_test.py +34 -27
  9. game/end_to_end_tests/editor_page.py +15 -0
  10. game/end_to_end_tests/game_page.py +88 -20
  11. game/end_to_end_tests/selenium_test_case.py +1 -20
  12. game/end_to_end_tests/test_cow_crashes.py +3 -5
  13. game/end_to_end_tests/test_level_editor.py +273 -10
  14. game/end_to_end_tests/test_level_selection.py +25 -3
  15. game/end_to_end_tests/test_play_through.py +222 -127
  16. game/end_to_end_tests/test_python_levels.py +41 -7
  17. game/end_to_end_tests/test_saving_workspace.py +2 -1
  18. game/forms.py +7 -1
  19. game/level_management.py +26 -11
  20. game/messages.py +899 -337
  21. game/migrations/0001_squashed_0025_levels_ordering_pt1.py +19 -1
  22. game/migrations/0026_levels_pt2.py +13 -2
  23. game/migrations/0032_cannot_turn_left_level.py +13 -2
  24. game/migrations/0033_recursion_level.py +13 -2
  25. game/migrations/0034_joes_level.py +13 -2
  26. game/migrations/0035_disable_route_score_level_70.py +0 -2
  27. game/migrations/0036_level_score_73.py +0 -2
  28. game/migrations/0037_level_score_79.py +0 -2
  29. game/migrations/0038_level_score_40.py +0 -1
  30. game/migrations/0042_level_score_73.py +0 -2
  31. game/migrations/0048_add_cow_field_and_blocks.py +0 -2
  32. game/migrations/0049_level_score_34.py +0 -2
  33. game/migrations/0050_level_score_40.py +0 -2
  34. game/migrations/0051_level_score_49.py +0 -1
  35. game/migrations/0086_loop_levels.py +13 -2
  36. game/migrations/0092_disable_algo_score_in_custom_levels.py +28 -0
  37. game/migrations/0093_alter_level_character_name.py +18 -0
  38. game/migrations/0094_add_hint_lesson_subtitle_to_levels.py +28 -0
  39. game/migrations/0095_level_commands.py +18 -0
  40. game/migrations/0096_alter_level_commands.py +18 -0
  41. game/migrations/0097_add_python_den_levels.py +1515 -0
  42. game/migrations/0098_add_episode_link_fields.py +44 -0
  43. game/migrations/0099_python_episodes_links.py +103 -0
  44. game/migrations/0100_reorder_python_levels.py +179 -0
  45. game/migrations/0101_rename_episodes.py +45 -0
  46. game/migrations/0102_reoder_episodes_13_14.py +136 -0
  47. game/migrations/0103_level_1015_solution.py +26 -0
  48. game/migrations/0104_remove_level_direct_drive.py +17 -0
  49. game/migrations/0105_delete_invalid_attempts.py +18 -0
  50. game/migrations/0106_fields_to_snake_case.py +48 -0
  51. game/migrations/0107_rename_worksheet_link_episode_student_worksheet_link.py +18 -0
  52. game/migrations/0108_episode_indy_worksheet_link.py +18 -0
  53. game/migrations/0109_create_episodes_23_and_24.py +99 -0
  54. game/migrations/0110_remove_episode_indy_worksheet_link_and_more.py +100 -0
  55. game/migrations/0111_create_worksheets.py +149 -0
  56. game/migrations/0112_worksheet_locked_classes.py +21 -0
  57. game/migrations/0113_level_needs_approval.py +18 -0
  58. game/migrations/0114_default_and_non_student_levels_no_approval.py +31 -0
  59. game/migrations/0115_level_level__default_does_not_need_approval.py +22 -0
  60. game/migrations/0116_update_worksheet_video_links.py +68 -0
  61. game/migrations/0117_update_solutions_to_if_else.py +61 -0
  62. game/models.py +127 -17
  63. game/permissions.py +51 -19
  64. game/python_den_urls.py +26 -0
  65. game/random_road.py +9 -9
  66. game/serializers.py +12 -17
  67. game/static/django_reverse_js/js/reverse.js +171 -0
  68. game/static/game/css/LilitaOne-Regular.ttf +0 -0
  69. game/static/game/css/backgrounds.css +8 -12
  70. game/static/game/css/dataTables.custom.css +3 -2
  71. game/static/game/css/editor.css +47 -0
  72. game/static/game/css/game.css +37 -43
  73. game/static/game/css/game_screen.css +16 -0
  74. game/static/game/css/level_editor.css +5 -0
  75. game/static/game/css/level_selection.css +17 -2
  76. game/static/game/image/Python_Den_hero_student.png +0 -0
  77. game/static/game/image/Python_levels_page.svg +1954 -0
  78. game/static/game/image/characters/front_view/Electric_van.svg +448 -0
  79. game/static/game/image/characters/top_view/Electric_van.svg +448 -0
  80. game/static/game/image/decor/city/solar_panel.svg +1200 -0
  81. game/static/game/image/decor/farm/solar_panel.svg +86 -0
  82. game/static/game/image/decor/grass/solar_panel.svg +86 -0
  83. game/static/game/image/decor/snow/solar_panel.svg +173 -0
  84. game/static/game/image/electric_van.svg +448 -0
  85. game/static/game/image/icons/description.svg +1 -0
  86. game/static/game/image/icons/hint.svg +1 -0
  87. game/static/game/image/icons/python.svg +1 -1
  88. game/static/game/image/pigeon.svg +684 -0
  89. game/static/game/image/python_den_header.svg +19 -0
  90. game/static/game/js/animation.js +65 -24
  91. game/static/game/js/blockly/msg/js/bg.js +52 -1
  92. game/static/game/js/blockly/msg/js/ca.js +52 -1
  93. game/static/game/js/blockly/msg/js/en-gb.js +2 -0
  94. game/static/game/js/blockly/msg/js/en.js +2 -0
  95. game/static/game/js/blockly/msg/js/es.js +52 -1
  96. game/static/game/js/blockly/msg/js/fr.js +2 -0
  97. game/static/game/js/blockly/msg/js/hi.js +2 -0
  98. game/static/game/js/blockly/msg/js/it.js +52 -1
  99. game/static/game/js/blockly/msg/js/pl.js +52 -1
  100. game/static/game/js/blockly/msg/js/pt-br.js +52 -1
  101. game/static/game/js/blockly/msg/js/ru.js +52 -1
  102. game/static/game/js/blockly/msg/js/ur.js +52 -1
  103. game/static/game/js/blocklyCustomBlocks.js +93 -52
  104. game/static/game/js/button.js +12 -0
  105. game/static/game/js/cow.js +11 -7
  106. game/static/game/js/drawing.js +68 -29
  107. game/static/game/js/editor.js +23 -0
  108. game/static/game/js/game.js +74 -110
  109. game/static/game/js/level_editor.js +646 -274
  110. game/static/game/js/level_moderation.js +33 -2
  111. game/static/game/js/level_selection.js +1 -1
  112. game/static/game/js/loadLanguages.js +2 -2
  113. game/static/game/js/model.js +32 -2
  114. game/static/game/js/pythonControl.js +14 -1
  115. game/static/game/js/scoreboard.js +0 -37
  116. game/static/game/js/scoreboardSharedLevels.js +48 -0
  117. game/static/game/js/skulpt/skulpt-stdlib.js +1 -1
  118. game/static/game/js/sound.js +52 -5
  119. game/static/game/raphael_image/characters/top_view/Electric_van.svg +448 -0
  120. game/static/game/raphael_image/decor/city/solar_panel.svg +1200 -0
  121. game/static/game/raphael_image/decor/farm/solar_panel.svg +86 -0
  122. game/static/game/raphael_image/decor/grass/solar_panel.svg +86 -0
  123. game/static/game/raphael_image/decor/snow/solar_panel.svg +173 -0
  124. game/static/game/raphael_image/pigeon.svg +685 -0
  125. game/static/game/sass/game.scss +2 -2
  126. game/static/game/sound/clown_horn.mp3 +0 -0
  127. game/static/game/sound/clown_horn.ogg +0 -0
  128. game/static/game/sound/electric_van_starting.mp3 +0 -0
  129. game/static/game/sound/electric_van_starting.ogg +0 -0
  130. game/static/game/sound/pigeon.mp3 +0 -0
  131. game/static/game/sound/pigeon.ogg +0 -0
  132. game/static/game/sound/sleigh_bells.mp3 +0 -0
  133. game/static/game/sound/sleigh_bells.ogg +0 -0
  134. game/static/game/sound/sleigh_crash.mp3 +0 -0
  135. game/static/game/sound/sleigh_crash.ogg +0 -0
  136. game/templates/game/base.html +34 -14
  137. game/templates/game/basenonav.html +11 -5
  138. game/templates/game/game.html +142 -38
  139. game/templates/game/level_editor.html +340 -236
  140. game/templates/game/level_moderation.html +19 -6
  141. game/templates/game/level_selection.html +18 -110
  142. game/templates/game/python_den_level_selection.html +291 -0
  143. game/templates/game/python_den_worksheet.html +101 -0
  144. game/templates/game/scoreboard.html +83 -64
  145. game/tests/test_level_editor.py +94 -26
  146. game/tests/test_level_selection.py +149 -46
  147. game/tests/test_python_den_worksheet.py +85 -0
  148. game/tests/test_scoreboard.py +34 -7
  149. game/tests/utils/level.py +32 -26
  150. game/theme.py +5 -5
  151. game/urls.py +199 -61
  152. game/views/language_code_conversions.py +86 -86
  153. game/views/level.py +155 -63
  154. game/views/level_editor.py +88 -55
  155. game/views/level_moderation.py +23 -0
  156. game/views/level_selection.py +116 -47
  157. game/views/level_solutions.py +491 -106
  158. game/views/scoreboard.py +76 -51
  159. game/views/worksheet.py +25 -0
  160. rapid_router-7.6.8.dist-info/METADATA +174 -0
  161. {rapid_router-5.18.0.dist-info → rapid_router-7.6.8.dist-info}/RECORD +164 -104
  162. {rapid_router-5.18.0.dist-info → rapid_router-7.6.8.dist-info}/WHEEL +1 -1
  163. example_project/manage.py +0 -10
  164. game/static/game/image/actions/go.svg +0 -18
  165. game/static/game/js/js-reverse.js +0 -14
  166. game/static/game/js/pqselect.min.js +0 -9
  167. game/static/game/js/widget-scroller.js +0 -906
  168. rapid_router-5.18.0.dist-info/METADATA +0 -17
  169. {rapid_router-5.18.0.dist-info → rapid_router-7.6.8.dist-info/licenses}/LICENSE.md +0 -0
  170. {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
- "django_js_reverse",
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
- STATICFILES_STORAGE = "pipeline.storage.PipelineStorage"
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"]
@@ -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
- "django_js_reverse",
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
- STATICFILES_STORAGE = "pipeline.storage.PipelineStorage"
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
- url(r"^", include(portal_urls)),
11
+ re_path(r"^", include(portal_urls)),
13
12
  path("administration/", admin.site.urls),
14
- url(r"^rapidrouter/", include(game_urls)),
15
- url(r"^aimmo/", include(aimmo_urls)),
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__ = "5.18.0"
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", "decorName"]
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
- page.next_level()
71
- page.assert_level_number(level_number + 1)
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
- path = reverse("play_default_level", kwargs={"levelName": str(level_name)})
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
- path = reverse("start_episode", kwargs={"episodeId": str(episodeId)})
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 run_python_commands_test(self, level):
132
- return self.go_to_level(level).check_python_commands()
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("ocargo.animation.FAST_ANIMATION_DURATION = 1;")
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((By.ID, button_id), wait_seconds=15)
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((By.ID, button_id), wait_seconds=15)
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(workspace_name)
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("return ocargo.solutionLoaded;")
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
- path = reverse("play_default_level", kwargs={"levelName": str(level_number)})
94
- assert_that(self.browser.current_url.replace("#myModal", ""), ends_with(path))
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
- def assert_episode_number(self, episode_number):
97
- path = reverse("start_episode", kwargs={"episodeId": str(episode_number)})
98
- assert_that(self.browser.current_url.replace("#myModal", ""), ends_with(path))
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(By.ID,
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((By.ID, wait_for_element_id), 45)
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(modal_content, contains_string("Try creating a simpler program."))
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, "blockly_tab"))
267
+ presence_of_all_elements_located((By.ID, tabId))
211
268
  )
212
269
  return self
213
270
 
214
- def next_level(self):
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, "blockly_tab"))
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(By.ID, "myModal-mainText").text
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.core.servers.basehttp import WSGIServer
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
- blocklyEnabled=True,
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
- pythonEnabled=False,
60
+ python_enabled=False,
63
61
  theme=grass,
64
62
  threads=1,
65
63
  traffic_lights="[]",