From 62e1b15b22ab369367913b2d2756556ecbd2a37e Mon Sep 17 00:00:00 2001 From: Josep Date: Fri, 17 Jul 2026 23:47:20 +0200 Subject: [PATCH 01/10] Makefile --- Makefile | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..7c8523535 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +default: help + +## Help +help: + @printf "Available targets:\n\n" + @awk '/^[a-zA-Z\-\_0-9%:\\]+/ { \ + helpMessage = match(lastLine, /^## (.*)/); \ + if (helpMessage) { \ + helpCommand = $$1; \ + helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \ + gsub("\\\\", "", helpCommand); \ + gsub(":+$$", "", helpCommand); \ + printf " \x1b[32;01m%-35s\x1b[0m %s\n", helpCommand, helpMessage; \ + } \ + } \ + { lastLine = $$0 }' $(MAKEFILE_LIST) | sort -u + @printf "\n" + +## Start the server in dev mode +start: + npm run dev From c71a0617978d2fd075b480b8f0ddf65b89d64d72 Mon Sep 17 00:00:00 2001 From: Josep Date: Sat, 18 Jul 2026 01:39:35 +0200 Subject: [PATCH 02/10] Implement toCellValue to perform the cast, where it applies --- www/form/export.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/www/form/export.js b/www/form/export.js index 792fd5dc4..4b3c7b045 100644 --- a/www/form/export.js +++ b/www/form/export.js @@ -136,6 +136,28 @@ define([ }); array.push(questions); + /** + * Numeric "Text" answers must stay JS strings for the CSV output + * (escapeCSV treats falsy values as empty), but need to be real + * Numbers for the "array" format so the Sheet importer + * (method `makePatch` inside onlyoffice/inner.js) creates numeric + * cells instead of text cells. + * + * @returns {*|number} + */ + var toCellValue = function (key, str) { + var opts = form[key].opts; + if ( + form[key].type === 'input' && + opts && opts.type === 'number' && + str !== '' && + !isNaN(Number(str)) + ) { + return Number(str); + } + return str; + }; + sortedKeys.forEach(function (k) { var obj = answers[k]; csv += '\n'; From 003d584ab0a744b27129217f9e5e081e5bdfa88d Mon Sep 17 00:00:00 2001 From: Josep Date: Sat, 18 Jul 2026 01:41:53 +0200 Subject: [PATCH 03/10] Implement fix. Created a complimentary array in order not to mess with the other export use-cases (CSV and JSON). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unanswered/empty number questions stay as '' (not coerced to 0 or NaN). Types with a custom exportCSV (radio, checkbox, sort, poll) are left untouched — they're categorical/textual by nature and out of scope for this issue. No changes to CSV or JSON export use-cases. --- www/form/export.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/www/form/export.js b/www/form/export.js index 4b3c7b045..4a156caf7 100644 --- a/www/form/export.js +++ b/www/form/export.js @@ -165,23 +165,29 @@ define([ var msg = obj.msg || {}; var user = msg._userdata || {}; var line = []; + var arrLine = []; line.push(time); + arrLine.push(time); line.push(user.name || Messages.anonymous); + arrLine.push(user.name || Messages.anonymous); order.forEach(function (key) { var type = form[key].type; if (!TYPES[type]) { return; } // Ignore static types if (TYPES[type].exportCSV) { var res = TYPES[type].exportCSV(msg[key], form[key]); Array.prototype.push.apply(line, res); + Array.prototype.push.apply(arrLine, res); return; } - line.push(String(msg[key] || '')); + var str = String(msg[key] || ''); + line.push(str); + arrLine.push(toCellValue(key, str)); }); line.forEach(function (v, i) { if (i) { csv += ','; } csv += escapeCSV(v); }); - array.push(line); + array.push(arrLine); }); if (isArray) { return array; } return csv; From d41e781c902776f926c7e5a50212c067ee68508c Mon Sep 17 00:00:00 2001 From: Josep Date: Sat, 18 Jul 2026 01:46:58 +0200 Subject: [PATCH 04/10] rm makefile --- Makefile | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 Makefile diff --git a/Makefile b/Makefile deleted file mode 100644 index 7c8523535..000000000 --- a/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -default: help - -## Help -help: - @printf "Available targets:\n\n" - @awk '/^[a-zA-Z\-\_0-9%:\\]+/ { \ - helpMessage = match(lastLine, /^## (.*)/); \ - if (helpMessage) { \ - helpCommand = $$1; \ - helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \ - gsub("\\\\", "", helpCommand); \ - gsub(":+$$", "", helpCommand); \ - printf " \x1b[32;01m%-35s\x1b[0m %s\n", helpCommand, helpMessage; \ - } \ - } \ - { lastLine = $$0 }' $(MAKEFILE_LIST) | sort -u - @printf "\n" - -## Start the server in dev mode -start: - npm run dev From 412b709b24d4c5b8a88b22f696d2ce2137aa283d Mon Sep 17 00:00:00 2001 From: Josep Date: Sat, 18 Jul 2026 01:51:08 +0200 Subject: [PATCH 05/10] reduce duplication --- www/form/export.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/www/form/export.js b/www/form/export.js index 4a156caf7..e5988b9a5 100644 --- a/www/form/export.js +++ b/www/form/export.js @@ -164,12 +164,15 @@ define([ var time = new Date(obj.time).toISOString(); var msg = obj.msg || {}; var user = msg._userdata || {}; - var line = []; - var arrLine = []; - line.push(time); - arrLine.push(time); - line.push(user.name || Messages.anonymous); - arrLine.push(user.name || Messages.anonymous); + var username = user.name || Messages.anonymous; + var line = [ + time, + username, + ]; + var arrLine = [ + time, + username, + ]; order.forEach(function (key) { var type = form[key].type; if (!TYPES[type]) { return; } // Ignore static types From 81dcb1a1c76c5ad3f6c55f7c7d1d0ccf9b349893 Mon Sep 17 00:00:00 2001 From: Josep Date: Sat, 18 Jul 2026 01:57:12 +0200 Subject: [PATCH 06/10] annoying funding.json --- funding.json | 147 ++------------------------------------------------- 1 file changed, 4 insertions(+), 143 deletions(-) diff --git a/funding.json b/funding.json index 6840ae3f0..8f17bc63f 100644 --- a/funding.json +++ b/funding.json @@ -1,146 +1,7 @@ { - "version": "v1.0.0", - "entity": { - "type": "organisation", - "role": "owner", - "name": "XWiki SAS", - "email": "funding@xwiki.com", - "phone": "", - "description": "XWiki SAS is a European Open Source organization, maintaining Open Source Collaboration projects, XWiki and CryptPad. We believe that Open Source projects are key to getting back control on software, and that companies are important to allow the maintenance of Open Source projects.", - "webpageUrl": { - "url": "https://xwiki.com", - "wellKnown": "https://xwiki.com/.well-known/funding-manifest-urls" + "drips": { + "ethereum": { + "ownedBy": "0xa117114b3a22e038c1545043d610dbd4029355b1" } - }, - "projects": [ - { - "guid": "cryptpad", - "name": "CryptPad", - "description": "CryptPad is an end-to-end encrypted Collaboration Platform focused on providing real-time editing tools while protecting privacy.", - "webpageUrl": { - "url": "https://cryptpad.org", - "wellKnown": "https://cryptpad.org/.well-known/funding-manifest-urls" - }, - "repositoryUrl": { - "url": "https://github.com/cryptpad/cryptpad" - }, - "licenses": [ - "spdx:AGPL-3.0-or-later" - ], - "tags": [ - "privacy", - "e2ee", - "collaboration", - "realtimeediting", - "office", - "drive" - ] - } - ], - "funding": { - "channels": [ - { - "guid": "opencollective-cryptpad", - "type": "payment-provider", - "address": "https://opencollective.com/cryptpad", - "description": "OpenCollective CryptPad Account" - }, - { - "guid": "bank", - "type": "bank", - "address": "Contact XWiki SAS", - "description": "Donation by Bank Transfer or other means with XWiki SAS" - } - ], - "plans": [ - { - "guid": "cryptpad-individual-helper", - "status": "active", - "name": "Individual Donator - Starter", - "description": "Individual Donator would like to help us fund the maintenance and further improvement of the software.\n\n35000 people donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- an occasional user of CryptPad\n- a regular user with limited funds\n- somebody just wanting to give us a nudge", - "amount": 20, - "currency": "EUR", - "frequency": "yearly", - "channels": [ - "opencollective-cryptpad" - ] - }, - { - "guid": "cryptpad-individual-user", - "status": "active", - "name": "Individual Donator - User", - "description": "Individual Donator would like to help us fund the maintenance and further improvement of the software.\n\n14000 people donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- an regular user of CryptPad\n- a privacy believer who would like to help us\n- a personal instance owner with limited funds", - "amount": 50, - "currency": "EUR", - "frequency": "yearly", - "channels": [ - "opencollective-cryptpad" - ] - }, - { - "guid": "cryptpad-individual-sponsor", - "status": "active", - "name": "Individual Donator - Sponsor", - "description": "Individual Donator would like to help us fund the maintenance and further improvement of the software.\n\n3500 people donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- an regular user of CryptPad who really likes CryptPad\n- a privacy believer who wants to accelerate privacy online\n- a personal or community small size instance owner", - "amount": 200, - "currency": "EUR", - "frequency": "yearly", - "channels": [ - "opencollective-cryptpad" - ] - }, - { - "guid": "cryptpad-instanceowner-smallorg", - "status": "active", - "name": "Small Organization Using CryptPad", - "description": "Small Organization which would like to help us fund the maintenance and further improvement of the software.\n\n1500 organizations donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- a non-profit organization having some funds hosting CryptPad for their members\n- a small company using CryptPad for its users\n- any organizations wanting Privacy software to progress", - "amount": 500, - "currency": "EUR", - "frequency": "yearly", - "channels": [ - "opencollective-cryptpad" - ] - }, - { - "guid": "cryptpad-instanceowner-bigorg", - "status": "active", - "name": "Small Organization Using CryptPad", - "description": "Small Organization which would like to help us fund the maintenance and further improvement of the software.\n\n350 organizations donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- a commercial company using CryptPad regularly internally or externally\n- any organization having sufficient funds wanting to support Open Source Software doing the right thing\n", - "amount": 2000, - "currency": "EUR", - "frequency": "yearly", - "channels": [ - "opencollective-cryptpad", - "bank" - ] - }, - { - "guid": "cryptpad-instanceowner-bigorg", - "status": "active", - "name": "Organization using CryptPad heavily or as part of their offering", - "description": "Organization using CryptPad heavily or as part of it's commercial offering and which would like to significantly contribute to it's development.\n\n70 organizations donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- a commercial company using CryptPad heavily or as part of a commercial offering\n- foundations who would like to support the development.", - "amount": 10000, - "currency": "EUR", - "frequency": "yearly", - "channels": [ - "opencollective-cryptpad", - "bank" - ] - }, - { - "guid": "cryptpad-org-funder", - "status": "active", - "name": "Funding organization or large company supporting the maintenance and development of CryptPad", - "description": "This is meant for an organization investing in Privacy Preserving technologies and/or Open Source software which is important for the internet and wanting to support CryptPad with a grant. The grant can be for specific development useful for our roadmap or just for maintenance of the software.\n\n12 such grants would be needed to fund the current team.", - "amount": 50000, - "currency": "EUR", - "frequency": "one-time", - "channels": [ - "opencollective-cryptpad", - "bank" - ] - } - ], - "history": [] } - } +} \ No newline at end of file From 192fa4d0b1737c730917f0688cdf8974a6707fcb Mon Sep 17 00:00:00 2001 From: Josep Date: Sat, 18 Jul 2026 02:09:48 +0200 Subject: [PATCH 07/10] Add unit test --- package.json | 1 + scripts/tests/test-export-form.js | 111 ++++++++++++++++++++++++++++++ scripts/tests/unit.js | 5 ++ 3 files changed, 117 insertions(+) create mode 100644 scripts/tests/test-export-form.js create mode 100644 scripts/tests/unit.js diff --git a/package.json b/package.json index 2be1d641c..9c770b783 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "unused-translations": "node ./scripts/translations/unused-translations.js", "test": "node scripts/TestSelenium.js", "test-rpc": "cd scripts/tests && node test-rpc", + "test-unit": "node --test scripts/tests/unit.js", "evict-inactive": "node scripts/evict-inactive.js", "build": "node scripts/build.js", "clear": "node scripts/clear.js", diff --git a/scripts/tests/test-export-form.js b/scripts/tests/test-export-form.js new file mode 100644 index 000000000..7d5cf5eb3 --- /dev/null +++ b/scripts/tests/test-export-form.js @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +const test = require('node:test'); +const assert = require('node:assert/strict'); +const path = require('node:path'); + +const EXPORT_PATH = path.join(__dirname, '..', '..', 'www', 'form', 'export.js'); + +// www/form/export.js is an AMD module (`define([...], factory)`) meant to run +// in a browser. Shim the global `define` so it can be loaded under Node +// without a real AMD loader or DOM, and hand it just the bits of its two +// dependencies (common-util.js, messages.js) that Export.results touches. +const loadExportModule = function () { + const mockUtil = { + clone: function (obj) { return JSON.parse(JSON.stringify(obj)); } + }; + const mockMessages = { + form_poll_time: 'Time', + share_formView: 'Answer', + form_default: 'Untitled question', + anonymous: 'Anonymous' + }; + + let ExportModule; + global.define = function (deps, factory) { + const modules = deps.map(function (dep) { + if (/common-util/.test(dep)) { return mockUtil; } + if (/messages/.test(dep)) { return mockMessages; } + throw new Error('test-export-form: unexpected AMD dependency "' + dep + '"'); + }); + ExportModule = factory.apply(null, modules); + }; + + delete require.cache[require.resolve(EXPORT_PATH)]; + require(EXPORT_PATH); + delete global.define; + + return ExportModule; +}; + +const Export = loadExportModule(); + +// Mirrors the real TYPES map built in www/form/inner.js: "input" questions +// have no exportCSV handler, so they fall through Export.results' generic +// branch, which is exactly where the array/CSV split lives. +const TYPES = { input: {} }; + +const content = { + form: { + age: { type: 'input', q: 'Age', opts: { type: 'number' } }, + score: { type: 'input', q: 'Score', opts: { type: 'number' } }, + name: { type: 'input', q: 'Name', opts: { type: 'text' } } + } +}; +const order = ['age', 'score', 'name']; + +const answers = { + curve1: { + user1: { + time: 1700000000000, + msg: { + age: '42', + score: '0', + name: 'Bob' + } + } + } +}; + +// --------------------------- +// BEGIN TESTS FOR ISSUE #2214 +// --------------------------- + +test('"array" format casts numeric "input" answers to real JS numbers', function () { + const rows = Export.results(content, answers, TYPES, order, 'array'); + const dataRow = rows[1]; + + assert.equal(typeof dataRow[2], 'number'); + assert.equal(dataRow[2], 42); +}); + +test('"array" format keeps a numeric zero answer as the number 0, not an empty cell', function () { + const rows = Export.results(content, answers, TYPES, order, 'array'); + const dataRow = rows[1]; + + assert.equal(typeof dataRow[3], 'number'); + assert.equal(dataRow[3], 0); +}); + +test('"array" format leaves non-numeric "input" answers as strings', function () { + const rows = Export.results(content, answers, TYPES, order, 'array'); + const dataRow = rows[1]; + + assert.equal(typeof dataRow[4], 'string'); + assert.equal(dataRow[4], 'Bob'); +}); + +test('CSV format (default) still renders numeric answers as text, unaffected by the fix', function () { + const csv = Export.results(content, answers, TYPES, order); + const fields = csv.split('\n')[1].split(','); + + assert.equal(fields[2], '42'); + assert.equal(fields[3], '0'); // must render as "0", not an empty cell + assert.equal(fields[4], 'Bob'); +}); + +// --------------------------- +// END TESTS FOR ISSUE #2214 +// --------------------------- diff --git a/scripts/tests/unit.js b/scripts/tests/unit.js new file mode 100644 index 000000000..cde5d418e --- /dev/null +++ b/scripts/tests/unit.js @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +require("./test-export-form"); From 8ae2fdceeaf3ea38cf7bd7fd73237f2156dfc891 Mon Sep 17 00:00:00 2001 From: Josep Date: Sat, 18 Jul 2026 02:13:18 +0200 Subject: [PATCH 08/10] revert dummy change --- FUNDING.json | 2 +- funding.json | 147 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 144 insertions(+), 5 deletions(-) diff --git a/FUNDING.json b/FUNDING.json index 8f17bc63f..7b8fa8f72 100644 --- a/FUNDING.json +++ b/FUNDING.json @@ -4,4 +4,4 @@ "ownedBy": "0xa117114b3a22e038c1545043d610dbd4029355b1" } } -} \ No newline at end of file +} diff --git a/funding.json b/funding.json index 8f17bc63f..6840ae3f0 100644 --- a/funding.json +++ b/funding.json @@ -1,7 +1,146 @@ { - "drips": { - "ethereum": { - "ownedBy": "0xa117114b3a22e038c1545043d610dbd4029355b1" + "version": "v1.0.0", + "entity": { + "type": "organisation", + "role": "owner", + "name": "XWiki SAS", + "email": "funding@xwiki.com", + "phone": "", + "description": "XWiki SAS is a European Open Source organization, maintaining Open Source Collaboration projects, XWiki and CryptPad. We believe that Open Source projects are key to getting back control on software, and that companies are important to allow the maintenance of Open Source projects.", + "webpageUrl": { + "url": "https://xwiki.com", + "wellKnown": "https://xwiki.com/.well-known/funding-manifest-urls" } + }, + "projects": [ + { + "guid": "cryptpad", + "name": "CryptPad", + "description": "CryptPad is an end-to-end encrypted Collaboration Platform focused on providing real-time editing tools while protecting privacy.", + "webpageUrl": { + "url": "https://cryptpad.org", + "wellKnown": "https://cryptpad.org/.well-known/funding-manifest-urls" + }, + "repositoryUrl": { + "url": "https://github.com/cryptpad/cryptpad" + }, + "licenses": [ + "spdx:AGPL-3.0-or-later" + ], + "tags": [ + "privacy", + "e2ee", + "collaboration", + "realtimeediting", + "office", + "drive" + ] + } + ], + "funding": { + "channels": [ + { + "guid": "opencollective-cryptpad", + "type": "payment-provider", + "address": "https://opencollective.com/cryptpad", + "description": "OpenCollective CryptPad Account" + }, + { + "guid": "bank", + "type": "bank", + "address": "Contact XWiki SAS", + "description": "Donation by Bank Transfer or other means with XWiki SAS" + } + ], + "plans": [ + { + "guid": "cryptpad-individual-helper", + "status": "active", + "name": "Individual Donator - Starter", + "description": "Individual Donator would like to help us fund the maintenance and further improvement of the software.\n\n35000 people donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- an occasional user of CryptPad\n- a regular user with limited funds\n- somebody just wanting to give us a nudge", + "amount": 20, + "currency": "EUR", + "frequency": "yearly", + "channels": [ + "opencollective-cryptpad" + ] + }, + { + "guid": "cryptpad-individual-user", + "status": "active", + "name": "Individual Donator - User", + "description": "Individual Donator would like to help us fund the maintenance and further improvement of the software.\n\n14000 people donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- an regular user of CryptPad\n- a privacy believer who would like to help us\n- a personal instance owner with limited funds", + "amount": 50, + "currency": "EUR", + "frequency": "yearly", + "channels": [ + "opencollective-cryptpad" + ] + }, + { + "guid": "cryptpad-individual-sponsor", + "status": "active", + "name": "Individual Donator - Sponsor", + "description": "Individual Donator would like to help us fund the maintenance and further improvement of the software.\n\n3500 people donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- an regular user of CryptPad who really likes CryptPad\n- a privacy believer who wants to accelerate privacy online\n- a personal or community small size instance owner", + "amount": 200, + "currency": "EUR", + "frequency": "yearly", + "channels": [ + "opencollective-cryptpad" + ] + }, + { + "guid": "cryptpad-instanceowner-smallorg", + "status": "active", + "name": "Small Organization Using CryptPad", + "description": "Small Organization which would like to help us fund the maintenance and further improvement of the software.\n\n1500 organizations donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- a non-profit organization having some funds hosting CryptPad for their members\n- a small company using CryptPad for its users\n- any organizations wanting Privacy software to progress", + "amount": 500, + "currency": "EUR", + "frequency": "yearly", + "channels": [ + "opencollective-cryptpad" + ] + }, + { + "guid": "cryptpad-instanceowner-bigorg", + "status": "active", + "name": "Small Organization Using CryptPad", + "description": "Small Organization which would like to help us fund the maintenance and further improvement of the software.\n\n350 organizations donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- a commercial company using CryptPad regularly internally or externally\n- any organization having sufficient funds wanting to support Open Source Software doing the right thing\n", + "amount": 2000, + "currency": "EUR", + "frequency": "yearly", + "channels": [ + "opencollective-cryptpad", + "bank" + ] + }, + { + "guid": "cryptpad-instanceowner-bigorg", + "status": "active", + "name": "Organization using CryptPad heavily or as part of their offering", + "description": "Organization using CryptPad heavily or as part of it's commercial offering and which would like to significantly contribute to it's development.\n\n70 organizations donating this every year would be needed to fund the current team to allow CryptPad to be independent from outside grants.\n\nThis plans corresponds to:\n\n- a commercial company using CryptPad heavily or as part of a commercial offering\n- foundations who would like to support the development.", + "amount": 10000, + "currency": "EUR", + "frequency": "yearly", + "channels": [ + "opencollective-cryptpad", + "bank" + ] + }, + { + "guid": "cryptpad-org-funder", + "status": "active", + "name": "Funding organization or large company supporting the maintenance and development of CryptPad", + "description": "This is meant for an organization investing in Privacy Preserving technologies and/or Open Source software which is important for the internet and wanting to support CryptPad with a grant. The grant can be for specific development useful for our roadmap or just for maintenance of the software.\n\n12 such grants would be needed to fund the current team.", + "amount": 50000, + "currency": "EUR", + "frequency": "one-time", + "channels": [ + "opencollective-cryptpad", + "bank" + ] + } + ], + "history": [] } -} \ No newline at end of file + } From 10f2298f1c68330c46ad6fec17cef3836097bccd Mon Sep 17 00:00:00 2001 From: Josep Date: Sat, 18 Jul 2026 02:14:18 +0200 Subject: [PATCH 09/10] . --- FUNDING.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FUNDING.json b/FUNDING.json index 7b8fa8f72..8f17bc63f 100644 --- a/FUNDING.json +++ b/FUNDING.json @@ -4,4 +4,4 @@ "ownedBy": "0xa117114b3a22e038c1545043d610dbd4029355b1" } } -} +} \ No newline at end of file From 8269fed2cf7d4d9973b7f66ee936be005e76851f Mon Sep 17 00:00:00 2001 From: Josep Date: Wed, 9 Sep 2026 12:08:29 +0200 Subject: [PATCH 10/10] remove extra changes as requested --- package.json | 1 - scripts/tests/test-export-form.js | 111 ------------------------------ scripts/tests/unit.js | 5 -- 3 files changed, 117 deletions(-) delete mode 100644 scripts/tests/test-export-form.js delete mode 100644 scripts/tests/unit.js diff --git a/package.json b/package.json index 9c770b783..2be1d641c 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,6 @@ "unused-translations": "node ./scripts/translations/unused-translations.js", "test": "node scripts/TestSelenium.js", "test-rpc": "cd scripts/tests && node test-rpc", - "test-unit": "node --test scripts/tests/unit.js", "evict-inactive": "node scripts/evict-inactive.js", "build": "node scripts/build.js", "clear": "node scripts/clear.js", diff --git a/scripts/tests/test-export-form.js b/scripts/tests/test-export-form.js deleted file mode 100644 index 7d5cf5eb3..000000000 --- a/scripts/tests/test-export-form.js +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -const test = require('node:test'); -const assert = require('node:assert/strict'); -const path = require('node:path'); - -const EXPORT_PATH = path.join(__dirname, '..', '..', 'www', 'form', 'export.js'); - -// www/form/export.js is an AMD module (`define([...], factory)`) meant to run -// in a browser. Shim the global `define` so it can be loaded under Node -// without a real AMD loader or DOM, and hand it just the bits of its two -// dependencies (common-util.js, messages.js) that Export.results touches. -const loadExportModule = function () { - const mockUtil = { - clone: function (obj) { return JSON.parse(JSON.stringify(obj)); } - }; - const mockMessages = { - form_poll_time: 'Time', - share_formView: 'Answer', - form_default: 'Untitled question', - anonymous: 'Anonymous' - }; - - let ExportModule; - global.define = function (deps, factory) { - const modules = deps.map(function (dep) { - if (/common-util/.test(dep)) { return mockUtil; } - if (/messages/.test(dep)) { return mockMessages; } - throw new Error('test-export-form: unexpected AMD dependency "' + dep + '"'); - }); - ExportModule = factory.apply(null, modules); - }; - - delete require.cache[require.resolve(EXPORT_PATH)]; - require(EXPORT_PATH); - delete global.define; - - return ExportModule; -}; - -const Export = loadExportModule(); - -// Mirrors the real TYPES map built in www/form/inner.js: "input" questions -// have no exportCSV handler, so they fall through Export.results' generic -// branch, which is exactly where the array/CSV split lives. -const TYPES = { input: {} }; - -const content = { - form: { - age: { type: 'input', q: 'Age', opts: { type: 'number' } }, - score: { type: 'input', q: 'Score', opts: { type: 'number' } }, - name: { type: 'input', q: 'Name', opts: { type: 'text' } } - } -}; -const order = ['age', 'score', 'name']; - -const answers = { - curve1: { - user1: { - time: 1700000000000, - msg: { - age: '42', - score: '0', - name: 'Bob' - } - } - } -}; - -// --------------------------- -// BEGIN TESTS FOR ISSUE #2214 -// --------------------------- - -test('"array" format casts numeric "input" answers to real JS numbers', function () { - const rows = Export.results(content, answers, TYPES, order, 'array'); - const dataRow = rows[1]; - - assert.equal(typeof dataRow[2], 'number'); - assert.equal(dataRow[2], 42); -}); - -test('"array" format keeps a numeric zero answer as the number 0, not an empty cell', function () { - const rows = Export.results(content, answers, TYPES, order, 'array'); - const dataRow = rows[1]; - - assert.equal(typeof dataRow[3], 'number'); - assert.equal(dataRow[3], 0); -}); - -test('"array" format leaves non-numeric "input" answers as strings', function () { - const rows = Export.results(content, answers, TYPES, order, 'array'); - const dataRow = rows[1]; - - assert.equal(typeof dataRow[4], 'string'); - assert.equal(dataRow[4], 'Bob'); -}); - -test('CSV format (default) still renders numeric answers as text, unaffected by the fix', function () { - const csv = Export.results(content, answers, TYPES, order); - const fields = csv.split('\n')[1].split(','); - - assert.equal(fields[2], '42'); - assert.equal(fields[3], '0'); // must render as "0", not an empty cell - assert.equal(fields[4], 'Bob'); -}); - -// --------------------------- -// END TESTS FOR ISSUE #2214 -// --------------------------- diff --git a/scripts/tests/unit.js b/scripts/tests/unit.js deleted file mode 100644 index cde5d418e..000000000 --- a/scripts/tests/unit.js +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -require("./test-export-form");