diff --git a/Gemfile.lock b/Gemfile.lock
index 62e1968cf..4e61a668b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -159,7 +159,7 @@ GEM
thread_safe (~> 0.3, >= 0.3.1)
base64 (0.3.0)
bcrypt (3.1.22)
- bigdecimal (4.1.0)
+ bigdecimal (4.1.2)
bindata (2.5.1)
bindex (0.8.1)
bootsnap (1.18.6)
@@ -208,7 +208,7 @@ GEM
date (3.5.1)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
- devise (5.0.3)
+ devise (5.0.4)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 7.0)
@@ -279,7 +279,7 @@ GEM
activesupport (>= 6.0.0)
railties (>= 6.0.0)
io-console (0.8.2)
- irb (1.17.0)
+ irb (1.18.0)
pp (>= 0.6.0)
prism (>= 1.3.0)
rdoc (>= 4.0.0)
@@ -292,7 +292,7 @@ GEM
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
- json (2.19.3)
+ json (2.19.5)
json-jwt (1.17.0)
activesupport (>= 4.2)
aes_key_wrap
@@ -344,7 +344,7 @@ GEM
mini_magick (5.3.1)
logger
mini_mime (1.1.5)
- minitest (6.0.3)
+ minitest (6.0.6)
drb (~> 2.0)
prism (~> 1.5)
msgpack (1.8.0)
@@ -353,7 +353,7 @@ GEM
bigdecimal (>= 3.1, < 5)
net-http (0.9.1)
uri (>= 0.11.1)
- net-imap (0.6.3)
+ net-imap (0.6.4)
date
net-protocol
net-pop (0.1.2)
@@ -365,21 +365,21 @@ GEM
newrelic_rpm (10.2.0)
logger
nio4r (2.7.5)
- nokogiri (1.19.2-aarch64-linux-gnu)
+ nokogiri (1.19.3-aarch64-linux-gnu)
racc (~> 1.4)
- nokogiri (1.19.2-aarch64-linux-musl)
+ nokogiri (1.19.3-aarch64-linux-musl)
racc (~> 1.4)
- nokogiri (1.19.2-arm-linux-gnu)
+ nokogiri (1.19.3-arm-linux-gnu)
racc (~> 1.4)
- nokogiri (1.19.2-arm-linux-musl)
+ nokogiri (1.19.3-arm-linux-musl)
racc (~> 1.4)
- nokogiri (1.19.2-arm64-darwin)
+ nokogiri (1.19.3-arm64-darwin)
racc (~> 1.4)
- nokogiri (1.19.2-x86_64-darwin)
+ nokogiri (1.19.3-x86_64-darwin)
racc (~> 1.4)
- nokogiri (1.19.2-x86_64-linux-gnu)
+ nokogiri (1.19.3-x86_64-linux-gnu)
racc (~> 1.4)
- nokogiri (1.19.2-x86_64-linux-musl)
+ nokogiri (1.19.3-x86_64-linux-musl)
racc (~> 1.4)
oauth2 (2.0.14)
faraday (>= 0.17.3, < 4.0)
@@ -489,7 +489,7 @@ GEM
tsort (>= 0.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
- rake (13.3.1)
+ rake (13.4.2)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
diff --git a/README.md b/README.md
index aae5583df..6e2dbe656 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ support continuous improvement of public service delivery.
An example Touchpoints form that includes every input element
is available in this
-[Kitchen Sink](https://touchpoints.app.cloud.gov/touchpoints/34d93e4e/submit)
+[Kitchen Sink](https://touchpoints.app.cloud.gov/touchpoints/34d93e4e)
example.
Touchpoints is a web application
@@ -24,7 +24,7 @@ GSA's Federal Acquisition Service (FAS) is developing Touchpoints in-house by th
within the Technology Transformation Services'
[Data Portfolio](https://www.gsa.gov/about-us/organization/federal-acquisition-service/technology-transformation-services/tts-solutions#data).
-Touchpoints is online at .
+Touchpoints documentation is online at .
A Demo environment is online at ,
and government customers are [encouraged](https://github.com/GSA/touchpoints/wiki/Touchpoints-Demo-Environment/) to sign up and try it out.
@@ -37,7 +37,7 @@ For developers, the wiki contains a [developer guide](https://github.com/GSA/tou
## Team Process
-The Touchpoints team tracks work in a [backlog](https://en.wikipedia.org/wiki/Kanban) board.
+The Touchpoints team tracks work in a [backlog](https://github.com/orgs/GSA/projects/348) board.
Issues and ideas are also noted in GitHub [Issues](https://github.com/gsa/touchpoints/issues).
diff --git a/app/controllers/admin/question_options_controller.rb b/app/controllers/admin/question_options_controller.rb
index 07048ecc9..cacb8d8dc 100644
--- a/app/controllers/admin/question_options_controller.rb
+++ b/app/controllers/admin/question_options_controller.rb
@@ -2,7 +2,7 @@
module Admin
class QuestionOptionsController < AdminController
- before_action :set_question, only: %i[new create create_other show edit update destroy]
+ before_action :set_question, only: %i[new create create_other show edit update destroy sort]
before_action :set_question_option, only: %i[show edit update destroy]
def index
@@ -21,11 +21,15 @@ def edit
end
def sort
- params[:question_option].each_with_index do |id, index|
- QuestionOption.find(id).update(position: index + 1)
+ current_option_ids = @question.question_options.pluck(:id).map(&:to_s)
+ if current_option_ids.sort == params[:question_option].sort
+ params[:question_option].each_with_index do |id, index|
+ QuestionOption.find(id).update(position: index + 1)
+ end
+ head :ok
+ else
+ render json: { error: "All question options must be listed exactly once in reordering request" }, status: :unprocessable_content
end
-
- head :ok
end
def update_title
diff --git a/app/views/admin/questions/_form.html.erb b/app/views/admin/questions/_form.html.erb
index 58d243705..70a344365 100644
--- a/app/views/admin/questions/_form.html.erb
+++ b/app/views/admin/questions/_form.html.erb
@@ -124,6 +124,7 @@
ele.html(resp);
ele.find(".question-option-edit").hide();
+ initializeSortableQuestionOptions(ele.find(".question-options"));
}
});
}
@@ -141,6 +142,7 @@
ele.html(resp);
ele.find(".question-option-edit").hide();
+ initializeSortableQuestionOptions(ele.find(".question-options"));
}
});
});
diff --git a/app/views/components/forms/edit/_builder.html.erb b/app/views/components/forms/edit/_builder.html.erb
index 8bb4b5386..6ff9b8b68 100644
--- a/app/views/components/forms/edit/_builder.html.erb
+++ b/app/views/components/forms/edit/_builder.html.erb
@@ -274,33 +274,9 @@ $(function() {
// Initialize sortable for question options
try {
- $(".question-options").sortable({
- items: '.question-option',
- handle: '.drag-handle',
- placeholder: 'sort-placeholder',
- update: function(e, ui) {
- try {
- var url = $(this).parent().data("url");
- if (!url) {
- console.error("Missing URL for question options sort");
- return;
- }
- $.ajax({
- url: url,
- type: "PATCH",
- data: $(this).sortable('serialize'),
- error: function(xhr, status, error) {
- console.error("Option sort update failed:", error);
- ui.item.animate({ left: 0 }, 300);
- }
- });
- } catch (err) {
- console.error("Error during option sort update:", err);
- }
- }
- }).disableSelection();
+ initializeSortableQuestionOptions($(".question-options"));
} catch (err) {
- console.error("Failed to initialize option sortable:", err);
+ console.error("Failed to initialize question option sortable:", err);
}
// Initialize sortable for sections
@@ -446,6 +422,39 @@ function initializeSortableQuestions($questionContainer) {
}).disableSelection();
}
+function initializeSortableQuestionOptions($questionOptionContainer) {
+ $questionOptionContainer.sortable({
+ items: '.question-option',
+ placeholder: 'sort-placeholder',
+ tolerance: 'pointer',
+ update: function(e, ui) {
+ try {
+ let url = $(this).parent().data("url") + "/sort";
+ let data = $(this).sortable('serialize');
+ if (!url) {
+ console.error("Missing URL for question options sort");
+ return;
+ }
+ if (!data) {
+ console.error("Failed to serialize sortable data");
+ return;
+ }
+ $.ajax({
+ url: url,
+ type: "PATCH",
+ data: data,
+ error: () => {
+ alert("Failed to update question option order");
+ $(this).sortable("cancel");
+ }
+ });
+ } catch (err) {
+ console.error("Error during option sort update:", err);
+ }
+ }
+ }).disableSelection();
+}
+
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll(".quill").forEach((wrapper) => {
const editorContainer = wrapper.querySelector(".editor");
diff --git a/config/routes.rb b/config/routes.rb
index 2fb61313e..99147d1a6 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -313,18 +313,16 @@
patch 'update_title', to: 'form_sections#update_title', as: :inline_update
end
resources :questions, except: :new do
- member do
- patch 'question_options', to: 'question_options#sort', as: :sort_question_options
+ collection do
+ patch 'sort', to: 'questions#sort', as: :sort_questions
end
resources :question_options, except: %i[index show] do
patch 'update_title', to: 'question_options#update_title', as: :inline_update
collection do
post 'create_other', to: 'question_options#create_other', as: :create_other
+ patch 'sort', to: 'question_options#sort', as: :sort_question_options
end
end
- collection do
- patch 'sort', to: 'questions#sort', as: :sort_questions
- end
end
resources :submissions, only: %i[show update destroy] do
collection do
diff --git a/package-lock.json b/package-lock.json
index 9597f1029..1b38e85a3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -44,15 +44,15 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.26.2",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
- "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
- "picocolors": "^1.0.0"
+ "picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
@@ -107,16 +107,16 @@
"license": "MIT"
},
"node_modules/@babel/generator": {
- "version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz",
- "integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==",
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.26.10",
- "@babel/types": "^7.26.10",
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.25",
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
},
"engines": {
@@ -210,6 +210,16 @@
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/helper-member-expression-to-functions": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz",
@@ -225,29 +235,29 @@
}
},
"node_modules/@babel/helper-module-imports": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
- "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/traverse": "^7.25.9",
- "@babel/types": "^7.25.9"
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
- "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-module-imports": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9",
- "@babel/traverse": "^7.25.9"
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -270,9 +280,9 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz",
- "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
"dev": true,
"license": "MIT",
"engines": {
@@ -330,9 +340,9 @@
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
- "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -340,9 +350,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
- "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -389,13 +399,13 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz",
- "integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==",
+ "version": "7.29.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
+ "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.26.10"
+ "@babel/types": "^7.29.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -954,16 +964,16 @@
}
},
"node_modules/@babel/plugin-transform-modules-systemjs": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz",
- "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==",
+ "version": "7.29.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz",
+ "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-module-transforms": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9",
- "@babel/traverse": "^7.25.9"
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.29.0"
},
"engines": {
"node": ">=6.9.0"
@@ -1497,48 +1507,48 @@
}
},
"node_modules/@babel/template": {
- "version": "7.26.9",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
- "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.26.2",
- "@babel/parser": "^7.26.9",
- "@babel/types": "^7.26.9"
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
- "version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.10.tgz",
- "integrity": "sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.26.2",
- "@babel/generator": "^7.26.10",
- "@babel/parser": "^7.26.10",
- "@babel/template": "^7.26.9",
- "@babel/types": "^7.26.10",
- "debug": "^4.3.1",
- "globals": "^11.1.0"
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": {
- "version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
- "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9"
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1581,18 +1591,14 @@
"license": "MIT"
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
- "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
@@ -1605,16 +1611,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
@@ -1623,9 +1619,9 @@
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/spec/controllers/admin/question_options_controller_spec.rb b/spec/controllers/admin/question_options_controller_spec.rb
index 22b758710..fda1bd52e 100644
--- a/spec/controllers/admin/question_options_controller_spec.rb
+++ b/spec/controllers/admin/question_options_controller_spec.rb
@@ -156,4 +156,26 @@
expect(response).to redirect_to(question_options_url)
end
end
+
+ describe 'PATCH #sort' do
+ context 'with multiple question options' do
+ let(:form) { FactoryBot.create(:form, organization:) }
+ let(:question) { FactoryBot.create(:question, form:, form_section: form.form_sections.first) }
+ let!(:option1) { FactoryBot.create(:question_option, question:, text: 'One', position: 1) }
+ let!(:option2) { FactoryBot.create(:question_option, question:, text: 'Two', position: 2) }
+ let!(:option3) { FactoryBot.create(:question_option, question:, text: 'Three', position: 3) }
+ let!(:option4) { FactoryBot.create(:question_option, question:, text: 'Four', position: 4) }
+
+ it 'reorders the question options' do
+ patch :sort, params: { question_id: question.id, form_id: form.id, question_option: [option2.id, option4.id, option3.id, option1.id] }, session: valid_session
+ question.reload
+ expect(question.question_options.order(:position).pluck(:text)).to eq(['Two', 'Four', 'Three', 'One'])
+ end
+
+ it 'validates that reorder is complete' do
+ patch :sort, params: { question_id: question.id, form_id: form.id, question_option: [option3.id, option4.id, option1.id] }, session: valid_session
+ expect(response).to have_http_status(:unprocessable_content)
+ end
+ end
+ end
end
diff --git a/spec/factories/question.rb b/spec/factories/question.rb
index 923964882..4c2a66ef1 100644
--- a/spec/factories/question.rb
+++ b/spec/factories/question.rb
@@ -73,5 +73,16 @@
FactoryBot.create(:question_option, question: dropdown_question, position: 4, text: 'Four')
end
end
+
+ trait :with_combobox_options do
+ text { 'Test Combo box Question' }
+ question_type { 'combobox' }
+ after(:create) do |combobox_question, _evaluator|
+ FactoryBot.create(:question_option, question: combobox_question, position: 1, text: 'One')
+ FactoryBot.create(:question_option, question: combobox_question, position: 2, text: 'Two')
+ FactoryBot.create(:question_option, question: combobox_question, position: 3, text: 'Three')
+ FactoryBot.create(:question_option, question: combobox_question, position: 4, text: 'Four')
+ end
+ end
end
end
diff --git a/spec/features/admin/forms_spec.rb b/spec/features/admin/forms_spec.rb
index 422c35792..8f2a65af6 100644
--- a/spec/features/admin/forms_spec.rb
+++ b/spec/features/admin/forms_spec.rb
@@ -1444,6 +1444,31 @@
end
end
end
+
+ describe 'reordering Question Options' do
+ let!(:question) { FactoryBot.create(:question, :with_combobox_options, form:, form_section: form.form_sections.first) }
+
+ before do
+ visit questions_admin_form_path(form)
+ wait_for_builder
+ source = find('.question-option', text: 'One')
+ target = find('.question-option', text: 'Two')
+ source.drag_to(target)
+ wait_for_ajax
+ end
+
+ it 'moves the first question option down' do
+ expect(page.all('.question-option')[0]).to have_content('Two')
+ expect(page.all('.question-option')[1]).to have_content('One')
+ end
+
+ it 'persists after refresh' do
+ visit questions_admin_form_path(form)
+ wait_for_builder
+ expect(page.all('.question-option')[0]).to have_content('Two')
+ expect(page.all('.question-option')[1]).to have_content('One')
+ end
+ end
end
end
end