@mcpher/gas-fakes 2.3.11 → 2.3.13

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.
Files changed (46) hide show
  1. package/README.md +14 -14
  2. package/gas-fakes.js +1 -0
  3. package/gf_agent/scripts/builder.js +26 -1
  4. package/package.json +1 -1
  5. package/src/cli/lib-manager.js +14 -4
  6. package/src/cli/setup.js +17 -4
  7. package/src/services/chartsapp/fakechartsapp.js +6 -1
  8. package/src/services/driveapp/driveiterators.js +53 -8
  9. package/src/services/driveapp/fakedriveapp.js +49 -15
  10. package/src/services/driveapp/fakedrivefile.js +105 -0
  11. package/src/services/driveapp/fakedrivefolder.js +8 -0
  12. package/src/services/driveapp/fakedrivemeta.js +68 -16
  13. package/src/services/driveapp/fakefolderapp.js +19 -6
  14. package/src/services/enums/chartsenums.js +53 -20
  15. package/src/services/enums/driveenums.js +1 -0
  16. package/src/services/slidesapp/fakeautofit.js +23 -15
  17. package/src/services/slidesapp/fakecolorscheme.js +160 -0
  18. package/src/services/slidesapp/fakelayout.js +11 -1
  19. package/src/services/slidesapp/fakemaster.js +10 -0
  20. package/src/services/slidesapp/fakepresentation.js +27 -0
  21. package/src/services/slidesapp/fakeslide.js +9 -0
  22. package/src/services/slidesapp/faketextrange.js +6 -11
  23. package/src/services/spreadsheetapp/chartenummapping.js +15 -0
  24. package/src/services/spreadsheetapp/fakebooleancondition.js +119 -0
  25. package/src/services/spreadsheetapp/fakecellimage.js +42 -0
  26. package/src/services/spreadsheetapp/fakecellimagebuilder.js +59 -0
  27. package/src/services/spreadsheetapp/fakeconditionalformatrule.js +55 -0
  28. package/src/services/spreadsheetapp/fakeconditionalformatrulebuilder.js +330 -0
  29. package/src/services/spreadsheetapp/fakedevelopermetadata.js +32 -4
  30. package/src/services/spreadsheetapp/fakedevelopermetadatalocation.js +27 -5
  31. package/src/services/spreadsheetapp/fakeembeddedchartbuilder.js +155 -21
  32. package/src/services/spreadsheetapp/fakegradientcondition.js +71 -0
  33. package/src/services/spreadsheetapp/fakesheet.js +63 -0
  34. package/src/services/spreadsheetapp/fakesheetrange.js +12 -1
  35. package/src/services/spreadsheetapp/fakespreadsheet.js +30 -11
  36. package/src/services/spreadsheetapp/fakespreadsheetapp.js +21 -3
  37. package/src/services/urlfetchapp/app.js +33 -1
  38. package/src/support/fileiterators.js +3 -1
  39. package/src/support/filesharers.js +7 -3
  40. package/src/support/peeker.js +8 -2
  41. package/src/support/sheetutils.js +1 -0
  42. package/src/support/sxdrive.js +26 -15
  43. package/src/support/syncit.js +4 -6
  44. package/src/support/workersync/synchronizer.js +24 -4
  45. package/src/support/workersync/worker.js +13 -2
  46. package/summarize_advanced.js +0 -69
@@ -0,0 +1,15 @@
1
+ export const ChartEnumMapping = {
2
+ Position: {
3
+ BOTTOM: "BOTTOM_LEGEND",
4
+ TOP: "TOP_LEGEND",
5
+ LEFT: "LEFT_LEGEND",
6
+ RIGHT: "RIGHT_LEGEND",
7
+ NONE: "NO_LEGEND",
8
+ },
9
+ ChartHiddenDimensionStrategy: {
10
+ IGNORE_BOTH: "SKIP_HIDDEN_ROWS_AND_COLUMNS",
11
+ IGNORE_ROWS: "SKIP_HIDDEN_ROWS",
12
+ IGNORE_COLUMNS: "SKIP_HIDDEN_COLUMNS",
13
+ SHOW_BOTH: "SHOW_ALL",
14
+ }
15
+ };
@@ -0,0 +1,119 @@
1
+ import { Proxies } from '../../support/proxies.js';
2
+ import { BooleanCriteria } from '../enums/sheetsenums.js';
3
+ import { newFakeColor } from '../common/fakecolor.js';
4
+ import { makeColorFromApi } from '../common/fakecolorbuilder.js';
5
+
6
+ export const newFakeBooleanCondition = (...args) => {
7
+ return Proxies.guard(new FakeBooleanCondition(...args));
8
+ };
9
+
10
+ export class FakeBooleanCondition {
11
+ constructor(apiCondition, format) {
12
+ this.__apiCondition = apiCondition;
13
+ this.__format = format || {};
14
+ }
15
+
16
+ getBackgroundObject() {
17
+ if (this.__format.backgroundColorStyle) {
18
+ return makeColorFromApi(this.__format.backgroundColorStyle);
19
+ }
20
+ if (this.__format.backgroundColor) {
21
+ return makeColorFromApi({ rgbColor: this.__format.backgroundColor });
22
+ }
23
+ return null;
24
+ }
25
+
26
+ getBold() {
27
+ return this.__format.textFormat?.bold ?? null;
28
+ }
29
+
30
+ getCriteriaType() {
31
+ // The API uses slightly different strings than the GAS enum for many rules
32
+ const map = {
33
+ 'BLANK': 'CELL_EMPTY',
34
+ 'NOT_BLANK': 'CELL_NOT_EMPTY',
35
+ 'DATE_AFTER': 'DATE_AFTER',
36
+ 'DATE_BEFORE': 'DATE_BEFORE',
37
+ 'DATE_EQ': 'DATE_EQUAL_TO',
38
+ 'DATE_NOT_EQ': 'DATE_NOT_EQUAL_TO',
39
+ 'DATE_AFTER_RELATIVE': 'DATE_AFTER_RELATIVE',
40
+ 'DATE_BEFORE_RELATIVE': 'DATE_BEFORE_RELATIVE',
41
+ 'DATE_EQUAL_TO_RELATIVE': 'DATE_EQUAL_TO_RELATIVE',
42
+ 'DATE_EQ_RELATIVE': 'DATE_EQUAL_TO_RELATIVE',
43
+ 'NUMBER_BETWEEN': 'NUMBER_BETWEEN',
44
+ 'NUMBER_NOT_BETWEEN': 'NUMBER_NOT_BETWEEN',
45
+ 'NUMBER_EQ': 'NUMBER_EQUAL_TO',
46
+ 'NUMBER_NOT_EQ': 'NUMBER_NOT_EQUAL_TO',
47
+ 'NUMBER_GREATER': 'NUMBER_GREATER_THAN',
48
+ 'NUMBER_GREATER_THAN_EQ': 'NUMBER_GREATER_THAN_OR_EQUAL_TO',
49
+ 'NUMBER_LESS': 'NUMBER_LESS_THAN',
50
+ 'NUMBER_LESS_THAN_EQ': 'NUMBER_LESS_THAN_OR_EQUAL_TO',
51
+ 'TEXT_CONTAINS': 'TEXT_CONTAINS',
52
+ 'TEXT_NOT_CONTAINS': 'TEXT_DOES_NOT_CONTAIN',
53
+ 'TEXT_EQ': 'TEXT_EQUAL_TO',
54
+ 'TEXT_NOT_EQ': 'TEXT_NOT_EQUAL_TO',
55
+ 'TEXT_STARTS_WITH': 'TEXT_STARTS_WITH',
56
+ 'TEXT_ENDS_WITH': 'TEXT_ENDS_WITH',
57
+ 'CUSTOM_FORMULA': 'CUSTOM_FORMULA'
58
+ };
59
+
60
+ // If the API value contains RELATIVE properties it should probably be resolved to the _RELATIVE equivalents
61
+ // However the API 'type' doesn't usually distinguish between DATE_EQ and DATE_EQUAL_TO_RELATIVE at the root level,
62
+ // they are often just DATE_EQ and the value object specifies if it's relative.
63
+ // Let's infer if it's relative.
64
+ let type = this.__apiCondition.type;
65
+ if (type && type.startsWith('DATE_')) {
66
+ const isRelative = this.__apiCondition.values && this.__apiCondition.values.some(v => v.relativeDate !== undefined);
67
+ if (isRelative && !type.endsWith('_RELATIVE')) {
68
+ type += '_RELATIVE';
69
+ }
70
+ }
71
+
72
+ const enumKey = map[type] || type;
73
+
74
+ // Return the Enum if available
75
+ if (BooleanCriteria[enumKey]) {
76
+ return BooleanCriteria[enumKey];
77
+ }
78
+
79
+ return enumKey || null;
80
+ }
81
+
82
+ getCriteriaValues() {
83
+ if (!this.__apiCondition.values) return [];
84
+
85
+ // API condition values are typically { userEnteredValue: 'string' } or { relativeDate: 'TODAY' }
86
+ return this.__apiCondition.values.map(v => {
87
+ if (v.userEnteredValue !== undefined) return v.userEnteredValue;
88
+ // In Apps Script, RelativeDate enum values are typically returned as strings matching the enum
89
+ if (v.relativeDate !== undefined) return SpreadsheetApp.RelativeDate[v.relativeDate] || v.relativeDate;
90
+ return null;
91
+ });
92
+ }
93
+
94
+ getFontColorObject() {
95
+ if (this.__format.textFormat?.foregroundColorStyle) {
96
+ return makeColorFromApi(this.__format.textFormat.foregroundColorStyle);
97
+ }
98
+ if (this.__format.textFormat?.foregroundColor) {
99
+ return makeColorFromApi({ rgbColor: this.__format.textFormat.foregroundColor });
100
+ }
101
+ return null;
102
+ }
103
+
104
+ getItalic() {
105
+ return this.__format.textFormat?.italic ?? null;
106
+ }
107
+
108
+ getStrikethrough() {
109
+ return this.__format.textFormat?.strikethrough ?? null;
110
+ }
111
+
112
+ getUnderline() {
113
+ return this.__format.textFormat?.underline ?? null;
114
+ }
115
+
116
+ toString() {
117
+ return 'BooleanCondition';
118
+ }
119
+ }
@@ -0,0 +1,42 @@
1
+ import { Proxies } from "../../support/proxies.js";
2
+ import { newFakeCellImageBuilder } from "./fakecellimagebuilder.js";
3
+
4
+ /**
5
+ * Fake CellImage
6
+ * @class FakeCellImage
7
+ */
8
+ export class FakeCellImage {
9
+ constructor(properties) {
10
+ this._properties = properties || {};
11
+ }
12
+
13
+ getAltTextDescription() {
14
+ return this._properties.altTextDescription || null;
15
+ }
16
+
17
+ getAltTextTitle() {
18
+ return this._properties.altTextTitle || null;
19
+ }
20
+
21
+ getContentUrl() {
22
+ const url = this._properties.sourceUrl || null;
23
+ if (url && !url.includes('googleusercontent.com')) {
24
+ throw new Error('Unexpected error while getting the method or property getContentUrl on object SpreadsheetApp.CellImage.');
25
+ }
26
+ return url;
27
+ }
28
+
29
+ toBuilder() {
30
+ const builder = newFakeCellImageBuilder();
31
+ if (this._properties.sourceUrl) builder.setSourceUrl(this._properties.sourceUrl);
32
+ if (this._properties.altTextTitle) builder.setAltTextTitle(this._properties.altTextTitle);
33
+ if (this._properties.altTextDescription) builder.setAltTextDescription(this._properties.altTextDescription);
34
+ return builder;
35
+ }
36
+
37
+ toString() {
38
+ return 'CellImage';
39
+ }
40
+ }
41
+
42
+ export const newFakeCellImage = (...args) => Proxies.guard(new FakeCellImage(...args));
@@ -0,0 +1,59 @@
1
+ import { Proxies } from "../../support/proxies.js";
2
+ import { newFakeCellImage } from "./fakecellimage.js";
3
+
4
+ /**
5
+ * Fake CellImageBuilder
6
+ * @class FakeCellImageBuilder
7
+ */
8
+ export class FakeCellImageBuilder {
9
+ constructor() {
10
+ this._properties = {};
11
+ }
12
+
13
+ build() {
14
+ return newFakeCellImage(this._properties);
15
+ }
16
+
17
+ getAltTextDescription() {
18
+ return this._properties.altTextDescription || null;
19
+ }
20
+
21
+ getAltTextTitle() {
22
+ return this._properties.altTextTitle || null;
23
+ }
24
+
25
+ getContentUrl() {
26
+ const url = this._properties.sourceUrl || null;
27
+ if (url && !url.includes('googleusercontent.com')) {
28
+ throw new Error('Unexpected error while getting the method or property getContentUrl on object SpreadsheetApp.CellImageBuilder.');
29
+ }
30
+ return url;
31
+ }
32
+
33
+ setAltTextDescription(description) {
34
+ this._properties.altTextDescription = description;
35
+ return this;
36
+ }
37
+
38
+ setAltTextTitle(title) {
39
+ this._properties.altTextTitle = title;
40
+ return this;
41
+ }
42
+
43
+ setSourceUrl(url) {
44
+ this._properties.sourceUrl = url;
45
+ return this;
46
+ }
47
+
48
+ toBuilder() {
49
+ const builder = newFakeCellImageBuilder();
50
+ builder._properties = { ...this._properties };
51
+ return builder;
52
+ }
53
+
54
+ toString() {
55
+ return 'CellImageBuilder';
56
+ }
57
+ }
58
+
59
+ export const newFakeCellImageBuilder = (...args) => Proxies.guard(new FakeCellImageBuilder(...args));
@@ -0,0 +1,55 @@
1
+ import { Proxies } from '../../support/proxies.js';
2
+ import { newFakeBooleanCondition } from './fakebooleancondition.js';
3
+ import { newFakeGradientCondition } from './fakegradientcondition.js';
4
+ import { newFakeSheetRange } from './fakesheetrange.js';
5
+ import { newFakeConditionalFormatRuleBuilder } from './fakeconditionalformatrulebuilder.js';
6
+
7
+ export const newFakeConditionalFormatRule = (...args) => {
8
+ return Proxies.guard(new FakeConditionalFormatRule(...args));
9
+ };
10
+
11
+ export class FakeConditionalFormatRule {
12
+ constructor(apiRule, sheet) {
13
+ this.__apiRule = apiRule;
14
+ this.__sheet = sheet;
15
+ }
16
+
17
+ copy() {
18
+ // Builder takes spreadsheet. Sheet gives us spreadsheet via getParent()
19
+ return newFakeConditionalFormatRuleBuilder(this.__apiRule, this.__sheet.getParent());
20
+ }
21
+
22
+ getBooleanCondition() {
23
+ if (!this.__apiRule.booleanRule) return null;
24
+ return newFakeBooleanCondition(
25
+ this.__apiRule.booleanRule.condition,
26
+ this.__apiRule.booleanRule.format
27
+ );
28
+ }
29
+
30
+ getGradientCondition() {
31
+ if (!this.__apiRule.gradientRule) return null;
32
+ return newFakeGradientCondition(this.__apiRule.gradientRule);
33
+ }
34
+
35
+ getRanges() {
36
+ if (!this.__apiRule.ranges) return [];
37
+ return this.__apiRule.ranges.map(gridRange => {
38
+ const sheetId = gridRange.sheetId;
39
+ let sheet = this.__sheet; // Default to the sheet this rule was fetched from
40
+ if (sheetId !== undefined && sheet.getSheetId() !== sheetId) {
41
+ sheet = this.__sheet.getParent().getSheets().find(s => s.getSheetId() === sheetId);
42
+ }
43
+
44
+ if (!sheet) return null;
45
+ return newFakeSheetRange(
46
+ gridRange,
47
+ sheet
48
+ );
49
+ }).filter(Boolean);
50
+ }
51
+
52
+ toString() {
53
+ return 'ConditionalFormatRule';
54
+ }
55
+ }
@@ -0,0 +1,330 @@
1
+ import { Proxies } from '../../support/proxies.js';
2
+ import { newFakeConditionalFormatRule } from './fakeconditionalformatrule.js';
3
+ import { newFakeBooleanCondition } from './fakebooleancondition.js';
4
+ import { newFakeGradientCondition } from './fakegradientcondition.js';
5
+ import { makeSheetsGridRange } from './sheetrangehelpers.js';
6
+ import { BooleanCriteria, InterpolationType } from '../enums/sheetsenums.js';
7
+ import { Utils } from '../../support/utils.js';
8
+
9
+ export const newFakeConditionalFormatRuleBuilder = (...args) => {
10
+ return Proxies.guard(new FakeConditionalFormatRuleBuilder(...args));
11
+ };
12
+
13
+ export class FakeConditionalFormatRuleBuilder {
14
+ constructor(apiRule = {}, spreadsheet = null) {
15
+ // We deep clone so that modifications to the builder don't affect the original rule immediately
16
+ this.__apiRule = JSON.parse(JSON.stringify(apiRule));
17
+ this.__spreadsheet = spreadsheet;
18
+
19
+ // Ensure we have at least one rule type established, defaulting to booleanRule if nothing is set
20
+ if (!this.__apiRule.booleanRule && !this.__apiRule.gradientRule) {
21
+ this.__apiRule.booleanRule = {
22
+ condition: { type: 'CUSTOM_FORMULA', values: [] },
23
+ format: {}
24
+ };
25
+ }
26
+ }
27
+
28
+ __ensureBooleanRule() {
29
+ if (this.__apiRule.gradientRule) {
30
+ delete this.__apiRule.gradientRule;
31
+ }
32
+ if (!this.__apiRule.booleanRule) {
33
+ this.__apiRule.booleanRule = { condition: { type: 'CUSTOM_FORMULA', values: [] }, format: {} };
34
+ }
35
+ return this.__apiRule.booleanRule;
36
+ }
37
+
38
+ __ensureGradientRule() {
39
+ if (this.__apiRule.booleanRule) {
40
+ delete this.__apiRule.booleanRule;
41
+ }
42
+ if (!this.__apiRule.gradientRule) {
43
+ this.__apiRule.gradientRule = { minpoint: {}, midpoint: {}, maxpoint: {} };
44
+ }
45
+ return this.__apiRule.gradientRule;
46
+ }
47
+
48
+ __setBooleanFormatProperty(property, value) {
49
+ const rule = this.__ensureBooleanRule();
50
+ if (!rule.format) rule.format = {};
51
+ if (property === 'backgroundColorStyle') {
52
+ // Reset standard rgbColor if style is set
53
+ delete rule.format.backgroundColor;
54
+ if (value === null) {
55
+ delete rule.format.backgroundColorStyle;
56
+ } else {
57
+ rule.format.backgroundColorStyle = value;
58
+ }
59
+ } else if (property === 'backgroundColor') {
60
+ delete rule.format.backgroundColorStyle;
61
+ if (value === null) {
62
+ delete rule.format.backgroundColor;
63
+ } else {
64
+ rule.format.backgroundColor = value;
65
+ }
66
+ } else {
67
+ if (!rule.format.textFormat) rule.format.textFormat = {};
68
+ if (property === 'foregroundColorStyle') {
69
+ delete rule.format.textFormat.foregroundColor;
70
+ if (value === null) {
71
+ delete rule.format.textFormat.foregroundColorStyle;
72
+ } else {
73
+ rule.format.textFormat.foregroundColorStyle = value;
74
+ }
75
+ } else if (property === 'foregroundColor') {
76
+ delete rule.format.textFormat.foregroundColorStyle;
77
+ if (value === null) {
78
+ delete rule.format.textFormat.foregroundColor;
79
+ } else {
80
+ rule.format.textFormat.foregroundColor = value;
81
+ }
82
+ } else {
83
+ if (value === null) {
84
+ delete rule.format.textFormat[property];
85
+ } else {
86
+ rule.format.textFormat[property] = value;
87
+ }
88
+ }
89
+ }
90
+ return this;
91
+ }
92
+
93
+ __colorToApiStyle(colorObj) {
94
+ if (!colorObj) return null;
95
+ const type = colorObj.getColorType().toString();
96
+ if (type === 'THEME') {
97
+ return { themeColor: colorObj.asThemeColor().getThemeColorType().toString() };
98
+ } else if (type === 'RGB') {
99
+ const rgb = colorObj.asRgbColor();
100
+ return {
101
+ rgbColor: {
102
+ red: rgb.getRed() / 255,
103
+ green: rgb.getGreen() / 255,
104
+ blue: rgb.getBlue() / 255
105
+ }
106
+ };
107
+ }
108
+ return null;
109
+ }
110
+
111
+ __cssToApiColor(cssColor) {
112
+ if (!cssColor) return null;
113
+ const hex = Utils.validateHex(cssColor);
114
+ if (!hex) return null;
115
+ return {
116
+ red: hex.r / 255,
117
+ green: hex.g / 255,
118
+ blue: hex.b / 255
119
+ };
120
+ }
121
+
122
+ build() {
123
+ let sheet = null;
124
+ if (this.__spreadsheet) {
125
+ if (this.__apiRule.ranges && this.__apiRule.ranges.length > 0) {
126
+ const sheetId = this.__apiRule.ranges[0].sheetId;
127
+ sheet = this.__spreadsheet.getSheets().find(s => s.getSheetId() === sheetId);
128
+ }
129
+ // If no ranges yet or sheet not found, just use the active sheet as a fallback context
130
+ // Note: getActiveSheet() might be stubbed.
131
+ if (!sheet && typeof this.__spreadsheet.getActiveSheet === 'function') {
132
+ try {
133
+ sheet = this.__spreadsheet.getActiveSheet();
134
+ } catch(e) {}
135
+ }
136
+ }
137
+
138
+ // Clean up empty gradient points so the API doesn't complain
139
+ if (this.__apiRule.gradientRule) {
140
+ const g = this.__apiRule.gradientRule;
141
+ ['minpoint', 'midpoint', 'maxpoint'].forEach(pt => {
142
+ if (g[pt] && Object.keys(g[pt]).length === 0) {
143
+ delete g[pt];
144
+ }
145
+ });
146
+ }
147
+
148
+ return newFakeConditionalFormatRule(this.__apiRule, sheet);
149
+ }
150
+
151
+ copy() {
152
+ return newFakeConditionalFormatRuleBuilder(this.__apiRule, this.__spreadsheet);
153
+ }
154
+
155
+ getBooleanCondition() {
156
+ if (!this.__apiRule.booleanRule) return null;
157
+ return newFakeBooleanCondition(
158
+ this.__apiRule.booleanRule.condition,
159
+ this.__apiRule.booleanRule.format
160
+ );
161
+ }
162
+
163
+ getGradientCondition() {
164
+ if (!this.__apiRule.gradientRule) return null;
165
+ return newFakeGradientCondition(this.__apiRule.gradientRule);
166
+ }
167
+
168
+ getRanges() {
169
+ if (!this.__apiRule.ranges) return [];
170
+ return this.__apiRule.ranges.map(gridRange => {
171
+ const sheet = this.__spreadsheet.getSheets().find(s => s.getSheetId() === gridRange.sheetId);
172
+ if (!sheet) return null;
173
+ return sheet.getRange(
174
+ gridRange.startRowIndex + 1,
175
+ gridRange.startColumnIndex + 1,
176
+ (gridRange.endRowIndex || sheet.getMaxRows()) - gridRange.startRowIndex,
177
+ (gridRange.endColumnIndex || sheet.getMaxColumns()) - gridRange.startColumnIndex
178
+ );
179
+ }).filter(Boolean);
180
+ }
181
+
182
+ setBackground(color) {
183
+ return this.__setBooleanFormatProperty('backgroundColor', this.__cssToApiColor(color));
184
+ }
185
+
186
+ setBackgroundObject(color) {
187
+ return this.__setBooleanFormatProperty('backgroundColorStyle', this.__colorToApiStyle(color));
188
+ }
189
+
190
+ setBold(bold) {
191
+ return this.__setBooleanFormatProperty('bold', bold);
192
+ }
193
+
194
+ setFontColor(color) {
195
+ return this.__setBooleanFormatProperty('foregroundColor', this.__cssToApiColor(color));
196
+ }
197
+
198
+ setFontColorObject(color) {
199
+ return this.__setBooleanFormatProperty('foregroundColorStyle', this.__colorToApiStyle(color));
200
+ }
201
+
202
+ setItalic(italic) {
203
+ return this.__setBooleanFormatProperty('italic', italic);
204
+ }
205
+
206
+ setStrikethrough(strikethrough) {
207
+ return this.__setBooleanFormatProperty('strikethrough', strikethrough);
208
+ }
209
+
210
+ setUnderline(underline) {
211
+ return this.__setBooleanFormatProperty('underline', underline);
212
+ }
213
+
214
+ __setGradientPoint(point, color, type, value) {
215
+ const rule = this.__ensureGradientRule();
216
+ if (!rule[point]) rule[point] = {};
217
+ if (color !== undefined) {
218
+ if (typeof color === 'string') {
219
+ rule[point].color = this.__cssToApiColor(color);
220
+ delete rule[point].colorStyle;
221
+ } else if (color !== null) {
222
+ rule[point].colorStyle = this.__colorToApiStyle(color);
223
+ delete rule[point].color;
224
+ }
225
+ }
226
+ if (type !== undefined) rule[point].type = type.toString();
227
+ if (value !== undefined) rule[point].value = value.toString();
228
+ return this;
229
+ }
230
+
231
+ setGradientMaxpoint(color) {
232
+ return this.__setGradientPoint('maxpoint', color, InterpolationType.MAX, "");
233
+ }
234
+
235
+ setGradientMaxpointObject(color) {
236
+ return this.__setGradientPoint('maxpoint', color, InterpolationType.MAX, "");
237
+ }
238
+
239
+ setGradientMaxpointObjectWithValue(color, type, value) {
240
+ return this.__setGradientPoint('maxpoint', color, type, value);
241
+ }
242
+
243
+ setGradientMaxpointWithValue(color, type, value) {
244
+ return this.__setGradientPoint('maxpoint', color, type, value);
245
+ }
246
+
247
+ setGradientMidpointObjectWithValue(color, type, value) {
248
+ return this.__setGradientPoint('midpoint', color, type, value);
249
+ }
250
+
251
+ setGradientMidpointWithValue(color, type, value) {
252
+ return this.__setGradientPoint('midpoint', color, type, value);
253
+ }
254
+
255
+ setGradientMinpoint(color) {
256
+ return this.__setGradientPoint('minpoint', color, InterpolationType.MIN, "");
257
+ }
258
+
259
+ setGradientMinpointObject(color) {
260
+ return this.__setGradientPoint('minpoint', color, InterpolationType.MIN, "");
261
+ }
262
+
263
+ setGradientMinpointObjectWithValue(color, type, value) {
264
+ return this.__setGradientPoint('minpoint', color, type, value);
265
+ }
266
+
267
+ setGradientMinpointWithValue(color, type, value) {
268
+ return this.__setGradientPoint('minpoint', color, type, value);
269
+ }
270
+
271
+ setRanges(ranges) {
272
+ const arr = Array.isArray(ranges) ? ranges : [ranges];
273
+ this.__apiRule.ranges = arr.map(r => makeSheetsGridRange(r));
274
+ return this;
275
+ }
276
+
277
+ __setBooleanCriteria(type, args = []) {
278
+ const rule = this.__ensureBooleanRule();
279
+ rule.condition = {
280
+ type: type,
281
+ values: args.map(a => {
282
+ // handle relative dates vs userEnteredValues
283
+ if (a && a.toString().indexOf('DATE_') === 0 || ['TODAY', 'TOMORROW', 'YESTERDAY', 'PAST_WEEK', 'PAST_MONTH', 'PAST_YEAR'].includes(a.toString())) {
284
+ return { relativeDate: a.toString() };
285
+ }
286
+ // Handle real Date objects
287
+ if (a instanceof Date) {
288
+ // Sheets API accepts dates in "MM/dd/yyyy" format for userEnteredValue, or as a formula like "=DATE(y,m,d)"
289
+ // Let's format it as a formula to be globally safe, or as MM/dd/yyyy.
290
+ // Actually, Sheets accepts "MM/dd/yyyy" universally in userEnteredValue for dates.
291
+ const m = a.getMonth() + 1;
292
+ const d = a.getDate();
293
+ const y = a.getFullYear();
294
+ return { userEnteredValue: `${m}/${d}/${y}` };
295
+ }
296
+ return { userEnteredValue: a.toString() };
297
+ })
298
+ };
299
+ return this;
300
+ }
301
+
302
+ whenCellEmpty() { return this.__setBooleanCriteria('BLANK'); }
303
+ whenCellNotEmpty() { return this.__setBooleanCriteria('NOT_BLANK'); }
304
+ whenDateAfter(date) { return this.__setBooleanCriteria('DATE_AFTER', [date]); }
305
+ whenDateBefore(date) { return this.__setBooleanCriteria('DATE_BEFORE', [date]); }
306
+ whenDateEqualTo(date) { return this.__setBooleanCriteria('DATE_EQ', [date]); }
307
+ whenFormulaSatisfied(formula) { return this.__setBooleanCriteria('CUSTOM_FORMULA', [formula]); }
308
+ whenNumberBetween(start, end) { return this.__setBooleanCriteria('NUMBER_BETWEEN', [start, end]); }
309
+ whenNumberEqualTo(number) { return this.__setBooleanCriteria('NUMBER_EQ', [number]); }
310
+ whenNumberGreaterThan(number) { return this.__setBooleanCriteria('NUMBER_GREATER', [number]); }
311
+ whenNumberGreaterThanOrEqualTo(number) { return this.__setBooleanCriteria('NUMBER_GREATER_THAN_EQ', [number]); }
312
+ whenNumberLessThan(number) { return this.__setBooleanCriteria('NUMBER_LESS', [number]); }
313
+ whenNumberLessThanOrEqualTo(number) { return this.__setBooleanCriteria('NUMBER_LESS_THAN_EQ', [number]); }
314
+ whenNumberNotBetween(start, end) { return this.__setBooleanCriteria('NUMBER_NOT_BETWEEN', [start, end]); }
315
+ whenNumberNotEqualTo(number) { return this.__setBooleanCriteria('NUMBER_NOT_EQ', [number]); }
316
+ whenTextContains(text) { return this.__setBooleanCriteria('TEXT_CONTAINS', [text]); }
317
+ whenTextDoesNotContain(text) { return this.__setBooleanCriteria('TEXT_NOT_CONTAINS', [text]); }
318
+ whenTextEndsWith(text) { return this.__setBooleanCriteria('TEXT_ENDS_WITH', [text]); }
319
+ whenTextEqualTo(text) { return this.__setBooleanCriteria('TEXT_EQ', [text]); }
320
+ whenTextStartsWith(text) { return this.__setBooleanCriteria('TEXT_STARTS_WITH', [text]); }
321
+
322
+ withCriteria(criteria, args) {
323
+ // API type is usually same as the enum, except enum uses string keys matching API types mostly.
324
+ return this.__setBooleanCriteria(criteria.toString(), args);
325
+ }
326
+
327
+ toString() {
328
+ return 'ConditionalFormatRuleBuilder';
329
+ }
330
+ }
@@ -1,5 +1,6 @@
1
1
  import { Proxies } from '../../support/proxies.js';
2
2
  import { Utils } from '../../support/utils.js';
3
+ import { signatureArgs } from '../../support/helpers.js';
3
4
  import { newFakeDeveloperMetadataLocation } from './fakedevelopermetadatalocation.js';
4
5
  import { batchUpdate, makeSheetsGridRange } from './sheetrangehelpers.js';
5
6
 
@@ -40,11 +41,36 @@ class FakeDeveloperMetadata {
40
41
  return SpreadsheetApp.DeveloperMetadataVisibility[this.__metadata.visibility];
41
42
  }
42
43
 
43
- moveTo(location) {
44
+ moveToColumn(column) {
45
+ const { nargs, matchThrow } = signatureArgs(arguments, "moveToColumn")
46
+ if (nargs !== 1 || column.toString() !== 'Range') matchThrow()
47
+ return this.__moveTo(column)
48
+ }
49
+
50
+ moveToRow(row) {
51
+ const { nargs, matchThrow } = signatureArgs(arguments, "moveToRow")
52
+ if (nargs !== 1 || row.toString() !== 'Range') matchThrow()
53
+ return this.__moveTo(row)
54
+ }
55
+
56
+ moveToSheet(sheet) {
57
+ const { nargs, matchThrow } = signatureArgs(arguments, "moveToSheet")
58
+ if (nargs !== 1 || sheet.toString() !== 'Sheet') matchThrow()
59
+ return this.__moveTo(sheet)
60
+ }
61
+
62
+ moveToSpreadsheet() {
63
+ const { nargs, matchThrow } = signatureArgs(arguments, "moveToSpreadsheet")
64
+ if (nargs !== 0) matchThrow()
65
+ return this.__moveTo(this.__spreadsheet)
66
+ }
67
+
68
+ __moveTo(location) {
44
69
  const newLocation = {};
45
- if (location.toString() === 'Sheet') {
70
+ const locType = location.toString()
71
+ if (locType === 'Sheet') {
46
72
  newLocation.sheetId = location.getSheetId();
47
- } else if (location.toString() === 'Range') {
73
+ } else if (locType === 'Range') {
48
74
  const isEntireRow = location.getNumRows() === 1 && location.getColumn() === 1 && location.getNumColumns() === location.getSheet().getMaxColumns();
49
75
  const isEntireColumn = location.getNumColumns() === 1 && location.getRow() === 1 && location.getNumRows() === location.getSheet().getMaxRows();
50
76
 
@@ -63,8 +89,10 @@ class FakeDeveloperMetadata {
63
89
  startIndex: startIndex,
64
90
  endIndex: endIndex,
65
91
  };
92
+ } else if (locType === 'Spreadsheet') {
93
+ newLocation.spreadsheet = true;
66
94
  } else {
67
- throw new Error('Location must be a Sheet or Range.');
95
+ throw new Error('Location must be a Sheet, Range or Spreadsheet.');
68
96
  }
69
97
 
70
98
  this.__updateMetadata({ location: newLocation }, 'location');