@mcpher/gas-fakes 1.2.19 → 1.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clasp.json +4 -0
- package/.claspignore +6 -0
- package/ex-logo.png +0 -0
- package/gas-fakes.js +4 -4
- package/logo.png +0 -0
- package/package.json +2 -1
- package/run.js +35 -0
- package/setup.js +65 -55
- package/src/Code.js +3 -0
- package/src/appsscript.json +5 -0
- package/src/services/advforms/fakeadvforms.js +2 -1
- package/src/services/advforms/fakeadvformsform.js +12 -0
- package/src/services/advforms/fakeresponses.js +30 -0
- package/src/services/formapp/fakecheckboxgriditem.js +105 -0
- package/src/services/formapp/fakecheckboxitem.js +2 -110
- package/src/services/formapp/fakechoice.js +36 -19
- package/src/services/formapp/fakechoiceitem.js +67 -0
- package/src/services/formapp/fakeform.js +332 -17
- package/src/services/formapp/fakeformapp.js +1 -1
- package/src/services/formapp/fakeformitem.js +62 -10
- package/src/services/formapp/fakeformresponse.js +84 -0
- package/src/services/formapp/fakegriditem.js +105 -0
- package/src/services/formapp/fakeitemresponse.js +56 -0
- package/src/services/formapp/fakelistitem.js +124 -0
- package/src/services/formapp/fakemultiplechoiceitem.js +25 -0
- package/src/services/formapp/fakepagebreakitem.js +54 -0
- package/src/services/formapp/fakescaleitem.js +121 -0
- package/src/services/formapp/fakesectionheaderitem.js +25 -0
- package/src/services/formapp/faketextitem.js +20 -0
- package/src/services/formapp/formitems.js +6 -1
- package/src/support/sxforms.js +8 -4
- package/src/support/syncit.js +2 -2
package/.clasp.json
ADDED
package/.claspignore
ADDED
package/ex-logo.png
ADDED
|
Binary file
|
package/gas-fakes.js
CHANGED
|
@@ -31,7 +31,7 @@ const VERSION = pjson.version;
|
|
|
31
31
|
// CONSTANTS & UTILITIES
|
|
32
32
|
// -----------------------------------------------------------------------------
|
|
33
33
|
|
|
34
|
-
const CLI_VERSION = "0.0.
|
|
34
|
+
const CLI_VERSION = "0.0.13";
|
|
35
35
|
const MCP_VERSION = "0.0.3";
|
|
36
36
|
const execAsync = promisify(exec);
|
|
37
37
|
|
|
@@ -475,9 +475,9 @@ async function main() {
|
|
|
475
475
|
// We check if the command is not one of the others.
|
|
476
476
|
const knownCommands = program.commands.map((cmd) => cmd.name());
|
|
477
477
|
if (!process.argv.slice(2).some((arg) => knownCommands.includes(arg))) {
|
|
478
|
-
console.error(
|
|
479
|
-
|
|
480
|
-
);
|
|
478
|
+
// console.error(
|
|
479
|
+
// "Error: You must provide a script via --filename or --script, or use a specific command (e.g., init, auth, mcp)."
|
|
480
|
+
// );
|
|
481
481
|
program.help();
|
|
482
482
|
process.exit(1);
|
|
483
483
|
}
|
package/logo.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"mime": "^4.0.7",
|
|
21
21
|
"prompts": "^2.4.2",
|
|
22
22
|
"sleep-synchronously": "^2.0.0",
|
|
23
|
+
"tape": "^5.9.0",
|
|
23
24
|
"unzipper": "^0.12.3",
|
|
24
25
|
"zod": "^3.25.76"
|
|
25
26
|
},
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
},
|
|
33
34
|
"name": "@mcpher/gas-fakes",
|
|
34
35
|
"author": "bruce mcpherson",
|
|
35
|
-
"version": "1.2.
|
|
36
|
+
"version": "1.2.21",
|
|
36
37
|
"license": "MIT",
|
|
37
38
|
"main": "main.js",
|
|
38
39
|
"description": "A proof of concept implementation of Apps Script Environment on Node",
|
package/run.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import vm from 'vm';
|
|
4
|
+
|
|
5
|
+
// Import gas-fakes to initialize the environment
|
|
6
|
+
import '@mcpher/gas-fakes';
|
|
7
|
+
|
|
8
|
+
// Create a sandboxed context
|
|
9
|
+
const context = vm.createContext({
|
|
10
|
+
ScriptApp: global.ScriptApp,
|
|
11
|
+
console: console,
|
|
12
|
+
// Add other GAS globals you need here (e.g., DocumentApp, SpreadsheetApp)
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Read the user's script file
|
|
16
|
+
const scriptPath = path.resolve('src/Code.js');
|
|
17
|
+
const scriptCode = fs.readFileSync(scriptPath, 'utf8');
|
|
18
|
+
|
|
19
|
+
// Run the user's script in the sandbox to define the functions
|
|
20
|
+
vm.runInContext(scriptCode, context);
|
|
21
|
+
|
|
22
|
+
// Define and run the calling logic to execute a function from the script
|
|
23
|
+
const callingCode = `
|
|
24
|
+
// 1. Enable sandbox mode for safety
|
|
25
|
+
ScriptApp.__behavior.sandBoxMode = true;
|
|
26
|
+
|
|
27
|
+
// 2. Call the specific function from your Code.js file
|
|
28
|
+
// myFunction();
|
|
29
|
+
|
|
30
|
+
// 3. Clean up the gas-fakes environment
|
|
31
|
+
ScriptApp.__behavior.trash();
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
// vm.runInContext(callingCode, context);
|
|
35
|
+
console.log("Project initialized. Edit run.js to execute a function.");
|
package/setup.js
CHANGED
|
@@ -145,17 +145,18 @@ export async function initializeConfiguration(options = {}) {
|
|
|
145
145
|
type: "text",
|
|
146
146
|
name: "GCP_PROJECT_ID",
|
|
147
147
|
message: "Enter your GCP Project ID",
|
|
148
|
-
initial:
|
|
148
|
+
initial:
|
|
149
|
+
existingConfig.GCP_PROJECT_ID || process.env.GOOGLE_CLOUD_PROJECT,
|
|
149
150
|
},
|
|
150
151
|
{
|
|
151
152
|
type: "text",
|
|
152
153
|
name: "DRIVE_TEST_FILE_ID",
|
|
153
|
-
message:
|
|
154
|
+
message:
|
|
155
|
+
"Enter a test Drive file ID for authentication checks (optional)",
|
|
154
156
|
initial: existingConfig.DRIVE_TEST_FILE_ID || "",
|
|
155
157
|
},
|
|
156
158
|
];
|
|
157
159
|
|
|
158
|
-
|
|
159
160
|
const basicInfoResponses = await prompts(basicInfoQuestions);
|
|
160
161
|
if (typeof basicInfoResponses.GCP_PROJECT_ID === "undefined") {
|
|
161
162
|
console.log("Initialization cancelled.");
|
|
@@ -176,7 +177,6 @@ export async function initializeConfiguration(options = {}) {
|
|
|
176
177
|
DEFAULT_SCOPES_VALUES.forEach((scope) => console.log(` - ${scope}`));
|
|
177
178
|
responses.DEFAULT_SCOPES = DEFAULT_SCOPES_VALUES;
|
|
178
179
|
|
|
179
|
-
|
|
180
180
|
const extraScopeQuestion = {
|
|
181
181
|
type: "multiselect",
|
|
182
182
|
name: "EXTRA_SCOPES",
|
|
@@ -220,10 +220,11 @@ export async function initializeConfiguration(options = {}) {
|
|
|
220
220
|
title: "Gmail compose",
|
|
221
221
|
value: "https://www.googleapis.com/auth/gmail.compose",
|
|
222
222
|
},
|
|
223
|
-
|
|
224
223
|
].map((scope) => ({
|
|
225
224
|
...scope,
|
|
226
|
-
title: scope.sensitivity
|
|
225
|
+
title: scope.sensitivity
|
|
226
|
+
? `[${scope.sensitivity}] ${scope.title}`
|
|
227
|
+
: scope.title,
|
|
227
228
|
// because we always need drive for ant extra scopes
|
|
228
229
|
selected:
|
|
229
230
|
existingExtraScopes.length > 0
|
|
@@ -252,15 +253,17 @@ export async function initializeConfiguration(options = {}) {
|
|
|
252
253
|
selectedExtraScopes.includes(s.value)
|
|
253
254
|
);
|
|
254
255
|
|
|
255
|
-
|
|
256
256
|
if (usesSensitiveScopes) {
|
|
257
257
|
console.log("\n--------------------------------------------------");
|
|
258
|
-
console.log(
|
|
259
|
-
|
|
258
|
+
console.log(
|
|
259
|
+
"You have selected sensitive or restricted scopes. Google requires an OAuth client credential file for these."
|
|
260
|
+
);
|
|
261
|
+
console.log(
|
|
262
|
+
"See the getting started guide https://github.com/brucemcpherson/gas-fakes/blob/main/GETTING_STARTED.md for how."
|
|
263
|
+
);
|
|
260
264
|
console.log("--------------------------------------------------");
|
|
261
265
|
}
|
|
262
266
|
|
|
263
|
-
|
|
264
267
|
const clientCredentialQuestion = {
|
|
265
268
|
type: "text",
|
|
266
269
|
name: "CLIENT_CREDENTIAL_FILE",
|
|
@@ -306,45 +309,46 @@ export async function initializeConfiguration(options = {}) {
|
|
|
306
309
|
? `\n - Extra: [${responses.EXTRA_SCOPES.join(", ")}]`
|
|
307
310
|
: "\n - Extra: [None]";
|
|
308
311
|
|
|
309
|
-
const remainingQuestions = [
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
) > -1
|
|
329
|
-
? ["CONSOLE", "CLOUD", "BOTH", "NONE"].indexOf(
|
|
312
|
+
const remainingQuestions = [
|
|
313
|
+
{
|
|
314
|
+
type: "toggle",
|
|
315
|
+
name: "QUIET",
|
|
316
|
+
message: "Run gas-fakes package in quiet mode",
|
|
317
|
+
initial: existingConfig.QUIET === "true" ? true : false,
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
type: "select",
|
|
321
|
+
name: "LOG_DESTINATION",
|
|
322
|
+
message: `Selected Scopes:${defaultScopesDisplay}${extraScopesDisplay}\n\nEnter logging destination`,
|
|
323
|
+
choices: [
|
|
324
|
+
{ title: "CONSOLE", value: "CONSOLE" },
|
|
325
|
+
{ title: "CLOUD", value: "CLOUD" },
|
|
326
|
+
{ title: "BOTH", value: "BOTH" },
|
|
327
|
+
{ title: "NONE", value: "NONE" },
|
|
328
|
+
],
|
|
329
|
+
initial:
|
|
330
|
+
["CONSOLE", "CLOUD", "BOTH", "NONE"].indexOf(
|
|
330
331
|
existingConfig.LOG_DESTINATION
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
332
|
+
) > -1
|
|
333
|
+
? ["CONSOLE", "CLOUD", "BOTH", "NONE"].indexOf(
|
|
334
|
+
existingConfig.LOG_DESTINATION
|
|
335
|
+
)
|
|
336
|
+
: 0,
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
type: "select",
|
|
340
|
+
name: "STORE_TYPE",
|
|
341
|
+
message: "Enter storage type",
|
|
342
|
+
choices: [
|
|
343
|
+
{ title: "FILE", value: "FILE" },
|
|
344
|
+
{ title: "UPSTASH", value: "UPSTASH" },
|
|
345
|
+
],
|
|
346
|
+
initial:
|
|
347
|
+
["FILE", "UPSTASH"].indexOf(existingConfig.STORE_TYPE?.toUpperCase()) >
|
|
344
348
|
-1
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
349
|
+
? ["FILE", "UPSTASH"].indexOf(existingConfig.STORE_TYPE.toUpperCase())
|
|
350
|
+
: 0,
|
|
351
|
+
},
|
|
348
352
|
];
|
|
349
353
|
|
|
350
354
|
const remainingResponses = await prompts(remainingQuestions);
|
|
@@ -406,16 +410,22 @@ export async function initializeConfiguration(options = {}) {
|
|
|
406
410
|
}
|
|
407
411
|
|
|
408
412
|
// --- File Writing Logic ---
|
|
409
|
-
console.log(`Writing configuration to ${envPath}...`);
|
|
410
|
-
const inits =
|
|
413
|
+
console.log(`Writing configuration to "${envPath}"...`);
|
|
414
|
+
const inits =
|
|
415
|
+
responses.STORE_TYPE !== "UPSTASH"
|
|
416
|
+
? { UPSTASH_REDIS_REST_TOKEN: "", UPSTASH_REDIS_REST_URL: "" }
|
|
417
|
+
: {};
|
|
411
418
|
const finalConfig = { ...existingConfig, ...responses, ...inits };
|
|
412
419
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
420
|
+
console.log("\n------------------ Final output ------------------");
|
|
421
|
+
const envContent = Reflect.ownKeys(finalConfig)
|
|
422
|
+
.map((key) => {
|
|
423
|
+
const item = finalConfig[key];
|
|
424
|
+
const res = `${key}="${(item.toString() || "").trim()}"`;
|
|
425
|
+
console.log(res);
|
|
426
|
+
return res;
|
|
427
|
+
})
|
|
428
|
+
.join("\n");
|
|
419
429
|
/* replacing this to include existing values
|
|
420
430
|
let envContent = `
|
|
421
431
|
# Google Cloud Project ID (required)
|
package/src/Code.js
ADDED
|
@@ -4,6 +4,7 @@ import { signatureArgs, ssError, gError } from '../../support/helpers.js';
|
|
|
4
4
|
|
|
5
5
|
import { Proxies } from '../../support/proxies.js';
|
|
6
6
|
import { Utils } from '../../support/utils.js';
|
|
7
|
+
import { newFakeResponses } from './fakeresponses.js';
|
|
7
8
|
const { is } = Utils
|
|
8
9
|
|
|
9
10
|
export const newFakeAdvFormsForm = (...args) => Proxies.guard(new FakeAdvFormsForm(...args))
|
|
@@ -13,6 +14,17 @@ class FakeAdvFormsForm extends FakeAdvResource {
|
|
|
13
14
|
super(mainService, 'forms', Syncit.fxForms);
|
|
14
15
|
this.forms = mainService;
|
|
15
16
|
this.__fakeObjectType = 'Forms.Forms';
|
|
17
|
+
this.__responses = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @returns {import('./fakeresponses.js').FakeResponses}
|
|
22
|
+
*/
|
|
23
|
+
get Responses() {
|
|
24
|
+
if (!this.__responses) {
|
|
25
|
+
this.__responses = newFakeResponses(this);
|
|
26
|
+
}
|
|
27
|
+
return this.__responses;
|
|
16
28
|
}
|
|
17
29
|
|
|
18
30
|
/**
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Proxies } from '../../support/proxies.js';
|
|
2
|
+
import { Utils } from '../../support/utils.js';
|
|
3
|
+
|
|
4
|
+
export const newFakeResponses = (...args) => {
|
|
5
|
+
return Proxies.guard(new FakeResponses(...args));
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @class FakeResponses
|
|
10
|
+
* @see https://developers.google.com/forms/api/reference/rest/v1/forms.responses
|
|
11
|
+
*/
|
|
12
|
+
export class FakeResponses {
|
|
13
|
+
constructor(mainService) {
|
|
14
|
+
this.forms = mainService;
|
|
15
|
+
this.__fakeObjectType = 'Forms.Responses';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Lists the responses of a form.
|
|
20
|
+
* @param {string} formId The form's ID.
|
|
21
|
+
* @returns {{responses: object[]}} A list of responses.
|
|
22
|
+
*/
|
|
23
|
+
list(formId) {
|
|
24
|
+
// this.forms is the FakeAdvFormsForm instance, which has the _call method
|
|
25
|
+
// from FakeAdvResource to interact with the syncit data source.
|
|
26
|
+
// We use subProp to specify the nested 'responses' object, following the library's pattern.
|
|
27
|
+
const { data } = this.forms._call('list', { formId }, null, 'responses');
|
|
28
|
+
return data || { responses: [] };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Proxies } from '../../support/proxies.js';
|
|
2
|
+
import { FakeFormItem } from './fakeformitem.js';
|
|
3
|
+
import { registerFormItem } from './formitemregistry.js';
|
|
4
|
+
import { signatureArgs } from '../../support/helpers.js';
|
|
5
|
+
import { Utils } from '../../support/utils.js';
|
|
6
|
+
import { ItemType } from '../enums/formsenums.js';
|
|
7
|
+
|
|
8
|
+
export const newFakeCheckboxGridItem = (...args) => {
|
|
9
|
+
return Proxies.guard(new FakeCheckboxGridItem(...args));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @class FakeCheckboxGridItem
|
|
14
|
+
* A fake for the CheckboxGridItem class in Apps Script.
|
|
15
|
+
* @see https://developers.google.com/apps-script/reference/forms/checkbox-grid-item
|
|
16
|
+
*/
|
|
17
|
+
export class FakeCheckboxGridItem extends FakeFormItem {
|
|
18
|
+
constructor(form, itemId) {
|
|
19
|
+
super(form, itemId);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Gets the values for every column in the grid.
|
|
24
|
+
* @returns {string[]} an array of column values
|
|
25
|
+
*/
|
|
26
|
+
getColumns() {
|
|
27
|
+
const choiceQuestion = this.__resource.questionGroupItem?.grid?.columns;
|
|
28
|
+
if (!choiceQuestion || !choiceQuestion.options) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
return choiceQuestion.options.map(option => option.value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Gets the values for every row in the grid.
|
|
36
|
+
* @returns {string[]} an array of row values
|
|
37
|
+
*/
|
|
38
|
+
getRows() {
|
|
39
|
+
const questions = this.__resource.questionGroupItem?.questions;
|
|
40
|
+
if (!questions) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
return questions.map(question => question.rowQuestion?.title || null).filter(f => f);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Sets the columns of the grid based on an array of values.
|
|
48
|
+
* @param {string[]} columns an array of column values
|
|
49
|
+
* @returns {FakeCheckboxGridItem} this item, for chaining
|
|
50
|
+
*/
|
|
51
|
+
setColumns(columns) {
|
|
52
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'CheckboxGridItem.setColumns');
|
|
53
|
+
if (nargs !== 1 || !Utils.is.array(columns) || !columns.every(Utils.is.string)) {
|
|
54
|
+
matchThrow('Invalid arguments: expected a string array.');
|
|
55
|
+
}
|
|
56
|
+
if (columns.length === 0) {
|
|
57
|
+
throw new Error('The array of columns cannot be empty.');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const updatedResource = JSON.parse(JSON.stringify(this.__resource));
|
|
61
|
+
updatedResource.questionGroupItem.grid.columns.options = columns.map(c => ({ value: c }));
|
|
62
|
+
|
|
63
|
+
const updateRequest = Forms.newRequest().setUpdateItem({
|
|
64
|
+
item: updatedResource,
|
|
65
|
+
location: { index: this.getIndex() },
|
|
66
|
+
updateMask: 'questionGroupItem.grid.columns.options',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return this.__update(updateRequest);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Sets the rows of the grid based on an array of values.
|
|
74
|
+
* @param {string[]} rows an array of row values
|
|
75
|
+
* @returns {FakeCheckboxGridItem} this item, for chaining
|
|
76
|
+
*/
|
|
77
|
+
setRows(rows) {
|
|
78
|
+
const { nargs, matchThrow } = signatureArgs(arguments, 'CheckboxGridItem.setRows');
|
|
79
|
+
if (nargs !== 1 || !Utils.is.array(rows) || !rows.every(Utils.is.string)) {
|
|
80
|
+
matchThrow('Invalid arguments: expected a string array.');
|
|
81
|
+
}
|
|
82
|
+
if (rows.length === 0) {
|
|
83
|
+
throw new Error('The array of rows cannot be empty.');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const updatedResource = JSON.parse(JSON.stringify(this.__resource));
|
|
87
|
+
updatedResource.questionGroupItem.questions = rows.map(r => ({
|
|
88
|
+
rowQuestion: { title: r },
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
const updateRequest = Forms.newRequest().setUpdateItem({
|
|
92
|
+
item: updatedResource,
|
|
93
|
+
location: { index: this.getIndex() },
|
|
94
|
+
updateMask: 'questionGroupItem.questions',
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return this.__update(updateRequest);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
toString() {
|
|
101
|
+
return 'CheckboxGridItem';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
registerFormItem(ItemType.CHECKBOX_GRID, newFakeCheckboxGridItem);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Proxies } from '../../support/proxies.js';
|
|
2
|
-
import {
|
|
2
|
+
import { FakeChoiceItem } from './fakechoiceitem.js';
|
|
3
3
|
import { signatureArgs } from '../../support/helpers.js';
|
|
4
4
|
import { Utils } from '../../support/utils.js';
|
|
5
5
|
const { is } = Utils;
|
|
@@ -16,7 +16,7 @@ export const newFakeCheckboxItem = (...args) => {
|
|
|
16
16
|
* A fake for the CheckboxItem class in Apps Script.
|
|
17
17
|
* @see https://developers.google.com/apps-script/reference/forms/checkbox-item
|
|
18
18
|
*/
|
|
19
|
-
export class FakeCheckboxItem extends
|
|
19
|
+
export class FakeCheckboxItem extends FakeChoiceItem {
|
|
20
20
|
/**
|
|
21
21
|
* @param {import('./fakeform.js').FakeForm} form The parent form.
|
|
22
22
|
* @param {string} itemId The ID of the item.
|
|
@@ -25,114 +25,6 @@ export class FakeCheckboxItem extends FakeFormItem {
|
|
|
25
25
|
super(form, itemId);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/**
|
|
29
|
-
* Creates a new choice for this item.
|
|
30
|
-
* @param {string} value The value of the choice.
|
|
31
|
-
* @param {boolean} [isCorrect] Whether the choice is correct.
|
|
32
|
-
* @returns {import('./fakechoice.js').FakeChoice} The new choice.
|
|
33
|
-
*/
|
|
34
|
-
createChoice(value, isCorrect) {
|
|
35
|
-
const { nargs, matchThrow } = signatureArgs(arguments, 'CheckboxItem.createChoice');
|
|
36
|
-
if (nargs < 1 || nargs > 2 || !is.string(value) || (nargs === 2 && !is.boolean(isCorrect))) {
|
|
37
|
-
matchThrow('Invalid arguments');
|
|
38
|
-
}
|
|
39
|
-
// For now, navigation is not supported.
|
|
40
|
-
return newFakeChoice(value, isCorrect || false, null, null);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Gets the choices for this item.
|
|
45
|
-
* @returns {import('./fakechoice.js').FakeChoice[]} An array of Choice objects.
|
|
46
|
-
*/
|
|
47
|
-
getChoices() {
|
|
48
|
-
const choiceQuestion = this.__resource.questionItem?.question?.choiceQuestion;
|
|
49
|
-
if (!choiceQuestion || !choiceQuestion.options) {
|
|
50
|
-
return [];
|
|
51
|
-
}
|
|
52
|
-
return choiceQuestion.options.map(option => {
|
|
53
|
-
return newFakeChoice(option.value, false, null, null);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Sets the choices for this item.
|
|
59
|
-
* @param {import('./fakechoice.js').FakeChoice[]} choices An array of Choice objects.
|
|
60
|
-
* @returns {FakeCheckboxItem} This item, for chaining.
|
|
61
|
-
*/
|
|
62
|
-
setChoices(choices) {
|
|
63
|
-
const { nargs, matchThrow } = signatureArgs(arguments, 'CheckboxItem.setChoices');
|
|
64
|
-
if (nargs !== 1 || !is.array(choices) || !choices.every(c => c.toString() === 'Choice')) {
|
|
65
|
-
matchThrow('Invalid arguments: expected an array of Choice objects.');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const newOptions = choices.map(choice => Forms.newOption().setValue(choice.getValue()));
|
|
69
|
-
const currentIndex = this._getCurrentIndex();
|
|
70
|
-
|
|
71
|
-
const updateRequest = Forms.newRequest().setUpdateItem({
|
|
72
|
-
item: {
|
|
73
|
-
itemId: this.getId(),
|
|
74
|
-
questionItem: {
|
|
75
|
-
question: {
|
|
76
|
-
choiceQuestion: {
|
|
77
|
-
options: newOptions,
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
location: {
|
|
83
|
-
index: currentIndex,
|
|
84
|
-
},
|
|
85
|
-
updateMask: 'questionItem.question.choiceQuestion.options',
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const batchRequest = Forms.newBatchUpdateFormRequest().setRequests([updateRequest]);
|
|
89
|
-
Forms.Form.batchUpdate(batchRequest, this.__form.getId());
|
|
90
|
-
|
|
91
|
-
return this;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Sets whether this item is required.
|
|
96
|
-
* @param {boolean} enabled Whether the item is required.
|
|
97
|
-
* @returns {FakeCheckboxItem} This item, for chaining.
|
|
98
|
-
*/
|
|
99
|
-
setRequired(enabled) {
|
|
100
|
-
const { nargs, matchThrow } = signatureArgs(arguments, 'CheckboxItem.setRequired');
|
|
101
|
-
if (nargs !== 1 || !is.boolean(enabled)) {
|
|
102
|
-
matchThrow('Invalid arguments: expected a boolean.');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const currentIndex = this._getCurrentIndex();
|
|
106
|
-
|
|
107
|
-
const updateRequest = Forms.newRequest().setUpdateItem({
|
|
108
|
-
item: {
|
|
109
|
-
itemId: this.getId(),
|
|
110
|
-
questionItem: {
|
|
111
|
-
question: {
|
|
112
|
-
required: enabled,
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
location: {
|
|
117
|
-
index: currentIndex,
|
|
118
|
-
},
|
|
119
|
-
updateMask: 'questionItem.question.required',
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const batchRequest = Forms.newBatchUpdateFormRequest().setRequests([updateRequest]);
|
|
123
|
-
Forms.Form.batchUpdate(batchRequest, this.__form.getId());
|
|
124
|
-
|
|
125
|
-
return this;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Returns whether this item is required.
|
|
130
|
-
* @returns {boolean} True if the item is required, false otherwise.
|
|
131
|
-
*/
|
|
132
|
-
isRequired() {
|
|
133
|
-
return this.__resource.questionItem?.question?.required || false;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
28
|
toString() {
|
|
137
29
|
return 'CheckboxItem';
|
|
138
30
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Proxies } from '../../support/proxies.js';
|
|
2
|
+
import { PageNavigationType, ItemType } from '../enums/formsenums.js';
|
|
3
|
+
import { newFakePageBreakItem } from './fakepagebreakitem.js';
|
|
2
4
|
|
|
3
5
|
export const newFakeChoice = (...args) => {
|
|
4
6
|
return Proxies.guard(new FakeChoice(...args));
|
|
@@ -10,33 +12,48 @@ export const newFakeChoice = (...args) => {
|
|
|
10
12
|
* @see https://developers.google.com/apps-script/reference/forms/choice
|
|
11
13
|
*/
|
|
12
14
|
export class FakeChoice {
|
|
13
|
-
|
|
14
|
-
* @param {string} value The value of the choice.
|
|
15
|
-
* @param {boolean} isCorrect Whether the choice is correct.
|
|
16
|
-
* @param {import('./pagebreakitem.js').FakePageBreakItem} page The page to navigate to.
|
|
17
|
-
* @param {FormApp.PageNavigationType} navigationType The navigation type.
|
|
18
|
-
*/
|
|
19
|
-
constructor(value, isCorrect, page, navigationType) {
|
|
15
|
+
constructor(value, navType, pageId, form, parentItemType) {
|
|
20
16
|
this.__value = value;
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
23
|
-
this.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
getGotoPage() {
|
|
27
|
-
return this.__page;
|
|
17
|
+
this.__navType = navType;
|
|
18
|
+
this.__pageId = pageId;
|
|
19
|
+
this.__form = form;
|
|
20
|
+
this.__parentItemType = parentItemType;
|
|
28
21
|
}
|
|
29
22
|
|
|
30
|
-
|
|
31
|
-
return this.
|
|
23
|
+
getValue() {
|
|
24
|
+
return this.__value;
|
|
32
25
|
}
|
|
33
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Gets the page navigation type for this choice.
|
|
29
|
+
* @returns {import('../enums/formsenums.js').PageNavigationType | null} The page navigation type, or null if no navigation is set.
|
|
30
|
+
*/
|
|
34
31
|
getPageNavigationType() {
|
|
35
|
-
|
|
32
|
+
if (this.__navType) {
|
|
33
|
+
// The navType is stored as a string like 'goToPage' or 'CONTINUE'.
|
|
34
|
+
// We need to return the corresponding enum value.
|
|
35
|
+
const navEnum = PageNavigationType[this.__navType.toUpperCase()];
|
|
36
|
+
return navEnum || null;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Gets the page-break item that this choice navigates to.
|
|
43
|
+
* @returns {import('./fakepagebreakitem.js').FakePageBreakItem | null} The page-break item, or null if the choice does not navigate to a page.
|
|
44
|
+
*/
|
|
45
|
+
getGoToPage() {
|
|
46
|
+
// Per live documentation, this method only applies to choices from a MultipleChoiceItem.
|
|
47
|
+
if (this.__parentItemType !== ItemType.MULTIPLE_CHOICE) {
|
|
48
|
+
throw new TypeError('getGoToPage is not a function');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (this.__navType === 'GO_TO_PAGE' && this.__pageId && this.__form) {
|
|
52
|
+
// We need the form context to find the item by its ID.
|
|
53
|
+
const item = this.__form.getItemById(this.__pageId);
|
|
54
|
+
return item ? item.asPageBreakItem() : null;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
40
57
|
}
|
|
41
58
|
|
|
42
59
|
toString() {
|