@mcpher/gas-fakes 2.3.10 → 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.
- package/README.md +14 -14
- package/gas-fakes.js +1 -0
- package/gf_agent/scripts/builder.js +26 -1
- package/package.json +1 -1
- package/src/cli/lib-manager.js +14 -4
- package/src/cli/setup.js +17 -4
- package/src/services/chartsapp/fakechartsapp.js +6 -1
- package/src/services/documentapp/elementhelpers.js +27 -0
- package/src/services/documentapp/fakeelement.js +8 -0
- package/src/services/documentapp/fakeparagraph.js +14 -0
- package/src/services/documentapp/faketext.js +174 -4
- package/src/services/driveapp/driveiterators.js +53 -8
- package/src/services/driveapp/fakedriveapp.js +49 -15
- package/src/services/driveapp/fakedrivefile.js +105 -0
- package/src/services/driveapp/fakedrivefolder.js +8 -0
- package/src/services/driveapp/fakedrivemeta.js +68 -16
- package/src/services/driveapp/fakefolderapp.js +19 -6
- package/src/services/enums/chartsenums.js +53 -20
- package/src/services/enums/driveenums.js +1 -0
- package/src/services/slidesapp/fakeautofit.js +23 -15
- package/src/services/slidesapp/fakecolorscheme.js +160 -0
- package/src/services/slidesapp/fakelayout.js +11 -1
- package/src/services/slidesapp/fakemaster.js +10 -0
- package/src/services/slidesapp/fakepresentation.js +27 -0
- package/src/services/slidesapp/fakeslide.js +9 -0
- package/src/services/slidesapp/faketextrange.js +6 -11
- package/src/services/spreadsheetapp/chartenummapping.js +15 -0
- package/src/services/spreadsheetapp/fakebooleancondition.js +119 -0
- package/src/services/spreadsheetapp/fakecellimage.js +42 -0
- package/src/services/spreadsheetapp/fakecellimagebuilder.js +59 -0
- package/src/services/spreadsheetapp/fakeconditionalformatrule.js +55 -0
- package/src/services/spreadsheetapp/fakeconditionalformatrulebuilder.js +330 -0
- package/src/services/spreadsheetapp/fakedevelopermetadata.js +32 -4
- package/src/services/spreadsheetapp/fakedevelopermetadatalocation.js +27 -5
- package/src/services/spreadsheetapp/fakeembeddedchartbuilder.js +155 -21
- package/src/services/spreadsheetapp/fakegradientcondition.js +71 -0
- package/src/services/spreadsheetapp/fakesheet.js +63 -0
- package/src/services/spreadsheetapp/fakesheetrange.js +12 -1
- package/src/services/spreadsheetapp/fakespreadsheet.js +30 -11
- package/src/services/spreadsheetapp/fakespreadsheetapp.js +21 -3
- package/src/services/urlfetchapp/app.js +33 -1
- package/src/support/fileiterators.js +3 -1
- package/src/support/filesharers.js +7 -3
- package/src/support/peeker.js +8 -2
- package/src/support/sheetutils.js +1 -0
- package/src/support/sxdrive.js +26 -15
- package/src/support/syncit.js +4 -6
- package/src/support/workersync/synchronizer.js +24 -4
- package/src/support/workersync/worker.js +13 -2
- package/summarize_advanced.js +0 -69
|
@@ -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
|
-
|
|
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
|
-
|
|
70
|
+
const locType = location.toString()
|
|
71
|
+
if (locType === 'Sheet') {
|
|
46
72
|
newLocation.sheetId = location.getSheetId();
|
|
47
|
-
} else if (
|
|
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
|
|
95
|
+
throw new Error('Location must be a Sheet, Range or Spreadsheet.');
|
|
68
96
|
}
|
|
69
97
|
|
|
70
98
|
this.__updateMetadata({ location: newLocation }, 'location');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Proxies } from '../../support/proxies.js';
|
|
2
2
|
import { Utils } from '../../support/utils.js';
|
|
3
|
+
import { SheetUtils } from '../../support/sheetutils.js';
|
|
3
4
|
import { newFakeSheetRange } from './fakesheetrange.js';
|
|
4
5
|
import { makeGridRange } from './sheetrangehelpers.js';
|
|
5
6
|
|
|
@@ -25,12 +26,27 @@ class FakeDeveloperMetadataLocation {
|
|
|
25
26
|
return null;
|
|
26
27
|
}
|
|
27
28
|
const dr = this.__location.dimensionRange;
|
|
28
|
-
const
|
|
29
|
-
|
|
29
|
+
const startA1 = SheetUtils.columnToLetter(dr.startIndex + 1);
|
|
30
|
+
const endA1 = SheetUtils.columnToLetter(dr.endIndex);
|
|
31
|
+
return this.getSheet().getRange(`${startA1}:${endA1}`);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
getLocationType() {
|
|
33
|
-
|
|
35
|
+
if (this.__location.locationType) {
|
|
36
|
+
return SpreadsheetApp.DeveloperMetadataLocationType[this.__location.locationType];
|
|
37
|
+
}
|
|
38
|
+
if (this.__location.spreadsheet) {
|
|
39
|
+
return SpreadsheetApp.DeveloperMetadataLocationType.SPREADSHEET;
|
|
40
|
+
}
|
|
41
|
+
if (this.__location.dimensionRange) {
|
|
42
|
+
return this.__location.dimensionRange.dimension === 'COLUMNS' ?
|
|
43
|
+
SpreadsheetApp.DeveloperMetadataLocationType.COLUMN :
|
|
44
|
+
SpreadsheetApp.DeveloperMetadataLocationType.ROW;
|
|
45
|
+
}
|
|
46
|
+
if (this.__location.sheetId !== undefined) {
|
|
47
|
+
return SpreadsheetApp.DeveloperMetadataLocationType.SHEET;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
34
50
|
}
|
|
35
51
|
|
|
36
52
|
getRow() {
|
|
@@ -38,8 +54,7 @@ class FakeDeveloperMetadataLocation {
|
|
|
38
54
|
return null;
|
|
39
55
|
}
|
|
40
56
|
const dr = this.__location.dimensionRange;
|
|
41
|
-
|
|
42
|
-
return this.getSheet().getRange(dr.startIndex + 1, 1, numRows, this.getSheet().getMaxColumns());
|
|
57
|
+
return this.getSheet().getRange(`${dr.startIndex + 1}:${dr.endIndex}`);
|
|
43
58
|
}
|
|
44
59
|
|
|
45
60
|
getSheet() {
|
|
@@ -48,6 +63,13 @@ class FakeDeveloperMetadataLocation {
|
|
|
48
63
|
return this.__spreadsheet.getSheetById(sheetId);
|
|
49
64
|
}
|
|
50
65
|
|
|
66
|
+
getSpreadsheet() {
|
|
67
|
+
if (this.getLocationType().toString() !== 'SPREADSHEET' || !this.__location.spreadsheet) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return this.__spreadsheet;
|
|
71
|
+
}
|
|
72
|
+
|
|
51
73
|
toString() {
|
|
52
74
|
return 'DeveloperMetadataLocation';
|
|
53
75
|
}
|
|
@@ -3,6 +3,8 @@ import { signatureArgs } from "../../support/helpers.js";
|
|
|
3
3
|
import { Utils } from "../../support/utils.js";
|
|
4
4
|
import { makeSheetsGridRange } from "./sheetrangehelpers.js";
|
|
5
5
|
import { newFakeEmbeddedChart } from "./fakeembeddedchart.js";
|
|
6
|
+
import { newFakeContainerInfo } from "./fakecontainerinfo.js";
|
|
7
|
+
import { ChartEnumMapping } from "./chartenummapping.js";
|
|
6
8
|
|
|
7
9
|
const { is } = Utils;
|
|
8
10
|
|
|
@@ -23,8 +25,6 @@ export class FakeEmbeddedChartBuilder {
|
|
|
23
25
|
spec: {
|
|
24
26
|
title: "",
|
|
25
27
|
basicChart: {
|
|
26
|
-
chartType: "COLUMN", // Default
|
|
27
|
-
legendPosition: "RIGHT_LEGEND",
|
|
28
28
|
axis: [],
|
|
29
29
|
domains: [],
|
|
30
30
|
series: [],
|
|
@@ -60,17 +60,11 @@ export class FakeEmbeddedChartBuilder {
|
|
|
60
60
|
|
|
61
61
|
const gridRange = makeSheetsGridRange(range);
|
|
62
62
|
|
|
63
|
-
// In Sheets API, ranges are usually mapped to domains or series.
|
|
64
|
-
// Simplifying: if the range has multiple columns, split it into separate sources if it's a series.
|
|
65
|
-
// But for this fake, we'll just encourage the user to use single-column ranges or handle it simply.
|
|
66
|
-
|
|
67
63
|
if (this.__apiChart.spec.basicChart.domains.length === 0) {
|
|
68
64
|
this.__apiChart.spec.basicChart.domains.push({
|
|
69
65
|
domain: { sourceRange: { sources: [gridRange] } },
|
|
70
66
|
});
|
|
71
67
|
} else {
|
|
72
|
-
// Check if gridRange has multiple columns/rows and split if necessary?
|
|
73
|
-
// For now, just add it. The test will be updated to use single column ranges.
|
|
74
68
|
this.__apiChart.spec.basicChart.series.push({
|
|
75
69
|
series: { sourceRange: { sources: [gridRange] } },
|
|
76
70
|
});
|
|
@@ -82,15 +76,10 @@ export class FakeEmbeddedChartBuilder {
|
|
|
82
76
|
const { nargs, matchThrow } = signatureArgs(arguments, "EmbeddedChartBuilder.setChartType");
|
|
83
77
|
if (nargs !== 1) matchThrow();
|
|
84
78
|
|
|
85
|
-
// type is Charts.ChartType enum (which should match API strings in our fakes)
|
|
86
79
|
this.__apiChart.spec.basicChart.chartType = type.toString();
|
|
87
80
|
return this;
|
|
88
81
|
}
|
|
89
82
|
|
|
90
|
-
/**
|
|
91
|
-
* Returns the chart type.
|
|
92
|
-
* @returns {ChartType}
|
|
93
|
-
*/
|
|
94
83
|
getChartType() {
|
|
95
84
|
const spec = this.__apiChart.spec;
|
|
96
85
|
const chartType = Charts.ChartType;
|
|
@@ -125,9 +114,6 @@ export class FakeEmbeddedChartBuilder {
|
|
|
125
114
|
if (option === "title") {
|
|
126
115
|
this.__apiChart.spec.title = value;
|
|
127
116
|
} else {
|
|
128
|
-
// For other options, we might want to store them in a general options object if needed,
|
|
129
|
-
// but for now, we'll just focus on "title" as requested.
|
|
130
|
-
// SpreadsheetApp.EmbeddedChartBuilder.setOption(option, value)
|
|
131
117
|
if (!this.__apiChart.spec.basicChart.options) {
|
|
132
118
|
this.__apiChart.spec.basicChart.options = {};
|
|
133
119
|
}
|
|
@@ -140,14 +126,162 @@ export class FakeEmbeddedChartBuilder {
|
|
|
140
126
|
const { nargs, matchThrow } = signatureArgs(arguments, "EmbeddedChartBuilder.build");
|
|
141
127
|
if (nargs !== 0) matchThrow();
|
|
142
128
|
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
129
|
+
// Perform translation from GAS internal state to Sheets REST API Spec
|
|
130
|
+
const apiChart = JSON.parse(JSON.stringify(this.__apiChart));
|
|
131
|
+
const spec = apiChart.spec;
|
|
132
|
+
const basic = spec.basicChart;
|
|
133
|
+
|
|
134
|
+
// 1. Translate Enums
|
|
135
|
+
if (basic && basic.legendPosition) {
|
|
136
|
+
basic.legendPosition = ChartEnumMapping.Position[basic.legendPosition] || basic.legendPosition;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (spec.hiddenDimensionStrategy) {
|
|
140
|
+
spec.hiddenDimensionStrategy = ChartEnumMapping.ChartHiddenDimensionStrategy[spec.hiddenDimensionStrategy] || spec.hiddenDimensionStrategy;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Translate background color from Hex to RGB object if present
|
|
144
|
+
if (basic && basic.options?.backgroundColor) {
|
|
145
|
+
const hex = basic.options.backgroundColor;
|
|
146
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
147
|
+
if (result) {
|
|
148
|
+
spec.backgroundColorStyle = {
|
|
149
|
+
rgbColor: {
|
|
150
|
+
red: parseInt(result[1], 16) / 255,
|
|
151
|
+
green: parseInt(result[2], 16) / 255,
|
|
152
|
+
blue: parseInt(result[3], 16) / 255
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 2. Handle Specialized Spec Blocks
|
|
159
|
+
if (basic && basic.chartType === "PIE") {
|
|
160
|
+
spec.pieChart = {
|
|
161
|
+
domain: basic.domains?.[0]?.domain,
|
|
162
|
+
series: basic.series?.[0]?.series,
|
|
163
|
+
threeDimensional: basic.options?.is3D || false,
|
|
164
|
+
pieHole: basic.options?.pieHole || 0,
|
|
165
|
+
};
|
|
166
|
+
if (basic.legendPosition) spec.pieChart.legendPosition = basic.legendPosition;
|
|
167
|
+
delete spec.basicChart;
|
|
168
|
+
} else if (basic && basic.chartType === "HISTOGRAM") {
|
|
169
|
+
spec.histogramChart = {
|
|
170
|
+
series: [
|
|
171
|
+
{
|
|
172
|
+
data: basic.series?.[0]?.series || basic.domains?.[0]?.domain,
|
|
173
|
+
}
|
|
174
|
+
],
|
|
175
|
+
};
|
|
176
|
+
if (basic.legendPosition) spec.histogramChart.legendPosition = basic.legendPosition;
|
|
177
|
+
delete spec.basicChart;
|
|
178
|
+
} else if (basic) {
|
|
179
|
+
// Sheets API quirk: even if basicChart.chartType is set, it often defaults to rendering as a LINE
|
|
180
|
+
// chart unless the series array explicitly mirrors the type.
|
|
181
|
+
basic.series = basic.series || [];
|
|
182
|
+
if (basic.chartType === "COMBO") {
|
|
183
|
+
if (basic.series.length === 0) basic.series.push({ type: "COLUMN" });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
basic.series.forEach(s => {
|
|
187
|
+
if (!s.type) s.type = basic.chartType === "COMBO" ? "COLUMN" : basic.chartType;
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Clean up internal-only 'options' field that the API doesn't recognize
|
|
191
|
+
delete basic.options;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return newFakeEmbeddedChart(apiChart, this.__sheet);
|
|
148
195
|
}
|
|
149
196
|
|
|
197
|
+
// --- Sub-builder coercion methods ---
|
|
198
|
+
asAreaChart() { return this.setChartType("AREA"); }
|
|
199
|
+
asBarChart() { return this.setChartType("BAR"); }
|
|
200
|
+
asColumnChart() { return this.setChartType("COLUMN"); }
|
|
201
|
+
asComboChart() { return this.setChartType("COMBO"); }
|
|
202
|
+
asHistogramChart() { return this.setChartType("HISTOGRAM"); }
|
|
203
|
+
asLineChart() { return this.setChartType("LINE"); }
|
|
204
|
+
asPieChart() { return this.setChartType("PIE"); }
|
|
205
|
+
asScatterChart() { return this.setChartType("SCATTER"); }
|
|
206
|
+
asTableChart() { return this.setChartType("TABLE"); }
|
|
207
|
+
|
|
208
|
+
// --- Common Builder Methods ---
|
|
209
|
+
clearRanges() {
|
|
210
|
+
this.__apiChart.spec.basicChart.domains = [];
|
|
211
|
+
this.__apiChart.spec.basicChart.series = [];
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
getContainer() {
|
|
216
|
+
return newFakeContainerInfo(this.__apiChart.position.overlayPosition || {});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
getRanges() {
|
|
220
|
+
// In gas-fakes, we don't deeply parse and unwrap ranges back from the API spec for this fake, returning empty array.
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
removeRange(range) { return this; }
|
|
225
|
+
|
|
226
|
+
setHiddenDimensionStrategy(strategy) {
|
|
227
|
+
this.__apiChart.spec.hiddenDimensionStrategy = strategy ? strategy.toString() : "IGNORE_ROWS";
|
|
228
|
+
return this;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
setMergeStrategy(strategy) { return this; }
|
|
232
|
+
setNumHeaders(headers) { return this; }
|
|
233
|
+
setTransposeRowsAndColumns(transpose) { return this; }
|
|
234
|
+
|
|
235
|
+
// --- Chart Specific Builder Methods ---
|
|
236
|
+
reverseCategories() { return this; }
|
|
237
|
+
setBackgroundColor(color) { return this.setOption("backgroundColor", color); }
|
|
238
|
+
setColors(colors) { return this; }
|
|
239
|
+
setLegendPosition(position) {
|
|
240
|
+
this.__apiChart.spec.basicChart.legendPosition = position ? position.toString() : "RIGHT_LEGEND";
|
|
241
|
+
return this;
|
|
242
|
+
}
|
|
243
|
+
setLegendTextStyle(textStyle) { return this; }
|
|
244
|
+
setPointStyle(pointStyle) { return this; }
|
|
245
|
+
setRange(min, max) { return this; }
|
|
246
|
+
setStacked() { return this; }
|
|
247
|
+
setTitle(title) {
|
|
248
|
+
this.__apiChart.spec.title = title;
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
251
|
+
setTitleTextStyle(textStyle) { return this; }
|
|
252
|
+
setXAxisTextStyle(textStyle) { return this; }
|
|
253
|
+
setXAxisTitle(title) { return this; }
|
|
254
|
+
setXAxisTitleTextStyle(textStyle) { return this; }
|
|
255
|
+
setYAxisTextStyle(textStyle) { return this; }
|
|
256
|
+
setYAxisTitle(title) { return this; }
|
|
257
|
+
setYAxisTitleTextStyle(textStyle) { return this; }
|
|
258
|
+
useLogScale() { return this; }
|
|
259
|
+
set3D() { return this.setOption("is3D", true); }
|
|
260
|
+
enablePaging(enable, pageSize) { return this; }
|
|
261
|
+
enableRtlTable(enable) { return this; }
|
|
262
|
+
enableSorting(enable) { return this; }
|
|
263
|
+
setFirstRowNumber(number) { return this; }
|
|
264
|
+
setInitialSortingAscending(column) { return this; }
|
|
265
|
+
setInitialSortingDescending(column) { return this; }
|
|
266
|
+
showRowNumberColumn(show) { return this; }
|
|
267
|
+
useAlternatingRowStyle(use) { return this; }
|
|
268
|
+
setXAxisLogScale() { return this; }
|
|
269
|
+
setXAxisRange(min, max) { return this; }
|
|
270
|
+
setYAxisLogScale() { return this; }
|
|
271
|
+
setYAxisRange(min, max) { return this; }
|
|
272
|
+
reverseDirection() { return this; }
|
|
273
|
+
|
|
150
274
|
toString() {
|
|
275
|
+
const type = this.__apiChart.spec.basicChart?.chartType;
|
|
276
|
+
if (type) {
|
|
277
|
+
// The API uses string constants like "COLUMN", "PIE", "SCATTER".
|
|
278
|
+
// We convert them to CamelCase format like "EmbeddedPieChartBuilder".
|
|
279
|
+
const formattedType = type.charAt(0).toUpperCase() + type.slice(1).toLowerCase();
|
|
280
|
+
// Handle the underscore in STEPPED_AREA
|
|
281
|
+
const finalType = formattedType.replace(/_([a-z])/g, function (g) { return g[1].toUpperCase(); });
|
|
282
|
+
return `Embedded${finalType}ChartBuilder`;
|
|
283
|
+
}
|
|
284
|
+
|
|
151
285
|
const hex = Math.floor(Math.random() * 0xffffffff).toString(16).padStart(8, "0");
|
|
152
286
|
return `com.google.apps.maestro.server.beans.trix.impl.ChartPropertyApiEmbeddedChartBuilder@${hex}`;
|
|
153
287
|
}
|