From 4017443a0453d711d8c4dbcd9f420ea24dd5121a Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Tue, 10 Nov 2026 10:56:04 +0100 Subject: [PATCH] feat(conditional): go to a question after checkbox questions Checkbox questions get the select "After question N", stored in extraSettings.next: one target for the whole question whichever options are checked, as in Microsoft Forms. Several checked options cannot lead to different targets, so there is no target per option. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Andy Scherzinger --- docs/DataStructure.md | 4 ++-- lib/Constants.php | 1 + src/utils/Jumps.ts | 1 + tests/Unit/Service/FormsServiceTest.php | 5 +++++ tests/fixtures/jump-paths.json | 10 ++++++++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/DataStructure.md b/docs/DataStructure.md index f9aea0f99..2a2e5af66 100644 --- a/docs/DataStructure.md +++ b/docs/DataStructure.md @@ -271,14 +271,14 @@ Optional extra settings for some [Question Types](#question-types) | `rows` | `grid` | Array | - | Array of row identifiers / labels for grid questions | | `questionType` | `grid` | String | `checkbox`, `number`, `radio` | Type of cell for grid questions (checkbox, numeric input, or radio) | | `goTo` | `dropdown, multiple_unique` | Array | see [Jumps](#jumps) | Where to go next for an answer | -| `next` | `color, file, grid, long, ranking, short` | Integer / string | question id, `'submit'` | Where to go after the question, see [Jumps](#jumps) | +| `next` | `color, file, grid, long, multiple, ranking, short` | Integer / string | question id, `'submit'` | Where to go after the question, see [Jumps](#jumps) | ### Jumps An answer can lead to a later question or to the end of the form, as in Microsoft and Google Forms. The form stays a flat list of questions. - `goTo` is a list of jumps per answer: `{"optionId": 12, "target": 34}` for an option, `{"other": true, "target": "submit"}` for the answer "Other". There is at most one jump per answer. -- `next` is one target after the whole question. +- `next` is one target after the whole question; on a checkbox question it applies whichever options are checked. - A question has either jumps per answer or one jump after the question, depending on its type: `goTo` only on `dropdown` and `multiple_unique`, `next` only on the other types listed above. Sending `next` on a question type with `goTo`, or `goTo` on a question type with `next`, is rejected with 400 "Invalid extraSettings". An answer without a `goTo` entry leads to the following question. - A target is the id of a later question of the same form or `"submit"` for the end of the form, and `optionId` is the id of an option of the question. Ids of questions of other forms and option ids that are not options of the question are rejected as invalid `extraSettings`; ids of deleted questions of the form are accepted. Without a jump the respondent goes to the following question. - Jumps never go back: the server removes jumps to the question itself or to an earlier question when a question is updated, reordered or copied, and when a form is copied or imported; copying or importing a form also drops `goTo` or `next` on question types without it. Updating, reordering and copying a question still succeed with their usual status, and the payload keeps its shape: a reorder adds the new `extraSettings` of the questions concerned, and a copied question is returned with its reduced `extraSettings`. The response header `X-Forms-Adjusted` lists the questions that lost jumps, e.g. `removed-jumps=(3 7)`, see [Adjustments by the server](API_v3.md#adjustments-by-the-server). diff --git a/lib/Constants.php b/lib/Constants.php index e896a9969..afb0e1bf2 100644 --- a/lib/Constants.php +++ b/lib/Constants.php @@ -245,6 +245,7 @@ class Constants { self::ANSWER_TYPE_FILE, self::ANSWER_TYPE_GRID, self::ANSWER_TYPE_LONG, + self::ANSWER_TYPE_MULTIPLE, self::ANSWER_TYPE_RANKING, self::ANSWER_TYPE_SHORT, ]; diff --git a/src/utils/Jumps.ts b/src/utils/Jumps.ts index 06a8bd07e..5dad119b5 100644 --- a/src/utils/Jumps.ts +++ b/src/utils/Jumps.ts @@ -20,6 +20,7 @@ export const QUESTION_TYPES_WITH_NEXT = [ 'file', 'grid', 'long', + 'multiple', 'ranking', 'short', ] diff --git a/tests/Unit/Service/FormsServiceTest.php b/tests/Unit/Service/FormsServiceTest.php index b87a9e5d3..04fa89b62 100644 --- a/tests/Unit/Service/FormsServiceTest.php +++ b/tests/Unit/Service/FormsServiceTest.php @@ -1717,6 +1717,11 @@ public static function dataAreExtraSettingsValid() { 'questionType' => Constants::ANSWER_TYPE_SHORT, 'expected' => false, ], + 'jump: next on checkboxes' => [ + 'extraSettings' => ['next' => 5], + 'questionType' => Constants::ANSWER_TYPE_MULTIPLE, + 'expected' => true, + ], 'jump: goTo not offered on checkboxes' => [ 'extraSettings' => ['goTo' => [['optionId' => 11, 'target' => 5]]], 'questionType' => Constants::ANSWER_TYPE_MULTIPLE, diff --git a/tests/fixtures/jump-paths.json b/tests/fixtures/jump-paths.json index 42e63c4ca..087f2f656 100644 --- a/tests/fixtures/jump-paths.json +++ b/tests/fixtures/jump-paths.json @@ -228,6 +228,16 @@ "answers": { "1": ["12"] }, "visible": [1, 3, 4] }, + { + "name": "a checkbox question has one target whichever options are checked", + "questions": [ + { "id": 1, "type": "multiple", "extraSettings": { "next": 3 } }, + { "id": 2, "type": "short", "extraSettings": {} }, + { "id": 3, "type": "short", "extraSettings": {} } + ], + "answers": { "1": ["11", "12"] }, + "visible": [1, 3] + }, { "name": "the jumps of skipped questions do not count", "questions": [