Scenario:
diff --git a/app/javascript/components/bootcamp/SolveExercisePage/TestResultsView/TestResultsButtons.tsx b/app/javascript/components/bootcamp/SolveExercisePage/TestResultsView/TestResultsButtons.tsx
index b73c6843dc..ab5ac48972 100644
--- a/app/javascript/components/bootcamp/SolveExercisePage/TestResultsView/TestResultsButtons.tsx
+++ b/app/javascript/components/bootcamp/SolveExercisePage/TestResultsView/TestResultsButtons.tsx
@@ -20,6 +20,7 @@ export function TestResultsButtons() {
{testSuiteResult.tests.map((test, idx) => {
return (
handleSetInspectedTestResult({
diff --git a/app/models/bootcamp/exercise.rb b/app/models/bootcamp/exercise.rb
index af316f6c4f..f8a5dd8681 100644
--- a/app/models/bootcamp/exercise.rb
+++ b/app/models/bootcamp/exercise.rb
@@ -50,6 +50,12 @@ def readonly_ranges
private
def file_contents(filename)
- File.read(Rails.root / "bootcamp_content/projects/#{project.slug}/exercises/#{slug}/#{filename}")
+ File.read(root_dir / "projects/#{project.slug}/exercises/#{slug}/#{filename}")
+ end
+
+ def root_dir
+ return Rails.root / "test/repos/bootcamp_content" if Rails.env.test?
+
+ Rails.root / "bootcamp_content"
end
end
diff --git a/app/views/layouts/bootcamp-ui.haml b/app/views/layouts/bootcamp-ui.haml
index 1e65c8f5a7..fe0fdc3093 100644
--- a/app/views/layouts/bootcamp-ui.haml
+++ b/app/views/layouts/bootcamp-ui.haml
@@ -70,11 +70,6 @@
%meta{ name: "turbo-prefetch", content: "false" }
%meta{ name: "user-id", content: current_user&.id }
- %link{ href: "/manifest.json", rel: "manifest" }
- %link{ href: "/icon.png", rel: "icon", type: "image/png" }
- %link{ href: "/icon.svg", rel: "icon", type: "image/svg+xml" }
- %link{ href: "/icon.png", rel: "apple-touch-icon" }
- -# = javascript_include_tag "application", "data-turbo-track": "reload", type: "module", crossorigin: :anonymous
= javascript_include_tag "bootcamp-ui-js", "data-turbo-track": "reload", type: "module", crossorigin: :anonymous
%body{ class: body_class }
diff --git a/test/factories/bootcamp/exercises.rb b/test/factories/bootcamp/exercises.rb
index 5dfb3da732..21ca3afe08 100644
--- a/test/factories/bootcamp/exercises.rb
+++ b/test/factories/bootcamp/exercises.rb
@@ -7,4 +7,24 @@
description { "Exercise Description" }
level_idx { (Bootcamp::Level.first || create(:bootcamp_level)).idx }
end
+
+ trait :penguin do
+ slug { "penguin" }
+ project { Bootcamp::Project.find_by(slug: "drawing") || create(:bootcamp_project, slug: "drawing") }
+ end
+
+ trait :even_or_odd do
+ slug { "even-or-odd" }
+ project { Bootcamp::Project.find_by(slug: "numbers") || create(:bootcamp_project, slug: "numbers") }
+ end
+
+ trait :manual_solve do
+ slug { "manual-solve" }
+ project { Bootcamp::Project.find_by(slug: "maze") || create(:bootcamp_project, slug: "maze") }
+ end
+
+ trait :automated_solve do
+ slug { "automated-solve" }
+ project { Bootcamp::Project.find_by(slug: "maze") || create(:bootcamp_project, slug: "maze") }
+ end
end
diff --git a/test/repos/bootcamp_content/projects/drawing/exercises/penguin/config.json b/test/repos/bootcamp_content/projects/drawing/exercises/penguin/config.json
new file mode 100644
index 0000000000..8dccbcffcb
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/drawing/exercises/penguin/config.json
@@ -0,0 +1,110 @@
+{
+ "idx": 4,
+ "title": "Penguin",
+ "description": "Make the penguin symmetrical",
+ "project_type": "draw",
+ "level": 1,
+ "concepts": [],
+ "tests_type": "state",
+ "readonly_ranges": [],
+ "tasks": [
+ {
+ "name": "Draw the scene",
+ "tests": [
+ {
+ "slug": "draw-scence",
+ "name": "Make the penguin symmetrical.",
+ "description_html": "Fix all the TODO comments to make the penguin symmetrical.",
+ "function": "main",
+ "checks": [
+ {
+ "name": "getRectangleAt(0, 0, 100, 100)",
+ "matcher": "toBeDefined",
+ "error_html": "The sky has gone wrong."
+ },
+ {
+ "name": "getRectangleAt(0, 70, 100, 30)",
+ "matcher": "toBeDefined",
+ "error_html": "The ground has gone wrong."
+ },
+ {
+ "name": "getEllipseAt(28, 55, 10, 25)",
+ "matcher": "toBeDefined",
+ "error_html": "The left wing doesn't seem right."
+ },
+ {
+ "name": "getEllipseAt(72, 55, 10, 25)",
+ "matcher": "toBeDefined",
+ "error_html": "The right wing doesn't seem right."
+ },
+ {
+ "name": "getEllipseAt(50, 53, 25, 40)",
+ "matcher": "toBeDefined",
+ "error_html": "The outer body has gone wrong."
+ },
+ {
+ "name": "getEllipseAt(50, 50, 21, 39)",
+ "matcher": "toBeDefined",
+ "error_html": "The inner body has gone wrong."
+ },
+ {
+ "name": "getCircleAt(50, 31, 23)",
+ "matcher": "toBeDefined",
+ "error_html": "The head has gone wrong."
+ },
+ {
+ "name": "getEllipseAt(41, 32, 11, 14)",
+ "matcher": "toBeDefined",
+ "error_html": "The left side of the face doesn't look right."
+ },
+ {
+ "name": "getEllipseAt(59, 32, 11, 14)",
+ "matcher": "toBeDefined",
+ "error_html": "The right side of the face doesn't look right."
+ },
+ {
+ "name": "getEllipseAt(50, 40, 16, 11)",
+ "matcher": "toBeDefined",
+ "error_html": "The lower part of the face doesn't look right."
+ },
+ {
+ "name": "getCircleAt(42, 33, 3)",
+ "matcher": "toBeDefined",
+ "error_html": "The left eye seems off."
+ },
+ {
+ "name": "getCircleAt(43, 34, 1)",
+ "matcher": "toBeDefined",
+ "error_html": "The left iris seems off."
+ },
+ {
+ "name": "getCircleAt(58, 33, 3)",
+ "matcher": "toBeDefined",
+ "error_html": "The right eye seems off."
+ },
+ {
+ "name": "getCircleAt(57, 34, 1)",
+ "matcher": "toBeDefined",
+ "error_html": "The right iris seems off."
+ },
+ {
+ "name": "getEllipseAt(40, 93, 7, 4)",
+ "matcher": "toBeDefined",
+ "error_html": "The left foot's gone astray."
+ },
+ {
+ "name": "getEllipseAt(60, 93, 7, 4)",
+ "matcher": "toBeDefined",
+ "error_html": "The right foot's not right."
+ },
+ {
+ "name": "getTriangleAt(46, 38, 54, 38, 50, 47)",
+ "matcher": "toBeDefined",
+ "error_html": "The nose isn't right."
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/test/repos/bootcamp_content/projects/drawing/exercises/penguin/example.jiki b/test/repos/bootcamp_content/projects/drawing/exercises/penguin/example.jiki
new file mode 100644
index 0000000000..9d57b7b4bd
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/drawing/exercises/penguin/example.jiki
@@ -0,0 +1,43 @@
+// Light blue background
+fill_color_hex("#ADD8E6")
+rectangle(0, 0, 100, 100)
+
+// Ground
+fill_color_hex("#ffffff") // Ice ground
+rectangle(0, 70, 100, 30) // Ice ground
+
+// Penguin wings
+fill_color_hex("#000000") // Black
+ellipse(28, 55, 10, 25) // Left wing
+ellipse(72, 55, 10, 25) // Right wing
+
+// Penguin body
+fill_color_hex("#000000") // Black for the body
+ellipse(50, 53, 25, 40) // Outer body (oval shape)
+fill_color_hex("#ffffff") // White for the belly
+ellipse(50, 50, 21, 39) // Inner belly (oval shape)
+
+// Penguin head
+fill_color_hex("#000000") // Black
+circle(50, 31, 23) // Head (circle)
+fill_color_hex("#ffffff") // White for the face
+ellipse(41, 32, 11, 14) // Left part of the face
+ellipse(59, 32, 11, 14) // Right part of the face
+ellipse(50, 40, 16, 11) // Lower part of the face
+
+// Penguin eyes
+fill_color_hex("#000000") // Black
+circle(42, 33, 3) // Left eye
+fill_color_hex("#ffffff") // White
+circle(43, 34, 1) // Left iris
+
+fill_color_hex("#000000") // Black
+circle(58, 33, 3) // Right eye
+fill_color_hex("#ffffff") // White
+circle(57, 34, 1) // Right iris
+
+// Feet
+fill_color_hex("#FFA500")
+ellipse(40, 93, 7, 4)
+ellipse(60, 93, 7, 4)
+triangle(46, 38, 54, 38, 50, 47)
\ No newline at end of file
diff --git a/test/repos/bootcamp_content/projects/drawing/exercises/penguin/introduction.md b/test/repos/bootcamp_content/projects/drawing/exercises/penguin/introduction.md
new file mode 100644
index 0000000000..df35aa51d9
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/drawing/exercises/penguin/introduction.md
@@ -0,0 +1,3 @@
+# Introduction
+
+This is fun!
diff --git a/test/repos/bootcamp_content/projects/drawing/exercises/penguin/stub.jiki b/test/repos/bootcamp_content/projects/drawing/exercises/penguin/stub.jiki
new file mode 100644
index 0000000000..5ab2f8a432
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/drawing/exercises/penguin/stub.jiki
@@ -0,0 +1 @@
+Hello
\ No newline at end of file
diff --git a/test/repos/bootcamp_content/projects/drawing/exercises/penguin/task-1.md b/test/repos/bootcamp_content/projects/drawing/exercises/penguin/task-1.md
new file mode 100644
index 0000000000..c9fc02faac
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/drawing/exercises/penguin/task-1.md
@@ -0,0 +1,5 @@
+Follow the `TODO` comments to make the penguin symmetrical.
+
+As you get each bit right, tidy up the comments by removing the `TODO` and replacing it with a nicer comment.
+
+Use the "Check Scenarios" button regularly, and have fun!
diff --git a/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/config.json b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/config.json
new file mode 100644
index 0000000000..f95d9d282d
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/config.json
@@ -0,0 +1,230 @@
+{
+ "title": "Programatically solve a maze",
+ "description": "Empower your blob to solve any maze with code!",
+ "level": 3,
+ "idx": 1,
+ "project_type": "maze",
+ "tests_type": "state",
+ "exercise_functions": [
+ "move",
+ "turn_left",
+ "turn_right",
+ "can_move",
+ "can_turn_left",
+ "can_turn_right"
+ ],
+ "interpreter_options": {
+ "max_repeat_until_game_over_iterations": 50
+ },
+ "tasks": [
+ {
+ "name": "A straight path",
+ "tests": [
+ {
+ "slug": "maze-1",
+ "name": "Guide person to the end of the maze",
+ "setup_functions": [
+ [
+ "setupGrid",
+ [
+ [
+ [1, 1, 1, 1, 2, 1, 1],
+ [1, 1, 1, 1, 0, 1, 1],
+ [1, 1, 1, 1, 0, 1, 1],
+ [1, 1, 1, 1, 0, 1, 1],
+ [1, 1, 1, 1, 0, 1, 1],
+ [1, 1, 1, 1, 0, 1, 1],
+ [1, 1, 1, 1, 3, 1, 1]
+ ]
+ ]
+ ],
+ ["setupDirection", ["down"]],
+ ["setupPosition", [4, 0]]
+ ],
+ "checks": [
+ {
+ "name": "position",
+ "value": [4, 6],
+ "error_html": "You didn't reach the end of the maze."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Turn left if you can",
+ "tests": [
+ {
+ "slug": "left-turn",
+ "name": "A single left turn",
+ "setup_functions": [
+ [
+ "setupGrid",
+ [
+ [
+ [2, 1, 1, 1, 1, 1, 1, 1, 1],
+ [0, 1, 1, 1, 1, 1, 1, 1, 1],
+ [0, 1, 1, 1, 1, 1, 1, 1, 1],
+ [0, 1, 1, 1, 1, 1, 1, 1, 1],
+ [0, 1, 1, 1, 1, 1, 1, 1, 1],
+ [0, 0, 0, 0, 0, 0, 0, 0, 3],
+ [1, 1, 1, 1, 1, 1, 1, 1, 1],
+ [1, 1, 1, 1, 1, 1, 1, 1, 1],
+ [1, 1, 1, 1, 1, 1, 1, 1, 1]
+ ]
+ ]
+ ],
+ ["setupDirection", ["down"]],
+ ["setupPosition", [0, 1]]
+ ],
+ "checks": [
+ {
+ "name": "position",
+ "value": [8, 5],
+ "error_html": "You didn't reach the end of the maze."
+ },
+ {
+ "name": "direction",
+ "value": "right",
+ "error_html": "You seem to have done an extra unnecessary turn at the end."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Turn right if you can't move straight or left",
+ "tests": [
+ {
+ "slug": "right-turn",
+ "name": "A single right turn",
+ "setup_functions": [
+ [
+ "setupGrid",
+ [
+ [
+ [1, 1, 1, 1, 1, 1, 1, 1, 2],
+ [1, 1, 1, 1, 1, 1, 1, 1, 0],
+ [1, 1, 1, 1, 1, 1, 1, 1, 0],
+ [1, 1, 1, 1, 1, 1, 1, 1, 0],
+ [1, 1, 1, 1, 1, 1, 1, 1, 0],
+ [3, 0, 0, 0, 0, 0, 0, 0, 0],
+ [1, 1, 1, 1, 1, 1, 1, 1, 1],
+ [1, 1, 1, 1, 1, 1, 1, 1, 1],
+ [1, 1, 1, 1, 1, 1, 1, 1, 1]
+ ]
+ ]
+ ],
+ ["setupDirection", ["down"]],
+ ["setupPosition", [8, 0]]
+ ],
+ "checks": [
+ {
+ "name": "position",
+ "value": [0, 5],
+ "error_html": "You didn't reach the end of the maze."
+ }
+ ]
+ },
+ {
+ "slug": "forks",
+ "name": "Choose left if you can, otherwise choose right",
+ "setup_functions": [
+ [
+ "setupGrid",
+ [
+ [
+ [2, 1, 1, 1, 1, 1, 1, 1, 1],
+ [0, 1, 1, 1, 1, 1, 1, 1, 1],
+ [0, 1, 1, 1, 0, 0, 0, 0, 3],
+ [0, 1, 1, 1, 0, 1, 1, 1, 1],
+ [0, 1, 1, 1, 0, 1, 1, 1, 1],
+ [0, 0, 0, 0, 0, 1, 1, 1, 1],
+ [1, 4, 1, 1, 4, 1, 1, 1, 1],
+ [1, 4, 4, 4, 4, 1, 1, 1, 1],
+ [1, 1, 1, 1, 4, 1, 1, 1, 1]
+ ]
+ ]
+ ],
+ ["setupDirection", ["down"]],
+ ["setupPosition", [0, 0]]
+ ],
+ "checks": [
+ {
+ "name": "position",
+ "value": [8, 2],
+ "error_html": "You didn't reach the end of the maze."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "Turn around if needed",
+ "tests": [
+ {
+ "slug": "turn-around",
+ "name": "Turn around if you can't move straight, left, or right",
+ "setup_functions": [
+ [
+ "setupGrid",
+ [
+ [
+ [1, 1, 1, 2, 1, 1, 1, 1, 1],
+ [1, 1, 1, 0, 1, 1, 1, 1, 1],
+ [1, 1, 1, 0, 1, 1, 1, 1, 1],
+ [1, 4, 4, 0, 1, 1, 0, 1, 1],
+ [1, 4, 1, 0, 1, 1, 0, 1, 1],
+ [1, 4, 4, 0, 0, 0, 0, 0, 1],
+ [1, 1, 1, 0, 1, 1, 1, 1, 1],
+ [3, 0, 0, 0, 1, 1, 1, 1, 1],
+ [1, 1, 1, 0, 1, 1, 1, 1, 1]
+ ]
+ ]
+ ],
+ ["setupDirection", ["down"]],
+ ["setupPosition", [3, 0]]
+ ],
+ "checks": [
+ {
+ "name": "position",
+ "value": [0, 7],
+ "error_html": "You didn't reach the end of the maze."
+ }
+ ]
+ },
+ {
+ "slug": "forks-2",
+ "name": "Choose right if you can, otherwise choose left",
+ "setup_functions": [
+ [
+ "setupGrid",
+ [
+ [
+ [2, 1, 1, 1, 1, 1, 1, 1, 1],
+ [0, 1, 1, 0, 0, 1, 1, 1, 1],
+ [0, 1, 1, 1, 0, 0, 0, 0, 1],
+ [0, 1, 1, 0, 0, 1, 0, 1, 1],
+ [0, 1, 1, 1, 0, 1, 0, 1, 1],
+ [0, 0, 0, 0, 0, 1, 0, 0, 1],
+ [1, 4, 1, 1, 0, 1, 1, 0, 1],
+ [1, 4, 4, 4, 0, 1, 0, 0, 1],
+ [1, 1, 1, 1, 3, 1, 1, 1, 1]
+ ]
+ ]
+ ],
+ ["setupDirection", ["down"]],
+ ["setupPosition", [0, 0]]
+ ],
+ "checks": [
+ {
+ "name": "position",
+ "value": [4, 8],
+ "error_html": "You didn't reach the end of the maze."
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/example.jiki b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/example.jiki
new file mode 100644
index 0000000000..c22af95a40
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/example.jiki
@@ -0,0 +1,14 @@
+repeat_until_game_over do
+ if can_turn_left() is true do
+ turn_left()
+ move()
+ else if can_move() is true do
+ move()
+ else if can_turn_right() is true do
+ turn_right()
+ move()
+ else do
+ turn_left()
+ turn_left()
+ end
+end
\ No newline at end of file
diff --git a/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/introduction.md b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/introduction.md
new file mode 100644
index 0000000000..5d96232c59
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/introduction.md
@@ -0,0 +1,26 @@
+# Solve the Maze
+
+The first exercise you solved was manually moving your character around the maze. Already in Level 3, you're ready to solve any maze programmatically using code!
+
+To make that possible, we're giving you three new functions:
+
+- `can_turn_left()`: returns `true` if the space to the character's left is not a wall.
+- `can_turn_right()`: returns `true` if the space to the character's right is not a wall.
+- `can_move()`: returns `true` if the space ahead of the character is not a wall.
+
+With those three functions and the `move()`, `turn_left()` and `turn_right()` you had in Level 1, you can solve any maze.
+
+Spend a little time trying to work out how (maybe 15-30 minutes). Treat it as a fun logic puzzle. Get some paper and draw things out. Then when you want to check your method (or if you give up), read the instructions below
+
+
+
+## Instructions
+
+You can solve all the mazes using the following method (this is what we call an "algorithm" - a method of solving a problem):
+
+- If you can turn left, turn left and move forward
+- Otherwise, if you can move forward, move forward.
+- Otherwise if you can turn right, turn right and move forward.
+- Otherwise turn around
+
+This exercise is broken into different tasks. Follow the instructions below and as you completed each task, one or more the scenarios will complete, and more instructions will appear below!
diff --git a/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/stub.jiki b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/stub.jiki
new file mode 100644
index 0000000000..6fa95fcc83
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/stub.jiki
@@ -0,0 +1,3 @@
+repeat_until_game_over do
+ // TODO: Implement the algorithm
+end
\ No newline at end of file
diff --git a/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/task-1.md b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/task-1.md
new file mode 100644
index 0000000000..601d75be06
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/task-1.md
@@ -0,0 +1,3 @@
+# Task 1
+
+Move forward to the end of the maze.
diff --git a/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/task-2.md b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/task-2.md
new file mode 100644
index 0000000000..11206813bc
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/task-2.md
@@ -0,0 +1,3 @@
+# Task 2
+
+Now implement the second step. If there's a path to the left, take it!
diff --git a/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/task-3.md b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/task-3.md
new file mode 100644
index 0000000000..3d57c75ba4
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/task-3.md
@@ -0,0 +1,3 @@
+# Task 3
+
+Great! Now we deal with right turns. If there's not a path to the left or straight ahead, take the path to the right.
diff --git a/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/task-4.md b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/task-4.md
new file mode 100644
index 0000000000..1363bab382
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/maze/exercises/automated-solve/task-4.md
@@ -0,0 +1,3 @@
+# Task 4
+
+Great work. Now you need to handle what happens when you get to a dead-end. In that case you need to turn around!
diff --git a/test/repos/bootcamp_content/projects/maze/exercises/manual-solve/config.json b/test/repos/bootcamp_content/projects/maze/exercises/manual-solve/config.json
new file mode 100644
index 0000000000..6efa7bc43e
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/maze/exercises/manual-solve/config.json
@@ -0,0 +1,52 @@
+{
+ "title": "Manually solve a maze",
+ "description": "Solve a maze using some basic functions",
+ "project_type": "maze",
+ "level": 1,
+ "idx": 1,
+ "exercise_functions": ["move", "turn_left", "turn_right"],
+ "tests_type": "state",
+ "concepts": ["functions-using"],
+ "tasks": [
+ {
+ "name": "Guide person to the end of the maze",
+ "tests": [
+ {
+ "slug": "maze-1",
+ "name": "Guide person to the end of the maze",
+ "description_html": "Your job is to reach the goal.",
+ "setup_functions": [
+ [
+ "setupGrid",
+ [
+ [
+ [2, 1, 0, 0, 0, 1, 0],
+ [0, 1, 0, 1, 0, 1, 1],
+ [0, 0, 0, 1, 0, 0, 0],
+ [0, 1, 1, 1, 0, 1, 1],
+ [3, 0, 1, 0, 0, 1, 0],
+ [1, 1, 1, 1, 0, 1, 1],
+ [0, 0, 0, 0, 0, 0, 0]
+ ]
+ ]
+ ],
+ ["setupDirection", ["down"]],
+ ["setupPosition", [0, 0]]
+ ],
+ "checks": [
+ {
+ "name": "position",
+ "value": [0, 4],
+ "error_html": "You didn't reach the end of the maze."
+ },
+ {
+ "name": "getGameResult()",
+ "value": "win",
+ "error_html": "You didn't reach the end of the maze."
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/test/repos/bootcamp_content/projects/maze/exercises/manual-solve/example.jiki b/test/repos/bootcamp_content/projects/maze/exercises/manual-solve/example.jiki
new file mode 100644
index 0000000000..e993c67878
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/maze/exercises/manual-solve/example.jiki
@@ -0,0 +1,21 @@
+move()
+move()
+turn_left()
+move()
+move()
+turn_left()
+move()
+move()
+turn_right()
+move()
+move()
+turn_right()
+move()
+move()
+move()
+move()
+move()
+move()
+turn_left()
+move()
+move()
\ No newline at end of file
diff --git a/test/repos/bootcamp_content/projects/maze/exercises/manual-solve/introduction.md b/test/repos/bootcamp_content/projects/maze/exercises/manual-solve/introduction.md
new file mode 100644
index 0000000000..bf45496945
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/maze/exercises/manual-solve/introduction.md
@@ -0,0 +1,11 @@
+# Solve the Maze
+
+Your task is to solve the following maze.
+
+You have three functions you can use:
+
+- `move()` which moves the character one step forward
+- `turn_left()` turns the character left (relative to the direction they're currently facing)
+- `turn_right()` turns the character right (relative to the direction they're currently facing)
+
+Remember to use one function per line.
diff --git a/test/repos/bootcamp_content/projects/maze/exercises/manual-solve/stub.jiki b/test/repos/bootcamp_content/projects/maze/exercises/manual-solve/stub.jiki
new file mode 100644
index 0000000000..27cc684a7b
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/maze/exercises/manual-solve/stub.jiki
@@ -0,0 +1,6 @@
+// You can use move(), turn_left() and turn_right()
+// Use the functions in the order you want your character
+// to use them to solve them maze. We'll start you off by
+// moving the character one step forward.
+
+move()
\ No newline at end of file
diff --git a/test/repos/bootcamp_content/projects/maze/exercises/manual-solve/task-1.md b/test/repos/bootcamp_content/projects/maze/exercises/manual-solve/task-1.md
new file mode 100644
index 0000000000..b470c01201
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/maze/exercises/manual-solve/task-1.md
@@ -0,0 +1,8 @@
+# Task 1
+
+We've started by adding a single `move()` for you, which will move the character one step forward.
+
+Use the "Run code" button to see how close you're getting.
+
+It's good practice to get into the habit of running your code reguarly.
+In this exercise, we'd recommend running it after adding each new instruction, although you might like to try and challenge yourself to solve it all in one go instead!
diff --git a/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/config.json b/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/config.json
new file mode 100644
index 0000000000..b9b991f5be
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/config.json
@@ -0,0 +1,70 @@
+{
+ "title": "Even or Odd",
+ "description": "Determine if a number is even or odd",
+ "concepts": ["strings-using", "conditionals"],
+ "level": 4,
+ "idx": 3,
+ "tests_type": "io",
+ "tasks": [
+ {
+ "name": "Correctly identify even numbers",
+ "tests": [
+ {
+ "slug": "number-14",
+ "type": "io",
+ "name": "Number 14",
+ "function": "even_or_odd",
+ "params": [14],
+ "expected": "Even",
+ "image_slug": "number-puzzles/even-or-odd/14.png"
+ },
+ {
+ "slug": "number-28",
+ "type": "io",
+ "name": "Number 28",
+ "function": "even_or_odd",
+ "params": [28],
+ "expected": "Even",
+ "image_slug": "number-puzzles/even-or-odd/28.png"
+ }
+ ]
+ },
+ {
+ "name": "Correctly identify odd numbers",
+ "tests": [
+ {
+ "slug": "number--1",
+ "type": "io",
+ "name": "Number -1",
+ "function": "even_or_odd",
+ "params": [-1],
+ "expected": "Odd",
+ "image_slug": "number-puzzles/even-or-odd/-1.png"
+ },
+ {
+ "slug": "number-17",
+ "type": "io",
+ "name": "Number 17",
+ "function": "even_or_odd",
+ "params": [17],
+ "expected": "Odd",
+ "image_slug": "number-puzzles/even-or-odd/17.png"
+ }
+ ]
+ },
+ {
+ "name": "Correctly identify zero",
+ "tests": [
+ {
+ "slug": "number-0",
+ "type": "io",
+ "name": "Number 0",
+ "function": "even_or_odd",
+ "params": [0],
+ "expected": "Even",
+ "image_slug": "number-puzzles/even-or-odd/0.png"
+ }
+ ]
+ }
+ ]
+}
diff --git a/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/example.jiki b/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/example.jiki
new file mode 100644
index 0000000000..4ba9754e83
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/example.jiki
@@ -0,0 +1,7 @@
+function even_or_odd with number do
+ if number % 2 equals 0 do
+ return "Even"
+ else do
+ return "Odd"
+ end
+end
\ No newline at end of file
diff --git a/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/introduction.md b/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/introduction.md
new file mode 100644
index 0000000000..ef2ef35123
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/introduction.md
@@ -0,0 +1,32 @@
+# Even or odd
+
+This exercise is very similar to "Positive, Negative or Zero", but this time you need to write the function from scratch. If you're stuck you can always look back at that exercise for a hint.
+
+## Instructions
+
+Your job is write a function called `even_or_odd`.
+
+It should have one input that expects a _number_. It should return a _string_ specifying whether the number is `"Even"` (0, 2, 4, 6, 8, etc), or `"Odd"` (1, 3, 5, 7, etc).
+
+To approach this problem, think about what it is that acutally makes a number odd or even.
+
+Remember, that Jiki understands plus (`+`), minus (`-`), multiply (`*`), divide (`/`) and remainder (`%`). Check the [Numbers Concept](https://exercism.org/bootcamp/concepts/numbers) for a recap on how they work.
+
+## Bonus Task
+
+Can you solve this exercise with only 6 lines of code?
+
+## Hints
+
+
+Hint 1: No idea where to start?
+
+Even numbers are numbers that do not have a remiander when divided by 2.
+
+You probably remember from school that a remainder is what’s left over when you divide a number but can’t divide it evenly. In other words, it’s the part of the number that doesn’t fit into equal groups.
+
+For example, if you divide 7 by 3, you can fit two groups of 3 into 7 (since 3 + 3 = 6), but there’s 1 left over. That leftover 1 is the remainder. And that remainder makes it an odd number.
+
+So to solve this exercise, you might like to use the **[remainder operator](https://exercism.org/bootcamp/concepts/numbers)**.
+
+
diff --git a/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/stub.jiki b/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/stub.jiki
new file mode 100644
index 0000000000..e2272724d7
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/stub.jiki
@@ -0,0 +1,3 @@
+// Write a function called even_or_odd that...
+// Receives a number as its input
+// Should return "Even" or "Odd"
\ No newline at end of file
diff --git a/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/task-1.md b/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/task-1.md
new file mode 100644
index 0000000000..ebd3b8b008
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/task-1.md
@@ -0,0 +1,3 @@
+# Task 1
+
+Start with trying to get the `"Even"` check in place.
diff --git a/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/task-2.md b/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/task-2.md
new file mode 100644
index 0000000000..a5378b687d
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/task-2.md
@@ -0,0 +1,3 @@
+# Task 2
+
+Great! Now get the `"Odd"` check in place.
diff --git a/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/task-3.md b/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/task-3.md
new file mode 100644
index 0000000000..da38a007cc
--- /dev/null
+++ b/test/repos/bootcamp_content/projects/numbers/exercises/even-or-odd/task-3.md
@@ -0,0 +1,3 @@
+# Task 3
+
+Great! Finally, let's check that `0` is returned as `Even`.
diff --git a/test/system/bootcamp/editor_test.rb b/test/system/bootcamp/editor_test.rb
new file mode 100644
index 0000000000..ebedbff5fc
--- /dev/null
+++ b/test/system/bootcamp/editor_test.rb
@@ -0,0 +1,567 @@
+require "application_system_test_case"
+
+# best for initially setting content when the page loads
+def change_codemirror_content(content)
+ escaped_content = content.gsub("\n", "\\n").gsub('"', '\"')
+
+ page.execute_script(%{
+ const observer = new MutationObserver((mutations, obs) => {
+ const lines = document.querySelectorAll('.cm-line');
+ if (lines.length > 0) {
+ lines.forEach(line => line.innerText = '');
+ lines[lines.length - 1].innerText = "#{escaped_content}";
+ obs.disconnect();
+ }
+ });
+
+ observer.observe(document.body, { childList: true, subtree: true });
+ })
+end
+
+# best for updating content after the page has loaded
+def update_codemirror_content(content)
+ escaped_content = content.gsub("\n", "\\n").gsub('"', '\"')
+ page.execute_script(%{
+ const lines = document.querySelectorAll('.cm-line');
+ if (lines.length > 0) {
+ lines.forEach(line => line.innerText = '');
+ lines[lines.length - 1].innerText = "#{escaped_content}";
+ }
+ })
+end
+
+def select_scenario(number)
+ find("[data-ci='test-selector-button']:nth-of-type(#{number})").click
+end
+
+def check_scenarios = find("[data-ci='check-scenarios-button']").click
+
+def toggle_information_tooltip = find("[data-ci=information-widget-toggle]").click
+
+def scrub_to(value)
+ page.execute_script(%{
+ let scrubber = document.querySelector("[data-ci='scrubber-range-input']");
+ if (scrubber) {
+ let previousValue = scrubber.value;
+
+ let pointerDownEvent = new PointerEvent('pointerdown', { bubbles: true });
+ scrubber.dispatchEvent(pointerDownEvent);
+
+ let inputEvent = new Event('input', { bubbles: true });
+ Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set.call(scrubber, #{value});
+ scrubber.dispatchEvent(inputEvent);
+
+ let pointerMoveEvent = new PointerEvent('pointermove', { bubbles: true });
+ scrubber.dispatchEvent(pointerMoveEvent);
+
+ let pointerUpEvent = new PointerEvent('pointerup', { bubbles: true });
+ scrubber.dispatchEvent(pointerUpEvent);
+
+ let changeEvent = new Event('change', { bubbles: true });
+ scrubber.dispatchEvent(changeEvent);
+
+ console.log(`Scrubber moved from ${previousValue} to ${scrubber.value}`);
+ }
+ })
+end
+
+def assert_scrubber_value(value)
+ scrubber = find("[data-ci='scrubber-range-input']")
+ assert_equal value, scrubber.value.to_i
+end
+
+module Bootcamp
+ class EditorTest < ApplicationSystemTestCase
+ test "things render" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :penguin
+
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ assert_selector("#bootcamp-cm-editor")
+ assert_selector("[data-ci='check-scenarios-button']")
+ assert_selector("[data-ci='control-buttons']")
+ assert_selector("[data-ci='task-preview']")
+ refute_selector("[data-ci='inspected-test-result-view']")
+ assert_selector(".page-header")
+ assert_selector(".page-body-rhs")
+ assert_selector(".scenario-rhs")
+ assert_text "This is fun!"
+ end
+ end
+
+ test "shows all scenario previews" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :even_or_odd
+
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ assert_selector("[data-ci='task-preview']")
+ assert_selector("[data-ci='preview-scenario-button']", count: 5)
+ assert_text "Number 14"
+ find("[data-ci='preview-scenario-button']:nth-of-type(2)").click
+ assert_text "Number 28"
+ end
+ end
+
+ test "sets up exercise view correctly" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :manual_solve
+
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ assert_selector("[data-ci='task-preview']")
+ assert_text "Guide person to the end of the maze"
+ assert_text "Your job is to reach the goal"
+ assert_selector("[data-ci='preview-scenario-button']", count: 1)
+ assert_selector(".cell", count: 49)
+ assert_selector(".character")
+ end
+ end
+
+ test "shows failing tests state" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :manual_solve
+
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ find("[data-ci='check-scenarios-button']").click
+ assert_selector(".test-button.fail")
+ assert_selector(".c-scenario.fail")
+ assert_text "Uh Oh. You didn't reach the end of the maze. "
+ end
+ end
+
+ test "shows passing tests state" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :manual_solve
+
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%{
+move()
+move()
+move()
+move()
+ })
+
+ find("[data-ci='check-scenarios-button']").click
+ refute_selector(".c-scneario.fail")
+ refute_selector(".c-scneario.pending")
+ assert_selector(".test-button.pass")
+ assert_selector(".c-scenario.pass")
+ end
+ end
+
+ test "shows modal on passing all tests and finishing timeline then tweaks further" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :manual_solve
+
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%{
+move()
+move()
+move()
+move()
+})
+
+ find("[data-ci='check-scenarios-button']").click
+
+ scrubber = find("[data-ci='scrubber-range-input']")
+ refute_equal scrubber.value, scrubber["max"]
+ refute_selector(".solve-exercise-page-react-modal-content")
+ sleep 1.5
+ assert_equal scrubber.value, scrubber["max"]
+ assert_selector(".solve-exercise-page-react-modal-content")
+
+ assert_text "Nice work!"
+ assert_selector ".solve-exercise-page-react-modal-content"
+ click_on "Tweak further"
+ refute_selector ".solve-exercise-page-react-modal-content"
+ end
+ end
+
+ test "shows modal on io test when all tasks are done" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :even_or_odd
+
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%(
+function even_or_odd with number do
+ if number % 2 equals 0 do
+ return "Even"
+ else do
+ return "Odd"
+ end
+end
+ ))
+
+ find("[data-ci='check-scenarios-button']").click
+ refute_selector(".c-scenario.fail")
+ refute_selector(".c-scenario.pending")
+ assert_selector(".test-button.pass")
+ assert_selector(".c-scenario.pass")
+
+ assert_text "Nice work!"
+ assert_selector ".solve-exercise-page-react-modal-content"
+ click_on "Tweak further"
+ refute_selector ".solve-exercise-page-react-modal-content"
+ end
+ end
+
+ test "doesnt show modal on io test when tasks are undone" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :even_or_odd
+
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%(function even_or_odd with number do
+ return "Even"
+end))
+
+ find("[data-ci='check-scenarios-button']").click
+ assert_selector(".c-scenario.fail")
+ refute_selector(".c-scenario.pending")
+ refute_selector(".c-scenario.pass")
+
+ refute_text "Nice work!"
+ refute_selector ".solve-exercise-page-react-modal-content"
+ end
+ end
+
+ test "shows modal on io test when after reiterating exercise correctly" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :even_or_odd
+
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%(function even_or_odd with number do
+ return "Even"
+end))
+
+ find("[data-ci='check-scenarios-button']").click
+ assert_selector(".c-scenario.fail")
+ refute_selector(".c-scenario.pass")
+
+ refute_text "Nice work!"
+ refute_selector ".solve-exercise-page-react-modal-content"
+
+ change_codemirror_content(%(
+ function even_or_odd with number do
+ if number % 2 equals 0 do
+ return "Even"
+ else do
+ return "Odd"
+ end
+ end
+ ))
+ find("[data-ci='check-scenarios-button']").click
+
+ assert_text "Nice work!"
+ assert_selector ".solve-exercise-page-react-modal-content"
+ refute_selector(".c-scenario.fail")
+ assert_selector(".c-scenario.pass")
+ end
+ end
+
+ test "shows first failing case" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :even_or_odd
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%(function even_or_odd with number do
+ return "Even"
+end))
+ find("[data-ci='check-scenarios-button']").click
+ assert_selector(".c-scenario.fail")
+ refute_selector(".c-scenario.pass")
+ assert find(".test-button.fail.selected").has_text?("3")
+ end
+ end
+
+ test "stays on inspected failing scenario after rerunning code" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :even_or_odd
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%(function even_or_odd with number do
+ return "Even"
+end))
+ check_scenarios
+ assert_selector(".c-scenario.fail")
+ refute_selector(".c-scenario.pass")
+ assert find(".test-button.fail.selected").has_text?("3")
+ find(".test-button.fail", text: "4").click
+ check_scenarios
+ assert find(".test-button.fail.selected").has_text?("4")
+ end
+ end
+
+ test "selects last scenario on passing tests" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :even_or_odd
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%(function even_or_odd with number do
+ return "Even"
+end))
+ check_scenarios
+ assert_selector(".c-scenario.fail")
+ refute_selector(".c-scenario.pass")
+ assert find(".test-button.fail.selected").has_text?("3")
+ find(".test-button.fail", text: "4").click
+ check_scenarios
+ assert find(".test-button.fail.selected").has_text?("4")
+
+ change_codemirror_content(%(
+ function even_or_odd with number do
+ if number % 2 equals 0 do
+ return "Even"
+ else do
+ return "Odd"
+ end
+ end
+ ))
+ check_scenarios
+ refute_selector('.test-button.fail')
+ assert find(".test-button.pass.selected").has_text?("5")
+ end
+ end
+
+ test "shows info-widget with error on erronous code" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :even_or_odd
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%(function even_or_odd with number do
+ run_this_thing()
+ return "Even"
+end))
+ check_scenarios
+
+ assert_text "[Your function didn't return anything]"
+ assert_text "Jiki couldn't find a function with the name run_this_thing."
+ assert_selector ".information-tooltip.error"
+ end
+ end
+
+ test "hides info-widget on editor change" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :even_or_odd
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%(function even_or_odd with number do
+ run_this_thing()
+ return "Even"
+end))
+ check_scenarios
+
+ assert_text "Your function didn't return anything"
+ assert_text "Jiki couldn't find a function with the name run_this_thing."
+ assert_selector ".information-tooltip.error"
+
+ update_codemirror_content(%(function even_or_odd with number do
+return "Even"
+end))
+ refute_text "Jiki couldn't find a function with the name run_this_thing."
+ refute_selector ".information-tooltip.error"
+ end
+ end
+
+ test "can inspect line with information widget" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :even_or_odd
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%(function even_or_odd with number do
+ set this to 5
+ return "Even"
+end))
+ check_scenarios
+
+ find("[data-ci=information-widget-toggle]").click
+
+ assert_text "This created a new variable called this and sets its value to 5."
+ assert_selector ".information-tooltip.description"
+ end
+ end
+
+ test "can scrub between animation timeline frames" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :manual_solve
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%(move()
+turn_right()
+))
+ check_scenarios
+
+ toggle_information_tooltip
+ assert_selector ".information-tooltip.description"
+ assert_text "This called the turn_right function."
+ scrub_to(0)
+ assert_text "This called the move function."
+ end
+ end
+
+ test "can scrub between io frames" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :even_or_odd
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%(function even_or_odd with number do
+ set this to 5
+ return "Even"
+ end))
+
+ check_scenarios
+
+ toggle_information_tooltip
+ assert_selector ".information-tooltip.description"
+ assert_text "This created a new variable called this and sets its value to 5."
+ scrub_to(1)
+ assert_text "This returned Even."
+ end
+ end
+
+ test "scrubbing interrupts animation" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :automated_solve
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%(repeat_until_game_over do
+ if can_turn_left() is true do
+ turn_left()
+ move()
+ else if can_move() is true do
+ move()
+ else if can_turn_right() is true do
+ turn_right()
+ move()
+ else do
+ turn_left()
+ turn_left()
+ end
+end))
+ check_scenarios
+
+ scrubber_val_6 = 7285
+
+ # interrupting animation
+ scrub_to scrubber_val_6
+ sleep 0.1
+ assert_scrubber_value scrubber_val_6
+ sleep 0.5
+ assert_scrubber_value scrubber_val_6
+ end
+ end
+
+ test "keeps scrubber value between inspected scenarios" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :automated_solve
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%(repeat_until_game_over do
+ if can_turn_left() is true do
+ turn_left()
+ move()
+ else if can_move() is true do
+ move()
+ else if can_turn_right() is true do
+ turn_right()
+ move()
+ else do
+ turn_left()
+ turn_left()
+ end
+end))
+ check_scenarios
+
+ scrubber_val_4 = 2430
+ scrubber_val_6 = 7285
+
+ # interrupting animation
+ scrub_to scrubber_val_6
+ select_scenario 4
+ scrub_to scrubber_val_4
+
+ select_scenario 6
+ sleep 0.5
+ assert_scrubber_value scrubber_val_6
+ select_scenario 4
+ sleep 0.5
+ assert_scrubber_value scrubber_val_4
+ end
+ end
+
+ test "switching to completed timeline scenario immediately fires complete modal" do
+ user = create(:user, bootcamp_attendee: true)
+ exercise = create :bootcamp_exercise, :automated_solve
+ use_capybara_host do
+ sign_in!(user)
+ visit bootcamp_project_exercise_url(exercise.project, exercise)
+
+ change_codemirror_content(%(repeat_until_game_over do
+ if can_turn_left() is true do
+ turn_left()
+ move()
+ else if can_move() is true do
+ move()
+ else if can_turn_right() is true do
+ turn_right()
+ move()
+ else do
+ turn_left()
+ turn_left()
+ end
+end))
+ check_scenarios
+
+ sleep 1
+ select_scenario 1
+ assert_text "Nice work!"
+ assert_selector ".solve-exercise-page-react-modal-content"
+ end
+ end
+ end
+end