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