{"id":"gui-events-logic","slug":"logic-in-a-class","title":"Logic in a class of its own","text":"The handlers so far did small things. In a bigger program, a handler that works everything out by itself gets long, and the only way to try what it computes is to click through the window. A better shape, common in programs of every size: a plain class does the work and knows nothing about windows, and the handlers only read the window, call the class, and show what it answers.\n\n### A color mixer\n\nA paint shop's window mixes the three primary colors. The mixing is in `ColorMixer`, a class without a single import from JavaFX:\n\n```java\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        ColorMixer mixer = new ColorMixer();\n        Label mix = new Label(\"Mix: nothing yet\");\n        Button red = new Button(\"Red\");\n        Button yellow = new Button(\"Yellow\");\n        Button blue = new Button(\"Blue\");\n        Button clear = new Button(\"Clear\");\n        red.setOnAction(event -> {\n            mixer.add(\"red\");\n            mix.setText(\"Mix: \" + mixer.result());\n        });\n        yellow.setOnAction(event -> {\n            mixer.add(\"yellow\");\n            mix.setText(\"Mix: \" + mixer.result());\n        });\n        blue.setOnAction(event -> {\n            mixer.add(\"blue\");\n            mix.setText(\"Mix: \" + mixer.result());\n        });\n        clear.setOnAction(event -> {\n            mixer.clear();\n            mix.setText(\"Mix: \" + mixer.result());\n        });\n\n        stage.setScene(new Scene(new VBox(10, new HBox(5, red, yellow, blue), clear, mix)));\n        stage.setTitle(\"Color mixer\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n// ==== ColorMixer.java ====\npublic class ColorMixer {\n    private boolean red;\n    private boolean yellow;\n    private boolean blue;\n\n    public void add(String color) {\n        if (color.equals(\"red\")) {\n            this.red = true;\n        } else if (color.equals(\"yellow\")) {\n            this.yellow = true;\n        } else if (color.equals(\"blue\")) {\n            this.blue = true;\n        }\n    }\n\n    public void clear() {\n        this.red = false;\n        this.yellow = false;\n        this.blue = false;\n    }\n\n    public String result() {\n        if (this.red && this.yellow && this.blue) {\n            return \"brown\";\n        }\n        if (this.red && this.yellow) {\n            return \"orange\";\n        }\n        if (this.yellow && this.blue) {\n            return \"green\";\n        }\n        if (this.red && this.blue) {\n            return \"purple\";\n        }\n        if (this.red) {\n            return \"red\";\n        }\n        if (this.yellow) {\n            return \"yellow\";\n        }\n        if (this.blue) {\n            return \"blue\";\n        }\n        return \"nothing yet\";\n    }\n}\n```\n\n```window\n{\"events\":[\"click Button \\\"Red\\\"\",\"click Button \\\"Yellow\\\"\"],\"window\":{\"windows\":[{\"title\":\"Color mixer\",\"scene\":{\"root\":{\"n\":1,\"type\":\"VBox\",\"spacing\":10,\"children\":[{\"n\":2,\"type\":\"HBox\",\"spacing\":5,\"children\":[{\"n\":3,\"type\":\"Button\",\"text\":\"Red\",\"onAction\":true},{\"n\":4,\"type\":\"Button\",\"text\":\"Yellow\",\"onAction\":true},{\"n\":5,\"type\":\"Button\",\"text\":\"Blue\",\"onAction\":true}]},{\"n\":6,\"type\":\"Button\",\"text\":\"Clear\",\"onAction\":true},{\"n\":7,\"type\":\"Label\",\"text\":\"Mix: orange\"}]}}}],\"problems\":[]},\"outline\":\"Window \\\"Color mixer\\\"\\n  VBox spacing 10\\n    HBox spacing 5\\n      Button \\\"Red\\\"\\n      Button \\\"Yellow\\\"\\n      Button \\\"Blue\\\"\\n    Button \\\"Clear\\\"\\n    Label \\\"Mix: orange\\\"\"}\n```\n\n- `start` makes one ColorMixer, and every handler uses it. `mixer` never changes, so the lambdas may use it (step 2); the mixer object's own variables change.\n- Each handler does the same two things: it tells the mixer what happened, and it shows the mixer's answer in the label. None of them knows how colors mix.\n- ColorMixer knows nothing about buttons or labels. It takes a color's name and answers with a name.\n\n### Logic you can check without a window\n\nSince ColorMixer doesn't need a window, a program can use it without one:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        ColorMixer mixer = new ColorMixer();\n        System.out.println(mixer.result());\n        mixer.add(\"blue\");\n        mixer.add(\"yellow\");\n        System.out.println(mixer.result());\n        mixer.add(\"red\");\n        System.out.println(mixer.result());\n    }\n}\n// ==== ColorMixer.java ====\npublic class ColorMixer {\n    private boolean red;\n    private boolean yellow;\n    private boolean blue;\n\n    public void add(String color) {\n        if (color.equals(\"red\")) {\n            this.red = true;\n        } else if (color.equals(\"yellow\")) {\n            this.yellow = true;\n        } else if (color.equals(\"blue\")) {\n            this.blue = true;\n        }\n    }\n\n    public void clear() {\n        this.red = false;\n        this.yellow = false;\n        this.blue = false;\n    }\n\n    public String result() {\n        if (this.red && this.yellow && this.blue) {\n            return \"brown\";\n        }\n        if (this.red && this.yellow) {\n            return \"orange\";\n        }\n        if (this.yellow && this.blue) {\n            return \"green\";\n        }\n        if (this.red && this.blue) {\n            return \"purple\";\n        }\n        if (this.red) {\n            return \"red\";\n        }\n        if (this.yellow) {\n            return \"yellow\";\n        }\n        if (this.blue) {\n            return \"blue\";\n        }\n        return \"nothing yet\";\n    }\n}\n```\n\n```output\nnothing yet\ngreen\nbrown\n```\n\nThat's what makes the shape worth it. Every case of the mixing can be tried by calling methods, as the checks of the next challenge do, or with JUnit tests (module 26), with no clicking. The window then needs only a few clicks to show that its handlers call the right methods. And the same ColorMixer could serve a text user interface (module 25) or a different window, unchanged.\n\n### One handler for several buttons\n\nThe three color buttons do the same thing with a different color. A handler is an object, so one handler can be kept in a variable and given to all three. The event tells which button was clicked: `event.getSource()` returns it. Its type is Object, so the handler casts it to a Button (module 36):\n\n```java\nEventHandler<ActionEvent> addColor = event -> {\n    Button clicked = (Button) event.getSource();\n    mixer.add(clicked.getText().toLowerCase());\n    mix.setText(\"Mix: \" + mixer.result());\n};\nred.setOnAction(addColor);\nyellow.setOnAction(addColor);\nblue.setOnAction(addColor);\n```\n\nNaming the handler's type, `EventHandler<ActionEvent>`, needs two more imports: `javafx.event.ActionEvent` and `javafx.event.EventHandler`. The buttons' texts `Red`, `Yellow` and `Blue` turn into the mixer's names with `toLowerCase()`.\n","task":"A sign-up page checks whether a username is free, against the names already taken. Write the class `UsernameChecker`, without anything from JavaFX, with:\n\n- a constructor that takes no parameters: at first, no name is taken\n- `addTaken(String name)`, which marks a name as taken\n- `isFree(String name)`, which returns `true` when the name has at least 3 characters and isn't taken\n- `message(String name)`, which returns `At least 3 characters` for a name of fewer than 3 characters, the name and ` is taken` for a taken one (`ada is taken`), and the name and ` is free` otherwise (`ada_l is free`)\n\nCapital letters don't matter when comparing names: after `addTaken(\"Ada\")`, `ADA` and `ada` are taken too. The message shows the name as it was given. The check runs:\n\n```java\nUsernameChecker checker = new UsernameChecker();\nchecker.addTaken(\"admin\");\nchecker.addTaken(\"Ada\");\nSystem.out.println(checker.message(\"ad\"));\nSystem.out.println(checker.message(\"ADA\"));\nSystem.out.println(checker.message(\"ada_l\"));\nSystem.out.println(checker.isFree(\"Admin\") + \" \" + checker.isFree(\"linus\"));\n```\n\nand expects:\n\n```\nAt least 3 characters\nADA is taken\nada_l is free\nfalse true\n```\n","kind":"code","seed":"// ==== UsernameChecker.java ====\npublic class UsernameChecker {\n\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        UsernameChecker checker = new UsernameChecker();\n        checker.addTaken(\"admin\");\n        checker.addTaken(\"Ada\");\n        System.out.println(checker.message(\"ad\"));\n        System.out.println(checker.message(\"ADA\"));\n        System.out.println(checker.message(\"ada_l\"));\n        System.out.println(checker.isFree(\"Admin\") + \" \" + checker.isFree(\"linus\"));\n    }\n}\n","solution":"// ==== UsernameChecker.java ====\nimport java.util.ArrayList;\n\npublic class UsernameChecker {\n    private ArrayList<String> taken;\n\n    public UsernameChecker() {\n        this.taken = new ArrayList<>();\n    }\n\n    public void addTaken(String name) {\n        this.taken.add(name.toLowerCase());\n    }\n\n    public boolean isFree(String name) {\n        return name.length() >= 3 && !this.taken.contains(name.toLowerCase());\n    }\n\n    public String message(String name) {\n        if (name.length() < 3) {\n            return \"At least 3 characters\";\n        }\n        if (this.taken.contains(name.toLowerCase())) {\n            return name + \" is taken\";\n        }\n        return name + \" is free\";\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        UsernameChecker checker = new UsernameChecker();\n        checker.addTaken(\"admin\");\n        checker.addTaken(\"Ada\");\n        System.out.println(checker.message(\"ad\"));\n        System.out.println(checker.message(\"ADA\"));\n        System.out.println(checker.message(\"ada_l\"));\n        System.out.println(checker.isFree(\"Admin\") + \" \" + checker.isFree(\"linus\"));\n    }\n}\n","hints":["Keep the taken names in an `ArrayList<String>`, made in the constructor. Store each name in small letters (`name.toLowerCase()`), and compare in small letters too: `this.taken.contains(name.toLowerCase())`.","`message` checks the length first, then whether the name is taken: `return name + \" is taken\";`. `isFree` is `true` when both checks pass."],"tests":[{"name":"Test 1","stdin":"","call":"UsernameChecker checker = new UsernameChecker();\nchecker.addTaken(\"admin\");\nchecker.addTaken(\"Ada\");\nSystem.out.println(checker.message(\"ad\"));\nSystem.out.println(checker.message(\"ADA\"));\nSystem.out.println(checker.message(\"ada_l\"));\nSystem.out.println(checker.isFree(\"Admin\") + \" \" + checker.isFree(\"linus\"));\n","expect":"At least 3 characters\nADA is taken\nada_l is free\nfalse true","hidden":false},{"name":"Test 2","stdin":"","call":"UsernameChecker checker = new UsernameChecker();\nSystem.out.println(checker.message(\"kim\") + \", \" + checker.isFree(\"kim\"));\nSystem.out.println(checker.message(\"\") + \", \" + checker.isFree(\"\"));\nSystem.out.println(checker.message(\"Jo\") + \", \" + checker.isFree(\"Jo\"));\nchecker.addTaken(\"KIM\");\nchecker.addTaken(\"grace\");\nSystem.out.println(checker.message(\"Kim\") + \", \" + checker.isFree(\"kIm\"));\nSystem.out.println(checker.message(\"GRACE\") + \", \" + checker.isFree(\"Grace\"));\nSystem.out.println(checker.message(\"graces\") + \", \" + checker.isFree(\"graces\"));\n","expect":"kim is free, true\nAt least 3 characters, false\nAt least 3 characters, false\nKim is taken, false\nGRACE is taken, false\ngraces is free, true","hidden":true}],"require":[],"forbid":[],"more":[{"task":"Now the sign-up window, with `UsernameChecker` from the last challenge ready in its own file. The window is built already: a text field with the prompt text `Username`, the label `At least 3 characters`, and the disabled button `Sign up`. The names `admin`, `ada` and `linus` are taken. Make it work, with the checker doing the checking:\n\n- After every change of the text, the label shows the checker's message for the text, and the button is enabled only when the checker says the name is free.\n- **Sign up** marks the name in the field as taken, sets the label to `Signed up: ` and the name (`Signed up: grace`), and disables the button.\n\nThe task card shows the window after typing `grace` and clicking Sign up.\n","kind":"code","seed":"// ==== Main.java ====\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TextField;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        UsernameChecker checker = new UsernameChecker();\n        checker.addTaken(\"admin\");\n        checker.addTaken(\"ada\");\n        checker.addTaken(\"linus\");\n\n        TextField username = new TextField();\n        username.setPromptText(\"Username\");\n        Label status = new Label(\"At least 3 characters\");\n        Button signUp = new Button(\"Sign up\");\n        signUp.setDisable(true);\n\n        stage.setScene(new Scene(new VBox(username, status, signUp)));\n        stage.setTitle(\"Sign up\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n\n// ==== UsernameChecker.java ====\nimport java.util.ArrayList;\n\npublic class UsernameChecker {\n    private ArrayList<String> taken;\n\n    public UsernameChecker() {\n        this.taken = new ArrayList<>();\n    }\n\n    public void addTaken(String name) {\n        this.taken.add(name.toLowerCase());\n    }\n\n    public boolean isFree(String name) {\n        return name.length() >= 3 && !this.taken.contains(name.toLowerCase());\n    }\n\n    public String message(String name) {\n        if (name.length() < 3) {\n            return \"At least 3 characters\";\n        }\n        if (this.taken.contains(name.toLowerCase())) {\n            return name + \" is taken\";\n        }\n        return name + \" is free\";\n    }\n}\n","solution":"// ==== Main.java ====\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.control.TextField;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        UsernameChecker checker = new UsernameChecker();\n        checker.addTaken(\"admin\");\n        checker.addTaken(\"ada\");\n        checker.addTaken(\"linus\");\n\n        TextField username = new TextField();\n        username.setPromptText(\"Username\");\n        Label status = new Label(\"At least 3 characters\");\n        Button signUp = new Button(\"Sign up\");\n        signUp.setDisable(true);\n        username.textProperty().addListener((observable, oldValue, newValue) -> {\n            status.setText(checker.message(newValue));\n            signUp.setDisable(!checker.isFree(newValue));\n        });\n        signUp.setOnAction(event -> {\n            String name = username.getText();\n            checker.addTaken(name);\n            status.setText(\"Signed up: \" + name);\n            signUp.setDisable(true);\n        });\n\n        stage.setScene(new Scene(new VBox(username, status, signUp)));\n        stage.setTitle(\"Sign up\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n\n// ==== UsernameChecker.java ====\nimport java.util.ArrayList;\n\npublic class UsernameChecker {\n    private ArrayList<String> taken;\n\n    public UsernameChecker() {\n        this.taken = new ArrayList<>();\n    }\n\n    public void addTaken(String name) {\n        this.taken.add(name.toLowerCase());\n    }\n\n    public boolean isFree(String name) {\n        return name.length() >= 3 && !this.taken.contains(name.toLowerCase());\n    }\n\n    public String message(String name) {\n        if (name.length() < 3) {\n            return \"At least 3 characters\";\n        }\n        if (this.taken.contains(name.toLowerCase())) {\n            return name + \" is taken\";\n        }\n        return name + \" is free\";\n    }\n}\n","hints":["The listener asks the checker twice: once with `message` for the label's text, and once with `isFree`, negated, for whether the button is disabled.","The listener is two lines: `status.setText(checker.message(newValue));` and `signUp.setDisable(!checker.isFree(newValue));`. In Sign up's handler, read the name with `username.getText()`, then call `checker.addTaken(name)`, set the label and disable the button."],"tests":[{"name":"Test 1","stdin":"","expect":"","hidden":false,"events":["type TextField 1 \"grace\"","click Button \"Sign up\""],"window":{"windows":[{"title":"Sign up","scene":{"root":{"n":1,"type":"VBox","children":[{"n":2,"type":"TextField","text":"grace","promptText":"Username"},{"n":3,"type":"Label","text":"Signed up: grace"},{"n":4,"type":"Button","text":"Sign up","disable":true,"onAction":true}]}}}],"problems":[]},"outline":"Window \"Sign up\"\n  VBox\n    TextField \"grace\", prompt \"Username\"\n    Label \"Signed up: grace\"\n    Button \"Sign up\" (disabled)"},{"name":"Test 2","stdin":"","expect":"","hidden":true,"events":["type TextField 1 \"Ada\"","click Button \"Sign up\""],"window":{"windows":[{"title":"Sign up","scene":{"root":{"n":1,"type":"VBox","children":[{"n":2,"type":"TextField","text":"Ada","promptText":"Username"},{"n":3,"type":"Label","text":"Ada is taken"},{"n":4,"type":"Button","text":"Sign up","disable":true,"onAction":true}]}}}],"problems":[]},"outline":"Window \"Sign up\"\n  VBox\n    TextField \"Ada\", prompt \"Username\"\n    Label \"Ada is taken\"\n    Button \"Sign up\" (disabled)"},{"name":"Test 3","stdin":"","expect":"","hidden":true,"events":["type TextField 1 \"lin\"","click Button \"Sign up\"","type TextField 1 \"a\"","set TextField 1 \"lin\""],"window":{"windows":[{"title":"Sign up","scene":{"root":{"n":1,"type":"VBox","children":[{"n":2,"type":"TextField","text":"lin","promptText":"Username"},{"n":3,"type":"Label","text":"lin is taken"},{"n":4,"type":"Button","text":"Sign up","disable":true,"onAction":true}]}}}],"problems":[]},"outline":"Window \"Sign up\"\n  VBox\n    TextField \"lin\", prompt \"Username\"\n    Label \"lin is taken\"\n    Button \"Sign up\" (disabled)"},{"name":"Test 4","stdin":"","expect":"","hidden":true,"events":["type TextField 1 \"admins\"","set TextField 1 \"ma\""],"window":{"windows":[{"title":"Sign up","scene":{"root":{"n":1,"type":"VBox","children":[{"n":2,"type":"TextField","text":"ma","promptText":"Username"},{"n":3,"type":"Label","text":"At least 3 characters"},{"n":4,"type":"Button","text":"Sign up","disable":true,"onAction":true}]}}}],"problems":[]},"outline":"Window \"Sign up\"\n  VBox\n    TextField \"ma\", prompt \"Username\"\n    Label \"At least 3 characters\"\n    Button \"Sign up\" (disabled)"}],"require":[{"pattern":"\\.message\\s*\\(","message":"Let the checker write the label's text: `checker.message(...)`."},{"pattern":"\\.isFree\\s*\\(","message":"Let the checker say whether the name is free: `checker.isFree(...)`."}],"forbid":[]},{"task":"A pomodoro timer goes through a fixed plan of steps: `Work 1`, `Short break`, `Work 2`, `Short break`, `Work 3`, `Short break`, `Work 4`, `Long break`, and then from `Work 1` again. Write both parts:\n\n- In `PomodoroCycle.java`, the class `PomodoroCycle`, without anything from JavaFX: a constructor that starts at `Work 1`, `current()`, which returns the name of the current step, `next()`, which moves to the next step, and `restart()`, which goes back to `Work 1`.\n- In `Main.java`, the window is built already: the label `Now: Work 1`, and under it the buttons `Next` and `Start over`. Make a PomodoroCycle in `start`. **Next** moves the cycle to its next step and **Start over** restarts it; after either one, the label shows `Now: ` and the current step, like `Now: Short break`.\n\nThe task card shows the window after seven clicks on Next.\n","kind":"code","seed":"// ==== PomodoroCycle.java ====\npublic class PomodoroCycle {\n\n}\n\n// ==== Main.java ====\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        Label now = new Label(\"Now: Work 1\");\n        Button next = new Button(\"Next\");\n        Button startOver = new Button(\"Start over\");\n\n        stage.setScene(new Scene(new VBox(now, new HBox(next, startOver))));\n        stage.setTitle(\"Pomodoro plan\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","solution":"// ==== PomodoroCycle.java ====\npublic class PomodoroCycle {\n    private int step;\n\n    public PomodoroCycle() {\n        this.step = 0;\n    }\n\n    public String current() {\n        if (this.step == 7) {\n            return \"Long break\";\n        }\n        if (this.step % 2 == 1) {\n            return \"Short break\";\n        }\n        return \"Work \" + (this.step / 2 + 1);\n    }\n\n    public void next() {\n        this.step = (this.step + 1) % 8;\n    }\n\n    public void restart() {\n        this.step = 0;\n    }\n}\n\n// ==== Main.java ====\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Button;\nimport javafx.scene.control.Label;\nimport javafx.scene.layout.HBox;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\npublic class Main extends Application {\n\n    @Override\n    public void start(Stage stage) {\n        PomodoroCycle cycle = new PomodoroCycle();\n        Label now = new Label(\"Now: Work 1\");\n        Button next = new Button(\"Next\");\n        Button startOver = new Button(\"Start over\");\n        next.setOnAction(event -> {\n            cycle.next();\n            now.setText(\"Now: \" + cycle.current());\n        });\n        startOver.setOnAction(event -> {\n            cycle.restart();\n            now.setText(\"Now: \" + cycle.current());\n        });\n\n        stage.setScene(new Scene(new VBox(now, new HBox(next, startOver))));\n        stage.setTitle(\"Pomodoro plan\");\n        stage.show();\n    }\n\n    public static void main(String[] args) {\n        launch(Main.class);\n    }\n}\n","hints":["PomodoroCycle can keep the number of the step, 0 to 7: the even steps 0, 2, 4 and 6 are `Work 1` to `Work 4` (`\"Work \" + (step / 2 + 1)`), step 7 is the long break, and the other odd steps are short breaks. `next()` adds one, and goes from 7 back to 0.","In `start`, make the cycle with `PomodoroCycle cycle = new PomodoroCycle();`. Each handler calls the cycle's method, then sets the label: `now.setText(\"Now: \" + cycle.current());`."],"tests":[{"name":"Test 1","stdin":"","expect":"","hidden":false,"events":["click Button \"Next\"","click Button \"Next\"","click Button \"Next\"","click Button \"Next\"","click Button \"Next\"","click Button \"Next\"","click Button \"Next\""],"window":{"windows":[{"title":"Pomodoro plan","scene":{"root":{"n":1,"type":"VBox","children":[{"n":2,"type":"Label","text":"Now: Long break"},{"n":3,"type":"HBox","children":[{"n":4,"type":"Button","text":"Next","onAction":true},{"n":5,"type":"Button","text":"Start over","onAction":true}]}]}}}],"problems":[]},"outline":"Window \"Pomodoro plan\"\n  VBox\n    Label \"Now: Long break\"\n    HBox\n      Button \"Next\"\n      Button \"Start over\""},{"name":"Test 2","stdin":"","expect":"","hidden":true,"events":["click Button \"Next\"","click Button \"Next\"","click Button \"Next\""],"window":{"windows":[{"title":"Pomodoro plan","scene":{"root":{"n":1,"type":"VBox","children":[{"n":2,"type":"Label","text":"Now: Short break"},{"n":3,"type":"HBox","children":[{"n":4,"type":"Button","text":"Next","onAction":true},{"n":5,"type":"Button","text":"Start over","onAction":true}]}]}}}],"problems":[]},"outline":"Window \"Pomodoro plan\"\n  VBox\n    Label \"Now: Short break\"\n    HBox\n      Button \"Next\"\n      Button \"Start over\""},{"name":"Test 3","stdin":"","expect":"","hidden":true,"events":["click Button \"Next\"","click Button \"Next\"","click Button \"Next\"","click Button \"Next\"","click Button \"Next\"","click Button \"Next\"","click Button \"Next\"","click Button \"Next\"","click Button \"Next\"","click Button \"Next\""],"window":{"windows":[{"title":"Pomodoro plan","scene":{"root":{"n":1,"type":"VBox","children":[{"n":2,"type":"Label","text":"Now: Work 2"},{"n":3,"type":"HBox","children":[{"n":4,"type":"Button","text":"Next","onAction":true},{"n":5,"type":"Button","text":"Start over","onAction":true}]}]}}}],"problems":[]},"outline":"Window \"Pomodoro plan\"\n  VBox\n    Label \"Now: Work 2\"\n    HBox\n      Button \"Next\"\n      Button \"Start over\""},{"name":"Test 4","stdin":"","expect":"","hidden":true,"events":["click Button \"Next\"","click Button \"Next\"","click Button \"Next\"","click Button \"Next\"","click Button \"Start over\"","click Button \"Next\"","click Button \"Next\""],"window":{"windows":[{"title":"Pomodoro plan","scene":{"root":{"n":1,"type":"VBox","children":[{"n":2,"type":"Label","text":"Now: Work 2"},{"n":3,"type":"HBox","children":[{"n":4,"type":"Button","text":"Next","onAction":true},{"n":5,"type":"Button","text":"Start over","onAction":true}]}]}}}],"problems":[]},"outline":"Window \"Pomodoro plan\"\n  VBox\n    Label \"Now: Work 2\"\n    HBox\n      Button \"Next\"\n      Button \"Start over\""},{"name":"Test 5","stdin":"","expect":"","hidden":true,"events":["click Button \"Start over\""],"window":{"windows":[{"title":"Pomodoro plan","scene":{"root":{"n":1,"type":"VBox","children":[{"n":2,"type":"Label","text":"Now: Work 1"},{"n":3,"type":"HBox","children":[{"n":4,"type":"Button","text":"Next","onAction":true},{"n":5,"type":"Button","text":"Start over","onAction":true}]}]}}}],"problems":[]},"outline":"Window \"Pomodoro plan\"\n  VBox\n    Label \"Now: Work 1\"\n    HBox\n      Button \"Next\"\n      Button \"Start over\""}],"require":[{"pattern":"\\bnew\\s+PomodoroCycle\\s*\\(","message":"Make a PomodoroCycle in `start`, and let the handlers use it."},{"pattern":"\\.current\\s*\\(\\s*\\)","message":"Show the step that the cycle's `current()` returns."}],"forbid":[]}]}