@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 ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "scriptId": "",
3
+ "rootDir": "./src/"
4
+ }
package/.claspignore ADDED
@@ -0,0 +1,6 @@
1
+ # Ignore local development files
2
+ run.js
3
+ node_modules/
4
+ package.json
5
+ package-lock.json
6
+ .gemini/
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.12";
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
- "Error: You must provide a script via --filename or --script, or use a specific command (e.g., init, auth, mcp)."
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.19",
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: existingConfig.GCP_PROJECT_ID || process.env.GOOGLE_CLOUD_PROJECT,
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: "Enter a test Drive file ID for authentication checks (optional)",
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 ? `[${scope.sensitivity}] ${scope.title}` : scope.title,
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("You have selected sensitive or restricted scopes. Google requires an OAuth client credential file for these.");
259
- console.log('See the getting started guide https://github.com/brucemcpherson/gas-fakes/blob/main/GETTING_STARTED.md for how.')
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
- type: "toggle",
311
- name: "QUIET",
312
- message: "Run gas-fakes package in quiet mode",
313
- initial: existingConfig.QUIET === "true" ? true : false,
314
- },
315
- {
316
- type: "select",
317
- name: "LOG_DESTINATION",
318
- message: `Selected Scopes:${defaultScopesDisplay}${extraScopesDisplay}\n\nEnter logging destination`,
319
- choices: [
320
- { title: "CONSOLE", value: "CONSOLE" },
321
- { title: "CLOUD", value: "CLOUD" },
322
- { title: "BOTH", value: "BOTH" },
323
- { title: "NONE", value: "NONE" },
324
- ],
325
- initial:
326
- ["CONSOLE", "CLOUD", "BOTH", "NONE"].indexOf(
327
- existingConfig.LOG_DESTINATION
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
- : 0,
333
- },
334
- {
335
- type: "select",
336
- name: "STORE_TYPE",
337
- message: "Enter storage type",
338
- choices: [
339
- { title: "FILE", value: "FILE" },
340
- { title: "UPSTASH", value: "UPSTASH" },
341
- ],
342
- initial:
343
- ["FILE", "UPSTASH"].indexOf(existingConfig.STORE_TYPE?.toUpperCase()) >
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
- ? ["FILE", "UPSTASH"].indexOf(existingConfig.STORE_TYPE.toUpperCase())
346
- : 0,
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 = responses.STORE_TYPE !== "UPSTASH" ? { UPSTASH_REDIS_REST_TOKEN: "", UPSTASH_REDIS_REST_URL: "" } : {}
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
- const envContent = Reflect.ownKeys(finalConfig).map((key) => {
414
-
415
- const item = finalConfig[key];
416
- console.log (key, item)
417
- return `${key}="${(item.toString() || "").trim()}"`;
418
- }).join("\n")
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
@@ -0,0 +1,3 @@
1
+ function myFunction() {
2
+ console.log('Hello from Google Apps Script!');
3
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "dependencies": {},
3
+ "exceptionLogging": "STACKDRIVER",
4
+ "runtimeVersion": "V8"
5
+ }
@@ -42,7 +42,8 @@ class FakeAdvForms {
42
42
  "newFormInfo": [
43
43
  "title",
44
44
  "documentTitle",
45
- "description"
45
+ "description",
46
+ "state"
46
47
  ],
47
48
  "newUpdateSettingsRequest": [
48
49
  "settings",
@@ -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 { FakeFormItem } from './fakeformitem.js';
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 FakeFormItem {
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.__isCorrect = isCorrect;
22
- this.__page = page;
23
- this.__navigationType = navigationType;
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
- getIsCorrect() {
31
- return this.__isCorrect;
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
- return this.__navigationType;
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
- getValue() {
39
- return this.__value;
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() {